@piecemaker-legal/piecemaker 2.0.10 → 2.0.12

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 (136) hide show
  1. package/dist/assets/{FindReplaceDialog-KU5TEIBJ-CQ76sPaJ.js → FindReplaceDialog-KU5TEIBJ-BBxf1he7.js} +1 -1
  2. package/dist/assets/{FootnotePropertiesDialog-W3YHM4BV-BpgYQvvd.js → FootnotePropertiesDialog-W3YHM4BV-BYqp6U4x.js} +1 -1
  3. package/dist/assets/{HyperlinkDialog-ZYH7ZHRF-z08UbYbL.js → HyperlinkDialog-ZYH7ZHRF-BVC9zOMs.js} +4 -4
  4. package/dist/assets/{ImagePositionDialog-HEWYR6Q3-CFzOnzAt.js → ImagePositionDialog-HEWYR6Q3-Xn2mC64y.js} +1 -1
  5. package/dist/assets/{ImagePropertiesDialog-RAB47C6M-D-ThGU-b.js → ImagePropertiesDialog-RAB47C6M-CFcdqEif.js} +1 -1
  6. package/dist/assets/{PageSetupDialog-AIGIXO3E-_PYIO8eb.js → PageSetupDialog-AIGIXO3E-DECaeRS9.js} +1 -1
  7. package/dist/assets/{SplitCellDialog-IFBKYCD6-Cb9uQOB8.js → SplitCellDialog-IFBKYCD6-ByYa04H_.js} +1 -1
  8. package/dist/assets/{TablePropertiesDialog-TMLKGOOW-12vmjb6B.js → TablePropertiesDialog-TMLKGOOW-BuyqZB5A.js} +1 -1
  9. package/dist/assets/{WatermarkDialog-KRL7WMIS-C-d8rzt2.js → WatermarkDialog-KRL7WMIS-ByZ1Acr2.js} +1 -1
  10. package/dist/assets/{abnfDiagram-VCTEODGH-B4OsX7eF.js → abnfDiagram-VCTEODGH-BtttzdZA.js} +1 -1
  11. package/dist/assets/{arc-BK1oUIk7.js → arc-DN4m1tLs.js} +1 -1
  12. package/dist/assets/{architectureDiagram-5GKGNRK7-CfaFI6L7.js → architectureDiagram-5GKGNRK7-Bt-zFumG.js} +1 -1
  13. package/dist/assets/{blockDiagram-I7D4REHJ-DQEI2azY.js → blockDiagram-I7D4REHJ-D14V8v4j.js} +1 -1
  14. package/dist/assets/{c4Diagram-7LVT6UL2-DbravpVi.js → c4Diagram-7LVT6UL2-BynDo1-W.js} +1 -1
  15. package/dist/assets/channel-C4gJXzYa.js +1 -0
  16. package/dist/assets/{chunk-2Q5K7J3B-BLpZ9huC.js → chunk-2Q5K7J3B-B8gKhSj9.js} +1 -1
  17. package/dist/assets/{chunk-5VM5RSS4-BLlEdmX9.js → chunk-5VM5RSS4-GvOpglRR.js} +1 -1
  18. package/dist/assets/{chunk-F27PBJKO-D-ZNhI6q.js → chunk-F27PBJKO-CAgZy0qc.js} +1 -1
  19. package/dist/assets/{chunk-IMKFNOWR-vzSiKnhh.js → chunk-IMKFNOWR-gf8Ux8Zd.js} +1 -1
  20. package/dist/assets/{chunk-JWPE2WC7-DfF0k4nb.js → chunk-JWPE2WC7-D4zFFRMp.js} +1 -1
  21. package/dist/assets/{chunk-POPQ4Y6H-BnwCYsPP.js → chunk-POPQ4Y6H-Ct8X_LaT.js} +1 -1
  22. package/dist/assets/{chunk-SVP7TREG-DRxc8fhq.js → chunk-SVP7TREG-g5iH1bs5.js} +1 -1
  23. package/dist/assets/{chunk-TICWLB2K-Dd2Q-md-.js → chunk-TICWLB2K-89-VFKTn.js} +1 -1
  24. package/dist/assets/{chunk-XXDRQBXY-DBjkEUp_.js → chunk-XXDRQBXY-CaQbdObb.js} +1 -1
  25. package/dist/assets/classDiagram-ZZMXUADV-Cm08FeYk.js +1 -0
  26. package/dist/assets/classDiagram-v2-VYDZK3BY-Cm08FeYk.js +1 -0
  27. package/dist/assets/{cose-bilkent-JH36ORCC-By6PS4oe.js → cose-bilkent-JH36ORCC-BiYOGJAU.js} +1 -1
  28. package/dist/assets/{cynefin-OW5HDTMX-YxWpjc5W.js → cynefin-OW5HDTMX-F6mJ-4tj.js} +1 -1
  29. package/dist/assets/{cynefinDiagram-5FMLGOSQ-DrtXzz3p.js → cynefinDiagram-5FMLGOSQ-q-Rggn6r.js} +1 -1
  30. package/dist/assets/{dagre-GXQ25YYZ-BDpTLKqZ.js → dagre-GXQ25YYZ-COqImSUH.js} +1 -1
  31. package/dist/assets/{diagram-S7CK7UJ4-C4LvqeCs.js → diagram-S7CK7UJ4-BAg1dyEc.js} +1 -1
  32. package/dist/assets/{diagram-UQ7AKVKN-BTpnMeHp.js → diagram-UQ7AKVKN-DFJS8qCV.js} +1 -1
  33. package/dist/assets/{diagram-VSXAHHWV-DucHHWev.js → diagram-VSXAHHWV-BfldL35r.js} +1 -1
  34. package/dist/assets/{diagram-VX7I27RA-DYHZNmmH.js → diagram-VX7I27RA-kLjovpYe.js} +1 -1
  35. package/dist/assets/{diagram-Z3DM3KII-BHIH2BGN.js → diagram-Z3DM3KII-Dhwvi3Iu.js} +1 -1
  36. package/dist/assets/{ebnfDiagram-PWID7BFC-CGiEkwj0.js → ebnfDiagram-PWID7BFC-Bp4YE1i7.js} +1 -1
  37. package/dist/assets/{erDiagram-RLTQ6QDP-AJMHi2Us.js → erDiagram-RLTQ6QDP-BKDdaSWz.js} +1 -1
  38. package/dist/assets/{flowDiagram-HODETNUW-BqzANBh2.js → flowDiagram-HODETNUW-zHProVxL.js} +1 -1
  39. package/dist/assets/{ganttDiagram-EL5Y4UJY-B0xPDY2_.js → ganttDiagram-EL5Y4UJY-B-B-BI6j.js} +1 -1
  40. package/dist/assets/{gitGraphDiagram-WWUBYQGX-CzPX5CNO.js → gitGraphDiagram-WWUBYQGX-BTHMg5CH.js} +1 -1
  41. package/dist/assets/index-C5slQNvE.css +1 -0
  42. package/dist/assets/{index-Bk2iXKnJ.js → index-CJP4dZ-c.js} +146 -146
  43. package/dist/assets/{infoDiagram-27XIBGKW-Cjc44H0O.js → infoDiagram-27XIBGKW-mabp4H9I.js} +1 -1
  44. package/dist/assets/{ishikawaDiagram-5VMMS53U-BWhr2v7r.js → ishikawaDiagram-5VMMS53U-CLNhcCrM.js} +1 -1
  45. package/dist/assets/{journeyDiagram-3NMN7TZE-Bl4r6RrP.js → journeyDiagram-3NMN7TZE-CGEi2RB6.js} +1 -1
  46. package/dist/assets/{kanban-definition-UXKFOSKX-FV-T0Vy1.js → kanban-definition-UXKFOSKX-B2fLX1fK.js} +1 -1
  47. package/dist/assets/{layout-CTfb1sAF.js → layout-CloKWsFq.js} +1 -1
  48. package/dist/assets/{linear-Dfgi6vlo.js → linear-BFyydI0c.js} +1 -1
  49. package/dist/assets/{mermaid.core-DOCGsdlo.js → mermaid.core-Dxl3LVAh.js} +6 -6
  50. package/dist/assets/{mindmap-definition-YA3MSWOX-CymQ86ir.js → mindmap-definition-YA3MSWOX-FT4pm55s.js} +1 -1
  51. package/dist/assets/{pegDiagram-XKGWAZYB-1VMq_vi0.js → pegDiagram-XKGWAZYB-okyIfJWM.js} +1 -1
  52. package/dist/assets/{pieDiagram-E7YTZNPT-C5GgXtJU.js → pieDiagram-E7YTZNPT-NF61CQz8.js} +1 -1
  53. package/dist/assets/{quadrantDiagram-AXDQQJYC-C6zpryYf.js → quadrantDiagram-AXDQQJYC-uVfvZzzt.js} +1 -1
  54. package/dist/assets/{railroadDiagram-O6MQD6OU-D-sv4SWi.js → railroadDiagram-O6MQD6OU-Dp8IpeoH.js} +1 -1
  55. package/dist/assets/{requirementDiagram-BXWQKSXE-CkjY-z4B.js → requirementDiagram-BXWQKSXE-D8VV0oQe.js} +1 -1
  56. package/dist/assets/{sankeyDiagram-P5KCCOFB-De-d8Q6L.js → sankeyDiagram-P5KCCOFB-D-xhxjob.js} +1 -1
  57. package/dist/assets/{sequenceDiagram-WJ2MYXX4-DCsGSJa9.js → sequenceDiagram-WJ2MYXX4-BAQBK95h.js} +1 -1
  58. package/dist/assets/{sizeCapture-INFHLROL-xINZhgER.js → sizeCapture-INFHLROL-C6rk9SVx.js} +1 -1
  59. package/dist/assets/{stateDiagram-D77RDMKH-CuLUTWSL.js → stateDiagram-D77RDMKH-DshqaEOj.js} +1 -1
  60. package/dist/assets/{stateDiagram-v2-MP3YSRHH-Cnbs-M92.js → stateDiagram-v2-MP3YSRHH-BohZIrk5.js} +1 -1
  61. package/dist/assets/{swimlanes-42K2YHIH-BwxARD7N.js → swimlanes-42K2YHIH-DgcPDy4l.js} +1 -1
  62. package/dist/assets/swimlanesDiagram-VR7AAH4N-CuM3Su0K.js +8 -0
  63. package/dist/assets/{timeline-definition-24CTP7MA-BIOvnn-l.js → timeline-definition-24CTP7MA-OgpIe-gQ.js} +1 -1
  64. package/dist/assets/{vennDiagram-4TSXK5OY-BSD6Pfmt.js → vennDiagram-4TSXK5OY-BZzGd6jt.js} +1 -1
  65. package/dist/assets/{wardleyDiagram-VM6X3IG4-7Zz2iRkF.js → wardleyDiagram-VM6X3IG4-1tg-H7l_.js} +1 -1
  66. package/dist/assets/{xychartDiagram-S5SC5T6Z-Ctx17Y1D.js → xychartDiagram-S5SC5T6Z-ByTsX8g0.js} +1 -1
  67. package/dist/index.html +2 -2
  68. package/dist/sw.js +1 -1
  69. package/dist-server/plugins/piecemaker-dossier/src/knowledge.js +61 -1
  70. package/dist-server/plugins/piecemaker-dossier/src/knowledge.js.map +1 -1
  71. package/dist-server/plugins/piecemaker-dossier/src/scan-result.js +0 -48
  72. package/dist-server/plugins/piecemaker-dossier/src/scan-result.js.map +1 -1
  73. package/dist-server/server/piecemaker/agent-instructions.js +148 -0
  74. package/dist-server/server/piecemaker/agent-instructions.js.map +1 -0
  75. package/dist-server/server/piecemaker/index.js +4 -0
  76. package/dist-server/server/piecemaker/index.js.map +1 -1
  77. package/dist-server/server/piecemaker/knowledge/pipeline.js +25 -29
  78. package/dist-server/server/piecemaker/knowledge/pipeline.js.map +1 -1
  79. package/dist-server/server/piecemaker/knowledge/service.js +1 -16
  80. package/dist-server/server/piecemaker/knowledge/service.js.map +1 -1
  81. package/dist-server/server/piecemaker/project-registry.js +5 -5
  82. package/dist-server/server/piecemaker/project-registry.js.map +1 -1
  83. package/package.json +1 -1
  84. package/scripts/piecemaker/cli/lib/plugins.mjs +1 -0
  85. package/server/piecemaker/agent-instructions.ts +148 -0
  86. package/server/piecemaker/anonymizer/dictionary.cjs +34 -3
  87. package/server/piecemaker/anonymizer/hudsucker-fixture.cjs +1 -1
  88. package/server/piecemaker/anonymizer/hudsucker-proxy/src/main.rs +2 -1
  89. package/server/piecemaker/anonymizer/hudsucker-smoke.cjs +343 -0
  90. package/server/piecemaker/anonymizer/rewrite.cjs +2 -2
  91. package/server/piecemaker/anonymizer/rewriter-bridge.cjs +7 -4
  92. package/server/piecemaker/anonymizer/rewriter-worker.cjs +85 -0
  93. package/server/piecemaker/anonymizer/service.cjs +10 -8
  94. package/server/piecemaker/anonymizer/sqlite-dictionary.cjs +15 -32
  95. package/server/piecemaker/index.ts +5 -1
  96. package/server/piecemaker/knowledge/pipeline.ts +24 -31
  97. package/server/piecemaker/knowledge/service.ts +1 -14
  98. package/server/piecemaker/project-registry.ts +6 -6
  99. package/server/piecemaker/router.cjs +0 -1
  100. package/server/piecemaker/vendor/installer/bin/piecemaker.mjs +9 -162
  101. package/server/piecemaker/vendor/installer/lib/commandes.mjs +1 -2
  102. package/server/piecemaker/vendor/installer/lib/conversion-client.mjs +62 -3
  103. package/server/piecemaker/vendor/installer/steps/02-dependances-node.mjs +5 -5
  104. package/server/piecemaker/vendor/installer/steps/06-hooks.mjs +23 -69
  105. package/server/piecemaker/vendor/installer/steps/12-mcp-piecemaker.mjs +3 -3
  106. package/server/piecemaker/vendor/mcp/README.md +1 -1
  107. package/server/piecemaker/vendor/mcp/piecemaker/server.mjs +12 -75
  108. package/server/piecemaker/vendor/piecemaker-plugin/hooks/hooks.json +0 -40
  109. package/server/piecemaker/vendor/piecemaker-plugin/scripts/compile-recherche.mjs +2 -2
  110. package/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/case-folder-structure.cjs +2 -90
  111. package/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/case-folders.cjs +26 -7
  112. package/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/commits.cjs +0 -28
  113. package/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/hook-io.mjs +0 -13
  114. package/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/institutional-terms.cjs +3 -3
  115. package/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/mapping.cjs +10 -143
  116. package/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/protection.cjs +1 -1
  117. package/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/session-timing.cjs +0 -65
  118. package/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/substitution.cjs +189 -41
  119. package/server/piecemaker/vendor/piecemaker-plugin/scripts/protect-originals.mjs +11 -2
  120. package/server/piecemaker/vendor/piecemaker-plugin/skills/ajout-de-fonctionnalites/SKILL.md +4 -18
  121. package/server/piecemaker/vendor/piecemaker-plugin/skills/conversion-md/SKILL.md +9 -5
  122. package/server/piecemaker/vendor/websocket-server/admin-routes.cjs +3 -179
  123. package/server/piecemaker/vendor/websocket-server/case-registry.cjs +0 -10
  124. package/server/piecemaker/vendor/websocket-server/claude-hooks.cjs +3 -0
  125. package/server/piecemaker/vendor/websocket-server/originals-pipeline.cjs +23 -372
  126. package/server/piecemaker/vendor/websocket-server/renamed-originals.cjs +136 -0
  127. package/server/piecemaker/vendor/websocket-server/scripts/convert_and_scan_pipeline.py +115 -96
  128. package/dist/assets/channel-DVyNr2Yg.js +0 -1
  129. package/dist/assets/classDiagram-ZZMXUADV-2pGPHl5H.js +0 -1
  130. package/dist/assets/classDiagram-v2-VYDZK3BY-2pGPHl5H.js +0 -1
  131. package/dist/assets/index-Dc9wNuS4.css +0 -1
  132. package/dist/assets/swimlanesDiagram-VR7AAH4N-D7p8qZI2.js +0 -8
  133. package/server/piecemaker/vendor/piecemaker-plugin/scripts/billing-track.mjs +0 -194
  134. package/server/piecemaker/vendor/piecemaker-plugin/scripts/commit-track.mjs +0 -71
  135. package/server/piecemaker/vendor/piecemaker-plugin/scripts/session-commit-worker.mjs +0 -260
  136. package/server/piecemaker/vendor/piecemaker-plugin/scripts/session-commit.mjs +0 -83
