@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
@@ -7,7 +7,6 @@ const CASE_FOLDER_STRUCTURE_VERSION = 1;
7
7
  const CASE_FOLDER_STRUCTURE_RELATIVE = '.piecemaker/case-folder-structure.json';
8
8
 
9
9
  const DEFAULT_CASE_FOLDER_STRUCTURE = Object.freeze({
10
- administrative: '00_ADMINISTRATIF_ET_FACTURATION',
11
10
  correspondence: '01_CORRESPONDANCE',
12
11
  correspondenceClient: '01_Client',
13
12
  correspondenceOpposingCounsel: '02_Avocats_adverses',
@@ -65,7 +64,6 @@ function normalizeCaseFolderStructure(value = {}) {
65
64
  }
66
65
 
67
66
  ensureUniqueSiblingNames(structure, [
68
- 'administrative',
69
67
  'correspondence',
70
68
  'dataRoom',
71
69
  'drafts',
@@ -107,9 +105,8 @@ function readStructureManifest(caseRoot) {
107
105
  }
108
106
 
109
107
  /**
110
- * La structure est figée par dossier lors de son premier enregistrement. Ainsi,
111
- * personnaliser les prochains dossiers ne change pas le routage du Markdown
112
- * des dossiers déjà existants.
108
+ * Seuls les anciens dossiers portent encore un manifeste d'arborescence : il
109
+ * continue de router leur Markdown. Sans manifeste, le dossier n'est pas géré.
113
110
  */
114
111
  function readCaseFolderStructure(caseRoot, config = {}) {
115
112
  const manifest = readStructureManifest(caseRoot);
@@ -119,78 +116,6 @@ function readCaseFolderStructure(caseRoot, config = {}) {
119
116
  };
120
117
  }
121
118
 
122
- function caseFolderDirectoriesFromStructure(structure) {
123
- return [
124
- structure.administrative,
125
- structure.correspondence,
126
- path.join(structure.correspondence, structure.correspondenceMarkdown),
127
- path.join(structure.correspondence, structure.correspondenceClient),
128
- path.join(structure.correspondence, structure.correspondenceOpposingCounsel),
129
- path.join(structure.correspondence, structure.correspondenceThirdParties),
130
- structure.dataRoom,
131
- path.join(structure.dataRoom, structure.dataRoomMarkdown),
132
- structure.drafts,
133
- structure.notesAndResearch,
134
- structure.procedure,
135
- ];
136
- }
137
-
138
- /** Chemins relatifs, dans l'ordre lisible de l'arborescence documentée. */
139
- function caseFolderDirectories(config = {}) {
140
- return caseFolderDirectoriesFromStructure(configuredCaseFolderStructure(config));
141
- }
142
-
143
- function writeStructureManifest(file, structure) {
144
- fs.mkdirSync(path.dirname(file), { recursive: true });
145
- const temporary = `${file}.piecemaker-${process.pid}.tmp`;
146
- fs.writeFileSync(temporary, `${JSON.stringify({
147
- version: CASE_FOLDER_STRUCTURE_VERSION,
148
- structure,
149
- }, null, 2)}\n`, { encoding: 'utf8', mode: 0o600 });
150
- fs.renameSync(temporary, file);
151
- }
152
-
153
- /**
154
- * Crée uniquement les dossiers manquants. Aucun fichier ni dossier existant
155
- * n'est renommé ou remplacé quand l'utilisateur modifie les noms par défaut.
156
- */
157
- function ensureCaseFolderStructure(caseRoot, config = {}) {
158
- const requested = String(caseRoot || '').trim();
159
- if (!requested || !path.isAbsolute(requested)) throw new Error('Racine du dossier juridique invalide.');
160
- const root = path.resolve(requested);
161
- let rootStat;
162
- try {
163
- rootStat = fs.statSync(root);
164
- } catch {
165
- throw new Error('Racine du dossier juridique introuvable.');
166
- }
167
- if (!rootStat.isDirectory()) throw new Error('Racine du dossier juridique invalide.');
168
-
169
- const created = [];
170
- const manifest = readStructureManifest(root);
171
- const structure = manifest.structure || configuredCaseFolderStructure(config);
172
- const directories = caseFolderDirectoriesFromStructure(structure);
173
- for (const relative of directories) {
174
- const target = path.join(root, relative);
175
- if (fs.existsSync(target)) {
176
- if (!fs.statSync(target).isDirectory()) {
177
- throw new Error(`L’emplacement « ${relative} » existe mais n’est pas un dossier.`);
178
- }
179
- continue;
180
- }
181
- fs.mkdirSync(target, { recursive: true });
182
- created.push(relative.split(path.sep).join('/'));
183
- }
184
- if (!manifest.exists) writeStructureManifest(manifest.file, structure);
185
-
186
- return {
187
- file: manifest.file,
188
- names: structure,
189
- directories: directories.map((relative) => relative.split(path.sep).join('/')),
190
- created,
191
- };
192
- }
193
-
194
119
  function relativeSegments(relativePath) {
195
120
  const value = String(relativePath || '').replaceAll('\\', '/').replace(/^\.\//, '');
196
121
  if (!value || value.startsWith('/') || value.split('/').some((segment) => !segment || segment === '.' || segment === '..')) {
@@ -278,20 +203,7 @@ function structuredMarkdownCounterpart(absolutePath, caseRoot, config = {}) {
278
203
  }
279
204
 
280
205
  module.exports = {
281
- CASE_FOLDER_KEYS,
282
- CASE_FOLDER_STRUCTURE_KEY,
283
- CASE_FOLDER_STRUCTURE_RELATIVE,
284
- DEFAULT_CASE_FOLDER_STRUCTURE,
285
- caseConversionOutputDirectory,
286
- caseFolderDirectories,
287
- caseFolderStructureFile,
288
- classifyCaseFolderPath,
289
206
  classifyRelativeCaseFolderPath,
290
- configuredCaseFolderStructure,
291
- ensureCaseFolderStructure,
292
- isCaseGeneratedPath,
293
- normalizeCaseFolderStructure,
294
207
  readCaseFolderStructure,
295
208
  structuredMarkdownCounterpart,
296
- validateCaseFolderName,
297
209
  };
@@ -1,7 +1,18 @@
1
- /** Registered PieceMaker legal-case folders, including folders outside a common root. */
1
+ /** Legal-case folders: every CloudCLI project, as published by the PieceMaker server. */
2
2
  const fs = require('node:fs');
3
+ const os = require('node:os');
3
4
  const path = require('node:path');
4
5
 
6
+ const PROJECTS_FILE = 'projects.json';
7
+
8
+ function piecemakerHome() {
9
+ return process.env.PIECEMAKER_HOME || path.join(os.homedir(), '.piecemaker');
10
+ }
11
+
12
+ function projectsFile() {
13
+ return path.join(piecemakerHome(), PROJECTS_FILE);
14
+ }
15
+
5
16
  function realDirectory(value) {
6
17
  const requested = String(value || '').trim();
7
18
  if (!requested || !path.isAbsolute(requested)) return null;
@@ -13,23 +24,57 @@ function realDirectory(value) {
13
24
  }
14
25
  }
15
26
 
16
- /** Absolute, existing and de-duplicated folders explicitly registered by the admin. */
17
- function registeredCaseFolders(config) {
18
- const folders = Array.isArray(config?.caseFolders) ? config.caseFolders : [];
19
- return [...new Set(folders.map(realDirectory).filter(Boolean))];
20
- }
21
-
22
- function isInsideOrEqual(root, candidate) {
27
+ function isInsideOrEqualPath(root, candidate) {
23
28
  const relative = path.relative(root, candidate);
24
29
  return relative === '' || (relative !== '..' && !relative.startsWith(`..${path.sep}`) && !path.isAbsolute(relative));
25
30
  }
26
31
 
27
- /** Locate a target inside one of the explicitly registered legal-case folders. */
28
- function locateRegisteredCase(folders, target) {
29
- if (!target) return null;
30
- let absolute;
32
+ function excludedRoots() {
33
+ return [os.tmpdir(), '/tmp', '/private/tmp', '/var/folders', '/private/var/folders']
34
+ .map(realDirectory)
35
+ .filter(Boolean);
36
+ }
37
+
38
+ function isEligibleProjectFolder(folder) {
39
+ const home = realDirectory(os.homedir());
40
+ if (home && isInsideOrEqualPath(folder, home)) return false;
41
+ return !excludedRoots().some((root) => isInsideOrEqualPath(root, folder));
42
+ }
43
+
44
+ function readProjectSources() {
45
+ try {
46
+ const parsed = JSON.parse(fs.readFileSync(projectsFile(), 'utf8'));
47
+ return parsed?.sources && typeof parsed.sources === 'object' && !Array.isArray(parsed.sources) ? parsed.sources : {};
48
+ } catch {
49
+ return {};
50
+ }
51
+ }
52
+
53
+ /** Absolute, existing and de-duplicated project folders, across every publishing server. */
54
+ function registeredProjectFolders() {
55
+ const folders = Object.values(readProjectSources()).flatMap((list) => (Array.isArray(list) ? list : []));
56
+ return [...new Set(folders.map(realDirectory).filter(Boolean))].filter(isEligibleProjectFolder);
57
+ }
58
+
59
+ function publishProjectSource(sourceId, folders) {
60
+ const file = projectsFile();
61
+ const sources = readProjectSources();
62
+ if (folders === null) delete sources[sourceId];
63
+ else sources[sourceId] = [...new Set(folders.map((folder) => path.resolve(String(folder))))].sort();
64
+ fs.mkdirSync(path.dirname(file), { recursive: true });
65
+ const temporaryFile = `${file}.${process.pid}.${Date.now()}.tmp`;
66
+ try {
67
+ fs.writeFileSync(temporaryFile, `${JSON.stringify({ version: 1, sources }, null, 2)}\n`, 'utf8');
68
+ fs.renameSync(temporaryFile, file);
69
+ } catch (error) {
70
+ fs.rmSync(temporaryFile, { force: true });
71
+ throw error;
72
+ }
73
+ }
74
+
75
+ function resolveTarget(target) {
31
76
  try {
32
- absolute = fs.realpathSync(path.resolve(String(target)));
77
+ return fs.realpathSync(path.resolve(String(target)));
33
78
  } catch {
34
79
  // Write/Edit may target a file that does not exist yet. Resolve its nearest
35
80
  // existing parent, then append the missing tail without following links.
@@ -37,22 +82,24 @@ function locateRegisteredCase(folders, target) {
37
82
  const tail = [];
38
83
  while (path.dirname(current) !== current) {
39
84
  try {
40
- absolute = path.join(fs.realpathSync(current), ...tail);
41
- break;
85
+ return path.join(fs.realpathSync(current), ...tail);
42
86
  } catch {
43
87
  tail.unshift(path.basename(current));
44
88
  current = path.dirname(current);
45
89
  }
46
90
  }
47
- if (!absolute) return null;
91
+ return null;
48
92
  }
93
+ }
49
94
 
50
- // A nested registered matter wins over its parent. It prevents a folder
51
- // intentionally registered as its own case from sharing the parent's map.
52
- const roots = [...new Set((folders || []).map(realDirectory).filter(Boolean))]
53
- .sort((a, b) => b.length - a.length);
95
+ /** Locate a target inside one of the project folders; a nested project wins over its parent. */
96
+ function locateProjectCase(target) {
97
+ if (!target) return null;
98
+ const absolute = resolveTarget(target);
99
+ if (!absolute) return null;
100
+ const roots = registeredProjectFolders().sort((a, b) => b.length - a.length);
54
101
  for (const caseRoot of roots) {
55
- if (!isInsideOrEqual(caseRoot, absolute)) continue;
102
+ if (!isInsideOrEqualPath(caseRoot, absolute)) continue;
56
103
  return {
57
104
  casesRoot: path.dirname(caseRoot),
58
105
  caseName: path.basename(caseRoot),
@@ -65,22 +112,8 @@ function locateRegisteredCase(folders, target) {
65
112
  return null;
66
113
  }
67
114
 
68
- /**
69
- * Locate a target inside one of the explicitly registered legal-case folders.
70
- * There is no longer a workspace-root fallback: a matter is protected only once
71
- * it is explicitly registered, never by mere location under a common root.
72
- */
73
- function locateConfiguredCase(config, target) {
74
- return locateRegisteredCase(registeredCaseFolders(config), target);
75
- }
76
-
77
- function configuredWatchPaths(config) {
78
- return registeredCaseFolders(config);
79
- }
80
-
81
115
  module.exports = {
82
- configuredWatchPaths,
83
- locateConfiguredCase,
84
- locateRegisteredCase,
85
- registeredCaseFolders,
116
+ locateProjectCase,
117
+ publishProjectSource,
118
+ registeredProjectFolders,
86
119
  };
@@ -2,8 +2,7 @@
2
2
  * Mapping d'anonymisation d'un dossier juridique — implémentation unique.
3
3
  *
4
4
  * Le pipeline admin (`websocket-server/originals-pipeline.cjs`), le serveur
5
- * Word, l'historique et les vues juridiques la partagent. Le proxy PII lit le
6
- * mapping central produit à partir de ces mappings de dossier.
5
+ * Word, l'historique et les vues juridiques la partagent.
7
6
  *
8
7
  * Deux sens, jamais symétriques dans leur usage :
9
8
  * - `applyMapping` entité → code, sur tout ce que l'IA s'apprête à lire ;
@@ -16,11 +15,11 @@
16
15
  const fs = require('node:fs');
17
16
  const path = require('node:path');
18
17
 
19
- const { locateCase, WORKSPACE_SUBDIR } = require('./protection.cjs');
20
- const { locateConfiguredCase } = require('./case-folders.cjs');
18
+ const { WORKSPACE_SUBDIR } = require('./protection.cjs');
19
+ const { locateProjectCase } = require('./case-folders.cjs');
21
20
  const { isInstitutionalEntity } = require('./institutional-terms.cjs');
22
21
  // Le moteur de substitution est extrait dans un module autonome : il est aussi
23
- // requis par le hook central global, distribué hors du plugin. Une seule
22
+ // requis par le proxy PII (`anonymizer/dictionary.cjs`). Une seule
24
23
  // implémentation, ré-exportée ici pour les appelants historiques.
25
24
  const {
26
25
  applyMapping,
@@ -286,40 +285,13 @@ function readCaseMapping(caseRoot) {
286
285
  };
287
286
  }
288
287
 
289
- /**
290
- * Un mapping d'anonymisation exploitable existe-t-il pour ce dossier ? C'est la
291
- * condition de la garantie « anonymisé avant envoi » : sans lui, le proxy n'a
292
- * rien à coder et une surface lisible partirait en clair. `protect-originals`
293
- * s'en sert pour refuser la lecture d'un dossier PieceMaker pas encore anonymisé.
294
- * On s'appuie sur `readCaseMapping` (et non sur la seule présence du fichier) pour
295
- * qu'un `mapping_default.json` illisible compte comme absent — c'est exactement ce
296
- * que voit la reconstruction du mapping central.
297
- */
298
- function caseHasMapping(caseRoot) {
299
- return readCaseMapping(caseRoot).exists;
300
- }
301
-
302
288
  // La substitution (`applyMapping` / `revertMapping`) vit dans `substitution.cjs`
303
289
  // et est importée en tête de fichier. Elle est ré-exportée ci-dessous pour les
304
290
  // appelants historiques de `mapping.cjs`.
305
291
 
306
- /**
307
- * Retrouve le mapping du dossier juridique auquel `hint` appartient. `hint` est
308
- * un chemin de fichier (Read, Write, Edit) ou un répertoire de travail (Bash,
309
- * Telegram). Retourne `null` hors dossier juridique ou sans mapping utilisable :
310
- * les hooks n'ont alors rien à faire.
311
- */
312
- function resolveCaseMapping(casesRoot, hint) {
313
- const located = locateCase(casesRoot, hint);
314
- if (!located) return null;
315
- const mapping = readCaseMapping(located.caseRoot);
316
- if (!mapping.exists) return null;
317
- return { caseRoot: located.caseRoot, caseName: located.caseName, ...mapping };
318
- }
319
-
320
- /** Resolve a mapping from the explicit folder registry plus legacy workspace. */
321
- function resolveConfiguredCaseMapping(config, hint) {
322
- const located = locateConfiguredCase(config, hint);
292
+ /** Resolve the mapping of the project folder containing the hint. */
293
+ function resolveProjectCaseMapping(hint) {
294
+ const located = locateProjectCase(hint);
323
295
  if (!located) return null;
324
296
  const mapping = readCaseMapping(located.caseRoot);
325
297
  if (!mapping.exists) return null;
@@ -330,15 +302,13 @@ module.exports = {
330
302
  applyMapping,
331
303
  buildEntityRegex,
332
304
  byDescendingEntityLength,
333
- caseHasMapping,
334
305
  caseMappingFile,
335
306
  escapeWithVariants,
336
307
  normalizeMappingDocument,
337
308
  normalizeProcedureInfo,
338
309
  readCaseMapping,
339
310
  readJsonFile,
340
- resolveConfiguredCaseMapping,
341
- resolveCaseMapping,
311
+ resolveProjectCaseMapping,
342
312
  resolveMappedPath,
343
313
  revertMapping,
344
314
  sortedMapping,
@@ -3,8 +3,8 @@
3
3
  *
4
4
  * L'ancienne règle protégeait le contenu d'un sous-dossier « Pièces
5
5
  * originales ». Elle ne protégeait donc rien dans un dossier organisé
6
- * autrement, ce qui est le cas courant : les PDF et DOCX y côtoient le Markdown
7
- * qu'on en a tiré. La protection est désormais une propriété du fichier,
6
+ * autrement, ce qui est le cas courant : les PDF y côtoient le Markdown qu'on
7
+ * en a tiré. La protection est désormais une propriété du fichier,
8
8
  * décidée dans l'administration.
9
9
  *
10
10
  * **Protégés par défaut : les PDF et les images** (`PROTECTED_EXTENSIONS`), qui
@@ -81,7 +81,7 @@ const PROTECTED_EXTENSIONS = new Set([
81
81
  * celles rangées sous `ignored` et les variantes d'`extracted_data` ressortiraient
82
82
  * telles quelles. Le seul traitement correct est le refus.
83
83
  */
84
- const FORBIDDEN_JSON_PATTERNS = [/^mapping.*\.json$/i, /_sensitive_map\.json$/i, /^central-mapping\.json$/i];
84
+ const FORBIDDEN_JSON_PATTERNS = [/^mapping.*\.json$/i, /_sensitive_map\.json$/i];
85
85
 
86
86
  /** Vrai pour un mapping de dossier ou un scan PII, où qu'il soit rangé. */
87
87
  function isMappingFile(filePath) {
@@ -172,37 +172,6 @@ function relativeKey(absolutePath, caseRoot) {
172
172
  return relative.split(path.sep).join('/');
173
173
  }
174
174
 
175
- /**
176
- * Situe un chemin dans la racine PieceMaker : chaque enfant direct de cette
177
- * racine est un dossier juridique indépendant. Retourne `null` hors racine, ce
178
- * qui est le cas de la grande majorité des appels d'outil — c'est le chemin
179
- * rapide des hooks.
180
- */
181
- function locateCase(casesRoot, target) {
182
- if (!casesRoot || !target) return null;
183
- let root;
184
- try {
185
- root = fs.realpathSync(path.resolve(String(casesRoot)));
186
- } catch {
187
- return null;
188
- }
189
- // Les deux côtés doivent être résolus : sur macOS `/var/…` est un lien vers
190
- // `/private/var/…`, si bien qu'une racine résolue et une cible qui ne l'est
191
- // pas ne se recouvrent jamais — le dossier passait alors pour hors racine et
192
- // rien n'était protégé.
193
- const absolute = realpathOrParent(path.resolve(String(target)));
194
- if (absolute === root || !absolute.startsWith(`${root}${path.sep}`)) return null;
195
- const [caseName, ...rest] = path.relative(root, absolute).split(path.sep);
196
- if (!caseName || caseName.startsWith('.')) return null;
197
- const caseRoot = path.join(root, caseName);
198
- try {
199
- if (!fs.statSync(caseRoot).isDirectory()) return null;
200
- } catch {
201
- return null;
202
- }
203
- return { casesRoot: root, caseName, caseRoot, absolute, relative: rest.join('/') };
204
- }
205
-
206
175
  /** Normalise une liste de chemins relatifs en Set de clés POSIX. */
207
176
  function normalizeKeySet(list) {
208
177
  return new Set(
@@ -244,26 +213,76 @@ function readProtection(caseRoot) {
244
213
  * que `unprotected` — l'initialisation d'un dossier — n'efface pas les
245
214
  * ressources déjà déclarées.
246
215
  */
247
- function writeProtection(caseRoot, { unprotected, resources } = {}) {
216
+ function writeProtection(caseRoot, lists = {}) {
217
+ return mutateProtection(caseRoot, () => lists);
218
+ }
219
+
220
+ const PROTECTION_LOCK_TIMEOUT_MS = 3000;
221
+ const PROTECTION_LOCK_STALE_MS = 10000;
222
+ const PROTECTION_LOCK_RETRY_MS = 25;
223
+
224
+ function sleepSync(milliseconds) {
225
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, milliseconds);
226
+ }
227
+
228
+ function acquireProtectionLock(lockFile) {
229
+ const deadline = Date.now() + PROTECTION_LOCK_TIMEOUT_MS;
230
+ for (;;) {
231
+ try {
232
+ fs.closeSync(fs.openSync(lockFile, 'wx'));
233
+ return;
234
+ } catch (error) {
235
+ if (error.code !== 'EEXIST') throw error;
236
+ }
237
+ try {
238
+ if (Date.now() - fs.statSync(lockFile).mtimeMs > PROTECTION_LOCK_STALE_MS) {
239
+ fs.rmSync(lockFile, { force: true });
240
+ continue;
241
+ }
242
+ } catch {
243
+ continue;
244
+ }
245
+ if (Date.now() > deadline) throw new Error(`Le fichier de protection est verrouillé : ${lockFile}`);
246
+ sleepSync(PROTECTION_LOCK_RETRY_MS);
247
+ }
248
+ }
249
+
250
+ function mutateProtection(caseRoot, mutate) {
248
251
  const file = protectionFile(caseRoot);
249
- const existing = readProtection(caseRoot);
250
- const clean = (list, fallback) => [...new Set(
251
- (Array.isArray(list) ? list : [...fallback])
252
- .map((entry) => String(entry || '').replaceAll('\\', '/').trim())
253
- .filter((entry) => entry && !entry.startsWith('../') && !path.isAbsolute(entry))
254
- )].sort((a, b) => a.localeCompare(b, 'fr'));
255
- // Une pièce ne peut être à la fois « espace de travail » et « ressource » :
256
- // `resources` a priorité, on la retire donc de `unprotected`.
257
- const resourcesList = clean(resources, existing.resources);
258
- const resourceSet = new Set(resourcesList);
259
- const unprotectedList = clean(unprotected, existing.unprotected).filter((key) => !resourceSet.has(key));
252
+ const lockFile = `${file}.lock`;
260
253
  fs.mkdirSync(path.dirname(file), { recursive: true });
261
- fs.writeFileSync(
262
- file,
263
- `${JSON.stringify({ version: 1, unprotected: unprotectedList, resources: resourcesList }, null, 2)}\n`,
264
- 'utf8'
265
- );
266
- return { file, unprotected: new Set(unprotectedList), resources: resourceSet };
254
+ acquireProtectionLock(lockFile);
255
+ try {
256
+ const existing = readProtection(caseRoot);
257
+ const next = mutate(existing);
258
+ if (!next) return { file, unprotected: existing.unprotected, resources: existing.resources };
259
+ const { unprotected, resources } = next;
260
+ const clean = (list, fallback) => [...new Set(
261
+ (Array.isArray(list) ? list : [...fallback])
262
+ .map((entry) => String(entry || '').replaceAll('\\', '/').trim())
263
+ .filter((entry) => entry && !entry.startsWith('../') && !path.isAbsolute(entry))
264
+ )].sort((a, b) => a.localeCompare(b, 'fr'));
265
+ // Une pièce ne peut être à la fois « espace de travail » et « ressource » :
266
+ // `resources` a priorité, on la retire donc de `unprotected`.
267
+ const resourcesList = clean(resources, existing.resources);
268
+ const resourceSet = new Set(resourcesList);
269
+ const unprotectedList = clean(unprotected, existing.unprotected).filter((key) => !resourceSet.has(key));
270
+ const temporaryFile = `${file}.${process.pid}.${Date.now()}.tmp`;
271
+ try {
272
+ fs.writeFileSync(
273
+ temporaryFile,
274
+ `${JSON.stringify({ version: 1, unprotected: unprotectedList, resources: resourcesList }, null, 2)}\n`,
275
+ 'utf8'
276
+ );
277
+ fs.renameSync(temporaryFile, file);
278
+ } catch (error) {
279
+ fs.rmSync(temporaryFile, { force: true });
280
+ throw error;
281
+ }
282
+ return { file, unprotected: new Set(unprotectedList), resources: resourceSet };
283
+ } finally {
284
+ fs.rmSync(lockFile, { force: true });
285
+ }
267
286
  }
268
287
 
269
288
  /** Vrai pour un chemin recevable dans une des deux listes d'exceptions. */
@@ -367,9 +386,9 @@ function markdownCounterpart(absolutePath, caseRoot) {
367
386
  * de naître au coffre-fort, elle ne pourrait plus se relire.
368
387
  *
369
388
  * Ne fait rien, et retourne pourquoi, quand le chemin n'est pas une pièce
370
- * classable : hors dossier, `.md`/`.json` (déjà lisibles), dotfile, mapping ou
371
- * scan PII (jamais déclassés), espace de travail OOXML implicite, ou clé déjà
372
- * inscrite. `resources` est prioritaire : une ressource n'est jamais
389
+ * classable : hors dossier, extension jamais protégée (seuls PDF et images le
390
+ * sont), dotfile, mapping ou scan PII (jamais déclassés), espace de travail
391
+ * OOXML implicite, ou clé déjà inscrite. `resources` est prioritaire : une ressource n'est jamais
373
392
  * rétrogradée en espace de travail. Idempotent, ne lève jamais.
374
393
  */
375
394
  function classifyAsWorkspace(absolutePath, caseRoot) {
@@ -378,12 +397,15 @@ function classifyAsWorkspace(absolutePath, caseRoot) {
378
397
  if (isMappingFile(absolutePath)) return { classified: false, key: null, reason: 'mapping' };
379
398
  const key = exceptionKey(absolutePath, caseRoot);
380
399
  if (!key) return { classified: false, key: null, reason: 'non-classable' };
400
+ if (!PROTECTED_EXTENSIONS.has(path.extname(key).toLowerCase())) return { classified: false, key, reason: 'jamais protégée' };
381
401
  if (isOoxmlWorkspacePath(absolutePath, caseRoot)) return { classified: false, key, reason: 'ooxml' };
382
- const { unprotected, resources } = readProtection(caseRoot);
383
- if (resources.has(key)) return { classified: false, key, reason: 'ressource' };
384
- if (unprotected.has(key)) return { classified: false, key, reason: 'déjà classée' };
385
- writeProtection(caseRoot, { unprotected: [...unprotected, key] });
386
- return { classified: true, key, reason: null };
402
+ let reason = null;
403
+ mutateProtection(caseRoot, ({ unprotected, resources }) => {
404
+ if (resources.has(key)) reason = 'ressource';
405
+ else if (unprotected.has(key)) reason = 'déjà classée';
406
+ return reason ? null : { unprotected: [...unprotected, key] };
407
+ });
408
+ return { classified: !reason, key, reason };
387
409
  } catch (error) {
388
410
  return { classified: false, key: null, reason: error?.message || 'erreur' };
389
411
  }
@@ -395,7 +417,6 @@ module.exports = {
395
417
  exceptionKey,
396
418
  isMappingFile,
397
419
  isSecretFile,
398
- locateCase,
399
420
  isProtectedFile,
400
421
  isProtectionBypassed,
401
422
  isOoxmlWorkspacePath,