@piecemaker-legal/piecemaker 2.0.10 → 2.0.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (136) hide show
  1. package/dist/assets/{FindReplaceDialog-KU5TEIBJ-CQ76sPaJ.js → FindReplaceDialog-KU5TEIBJ-BBxf1he7.js} +1 -1
  2. package/dist/assets/{FootnotePropertiesDialog-W3YHM4BV-BpgYQvvd.js → FootnotePropertiesDialog-W3YHM4BV-BYqp6U4x.js} +1 -1
  3. package/dist/assets/{HyperlinkDialog-ZYH7ZHRF-z08UbYbL.js → HyperlinkDialog-ZYH7ZHRF-BVC9zOMs.js} +4 -4
  4. package/dist/assets/{ImagePositionDialog-HEWYR6Q3-CFzOnzAt.js → ImagePositionDialog-HEWYR6Q3-Xn2mC64y.js} +1 -1
  5. package/dist/assets/{ImagePropertiesDialog-RAB47C6M-D-ThGU-b.js → ImagePropertiesDialog-RAB47C6M-CFcdqEif.js} +1 -1
  6. package/dist/assets/{PageSetupDialog-AIGIXO3E-_PYIO8eb.js → PageSetupDialog-AIGIXO3E-DECaeRS9.js} +1 -1
  7. package/dist/assets/{SplitCellDialog-IFBKYCD6-Cb9uQOB8.js → SplitCellDialog-IFBKYCD6-ByYa04H_.js} +1 -1
  8. package/dist/assets/{TablePropertiesDialog-TMLKGOOW-12vmjb6B.js → TablePropertiesDialog-TMLKGOOW-BuyqZB5A.js} +1 -1
  9. package/dist/assets/{WatermarkDialog-KRL7WMIS-C-d8rzt2.js → WatermarkDialog-KRL7WMIS-ByZ1Acr2.js} +1 -1
  10. package/dist/assets/{abnfDiagram-VCTEODGH-B4OsX7eF.js → abnfDiagram-VCTEODGH-BtttzdZA.js} +1 -1
  11. package/dist/assets/{arc-BK1oUIk7.js → arc-DN4m1tLs.js} +1 -1
  12. package/dist/assets/{architectureDiagram-5GKGNRK7-CfaFI6L7.js → architectureDiagram-5GKGNRK7-Bt-zFumG.js} +1 -1
  13. package/dist/assets/{blockDiagram-I7D4REHJ-DQEI2azY.js → blockDiagram-I7D4REHJ-D14V8v4j.js} +1 -1
  14. package/dist/assets/{c4Diagram-7LVT6UL2-DbravpVi.js → c4Diagram-7LVT6UL2-BynDo1-W.js} +1 -1
  15. package/dist/assets/channel-C4gJXzYa.js +1 -0
  16. package/dist/assets/{chunk-2Q5K7J3B-BLpZ9huC.js → chunk-2Q5K7J3B-B8gKhSj9.js} +1 -1
  17. package/dist/assets/{chunk-5VM5RSS4-BLlEdmX9.js → chunk-5VM5RSS4-GvOpglRR.js} +1 -1
  18. package/dist/assets/{chunk-F27PBJKO-D-ZNhI6q.js → chunk-F27PBJKO-CAgZy0qc.js} +1 -1
  19. package/dist/assets/{chunk-IMKFNOWR-vzSiKnhh.js → chunk-IMKFNOWR-gf8Ux8Zd.js} +1 -1
  20. package/dist/assets/{chunk-JWPE2WC7-DfF0k4nb.js → chunk-JWPE2WC7-D4zFFRMp.js} +1 -1
  21. package/dist/assets/{chunk-POPQ4Y6H-BnwCYsPP.js → chunk-POPQ4Y6H-Ct8X_LaT.js} +1 -1
  22. package/dist/assets/{chunk-SVP7TREG-DRxc8fhq.js → chunk-SVP7TREG-g5iH1bs5.js} +1 -1
  23. package/dist/assets/{chunk-TICWLB2K-Dd2Q-md-.js → chunk-TICWLB2K-89-VFKTn.js} +1 -1
  24. package/dist/assets/{chunk-XXDRQBXY-DBjkEUp_.js → chunk-XXDRQBXY-CaQbdObb.js} +1 -1
  25. package/dist/assets/classDiagram-ZZMXUADV-Cm08FeYk.js +1 -0
  26. package/dist/assets/classDiagram-v2-VYDZK3BY-Cm08FeYk.js +1 -0
  27. package/dist/assets/{cose-bilkent-JH36ORCC-By6PS4oe.js → cose-bilkent-JH36ORCC-BiYOGJAU.js} +1 -1
  28. package/dist/assets/{cynefin-OW5HDTMX-YxWpjc5W.js → cynefin-OW5HDTMX-F6mJ-4tj.js} +1 -1
  29. package/dist/assets/{cynefinDiagram-5FMLGOSQ-DrtXzz3p.js → cynefinDiagram-5FMLGOSQ-q-Rggn6r.js} +1 -1
  30. package/dist/assets/{dagre-GXQ25YYZ-BDpTLKqZ.js → dagre-GXQ25YYZ-COqImSUH.js} +1 -1
  31. package/dist/assets/{diagram-S7CK7UJ4-C4LvqeCs.js → diagram-S7CK7UJ4-BAg1dyEc.js} +1 -1
  32. package/dist/assets/{diagram-UQ7AKVKN-BTpnMeHp.js → diagram-UQ7AKVKN-DFJS8qCV.js} +1 -1
  33. package/dist/assets/{diagram-VSXAHHWV-DucHHWev.js → diagram-VSXAHHWV-BfldL35r.js} +1 -1
  34. package/dist/assets/{diagram-VX7I27RA-DYHZNmmH.js → diagram-VX7I27RA-kLjovpYe.js} +1 -1
  35. package/dist/assets/{diagram-Z3DM3KII-BHIH2BGN.js → diagram-Z3DM3KII-Dhwvi3Iu.js} +1 -1
  36. package/dist/assets/{ebnfDiagram-PWID7BFC-CGiEkwj0.js → ebnfDiagram-PWID7BFC-Bp4YE1i7.js} +1 -1
  37. package/dist/assets/{erDiagram-RLTQ6QDP-AJMHi2Us.js → erDiagram-RLTQ6QDP-BKDdaSWz.js} +1 -1
  38. package/dist/assets/{flowDiagram-HODETNUW-BqzANBh2.js → flowDiagram-HODETNUW-zHProVxL.js} +1 -1
  39. package/dist/assets/{ganttDiagram-EL5Y4UJY-B0xPDY2_.js → ganttDiagram-EL5Y4UJY-B-B-BI6j.js} +1 -1
  40. package/dist/assets/{gitGraphDiagram-WWUBYQGX-CzPX5CNO.js → gitGraphDiagram-WWUBYQGX-BTHMg5CH.js} +1 -1
  41. package/dist/assets/index-C5slQNvE.css +1 -0
  42. package/dist/assets/{index-Bk2iXKnJ.js → index-CJP4dZ-c.js} +146 -146
  43. package/dist/assets/{infoDiagram-27XIBGKW-Cjc44H0O.js → infoDiagram-27XIBGKW-mabp4H9I.js} +1 -1
  44. package/dist/assets/{ishikawaDiagram-5VMMS53U-BWhr2v7r.js → ishikawaDiagram-5VMMS53U-CLNhcCrM.js} +1 -1
  45. package/dist/assets/{journeyDiagram-3NMN7TZE-Bl4r6RrP.js → journeyDiagram-3NMN7TZE-CGEi2RB6.js} +1 -1
  46. package/dist/assets/{kanban-definition-UXKFOSKX-FV-T0Vy1.js → kanban-definition-UXKFOSKX-B2fLX1fK.js} +1 -1
  47. package/dist/assets/{layout-CTfb1sAF.js → layout-CloKWsFq.js} +1 -1
  48. package/dist/assets/{linear-Dfgi6vlo.js → linear-BFyydI0c.js} +1 -1
  49. package/dist/assets/{mermaid.core-DOCGsdlo.js → mermaid.core-Dxl3LVAh.js} +6 -6
  50. package/dist/assets/{mindmap-definition-YA3MSWOX-CymQ86ir.js → mindmap-definition-YA3MSWOX-FT4pm55s.js} +1 -1
  51. package/dist/assets/{pegDiagram-XKGWAZYB-1VMq_vi0.js → pegDiagram-XKGWAZYB-okyIfJWM.js} +1 -1
  52. package/dist/assets/{pieDiagram-E7YTZNPT-C5GgXtJU.js → pieDiagram-E7YTZNPT-NF61CQz8.js} +1 -1
  53. package/dist/assets/{quadrantDiagram-AXDQQJYC-C6zpryYf.js → quadrantDiagram-AXDQQJYC-uVfvZzzt.js} +1 -1
  54. package/dist/assets/{railroadDiagram-O6MQD6OU-D-sv4SWi.js → railroadDiagram-O6MQD6OU-Dp8IpeoH.js} +1 -1
  55. package/dist/assets/{requirementDiagram-BXWQKSXE-CkjY-z4B.js → requirementDiagram-BXWQKSXE-D8VV0oQe.js} +1 -1
  56. package/dist/assets/{sankeyDiagram-P5KCCOFB-De-d8Q6L.js → sankeyDiagram-P5KCCOFB-D-xhxjob.js} +1 -1
  57. package/dist/assets/{sequenceDiagram-WJ2MYXX4-DCsGSJa9.js → sequenceDiagram-WJ2MYXX4-BAQBK95h.js} +1 -1
  58. package/dist/assets/{sizeCapture-INFHLROL-xINZhgER.js → sizeCapture-INFHLROL-C6rk9SVx.js} +1 -1
  59. package/dist/assets/{stateDiagram-D77RDMKH-CuLUTWSL.js → stateDiagram-D77RDMKH-DshqaEOj.js} +1 -1
  60. package/dist/assets/{stateDiagram-v2-MP3YSRHH-Cnbs-M92.js → stateDiagram-v2-MP3YSRHH-BohZIrk5.js} +1 -1
  61. package/dist/assets/{swimlanes-42K2YHIH-BwxARD7N.js → swimlanes-42K2YHIH-DgcPDy4l.js} +1 -1
  62. package/dist/assets/swimlanesDiagram-VR7AAH4N-CuM3Su0K.js +8 -0
  63. package/dist/assets/{timeline-definition-24CTP7MA-BIOvnn-l.js → timeline-definition-24CTP7MA-OgpIe-gQ.js} +1 -1
  64. package/dist/assets/{vennDiagram-4TSXK5OY-BSD6Pfmt.js → vennDiagram-4TSXK5OY-BZzGd6jt.js} +1 -1
  65. package/dist/assets/{wardleyDiagram-VM6X3IG4-7Zz2iRkF.js → wardleyDiagram-VM6X3IG4-1tg-H7l_.js} +1 -1
  66. package/dist/assets/{xychartDiagram-S5SC5T6Z-Ctx17Y1D.js → xychartDiagram-S5SC5T6Z-ByTsX8g0.js} +1 -1
  67. package/dist/index.html +2 -2
  68. package/dist/sw.js +1 -1
  69. package/dist-server/plugins/piecemaker-dossier/src/knowledge.js +61 -1
  70. package/dist-server/plugins/piecemaker-dossier/src/knowledge.js.map +1 -1
  71. package/dist-server/plugins/piecemaker-dossier/src/scan-result.js +0 -48
  72. package/dist-server/plugins/piecemaker-dossier/src/scan-result.js.map +1 -1
  73. package/dist-server/server/piecemaker/agent-instructions.js +148 -0
  74. package/dist-server/server/piecemaker/agent-instructions.js.map +1 -0
  75. package/dist-server/server/piecemaker/index.js +4 -0
  76. package/dist-server/server/piecemaker/index.js.map +1 -1
  77. package/dist-server/server/piecemaker/knowledge/pipeline.js +25 -29
  78. package/dist-server/server/piecemaker/knowledge/pipeline.js.map +1 -1
  79. package/dist-server/server/piecemaker/knowledge/service.js +1 -16
  80. package/dist-server/server/piecemaker/knowledge/service.js.map +1 -1
  81. package/dist-server/server/piecemaker/project-registry.js +5 -5
  82. package/dist-server/server/piecemaker/project-registry.js.map +1 -1
  83. package/package.json +1 -1
  84. package/scripts/piecemaker/cli/lib/plugins.mjs +1 -0
  85. package/server/piecemaker/agent-instructions.ts +148 -0
  86. package/server/piecemaker/anonymizer/dictionary.cjs +34 -3
  87. package/server/piecemaker/anonymizer/hudsucker-fixture.cjs +1 -1
  88. package/server/piecemaker/anonymizer/hudsucker-proxy/src/main.rs +2 -1
  89. package/server/piecemaker/anonymizer/hudsucker-smoke.cjs +343 -0
  90. package/server/piecemaker/anonymizer/rewrite.cjs +2 -2
  91. package/server/piecemaker/anonymizer/rewriter-bridge.cjs +7 -4
  92. package/server/piecemaker/anonymizer/rewriter-worker.cjs +85 -0
  93. package/server/piecemaker/anonymizer/service.cjs +10 -8
  94. package/server/piecemaker/anonymizer/sqlite-dictionary.cjs +15 -32
  95. package/server/piecemaker/index.ts +5 -1
  96. package/server/piecemaker/knowledge/pipeline.ts +24 -31
  97. package/server/piecemaker/knowledge/service.ts +1 -14
  98. package/server/piecemaker/project-registry.ts +6 -6
  99. package/server/piecemaker/router.cjs +0 -1
  100. package/server/piecemaker/vendor/installer/bin/piecemaker.mjs +9 -162
  101. package/server/piecemaker/vendor/installer/lib/commandes.mjs +1 -2
  102. package/server/piecemaker/vendor/installer/lib/conversion-client.mjs +62 -3
  103. package/server/piecemaker/vendor/installer/steps/02-dependances-node.mjs +5 -5
  104. package/server/piecemaker/vendor/installer/steps/06-hooks.mjs +23 -69
  105. package/server/piecemaker/vendor/installer/steps/12-mcp-piecemaker.mjs +3 -3
  106. package/server/piecemaker/vendor/mcp/README.md +1 -1
  107. package/server/piecemaker/vendor/mcp/piecemaker/server.mjs +12 -75
  108. package/server/piecemaker/vendor/piecemaker-plugin/hooks/hooks.json +0 -40
  109. package/server/piecemaker/vendor/piecemaker-plugin/scripts/compile-recherche.mjs +2 -2
  110. package/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/case-folder-structure.cjs +2 -90
  111. package/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/case-folders.cjs +26 -7
  112. package/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/commits.cjs +0 -28
  113. package/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/hook-io.mjs +0 -13
  114. package/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/institutional-terms.cjs +3 -3
  115. package/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/mapping.cjs +10 -143
  116. package/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/protection.cjs +1 -1
  117. package/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/session-timing.cjs +0 -65
  118. package/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/substitution.cjs +189 -41
  119. package/server/piecemaker/vendor/piecemaker-plugin/scripts/protect-originals.mjs +11 -2
  120. package/server/piecemaker/vendor/piecemaker-plugin/skills/ajout-de-fonctionnalites/SKILL.md +4 -18
  121. package/server/piecemaker/vendor/piecemaker-plugin/skills/conversion-md/SKILL.md +9 -5
  122. package/server/piecemaker/vendor/websocket-server/admin-routes.cjs +3 -179
  123. package/server/piecemaker/vendor/websocket-server/case-registry.cjs +0 -10
  124. package/server/piecemaker/vendor/websocket-server/claude-hooks.cjs +3 -0
  125. package/server/piecemaker/vendor/websocket-server/originals-pipeline.cjs +23 -372
  126. package/server/piecemaker/vendor/websocket-server/renamed-originals.cjs +136 -0
  127. package/server/piecemaker/vendor/websocket-server/scripts/convert_and_scan_pipeline.py +115 -96
  128. package/dist/assets/channel-DVyNr2Yg.js +0 -1
  129. package/dist/assets/classDiagram-ZZMXUADV-2pGPHl5H.js +0 -1
  130. package/dist/assets/classDiagram-v2-VYDZK3BY-2pGPHl5H.js +0 -1
  131. package/dist/assets/index-Dc9wNuS4.css +0 -1
  132. package/dist/assets/swimlanesDiagram-VR7AAH4N-D7p8qZI2.js +0 -8
  133. package/server/piecemaker/vendor/piecemaker-plugin/scripts/billing-track.mjs +0 -194
  134. package/server/piecemaker/vendor/piecemaker-plugin/scripts/commit-track.mjs +0 -71
  135. package/server/piecemaker/vendor/piecemaker-plugin/scripts/session-commit-worker.mjs +0 -260
  136. package/server/piecemaker/vendor/piecemaker-plugin/scripts/session-commit.mjs +0 -83
