pipcode 0.0.0-stage → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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/mcp/handle-memory.js — Handlers MCP de la mémoire persistante :
2
+ // plugcto_memory_save (gate + filtrage strict + append) et
3
+ // plugcto_memory_recall (gate + lecture re-filtrée). Pro/essai uniquement
4
+ // (memory-gate.js) — le plan gratuit n'en dispose pas.
5
+ import { gateMemory } from "../memory/memory-gate.js";
6
+ import { appendMemory, readMemory } from "../memory/memory-store.js";
7
+
8
+ /** @param {Record<string, unknown> | undefined} args @returns {Promise<McpToolResponse>} */
9
+ export async function handleMemorySave(args) {
10
+ const gate = await gateMemory();
11
+ if (!gate.ok) return { isError: true, content: gate.content };
12
+ const a = args || {};
13
+ const text = typeof a.text === "string" ? a.text.trim() : "";
14
+ if (!text) {
15
+ return { isError: true, content: [{ type: "text", text: 'Aucun texte à mémoriser : passez { "text": "..." } (optionnel "kind" : note | preference | pattern | project).' }] };
16
+ }
17
+ const kind = typeof a.kind === "string" && a.kind.trim() ? a.kind.trim().slice(0, 40) : "note";
18
+ const entry = { kind, text: text.slice(0, 10_000), source: "user", createdAt: new Date().toISOString() };
19
+ const r = await appendMemory(entry);
20
+ if (!r.ok) {
21
+ return { isError: true, content: [{ type: "text", text: "Entrée rejetée : les données produit (doctrine/fiches) ne sont jamais persistées — RAM uniquement." }] };
22
+ }
23
+ return { content: [{ type: "text", text: `💾 Mémorisé (${entry.kind}) : la mémoire persistera entre vos sessions.` }] };
24
+ }
25
+
26
+ /** @param {Record<string, unknown> | undefined} args @returns {Promise<McpToolResponse>} */
27
+ export async function handleMemoryRecall(args) {
28
+ const gate = await gateMemory();
29
+ if (!gate.ok) return { isError: true, content: gate.content };
30
+ const q = ((args && typeof args.query === "string" && args.query.trim().toLowerCase()) || "");
31
+ const entries = (await readMemory()).filter((e) => !q || e.text.toLowerCase().includes(q));
32
+ if (!entries.length) {
33
+ return { content: [{ type: "text", text: "Aucun souvenir persisté ne correspond." }] };
34
+ }
35
+ return { content: [{ type: "text", text: entries.map((e) => `- [${e.kind}] ${e.text}`).join("\n") }] };
36
+ }
37
+ // MODULE_COMPLETE
@@ -0,0 +1,50 @@
1
+ // src/mcp/handle-skill-fetch.js — Fetch cloud d'une fiche de compétence :
2
+ // payload signé { data, sig }, vérification Ed25519 FAIL-SAFE, déballage
3
+ // JSON, enveloppement dans le Master Prompt + cache DLP.
4
+ import { fetchSignedSkill } from "../core/doctrine-fetch.js";
5
+ import { verifySignature } from "../core/crypto.js";
6
+ import { buildMasterPrompt } from "../core/master-prompt.js";
7
+ import { extractFingerprints, setVaultFingerprints } from "../core/dlp.js";
8
+ import { LITE_BLOCK_TEXT } from "../core/tier.js";
9
+
10
+ /** @typedef {{ slug: string, tier: string }} SkillRef */
11
+
12
+ /** @typedef {{ ok: true, text: string } | { ok: false, content: McpTextContent[] }} SkillFetchResult */
13
+
14
+ /** @param {SkillRef} skill @param {PlugConfig} cfg @returns {Promise<SkillFetchResult>} */
15
+ export async function fetchSkillContent(skill, cfg) {
16
+ // --- Fetch cloud (payload signé { data, sig }) ---
17
+ let payload;
18
+ try {
19
+ payload = await fetchSignedSkill(skill.slug, skill.tier, cfg);
20
+ } catch (err) {
21
+ const e = /** @type {EnrichedError} */ (err);
22
+ if (/HTTP 402/.test(e.message)) return { ok: false, content: [{ type: "text", text: LITE_BLOCK_TEXT }] }; // essai terminé côté serveur : même message que le refus Lite, avec le lien d'achat
23
+ return { ok: false, content: [{ type: "text", text: `Fiche de compétence indisponible (« ${skill.slug} », tier=${skill.tier}). ${e.message.replace(/https?:\/\/\S+/g, "le serveur PipCode")}` }] }; // jamais l'adresse du serveur dans un message utilisateur
24
+ }
25
+
26
+ // --- Vérification Ed25519 (FAIL-SAFE) ---
27
+ // Aucun déballage du contenu dans le <CTO_VAULT> tant que la signature
28
+ // n'est pas cryptographiquement valide. Rejet → erreur MCP explicite.
29
+ if (!verifySignature(payload.data, payload.sig)) {
30
+ return { ok: false, content: [{ type: "text", text: `🛑 [INTÉGRITÉ COMPROMISE] La signature Ed25519 de la fiche « ${skill.slug} » est INVALIDE. Payload rejeté — possible attaque MITM ou altération de la doctrine. Aucun contenu n'a été chargé dans le <CTO_VAULT>.` }] };
31
+ }
32
+
33
+ // --- Déballage (uniquement après vérification) ---
34
+ let parsed;
35
+ try {
36
+ parsed = JSON.parse(payload.data);
37
+ } catch {
38
+ return { ok: false, content: [{ type: "text", text: `Payload signé valide mais données illisibles pour « ${skill.slug} ».` }] };
39
+ }
40
+ const content = typeof parsed.content === "string" ? parsed.content : "";
41
+ if (!content.trim()) {
42
+ return { ok: false, content: [{ type: "text", text: `Fiche vide (content absent) pour « ${skill.slug} ».` }] };
43
+ }
44
+
45
+ // --- Enveloppement EXACT dans le Master Prompt blindé + cache DLP ---
46
+ const text = buildMasterPrompt(content);
47
+ setVaultFingerprints(extractFingerprints(text));
48
+ return { ok: true, text };
49
+ }
50
+ // MODULE_COMPLETE
@@ -0,0 +1,38 @@
1
+ // src/mcp/handle-skill.js — Handler générique des fiches de compétence :
2
+ // validation du nom d'outil, gate Freemium (3 états), délégation du fetch
3
+ // signé à handle-skill-fetch.js.
4
+ import { toolsByName, isValidToolName } from "../core/manifest.js";
5
+ import { loadConfigCached } from "../core/config-cache.js";
6
+ import { isWithinTrial, LITE_BLOCK_TEXT } from "../core/tier.js";
7
+ import { fetchSkillContent } from "./handle-skill-fetch.js";
8
+ import { gateEngineRules } from "./engine-rules.js";
9
+
10
+ /** @param {string} name @returns {Promise<McpToolResponse>} */
11
+ export async function handleSkill(name) {
12
+ if (!isValidToolName(name)) {
13
+ return { isError: true, content: [{ type: "text", text: `Nom d'outil invalide ou mal formé : "${name}". Outils disponibles : ${[...toolsByName.keys()].join(", ")}, dlp_scan, plugcto_licence, plugcto_memory_save, plugcto_memory_recall.` }] };
14
+ }
15
+ const skill = toolsByName.get(name);
16
+ if (!skill) {
17
+ return { isError: true, content: [{ type: "text", text: `Outil inconnu : « ${name} ». Outils disponibles : ${[...toolsByName.keys()].join(", ")}, dlp_scan, plugcto_licence, plugcto_memory_save, plugcto_memory_recall.` }] };
18
+ }
19
+
20
+ // --- Gate Freemium (modèle 3 états : Trial / Lite / Pro) ---
21
+ // Lite (aucune licence + hors essai local) : seules les fiches tier "lite"
22
+ // du MANIFEST passent (token_optimization, gratuit à vie) ; les autres →
23
+ // LITE_BLOCK_TEXT. Trial (5 j, sans licence) et Pro (licence Polar.sh) :
24
+ // toutes les fiches. L'autorité monétaire RÉELLE est le serveur (SKILL_TIERS
25
+ // + trialGate worker, licence validée au fetch signé) — le client ne fait que
26
+ // bloquer AVANT fetch, le serveur re-gate.
27
+ const cfg = await loadConfigCached();
28
+ const _hasLic = !!(process.env.PLUGCTO_LICENSE_KEY || cfg.licenseKey);
29
+ if (!_hasLic && !isWithinTrial(cfg) && skill.tier !== "lite") {
30
+ return { isError: true, content: [{ type: "text", text: LITE_BLOCK_TEXT }] };
31
+ }
32
+ const rules = await gateEngineRules(cfg); // gabarit du Master Prompt servi par le serveur.
33
+ if (!rules.ok) return { isError: true, content: rules.content };
34
+ const f = await fetchSkillContent(skill, cfg);
35
+ if (!f.ok) return { isError: true, content: f.content };
36
+ return { content: [{ type: "text", text: f.text }] };
37
+ }
38
+ // MODULE_COMPLETE
@@ -0,0 +1,29 @@
1
+ // src/mcp/licence-text.js — Textes de l'outil de licence (purs, sans réseau ni disque) : état lisible, achat, clé masquée. Jamais la clé en clair dans une réponse.
2
+ import { TRIAL_HOURS, PRO_UPGRADE_URL } from "../core/constants.js";
3
+
4
+ const DAY_MS = 24 * 3600 * 1000;
5
+
6
+ /** @param {string} key @returns {string} 3 premiers caractères puis des étoiles (jamais la clé entière) */
7
+ export const maskKey = (key) => `${String(key).slice(0, 3)}******`;
8
+
9
+ /** @param {string} installedAt @param {number} [now] @returns {number} jours d'essai restants (0 si terminé ou date inconnue) */
10
+ export function trialDaysLeft(installedAt, now = Date.now()) {
11
+ const t = Date.parse(installedAt);
12
+ return Number.isNaN(t) ? 0 : Math.max(0, Math.ceil((TRIAL_HOURS * 3600 * 1000 - (now - t)) / DAY_MS));
13
+ }
14
+
15
+ const BUY = `Acheter une licence Pro (14,99 USD, paiement unique) : ${PRO_UPGRADE_URL}`;
16
+ const HAVE = "Vous avez déjà une clé ? Collez-la ici dans le chat : elle sera enregistrée (action « register »).";
17
+
18
+ /** @param {"pro" | "trial" | "lite"} tier @param {string} licenseKey @param {string} installedAt @param {number} [now] @returns {string} */
19
+ export function statusText(tier, licenseKey, installedAt, now = Date.now()) {
20
+ if (tier === "pro") return `✅ Licence Pro active${licenseKey ? ` (clé ${maskKey(licenseKey)})` : ""}. Tout PipCode est débloqué.`;
21
+ const head = tier === "trial"
22
+ ? `🕒 Essai en cours : il reste environ ${trialDaysLeft(installedAt, now)} jour(s), toutes les fonctions sont débloquées. Ensuite : version Lite, gratuite à vie.`
23
+ : "Version Lite (gratuite à vie). La licence Pro débloque le catalogue complet.";
24
+ return [head, BUY, HAVE].join("\n");
25
+ }
26
+
27
+ /** @param {string} reason @returns {string} message d'échec d'enregistrement (la clé n'est pas enregistrée) */
28
+ export const refusedText = (reason) => `❌ ${reason} La clé n'a pas été enregistrée.\n${BUY}`;
29
+ // MODULE_COMPLETE
@@ -0,0 +1,11 @@
1
+ // src/mcp/prompt-licence.js — Invite MCP « licence » : dans Claude Code elle apparaît comme commande /mcp__pipcode__licence ; ailleurs, l'utilisateur demande « ma licence » et l'agent appelle l'outil.
2
+
3
+ export const LICENCE_PROMPT = { name: "licence", description: "Voir, acheter ou enregistrer votre licence PipCode" };
4
+
5
+ const BODY = "Appelle l'outil plugcto_licence (sans argument) et présente le résultat à l'utilisateur. S'il veut acheter, rappelle l'outil avec l'action « buy ». S'il te donne sa clé de licence, rappelle l'outil avec l'action « register » et la clé. Ne répète jamais la clé en clair.";
6
+
7
+ /** @param {string} name @returns {{ description: string, messages: Array<{ role: "user", content: { type: "text", text: string } }> } | null} */
8
+ export function getPrompt(name) {
9
+ return name === LICENCE_PROMPT.name ? { description: LICENCE_PROMPT.description, messages: [{ role: "user", content: { type: "text", text: BODY } }] } : null;
10
+ }
11
+ // MODULE_COMPLETE
@@ -0,0 +1,33 @@
1
+ // src/mcp/server-create.js — Création du Server MCP + capture de l'agent hôte
2
+ // (clientInfo à l'initialize — gap d'audit #1) + handlers tools/list et tools/call.
3
+ // Extrait de server-main.js (plafond de lignes) : main() reste un réorienteur.
4
+
5
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
6
+ import { ListToolsRequestSchema, CallToolRequestSchema, ListPromptsRequestSchema, GetPromptRequestSchema } from "@modelcontextprotocol/sdk/types.js";
7
+ import { handleListTools } from "./handle-list.js";
8
+ import { handleCallTool } from "./call-tool.js";
9
+ import { rememberClient } from "./client-info.js";
10
+ import { LICENCE_PROMPT, getPrompt } from "./prompt-licence.js";
11
+
12
+ // Crée le Server, y branche les handlers et capture l'agent hôte (multi-agent
13
+ // natif → PipCode lui laisse la délégation au lieu de doubler l'orchestration).
14
+ /** @returns {Server} */
15
+ export function createPlugServer() {
16
+ const server = new Server(
17
+ { name: "virtual-cto-skills", version: "2.0.0" },
18
+ { capabilities: { tools: {}, prompts: {} } }
19
+ );
20
+ server.setRequestHandler(ListToolsRequestSchema, handleListTools);
21
+ server.setRequestHandler(CallToolRequestSchema, handleCallTool);
22
+ server.setRequestHandler(ListPromptsRequestSchema, async () => ({ prompts: [LICENCE_PROMPT] }));
23
+ server.setRequestHandler(GetPromptRequestSchema, async (request) => {
24
+ const prompt = getPrompt(request.params.name);
25
+ if (!prompt) throw new Error(`Invite inconnue : ${request.params.name}`);
26
+ return prompt;
27
+ });
28
+ server.oninitialized = () => {
29
+ try { rememberClient(server.getClientVersion()); } catch { /* best-effort : sans clientInfo, pas de délégation */ }
30
+ };
31
+ return server;
32
+ }
33
+ // MODULE_COMPLETE
@@ -0,0 +1,39 @@
1
+ // src/mcp/server-main.js — Runtime du serveur MCP (main) : guards boot,
2
+ // création Server (server-create.js : handlers + capture de l'agent hôte),
3
+ // transport stdio, bannière stderr.
4
+ // Architecture "Thin Client" 100 % Cloud-Only : aucune fiche stockée
5
+ // localement — fetch dynamique signé Ed25519 (le catalogue MANIFEST reste
6
+ // embarqué pour un tools/list instantané hors-ligne). Modèle 3 états :
7
+ // Trial 5j / Lite / Pro (Polar.sh). Invariant protocole : stdout = canal
8
+ // JSON-RPC MCP (aucun log), logs → stderr.
9
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
10
+ import { API_BASE } from "../core/constants.js";
11
+ import { MANIFEST } from "../core/manifest.js";
12
+ import { installStdoutGuard } from "../core/stdout-guard.js";
13
+ import { startBootTasks } from "./boot-warmup.js";
14
+ import { createPlugServer } from "./server-create.js";
15
+ import { buildToolsList } from "./tools-list.js";
16
+
17
+ /** @returns {Promise<void>} */
18
+ export async function main() {
19
+ installStdoutGuard();
20
+ // Ancre Ed25519 fail-closed (SecOps) : crypto.js lève au chargement si absente
21
+ // ou invalide — plus de fallback DEV, donc plus d'avertissement de runtime.
22
+ if (MANIFEST.length === 0) {
23
+ console.error("[virtual-cto] Catalogue d'outils vide — aucune fiche exposée.");
24
+ process.exit(1);
25
+ }
26
+ // Boot v1.1 : init AST (lazy) + warmup cache prompt, en arrière-plan
27
+ // (boot-warmup.js) — ne bloque pas le handshake stdio.
28
+ startBootTasks();
29
+ const server = createPlugServer();
30
+ const transport = new StdioServerTransport();
31
+ await server.connect(transport);
32
+ // Compte déduit de la vraie source tools/list (fiches + transverses),
33
+ // jamais d'addition codée en dur (audit MCP).
34
+ const toolCount = buildToolsList().tools.length;
35
+ console.error(
36
+ `[virtual-cto] Serveur MCP prêt sur stdio (thin client) — ${MANIFEST.length} fiche(s) catalogue, ${toolCount} outil(s) exposés via tools/list. API: ${API_BASE} | Intégrité: Ed25519 | auto-correction locale (OpenAI/Anthropic/Gemini/Zhipu/OpenRouter/Ollama) + token session /v1/session | Phase 0: moteur métier + Auto Mode | Tiers: Trial 5j / Lite / Pro (Polar.sh)`
37
+ );
38
+ }
39
+ // MODULE_COMPLETE
@@ -0,0 +1,28 @@
1
+ // src/mcp/tool-dispatch.js — Chaîne de dispatch tools/call par nom d'outil.
2
+ // Extraite de call-tool.js (plafond 40 lignes du fichier d'origine). Aucune
3
+ // donnée utilisateur n'y est interprétée autrement que pour le routage.
4
+ import { handleDlpScan } from "./handle-dlp.js";
5
+ import { handleSkill } from "./handle-skill.js";
6
+ import { handleLicence } from "./handle-licence.js";
7
+ import { handleMemorySave, handleMemoryRecall } from "./handle-memory.js";
8
+
9
+ // Route la requête vers le handler dédié. Retourne toujours une réponse MCP.
10
+ /** @param {McpToolRequest} request @returns {Promise<McpToolResponse>} */
11
+ export async function dispatchTool(request) {
12
+ try {
13
+ const { name, arguments: args } = request.params ?? {};
14
+ // --- Outil DLP : scan anti-fuite de doctrine (handle-dlp.js) ---
15
+ if (name === "dlp_scan") return handleDlpScan(args);
16
+ // --- Licence : état, achat, enregistrement de la clé (handle-licence.js, tous niveaux) ---
17
+ if (name === "plugcto_licence") return handleLicence(args);
18
+ // --- Mémoire persistante (handle-memory.js, Pro/Essai) ---
19
+ if (name === "plugcto_memory_save") return handleMemorySave(args);
20
+ if (name === "plugcto_memory_recall") return handleMemoryRecall(args);
21
+ // --- Fiches de compétence (handle-skill.js) ---
22
+ return handleSkill(name);
23
+ } catch (err) {
24
+ console.error("[virtual-cto] Erreur CallTool inattendue :", err);
25
+ return /** @type {McpToolResponse} */ ({ isError: true, content: [{ type: "text", text: "Erreur interne inattendue lors de l'appel de l'outil. Détail tracé côté serveur (stderr)." }] });
26
+ }
27
+ }
28
+ // MODULE_COMPLETE
@@ -0,0 +1,20 @@
1
+ // src/mcp/tool-dlp.js — Définition MCP de l'outil dlp_scan (watchdog DLP).
2
+
3
+ /** @type {McpToolDef} */
4
+ export const dlpTool = {
5
+ name: "dlp_scan",
6
+ description:
7
+ "[Sécurité] Watchdog DLP. Soumet un texte candidat (sortie de l'IA, contenu d'un fichier .md/.txt à créer) au scan anti-extraction de doctrine. Compare aux empreintes du <CTO_VAULT> actuellement chargé. Bloque (isError) si recopie verbatim de la doctrine détectée. À appeler AVANT d'émettre ou d'écrire un contenu généré par l'IA.",
8
+ inputSchema: {
9
+ type: "object",
10
+ properties: {
11
+ text: {
12
+ type: "string",
13
+ description: "Texte candidat à scanner (sortie IA ou contenu de fichier).",
14
+ },
15
+ },
16
+ required: ["text"],
17
+ additionalProperties: false,
18
+ },
19
+ };
20
+ // MODULE_COMPLETE
@@ -0,0 +1,17 @@
1
+ // src/mcp/tool-licence.js — Définition MCP de l'outil plugcto_licence (commande /licence).
2
+
3
+ /** @type {McpToolDef} */
4
+ export const licenceTool = {
5
+ name: "plugcto_licence",
6
+ description:
7
+ "[Licence] Commande /licence. Sans argument : affiche l'état de la licence PipCode (essai, Lite gratuit, Pro) et propose l'achat. action « buy » : ouvre la page d'achat dans le navigateur. action « register » + key : enregistre la clé de licence de l'utilisateur après vérification par le serveur de licence. Disponible dans toutes les versions. Ne répète jamais la clé en clair.",
8
+ inputSchema: {
9
+ type: "object",
10
+ properties: {
11
+ action: { type: "string", enum: ["status", "buy", "register"], description: "status (défaut) : état ; buy : ouvre la page d'achat ; register : enregistre la clé donnée." },
12
+ key: { type: "string", description: "Clé de licence à enregistrer (action register seulement)." },
13
+ },
14
+ additionalProperties: false,
15
+ },
16
+ };
17
+ // MODULE_COMPLETE
@@ -0,0 +1,32 @@
1
+ // src/mcp/tool-memory.js — Définitions MCP des outils de mémoire persistante.
2
+
3
+ /** @type {McpToolDef} */
4
+ export const memorySaveTool = {
5
+ name: "plugcto_memory_save",
6
+ description:
7
+ "[Pro/Essai] Mémoire long terme. Enregistre durablement (localement) une préférence, un pattern de projet ou un contexte utile pour les missions futures. Les données produit (doctrine/fiches) sont systématiquement exclues — elles ne vivent qu'en RAM.",
8
+ inputSchema: {
9
+ type: "object",
10
+ properties: {
11
+ text: { type: "string", description: "Le souvenir à mémoriser (10000 caractères max)." },
12
+ kind: { type: "string", description: "Catégorie : note | preference | pattern | project." },
13
+ },
14
+ required: ["text"],
15
+ additionalProperties: false,
16
+ },
17
+ };
18
+
19
+ /** @type {McpToolDef} */
20
+ export const memoryRecallTool = {
21
+ name: "plugcto_memory_recall",
22
+ description:
23
+ "[Pro/Essai] Rappel de la mémoire long terme. Retourne les souvenirs persistés localement, filtrables par requête. Aucune donnée produit n'y figure jamais.",
24
+ inputSchema: {
25
+ type: "object",
26
+ properties: {
27
+ query: { type: "string", description: "Filtre optionnel (sous-chaîne insensible à la casse)." },
28
+ },
29
+ additionalProperties: false,
30
+ },
31
+ };
32
+ // MODULE_COMPLETE
@@ -0,0 +1,24 @@
1
+ // src/mcp/tool-skill.js — Définitions MCP des fiches enforce_* (tools/list).
2
+ import { MANIFEST } from "../core/manifest.js";
3
+
4
+ // Description d'une fiche : routage domaine strict + tier (Pro/Lite).
5
+ /** @returns {McpToolDef[]} */
6
+ export function buildSkillTools() {
7
+ return MANIFEST.map((m) => ({
8
+ name: m.name,
9
+ description:
10
+ `[CTO Router] Domaine strict : ${m.slug}. N'appeler CET outil que si la requête relève de ce domaine précis — une SEULE fiche enforce_* par passe (voir ORCHESTRATOR). ` +
11
+ (m.tier === "pro"
12
+ ? `[Pro] `
13
+ : `[Lite] `) +
14
+ (m.title
15
+ ? `Applique la fiche « ${m.title} » (${m.slug}) du Virtual CTO, enveloppée dans le Master Prompt blindé (CTO_VAULT + ANTI_LEAK_PROTOCOL). Signature Ed25519 vérifiée.`
16
+ : `Applique la fiche ${m.slug} du Virtual CTO, enveloppée dans le Master Prompt blindé.`),
17
+ inputSchema: {
18
+ type: "object",
19
+ properties: {},
20
+ additionalProperties: false,
21
+ },
22
+ }));
23
+ }
24
+ // MODULE_COMPLETE
@@ -0,0 +1,13 @@
1
+ // src/mcp/tools-list.js — Catalogue tools/list : fiches enforce_* + outils
2
+ // transverses (DLP, licence, mémoire). Aucun outil n'appelle de modèle : l'agent hôte EST le modèle (le Mode Auto vit dans l'extension).
3
+ import { buildSkillTools } from "./tool-skill.js";
4
+ import { dlpTool } from "./tool-dlp.js";
5
+ import { memorySaveTool, memoryRecallTool } from "./tool-memory.js";
6
+ import { licenceTool } from "./tool-licence.js";
7
+
8
+ // Catalogue statique (hors-ligne, instantané) servi au handler ListTools.
9
+ /** @returns {{ tools: McpToolDef[] }} */
10
+ export function buildToolsList() {
11
+ return { tools: [...buildSkillTools(), dlpTool, licenceTool, memorySaveTool, memoryRecallTool] };
12
+ }
13
+ // MODULE_COMPLETE
@@ -0,0 +1,5 @@
1
+ // src/memory/exports.js — Surface publique du domaine mémoire (hub index.js).
2
+ export { isProductData, filterMemoryEntries } from "./memory-filter.js";
3
+ export { readMemory, appendMemory, clearMemory } from "./memory-store.js";
4
+ export { gateMemory } from "./memory-gate.js";
5
+ // MODULE_COMPLETE
@@ -0,0 +1,32 @@
1
+ // src/memory/memory-filter.js — FILTRAGE STRICT de la mémoire persistante.
2
+ // Règle produit (SecOps) : les données PRODUIT (fiches de doctrine, payloads
3
+ // signés {data, sig}) sont SYSTÉMATIQUEMENT exclues — elles ne vivent qu'en
4
+ // RAM (cache TTL 5 h, cache-ttl.js), jamais sur disque. Filtre appliqué à
5
+ // l'écriture ET à la relecture (défense en profondeur, memory-store.js).
6
+ import { MANIFEST } from "../core/manifest.js";
7
+
8
+ // Sources interdites : slugs du MANIFEST + origines produit explicites.
9
+ const PRODUCT_SOURCES = new Set([...MANIFEST.map((m) => m.slug), "doctrine", "skill", "payload"]);
10
+
11
+ /** @param {unknown} v @returns {boolean} */
12
+ function looksLikeSignedPayload(v) {
13
+ if (!v || typeof v !== "object") return false;
14
+ const o = /** @type {Record<string, unknown>} */ (v);
15
+ return typeof o.data === "string" && typeof o.sig === "string";
16
+ }
17
+
18
+ // true = à EXCLURE (donnée produit ou entrée invalide).
19
+ /** @param {MemoryEntry} e @returns {boolean} */
20
+ export function isProductData(e) {
21
+ if (looksLikeSignedPayload(e)) return true;
22
+ if (!e || typeof e.text !== "string" || !e.text.trim()) return true;
23
+ if (/^== \S+ \((lite|pro)\) — /.test(e.text)) return true; // bloc doctrine concaténée
24
+ return PRODUCT_SOURCES.has(e.source);
25
+ }
26
+
27
+ /** @param {unknown} entries @returns {MemoryEntry[]} */
28
+ export function filterMemoryEntries(entries) {
29
+ if (!Array.isArray(entries)) return [];
30
+ return /** @type {MemoryEntry[]} */ (entries.filter((e) => e && !isProductData(/** @type {MemoryEntry} */ (e))));
31
+ }
32
+ // MODULE_COMPLETE
@@ -0,0 +1,21 @@
1
+ // src/memory/memory-gate.js — Gate tier de la mémoire persistante (monétisation).
2
+ // Règle : mémoire long terme débloquée pendant l'essai (5 j) puis avec la
3
+ // licence Pro. Plan gratuit illimité : économiseur de tokens UNIQUEMENT.
4
+ // Session mutualisée : core/session-resilient.js (purge anti-hostage incluse).
5
+ import { loadConfigCached } from "../core/config-cache.js";
6
+ import { resolveSessionResilient } from "../core/session-resilient.js";
7
+ import { PRO_UPGRADE_URL } from "../core/constants.js";
8
+
9
+ /** @typedef {{ ok: true, cfg: PlugConfig } | { ok: false, content: McpTextContent[] }} GateMemoryResult */
10
+
11
+ /** @returns {Promise<GateMemoryResult>} */
12
+ export async function gateMemory() {
13
+ const cfg = await loadConfigCached();
14
+ const r = await resolveSessionResilient(cfg);
15
+ if (r.failure) return { ok: false, content: r.failure };
16
+ if (r.tier === "lite") {
17
+ return { ok: false, content: [{ type: "text", text: `🔒 La mémoire persistante est débloquée pendant l'essai de 5 jours, puis avec la licence Pro. Version gratuite (Lite) : économiseur de tokens uniquement. Passez à la version Pro ($14.99 USD à vie, Polar.sh) : ${PRO_UPGRADE_URL}` }] };
18
+ }
19
+ return { ok: true, cfg };
20
+ }
21
+ // MODULE_COMPLETE
@@ -0,0 +1,44 @@
1
+ // src/memory/memory-store.js — Persistance locale de la mémoire long terme
2
+ // (~/.pipcode/memory.json, 0600, écriture atomique via fs/atomic-write.js).
3
+ // FILTRAGE STRICT à l'écriture ET à la relecture (défense en profondeur) :
4
+ // aucune donnée produit (doctrine/payloads) ne touche jamais le disque.
5
+ import fs from "node:fs/promises";
6
+ import { MEMORY_PATH } from "../core/constants.js";
7
+ import { writeConfigAtomic } from "../fs/atomic-write.js";
8
+ import { filterMemoryEntries, isProductData } from "./memory-filter.js";
9
+
10
+ /** @param {MemoryEntry[]} entries @returns {Promise<void>} */
11
+ async function writeMemory(entries) {
12
+ await writeConfigAtomic(MEMORY_PATH, { version: 1, entries: filterMemoryEntries(entries) });
13
+ }
14
+
15
+ // Lit la mémoire persistée (RE-FILTRÉE : toute donnée produit qui s'y serait
16
+ // glissée est écartée à la lecture). Absente → []. Corrompue → reset + stderr.
17
+ /** @returns {Promise<MemoryEntry[]>} */
18
+ export async function readMemory() {
19
+ try {
20
+ const f = /** @type {MemoryFile} */ (JSON.parse(await fs.readFile(MEMORY_PATH, "utf8")));
21
+ return filterMemoryEntries(f.entries);
22
+ } catch (err) {
23
+ const e = /** @type {EnrichedError} */ (err);
24
+ if (e && e.code !== "ENOENT") console.error(`[virtual-cto] Mémoire illisible, réinitialisation : ${e.message}`);
25
+ return [];
26
+ }
27
+ }
28
+
29
+ // Ajoute une entrée. Refus explicite si donnée produit (règle RAM-only).
30
+ /** @param {MemoryEntry} entry @returns {Promise<{ ok: boolean, reason?: string }>} */
31
+ export async function appendMemory(entry) {
32
+ if (isProductData(entry)) return { ok: false, reason: "product_data" };
33
+ const entries = await readMemory();
34
+ entries.push(entry);
35
+ await writeMemory(entries);
36
+ return { ok: true };
37
+ }
38
+
39
+ // Purge totale de la mémoire (révocation/teardown). Best-effort.
40
+ /** @returns {Promise<void>} */
41
+ export async function clearMemory() {
42
+ await writeMemory([]);
43
+ }
44
+ // MODULE_COMPLETE
@@ -0,0 +1,49 @@
1
+ // src/net/auth-browser.js — Authentification navigateur zéro friction (Étape 1).
2
+ // Micro-serveur HTTP éphémère sur 127.0.0.1:port intercepte le callback
3
+ // /callback?key=...&email=...&state=... . C3 (CSRF + host header injection) :
4
+ // nonce `state` validé au callback (timingSafeEqual), en-tête Host strictement
5
+ // vérifié contre 127.0.0.1:<port> (DNS rebinding rejeté).
6
+
7
+ import crypto from "node:crypto";
8
+ import http from "node:http";
9
+ import { AUTH_CALLBACK_PORT, authUrl, openBrowser } from "./auth-endpoints.js";
10
+
11
+ /** @typedef {Error & { code?: string, status?: number }} NodeErr */
12
+ /** @typedef {{ port?: number, timeoutMs?: number, open?: boolean, state?: string }} AuthBrowserOpts */
13
+ /** @typedef {{ key: string, email: string, source: "browser", error?: string }} AuthBrowserResult */
14
+
15
+ /**
16
+ * @param {AuthBrowserOpts} [opts]
17
+ * @returns {Promise<AuthBrowserResult>}
18
+ */
19
+ export function authViaBrowser({ port = AUTH_CALLBACK_PORT, timeoutMs = 180_000, open = true, state } = {}) {
20
+ const redirect = `http://127.0.0.1:${port}/callback`;
21
+ const nonce = state || crypto.randomBytes(16).toString("hex");
22
+ const expectedHost = `127.0.0.1:${port}`;
23
+ const target = authUrl(redirect, nonce);
24
+ return new Promise((resolve) => {
25
+ let settled = false;
26
+ /** @param {AuthBrowserResult} val */
27
+ const finish = (val) => { if (!settled) { settled = true; clearTimeout(to); try { server.close(); } catch { /* best-effort : fermeture du serveur local */ } resolve(val); } };
28
+ const server = http.createServer((req, res) => {
29
+ let u;
30
+ try { u = new URL(/** @type {string} */ (req.url), redirect); } catch { res.writeHead(400); res.end("bad"); return; }
31
+ const host = (req.headers.host || "").toLowerCase();
32
+ if (host !== expectedHost.toLowerCase()) { res.writeHead(403); res.end("forbidden"); return; }
33
+ if (u.pathname !== "/callback") { res.writeHead(404); res.end("not found"); return; }
34
+ const reqState = u.searchParams.get("state") || "";
35
+ if (!reqState || reqState.length !== nonce.length || !crypto.timingSafeEqual(Buffer.from(reqState), Buffer.from(nonce))) {
36
+ res.writeHead(403); res.end("forbidden: state"); return;
37
+ }
38
+ const key = u.searchParams.get("key") || "";
39
+ const email = u.searchParams.get("email") || "";
40
+ res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
41
+ res.end("<!doctype html><html><body style='font-family:sans-serif;padding:2rem'><h2>✅ PipCode — connexion réussie</h2><p>Vous pouvez fermer cet onglet et revenir au terminal.</p></body></html>");
42
+ finish({ key, email, source: "browser" });
43
+ });
44
+ server.on("error", (e) => finish({ key: "", email: "", source: "browser", error: String((/** @type {NodeErr} */ (e)).code || e.message) }));
45
+ const to = setTimeout(() => finish({ key: "", email: "", source: "browser", error: "timeout" }), timeoutMs);
46
+ server.listen(port, "127.0.0.1", () => { if (open) openBrowser(target); });
47
+ });
48
+ }
49
+ // MODULE_COMPLETE
@@ -0,0 +1,33 @@
1
+ // src/net/auth-endpoints.js — Endpoints et helpers d'authentification navigateur.
2
+ // Port du callback local (127.0.0.1), base URL du Worker, construction de l'URL
3
+ // d'auth (page Polar/Worker qui redirige vers le callback) + ouverture du navigateur.
4
+
5
+ import { execFile } from "node:child_process";
6
+ import { API_BASE as AUTH_API_BASE } from "../core/constants.js";
7
+
8
+ // Port du callback local (127.0.0.1:9876). Surchargeable via env pour les tests.
9
+ export const AUTH_CALLBACK_PORT = Number(process.env.PLUGCTO_AUTH_PORT) || 9876;
10
+ // Base URL du Worker (validation licence / lookup email) — source unique : constants.js
11
+ // (même env PLUGCTO_API_BASE ; zéro duplication de la valeur de référence).
12
+ export { AUTH_API_BASE };
13
+
14
+ // URL de la page d'auth navigateur (Polar/Worker) — redirige vers le callback.
15
+ // `state` (C3) : nonce CSRF optionnel propagé à la page d'auth, qui doit le
16
+ // renvoyer tel quel sur le callback. Requis pour authViaBrowser (validation).
17
+ /** @param {string} redirect @param {string} [state] @returns {string} */
18
+ export function authUrl(redirect, state) {
19
+ const custom = process.env.PLUGCTO_AUTH_URL;
20
+ if (custom) return custom;
21
+ let u = `${AUTH_API_BASE}/auth?redirect=${encodeURIComponent(redirect)}`;
22
+ if (state) u += `&state=${encodeURIComponent(state)}`;
23
+ return u;
24
+ }
25
+
26
+ // Ouvre une URL dans le navigateur par défaut (best-effort, non bloquant).
27
+ /** @param {string} url */
28
+ export function openBrowser(url) {
29
+ const cmd = process.platform === "win32" ? "explorer"
30
+ : process.platform === "darwin" ? "open" : "xdg-open";
31
+ try { execFile(cmd, [url], () => {}); } catch { /* best-effort */ }
32
+ }
33
+ // MODULE_COMPLETE
@@ -0,0 +1,31 @@
1
+ // src/net/local-fetch.js — Transport des modèles LOCAUX (Ollama…) : IPv4 strict (family 4), socket réutilisé (keepAlive) et attente
2
+ // de réponse alignée sur la génération du modèle (LLM_LOCAL_TIMEOUT_MS = 300 000 ms) : le socket n'est jamais fermé parce qu'aucun
3
+ // octet n'arrive pendant le calcul. Interface minimale compatible fetch (ok, status, text). Annulation par `signal`.
4
+
5
+ import http from "node:http";
6
+ import { StringDecoder } from "node:string_decoder";
7
+ import https from "node:https";
8
+ import { LLM_LOCAL_TIMEOUT_MS } from "../core/constants.js";
9
+ import { ipv4Loopback } from "./loopback.js";
10
+
11
+ const OPTIONS = { keepAlive: true, family: 4, timeout: LLM_LOCAL_TIMEOUT_MS };
12
+ const AGENTS = { "http:": new http.Agent(OPTIONS), "https:": new https.Agent(OPTIONS) };
13
+
14
+ /** @param {string} url @param {RequestInit} [init] @param {(chunk: string) => void} [onData] morceaux de texte au fil de l'eau (flux) @returns {Promise<{ ok: boolean, status: number, text: () => Promise<string> }>} */
15
+ export function localFetch(url, init = {}, onData) {
16
+ const u = new URL(ipv4Loopback(url));
17
+ const lib = u.protocol === "https:" ? https : http;
18
+ return new Promise((resolve, reject) => {
19
+ const req = lib.request(u, { method: init.method || "GET", headers: /** @type {Record<string, string>} */ (init.headers || {}), agent: AGENTS[/** @type {"http:" | "https:"} */ (u.protocol)], family: 4, signal: init.signal ?? undefined }, (res) => {
20
+ /** @type {Buffer[]} */ const chunks = [];
21
+ const decoder = new StringDecoder("utf8");
22
+ res.on("data", (c) => { chunks.push(c); if (onData) onData(decoder.write(c)); });
23
+ res.on("end", () => { const status = res.statusCode || 0; const body = Buffer.concat(chunks).toString("utf8"); resolve({ ok: status >= 200 && status < 300, status, text: async () => body }); });
24
+ res.on("error", reject);
25
+ });
26
+ req.setTimeout(LLM_LOCAL_TIMEOUT_MS, () => req.destroy(new Error(`Aucune donnée reçue depuis ${Math.round(LLM_LOCAL_TIMEOUT_MS / 1000)} s.`))); // silence prolongé sur le socket
27
+ req.on("error", reject); // l'annulation (AbortError) et les erreurs réseau remontent telles quelles à l'appelant
28
+ req.end(typeof init.body === "string" ? init.body : undefined);
29
+ });
30
+ }
31
+ // MODULE_COMPLETE
@@ -0,0 +1,8 @@
1
+ // src/net/loopback.js — Boucle locale en IPv4 STRICT : « localhost » (et ::1) devient 127.0.0.1 (Windows résout localhost en IPv6
2
+ // d'abord, Ollama n'écoute qu'en IPv4 : délais de 60 s et refus de connexion). Pur ; seule l'adresse locale est réécrite.
3
+
4
+ const LOCAL_NAME = /^(https?:\/\/)(?:localhost|\[::1\])(?=[:/]|$)/i;
5
+
6
+ /** « http://localhost:11434/v1 » → « http://127.0.0.1:11434/v1 » ; toute autre adresse inchangée. @param {string} url @returns {string} */
7
+ export const ipv4Loopback = (url) => String(url).replace(LOCAL_NAME, "$1127.0.0.1");
8
+ // MODULE_COMPLETE