@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
@@ -9,7 +9,7 @@
9
9
  import fs from 'node:fs';
10
10
  import path from 'node:path';
11
11
  import { log, spinner, columns } from '../lib/ui.mjs';
12
- import { run, npmBin, npmEnv, REPO_ROOT } from '../lib/platform.mjs';
12
+ import { run, npmBin, npmEnv, GIT_REPO_ROOT } from '../lib/platform.mjs';
13
13
 
14
14
  export const meta = {
15
15
  id: '02-dependances-node',
@@ -36,7 +36,7 @@ function readPackageJson(dir) {
36
36
  // Certaines distributions npm conservent spawn-helper de node-pty en 0644.
37
37
  // Le module se charge alors normalement mais tout pty.spawn échoue avec
38
38
  // « posix_spawnp failed ». Réparer le bit exécutable est idempotent.
39
- export function repairNodePtySpawnHelpers(repoRoot = REPO_ROOT, { repair = true } = {}) {
39
+ export function repairNodePtySpawnHelpers(repoRoot = GIT_REPO_ROOT, { repair = true } = {}) {
40
40
  if (process.platform === 'win32') return { found: 0, repaired: 0, ready: true };
41
41
  const prebuilds = path.join(repoRoot, 'node_modules', 'node-pty', 'prebuilds');
42
42
  if (!fs.existsSync(prebuilds)) return { found: 0, repaired: 0, ready: false };
@@ -88,7 +88,7 @@ async function npmInstall(dir, label, ctx) {
88
88
  export async function install(ctx) {
89
89
  const results = [];
90
90
 
91
- results.push(['racine du projet', await npmInstall(REPO_ROOT, 'Racine', ctx)]);
91
+ results.push(['racine du projet', await npmInstall(GIT_REPO_ROOT, 'Racine', ctx)]);
92
92
 
93
93
  if (ctx.dryRun) {
94
94
  return { status: 'skipped', note: 'Mode simulation — aucune installation effectuée.' };
@@ -116,8 +116,8 @@ export async function install(ctx) {
116
116
  }
117
117
 
118
118
  export async function check(ctx) {
119
- const rootInstalled = fs.existsSync(path.join(REPO_ROOT, 'node_modules'));
120
- const ptyReady = repairNodePtySpawnHelpers(REPO_ROOT, { repair: false }).ready;
119
+ const rootInstalled = fs.existsSync(path.join(GIT_REPO_ROOT, 'node_modules'));
120
+ const ptyReady = repairNodePtySpawnHelpers(GIT_REPO_ROOT, { repair: false }).ready;
121
121
 
122
122
  if (rootInstalled && ptyReady) return { status: 'done', note: '' };
123
123
  if (rootInstalled) {
@@ -1,8 +1,7 @@
1
1
  /**
2
- * Étape 06 — hooks Claude Code (protection, commits, facturation).
2
+ * Étape 06 — hooks Claude Code (protection des pièces, recherche, documents IA).
3
3
  *
4
- * Écrit la configuration d'exécution des hooks dans ~/.piecemaker/config.json,
5
- * puis prouve que chaque script tourne proprement en lui envoyant une charge
4
+ * Prouve que chaque script tourne proprement en lui envoyant une charge
6
5
  * utile synthétique sur stdin — le contrat exact qu'utilise Claude Code (voir
7
6
  * piecemaker-plugin/scripts/lib/hook-io.mjs).
8
7
  *
@@ -17,8 +16,7 @@ import os from 'node:os';
17
16
  import path from 'node:path';
18
17
  import { createRequire } from 'node:module';
19
18
  import { log, spinner } from '../lib/ui.mjs';
20
- import { REPO_ROOT, HOME_DIR, runCapture, ensureDir } from '../lib/platform.mjs';
21
- import { updateConfig } from '../lib/state.mjs';
19
+ import { HOME_DIR, REPO_ROOT, runCapture } from '../lib/platform.mjs';
22
20
 
23
21
  const require = createRequire(import.meta.url);
24
22
  const {
@@ -30,21 +28,17 @@ const {
30
28
 
31
29
  export const meta = {
32
30
  id: '06-hooks',
33
- label: 'Hooks Claude Code (protection, commits & facturation)',
34
- description: 'Configure les garde-fous, les commits PostToolUse et le suivi de facturation',
31
+ label: 'Hooks Claude Code (protection des pièces)',
32
+ description: 'Configure les garde-fous et les hooks de recherche et de classement des documents IA',
35
33
  };
36
34
 
37
35
  const PLUGIN_ROOT = path.join(REPO_ROOT, 'piecemaker-plugin');
38
36
  const SCRIPTS_DIR = path.join(PLUGIN_ROOT, 'scripts');
39
37
  const HOOKS_JSON = path.join(PLUGIN_ROOT, 'hooks', 'hooks.json');
40
- const BILLING_DIR = path.join(HOME_DIR, 'billing');
41
- const SYNTHESE_DIR = path.join(BILLING_DIR, 'synthese');
42
38
 
43
39
  const HOOK_SCRIPTS = {
44
40
  protect: path.join(SCRIPTS_DIR, 'protect-originals.mjs'),
45
- commit: path.join(SCRIPTS_DIR, 'commit-track.mjs'),
46
41
  classify: path.join(SCRIPTS_DIR, 'classify-ai-documents.mjs'),
47
- billing: path.join(SCRIPTS_DIR, 'billing-track.mjs'),
48
42
  statusline: path.join(SCRIPTS_DIR, 'statusline.mjs'),
49
43
  };
50
44
 
@@ -73,29 +67,12 @@ function runHookSelfTest(label, scriptPath, payload) {
73
67
  return { label, ok: false, note: 'sortie stdout non-JSON — contrat de hook invalide' };
74
68
  }
75
69
  }
76
- return { label, ok: true, note: result.stdout ? 'sortie JSON valide' : 'exit 0 (aucune sortie)' };
70
+ return { label, ok: true, stdout: result.stdout, note: result.stdout ? 'sortie JSON valide' : 'exit 0 (aucune sortie)' };
77
71
  }
78
72
 
79
73
  export async function install(ctx) {
80
- log.step('Répertoires de facturation...');
81
- if (!ctx.dryRun) {
82
- ensureDir(BILLING_DIR);
83
- ensureDir(SYNTHESE_DIR);
84
- }
85
- log.ok(`${BILLING_DIR}`);
86
-
87
74
  if (ctx.dryRun) {
88
- log.info('[simulation] configuration commits/facturation non écrite');
89
75
  log.info('[simulation] hooks PieceMaker fusionnés directement dans ~/.claude/settings.json');
90
- } else {
91
- updateConfig({
92
- commits: { enabled: true, timeoutMs: 8000 },
93
- billing: { enabled: true },
94
- });
95
- log.ok('Configuration écrite dans ~/.piecemaker/config.json (commits, billing)');
96
- }
97
-
98
- if (ctx.dryRun) {
99
76
  return { status: 'skipped', note: 'Mode simulation — hooks non vérifiés.' };
100
77
  }
101
78
 
@@ -109,10 +86,14 @@ export async function install(ctx) {
109
86
  // Un vrai dossier juridique factice, sans point de tête : les hooks
110
87
  // ignorent les répertoires cachés, un bac de test caché ne prouverait donc
111
88
  // rien du garde-fou. Comme seul un projet publié est un dossier juridique,
112
- // on le publie le temps du test sous une source dédiée, retirée ensuite.
113
- testDir = fs.realpathSync(fs.mkdtempSync(path.join(os.tmpdir(), 'piecemaker-hook-selftest-')));
89
+ // on le publie le temps du test sous une source dédiée, retirée ensuite,
90
+ // et comme anonymisé : le garde-fou n'agit que sous un bouclier vert.
91
+ // Les dossiers temporaires du système ne sont jamais des dossiers
92
+ // juridiques : le bac vit donc dans le répertoire PieceMaker.
93
+ fs.mkdirSync(HOME_DIR, { recursive: true });
94
+ testDir = fs.realpathSync(fs.mkdtempSync(path.join(HOME_DIR, 'hook-selftest-')));
114
95
  fs.writeFileSync(path.join(testDir, 'piece-selftest.pdf'), 'PIECE DE TEST', 'utf8');
115
- publishProjectSource('installer-selftest', [testDir]);
96
+ publishProjectSource('installer-selftest', [testDir], [testDir]);
116
97
  selftestRegistered = true;
117
98
 
118
99
  // 1. protect-originals.mjs — une pièce du bac de test : protégée par
@@ -127,38 +108,15 @@ export async function install(ctx) {
127
108
  tool_input: { file_path: path.join(testDir, 'piece-selftest.pdf') },
128
109
  tool_use_id: 'toolu_installer_originals_selftest',
129
110
  });
111
+ if (protectedResult.ok && !/"permissionDecision"\s*:\s*"deny"/.test(protectedResult.stdout || '')) {
112
+ protectedResult.ok = false;
113
+ protectedResult.note = 'la pièce du bac de test n\'a pas été bloquée';
114
+ } else if (protectedResult.ok) {
115
+ protectedResult.note = 'pièce protégée bloquée';
116
+ }
130
117
  testResults.push(protectedResult);
131
118
 
132
- // 2. commit-track.mjs — réponse d'outil en échec : exerce le contrat
133
- // d'entrée/sortie sans créer de vrai commit pendant l'installation.
134
- const commitResult = runHookSelfTest('commit-track.mjs', HOOK_SCRIPTS.commit, {
135
- hook_event_name: 'PostToolUse',
136
- session_id: 'installer-selftest',
137
- cwd: REPO_ROOT,
138
- transcript_path: '',
139
- permission_mode: 'default',
140
- tool_name: 'Edit',
141
- tool_input: { file_path: path.join(REPO_ROOT, 'README.md') },
142
- tool_response: { success: false },
143
- tool_use_id: 'toolu_installer_commit_selftest',
144
- });
145
- testResults.push(commitResult);
146
-
147
- // 3. billing-track.mjs — une vraie charge utile Stop : ajoute une ligne
148
- // (clairement identifiée) au registre du mois et écrit une synthèse,
149
- // ce qui prouve le chemin d'écriture de bout en bout.
150
- const billingResult = runHookSelfTest('billing-track.mjs', HOOK_SCRIPTS.billing, {
151
- hook_event_name: 'Stop',
152
- session_id: 'installer-selftest',
153
- cwd: REPO_ROOT,
154
- transcript_path: path.join(testDir, 'transcript-selftest.jsonl'),
155
- permission_mode: 'default',
156
- last_assistant_message: 'Test d\'installation PieceMaker — vérification du hook de facturation.',
157
- stop_reason: 'end_turn',
158
- });
159
- testResults.push(billingResult);
160
-
161
- // 4. classify-ai-documents.mjs — un document « créé par l'IA » dans le bac
119
+ // 2. classify-ai-documents.mjs — un document « créé par l'IA » dans le bac
162
120
  // de test enregistré doit ressortir classé « espace de travail » dans
163
121
  // `.piecemaker/protection.json`, sans quoi l'IA ne pourrait pas se
164
122
  // relire. On vérifie l'effet de bord, pas seulement le code de sortie.
@@ -201,7 +159,6 @@ export async function install(ctx) {
201
159
  if (r.ok) log.ok(`${r.label} : ${r.note}`);
202
160
  else log.error(`${r.label} : ${r.note}`);
203
161
  }
204
- log.detail(`Une ligne de test a été ajoutée à ${path.join(BILLING_DIR, `${new Date().toISOString().slice(0, 7)}.jsonl`)} (session "installer-selftest") — supprimez-la si besoin.`);
205
162
  if (!fs.existsSync(HOOKS_JSON)) {
206
163
  log.warn(`hooks/hooks.json introuvable dans le plugin : ${HOOKS_JSON}`);
207
164
  }
@@ -232,12 +189,9 @@ export async function install(ctx) {
232
189
  return { status: 'done', note: '' };
233
190
  }
234
191
 
235
- export async function check(ctx) {
192
+ export async function check() {
236
193
  const scriptsExist = Object.values(HOOK_SCRIPTS).every((p) => fs.existsSync(p));
237
194
  const hooksJsonExists = fs.existsSync(HOOKS_JSON);
238
- const dirsExist = fs.existsSync(BILLING_DIR) && fs.existsSync(SYNTHESE_DIR);
239
- const cfg = ctx.config || {};
240
- const configOk = Boolean(cfg.commits && cfg.billing);
241
195
  const hooksRegistered = claudeHooksStatus(REPO_ROOT, os.homedir()).ok;
242
196
  const statusLine = claudeStatusLineStatus(REPO_ROOT, os.homedir());
243
197
  const statusLineOk = statusLine.ok || statusLine.conflict; // une statusLine personnelle n'est pas une anomalie
@@ -245,8 +199,8 @@ export async function check(ctx) {
245
199
  if (!scriptsExist || !hooksJsonExists) {
246
200
  return { status: 'failed', note: 'Fichiers du plugin manquants (scripts ou hooks.json) — réinstallez piecemaker-plugin/.' };
247
201
  }
248
- if (!dirsExist || !configOk || !hooksRegistered) {
249
- return { status: 'partial', note: 'Scripts présents mais configuration, enregistrement Claude ou répertoires de facturation incomplets — relancez cette étape.' };
202
+ if (!hooksRegistered) {
203
+ return { status: 'partial', note: 'Scripts présents mais hooks non enregistrés dans Claude Code — relancez cette étape.' };
250
204
  }
251
205
  if (!statusLineOk) {
252
206
  return { status: 'partial', note: `statusLine Claude Code absente ou périmée (${statusLine.reason}) — relancez cette étape.` };
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Étape 12 — serveur MCP « piecemaker » (conversion, chronologie).
2
+ * Étape 12 — serveur MCP « piecemaker » (conversion).
3
3
  *
4
4
  * Les commandes de conversion sont aujourd'hui présentées au
5
5
  * modèle par du texte injecté dans les templates, sans rien qui relie ce
@@ -21,8 +21,8 @@ import { REPO_ROOT, commandExists, run, runCapture } from '../lib/platform.mjs';
21
21
 
22
22
  export const meta = {
23
23
  id: '12-mcp-piecemaker',
24
- label: 'Serveur MCP piecemaker (conversion, chronologie)',
25
- description: 'Enregistre dans Claude Code le serveur MCP qui expose la conversion et la chronologie',
24
+ label: 'Serveur MCP piecemaker (conversion)',
25
+ description: 'Enregistre dans Claude Code le serveur MCP qui expose la conversion',
26
26
  };
27
27
 
28
28
  const SERVER_NAME = 'piecemaker';
@@ -1,7 +1,7 @@
1
1
  # MCP PieceMaker
2
2
 
3
3
  Point d'entrée du serveur MCP `piecemaker` (`piecemaker/server.mjs`), qui expose
4
- les outils `chronologie` et `conversion` en pilotant
4
+ l'outil `conversion` en pilotant
5
5
  `installer/bin/piecemaker.mjs`.
6
6
 
7
7
  - `REPO_ROOT`, calculé deux niveaux au-dessus de ce fichier, résout vers
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
- * Serveur MCP « piecemaker » — outils de conversion et de chronologie exposés
3
+ * Serveur MCP « piecemaker » — outil de conversion exposé
4
4
  * à Claude Code (et à tout client MCP) sans passer
5
5
  * par du texte injecté dans un CLAUDE.md.
6
6
  *
@@ -10,10 +10,10 @@
10
10
  * d'arguments, même localisation de dossier, une seule implémentation
11
11
  * (voir `installer/bin/piecemaker.mjs`).
12
12
  *
13
- * Les commandes lancées ici (`conversion --json`, `chronology --json`, `chronology --action write/edit
14
- * --json`) court-circuitent toutes le bandeau, la vérification de mise à jour
15
- * et le menu interactif (`installer/bin/piecemaker.mjs:1006-1009`) : aucun
16
- * service PieceMaker n'est démarré, arrêté ni redémarré par ce serveur.
13
+ * La commande lancée ici (`conversion --json`) court-circuite le bandeau, la
14
+ * vérification de mise à jour et le menu interactif. Elle confie le travail au
15
+ * serveur applicatif déjà lancé (application de bureau ou `piecemaker`) et ne
16
+ * démarre un serveur que si aucun ne répond.
17
17
  */
18
18
 
19
19
  import { fileURLToPath, pathToFileURL } from 'node:url';
@@ -95,28 +95,6 @@ export function conversionArgs({ dossier, pieces, force }) {
95
95
  return args;
96
96
  }
97
97
 
98
- export function chronologyArgs({ dossier }) {
99
- return ['chronology', '--json', '--case', dossier];
100
- }
101
-
102
- export function chronologyCorrectionArgs(action, { dossier, path: piecePath, nature, dateIso, localisation, fields, entityAdditions, entityExclusions, reason }) {
103
- const correction = {};
104
- if (nature !== undefined) correction.nature = nature;
105
- if (dateIso !== undefined) correction.dateIso = dateIso;
106
- if (localisation !== undefined) correction.localisation = localisation;
107
- if (fields !== undefined) correction.fields = fields;
108
- if (reason !== undefined) correction.reason = reason;
109
- if (entityAdditions !== undefined || entityExclusions !== undefined) {
110
- correction.entityDecisions = {
111
- additions: entityAdditions || [],
112
- exclusions: entityExclusions || [],
113
- };
114
- }
115
- const args = ['chronology', '--action', action, '--path', piecePath, '--correction-json', JSON.stringify(correction), '--json'];
116
- if (dossier) args.push('--case', dossier);
117
- return args;
118
- }
119
-
120
98
  /** Dossier ciblé par un appel d'outil : celui demandé, sinon la session en cours. */
121
99
  export function resolveDossier(dossier) {
122
100
  return dossier && String(dossier).trim() ? dossier : process.cwd();
@@ -126,21 +104,6 @@ const DOSSIER_SCHEMA = z.string()
126
104
  .optional()
127
105
  .describe('Chemin absolu du dossier juridique ciblé. Par défaut, le répertoire de la session Claude Code en cours.');
128
106
 
129
- const CHRONOLOGY_CORRECTION_SCHEMA = {
130
- dossier: DOSSIER_SCHEMA,
131
- path: z.string().min(1).describe('Chemin relatif de la pièce, tel qu\'indexé par la chronologie.'),
132
- nature: z.string().optional().describe('Nature de l\'acte (ex. « Assignation », « Conclusions »).'),
133
- dateIso: z.string().optional().describe('Date de la pièce au format ISO 8601 (AAAA-MM-JJ).'),
134
- localisation: z.string().optional().describe('Juridiction ou localisation associée à la pièce.'),
135
- fields: z.array(z.object({ label: z.string(), value: z.string() })).optional()
136
- .describe('Champs personnalisés additionnels de la pièce.'),
137
- entityAdditions: z.array(z.string()).optional()
138
- .describe('Codes d\'entités (personnes/sociétés pseudonymisées) à rattacher manuellement à la pièce.'),
139
- entityExclusions: z.array(z.string()).optional()
140
- .describe('Codes d\'entités détectés automatiquement à exclure manuellement de la pièce.'),
141
- reason: z.string().optional().describe('Motif de la correction, consigné dans l\'historique du dossier.'),
142
- };
143
-
144
107
  /**
145
108
  * Construit le serveur MCP et y enregistre les outils. Séparé de
146
109
  * `main()` pour rester testable sans jamais brancher de transport stdio.
@@ -160,45 +123,19 @@ export function createServer({ execFn } = {}) {
160
123
  .describe('Noms ou chemins relatifs des pièces à convertir. Par défaut, toutes les pièces pas encore prêtes.'),
161
124
  force: z.boolean().optional().describe('Reconvertit et rescanne même les pièces déjà prêtes.'),
162
125
  },
126
+ annotations: {
127
+ title: 'Conversion et pseudonymisation des pièces',
128
+ readOnlyHint: false,
129
+ destructiveHint: false,
130
+ idempotentHint: true,
131
+ openWorldHint: false,
132
+ },
163
133
  }, async ({ dossier, pieces, force }) => {
164
134
  const resolved = resolveDossier(dossier);
165
135
  const result = await run(conversionArgs({ dossier: resolved, pieces, force }), resolved);
166
136
  return toToolResult(result);
167
137
  });
168
138
 
169
- server.registerTool('chronologie', {
170
- description: 'Affiche la chronologie pseudonymisée du dossier juridique (dates, pièces, événements), en JSON.',
171
- inputSchema: {
172
- dossier: DOSSIER_SCHEMA,
173
- },
174
- }, async ({ dossier }) => {
175
- const resolved = resolveDossier(dossier);
176
- const result = await run(chronologyArgs({ dossier: resolved }), resolved);
177
- return toToolResult(result);
178
- });
179
-
180
- server.registerTool('write_chronology', {
181
- description: 'Crée une correction manuelle de chronologie pour une pièce (date, nature d\'acte, '
182
- + 'localisation, personnes citées, champs custom). Échoue si une correction existe déjà pour cette '
183
- + 'pièce — utiliser edit_chronology pour la modifier.',
184
- inputSchema: CHRONOLOGY_CORRECTION_SCHEMA,
185
- }, async ({ dossier, ...params }) => {
186
- const resolved = resolveDossier(dossier);
187
- const result = await run(chronologyCorrectionArgs('write', { dossier: resolved, ...params }), resolved);
188
- return toToolResult(result);
189
- });
190
-
191
- server.registerTool('edit_chronology', {
192
- description: 'Modifie la correction manuelle de chronologie déjà enregistrée pour une pièce (date, '
193
- + 'nature d\'acte, localisation, personnes citées, champs custom). Échoue si aucune correction '
194
- + 'n\'existe pour cette pièce — utiliser write_chronology pour en créer une.',
195
- inputSchema: CHRONOLOGY_CORRECTION_SCHEMA,
196
- }, async ({ dossier, ...params }) => {
197
- const resolved = resolveDossier(dossier);
198
- const result = await run(chronologyCorrectionArgs('edit', { dossier: resolved, ...params }), resolved);
199
- return toToolResult(result);
200
- });
201
-
202
139
  return server;
203
140
  }
204
141
 
@@ -21,15 +21,6 @@
21
21
  }
22
22
  ]
23
23
  },
24
- {
25
- "matcher": "Write|Edit",
26
- "hooks": [
27
- {
28
- "type": "command",
29
- "command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/commit-track.mjs\""
30
- }
31
- ]
32
- },
33
24
  {
34
25
  "matcher": "Write",
35
26
  "hooks": [
@@ -48,37 +39,6 @@
48
39
  }
49
40
  ]
50
41
  }
51
- ],
52
- "Stop": [
53
- {
54
- "matcher": "*",
55
- "hooks": [
56
- {
57
- "type": "command",
58
- "command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/billing-track.mjs\""
59
- }
60
- ]
61
- },
62
- {
63
- "matcher": "*",
64
- "hooks": [
65
- {
66
- "type": "command",
67
- "command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/session-commit.mjs\""
68
- }
69
- ]
70
- }
71
- ],
72
- "TaskCompleted": [
73
- {
74
- "matcher": "*",
75
- "hooks": [
76
- {
77
- "type": "command",
78
- "command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/billing-track.mjs\""
79
- }
80
- ]
81
- }
82
42
  ]
83
43
  }
84
44
  }
@@ -72,8 +72,8 @@ async function main() {
72
72
  const pdfPath = path.join(rechercheDir, `${slug}.pdf`);
73
73
 
74
74
  // Le modèle n'a vu que des codes ; l'historique du cabinet doit rester
75
- // lisible → on ré-identifie le Markdown final avant écriture disque (même
76
- // logique que commit-track sur les libellés). Idempotent.
75
+ // lisible → on ré-identifie le Markdown final avant écriture disque.
76
+ // Idempotent.
77
77
  let markdown = renderMarkdown(data, { caseName: located.caseName });
78
78
  const legalCase = resolveProjectCaseMapping(located.caseRoot);
79
79
  if (legalCase?.reverse_mapping) markdown = revertMapping(markdown, legalCase.reverse_mapping);
@@ -7,7 +7,6 @@ const CASE_FOLDER_STRUCTURE_VERSION = 1;
7
7
  const CASE_FOLDER_STRUCTURE_RELATIVE = '.piecemaker/case-folder-structure.json';
8
8
 
9
9
  const DEFAULT_CASE_FOLDER_STRUCTURE = Object.freeze({
10
- administrative: '00_ADMINISTRATIF_ET_FACTURATION',
11
10
  correspondence: '01_CORRESPONDANCE',
12
11
  correspondenceClient: '01_Client',
13
12
  correspondenceOpposingCounsel: '02_Avocats_adverses',
@@ -65,7 +64,6 @@ function normalizeCaseFolderStructure(value = {}) {
65
64
  }
66
65
 
67
66
  ensureUniqueSiblingNames(structure, [
68
- 'administrative',
69
67
  'correspondence',
70
68
  'dataRoom',
71
69
  'drafts',
@@ -107,9 +105,8 @@ function readStructureManifest(caseRoot) {
107
105
  }
108
106
 
109
107
  /**
110
- * La structure est figée par dossier lors de son premier enregistrement. Ainsi,
111
- * personnaliser les prochains dossiers ne change pas le routage du Markdown
112
- * des dossiers déjà existants.
108
+ * Seuls les anciens dossiers portent encore un manifeste d'arborescence : il
109
+ * continue de router leur Markdown. Sans manifeste, le dossier n'est pas géré.
113
110
  */
114
111
  function readCaseFolderStructure(caseRoot, config = {}) {
115
112
  const manifest = readStructureManifest(caseRoot);
@@ -119,78 +116,6 @@ function readCaseFolderStructure(caseRoot, config = {}) {
119
116
  };
120
117
  }
121
118
 
122
- function caseFolderDirectoriesFromStructure(structure) {
123
- return [
124
- structure.administrative,
125
- structure.correspondence,
126
- path.join(structure.correspondence, structure.correspondenceMarkdown),
127
- path.join(structure.correspondence, structure.correspondenceClient),
128
- path.join(structure.correspondence, structure.correspondenceOpposingCounsel),
129
- path.join(structure.correspondence, structure.correspondenceThirdParties),
130
- structure.dataRoom,
131
- path.join(structure.dataRoom, structure.dataRoomMarkdown),
132
- structure.drafts,
133
- structure.notesAndResearch,
134
- structure.procedure,
135
- ];
136
- }
137
-
138
- /** Chemins relatifs, dans l'ordre lisible de l'arborescence documentée. */
139
- function caseFolderDirectories(config = {}) {
140
- return caseFolderDirectoriesFromStructure(configuredCaseFolderStructure(config));
141
- }
142
-
143
- function writeStructureManifest(file, structure) {
144
- fs.mkdirSync(path.dirname(file), { recursive: true });
145
- const temporary = `${file}.piecemaker-${process.pid}.tmp`;
146
- fs.writeFileSync(temporary, `${JSON.stringify({
147
- version: CASE_FOLDER_STRUCTURE_VERSION,
148
- structure,
149
- }, null, 2)}\n`, { encoding: 'utf8', mode: 0o600 });
150
- fs.renameSync(temporary, file);
151
- }
152
-
153
- /**
154
- * Crée uniquement les dossiers manquants. Aucun fichier ni dossier existant
155
- * n'est renommé ou remplacé quand l'utilisateur modifie les noms par défaut.
156
- */
157
- function ensureCaseFolderStructure(caseRoot, config = {}) {
158
- const requested = String(caseRoot || '').trim();
159
- if (!requested || !path.isAbsolute(requested)) throw new Error('Racine du dossier juridique invalide.');
160
- const root = path.resolve(requested);
161
- let rootStat;
162
- try {
163
- rootStat = fs.statSync(root);
164
- } catch {
165
- throw new Error('Racine du dossier juridique introuvable.');
166
- }
167
- if (!rootStat.isDirectory()) throw new Error('Racine du dossier juridique invalide.');
168
-
169
- const created = [];
170
- const manifest = readStructureManifest(root);
171
- const structure = manifest.structure || configuredCaseFolderStructure(config);
172
- const directories = caseFolderDirectoriesFromStructure(structure);
173
- for (const relative of directories) {
174
- const target = path.join(root, relative);
175
- if (fs.existsSync(target)) {
176
- if (!fs.statSync(target).isDirectory()) {
177
- throw new Error(`L’emplacement « ${relative} » existe mais n’est pas un dossier.`);
178
- }
179
- continue;
180
- }
181
- fs.mkdirSync(target, { recursive: true });
182
- created.push(relative.split(path.sep).join('/'));
183
- }
184
- if (!manifest.exists) writeStructureManifest(manifest.file, structure);
185
-
186
- return {
187
- file: manifest.file,
188
- names: structure,
189
- directories: directories.map((relative) => relative.split(path.sep).join('/')),
190
- created,
191
- };
192
- }
193
-
194
119
  function relativeSegments(relativePath) {
195
120
  const value = String(relativePath || '').replaceAll('\\', '/').replace(/^\.\//, '');
196
121
  if (!value || value.startsWith('/') || value.split('/').some((segment) => !segment || segment === '.' || segment === '..')) {
@@ -278,20 +203,7 @@ function structuredMarkdownCounterpart(absolutePath, caseRoot, config = {}) {
278
203
  }
279
204
 
280
205
  module.exports = {
281
- CASE_FOLDER_KEYS,
282
- CASE_FOLDER_STRUCTURE_KEY,
283
- CASE_FOLDER_STRUCTURE_RELATIVE,
284
- DEFAULT_CASE_FOLDER_STRUCTURE,
285
- caseConversionOutputDirectory,
286
- caseFolderDirectories,
287
- caseFolderStructureFile,
288
- classifyCaseFolderPath,
289
206
  classifyRelativeCaseFolderPath,
290
- configuredCaseFolderStructure,
291
- ensureCaseFolderStructure,
292
- isCaseGeneratedPath,
293
- normalizeCaseFolderStructure,
294
207
  readCaseFolderStructure,
295
208
  structuredMarkdownCounterpart,
296
- validateCaseFolderName,
297
209
  };
@@ -4,6 +4,7 @@ const os = require('node:os');
4
4
  const path = require('node:path');
5
5
 
6
6
  const PROJECTS_FILE = 'projects.json';
7
+ const ANONYMIZED_PROJECTS_FILE = 'anonymized-projects.json';
7
8
 
8
9
  function piecemakerHome() {
9
10
  return process.env.PIECEMAKER_HOME || path.join(os.homedir(), '.piecemaker');
@@ -13,6 +14,10 @@ function projectsFile() {
13
14
  return path.join(piecemakerHome(), PROJECTS_FILE);
14
15
  }
15
16
 
17
+ function anonymizedProjectsFile() {
18
+ return path.join(piecemakerHome(), ANONYMIZED_PROJECTS_FILE);
19
+ }
20
+
16
21
  function realDirectory(value) {
17
22
  const requested = String(value || '').trim();
18
23
  if (!requested || !path.isAbsolute(requested)) return null;
@@ -41,24 +46,31 @@ function isEligibleProjectFolder(folder) {
41
46
  return !excludedRoots().some((root) => isInsideOrEqualPath(root, folder));
42
47
  }
43
48
 
44
- function readProjectSources() {
49
+ function readSources(file) {
45
50
  try {
46
- const parsed = JSON.parse(fs.readFileSync(projectsFile(), 'utf8'));
51
+ const parsed = JSON.parse(fs.readFileSync(file, 'utf8'));
47
52
  return parsed?.sources && typeof parsed.sources === 'object' && !Array.isArray(parsed.sources) ? parsed.sources : {};
48
53
  } catch {
49
54
  return {};
50
55
  }
51
56
  }
52
57
 
58
+ function publishedFolders(file) {
59
+ const folders = Object.values(readSources(file)).flatMap((list) => (Array.isArray(list) ? list : []));
60
+ return [...new Set(folders.map(realDirectory).filter(Boolean))].filter(isEligibleProjectFolder);
61
+ }
62
+
53
63
  /** Absolute, existing and de-duplicated project folders, across every publishing server. */
54
64
  function registeredProjectFolders() {
55
- const folders = Object.values(readProjectSources()).flatMap((list) => (Array.isArray(list) ? list : []));
56
- return [...new Set(folders.map(realDirectory).filter(Boolean))].filter(isEligibleProjectFolder);
65
+ return publishedFolders(projectsFile());
66
+ }
67
+
68
+ function anonymizedProjectFolders() {
69
+ return publishedFolders(anonymizedProjectsFile());
57
70
  }
58
71
 
59
- function publishProjectSource(sourceId, folders) {
60
- const file = projectsFile();
61
- const sources = readProjectSources();
72
+ function writeSource(file, sourceId, folders) {
73
+ const sources = readSources(file);
62
74
  if (folders === null) delete sources[sourceId];
63
75
  else sources[sourceId] = [...new Set(folders.map((folder) => path.resolve(String(folder))))].sort();
64
76
  fs.mkdirSync(path.dirname(file), { recursive: true });
@@ -72,6 +84,11 @@ function publishProjectSource(sourceId, folders) {
72
84
  }
73
85
  }
74
86
 
87
+ function publishProjectSource(sourceId, folders, anonymizedFolders = []) {
88
+ writeSource(projectsFile(), sourceId, folders);
89
+ writeSource(anonymizedProjectsFile(), sourceId, folders === null ? null : anonymizedFolders);
90
+ }
91
+
75
92
  function resolveTarget(target) {
76
93
  try {
77
94
  return fs.realpathSync(path.resolve(String(target)));
@@ -101,6 +118,7 @@ function locateProjectCase(target) {
101
118
  for (const caseRoot of roots) {
102
119
  if (!isInsideOrEqualPath(caseRoot, absolute)) continue;
103
120
  return {
121
+ anonymized: anonymizedProjectFolders().includes(caseRoot),
104
122
  casesRoot: path.dirname(caseRoot),
105
123
  caseName: path.basename(caseRoot),
106
124
  caseRoot,
@@ -113,6 +131,7 @@ function locateProjectCase(target) {
113
131
  }
114
132
 
115
133
  module.exports = {
134
+ anonymizedProjectFolders,
116
135
  locateProjectCase,
117
136
  publishProjectSource,
118
137
  registeredProjectFolders,