pipcode 0.0.0-stage → 2.1.1

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 (401) hide show
  1. package/LICENSE +47 -0
  2. package/README.md +44 -2
  3. package/auto.js +40 -0
  4. package/bin/setup.js +31 -0
  5. package/bin/teardown.js +18 -0
  6. package/grammars/tree-sitter-javascript.wasm +0 -0
  7. package/grammars/tree-sitter-python.wasm +0 -0
  8. package/grammars/tree-sitter-tsx.wasm +0 -0
  9. package/grammars/tree-sitter-typescript.wasm +0 -0
  10. package/index.js +39 -0
  11. package/package.json +45 -4
  12. package/src/auth/auth-email.js +32 -0
  13. package/src/auth/trusted-key.js +13 -0
  14. package/src/auto/adjust.js +41 -0
  15. package/src/auto/amnesia.js +37 -0
  16. package/src/auto/anonymize-tasks.js +13 -0
  17. package/src/auto/apply-diff-backed.js +10 -0
  18. package/src/auto/apply-diff.js +60 -0
  19. package/src/auto/ask-guard.js +16 -0
  20. package/src/auto/ask-pause.js +36 -0
  21. package/src/auto/ask-protocol.js +6 -0
  22. package/src/auto/ask-user.js +29 -0
  23. package/src/auto/backup-touched.js +30 -0
  24. package/src/auto/budget.js +52 -0
  25. package/src/auto/cancel.js +20 -0
  26. package/src/auto/constants.js +30 -0
  27. package/src/auto/crypto-util.js +17 -0
  28. package/src/auto/decompose.js +47 -0
  29. package/src/auto/diff.js +19 -0
  30. package/src/auto/fs-list.js +34 -0
  31. package/src/auto/git-base.js +16 -0
  32. package/src/auto/git-init.js +39 -0
  33. package/src/auto/git-restore.js +34 -0
  34. package/src/auto/git-scope.js +10 -0
  35. package/src/auto/git.js +38 -0
  36. package/src/auto/mission-docs.js +29 -0
  37. package/src/auto/mission-line.js +22 -0
  38. package/src/auto/node-check.js +30 -0
  39. package/src/auto/observe.js +23 -0
  40. package/src/auto/orchestrator.js +30 -0
  41. package/src/auto/patch-ask.js +16 -0
  42. package/src/auto/paths.js +18 -0
  43. package/src/auto/plan.js +42 -0
  44. package/src/auto/pricing.js +49 -0
  45. package/src/auto/project-files.js +12 -0
  46. package/src/auto/prompts.js +24 -0
  47. package/src/auto/quote-format.js +33 -0
  48. package/src/auto/retry.js +40 -0
  49. package/src/auto/run-lock.js +40 -0
  50. package/src/auto/run-loop.js +38 -0
  51. package/src/auto/run.js +49 -0
  52. package/src/auto/snapshot-gate.js +51 -0
  53. package/src/auto/snapshot-paths.js +33 -0
  54. package/src/auto/snapshot-restore.js +59 -0
  55. package/src/auto/snapshot-take.js +30 -0
  56. package/src/auto/state.js +52 -0
  57. package/src/auto/step-commit.js +46 -0
  58. package/src/auto/step-green.js +38 -0
  59. package/src/auto/step-loop.js +56 -0
  60. package/src/auto/step-prep.js +60 -0
  61. package/src/auto/step-qa.js +53 -0
  62. package/src/auto/step.js +30 -0
  63. package/src/auto/stop-options.js +34 -0
  64. package/src/auto/stop-report.js +17 -0
  65. package/src/auto/tasks.js +44 -0
  66. package/src/cli/apply-loop.js +29 -0
  67. package/src/cli/apply-toml.js +26 -0
  68. package/src/cli/apply.js +45 -0
  69. package/src/cli/ask-prepare.js +37 -0
  70. package/src/cli/auth-branches.js +53 -0
  71. package/src/cli/auth-interactive.js +46 -0
  72. package/src/cli/auto-ask.js +33 -0
  73. package/src/cli/auto-audit.js +16 -0
  74. package/src/cli/auto-cli.js +46 -0
  75. package/src/cli/auto-consent.js +22 -0
  76. package/src/cli/auto-flags.js +39 -0
  77. package/src/cli/auto-gate-session.js +27 -0
  78. package/src/cli/auto-gate.js +34 -0
  79. package/src/cli/auto-loader.js +17 -0
  80. package/src/cli/auto-plan.js +36 -0
  81. package/src/cli/auto-preflight.js +35 -0
  82. package/src/cli/auto-quote.js +28 -0
  83. package/src/cli/auto-report.js +36 -0
  84. package/src/cli/auto-resume.js +28 -0
  85. package/src/cli/auto-rules.js +20 -0
  86. package/src/cli/auto-run.js +26 -0
  87. package/src/cli/auto-task.js +26 -0
  88. package/src/cli/banner.js +20 -0
  89. package/src/cli/centralize.js +23 -0
  90. package/src/cli/collect-step.js +25 -0
  91. package/src/cli/config-pairs.js +46 -0
  92. package/src/cli/config.js +51 -0
  93. package/src/cli/detect-os.js +11 -0
  94. package/src/cli/dispatch.js +36 -0
  95. package/src/cli/env-file.js +44 -0
  96. package/src/cli/env-load.js +28 -0
  97. package/src/cli/env-parse.js +39 -0
  98. package/src/cli/env-rebuild.js +33 -0
  99. package/src/cli/gitignore-env.js +46 -0
  100. package/src/cli/help.js +28 -0
  101. package/src/cli/ide-entries.js +42 -0
  102. package/src/cli/ide-paths.js +37 -0
  103. package/src/cli/ide-roots.js +25 -0
  104. package/src/cli/interactive.js +26 -0
  105. package/src/cli/jetbrains.js +40 -0
  106. package/src/cli/legacy-entry.js +17 -0
  107. package/src/cli/main.js +42 -0
  108. package/src/cli/memory-cli.js +15 -0
  109. package/src/cli/prompts.js +24 -0
  110. package/src/cli/server-entry.js +42 -0
  111. package/src/cli/setup-args.js +39 -0
  112. package/src/cli/target-manual.js +24 -0
  113. package/src/cli/target-select.js +51 -0
  114. package/src/cli/toml-merge.js +56 -0
  115. package/src/cli/trial-check.js +15 -0
  116. package/src/cli/welcome.js +7 -0
  117. package/src/core/abort-signal.js +38 -0
  118. package/src/core/auto-deps.js +29 -0
  119. package/src/core/cache-ttl.js +21 -0
  120. package/src/core/config-base.js +38 -0
  121. package/src/core/config-cache.js +50 -0
  122. package/src/core/config.js +58 -0
  123. package/src/core/constants.js +31 -0
  124. package/src/core/crypto.js +45 -0
  125. package/src/core/dlp.js +51 -0
  126. package/src/core/doctrine-all.js +31 -0
  127. package/src/core/doctrine-caches.js +10 -0
  128. package/src/core/doctrine-fetch.js +49 -0
  129. package/src/core/doctrine-one.js +18 -0
  130. package/src/core/doctrine.js +39 -0
  131. package/src/core/dual-stream-parser.js +55 -0
  132. package/src/core/exports.js +28 -0
  133. package/src/core/google-route.js +17 -0
  134. package/src/core/identity.js +23 -0
  135. package/src/core/lang.js +19 -0
  136. package/src/core/leak-guard.js +40 -0
  137. package/src/core/llm-builder-anthropic.js +26 -0
  138. package/src/core/llm-builder-gemini.js +22 -0
  139. package/src/core/llm-builder-openai.js +26 -0
  140. package/src/core/llm-call.js +53 -0
  141. package/src/core/llm-config-secondary.js +28 -0
  142. package/src/core/llm-config.js +32 -0
  143. package/src/core/llm-errors.js +25 -0
  144. package/src/core/llm-exchange.js +27 -0
  145. package/src/core/llm-extract.js +52 -0
  146. package/src/core/llm-label.js +33 -0
  147. package/src/core/llm-ollama-request.js +32 -0
  148. package/src/core/llm-ollama.js +42 -0
  149. package/src/core/llm-prompts.js +14 -0
  150. package/src/core/llm-providers.js +26 -0
  151. package/src/core/llm-request.js +17 -0
  152. package/src/core/llm-retry-429.js +23 -0
  153. package/src/core/llm-stream-events.js +29 -0
  154. package/src/core/llm-stream.js +42 -0
  155. package/src/core/llm-unbounded.js +15 -0
  156. package/src/core/llm-warmup.js +44 -0
  157. package/src/core/manifest.js +42 -0
  158. package/src/core/master-prompt.js +11 -0
  159. package/src/core/model-id.js +19 -0
  160. package/src/core/ollama-lines.js +48 -0
  161. package/src/core/ollama.js +43 -0
  162. package/src/core/pkg-manager.js +35 -0
  163. package/src/core/process-guards.js +47 -0
  164. package/src/core/progress-emit.js +25 -0
  165. package/src/core/purge-key.js +20 -0
  166. package/src/core/reasoning-cut.js +19 -0
  167. package/src/core/reasoning-models.js +8 -0
  168. package/src/core/session-net.js +60 -0
  169. package/src/core/session-resilient.js +44 -0
  170. package/src/core/session.js +49 -0
  171. package/src/core/sse-parser.js +25 -0
  172. package/src/core/stdout-guard.js +11 -0
  173. package/src/core/tier.js +48 -0
  174. package/src/core/trial-resolve.js +22 -0
  175. package/src/core/trial-session.js +60 -0
  176. package/src/core/trial-status.js +35 -0
  177. package/src/core/usage-cost.js +25 -0
  178. package/src/core/usage-extract.js +28 -0
  179. package/src/core/usage-meter.js +41 -0
  180. package/src/engine/analyze-need.js +24 -0
  181. package/src/engine/analyze-ux.js +42 -0
  182. package/src/engine/ask-history.js +43 -0
  183. package/src/engine/ask-llm.js +40 -0
  184. package/src/engine/ask-prompt.js +48 -0
  185. package/src/engine/ask-rules.js +13 -0
  186. package/src/engine/ast-call-rules-danger.js +24 -0
  187. package/src/engine/ast-call-rules-inject.js +29 -0
  188. package/src/engine/ast-context.js +28 -0
  189. package/src/engine/ast-import-helpers.js +46 -0
  190. package/src/engine/ast-lang.js +24 -0
  191. package/src/engine/ast-node-helpers.js +51 -0
  192. package/src/engine/ast-rules-calls.js +16 -0
  193. package/src/engine/ast-rules-graph.js +58 -0
  194. package/src/engine/ast-rules-js.js +30 -0
  195. package/src/engine/ast-rules-jsx-py.js +26 -0
  196. package/src/engine/ast-runtime.js +60 -0
  197. package/src/engine/ast-scan.js +48 -0
  198. package/src/engine/audit-citations.js +28 -0
  199. package/src/engine/audit-facts.js +47 -0
  200. package/src/engine/audit-report.js +40 -0
  201. package/src/engine/audit-task.js +37 -0
  202. package/src/engine/cap-cache.js +50 -0
  203. package/src/engine/cap-eval.js +49 -0
  204. package/src/engine/cap-http.js +32 -0
  205. package/src/engine/cap-meta-anthropic.js +15 -0
  206. package/src/engine/cap-meta-ollama.js +15 -0
  207. package/src/engine/cap-meta-openrouter.js +19 -0
  208. package/src/engine/cap-meta.js +19 -0
  209. package/src/engine/cap-probe.js +34 -0
  210. package/src/engine/cap-types.js +29 -0
  211. package/src/engine/chat-doctrine.js +31 -0
  212. package/src/engine/checkpoint-control.js +23 -0
  213. package/src/engine/checkpoint-detect.js +19 -0
  214. package/src/engine/checkpoint-rules.js +19 -0
  215. package/src/engine/code-decls.js +12 -0
  216. package/src/engine/code-graph.js +52 -0
  217. package/src/engine/coerce.js +8 -0
  218. package/src/engine/collect.js +21 -0
  219. package/src/engine/compute-impact.js +17 -0
  220. package/src/engine/context-files.js +42 -0
  221. package/src/engine/context-fit.js +28 -0
  222. package/src/engine/context-margin.js +37 -0
  223. package/src/engine/context-relay.js +60 -0
  224. package/src/engine/control-duo.js +38 -0
  225. package/src/engine/control-phase.js +32 -0
  226. package/src/engine/control-prompt.js +25 -0
  227. package/src/engine/control-verdict.js +36 -0
  228. package/src/engine/default-steps.js +7 -0
  229. package/src/engine/deps-parse-js.js +49 -0
  230. package/src/engine/deps-parse-more.js +30 -0
  231. package/src/engine/deps-parse-other.js +41 -0
  232. package/src/engine/deps-parse.js +31 -0
  233. package/src/engine/det-builders-1.js +38 -0
  234. package/src/engine/det-builders-2.js +50 -0
  235. package/src/engine/detect-domains.js +27 -0
  236. package/src/engine/diff-files.js +35 -0
  237. package/src/engine/doc-suite.js +39 -0
  238. package/src/engine/doctrine-prefix.js +13 -0
  239. package/src/engine/domain-regex.js +11 -0
  240. package/src/engine/exports.js +35 -0
  241. package/src/engine/extract-deterministic.js +33 -0
  242. package/src/engine/extract-llm.js +48 -0
  243. package/src/engine/facts-binary.js +12 -0
  244. package/src/engine/facts-embedded.js +18 -0
  245. package/src/engine/facts-functions.js +21 -0
  246. package/src/engine/facts-html.js +22 -0
  247. package/src/engine/facts-inventory.js +33 -0
  248. package/src/engine/facts-lang.js +34 -0
  249. package/src/engine/facts-measure.js +43 -0
  250. package/src/engine/facts-patterns.js +28 -0
  251. package/src/engine/facts-project.js +26 -0
  252. package/src/engine/facts-size.js +26 -0
  253. package/src/engine/facts-wordpress.js +36 -0
  254. package/src/engine/generate-architecture-guide.js +46 -0
  255. package/src/engine/generate-readme.js +38 -0
  256. package/src/engine/generate-runbook.js +47 -0
  257. package/src/engine/git-facts.js +16 -0
  258. package/src/engine/history-purge.js +16 -0
  259. package/src/engine/json-balance.js +37 -0
  260. package/src/engine/json-safe.js +13 -0
  261. package/src/engine/line-numbers.js +16 -0
  262. package/src/engine/md-build-mid.js +35 -0
  263. package/src/engine/md-build-top.js +58 -0
  264. package/src/engine/md-build.js +36 -0
  265. package/src/engine/md-parse-helpers.js +7 -0
  266. package/src/engine/md-parse-pillars.js +38 -0
  267. package/src/engine/md-parse-ux-design.js +31 -0
  268. package/src/engine/md-parse.js +33 -0
  269. package/src/engine/mission-context-prefix.js +30 -0
  270. package/src/engine/mission-guard.js +36 -0
  271. package/src/engine/mission-helpers.js +23 -0
  272. package/src/engine/mission-log.js +56 -0
  273. package/src/engine/mission-paths.js +7 -0
  274. package/src/engine/mission-store.js +51 -0
  275. package/src/engine/normalize-extraction.js +35 -0
  276. package/src/engine/normalize-impact-design.js +39 -0
  277. package/src/engine/normalize-pillars.js +36 -0
  278. package/src/engine/orchestrate-route.js +39 -0
  279. package/src/engine/orchestrator-build.js +37 -0
  280. package/src/engine/orchestrator-phases.js +55 -0
  281. package/src/engine/orchestrator.js +40 -0
  282. package/src/engine/osv.js +43 -0
  283. package/src/engine/parse-json-loose.js +36 -0
  284. package/src/engine/patch-file-sections.js +28 -0
  285. package/src/engine/patch-scan-cheat.js +52 -0
  286. package/src/engine/patch-scan-prep.js +37 -0
  287. package/src/engine/patch-scan-rules.js +51 -0
  288. package/src/engine/patch-scan.js +38 -0
  289. package/src/engine/patch-secret-guard.js +50 -0
  290. package/src/engine/patch-ui-guard.js +49 -0
  291. package/src/engine/pattern-sanitize.js +23 -0
  292. package/src/engine/plan-draft.js +37 -0
  293. package/src/engine/plan-mission.js +60 -0
  294. package/src/engine/plan-reply-legacy.js +34 -0
  295. package/src/engine/plan-reply-pillars.js +34 -0
  296. package/src/engine/plan-reply-ux.js +17 -0
  297. package/src/engine/plan-reply.js +28 -0
  298. package/src/engine/plan-summary.js +16 -0
  299. package/src/engine/preflight-text.js +27 -0
  300. package/src/engine/preflight.js +27 -0
  301. package/src/engine/project-brief.js +41 -0
  302. package/src/engine/project-context.js +49 -0
  303. package/src/engine/project-instructions.js +19 -0
  304. package/src/engine/py-imports.js +31 -0
  305. package/src/engine/relay-analysis.js +44 -0
  306. package/src/engine/relay-digest.js +42 -0
  307. package/src/engine/route-llm.js +25 -0
  308. package/src/engine/rule-text.js +12 -0
  309. package/src/engine/self-assessment.js +33 -0
  310. package/src/engine/steps-block.js +19 -0
  311. package/src/engine/ux-violations-leak.js +43 -0
  312. package/src/engine/vuln-facts.js +37 -0
  313. package/src/fs/atomic-text.js +45 -0
  314. package/src/fs/atomic-write.js +21 -0
  315. package/src/fs/backup-once.js +27 -0
  316. package/src/fs/config-path.js +39 -0
  317. package/src/fs/config-read.js +23 -0
  318. package/src/fs/config-write.js +60 -0
  319. package/src/fs/home-root.js +55 -0
  320. package/src/fs/load-config.js +36 -0
  321. package/src/fs/os-errors.js +26 -0
  322. package/src/fs/permissions.js +48 -0
  323. package/src/mcp/boot-warmup.js +9 -0
  324. package/src/mcp/call-tool.js +20 -0
  325. package/src/mcp/client-info.js +35 -0
  326. package/src/mcp/engine-rules.js +16 -0
  327. package/src/mcp/handle-dlp.js +37 -0
  328. package/src/mcp/handle-licence.js +46 -0
  329. package/src/mcp/handle-list.js +15 -0
  330. package/src/mcp/handle-memory.js +37 -0
  331. package/src/mcp/handle-skill-fetch.js +50 -0
  332. package/src/mcp/handle-skill.js +38 -0
  333. package/src/mcp/licence-text.js +29 -0
  334. package/src/mcp/prompt-licence.js +11 -0
  335. package/src/mcp/server-create.js +33 -0
  336. package/src/mcp/server-main.js +39 -0
  337. package/src/mcp/tool-dispatch.js +28 -0
  338. package/src/mcp/tool-dlp.js +20 -0
  339. package/src/mcp/tool-licence.js +17 -0
  340. package/src/mcp/tool-memory.js +32 -0
  341. package/src/mcp/tool-skill.js +24 -0
  342. package/src/mcp/tools-list.js +13 -0
  343. package/src/memory/exports.js +5 -0
  344. package/src/memory/memory-filter.js +32 -0
  345. package/src/memory/memory-gate.js +21 -0
  346. package/src/memory/memory-store.js +44 -0
  347. package/src/net/auth-browser.js +49 -0
  348. package/src/net/auth-endpoints.js +33 -0
  349. package/src/net/local-fetch.js +31 -0
  350. package/src/net/loopback.js +8 -0
  351. package/src/net/proxy-env.js +59 -0
  352. package/src/project-memory/batches.js +17 -0
  353. package/src/project-memory/cahiers.js +42 -0
  354. package/src/project-memory/decision-settle.js +15 -0
  355. package/src/project-memory/decisions.js +36 -0
  356. package/src/project-memory/index-build.js +35 -0
  357. package/src/project-memory/memory-build.js +16 -0
  358. package/src/project-memory/memory-tags.js +40 -0
  359. package/src/project-memory/pending.js +24 -0
  360. package/src/project-memory/qa-apply.js +39 -0
  361. package/src/project-memory/qa-tags.js +28 -0
  362. package/src/project-memory/recall.js +38 -0
  363. package/src/project-memory/store.js +37 -0
  364. package/src/project-memory/window.js +13 -0
  365. package/src/rules/rules-advisor.js +17 -0
  366. package/src/rules/rules-frugality.js +17 -0
  367. package/src/rules/rules-load.js +56 -0
  368. package/src/rules/rules-project.js +16 -0
  369. package/src/rules/rules-registry.js +32 -0
  370. package/src/rules/rules-validate-pricing.js +22 -0
  371. package/src/rules/rules-validate-scan.js +45 -0
  372. package/src/rules/rules-validate.js +53 -0
  373. package/src/teardown/consent.js +31 -0
  374. package/src/teardown/flags.js +39 -0
  375. package/src/teardown/main.js +41 -0
  376. package/src/teardown/plan.js +31 -0
  377. package/src/teardown/remove-entry.js +39 -0
  378. package/src/teardown/remove-file.js +37 -0
  379. package/src/teardown/remove-toml.js +33 -0
  380. package/src/teardown/report.js +34 -0
  381. package/src/types/ast.d.ts +40 -0
  382. package/src/types/auto-ask.d.ts +6 -0
  383. package/src/types/auto-orch.d.ts +59 -0
  384. package/src/types/auto.d.ts +45 -0
  385. package/src/types/capability.d.ts +12 -0
  386. package/src/types/cli-auto-gate.d.ts +45 -0
  387. package/src/types/cli-auto.d.ts +42 -0
  388. package/src/types/config.d.ts +53 -0
  389. package/src/types/control.d.ts +56 -0
  390. package/src/types/docs.d.ts +6 -0
  391. package/src/types/doctrine.d.ts +22 -0
  392. package/src/types/llm.d.ts +28 -0
  393. package/src/types/mcp.d.ts +32 -0
  394. package/src/types/memory.d.ts +17 -0
  395. package/src/types/mission.d.ts +46 -0
  396. package/src/types/orchestrator.d.ts +33 -0
  397. package/src/types/routing.d.ts +25 -0
  398. package/src/types/rules.d.ts +30 -0
  399. package/src/types/snapshots.d.ts +25 -0
  400. package/src/types/teardown.d.ts +13 -0
  401. package/src/types/usage.d.ts +30 -0