@@ -1,31 +1,10 @@
1
+ const fs = require('node:fs');
1
2
  const path = require('node:path');
2
3
 
3
4
  const Database = require('better-sqlite3');
4
5
 
5
6
  const { buildDictionary, EMPTY_DICTIONARY } = require('./dictionary.cjs');
6
7
 
7
- function codePrefix(code) {
8
- const match = /^(.*?)_(\d+)$/.exec(code);
9
- return match ? match[1] : code || 'ENTITE';
10
- }
11
-
12
- function codeNumber(code) {
13
- const match = /_(\d+)$/.exec(code);
14
- return match ? Number(match[1]) : 0;
15
- }
16
-
17
- function allocateCode(localCode, counters, used) {
18
- const prefix = codePrefix(localCode);
19
- let index = counters.get(prefix) || 0;
20
- let candidate;
21
- do {
22
- index += 1;
23
- candidate = `${prefix}_${String(index).padStart(2, '0')}`;
24
- } while (used.has(candidate));
25
- counters.set(prefix, index);
26
- return candidate;
27
- }
28
-
29
8
  function buildMappingDocument(rows) {
30
9
  const grouped = new Map();
31
10
  let updatedAt = null;
@@ -37,20 +16,14 @@ function buildMappingDocument(rows) {
37
16
  if (!updatedAt || row.updated_at > updatedAt) updatedAt = row.updated_at;
38
17
  }
39
18
 
40
- const used = new Set();
41
- const counters = new Map();
42
19
  const mapping = {};
43
20
  const reverse_mapping = {};
44
21
  for (const group of grouped.values()) {
45
- const desired = group.localCode;
46
- const globalCode = used.has(desired) ? allocateCode(desired, counters, used) : desired;
47
- used.add(globalCode);
48
- const prefix = codePrefix(globalCode);
49
- counters.set(prefix, Math.max(counters.get(prefix) || 0, codeNumber(globalCode)));
22
+ const code = group.localCode;
50
23
  const canonical = group.variants.includes(group.label) ? group.label : group.variants[0];
51
24
  const variants = [canonical, ...group.variants.filter((value) => value !== canonical)];
52
- reverse_mapping[globalCode] = variants;
53
- for (const variant of variants) if (!mapping[variant]) mapping[variant] = globalCode;
25
+ reverse_mapping[code] = variants;
26
+ for (const variant of variants) if (!mapping[variant]) mapping[variant] = code;
54
27
  }
55
28
  return { mapping, reverse_mapping, updated_at: updatedAt };
56
29
  }
