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,24 @@
1
+ // src/cli/target-manual.js — Branche [M] du flux TTY : saisie manuelle des numéros.
2
+ // "1, 3" → cibles valides via parsePicks, ou null si aucune sélection valide.
3
+
4
+ import readline from "node:readline/promises";
5
+ import { stdin, stdout } from "node:process";
6
+ import { parsePicks } from "./target-select.js";
7
+
8
+ /** @typedef {{ id: string, name: string, path: string, parentExists: boolean, configExists: boolean, schema?: "mcpServers" | "servers" | "context_servers" | "toml" }} IdeConfigEntry */
9
+
10
+ // [M] : saisie manuelle des numéros → cibles valides, ou null si aucune valide.
11
+ /** @param {IdeConfigEntry[]} detected @returns {Promise<IdeConfigEntry[] | null>} */
12
+ export async function manualSelect(detected) {
13
+ const rl3 = readline.createInterface({ input: stdin, output: stdout });
14
+ try {
15
+ const sel = (await rl3.question(" Saisir les numéros (ex: 1, 3) : ")).trim();
16
+ const targets = parsePicks(sel, detected);
17
+ if (targets.length === 0) {
18
+ console.log("\n ⚠️ Aucun numéro valide saisi. Aucune configuration n'a été écrite.");
19
+ return null;
20
+ }
21
+ return targets;
22
+ } finally { rl3.close(); }
23
+ }
24
+ // MODULE_COMPLETE
@@ -0,0 +1,51 @@
1
+ // src/cli/target-select.js — Étape 4 : sélection granulaire Opt-in des cibles IDE.
2
+ // [A] Tout (défaut) | [M] au cas par cas | [Q] Quitter. Seuls les détectés listés.
3
+ // Retourne les cibles, ou null si arrêt (message imprimé). Non-TTY : détectés sinon candidats.
4
+
5
+ import readline from "node:readline/promises";
6
+ import { stdin, stdout } from "node:process";
7
+ import { manualSelect } from "./target-manual.js";
8
+
9
+ /** @typedef {{ id: string, name: string, path: string, parentExists: boolean, configExists: boolean, schema?: "mcpServers" | "servers" | "context_servers" | "toml" }} IdeConfigEntry */
10
+
11
+ // Parse "1, 3" → indices valides uniques (filtrage des hors-range).
12
+ /** @param {string} sel @param {IdeConfigEntry[]} detected @returns {IdeConfigEntry[]} */
13
+ export function parsePicks(sel, detected) {
14
+ const picks = sel
15
+ .split(/[,\s]+/)
16
+ .map((s) => parseInt(s, 10))
17
+ .filter((n) => Number.isInteger(n) && n >= 1 && n <= detected.length);
18
+ return [...new Set(picks)].map((n) => detected[n - 1]).filter(Boolean);
19
+ }
20
+
21
+ // Flux TTY : liste les détectés, demande [A]/[M]/[Q], gère la sélection manuelle.
22
+ /** @param {IdeConfigEntry[]} detected @returns {Promise<IdeConfigEntry[] | null>} */
23
+ export async function selectTargetsInteractive(detected) {
24
+ if (detected.length === 0) {
25
+ console.log("\n ℹ️ Aucun environnement IDE détecté sur cette machine.");
26
+ console.log(" Aucune configuration n'a été écrite. (Installe un IDE supporté puis relance `npx pipcode setup`.)");
27
+ console.log("\n ✅ Configuration terminée (rien à écrire).");
28
+ return null;
29
+ }
30
+ console.log("\n Environnements détectés :");
31
+ detected.forEach((c, i) => console.log(` [${i + 1}] ${c.name}`));
32
+ console.log("\n [A] Tout installer sur les environnements détectés ← défaut (Entrée)");
33
+ console.log(" [M] Sélection au cas par cas (ex: 1, 3)");
34
+ console.log(" [Q] Quitter sans écrire");
35
+ const rl2 = readline.createInterface({ input: stdin, output: stdout });
36
+ try {
37
+ const choice = (await rl2.question("\n Choix [A / M / Q] (Entrée = A) : ")).trim().toLowerCase();
38
+ const norm = choice || "a";
39
+ if (norm === "q") { console.log("\n ✋ Aucune configuration n'a été écrite (Q)."); return null; }
40
+ if (norm === "m") return await manualSelect(detected);
41
+ return detected; // [A] ou défaut
42
+ } finally { rl2.close(); }
43
+ }
44
+
45
+ // Dispatcher : TTY → sélection interactive ; non-TTY → détectés sinon candidats.
46
+ /** @param {IdeConfigEntry[]} detected @param {IdeConfigEntry[]} candidates @param {boolean} isTTY @returns {Promise<IdeConfigEntry[] | null>} */
47
+ export async function selectTargets(detected, candidates, isTTY) {
48
+ if (isTTY) return selectTargetsInteractive(detected);
49
+ return detected.length > 0 ? detected : candidates;
50
+ }
51
+ // MODULE_COMPLETE
@@ -0,0 +1,56 @@
1
+ // src/cli/toml-merge.js — Fusion PRUDENTE de l'entrée MCP `pipcode` dans le
2
+ // config.toml de Codex, sans dépendance. On ne sait écrire/retirer QUE nos deux
3
+ // tables ([mcp_servers.plugcto] et sa sous-table .env) : tout le reste du fichier
4
+ // est conservé à l'octet près. Ce n'est pas un parseur TOML complet → fail-safe :
5
+ // un fichier contenant des chaînes multilignes (""" ou ''') est REFUSÉ (jamais de
6
+ // réécriture à l'aveugle). Valeurs échappées en chaînes TOML de base (JSON.stringify
7
+ // produit des échappements TOML valides). Pur (aucun I/O).
8
+
9
+ const BARE_KEY = /^[A-Za-z0-9_-]+$/;
10
+ const HEADER = /^\s*\[\[?\s*([^\]]+?)\s*\]\]?\s*(?:#.*)?$/;
11
+ const STARTUP_TIMEOUT_SEC = 30;
12
+ const TOOL_TIMEOUT_SEC = 600; // Mode auto : outils longs (défaut Codex = 60 s)
13
+
14
+ /** @param {string} s @returns {string} */
15
+ const q = (s) => JSON.stringify(String(s));
16
+ /** @param {string} s @returns {string} */
17
+ const key = (s) => (BARE_KEY.test(s) ? s : q(s));
18
+
19
+ /** @param {string} text @param {string} name @returns {string} */
20
+ export function stripServerTables(text, name) {
21
+ if (/"""|'''/.test(text)) {
22
+ throw new Error("config.toml contient des chaînes multilignes : PipCode ne le modifie pas à l'aveugle. Ajoutez le serveur manuellement (Codex : `codex mcp add`).");
23
+ }
24
+ const own = new Set([`mcp_servers.${name}`, `mcp_servers.${name}.env`]);
25
+ let skipping = false;
26
+ return text.split("\n").filter((line) => {
27
+ const m = HEADER.exec(line.replace(/\r$/, ""));
28
+ if (m) skipping = own.has(m[1].replace(/"/g, "").trim());
29
+ return !skipping;
30
+ }).join("\n");
31
+ }
32
+
33
+ /** @param {string} name @param {{ command: string, args: string[], env?: Record<string, string> }} entry @returns {string} */
34
+ export function renderServerTables(name, entry) {
35
+ const k = key(name);
36
+ const lines = [`[mcp_servers.${k}]`, `command = ${q(entry.command)}`, `args = [${entry.args.map(q).join(", ")}]`,
37
+ `startup_timeout_sec = ${STARTUP_TIMEOUT_SEC}`, `tool_timeout_sec = ${TOOL_TIMEOUT_SEC}`];
38
+ const env = Object.entries(entry.env || {});
39
+ if (env.length) lines.push("", `[mcp_servers.${k}.env]`, ...env.map(([n, v]) => `${key(n)} = ${q(v)}`));
40
+ return lines.join("\n") + "\n";
41
+ }
42
+
43
+ /** Texte final : ancien bloc PipCode retiré, nouveau bloc ajouté en fin (idempotent). */
44
+ /** @param {string} text @param {string} name @param {{ command: string, args: string[], env?: Record<string, string> }} entry @returns {string} */
45
+ export function mergeToml(text, name, entry) {
46
+ const base = stripServerTables(text, name).replace(/\s+$/, "");
47
+ return (base ? base + "\n\n" : "") + renderServerTables(name, entry);
48
+ }
49
+
50
+ /** Texte sans le bloc PipCode (désinstallation) ; `changed` = un bloc existait. */
51
+ /** @param {string} text @param {string} name @returns {{ text: string, changed: boolean }} */
52
+ export function removeToml(text, name) {
53
+ const stripped = stripServerTables(text, name);
54
+ return { text: stripped.replace(/\s+$/, "") + (stripped.trim() ? "\n" : ""), changed: stripped !== text };
55
+ }
56
+ // MODULE_COMPLETE
@@ -0,0 +1,15 @@
1
+ // src/cli/trial-check.js — `pipcode trial` : demande au serveur l'état de l'essai et l'écrit en UNE
2
+ // ligne `PLUGCTO_TRIAL <json>` (lue par l'extension VS Code ; lisible par un humain). Aucun secret,
3
+ // aucune écriture. Fin par process.exitCode (jamais process.exit après le réseau : crash Windows).
4
+
5
+ import { loadConfig } from "../core/config.js";
6
+ import { trialStatus } from "../core/trial-status.js";
7
+
8
+ export const TRIAL_LINE_PREFIX = "PLUGCTO_TRIAL ";
9
+
10
+ export async function runTrialCheck() {
11
+ const status = await trialStatus(await loadConfig());
12
+ console.log(TRIAL_LINE_PREFIX + JSON.stringify(status));
13
+ process.exitCode = status.state === "error" ? 1 : 0;
14
+ }
15
+ // MODULE_COMPLETE
@@ -0,0 +1,7 @@
1
+ // src/cli/welcome.js — Message d'accueil préenregistré (exigence produit :
2
+ // après installation, le plug se présente SANS consommer de token — chaîne
3
+ // statique, jamais générée par un LLM). Jumeau exact de
4
+ // vscode-extension/src/logic.ts (WELCOME_MESSAGE) : modifier les DEUX ensemble.
5
+
6
+ export const WELCOME_MESSAGE = "🤖 Votre assistant PipCode à votre service — j'écoute votre demande.";
7
+ // MODULE_COMPLETE
@@ -0,0 +1,38 @@
1
+ // src/core/abort-signal.js — Disjoncteur d'arrêt du moteur : UN AbortController par processus. L'interface écrit « ABORT » sur l'entrée standard du
2
+ // moteur (un signal d'arrêt n'exécute aucun gestionnaire sous Windows) ; le signal annule les requêtes réseau en cours (appels de modèle), les
3
+ // commandes de vérification (tests) et fait sortir proprement la boucle d'exécution, qui sauvegarde l'état et se met en PAUSE. Actif seulement si
4
+ // l'interface l'a demandé (PLUGCTO_ABORT_CHANNEL=1) : un moteur lancé à la main n'écoute rien.
5
+ import { createInterface } from "node:readline";
6
+
7
+ export const ABORT_LINE = "ABORT";
8
+ export const CANCELLED_MARK = "@@VCTO_CANCELLED@@";
9
+ const controller = new AbortController();
10
+
11
+ /** Signal unique du processus (annule fetch / execFile). */
12
+ export const runSignal = controller.signal;
13
+ export const isRunAborted = () => controller.signal.aborted;
14
+ export const abortRun = () => controller.abort();
15
+
16
+ /** Erreur typée : l'utilisateur a demandé l'arrêt (jamais traitée comme une panne). */
17
+ export class RunAbortedError extends Error {
18
+ constructor() { super("Exécution interrompue par l'utilisateur."); this.name = "RunAbortedError"; this.code = "aborted"; }
19
+ }
20
+
21
+ /** Relie un AbortController local (un appel réseau) au signal du processus. @param {AbortController} local */
22
+ export function linkAbort(local) {
23
+ if (controller.signal.aborted) local.abort();
24
+ else controller.signal.addEventListener("abort", () => local.abort(), { once: true });
25
+ }
26
+
27
+ /** Écoute « ABORT » sur l'entrée standard ; ne retient pas le processus. @param {NodeJS.ReadableStream} [input] @param {boolean} [enabled] @returns {boolean} */
28
+ export function listenForAbort(input = process.stdin, enabled = process.env.PLUGCTO_ABORT_CHANNEL === "1") {
29
+ if (!enabled) return false;
30
+ createInterface({ input }).on("line", (line) => {
31
+ if (line.trim() !== ABORT_LINE || controller.signal.aborted) return;
32
+ process.stderr.write(`${CANCELLED_MARK}\n`); // repère lisible par l'interface même si le moteur sort par une erreur
33
+ abortRun();
34
+ });
35
+ /** @type {{ unref?: () => void }} */ (input).unref?.();
36
+ return true;
37
+ }
38
+ // MODULE_COMPLETE
@@ -0,0 +1,29 @@
1
+ // src/core/auto-deps.js — Auto Mode : factory de dépendances (réutilisée par
2
+ // le dispatch MCP et le shell CLI bin/setup.js). Centralise le binding des
3
+ // helpers → auto.js reste autonome (aucun import d'index.js, zéro cycle) et
4
+ // testable avec mocks.
5
+ import { ANALYSIS_MAX_TOKENS, patchMaxTokens } from "./constants.js";
6
+ import { callLLM } from "./llm-call.js";
7
+ import { loadDoctrine } from "./doctrine.js";
8
+ import { loadAllDoctrine } from "./doctrine-all.js";
9
+ import { orchestrateLLM } from "../engine/orchestrator.js";
10
+ import { parseJsonLoose } from "../engine/parse-json-loose.js";
11
+ import { loadAstRuntime } from "../engine/ast-runtime.js";
12
+ import { checkMissionGuard } from "../engine/mission-guard.js";
13
+ import { scanPatchForViolations } from "../engine/patch-scan.js";
14
+ import { missionContextPrefix } from "../engine/mission-context-prefix.js";
15
+ import { computeImpact } from "../engine/compute-impact.js";
16
+ import { extractMission } from "../engine/extract-llm.js";
17
+ import { readMissionContext, writeMission } from "../engine/mission-store.js";
18
+ import { logIteration } from "../engine/mission-log.js";
19
+
20
+ export function createAutoDeps() {
21
+ return {
22
+ orchestrateLLM, callLLM, loadDoctrine, loadAllDoctrine,
23
+ checkMissionGuard, scanPatchForViolations, missionContextPrefix,
24
+ computeImpact, extractMission, readMissionContext, writeMission, logIteration,
25
+ loadAstRuntime, parseJsonLoose, patchMaxTokens,
26
+ ANALYSIS_MAX_TOKENS,
27
+ };
28
+ }
29
+ // MODULE_COMPLETE
@@ -0,0 +1,21 @@
1
+ // src/core/cache-ttl.js — TTL des caches RAM de doctrine : 5 h, RAM-ONLY.
2
+ // Garantie anti-persistance (règle produit) : les données produit (fiches
3
+ // signées Ed25519) ne vivent QUE dans ces caches mémoire, TTL 5 h maximum —
4
+ // JAMAIS écrites sur disque (la mémoire persistante les exclue par filtrage
5
+ // strict, memory-filter.js). Surcharge test : PLUGCTO_DOCTRINE_TTL_MS.
6
+
7
+ export const DOCTRINE_TTL_MS = process.env.PLUGCTO_DOCTRINE_TTL_MS !== undefined
8
+ ? Math.max(0, Number(process.env.PLUGCTO_DOCTRINE_TTL_MS) || 0)
9
+ : 5 * 3600 * 1000;
10
+
11
+ /** @param {DoctrineCache | null} cache @returns {cache is DoctrineCache} */
12
+ export function isCacheFresh(cache) {
13
+ return !!cache && typeof cache.content === "string" && !!cache.content.trim()
14
+ && Date.now() - cache.cachedAt < DOCTRINE_TTL_MS;
15
+ }
16
+
17
+ /** @param {string} content @returns {DoctrineCache} */
18
+ export function freshCache(content) {
19
+ return { content, cachedAt: Date.now() };
20
+ }
21
+ // MODULE_COMPLETE
@@ -0,0 +1,38 @@
1
+ // src/core/config-base.js — Primitives de config client (création + persistance).
2
+ // freshConfig : nouvelle config (clientId UUID). ensureConfigDir : mkdir ~/.pipcode.
3
+ // saveConfig : écriture restreinte 0600 (config contient licenseKey). Base sans cycle.
4
+
5
+ import fs from "node:fs/promises";
6
+ import crypto from "node:crypto";
7
+ import { CONFIG_DIR, CONFIG_PATH } from "./constants.js";
8
+
9
+ // Nouvelle config vierge (installedAt + clientId UUID persistant).
10
+ /** @returns {PlugConfig} */
11
+ export function freshConfig() {
12
+ return { installedAt: new Date().toISOString(), clientId: crypto.randomUUID(), licenseKey: "", email: "", trialExpiredNotified: false };
13
+ }
14
+
15
+ // Crée le dossier de config (best-effort, échec non silencieux : on relance).
16
+ /** @returns {Promise<void>} */
17
+ export async function ensureConfigDir() {
18
+ try { await fs.mkdir(CONFIG_DIR, { recursive: true }); }
19
+ catch (err) {
20
+ const e = /** @type {EnrichedError} */ (err);
21
+ console.error(`[virtual-cto] Impossible de créer ${CONFIG_DIR} : ${e.message}`);
22
+ throw err;
23
+ }
24
+ }
25
+
26
+ // Écrit la config en 0600 (licenseKey non lisible par les autres utilisateurs).
27
+ /** @param {PlugConfig} cfg @returns {Promise<void>} */
28
+ export async function saveConfig(cfg) {
29
+ try {
30
+ await ensureConfigDir();
31
+ await fs.writeFile(CONFIG_PATH, JSON.stringify(cfg, null, 2), { encoding: "utf8", mode: 0o600 });
32
+ } catch (err) {
33
+ const e = /** @type {Error} */ (err);
34
+ console.error(`[virtual-cto] Impossible d'écrire la config ${CONFIG_PATH} : ${e.message}`);
35
+ // Non fatal : on continue sans persister (quota recalculé au prochain démarrage).
36
+ }
37
+ }
38
+ // MODULE_COMPLETE
@@ -0,0 +1,50 @@
1
+ // src/core/config-cache.js — Cache TTL RAM de la config (audit hot path tools/call).
2
+ // Pattern cache-ttl.js : TTL court surchargeable (PLUGCTO_CONFIG_TTL_MS, défaut
3
+ // 3 s). Sort l'I/O disque du chemin tools/call : au plus UNE lecture (et UNE
4
+ // migration-réécriture) par fenêtre TTL. Cohérence : les écritures IN-PROCESS
5
+ // (notice d'essai, purge anti-hostage) mutent l'OBJET CACHÉ lui-même avant
6
+ // saveConfig → le cache reste toujours vrai ; une réécriture par un AUTRE
7
+ // processus (CLI) est rattrapée à l'expiration du TTL (déviation de gate
8
+ // BORNÉE au TTL, jamais permanente). Single-flight : un seul loadConfig en vol.
9
+
10
+ import { loadConfig } from "./config.js";
11
+
12
+ export const CONFIG_TTL_MS = process.env.PLUGCTO_CONFIG_TTL_MS !== undefined
13
+ ? Math.max(0, Number(process.env.PLUGCTO_CONFIG_TTL_MS) || 0)
14
+ : 3000;
15
+
16
+ /** @typedef {{ cfg: PlugConfig, cachedAt: number }} ConfigCacheEntry */
17
+ /** @type {ConfigCacheEntry | null} */
18
+ let entry = null;
19
+ /** @type {Promise<PlugConfig> | null} */
20
+ let inFlight = null;
21
+
22
+ /** @param {ConfigCacheEntry | null} e @returns {e is ConfigCacheEntry} */
23
+ function isFresh(e) {
24
+ return !!e && Date.now() - e.cachedAt < CONFIG_TTL_MS;
25
+ }
26
+
27
+ /** @param {PlugConfig} cfg */
28
+ function settle(cfg) { entry = { cfg, cachedAt: Date.now() }; inFlight = null; return cfg; }
29
+
30
+ /** @param {unknown} err @returns {never} */
31
+ function fail(err) { inFlight = null; throw err; }
32
+
33
+ // Lecture réelle (unique par fenêtre) ; dédoublonne les appels concurrents.
34
+ /** @returns {Promise<PlugConfig>} */
35
+ function loadOnce() {
36
+ if (!inFlight) inFlight = loadConfig().then(settle, fail);
37
+ return inFlight;
38
+ }
39
+
40
+ // Invalidation explicite : rattrape immédiatement une réécriture externe.
41
+ /** @returns {void} */
42
+ export function invalidateConfigCache() { entry = null; }
43
+
44
+ // Chargeur du hot path : RAM si frais, lecture disque sinon.
45
+ /** @returns {Promise<PlugConfig>} */
46
+ export async function loadConfigCached() {
47
+ if (isFresh(entry)) return entry.cfg;
48
+ return loadOnce();
49
+ }
50
+ // MODULE_COMPLETE
@@ -0,0 +1,58 @@
1
+ // src/core/config.js — loadConfig (lecture + parsing + migration additive).
2
+ // readConfigRaw : readFile + premier lancement (ENOENT/I/O → fresh). parseConfigRaw :
3
+ // JSON.parse + validation installedAt. migrateConfig : garantit clientId/licenseKey/
4
+ // email/trialExpiredNotified sans casser l'existant. Charge config-base (anti-cycle).
5
+
6
+ import fs from "node:fs/promises";
7
+ import crypto from "node:crypto";
8
+ import { CONFIG_PATH } from "./constants.js";
9
+ import { freshConfig, saveConfig } from "./config-base.js";
10
+
11
+ // Lit le fichier config. Retourne { raw } ou { cfg } (fresh sauvée si absent/illisible).
12
+ /** @returns {Promise<{ raw?: string, cfg?: PlugConfig }>} */
13
+ async function readConfigRaw() {
14
+ try { return { raw: await fs.readFile(CONFIG_PATH, "utf8") }; }
15
+ catch (err) {
16
+ const e = /** @type {EnrichedError} */ (err);
17
+ if (!(e && e.code === "ENOENT")) console.error(`[virtual-cto] Config illisible, réinitialisation : ${e.message}`);
18
+ const cfg = freshConfig(); await saveConfig(cfg); return { cfg };
19
+ }
20
+ }
21
+
22
+ // Parse + valide raw. Retourne { cfg } ou { cfg, resave } (fresh si corrompue).
23
+ /** @param {string} raw @returns {{ cfg: PlugConfig, resave?: boolean }} */
24
+ function parseConfigRaw(raw) {
25
+ try {
26
+ const cfg = /** @type {PlugConfig} */ (JSON.parse(raw));
27
+ if (typeof cfg.installedAt !== "string") throw new Error("installedAt manquant");
28
+ return { cfg };
29
+ } catch (err) {
30
+ const e = /** @type {Error} */ (err);
31
+ console.error(`[virtual-cto] Config corrompue, réinitialisation : ${e.message}`);
32
+ return { cfg: freshConfig(), resave: true };
33
+ }
34
+ }
35
+
36
+ // Migration additive : garantit les champs nouveaux sans casser l'existant.
37
+ /** @param {PlugConfig} cfg @returns {{ cfg: PlugConfig, migrated: boolean }} */
38
+ function migrateConfig(cfg) {
39
+ let migrated = false;
40
+ if (typeof cfg.clientId !== "string" || !cfg.clientId) { cfg.clientId = crypto.randomUUID(); migrated = true; }
41
+ if (typeof cfg.licenseKey !== "string") { cfg.licenseKey = ""; migrated = true; }
42
+ if (typeof cfg.trialExpiredNotified !== "boolean") { cfg.trialExpiredNotified = false; migrated = true; }
43
+ if (typeof cfg.email !== "string") { cfg.email = ""; migrated = true; }
44
+ return { cfg, migrated };
45
+ }
46
+
47
+ // Charge la config : read → parse → migrate. Crée/migre au premier lancement.
48
+ /** @returns {Promise<PlugConfig>} */
49
+ export async function loadConfig() {
50
+ const read = await readConfigRaw();
51
+ if (read.cfg) return read.cfg;
52
+ const parsed = parseConfigRaw(/** @type {string} */ (read.raw));
53
+ if (parsed.resave) await saveConfig(parsed.cfg);
54
+ const mig = migrateConfig(parsed.cfg);
55
+ if (mig.migrated) await saveConfig(mig.cfg);
56
+ return mig.cfg;
57
+ }
58
+ // MODULE_COMPLETE
@@ -0,0 +1,31 @@
1
+ // src/core/constants.js — Constantes de configuration globales + budget tokens.
2
+ // Toutes surchargeables via env (PLUGCTO_API_BASE, PLUGCTO_PRO_URL, PLUGCTO_MAX_TOKENS,
3
+ // PLUGCTO_CONFIG_DIR) pour tests/staging. patchMaxTokens : budget patch échelonné.
4
+
5
+ import path from "node:path";
6
+ import { resolvePlugctoRoot } from "../fs/home-root.js";
7
+
8
+ // Adresse du serveur PipCode (surchargeable par PLUGCTO_API_BASE).
9
+ export const API_BASE = process.env.PLUGCTO_API_BASE || "https://api.pipcode.app";
10
+ export const PRO_UPGRADE_URL = process.env.PLUGCTO_PRO_URL || "https://buy.polar.sh/polar_cl_ZB8s48w6kpT0pP1L4YD8EuuOZAhk4QF7FMh1x0mkEkX";
11
+ export const TRIAL_HOURS = 120; // 5 jours d'essai, 100 % fonctions débloquées.
12
+ export const FETCH_TIMEOUT_MS = 10_000;
13
+ export const LLM_TIMEOUT_MS = 60_000; // BYOK cloud : timeout pour un provider muet.
14
+ export const LLM_LOCAL_TIMEOUT_MS = 300_000; // modèle local : silence maximal toléré sur le socket (5 min) ; en streaming des octets arrivent en continu
15
+ export const LLM_LOCAL_TOTAL_MS = 1_800_000; // modèle local : durée totale maximale d'une génération (30 min)
16
+ export const DEFAULT_MAX_TOKENS = Math.max(256, Number(process.env.PLUGCTO_MAX_TOKENS) || 4096);
17
+ export const ANALYSIS_MAX_TOKENS = 1024;
18
+ export const EXTRACTION_MAX_TOKENS = 4096;
19
+ export const SESSION_RENEW_MARGIN_SEC = 60;
20
+ export const SESSION_GRACE_SEC = 300; // marge d'horloge client/serveur (anti-rollback du jeton d'essai).
21
+ export const CONFIG_DIR = resolvePlugctoRoot(); // résilient : repli tmpdir si profil verrouillé
22
+ export const CONFIG_PATH = path.join(CONFIG_DIR, "config.json");
23
+ export const MEMORY_PATH = path.join(CONFIG_DIR, "memory.json"); // mémoire long terme (Pro)
24
+
25
+ // Budget patch : min 768 (retouche minimale), max DEFAULT_MAX_TOKENS, ~1 token / 3 chars.
26
+ /** @param {number} codeLen @returns {number} */
27
+ export function patchMaxTokens(codeLen) {
28
+ const n = Math.max(0, Number(codeLen) || 0);
29
+ return Math.min(DEFAULT_MAX_TOKENS, Math.max(768, Math.ceil(n / 3)));
30
+ }
31
+ // MODULE_COMPLETE
@@ -0,0 +1,45 @@
1
+ // src/core/crypto.js — Intégrité cryptographique Ed25519 (trust anchor client).
2
+ // Ancre : env PLUGCTO_TRUSTED_PUBLIC_X (rotation/staging) sinon clé publique
3
+ // PROD embarquée (trusted-key.js). FAIL-CLOSED (SecOps) : AUCUN fallback DEV —
4
+ // ancre absente ou JWK invalide → exception explicite, arrêt net du serveur.
5
+
6
+ import crypto from "node:crypto";
7
+ import { PROD_TRUSTED_PUBLIC_X } from "../auth/trusted-key.js";
8
+
9
+ // Résout l'ancre de confiance ou lève une Error explicite (fail-closed, testable).
10
+ /** @param {string | undefined} envX @param {string} anchorX @returns {string} */
11
+ export function resolveTrustAnchor(envX, anchorX) {
12
+ const x = envX || anchorX;
13
+ if (!x || typeof x !== "string") {
14
+ throw new Error(
15
+ "Ancre Ed25519 absente : PLUGCTO_TRUSTED_PUBLIC_X non fixé et ancre PROD " +
16
+ "embarquée manquante. Arrêt immédiat (fail-closed).",
17
+ );
18
+ }
19
+ return x;
20
+ }
21
+
22
+ export const TRUSTED_PUBLIC_X = resolveTrustAnchor(process.env.PLUGCTO_TRUSTED_PUBLIC_X, PROD_TRUSTED_PUBLIC_X);
23
+
24
+ // Clé publique de vérification — JWK invalide → arrêt net (aucun fallback toléré).
25
+ export const TRUSTED_PUBLIC_KEY = (() => {
26
+ try {
27
+ return crypto.createPublicKey({ key: { kty: "OKP", crv: "Ed25519", x: TRUSTED_PUBLIC_X }, format: "jwk" });
28
+ } catch (err) {
29
+ const e = /** @type {Error} */ (err);
30
+ throw new Error(`Ancre Ed25519 invalide ("${TRUSTED_PUBLIC_X.slice(0, 12)}…") : ${e.message}. Arrêt immédiat (fail-closed).`);
31
+ }
32
+ })();
33
+
34
+ // Vérifie la signature Ed25519 d'un payload signé (dataStr + sigB64). true si valide.
35
+ /** @param {string} dataStr @param {string} sigB64 @returns {boolean} */
36
+ export function verifySignature(dataStr, sigB64) {
37
+ try {
38
+ return crypto.verify(null, Buffer.from(dataStr, "utf8"), TRUSTED_PUBLIC_KEY, Buffer.from(sigB64, "base64"));
39
+ } catch (err) {
40
+ const e = /** @type {Error} */ (err);
41
+ console.error("[virtual-cto] Erreur de vérification Ed25519 :", e.message);
42
+ return false;
43
+ }
44
+ }
45
+ // MODULE_COMPLETE
@@ -0,0 +1,51 @@
1
+ // src/core/dlp.js — Watchdog DLP (Data Loss Prevention) local.
2
+ // Empreintes textuelles du <CTO_VAULT> mises en cache après chaque livraison vérifiée.
3
+ // dlp_scan compare un candidat aux empreintes : recopie verbatim de doctrine → blocage.
4
+ // Cache vaultFingerprints encapsulé (setter/getter) — pas de binding mutable cross-module.
5
+
6
+ export const DLP_BLOCK_TEXT = "🚫 [DLP Triggered] Tentative d'extraction de la doctrine bloquée.";
7
+
8
+ // Marqueurs fixes haute-signal (présents dans tout vault, indépendants de la fiche).
9
+ export const DLP_FIXED_MARKERS = [
10
+ "system_priority_override: max",
11
+ "anti_leak_protocol",
12
+ "censure verbatim",
13
+ "refus catégorique",
14
+ "amnésie d'exécution",
15
+ "cto_vault",
16
+ "je suis votre cto virtuel. je me concentre sur la sécurité et l'architecture de votre code",
17
+ ];
18
+
19
+ // Normalisation : minuscules + collapsage espaces (comparaison robuste).
20
+ /** @param {string} s @returns {string} */
21
+ export function normalizeText(s) {
22
+ return String(s ?? "").toLowerCase().replace(/\s+/g, " ").trim();
23
+ }
24
+
25
+ // Extrait les empreintes d'un vault : lignes longues (>= 40 chars normalisés) + marqueurs.
26
+ /** @param {string} vaultText @returns {Set<string>} */
27
+ export function extractFingerprints(vaultText) {
28
+ const lines = String(vaultText).split(/\r?\n/).map(normalizeText).filter((l) => l.length >= 40);
29
+ return new Set([...DLP_FIXED_MARKERS, ...lines]);
30
+ }
31
+
32
+ // Cache en mémoire des empreintes du dernier vault livré (vie du process).
33
+ /** @type {Set<string>} */
34
+ let vaultFingerprints = new Set();
35
+
36
+ // Mutateurs sécurisés : encapsule l'état mutable (Zero Trust, SRP).
37
+ /** @param {Set<string>} fp @returns {void} */
38
+ export function setVaultFingerprints(fp) { vaultFingerprints = fp; }
39
+ /** @returns {boolean} */
40
+ export function hasVaultFingerprints() { return !!vaultFingerprints && vaultFingerprints.size > 0; }
41
+
42
+ // Détecte une fuite : true si le candidat contient une empreinte du vault.
43
+ /** @param {string} candidate @returns {boolean} */
44
+ export function scanForLeak(candidate) {
45
+ if (!vaultFingerprints || vaultFingerprints.size === 0) return false;
46
+ const t = normalizeText(candidate);
47
+ if (!t) return false;
48
+ for (const fp of vaultFingerprints) { if (t.includes(fp)) return true; }
49
+ return false;
50
+ }
51
+ // MODULE_COMPLETE
@@ -0,0 +1,31 @@
1
+ // src/core/doctrine-all.js — loadAllDoctrine : les 19 fiches du MANIFEST, vérifiées Ed25519, concaténées.
2
+ // Cache RAM TTL 5 h (cache-ttl.js), jamais sur disque. Fail-Fast : signature invalide → rejet. Dégradation 402/403.
3
+ import { MANIFEST } from "./manifest.js";
4
+ import { loadFiche } from "./doctrine-one.js";
5
+ import { freshCache, isCacheFresh } from "./cache-ttl.js";
6
+
7
+ /** @type {DoctrineCache | null} */
8
+ let allDoctrineCache = null;
9
+
10
+ // Charge la doctrine complète (19 fiches). Dégrade sur fiche Pro inaccessible (trial sans licence).
11
+ /** @param {PlugConfig} cfg @returns {Promise<string>} */
12
+ export async function loadAllDoctrine(cfg) {
13
+ if (isCacheFresh(allDoctrineCache)) return allDoctrineCache.content;
14
+ const blocks = [];
15
+ for (const m of MANIFEST) {
16
+ const block = await loadFiche(m, cfg);
17
+ if (block) blocks.push(block);
18
+ }
19
+ if (!blocks.length) {
20
+ throw new Error("Doctrine complète (19 fiches) indisponible — aucune fiche accessible.");
21
+ }
22
+ const content = blocks.join("\n\n");
23
+ allDoctrineCache = freshCache(content);
24
+ return content;
25
+ }
26
+
27
+ // Test-only : réinitialise le cache des 19 fiches.
28
+ export function resetAllDoctrineCache() {
29
+ allDoctrineCache = null;
30
+ }
31
+ // MODULE_COMPLETE
@@ -0,0 +1,10 @@
1
+ // src/core/doctrine-caches.js — Test-only : réinitialise les caches de
2
+ // doctrine (core_rules + 19 fiches).
3
+ import { resetDoctrineCache } from "./doctrine.js";
4
+ import { resetAllDoctrineCache } from "./doctrine-all.js";
5
+
6
+ export function __resetDoctrineCachesForTest() {
7
+ resetDoctrineCache();
8
+ resetAllDoctrineCache();
9
+ }
10
+ // MODULE_COMPLETE
@@ -0,0 +1,49 @@
1
+ // src/core/doctrine-fetch.js — fetchSignedSkill : fetch cloud d'une fiche signée (thin client).
2
+ // Séparation fetch / verify pour des messages d'erreur distincts. Timeout AbortController.
3
+ import { API_BASE, FETCH_TIMEOUT_MS } from "./constants.js";
4
+ import { machineFp } from "./identity.js";
5
+
6
+ // Fetche une fiche signée (slug) depuis le Worker. Retourne { data, sig } ou jette une Error.
7
+ /** @param {string} slug @param {string} tier @param {PlugConfig} cfg @returns {Promise<SignedSkillPayload>} */
8
+ export async function fetchSignedSkill(slug, tier, cfg) {
9
+ const installedAt = (cfg && cfg.installedAt) || "";
10
+ const clientId = (cfg && cfg.clientId) || "";
11
+ const licenseKey = process.env.PLUGCTO_LICENSE_KEY || (cfg && cfg.licenseKey) || "";
12
+ const url = `${API_BASE}/skills/${encodeURIComponent(slug)}?tier=${encodeURIComponent(tier)}`;
13
+ const controller = new AbortController();
14
+ const timer = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
15
+ try {
16
+ const res = await fetch(url, {
17
+ signal: controller.signal,
18
+ headers: {
19
+ Accept: "application/json",
20
+ "X-PlugCTO-Client": "thin/1.0",
21
+ "X-PlugCTO-Installed-At": installedAt,
22
+ "X-PlugCTO-Tier": tier,
23
+ "X-PlugCTO-Client-Id": clientId,
24
+ "X-PlugCTO-License": licenseKey,
25
+ "X-PlugCTO-Machine-Fp": machineFp(),
26
+ },
27
+ });
28
+ if (!res.ok) throw new Error(`HTTP ${res.status} ${res.statusText} sur ${url}`);
29
+ const body = await res.text();
30
+ if (!body || !body.trim()) throw new Error(`Réponse vide pour "${slug}" (tier=${tier})`);
31
+ let payload;
32
+ try {
33
+ payload = /** @type {SignedSkillPayload} */ (JSON.parse(body));
34
+ } catch {
35
+ throw new Error(`Réponse non-JSON pour "${slug}" (payload signé attendu)`);
36
+ }
37
+ if (typeof payload.data !== "string" || typeof payload.sig !== "string") {
38
+ throw new Error(`Payload signé malformé pour "${slug}" (champs data/sig manquants)`);
39
+ }
40
+ return payload;
41
+ } catch (err) {
42
+ const e = /** @type {EnrichedError} */ (err);
43
+ if (e && e.name === "AbortError") throw new Error(`Timeout (${FETCH_TIMEOUT_MS}ms) sur ${url}`);
44
+ throw new Error(`Échec fetch ${url} : ${e.message}`);
45
+ } finally {
46
+ clearTimeout(timer);
47
+ }
48
+ }
49
+ // MODULE_COMPLETE
@@ -0,0 +1,18 @@
1
+ // src/core/doctrine-one.js — loadFiche : UNE fiche du MANIFEST récupérée en payload signé, vérifiée Ed25519 puis dépaquetée. Partagé par la doctrine complète (Auto) et le chat.
2
+ // Fiche Pro inaccessible (essai terminé, quota, réseau) → "" ; signature invalide ou données illisibles → erreur (Fail-Fast).
3
+ import { fetchSignedSkill } from "./doctrine-fetch.js";
4
+ import { verifySignature } from "./crypto.js";
5
+
6
+ /** @param {import("./manifest.js").ManifestEntry} m @param {PlugConfig} cfg @param {{ fetchSkill?: typeof fetchSignedSkill }} [deps] @returns {Promise<string>} bloc « == slug (tier) — titre == » + contenu, ou "" */
7
+ export async function loadFiche(m, cfg, { fetchSkill = fetchSignedSkill } = {}) {
8
+ let payload;
9
+ try { payload = await fetchSkill(m.slug, m.tier, cfg); } catch { return ""; }
10
+ if (!verifySignature(payload.data, payload.sig)) {
11
+ throw new Error(`Signature Ed25519 de la fiche "${m.slug}" INVALIDE — doctrine rejetée. Cause probable : ancre de confiance (PLUGCTO_TRUSTED_PUBLIC_X) ≠ clé privée du Worker.`);
12
+ }
13
+ let parsed;
14
+ try { parsed = /** @type {SkillPayloadContent} */ (JSON.parse(payload.data)); } catch { throw new Error(`Fiche "${m.slug}" : payload signé valide mais données illisibles.`); }
15
+ const content = typeof parsed.content === "string" ? parsed.content : "";
16
+ return content.trim() ? `== ${m.slug} (${m.tier}) — ${m.title} ==\n${content}` : "";
17
+ }
18
+ // MODULE_COMPLETE