@@ -0,0 +1,37 @@
1
+ // src/engine/vuln-facts.js — Bloc « Vulnérabilités connues » de l'audit : dépendances lues dans les fichiers du projet, interrogées sur OSV.dev. Honnête : version non épinglée = non vérifiable (dit), panne ou réglage = « non vérifiées »
2
+ // (jamais « aucune faille »). Désactivable : PLUGCTO_NO_OSV=1. Seuls noms et versions sont envoyés.
3
+ import { readDeps } from "./deps-parse.js";
4
+ import { osvScan, SEVERITY } from "./osv.js";
5
+
6
+ const HEAD = "--- Vulnérabilités connues (base publique OSV.dev) ---";
7
+ const ORDER = ["critique", "élevée", "moyenne", "faible", "inconnue"];
8
+ const SHOWN = 15;
9
+
10
+ /** @param {string} root @param {string[]} files @param {typeof fetch} [f] @param {Record<string, string | undefined>} [env] @returns {Promise<string>} */
11
+ export async function vulnFacts(root, files, f = fetch, env = process.env) {
12
+ if (env.PLUGCTO_NO_OSV === "1") return `${HEAD}\nNON VÉRIFIÉES : interrogation désactivée par réglage (PLUGCTO_NO_OSV=1).`;
13
+ const { deps, unsupported } = await readDeps(root, files);
14
+ const skipped = unsupported.length ? ` Gestionnaires non analysés : ${unsupported.join(", ")}.` : "";
15
+ if (deps.length === 0) return `${HEAD}\nAucune dépendance analysable trouvée.${skipped}`;
16
+ const priv = deps.filter((d) => d.priv).length;
17
+ const loose = deps.filter((d) => !d.priv && (!d.pinned || !d.version));
18
+ const looseTxt = loose.length ? ` ; ${loose.length} sans version épinglée, donc NON vérifiables (${loose.slice(0, 8).map((d) => d.name).join(", ")}${loose.length > 8 ? ", …" : ""})` : "";
19
+ try {
20
+ const r = await osvScan(deps, f);
21
+ const rows = r.hits.map((h) => {
22
+ const infos = h.ids.map((id) => r.info.get(id) ?? { id, cve: "", severity: "inconnue", fixed: "" });
23
+ const worst = ORDER.find((s) => infos.some((x) => x.severity === s)) ?? "inconnue";
24
+ const fixed = infos.map((x) => x.fixed).filter(Boolean).sort((a, b) => a.localeCompare(b, undefined, { numeric: true })).pop();
25
+ return { worst, text: `${h.dep.name}@${h.dep.version} (${h.dep.ecosystem}) : ${h.ids.length} faille(s), la pire ${worst} — ${infos.slice(0, 3).map((x) => x.cve || x.id).join(", ")}${fixed ? ` ; corrigée à partir de ${fixed}` : ""}` };
26
+ }).sort((a, b) => ORDER.indexOf(a.worst) - ORDER.indexOf(b.worst));
27
+ const counts = ORDER.map((s) => `${s} ${rows.filter((x) => x.worst === s).length}`).join(", ");
28
+ const day = new Date().toISOString().slice(0, 10);
29
+ return [HEAD, `Interrogée le ${day} ; seuls les noms et versions des dépendances ont été envoyés.`,
30
+ `Dépendances analysées : ${r.scanned} (versions exactes)${looseTxt}${priv ? ` ; ${priv} privée(s) ou locale(s), jamais envoyée(s)` : ""}.${skipped}`,
31
+ rows.length ? `Dépendances vulnérables : ${rows.length} (${counts})` : r.scanned === 0 ? "Dépendances vulnérables : NON VÉRIFIÉES (aucune version exacte à interroger) ; n'écris jamais « aucune faille connue »." : "Dépendances vulnérables : aucune dans la base à cette date pour les versions analysées (cela ne prouve pas l'absence de faille inconnue).",
32
+ ...rows.slice(0, SHOWN).map((x) => `- ${x.text}`), ...(rows.length > SHOWN ? [`- … ${rows.length - SHOWN} autres`] : [])].join("\n");
33
+ } catch (e) {
34
+ return `${HEAD}\nNON VÉRIFIÉES : la base OSV.dev n'a pas pu être interrogée (${/** @type {Error} */ (e).message.slice(0, 80)}). Dépendances lues : ${deps.length}${looseTxt}. À contrôler avec l'outil du gestionnaire (npm audit, pip-audit…).${skipped}`;
35
+ }
36
+ }
37
+ // MODULE_COMPLETE
@@ -0,0 +1,45 @@
1
+ // src/fs/atomic-text.js — Écriture atomique d'un fichier TEXTE de config (tmp +
2
+ // rename). Anti TOCTOU/symlink (tmp à entropie), rename atomique, durcissement
3
+ // 0600 (H2). Socle commun des écritures JSON (atomic-write.js) et TOML (Codex).
4
+
5
+ import fs from "node:fs/promises";
6
+ import path from "node:path";
7
+ import crypto from "node:crypto";
8
+ import { restrictSecretPerms } from "./permissions.js";
9
+ import { warnIfBlocked } from "./os-errors.js";
10
+
11
+ /** @typedef {Error & { code?: string, status?: number }} NodeErr */
12
+ /** @typedef {{ restrict?: typeof restrictSecretPerms }} WriteTextOpts */
13
+
14
+ // Nom de fichier temporaire à entropie (anti TOCTOU/symlink attack).
15
+ /** @param {string} filePath @returns {string} */
16
+ export function tmpNameFor(filePath) {
17
+ return `${filePath}.${crypto.randomBytes(6).toString("hex")}.tmp`;
18
+ }
19
+
20
+ /** @param {string} filePath @param {string} data @param {WriteTextOpts} [opts] @returns {Promise<void>} */
21
+ export async function writeTextAtomic(filePath, data, { restrict = restrictSecretPerms } = {}) {
22
+ const dir = path.dirname(filePath);
23
+ try { await fs.mkdir(dir, { recursive: true }); }
24
+ catch (err) { warnIfBlocked(`création du dossier ${dir}`, err); throw err; }
25
+ const tmp = tmpNameFor(filePath);
26
+ try {
27
+ await fs.writeFile(tmp, data, { encoding: "utf8", mode: 0o600 });
28
+ await fs.rename(tmp, filePath); // atomique (POSIX) ; Windows best-effort.
29
+ } catch {
30
+ // tmp/rename en échec (handle IDE ouvert, blocage antivirus) → fallback
31
+ // write-then-unlink (non atomique mais fonctionnel).
32
+ try {
33
+ await fs.writeFile(filePath, data, { encoding: "utf8", mode: 0o600 });
34
+ await fs.rm(tmp, { force: true });
35
+ } catch (err2) {
36
+ const e2 = /** @type {NodeErr} */ (err2);
37
+ warnIfBlocked(`écriture de ${filePath}`, err2);
38
+ try { await fs.rm(tmp, { force: true }); } catch { /* best-effort : nettoyage */ }
39
+ throw new Error(`Écriture impossible (${filePath}). Fermez l'IDE cible et relancez setup. Détail : ${e2.message}`);
40
+ }
41
+ }
42
+ // H2 : durcissement ACL best-effort (Windows icacls / POSIX chmod 0o600).
43
+ await restrict(filePath);
44
+ }
45
+ // MODULE_COMPLETE
@@ -0,0 +1,21 @@
1
+ // src/fs/atomic-write.js — Écriture atomique d'un fichier de config JSON : sérialise
2
+ // puis délègue à writeTextAtomic (atomic-text.js). tmpNameFor est ré-exporté
3
+ // (invariant de tests : signature publique inchangée).
4
+
5
+ import { writeTextAtomic, tmpNameFor } from "./atomic-text.js";
6
+ import { restrictSecretPerms } from "./permissions.js";
7
+
8
+ export { tmpNameFor };
9
+
10
+ /** @typedef {{ restrict?: typeof restrictSecretPerms }} WriteConfigOpts */
11
+
12
+ /**
13
+ * @param {string} filePath
14
+ * @param {Record<string, unknown>} config
15
+ * @param {WriteConfigOpts} [opts]
16
+ * @returns {Promise<void>}
17
+ */
18
+ export async function writeConfigAtomic(filePath, config, opts = {}) {
19
+ await writeTextAtomic(filePath, JSON.stringify(config, null, 2) + "\n", opts);
20
+ }
21
+ // MODULE_COMPLETE
@@ -0,0 +1,27 @@
1
+ // src/fs/backup-once.js — Sauvegarde UNIQUE du fichier de config d'origine d'un
2
+ // utilisateur avant la première modification par PipCode (`<fichier>.pipcode.bak`,
3
+ // permissions restreintes). N'écrase jamais une sauvegarde existante (la toute
4
+ // première reste l'état d'origine). Fichier absent → rien à sauvegarder. Si la
5
+ // sauvegarde est impossible, l'erreur remonte : on ne modifie PAS sans filet.
6
+
7
+ import fs from "node:fs/promises";
8
+ import { constants } from "node:fs";
9
+ import { restrictSecretPerms } from "./permissions.js";
10
+
11
+ /** @typedef {Error & { code?: string }} NodeErr */
12
+
13
+ /** @param {string} filePath @returns {Promise<"created" | "kept" | "none">} */
14
+ export async function backupOnce(filePath) {
15
+ const bak = `${filePath}.pipcode.bak`;
16
+ try {
17
+ await fs.copyFile(filePath, bak, constants.COPYFILE_EXCL);
18
+ } catch (err) {
19
+ const code = /** @type {NodeErr} */ (err).code;
20
+ if (code === "ENOENT") return "none"; // première installation : rien à sauvegarder.
21
+ if (code === "EEXIST") return "kept"; // l'état d'origine est déjà sauvegardé.
22
+ throw new Error(`Sauvegarde de ${filePath} impossible (${/** @type {NodeErr} */ (err).message}) : fichier non modifié.`);
23
+ }
24
+ await restrictSecretPerms(bak);
25
+ return "created";
26
+ }
27
+ // MODULE_COMPLETE
@@ -0,0 +1,39 @@
1
+ // src/fs/config-path.js — Bornage des racines config sous le home utilisateur.
2
+ // Les fichiers de config IDE contiennent les clés API en clair (0600). Un env
3
+ // contrôlé (APPDATA / XDG_CONFIG_HOME) ne doit pas rediriger l'écriture hors du
4
+ // profil. `underHome` vérifie qu'un chemin résolu reste sous `home` ; `boundRoot`
5
+ // honore l'override env uniquement s'il est borné, sinon retombe sur le défaut.
6
+
7
+ import path from "node:path";
8
+ import { resolvePlugctoRoot } from "./home-root.js";
9
+
10
+ // Racine de la config partagée (~/.pipcode/config.json) — résiliente : la racine
11
+ // honore PLUGCTO_CONFIG_DIR, sinon le home sondé, sinon le repli tmpdir (Point #3).
12
+ /** @param {string} [configDir] @returns {string} */
13
+ export function sharedConfigPath(configDir = resolvePlugctoRoot()) {
14
+ return path.join(configDir, "config.json");
15
+ }
16
+
17
+ /** @param {string} p @param {string} home @returns {boolean} */
18
+ export function underHome(p, home) {
19
+ if (!p || typeof p !== "string") return false;
20
+ try {
21
+ const rp = path.resolve(p);
22
+ const rh = path.resolve(home);
23
+ return rp === rh || rp.startsWith(rh + path.sep);
24
+ } catch {
25
+ return false;
26
+ }
27
+ }
28
+
29
+ /** @param {string | undefined} envVal @param {string} home @param {string} fallback @returns {string} */
30
+ export function boundRoot(envVal, home, fallback) {
31
+ return (envVal && underHome(envVal, home)) ? envVal : fallback;
32
+ }
33
+
34
+ // Racines config par défaut (sous home). Partagées par la localisation IDE.
35
+ /** @param {string} home @returns {string} */
36
+ export const DEFAULT_XDG = (home) => path.join(home, ".config");
37
+ /** @param {string} home @returns {string} */
38
+ export const DEFAULT_APPDATA = (home) => path.join(home, "AppData", "Roaming");
39
+ // MODULE_COMPLETE
@@ -0,0 +1,23 @@
1
+ // src/fs/config-read.js — Lecture best-effort de ~/.pipcode/config.json.
2
+ // Retourne {} si absent/corrompu. Sert à PRÉVENIR l'écrasement d'une licence Pro
3
+ // existante (U1) lors d'un relance de setup.
4
+
5
+ import path from "node:path";
6
+ import fs from "node:fs/promises";
7
+ import { sharedConfigPath } from "./config-path.js";
8
+
9
+ /** @typedef {{ installedAt?: string, clientId?: string, licenseKey?: string, email?: string, trialExpiredNotified?: boolean }} SharedConfig */
10
+
11
+ // Lit la config partagée ; {} si absent, non-objet ou corrompu (parse échoue).
12
+ /** @param {{ configDir?: string, readFile?: typeof fs.readFile }} [opts] @returns {Promise<SharedConfig>} */
13
+ export async function readSharedConfig({ configDir, readFile = fs.readFile } = {}) {
14
+ const file = configDir ? path.join(configDir, "config.json") : sharedConfigPath();
15
+ try {
16
+ const cfg = /** @type {SharedConfig} */ (JSON.parse(await readFile(file, "utf8")));
17
+ if (typeof cfg !== "object" || Array.isArray(cfg) || cfg === null) return {};
18
+ return cfg;
19
+ } catch {
20
+ return {};
21
+ }
22
+ }
23
+ // MODULE_COMPLETE
@@ -0,0 +1,60 @@
1
+ // src/fs/config-write.js — Écriture atomique de ~/.pipcode/config.json : merge
2
+ // idempotent, tmp + rename + ACL 0600, repli direct. Blocages OS → warning propre.
3
+
4
+ import path from "node:path";
5
+ import fs from "node:fs/promises";
6
+ import crypto from "node:crypto";
7
+ import { sharedConfigPath } from "./config-path.js";
8
+ import { tmpNameFor } from "./atomic-write.js";
9
+ import { restrictSecretPerms } from "./permissions.js";
10
+ import { warnIfBlocked } from "./os-errors.js";
11
+
12
+ /** @typedef {Error & { code?: string, status?: number }} NodeErr */
13
+ /** @typedef {{ installedAt?: string, clientId?: string, licenseKey?: string, email?: string, trialExpiredNotified?: boolean }} SharedConfig */
14
+ /** @typedef {{ licenseKey?: string, email?: string }} WriteSharedConfigInput */
15
+
16
+ // Fusionne la cfg existante avec licenseKey/email (préserve installedAt/clientId).
17
+ /** @param {SharedConfig} cfg @param {WriteSharedConfigInput} input @returns {SharedConfig} */
18
+ function mergeSharedCfg(cfg, { licenseKey, email }) {
19
+ if (!cfg.installedAt) cfg.installedAt = new Date().toISOString();
20
+ if (!cfg.clientId) cfg.clientId = crypto.randomUUID();
21
+ if (licenseKey !== undefined) cfg.licenseKey = String(licenseKey || "");
22
+ if (email !== undefined) cfg.email = String(email || "");
23
+ return cfg;
24
+ }
25
+
26
+ /**
27
+ * @param {WriteSharedConfigInput} input
28
+ * @param {{ configDir?: string, readFile?: typeof fs.readFile, writeFile?: typeof fs.writeFile, mkdir?: typeof fs.mkdir, rename?: typeof fs.rename, rm?: typeof fs.rm, restrict?: typeof restrictSecretPerms }} [opts]
29
+ * @returns {Promise<{ path: string, email: string, licenseKey: string }>}
30
+ */
31
+ export async function writeSharedConfig(
32
+ { licenseKey, email },
33
+ { configDir, readFile = fs.readFile, writeFile = fs.writeFile, mkdir = fs.mkdir, rename = fs.rename, rm = fs.rm, restrict = restrictSecretPerms } = {},
34
+ ) {
35
+ const file = configDir ? path.join(configDir, "config.json") : sharedConfigPath();
36
+ const dir = path.dirname(file);
37
+ /** @type {SharedConfig} */ let cfg = {};
38
+ try { cfg = /** @type {SharedConfig} */ (JSON.parse(await readFile(file, "utf8"))); } catch { cfg = {}; }
39
+ if (typeof cfg !== "object" || Array.isArray(cfg) || cfg === null) cfg = {};
40
+ cfg = mergeSharedCfg(cfg, { licenseKey, email });
41
+ await mkdir(dir, { recursive: true });
42
+ const data = JSON.stringify(cfg, null, 2) + "\n";
43
+ const tmp = tmpNameFor(file);
44
+ try {
45
+ await writeFile(tmp, data, { encoding: "utf8", mode: 0o600 });
46
+ await rename(tmp, file);
47
+ } catch {
48
+ // tmp/rename en échec (handle IDE, blocage antivirus) → écriture directe en repli.
49
+ try { await writeFile(file, data, { encoding: "utf8", mode: 0o600 }); } catch (err2) {
50
+ const e2 = /** @type {NodeErr} */ (err2);
51
+ warnIfBlocked(`écriture de ${file}`, err2);
52
+ try { await rm(tmp, { force: true }); } catch { /* best-effort : nettoyage du tmp */ }
53
+ throw new Error(`Écriture config impossible (${file}). Détail : ${e2.message}`);
54
+ }
55
+ try { await rm(tmp, { force: true }); } catch { /* best-effort : nettoyage du tmp */ }
56
+ }
57
+ await restrict(file);
58
+ return { path: file, email: /** @type {string} */ (cfg.email), licenseKey: /** @type {string} */ (cfg.licenseKey) };
59
+ }
60
+ // MODULE_COMPLETE
@@ -0,0 +1,55 @@
1
+ // src/fs/home-root.js — Résilience de la racine ~/.pipcode (Point bloquant #3).
2
+ // Ordre : override PLUGCTO_CONFIG_DIR (honoré tel quel) → home utilisateur
3
+ // (USERPROFILE/AppData en repli Windows si homedir() est vide) → os.tmpdir() en
4
+ // dernier recours si le profil est verrouillé en écriture (sonde réelle, pas de
5
+ // croyance ACL) + avertissement propre sur stderr. La sonde ne crée JAMAIS de
6
+ // dossier : un dry-run teardown reste non intrusif.
7
+
8
+ import fsSync from "node:fs";
9
+ import os from "node:os";
10
+ import path from "node:path";
11
+
12
+ /** @typedef {{ probeDir?: (dir: string) => boolean, warn?: (msg: string) => void }} RootOpts */
13
+
14
+ // Cache des sondes par répertoire (une seule sonde par dossier et par process).
15
+ /** @type {Map<string, boolean>} */
16
+ const probeCache = new Map();
17
+
18
+ // Sonde réelle d'inscriptibilité : écrit puis supprime un fichier éphémère.
19
+ /** @param {string} dir @returns {boolean} */
20
+ function probeDir(dir) {
21
+ if (probeCache.has(dir)) return /** @type {boolean} */ (probeCache.get(dir));
22
+ let ok = false;
23
+ try {
24
+ const probe = path.join(dir, `.pc-probe-${process.pid}`);
25
+ fsSync.writeFileSync(probe, "");
26
+ fsSync.rmSync(probe, { force: true });
27
+ ok = true;
28
+ } catch { ok = false; }
29
+ probeCache.set(dir, ok);
30
+ return ok;
31
+ }
32
+
33
+ // Home candidat : homedir() puis USERPROFILE/AppData (Windows) puis tmpdir.
34
+ /** @param {NodeJS.ProcessEnv} env @returns {string} */
35
+ function candidateHome(env) {
36
+ return os.homedir()
37
+ || /** @type {string} */ (env.USERPROFILE)
38
+ || (env.APPDATA ? path.dirname(env.APPDATA) : os.tmpdir());
39
+ }
40
+
41
+ // Racine effective du dossier .pipcode (défaut de sharedConfigPath/teardown).
42
+ /** @param {NodeJS.ProcessEnv} [env] @param {RootOpts} [opts] @returns {string} */
43
+ export function resolvePlugctoRoot(env = process.env, opts = {}) {
44
+ if (env.PLUGCTO_CONFIG_DIR) return env.PLUGCTO_CONFIG_DIR;
45
+ const probe = opts.probeDir || probeDir;
46
+ const warn = opts.warn || console.error;
47
+ const home = candidateHome(env);
48
+ const primary = path.join(home, ".pipcode");
49
+ // Sonde : dans ~/.pipcode s'il existe, sinon dans home (aucune création).
50
+ if (probe(fsSync.existsSync(primary) ? primary : home)) return primary;
51
+ const fallback = path.join(os.tmpdir(), "plugcto");
52
+ warn(`[pipcode] Répertoire utilisateur non inscriptible (${primary}) — antivirus ou droits insuffisants. Repli gracieux sur ${fallback}.`);
53
+ return fallback;
54
+ }
55
+ // MODULE_COMPLETE
@@ -0,0 +1,36 @@
1
+ // src/fs/load-config.js — Lecture générique d'un fichier de config JSON.
2
+ // Fichier absent → {} (premier lancement). Fichier vide → {}.
3
+ // Fichier corrompu → jette une Error explicite (on n'écrase jamais silencieusement
4
+ // une config existante qu'on n'arrive pas à lire). Utilisé par applySetup (IDE).
5
+
6
+ import fs from "node:fs/promises";
7
+
8
+ /** @typedef {Error & { code?: string, status?: number }} NodeErr */
9
+
10
+ /**
11
+ * @param {string} filePath
12
+ * @returns {Promise<Record<string, unknown>>}
13
+ */
14
+ export async function loadConfig(filePath) {
15
+ let raw;
16
+ try {
17
+ raw = await fs.readFile(filePath, "utf8");
18
+ } catch (err) {
19
+ const e = /** @type {NodeErr} */ (err);
20
+ if (e && e.code === "ENOENT") return {};
21
+ throw e;
22
+ }
23
+ const trimmed = raw.trim();
24
+ if (!trimmed) return {};
25
+ try {
26
+ const parsed = /** @type {Record<string, unknown>} */ (JSON.parse(trimmed));
27
+ return (parsed && typeof parsed === "object" && !Array.isArray(parsed)) ? parsed : {};
28
+ } catch (err) {
29
+ const e = /** @type {NodeErr} */ (err);
30
+ throw new Error(
31
+ `Configuration existante illisible (${filePath}) : JSON invalide. ` +
32
+ `Corrigez ou supprimez le fichier avant de relancer setup. Détail : ${e.message}`,
33
+ );
34
+ }
35
+ }
36
+ // MODULE_COMPLETE
@@ -0,0 +1,26 @@
1
+ // src/fs/os-errors.js — Interception typée des blocages d'accès OS (Point #3) :
2
+ // antivirus, droits restreints, verrou exclusif, FS en lecture seule. Source
3
+ // unique du test isOsBlock et de l'avertissement stderr propre (sans stack).
4
+
5
+ /** @typedef {Error & { code?: string }} NodeErr */
6
+
7
+ // Codes signalant un blocage d'accès fichier (AV / droits / verrou / FS ro).
8
+ export const OS_BLOCK_CODES = ["EACCES", "EPERM", "EBUSY", "EROFS"];
9
+
10
+ // Vrai si l'erreur est un blocage d'accès OS typé (code explicite).
11
+ /** @param {unknown} err @returns {boolean} */
12
+ export function isOsBlock(err) {
13
+ if (!err || typeof err !== "object") return false;
14
+ const e = /** @type {NodeErr} */ (err);
15
+ return typeof e.code === "string" && OS_BLOCK_CODES.includes(e.code);
16
+ }
17
+
18
+ // Avertissement propre sur stderr (jamais de stack brute) si et seulement si
19
+ // l'erreur est un blocage OS. Les autres erreurs restent silencieuses ici.
20
+ /** @param {string} label @param {unknown} err @param {(msg: string) => void} [warn] @returns {void} */
21
+ export function warnIfBlocked(label, err, warn = console.error) {
22
+ const e = /** @type {NodeErr} */ (err);
23
+ if (!isOsBlock(err)) return;
24
+ warn(`[pipcode] ${label} : accès bloqué par l'OS (${e.code}) — antivirus ou droits insuffisants.`);
25
+ }
26
+ // MODULE_COMPLETE
@@ -0,0 +1,48 @@
1
+ // src/fs/permissions.js — Restriction des permissions d'un fichier secret (H2).
2
+ // POSIX : chmod 0o600. Windows : `mode` de fs.writeFile ignoré → ACL via icacls
3
+ // (héritage coupé, accès réservé à l'utilisateur courant, retrait des groupes).
4
+ // Best-effort : un échec icacls ne fait pas échouer l'écriture.
5
+
6
+ import { execFile } from "node:child_process";
7
+ import fs from "node:fs/promises";
8
+ import { detectOS } from "../cli/detect-os.js";
9
+
10
+ /** @typedef {{ stdout: string, stderr: string }} ExecResult */
11
+ /** @typedef {Error & { code?: string, status?: number }} NodeErr */
12
+ /** @typedef {{ osName?: string, runTool?: (cmd: string, args: string[]) => Promise<ExecResult> }} RestrictOpts */
13
+ /** @typedef {{ ok: boolean, method: string, error?: string }} RestrictResult */
14
+
15
+ // Promisification de execFile (windowsHide pour éviter une console flash).
16
+ /** @param {string} cmd @param {string[]} args @returns {Promise<ExecResult>} */
17
+ export function execFileAsync(cmd, args) {
18
+ return new Promise((resolve, reject) => {
19
+ execFile(cmd, args, { windowsHide: true }, (err, stdout, stderr) => {
20
+ if (err) reject(err);
21
+ else resolve({ stdout: stdout, stderr: stderr });
22
+ });
23
+ });
24
+ }
25
+
26
+ /**
27
+ * @param {string} filePath
28
+ * @param {RestrictOpts} [opts]
29
+ * @returns {Promise<RestrictResult>}
30
+ */
31
+ export async function restrictSecretPerms(filePath, { osName = detectOS(), runTool = execFileAsync } = {}) {
32
+ if (osName !== "windows") {
33
+ try { await fs.chmod(filePath, 0o600); } catch { /* best-effort POSIX */ }
34
+ return { ok: true, method: "chmod" };
35
+ }
36
+ const user = (process.env.USERNAME || process.env.USER || "").trim();
37
+ const args = [filePath, "/inheritance:r"];
38
+ if (user) args.push("/grant:r", `${user}:F`);
39
+ args.push("/remove:g", "Everyone", "Users", "Authenticated Users");
40
+ try {
41
+ await runTool("icacls", args);
42
+ return { ok: true, method: "icacls" };
43
+ } catch (err) {
44
+ const e = /** @type {NodeErr} */ (err);
45
+ return { ok: false, method: "icacls", error: String(e && (e.message || e)) };
46
+ }
47
+ }
48
+ // MODULE_COMPLETE
@@ -0,0 +1,9 @@
1
+ // src/mcp/boot-warmup.js — Tâche de boot en arrière-plan du serveur MCP : init AST (lazy), fire-and-forget. L'init Tree-sitter WASM est async (~50-100ms) : lancée en arrière-plan
2
+ // pour ne pas bloquer le handshake stdio ; tant qu'elle n'est pas prête, l'analyse retombe sur le repli regex (jamais de crash). Aucun appel à un modèle : le serveur n'a aucune clé d'IA.
3
+ import { loadAstRuntime } from "../engine/ast-runtime.js";
4
+
5
+ /** @returns {void} */
6
+ export function startBootTasks() {
7
+ loadAstRuntime().catch((e) => console.error("[virtual-cto] init AST:", (e && e.message) || e));
8
+ }
9
+ // MODULE_COMPLETE
@@ -0,0 +1,20 @@
1
+ // src/mcp/call-tool.js — Handler tools/call : dispatch par nom d'outil (extrait
2
+ // dans tool-dispatch.js, plafond de lignes) + notice de transition Trial → Lite
3
+ // (une fois, flag persisté par maybeTrialExpiredNotice ; la notice va dans le
4
+ // content JSON-RPC, jamais sur stdout — stdout guard).
5
+ import { loadConfigCached } from "../core/config-cache.js";
6
+ import { maybeTrialExpiredNotice } from "../core/tier.js";
7
+ import { dispatchTool } from "./tool-dispatch.js";
8
+
9
+ /** @param {McpToolRequest} request @returns {Promise<McpToolResponse>} */
10
+ export async function handleCallTool(request) {
11
+ let _trialNotice = "";
12
+ try { _trialNotice = await maybeTrialExpiredNotice(await loadConfigCached()); }
13
+ catch { _trialNotice = ""; }
14
+ const _res = await dispatchTool(request);
15
+ if (_trialNotice && _res && Array.isArray(_res.content)) {
16
+ _res.content = [..._res.content, { type: "text", text: `\n\nℹ️ ${_trialNotice}` }];
17
+ }
18
+ return _res;
19
+ }
20
+ // MODULE_COMPLETE
@@ -0,0 +1,35 @@
1
+ // src/mcp/client-info.js — Capture du clientInfo MCP (nom/version de l'agent
2
+ // hôte : Claude Code, Cursor, Copilot…) + table de capacités multi-agent. État
3
+ // module pur (aucune I/O, aucune dépendance SDK). Gap d'audit #1 : PipCode sait
4
+ // désormais si l'agent de l'environnement peut déployer lui-même le multi-agent.
5
+
6
+ /** @type {ClientInfo | null} */ let current = null;
7
+
8
+ // Mémorise le client MCP (validation Zero Trust : objet + name non vide requis).
9
+ /** @param {unknown} info @returns {void} */
10
+ export function rememberClient(info) {
11
+ if (!info || typeof info !== "object") return;
12
+ const name = /** @type {{ name?: unknown }} */ (info).name;
13
+ if (typeof name !== "string" || !name.trim()) return;
14
+ const version = /** @type {{ version?: unknown }} */ (info).version;
15
+ current = { name: name.trim(), version: typeof version === "string" ? version : "" };
16
+ }
17
+
18
+ // Client courant (ou null si l'initialize n'a pas encore eu lieu).
19
+ /** @returns {ClientInfo | null} */
20
+ export function getClientInfo() { return current; }
21
+
22
+ // L'agent hôte sait-il déployer nativement plusieurs sous-agents ?
23
+ /** @param {string} name @returns {boolean} */
24
+ export function hostCanMultiAgent(name) {
25
+ return /claude|cursor|cline|windsurf|copilot|trae|kilo|codex/i.test(String(name || "").toLowerCase());
26
+ }
27
+
28
+ // Indice de délégation pour les rapports (vide si hôte inconnu ou non capable).
29
+ /** @returns {string} */
30
+ export function multiAgentHint() {
31
+ const c = current;
32
+ if (!c || !hostCanMultiAgent(c.name)) return "";
33
+ return `Délégation : votre agent hôte (${c.name}) sait déployer plusieurs sous-agents — déléguez-lui la correction multi-agents plutôt que de relancer PipCode en boucle.`;
34
+ }
35
+ // MODULE_COMPLETE
@@ -0,0 +1,16 @@
1
+ // src/mcp/engine-rules.js — Gate « règles métier » des outils MCP : charge (ou rafraîchit) les règles
2
+ // signées du serveur PipCode avant tout traitement. Échec = réponse d'erreur honnête, jamais de repli sur
3
+ // des règles embarquées (il n'y en a plus). Même forme que les autres gates (ok / content).
4
+
5
+ import { ensureRules } from "../rules/rules-load.js";
6
+
7
+ /** @param {PlugConfig} cfg @returns {Promise<{ ok: true } | { ok: false, content: { type: "text", text: string }[] }>} */
8
+ export async function gateEngineRules(cfg) {
9
+ try {
10
+ await ensureRules(cfg);
11
+ return { ok: true };
12
+ } catch (e) {
13
+ return { ok: false, content: [{ type: "text", text: `🛑 ${/** @type {Error} */ (e).message}` }] };
14
+ }
15
+ }
16
+ // MODULE_COMPLETE
@@ -0,0 +1,37 @@
1
+ // src/mcp/handle-dlp.js — Handler de l'outil dlp_scan : scan anti-fuite de
2
+ // doctrine (sortie IA / contenu fichier) contre les empreintes du <CTO_VAULT>.
3
+ import { DLP_BLOCK_TEXT, scanForLeak, hasVaultFingerprints } from "../core/dlp.js";
4
+
5
+ /** @param {Record<string, unknown> | undefined} args @returns {McpToolResponse} */
6
+ export function handleDlpScan(args) {
7
+ const candidate = args && typeof args.text === "string" ? args.text : "";
8
+ if (!candidate.trim()) {
9
+ return {
10
+ isError: true,
11
+ content: [{ type: "text", text: "Paramètre `text` manquant ou vide pour dlp_scan." }],
12
+ };
13
+ }
14
+ if (!hasVaultFingerprints()) {
15
+ return {
16
+ content: [
17
+ {
18
+ type: "text",
19
+ text: "ℹ️ [DLP] Aucun <CTO_VAULT> chargé en session — rien à protéger. Appelez d'abord un outil enforce_* pour charger la doctrine.",
20
+ },
21
+ ],
22
+ };
23
+ }
24
+ if (scanForLeak(candidate)) {
25
+ // Blocage DLP : extraction de doctrine détectée.
26
+ return {
27
+ isError: true,
28
+ content: [{ type: "text", text: DLP_BLOCK_TEXT }],
29
+ };
30
+ }
31
+ return {
32
+ content: [
33
+ { type: "text", text: "✅ [DLP] Aucune fuite de doctrine détectée. Sortie autorisée." },
34
+ ],
35
+ };
36
+ }
37
+ // MODULE_COMPLETE
@@ -0,0 +1,46 @@
1
+ // src/mcp/handle-licence.js — Outil plugcto_licence (commande /licence) : voir l'état de la licence, ouvrir la page d'achat, enregistrer une clé. Disponible en Lite (aucun gate).
2
+ // La clé est contrôlée par le serveur de licence AVANT d'être enregistrée ; une clé refusée n'efface jamais une licence existante ; la clé n'apparaît jamais en clair dans une réponse.
3
+ import { loadConfig } from "../core/config.js";
4
+ import { saveConfig } from "../core/config-base.js";
5
+ import { invalidateConfigCache } from "../core/config-cache.js";
6
+ import { resolveSessionResilient } from "../core/session-resilient.js";
7
+ import { requestSession, purgeSessionCache } from "../core/session-net.js";
8
+ import { PRO_UPGRADE_URL } from "../core/constants.js";
9
+ import { openBrowser } from "../net/auth-endpoints.js";
10
+ import { KEY_RE } from "../cli/setup-args.js";
11
+ import { maskKey, statusText, refusedText } from "./licence-text.js";
12
+
13
+ /** @param {string} text @param {boolean} [isError] @returns {McpToolResponse} */
14
+ const reply = (text, isError = false) => ({ isError, content: [{ type: "text", text }] });
15
+ /** @typedef {{ open?: (url: string) => void, request?: typeof requestSession, resolve?: typeof resolveSessionResilient }} LicenceDeps */
16
+
17
+ /** @param {string} key @param {LicenceDeps} deps @returns {Promise<McpToolResponse>} */
18
+ async function register(key, deps) {
19
+ if (!KEY_RE.test(key)) return reply(refusedText("Clé mal formée (8 à 128 caractères : lettres, chiffres, tirets)."), true);
20
+ const previous = process.env.PLUGCTO_LICENSE_KEY;
21
+ process.env.PLUGCTO_LICENSE_KEY = key; // la vérification utilise cette clé (l'environnement prime sur la config)
22
+ const restore = () => { if (previous === undefined) delete process.env.PLUGCTO_LICENSE_KEY; else process.env.PLUGCTO_LICENSE_KEY = previous; };
23
+ const cfg = await loadConfig();
24
+ try { await (deps.request ?? requestSession)({ ...cfg, licenseKey: key }); }
25
+ catch (err) {
26
+ restore();
27
+ const e = /** @type {EnrichedError} */ (err);
28
+ return reply(refusedText(e.status === 402 || e.status === 403 || e.status === 404 ? "Clé refusée par le serveur de licence." : "Serveur de licence injoignable."), true);
29
+ }
30
+ cfg.licenseKey = key; await saveConfig(cfg); invalidateConfigCache(); purgeSessionCache();
31
+ const kept = (await loadConfig()).licenseKey === key;
32
+ const stale = previous && previous !== key ? "\nVotre outil garde l'ancienne clé dans sa configuration : relancez « npx pipcode setup --key <votre clé> » pour la mettre à jour." : "";
33
+ return reply(`✅ Licence enregistrée (clé ${maskKey(key)}).${kept ? "" : " ⚠️ Enregistrement sur le disque impossible : valable pour cette session seulement."}${stale}`);
34
+ }
35
+
36
+ /** @param {Record<string, unknown> | undefined} args @param {LicenceDeps} [deps] @returns {Promise<McpToolResponse>} */
37
+ export async function handleLicence(args, deps = {}) {
38
+ const action = args && typeof args.action === "string" ? args.action : "status";
39
+ if (action === "buy") { (deps.open ?? openBrowser)(PRO_UPGRADE_URL); return reply(`🌐 Page d'achat ouverte dans votre navigateur : ${PRO_UPGRADE_URL}\nUne fois la clé reçue par e-mail, collez-la ici : elle sera enregistrée.`); }
40
+ if (action === "register") return register(args && typeof args.key === "string" ? args.key.trim() : "", deps);
41
+ if (action !== "status") return reply("Action inconnue : utilisez « status », « buy » ou « register ».", true);
42
+ const cfg = await loadConfig();
43
+ const r = await (deps.resolve ?? resolveSessionResilient)(cfg);
44
+ return reply(statusText(r.tier, process.env.PLUGCTO_LICENSE_KEY || cfg.licenseKey || "", cfg.installedAt));
45
+ }
46
+ // MODULE_COMPLETE
@@ -0,0 +1,15 @@
1
+ // src/mcp/handle-list.js — Handler tools/list : catalogue statique (hors-ligne,
2
+ // instantané) + DLP. Délégation à buildToolsList ; erreur tracée stderr puis
3
+ // relancée (le SDK transforme l'exception en erreur JSON-RPC propre).
4
+ import { buildToolsList } from "./tools-list.js";
5
+
6
+ /** @returns {Promise<{ tools: McpToolDef[] }>} */
7
+ export async function handleListTools() {
8
+ try {
9
+ return buildToolsList();
10
+ } catch (err) {
11
+ console.error("[virtual-cto] Erreur ListTools :", err);
12
+ throw err;
13
+ }
14
+ }
15
+ // MODULE_COMPLETE