@@ -62,6 +35,7 @@ function createSqliteDictionaryLoader({ databasePath }) {
62
35
  let mappingSignature = null;
63
36
  let generation = 0;
64
37
  let current = EMPTY_DICTIONARY;
38
+ let failure = null;
65
39
 
66
40
  function connection() {
67
41
  if (!database) {
@@ -97,15 +71,24 @@ function createSqliteDictionaryLoader({ databasePath }) {
97
71
  generation += 1;
98
72
  current = buildDictionary(buildMappingDocument(rows), generation);
99
73
  dataVersion = nextDataVersion;
74
+ failure = null;
100
75
  return current;
101
- } catch {
76
+ } catch (error) {
77
+ failure = fs.existsSync(file) ? error : null;
102
78
  return current;
103
79
  }
104
80
  }
105
81
 
82
+ function loadForOutbound() {
83
+ const dictionary = load(false);
84
+ if (failure) throw new Error(`dictionnaire illisible : ${failure.message}`);
85
+ return dictionary;
86
+ }
87
+
106
88
  return {
107
89
  file,
108
90
  get: () => load(false),
91
+ getForOutbound: loadForOutbound,
109
92
  refresh: () => load(true),
110
93
  exists: () => !load(false).empty,
111
94
  close: () => {
@@ -3,11 +3,13 @@ import path from 'path';
3
3
 
4
4
  import type { Router } from 'express';
5
5
 
6
+ import { projectsDb } from '@/modules/database/index.js';
6
7
  import { providerRuntimeService, sessionsService } from '@/modules/providers/index.js';
7
8
  import { providerRegistry } from '@/modules/providers/provider.registry.js';
8
9
  import { findApplicationRoot, getModuleDirectory } from '@/shared/utils.js';
9
10
 
10
11
 
12
+ import { createAgentInstructionsRouter, startAgentInstructionsMirror } from './agent-instructions.js';
11
13
  import { startRequiredAnonymizer } from './anonymizer/lifecycle.js';
12
14
  import { createCitationStore } from './harness/citation-store.js';
13
15
  import { installChatCitationHarness } from './harness/chat-harness.js';
@@ -43,7 +45,7 @@ type PieceMakerRuntimeStatus = {
43
45
  type PieceMakerVendorModule = {
44
46
  createPieceMakerRouter(options?: { getRuntimeStatus?: () => PieceMakerRuntimeStatus; anonymizer?: unknown; publishProjects?: () => void }): Router;
45
47
  piecemakerHome(): string;
46
- publishProjectSource(sourceId: string, folders: string[] | null): void;
48
+ publishProjectSource(sourceId: string, folders: string[] | null, anonymizedFolders?: string[]): void;
47
49
  stopOriginalsJobs(): Promise<void>;
48
50
  };
49
51
 
@@ -62,6 +64,7 @@ installLibraryRuntime(providerRuntimeService, sessionsService, library.store);
62
64
  installModelDiscovery(providerRegistry);
63
65
  const timesheet = createTimesheetBackend(piecemakerHome());
64
66
  const knowledge = createKnowledgeBackend(applicationRoot);
67
+ startAgentInstructionsMirror(() => projectsDb.getProjectPaths().map((row) => row.project_path));
65
68
 
66
69
  export function createPieceMakerLocalRouter() {
67
70
  return knowledge.localRouter;
@@ -78,6 +81,7 @@ export function createPieceMakerRouter(options: { getRuntimeStatus?: () => Piece
78
81
  router.use(createCompanySearchRouter());
79
82
  router.use(createBodaccSearchRouter());
80
83
  router.use(createDocxDocumentRouter());
84
+ router.use(createAgentInstructionsRouter(path.join(piecemakerHome(), 'CLAUDE.md')));
81
85
  router.use(createShellEnvironmentRouter(shellEnvironment));
82
86
  return router;
83
87
  }
@@ -5,6 +5,7 @@ import os from 'node:os';
5
5
  import path from 'node:path';
6
6
 
7
7
  import { findApplicationRoot, getModuleDirectory } from '../../shared/utils.js';
8
+ import { resolveKnowledgeDatabasePath } from '../../../plugins/piecemaker-dossier/src/knowledge.js';
8
9
  import type { KnowledgeStore } from '../../../plugins/piecemaker-dossier/src/knowledge.js';
9
10
  import { persistScanResult, scanResultOperations } from '../../../plugins/piecemaker-dossier/src/scan-result.js';
10
11
  import type { GlinerDocument, GlinerMappingDocument, JsonData } from '../../../plugins/piecemaker-dossier/src/types.js';
@@ -25,7 +26,6 @@ type PipelineOptions = {
25
26
  type OriginalFile = { path: string; resource?: boolean };
26
27
 
27
28
  type OriginalsPipeline = {
28
- caseMappingFile(caseRoot: string): string;
29
29
  listOriginals(caseRoot: string): Promise<OriginalFile[]>;
30
30
  runManagedPythonJob(options: {
31
31
  action: 'convert' | 'anonymize';
@@ -33,16 +33,15 @@ type OriginalsPipeline = {
33
33
  args: string[];
34
34
  onProgress?: (progress: KnowledgeScanProgress) => void;
35
35
  signal?: AbortSignal;
36
+ input?: string;
37
+ onMapping?: (mapping: unknown) => void;
36
38
  }): Promise<unknown>;
37
- writeCaseMapping(caseRoot: string, document: GlinerMappingDocument): unknown;
38
39
  };
39
40
 
40
41
  const require = createRequire(import.meta.url);
41
42
  const {
42
- caseMappingFile,
43
43
  listOriginals,
44
44
  runManagedPythonJob,
45
- writeCaseMapping,
46
45
  } = require(path.join(
47
46
  findApplicationRoot(getModuleDirectory(import.meta.url)),
48
47
  'server',
@@ -117,18 +116,7 @@ function pythonExecutable(explicit: string | undefined): string {
117
116
  return fs.existsSync(candidate) ? candidate : 'python3';
118
117
  }
119
118
 
120
- export function legacyExclusions(projectPath: string): string[] {
121
- const candidate = path.join(projectPath, 'Fichiers convertis PieceMaker', 'mapping_default.json');
122
- if (!fs.existsSync(candidate)) return [];
123
- try {
124
- const document = parseObject(candidate);
125
- return Array.isArray(document.ignored) ? document.ignored.map(textValue).filter(Boolean) : [];
126
- } catch {
127
- return [];
128
- }
129
- }
130
-
131
- function mappingSeed(projectId: string, projectPath: string, store: KnowledgeStore): GlinerMappingDocument {
119
+ function mappingSeed(projectId: string, store: KnowledgeStore): GlinerMappingDocument {
132
120
  const snapshot = store.snapshot(projectId);
133
121
  const mapping: Record<string, string> = {};
134
122
  const reverseMapping: Record<string, string[]> = {};
@@ -145,7 +133,7 @@ function mappingSeed(projectId: string, projectPath: string, store: KnowledgeSto
145
133
  extractedData[category] ||= {};
146
134
  extractedData[category][code] = node.data;
147
135
  }
148
- const ignored = snapshot.exclusionsInitialized ? snapshot.exclusions || [] : legacyExclusions(projectPath);
136
+ const ignored = snapshot.exclusions || [];
149
137
  return { mapping, reverse_mapping: reverseMapping, extracted_data: extractedData, ignored };
150
138
  }
151
139
 
@@ -174,34 +162,39 @@ export function createKnowledgePipeline(options: PipelineOptions) {
174
162
  const explicitFiles = Array.isArray(requestedFiles) && requestedFiles.length > 0;
175
163
  const files = await safeFiles(projectPath, requestedFiles);
176
164
  if (!files.length) throw new Error('No supported file to scan.');
177
- const mappingFile = caseMappingFile(projectPath);
178
165
  const stateFile = path.join(projectPath, '.piecemaker', 'anonymization-state.json');
179
166
  const outputDirectory = path.join(projectPath, 'Fichiers convertis PieceMaker');
180
- fs.mkdirSync(path.dirname(mappingFile), { recursive: true });
181
167
  fs.mkdirSync(path.dirname(stateFile), { recursive: true });
182
168
  fs.mkdirSync(outputDirectory, { recursive: true });
183
- fs.writeFileSync(mappingFile, JSON.stringify(mappingSeed(projectId, projectPath, options.store)), { mode: 0o600 });
169
+ const seed = mappingSeed(projectId, options.store);
184
170
  const args = [
185
171
  ...files,
186
172
  '-o',
187
173
  outputDirectory,
188
- '--mapping-file',
189
- mappingFile,
174
+ '--database',
175
+ resolveKnowledgeDatabasePath(),
190
176
  '--case-root',
191
177
  projectPath,
192
178
  '--state-file',
193
179
  stateFile,
194
180
  ];
195
181
  if (!explicitFiles) args.push('--skip-existing');
196
- await withPythonPath(pythonExecutable(options.pythonPath), () => runManagedPythonJob({
197
- action: 'anonymize',
198
- script,
199
- args,
200
- onProgress,
201
- signal,
202
- }));
203
- const mapping = parseObject(mappingFile) as GlinerMappingDocument;
204
- writeCaseMapping(projectPath, mapping);
182
+ let received: GlinerMappingDocument | null = null;
183
+ try {
184
+ await withPythonPath(pythonExecutable(options.pythonPath), () => runManagedPythonJob({
185
+ action: 'anonymize',
186
+ script,
187
+ args,
188
+ onProgress,
189
+ signal,
190
+ input: JSON.stringify(seed),
191
+ onMapping: (value) => { received = value as GlinerMappingDocument; },
192
+ }));
193
+ } catch (error) {
194
+ if (received) options.store.update({ projectId, operations: scanResultOperations({ projectId, mapping: received, documents: [] }) });
195
+ throw error;
196
+ }
197
+ const mapping: GlinerMappingDocument = received || seed;
205
198
  const documents = documentsFromIndex(projectPath, files, readDocumentIndex(projectPath));
206
199
  const scanResult = { projectId, mapping, documents };
207
200
  const result = explicitFiles
@@ -1,8 +1,6 @@
1
1
  import type { KnowledgeStore } from '../../../plugins/piecemaker-dossier/src/knowledge.js';
2
- import { exclusionNodeOperation } from '../../../plugins/piecemaker-dossier/src/scan-result.js';
3
2
  import type { KnowledgeQueryInput, KnowledgeUpdateInput } from '../../../plugins/piecemaker-dossier/src/types.js';
4
3
 
5
- import { legacyExclusions } from './pipeline.js';
6
4
  import type { createKnowledgePipeline } from './pipeline.js';
7
5
  import { createKnowledgeScanJobs } from './scan-jobs.js';
8
6
 
@@ -22,16 +20,6 @@ export function createKnowledgeService(store: KnowledgeStore, projects: ProjectL
22
20
  if (!projects.getProjectById(id)) throw new Error('Project not found.');
23
21
  return id;
24
22
  };
25
- const snapshot = (id: string) => {
26
- const current = store.snapshot(id);
27
- if (current.exclusionsInitialized) return current;
28
- const project = projects.getProjectById(id);
29
- const values = project ? legacyExclusions(project.project_path) : [];
30
- if (!values.length) return current;
31
- const operation = exclusionNodeOperation(values);
32
- store.update({ projectId: id, operations: [operation] });
33
- return store.snapshot(id);
34
- };
35
23
  return {
36
24
  query(input: KnowledgeQueryInput) {
37
25
  return store.query({ ...input, projectId: ensureProject(input.projectId), projectPath: undefined });
@@ -40,8 +28,7 @@ export function createKnowledgeService(store: KnowledgeStore, projects: ProjectL
40
28
  return store.update({ ...input, projectId: ensureProject(input.projectId) });
41
29
  },
42
30
  graph(value: unknown) {
43
- const id = ensureProject(value);
44
- return snapshot(id);
31
+ return store.snapshot(ensureProject(value));
45
32
  },
46
33
  scan(value: unknown, files?: unknown) {
47
34
  const id = ensureProject(value);
@@ -4,19 +4,19 @@ import { projectsDb } from '@/modules/database/index.js';
4
4
 
5
5
  const PUBLISH_INTERVAL_MS = 5000;
6
6
 
7
- type PublishProjectSource = (sourceId: string, folders: string[] | null) => void;
7
+ type PublishProjectSource = (sourceId: string, folders: string[] | null, anonymizedFolders?: string[]) => void;
8
8
 
9
9
  export function startProjectRegistry(publishProjectSource: PublishProjectSource): () => void {
10
10
  const sourceId = `database:${path.resolve(process.env.DATABASE_PATH || 'auth.db')}`;
11
11
  let publishedSignature: string | null = null;
12
12
 
13
13
  const publishProjects = () => {
14
- const folders = [...projectsDb.getProjectPaths(), ...projectsDb.getArchivedProjectPaths()]
15
- .map((row) => row.project_path)
16
- .sort();
17
- const signature = folders.join('\n');
14
+ const rows = [...projectsDb.getProjectPaths(), ...projectsDb.getArchivedProjectPaths()];
15
+ const folders = rows.map((row) => row.project_path).sort();
16
+ const anonymizedFolders = rows.filter((row) => row.anonymization_complete).map((row) => row.project_path).sort();
17
+ const signature = `${folders.join('\n')}\n--\n${anonymizedFolders.join('\n')}`;
18
18
  if (signature === publishedSignature) return;
19
- publishProjectSource(sourceId, folders);
19
+ publishProjectSource(sourceId, folders, anonymizedFolders);
20
20
  publishedSignature = signature;
21
21
  };
22
22
 
@@ -67,7 +67,6 @@ function createPieceMakerRouter({ getRuntimeStatus = defaultRuntimeStatus, anony
67
67
  publishProjects();
68
68
  const result = await registerLegalCase({
69
69
  folder: request.body?.folder,
70
- configFile: path.join(homeDir, 'config.json'),
71
70
  homeDir,
72
71
  });
73
72
  response.status(201).json({ ok: true, ...result });
@@ -8,11 +8,6 @@
8
8
  *
9
9
  * Usage:
10
10
  * piecemaker menu interactif
11
- * piecemaker chronology affiche la chronologie du dossier courant
12
- * piecemaker chronology write --path <pièce> --correction-json <json>
13
- * crée une correction de chronologie pour une pièce
14
- * piecemaker chronology edit --path <pièce> --correction-json <json>
15
- * modifie une correction de chronologie existante
16
11
  * piecemaker conversion convertit et pseudonymise les pièces manquantes
17
12
  * piecemaker install ouvre le menu des composants
18
13
  * piecemaker doctor diagnostic seul
@@ -33,16 +28,15 @@ import { fileURLToPath, pathToFileURL } from 'node:url';
33
28
  import { banner, title, log, write, blank, summary, spinner, badge, c } from '../lib/ui.mjs';
34
29
  import { select, confirm, multiSelect, pause, nonInteractive } from '../lib/prompt.mjs';
35
30
  import { GIT_REPO_ROOT, HOME_DIR, REPO_ROOT, commandExists, findPython } from '../lib/platform.mjs';
36
- import { COMMANDS, CHRONOLOGY_ACTIONS } from '../lib/commandes.mjs';
31
+ import { COMMANDS } from '../lib/commandes.mjs';
37
32
  import { loadConfig, readEnv, markStep, loadState, CONFIG_FILE } from '../lib/state.mjs';
38
33
  import { scheduleStepResume, selectStepsToResume } from '../lib/resume-steps.mjs';
39
- import { appServerPort, readLocalScanJob, startLocalScan } from '../lib/conversion-client.mjs';
34
+ import { adoptRunningServerPort, appServerPort, localServerReachable, readLocalScanJob, startLocalScan } from '../lib/conversion-client.mjs';
40
35
  import { checkForUpdate, updateRepository } from '../lib/service.mjs';
41
36
 
42
37
  const require = createRequire(import.meta.url);
43
38
  const CLAUDE_ASSETS_MODULE = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../../websocket-server/claude-assets.cjs');
44
39
  const CLAUDE_HOOKS_MODULE = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../../websocket-server/claude-hooks.cjs');
45
- const DOCUMENT_INDEX_MODULE = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../../websocket-server/document-index.cjs');
46
40
  const ORIGINALS_PIPELINE_MODULE = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../../websocket-server/originals-pipeline.cjs');
47
41
  const APP_CLI_LIB = path.join(GIT_REPO_ROOT, 'scripts', 'piecemaker', 'cli', 'lib');
48
42
 
@@ -129,11 +123,8 @@ function parseArgs(argv) {
129
123
  check: false,
130
124
  dryRun: false,
131
125
  conversionDocuments: [],
132
- correctionJson: null,
133
126
  force: false,
134
- chronologyAction: 'read',
135
127
  json: false,
136
- piecePath: null,
137
128
  resumeSteps: null,
138
129
  step: null,
139
130
  yes: false,
@@ -142,18 +133,13 @@ function parseArgs(argv) {
142
133
  for (let i = 0; i < argv.length; i++) {
143
134
  const arg = argv[i];
144
135
  if (!arg.startsWith('-') && !flags.command && COMMANDS.has(arg)) flags.command = arg;
145
- else if (!arg.startsWith('-') && flags.command === 'chronology' && flags.chronologyAction === 'read' && CHRONOLOGY_ACTIONS.has(arg)) flags.chronologyAction = arg;
146
- else if (!arg.startsWith('-') && flags.command === 'chronology' && !flags.caseTarget) flags.caseTarget = arg;
147
136
  else if (!arg.startsWith('-') && flags.command === 'conversion') flags.conversionDocuments.push(arg);
148
137
  else if (arg === '--all') flags.all = true;
149
- else if (arg === '--action') flags.chronologyAction = argv[++i];
150
138
  else if (arg === '--case') flags.caseTarget = argv[++i];
151
139
  else if (arg === '--check') flags.check = true;
152
- else if (arg === '--correction-json') flags.correctionJson = argv[++i];
153
140
  else if (arg === '--dry-run') flags.dryRun = true;
154
141
  else if (arg === '--force') flags.force = true;
155
142
  else if (arg === '--json') flags.json = true;
156
- else if (arg === '--path') flags.piecePath = argv[++i];
157
143
  else if (arg === '--yes' || arg === '-y') flags.yes = true;
158
144
  else if (arg === '--step') flags.step = argv[++i];
159
145
  else if (arg === '--resume-steps') {
@@ -313,20 +299,15 @@ async function runCheck(steps, ctx) {
313
299
  function printHelp() {
314
300
  write(` ${c.bold('piecemaker')} — PieceMaker local`);
315
301
  blank();
316
- write(' chronology [read] affiche la chronologie pseudonymisée du dossier courant');
317
- write(' chronology write --path <pièce> --correction-json <json> crée une correction de chronologie');
318
- write(' chronology edit --path <pièce> --correction-json <json> modifie une correction existante');
319
302
  write(' conversion [pièce…] convertit et pseudonymise les pièces manquantes ou indiquées');
320
303
  write(' install ouvre le menu d’installation/réparation');
321
304
  write(' doctor, check diagnostic seul, n’installe rien');
322
305
  write(' update met à jour PieceMaker');
323
306
  blank();
324
307
  write(' --all installe tout sans menu');
325
- write(' --case <chemin> cible un dossier enregistré (chronology/conversion)');
326
- write(' --path <pièce> chemin relatif de la pièce (chronology write/edit)');
327
- write(' --correction-json <json> correction à appliquer (chronology write/edit)');
308
+ write(' --case <chemin> cible un dossier enregistré (conversion)');
328
309
  write(' --force retraite les pièces');
329
- write(' --json produit une sortie JSON sans décor (chronology/conversion)');
310
+ write(' --json produit une sortie JSON sans décor (conversion)');
330
311
  write(' --check diagnostic seul, n\'installe rien');
331
312
  write(' --step <id> rejoue une seule étape');
332
313
  write(' --resume-steps <ids> rejoue les étapes indiquées sans interaction (usage interne)');
@@ -375,135 +356,6 @@ async function installerMenu(steps, ctx, { allowBack = false } = {}) {
375
356
  }
376
357
  }
377
358
 
378
- function formatChronologyText(chronology) {
379
- const lines = [];
380
- lines.push(`Chronologie : ${chronology.stats.documents} pièce(s), ${chronology.stats.dated} datée(s), ${chronology.stats.entities} entité(s).`);
381
- lines.push('');
382
- for (const doc of chronology.documents) {
383
- lines.push(`${doc.dateIso || '(date manquante)'} ${doc.nature || '(nature inconnue)'} ${doc.name}`);
384
- }
385
- const missing = chronology.documents.filter((doc) => !doc.dateIso);
386
- if (missing.length) {
387
- lines.push('');
388
- lines.push('Dates manquantes :');
389
- for (const doc of missing) lines.push(` - ${doc.name}`);
390
- }
391
- return `${lines.join('\n')}\n`;
392
- }
393
-
394
- function locateChronologyCase(flags) {
395
- const { locateProjectCase } = require('../../piecemaker-plugin/scripts/lib/case-folders.cjs');
396
- const located = locateProjectCase(flags.caseTarget || process.cwd());
397
- if (!located) {
398
- throw new Error('Lancez la commande depuis un dossier juridique enregistré ou passez --case <chemin>.');
399
- }
400
- return located;
401
- }
402
-
403
- async function runChronologyReadCommand(flags) {
404
- if (!fs.existsSync(DOCUMENT_INDEX_MODULE)) {
405
- throw new Error('Le module de chronologie PieceMaker est introuvable.');
406
- }
407
- const located = locateChronologyCase(flags);
408
- const { buildChronology } = require(DOCUMENT_INDEX_MODULE);
409
- const chronology = await buildChronology(located.caseRoot, { deanonymizeLabels: false, includeManualDecisions: true });
410
- const output = flags.json
411
- ? `${JSON.stringify(chronology, null, 2)}\n`
412
- : formatChronologyText(chronology);
413
- process.stdout.write(output);
414
- return 0;
415
- }
416
-
417
- function resolveChronologyPiecePath(caseRoot, relativePath) {
418
- const relative = String(relativePath || '').replaceAll('\\', '/').replace(/^\.\//, '');
419
- if (!relative) throw new Error('Chemin de pièce manquant.');
420
- const absolute = path.resolve(caseRoot, ...relative.split('/'));
421
- if (absolute !== caseRoot && !absolute.startsWith(`${caseRoot}${path.sep}`)) {
422
- throw new Error('Pièce hors du dossier juridique.');
423
- }
424
- if (!fs.existsSync(absolute)) throw new Error('Pièce introuvable.');
425
- return relative;
426
- }
427
-
428
- async function runChronologyCorrectionCommand(flags) {
429
- if (!fs.existsSync(DOCUMENT_INDEX_MODULE)) {
430
- throw new Error('Le module de chronologie PieceMaker est introuvable.');
431
- }
432
- const located = locateChronologyCase(flags);
433
- const relative = resolveChronologyPiecePath(located.caseRoot, flags.piecePath);
434
-
435
- let correction;
436
- try {
437
- correction = JSON.parse(flags.correctionJson || '{}');
438
- } catch (error) {
439
- throw new Error(`Correction JSON invalide : ${error.message}`);
440
- }
441
-
442
- const { stateKey } = require('../../piecemaker-plugin/scripts/lib/anonymization-state.cjs');
443
- const { applyDocumentIndexCorrection, readDocumentIndex, documentIndexFile } = require(DOCUMENT_INDEX_MODULE);
444
- const key = stateKey(relative);
445
- const existing = readDocumentIndex(located.caseRoot).overrides[key] || null;
446
-
447
- if (flags.chronologyAction === 'write' && existing) {
448
- throw new Error('Une correction existe déjà pour cette pièce ; utilisez edit_chronology pour la modifier.');
449
- }
450
- if (flags.chronologyAction === 'edit' && !existing) {
451
- throw new Error('Aucune correction existante pour cette pièce ; utilisez write_chronology pour en créer une.');
452
- }
453
-
454
- const mutation = applyDocumentIndexCorrection(located.caseRoot, relative, correction);
455
-
456
- let history;
457
- try {
458
- const { createCommit } = require('../../piecemaker-plugin/scripts/lib/commits.cjs');
459
- history = await createCommit({
460
- casesRoot: located.casesRoot,
461
- caseName: located.caseName,
462
- homeDir: HOME_DIR,
463
- envFile: path.join(REPO_ROOT, '.env'),
464
- label: flags.chronologyAction === 'write'
465
- ? 'Création d’une correction de chronologie'
466
- : 'Modification d’une correction de chronologie',
467
- description: `Mise à jour déterministe de la pièce ${mutation.documentKey.slice(0, 12).toUpperCase()}.`,
468
- event: 'assistant-chronology-correction',
469
- paths: [path.relative(located.caseRoot, documentIndexFile(located.caseRoot)).split(path.sep).join('/')],
470
- waitForLockMs: 10_000,
471
- });
472
- } catch (error) {
473
- history = { created: false, error: error.message };
474
- }
475
-
476
- const result = {
477
- ok: true,
478
- action: flags.chronologyAction,
479
- path: relative,
480
- documentKey: mutation.documentKey,
481
- override: mutation.override,
482
- entityDecisions: mutation.entityDecisions,
483
- editRevision: mutation.editRevision,
484
- history: {
485
- created: Boolean(history?.created),
486
- hash: history?.commit || null,
487
- error: history?.error || null,
488
- },
489
- };
490
-
491
- if (flags.json) {
492
- process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
493
- } else {
494
- const verb = flags.chronologyAction === 'write' ? 'Correction créée' : 'Correction modifiée';
495
- log.ok(`${verb} pour ${relative}.`);
496
- }
497
- return 0;
498
- }
499
-
500
- async function runChronologyCommand(flags) {
501
- if (flags.chronologyAction === 'write' || flags.chronologyAction === 'edit') {
502
- return runChronologyCorrectionCommand(flags);
503
- }
504
- return runChronologyReadCommand(flags);
505
- }
506
-
507
359
  function normalizedConversionRequest(value, caseRoot) {
508
360
  const raw = String(value || '').trim();
509
361
  if (!raw) throw new Error('Nom de pièce vide.');
@@ -570,6 +422,8 @@ async function waitForConversionJob({ folder, id }, { json = false } = {}) {
570
422
  }
571
423
 
572
424
  async function ensureServerForConversion({ json = false } = {}) {
425
+ await adoptRunningServerPort();
426
+ if (await localServerReachable()) return;
573
427
  const servicesModule = path.join(APP_CLI_LIB, 'services.mjs');
574
428
  if (!fs.existsSync(servicesModule)) {
575
429
  throw new Error(`Le serveur applicatif doit être démarré par la commande « piecemaker » avant la conversion (port ${appServerPort()}).`);
@@ -625,7 +479,6 @@ async function runConversionCommand(flags) {
625
479
  }
626
480
 
627
481
  async function runOperationalCommand(command, knownUpdate = null, flags = {}) {
628
- if (command === 'chronology') return runChronologyCommand(flags);
629
482
  if (command === 'conversion') return runConversionCommand(flags);
630
483
  if (command === 'update') {
631
484
  const pending = knownUpdate ?? checkForUpdate();
@@ -749,20 +602,14 @@ async function main() {
749
602
  return 1;
750
603
  }
751
604
 
752
- if (!['chronology', 'conversion'].includes(flags.command) && (flags.caseTarget || flags.json)) {
605
+ if (flags.command !== 'conversion' && (flags.caseTarget || flags.json)) {
753
606
  banner();
754
- log.error('Les options --case et --json sont réservées aux commandes chronology et conversion.');
755
- return 1;
756
- }
757
-
758
- if (flags.command === 'chronology' && !CHRONOLOGY_ACTIONS.has(flags.chronologyAction)) {
759
- log.error('Action inconnue : utilisez « piecemaker chronology read|write|edit ».');
607
+ log.error('Les options --case et --json sont réservées à la commande conversion.');
760
608
  return 1;
761
609
  }
762
610
 
763
611
  // Les sorties JSON sont directement consommées par les assistants.
764
- if ((flags.command === 'chronology' && flags.json)
765
- || (flags.command === 'conversion' && flags.json)) {
612
+ if (flags.command === 'conversion' && flags.json) {
766
613
  return runOperationalCommand(flags.command, null, flags);
767
614
  }
768
615
 
@@ -7,5 +7,4 @@
7
7
  * est importable sans effet de bord.
8
8
  */
9
9
 
10
- export const COMMANDS = new Set(['chronology', 'conversion', 'install', 'doctor', 'check', 'update']);
11
- export const CHRONOLOGY_ACTIONS = new Set(['read', 'write', 'edit']);
10
+ export const COMMANDS = new Set(['conversion', 'install', 'doctor', 'check', 'update']);
@@ -4,20 +4,79 @@
4
4
  * La conversion et l'analyse PII n'ont qu'un seul point d'entrée : la route
5
5
  * `knowledge/scan` du serveur applicatif. Le CLI en est un client, sur la
6
6
  * boucle locale, via le montage `/api/piecemaker/local` placé avant
7
- * l'authentification. Le serveur applicatif écoute en clair sur le port du
8
- * CLI (`PIECEMAKER_APP_PORT`, 3003 par défaut) : aucun certificat n'entre en
9
- * jeu et la requête ne sort jamais de 127.0.0.1.
7
+ * l'authentification. Le serveur applicatif écoute en clair sur la boucle
8
+ * locale : celui qui tourne déjà (application de bureau comprise, annoncé par
9
+ * `local-server.json` dans le dossier de données), sinon le port du CLI
10
+ * (`PIECEMAKER_APP_PORT`, 3003 par défaut). Aucun certificat n'entre en jeu et
11
+ * la requête ne sort jamais de 127.0.0.1.
10
12
  */
11
13
 
14
+ import fs from 'node:fs';
12
15
  import http from 'node:http';
16
+ import os from 'node:os';
17
+ import { join } from 'node:path';
18
+
19
+ import { GIT_REPO_ROOT } from './platform.mjs';
13
20
 
14
21
  const LOCAL_BASE = '/api/piecemaker/local';
22
+ const LOOPBACK_HOSTS = new Set(['127.0.0.1', 'localhost', '::1', '0.0.0.0', '::']);
15
23
 
16
24
  export function appServerPort() {
17
25
  const parsed = Number.parseInt(process.env.PIECEMAKER_APP_PORT || '', 10);
18
26
  return Number.isInteger(parsed) && parsed > 0 ? parsed : 3003;
19
27
  }
20
28
 
29
+ function runningServerMarkerPath() {
30
+ if (process.env.CLOUDCLI_HOME) return join(process.env.CLOUDCLI_HOME, 'local-server.json');
31
+ try {
32
+ const config = JSON.parse(fs.readFileSync(join(GIT_REPO_ROOT, 'product.config.json'), 'utf8'));
33
+ return join(os.homedir(), config.dataDirectoryName, 'local-server.json');
34
+ } catch {
35
+ return null;
36
+ }
37
+ }
38
+
39
+ function processAlive(pid) {
40
+ try {
41
+ process.kill(pid, 0);
42
+ return true;
43
+ } catch (error) {
44
+ return error.code === 'EPERM';
45
+ }
46
+ }
47
+
48
+ export function runningServerPort(markerPath = runningServerMarkerPath()) {
49
+ if (!markerPath) return null;
50
+ try {
51
+ const marker = JSON.parse(fs.readFileSync(markerPath, 'utf8'));
52
+ const port = Number(marker?.port);
53
+ const pid = Number(marker?.pid);
54
+ if (!Number.isInteger(port) || port <= 0 || !LOOPBACK_HOSTS.has(marker?.host)) return null;
55
+ if (!Number.isInteger(pid) || pid <= 0 || !processAlive(pid)) return null;
56
+ return port;
57
+ } catch {
58
+ return null;
59
+ }
60
+ }
61
+
62
+ export function localServerReachable(port = appServerPort()) {
63
+ return new Promise((resolve) => {
64
+ const request = http.get({ hostname: '127.0.0.1', port, path: '/api/auth/status', timeout: 2_000 }, (response) => {
65
+ response.resume();
66
+ resolve(response.statusCode < 500);
67
+ });
68
+ request.on('timeout', () => request.destroy());
69
+ request.on('error', () => resolve(false));
70
+ });
71
+ }
72
+
73
+ export async function adoptRunningServerPort({ markerPath, reachable = localServerReachable } = {}) {
74
+ if (process.env.PIECEMAKER_APP_PORT) return appServerPort();
75
+ const port = runningServerPort(markerPath);
76
+ if (port && await reachable(port)) process.env.PIECEMAKER_APP_PORT = String(port);
77
+ return appServerPort();
78
+ }
79
+
21
80
  function requestJson({ method, path, body }) {
22
81
  const payload = body === undefined ? null : Buffer.from(JSON.stringify(body));
23
82
  return new Promise((resolve, reject) => {