@piecemaker-legal/piecemaker 1.0.6 → 1.0.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/assets/{abnfDiagram-VCTEODGH-DF7LFtT-.js → abnfDiagram-VCTEODGH-K8VucbQ5.js} +1 -1
- package/dist/assets/{arc-xOmjWeVH.js → arc-DUmCdRoC.js} +1 -1
- package/dist/assets/{architectureDiagram-5GKGNRK7-CESzNPwa.js → architectureDiagram-5GKGNRK7-DYhs5vqD.js} +1 -1
- package/dist/assets/{blockDiagram-I7D4REHJ-BjdN78Ad.js → blockDiagram-I7D4REHJ-DaXJ9nve.js} +1 -1
- package/dist/assets/{c4Diagram-7LVT6UL2-BcK9EDWT.js → c4Diagram-7LVT6UL2-BLbi-zPT.js} +1 -1
- package/dist/assets/channel-CJIq1vqf.js +1 -0
- package/dist/assets/{chunk-2Q5K7J3B-DGUN1JFP.js → chunk-2Q5K7J3B-Bzs3gxaq.js} +1 -1
- package/dist/assets/{chunk-5VM5RSS4-BG3BBcFr.js → chunk-5VM5RSS4-CQNzml7k.js} +1 -1
- package/dist/assets/{chunk-F27PBJKO-CLIOfrF5.js → chunk-F27PBJKO-B8xedWWH.js} +1 -1
- package/dist/assets/{chunk-IMKFNOWR-D6lGkqij.js → chunk-IMKFNOWR-CjBGlNOu.js} +1 -1
- package/dist/assets/{chunk-JWPE2WC7-BmF9vLBT.js → chunk-JWPE2WC7-vfnYEpgA.js} +1 -1
- package/dist/assets/{chunk-POPQ4Y6H-C_0SR3ak.js → chunk-POPQ4Y6H-Btl98tE8.js} +1 -1
- package/dist/assets/{chunk-SVP7TREG-rT20Wfpp.js → chunk-SVP7TREG-Bp6-pLjR.js} +1 -1
- package/dist/assets/{chunk-TICWLB2K-Cn2hkmcf.js → chunk-TICWLB2K-DvmznW2q.js} +1 -1
- package/dist/assets/{chunk-XXDRQBXY-Dm_eP1_v.js → chunk-XXDRQBXY-QSHJTsI3.js} +1 -1
- package/dist/assets/classDiagram-ZZMXUADV-BYkEWggn.js +1 -0
- package/dist/assets/classDiagram-v2-VYDZK3BY-BYkEWggn.js +1 -0
- package/dist/assets/{cose-bilkent-JH36ORCC-cJeeJsep.js → cose-bilkent-JH36ORCC-DxpyZoe2.js} +1 -1
- package/dist/assets/{cynefin-OW5HDTMX-DX2ePIXe.js → cynefin-OW5HDTMX-B9GRp2Gn.js} +1 -1
- package/dist/assets/{cynefinDiagram-5FMLGOSQ-BvWXN-RR.js → cynefinDiagram-5FMLGOSQ-Q-2tz5GW.js} +1 -1
- package/dist/assets/{dagre-GXQ25YYZ-CkwPRhGR.js → dagre-GXQ25YYZ-3Rl3NqNe.js} +1 -1
- package/dist/assets/{diagram-S7CK7UJ4-CBGNzYyv.js → diagram-S7CK7UJ4-CreeWCO1.js} +1 -1
- package/dist/assets/{diagram-UQ7AKVKN-CiwCU0SJ.js → diagram-UQ7AKVKN-DNBoIyDJ.js} +1 -1
- package/dist/assets/{diagram-VSXAHHWV-Aekg4nTk.js → diagram-VSXAHHWV-ZkcE_cRk.js} +1 -1
- package/dist/assets/{diagram-VX7I27RA-DPFP5K7r.js → diagram-VX7I27RA-Dzdf7N2Q.js} +1 -1
- package/dist/assets/{diagram-Z3DM3KII-BHJkxoey.js → diagram-Z3DM3KII-M6b-nr1u.js} +1 -1
- package/dist/assets/{ebnfDiagram-PWID7BFC-2rV1P32T.js → ebnfDiagram-PWID7BFC-DunIw64n.js} +1 -1
- package/dist/assets/{erDiagram-RLTQ6QDP-C4BjyS_H.js → erDiagram-RLTQ6QDP-kN0Se5uc.js} +1 -1
- package/dist/assets/{flowDiagram-HODETNUW-DL1TBlAG.js → flowDiagram-HODETNUW-24YCovut.js} +1 -1
- package/dist/assets/{ganttDiagram-EL5Y4UJY-MKjvzCWz.js → ganttDiagram-EL5Y4UJY-DTUUEEy6.js} +1 -1
- package/dist/assets/{gitGraphDiagram-WWUBYQGX-CS_oTGK5.js → gitGraphDiagram-WWUBYQGX-BitrqhMr.js} +1 -1
- package/dist/assets/{index-XOGQDqU3.js → index-CG9hW1GU.js} +120 -120
- package/dist/assets/index-D_Dc_nQv.css +1 -0
- package/dist/assets/{infoDiagram-27XIBGKW-ihUr2GjN.js → infoDiagram-27XIBGKW-BNHN5Mby.js} +1 -1
- package/dist/assets/{ishikawaDiagram-5VMMS53U-D2YAnuir.js → ishikawaDiagram-5VMMS53U-De3IQKso.js} +1 -1
- package/dist/assets/{journeyDiagram-3NMN7TZE-Bc2hSZGD.js → journeyDiagram-3NMN7TZE-CWlkZ25T.js} +1 -1
- package/dist/assets/{kanban-definition-UXKFOSKX-LRMGNonX.js → kanban-definition-UXKFOSKX-2o_ax-Nk.js} +1 -1
- package/dist/assets/{layout-CcfWOlPV.js → layout-Btga9ZYP.js} +1 -1
- package/dist/assets/{linear-7JoS2gQO.js → linear-BoGGmqPG.js} +1 -1
- package/dist/assets/{mermaid.core-ijh_SsuN.js → mermaid.core-M308jnLQ.js} +6 -6
- package/dist/assets/{mindmap-definition-YA3MSWOX-DCE4nzfF.js → mindmap-definition-YA3MSWOX-BJ6C_grh.js} +1 -1
- package/dist/assets/{pegDiagram-XKGWAZYB-SjaGa7lE.js → pegDiagram-XKGWAZYB-qcdYX0Xe.js} +1 -1
- package/dist/assets/{pieDiagram-E7YTZNPT-1xcRidSZ.js → pieDiagram-E7YTZNPT-XPrSlt1M.js} +1 -1
- package/dist/assets/{quadrantDiagram-AXDQQJYC-obHAHnYV.js → quadrantDiagram-AXDQQJYC-COhHhra5.js} +1 -1
- package/dist/assets/{railroadDiagram-O6MQD6OU-BsCVcRZb.js → railroadDiagram-O6MQD6OU-Mw_KF6gb.js} +1 -1
- package/dist/assets/{requirementDiagram-BXWQKSXE-CUNkhvqb.js → requirementDiagram-BXWQKSXE-DPbt7jUM.js} +1 -1
- package/dist/assets/{sankeyDiagram-P5KCCOFB-DhezXMKm.js → sankeyDiagram-P5KCCOFB-B6HzL6Gh.js} +1 -1
- package/dist/assets/{sequenceDiagram-WJ2MYXX4-CL9VswXH.js → sequenceDiagram-WJ2MYXX4-B_dXXCC0.js} +1 -1
- package/dist/assets/{sizeCapture-INFHLROL-t0g9jiEz.js → sizeCapture-INFHLROL-DwODubWN.js} +1 -1
- package/dist/assets/{stateDiagram-D77RDMKH-BW-g-IOz.js → stateDiagram-D77RDMKH-5AUnOL3S.js} +1 -1
- package/dist/assets/stateDiagram-v2-MP3YSRHH-CFM_nDL9.js +1 -0
- package/dist/assets/{swimlanes-42K2YHIH-LsuyeBte.js → swimlanes-42K2YHIH-BLnIwbnd.js} +1 -1
- package/dist/assets/swimlanesDiagram-VR7AAH4N-BRQJYnDN.js +8 -0
- package/dist/assets/{timeline-definition-24CTP7MA-B0cFka1R.js → timeline-definition-24CTP7MA-B1Yc7-na.js} +1 -1
- package/dist/assets/{vendor-codemirror-C5qqu4R7.js → vendor-codemirror-CjW0lHR5.js} +5 -5
- package/dist/assets/{vennDiagram-4TSXK5OY-D3HNCDR3.js → vennDiagram-4TSXK5OY-BFRtl-NI.js} +1 -1
- package/dist/assets/{wardleyDiagram-VM6X3IG4-DJ3Co8nb.js → wardleyDiagram-VM6X3IG4-BsR8iaD_.js} +1 -1
- package/dist/assets/{xychartDiagram-S5SC5T6Z-Cz5E9New.js → xychartDiagram-S5SC5T6Z-Crh1SwrA.js} +1 -1
- package/dist/favicon.png +0 -0
- package/dist/favicon.svg +1 -9
- package/dist/generate-icons.js +47 -46
- package/dist/icons/icon-128x128.png +0 -0
- package/dist/icons/icon-128x128.svg +1 -12
- package/dist/icons/icon-144x144.png +0 -0
- package/dist/icons/icon-144x144.svg +1 -12
- package/dist/icons/icon-152x152.png +0 -0
- package/dist/icons/icon-152x152.svg +1 -12
- package/dist/icons/icon-192x192.png +0 -0
- package/dist/icons/icon-192x192.svg +1 -12
- package/dist/icons/icon-384x384.png +0 -0
- package/dist/icons/icon-384x384.svg +1 -12
- package/dist/icons/icon-512x512.png +0 -0
- package/dist/icons/icon-512x512.svg +1 -12
- package/dist/icons/icon-72x72.png +0 -0
- package/dist/icons/icon-72x72.svg +1 -12
- package/dist/icons/icon-96x96.png +0 -0
- package/dist/icons/icon-96x96.svg +1 -12
- package/dist/index.html +3 -3
- 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-sources/logo-black.png +0 -0
- package/dist/logo-sources/logo-white.png +0 -0
- package/dist/logo.svg +1 -17
- package/dist/sw.js +1 -1
- package/dist-server/plugins/piecemaker-dossier/src/institutional-terms.js +91 -0
- package/dist-server/plugins/piecemaker-dossier/src/institutional-terms.js.map +1 -0
- package/dist-server/plugins/piecemaker-dossier/src/knowledge.js +112 -10
- package/dist-server/plugins/piecemaker-dossier/src/knowledge.js.map +1 -1
- package/dist-server/plugins/piecemaker-dossier/src/scan-result.js +5 -1
- package/dist-server/plugins/piecemaker-dossier/src/scan-result.js.map +1 -1
- package/dist-server/server/index.js +60 -26
- package/dist-server/server/index.js.map +1 -1
- package/dist-server/server/modules/cli/desktop-shortcut.js +80 -0
- package/dist-server/server/modules/cli/desktop-shortcut.js.map +1 -0
- package/dist-server/server/modules/cli/pwa-launcher.js +27 -4
- package/dist-server/server/modules/cli/pwa-launcher.js.map +1 -1
- package/dist-server/server/modules/database/repositories/projects.db.js +28 -6
- package/dist-server/server/modules/database/repositories/projects.db.js.map +1 -1
- package/dist-server/server/modules/database/tests/projects.db.integration.test.js +58 -1
- package/dist-server/server/modules/database/tests/projects.db.integration.test.js.map +1 -1
- package/dist-server/server/modules/plugins/plugins.routes.js +8 -1
- package/dist-server/server/modules/plugins/plugins.routes.js.map +1 -1
- package/dist-server/server/modules/projects/services/projects-with-sessions-fetch.service.js +2 -0
- package/dist-server/server/modules/projects/services/projects-with-sessions-fetch.service.js.map +1 -1
- package/dist-server/server/piecemaker/bodacc-search.js +191 -0
- package/dist-server/server/piecemaker/bodacc-search.js.map +1 -0
- package/dist-server/server/piecemaker/company-search.js +145 -0
- package/dist-server/server/piecemaker/company-search.js.map +1 -0
- package/dist-server/server/piecemaker/company-search.test.js +18 -0
- package/dist-server/server/piecemaker/company-search.test.js.map +1 -0
- package/dist-server/server/piecemaker/index.js +6 -2
- package/dist-server/server/piecemaker/index.js.map +1 -1
- package/dist-server/server/piecemaker/knowledge/index.js +9 -4
- package/dist-server/server/piecemaker/knowledge/index.js.map +1 -1
- package/dist-server/server/piecemaker/knowledge/pipeline.js +68 -58
- package/dist-server/server/piecemaker/knowledge/pipeline.js.map +1 -1
- package/dist-server/server/piecemaker/knowledge/routes.js +2 -4
- package/dist-server/server/piecemaker/knowledge/routes.js.map +1 -1
- package/dist-server/server/piecemaker/knowledge/scan-jobs.js +91 -0
- package/dist-server/server/piecemaker/knowledge/scan-jobs.js.map +1 -0
- package/dist-server/server/piecemaker/knowledge/service.js +18 -36
- package/dist-server/server/piecemaker/knowledge/service.js.map +1 -1
- package/dist-server/server/piecemaker/library/connector-installation.js +224 -0
- package/dist-server/server/piecemaker/library/connector-installation.js.map +1 -0
- package/dist-server/server/piecemaker/library/index.js +11 -1
- package/dist-server/server/piecemaker/library/index.js.map +1 -1
- package/dist-server/server/piecemaker/library/marketplace.js +102 -43
- package/dist-server/server/piecemaker/library/marketplace.js.map +1 -1
- package/dist-server/server/piecemaker/library/migrate.js +8 -1
- package/dist-server/server/piecemaker/library/migrate.js.map +1 -1
- package/dist-server/server/piecemaker/library/provider-agents.js +50 -0
- package/dist-server/server/piecemaker/library/provider-agents.js.map +1 -0
- package/dist-server/server/piecemaker/library/provider-connectors.js +80 -0
- package/dist-server/server/piecemaker/library/provider-connectors.js.map +1 -0
- package/dist-server/server/piecemaker/library/provider-skills.js +11 -2
- package/dist-server/server/piecemaker/library/provider-skills.js.map +1 -1
- package/dist-server/server/piecemaker/library/store.js +125 -35
- package/dist-server/server/piecemaker/library/store.js.map +1 -1
- package/dist-server/server/piecemaker/library/workspace-installation.js +53 -29
- package/dist-server/server/piecemaker/library/workspace-installation.js.map +1 -1
- package/dist-server/server/piecemaker/timesheet/service.js +4 -21
- package/dist-server/server/piecemaker/timesheet/service.js.map +1 -1
- package/package.json +3 -2
- package/product.config.json +1 -0
- package/scripts/piecemaker/cli/install-command.mjs +35 -9
- package/scripts/piecemaker/cli/lib/app-window.mjs +47 -0
- package/scripts/piecemaker/cli/lib/config.mjs +1 -0
- package/scripts/piecemaker/cli/lib/piecemaker-anonymizer.mjs +78 -0
- package/scripts/piecemaker/cli/lib/plugins.mjs +2 -0
- package/scripts/piecemaker/cli/lib/pwa.mjs +49 -13
- package/scripts/piecemaker/cli/lib/services.mjs +65 -4
- package/scripts/piecemaker/cli/piecemaker-command.test.mjs +25 -0
- package/scripts/piecemaker/cli/piecemaker.mjs +60 -45
- package/scripts/piecemaker/cli/piecemaker.ps1 +8 -0
- package/scripts/piecemaker/cli/piecemaker.sh +6 -1
- package/server/index.ts +52 -23
- package/server/modules/cli/desktop-shortcut.ts +89 -0
- package/server/modules/cli/pwa-launcher.ts +29 -4
- package/server/modules/database/repositories/projects.db.ts +28 -6
- package/server/modules/database/tests/projects.db.integration.test.ts +63 -1
- package/server/modules/plugins/plugins.routes.ts +8 -1
- package/server/modules/projects/services/projects-with-sessions-fetch.service.ts +5 -0
- package/server/piecemaker/anonymizer/client-config.cjs +10 -2
- package/server/piecemaker/anonymizer/dictionary.cjs +6 -72
- package/server/piecemaker/bodacc-search.ts +205 -0
- package/server/piecemaker/company-search.test.ts +26 -0
- package/server/piecemaker/company-search.ts +183 -0
- package/server/piecemaker/index.ts +8 -2
- package/server/piecemaker/knowledge/index.ts +9 -4
- package/server/piecemaker/knowledge/pipeline.ts +89 -57
- package/server/piecemaker/knowledge/routes.ts +2 -4
- package/server/piecemaker/knowledge/scan-jobs.ts +111 -0
- package/server/piecemaker/knowledge/service.ts +18 -33
- package/server/piecemaker/library/connector-installation.ts +248 -0
- package/server/piecemaker/library/index.ts +5 -1
- package/server/piecemaker/library/marketplace.ts +104 -39
- package/server/piecemaker/library/migrate.ts +8 -1
- package/server/piecemaker/library/provider-agents.ts +49 -0
- package/server/piecemaker/library/provider-connectors.ts +89 -0
- package/server/piecemaker/library/provider-skills.ts +10 -1
- package/server/piecemaker/library/store.ts +129 -36
- package/server/piecemaker/library/workspace-installation.ts +54 -29
- package/server/piecemaker/router.cjs +2 -1
- package/server/piecemaker/timesheet/service.ts +4 -16
- package/server/piecemaker/vendor/installer/steps/03-python-gliner.mjs +0 -27
- package/server/piecemaker/vendor/installer/steps/14-mxc-sandbox.mjs +1 -1
- package/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/substitution.cjs +2 -3
- package/server/piecemaker/vendor/piecemaker-plugin/skills/conversion-md/SKILL.md +3 -3
- package/server/piecemaker/vendor/websocket-server/admin-routes.cjs +3 -10
- package/server/piecemaker/vendor/websocket-server/originals-pipeline.cjs +135 -4
- package/server/piecemaker/vendor/websocket-server/process-group.cjs +95 -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 +280 -149
- 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/__pycache__/scanner_worker.cpython-312.pyc +0 -0
- package/server/piecemaker/vendor/websocket-server/scripts/presidio-gliner/scanner_worker.py +35 -29
- package/server/piecemaker/vendor/websocket-server/scripts/requirements.txt +0 -5
- package/server/shared/types.ts +1 -0
- package/dist/assets/channel-Dz3eE5vU.js +0 -1
- package/dist/assets/classDiagram-ZZMXUADV-BC1eKV2j.js +0 -1
- package/dist/assets/classDiagram-v2-VYDZK3BY-BC1eKV2j.js +0 -1
- package/dist/assets/index-DhwJpHCq.css +0 -1
- package/dist/assets/stateDiagram-v2-MP3YSRHH-BXuT4zDG.js +0 -1
- package/dist/assets/swimlanesDiagram-VR7AAH4N-B0eYrB4U.js +0 -8
- package/server/piecemaker/vendor/websocket-server/case-instructions.cjs +0 -111
- package/server/piecemaker/vendor/websocket-server/scripts/ADDRESS_ANONYMIZATION.md +0 -216
- package/server/piecemaker/vendor/websocket-server/scripts/DEDUPLICATION_CHANGES.md +0 -134
- package/server/piecemaker/vendor/websocket-server/scripts/GLINER2_README.md +0 -138
- package/server/piecemaker/vendor/websocket-server/scripts/presidio-gliner/AUDIT_PERF_QUALITE.md +0 -246
- package/server/piecemaker/vendor/websocket-server/scripts/presidio-gliner/MODELE_COREML.md +0 -62
- 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/build_coreml.py +0 -118
- package/server/piecemaker/vendor/websocket-server/scripts/presidio-gliner/coreml_runtime.py +0 -164
- package/server/piecemaker/vendor/websocket-server/scripts/presidio-gliner/presidio-gliner.py +0 -479
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import express, { type Request, type Response } from 'express';
|
|
2
|
+
|
|
3
|
+
const REGISTRE_PUBLIC_MCP_URL = 'https://registre-public.com/api/mcp';
|
|
4
|
+
const MCP_PROTOCOL_VERSION = '2025-03-26';
|
|
5
|
+
const MCP_TIMEOUT_MS = 20_000;
|
|
6
|
+
|
|
7
|
+
type JsonRpcPayload = {
|
|
8
|
+
result?: {
|
|
9
|
+
content?: Array<{ type?: string; text?: string }>;
|
|
10
|
+
isError?: boolean;
|
|
11
|
+
};
|
|
12
|
+
error?: { message?: string };
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export type CompanyDirector = { name: string; role: string };
|
|
16
|
+
|
|
17
|
+
export type CompanySearchFields = {
|
|
18
|
+
legalName: string;
|
|
19
|
+
legalForm: string;
|
|
20
|
+
status: string;
|
|
21
|
+
siren: string;
|
|
22
|
+
siret: string;
|
|
23
|
+
vat: string;
|
|
24
|
+
legalFormCode: string;
|
|
25
|
+
naf: string;
|
|
26
|
+
creationDate: string;
|
|
27
|
+
category: string;
|
|
28
|
+
address: string;
|
|
29
|
+
directors: CompanyDirector[];
|
|
30
|
+
finances: string[];
|
|
31
|
+
source: string;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export type CompanySearchResult = {
|
|
35
|
+
name: string;
|
|
36
|
+
siren: string;
|
|
37
|
+
summary: string;
|
|
38
|
+
url: string;
|
|
39
|
+
details: string;
|
|
40
|
+
fields: CompanySearchFields;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
function parseMcpPayload(body: string): JsonRpcPayload {
|
|
44
|
+
const trimmed = body.trim();
|
|
45
|
+
if (trimmed.startsWith('{')) return JSON.parse(trimmed) as JsonRpcPayload;
|
|
46
|
+
const payloads = trimmed
|
|
47
|
+
.split(/\r?\n/)
|
|
48
|
+
.filter((line) => line.startsWith('data:'))
|
|
49
|
+
.map((line) => line.slice(5).trim())
|
|
50
|
+
.filter(Boolean)
|
|
51
|
+
.map((line) => JSON.parse(line) as JsonRpcPayload);
|
|
52
|
+
const payload = payloads.at(-1);
|
|
53
|
+
if (!payload) throw new Error('Réponse PERS_MORALE_1 invalide.');
|
|
54
|
+
return payload;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async function mcpRequest(id: number, method: string, params: Record<string, unknown>, sessionId?: string): Promise<{ payload: JsonRpcPayload; sessionId?: string }> {
|
|
58
|
+
const response = await fetch(REGISTRE_PUBLIC_MCP_URL, {
|
|
59
|
+
method: 'POST',
|
|
60
|
+
headers: {
|
|
61
|
+
Accept: 'application/json, text/event-stream',
|
|
62
|
+
'Content-Type': 'application/json',
|
|
63
|
+
...(sessionId ? { 'Mcp-Session-Id': sessionId } : {}),
|
|
64
|
+
},
|
|
65
|
+
body: JSON.stringify({ jsonrpc: '2.0', id, method, params }),
|
|
66
|
+
signal: AbortSignal.timeout(MCP_TIMEOUT_MS),
|
|
67
|
+
});
|
|
68
|
+
const body = await response.text();
|
|
69
|
+
if (!response.ok) throw new Error(`Registre Public a répondu ${response.status}.`);
|
|
70
|
+
const payload = parseMcpPayload(body);
|
|
71
|
+
if (payload.error?.message) throw new Error(payload.error.message);
|
|
72
|
+
return { payload, sessionId: response.headers.get('mcp-session-id') || sessionId };
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
async function mcpNotification(method: string, params: Record<string, unknown>, sessionId?: string): Promise<void> {
|
|
76
|
+
const response = await fetch(REGISTRE_PUBLIC_MCP_URL, {
|
|
77
|
+
method: 'POST',
|
|
78
|
+
headers: {
|
|
79
|
+
Accept: 'application/json, text/event-stream',
|
|
80
|
+
'Content-Type': 'application/json',
|
|
81
|
+
...(sessionId ? { 'Mcp-Session-Id': sessionId } : {}),
|
|
82
|
+
},
|
|
83
|
+
body: JSON.stringify({ jsonrpc: '2.0', method, params }),
|
|
84
|
+
signal: AbortSignal.timeout(MCP_TIMEOUT_MS),
|
|
85
|
+
});
|
|
86
|
+
if (!response.ok) throw new Error(`Registre Public a répondu ${response.status}.`);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function resultText(payload: JsonRpcPayload): string {
|
|
90
|
+
const text = payload.result?.content?.filter((entry) => entry.type === 'text' && typeof entry.text === 'string').map((entry) => entry.text).join('\n').trim();
|
|
91
|
+
if (!text || payload.result?.isError) throw new Error('Registre Public n’a renvoyé aucun résultat exploitable.');
|
|
92
|
+
return text;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function lineValue(text: string, label: string): string {
|
|
96
|
+
const line = text.split('\n').find((entry) => entry.startsWith(label));
|
|
97
|
+
return line ? line.slice(label.length).trim() : '';
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export function parseSearchLine(line: string): { name: string; siren: string; summary: string } | null {
|
|
101
|
+
const match = /^-\s+(.+?)(?:\s+\(([^)]+)\))?\s+\|\s+SIREN\s+(\d{9})\s+\|\s+(.+?)\s+\|\s+NAF\s+([^|]+)\s+\|\s+(.+)$/.exec(line.trim());
|
|
102
|
+
if (!match) return null;
|
|
103
|
+
return { name: match[1].trim(), siren: match[3], summary: [match[2]?.trim(), match[4].trim(), `NAF ${match[5].trim()}`, match[6].trim()].filter(Boolean).join(' · ') };
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function parseDirectors(text: string): CompanyDirector[] {
|
|
107
|
+
const start = text.indexOf('Dirigeants:');
|
|
108
|
+
if (start < 0) return [];
|
|
109
|
+
const block = text.slice(start + 'Dirigeants:'.length).split(/\n(?=Finances\b|$)/)[0];
|
|
110
|
+
return block.split('\n').map((line) => /^-\s+(.+?)\s+\(([^)]+)\)$/.exec(line.trim())).filter((match): match is RegExpExecArray => Boolean(match)).map((match) => ({ name: match[1].trim(), role: match[2].trim() }));
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function parseCompanyDetails(text: string, fallback: { name: string; siren: string }): CompanySearchFields {
|
|
114
|
+
const heading = /^#\s+(.+?)\s+\(([^)]+)\)\s+\(([^)]+)\)/m.exec(text);
|
|
115
|
+
const formAndActivity = lineValue(text, 'Forme juridique (code) :');
|
|
116
|
+
const [legalFormCode, nafPart] = formAndActivity.split('|').map((value) => value.trim());
|
|
117
|
+
const creation = lineValue(text, 'Création :');
|
|
118
|
+
const [creationDate, categoryPart] = creation.split('|').map((value) => value.trim());
|
|
119
|
+
return {
|
|
120
|
+
legalName: heading?.[1]?.trim() || fallback.name,
|
|
121
|
+
legalForm: '',
|
|
122
|
+
status: heading?.[3]?.trim() || '',
|
|
123
|
+
siren: lineValue(text, 'SIREN :').split('|')[0].trim() || fallback.siren,
|
|
124
|
+
siret: (lineValue(text, 'SIREN :').match(/SIRET siège\s*:\s*([^|]+)/)?.[1] || '').trim(),
|
|
125
|
+
vat: lineValue(text, 'N° TVA intracommunautaire :'),
|
|
126
|
+
legalFormCode: legalFormCode || '',
|
|
127
|
+
naf: nafPart?.replace(/^NAF\s*:\s*/, '') || '',
|
|
128
|
+
creationDate: creationDate || '',
|
|
129
|
+
category: categoryPart?.replace(/^Catégorie\s*:\s*/, '') || '',
|
|
130
|
+
address: lineValue(text, 'Adresse du siège :'),
|
|
131
|
+
directors: parseDirectors(text),
|
|
132
|
+
finances: text.split('\n').filter((line) => line.startsWith('Finances ')).map((line) => line.trim()),
|
|
133
|
+
source: lineValue(text, 'Fiche web :'),
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
async function callTool(tool: string, args: Record<string, unknown>): Promise<string> {
|
|
138
|
+
let sessionId: string | undefined;
|
|
139
|
+
const initialized = await mcpRequest(1, 'initialize', {
|
|
140
|
+
protocolVersion: MCP_PROTOCOL_VERSION,
|
|
141
|
+
capabilities: {},
|
|
142
|
+
clientInfo: { name: 'piecemaker-dossier', version: '0.1.0' },
|
|
143
|
+
});
|
|
144
|
+
sessionId = initialized.sessionId;
|
|
145
|
+
await mcpNotification('notifications/initialized', {}, sessionId);
|
|
146
|
+
const result = await mcpRequest(2, 'tools/call', { name: tool, arguments: args }, sessionId);
|
|
147
|
+
return resultText(result.payload);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
async function searchCompanies(query: string): Promise<CompanySearchResult[]> {
|
|
151
|
+
const searchText = await callTool('rechercher_entreprise', { query });
|
|
152
|
+
const candidates = searchText.split('\n').map(parseSearchLine).filter((entry): entry is { name: string; siren: string; summary: string } => Boolean(entry));
|
|
153
|
+
const results = await Promise.all(candidates.map(async (candidate) => {
|
|
154
|
+
const details = await callTool('fiche_entreprise', { siren: candidate.siren });
|
|
155
|
+
const fields = parseCompanyDetails(details, candidate);
|
|
156
|
+
return {
|
|
157
|
+
name: fields.legalName || candidate.name,
|
|
158
|
+
siren: fields.siren || candidate.siren,
|
|
159
|
+
summary: candidate.summary,
|
|
160
|
+
url: fields.source,
|
|
161
|
+
details,
|
|
162
|
+
fields,
|
|
163
|
+
};
|
|
164
|
+
}));
|
|
165
|
+
return results;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export function createCompanySearchRouter() {
|
|
169
|
+
const router = express.Router();
|
|
170
|
+
router.post('/company-search', async (request: Request, response: Response) => {
|
|
171
|
+
const query = typeof request.body?.query === 'string' ? request.body.query.trim().slice(0, 200) : '';
|
|
172
|
+
if (!query) {
|
|
173
|
+
response.status(400).json({ error: 'Un nom ou un SIREN est requis.' });
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
try {
|
|
177
|
+
response.json({ query, results: await searchCompanies(query) });
|
|
178
|
+
} catch (error) {
|
|
179
|
+
response.status(502).json({ error: error instanceof Error ? error.message : 'Recherche Registre Public impossible.' });
|
|
180
|
+
}
|
|
181
|
+
});
|
|
182
|
+
return router;
|
|
183
|
+
}
|
|
@@ -6,6 +6,7 @@ import type { Router } from 'express';
|
|
|
6
6
|
import { providerRuntimeService, sessionsService } from '@/modules/providers/index.js';
|
|
7
7
|
import { findApplicationRoot, getModuleDirectory } from '@/shared/utils.js';
|
|
8
8
|
|
|
9
|
+
|
|
9
10
|
import { startRequiredAnonymizer } from './anonymizer/lifecycle.js';
|
|
10
11
|
import { createCitationStore } from './harness/citation-store.js';
|
|
11
12
|
import { installChatCitationHarness } from './harness/chat-harness.js';
|
|
@@ -13,6 +14,8 @@ import { createCitationsRouter } from './harness/citations.routes.js';
|
|
|
13
14
|
import { startLibraryBackend, installLibraryRuntime } from './library/index.js';
|
|
14
15
|
import { createKnowledgeBackend } from './knowledge/index.js';
|
|
15
16
|
import { createTimesheetBackend } from './timesheet/index.js';
|
|
17
|
+
import { createCompanySearchRouter } from './company-search.js';
|
|
18
|
+
import { createBodaccSearchRouter } from './bodacc-search.js';
|
|
16
19
|
|
|
17
20
|
/**
|
|
18
21
|
* Point d'entrée PieceMaker. Les modules de `server/piecemaker/vendor/` sont du
|
|
@@ -35,11 +38,12 @@ type PieceMakerRuntimeStatus = {
|
|
|
35
38
|
type PieceMakerVendorModule = {
|
|
36
39
|
createPieceMakerRouter(options?: { getRuntimeStatus?: () => PieceMakerRuntimeStatus; anonymizer?: unknown }): Router;
|
|
37
40
|
piecemakerHome(): string;
|
|
41
|
+
stopOriginalsJobs(): Promise<void>;
|
|
38
42
|
};
|
|
39
43
|
|
|
40
44
|
const vendor = createRequire(import.meta.url)(routerPath) as PieceMakerVendorModule;
|
|
41
45
|
|
|
42
|
-
export const { piecemakerHome } = vendor;
|
|
46
|
+
export const { piecemakerHome, stopOriginalsJobs } = vendor;
|
|
43
47
|
const { createAnonymizerService } = createRequire(import.meta.url)(path.join(applicationRoot, 'server/piecemaker/anonymizer/service.cjs'));
|
|
44
48
|
const anonymizer = createAnonymizerService({ homeDir: piecemakerHome(), required: true });
|
|
45
49
|
const ensureProxy = await startRequiredAnonymizer(anonymizer);
|
|
@@ -56,7 +60,9 @@ export function createPieceMakerRouter(options: { getRuntimeStatus?: () => Piece
|
|
|
56
60
|
try { sessionsService.getSessionDetailsById(id); return true; } catch { return false; }
|
|
57
61
|
}));
|
|
58
62
|
router.use(timesheet);
|
|
59
|
-
router.use(knowledge);
|
|
63
|
+
router.use(knowledge.router);
|
|
64
|
+
router.use(createCompanySearchRouter());
|
|
65
|
+
router.use(createBodaccSearchRouter());
|
|
60
66
|
return router;
|
|
61
67
|
}
|
|
62
68
|
export type { PieceMakerRuntimeStatus };
|
|
@@ -3,6 +3,7 @@ import express from 'express';
|
|
|
3
3
|
import { getConnection, projectsDb } from '@/modules/database/index.js';
|
|
4
4
|
|
|
5
5
|
import { KnowledgeStore } from '../../../plugins/piecemaker-dossier/src/knowledge.js';
|
|
6
|
+
|
|
6
7
|
import { createKnowledgePipeline } from './pipeline.js';
|
|
7
8
|
import { createKnowledgeRouter } from './routes.js';
|
|
8
9
|
import { createKnowledgeService } from './service.js';
|
|
@@ -10,13 +11,17 @@ import { createKnowledgeService } from './service.js';
|
|
|
10
11
|
export function createKnowledgeBackend(applicationRoot: string) {
|
|
11
12
|
const router = express.Router();
|
|
12
13
|
let knowledgeRouter: express.Router | null = null;
|
|
14
|
+
let store: KnowledgeStore | null = null;
|
|
15
|
+
const getStore = () => {
|
|
16
|
+
if (!store) store = new KnowledgeStore(getConnection());
|
|
17
|
+
return store;
|
|
18
|
+
};
|
|
13
19
|
router.use((request, response, next) => {
|
|
14
20
|
if (!knowledgeRouter) {
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
knowledgeRouter = createKnowledgeRouter(createKnowledgeService(store, projectsDb, pipeline));
|
|
21
|
+
const pipeline = createKnowledgePipeline({ applicationRoot, projects: projectsDb, store: getStore() });
|
|
22
|
+
knowledgeRouter = createKnowledgeRouter(createKnowledgeService(getStore(), projectsDb, pipeline));
|
|
18
23
|
}
|
|
19
24
|
knowledgeRouter(request, response, next);
|
|
20
25
|
});
|
|
21
|
-
return router;
|
|
26
|
+
return { router };
|
|
22
27
|
}
|
|
@@ -1,12 +1,14 @@
|
|
|
1
|
+
import { createRequire } from 'node:module';
|
|
1
2
|
import crypto from 'node:crypto';
|
|
2
3
|
import fs from 'node:fs';
|
|
3
4
|
import os from 'node:os';
|
|
4
5
|
import path from 'node:path';
|
|
5
|
-
import { spawn } from 'node:child_process';
|
|
6
6
|
|
|
7
7
|
import type { KnowledgeStore } from '../../../plugins/piecemaker-dossier/src/knowledge.js';
|
|
8
|
-
import type { GlinerDocument, GlinerMappingDocument, JsonData } from '../../../plugins/piecemaker-dossier/src/types.js';
|
|
9
8
|
import { persistScanResult, scanResultOperations } from '../../../plugins/piecemaker-dossier/src/scan-result.js';
|
|
9
|
+
import type { GlinerDocument, GlinerMappingDocument, JsonData } from '../../../plugins/piecemaker-dossier/src/types.js';
|
|
10
|
+
|
|
11
|
+
import type { KnowledgeScanProgress } from './scan-jobs.js';
|
|
10
12
|
|
|
11
13
|
type ProjectLookup = {
|
|
12
14
|
getProjectById(projectId: string): { project_id: string; project_path: string } | null;
|
|
@@ -19,6 +21,29 @@ type PipelineOptions = {
|
|
|
19
21
|
pythonPath?: string;
|
|
20
22
|
};
|
|
21
23
|
|
|
24
|
+
type OriginalFile = { path: string; resource?: boolean };
|
|
25
|
+
|
|
26
|
+
type OriginalsPipeline = {
|
|
27
|
+
caseMappingFile(caseRoot: string): string;
|
|
28
|
+
listOriginals(caseRoot: string): Promise<OriginalFile[]>;
|
|
29
|
+
runManagedPythonJob(options: {
|
|
30
|
+
action: 'convert' | 'anonymize';
|
|
31
|
+
script: string;
|
|
32
|
+
args: string[];
|
|
33
|
+
onProgress?: (progress: KnowledgeScanProgress) => void;
|
|
34
|
+
signal?: AbortSignal;
|
|
35
|
+
}): Promise<unknown>;
|
|
36
|
+
writeCaseMapping(caseRoot: string, document: GlinerMappingDocument): unknown;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const require = createRequire(import.meta.url);
|
|
40
|
+
const {
|
|
41
|
+
caseMappingFile,
|
|
42
|
+
listOriginals,
|
|
43
|
+
runManagedPythonJob,
|
|
44
|
+
writeCaseMapping,
|
|
45
|
+
} = require('../vendor/websocket-server/originals-pipeline.cjs') as OriginalsPipeline;
|
|
46
|
+
|
|
22
47
|
const SUPPORTED_EXTENSIONS = new Set(['.pdf', '.docx', '.doc', '.odt', '.rtf', '.txt', '.md', '.png', '.jpg', '.jpeg', '.tif', '.tiff', '.bmp']);
|
|
23
48
|
|
|
24
49
|
function relativeKey(projectPath: string, filePath: string): string {
|
|
@@ -26,15 +51,18 @@ function relativeKey(projectPath: string, filePath: string): string {
|
|
|
26
51
|
return crypto.createHash('sha256').update(relative).digest('hex');
|
|
27
52
|
}
|
|
28
53
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
54
|
+
/** Pièces scannées par défaut : même liste que l'administration, récursive, hors Markdown généré. */
|
|
55
|
+
export async function defaultScanFiles(projectPath: string): Promise<string[]> {
|
|
56
|
+
const root = fs.realpathSync(projectPath);
|
|
57
|
+
const originals = await listOriginals(root);
|
|
58
|
+
return originals
|
|
59
|
+
.filter((file) => !file.resource)
|
|
60
|
+
.map((file) => path.resolve(root, file.path));
|
|
33
61
|
}
|
|
34
62
|
|
|
35
|
-
function safeFiles(projectPath: string, requested: unknown): string[] {
|
|
63
|
+
async function safeFiles(projectPath: string, requested: unknown): Promise<string[]> {
|
|
36
64
|
const root = fs.realpathSync(projectPath);
|
|
37
|
-
const candidates = Array.isArray(requested) && requested.length ? requested :
|
|
65
|
+
const candidates = Array.isArray(requested) && requested.length ? requested : await defaultScanFiles(root);
|
|
38
66
|
return [...new Set(candidates.map((candidate) => {
|
|
39
67
|
const raw = String(candidate || '');
|
|
40
68
|
const resolved = path.resolve(root, raw);
|
|
@@ -45,29 +73,18 @@ function safeFiles(projectPath: string, requested: unknown): string[] {
|
|
|
45
73
|
}))];
|
|
46
74
|
}
|
|
47
75
|
|
|
48
|
-
function runProcess(executable: string, args: string[], cwd: string): Promise<{ stdout: string; stderr: string }> {
|
|
49
|
-
return new Promise((resolve, reject) => {
|
|
50
|
-
const child = spawn(executable, args, { cwd, env: process.env, windowsHide: true, stdio: ['ignore', 'pipe', 'pipe'] });
|
|
51
|
-
let stdout = '';
|
|
52
|
-
let stderr = '';
|
|
53
|
-
child.stdout.setEncoding('utf8');
|
|
54
|
-
child.stderr.setEncoding('utf8');
|
|
55
|
-
child.stdout.on('data', (chunk) => { stdout += chunk; });
|
|
56
|
-
child.stderr.on('data', (chunk) => { stderr += chunk; });
|
|
57
|
-
child.once('error', reject);
|
|
58
|
-
child.once('close', (code) => {
|
|
59
|
-
if (code === 0) resolve({ stdout, stderr });
|
|
60
|
-
else reject(new Error(stderr.trim() || stdout.trim() || `Pipeline failed with code ${code ?? 1}.`));
|
|
61
|
-
});
|
|
62
|
-
});
|
|
63
|
-
}
|
|
64
|
-
|
|
65
76
|
function parseObject(filePath: string): JsonData {
|
|
66
|
-
const parsed = JSON.parse(fs.readFileSync(filePath, 'utf8').replace(
|
|
77
|
+
const parsed = JSON.parse(fs.readFileSync(filePath, 'utf8').replace(/^\uFEFF/, '')) as unknown;
|
|
67
78
|
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) throw new Error('Invalid pipeline output.');
|
|
68
79
|
return parsed as JsonData;
|
|
69
80
|
}
|
|
70
81
|
|
|
82
|
+
function readDocumentIndex(projectPath: string): JsonData {
|
|
83
|
+
const indexPath = path.join(projectPath, '.piecemaker', 'document-index.json');
|
|
84
|
+
if (!fs.existsSync(indexPath)) return { documents: {} };
|
|
85
|
+
return parseObject(indexPath);
|
|
86
|
+
}
|
|
87
|
+
|
|
71
88
|
function documentsFromIndex(projectPath: string, files: string[], documentIndex: JsonData): GlinerDocument[] {
|
|
72
89
|
const entries = documentIndex.documents && typeof documentIndex.documents === 'object' && !Array.isArray(documentIndex.documents)
|
|
73
90
|
? documentIndex.documents as Record<string, JsonData>
|
|
@@ -128,46 +145,61 @@ function textValue(value: unknown): string {
|
|
|
128
145
|
return typeof value === 'string' ? value.trim() : '';
|
|
129
146
|
}
|
|
130
147
|
|
|
148
|
+
async function withPythonPath<T>(pythonPath: string, run: () => Promise<T>): Promise<T> {
|
|
149
|
+
const previous = process.env.PYTHON_PATH;
|
|
150
|
+
process.env.PYTHON_PATH = pythonPath;
|
|
151
|
+
try {
|
|
152
|
+
return await run();
|
|
153
|
+
} finally {
|
|
154
|
+
if (previous === undefined) delete process.env.PYTHON_PATH;
|
|
155
|
+
else process.env.PYTHON_PATH = previous;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
131
159
|
export function createKnowledgePipeline(options: PipelineOptions) {
|
|
132
160
|
const script = path.join(options.applicationRoot, 'server', 'piecemaker', 'vendor', 'websocket-server', 'scripts', 'convert_and_scan_pipeline.py');
|
|
133
161
|
return {
|
|
134
|
-
async scan(projectId: string, requestedFiles?: unknown) {
|
|
162
|
+
async scan(projectId: string, requestedFiles?: unknown, onProgress?: (progress: KnowledgeScanProgress) => void, signal?: AbortSignal) {
|
|
135
163
|
const project = options.projects.getProjectById(projectId);
|
|
136
164
|
if (!project) throw new Error('Project not found.');
|
|
137
165
|
const projectPath = fs.realpathSync(project.project_path);
|
|
138
|
-
const
|
|
166
|
+
const explicitFiles = Array.isArray(requestedFiles) && requestedFiles.length > 0;
|
|
167
|
+
const files = await safeFiles(projectPath, requestedFiles);
|
|
139
168
|
if (!files.length) throw new Error('No supported file to scan.');
|
|
140
|
-
const
|
|
141
|
-
const
|
|
142
|
-
const stateFile = path.join(temporaryRoot, 'state.json');
|
|
169
|
+
const mappingFile = caseMappingFile(projectPath);
|
|
170
|
+
const stateFile = path.join(projectPath, '.piecemaker', 'anonymization-state.json');
|
|
143
171
|
const outputDirectory = path.join(projectPath, 'Fichiers convertis PieceMaker');
|
|
172
|
+
fs.mkdirSync(path.dirname(mappingFile), { recursive: true });
|
|
173
|
+
fs.mkdirSync(path.dirname(stateFile), { recursive: true });
|
|
144
174
|
fs.mkdirSync(outputDirectory, { recursive: true });
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
175
|
+
fs.writeFileSync(mappingFile, JSON.stringify(mappingSeed(projectId, projectPath, options.store)), { mode: 0o600 });
|
|
176
|
+
const args = [
|
|
177
|
+
...files,
|
|
178
|
+
'-o',
|
|
179
|
+
outputDirectory,
|
|
180
|
+
'--mapping-file',
|
|
181
|
+
mappingFile,
|
|
182
|
+
'--case-root',
|
|
183
|
+
projectPath,
|
|
184
|
+
'--state-file',
|
|
185
|
+
stateFile,
|
|
186
|
+
];
|
|
187
|
+
if (!explicitFiles) args.push('--skip-existing');
|
|
188
|
+
await withPythonPath(pythonExecutable(options.pythonPath), () => runManagedPythonJob({
|
|
189
|
+
action: 'anonymize',
|
|
190
|
+
script,
|
|
191
|
+
args,
|
|
192
|
+
onProgress,
|
|
193
|
+
signal,
|
|
194
|
+
}));
|
|
195
|
+
const mapping = parseObject(mappingFile) as GlinerMappingDocument;
|
|
196
|
+
writeCaseMapping(projectPath, mapping);
|
|
197
|
+
const documents = documentsFromIndex(projectPath, files, readDocumentIndex(projectPath));
|
|
198
|
+
const scanResult = { projectId, mapping, documents };
|
|
199
|
+
const result = explicitFiles
|
|
200
|
+
? options.store.update({ projectId, operations: scanResultOperations(scanResult) })
|
|
201
|
+
: persistScanResult(scanResult, options.store);
|
|
202
|
+
return { ...result, documents: documents.length };
|
|
171
203
|
},
|
|
172
204
|
};
|
|
173
205
|
}
|
|
@@ -17,13 +17,11 @@ export function createKnowledgeRouter(service: KnowledgeService) {
|
|
|
17
17
|
response.status(statusFor(error)).json({ error: error instanceof Error ? error.message : 'Knowledge operation failed.' });
|
|
18
18
|
});
|
|
19
19
|
};
|
|
20
|
-
router.get('/knowledge/overview', (request, response) => respond(() => service.overview(request.query.projectId), response));
|
|
21
|
-
router.get('/knowledge/mapping', (request, response) => respond(() => service.mapping(request.query.projectId), response));
|
|
22
|
-
router.get('/knowledge/chronology', (request, response) => respond(() => service.chronology(request.query.projectId), response));
|
|
23
20
|
router.get('/knowledge/graph', (request, response) => respond(() => service.graph(request.query.projectId), response));
|
|
24
|
-
router.get('/knowledge/agents', (request, response) => respond(() => service.agents(request.query.projectId), response));
|
|
25
21
|
router.post('/knowledge/query', (request, response) => respond(() => service.query(request.body), response));
|
|
26
22
|
router.post('/knowledge/update', (request, response) => respond(() => service.update(request.body), response));
|
|
27
23
|
router.post('/knowledge/scan', (request, response) => respond(() => service.scan(request.body?.projectId, request.body?.files), response));
|
|
24
|
+
router.get('/knowledge/scan/job', (request, response) => respond(() => service.scanJob(request.query.id, request.query.projectId), response));
|
|
25
|
+
router.post('/knowledge/scan/cancel', (request, response) => respond(() => service.cancelScan(request.body?.id, request.body?.projectId), response));
|
|
28
26
|
return router;
|
|
29
27
|
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import crypto from 'node:crypto';
|
|
2
|
+
|
|
3
|
+
export type KnowledgeScanPhase = 'convert' | 'scan' | 'commit';
|
|
4
|
+
|
|
5
|
+
export type KnowledgeScanProgress = {
|
|
6
|
+
phase: KnowledgeScanPhase;
|
|
7
|
+
percent: number;
|
|
8
|
+
processed: number;
|
|
9
|
+
total: number;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export type KnowledgeScanJob = {
|
|
13
|
+
id: string;
|
|
14
|
+
projectId: string;
|
|
15
|
+
source: 'knowledge';
|
|
16
|
+
action: 'anonymize';
|
|
17
|
+
state: 'running' | 'done' | 'error' | 'cancelled';
|
|
18
|
+
phase: KnowledgeScanPhase;
|
|
19
|
+
percent: number;
|
|
20
|
+
processed: number;
|
|
21
|
+
total: number;
|
|
22
|
+
error: string | null;
|
|
23
|
+
startedAt: string;
|
|
24
|
+
finishedAt: string | null;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const COMPLETED_RETENTION_MS = 5 * 60_000;
|
|
28
|
+
|
|
29
|
+
function isPending(job: KnowledgeScanJob): boolean {
|
|
30
|
+
return job.state === 'running';
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function createKnowledgeScanJobs() {
|
|
34
|
+
const jobs = new Map<string, KnowledgeScanJob>();
|
|
35
|
+
const controllers = new Map<string, AbortController>();
|
|
36
|
+
|
|
37
|
+
const prune = () => {
|
|
38
|
+
const deadline = Date.now() - COMPLETED_RETENTION_MS;
|
|
39
|
+
for (const [id, job] of jobs) {
|
|
40
|
+
if (isPending(job)) continue;
|
|
41
|
+
if (Date.parse(job.finishedAt || job.startedAt) < deadline) jobs.delete(id);
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const runningForProject = (projectId: string): KnowledgeScanJob | null => {
|
|
46
|
+
for (const job of jobs.values()) if (job.projectId === projectId && isPending(job)) return job;
|
|
47
|
+
return null;
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
return {
|
|
51
|
+
start(projectId: string, run: (report: (progress: KnowledgeScanProgress) => void, signal: AbortSignal) => Promise<unknown>): KnowledgeScanJob {
|
|
52
|
+
prune();
|
|
53
|
+
const running = runningForProject(projectId);
|
|
54
|
+
if (running) return running;
|
|
55
|
+
const job: KnowledgeScanJob = {
|
|
56
|
+
id: crypto.randomUUID(),
|
|
57
|
+
projectId,
|
|
58
|
+
source: 'knowledge',
|
|
59
|
+
action: 'anonymize',
|
|
60
|
+
state: 'running',
|
|
61
|
+
phase: 'convert',
|
|
62
|
+
percent: 0,
|
|
63
|
+
processed: 0,
|
|
64
|
+
total: 0,
|
|
65
|
+
error: null,
|
|
66
|
+
startedAt: new Date().toISOString(),
|
|
67
|
+
finishedAt: null,
|
|
68
|
+
};
|
|
69
|
+
jobs.set(job.id, job);
|
|
70
|
+
const report = (progress: KnowledgeScanProgress) => {
|
|
71
|
+
if (job.state !== 'running') return;
|
|
72
|
+
job.phase = progress.phase;
|
|
73
|
+
job.percent = Math.max(job.percent, Math.min(100, progress.percent));
|
|
74
|
+
job.processed = progress.processed;
|
|
75
|
+
job.total = progress.total || job.total;
|
|
76
|
+
};
|
|
77
|
+
const controller = new AbortController();
|
|
78
|
+
controllers.set(job.id, controller);
|
|
79
|
+
run(report, controller.signal).then(() => {
|
|
80
|
+
if (job.state !== 'running') return;
|
|
81
|
+
job.state = 'done';
|
|
82
|
+
job.phase = 'commit';
|
|
83
|
+
job.percent = 100;
|
|
84
|
+
}, (error: unknown) => {
|
|
85
|
+
if (job.state !== 'running') return;
|
|
86
|
+
job.state = 'error';
|
|
87
|
+
job.error = error instanceof Error ? error.message : String(error);
|
|
88
|
+
}).finally(() => {
|
|
89
|
+
controllers.delete(job.id);
|
|
90
|
+
job.finishedAt = job.finishedAt || new Date().toISOString();
|
|
91
|
+
});
|
|
92
|
+
return job;
|
|
93
|
+
},
|
|
94
|
+
get(id: unknown): KnowledgeScanJob | null {
|
|
95
|
+
prune();
|
|
96
|
+
return typeof id === 'string' ? jobs.get(id) || null : null;
|
|
97
|
+
},
|
|
98
|
+
runningForProject(projectId: string): KnowledgeScanJob | null {
|
|
99
|
+
prune();
|
|
100
|
+
return runningForProject(projectId);
|
|
101
|
+
},
|
|
102
|
+
cancel(job: KnowledgeScanJob | null): KnowledgeScanJob | null {
|
|
103
|
+
if (!job || job.state !== 'running') return job;
|
|
104
|
+
job.state = 'cancelled';
|
|
105
|
+
job.finishedAt = new Date().toISOString();
|
|
106
|
+
controllers.get(job.id)?.abort();
|
|
107
|
+
controllers.delete(job.id);
|
|
108
|
+
return job;
|
|
109
|
+
},
|
|
110
|
+
};
|
|
111
|
+
}
|
|
@@ -1,11 +1,10 @@
|
|
|
1
|
-
import fs from 'node:fs';
|
|
2
|
-
import path from 'node:path';
|
|
3
|
-
|
|
4
1
|
import type { KnowledgeStore } from '../../../plugins/piecemaker-dossier/src/knowledge.js';
|
|
5
2
|
import { exclusionNodeOperation } from '../../../plugins/piecemaker-dossier/src/scan-result.js';
|
|
6
3
|
import type { KnowledgeQueryInput, KnowledgeUpdateInput } from '../../../plugins/piecemaker-dossier/src/types.js';
|
|
4
|
+
|
|
7
5
|
import { legacyExclusions } from './pipeline.js';
|
|
8
6
|
import type { createKnowledgePipeline } from './pipeline.js';
|
|
7
|
+
import { createKnowledgeScanJobs } from './scan-jobs.js';
|
|
9
8
|
|
|
10
9
|
type ProjectLookup = {
|
|
11
10
|
getProjectById(projectId: string): { project_id: string; project_path: string } | null;
|
|
@@ -17,6 +16,7 @@ function projectId(value: unknown): string {
|
|
|
17
16
|
}
|
|
18
17
|
|
|
19
18
|
export function createKnowledgeService(store: KnowledgeStore, projects: ProjectLookup, pipeline: ReturnType<typeof createKnowledgePipeline>) {
|
|
19
|
+
const scanJobs = createKnowledgeScanJobs();
|
|
20
20
|
const ensureProject = (value: unknown): string => {
|
|
21
21
|
const id = projectId(value);
|
|
22
22
|
if (!projects.getProjectById(id)) throw new Error('Project not found.');
|
|
@@ -39,42 +39,27 @@ export function createKnowledgeService(store: KnowledgeStore, projects: ProjectL
|
|
|
39
39
|
update(input: KnowledgeUpdateInput) {
|
|
40
40
|
return store.update({ ...input, projectId: ensureProject(input.projectId) });
|
|
41
41
|
},
|
|
42
|
-
overview(value: unknown) {
|
|
43
|
-
const id = ensureProject(value);
|
|
44
|
-
const nodes = snapshot(id).nodes;
|
|
45
|
-
const counts = nodes.reduce<Record<string, number>>((result, node) => {
|
|
46
|
-
result[node.kind] = (result[node.kind] || 0) + 1;
|
|
47
|
-
return result;
|
|
48
|
-
}, {});
|
|
49
|
-
return { projectId: id, counts, total: nodes.length };
|
|
50
|
-
},
|
|
51
|
-
mapping(value: unknown) {
|
|
52
|
-
const id = ensureProject(value);
|
|
53
|
-
const current = snapshot(id);
|
|
54
|
-
return { projectId: id, nodes: current.nodes.filter((node) => node.kind !== 'document'), mappings: current.mappings, exclusions: current.exclusions || [] };
|
|
55
|
-
},
|
|
56
|
-
chronology(value: unknown) {
|
|
57
|
-
const id = ensureProject(value);
|
|
58
|
-
const current = snapshot(id);
|
|
59
|
-
return { projectId: id, documents: current.nodes.filter((node) => node.kind === 'document'), links: current.links.filter((link) => link.relation === 'mentions') };
|
|
60
|
-
},
|
|
61
42
|
graph(value: unknown) {
|
|
62
43
|
const id = ensureProject(value);
|
|
63
44
|
return snapshot(id);
|
|
64
45
|
},
|
|
65
|
-
|
|
46
|
+
scan(value: unknown, files?: unknown) {
|
|
66
47
|
const id = ensureProject(value);
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
return { projectId: id, content: fs.readFileSync(real, 'utf8').slice(0, 2_000_000), exists: true };
|
|
48
|
+
return {
|
|
49
|
+
job: scanJobs.start(id, async (report, signal) => {
|
|
50
|
+
const result = await pipeline.scan(id, files, report, signal);
|
|
51
|
+
store.markAnonymizationComplete(id);
|
|
52
|
+
return result;
|
|
53
|
+
}),
|
|
54
|
+
};
|
|
75
55
|
},
|
|
76
|
-
|
|
77
|
-
|
|
56
|
+
scanJob(jobId: unknown, value?: unknown) {
|
|
57
|
+
const job = scanJobs.get(jobId) || (value ? scanJobs.runningForProject(ensureProject(value)) : null);
|
|
58
|
+
return { job };
|
|
59
|
+
},
|
|
60
|
+
cancelScan(jobId: unknown, value?: unknown) {
|
|
61
|
+
const job = scanJobs.get(jobId) || (value ? scanJobs.runningForProject(ensureProject(value)) : null);
|
|
62
|
+
return { job: scanJobs.cancel(job) };
|
|
78
63
|
},
|
|
79
64
|
};
|
|
80
65
|
}
|