@@ -564,33 +564,6 @@ function resolveCase(casesRoot, caseName) {
564
564
  return { casesRoot: root, name, root: caseRoot };
565
565
  }
566
566
 
567
- function locateCaseFile(casesRoot, filePath) {
568
- const root = resolveCasesRoot(casesRoot);
569
- const requested = path.resolve(String(filePath || ''));
570
- let absolute = requested;
571
- try {
572
- absolute = fs.realpathSync(requested);
573
- } catch {
574
- try {
575
- absolute = path.join(fs.realpathSync(path.dirname(requested)), path.basename(requested));
576
- } catch {}
577
- }
578
- if (absolute === root || !absolute.startsWith(`${root}${path.sep}`)) return null;
579
- const relativeToRoot = path.relative(root, absolute);
580
- const [caseName, ...parts] = relativeToRoot.split(path.sep);
581
- if (!caseName || parts.length === 0) return null;
582
- const legalCase = resolveCase(root, caseName);
583
- if (isProtectedFile(absolute, legalCase.root)) {
584
- return { ...legalCase, absolute, protected: true, relative: parts.join('/') };
585
- }
586
- if (path.extname(absolute).toLowerCase() && !SAFE_EXTENSIONS.has(path.extname(absolute).toLowerCase())) {
587
- return { ...legalCase, absolute, protected: false, safe: false, relative: parts.join('/') };
588
- }
589
- const relative = path.relative(legalCase.root, absolute).split(path.sep).join('/');
590
- if (!relative || relative.startsWith('../')) return null;
591
- return { ...legalCase, absolute, protected: false, safe: SAFE_EXTENSIONS.has(path.extname(relative).toLowerCase()), relative };
592
- }
593
-
594
567
  async function safeCaseFiles(caseRoot) {
595
568
  const startedAt = performance.now();
596
569
  const root = await fsp.realpath(caseRoot);
@@ -1668,7 +1641,6 @@ module.exports = {
1668
1641
  logPerformance,
1669
1642
  listHistory,
1670
1643
  listHistoryPeriod,
1671
- locateCaseFile,
1672
1644
  parseCommitTrailers,
1673
1645
  originalFilesOverview,
1674
1646
  repositoryOverview,
@@ -25,8 +25,6 @@ import path from 'node:path';
25
25
 
26
26
  export const HOME_DIR = path.join(os.homedir(), '.piecemaker');
27
27
  export const CONFIG_FILE = path.join(HOME_DIR, 'config.json');
28
- export const BILLING_DIR = path.join(HOME_DIR, 'billing');
29
- export const SYNTHESIS_DIR = path.join(BILLING_DIR, 'synthese');
30
28
 
31
29
  /** Upper bound on waiting for stdout to drain before exiting anyway. */
32
30
  const FLUSH_TIMEOUT_MS = 2000;
@@ -236,14 +234,3 @@ export function ensureDirSafe(dir) {
236
234
  return false;
237
235
  }
238
236
  }
239
-
240
- /** Append one line to a JSONL file. Single appendFileSync call, never throws. */
241
- export function appendJsonl(filePath, obj) {
242
- try {
243
- ensureDirSafe(path.dirname(filePath));
244
- fs.appendFileSync(filePath, `${JSON.stringify(obj)}\n`, 'utf8');
245
- return true;
246
- } catch {
247
- return false;
248
- }
249
- }
@@ -9,9 +9,9 @@
9
9
  *
10
10
  * On ne débranche pas la détection : GLiNER continue de les trouver. C'est au
11
11
  * moment de bâtir le mapping qu'elles sont écartées — `normalizeMappingDocument`
12
- * (mapping.cjs) appelle `isInstitutionalEntity` sur chaque entité, à la lecture
13
- * comme à l'écriture. Une entité bannie ne persiste donc jamais dans
14
- * `mapping_default.json` et n'est jamais substituée par les hooks.
12
+ * (mapping.cjs) appelle `isInstitutionalEntity` sur chaque entité lue, et le
13
+ * magasin du dossier les retire de la base. Une entité bannie n'est donc jamais
14
+ * substituée par les hooks.
15
15
  *
16
16
  * La liste est globale (tous dossiers confondus), éditable depuis les paramètres
17
17
  * de l'administration, et la comparaison est insensible à la casse et aux
@@ -47,8 +47,7 @@ function readJsonFile(file, fallback = null) {
47
47
  * Tous les mappings présents, sous-dossier `WORKSPACE_SUBDIR` **et** racine
48
48
  * (lecture tolérante pendant la migration). Le fichier canonique du sous-dossier
49
49
  * passe en dernier : c'est lui qui gagne quand `readCaseMapping` fusionne, tandis
50
- * que les copies legacy (racine ou `mapping_<id>.json`) ne servent qu'à ne rien
51
- * perdre avant le prochain enregistrement.
50
+ * que les copies legacy (racine ou `mapping_<id>.json`) complètent la lecture.
52
51
  */
53
52
  function existingMappingFiles(caseRoot) {
54
53
  const dirs = [
@@ -80,113 +79,27 @@ function existingMappingFiles(caseRoot) {
80
79
  }
81
80
 
82
81
  /**
83
- * L'unique cible d'écriture par dossier vit désormais dans le sous-dossier
84
- * `WORKSPACE_SUBDIR`, pour garder la racine propre. Les anciens
85
- * `mapping_dossier.json` / `mapping_<id>.json` et un `mapping_default.json` resté
86
- * à la racine sont lus pour migration (voir `existingMappingFiles`), mais toute
87
- * écriture converge vers `<dossier>/<WORKSPACE_SUBDIR>/mapping_default.json`.
82
+ * Emplacement historique du mapping JSON d'un dossier, lu seulement : plus
83
+ * aucun code ne l'écrit, le mapping vit en base.
88
84
  */
89
85
  function caseMappingFile(caseRoot) {
90
86
  return path.join(caseRoot, WORKSPACE_SUBDIR, CANONICAL_MAPPING_FILE);
91
87
  }
92
88
 
93
- function cleanMappingString(value) {
94
- return String(value || '').trim();
95
- }
96
-
97
- function normalizePartyAssignments(value) {
98
- if (!Array.isArray(value)) return [];
99
- return value.map((raw) => {
100
- const assignment = raw && typeof raw === 'object' && !Array.isArray(raw) ? raw : {};
101
- const variants = [...new Set((Array.isArray(assignment.variants) ? assignment.variants : [])
102
- .map(cleanMappingString).filter(Boolean))];
103
- return {
104
- field: cleanMappingString(assignment.field),
105
- code: cleanMappingString(assignment.code),
106
- original_code: cleanMappingString(assignment.original_code),
107
- category: cleanMappingString(assignment.category),
108
- principal: cleanMappingString(assignment.principal),
109
- variants,
110
- };
111
- }).filter((assignment) => assignment.code && assignment.variants.length);
112
- }
113
-
114
- function normalizeProcedureParty(raw, side) {
115
- const party = raw && typeof raw === 'object' && !Array.isArray(raw) ? raw : {};
116
- const type = party.type === 'societe' ? 'societe' : 'personne_physique';
117
- const allowedPositions = new Set(['demandeur', 'defendeur', 'appelant', 'intime', 'requerant', 'mis_en_cause', 'intervenant', 'autre']);
118
- const position = allowedPositions.has(party.position) ? party.position : side === 'client' ? 'demandeur' : 'defendeur';
119
- return {
120
- type,
121
- position,
122
- position_libelle: position === 'autre' ? cleanMappingString(party.position_libelle) : '',
123
- civilite: type === 'personne_physique' ? cleanMappingString(party.civilite) : '',
124
- nom: type === 'personne_physique' ? cleanMappingString(party.nom) : '',
125
- date_naissance: type === 'personne_physique' ? cleanMappingString(party.date_naissance) : '',
126
- lieu_naissance: type === 'personne_physique' ? cleanMappingString(party.lieu_naissance) : '',
127
- adresse: type === 'personne_physique' ? cleanMappingString(party.adresse) : '',
128
- societe_nom: type === 'societe' ? cleanMappingString(party.societe_nom) : '',
129
- forme_sociale: type === 'societe' ? cleanMappingString(party.forme_sociale) : '',
130
- pays: type === 'societe' ? cleanMappingString(party.pays) || 'France' : '',
131
- siren: type === 'societe' ? cleanMappingString(party.siren) : '',
132
- siege_social: type === 'societe' ? cleanMappingString(party.siege_social) : '',
133
- representant: type === 'societe' ? cleanMappingString(party.representant) : '',
134
- mapping_assignments: normalizePartyAssignments(party.mapping_assignments),
135
- };
136
- }
137
-
138
- function relationshipId(source, target, role) {
139
- return `relation:${source}\u0000${target}\u0000${role}`;
140
- }
141
-
142
- function normalizeProfileRelationships(value) {
143
- if (!Array.isArray(value)) return [];
144
- const ids = new Set();
145
- const relationships = new Set();
146
- return value.flatMap((raw) => {
147
- const relationship = raw && typeof raw === 'object' && !Array.isArray(raw) ? raw : {};
148
- const source = cleanMappingString(relationship.source);
149
- const original_source = cleanMappingString(relationship.original_source);
150
- const target = cleanMappingString(relationship.target);
151
- const role = cleanMappingString(relationship.role);
152
- const id = cleanMappingString(relationship.id) || relationshipId(source, target, role);
153
- const key = `${source}\u0000${target}\u0000${role}`;
154
- if (!source || !target || !role || source === target || ids.has(id) || relationships.has(key)) return [];
155
- ids.add(id);
156
- relationships.add(key);
157
- return [{ id, source, original_source, target, role }];
158
- });
159
- }
160
-
161
- function normalizeProcedureInfo(raw) {
162
- const info = raw && typeof raw === 'object' && !Array.isArray(raw) ? raw : {};
163
- return {
164
- parties_clientes: (Array.isArray(info.parties_clientes) ? info.parties_clientes : [])
165
- .map((party) => normalizeProcedureParty(party, 'client')),
166
- parties_adverses: (Array.isArray(info.parties_adverses) ? info.parties_adverses : [])
167
- .map((party) => normalizeProcedureParty(party, 'adversaire')),
168
- relations: normalizeProfileRelationships(info.relations),
169
- };
170
- }
171
-
172
89
  function normalizeMappingDocument(raw) {
173
90
  const document = raw && typeof raw === 'object' && !Array.isArray(raw) ? raw : {};
174
91
  const mapping = {};
175
92
  const reverse = {};
176
93
  const source = document.mapping && typeof document.mapping === 'object' ? document.mapping : {};
177
94
  // Les entités institutionnelles (juridictions, registres, publications
178
- // officielles…) sont écartées ici même : c'est le point de passage unique de la
179
- // lecture comme de l'écriture, si bien qu'une entité bannie ne persiste jamais
180
- // dans `mapping_default.json` et n'est jamais substituée par les hooks. GLiNER
181
- // continue de les détecter — on ne débranche que le codage.
95
+ // officielles…) sont écartées ici même, à la lecture : une entité bannie n'est
96
+ // jamais substituée par les hooks. GLiNER continue de les détecter — on ne
97
+ // débranche que le codage.
182
98
  for (const [entity, code] of Object.entries(source)) {
183
99
  const from = String(entity || '').trim();
184
100
  const to = String(code || '').trim();
185
101
  if (from && to && !isInstitutionalEntity(from)) mapping[from] = to;
186
102
  }
187
- const ignored = [...new Set((Array.isArray(document.ignored) ? document.ignored : [])
188
- .map((entity) => String(entity || '').trim())
189
- .filter(Boolean))];
190
103
  const reverseSource = document.reverse_mapping && typeof document.reverse_mapping === 'object' ? document.reverse_mapping : {};
191
104
  for (const [code, value] of Object.entries(reverseSource)) {
192
105
  const key = String(code || '').trim();
@@ -202,36 +115,10 @@ function normalizeMappingDocument(raw) {
202
115
  if (!reverse[code]) reverse[code] = [entity];
203
116
  else if (!reverse[code].includes(entity)) reverse[code].push(entity);
204
117
  }
205
- // `extracted_data` est écrit par `convert_and_scan_pipeline.py` : c'est lui
206
- // qui porte les variants d'une entité et l'analyse des adresses, dont la
207
- // dé-anonymisation partielle a besoin (anonymization-server.cjs).
208
- // L'administration ne l'édite pas, mais elle ne doit surtout pas le détruire —
209
- // seules les entrées dont le code a disparu du mapping sont retirées.
210
- const extracted = {};
211
- const extractedSource = document.extracted_data && typeof document.extracted_data === 'object'
212
- && !Array.isArray(document.extracted_data) ? document.extracted_data : {};
213
- for (const [category, codes] of Object.entries(extractedSource)) {
214
- if (!codes || typeof codes !== 'object' || Array.isArray(codes)) continue;
215
- extracted[category] = Object.fromEntries(
216
- Object.entries(codes).filter(([code]) => reverse[code])
217
- );
218
- }
219
- return {
220
- mapping,
221
- reverse_mapping: reverse,
222
- extracted_data: extracted,
223
- ignored: ignored.filter((entity) => !mapping[entity]),
224
- informations_dossier: normalizeProcedureInfo(document.informations_dossier),
225
- };
118
+ return { mapping, reverse_mapping: reverse };
226
119
  }
227
120
 
228
121
  /** L'ordre d'écriture suit `byDescendingEntityLength`. */
229
- function sortedMapping(mapping) {
230
- return Object.fromEntries(
231
- Object.entries(mapping).sort((a, b) => b[0].length - a[0].length || a[0].localeCompare(b[0], 'fr'))
232
- );
233
- }
234
-
235
122
  function readCaseMapping(caseRoot) {
236
123
  const file = caseMappingFile(caseRoot);
237
124
  const sourceFiles = existingMappingFiles(caseRoot);
@@ -239,15 +126,10 @@ function readCaseMapping(caseRoot) {
239
126
  return { file, sourceFiles: [], exists: false, ...normalizeMappingDocument(null) };
240
127
  }
241
128
 
242
- // Migration non destructive à la lecture : tous les anciens mappings sont
243
- // réunis en mémoire. Le canonique passe en dernier et gagne donc si une même
244
- // entité a été recodée. Le prochain enregistrement écrira l'ensemble dans le
245
- // seul `mapping_default.json`.
129
+ // Tous les anciens mappings sont réunis en mémoire. Le canonique passe en
130
+ // dernier et gagne donc si une même entité a été recodée.
246
131
  const mapping = {};
247
132
  const preferredVariants = {};
248
- const extracted_data = {};
249
- const ignored = [];
250
- let informations_dossier = normalizeProcedureInfo();
251
133
  const readableFiles = [];
252
134
  for (const sourceFile of sourceFiles) {
253
135
  const raw = readJsonFile(sourceFile, null);
@@ -255,18 +137,9 @@ function readCaseMapping(caseRoot) {
255
137
  readableFiles.push(sourceFile);
256
138
  const document = normalizeMappingDocument(raw);
257
139
  Object.assign(mapping, document.mapping);
258
- ignored.push(...document.ignored);
259
140
  for (const [code, variants] of Object.entries(document.reverse_mapping)) {
260
141
  preferredVariants[code] = [...new Set([...(preferredVariants[code] || []), ...variants])];
261
142
  }
262
- for (const [category, codes] of Object.entries(document.extracted_data)) {
263
- extracted_data[category] = { ...(extracted_data[category] || {}), ...codes };
264
- }
265
- if (path.basename(sourceFile) === CANONICAL_MAPPING_FILE
266
- || document.informations_dossier.parties_clientes.length
267
- || document.informations_dossier.parties_adverses.length) {
268
- informations_dossier = document.informations_dossier;
269
- }
270
143
  }
271
144
 
272
145
  const reverse_mapping = {};
@@ -281,7 +154,7 @@ function readCaseMapping(caseRoot) {
281
154
  file,
282
155
  sourceFiles: readableFiles,
283
156
  exists: readableFiles.length > 0,
284
- ...normalizeMappingDocument({ mapping, reverse_mapping, extracted_data, ignored, informations_dossier }),
157
+ ...normalizeMappingDocument({ mapping, reverse_mapping }),
285
158
  };
286
159
  }
287
160
 
@@ -302,15 +175,9 @@ module.exports = {
302
175
  applyMapping,
303
176
  buildEntityRegex,
304
177
  byDescendingEntityLength,
305
- caseMappingFile,
306
178
  escapeWithVariants,
307
- normalizeMappingDocument,
308
- normalizeProcedureInfo,
309
179
  readCaseMapping,
310
- readJsonFile,
311
180
  resolveProjectCaseMapping,
312
181
  resolveMappedPath,
313
182
  revertMapping,
314
- sortedMapping,
315
- CANONICAL_MAPPING_FILE,
316
183
  };
@@ -30,7 +30,7 @@ const PROTECTION_FILE = 'protection.json';
30
30
  const PROTECTION_BYPASS_FILE = 'protection-bypass.json';
31
31
 
32
32
  /**
33
- * Sous-dossier technique historique et emplacement du `mapping_default.json`.
33
+ * Sous-dossier technique historique (ancien emplacement du mapping JSON).
34
34
  * Le Markdown courant vit dans les sous-dossiers de conversion de
35
35
  * Correspondance et Data Room ; ce repli reste nécessaire aux anciens dossiers
36
36
  * et aux espaces de travail OOXML. `mapping.cjs` et `commits.cjs` l'importent.
@@ -1,66 +1,3 @@
1
- /**
2
- * Session timing helpers shared by the commit and billing hooks.
3
- *
4
- * A hook runs once per event with no memory of the session's start. The
5
- * transcript is the only durable clock: its first entry's `timestamp` is the
6
- * session start. `commit-track.mjs` needs that start on every Write/Edit to
7
- * stamp the elapsed session time into the commit, so reading the whole
8
- * transcript on each tool call would be wasteful — we only need the first
9
- * timestamp, so we scan a bounded head of the file.
10
- *
11
- * Everything here fails soft: an unreadable/empty/malformed transcript yields
12
- * `null`, and the caller simply omits the time trailer.
13
- */
14
- const fs = require('node:fs');
15
-
16
- // Enough to comfortably contain the first JSONL record even with a large
17
- // system prompt echoed into it; we stop at the first line that parses.
18
- const HEAD_BYTES = 256 * 1024;
19
-
20
- /** First ISO timestamp in the transcript (session start), or null. */
21
- function readSessionStartTimestamp(transcriptPath) {
22
- if (!transcriptPath) return null;
23
- let fd;
24
- try {
25
- fd = fs.openSync(transcriptPath, 'r');
26
- const stat = fs.fstatSync(fd);
27
- const length = Math.min(stat.size, HEAD_BYTES);
28
- if (length === 0) return null;
29
- const buffer = Buffer.allocUnsafe(length);
30
- const bytesRead = fs.readSync(fd, buffer, 0, length, 0);
31
- const text = buffer.subarray(0, bytesRead).toString('utf8');
32
- for (const line of text.split('\n')) {
33
- const trimmed = line.trim();
34
- if (!trimmed) continue;
35
- // The last line of a partial head read may be truncated; ignore parse
36
- // failures and keep looking for the first fully-formed record.
37
- let entry;
38
- try {
39
- entry = JSON.parse(trimmed);
40
- } catch {
41
- continue;
42
- }
43
- const ts = entry?.timestamp || entry?.message?.timestamp;
44
- if (ts && Number.isFinite(Date.parse(ts))) return ts;
45
- }
46
- return null;
47
- } catch {
48
- return null;
49
- } finally {
50
- if (fd !== undefined) {
51
- try { fs.closeSync(fd); } catch {}
52
- }
53
- }
54
- }
55
-
56
- /** Milliseconds elapsed between the session's first entry and `now`. */
57
- function sessionElapsedMs(transcriptPath, now = Date.now()) {
58
- const start = readSessionStartTimestamp(transcriptPath);
59
- if (!start) return null;
60
- const ms = now - Date.parse(start);
61
- return Number.isFinite(ms) && ms >= 0 ? ms : null;
62
- }
63
-
64
1
  /** Compact French duration, e.g. "12 min 30 s", "1 h 05 min", "8 s". */
65
2
  function formatDurationFr(ms) {
66
3
  if (!Number.isFinite(ms) || ms < 0) return null;
@@ -75,6 +12,4 @@ function formatDurationFr(ms) {
75
12
 
76
13
  module.exports = {
77
14
  formatDurationFr,
78
- readSessionStartTimestamp,
79
- sessionElapsedMs,
80
15
  };
@@ -115,6 +115,157 @@ function byDescendingEntityLength(getEntity) {
115
115
  };
116
116
  }
117
117
 
118
+ function mappingSignature(entries) {
119
+ let signature = '';
120
+ for (const [key, value] of entries) signature += key + '\u0000' + value + '\u0001';
121
+ return signature;
122
+ }
123
+
124
+ function literalAnchor(value) {
125
+ const runs = String(value).match(/[A-Za-z0-9_]+/g) || [];
126
+ return runs.reduce((longest, run) => (run.length > longest.length ? run : longest), '');
127
+ }
128
+
129
+ const ANCHOR_WIDTH = 37;
130
+ const ANCHOR_SYMBOL = new Int8Array(128).fill(-1);
131
+ for (let letter = 0; letter < 26; letter += 1) {
132
+ ANCHOR_SYMBOL[65 + letter] = letter;
133
+ ANCHOR_SYMBOL[97 + letter] = letter;
134
+ }
135
+ for (let digit = 0; digit < 10; digit += 1) ANCHOR_SYMBOL[48 + digit] = 26 + digit;
136
+ ANCHOR_SYMBOL[95] = 36;
137
+ const KELVIN_SIGN = 0x212A;
138
+ const LONG_S = 0x017F;
139
+
140
+ function anchorSymbol(unit) {
141
+ if (unit < 128) return ANCHOR_SYMBOL[unit];
142
+ if (unit === KELVIN_SIGN) return ANCHOR_SYMBOL[107];
143
+ if (unit === LONG_S) return ANCHOR_SYMBOL[115];
144
+ return -1;
145
+ }
146
+
147
+ function buildAnchorScanner(anchors) {
148
+ let transitions = new Int32Array(ANCHOR_WIDTH * 16).fill(-1);
149
+ const outputs = [[]];
150
+ let size = 1;
151
+ anchors.forEach((anchor, index) => {
152
+ if (!anchor) return;
153
+ let node = 0;
154
+ for (let position = 0; position < anchor.length; position += 1) {
155
+ const slot = node * ANCHOR_WIDTH + ANCHOR_SYMBOL[anchor.charCodeAt(position)];
156
+ if (transitions[slot] === -1) {
157
+ if ((size + 1) * ANCHOR_WIDTH > transitions.length) {
158
+ const grown = new Int32Array(transitions.length * 2).fill(-1);
159
+ grown.set(transitions);
160
+ transitions = grown;
161
+ }
162
+ transitions[slot] = size;
163
+ outputs.push([]);
164
+ size += 1;
165
+ }
166
+ node = transitions[slot];
167
+ }
168
+ outputs[node].push(index);
169
+ });
170
+
171
+ const failure = new Int32Array(size);
172
+ const queue = [];
173
+ for (let symbol = 0; symbol < ANCHOR_WIDTH; symbol += 1) {
174
+ const child = transitions[symbol];
175
+ if (child === -1) transitions[symbol] = 0;
176
+ else queue.push(child);
177
+ }
178
+ for (let head = 0; head < queue.length; head += 1) {
179
+ const node = queue[head];
180
+ outputs[node] = outputs[node].concat(outputs[failure[node]]);
181
+ for (let symbol = 0; symbol < ANCHOR_WIDTH; symbol += 1) {
182
+ const slot = node * ANCHOR_WIDTH + symbol;
183
+ const child = transitions[slot];
184
+ if (child === -1) {
185
+ transitions[slot] = transitions[failure[node] * ANCHOR_WIDTH + symbol];
186
+ } else {
187
+ failure[child] = transitions[failure[node] * ANCHOR_WIDTH + symbol];
188
+ queue.push(child);
189
+ }
190
+ }
191
+ }
192
+
193
+ const next = new Int32Array(size * ANCHOR_WIDTH);
194
+ for (let slot = 0; slot < next.length; slot += 1) next[slot] = transitions[slot] * ANCHOR_WIDTH;
195
+ const emitted = new Array(size * ANCHOR_WIDTH).fill(null);
196
+ for (let node = 0; node < size; node += 1) if (outputs[node].length) emitted[node * ANCHOR_WIDTH] = outputs[node];
197
+
198
+ return (text) => {
199
+ const found = new Set();
200
+ let state = 0;
201
+ for (let position = 0; position < text.length; position += 1) {
202
+ const symbol = anchorSymbol(text.charCodeAt(position));
203
+ if (symbol === -1) {
204
+ state = 0;
205
+ continue;
206
+ }
207
+ state = next[state + symbol];
208
+ const matched = emitted[state];
209
+ if (matched !== null) for (const index of matched) found.add(index);
210
+ }
211
+ return found;
212
+ };
213
+ }
214
+
215
+ function compileReplacements(replacements) {
216
+ return { replacements, scan: buildAnchorScanner(replacements.map(({ anchor }) => anchor.toLowerCase())) };
217
+ }
218
+
219
+ function isDeeplyFrozen(object) {
220
+ return Object.isFrozen(object) && Object.values(object).every((value) => !Array.isArray(value) || Object.isFrozen(value));
221
+ }
222
+
223
+ const compiledMappings = new WeakMap();
224
+
225
+ function compileMapping(mapping) {
226
+ const cached = compiledMappings.get(mapping);
227
+ if (cached && cached.frozen) return cached;
228
+ const entries = Object.entries(mapping);
229
+ const signature = mappingSignature(entries);
230
+ if (cached && cached.signature === signature) return cached;
231
+
232
+ const codes = [...new Set(entries.map(([, code]) => String(code)).filter(Boolean))]
233
+ .sort((a, b) => b.length - a.length);
234
+ const replacements = [];
235
+ for (const [entity, code] of entries.sort(byDescendingEntityLength(([key]) => key))) {
236
+ const regex = buildEntityRegex(entity);
237
+ if (!regex) continue;
238
+ const trimmed = entity.trim();
239
+ replacements.push({ regex, code, anchor: literalAnchor(regex.flags.includes('i') ? trimmed : trimmed.toUpperCase()) });
240
+ }
241
+ const compiled = {
242
+ frozen: Object.isFrozen(mapping),
243
+ signature,
244
+ codes,
245
+ codeProbe: codes.length ? new RegExp(codes.map(escapeRegex).join('|')) : null,
246
+ replacements: compileReplacements(replacements),
247
+ };
248
+ compiledMappings.set(mapping, compiled);
249
+ return compiled;
250
+ }
251
+
252
+ function applyReplacements({ replacements, scan }, text) {
253
+ let output = text;
254
+ let present = null;
255
+ replacements.forEach(({ regex, code, anchor }, index) => {
256
+ if (anchor) {
257
+ if (present === null) present = scan(output);
258
+ if (!present.has(index)) return;
259
+ }
260
+ const next = output.replace(regex, code);
261
+ if (next !== output) {
262
+ output = next;
263
+ present = null;
264
+ }
265
+ });
266
+ return output;
267
+ }
268
+
118
269
  /**
119
270
  * Entité → code. Les entrées passent de la plus longue à la plus courte pour
120
271
  * qu'un nom contenu dans un autre ne consomme jamais le plus long en premier
@@ -122,41 +273,49 @@ function byDescendingEntityLength(getEntity) {
122
273
  */
123
274
  function applyMapping(text, mapping) {
124
275
  if (typeof text !== 'string' || !text) return text;
125
- const entries = Object.entries(mapping || {});
126
- if (!entries.length) return text;
127
-
128
- // Masquage préalable des codes déjà présents dans le texte. La substitution en
129
- // sous-chaîne (≥ 3 car.) et l'underscore-séparateur (acronymes 2 car.)
130
- // pourraient sinon réécrire l'intérieur d'un code — « SA » dans « URGOT SA »,
131
- // « Moral » dans « PERSONNE_MORALE_01 » — et le corrompre. Chaque code distinct
132
- // est remplacé par un caractère de zone privée (ni lettre ni chiffre, absent des
133
- // documents, jamais matché par une regex d'entité), on anonymise, puis on
134
- // restaure. C'est aussi ce qui garantit l'idempotence : réappliquer le mapping à
135
- // un texte déjà codé masque ses codes et ne touche à rien. Codes triés du plus
136
- // long au plus court pour qu'un code contenu dans un autre ne soit pas masqué
137
- // en premier.
138
- const codes = [...new Set(entries.map(([, code]) => String(code)).filter(Boolean))]
139
- .sort((a, b) => b.length - a.length);
276
+ if (!mapping || typeof mapping !== 'object') return text;
277
+ const compiled = compileMapping(mapping);
278
+ if (!compiled.replacements.replacements.length) return text;
279
+
140
280
  const restore = [];
141
281
  let masked = text;
142
- codes.forEach((code, idx) => {
143
- if (!masked.includes(code)) return;
144
- const token = String.fromCodePoint(0xE000 + idx);
145
- restore.push([token, code]);
146
- masked = masked.split(code).join(token);
147
- });
148
-
149
- let output = masked;
150
- for (const [entity, code] of entries.sort(byDescendingEntityLength(([key]) => key))) {
151
- const regex = buildEntityRegex(entity);
152
- if (!regex) continue;
153
- output = output.replace(regex, code);
282
+ if (compiled.codeProbe && compiled.codeProbe.test(text)) {
283
+ compiled.codes.forEach((code, idx) => {
284
+ if (!masked.includes(code)) return;
285
+ const token = String.fromCodePoint(0xE000 + idx);
286
+ restore.push([token, code]);
287
+ masked = masked.split(code).join(token);
288
+ });
154
289
  }
155
290
 
291
+ let output = applyReplacements(compiled.replacements, masked);
292
+ if (output === masked) return text;
293
+
156
294
  for (const [token, code] of restore) output = output.split(token).join(code);
157
295
  return output;
158
296
  }
159
297
 
298
+ const compiledReverseMappings = new WeakMap();
299
+
300
+ function compileReverseMapping(reverseMapping) {
301
+ const cached = compiledReverseMappings.get(reverseMapping);
302
+ if (cached && cached.frozen) return cached;
303
+ const entries = Object.entries(reverseMapping);
304
+ const signature = mappingSignature(entries.map(([code, variants]) => [code, Array.isArray(variants) ? variants[0] : variants]));
305
+ if (cached && cached.signature === signature) return cached;
306
+
307
+ const replacements = [];
308
+ for (const [code, variants] of entries.sort(byDescendingEntityLength(([key]) => key))) {
309
+ const canonical = Array.isArray(variants) ? variants[0] : variants;
310
+ if (!canonical) continue;
311
+ const regex = new RegExp(`${WORD_BOUNDARY_BEFORE}${escapeRegex(String(code))}${WORD_BOUNDARY_AFTER}`, 'giu');
312
+ replacements.push({ regex, code: String(canonical), anchor: literalAnchor(code) });
313
+ }
314
+ const compiled = { frozen: isDeeplyFrozen(reverseMapping), signature, replacements: compileReplacements(replacements) };
315
+ compiledReverseMappings.set(reverseMapping, compiled);
316
+ return compiled;
317
+ }
318
+
160
319
  /**
161
320
  * Code → entité. Le premier variant du tableau fait foi : c'est l'orthographe
162
321
  * canonique retenue par `consolidate_duplicate_entities`, celle qu'un humain
@@ -175,20 +334,9 @@ function applyMapping(text, mapping) {
175
334
  */
176
335
  function revertMapping(text, reverseMapping) {
177
336
  if (typeof text !== 'string' || !text) return text;
178
- const entries = Object.entries(reverseMapping || {});
179
- if (!entries.length) return text;
180
-
181
- let output = text;
182
- for (const [code, variants] of entries.sort(byDescendingEntityLength(([key]) => key))) {
183
- const canonical = Array.isArray(variants) ? variants[0] : variants;
184
- if (!canonical) continue;
185
- // Un code est un identifiant ASCII : pas de variantes Unicode à gérer, mais
186
- // les mêmes frontières de mots, pour ne pas réécrire un code cité dans un
187
- // mot plus long.
188
- const regex = new RegExp(`${WORD_BOUNDARY_BEFORE}${escapeRegex(String(code))}${WORD_BOUNDARY_AFTER}`, 'giu');
189
- output = output.replace(regex, String(canonical));
190
- }
191
- return output;
337
+ if (!reverseMapping || typeof reverseMapping !== 'object') return text;
338
+ const compiled = compileReverseMapping(reverseMapping);
339
+ return applyReplacements(compiled.replacements, text);
192
340
  }
193
341
 
194
342
  function normalizedPathName(value) {