@piecemaker-legal/piecemaker 1.0.0
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.
- package/LICENSE +718 -0
- package/README.md +80 -0
- package/dist/Mistral-Icon-Gradient-RGB.svg +12 -0
- package/dist/api-docs.html +901 -0
- package/dist/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
- package/dist/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
- package/dist/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
- package/dist/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
- package/dist/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
- package/dist/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
- package/dist/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
- package/dist/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
- package/dist/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
- package/dist/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
- package/dist/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
- package/dist/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
- package/dist/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
- package/dist/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
- package/dist/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
- package/dist/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
- package/dist/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
- package/dist/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
- package/dist/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
- package/dist/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
- package/dist/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
- package/dist/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
- package/dist/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
- package/dist/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
- package/dist/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
- package/dist/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
- package/dist/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
- package/dist/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
- package/dist/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
- package/dist/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
- package/dist/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
- package/dist/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
- package/dist/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
- package/dist/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
- package/dist/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
- package/dist/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
- package/dist/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
- package/dist/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
- package/dist/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
- package/dist/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
- package/dist/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
- package/dist/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
- package/dist/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
- package/dist/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
- package/dist/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
- package/dist/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
- package/dist/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
- package/dist/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
- package/dist/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
- package/dist/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
- package/dist/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
- package/dist/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
- package/dist/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
- package/dist/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
- package/dist/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
- package/dist/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
- package/dist/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
- package/dist/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
- package/dist/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
- package/dist/assets/abnfDiagram-N423BO3Z-DjEx6DRO.js +1 -0
- package/dist/assets/arc-DYt0oU_6.js +1 -0
- package/dist/assets/architectureDiagram-T3A2C74G-Dn6DhYDe.js +36 -0
- package/dist/assets/blockDiagram-VBNYF7ZC-CsvhTLZi.js +132 -0
- package/dist/assets/c4Diagram-5PPSVZJV-BuPnjHm6.js +10 -0
- package/dist/assets/channel-Czt0N91A.js +1 -0
- package/dist/assets/chunk-2GRJ4B5K-BZJJc1-J.js +1 -0
- package/dist/assets/chunk-2Q5K7J3B-6stdrDbe.js +1 -0
- package/dist/assets/chunk-5RXB4S5H-D5UwN5cg.js +231 -0
- package/dist/assets/chunk-5VM5RSS4-BDB8AQ_I.js +15 -0
- package/dist/assets/chunk-6Q2QTUOP-RhjQ-NDz.js +88 -0
- package/dist/assets/chunk-GF5L2VYU-CJuiA7R4.js +206 -0
- package/dist/assets/chunk-JWPE2WC7-DjFkIZqh.js +1 -0
- package/dist/assets/chunk-KBJHAD2P-BeSQsoqv.js +1 -0
- package/dist/assets/chunk-RYQCIY6F-CPgwsb0o.js +1 -0
- package/dist/assets/chunk-XXDRQBXY-CMMVEsX3.js +1 -0
- package/dist/assets/classDiagram-JCYQIIEL-BrnQyrwR.js +1 -0
- package/dist/assets/classDiagram-v2-OCEON4UE-BrnQyrwR.js +1 -0
- package/dist/assets/cose-bilkent-JH36ORCC-XAdYMfi5.js +1 -0
- package/dist/assets/cynefin-VYW2F7L2-d2GpDBn-.js +178 -0
- package/dist/assets/cynefinDiagram-MW4NZA55-CGtqUrU1.js +62 -0
- package/dist/assets/cytoscape.esm-C10L9Icf.js +331 -0
- package/dist/assets/dagre-VZM6K2ZE-BxujKIsf.js +4 -0
- package/dist/assets/defaultLocale-DX6XiGOO.js +1 -0
- package/dist/assets/diagram-7IWD3JNH-CJn9hobL.js +30 -0
- package/dist/assets/diagram-B4RE2ZJO-DF0Cnjzz.js +3 -0
- package/dist/assets/diagram-LBJQPF4R-Bq4d3NfR.js +24 -0
- package/dist/assets/diagram-Q27KOJAE-DDdfoXt-.js +24 -0
- package/dist/assets/diagram-UB23O5K3-TSFk7FuJ.js +41 -0
- package/dist/assets/ebnfDiagram-BXEA7PRR-DRvm2Wy9.js +1 -0
- package/dist/assets/erDiagram-JOGREHBK-BmdN66-A.js +85 -0
- package/dist/assets/flowDiagram-UKHOOZJN-Dsp5LwJz.js +156 -0
- package/dist/assets/ganttDiagram-PKOTCBZU-CYfCQ4Yi.js +292 -0
- package/dist/assets/gitGraphDiagram-DS77QQ5N-DEzmuv8v.js +106 -0
- package/dist/assets/graph-Cx-EqisW.js +1 -0
- package/dist/assets/index-CqFxV_pT.css +32 -0
- package/dist/assets/index-dWwHeqcq.js +4989 -0
- package/dist/assets/infoDiagram-6WML65LV-B1EfV5Bm.js +2 -0
- package/dist/assets/init-Gi6I4Gst.js +1 -0
- package/dist/assets/ishikawaDiagram-WSZJBQD7-OuQJfUii.js +70 -0
- package/dist/assets/journeyDiagram-NVQOT4AX-DHIilb_4.js +139 -0
- package/dist/assets/kanban-definition-27J2QSJJ-D4CvKWBP.js +89 -0
- package/dist/assets/layout-D6BstSzF.js +1 -0
- package/dist/assets/linear-i_Nzyzap.js +1 -0
- package/dist/assets/map-CV9uif0g.js +1 -0
- package/dist/assets/mermaid.core-DbSHejzG.js +312 -0
- package/dist/assets/mindmap-definition-FAOFIHXS-DqhC-BPZ.js +96 -0
- package/dist/assets/ordinal-Cboi1Yqb.js +1 -0
- package/dist/assets/pegDiagram-VL7TDLO6-pBqvOEvm.js +1 -0
- package/dist/assets/pieDiagram-7S7Q4E2Y-BH53iuaz.js +39 -0
- package/dist/assets/quadrantDiagram-CIZ2JOQS-CvW9ldJo.js +7 -0
- package/dist/assets/railroadDiagram-AXF67PYL-CERi66oR.js +1 -0
- package/dist/assets/requirementDiagram-LRYGKXZP-DbcP4MGI.js +84 -0
- package/dist/assets/sankeyDiagram-W5VNT64P-DO9bPHWO.js +40 -0
- package/dist/assets/sequenceDiagram-SI44F4Z6-D3zd2eJS.js +162 -0
- package/dist/assets/server.browser-CtX3Hdl5.js +27 -0
- package/dist/assets/sizeCapture-X5ZJPWSS-mcEXJiem.js +1 -0
- package/dist/assets/stateDiagram-OKZ733FA-DMqQgpiD.js +1 -0
- package/dist/assets/stateDiagram-v2-UEYNNEHI-DprF_P9y.js +1 -0
- package/dist/assets/swimlanes-SLNWSIFB-Cb8rIhB1.js +2 -0
- package/dist/assets/swimlanesDiagram-ULZ7WXOC-C6NVYMho.js +8 -0
- package/dist/assets/timeline-definition-Z64GVDOM-BQ4r_QsK.js +120 -0
- package/dist/assets/vendor-codemirror-CYrSt7-U.js +41 -0
- package/dist/assets/vendor-react-BGZc9oRE.js +59 -0
- package/dist/assets/vendor-xterm-CS4rQ5Mr.js +66 -0
- package/dist/assets/vennDiagram-T6HMQDX7-BEkKGTum.js +34 -0
- package/dist/assets/wardleyDiagram-T6FBY63Y-kJwmgH-h.js +78 -0
- package/dist/assets/xychartDiagram-ELKLHX3M-CSJRTKCr.js +7 -0
- package/dist/clear-cache.html +85 -0
- package/dist/convert-icons.md +53 -0
- package/dist/favicon.png +0 -0
- package/dist/favicon.svg +9 -0
- package/dist/generate-icons.js +49 -0
- package/dist/icons/claude-ai-icon.svg +1 -0
- package/dist/icons/codex-white.svg +3 -0
- package/dist/icons/codex.svg +3 -0
- package/dist/icons/cursor-white.svg +12 -0
- package/dist/icons/cursor.svg +1 -0
- package/dist/icons/icon-128x128.png +0 -0
- package/dist/icons/icon-128x128.svg +12 -0
- package/dist/icons/icon-144x144.png +0 -0
- package/dist/icons/icon-144x144.svg +12 -0
- package/dist/icons/icon-152x152.png +0 -0
- package/dist/icons/icon-152x152.svg +12 -0
- package/dist/icons/icon-192x192.png +0 -0
- package/dist/icons/icon-192x192.svg +12 -0
- package/dist/icons/icon-384x384.png +0 -0
- package/dist/icons/icon-384x384.svg +12 -0
- package/dist/icons/icon-512x512.png +0 -0
- package/dist/icons/icon-512x512.svg +12 -0
- package/dist/icons/icon-72x72.png +0 -0
- package/dist/icons/icon-72x72.svg +12 -0
- package/dist/icons/icon-96x96.png +0 -0
- package/dist/icons/icon-96x96.svg +12 -0
- package/dist/icons/icon-template.svg +12 -0
- package/dist/index.html +60 -0
- package/dist/logo-128.png +0 -0
- package/dist/logo-256.png +0 -0
- package/dist/logo-32.png +0 -0
- package/dist/logo-512.png +0 -0
- package/dist/logo-64.png +0 -0
- package/dist/logo.svg +17 -0
- package/dist/manifest.json +61 -0
- package/dist/screenshots/cli-selection.png +0 -0
- package/dist/screenshots/desktop-main.png +0 -0
- package/dist/screenshots/mobile-chat.png +0 -0
- package/dist/screenshots/tools-modal.png +0 -0
- package/dist/sw.js +125 -0
- package/dist-server/server/index.js +345 -0
- package/dist-server/server/index.js.map +1 -0
- package/dist-server/server/load-env.js +46 -0
- package/dist-server/server/load-env.js.map +1 -0
- package/dist-server/server/modules/agent/agent.module.js +38 -0
- package/dist-server/server/modules/agent/agent.module.js.map +1 -0
- package/dist-server/server/modules/agent/agent.routes.js +1161 -0
- package/dist-server/server/modules/agent/agent.routes.js.map +1 -0
- package/dist-server/server/modules/agent/index.js +3 -0
- package/dist-server/server/modules/agent/index.js.map +1 -0
- package/dist-server/server/modules/agent/tests/agent.routes.test.js +176 -0
- package/dist-server/server/modules/agent/tests/agent.routes.test.js.map +1 -0
- package/dist-server/server/modules/assets/assets.routes.js +130 -0
- package/dist-server/server/modules/assets/assets.routes.js.map +1 -0
- package/dist-server/server/modules/assets/index.js +4 -0
- package/dist-server/server/modules/assets/index.js.map +1 -0
- package/dist-server/server/modules/assets/services/image-assets.service.js +95 -0
- package/dist-server/server/modules/assets/services/image-assets.service.js.map +1 -0
- package/dist-server/server/modules/assets/tests/image-assets.service.test.js +56 -0
- package/dist-server/server/modules/assets/tests/image-assets.service.test.js.map +1 -0
- package/dist-server/server/modules/auth/auth.middleware.js +133 -0
- package/dist-server/server/modules/auth/auth.middleware.js.map +1 -0
- package/dist-server/server/modules/auth/auth.module.js +29 -0
- package/dist-server/server/modules/auth/auth.module.js.map +1 -0
- package/dist-server/server/modules/auth/auth.routes.js +45 -0
- package/dist-server/server/modules/auth/auth.routes.js.map +1 -0
- package/dist-server/server/modules/auth/auth.service.js +113 -0
- package/dist-server/server/modules/auth/auth.service.js.map +1 -0
- package/dist-server/server/modules/auth/index.js +9 -0
- package/dist-server/server/modules/auth/index.js.map +1 -0
- package/dist-server/server/modules/auth/tests/auth.service.test.js +80 -0
- package/dist-server/server/modules/auth/tests/auth.service.test.js.map +1 -0
- package/dist-server/server/modules/browser-use/browser-use-mcp.js +352 -0
- package/dist-server/server/modules/browser-use/browser-use-mcp.js.map +1 -0
- package/dist-server/server/modules/browser-use/browser-use-mcp.routes.js +107 -0
- package/dist-server/server/modules/browser-use/browser-use-mcp.routes.js.map +1 -0
- package/dist-server/server/modules/browser-use/browser-use-runtime.js +10 -0
- package/dist-server/server/modules/browser-use/browser-use-runtime.js.map +1 -0
- package/dist-server/server/modules/browser-use/browser-use.routes.js +93 -0
- package/dist-server/server/modules/browser-use/browser-use.routes.js.map +1 -0
- package/dist-server/server/modules/browser-use/browser-use.service.js +686 -0
- package/dist-server/server/modules/browser-use/browser-use.service.js.map +1 -0
- package/dist-server/server/modules/browser-use/index.js +11 -0
- package/dist-server/server/modules/browser-use/index.js.map +1 -0
- package/dist-server/server/modules/browser-use/tests/browser-use.service.test.js +8 -0
- package/dist-server/server/modules/browser-use/tests/browser-use.service.test.js.map +1 -0
- package/dist-server/server/modules/cli/cli.js +17 -0
- package/dist-server/server/modules/cli/cli.js.map +1 -0
- package/dist-server/server/modules/cli/cli.module.js +93 -0
- package/dist-server/server/modules/cli/cli.module.js.map +1 -0
- package/dist-server/server/modules/cli/cli.service.js +230 -0
- package/dist-server/server/modules/cli/cli.service.js.map +1 -0
- package/dist-server/server/modules/cli/index.js +3 -0
- package/dist-server/server/modules/cli/index.js.map +1 -0
- package/dist-server/server/modules/cli/pwa-launcher.js +116 -0
- package/dist-server/server/modules/cli/pwa-launcher.js.map +1 -0
- package/dist-server/server/modules/cli/sandbox.service.js +332 -0
- package/dist-server/server/modules/cli/sandbox.service.js.map +1 -0
- package/dist-server/server/modules/cli/tests/cli-environment-bootstrap.test.js +154 -0
- package/dist-server/server/modules/cli/tests/cli-environment-bootstrap.test.js.map +1 -0
- package/dist-server/server/modules/cli/tests/cli.service.test.js +91 -0
- package/dist-server/server/modules/cli/tests/cli.service.test.js.map +1 -0
- package/dist-server/server/modules/cli/tests/sandbox.service.test.js +55 -0
- package/dist-server/server/modules/cli/tests/sandbox.service.test.js.map +1 -0
- package/dist-server/server/modules/commands/commands.module.js +20 -0
- package/dist-server/server/modules/commands/commands.module.js.map +1 -0
- package/dist-server/server/modules/commands/commands.routes.js +498 -0
- package/dist-server/server/modules/commands/commands.routes.js.map +1 -0
- package/dist-server/server/modules/commands/index.js +3 -0
- package/dist-server/server/modules/commands/index.js.map +1 -0
- package/dist-server/server/modules/commands/tests/commands.test.js +91 -0
- package/dist-server/server/modules/commands/tests/commands.test.js.map +1 -0
- package/dist-server/server/modules/database/connection.js +125 -0
- package/dist-server/server/modules/database/connection.js.map +1 -0
- package/dist-server/server/modules/database/index.js +23 -0
- package/dist-server/server/modules/database/index.js.map +1 -0
- package/dist-server/server/modules/database/init-db.js +18 -0
- package/dist-server/server/modules/database/init-db.js.map +1 -0
- package/dist-server/server/modules/database/migrations.js +465 -0
- package/dist-server/server/modules/database/migrations.js.map +1 -0
- package/dist-server/server/modules/database/repositories/api-keys.js +72 -0
- package/dist-server/server/modules/database/repositories/api-keys.js.map +1 -0
- package/dist-server/server/modules/database/repositories/app-config.js +47 -0
- package/dist-server/server/modules/database/repositories/app-config.js.map +1 -0
- package/dist-server/server/modules/database/repositories/credentials.js +68 -0
- package/dist-server/server/modules/database/repositories/credentials.js.map +1 -0
- package/dist-server/server/modules/database/repositories/github-tokens.js +54 -0
- package/dist-server/server/modules/database/repositories/github-tokens.js.map +1 -0
- package/dist-server/server/modules/database/repositories/notification-channel-endpoints.js +100 -0
- package/dist-server/server/modules/database/repositories/notification-channel-endpoints.js.map +1 -0
- package/dist-server/server/modules/database/repositories/notification-preferences.js +82 -0
- package/dist-server/server/modules/database/repositories/notification-preferences.js.map +1 -0
- package/dist-server/server/modules/database/repositories/projects.db.js +172 -0
- package/dist-server/server/modules/database/repositories/projects.db.js.map +1 -0
- package/dist-server/server/modules/database/repositories/provider-models.js +112 -0
- package/dist-server/server/modules/database/repositories/provider-models.js.map +1 -0
- package/dist-server/server/modules/database/repositories/push-subscriptions.js +49 -0
- package/dist-server/server/modules/database/repositories/push-subscriptions.js.map +1 -0
- package/dist-server/server/modules/database/repositories/scan-state.db.js +31 -0
- package/dist-server/server/modules/database/repositories/scan-state.db.js.map +1 -0
- package/dist-server/server/modules/database/repositories/scheduled-messages.db.js +71 -0
- package/dist-server/server/modules/database/repositories/scheduled-messages.db.js.map +1 -0
- package/dist-server/server/modules/database/repositories/session-drafts.db.js +105 -0
- package/dist-server/server/modules/database/repositories/session-drafts.db.js.map +1 -0
- package/dist-server/server/modules/database/repositories/sessions.db.js +507 -0
- package/dist-server/server/modules/database/repositories/sessions.db.js.map +1 -0
- package/dist-server/server/modules/database/repositories/user-preferences.db.js +65 -0
- package/dist-server/server/modules/database/repositories/user-preferences.db.js.map +1 -0
- package/dist-server/server/modules/database/repositories/users.js +88 -0
- package/dist-server/server/modules/database/repositories/users.js.map +1 -0
- package/dist-server/server/modules/database/repositories/vapid-keys.js +38 -0
- package/dist-server/server/modules/database/repositories/vapid-keys.js.map +1 -0
- package/dist-server/server/modules/database/schema.js +291 -0
- package/dist-server/server/modules/database/schema.js.map +1 -0
- package/dist-server/server/modules/database/tests/projects.db.integration.test.js +64 -0
- package/dist-server/server/modules/database/tests/projects.db.integration.test.js.map +1 -0
- package/dist-server/server/modules/database/tests/provider-models.db.integration.test.js +102 -0
- package/dist-server/server/modules/database/tests/provider-models.db.integration.test.js.map +1 -0
- package/dist-server/server/modules/database/tests/sessions-provider-mapping.test.js +79 -0
- package/dist-server/server/modules/database/tests/sessions-provider-mapping.test.js.map +1 -0
- package/dist-server/server/modules/database/tests/sessions.db.integration.test.js +93 -0
- package/dist-server/server/modules/database/tests/sessions.db.integration.test.js.map +1 -0
- package/dist-server/server/modules/database/tests/user-preferences.db.integration.test.js +105 -0
- package/dist-server/server/modules/database/tests/user-preferences.db.integration.test.js.map +1 -0
- package/dist-server/server/modules/file-tree/file-tree.module.js +95 -0
- package/dist-server/server/modules/file-tree/file-tree.module.js.map +1 -0
- package/dist-server/server/modules/file-tree/file-tree.routes.js +208 -0
- package/dist-server/server/modules/file-tree/file-tree.routes.js.map +1 -0
- package/dist-server/server/modules/file-tree/file-tree.service.js +584 -0
- package/dist-server/server/modules/file-tree/file-tree.service.js.map +1 -0
- package/dist-server/server/modules/file-tree/index.js +3 -0
- package/dist-server/server/modules/file-tree/index.js.map +1 -0
- package/dist-server/server/modules/file-tree/tests/file-tree.routes.test.js +123 -0
- package/dist-server/server/modules/file-tree/tests/file-tree.routes.test.js.map +1 -0
- package/dist-server/server/modules/file-tree/tests/file-tree.service.test.js +311 -0
- package/dist-server/server/modules/file-tree/tests/file-tree.service.test.js.map +1 -0
- package/dist-server/server/modules/git/git-branch.service.js +15 -0
- package/dist-server/server/modules/git/git-branch.service.js.map +1 -0
- package/dist-server/server/modules/git/git-parsing.service.js +61 -0
- package/dist-server/server/modules/git/git-parsing.service.js.map +1 -0
- package/dist-server/server/modules/git/git.module.js +14 -0
- package/dist-server/server/modules/git/git.module.js.map +1 -0
- package/dist-server/server/modules/git/git.routes.js +1347 -0
- package/dist-server/server/modules/git/git.routes.js.map +1 -0
- package/dist-server/server/modules/git/index.js +3 -0
- package/dist-server/server/modules/git/index.js.map +1 -0
- package/dist-server/server/modules/git/tests/git-branch.service.test.js +52 -0
- package/dist-server/server/modules/git/tests/git-branch.service.test.js.map +1 -0
- package/dist-server/server/modules/git/tests/git-init.routes.test.js +132 -0
- package/dist-server/server/modules/git/tests/git-init.routes.test.js.map +1 -0
- package/dist-server/server/modules/git/tests/git.test.js +80 -0
- package/dist-server/server/modules/git/tests/git.test.js.map +1 -0
- package/dist-server/server/modules/notifications/index.js +20 -0
- package/dist-server/server/modules/notifications/index.js.map +1 -0
- package/dist-server/server/modules/notifications/notifications.routes.js +115 -0
- package/dist-server/server/modules/notifications/notifications.routes.js.map +1 -0
- package/dist-server/server/modules/notifications/services/desktop-notification-clients.service.js +97 -0
- package/dist-server/server/modules/notifications/services/desktop-notification-clients.service.js.map +1 -0
- package/dist-server/server/modules/notifications/services/notification-orchestrator.service.js +249 -0
- package/dist-server/server/modules/notifications/services/notification-orchestrator.service.js.map +1 -0
- package/dist-server/server/modules/notifications/tests/notification-orchestrator.integration.test.js +44 -0
- package/dist-server/server/modules/notifications/tests/notification-orchestrator.integration.test.js.map +1 -0
- package/dist-server/server/modules/notifications/vapid-keys.service.js +28 -0
- package/dist-server/server/modules/notifications/vapid-keys.service.js.map +1 -0
- package/dist-server/server/modules/notifications/websocket/desktop-notifications-websocket.service.js +79 -0
- package/dist-server/server/modules/notifications/websocket/desktop-notifications-websocket.service.js.map +1 -0
- package/dist-server/server/modules/plugins/index.js +9 -0
- package/dist-server/server/modules/plugins/index.js.map +1 -0
- package/dist-server/server/modules/plugins/plugin-process.service.js +191 -0
- package/dist-server/server/modules/plugins/plugin-process.service.js.map +1 -0
- package/dist-server/server/modules/plugins/plugin-registry.service.js +415 -0
- package/dist-server/server/modules/plugins/plugin-registry.service.js.map +1 -0
- package/dist-server/server/modules/plugins/plugins.module.js +30 -0
- package/dist-server/server/modules/plugins/plugins.module.js.map +1 -0
- package/dist-server/server/modules/plugins/plugins.routes.js +70 -0
- package/dist-server/server/modules/plugins/plugins.routes.js.map +1 -0
- package/dist-server/server/modules/plugins/plugins.service.js +123 -0
- package/dist-server/server/modules/plugins/plugins.service.js.map +1 -0
- package/dist-server/server/modules/plugins/tests/plugins.service.test.js +28 -0
- package/dist-server/server/modules/plugins/tests/plugins.service.test.js.map +1 -0
- package/dist-server/server/modules/projects/index.js +9 -0
- package/dist-server/server/modules/projects/index.js.map +1 -0
- package/dist-server/server/modules/projects/projects.routes.js +205 -0
- package/dist-server/server/modules/projects/projects.routes.js.map +1 -0
- package/dist-server/server/modules/projects/services/project-clone.service.js +220 -0
- package/dist-server/server/modules/projects/services/project-clone.service.js.map +1 -0
- package/dist-server/server/modules/projects/services/project-delete.service.js +89 -0
- package/dist-server/server/modules/projects/services/project-delete.service.js.map +1 -0
- package/dist-server/server/modules/projects/services/project-management.service.js +90 -0
- package/dist-server/server/modules/projects/services/project-management.service.js.map +1 -0
- package/dist-server/server/modules/projects/services/project-star.service.js +60 -0
- package/dist-server/server/modules/projects/services/project-star.service.js.map +1 -0
- package/dist-server/server/modules/projects/services/projects-has-taskmaster.service.js +167 -0
- package/dist-server/server/modules/projects/services/projects-has-taskmaster.service.js.map +1 -0
- package/dist-server/server/modules/projects/services/projects-with-sessions-fetch.service.js +188 -0
- package/dist-server/server/modules/projects/services/projects-with-sessions-fetch.service.js.map +1 -0
- package/dist-server/server/modules/projects/tests/project-clone.service.test.js +126 -0
- package/dist-server/server/modules/projects/tests/project-clone.service.test.js.map +1 -0
- package/dist-server/server/modules/projects/tests/project-management.service.test.js +85 -0
- package/dist-server/server/modules/projects/tests/project-management.service.test.js.map +1 -0
- package/dist-server/server/modules/projects/tests/project-star.service.test.js +95 -0
- package/dist-server/server/modules/projects/tests/project-star.service.test.js.map +1 -0
- package/dist-server/server/modules/projects/tests/projects-has-taskmaster.service.test.js +87 -0
- package/dist-server/server/modules/projects/tests/projects-has-taskmaster.service.test.js.map +1 -0
- package/dist-server/server/modules/providers/index.js +12 -0
- package/dist-server/server/modules/providers/index.js.map +1 -0
- package/dist-server/server/modules/providers/list/claude/claude-auth.provider.js +151 -0
- package/dist-server/server/modules/providers/list/claude/claude-auth.provider.js.map +1 -0
- package/dist-server/server/modules/providers/list/claude/claude-fork.provider.js +46 -0
- package/dist-server/server/modules/providers/list/claude/claude-fork.provider.js.map +1 -0
- package/dist-server/server/modules/providers/list/claude/claude-mcp.provider.js +103 -0
- package/dist-server/server/modules/providers/list/claude/claude-mcp.provider.js.map +1 -0
- package/dist-server/server/modules/providers/list/claude/claude-models.provider.js +267 -0
- package/dist-server/server/modules/providers/list/claude/claude-models.provider.js.map +1 -0
- package/dist-server/server/modules/providers/list/claude/claude-runtime.provider.js +1058 -0
- package/dist-server/server/modules/providers/list/claude/claude-runtime.provider.js.map +1 -0
- package/dist-server/server/modules/providers/list/claude/claude-session-synchronizer.provider.js +141 -0
- package/dist-server/server/modules/providers/list/claude/claude-session-synchronizer.provider.js.map +1 -0
- package/dist-server/server/modules/providers/list/claude/claude-sessions.provider.js +924 -0
- package/dist-server/server/modules/providers/list/claude/claude-sessions.provider.js.map +1 -0
- package/dist-server/server/modules/providers/list/claude/claude-skills.provider.js +205 -0
- package/dist-server/server/modules/providers/list/claude/claude-skills.provider.js.map +1 -0
- package/dist-server/server/modules/providers/list/claude/claude.provider.js +23 -0
- package/dist-server/server/modules/providers/list/claude/claude.provider.js.map +1 -0
- package/dist-server/server/modules/providers/list/codex/codex-app-server.client.js +204 -0
- package/dist-server/server/modules/providers/list/codex/codex-app-server.client.js.map +1 -0
- package/dist-server/server/modules/providers/list/codex/codex-auth.provider.js +84 -0
- package/dist-server/server/modules/providers/list/codex/codex-auth.provider.js.map +1 -0
- package/dist-server/server/modules/providers/list/codex/codex-fork.provider.js +32 -0
- package/dist-server/server/modules/providers/list/codex/codex-fork.provider.js.map +1 -0
- package/dist-server/server/modules/providers/list/codex/codex-mcp.provider.js +107 -0
- package/dist-server/server/modules/providers/list/codex/codex-mcp.provider.js.map +1 -0
- package/dist-server/server/modules/providers/list/codex/codex-models.provider.js +109 -0
- package/dist-server/server/modules/providers/list/codex/codex-models.provider.js.map +1 -0
- package/dist-server/server/modules/providers/list/codex/codex-runtime.provider.js +498 -0
- package/dist-server/server/modules/providers/list/codex/codex-runtime.provider.js.map +1 -0
- package/dist-server/server/modules/providers/list/codex/codex-session-synchronizer.provider.js +153 -0
- package/dist-server/server/modules/providers/list/codex/codex-session-synchronizer.provider.js.map +1 -0
- package/dist-server/server/modules/providers/list/codex/codex-sessions.provider.js +1955 -0
- package/dist-server/server/modules/providers/list/codex/codex-sessions.provider.js.map +1 -0
- package/dist-server/server/modules/providers/list/codex/codex-skills.provider.js +62 -0
- package/dist-server/server/modules/providers/list/codex/codex-skills.provider.js.map +1 -0
- package/dist-server/server/modules/providers/list/codex/codex.provider.js +23 -0
- package/dist-server/server/modules/providers/list/codex/codex.provider.js.map +1 -0
- package/dist-server/server/modules/providers/list/cursor/cursor-auth.provider.js +118 -0
- package/dist-server/server/modules/providers/list/cursor/cursor-auth.provider.js.map +1 -0
- package/dist-server/server/modules/providers/list/cursor/cursor-mcp.provider.js +80 -0
- package/dist-server/server/modules/providers/list/cursor/cursor-mcp.provider.js.map +1 -0
- package/dist-server/server/modules/providers/list/cursor/cursor-models.provider.js +100 -0
- package/dist-server/server/modules/providers/list/cursor/cursor-models.provider.js.map +1 -0
- package/dist-server/server/modules/providers/list/cursor/cursor-runtime.provider.js +305 -0
- package/dist-server/server/modules/providers/list/cursor/cursor-runtime.provider.js.map +1 -0
- package/dist-server/server/modules/providers/list/cursor/cursor-session-synchronizer.provider.js +111 -0
- package/dist-server/server/modules/providers/list/cursor/cursor-session-synchronizer.provider.js.map +1 -0
- package/dist-server/server/modules/providers/list/cursor/cursor-sessions.provider.js +566 -0
- package/dist-server/server/modules/providers/list/cursor/cursor-sessions.provider.js.map +1 -0
- package/dist-server/server/modules/providers/list/cursor/cursor-skills.provider.js +35 -0
- package/dist-server/server/modules/providers/list/cursor/cursor-skills.provider.js.map +1 -0
- package/dist-server/server/modules/providers/list/cursor/cursor.provider.js +21 -0
- package/dist-server/server/modules/providers/list/cursor/cursor.provider.js.map +1 -0
- package/dist-server/server/modules/providers/list/mistral/mistral-auth.provider.js +147 -0
- package/dist-server/server/modules/providers/list/mistral/mistral-auth.provider.js.map +1 -0
- package/dist-server/server/modules/providers/list/mistral/mistral-mcp.provider.js +15 -0
- package/dist-server/server/modules/providers/list/mistral/mistral-mcp.provider.js.map +1 -0
- package/dist-server/server/modules/providers/list/mistral/mistral-models.provider.js +69 -0
- package/dist-server/server/modules/providers/list/mistral/mistral-models.provider.js.map +1 -0
- package/dist-server/server/modules/providers/list/mistral/mistral-runtime.provider.js +326 -0
- package/dist-server/server/modules/providers/list/mistral/mistral-runtime.provider.js.map +1 -0
- package/dist-server/server/modules/providers/list/mistral/mistral-session-synchronizer.provider.js +12 -0
- package/dist-server/server/modules/providers/list/mistral/mistral-session-synchronizer.provider.js.map +1 -0
- package/dist-server/server/modules/providers/list/mistral/mistral-sessions.provider.js +180 -0
- package/dist-server/server/modules/providers/list/mistral/mistral-sessions.provider.js.map +1 -0
- package/dist-server/server/modules/providers/list/mistral/mistral-skills.provider.js +15 -0
- package/dist-server/server/modules/providers/list/mistral/mistral-skills.provider.js.map +1 -0
- package/dist-server/server/modules/providers/list/mistral/mistral.provider.js +25 -0
- package/dist-server/server/modules/providers/list/mistral/mistral.provider.js.map +1 -0
- package/dist-server/server/modules/providers/list/opencode/opencode-auth.provider.js +91 -0
- package/dist-server/server/modules/providers/list/opencode/opencode-auth.provider.js.map +1 -0
- package/dist-server/server/modules/providers/list/opencode/opencode-mcp.provider.js +181 -0
- package/dist-server/server/modules/providers/list/opencode/opencode-mcp.provider.js.map +1 -0
- package/dist-server/server/modules/providers/list/opencode/opencode-models.provider.js +257 -0
- package/dist-server/server/modules/providers/list/opencode/opencode-models.provider.js.map +1 -0
- package/dist-server/server/modules/providers/list/opencode/opencode-runtime.provider.js +368 -0
- package/dist-server/server/modules/providers/list/opencode/opencode-runtime.provider.js.map +1 -0
- package/dist-server/server/modules/providers/list/opencode/opencode-runtime.provider.test.js +225 -0
- package/dist-server/server/modules/providers/list/opencode/opencode-runtime.provider.test.js.map +1 -0
- package/dist-server/server/modules/providers/list/opencode/opencode-session-synchronizer.provider.js +136 -0
- package/dist-server/server/modules/providers/list/opencode/opencode-session-synchronizer.provider.js.map +1 -0
- package/dist-server/server/modules/providers/list/opencode/opencode-sessions.provider.js +404 -0
- package/dist-server/server/modules/providers/list/opencode/opencode-sessions.provider.js.map +1 -0
- package/dist-server/server/modules/providers/list/opencode/opencode-skills.provider.js +62 -0
- package/dist-server/server/modules/providers/list/opencode/opencode-skills.provider.js.map +1 -0
- package/dist-server/server/modules/providers/list/opencode/opencode.provider.js +21 -0
- package/dist-server/server/modules/providers/list/opencode/opencode.provider.js.map +1 -0
- package/dist-server/server/modules/providers/provider.registry.js +33 -0
- package/dist-server/server/modules/providers/provider.registry.js.map +1 -0
- package/dist-server/server/modules/providers/provider.routes.js +676 -0
- package/dist-server/server/modules/providers/provider.routes.js.map +1 -0
- package/dist-server/server/modules/providers/services/mcp.service.js +84 -0
- package/dist-server/server/modules/providers/services/mcp.service.js.map +1 -0
- package/dist-server/server/modules/providers/services/provider-auth.service.js +25 -0
- package/dist-server/server/modules/providers/services/provider-auth.service.js.map +1 -0
- package/dist-server/server/modules/providers/services/provider-capabilities.service.js +94 -0
- package/dist-server/server/modules/providers/services/provider-capabilities.service.js.map +1 -0
- package/dist-server/server/modules/providers/services/provider-models.service.js +289 -0
- package/dist-server/server/modules/providers/services/provider-models.service.js.map +1 -0
- package/dist-server/server/modules/providers/services/provider-runtime.service.js +66 -0
- package/dist-server/server/modules/providers/services/provider-runtime.service.js.map +1 -0
- package/dist-server/server/modules/providers/services/provider-token-usage.service.js +372 -0
- package/dist-server/server/modules/providers/services/provider-token-usage.service.js.map +1 -0
- package/dist-server/server/modules/providers/services/session-conversations-search.service.js +974 -0
- package/dist-server/server/modules/providers/services/session-conversations-search.service.js.map +1 -0
- package/dist-server/server/modules/providers/services/session-history-cache.service.js +87 -0
- package/dist-server/server/modules/providers/services/session-history-cache.service.js.map +1 -0
- package/dist-server/server/modules/providers/services/session-synchronizer.service.js +130 -0
- package/dist-server/server/modules/providers/services/session-synchronizer.service.js.map +1 -0
- package/dist-server/server/modules/providers/services/sessions-watcher.service.js +214 -0
- package/dist-server/server/modules/providers/services/sessions-watcher.service.js.map +1 -0
- package/dist-server/server/modules/providers/services/sessions.service.js +519 -0
- package/dist-server/server/modules/providers/services/sessions.service.js.map +1 -0
- package/dist-server/server/modules/providers/services/skills.service.js +22 -0
- package/dist-server/server/modules/providers/services/skills.service.js.map +1 -0
- package/dist-server/server/modules/providers/shared/base/abstract.provider.js +13 -0
- package/dist-server/server/modules/providers/shared/base/abstract.provider.js.map +1 -0
- package/dist-server/server/modules/providers/shared/mcp/mcp.provider.js +102 -0
- package/dist-server/server/modules/providers/shared/mcp/mcp.provider.js.map +1 -0
- package/dist-server/server/modules/providers/shared/skills/skills.provider.js +207 -0
- package/dist-server/server/modules/providers/shared/skills/skills.provider.js.map +1 -0
- package/dist-server/server/modules/providers/tests/claude-auth.test.js +174 -0
- package/dist-server/server/modules/providers/tests/claude-auth.test.js.map +1 -0
- package/dist-server/server/modules/providers/tests/claude-sessions.test.js +470 -0
- package/dist-server/server/modules/providers/tests/claude-sessions.test.js.map +1 -0
- package/dist-server/server/modules/providers/tests/claude-subagent-echo.test.js +16 -0
- package/dist-server/server/modules/providers/tests/claude-subagent-echo.test.js.map +1 -0
- package/dist-server/server/modules/providers/tests/claude-token-budget.test.js +85 -0
- package/dist-server/server/modules/providers/tests/claude-token-budget.test.js.map +1 -0
- package/dist-server/server/modules/providers/tests/codex-message-editing.test.js +338 -0
- package/dist-server/server/modules/providers/tests/codex-message-editing.test.js.map +1 -0
- package/dist-server/server/modules/providers/tests/codex-session-fork.test.js +54 -0
- package/dist-server/server/modules/providers/tests/codex-session-fork.test.js.map +1 -0
- package/dist-server/server/modules/providers/tests/codex-sessions.test.js +491 -0
- package/dist-server/server/modules/providers/tests/codex-sessions.test.js.map +1 -0
- package/dist-server/server/modules/providers/tests/mcp.test.js +291 -0
- package/dist-server/server/modules/providers/tests/mcp.test.js.map +1 -0
- package/dist-server/server/modules/providers/tests/opencode-models.test.js +103 -0
- package/dist-server/server/modules/providers/tests/opencode-models.test.js.map +1 -0
- package/dist-server/server/modules/providers/tests/opencode-sessions.test.js +424 -0
- package/dist-server/server/modules/providers/tests/opencode-sessions.test.js.map +1 -0
- package/dist-server/server/modules/providers/tests/provider-attachment-history.test.js +205 -0
- package/dist-server/server/modules/providers/tests/provider-attachment-history.test.js.map +1 -0
- package/dist-server/server/modules/providers/tests/provider-models.service.test.js +268 -0
- package/dist-server/server/modules/providers/tests/provider-models.service.test.js.map +1 -0
- package/dist-server/server/modules/providers/tests/provider-runtime.service.test.js +126 -0
- package/dist-server/server/modules/providers/tests/provider-runtime.service.test.js.map +1 -0
- package/dist-server/server/modules/providers/tests/provider-token-usage.service.test.js +329 -0
- package/dist-server/server/modules/providers/tests/provider-token-usage.service.test.js.map +1 -0
- package/dist-server/server/modules/providers/tests/provider.routes.test.js +161 -0
- package/dist-server/server/modules/providers/tests/provider.routes.test.js.map +1 -0
- package/dist-server/server/modules/providers/tests/session-fork.test.js +113 -0
- package/dist-server/server/modules/providers/tests/session-fork.test.js.map +1 -0
- package/dist-server/server/modules/providers/tests/session-history-cache.service.test.js +120 -0
- package/dist-server/server/modules/providers/tests/session-history-cache.service.test.js.map +1 -0
- package/dist-server/server/modules/providers/tests/session-orphan-prune.test.js +90 -0
- package/dist-server/server/modules/providers/tests/session-orphan-prune.test.js.map +1 -0
- package/dist-server/server/modules/providers/tests/session-synchronizer-coalescing.test.js +122 -0
- package/dist-server/server/modules/providers/tests/session-synchronizer-coalescing.test.js.map +1 -0
- package/dist-server/server/modules/providers/tests/sessions-details.test.js +62 -0
- package/dist-server/server/modules/providers/tests/sessions-details.test.js.map +1 -0
- package/dist-server/server/modules/providers/tests/sessions.service.test.js +131 -0
- package/dist-server/server/modules/providers/tests/sessions.service.test.js.map +1 -0
- package/dist-server/server/modules/providers/tests/skills.test.js +398 -0
- package/dist-server/server/modules/providers/tests/skills.test.js.map +1 -0
- package/dist-server/server/modules/scheduled-messages/index.js +5 -0
- package/dist-server/server/modules/scheduled-messages/index.js.map +1 -0
- package/dist-server/server/modules/scheduled-messages/scheduled-messages.routes.js +47 -0
- package/dist-server/server/modules/scheduled-messages/scheduled-messages.routes.js.map +1 -0
- package/dist-server/server/modules/scheduled-messages/services/scheduled-message-dispatcher.service.js +163 -0
- package/dist-server/server/modules/scheduled-messages/services/scheduled-message-dispatcher.service.js.map +1 -0
- package/dist-server/server/modules/scheduled-messages/services/scheduled-messages.service.js +95 -0
- package/dist-server/server/modules/scheduled-messages/services/scheduled-messages.service.js.map +1 -0
- package/dist-server/server/modules/scheduled-messages/tests/scheduled-messages.test.js +255 -0
- package/dist-server/server/modules/scheduled-messages/tests/scheduled-messages.test.js.map +1 -0
- package/dist-server/server/modules/settings/index.js +3 -0
- package/dist-server/server/modules/settings/index.js.map +1 -0
- package/dist-server/server/modules/settings/settings.module.js +35 -0
- package/dist-server/server/modules/settings/settings.module.js.map +1 -0
- package/dist-server/server/modules/settings/settings.routes.js +34 -0
- package/dist-server/server/modules/settings/settings.routes.js.map +1 -0
- package/dist-server/server/modules/settings/settings.service.js +116 -0
- package/dist-server/server/modules/settings/settings.service.js.map +1 -0
- package/dist-server/server/modules/settings/tests/settings.service.test.js +48 -0
- package/dist-server/server/modules/settings/tests/settings.service.test.js.map +1 -0
- package/dist-server/server/modules/system/index.js +3 -0
- package/dist-server/server/modules/system/index.js.map +1 -0
- package/dist-server/server/modules/system/system.module.js +44 -0
- package/dist-server/server/modules/system/system.module.js.map +1 -0
- package/dist-server/server/modules/system/system.routes.js +16 -0
- package/dist-server/server/modules/system/system.routes.js.map +1 -0
- package/dist-server/server/modules/system/system.service.js +45 -0
- package/dist-server/server/modules/system/system.service.js.map +1 -0
- package/dist-server/server/modules/system/tests/system.service.test.js +100 -0
- package/dist-server/server/modules/system/tests/system.service.test.js.map +1 -0
- package/dist-server/server/modules/taskmaster/index.js +3 -0
- package/dist-server/server/modules/taskmaster/index.js.map +1 -0
- package/dist-server/server/modules/taskmaster/taskmaster.module.js +20 -0
- package/dist-server/server/modules/taskmaster/taskmaster.module.js.map +1 -0
- package/dist-server/server/modules/taskmaster/taskmaster.routes.js +1372 -0
- package/dist-server/server/modules/taskmaster/taskmaster.routes.js.map +1 -0
- package/dist-server/server/modules/taskmaster/taskmaster.service.js +107 -0
- package/dist-server/server/modules/taskmaster/taskmaster.service.js.map +1 -0
- package/dist-server/server/modules/taskmaster/tests/taskmaster.routes.test.js +180 -0
- package/dist-server/server/modules/taskmaster/tests/taskmaster.routes.test.js.map +1 -0
- package/dist-server/server/modules/taskmaster/tests/taskmaster.service.test.js +85 -0
- package/dist-server/server/modules/taskmaster/tests/taskmaster.service.test.js.map +1 -0
- package/dist-server/server/modules/user/index.js +3 -0
- package/dist-server/server/modules/user/index.js.map +1 -0
- package/dist-server/server/modules/user/tests/user.service.test.js +123 -0
- package/dist-server/server/modules/user/tests/user.service.test.js.map +1 -0
- package/dist-server/server/modules/user/user.module.js +56 -0
- package/dist-server/server/modules/user/user.module.js.map +1 -0
- package/dist-server/server/modules/user/user.routes.js +88 -0
- package/dist-server/server/modules/user/user.routes.js.map +1 -0
- package/dist-server/server/modules/user/user.service.js +134 -0
- package/dist-server/server/modules/user/user.service.js.map +1 -0
- package/dist-server/server/modules/voice/index.js +3 -0
- package/dist-server/server/modules/voice/index.js.map +1 -0
- package/dist-server/server/modules/voice/tests/voice.service.test.js +87 -0
- package/dist-server/server/modules/voice/tests/voice.service.test.js.map +1 -0
- package/dist-server/server/modules/voice/voice.module.js +44 -0
- package/dist-server/server/modules/voice/voice.module.js.map +1 -0
- package/dist-server/server/modules/voice/voice.routes.js +87 -0
- package/dist-server/server/modules/voice/voice.routes.js.map +1 -0
- package/dist-server/server/modules/voice/voice.service.js +148 -0
- package/dist-server/server/modules/voice/voice.service.js.map +1 -0
- package/dist-server/server/modules/websocket/index.js +11 -0
- package/dist-server/server/modules/websocket/index.js.map +1 -0
- package/dist-server/server/modules/websocket/services/chat-run-registry.service.js +232 -0
- package/dist-server/server/modules/websocket/services/chat-run-registry.service.js.map +1 -0
- package/dist-server/server/modules/websocket/services/chat-session-writer.service.js +135 -0
- package/dist-server/server/modules/websocket/services/chat-session-writer.service.js.map +1 -0
- package/dist-server/server/modules/websocket/services/chat-websocket.service.js +467 -0
- package/dist-server/server/modules/websocket/services/chat-websocket.service.js.map +1 -0
- package/dist-server/server/modules/websocket/services/plugin-websocket-proxy.service.js +52 -0
- package/dist-server/server/modules/websocket/services/plugin-websocket-proxy.service.js.map +1 -0
- package/dist-server/server/modules/websocket/services/session-upsert-broadcast.service.js +94 -0
- package/dist-server/server/modules/websocket/services/session-upsert-broadcast.service.js.map +1 -0
- package/dist-server/server/modules/websocket/services/shell-websocket.service.js +472 -0
- package/dist-server/server/modules/websocket/services/shell-websocket.service.js.map +1 -0
- package/dist-server/server/modules/websocket/services/websocket-auth.service.js +36 -0
- package/dist-server/server/modules/websocket/services/websocket-auth.service.js.map +1 -0
- package/dist-server/server/modules/websocket/services/websocket-server.service.js +90 -0
- package/dist-server/server/modules/websocket/services/websocket-server.service.js.map +1 -0
- package/dist-server/server/modules/websocket/services/websocket-state.service.js +14 -0
- package/dist-server/server/modules/websocket/services/websocket-state.service.js.map +1 -0
- package/dist-server/server/modules/websocket/services/websocket-writer.service.js +32 -0
- package/dist-server/server/modules/websocket/services/websocket-writer.service.js.map +1 -0
- package/dist-server/server/modules/websocket/tests/chat-attachment-filter.test.js +40 -0
- package/dist-server/server/modules/websocket/tests/chat-attachment-filter.test.js.map +1 -0
- package/dist-server/server/modules/websocket/tests/chat-edit-send.test.js +263 -0
- package/dist-server/server/modules/websocket/tests/chat-edit-send.test.js.map +1 -0
- package/dist-server/server/modules/websocket/tests/chat-run-registry.test.js +275 -0
- package/dist-server/server/modules/websocket/tests/chat-run-registry.test.js.map +1 -0
- package/dist-server/server/modules/websocket/tests/session-upsert-broadcast.test.js +106 -0
- package/dist-server/server/modules/websocket/tests/session-upsert-broadcast.test.js.map +1 -0
- package/dist-server/server/modules/websocket/tests/shell-websocket.service.test.js +177 -0
- package/dist-server/server/modules/websocket/tests/shell-websocket.service.test.js.map +1 -0
- package/dist-server/server/modules/websocket/tests/websocket-heartbeat.service.test.js +61 -0
- package/dist-server/server/modules/websocket/tests/websocket-heartbeat.service.test.js.map +1 -0
- package/dist-server/server/modules/worktrees/index.js +3 -0
- package/dist-server/server/modules/worktrees/index.js.map +1 -0
- package/dist-server/server/modules/worktrees/services/worktree-create-and-open.service.js +37 -0
- package/dist-server/server/modules/worktrees/services/worktree-create-and-open.service.js.map +1 -0
- package/dist-server/server/modules/worktrees/services/worktree-create.service.js +67 -0
- package/dist-server/server/modules/worktrees/services/worktree-create.service.js.map +1 -0
- package/dist-server/server/modules/worktrees/services/worktree-git.service.js +172 -0
- package/dist-server/server/modules/worktrees/services/worktree-git.service.js.map +1 -0
- package/dist-server/server/modules/worktrees/services/worktree-list.service.js +91 -0
- package/dist-server/server/modules/worktrees/services/worktree-list.service.js.map +1 -0
- package/dist-server/server/modules/worktrees/services/worktree-merge.service.js +118 -0
- package/dist-server/server/modules/worktrees/services/worktree-merge.service.js.map +1 -0
- package/dist-server/server/modules/worktrees/services/worktree-open.service.js +58 -0
- package/dist-server/server/modules/worktrees/services/worktree-open.service.js.map +1 -0
- package/dist-server/server/modules/worktrees/services/worktree-remove.service.js +65 -0
- package/dist-server/server/modules/worktrees/services/worktree-remove.service.js.map +1 -0
- package/dist-server/server/modules/worktrees/tests/worktree-create-and-open.service.test.js +58 -0
- package/dist-server/server/modules/worktrees/tests/worktree-create-and-open.service.test.js.map +1 -0
- package/dist-server/server/modules/worktrees/tests/worktree-create.service.test.js +85 -0
- package/dist-server/server/modules/worktrees/tests/worktree-create.service.test.js.map +1 -0
- package/dist-server/server/modules/worktrees/tests/worktree-git.service.test.js +58 -0
- package/dist-server/server/modules/worktrees/tests/worktree-git.service.test.js.map +1 -0
- package/dist-server/server/modules/worktrees/tests/worktree-list.service.test.js +34 -0
- package/dist-server/server/modules/worktrees/tests/worktree-list.service.test.js.map +1 -0
- package/dist-server/server/modules/worktrees/tests/worktree-merge.service.test.js +137 -0
- package/dist-server/server/modules/worktrees/tests/worktree-merge.service.test.js.map +1 -0
- package/dist-server/server/modules/worktrees/tests/worktree-open.service.test.js +99 -0
- package/dist-server/server/modules/worktrees/tests/worktree-open.service.test.js.map +1 -0
- package/dist-server/server/modules/worktrees/tests/worktree-remove.service.test.js +157 -0
- package/dist-server/server/modules/worktrees/tests/worktree-remove.service.test.js.map +1 -0
- package/dist-server/server/modules/worktrees/tests/worktrees.routes.test.js +167 -0
- package/dist-server/server/modules/worktrees/tests/worktrees.routes.test.js.map +1 -0
- package/dist-server/server/modules/worktrees/worktrees.module.js +97 -0
- package/dist-server/server/modules/worktrees/worktrees.module.js.map +1 -0
- package/dist-server/server/modules/worktrees/worktrees.routes.js +84 -0
- package/dist-server/server/modules/worktrees/worktrees.routes.js.map +1 -0
- package/dist-server/server/piecemaker/activation/claude-mcp.cjs +109 -0
- package/dist-server/server/piecemaker/activation/claude-mcp.cjs.map +1 -0
- package/dist-server/server/piecemaker/activation/claude-plugins.cjs +60 -0
- package/dist-server/server/piecemaker/activation/claude-plugins.cjs.map +1 -0
- package/dist-server/server/piecemaker/activation/codex-mcp.cjs +93 -0
- package/dist-server/server/piecemaker/activation/codex-mcp.cjs.map +1 -0
- package/dist-server/server/piecemaker/activation/errors.cjs +14 -0
- package/dist-server/server/piecemaker/activation/errors.cjs.map +1 -0
- package/dist-server/server/piecemaker/activation/library.cjs +281 -0
- package/dist-server/server/piecemaker/activation/library.cjs.map +1 -0
- package/dist-server/server/piecemaker/activation/library.test.js +281 -0
- package/dist-server/server/piecemaker/activation/library.test.js.map +1 -0
- package/dist-server/server/piecemaker/activation/markdown.cjs +103 -0
- package/dist-server/server/piecemaker/activation/markdown.cjs.map +1 -0
- package/dist-server/server/piecemaker/activation/paths.cjs +103 -0
- package/dist-server/server/piecemaker/activation/paths.cjs.map +1 -0
- package/dist-server/server/piecemaker/activation/paths.test.js +91 -0
- package/dist-server/server/piecemaker/activation/paths.test.js.map +1 -0
- package/dist-server/server/piecemaker/activation/routes.cjs +100 -0
- package/dist-server/server/piecemaker/activation/routes.cjs.map +1 -0
- package/dist-server/server/piecemaker/activation/routes.test.js +307 -0
- package/dist-server/server/piecemaker/activation/routes.test.js.map +1 -0
- package/dist-server/server/piecemaker/activation/snapshot.cjs +28 -0
- package/dist-server/server/piecemaker/activation/snapshot.cjs.map +1 -0
- package/dist-server/server/piecemaker/anonymizer/anonymizer.test.js +282 -0
- package/dist-server/server/piecemaker/anonymizer/anonymizer.test.js.map +1 -0
- package/dist-server/server/piecemaker/anonymizer/client-config.cjs +224 -0
- package/dist-server/server/piecemaker/anonymizer/client-config.cjs.map +1 -0
- package/dist-server/server/piecemaker/anonymizer/codex-http.test.js +19 -0
- package/dist-server/server/piecemaker/anonymizer/codex-http.test.js.map +1 -0
- package/dist-server/server/piecemaker/anonymizer/dictionary.cjs +163 -0
- package/dist-server/server/piecemaker/anonymizer/dictionary.cjs.map +1 -0
- package/dist-server/server/piecemaker/anonymizer/lifecycle.js +20 -0
- package/dist-server/server/piecemaker/anonymizer/lifecycle.js.map +1 -0
- package/dist-server/server/piecemaker/anonymizer/lifecycle.test.js +35 -0
- package/dist-server/server/piecemaker/anonymizer/lifecycle.test.js.map +1 -0
- package/dist-server/server/piecemaker/anonymizer/providers.cjs +236 -0
- package/dist-server/server/piecemaker/anonymizer/providers.cjs.map +1 -0
- package/dist-server/server/piecemaker/anonymizer/proxy.cjs +338 -0
- package/dist-server/server/piecemaker/anonymizer/proxy.cjs.map +1 -0
- package/dist-server/server/piecemaker/anonymizer/rewrite.cjs +249 -0
- package/dist-server/server/piecemaker/anonymizer/rewrite.cjs.map +1 -0
- package/dist-server/server/piecemaker/anonymizer/service.cjs +217 -0
- package/dist-server/server/piecemaker/anonymizer/service.cjs.map +1 -0
- package/dist-server/server/piecemaker/harness/chat-harness.js +157 -0
- package/dist-server/server/piecemaker/harness/chat-harness.js.map +1 -0
- package/dist-server/server/piecemaker/harness/chat-harness.test.js +120 -0
- package/dist-server/server/piecemaker/harness/chat-harness.test.js.map +1 -0
- package/dist-server/server/piecemaker/harness/citation-instructions.js +17 -0
- package/dist-server/server/piecemaker/harness/citation-instructions.js.map +1 -0
- package/dist-server/server/piecemaker/harness/citation-store.js +72 -0
- package/dist-server/server/piecemaker/harness/citation-store.js.map +1 -0
- package/dist-server/server/piecemaker/harness/citation-turn.js +211 -0
- package/dist-server/server/piecemaker/harness/citation-turn.js.map +1 -0
- package/dist-server/server/piecemaker/harness/citation-turn.test.js +144 -0
- package/dist-server/server/piecemaker/harness/citation-turn.test.js.map +1 -0
- package/dist-server/server/piecemaker/harness/citations.routes.js +16 -0
- package/dist-server/server/piecemaker/harness/citations.routes.js.map +1 -0
- package/dist-server/server/piecemaker/harness/citations.routes.test.js +68 -0
- package/dist-server/server/piecemaker/harness/citations.routes.test.js.map +1 -0
- package/dist-server/server/piecemaker/harness/decisions.cjs +298 -0
- package/dist-server/server/piecemaker/harness/decisions.cjs.map +1 -0
- package/dist-server/server/piecemaker/harness/flux-sse.cjs +108 -0
- package/dist-server/server/piecemaker/harness/flux-sse.cjs.map +1 -0
- package/dist-server/server/piecemaker/harness/harness.test.js +259 -0
- package/dist-server/server/piecemaker/harness/harness.test.js.map +1 -0
- package/dist-server/server/piecemaker/harness/index.cjs +100 -0
- package/dist-server/server/piecemaker/harness/index.cjs.map +1 -0
- package/dist-server/server/piecemaker/harness/proxy-harness.test.js +199 -0
- package/dist-server/server/piecemaker/harness/proxy-harness.test.js.map +1 -0
- package/dist-server/server/piecemaker/harness/source-passages.js +19 -0
- package/dist-server/server/piecemaker/harness/source-passages.js.map +1 -0
- package/dist-server/server/piecemaker/harness/verification.cjs +152 -0
- package/dist-server/server/piecemaker/harness/verification.cjs.map +1 -0
- package/dist-server/server/piecemaker/index.js +46 -0
- package/dist-server/server/piecemaker/index.js.map +1 -0
- package/dist-server/server/piecemaker/library/index.js +66 -0
- package/dist-server/server/piecemaker/library/index.js.map +1 -0
- package/dist-server/server/piecemaker/library/marketplace.js +204 -0
- package/dist-server/server/piecemaker/library/marketplace.js.map +1 -0
- package/dist-server/server/piecemaker/library/migrate-cli.js +13 -0
- package/dist-server/server/piecemaker/library/migrate-cli.js.map +1 -0
- package/dist-server/server/piecemaker/library/migrate.js +53 -0
- package/dist-server/server/piecemaker/library/migrate.js.map +1 -0
- package/dist-server/server/piecemaker/library/provider-skills.js +40 -0
- package/dist-server/server/piecemaker/library/provider-skills.js.map +1 -0
- package/dist-server/server/piecemaker/library/provider-skills.test.js +55 -0
- package/dist-server/server/piecemaker/library/provider-skills.test.js.map +1 -0
- package/dist-server/server/piecemaker/library/routes.js +56 -0
- package/dist-server/server/piecemaker/library/routes.js.map +1 -0
- package/dist-server/server/piecemaker/library/runtime.js +34 -0
- package/dist-server/server/piecemaker/library/runtime.js.map +1 -0
- package/dist-server/server/piecemaker/library/store.js +440 -0
- package/dist-server/server/piecemaker/library/store.js.map +1 -0
- package/dist-server/server/piecemaker/library/store.test.js +265 -0
- package/dist-server/server/piecemaker/library/store.test.js.map +1 -0
- package/dist-server/server/piecemaker/library/workspace-installation.js +70 -0
- package/dist-server/server/piecemaker/library/workspace-installation.js.map +1 -0
- package/dist-server/server/piecemaker/originals-cache.cjs +45 -0
- package/dist-server/server/piecemaker/originals-cache.cjs.map +1 -0
- package/dist-server/server/piecemaker/originals-cache.test.js +68 -0
- package/dist-server/server/piecemaker/originals-cache.test.js.map +1 -0
- package/dist-server/server/piecemaker/protection/bypass.cjs +66 -0
- package/dist-server/server/piecemaker/protection/bypass.cjs.map +1 -0
- package/dist-server/server/piecemaker/protection/bypass.test.js +117 -0
- package/dist-server/server/piecemaker/protection/bypass.test.js.map +1 -0
- package/dist-server/server/piecemaker/protection/routes.cjs +38 -0
- package/dist-server/server/piecemaker/protection/routes.cjs.map +1 -0
- package/dist-server/server/piecemaker/timesheet/index.js +9 -0
- package/dist-server/server/piecemaker/timesheet/index.js.map +1 -0
- package/dist-server/server/piecemaker/timesheet/routes.js +42 -0
- package/dist-server/server/piecemaker/timesheet/routes.js.map +1 -0
- package/dist-server/server/piecemaker/timesheet/service.js +241 -0
- package/dist-server/server/piecemaker/timesheet/service.js.map +1 -0
- package/dist-server/server/piecemaker/timesheet/service.test.js +124 -0
- package/dist-server/server/piecemaker/timesheet/service.test.js.map +1 -0
- package/dist-server/server/piecemaker/timesheet/store.js +126 -0
- package/dist-server/server/piecemaker/timesheet/store.js.map +1 -0
- package/dist-server/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/anonymization-state.cjs +147 -0
- package/dist-server/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/anonymization-state.cjs.map +1 -0
- package/dist-server/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/case-folder-structure.cjs +285 -0
- package/dist-server/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/case-folder-structure.cjs.map +1 -0
- package/dist-server/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/case-folders.cjs +87 -0
- package/dist-server/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/case-folders.cjs.map +1 -0
- package/dist-server/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/central-mapping.cjs +0 -0
- package/dist-server/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/central-mapping.cjs.map +1 -0
- package/dist-server/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/citation-sources.cjs +526 -0
- package/dist-server/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/citation-sources.cjs.map +1 -0
- package/dist-server/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/citations.cjs +304 -0
- package/dist-server/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/citations.cjs.map +1 -0
- package/dist-server/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/commits.cjs +1660 -0
- package/dist-server/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/commits.cjs.map +1 -0
- package/dist-server/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/institutional-terms.cjs +152 -0
- package/dist-server/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/institutional-terms.cjs.map +1 -0
- package/dist-server/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/legifrance-reads.cjs +84 -0
- package/dist-server/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/legifrance-reads.cjs.map +1 -0
- package/dist-server/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/mapping.cjs +332 -0
- package/dist-server/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/mapping.cjs.map +1 -0
- package/dist-server/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/mapping.test.js +64 -0
- package/dist-server/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/mapping.test.js.map +1 -0
- package/dist-server/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/protection.cjs +420 -0
- package/dist-server/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/protection.cjs.map +1 -0
- package/dist-server/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/session-timing.cjs +91 -0
- package/dist-server/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/session-timing.cjs.map +1 -0
- package/dist-server/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/substitution.cjs +304 -0
- package/dist-server/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/substitution.cjs.map +1 -0
- package/dist-server/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/verify-citations.cjs +293 -0
- package/dist-server/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/verify-citations.cjs.map +1 -0
- package/dist-server/server/piecemaker/vendor/websocket-server/case-folder-structure.cjs +5 -0
- package/dist-server/server/piecemaker/vendor/websocket-server/case-folder-structure.cjs.map +1 -0
- package/dist-server/server/piecemaker/vendor/websocket-server/legal-forms.cjs +91 -0
- package/dist-server/server/piecemaker/vendor/websocket-server/legal-forms.cjs.map +1 -0
- package/dist-server/server/piecemaker/vendor/websocket-server/legal-forms.test.js +11 -0
- package/dist-server/server/piecemaker/vendor/websocket-server/legal-forms.test.js.map +1 -0
- package/dist-server/server/piecemaker/vendor/websocket-server/originals-pipeline.cjs +955 -0
- package/dist-server/server/piecemaker/vendor/websocket-server/originals-pipeline.cjs.map +1 -0
- package/dist-server/server/shared/claude-cli-path.js +120 -0
- package/dist-server/server/shared/claude-cli-path.js.map +1 -0
- package/dist-server/server/shared/frontmatter.js +16 -0
- package/dist-server/server/shared/frontmatter.js.map +1 -0
- package/dist-server/server/shared/image-attachments.js +344 -0
- package/dist-server/server/shared/image-attachments.js.map +1 -0
- package/dist-server/server/shared/interfaces.js +2 -0
- package/dist-server/server/shared/interfaces.js.map +1 -0
- package/dist-server/server/shared/message-unification.js +376 -0
- package/dist-server/server/shared/message-unification.js.map +1 -0
- package/dist-server/server/shared/tests/application-data-root.test.js +33 -0
- package/dist-server/server/shared/tests/application-data-root.test.js.map +1 -0
- package/dist-server/server/shared/tests/claude-cli-path.test.js +89 -0
- package/dist-server/server/shared/tests/claude-cli-path.test.js.map +1 -0
- package/dist-server/server/shared/tests/image-attachments.test.js +262 -0
- package/dist-server/server/shared/tests/image-attachments.test.js.map +1 -0
- package/dist-server/server/shared/tests/message-unification.test.js +174 -0
- package/dist-server/server/shared/tests/message-unification.test.js.map +1 -0
- package/dist-server/server/shared/tests/slice-tail-page.test.js +35 -0
- package/dist-server/server/shared/tests/slice-tail-page.test.js.map +1 -0
- package/dist-server/server/shared/types.js +2 -0
- package/dist-server/server/shared/types.js.map +1 -0
- package/dist-server/server/shared/utils.js +1010 -0
- package/dist-server/server/shared/utils.js.map +1 -0
- package/dist-server/shared/networkHosts.js +20 -0
- package/dist-server/shared/networkHosts.js.map +1 -0
- package/dist-server/shared/product-config.mjs +79 -0
- package/dist-server/shared/product-config.mjs.map +1 -0
- package/package.json +271 -0
- package/product.config.json +17 -0
- package/public/api-docs.html +901 -0
- package/scripts/fix-node-pty.js +67 -0
- package/scripts/piecemaker/cli/install-command.mjs +130 -0
- package/scripts/piecemaker/cli/lib/composants.mjs +68 -0
- package/scripts/piecemaker/cli/lib/config.mjs +32 -0
- package/scripts/piecemaker/cli/lib/exec.mjs +79 -0
- package/scripts/piecemaker/cli/lib/node-runtime.mjs +76 -0
- package/scripts/piecemaker/cli/lib/ports.mjs +96 -0
- package/scripts/piecemaker/cli/lib/pwa.mjs +323 -0
- package/scripts/piecemaker/cli/lib/repos.mjs +140 -0
- package/scripts/piecemaker/cli/lib/services.mjs +71 -0
- package/scripts/piecemaker/cli/lib/ui.mjs +41 -0
- package/scripts/piecemaker/cli/piecemaker.mjs +159 -0
- package/scripts/piecemaker/cli/piecemaker.ps1 +63 -0
- package/scripts/piecemaker/cli/piecemaker.sh +60 -0
- package/scripts/piecemaker/generate-i18n-overrides.mjs +219 -0
- package/scripts/piecemaker/hooks/proxy-guard.mjs +286 -0
- package/scripts/promote-dist-server.mjs +50 -0
- package/scripts/release/build-server-bundle.js +178 -0
- package/scripts/release/package-piecemaker.sh +73 -0
- package/scripts/release/prepare-desktop-app.js +168 -0
- package/server/index.ts +410 -0
- package/server/load-env.ts +48 -0
- package/server/modules/agent/agent.module.ts +52 -0
- package/server/modules/agent/agent.routes.ts +1287 -0
- package/server/modules/agent/index.ts +2 -0
- package/server/modules/agent/tests/agent.routes.test.ts +205 -0
- package/server/modules/assets/assets.routes.ts +150 -0
- package/server/modules/assets/index.ts +3 -0
- package/server/modules/assets/services/image-assets.service.ts +127 -0
- package/server/modules/assets/tests/image-assets.service.test.ts +74 -0
- package/server/modules/auth/auth.middleware.ts +163 -0
- package/server/modules/auth/auth.module.ts +38 -0
- package/server/modules/auth/auth.routes.ts +57 -0
- package/server/modules/auth/auth.service.ts +155 -0
- package/server/modules/auth/index.ts +9 -0
- package/server/modules/auth/tests/auth.service.test.ts +95 -0
- package/server/modules/browser-use/browser-use-mcp.routes.ts +120 -0
- package/server/modules/browser-use/browser-use-mcp.ts +386 -0
- package/server/modules/browser-use/browser-use-runtime.ts +10 -0
- package/server/modules/browser-use/browser-use.routes.ts +96 -0
- package/server/modules/browser-use/browser-use.service.ts +835 -0
- package/server/modules/browser-use/index.ts +11 -0
- package/server/modules/browser-use/tests/browser-use.service.test.ts +10 -0
- package/server/modules/cli/cli.module.ts +110 -0
- package/server/modules/cli/cli.service.ts +266 -0
- package/server/modules/cli/cli.ts +19 -0
- package/server/modules/cli/index.ts +2 -0
- package/server/modules/cli/pwa-launcher.ts +131 -0
- package/server/modules/cli/sandbox.service.ts +413 -0
- package/server/modules/cli/tests/cli-environment-bootstrap.test.ts +186 -0
- package/server/modules/cli/tests/cli.service.test.ts +107 -0
- package/server/modules/cli/tests/sandbox.service.test.ts +61 -0
- package/server/modules/commands/commands.module.ts +22 -0
- package/server/modules/commands/commands.routes.ts +605 -0
- package/server/modules/commands/index.ts +2 -0
- package/server/modules/commands/tests/commands.test.ts +114 -0
- package/server/modules/database/connection.ts +144 -0
- package/server/modules/database/index.ts +27 -0
- package/server/modules/database/init-db.ts +17 -0
- package/server/modules/database/migrations.ts +552 -0
- package/server/modules/database/repositories/api-keys.ts +119 -0
- package/server/modules/database/repositories/app-config.ts +53 -0
- package/server/modules/database/repositories/credentials.ts +106 -0
- package/server/modules/database/repositories/github-tokens.ts +100 -0
- package/server/modules/database/repositories/notification-channel-endpoints.ts +153 -0
- package/server/modules/database/repositories/notification-preferences.ts +117 -0
- package/server/modules/database/repositories/projects.db.ts +196 -0
- package/server/modules/database/repositories/provider-models.ts +168 -0
- package/server/modules/database/repositories/push-subscriptions.ts +80 -0
- package/server/modules/database/repositories/scan-state.db.ts +42 -0
- package/server/modules/database/repositories/scheduled-messages.db.ts +127 -0
- package/server/modules/database/repositories/session-drafts.db.ts +171 -0
- package/server/modules/database/repositories/sessions.db.ts +714 -0
- package/server/modules/database/repositories/user-preferences.db.ts +80 -0
- package/server/modules/database/repositories/users.ts +140 -0
- package/server/modules/database/repositories/vapid-keys.ts +57 -0
- package/server/modules/database/schema.ts +306 -0
- package/server/modules/database/tests/projects.db.integration.test.ts +72 -0
- package/server/modules/database/tests/provider-models.db.integration.test.ts +125 -0
- package/server/modules/database/tests/sessions-provider-mapping.test.ts +109 -0
- package/server/modules/database/tests/sessions.db.integration.test.ts +116 -0
- package/server/modules/database/tests/user-preferences.db.integration.test.ts +135 -0
- package/server/modules/file-tree/file-tree.module.ts +116 -0
- package/server/modules/file-tree/file-tree.routes.ts +258 -0
- package/server/modules/file-tree/file-tree.service.ts +675 -0
- package/server/modules/file-tree/index.ts +2 -0
- package/server/modules/file-tree/tests/file-tree.routes.test.ts +157 -0
- package/server/modules/file-tree/tests/file-tree.service.test.ts +371 -0
- package/server/modules/git/git-branch.service.ts +43 -0
- package/server/modules/git/git-parsing.service.ts +66 -0
- package/server/modules/git/git.module.ts +22 -0
- package/server/modules/git/git.routes.ts +1600 -0
- package/server/modules/git/index.ts +2 -0
- package/server/modules/git/tests/git-branch.service.test.ts +60 -0
- package/server/modules/git/tests/git-init.routes.test.ts +135 -0
- package/server/modules/git/tests/git.test.ts +106 -0
- package/server/modules/notifications/index.ts +24 -0
- package/server/modules/notifications/notifications.routes.ts +127 -0
- package/server/modules/notifications/services/desktop-notification-clients.service.ts +124 -0
- package/server/modules/notifications/services/notification-orchestrator.service.js +310 -0
- package/server/modules/notifications/tests/notification-orchestrator.integration.test.ts +55 -0
- package/server/modules/notifications/vapid-keys.service.ts +38 -0
- package/server/modules/notifications/websocket/desktop-notifications-websocket.service.ts +109 -0
- package/server/modules/plugins/index.ts +9 -0
- package/server/modules/plugins/plugin-process.service.ts +217 -0
- package/server/modules/plugins/plugin-registry.service.ts +461 -0
- package/server/modules/plugins/plugins.module.ts +33 -0
- package/server/modules/plugins/plugins.routes.ts +66 -0
- package/server/modules/plugins/plugins.service.ts +147 -0
- package/server/modules/plugins/tests/plugins.service.test.ts +32 -0
- package/server/modules/projects/index.ts +11 -0
- package/server/modules/projects/projects.routes.ts +273 -0
- package/server/modules/projects/services/project-clone.service.ts +323 -0
- package/server/modules/projects/services/project-delete.service.ts +102 -0
- package/server/modules/projects/services/project-management.service.ts +144 -0
- package/server/modules/projects/services/project-star.service.ts +78 -0
- package/server/modules/projects/services/projects-has-taskmaster.service.ts +248 -0
- package/server/modules/projects/services/projects-with-sessions-fetch.service.ts +314 -0
- package/server/modules/projects/tests/project-clone.service.test.ts +183 -0
- package/server/modules/projects/tests/project-management.service.test.ts +117 -0
- package/server/modules/projects/tests/project-star.service.test.ts +123 -0
- package/server/modules/projects/tests/projects-has-taskmaster.service.test.ts +105 -0
- package/server/modules/providers/README.md +380 -0
- package/server/modules/providers/index.ts +14 -0
- package/server/modules/providers/list/claude/claude-auth.provider.ts +180 -0
- package/server/modules/providers/list/claude/claude-fork.provider.ts +57 -0
- package/server/modules/providers/list/claude/claude-mcp.provider.ts +135 -0
- package/server/modules/providers/list/claude/claude-models.provider.ts +314 -0
- package/server/modules/providers/list/claude/claude-runtime.provider.js +1217 -0
- package/server/modules/providers/list/claude/claude-session-synchronizer.provider.ts +203 -0
- package/server/modules/providers/list/claude/claude-sessions.provider.ts +1108 -0
- package/server/modules/providers/list/claude/claude-skills.provider.ts +265 -0
- package/server/modules/providers/list/claude/claude.provider.ts +33 -0
- package/server/modules/providers/list/codex/codex-app-server.client.ts +243 -0
- package/server/modules/providers/list/codex/codex-auth.provider.ts +100 -0
- package/server/modules/providers/list/codex/codex-fork.provider.ts +40 -0
- package/server/modules/providers/list/codex/codex-mcp.provider.ts +135 -0
- package/server/modules/providers/list/codex/codex-models.provider.ts +123 -0
- package/server/modules/providers/list/codex/codex-runtime.provider.js +572 -0
- package/server/modules/providers/list/codex/codex-session-synchronizer.provider.ts +212 -0
- package/server/modules/providers/list/codex/codex-sessions.provider.ts +2267 -0
- package/server/modules/providers/list/codex/codex-skills.provider.ts +73 -0
- package/server/modules/providers/list/codex/codex.provider.ts +33 -0
- package/server/modules/providers/list/cursor/cursor-auth.provider.ts +143 -0
- package/server/modules/providers/list/cursor/cursor-mcp.provider.ts +108 -0
- package/server/modules/providers/list/cursor/cursor-models.provider.ts +119 -0
- package/server/modules/providers/list/cursor/cursor-runtime.provider.js +387 -0
- package/server/modules/providers/list/cursor/cursor-session-synchronizer.provider.ts +158 -0
- package/server/modules/providers/list/cursor/cursor-sessions.provider.ts +654 -0
- package/server/modules/providers/list/cursor/cursor-skills.provider.ts +39 -0
- package/server/modules/providers/list/cursor/cursor.provider.ts +30 -0
- package/server/modules/providers/list/mistral/mistral-auth.provider.ts +181 -0
- package/server/modules/providers/list/mistral/mistral-mcp.provider.ts +20 -0
- package/server/modules/providers/list/mistral/mistral-models.provider.ts +76 -0
- package/server/modules/providers/list/mistral/mistral-runtime.provider.ts +417 -0
- package/server/modules/providers/list/mistral/mistral-session-synchronizer.provider.ts +15 -0
- package/server/modules/providers/list/mistral/mistral-sessions.provider.ts +210 -0
- package/server/modules/providers/list/mistral/mistral-skills.provider.ts +20 -0
- package/server/modules/providers/list/mistral/mistral.provider.ts +35 -0
- package/server/modules/providers/list/opencode/opencode-auth.provider.ts +110 -0
- package/server/modules/providers/list/opencode/opencode-mcp.provider.ts +228 -0
- package/server/modules/providers/list/opencode/opencode-models.provider.ts +304 -0
- package/server/modules/providers/list/opencode/opencode-runtime.provider.js +434 -0
- package/server/modules/providers/list/opencode/opencode-runtime.provider.test.js +261 -0
- package/server/modules/providers/list/opencode/opencode-session-synchronizer.provider.ts +179 -0
- package/server/modules/providers/list/opencode/opencode-sessions.provider.ts +500 -0
- package/server/modules/providers/list/opencode/opencode-skills.provider.ts +78 -0
- package/server/modules/providers/list/opencode/opencode.provider.ts +30 -0
- package/server/modules/providers/provider.registry.ts +38 -0
- package/server/modules/providers/provider.routes.ts +909 -0
- package/server/modules/providers/services/mcp.service.ts +109 -0
- package/server/modules/providers/services/provider-auth.service.ts +26 -0
- package/server/modules/providers/services/provider-capabilities.service.ts +133 -0
- package/server/modules/providers/services/provider-models.service.ts +401 -0
- package/server/modules/providers/services/provider-runtime.service.ts +108 -0
- package/server/modules/providers/services/provider-token-usage.service.ts +484 -0
- package/server/modules/providers/services/session-conversations-search.service.ts +1321 -0
- package/server/modules/providers/services/session-history-cache.service.ts +139 -0
- package/server/modules/providers/services/session-synchronizer.service.ts +162 -0
- package/server/modules/providers/services/sessions-watcher.service.ts +266 -0
- package/server/modules/providers/services/sessions.service.ts +663 -0
- package/server/modules/providers/services/skills.service.ts +39 -0
- package/server/modules/providers/shared/base/abstract.provider.ts +32 -0
- package/server/modules/providers/shared/mcp/mcp.provider.ts +151 -0
- package/server/modules/providers/shared/skills/skills.provider.ts +287 -0
- package/server/modules/providers/tests/claude-auth.test.ts +208 -0
- package/server/modules/providers/tests/claude-sessions.test.ts +573 -0
- package/server/modules/providers/tests/claude-subagent-echo.test.ts +25 -0
- package/server/modules/providers/tests/claude-token-budget.test.ts +104 -0
- package/server/modules/providers/tests/codex-message-editing.test.ts +419 -0
- package/server/modules/providers/tests/codex-session-fork.test.ts +59 -0
- package/server/modules/providers/tests/codex-sessions.test.ts +553 -0
- package/server/modules/providers/tests/mcp.test.ts +349 -0
- package/server/modules/providers/tests/opencode-models.test.ts +154 -0
- package/server/modules/providers/tests/opencode-sessions.test.ts +522 -0
- package/server/modules/providers/tests/provider-attachment-history.test.ts +247 -0
- package/server/modules/providers/tests/provider-models.service.test.ts +353 -0
- package/server/modules/providers/tests/provider-runtime.service.test.ts +138 -0
- package/server/modules/providers/tests/provider-token-usage.service.test.ts +373 -0
- package/server/modules/providers/tests/provider.routes.test.ts +244 -0
- package/server/modules/providers/tests/session-fork.test.ts +173 -0
- package/server/modules/providers/tests/session-history-cache.service.test.ts +143 -0
- package/server/modules/providers/tests/session-orphan-prune.test.ts +107 -0
- package/server/modules/providers/tests/session-synchronizer-coalescing.test.ts +136 -0
- package/server/modules/providers/tests/sessions-details.test.ts +73 -0
- package/server/modules/providers/tests/sessions.service.test.ts +193 -0
- package/server/modules/providers/tests/skills.test.ts +692 -0
- package/server/modules/scheduled-messages/index.ts +8 -0
- package/server/modules/scheduled-messages/scheduled-messages.routes.ts +68 -0
- package/server/modules/scheduled-messages/services/scheduled-message-dispatcher.service.ts +203 -0
- package/server/modules/scheduled-messages/services/scheduled-messages.service.ts +121 -0
- package/server/modules/scheduled-messages/tests/scheduled-messages.test.ts +310 -0
- package/server/modules/settings/index.ts +2 -0
- package/server/modules/settings/settings.module.ts +50 -0
- package/server/modules/settings/settings.routes.ts +51 -0
- package/server/modules/settings/settings.service.ts +186 -0
- package/server/modules/settings/tests/settings.service.test.ts +54 -0
- package/server/modules/system/index.ts +2 -0
- package/server/modules/system/system.module.ts +62 -0
- package/server/modules/system/system.routes.ts +21 -0
- package/server/modules/system/system.service.ts +76 -0
- package/server/modules/system/tests/system.service.test.ts +118 -0
- package/server/modules/taskmaster/index.ts +2 -0
- package/server/modules/taskmaster/taskmaster.module.ts +24 -0
- package/server/modules/taskmaster/taskmaster.routes.ts +1472 -0
- package/server/modules/taskmaster/taskmaster.service.ts +132 -0
- package/server/modules/taskmaster/tests/taskmaster.routes.test.ts +201 -0
- package/server/modules/taskmaster/tests/taskmaster.service.test.ts +97 -0
- package/server/modules/user/index.ts +2 -0
- package/server/modules/user/tests/user.service.test.ts +151 -0
- package/server/modules/user/user.module.ts +67 -0
- package/server/modules/user/user.routes.ts +94 -0
- package/server/modules/user/user.service.ts +190 -0
- package/server/modules/voice/index.ts +2 -0
- package/server/modules/voice/tests/voice.service.test.ts +99 -0
- package/server/modules/voice/voice.module.ts +47 -0
- package/server/modules/voice/voice.routes.ts +113 -0
- package/server/modules/voice/voice.service.ts +192 -0
- package/server/modules/websocket/README.md +273 -0
- package/server/modules/websocket/index.ts +11 -0
- package/server/modules/websocket/services/chat-run-registry.service.ts +310 -0
- package/server/modules/websocket/services/chat-session-writer.service.ts +175 -0
- package/server/modules/websocket/services/chat-websocket.service.ts +654 -0
- package/server/modules/websocket/services/plugin-websocket-proxy.service.ts +65 -0
- package/server/modules/websocket/services/session-upsert-broadcast.service.ts +108 -0
- package/server/modules/websocket/services/shell-websocket.service.ts +628 -0
- package/server/modules/websocket/services/websocket-auth.service.ts +59 -0
- package/server/modules/websocket/services/websocket-server.service.ts +126 -0
- package/server/modules/websocket/services/websocket-state.service.ts +16 -0
- package/server/modules/websocket/services/websocket-writer.service.ts +38 -0
- package/server/modules/websocket/tests/chat-attachment-filter.test.ts +59 -0
- package/server/modules/websocket/tests/chat-edit-send.test.ts +309 -0
- package/server/modules/websocket/tests/chat-run-registry.test.ts +313 -0
- package/server/modules/websocket/tests/session-upsert-broadcast.test.ts +138 -0
- package/server/modules/websocket/tests/shell-websocket.service.test.ts +227 -0
- package/server/modules/websocket/tests/websocket-heartbeat.service.test.ts +78 -0
- package/server/modules/worktrees/index.ts +2 -0
- package/server/modules/worktrees/services/worktree-create-and-open.service.ts +53 -0
- package/server/modules/worktrees/services/worktree-create.service.ts +104 -0
- package/server/modules/worktrees/services/worktree-git.service.ts +207 -0
- package/server/modules/worktrees/services/worktree-list.service.ts +143 -0
- package/server/modules/worktrees/services/worktree-merge.service.ts +157 -0
- package/server/modules/worktrees/services/worktree-open.service.ts +85 -0
- package/server/modules/worktrees/services/worktree-remove.service.ts +88 -0
- package/server/modules/worktrees/tests/worktree-create-and-open.service.test.ts +68 -0
- package/server/modules/worktrees/tests/worktree-create.service.test.ts +142 -0
- package/server/modules/worktrees/tests/worktree-git.service.test.ts +84 -0
- package/server/modules/worktrees/tests/worktree-list.service.test.ts +45 -0
- package/server/modules/worktrees/tests/worktree-merge.service.test.ts +213 -0
- package/server/modules/worktrees/tests/worktree-open.service.test.ts +122 -0
- package/server/modules/worktrees/tests/worktree-remove.service.test.ts +227 -0
- package/server/modules/worktrees/tests/worktrees.routes.test.ts +199 -0
- package/server/modules/worktrees/worktrees.module.ts +113 -0
- package/server/modules/worktrees/worktrees.routes.ts +116 -0
- package/server/piecemaker/activation/claude-mcp.cjs +115 -0
- package/server/piecemaker/activation/claude-plugins.cjs +65 -0
- package/server/piecemaker/activation/codex-mcp.cjs +102 -0
- package/server/piecemaker/activation/errors.cjs +15 -0
- package/server/piecemaker/activation/index.cjs +5 -0
- package/server/piecemaker/activation/library.cjs +284 -0
- package/server/piecemaker/activation/library.cjs.bak +223 -0
- package/server/piecemaker/activation/library.test.js +365 -0
- package/server/piecemaker/activation/markdown.cjs +108 -0
- package/server/piecemaker/activation/markdown.cjs.bak +78 -0
- package/server/piecemaker/activation/paths.cjs +104 -0
- package/server/piecemaker/activation/paths.test.js +158 -0
- package/server/piecemaker/activation/routes.cjs +101 -0
- package/server/piecemaker/activation/routes.test.js +344 -0
- package/server/piecemaker/activation/snapshot.cjs +30 -0
- package/server/piecemaker/anonymizer/anonymizer.test.js +320 -0
- package/server/piecemaker/anonymizer/client-config.cjs +228 -0
- package/server/piecemaker/anonymizer/codex-http.test.js +20 -0
- package/server/piecemaker/anonymizer/dictionary.cjs +174 -0
- package/server/piecemaker/anonymizer/lifecycle.test.ts +37 -0
- package/server/piecemaker/anonymizer/lifecycle.ts +22 -0
- package/server/piecemaker/anonymizer/providers.cjs +241 -0
- package/server/piecemaker/anonymizer/proxy.cjs +365 -0
- package/server/piecemaker/anonymizer/rewrite.cjs +251 -0
- package/server/piecemaker/anonymizer/routes.cjs +43 -0
- package/server/piecemaker/anonymizer/service.cjs +218 -0
- package/server/piecemaker/harness/chat-harness.test.ts +131 -0
- package/server/piecemaker/harness/chat-harness.ts +151 -0
- package/server/piecemaker/harness/citation-instructions.ts +18 -0
- package/server/piecemaker/harness/citation-store.ts +80 -0
- package/server/piecemaker/harness/citation-turn.test.ts +155 -0
- package/server/piecemaker/harness/citation-turn.ts +197 -0
- package/server/piecemaker/harness/citations.routes.test.ts +68 -0
- package/server/piecemaker/harness/citations.routes.ts +18 -0
- package/server/piecemaker/harness/decisions.cjs +302 -0
- package/server/piecemaker/harness/flux-sse.cjs +111 -0
- package/server/piecemaker/harness/harness.test.js +296 -0
- package/server/piecemaker/harness/index.cjs +103 -0
- package/server/piecemaker/harness/proxy-harness.test.js +226 -0
- package/server/piecemaker/harness/source-passages.ts +21 -0
- package/server/piecemaker/harness/verification.cjs +159 -0
- package/server/piecemaker/index.ts +59 -0
- package/server/piecemaker/library/index.ts +54 -0
- package/server/piecemaker/library/marketplace.ts +152 -0
- package/server/piecemaker/library/migrate-cli.ts +11 -0
- package/server/piecemaker/library/migrate.ts +49 -0
- package/server/piecemaker/library/policy.cjs +9 -0
- package/server/piecemaker/library/provider-skills.test.ts +61 -0
- package/server/piecemaker/library/provider-skills.ts +48 -0
- package/server/piecemaker/library/routes.ts +38 -0
- package/server/piecemaker/library/runtime.ts +39 -0
- package/server/piecemaker/library/store.test.ts +280 -0
- package/server/piecemaker/library/store.ts +429 -0
- package/server/piecemaker/library/workspace-installation.ts +61 -0
- package/server/piecemaker/originals-cache.cjs +54 -0
- package/server/piecemaker/originals-cache.test.js +77 -0
- package/server/piecemaker/protection/bypass.cjs +73 -0
- package/server/piecemaker/protection/bypass.test.js +140 -0
- package/server/piecemaker/protection/routes.cjs +39 -0
- package/server/piecemaker/router.cjs +109 -0
- package/server/piecemaker/timesheet/index.ts +9 -0
- package/server/piecemaker/timesheet/routes.ts +48 -0
- package/server/piecemaker/timesheet/service.test.ts +142 -0
- package/server/piecemaker/timesheet/service.ts +298 -0
- package/server/piecemaker/timesheet/store.ts +167 -0
- package/server/piecemaker/vendor/.env +2 -0
- package/server/piecemaker/vendor/installer/assets/PieceMakerApp.swift +189 -0
- package/server/piecemaker/vendor/installer/assets/claude-hooks/piecemaker-guard-secrets.mjs +230 -0
- package/server/piecemaker/vendor/installer/assets/claude-hooks/piecemaker-secret-paths.default.json +1 -0
- package/server/piecemaker/vendor/installer/assets/piecemaker.icns +0 -0
- package/server/piecemaker/vendor/installer/assets/piecemaker.ico +0 -0
- package/server/piecemaker/vendor/installer/bin/piecemaker.mjs +1018 -0
- package/server/piecemaker/vendor/installer/lib/banner.mjs +35 -0
- package/server/piecemaker/vendor/installer/lib/codex-skills.mjs +343 -0
- package/server/piecemaker/vendor/installer/lib/commandes.mjs +13 -0
- package/server/piecemaker/vendor/installer/lib/desktop-launcher.mjs +305 -0
- package/server/piecemaker/vendor/installer/lib/legacy-legifrance.mjs +72 -0
- package/server/piecemaker/vendor/installer/lib/platform.mjs +163 -0
- package/server/piecemaker/vendor/installer/lib/prompt.mjs +138 -0
- package/server/piecemaker/vendor/installer/lib/resume-steps.mjs +174 -0
- package/server/piecemaker/vendor/installer/lib/secrets-guard.mjs +193 -0
- package/server/piecemaker/vendor/installer/lib/service.mjs +467 -0
- package/server/piecemaker/vendor/installer/lib/state.mjs +114 -0
- package/server/piecemaker/vendor/installer/lib/ui.mjs +159 -0
- package/server/piecemaker/vendor/installer/steps/00-identite.mjs +71 -0
- package/server/piecemaker/vendor/installer/steps/01-prerequis.mjs +119 -0
- package/server/piecemaker/vendor/installer/steps/02-dependances-node.mjs +127 -0
- package/server/piecemaker/vendor/installer/steps/03-python-gliner.mjs +249 -0
- package/server/piecemaker/vendor/installer/steps/03b-python-graphify.mjs +161 -0
- package/server/piecemaker/vendor/installer/steps/04-conversion-md.mjs +147 -0
- package/server/piecemaker/vendor/installer/steps/05-certificats.mjs +171 -0
- package/server/piecemaker/vendor/installer/steps/06-hooks.mjs +266 -0
- package/server/piecemaker/vendor/installer/steps/07-legifrance.mjs +279 -0
- package/server/piecemaker/vendor/installer/steps/08-telegram.mjs +482 -0
- package/server/piecemaker/vendor/installer/steps/09-claude-assets.mjs +151 -0
- package/server/piecemaker/vendor/installer/steps/09-codex-plugin.mjs +142 -0
- package/server/piecemaker/vendor/installer/steps/10-libreoffice.mjs +130 -0
- package/server/piecemaker/vendor/installer/steps/10-pandoc.mjs +212 -0
- package/server/piecemaker/vendor/installer/steps/11-docx-cli.mjs +144 -0
- package/server/piecemaker/vendor/installer/steps/12-mcp-piecemaker.mjs +124 -0
- package/server/piecemaker/vendor/installer/steps/13-garde-secrets.mjs +136 -0
- package/server/piecemaker/vendor/installer/steps/14-mxc-sandbox.mjs +271 -0
- package/server/piecemaker/vendor/installer/steps/15-pwa-desktop.mjs +64 -0
- package/server/piecemaker/vendor/installer/templates/root-CLAUDE.md +79 -0
- package/server/piecemaker/vendor/installer/templates/workspace-CLAUDE.md +111 -0
- package/server/piecemaker/vendor/mcp/README.md +13 -0
- package/server/piecemaker/vendor/mcp/piecemaker/server.mjs +222 -0
- package/server/piecemaker/vendor/mike-defaults-fr/PROVENANCE.md +10 -0
- package/server/piecemaker/vendor/mike-defaults-fr/assistant-workflows/compare-documents/SKILL.md +51 -0
- package/server/piecemaker/vendor/mike-defaults-fr/assistant-workflows/draft-from-template/SKILL.md +27 -0
- package/server/piecemaker/vendor/mike-defaults-fr/assistant-workflows/extract-key-terms/SKILL.md +43 -0
- package/server/piecemaker/vendor/mike-defaults-fr/assistant-workflows/proofread/SKILL.md +98 -0
- package/server/piecemaker/vendor/mike-defaults-fr/catalog.json +22 -0
- package/server/piecemaker/vendor/mike-defaults-fr/tabular-review-workflows/commercial-agreement-tabular-review/SKILL.md +31 -0
- package/server/piecemaker/vendor/mike-defaults-fr/tabular-review-workflows/commercial-agreement-tabular-review/table-columns.yaml +92 -0
- package/server/piecemaker/vendor/package.json +7 -0
- package/server/piecemaker/vendor/piecemaker-plugin/README.md +115 -0
- package/server/piecemaker/vendor/piecemaker-plugin/agents/analyste-piece.md +38 -0
- package/server/piecemaker/vendor/piecemaker-plugin/agents/jurisprudence-scanner.md +50 -0
- package/server/piecemaker/vendor/piecemaker-plugin/hooks/hooks.json +84 -0
- package/server/piecemaker/vendor/piecemaker-plugin/scripts/billing-track.mjs +194 -0
- package/server/piecemaker/vendor/piecemaker-plugin/scripts/build-recherche-pdf.mjs +91 -0
- package/server/piecemaker/vendor/piecemaker-plugin/scripts/classify-ai-documents.mjs +123 -0
- package/server/piecemaker/vendor/piecemaker-plugin/scripts/commit-track.mjs +71 -0
- package/server/piecemaker/vendor/piecemaker-plugin/scripts/compile-recherche.mjs +107 -0
- package/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/anonymization-state.cjs +146 -0
- package/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/case-folder-structure.cjs +303 -0
- package/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/case-folders.cjs +86 -0
- package/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/central-mapping.cjs +0 -0
- package/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/citation-sources.cjs +519 -0
- package/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/citations.cjs +292 -0
- package/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/commits.cjs +1683 -0
- package/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/docx-text-worker.cjs +43 -0
- package/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/hook-io.mjs +249 -0
- package/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/institutional-terms.cjs +160 -0
- package/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/legifrance-reads.cjs +85 -0
- package/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/mapping.cjs +346 -0
- package/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/mapping.test.js +68 -0
- package/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/protection.cjs +419 -0
- package/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/recherche-report.cjs +177 -0
- package/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/session-timing.cjs +80 -0
- package/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/substitution.cjs +318 -0
- package/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/verify-citations.cjs +318 -0
- package/server/piecemaker/vendor/piecemaker-plugin/scripts/protect-originals.mjs +256 -0
- package/server/piecemaker/vendor/piecemaker-plugin/scripts/session-commit-worker.mjs +260 -0
- package/server/piecemaker/vendor/piecemaker-plugin/scripts/session-commit.mjs +83 -0
- package/server/piecemaker/vendor/piecemaker-plugin/scripts/statusline.mjs +179 -0
- package/server/piecemaker/vendor/piecemaker-plugin/scripts/track-legifrance-reads.mjs +46 -0
- package/server/piecemaker/vendor/piecemaker-plugin/skills/ajout-de-fonctionnalites/SKILL.md +66 -0
- package/server/piecemaker/vendor/piecemaker-plugin/skills/conversion-md/SKILL.md +60 -0
- package/server/piecemaker/vendor/piecemaker-plugin/skills/graphe-juridique/SKILL.md +105 -0
- package/server/piecemaker/vendor/piecemaker-plugin/skills/recherche-juridique/SKILL.md +234 -0
- package/server/piecemaker/vendor/piecemaker-plugin/skills/redaction-juridique/SKILL.md +96 -0
- package/server/piecemaker/vendor/piecemaker-plugin/skills/tamponnage/SKILL.md +148 -0
- package/server/piecemaker/vendor/websocket-server/admin-routes.cjs +3195 -0
- package/server/piecemaker/vendor/websocket-server/case-folder-structure.cjs +3 -0
- package/server/piecemaker/vendor/websocket-server/case-instructions.cjs +114 -0
- package/server/piecemaker/vendor/websocket-server/case-registry.cjs +93 -0
- package/server/piecemaker/vendor/websocket-server/claude-assets.cjs +338 -0
- package/server/piecemaker/vendor/websocket-server/claude-hooks.cjs +380 -0
- package/server/piecemaker/vendor/websocket-server/document-index.cjs +682 -0
- package/server/piecemaker/vendor/websocket-server/graphify-document-graph.cjs +456 -0
- package/server/piecemaker/vendor/websocket-server/legal-chronology.cjs +502 -0
- package/server/piecemaker/vendor/websocket-server/legal-forms.cjs +89 -0
- package/server/piecemaker/vendor/websocket-server/legal-forms.test.js +10 -0
- package/server/piecemaker/vendor/websocket-server/legal-graph-materializer.cjs +234 -0
- package/server/piecemaker/vendor/websocket-server/legal-graph-prompt.txt +155 -0
- package/server/piecemaker/vendor/websocket-server/legal-graph.cjs +2021 -0
- package/server/piecemaker/vendor/websocket-server/legal-identity-boundary.cjs +247 -0
- package/server/piecemaker/vendor/websocket-server/legal-party-registry.cjs +261 -0
- package/server/piecemaker/vendor/websocket-server/lib/doc-generate.cjs +229 -0
- package/server/piecemaker/vendor/websocket-server/lib/export-render.cjs +442 -0
- package/server/piecemaker/vendor/websocket-server/lib/office-to-pdf.cjs +306 -0
- package/server/piecemaker/vendor/websocket-server/lib/stamping.cjs +39 -0
- package/server/piecemaker/vendor/websocket-server/originals-pipeline.cjs +965 -0
- package/server/piecemaker/vendor/websocket-server/scripts/ADDRESS_ANONYMIZATION.md +216 -0
- package/server/piecemaker/vendor/websocket-server/scripts/DEDUPLICATION_CHANGES.md +134 -0
- package/server/piecemaker/vendor/websocket-server/scripts/GLINER2_README.md +138 -0
- package/server/piecemaker/vendor/websocket-server/scripts/__pycache__/scan_utils.cpython-312.pyc +0 -0
- package/server/piecemaker/vendor/websocket-server/scripts/convert_and_scan_pipeline.py +1984 -0
- package/server/piecemaker/vendor/websocket-server/scripts/graphify-legal-sitecustomize.py +50 -0
- package/server/piecemaker/vendor/websocket-server/scripts/mineru_improved_1.py +208 -0
- package/server/piecemaker/vendor/websocket-server/scripts/presidio-gliner/AUDIT_PERF_QUALITE.md +246 -0
- package/server/piecemaker/vendor/websocket-server/scripts/presidio-gliner/MODELE_COREML.md +62 -0
- package/server/piecemaker/vendor/websocket-server/scripts/presidio-gliner/__pycache__/coreml_runtime.cpython-312.pyc +0 -0
- package/server/piecemaker/vendor/websocket-server/scripts/presidio-gliner/__pycache__/model_config.cpython-312.pyc +0 -0
- package/server/piecemaker/vendor/websocket-server/scripts/presidio-gliner/build_coreml.py +118 -0
- package/server/piecemaker/vendor/websocket-server/scripts/presidio-gliner/coreml_runtime.py +164 -0
- package/server/piecemaker/vendor/websocket-server/scripts/presidio-gliner/eval/BACKENDS_MESURES.md +384 -0
- package/server/piecemaker/vendor/websocket-server/scripts/presidio-gliner/eval/CRITERES_QUALITE.md +81 -0
- package/server/piecemaker/vendor/websocket-server/scripts/presidio-gliner/eval/__pycache__/coreml_encoder.cpython-312.pyc +0 -0
- package/server/piecemaker/vendor/websocket-server/scripts/presidio-gliner/eval/bench_backend.py +151 -0
- package/server/piecemaker/vendor/websocket-server/scripts/presidio-gliner/eval/bench_batchsize.py +47 -0
- package/server/piecemaker/vendor/websocket-server/scripts/presidio-gliner/eval/bench_coreml_pipeline.py +138 -0
- package/server/piecemaker/vendor/websocket-server/scripts/presidio-gliner/eval/bench_gensight.py +124 -0
- package/server/piecemaker/vendor/websocket-server/scripts/presidio-gliner/eval/check_gold.py +66 -0
- package/server/piecemaker/vendor/websocket-server/scripts/presidio-gliner/eval/coreml_encoder.py +182 -0
- package/server/piecemaker/vendor/websocket-server/scripts/presidio-gliner/eval/encoder_backends.py +179 -0
- package/server/piecemaker/vendor/websocket-server/scripts/presidio-gliner/eval/evaluate.py +130 -0
- package/server/piecemaker/vendor/websocket-server/scripts/presidio-gliner/eval/gold.py +59 -0
- package/server/piecemaker/vendor/websocket-server/scripts/presidio-gliner/eval/profile_split.py +65 -0
- package/server/piecemaker/vendor/websocket-server/scripts/presidio-gliner/eval/slice_A.md +67 -0
- package/server/piecemaker/vendor/websocket-server/scripts/presidio-gliner/eval/slice_B.md +57 -0
- package/server/piecemaker/vendor/websocket-server/scripts/presidio-gliner/eval/slice_C.md +907 -0
- package/server/piecemaker/vendor/websocket-server/scripts/presidio-gliner/eval/sweep.py +115 -0
- package/server/piecemaker/vendor/websocket-server/scripts/presidio-gliner/eval/verify_substitution.cjs +116 -0
- package/server/piecemaker/vendor/websocket-server/scripts/presidio-gliner/eval/verify_whitespace.py +67 -0
- package/server/piecemaker/vendor/websocket-server/scripts/presidio-gliner/eval/verify_worker.py +106 -0
- package/server/piecemaker/vendor/websocket-server/scripts/presidio-gliner/model_config.py +28 -0
- package/server/piecemaker/vendor/websocket-server/scripts/presidio-gliner/presidio-gliner.py +471 -0
- package/server/piecemaker/vendor/websocket-server/scripts/presidio-gliner/scanner_worker.py +668 -0
- package/server/piecemaker/vendor/websocket-server/scripts/presidio_analyzer/__init__.py +10 -0
- package/server/piecemaker/vendor/websocket-server/scripts/presidio_analyzer/__pycache__/__init__.cpython-312.pyc +0 -0
- package/server/piecemaker/vendor/websocket-server/scripts/presidio_analyzer/__pycache__/analysis_explanation.cpython-312.pyc +0 -0
- package/server/piecemaker/vendor/websocket-server/scripts/presidio_analyzer/__pycache__/entity_recognizer.cpython-312.pyc +0 -0
- package/server/piecemaker/vendor/websocket-server/scripts/presidio_analyzer/__pycache__/local_recognizer.cpython-312.pyc +0 -0
- package/server/piecemaker/vendor/websocket-server/scripts/presidio_analyzer/__pycache__/pattern.cpython-312.pyc +0 -0
- package/server/piecemaker/vendor/websocket-server/scripts/presidio_analyzer/__pycache__/pattern_recognizer.cpython-312.pyc +0 -0
- package/server/piecemaker/vendor/websocket-server/scripts/presidio_analyzer/__pycache__/recognizer_result.cpython-312.pyc +0 -0
- package/server/piecemaker/vendor/websocket-server/scripts/presidio_analyzer/analysis_explanation.py +65 -0
- package/server/piecemaker/vendor/websocket-server/scripts/presidio_analyzer/entity_recognizer.py +213 -0
- package/server/piecemaker/vendor/websocket-server/scripts/presidio_analyzer/input_validation/__init__.py +21 -0
- package/server/piecemaker/vendor/websocket-server/scripts/presidio_analyzer/input_validation/language_validation.py +18 -0
- package/server/piecemaker/vendor/websocket-server/scripts/presidio_analyzer/input_validation/schemas.py +132 -0
- package/server/piecemaker/vendor/websocket-server/scripts/presidio_analyzer/input_validation/yaml_recognizer_models.py +422 -0
- package/server/piecemaker/vendor/websocket-server/scripts/presidio_analyzer/local_recognizer.py +7 -0
- package/server/piecemaker/vendor/websocket-server/scripts/presidio_analyzer/nlp_engine/__init__.py +15 -0
- package/server/piecemaker/vendor/websocket-server/scripts/presidio_analyzer/nlp_engine/__pycache__/__init__.cpython-312.pyc +0 -0
- package/server/piecemaker/vendor/websocket-server/scripts/presidio_analyzer/nlp_engine/__pycache__/device_detector.cpython-312.pyc +0 -0
- package/server/piecemaker/vendor/websocket-server/scripts/presidio_analyzer/nlp_engine/__pycache__/ner_model_configuration.cpython-312.pyc +0 -0
- package/server/piecemaker/vendor/websocket-server/scripts/presidio_analyzer/nlp_engine/__pycache__/nlp_artifacts.cpython-312.pyc +0 -0
- package/server/piecemaker/vendor/websocket-server/scripts/presidio_analyzer/nlp_engine/__pycache__/nlp_engine.cpython-312.pyc +0 -0
- package/server/piecemaker/vendor/websocket-server/scripts/presidio_analyzer/nlp_engine/__pycache__/spacy_nlp_engine.cpython-312.pyc +0 -0
- package/server/piecemaker/vendor/websocket-server/scripts/presidio_analyzer/nlp_engine/device_detector.py +65 -0
- package/server/piecemaker/vendor/websocket-server/scripts/presidio_analyzer/nlp_engine/ner_model_configuration.py +128 -0
- package/server/piecemaker/vendor/websocket-server/scripts/presidio_analyzer/nlp_engine/nlp_artifacts.py +88 -0
- package/server/piecemaker/vendor/websocket-server/scripts/presidio_analyzer/nlp_engine/nlp_engine.py +65 -0
- package/server/piecemaker/vendor/websocket-server/scripts/presidio_analyzer/nlp_engine/nlp_engine_provider.py +114 -0
- package/server/piecemaker/vendor/websocket-server/scripts/presidio_analyzer/nlp_engine/spacy_nlp_engine.py +290 -0
- package/server/piecemaker/vendor/websocket-server/scripts/presidio_analyzer/pattern.py +66 -0
- package/server/piecemaker/vendor/websocket-server/scripts/presidio_analyzer/pattern_recognizer.py +297 -0
- package/server/piecemaker/vendor/websocket-server/scripts/presidio_analyzer/predefined_recognizers/__init__.py +4 -0
- package/server/piecemaker/vendor/websocket-server/scripts/presidio_analyzer/predefined_recognizers/__pycache__/__init__.cpython-312.pyc +0 -0
- package/server/piecemaker/vendor/websocket-server/scripts/presidio_analyzer/predefined_recognizers/__pycache__/gliner_recognizer.cpython-312.pyc +0 -0
- package/server/piecemaker/vendor/websocket-server/scripts/presidio_analyzer/predefined_recognizers/generic/__init__.py +0 -0
- package/server/piecemaker/vendor/websocket-server/scripts/presidio_analyzer/predefined_recognizers/generic/__pycache__/__init__.cpython-312.pyc +0 -0
- package/server/piecemaker/vendor/websocket-server/scripts/presidio_analyzer/predefined_recognizers/generic/__pycache__/credit_card_recognizer.cpython-312.pyc +0 -0
- package/server/piecemaker/vendor/websocket-server/scripts/presidio_analyzer/predefined_recognizers/generic/__pycache__/crypto_recognizer.cpython-312.pyc +0 -0
- package/server/piecemaker/vendor/websocket-server/scripts/presidio_analyzer/predefined_recognizers/generic/__pycache__/email_recognizer.cpython-312.pyc +0 -0
- package/server/piecemaker/vendor/websocket-server/scripts/presidio_analyzer/predefined_recognizers/generic/__pycache__/iban_patterns.cpython-312.pyc +0 -0
- package/server/piecemaker/vendor/websocket-server/scripts/presidio_analyzer/predefined_recognizers/generic/__pycache__/iban_recognizer.cpython-312.pyc +0 -0
- package/server/piecemaker/vendor/websocket-server/scripts/presidio_analyzer/predefined_recognizers/generic/__pycache__/ip_recognizer.cpython-312.pyc +0 -0
- package/server/piecemaker/vendor/websocket-server/scripts/presidio_analyzer/predefined_recognizers/generic/__pycache__/mac_recognizer.cpython-312.pyc +0 -0
- package/server/piecemaker/vendor/websocket-server/scripts/presidio_analyzer/predefined_recognizers/generic/__pycache__/url_recognizer.cpython-312.pyc +0 -0
- package/server/piecemaker/vendor/websocket-server/scripts/presidio_analyzer/predefined_recognizers/generic/credit_card_recognizer.py +82 -0
- package/server/piecemaker/vendor/websocket-server/scripts/presidio_analyzer/predefined_recognizers/generic/crypto_recognizer.py +141 -0
- package/server/piecemaker/vendor/websocket-server/scripts/presidio_analyzer/predefined_recognizers/generic/date_recognizer.py +102 -0
- package/server/piecemaker/vendor/websocket-server/scripts/presidio_analyzer/predefined_recognizers/generic/email_recognizer.py +48 -0
- package/server/piecemaker/vendor/websocket-server/scripts/presidio_analyzer/predefined_recognizers/generic/iban_patterns.py +173 -0
- package/server/piecemaker/vendor/websocket-server/scripts/presidio_analyzer/predefined_recognizers/generic/iban_recognizer.py +221 -0
- package/server/piecemaker/vendor/websocket-server/scripts/presidio_analyzer/predefined_recognizers/generic/ip_recognizer.py +65 -0
- package/server/piecemaker/vendor/websocket-server/scripts/presidio_analyzer/predefined_recognizers/generic/mac_recognizer.py +73 -0
- package/server/piecemaker/vendor/websocket-server/scripts/presidio_analyzer/predefined_recognizers/generic/url_recognizer.py +50 -0
- package/server/piecemaker/vendor/websocket-server/scripts/presidio_analyzer/predefined_recognizers/gliner_recognizer.py +218 -0
- package/server/piecemaker/vendor/websocket-server/scripts/presidio_analyzer/predefined_recognizers/spacy_recognizer.py +139 -0
- package/server/piecemaker/vendor/websocket-server/scripts/presidio_analyzer/recognizer_result.py +188 -0
- package/server/piecemaker/vendor/websocket-server/scripts/requirements.txt +29 -0
- package/server/piecemaker/vendor/websocket-server/scripts/scan_utils.py +469 -0
- package/server/piecemaker/vendor/websocket-server/scripts/smart_converter.py +356 -0
- package/server/piecemaker/vendor/websocket-server/scripts/warmup.py +454 -0
- package/server/piecemaker/vendor/websocket-server/stamping-routes.cjs +334 -0
- package/server/piecemaker/vendor/websocket-server/telegram-admin.cjs +602 -0
- package/server/piecemaker/vendor/websocket-server/workspace-paths.cjs +22 -0
- package/server/shared/claude-cli-path.ts +157 -0
- package/server/shared/frontmatter.ts +18 -0
- package/server/shared/image-attachments.ts +437 -0
- package/server/shared/interfaces.ts +241 -0
- package/server/shared/message-unification.ts +449 -0
- package/server/shared/tests/application-data-root.test.ts +29 -0
- package/server/shared/tests/claude-cli-path.test.ts +118 -0
- package/server/shared/tests/image-attachments.test.ts +353 -0
- package/server/shared/tests/message-unification.test.ts +218 -0
- package/server/shared/tests/slice-tail-page.test.ts +42 -0
- package/server/shared/types.ts +1354 -0
- package/server/shared/utils.ts +1212 -0
- package/server/tsconfig.json +40 -0
- package/shared/networkHosts.js +22 -0
- package/shared/product-config.mjs +86 -0
|
@@ -0,0 +1,1984 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Convert & Scan Pipeline โ orchestrates document conversion and PII scanning.
|
|
4
|
+
|
|
5
|
+
This script chains smart_converter.py (conversion to Markdown) and
|
|
6
|
+
presidio-gliner.py (PII scanning) for batch processing of multiple documents.
|
|
7
|
+
|
|
8
|
+
Usage:
|
|
9
|
+
python3 convert_and_scan_pipeline.py <file1> [file2 ...] -o <output_dir> [options]
|
|
10
|
+
|
|
11
|
+
Options:
|
|
12
|
+
-o, --output DIR Output directory for all generated files
|
|
13
|
+
--engine ENGINE Conversion engine: auto|markitdown|mineru (default: auto)
|
|
14
|
+
--mode MODE MinerU mode: pipeline|hybrid|vlm (default: pipeline)
|
|
15
|
+
--lang CODE OCR language code for MinerU (default: auto)
|
|
16
|
+
|
|
17
|
+
Progress Format:
|
|
18
|
+
PROGRESS:CONVERT:percentage:current:total (Phase 1: Converting documents)
|
|
19
|
+
PROGRESS:SCAN:percentage:current:total (Phase 2: Scanning for PII)
|
|
20
|
+
|
|
21
|
+
Persistent Output Structure:
|
|
22
|
+
{output_dir}/
|
|
23
|
+
โโโ document1.md
|
|
24
|
+
โโโ document2.md
|
|
25
|
+
โโโ mapping_default.json
|
|
26
|
+
โโโ .piecemaker/anonymization-state.json
|
|
27
|
+
|
|
28
|
+
The per-document sensitive maps are transient scanner payloads. They are
|
|
29
|
+
created in a private temporary directory, merged into mapping_default.json,
|
|
30
|
+
then removed automatically.
|
|
31
|
+
|
|
32
|
+
Exit Codes:
|
|
33
|
+
0: Success (at least one file fully processed)
|
|
34
|
+
1: No file could be fully converted and scanned
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
import sys
|
|
38
|
+
import os
|
|
39
|
+
import time
|
|
40
|
+
import argparse
|
|
41
|
+
import subprocess
|
|
42
|
+
import json
|
|
43
|
+
import hashlib
|
|
44
|
+
import tempfile
|
|
45
|
+
import unicodedata
|
|
46
|
+
import re
|
|
47
|
+
from pathlib import Path
|
|
48
|
+
from typing import List, Optional, Tuple, Dict, Set
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def print_progress(phase: str, current: int, total: int) -> None:
|
|
52
|
+
"""Print standardized progress message.
|
|
53
|
+
|
|
54
|
+
Args:
|
|
55
|
+
phase: 'CONVERT' or 'SCAN'
|
|
56
|
+
current: Current file index (1-based)
|
|
57
|
+
total: Total number of files
|
|
58
|
+
"""
|
|
59
|
+
percentage = int((current / total) * 100)
|
|
60
|
+
print(f"PROGRESS:{phase}:{percentage}:{current}:{total}", flush=True)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def start_scanner_worker() -> Optional[subprocess.Popen]:
|
|
64
|
+
"""Start the long-lived scanner worker subprocess.
|
|
65
|
+
|
|
66
|
+
The worker loads GLiNER2 + spaCy models once and stays alive to process
|
|
67
|
+
multiple files via a JSON-line stdin/stdout protocol. Launch this at the
|
|
68
|
+
start of the pipeline so model loading overlaps with Phase 1 (conversion).
|
|
69
|
+
|
|
70
|
+
Returns:
|
|
71
|
+
Worker subprocess handle, or None if the launch failed.
|
|
72
|
+
"""
|
|
73
|
+
script_dir = Path(__file__).parent
|
|
74
|
+
worker_script = script_dir / "presidio-gliner" / "scanner_worker.py"
|
|
75
|
+
|
|
76
|
+
if not worker_script.exists():
|
|
77
|
+
print(f"โ ๏ธ scanner_worker.py not found at {worker_script}", file=sys.stderr)
|
|
78
|
+
return None
|
|
79
|
+
|
|
80
|
+
try:
|
|
81
|
+
proc = subprocess.Popen(
|
|
82
|
+
[sys.executable, str(worker_script)],
|
|
83
|
+
stdin=subprocess.PIPE,
|
|
84
|
+
stdout=subprocess.PIPE,
|
|
85
|
+
stderr=subprocess.PIPE,
|
|
86
|
+
text=True,
|
|
87
|
+
bufsize=1, # Line buffered
|
|
88
|
+
)
|
|
89
|
+
print(f"๐ Scanner worker starting in background (PID {proc.pid})...")
|
|
90
|
+
return proc
|
|
91
|
+
except Exception as e:
|
|
92
|
+
print(f"โ ๏ธ Failed to start scanner worker: {e}", file=sys.stderr)
|
|
93
|
+
return None
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def wait_for_worker_ready(proc: Optional[subprocess.Popen], timeout: int = 300) -> bool:
|
|
97
|
+
"""Wait for the scanner worker to print READY on stdout.
|
|
98
|
+
|
|
99
|
+
While waiting, streams stderr output (model loading progress) in real-time.
|
|
100
|
+
|
|
101
|
+
Args:
|
|
102
|
+
proc: Worker subprocess
|
|
103
|
+
timeout: Max seconds to wait
|
|
104
|
+
|
|
105
|
+
Returns:
|
|
106
|
+
True if worker is ready, False on failure/timeout.
|
|
107
|
+
"""
|
|
108
|
+
if proc is None:
|
|
109
|
+
return False
|
|
110
|
+
|
|
111
|
+
import re
|
|
112
|
+
import select
|
|
113
|
+
import threading
|
|
114
|
+
|
|
115
|
+
_chunk_re = re.compile(r"^PROGRESS:CHUNKS:\d+:\d+:\d+")
|
|
116
|
+
|
|
117
|
+
# Stream stderr in a background thread so we see model loading progress. The
|
|
118
|
+
# thread lives for the whole process, so it also carries the worker's per-chunk
|
|
119
|
+
# progress during a scan. A CHUNKS line is re-emitted CLEAN on stdout: the Node
|
|
120
|
+
# parent only reads stdout and requires a leading "PROGRESS:", so the "[worker]"
|
|
121
|
+
# prefix otherwise hid it and the admin bar stayed frozen on a long file. Only
|
|
122
|
+
# lines matching the strict pattern reach stdout โ never arbitrary worker stderr,
|
|
123
|
+
# which can carry document text under PIECEMAKER_DEBUG_ENTITIES.
|
|
124
|
+
def _stream_stderr():
|
|
125
|
+
try:
|
|
126
|
+
for line in proc.stderr:
|
|
127
|
+
stripped = line.strip()
|
|
128
|
+
if _chunk_re.match(stripped):
|
|
129
|
+
print(stripped, flush=True)
|
|
130
|
+
else:
|
|
131
|
+
print(f" [worker] {line}", end="", flush=True)
|
|
132
|
+
except (ValueError, OSError):
|
|
133
|
+
pass # Pipe closed
|
|
134
|
+
|
|
135
|
+
stderr_thread = threading.Thread(target=_stream_stderr, daemon=True)
|
|
136
|
+
stderr_thread.start()
|
|
137
|
+
|
|
138
|
+
start = time.time()
|
|
139
|
+
while time.time() - start < timeout:
|
|
140
|
+
if proc.poll() is not None:
|
|
141
|
+
print(f"โ ๏ธ Scanner worker exited prematurely (exit {proc.returncode})", file=sys.stderr)
|
|
142
|
+
return False
|
|
143
|
+
|
|
144
|
+
# Non-blocking read from stdout
|
|
145
|
+
try:
|
|
146
|
+
os.set_blocking(proc.stdout.fileno(), False)
|
|
147
|
+
line = proc.stdout.readline()
|
|
148
|
+
os.set_blocking(proc.stdout.fileno(), True)
|
|
149
|
+
except (IOError, OSError):
|
|
150
|
+
line = ""
|
|
151
|
+
|
|
152
|
+
if line.strip() == "READY":
|
|
153
|
+
print("โ
Scanner worker ready (models loaded)")
|
|
154
|
+
return True
|
|
155
|
+
|
|
156
|
+
if not line:
|
|
157
|
+
time.sleep(0.1)
|
|
158
|
+
|
|
159
|
+
# Timeout
|
|
160
|
+
print("โ ๏ธ Scanner worker timed out waiting for READY", file=sys.stderr)
|
|
161
|
+
proc.kill()
|
|
162
|
+
return False
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def scan_file_via_worker(proc: subprocess.Popen, md_file: str, output_dir: str) -> bool:
|
|
166
|
+
"""Send a scan command to the worker and wait for the result.
|
|
167
|
+
|
|
168
|
+
Args:
|
|
169
|
+
proc: Worker subprocess
|
|
170
|
+
md_file: Path to markdown file
|
|
171
|
+
output_dir: Directory for output JSON
|
|
172
|
+
|
|
173
|
+
Returns:
|
|
174
|
+
True if scan succeeded, False otherwise.
|
|
175
|
+
"""
|
|
176
|
+
cmd = json.dumps({"cmd": "scan", "md_file": md_file, "output_dir": output_dir})
|
|
177
|
+
try:
|
|
178
|
+
proc.stdin.write(cmd + "\n")
|
|
179
|
+
proc.stdin.flush()
|
|
180
|
+
except (BrokenPipeError, OSError) as e:
|
|
181
|
+
print(f"โ ๏ธ Failed to send command to worker: {e}", file=sys.stderr)
|
|
182
|
+
return False
|
|
183
|
+
|
|
184
|
+
# Read the JSON-line response (blocking)
|
|
185
|
+
try:
|
|
186
|
+
response_line = proc.stdout.readline()
|
|
187
|
+
except (IOError, OSError) as e:
|
|
188
|
+
print(f"โ ๏ธ Failed to read response from worker: {e}", file=sys.stderr)
|
|
189
|
+
return False
|
|
190
|
+
|
|
191
|
+
if not response_line:
|
|
192
|
+
print("โ ๏ธ Worker closed stdout unexpectedly", file=sys.stderr)
|
|
193
|
+
return False
|
|
194
|
+
|
|
195
|
+
try:
|
|
196
|
+
response = json.loads(response_line.strip())
|
|
197
|
+
except json.JSONDecodeError as e:
|
|
198
|
+
print(f"โ ๏ธ Invalid JSON from worker: {response_line.strip()}", file=sys.stderr)
|
|
199
|
+
return False
|
|
200
|
+
|
|
201
|
+
if response.get("status") == "ok":
|
|
202
|
+
return True
|
|
203
|
+
else:
|
|
204
|
+
print(f"โ ๏ธ Worker scan error: {response.get('message', 'unknown')}", file=sys.stderr)
|
|
205
|
+
return False
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def stop_scanner_worker(proc: Optional[subprocess.Popen]) -> None:
|
|
209
|
+
"""Gracefully stop the scanner worker."""
|
|
210
|
+
if proc is None or proc.poll() is not None:
|
|
211
|
+
return
|
|
212
|
+
|
|
213
|
+
try:
|
|
214
|
+
proc.stdin.write(json.dumps({"cmd": "quit"}) + "\n")
|
|
215
|
+
proc.stdin.flush()
|
|
216
|
+
proc.wait(timeout=10)
|
|
217
|
+
except Exception:
|
|
218
|
+
proc.kill()
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
def convert_file(
|
|
222
|
+
input_file: str,
|
|
223
|
+
output_dir: str,
|
|
224
|
+
engine: Optional[str] = None,
|
|
225
|
+
mode: Optional[str] = None,
|
|
226
|
+
lang: Optional[str] = None,
|
|
227
|
+
) -> Tuple[bool, Optional[str]]:
|
|
228
|
+
"""Run smart_converter.py subprocess to convert a file to Markdown with real-time output streaming.
|
|
229
|
+
|
|
230
|
+
Args:
|
|
231
|
+
input_file: Path to input document
|
|
232
|
+
output_dir: Directory for output markdown
|
|
233
|
+
engine: Conversion engine (auto/markitdown/mineru)
|
|
234
|
+
mode: MinerU mode (pipeline/hybrid/vlm)
|
|
235
|
+
lang: OCR language code
|
|
236
|
+
|
|
237
|
+
Returns:
|
|
238
|
+
(success, md_path): Tuple of success flag and path to generated .md file
|
|
239
|
+
"""
|
|
240
|
+
script_dir = Path(__file__).parent
|
|
241
|
+
converter_script = script_dir / "smart_converter.py"
|
|
242
|
+
|
|
243
|
+
if not converter_script.exists():
|
|
244
|
+
print(f"โ smart_converter.py not found at {converter_script}", file=sys.stderr)
|
|
245
|
+
return False, None
|
|
246
|
+
|
|
247
|
+
# Build command
|
|
248
|
+
cmd = [sys.executable, str(converter_script), input_file, "-o", output_dir]
|
|
249
|
+
|
|
250
|
+
if engine:
|
|
251
|
+
cmd.extend(["--engine", engine])
|
|
252
|
+
if mode:
|
|
253
|
+
cmd.extend(["--mode", mode])
|
|
254
|
+
if lang:
|
|
255
|
+
cmd.extend(["--lang", lang])
|
|
256
|
+
|
|
257
|
+
try:
|
|
258
|
+
# Run converter with real-time output streaming
|
|
259
|
+
process = subprocess.Popen(
|
|
260
|
+
cmd,
|
|
261
|
+
stdout=subprocess.PIPE,
|
|
262
|
+
stderr=subprocess.PIPE,
|
|
263
|
+
text=True,
|
|
264
|
+
bufsize=1, # Line buffered
|
|
265
|
+
)
|
|
266
|
+
|
|
267
|
+
# Stream output in real-time
|
|
268
|
+
os.set_blocking(process.stdout.fileno(), False)
|
|
269
|
+
os.set_blocking(process.stderr.fileno(), False)
|
|
270
|
+
|
|
271
|
+
return_code = None
|
|
272
|
+
while return_code is None:
|
|
273
|
+
# Check if process has finished
|
|
274
|
+
return_code = process.poll()
|
|
275
|
+
|
|
276
|
+
# Read available output without blocking
|
|
277
|
+
try:
|
|
278
|
+
# Read stdout
|
|
279
|
+
while True:
|
|
280
|
+
line = process.stdout.readline()
|
|
281
|
+
if not line:
|
|
282
|
+
break
|
|
283
|
+
print(line, end="", flush=True)
|
|
284
|
+
|
|
285
|
+
# Read stderr
|
|
286
|
+
while True:
|
|
287
|
+
line = process.stderr.readline()
|
|
288
|
+
if not line:
|
|
289
|
+
break
|
|
290
|
+
print(line, end="", file=sys.stderr, flush=True)
|
|
291
|
+
|
|
292
|
+
except (IOError, OSError):
|
|
293
|
+
pass
|
|
294
|
+
|
|
295
|
+
# Small delay to prevent CPU spinning
|
|
296
|
+
if return_code is None:
|
|
297
|
+
time.sleep(0.01)
|
|
298
|
+
|
|
299
|
+
# Ensure we read any remaining output
|
|
300
|
+
try:
|
|
301
|
+
remaining_stdout, remaining_stderr = process.communicate(timeout=1)
|
|
302
|
+
if remaining_stdout:
|
|
303
|
+
print(remaining_stdout, end="", flush=True)
|
|
304
|
+
if remaining_stderr:
|
|
305
|
+
print(remaining_stderr, end="", file=sys.stderr, flush=True)
|
|
306
|
+
except subprocess.TimeoutExpired:
|
|
307
|
+
pass
|
|
308
|
+
|
|
309
|
+
if return_code != 0:
|
|
310
|
+
print(f"โ ๏ธ Conversion failed with exit code {return_code}", file=sys.stderr)
|
|
311
|
+
return False, None
|
|
312
|
+
|
|
313
|
+
# Determine generated .md file path
|
|
314
|
+
input_stem = Path(input_file).stem
|
|
315
|
+
md_path = Path(output_dir) / f"{input_stem}.md"
|
|
316
|
+
|
|
317
|
+
if not md_path.exists():
|
|
318
|
+
print(f"โ ๏ธ Expected output file not found: {md_path}", file=sys.stderr)
|
|
319
|
+
return False, None
|
|
320
|
+
|
|
321
|
+
return True, str(md_path)
|
|
322
|
+
|
|
323
|
+
except subprocess.TimeoutExpired:
|
|
324
|
+
print(f"โ Conversion timeout (>10 minutes): {input_file}", file=sys.stderr)
|
|
325
|
+
return False, None
|
|
326
|
+
except Exception as e:
|
|
327
|
+
print(f"โ Conversion error: {e}", file=sys.stderr)
|
|
328
|
+
return False, None
|
|
329
|
+
|
|
330
|
+
|
|
331
|
+
def scan_file(md_file: str, output_dir: str) -> bool:
|
|
332
|
+
"""Run presidio-gliner.py subprocess to scan Markdown for PII with real-time output streaming.
|
|
333
|
+
|
|
334
|
+
Args:
|
|
335
|
+
md_file: Path to markdown file
|
|
336
|
+
output_dir: Directory for output JSON
|
|
337
|
+
|
|
338
|
+
Returns:
|
|
339
|
+
success: True if scan succeeded
|
|
340
|
+
"""
|
|
341
|
+
script_dir = Path(__file__).parent
|
|
342
|
+
scanner_script = script_dir / "presidio-gliner" / "presidio-gliner.py"
|
|
343
|
+
|
|
344
|
+
if not scanner_script.exists():
|
|
345
|
+
print(f"โ presidio-gliner.py not found at {scanner_script}", file=sys.stderr)
|
|
346
|
+
return False
|
|
347
|
+
|
|
348
|
+
# Build command
|
|
349
|
+
cmd = [sys.executable, str(scanner_script), md_file, "-o", output_dir]
|
|
350
|
+
|
|
351
|
+
try:
|
|
352
|
+
# Run scanner with real-time output streaming
|
|
353
|
+
process = subprocess.Popen(
|
|
354
|
+
cmd,
|
|
355
|
+
stdout=subprocess.PIPE,
|
|
356
|
+
stderr=subprocess.PIPE,
|
|
357
|
+
text=True,
|
|
358
|
+
bufsize=1, # Line buffered
|
|
359
|
+
)
|
|
360
|
+
|
|
361
|
+
# Stream output in real-time
|
|
362
|
+
os.set_blocking(process.stdout.fileno(), False)
|
|
363
|
+
os.set_blocking(process.stderr.fileno(), False)
|
|
364
|
+
|
|
365
|
+
return_code = None
|
|
366
|
+
while return_code is None:
|
|
367
|
+
# Check if process has finished
|
|
368
|
+
return_code = process.poll()
|
|
369
|
+
|
|
370
|
+
# Read available output without blocking
|
|
371
|
+
try:
|
|
372
|
+
# Read stdout
|
|
373
|
+
while True:
|
|
374
|
+
line = process.stdout.readline()
|
|
375
|
+
if not line:
|
|
376
|
+
break
|
|
377
|
+
print(line, end="", flush=True)
|
|
378
|
+
|
|
379
|
+
# Read stderr
|
|
380
|
+
while True:
|
|
381
|
+
line = process.stderr.readline()
|
|
382
|
+
if not line:
|
|
383
|
+
break
|
|
384
|
+
print(line, end="", file=sys.stderr, flush=True)
|
|
385
|
+
|
|
386
|
+
except (IOError, OSError):
|
|
387
|
+
pass
|
|
388
|
+
|
|
389
|
+
# Small delay to prevent CPU spinning
|
|
390
|
+
if return_code is None:
|
|
391
|
+
time.sleep(0.01)
|
|
392
|
+
|
|
393
|
+
# Ensure we read any remaining output
|
|
394
|
+
try:
|
|
395
|
+
remaining_stdout, remaining_stderr = process.communicate(timeout=1)
|
|
396
|
+
if remaining_stdout:
|
|
397
|
+
print(remaining_stdout, end="", flush=True)
|
|
398
|
+
if remaining_stderr:
|
|
399
|
+
print(remaining_stderr, end="", file=sys.stderr, flush=True)
|
|
400
|
+
except subprocess.TimeoutExpired:
|
|
401
|
+
pass
|
|
402
|
+
|
|
403
|
+
if return_code != 0:
|
|
404
|
+
print(f"โ ๏ธ Scan failed with exit code {return_code}", file=sys.stderr)
|
|
405
|
+
return False
|
|
406
|
+
|
|
407
|
+
# Verify JSON output was created
|
|
408
|
+
md_stem = Path(md_file).stem
|
|
409
|
+
json_path = Path(output_dir) / f"{md_stem}_sensitive_map.json"
|
|
410
|
+
|
|
411
|
+
if not json_path.exists():
|
|
412
|
+
print(f"โ ๏ธ Expected JSON output not found: {json_path}", file=sys.stderr)
|
|
413
|
+
return False
|
|
414
|
+
|
|
415
|
+
return True
|
|
416
|
+
|
|
417
|
+
except subprocess.TimeoutExpired:
|
|
418
|
+
print(f"โ Scan timeout (>10 minutes): {md_file}", file=sys.stderr)
|
|
419
|
+
return False
|
|
420
|
+
except Exception as e:
|
|
421
|
+
print(f"โ Scan error: {e}", file=sys.stderr)
|
|
422
|
+
return False
|
|
423
|
+
|
|
424
|
+
|
|
425
|
+
def load_existing_mapping(mapping_path: Path) -> Optional[Dict]:
|
|
426
|
+
"""Load the case/document mapping file if it exists.
|
|
427
|
+
|
|
428
|
+
This is the SAME file that the server uses (server.cjs line 2146),
|
|
429
|
+
ensuring single source of truth for mappings.
|
|
430
|
+
|
|
431
|
+
Args:
|
|
432
|
+
mapping_path: Full path to mapping_default.json (or an explicit target)
|
|
433
|
+
|
|
434
|
+
Returns:
|
|
435
|
+
Existing mapping data or None if file doesn't exist
|
|
436
|
+
"""
|
|
437
|
+
if not mapping_path.exists():
|
|
438
|
+
print("โน๏ธ No existing mapping found, will create new one")
|
|
439
|
+
return None
|
|
440
|
+
|
|
441
|
+
try:
|
|
442
|
+
with open(mapping_path, 'r', encoding='utf-8') as f:
|
|
443
|
+
data = json.load(f)
|
|
444
|
+
print(f"โ
Loaded existing mapping: {len(data.get('mapping', {}))} entries")
|
|
445
|
+
return data
|
|
446
|
+
except Exception as e:
|
|
447
|
+
print(f"โ ๏ธ Error loading existing mapping: {e}", file=sys.stderr)
|
|
448
|
+
return None
|
|
449
|
+
|
|
450
|
+
|
|
451
|
+
def read_individual_mappings(json_files: List[str]) -> Dict:
|
|
452
|
+
"""Read and consolidate multiple individual _sensitive_map.json files.
|
|
453
|
+
|
|
454
|
+
Args:
|
|
455
|
+
json_files: List of paths to individual sensitive map files
|
|
456
|
+
|
|
457
|
+
Returns:
|
|
458
|
+
Consolidated entities dictionary
|
|
459
|
+
"""
|
|
460
|
+
consolidated = {
|
|
461
|
+
"entities": {},
|
|
462
|
+
"summary": {
|
|
463
|
+
"total_entities_found": 0,
|
|
464
|
+
"entity_types": set(),
|
|
465
|
+
"source_files": []
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
for json_file in json_files:
|
|
470
|
+
if not os.path.exists(json_file):
|
|
471
|
+
print(f"โ ๏ธ Sensitive map not found: {json_file}", file=sys.stderr)
|
|
472
|
+
continue
|
|
473
|
+
|
|
474
|
+
with open(json_file, 'r', encoding='utf-8') as f:
|
|
475
|
+
data = json.load(f)
|
|
476
|
+
|
|
477
|
+
# Merge entities by type
|
|
478
|
+
for entity_type, entities in data.get("entities", {}).items():
|
|
479
|
+
if entity_type not in consolidated["entities"]:
|
|
480
|
+
consolidated["entities"][entity_type] = []
|
|
481
|
+
consolidated["entities"][entity_type].extend(entities)
|
|
482
|
+
|
|
483
|
+
# Update summary
|
|
484
|
+
consolidated["summary"]["source_files"].append(data.get("source_file", json_file))
|
|
485
|
+
consolidated["summary"]["entity_types"].update(data.get("summary", {}).get("entity_types", []))
|
|
486
|
+
|
|
487
|
+
# Convert set to list for JSON serialization
|
|
488
|
+
consolidated["summary"]["entity_types"] = sorted(list(consolidated["summary"]["entity_types"]))
|
|
489
|
+
consolidated["summary"]["total_entities_found"] = sum(
|
|
490
|
+
len(entities) for entities in consolidated["entities"].values()
|
|
491
|
+
)
|
|
492
|
+
|
|
493
|
+
return consolidated
|
|
494
|
+
|
|
495
|
+
|
|
496
|
+
def normalize_name(text: str) -> str:
|
|
497
|
+
"""Normalize a name for comparison purposes.
|
|
498
|
+
|
|
499
|
+
Removes diacritics, converts to lowercase, removes honorifics/titles,
|
|
500
|
+
and normalizes whitespace.
|
|
501
|
+
|
|
502
|
+
Args:
|
|
503
|
+
text: Name to normalize
|
|
504
|
+
|
|
505
|
+
Returns:
|
|
506
|
+
Normalized name string (for comparison only)
|
|
507
|
+
"""
|
|
508
|
+
# Remove diacritics (รฉ โ e, ร โ a, etc.)
|
|
509
|
+
nfkd_form = unicodedata.normalize('NFKD', text)
|
|
510
|
+
text_no_accents = ''.join([c for c in nfkd_form if not unicodedata.combining(c)])
|
|
511
|
+
|
|
512
|
+
# Convert to lowercase
|
|
513
|
+
text_lower = text_no_accents.lower()
|
|
514
|
+
|
|
515
|
+
# Remove honorifics/titles (French and English)
|
|
516
|
+
# Matches: Mr., Mrs., Ms., Dr., M., Mme., Maรฎtre, etc.
|
|
517
|
+
# Important: "m." and "me." must have the dot to avoid matching "martin" or "marie"
|
|
518
|
+
honorifics_pattern = r'\b(mr\.?|mrs\.?|ms\.?|dr\.?|prof\.?|m\.|mme\.?|mlle\.?|maitre)\s*'
|
|
519
|
+
text_no_titles = re.sub(honorifics_pattern, '', text_lower, flags=re.IGNORECASE)
|
|
520
|
+
|
|
521
|
+
# Normalize whitespace (multiple spaces/newlines โ single space)
|
|
522
|
+
text_normalized = ' '.join(text_no_titles.split())
|
|
523
|
+
|
|
524
|
+
return text_normalized.strip()
|
|
525
|
+
|
|
526
|
+
|
|
527
|
+
def are_names_similar(name1: str, norm1: str, name2: str, norm2: str) -> bool:
|
|
528
|
+
"""Check if two names refer to the same person.
|
|
529
|
+
|
|
530
|
+
Uses multiple matching strategies:
|
|
531
|
+
- Exact match after normalization
|
|
532
|
+
- Substring match (one contains the other, min 3 chars)
|
|
533
|
+
- Token-based match (all tokens of shorter name appear in longer)
|
|
534
|
+
|
|
535
|
+
Args:
|
|
536
|
+
name1: First name (original)
|
|
537
|
+
norm1: First name (normalized)
|
|
538
|
+
name2: Second name (original)
|
|
539
|
+
norm2: Second name (normalized)
|
|
540
|
+
|
|
541
|
+
Returns:
|
|
542
|
+
True if names are similar enough to be considered the same person
|
|
543
|
+
"""
|
|
544
|
+
# Exact match after normalization
|
|
545
|
+
if norm1 == norm2:
|
|
546
|
+
return True
|
|
547
|
+
|
|
548
|
+
# Substring match (one contains the other, min 3 chars)
|
|
549
|
+
min_length = min(len(norm1), len(norm2))
|
|
550
|
+
if min_length >= 3:
|
|
551
|
+
if norm1 in norm2 or norm2 in norm1:
|
|
552
|
+
return True
|
|
553
|
+
|
|
554
|
+
# Token-based match: all tokens of shorter name appear in longer
|
|
555
|
+
tokens1 = set(norm1.split())
|
|
556
|
+
tokens2 = set(norm2.split())
|
|
557
|
+
|
|
558
|
+
if not tokens1 or not tokens2:
|
|
559
|
+
return False
|
|
560
|
+
|
|
561
|
+
# Check if all tokens of shorter set appear in longer set
|
|
562
|
+
shorter_tokens = tokens1 if len(tokens1) <= len(tokens2) else tokens2
|
|
563
|
+
longer_tokens = tokens2 if len(tokens1) <= len(tokens2) else tokens1
|
|
564
|
+
|
|
565
|
+
if shorter_tokens and shorter_tokens.issubset(longer_tokens):
|
|
566
|
+
return True
|
|
567
|
+
|
|
568
|
+
return False
|
|
569
|
+
|
|
570
|
+
|
|
571
|
+
def consolidate_duplicate_entities(entities: List[Dict]) -> List[Dict]:
|
|
572
|
+
"""Consolidate duplicate entities into single entries with variants.
|
|
573
|
+
|
|
574
|
+
Groups entities by similarity (exact duplicates and variations like
|
|
575
|
+
"Mr. Gilly" vs "Bernard Gilly"), choosing the longest variant as
|
|
576
|
+
canonical and storing all unique variants.
|
|
577
|
+
|
|
578
|
+
Args:
|
|
579
|
+
entities: List of entity dictionaries with 'text', 'score', etc.
|
|
580
|
+
|
|
581
|
+
Returns:
|
|
582
|
+
Deduplicated entity list with consolidated variants
|
|
583
|
+
"""
|
|
584
|
+
if not entities:
|
|
585
|
+
return []
|
|
586
|
+
|
|
587
|
+
# Pre-compute normalized forms
|
|
588
|
+
entities_with_norm = [
|
|
589
|
+
{**entity, '_normalized': normalize_name(entity['text'])}
|
|
590
|
+
for entity in entities
|
|
591
|
+
]
|
|
592
|
+
|
|
593
|
+
# Group similar entities
|
|
594
|
+
groups = []
|
|
595
|
+
used_indices = set()
|
|
596
|
+
|
|
597
|
+
for i, entity1 in enumerate(entities_with_norm):
|
|
598
|
+
if i in used_indices:
|
|
599
|
+
continue
|
|
600
|
+
|
|
601
|
+
# Start a new group with this entity
|
|
602
|
+
group = [entity1]
|
|
603
|
+
used_indices.add(i)
|
|
604
|
+
|
|
605
|
+
# Find all similar entities
|
|
606
|
+
for j, entity2 in enumerate(entities_with_norm):
|
|
607
|
+
if j in used_indices:
|
|
608
|
+
continue
|
|
609
|
+
|
|
610
|
+
if are_names_similar(
|
|
611
|
+
entity1['text'], entity1['_normalized'],
|
|
612
|
+
entity2['text'], entity2['_normalized']
|
|
613
|
+
):
|
|
614
|
+
group.append(entity2)
|
|
615
|
+
used_indices.add(j)
|
|
616
|
+
|
|
617
|
+
groups.append(group)
|
|
618
|
+
|
|
619
|
+
# Consolidate each group
|
|
620
|
+
consolidated = []
|
|
621
|
+
|
|
622
|
+
for group in groups:
|
|
623
|
+
# Choose longest variant as canonical text
|
|
624
|
+
canonical = max(group, key=lambda e: len(e['text']))
|
|
625
|
+
|
|
626
|
+
# Get highest score
|
|
627
|
+
best_score = max(e.get('score', 0.0) for e in group)
|
|
628
|
+
|
|
629
|
+
# Collect all unique variants (remove duplicates while preserving order)
|
|
630
|
+
all_variants = [e['text'] for e in group]
|
|
631
|
+
unique_variants = list(dict.fromkeys(all_variants)) # Preserves order, removes duplicates
|
|
632
|
+
|
|
633
|
+
# Create consolidated entry
|
|
634
|
+
consolidated_entity = {
|
|
635
|
+
'text': canonical['text'],
|
|
636
|
+
'score': best_score,
|
|
637
|
+
'recognizer': canonical.get('recognizer', 'unknown'),
|
|
638
|
+
'variants': unique_variants
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
consolidated.append(consolidated_entity)
|
|
642
|
+
|
|
643
|
+
return consolidated
|
|
644
|
+
|
|
645
|
+
|
|
646
|
+
def is_known_city_or_country(text: str) -> bool:
|
|
647
|
+
"""Check if text matches known city or country names.
|
|
648
|
+
|
|
649
|
+
Args:
|
|
650
|
+
text: Text to check
|
|
651
|
+
|
|
652
|
+
Returns:
|
|
653
|
+
True if text is a known city or country name
|
|
654
|
+
"""
|
|
655
|
+
known_locations = {
|
|
656
|
+
'paris', 'lyon', 'marseille', 'toulouse', 'nice', 'nantes',
|
|
657
|
+
'bordeaux', 'lille', 'rennes', 'strasbourg', 'montpellier',
|
|
658
|
+
'reims', 'le havre', 'saint-รฉtienne', 'toulon', 'grenoble',
|
|
659
|
+
'dijon', 'angers', 'nรฎmes', 'villeurbanne', 'clermont-ferrand',
|
|
660
|
+
'aix-en-provence', 'brest', 'limoges', 'tours', 'amiens',
|
|
661
|
+
'perpignan', 'metz', 'besanรงon', 'orlรฉans', 'rouen',
|
|
662
|
+
'bruxelles', 'genรจve', 'lausanne', 'luxembourg',
|
|
663
|
+
'france', 'belgique', 'suisse', 'luxembourg'
|
|
664
|
+
}
|
|
665
|
+
return text.strip().lower() in known_locations
|
|
666
|
+
|
|
667
|
+
|
|
668
|
+
def is_city_or_country_only(text: str) -> bool:
|
|
669
|
+
"""Check if text is only a city or country name (no street number).
|
|
670
|
+
|
|
671
|
+
Args:
|
|
672
|
+
text: Address text to check
|
|
673
|
+
|
|
674
|
+
Returns:
|
|
675
|
+
True if text appears to be only a city/country (no street address)
|
|
676
|
+
"""
|
|
677
|
+
# No digits = likely just city/country
|
|
678
|
+
if not re.search(r'\d', text):
|
|
679
|
+
# Check against known cities/countries
|
|
680
|
+
return is_known_city_or_country(text)
|
|
681
|
+
return False
|
|
682
|
+
|
|
683
|
+
|
|
684
|
+
def parse_address(address_text: str) -> Dict:
|
|
685
|
+
"""Parse address into street, city, postal, country components.
|
|
686
|
+
|
|
687
|
+
Supports French and basic international patterns (Belgium, Switzerland, Luxembourg).
|
|
688
|
+
|
|
689
|
+
Args:
|
|
690
|
+
address_text: Full address string
|
|
691
|
+
|
|
692
|
+
Returns:
|
|
693
|
+
Dictionary with parsing results:
|
|
694
|
+
{
|
|
695
|
+
"success": bool, # True if parsing succeeded
|
|
696
|
+
"is_city_only": bool, # True if only city/country (no street)
|
|
697
|
+
"street_part": Optional[str], # Street to anonymize
|
|
698
|
+
"postal_code": Optional[str],
|
|
699
|
+
"city": Optional[str],
|
|
700
|
+
"country": Optional[str],
|
|
701
|
+
"anonymization_strategy": "none" | "partial" | "full"
|
|
702
|
+
}
|
|
703
|
+
"""
|
|
704
|
+
# Check if it's ONLY a city/country name (no street number)
|
|
705
|
+
if is_city_or_country_only(address_text):
|
|
706
|
+
# Extract city name (simple approach for known cities)
|
|
707
|
+
city_name = address_text.strip()
|
|
708
|
+
return {
|
|
709
|
+
"success": True,
|
|
710
|
+
"is_city_only": True,
|
|
711
|
+
"street_part": None,
|
|
712
|
+
"postal_code": None,
|
|
713
|
+
"city": city_name,
|
|
714
|
+
"country": None,
|
|
715
|
+
"anonymization_strategy": "none" # Keep visible
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
# French address pattern
|
|
719
|
+
# Pattern: [Number] [Type] [Name], [Postal] [City], [Country]
|
|
720
|
+
# Examples:
|
|
721
|
+
# "123 Rue de Rivoli, 75001 Paris, France"
|
|
722
|
+
# "11 rue Heinrich, Lyon"
|
|
723
|
+
# "20 Bis rue de la Princesse"
|
|
724
|
+
french_pattern = re.compile(
|
|
725
|
+
r'''
|
|
726
|
+
# Street number (required): 123, 20 Bis, 11 ter
|
|
727
|
+
(?P<street_number>\d+(?:\s*(?:[Bb]is|[Tt]er|[Qq]uater|[AaBb]))?)
|
|
728
|
+
\s+
|
|
729
|
+
|
|
730
|
+
# Street type: rue, avenue, boulevard, etc.
|
|
731
|
+
(?P<street_type>
|
|
732
|
+
(?:[Rr]ue|[Aa]venue|[Aa]v|[Bb]oulevard|[Bb]d|[Pp]lace|[Pp]l|
|
|
733
|
+
[Cc]hemin|[Aa]llรฉe|[Ii]mpasse|[Pp]assage|[Qq]uai|[Cc]ours|
|
|
734
|
+
[Rr]oute|[Vv]oie|[Ss]quare)
|
|
735
|
+
)
|
|
736
|
+
\s+
|
|
737
|
+
|
|
738
|
+
# Street name: everything until postal/city marker (greedy until comma or end)
|
|
739
|
+
(?P<street_name>
|
|
740
|
+
(?:[Dd]e\s+|[Ll]a\s+|[Ll]'\s*|[Dd]u\s+|[Dd]es\s+)? # Articles
|
|
741
|
+
[A-Zร-ลธa-zร -รฟ0-9\s'-]+ # Name (greedy - captures until comma or end)
|
|
742
|
+
)
|
|
743
|
+
(?=,|\s*$|\s+\d{5}) # Lookahead: stop at comma, end of string, or postal code
|
|
744
|
+
|
|
745
|
+
# Optional: Postal code + City OR just City
|
|
746
|
+
(?:
|
|
747
|
+
,?\s*
|
|
748
|
+
(?:
|
|
749
|
+
# Option 1: Postal code + City
|
|
750
|
+
(?P<postal_code>\d{5})
|
|
751
|
+
\s+
|
|
752
|
+
(?P<city>[A-Zร-ลธ][A-Zร-ลธa-zร -รฟ-]+(?:\s+[A-Zร-ลธa-zร -รฟ-]+)*)
|
|
753
|
+
|
|
|
754
|
+
# Option 2: Just City (no postal code)
|
|
755
|
+
(?P<city_only>[A-Zร-ลธ][A-Zร-ลธa-zร -รฟ-]+(?:\s+[A-Zร-ลธa-zร -รฟ-]+)*)
|
|
756
|
+
)
|
|
757
|
+
)?
|
|
758
|
+
|
|
759
|
+
# Optional: Country
|
|
760
|
+
(?:
|
|
761
|
+
,?\s*
|
|
762
|
+
(?P<country>France|Belgique|Suisse|Luxembourg)
|
|
763
|
+
)?
|
|
764
|
+
''',
|
|
765
|
+
re.VERBOSE
|
|
766
|
+
)
|
|
767
|
+
|
|
768
|
+
# Belgian pattern: "Rue de la Loi 123, 1000 Bruxelles, Belgique"
|
|
769
|
+
belgian_pattern = re.compile(
|
|
770
|
+
r'''
|
|
771
|
+
^ # Must start at beginning (prevents matching after French number)
|
|
772
|
+
# Street type + name first (Belgian style)
|
|
773
|
+
(?P<street_type>
|
|
774
|
+
(?:[Rr]ue|[Aa]venue|[Bb]oulevard|[Pp]lace|[Cc]haussรฉe)
|
|
775
|
+
)
|
|
776
|
+
\s+
|
|
777
|
+
(?P<street_name>
|
|
778
|
+
(?:[Dd]e\s+|[Ll]a\s+|[Dd]u\s+|[Dd]es\s+)?
|
|
779
|
+
[A-Zร-ลธa-zร -รฟ0-9\s'-]+ # Greedy
|
|
780
|
+
)
|
|
781
|
+
\s+
|
|
782
|
+
# Street number at the end
|
|
783
|
+
(?P<street_number>\d+(?:\s*(?:[Bb]is|[A-Za-z])?)?)
|
|
784
|
+
(?=,|\s*$) # Lookahead: stop at comma or end
|
|
785
|
+
|
|
786
|
+
# Optional: Postal code + City
|
|
787
|
+
(?:
|
|
788
|
+
,?\s*
|
|
789
|
+
(?P<postal_code>\d{4})
|
|
790
|
+
\s+
|
|
791
|
+
(?P<city>[A-Zร-ลธ][A-Zร-ลธa-zร -รฟ-]+(?:\s+[A-Zร-ลธa-zร -รฟ-]+)*)
|
|
792
|
+
)?
|
|
793
|
+
|
|
794
|
+
# Optional: Country
|
|
795
|
+
(?:
|
|
796
|
+
,?\s*
|
|
797
|
+
(?P<country>Belgique|Belgium)
|
|
798
|
+
)?
|
|
799
|
+
''',
|
|
800
|
+
re.VERBOSE
|
|
801
|
+
)
|
|
802
|
+
|
|
803
|
+
# Swiss pattern: "Rue du Rhรดne 50, 1204 Genรจve, Suisse"
|
|
804
|
+
swiss_pattern = re.compile(
|
|
805
|
+
r'''
|
|
806
|
+
^ # Must start at beginning
|
|
807
|
+
# Street type + name first
|
|
808
|
+
(?P<street_type>
|
|
809
|
+
(?:[Rr]ue|[Aa]venue|[Cc]hemin|[Pp]lace)
|
|
810
|
+
)
|
|
811
|
+
\s+
|
|
812
|
+
(?P<street_name>
|
|
813
|
+
(?:[Dd]e\s+|[Ll]a\s+|[Dd]u\s+|[Dd]es\s+)?
|
|
814
|
+
[A-Zร-ลธa-zร -รฟ0-9\s'-]+ # Greedy
|
|
815
|
+
)
|
|
816
|
+
\s+
|
|
817
|
+
# Street number
|
|
818
|
+
(?P<street_number>\d+(?:\s*[A-Za-z])?)
|
|
819
|
+
(?=,|\s*$) # Lookahead: stop at comma or end
|
|
820
|
+
|
|
821
|
+
# Optional: Postal code + City
|
|
822
|
+
(?:
|
|
823
|
+
,?\s*
|
|
824
|
+
(?P<postal_code>\d{4})
|
|
825
|
+
\s+
|
|
826
|
+
(?P<city>[A-Zร-ลธ][A-Zร-ลธa-zร -รฟ-]+(?:\s+[A-Zร-ลธa-zร -รฟ-]+)*)
|
|
827
|
+
)?
|
|
828
|
+
|
|
829
|
+
# Optional: Country
|
|
830
|
+
(?:
|
|
831
|
+
,?\s*
|
|
832
|
+
(?P<country>Suisse|Switzerland)
|
|
833
|
+
)?
|
|
834
|
+
''',
|
|
835
|
+
re.VERBOSE
|
|
836
|
+
)
|
|
837
|
+
|
|
838
|
+
# Luxembourg pattern: "Avenue de la Libertรฉ 10, L-1931 Luxembourg"
|
|
839
|
+
luxembourg_pattern = re.compile(
|
|
840
|
+
r'''
|
|
841
|
+
^ # Must start at beginning
|
|
842
|
+
# Street type + name first
|
|
843
|
+
(?P<street_type>
|
|
844
|
+
(?:[Aa]venue|[Rr]ue|[Bb]oulevard|[Pp]lace)
|
|
845
|
+
)
|
|
846
|
+
\s+
|
|
847
|
+
(?P<street_name>
|
|
848
|
+
(?:[Dd]e\s+|[Ll]a\s+|[Dd]u\s+|[Dd]es\s+)?
|
|
849
|
+
[A-Zร-ลธa-zร -รฟ0-9\s'-]+ # Greedy
|
|
850
|
+
)
|
|
851
|
+
\s+
|
|
852
|
+
# Street number
|
|
853
|
+
(?P<street_number>\d+(?:\s*[A-Za-z])?)
|
|
854
|
+
(?=,|\s*$) # Lookahead: stop at comma or end
|
|
855
|
+
|
|
856
|
+
# Optional: Postal code + City
|
|
857
|
+
(?:
|
|
858
|
+
,?\s*
|
|
859
|
+
(?P<postal_code>L-\d{4})
|
|
860
|
+
\s+
|
|
861
|
+
(?P<city>[A-Zร-ลธ][A-Zร-ลธa-zร -รฟ-]+(?:\s+[A-Zร-ลธa-zร -รฟ-]+)*)
|
|
862
|
+
)?
|
|
863
|
+
|
|
864
|
+
# Optional: Country
|
|
865
|
+
(?:
|
|
866
|
+
,?\s*
|
|
867
|
+
(?P<country>Luxembourg)
|
|
868
|
+
)?
|
|
869
|
+
''',
|
|
870
|
+
re.VERBOSE
|
|
871
|
+
)
|
|
872
|
+
|
|
873
|
+
# Smart pattern ordering based on distinguishing features
|
|
874
|
+
# This prevents Belgian pattern from matching Swiss/Luxembourg addresses
|
|
875
|
+
pattern_configs = []
|
|
876
|
+
|
|
877
|
+
# Check for distinguishing features to prioritize patterns
|
|
878
|
+
if 'L-' in address_text and re.search(r'L-\d{4}', address_text):
|
|
879
|
+
# Luxembourg postal code detected
|
|
880
|
+
pattern_configs = [
|
|
881
|
+
(luxembourg_pattern, 'luxembourg'),
|
|
882
|
+
(belgian_pattern, 'belgian'),
|
|
883
|
+
(swiss_pattern, 'swiss'),
|
|
884
|
+
(french_pattern, 'french')
|
|
885
|
+
]
|
|
886
|
+
elif 'Suisse' in address_text or 'Switzerland' in address_text:
|
|
887
|
+
# Swiss country detected
|
|
888
|
+
pattern_configs = [
|
|
889
|
+
(swiss_pattern, 'swiss'),
|
|
890
|
+
(belgian_pattern, 'belgian'),
|
|
891
|
+
(luxembourg_pattern, 'luxembourg'),
|
|
892
|
+
(french_pattern, 'french')
|
|
893
|
+
]
|
|
894
|
+
elif 'Belgique' in address_text or 'Belgium' in address_text:
|
|
895
|
+
# Belgian country detected
|
|
896
|
+
pattern_configs = [
|
|
897
|
+
(belgian_pattern, 'belgian'),
|
|
898
|
+
(swiss_pattern, 'swiss'),
|
|
899
|
+
(luxembourg_pattern, 'luxembourg'),
|
|
900
|
+
(french_pattern, 'french')
|
|
901
|
+
]
|
|
902
|
+
else:
|
|
903
|
+
# Default order: Belgian/Swiss/Luxembourg first (number at end), then French (number first)
|
|
904
|
+
pattern_configs = [
|
|
905
|
+
(belgian_pattern, 'belgian'),
|
|
906
|
+
(swiss_pattern, 'swiss'),
|
|
907
|
+
(luxembourg_pattern, 'luxembourg'),
|
|
908
|
+
(french_pattern, 'french')
|
|
909
|
+
]
|
|
910
|
+
|
|
911
|
+
for pattern, pattern_type in pattern_configs:
|
|
912
|
+
match = pattern.search(address_text)
|
|
913
|
+
if match and match.group('street_number'):
|
|
914
|
+
# SUCCESS: Partial anonymization
|
|
915
|
+
groups = match.groupdict()
|
|
916
|
+
|
|
917
|
+
# Build street part (what gets anonymized)
|
|
918
|
+
# Belgian/Swiss/Luxembourg: street_type + street_name + number
|
|
919
|
+
# French: number + street_type + street_name
|
|
920
|
+
if pattern_type in ('belgian', 'swiss', 'luxembourg'):
|
|
921
|
+
# Number at the end
|
|
922
|
+
street_components = []
|
|
923
|
+
if groups.get('street_type'):
|
|
924
|
+
street_components.append(groups['street_type'])
|
|
925
|
+
if groups.get('street_name'):
|
|
926
|
+
street_components.append(groups['street_name'].strip())
|
|
927
|
+
if groups.get('street_number'):
|
|
928
|
+
street_components.append(groups['street_number'])
|
|
929
|
+
else:
|
|
930
|
+
# French: Number at the beginning
|
|
931
|
+
street_components = []
|
|
932
|
+
if groups.get('street_number'):
|
|
933
|
+
street_components.append(groups['street_number'])
|
|
934
|
+
if groups.get('street_type'):
|
|
935
|
+
street_components.append(groups['street_type'])
|
|
936
|
+
if groups.get('street_name'):
|
|
937
|
+
street_components.append(groups['street_name'].strip())
|
|
938
|
+
|
|
939
|
+
street_part = ' '.join(street_components)
|
|
940
|
+
|
|
941
|
+
# Handle French pattern's city_only capture group
|
|
942
|
+
city = groups.get('city') or groups.get('city_only')
|
|
943
|
+
|
|
944
|
+
return {
|
|
945
|
+
"success": True,
|
|
946
|
+
"is_city_only": False,
|
|
947
|
+
"street_part": street_part,
|
|
948
|
+
"postal_code": groups.get('postal_code'),
|
|
949
|
+
"city": city,
|
|
950
|
+
"country": groups.get('country'),
|
|
951
|
+
"anonymization_strategy": "partial"
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
# FAILURE: Full anonymization (safest - prevents accidental exposure)
|
|
955
|
+
return {
|
|
956
|
+
"success": False,
|
|
957
|
+
"is_city_only": False,
|
|
958
|
+
"street_part": address_text,
|
|
959
|
+
"postal_code": None,
|
|
960
|
+
"city": None,
|
|
961
|
+
"country": None,
|
|
962
|
+
"anonymization_strategy": "full"
|
|
963
|
+
}
|
|
964
|
+
|
|
965
|
+
|
|
966
|
+
# ---------------------------------------------------------------------------
|
|
967
|
+
# Sociรฉtรฉ codes : le sigle sert de prรฉfixe (SA_1, SARL_1, SCI_1, GMBH_1, LLC_1โฆ)
|
|
968
|
+
# ---------------------------------------------------------------------------
|
|
969
|
+
# Une organisation dont le nom (ou son contexte immรฉdiat) porte une forme juridique
|
|
970
|
+
# โ dรฉtectรฉe littรฉralement par scan_utils.extract_legal_form, qui a typรฉ l'entitรฉ
|
|
971
|
+
# ORGANIZATION_<sigle> โ est codรฉe <sigle>_<n>. Sans sigle, elle tombe sur
|
|
972
|
+
# PERS_MORALE_<n>. Chaque sigle a son propre compteur (SA_1, SA_2, puis SARL_1โฆ),
|
|
973
|
+
# non paddรฉ, conformรฉment au choix du cabinet. Les personnes/adresses gardent leur
|
|
974
|
+
# padding _01. Ce vocabulaire est reflรฉtรฉ ร l'identique cรดtรฉ administration
|
|
975
|
+
# (`originals-pipeline.cjs`) : les deux chemins รฉcrivent le mรชme mapping.
|
|
976
|
+
|
|
977
|
+
def _societe_form_key(entity_type: str) -> str:
|
|
978
|
+
"""Clรฉ de compteur / prรฉfixe de code d'une organisation.
|
|
979
|
+
|
|
980
|
+
ORGANIZATION_SA โ 'SA' (code SA_1) ; ORGANIZATION nu โ 'PERS_MORALE'.
|
|
981
|
+
"""
|
|
982
|
+
if entity_type.startswith("ORGANIZATION_"):
|
|
983
|
+
return entity_type.split("_", 1)[1] or "PERS_MORALE"
|
|
984
|
+
return "PERS_MORALE"
|
|
985
|
+
|
|
986
|
+
|
|
987
|
+
def _societe_counter_key_of_code(code: str) -> str:
|
|
988
|
+
"""Clรฉ de compteur d'un code sociรฉtรฉ dรฉjร attribuรฉ (legacy compris).
|
|
989
|
+
|
|
990
|
+
SA_3 โ 'SA' ; URGOT SA โ 'SA' ; role-prรฉfixรฉ CLIENT_DEMANDEUR_SA_1 โ 'SA' ;
|
|
991
|
+
PERS_MORALE_2 / PERSONNE_MORALE_01 โ 'PERS_MORALE'. On lit le dernier segment
|
|
992
|
+
avant le numรฉro, sauf prรฉsence de MORALE (repli sans sigle).
|
|
993
|
+
"""
|
|
994
|
+
body = re.sub(r"_\d+$", "", str(code)).upper()
|
|
995
|
+
tokens = [t for t in body.split("_") if t]
|
|
996
|
+
if "MORALE" in tokens:
|
|
997
|
+
return "PERS_MORALE"
|
|
998
|
+
return tokens[-1] if tokens else "PERS_MORALE"
|
|
999
|
+
|
|
1000
|
+
|
|
1001
|
+
def _next_societe_code(entity_type: str, societe_counters: Dict[str, int]) -> str:
|
|
1002
|
+
"""Prochain code sociรฉtรฉ pour *entity_type*, en incrรฉmentant son compteur propre."""
|
|
1003
|
+
key = _societe_form_key(entity_type)
|
|
1004
|
+
n = societe_counters.get(key, 0) + 1
|
|
1005
|
+
societe_counters[key] = n
|
|
1006
|
+
return f"{key}_{n}"
|
|
1007
|
+
|
|
1008
|
+
|
|
1009
|
+
def convert_to_anonymization_format(consolidated_entities: Dict) -> Dict:
|
|
1010
|
+
"""Convert consolidated entities to anonymization mapping format.
|
|
1011
|
+
|
|
1012
|
+
Expected output format matches what showMappingValidation() expects:
|
|
1013
|
+
{
|
|
1014
|
+
mapping: { "original_text": "CODE_01", ... },
|
|
1015
|
+
reverse_mapping: { "CODE_01": ["original_text"], ... },
|
|
1016
|
+
extracted_data: {
|
|
1017
|
+
personnes_physiques: { "CODE_01": {...}, ... },
|
|
1018
|
+
societes: { "CODE_02": {...}, ... },
|
|
1019
|
+
adresses: { "CODE_03": {...}, ... },
|
|
1020
|
+
siren: { "CODE_04": {...}, ... }
|
|
1021
|
+
}
|
|
1022
|
+
}
|
|
1023
|
+
"""
|
|
1024
|
+
mapping = {}
|
|
1025
|
+
reverse_mapping = {}
|
|
1026
|
+
# Texts deliberately left readable (city-only locations, the non-street part
|
|
1027
|
+
# of a partially anonymised address). They go to `ignored` so that the admin
|
|
1028
|
+
# rebuild (originals-pipeline.cjs) does not code them back from the raw scan.
|
|
1029
|
+
left_visible: List[str] = []
|
|
1030
|
+
extracted_data = {
|
|
1031
|
+
"personnes_physiques": {},
|
|
1032
|
+
"societes": {},
|
|
1033
|
+
"adresses": {},
|
|
1034
|
+
"siren": {},
|
|
1035
|
+
"autres": {} # For emails, phones, etc.
|
|
1036
|
+
}
|
|
1037
|
+
|
|
1038
|
+
# Entity type to category mapping
|
|
1039
|
+
# ORGANIZATION_* variants (e.g. ORGANIZATION_SA, ORGANIZATION_GMBH) are handled
|
|
1040
|
+
# by the startswith fallback below โ no need to enumerate every legal form here.
|
|
1041
|
+
category_map = {
|
|
1042
|
+
"PERSON": "personnes_physiques",
|
|
1043
|
+
"ORGANIZATION": "societes",
|
|
1044
|
+
"LOCATION": "adresses",
|
|
1045
|
+
"EMAIL": "autres",
|
|
1046
|
+
"PHONE": "autres",
|
|
1047
|
+
"CREDIT_CARD": "autres",
|
|
1048
|
+
"IBAN": "autres",
|
|
1049
|
+
"IP_ADDRESS": "autres",
|
|
1050
|
+
"URL": "autres",
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1053
|
+
# Code counters per category
|
|
1054
|
+
counters = {
|
|
1055
|
+
"personnes_physiques": 1,
|
|
1056
|
+
"societes": 1,
|
|
1057
|
+
"adresses": 1,
|
|
1058
|
+
"siren": 1,
|
|
1059
|
+
"autres": 1
|
|
1060
|
+
}
|
|
1061
|
+
# Sociรฉtรฉs : un compteur par sigle (SA, SARL, GMBHโฆ) et un pour PERS_MORALE,
|
|
1062
|
+
# sรฉparรฉ de `counters["societes"]` qui n'est plus utilisรฉ pour les coder.
|
|
1063
|
+
societe_counters: Dict[str, int] = {}
|
|
1064
|
+
|
|
1065
|
+
# Track seen entities to avoid duplicates across files
|
|
1066
|
+
seen_entities = {} # text -> code
|
|
1067
|
+
|
|
1068
|
+
# Consolidate PERSON entities to remove duplicates
|
|
1069
|
+
for entity_type in consolidated_entities.get("entities", {}).keys():
|
|
1070
|
+
if entity_type == "PERSON":
|
|
1071
|
+
consolidated_entities["entities"][entity_type] = consolidate_duplicate_entities(
|
|
1072
|
+
consolidated_entities["entities"][entity_type]
|
|
1073
|
+
)
|
|
1074
|
+
|
|
1075
|
+
for entity_type, entities in consolidated_entities.get("entities", {}).items():
|
|
1076
|
+
# Scalable fallback: ORGANIZATION_SA, ORGANIZATION_GMBH, etc. โ societes
|
|
1077
|
+
category = category_map.get(entity_type) or (
|
|
1078
|
+
"societes" if entity_type.startswith("ORGANIZATION_") else "autres"
|
|
1079
|
+
)
|
|
1080
|
+
|
|
1081
|
+
for entity in entities:
|
|
1082
|
+
text = entity["text"]
|
|
1083
|
+
text_lower = text.lower()
|
|
1084
|
+
|
|
1085
|
+
# Skip if already processed
|
|
1086
|
+
if text_lower in seen_entities:
|
|
1087
|
+
mapping[text] = seen_entities[text_lower]
|
|
1088
|
+
continue
|
|
1089
|
+
|
|
1090
|
+
# Generate code based on category
|
|
1091
|
+
if category == "personnes_physiques":
|
|
1092
|
+
code = f"PERSONNE_PHYSIQUE_{counters[category]:02d}"
|
|
1093
|
+
counters[category] += 1
|
|
1094
|
+
elif category == "societes":
|
|
1095
|
+
# Sigle en prรฉfixe (SA_1, SARL_1โฆ) ou PERS_MORALE_1, compteur par sigle.
|
|
1096
|
+
code = _next_societe_code(entity_type, societe_counters)
|
|
1097
|
+
elif category == "adresses":
|
|
1098
|
+
code = f"ADRESSE_{counters[category]:02d}"
|
|
1099
|
+
counters[category] += 1
|
|
1100
|
+
elif category == "siren":
|
|
1101
|
+
code = f"SIREN_{counters[category]:02d}"
|
|
1102
|
+
counters[category] += 1
|
|
1103
|
+
else:
|
|
1104
|
+
code = f"{entity_type}_{counters[category]:02d}"
|
|
1105
|
+
counters[category] += 1
|
|
1106
|
+
|
|
1107
|
+
# Get variants from entity (pre-consolidated for PERSON entities)
|
|
1108
|
+
variants = entity.get("variants", [text])
|
|
1109
|
+
|
|
1110
|
+
# Store mappings for all variants (no lowercase duplicates)
|
|
1111
|
+
for variant in variants:
|
|
1112
|
+
mapping[variant] = code
|
|
1113
|
+
|
|
1114
|
+
reverse_mapping[code] = [text] # Principal value (array for consistency)
|
|
1115
|
+
seen_entities[text_lower] = code
|
|
1116
|
+
|
|
1117
|
+
# Store in extracted_data
|
|
1118
|
+
extracted_data[category][code] = {
|
|
1119
|
+
"original": text,
|
|
1120
|
+
"code": code,
|
|
1121
|
+
"variants": variants,
|
|
1122
|
+
"score": entity.get("score", 1.0),
|
|
1123
|
+
"recognizer": entity.get("recognizer", "unknown")
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1126
|
+
# Parse addresses for partial anonymization
|
|
1127
|
+
if category == "adresses":
|
|
1128
|
+
parsed_result = parse_address(text)
|
|
1129
|
+
extracted_data[category][code]["parsed"] = parsed_result
|
|
1130
|
+
|
|
1131
|
+
# If city-only, don't add to mapping (no anonymization needed)
|
|
1132
|
+
if parsed_result.get("is_city_only"):
|
|
1133
|
+
# Remove from mapping - city names stay visible
|
|
1134
|
+
for variant in variants:
|
|
1135
|
+
mapping.pop(variant, None)
|
|
1136
|
+
left_visible.extend(variants)
|
|
1137
|
+
reverse_mapping.pop(code, None)
|
|
1138
|
+
del extracted_data[category][code]
|
|
1139
|
+
counters[category] -= 1 # Revert counter
|
|
1140
|
+
elif parsed_result.get("anonymization_strategy") == "partial":
|
|
1141
|
+
# Update mapping to only anonymize street part
|
|
1142
|
+
# Remove full address from mapping
|
|
1143
|
+
for variant in variants:
|
|
1144
|
+
mapping.pop(variant, None)
|
|
1145
|
+
# Add only street part to mapping
|
|
1146
|
+
street_part = parsed_result.get("street_part")
|
|
1147
|
+
if street_part:
|
|
1148
|
+
mapping[street_part] = code
|
|
1149
|
+
# Update variants to only include street part
|
|
1150
|
+
extracted_data[category][code]["variants"] = [street_part]
|
|
1151
|
+
reverse_mapping[code] = [street_part]
|
|
1152
|
+
left_visible.extend(v for v in variants if v != street_part)
|
|
1153
|
+
|
|
1154
|
+
return {
|
|
1155
|
+
"mapping": mapping,
|
|
1156
|
+
"reverse_mapping": reverse_mapping,
|
|
1157
|
+
"extracted_data": extracted_data,
|
|
1158
|
+
**({"ignored": list(dict.fromkeys(left_visible))} if left_visible else {}),
|
|
1159
|
+
}
|
|
1160
|
+
|
|
1161
|
+
|
|
1162
|
+
def merge_with_existing_mapping(new_mapping: Dict, existing_mapping: Optional[Dict]) -> Dict:
|
|
1163
|
+
"""Merge new mapping with existing mapping, avoiding duplicates.
|
|
1164
|
+
|
|
1165
|
+
Args:
|
|
1166
|
+
new_mapping: Newly generated mapping from current batch
|
|
1167
|
+
existing_mapping: Existing mapping data (or None)
|
|
1168
|
+
|
|
1169
|
+
Returns:
|
|
1170
|
+
Merged mapping data
|
|
1171
|
+
"""
|
|
1172
|
+
if not existing_mapping:
|
|
1173
|
+
return new_mapping
|
|
1174
|
+
|
|
1175
|
+
# `ignored` is written by the admin editor (originals-pipeline.cjs): entities
|
|
1176
|
+
# the lawyer removed by hand are false positives and must not come back.
|
|
1177
|
+
ignored = [str(text).strip() for text in existing_mapping.get('ignored', []) if str(text).strip()]
|
|
1178
|
+
ignored_lower = {text.lower() for text in ignored}
|
|
1179
|
+
left_visible: List[str] = []
|
|
1180
|
+
|
|
1181
|
+
merged_mapping = {**existing_mapping.get('mapping', {})}
|
|
1182
|
+
merged_reverse = {**existing_mapping.get('reverse_mapping', {})}
|
|
1183
|
+
merged_extracted = {
|
|
1184
|
+
"personnes_physiques": {**existing_mapping.get('extracted_data', {}).get('personnes_physiques', {})},
|
|
1185
|
+
"societes": {**existing_mapping.get('extracted_data', {}).get('societes', {})},
|
|
1186
|
+
"adresses": {**existing_mapping.get('extracted_data', {}).get('adresses', {})},
|
|
1187
|
+
"siren": {**existing_mapping.get('extracted_data', {}).get('siren', {})},
|
|
1188
|
+
"autres": {**existing_mapping.get('extracted_data', {}).get('autres', {})}
|
|
1189
|
+
}
|
|
1190
|
+
|
|
1191
|
+
# Track seen entities to avoid duplicates
|
|
1192
|
+
seen_entities_lower = {k.lower(): v for k, v in merged_mapping.items()}
|
|
1193
|
+
|
|
1194
|
+
# Find highest existing code numbers per category
|
|
1195
|
+
code_counters = {
|
|
1196
|
+
"personnes_physiques": 1,
|
|
1197
|
+
"adresses": 1,
|
|
1198
|
+
"siren": 1,
|
|
1199
|
+
"autres": 1
|
|
1200
|
+
}
|
|
1201
|
+
# Sociรฉtรฉs : un compteur par sigle (SA, SARL, GMBHโฆ) + PERS_MORALE, amorcรฉ
|
|
1202
|
+
# depuis les codes dรฉjร prรฉsents. Le bucket `societes` d'extracted_data donne
|
|
1203
|
+
# les codes sociรฉtรฉ quelle que soit leur forme (bare SA_3 comme legacy
|
|
1204
|
+
# URGOT SA), lร oรน un test par sous-chaรฎne ne reconnaรฎtrait pas SA_3.
|
|
1205
|
+
societe_counters: Dict[str, int] = {}
|
|
1206
|
+
|
|
1207
|
+
for code in merged_reverse.keys():
|
|
1208
|
+
if "PERSONNE_PHYSIQUE_" in code or code.startswith("DIRIGEANT_"):
|
|
1209
|
+
num = int(code.split("_")[-1])
|
|
1210
|
+
code_counters["personnes_physiques"] = max(code_counters["personnes_physiques"], num + 1)
|
|
1211
|
+
elif code.startswith("ADRESSE_"):
|
|
1212
|
+
num = int(code.split("_")[-1])
|
|
1213
|
+
code_counters["adresses"] = max(code_counters["adresses"], num + 1)
|
|
1214
|
+
elif code.startswith("SIREN_"):
|
|
1215
|
+
num = int(code.split("_")[-1])
|
|
1216
|
+
code_counters["siren"] = max(code_counters["siren"], num + 1)
|
|
1217
|
+
|
|
1218
|
+
for code in merged_extracted.get("societes", {}).keys():
|
|
1219
|
+
match = re.search(r"_(\d+)$", code)
|
|
1220
|
+
if not match:
|
|
1221
|
+
continue
|
|
1222
|
+
key = _societe_counter_key_of_code(code)
|
|
1223
|
+
societe_counters[key] = max(societe_counters.get(key, 0), int(match.group(1)) + 1)
|
|
1224
|
+
|
|
1225
|
+
# Merge new entries
|
|
1226
|
+
for text, code in new_mapping.get('mapping', {}).items():
|
|
1227
|
+
text_lower = text.lower()
|
|
1228
|
+
|
|
1229
|
+
# Skip if already exists (use existing code)
|
|
1230
|
+
if text_lower in seen_entities_lower:
|
|
1231
|
+
continue
|
|
1232
|
+
|
|
1233
|
+
# Skip entities the lawyer discarded from the mapping
|
|
1234
|
+
if text_lower in ignored_lower:
|
|
1235
|
+
continue
|
|
1236
|
+
|
|
1237
|
+
# Determine category
|
|
1238
|
+
category = None
|
|
1239
|
+
for cat_key in merged_extracted.keys():
|
|
1240
|
+
if code in new_mapping.get('extracted_data', {}).get(cat_key, {}):
|
|
1241
|
+
category = cat_key
|
|
1242
|
+
break
|
|
1243
|
+
|
|
1244
|
+
if not category:
|
|
1245
|
+
category = "autres"
|
|
1246
|
+
|
|
1247
|
+
# Generate new code with incremented counter
|
|
1248
|
+
if category == "personnes_physiques":
|
|
1249
|
+
new_code = f"PERSONNE_PHYSIQUE_{code_counters[category]:02d}"
|
|
1250
|
+
code_counters[category] += 1
|
|
1251
|
+
elif category == "societes":
|
|
1252
|
+
# Le sigle du code d'origine est prรฉservรฉ : SA_3 โ SA_<n>, legacy
|
|
1253
|
+
# URGOT SA โ SA_<n>, sinon PERS_MORALE_<n>. Compteur par sigle.
|
|
1254
|
+
key = _societe_counter_key_of_code(code)
|
|
1255
|
+
n = societe_counters.get(key, 0) + 1
|
|
1256
|
+
societe_counters[key] = n
|
|
1257
|
+
new_code = f"{key}_{n}"
|
|
1258
|
+
elif category == "adresses":
|
|
1259
|
+
new_code = f"ADRESSE_{code_counters[category]:02d}"
|
|
1260
|
+
code_counters[category] += 1
|
|
1261
|
+
elif category == "siren":
|
|
1262
|
+
new_code = f"SIREN_{code_counters[category]:02d}"
|
|
1263
|
+
code_counters[category] += 1
|
|
1264
|
+
else:
|
|
1265
|
+
# Extract entity type from new code
|
|
1266
|
+
entity_type = code.split("_")[0] if "_" in code else "AUTRE"
|
|
1267
|
+
new_code = f"{entity_type}_{code_counters[category]:02d}"
|
|
1268
|
+
code_counters[category] += 1
|
|
1269
|
+
|
|
1270
|
+
# Get variants from existing entry
|
|
1271
|
+
old_entry = new_mapping.get('extracted_data', {}).get(category, {}).get(code, {})
|
|
1272
|
+
variants = old_entry.get("variants", [text])
|
|
1273
|
+
|
|
1274
|
+
# Add all variants to merged structures (no lowercase duplicates)
|
|
1275
|
+
for variant in variants:
|
|
1276
|
+
merged_mapping[variant] = new_code
|
|
1277
|
+
|
|
1278
|
+
seen_entities_lower[text_lower] = new_code
|
|
1279
|
+
|
|
1280
|
+
# Update reverse mapping
|
|
1281
|
+
original = new_mapping.get('reverse_mapping', {}).get(code, [text])[0]
|
|
1282
|
+
merged_reverse[new_code] = [original]
|
|
1283
|
+
|
|
1284
|
+
# Update extracted_data
|
|
1285
|
+
merged_extracted[category][new_code] = {
|
|
1286
|
+
"original": original,
|
|
1287
|
+
"code": new_code,
|
|
1288
|
+
"variants": variants,
|
|
1289
|
+
"score": old_entry.get("score", 1.0),
|
|
1290
|
+
"recognizer": old_entry.get("recognizer", "unknown")
|
|
1291
|
+
}
|
|
1292
|
+
|
|
1293
|
+
# Parse addresses for partial anonymization (same logic as convert_to_anonymization_format)
|
|
1294
|
+
if category == "adresses":
|
|
1295
|
+
parsed_result = parse_address(original)
|
|
1296
|
+
merged_extracted[category][new_code]["parsed"] = parsed_result
|
|
1297
|
+
|
|
1298
|
+
# If city-only, don't add to mapping (no anonymization needed)
|
|
1299
|
+
if parsed_result.get("is_city_only"):
|
|
1300
|
+
# Remove from merged structures
|
|
1301
|
+
for variant in variants:
|
|
1302
|
+
merged_mapping.pop(variant, None)
|
|
1303
|
+
left_visible.extend(variants)
|
|
1304
|
+
merged_reverse.pop(new_code, None)
|
|
1305
|
+
del merged_extracted[category][new_code]
|
|
1306
|
+
# Don't increment counter since we're removing this entry
|
|
1307
|
+
elif parsed_result.get("anonymization_strategy") == "partial":
|
|
1308
|
+
# Update mapping to only anonymize street part
|
|
1309
|
+
# Remove full address from mapping
|
|
1310
|
+
for variant in variants:
|
|
1311
|
+
merged_mapping.pop(variant, None)
|
|
1312
|
+
# Add only street part to mapping
|
|
1313
|
+
street_part = parsed_result.get("street_part")
|
|
1314
|
+
if street_part:
|
|
1315
|
+
merged_mapping[street_part] = new_code
|
|
1316
|
+
# Update variants to only include street part
|
|
1317
|
+
merged_extracted[category][new_code]["variants"] = [street_part]
|
|
1318
|
+
merged_reverse[new_code] = [street_part]
|
|
1319
|
+
left_visible.extend(v for v in variants if v != street_part)
|
|
1320
|
+
|
|
1321
|
+
merged_ignored = list(dict.fromkeys(
|
|
1322
|
+
ignored + [str(text).strip() for text in new_mapping.get('ignored', []) if str(text).strip()] + left_visible
|
|
1323
|
+
))
|
|
1324
|
+
return {
|
|
1325
|
+
"mapping": merged_mapping,
|
|
1326
|
+
"reverse_mapping": merged_reverse,
|
|
1327
|
+
"extracted_data": merged_extracted,
|
|
1328
|
+
"informations_dossier": existing_mapping.get("informations_dossier", {}),
|
|
1329
|
+
**({"ignored": merged_ignored} if merged_ignored else {}),
|
|
1330
|
+
}
|
|
1331
|
+
|
|
1332
|
+
|
|
1333
|
+
def save_mapping(mapping_path: Path, mapping_data: Dict) -> Path:
|
|
1334
|
+
"""Save the merged mapping to the case/document mapping file.
|
|
1335
|
+
|
|
1336
|
+
Uses the SAME file as server.cjs (line 2146), ensuring the validated
|
|
1337
|
+
mapping can be read by GET /api/anonymize/mapping/:documentId.
|
|
1338
|
+
|
|
1339
|
+
Args:
|
|
1340
|
+
mapping_path: Full path to the mapping file to write
|
|
1341
|
+
mapping_data: Complete mapping data structure
|
|
1342
|
+
|
|
1343
|
+
Returns:
|
|
1344
|
+
Path to saved file
|
|
1345
|
+
"""
|
|
1346
|
+
payload = {
|
|
1347
|
+
**mapping_data,
|
|
1348
|
+
"mapping": dict(sorted(
|
|
1349
|
+
mapping_data.get("mapping", {}).items(),
|
|
1350
|
+
key=lambda item: (-len(item[0]), item[0].casefold()),
|
|
1351
|
+
)),
|
|
1352
|
+
}
|
|
1353
|
+
mapping_path.parent.mkdir(parents=True, exist_ok=True)
|
|
1354
|
+
temporary = mapping_path.with_name(f"{mapping_path.name}.piecemaker-{os.getpid()}.tmp")
|
|
1355
|
+
with open(temporary, 'w', encoding='utf-8') as f:
|
|
1356
|
+
json.dump(payload, f, indent=2, ensure_ascii=False)
|
|
1357
|
+
f.write("\n")
|
|
1358
|
+
os.chmod(temporary, 0o600)
|
|
1359
|
+
os.replace(temporary, mapping_path)
|
|
1360
|
+
|
|
1361
|
+
return mapping_path
|
|
1362
|
+
|
|
1363
|
+
|
|
1364
|
+
def load_anonymization_state(state_path: Path) -> Dict:
|
|
1365
|
+
"""Load the non-sensitive per-file processing manifest."""
|
|
1366
|
+
try:
|
|
1367
|
+
with open(state_path, "r", encoding="utf-8") as state_file:
|
|
1368
|
+
raw = json.load(state_file)
|
|
1369
|
+
except (FileNotFoundError, json.JSONDecodeError, OSError):
|
|
1370
|
+
raw = {}
|
|
1371
|
+
source_files = raw.get("files", {}) if isinstance(raw, dict) else {}
|
|
1372
|
+
files = {}
|
|
1373
|
+
if isinstance(source_files, dict):
|
|
1374
|
+
for key, entry in source_files.items():
|
|
1375
|
+
if not isinstance(entry, dict):
|
|
1376
|
+
continue
|
|
1377
|
+
if "size" in entry and "mtimeMs" in entry:
|
|
1378
|
+
legacy = {
|
|
1379
|
+
"size": entry["size"],
|
|
1380
|
+
"mtimeMs": entry["mtimeMs"],
|
|
1381
|
+
**({"updatedAt": entry["scannedAt"]} if entry.get("scannedAt") else {}),
|
|
1382
|
+
}
|
|
1383
|
+
files[key] = {"converted": legacy, "scanned": legacy}
|
|
1384
|
+
else:
|
|
1385
|
+
files[key] = {
|
|
1386
|
+
phase: value for phase, value in entry.items()
|
|
1387
|
+
if phase in ("converted", "scanned") and isinstance(value, dict)
|
|
1388
|
+
}
|
|
1389
|
+
return {"version": 1, "files": files}
|
|
1390
|
+
|
|
1391
|
+
|
|
1392
|
+
def anonymization_state_key(source_file: str, case_root: str) -> Optional[str]:
|
|
1393
|
+
"""Hash source identity so the manifest itself contains no filename."""
|
|
1394
|
+
source = Path(source_file).resolve()
|
|
1395
|
+
try:
|
|
1396
|
+
identity = source.relative_to(Path(case_root).resolve()).as_posix()
|
|
1397
|
+
except ValueError:
|
|
1398
|
+
# Standalone CLI use can place outputs outside the input tree. Hashing
|
|
1399
|
+
# the absolute identity preserves --skip-existing without persisting it.
|
|
1400
|
+
identity = f"absolute:{source.as_posix()}"
|
|
1401
|
+
# Match the Node side (anonymization-state.cjs): an accented filename must
|
|
1402
|
+
# hash identically whether it reaches us as NFC or NFD, or the document-index
|
|
1403
|
+
# join drops it. Normalise to NFC on both sides, independent of the OS.
|
|
1404
|
+
identity = unicodedata.normalize("NFC", identity)
|
|
1405
|
+
return hashlib.sha256(identity.encode("utf-8")).hexdigest()
|
|
1406
|
+
|
|
1407
|
+
|
|
1408
|
+
def source_fingerprint(source_file: str) -> Dict:
|
|
1409
|
+
stat = os.stat(source_file)
|
|
1410
|
+
return {"size": stat.st_size, "mtimeMs": stat.st_mtime_ns // 1_000_000}
|
|
1411
|
+
|
|
1412
|
+
|
|
1413
|
+
def source_state_entry(state: Dict, source_file: str, case_root: str) -> Optional[Dict]:
|
|
1414
|
+
key = anonymization_state_key(source_file, case_root)
|
|
1415
|
+
return state.get("files", {}).get(key) if key else None
|
|
1416
|
+
|
|
1417
|
+
|
|
1418
|
+
def source_is_processed(state: Dict, source_file: str, case_root: str, phase: str) -> bool:
|
|
1419
|
+
entry = source_state_entry(state, source_file, case_root)
|
|
1420
|
+
if not isinstance(entry, dict):
|
|
1421
|
+
return False
|
|
1422
|
+
phase_entry = entry.get(phase)
|
|
1423
|
+
# Backward compatibility with the first, flat state format.
|
|
1424
|
+
if not isinstance(phase_entry, dict):
|
|
1425
|
+
phase_entry = entry if "size" in entry and "mtimeMs" in entry else None
|
|
1426
|
+
if not phase_entry:
|
|
1427
|
+
return False
|
|
1428
|
+
fingerprint = source_fingerprint(source_file)
|
|
1429
|
+
return phase_entry.get("size") == fingerprint["size"] and phase_entry.get("mtimeMs") == fingerprint["mtimeMs"]
|
|
1430
|
+
|
|
1431
|
+
|
|
1432
|
+
def source_is_converted(state: Dict, source_file: str, case_root: str) -> bool:
|
|
1433
|
+
return source_is_processed(state, source_file, case_root, "converted")
|
|
1434
|
+
|
|
1435
|
+
|
|
1436
|
+
def source_is_anonymized(state: Dict, source_file: str, case_root: str) -> bool:
|
|
1437
|
+
return source_is_processed(state, source_file, case_root, "scanned")
|
|
1438
|
+
|
|
1439
|
+
|
|
1440
|
+
def update_processing_state(state_path: Path, case_root: str, source_files: List[str], phase: str) -> None:
|
|
1441
|
+
"""Atomically add successful conversions/scans without paths or entities."""
|
|
1442
|
+
if not source_files:
|
|
1443
|
+
return
|
|
1444
|
+
state = load_anonymization_state(state_path)
|
|
1445
|
+
updated_at = time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())
|
|
1446
|
+
for source_file in source_files:
|
|
1447
|
+
key = anonymization_state_key(source_file, case_root)
|
|
1448
|
+
if not key:
|
|
1449
|
+
continue
|
|
1450
|
+
fingerprint = {**source_fingerprint(source_file), "updatedAt": updated_at}
|
|
1451
|
+
entry = state["files"].get(key, {})
|
|
1452
|
+
entry[phase] = fingerprint
|
|
1453
|
+
if phase == "scanned":
|
|
1454
|
+
entry["converted"] = fingerprint
|
|
1455
|
+
state["files"][key] = entry
|
|
1456
|
+
state_path.parent.mkdir(parents=True, exist_ok=True)
|
|
1457
|
+
temporary = state_path.with_name(f"{state_path.name}.piecemaker-{os.getpid()}.tmp")
|
|
1458
|
+
with open(temporary, "w", encoding="utf-8") as state_file:
|
|
1459
|
+
json.dump(state, state_file, indent=2, ensure_ascii=False)
|
|
1460
|
+
state_file.write("\n")
|
|
1461
|
+
os.chmod(temporary, 0o600)
|
|
1462
|
+
os.replace(temporary, state_path)
|
|
1463
|
+
|
|
1464
|
+
|
|
1465
|
+
def update_anonymization_state(state_path: Path, case_root: str, source_files: List[str]) -> None:
|
|
1466
|
+
update_processing_state(state_path, case_root, source_files, "scanned")
|
|
1467
|
+
|
|
1468
|
+
|
|
1469
|
+
# ---------------------------------------------------------------------------
|
|
1470
|
+
# Per-document index โ chronology / nature / entity attribution
|
|
1471
|
+
# ---------------------------------------------------------------------------
|
|
1472
|
+
# The individual sensitive maps are the only place that knows which entities and
|
|
1473
|
+
# which document_meta belong to a given file; they are deleted right after the
|
|
1474
|
+
# merge, so the attribution has to be captured here. The persistent index keys
|
|
1475
|
+
# each document by the SAME hash the scan-state manifest uses (a filename can
|
|
1476
|
+
# carry a client's name), and stores only entity CODES plus the non-PII
|
|
1477
|
+
# nature/date/juridiction. It is never a source of clear PII on disk.
|
|
1478
|
+
|
|
1479
|
+
def load_document_index(index_path: Path) -> Dict:
|
|
1480
|
+
try:
|
|
1481
|
+
with open(index_path, "r", encoding="utf-8") as handle:
|
|
1482
|
+
raw = json.load(handle)
|
|
1483
|
+
except (FileNotFoundError, json.JSONDecodeError, OSError):
|
|
1484
|
+
raw = {}
|
|
1485
|
+
documents = raw.get("documents", {}) if isinstance(raw, dict) else {}
|
|
1486
|
+
overrides = raw.get("overrides", {}) if isinstance(raw, dict) else {}
|
|
1487
|
+
entity_decisions = raw.get("entityDecisions", {}) if isinstance(raw, dict) else {}
|
|
1488
|
+
revisions = raw.get("revisions", []) if isinstance(raw, dict) else []
|
|
1489
|
+
return {
|
|
1490
|
+
"version": 2,
|
|
1491
|
+
"documents": documents if isinstance(documents, dict) else {},
|
|
1492
|
+
# Corrections manuelles รฉcrites par le serveur Node : un re-scan met ร
|
|
1493
|
+
# jour `documents`, mais ne doit jamais les effacer.
|
|
1494
|
+
"overrides": overrides if isinstance(overrides, dict) else {},
|
|
1495
|
+
# Les dรฉcisions locales d'entitรฉs et leur journal sont รฉgalement
|
|
1496
|
+
# append-only du point de vue du pipeline : seul le serveur admin les
|
|
1497
|
+
# modifie, un nouveau scan rafraรฎchit uniquement la dรฉtection brute.
|
|
1498
|
+
"entityDecisions": entity_decisions if isinstance(entity_decisions, dict) else {},
|
|
1499
|
+
"revisions": revisions if isinstance(revisions, list) else [],
|
|
1500
|
+
}
|
|
1501
|
+
|
|
1502
|
+
|
|
1503
|
+
def _mapping_code_lookup(final_mapping_data: Dict) -> Dict[str, str]:
|
|
1504
|
+
"""Case-insensitive {variant -> code}, longest variant first so a short name
|
|
1505
|
+
that is a substring of a stored variant does not shadow the longer code."""
|
|
1506
|
+
lookup: Dict[str, str] = {}
|
|
1507
|
+
items = sorted(
|
|
1508
|
+
final_mapping_data.get("mapping", {}).items(),
|
|
1509
|
+
key=lambda kv: len(kv[0]),
|
|
1510
|
+
reverse=True,
|
|
1511
|
+
)
|
|
1512
|
+
for variant, code in items:
|
|
1513
|
+
key = str(variant).strip().lower()
|
|
1514
|
+
if key:
|
|
1515
|
+
lookup.setdefault(key, code)
|
|
1516
|
+
return lookup
|
|
1517
|
+
|
|
1518
|
+
|
|
1519
|
+
# Legal-form / generic tokens shared by company names and court names alike โ
|
|
1520
|
+
# excluded from the sensitive set so a real "Tribunal de commerce" is not scrubbed
|
|
1521
|
+
# because some mapped company is a "... SA".
|
|
1522
|
+
_FREE_TEXT_STOPWORDS = frozenset({
|
|
1523
|
+
"sarl", "sasu", "sociรฉtรฉ", "societe", "compagnie", "groupe", "group",
|
|
1524
|
+
"holding", "association", "syndicat", "france",
|
|
1525
|
+
})
|
|
1526
|
+
|
|
1527
|
+
|
|
1528
|
+
def _sensitive_tokens(final_mapping_data: Dict) -> Set[str]:
|
|
1529
|
+
"""Distinctive tokens (>=4 chars) of every mapped entity.
|
|
1530
|
+
|
|
1531
|
+
Used to scrub free-text metadata (the juridiction): GLiNER's extract_json
|
|
1532
|
+
"juridiction" head routinely mis-captures a litigant's name as the court, and
|
|
1533
|
+
that name must never reach the non-PII index. Any free-text field sharing a
|
|
1534
|
+
token with a mapped entity is dropped rather than stored in clear.
|
|
1535
|
+
"""
|
|
1536
|
+
tokens: Set[str] = set()
|
|
1537
|
+
for variant in final_mapping_data.get("mapping", {}):
|
|
1538
|
+
for token in re.findall(r"[a-zร -รฟ0-9]{4,}", str(variant).lower()):
|
|
1539
|
+
if token not in _FREE_TEXT_STOPWORDS:
|
|
1540
|
+
tokens.add(token)
|
|
1541
|
+
return tokens
|
|
1542
|
+
|
|
1543
|
+
|
|
1544
|
+
def _scrub_free_text(value, sensitive_tokens: Set[str]) -> Optional[str]:
|
|
1545
|
+
"""Return the text, or None if it carries any mapped-entity token (PII)."""
|
|
1546
|
+
if not value:
|
|
1547
|
+
return None
|
|
1548
|
+
text = str(value).strip()
|
|
1549
|
+
if not text:
|
|
1550
|
+
return None
|
|
1551
|
+
found = set(re.findall(r"[a-zร -รฟ0-9]{4,}", text.lower()))
|
|
1552
|
+
# Noise-only extraction ("SA", "n/a", "RCS") carries no court name โ drop it.
|
|
1553
|
+
if not found:
|
|
1554
|
+
return None
|
|
1555
|
+
if found & sensitive_tokens:
|
|
1556
|
+
return None
|
|
1557
|
+
return text
|
|
1558
|
+
|
|
1559
|
+
|
|
1560
|
+
def _codes_for_entities(entities: Dict, code_lookup: Dict[str, str]) -> List[str]:
|
|
1561
|
+
codes: Set[str] = set()
|
|
1562
|
+
for entity_list in (entities or {}).values():
|
|
1563
|
+
if not isinstance(entity_list, list):
|
|
1564
|
+
continue
|
|
1565
|
+
for entity in entity_list:
|
|
1566
|
+
text = entity.get("text") if isinstance(entity, dict) else None
|
|
1567
|
+
if not text:
|
|
1568
|
+
continue
|
|
1569
|
+
code = code_lookup.get(str(text).strip().lower())
|
|
1570
|
+
if code:
|
|
1571
|
+
codes.add(code)
|
|
1572
|
+
return sorted(codes)
|
|
1573
|
+
|
|
1574
|
+
|
|
1575
|
+
def write_document_index(
|
|
1576
|
+
index_path: Path,
|
|
1577
|
+
case_root: str,
|
|
1578
|
+
scan_records: List[Dict],
|
|
1579
|
+
final_mapping_data: Dict,
|
|
1580
|
+
) -> int:
|
|
1581
|
+
"""Merge this run's scanned documents into <case>/.piecemaker/document-index.json.
|
|
1582
|
+
|
|
1583
|
+
Returns the number of document entries written/updated.
|
|
1584
|
+
"""
|
|
1585
|
+
if not scan_records:
|
|
1586
|
+
return 0
|
|
1587
|
+
code_lookup = _mapping_code_lookup(final_mapping_data)
|
|
1588
|
+
sensitive_tokens = _sensitive_tokens(final_mapping_data)
|
|
1589
|
+
index = load_document_index(index_path)
|
|
1590
|
+
updated_at = time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())
|
|
1591
|
+
written = 0
|
|
1592
|
+
for record in scan_records:
|
|
1593
|
+
source = record.get("source")
|
|
1594
|
+
if not source:
|
|
1595
|
+
continue
|
|
1596
|
+
# The payload fields are captured in-memory before the transient scan
|
|
1597
|
+
# workspace is cleaned; fall back to re-reading json_path for standalone
|
|
1598
|
+
# callers (and tests) that pass the file directly.
|
|
1599
|
+
if "entities" in record or "document_meta" in record:
|
|
1600
|
+
entities = record.get("entities") or {}
|
|
1601
|
+
meta = record.get("document_meta") or {}
|
|
1602
|
+
else:
|
|
1603
|
+
try:
|
|
1604
|
+
with open(record.get("json_path"), "r", encoding="utf-8") as handle:
|
|
1605
|
+
data = json.load(handle)
|
|
1606
|
+
except (FileNotFoundError, json.JSONDecodeError, OSError, TypeError):
|
|
1607
|
+
continue
|
|
1608
|
+
entities = data.get("entities") or {}
|
|
1609
|
+
meta = data.get("document_meta") or {}
|
|
1610
|
+
key = anonymization_state_key(source, case_root)
|
|
1611
|
+
if not key:
|
|
1612
|
+
continue
|
|
1613
|
+
index["documents"][key] = {
|
|
1614
|
+
"nature": meta.get("nature"),
|
|
1615
|
+
"nature_confidence": meta.get("nature_confidence"),
|
|
1616
|
+
"doc_date": meta.get("doc_date"),
|
|
1617
|
+
"doc_date_iso": meta.get("doc_date_iso"),
|
|
1618
|
+
"localisation": _scrub_free_text(meta.get("juridiction"), sensitive_tokens),
|
|
1619
|
+
"personnes_visees": _codes_for_entities(entities, code_lookup),
|
|
1620
|
+
"updatedAt": updated_at,
|
|
1621
|
+
}
|
|
1622
|
+
written += 1
|
|
1623
|
+
if not written:
|
|
1624
|
+
return 0
|
|
1625
|
+
index_path.parent.mkdir(parents=True, exist_ok=True)
|
|
1626
|
+
temporary = index_path.with_name(f"{index_path.name}.piecemaker-{os.getpid()}.tmp")
|
|
1627
|
+
with open(temporary, "w", encoding="utf-8") as handle:
|
|
1628
|
+
# Index interne compact : moins de volume et de tokens ร la lecture.
|
|
1629
|
+
json.dump(index, handle, ensure_ascii=False, separators=(",", ":"))
|
|
1630
|
+
handle.write("\n")
|
|
1631
|
+
os.chmod(temporary, 0o600)
|
|
1632
|
+
os.replace(temporary, index_path)
|
|
1633
|
+
return written
|
|
1634
|
+
|
|
1635
|
+
|
|
1636
|
+
def main():
|
|
1637
|
+
"""Main pipeline orchestration."""
|
|
1638
|
+
parser = argparse.ArgumentParser(
|
|
1639
|
+
description="Convert documents to Markdown and scan for sensitive data",
|
|
1640
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
1641
|
+
epilog=__doc__,
|
|
1642
|
+
)
|
|
1643
|
+
|
|
1644
|
+
parser.add_argument(
|
|
1645
|
+
"files", nargs="+", help="Input files to process (PDF, DOCX, images, etc.)"
|
|
1646
|
+
)
|
|
1647
|
+
parser.add_argument(
|
|
1648
|
+
"-o",
|
|
1649
|
+
"--output",
|
|
1650
|
+
required=True,
|
|
1651
|
+
help="Output directory for Markdown files and mapping_default.json",
|
|
1652
|
+
)
|
|
1653
|
+
parser.add_argument(
|
|
1654
|
+
"--engine",
|
|
1655
|
+
choices=["auto", "markitdown", "mineru"],
|
|
1656
|
+
default="auto",
|
|
1657
|
+
help="Conversion engine (default: auto)",
|
|
1658
|
+
)
|
|
1659
|
+
parser.add_argument(
|
|
1660
|
+
"--mode",
|
|
1661
|
+
choices=["pipeline", "hybrid", "vlm"],
|
|
1662
|
+
help="MinerU processing mode (only used if engine=mineru)",
|
|
1663
|
+
)
|
|
1664
|
+
parser.add_argument("--lang", help="OCR language code (only used if engine=mineru)")
|
|
1665
|
+
parser.add_argument(
|
|
1666
|
+
"--document-id",
|
|
1667
|
+
default="default",
|
|
1668
|
+
help=argparse.SUPPRESS,
|
|
1669
|
+
)
|
|
1670
|
+
parser.add_argument(
|
|
1671
|
+
"--mapping-dir",
|
|
1672
|
+
default=None,
|
|
1673
|
+
help="Directory for mapping_default.json (default: same as --output)",
|
|
1674
|
+
)
|
|
1675
|
+
parser.add_argument(
|
|
1676
|
+
"--mapping-file",
|
|
1677
|
+
default=None,
|
|
1678
|
+
help="Exact mapping file to read and rewrite (overrides --mapping-dir)",
|
|
1679
|
+
)
|
|
1680
|
+
parser.add_argument(
|
|
1681
|
+
"--state-file",
|
|
1682
|
+
default=None,
|
|
1683
|
+
help="Technical scan-state manifest (default: <output>/.piecemaker/anonymization-state.json)",
|
|
1684
|
+
)
|
|
1685
|
+
parser.add_argument(
|
|
1686
|
+
"--case-root",
|
|
1687
|
+
default=None,
|
|
1688
|
+
help=(
|
|
1689
|
+
"Legal-case root used to key the scan-state manifest. Defaults to "
|
|
1690
|
+
"--output for standalone CLI use; the admin pipeline passes the case "
|
|
1691
|
+
"directory so state keys match even when --output is a subfolder."
|
|
1692
|
+
),
|
|
1693
|
+
)
|
|
1694
|
+
parser.add_argument(
|
|
1695
|
+
"--skip-existing",
|
|
1696
|
+
action="store_true",
|
|
1697
|
+
help="Reuse existing Markdown and scans whose source fingerprint is unchanged",
|
|
1698
|
+
)
|
|
1699
|
+
args = parser.parse_args()
|
|
1700
|
+
|
|
1701
|
+
# Validate input files
|
|
1702
|
+
input_files = []
|
|
1703
|
+
for f in args.files:
|
|
1704
|
+
if not os.path.exists(f):
|
|
1705
|
+
print(f"โ ๏ธ File not found: {f}", file=sys.stderr)
|
|
1706
|
+
else:
|
|
1707
|
+
input_files.append(f)
|
|
1708
|
+
|
|
1709
|
+
if not input_files:
|
|
1710
|
+
print("โ No valid input files provided", file=sys.stderr)
|
|
1711
|
+
return 1
|
|
1712
|
+
|
|
1713
|
+
# Ensure output directory exists
|
|
1714
|
+
os.makedirs(args.output, exist_ok=True)
|
|
1715
|
+
mapping_target = (
|
|
1716
|
+
Path(args.mapping_file)
|
|
1717
|
+
if args.mapping_file
|
|
1718
|
+
else Path(args.mapping_dir or args.output) / "mapping_default.json"
|
|
1719
|
+
)
|
|
1720
|
+
state_target = Path(args.state_file) if args.state_file else Path(args.output) / ".piecemaker" / "anonymization-state.json"
|
|
1721
|
+
# Base pour les clรฉs du manifeste : le dossier juridique, dรฉcouplรฉ de --output
|
|
1722
|
+
# (dรฉsormais un sous-dossier). Les sources vivent sous le dossier, pas sous la
|
|
1723
|
+
# sortie ; sans ce dรฉcouplage leur clรฉ ne correspondrait plus ร celle du Node.
|
|
1724
|
+
state_case_root = args.case_root or args.output
|
|
1725
|
+
anonymization_state = load_anonymization_state(state_target)
|
|
1726
|
+
|
|
1727
|
+
print(f"๐ Starting Convert & Scan Pipeline")
|
|
1728
|
+
print(f" Files: {len(input_files)}")
|
|
1729
|
+
print(f" Output: {args.output}")
|
|
1730
|
+
print(f" Engine: {args.engine}")
|
|
1731
|
+
if args.mode:
|
|
1732
|
+
print(f" Mode: {args.mode}")
|
|
1733
|
+
if args.lang:
|
|
1734
|
+
print(f" Language: {args.lang}")
|
|
1735
|
+
print()
|
|
1736
|
+
|
|
1737
|
+
# What is left to do, decided before anything runs: the scanner worker loads
|
|
1738
|
+
# ~1.1GB of GLiNER2.5 + spaCy weights, so it must only start when at least one
|
|
1739
|
+
# file actually needs a PII scan.
|
|
1740
|
+
def markdown_path(input_file: str) -> Path:
|
|
1741
|
+
return Path(args.output) / f"{Path(input_file).stem}.md"
|
|
1742
|
+
|
|
1743
|
+
scan_needed = any(
|
|
1744
|
+
not (args.skip_existing and source_is_anonymized(anonymization_state, f, state_case_root))
|
|
1745
|
+
for f in input_files
|
|
1746
|
+
)
|
|
1747
|
+
|
|
1748
|
+
# Start scanner worker immediately so model loading overlaps with Phase 1.
|
|
1749
|
+
if scan_needed:
|
|
1750
|
+
scanner_worker = start_scanner_worker()
|
|
1751
|
+
else:
|
|
1752
|
+
scanner_worker = None
|
|
1753
|
+
print("โ
Every PII scan is already up to date โ GLiNER not started")
|
|
1754
|
+
print()
|
|
1755
|
+
|
|
1756
|
+
# Phase 1: Convert all files to Markdown
|
|
1757
|
+
print("=" * 70)
|
|
1758
|
+
print("PHASE 1: CONVERTING DOCUMENTS TO MARKDOWN")
|
|
1759
|
+
print("=" * 70)
|
|
1760
|
+
print()
|
|
1761
|
+
|
|
1762
|
+
md_files = []
|
|
1763
|
+
md_sources = {}
|
|
1764
|
+
converted_sources = []
|
|
1765
|
+
convert_success_count = 0
|
|
1766
|
+
|
|
1767
|
+
for i, input_file in enumerate(input_files, start=1):
|
|
1768
|
+
print_progress("CONVERT", i, len(input_files))
|
|
1769
|
+
existing_md = markdown_path(input_file)
|
|
1770
|
+
|
|
1771
|
+
state_entry = source_state_entry(anonymization_state, input_file, state_case_root)
|
|
1772
|
+
reusable_markdown = existing_md.exists() and (
|
|
1773
|
+
state_entry is None
|
|
1774
|
+
or source_is_converted(anonymization_state, input_file, state_case_root)
|
|
1775
|
+
)
|
|
1776
|
+
if args.skip_existing and reusable_markdown:
|
|
1777
|
+
print(f"๐ [{i}/{len(input_files)}] Already converted, reusing: {existing_md.name}")
|
|
1778
|
+
md_files.append(str(existing_md))
|
|
1779
|
+
md_sources[str(existing_md)] = input_file
|
|
1780
|
+
converted_sources.append(input_file)
|
|
1781
|
+
convert_success_count += 1
|
|
1782
|
+
print()
|
|
1783
|
+
continue
|
|
1784
|
+
|
|
1785
|
+
print(f"๐ [{i}/{len(input_files)}] Converting: {Path(input_file).name}")
|
|
1786
|
+
|
|
1787
|
+
success, md_path = convert_file(
|
|
1788
|
+
input_file, args.output, engine=args.engine, mode=args.mode, lang=args.lang
|
|
1789
|
+
)
|
|
1790
|
+
|
|
1791
|
+
if success and md_path:
|
|
1792
|
+
print(f" โ
Markdown generated: {Path(md_path).name}")
|
|
1793
|
+
md_files.append(md_path)
|
|
1794
|
+
md_sources[md_path] = input_file
|
|
1795
|
+
converted_sources.append(input_file)
|
|
1796
|
+
convert_success_count += 1
|
|
1797
|
+
else:
|
|
1798
|
+
print(f" โ Conversion failed, skipping...")
|
|
1799
|
+
|
|
1800
|
+
print()
|
|
1801
|
+
|
|
1802
|
+
if not md_files:
|
|
1803
|
+
print("โ All conversions failed โ pipeline aborted", file=sys.stderr)
|
|
1804
|
+
return 1
|
|
1805
|
+
|
|
1806
|
+
print(
|
|
1807
|
+
f"โ
Phase 1 complete: {convert_success_count}/{len(input_files)} files converted"
|
|
1808
|
+
)
|
|
1809
|
+
update_processing_state(state_target, state_case_root, converted_sources, "converted")
|
|
1810
|
+
print()
|
|
1811
|
+
|
|
1812
|
+
# Phase 2: Scan all Markdown files for PII
|
|
1813
|
+
print("=" * 70)
|
|
1814
|
+
print("PHASE 2: SCANNING FOR SENSITIVE DATA")
|
|
1815
|
+
print("=" * 70)
|
|
1816
|
+
print()
|
|
1817
|
+
|
|
1818
|
+
pending_scans = [
|
|
1819
|
+
md_file
|
|
1820
|
+
for md_file in md_files
|
|
1821
|
+
if not (
|
|
1822
|
+
args.skip_existing
|
|
1823
|
+
and source_is_anonymized(anonymization_state, md_sources[md_file], state_case_root)
|
|
1824
|
+
)
|
|
1825
|
+
]
|
|
1826
|
+
skipped_scans = len(md_files) - len(pending_scans)
|
|
1827
|
+
if skipped_scans:
|
|
1828
|
+
print(f"โญ๏ธ {skipped_scans} file(s) already scanned, left untouched")
|
|
1829
|
+
|
|
1830
|
+
# Wait for the scanner worker to finish loading models.
|
|
1831
|
+
worker_ready = wait_for_worker_ready(scanner_worker) if pending_scans else False
|
|
1832
|
+
|
|
1833
|
+
if pending_scans and not worker_ready:
|
|
1834
|
+
print("โ ๏ธ Scanner worker not available, falling back to subprocess-per-file", file=sys.stderr)
|
|
1835
|
+
print()
|
|
1836
|
+
|
|
1837
|
+
scan_success_count = 0
|
|
1838
|
+
successful_scan_sources = []
|
|
1839
|
+
json_files = []
|
|
1840
|
+
# Keeps each scanned document's payload tied to its source so the per-document
|
|
1841
|
+
# index can attribute entity codes and metadata after the merge (the payloads
|
|
1842
|
+
# are deleted with the workspace below).
|
|
1843
|
+
scan_records: List[Dict] = []
|
|
1844
|
+
# Raw detections contain PII. They live only in a private OS temporary
|
|
1845
|
+
# directory and disappear after consolidation, including on exceptions.
|
|
1846
|
+
scan_workspace = tempfile.TemporaryDirectory(prefix="piecemaker-scans-")
|
|
1847
|
+
scan_output_dir = scan_workspace.name
|
|
1848
|
+
|
|
1849
|
+
for i, md_file in enumerate(pending_scans, start=1):
|
|
1850
|
+
print_progress("SCAN", i, len(pending_scans))
|
|
1851
|
+
print(f"๐ [{i}/{len(pending_scans)}] Scanning: {Path(md_file).name}")
|
|
1852
|
+
|
|
1853
|
+
if worker_ready:
|
|
1854
|
+
success = scan_file_via_worker(scanner_worker, md_file, scan_output_dir)
|
|
1855
|
+
else:
|
|
1856
|
+
# Fallback: subprocess per file (old behavior)
|
|
1857
|
+
success = scan_file(md_file, scan_output_dir)
|
|
1858
|
+
|
|
1859
|
+
if success:
|
|
1860
|
+
json_path = Path(scan_output_dir) / f"{Path(md_file).stem}_sensitive_map.json"
|
|
1861
|
+
if not json_path.exists():
|
|
1862
|
+
print(f" โ ๏ธ Scanner reported success without a payload", file=sys.stderr)
|
|
1863
|
+
continue
|
|
1864
|
+
print(f" โ
Sensitive entities collected")
|
|
1865
|
+
scan_success_count += 1
|
|
1866
|
+
successful_scan_sources.append(md_sources[md_file])
|
|
1867
|
+
json_files.append(str(json_path))
|
|
1868
|
+
scan_records.append({"source": md_sources[md_file], "json_path": str(json_path)})
|
|
1869
|
+
else:
|
|
1870
|
+
print(f" โ ๏ธ Scan failed, markdown preserved")
|
|
1871
|
+
|
|
1872
|
+
print()
|
|
1873
|
+
|
|
1874
|
+
# Shut down worker
|
|
1875
|
+
stop_scanner_worker(scanner_worker)
|
|
1876
|
+
|
|
1877
|
+
print(f"โ
Phase 2 complete: {scan_success_count}/{len(pending_scans)} files scanned")
|
|
1878
|
+
print()
|
|
1879
|
+
|
|
1880
|
+
# Phase 3: Merge the transient detections into the one persistent mapping.
|
|
1881
|
+
print("=" * 70)
|
|
1882
|
+
print("PHASE 3: UPDATING THE CASE MAPPING")
|
|
1883
|
+
print("=" * 70)
|
|
1884
|
+
print()
|
|
1885
|
+
|
|
1886
|
+
if not json_files:
|
|
1887
|
+
scan_workspace.cleanup()
|
|
1888
|
+
if not pending_scans:
|
|
1889
|
+
print("โ
No new scan to merge; mapping and processing state are already up to date")
|
|
1890
|
+
return 0
|
|
1891
|
+
print("โ ๏ธ No successful PII scan to merge", file=sys.stderr)
|
|
1892
|
+
print(f"โ
Pipeline complete: {convert_success_count}/{len(input_files)} files converted")
|
|
1893
|
+
print()
|
|
1894
|
+
return 1
|
|
1895
|
+
|
|
1896
|
+
print(f"๐ Step 1: Reading {len(json_files)} individual mapping(s)...")
|
|
1897
|
+
|
|
1898
|
+
# Read individual mappings
|
|
1899
|
+
consolidated = read_individual_mappings(json_files)
|
|
1900
|
+
# Capture each document's entities + metadata in memory before the transient
|
|
1901
|
+
# scan workspace is deleted, so the per-document index can be built once the
|
|
1902
|
+
# final mapping (and therefore the codes) is known further down.
|
|
1903
|
+
for record in scan_records:
|
|
1904
|
+
try:
|
|
1905
|
+
with open(record["json_path"], "r", encoding="utf-8") as handle:
|
|
1906
|
+
payload = json.load(handle)
|
|
1907
|
+
record["entities"] = payload.get("entities") or {}
|
|
1908
|
+
record["document_meta"] = payload.get("document_meta") or {}
|
|
1909
|
+
except (FileNotFoundError, json.JSONDecodeError, OSError, KeyError):
|
|
1910
|
+
record["entities"] = {}
|
|
1911
|
+
record["document_meta"] = {}
|
|
1912
|
+
scan_workspace.cleanup()
|
|
1913
|
+
|
|
1914
|
+
# Convert to anonymization format
|
|
1915
|
+
print("๐ Step 2: Converting to anonymization format...")
|
|
1916
|
+
new_mapping_data = convert_to_anonymization_format(consolidated)
|
|
1917
|
+
|
|
1918
|
+
# Load existing mapping (if exists)
|
|
1919
|
+
print("๐ Step 3: Loading existing mapping...")
|
|
1920
|
+
existing_mapping = load_existing_mapping(mapping_target)
|
|
1921
|
+
|
|
1922
|
+
# Merge with existing mapping
|
|
1923
|
+
print("๐ Step 4: Merging with existing mapping...")
|
|
1924
|
+
final_mapping_data = merge_with_existing_mapping(new_mapping_data, existing_mapping)
|
|
1925
|
+
|
|
1926
|
+
# Save mapping (same file server uses)
|
|
1927
|
+
print("๐พ Step 5: Saving mapping...")
|
|
1928
|
+
mapping_path = save_mapping(mapping_target, final_mapping_data)
|
|
1929
|
+
update_anonymization_state(state_target, state_case_root, successful_scan_sources)
|
|
1930
|
+
|
|
1931
|
+
# Per-document index (chronology / nature / entity codes) lives next to the
|
|
1932
|
+
# scan-state manifest, keyed by the same hash so a filename is never stored.
|
|
1933
|
+
index_path = state_target.parent / "document-index.json"
|
|
1934
|
+
try:
|
|
1935
|
+
indexed = write_document_index(index_path, state_case_root, scan_records, final_mapping_data)
|
|
1936
|
+
if indexed:
|
|
1937
|
+
print(f"๐๏ธ Document index updated: {indexed} document(s)")
|
|
1938
|
+
except Exception as exc: # noqa: BLE001
|
|
1939
|
+
print(f"โ ๏ธ Document index update skipped: {exc}", file=sys.stderr)
|
|
1940
|
+
|
|
1941
|
+
print(f"โ
Mapping saved to: {mapping_path}")
|
|
1942
|
+
print(f" โข Total entities: {len(final_mapping_data['mapping'])} variants")
|
|
1943
|
+
print(f" โข Unique codes: {len(final_mapping_data['reverse_mapping'])}")
|
|
1944
|
+
print(f"๐งน Step 6: Removed {len(json_files)} transient sensitive payload(s)")
|
|
1945
|
+
print()
|
|
1946
|
+
|
|
1947
|
+
# Summary
|
|
1948
|
+
print("=" * 70)
|
|
1949
|
+
print("PIPELINE COMPLETE")
|
|
1950
|
+
print("=" * 70)
|
|
1951
|
+
print(f"๐ Results:")
|
|
1952
|
+
print(f" โข Converted: {convert_success_count}/{len(input_files)} files")
|
|
1953
|
+
print(f" โข Scanned: {scan_success_count}/{len(pending_scans)} files")
|
|
1954
|
+
print(f" โข Entities mapped: {len(final_mapping_data['mapping'])} variants")
|
|
1955
|
+
print(f" โข Unique entities: {len(final_mapping_data['reverse_mapping'])} codes")
|
|
1956
|
+
print(f"๐ Output directory: {args.output}")
|
|
1957
|
+
print(f"๐ Mapping file: {mapping_path}")
|
|
1958
|
+
print()
|
|
1959
|
+
|
|
1960
|
+
# Exit with success if at least one file was fully processed
|
|
1961
|
+
if scan_success_count > 0 or not pending_scans:
|
|
1962
|
+
return 0
|
|
1963
|
+
elif convert_success_count > 0:
|
|
1964
|
+
print(
|
|
1965
|
+
"โ ๏ธ Warning: All scans failed, but markdown files are available",
|
|
1966
|
+
file=sys.stderr,
|
|
1967
|
+
)
|
|
1968
|
+
return 0
|
|
1969
|
+
else:
|
|
1970
|
+
return 1
|
|
1971
|
+
|
|
1972
|
+
|
|
1973
|
+
if __name__ == "__main__":
|
|
1974
|
+
try:
|
|
1975
|
+
sys.exit(main())
|
|
1976
|
+
except KeyboardInterrupt:
|
|
1977
|
+
print("\n๐ Pipeline cancelled by user", file=sys.stderr)
|
|
1978
|
+
sys.exit(130)
|
|
1979
|
+
except Exception as e:
|
|
1980
|
+
print(f"\nโ Unexpected error: {e}", file=sys.stderr)
|
|
1981
|
+
import traceback
|
|
1982
|
+
|
|
1983
|
+
traceback.print_exc()
|
|
1984
|
+
sys.exit(1)
|