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,59 @@
1
+ // src/net/proxy-env.js — Support des proxys d'entreprise (Point bloquant #3).
2
+ // Node fetch (undici) ignore HTTP_PROXY par défaut ; l'indicateur natif
3
+ // NODE_USE_ENV_PROXY (Node ≥ 24) n'est lu qu'AU DÉMARRAGE du process. Si un
4
+ // proxy est détecté (variables standards ou variantes npm_config_*), le process
5
+ // se relance donc lui-même avec l'env augmentée — execFile sur process.execPath,
6
+ // jamais de shell. Garde anti-boucle PLUGCTO_PROXY_BOOT ; best-effort total.
7
+
8
+ import { execFileSync } from "node:child_process";
9
+
10
+ /** @typedef {Error & { code?: string, status?: number }} NodeErr */
11
+ /** @typedef {{ env?: NodeJS.ProcessEnv, nodeMajor?: number, execPath?: string, argv?: string[], spawnTool?: (execPath: string, argv: string[], env: NodeJS.ProcessEnv) => void, exit?: (code: number) => void, warn?: (msg: string) => void }} ProxyOpts */
12
+
13
+ // Config proxy normalisée : variables standards + variantes npm. null si aucun.
14
+ /** @param {NodeJS.ProcessEnv} [env] @returns {{ http: string, https: string, noProxy: string } | null} */
15
+ export function activeProxyConfig(env = process.env) {
16
+ const http = env.HTTP_PROXY || env.http_proxy || env.npm_config_proxy || "";
17
+ const https = env.HTTPS_PROXY || env.https_proxy || env.npm_config_https_proxy || http;
18
+ const noProxy = env.NO_PROXY || env.no_proxy || env.npm_config_noproxy || "";
19
+ if (!http && !https) return null;
20
+ return { http, https, noProxy };
21
+ }
22
+
23
+ // Relance synchrone de soi-même (stdio hérités : remplacement transparent).
24
+ /** @param {string} execPath @param {string[]} argv @param {NodeJS.ProcessEnv} env @returns {void} */
25
+ function relaunchSync(execPath, argv, env) {
26
+ execFileSync(execPath, argv, { stdio: "inherit", env });
27
+ }
28
+
29
+ // Active le support proxy par auto-relance unique. Retourne true si une relance
30
+ // a été engagée. Gardes : aucun proxy, déjà amorcé (anti-boucle), Node < 24.
31
+ /** @param {ProxyOpts} [opts] @returns {boolean} */
32
+ export function ensureProxySupport(opts = {}) {
33
+ const env = opts.env || process.env;
34
+ const warn = opts.warn || console.error;
35
+ const exit = opts.exit || process.exit;
36
+ const cfg = activeProxyConfig(env);
37
+ if (!cfg || env.PLUGCTO_PROXY_BOOT || env.NODE_USE_ENV_PROXY === "1") return false;
38
+ const nodeMajor = opts.nodeMajor ?? Number(process.versions.node.split(".")[0]);
39
+ if (nodeMajor < 24) {
40
+ warn(`[pipcode] Proxy détecté mais Node ${nodeMajor}.x ne lit pas NODE_USE_ENV_PROXY (Node ≥ 24 requis) — appels réseau en direct.`);
41
+ return false;
42
+ }
43
+ const childEnv = { ...env, HTTP_PROXY: cfg.http, HTTPS_PROXY: cfg.https, NO_PROXY: cfg.noProxy,
44
+ NODE_USE_ENV_PROXY: "1", PLUGCTO_PROXY_BOOT: "1" };
45
+ try {
46
+ const spawn = opts.spawnTool || relaunchSync;
47
+ const execPath = opts.execPath || process.execPath;
48
+ const argv = opts.argv || process.argv.slice(1);
49
+ spawn(execPath, argv, childEnv);
50
+ } catch (err) {
51
+ const e = /** @type {NodeErr} */ (err);
52
+ if (typeof e.status === "number") { exit(e.status || 1); return true; }
53
+ warn(`[pipcode] Relance proxy impossible (${e.message}) — appels réseau en direct.`);
54
+ return false;
55
+ }
56
+ exit(0); // l'enfant a terminé (code 0) : le parent reflète puis s'arrête.
57
+ return true;
58
+ }
59
+ // MODULE_COMPLETE
@@ -0,0 +1,17 @@
1
+ // src/project-memory/batches.js — Découpage SANS PERTE d'un texte plus grand que la fenêtre du modèle : parties successives sur des
2
+ // fins de ligne (la concaténation des parties redonne exactement le texte). Aucun résumé, aucune troncature.
3
+
4
+ /** Coupe un texte en morceaux ≤ max caractères, sur des fins de ligne (une ligne géante dépasse max seule : jamais coupée). @param {string} text @param {number} max @returns {Array<{ text: string, fromLine: number }>} */
5
+ export function splitLossless(text, max) {
6
+ if (text.length <= max) return [{ text, fromLine: 1 }];
7
+ const out = [];
8
+ let cur = ""; let from = 1; let line = 1;
9
+ for (const l of text.split(/(?<=\n)/)) {
10
+ if (cur && cur.length + l.length > max) { out.push({ text: cur, fromLine: from }); cur = ""; from = line; }
11
+ cur += l; line++;
12
+ }
13
+ if (cur) out.push({ text: cur, fromLine: from });
14
+ return out;
15
+ }
16
+
17
+ // MODULE_COMPLETE
@@ -0,0 +1,42 @@
1
+ // src/project-memory/cahiers.js — Cahiers de la mémoire projet (UI/UX, fonctionnalités, architecture, infrastructure, vision,
2
+ // général) : sections balisées <!-- vcto:section id sources --> ... <!-- /vcto:section -->. Chaque section cite les fichiers
3
+ // sources avec leur empreinte : si un source change, la section est signalée PÉRIMÉE (jamais supprimée, jamais présentée comme
4
+ // à jour). Une section COMPLÈTE le code, elle ne le remplace pas. Pur.
5
+
6
+ /** @typedef {{ id: string, sources: Array<{ path: string, sha: string }>, body: string }} Section */
7
+
8
+ export const CAHIERS = ["architecture", "fonctionnalites", "vision-objectifs", "cahier-general", "ui-ux", "infrastructure"];
9
+ const SECTION_RE = /<!-- vcto:section id="([a-z0-9-]+)" sources="([^"]*)" -->\n([\s\S]*?)\n<!-- \/vcto:section -->/g;
10
+
11
+ /** En-tête d'un fichier cahier neuf. @param {string} c @returns {string} */
12
+ export const headerOf = (c) => `# Mémoire projet — ${c}
13
+
14
+ > Généré par PipCode. Chaque section cite ses sources (empreinte) : une section dont un source a changé est PÉRIMÉE. Les décisions de l'utilisateur priment et ne sont jamais écrasées. Le code reste la référence.
15
+ `;
16
+
17
+ /** @param {Section} s @returns {string} */
18
+ export function formatSection(s) {
19
+ const src = s.sources.map((x) => `${x.path}@${x.sha.slice(0, 12)}`).join(",");
20
+ return `<!-- vcto:section id="${s.id}" sources="${src}" -->\n${s.body.trim()}\n<!-- /vcto:section -->`;
21
+ }
22
+
23
+ /** @param {string} md @returns {Section[]} */
24
+ export function parseSections(md) {
25
+ return [...md.matchAll(SECTION_RE)].map((m) => ({
26
+ id: m[1], body: m[3],
27
+ sources: m[2].split(",").filter(Boolean).map((x) => { const i = x.lastIndexOf("@"); return { path: x.slice(0, i), sha: x.slice(i + 1) }; }),
28
+ }));
29
+ }
30
+
31
+ /** Remplace la section de même id, sinon l'ajoute (le reste du fichier est conservé tel quel). @param {string} md @param {Section} s @returns {string} */
32
+ export function upsertSection(md, s) {
33
+ const block = formatSection(s);
34
+ const re = new RegExp(`<!-- vcto:section id="${s.id}" sources="[^"]*" -->\n[\\s\\S]*?\n<!-- /vcto:section -->`);
35
+ return re.test(md) ? md.replace(re, () => block) : `${md.trimEnd()}\n\n${block}\n`;
36
+ }
37
+
38
+ /** Ids des sections dont un source a changé ou disparu. @param {Section[]} sections @param {import("./index-build.js").MemoryIndex} index @returns {string[]} */
39
+ export function staleSections(sections, index) {
40
+ return sections.filter((s) => s.sources.some((x) => !index.files[x.path] || !index.files[x.path].sha.startsWith(x.sha))).map((s) => s.id);
41
+ }
42
+ // MODULE_COMPLETE
@@ -0,0 +1,15 @@
1
+ // src/project-memory/decision-settle.js — Verdict de l'utilisateur sur une proposition de PipCode (« ok », « je valide » / refus ou correction) : la décision « à confirmer »
2
+ // devient « confirmée » ou « refusée ». Un refus garde les mots EXACTS de l'utilisateur pour que la proposition écartée ne soit jamais refaite (cahier des charges).
3
+ import { CAHIERS } from "./cahiers.js";
4
+ import { readMemoryFile, writeMemoryFile } from "./store.js";
5
+ import { parseDecisions, recordDecision } from "./decisions.js";
6
+
7
+ /** @param {string} root @param {string} cahier @param {"confirme" | "refuse"} verdict @param {string} userMessage @returns {Promise<void>} */
8
+ export async function settleDecision(root, cahier, verdict, userMessage) {
9
+ if (!CAHIERS.includes(cahier)) return;
10
+ const md = (await readMemoryFile(root, `${cahier}.md`)) ?? "";
11
+ const open = parseDecisions(md).filter((d) => d.status === "a-confirmer").pop();
12
+ if (open) await writeMemoryFile(root, `${cahier}.md`, md.replace(`id="${open.id}" source="vcto" status="a-confirmer"`, `id="${open.id}" source="vcto" status="${verdict}"`));
13
+ if (verdict === "refuse") await recordDecision(root, cahier, { source: "utilisateur", status: "refuse", question: open ? `Proposition écartée : ${open.answer}` : "Proposition écartée par l'utilisateur", answer: userMessage });
14
+ }
15
+ // MODULE_COMPLETE
@@ -0,0 +1,36 @@
1
+ // src/project-memory/decisions.js — Décisions de l'UTILISATEUR (et choix proposés par PipCode) dans les cahiers : sections balisées
2
+ // <!-- vcto:decision ... -->, textes conservés TELS QUELS (aucun résumé). Elles priment sur le code, ne sont jamais écrasées par la
3
+ // mémoire générée, jamais périmées. Un choix de PipCode est « a-confirmer » avec son estimation de réussite (estimation, pas mesure).
4
+ // Les balises présentes dans les textes sont neutralisées (un utilisateur ne peut pas forger une section). Pur + écriture via store.
5
+
6
+ import { createHash } from "node:crypto";
7
+ import { CAHIERS, headerOf } from "./cahiers.js";
8
+ import { readMemoryFile, writeMemoryFile } from "./store.js";
9
+
10
+ /** @typedef {{ source: "utilisateur" | "vcto", status: "confirme" | "a-confirmer" | "refuse", confidence?: number, question: string, answer: string }} Decision */
11
+ const RE = /<!-- vcto:decision id="([a-f0-9]+)" source="(utilisateur|vcto)" status="(confirme|a-confirmer|refuse)" confidence="(\d*)" -->\n### Question\n([\s\S]*?)\n### Réponse\n([\s\S]*?)\n<!-- \/vcto:decision -->/g;
12
+
13
+ /** @param {string} s @returns {string} */
14
+ const safe = (s) => String(s).replace(/<!--/g, "&lt;!--").replace(/-->/g, "--&gt;").replace(/^### /gm, "#### ").replace(/\r\n/g, "\n").trim();
15
+ /** @param {Decision} d @returns {string} */
16
+ export const decisionId = (d) => createHash("sha256").update(`${d.source}\n${d.question}\n${d.answer}`).digest("hex").slice(0, 10);
17
+
18
+ /** @param {Decision} d @returns {string} */
19
+ export function formatDecision(d) {
20
+ const conf = Number.isInteger(d.confidence) && /** @type {number} */ (d.confidence) >= 0 && /** @type {number} */ (d.confidence) <= 100 ? String(d.confidence) : "";
21
+ return `<!-- vcto:decision id="${decisionId(d)}" source="${d.source}" status="${d.status}" confidence="${conf}" -->\n### Question\n${safe(d.question)}\n### Réponse\n${safe(d.answer)}\n<!-- /vcto:decision -->`;
22
+ }
23
+
24
+ /** @param {string} md @returns {Array<Decision & { id: string }>} */
25
+ export function parseDecisions(md) {
26
+ return [...md.matchAll(RE)].map((m) => ({ id: m[1], source: /** @type {Decision["source"]} */ (m[2]), status: /** @type {Decision["status"]} */ (m[3]), confidence: m[4] ? Number(m[4]) : undefined, question: m[5], answer: m[6] }));
27
+ }
28
+
29
+ /** Enregistre (idempotent : même contenu = même id, pas de doublon) dans le cahier ; cahier inconnu = erreur typée. @param {string} root @param {string} cahier @param {Decision} d @returns {Promise<void>} */
30
+ export async function recordDecision(root, cahier, d) {
31
+ if (!CAHIERS.includes(cahier)) throw new Error(`Cahier inconnu : ${cahier}`);
32
+ const md = (await readMemoryFile(root, `${cahier}.md`)) ?? headerOf(cahier);
33
+ if (parseDecisions(md).some((x) => x.id === decisionId(d))) return;
34
+ await writeMemoryFile(root, `${cahier}.md`, `${md.trimEnd()}\n\n${formatDecision(d)}\n`);
35
+ }
36
+ // MODULE_COMPLETE
@@ -0,0 +1,35 @@
1
+ // src/project-memory/index-build.js — Carte EXACTE du projet (déterministe, sans LLM, sans perte) : par fichier, empreinte
2
+ // sha256, taille, nombre de lignes, symboles déclarés AVEC leur ligne, imports résolus. C'est l'inventaire de ce qui existe ;
3
+ // il ne remplace jamais le code (le chat recharge le source exact).
4
+
5
+ import { createHash } from "node:crypto";
6
+ import { parseFile } from "../engine/code-graph.js";
7
+ import { declarations } from "../engine/code-decls.js";
8
+ import { safeRead } from "../engine/context-files.js";
9
+
10
+ /** @typedef {{ sha: string, bytes: number, lines: number, symbols: Array<{ name: string, line: number }>, imports: Array<{ to: string, names: string[] }> }} FileEntry */
11
+ /** @typedef {{ version: 1, files: Record<string, FileEntry> }} MemoryIndex */
12
+
13
+ /** @param {string} root @param {string[]} paths @returns {Promise<MemoryIndex>} */
14
+ export async function buildIndex(root, paths) {
15
+ const known = new Set(paths);
16
+ /** @type {Record<string, FileEntry>} */ const files = {};
17
+ for (const p of paths) {
18
+ const text = await safeRead(root, p);
19
+ if (text === null) continue;
20
+ files[p] = {
21
+ sha: createHash("sha256").update(text).digest("hex"), bytes: Buffer.byteLength(text), lines: text.split("\n").length,
22
+ symbols: declarations(p, text), imports: parseFile(p, text, known).imports,
23
+ };
24
+ }
25
+ return { version: 1, files };
26
+ }
27
+
28
+ /** Écarts entre deux cartes : fichiers ajoutés, modifiés (empreinte différente), supprimés. @param {MemoryIndex | null} prev @param {MemoryIndex} next @returns {{ added: string[], changed: string[], removed: string[] }} */
29
+ export function diffIndex(prev, next) {
30
+ const before = prev ? prev.files : {};
31
+ const added = Object.keys(next.files).filter((f) => !before[f]);
32
+ const changed = Object.keys(next.files).filter((f) => before[f] && before[f].sha !== next.files[f].sha);
33
+ return { added, changed, removed: Object.keys(before).filter((f) => !next.files[f]) };
34
+ }
35
+ // MODULE_COMPLETE
@@ -0,0 +1,16 @@
1
+ // src/project-memory/memory-build.js — Carte exacte du projet écrite dans .pipcode/memoire/index.json (gratuit : aucun LLM,
2
+ // aucun réseau). Retourne aussi les écarts depuis la carte précédente (ajoutés / modifiés / supprimés).
3
+
4
+ import { listProjectFiles } from "../auto/project-files.js";
5
+ import { NOISE_FILE } from "../engine/context-files.js";
6
+ import { buildIndex, diffIndex } from "./index-build.js";
7
+ import { readIndex, writeMemoryFile } from "./store.js";
8
+
9
+ /** @param {string} root @returns {Promise<{ index: import("./index-build.js").MemoryIndex, diff: ReturnType<typeof diffIndex> }>} */
10
+ export async function indexProject(root) {
11
+ const paths = (await listProjectFiles(root)).filter((f) => !NOISE_FILE.test(f)).sort();
12
+ const [prev, index] = [await readIndex(root), await buildIndex(root, paths)];
13
+ await writeMemoryFile(root, "index.json", JSON.stringify(index, null, 1));
14
+ return { index, diff: diffIndex(prev, index) };
15
+ }
16
+ // MODULE_COMPLETE
@@ -0,0 +1,40 @@
1
+ // src/project-memory/memory-tags.js — Mémoire écrite DANS la réponse de l'IA (une seule requête, aucun second appel) : bloc
2
+ // [[vcto:memory cahier="architecture" sources="src/a.js,src/b.js"]]contenu[[/vcto:memory]]. Le moteur l'extrait, l'enregistre
3
+ // comme section du cahier (sources = fichiers RÉELS de la carte, empreintes figées) et le RETIRE du texte montré à l'utilisateur.
4
+ // Sortie du modèle = donnée non fiable : cahier connu, sources existantes (≤ 20), contenu borné.
5
+
6
+ import { createHash } from "node:crypto";
7
+ import { CAHIERS, headerOf, upsertSection } from "./cahiers.js";
8
+ import { readIndex, readMemoryFile, writeMemoryFile } from "./store.js";
9
+
10
+ const TAG = /\[\[vcto:memory\s+cahier="([a-z-]+)"\s+sources="([^"]{1,2000})"\s*\]\]([\s\S]*?)\[\[\/vcto:memory\]\]/g;
11
+ const MAX_BODY = 20_000;
12
+ const MAX_SOURCES = 20;
13
+
14
+ /** @param {string} raw @returns {{ visible: string, items: Array<{ cahier: string, sources: string[], body: string }> }} */
15
+ export function parseMemoryTags(raw) {
16
+ /** @type {Array<{ cahier: string, sources: string[], body: string }>} */ const items = [];
17
+ const visible = String(raw).replace(TAG, (_m, cahier, src, body) => {
18
+ const text = String(body).trim();
19
+ if (CAHIERS.includes(cahier) && text && text.length <= MAX_BODY) items.push({ cahier, sources: String(src).split(",").map((s) => s.trim()).filter(Boolean).slice(0, MAX_SOURCES), body: text });
20
+ return "";
21
+ });
22
+ return { visible: visible.replace(/\n{3,}/g, "\n\n").trim(), items };
23
+ }
24
+
25
+ /** Enregistre les sections valides (sources présentes dans la carte) ; retourne les ids écrits. @param {string} root @param {Array<{ cahier: string, sources: string[], body: string }>} items @returns {Promise<string[]>} */
26
+ export async function recordMemory(root, items) {
27
+ const index = await readIndex(root);
28
+ if (!index) return [];
29
+ const written = [];
30
+ for (const it of items) {
31
+ const known = [...new Set(it.sources)].filter((p) => Object.hasOwn(index.files, p));
32
+ if (known.length === 0) continue;
33
+ const id = `${it.cahier}-m${createHash("sha256").update([...known].sort().join("\n")).digest("hex").slice(0, 8)}`;
34
+ const md = (await readMemoryFile(root, `${it.cahier}.md`)) ?? headerOf(it.cahier);
35
+ await writeMemoryFile(root, `${it.cahier}.md`, upsertSection(md, { id, sources: known.map((path) => ({ path, sha: index.files[path].sha })), body: it.body }));
36
+ written.push(id);
37
+ }
38
+ return written;
39
+ }
40
+ // MODULE_COMPLETE
@@ -0,0 +1,24 @@
1
+ // src/project-memory/pending.js — Questions posées à l'utilisateur et pas encore tranchées (.pipcode/memoire/pending.json) :
2
+ // lecture validée (donnée non fiable), au plus MAX questions, une par cahier et par texte. Sert à ne pas reposer la même question
3
+ // et à rattacher la réponse (mots exacts de l'utilisateur) au bon cahier.
4
+
5
+ import { CAHIERS } from "./cahiers.js";
6
+ import { readMemoryFile, writeMemoryFile } from "./store.js";
7
+
8
+ const MAX = 6;
9
+
10
+ /** @param {string} root @returns {Promise<Array<{ cahier: string, text: string }>>} */
11
+ export async function readPending(root) {
12
+ try {
13
+ const v = JSON.parse((await readMemoryFile(root, "pending.json")) ?? "[]");
14
+ return Array.isArray(v) ? v.filter((x) => x && CAHIERS.includes(x.cahier) && typeof x.text === "string" && x.text.length < 2000).slice(0, MAX) : [];
15
+ } catch { return []; } // fichier corrompu : aucune question en attente
16
+ }
17
+
18
+ /** @param {string} root @param {Array<{ cahier: string, text: string }>} list @returns {Promise<void>} */
19
+ export async function writePending(root, list) {
20
+ const seen = new Set();
21
+ const unique = list.filter((x) => { const k = `${x.cahier}|${x.text}`; return seen.has(k) ? false : (seen.add(k), true); }).slice(-MAX);
22
+ await writeMemoryFile(root, "pending.json", JSON.stringify(unique, null, 1));
23
+ }
24
+ // MODULE_COMPLETE
@@ -0,0 +1,39 @@
1
+ // src/project-memory/qa-apply.js — Applique le dialogue après la réponse de l'IA : la réponse de l'utilisateur est enregistrée
2
+ // MOT POUR MOT (aucun appel modèle de plus) sur la question en attente qu'elle tranche ; un choix « choisis pour moi » est
3
+ // enregistré « à confirmer » avec son estimation ; les nouvelles questions deviennent « en attente ». Retourne le texte à montrer.
4
+
5
+ import { parseQaTags } from "./qa-tags.js";
6
+ import { readPending, writePending } from "./pending.js";
7
+ import { recordDecision } from "./decisions.js";
8
+ import { settleDecision } from "./decision-settle.js";
9
+ import { parseMemoryTags, recordMemory } from "./memory-tags.js";
10
+
11
+ /** Texte montré sans aucune balise (repli si l'enregistrement échoue). @param {string} raw @returns {string} */
12
+ export const stripTags = (raw) => parseQaTags(parseMemoryTags(raw).visible).visible;
13
+
14
+ /** Bloc donné à l'IA : questions en attente (pour ne pas les reposer et reconnaître la réponse). @param {string} root @returns {Promise<string>} */
15
+ export async function pendingBlock(root) {
16
+ const p = await readPending(root);
17
+ return p.length ? `--- Questions déjà posées à l'utilisateur, en attente de réponse ---\n${p.map((x) => `[${x.cahier}] ${x.text}`).join("\n")}` : "";
18
+ }
19
+
20
+ /** @param {string} root @param {string} userMessage @param {string} rawAnswer @returns {Promise<string>} texte visible */
21
+ export async function applyQa(root, userMessage, rawAnswer) {
22
+ const mem = parseMemoryTags(rawAnswer);
23
+ await recordMemory(root, mem.items); // mémoire du projet écrite depuis CETTE réponse (aucun autre appel)
24
+ const { visible, asks, answered, choices, settled } = parseQaTags(mem.visible);
25
+ let pending = await readPending(root);
26
+ for (const c of answered) {
27
+ for (const q of pending.filter((x) => x.cahier === c)) await recordDecision(root, c, { source: "utilisateur", status: "confirme", question: q.text, answer: userMessage });
28
+ pending = pending.filter((x) => x.cahier !== c);
29
+ }
30
+ for (const ch of choices) {
31
+ const q = pending.find((x) => x.cahier === ch.cahier);
32
+ await recordDecision(root, ch.cahier, { source: "vcto", status: "a-confirmer", confidence: ch.confidence, question: q ? q.text : userMessage, answer: ch.text });
33
+ pending = pending.filter((x) => x.cahier !== ch.cahier);
34
+ }
35
+ for (const s of settled) await settleDecision(root, s.cahier, s.verdict, userMessage); // « ok / je valide » ou refus : la proposition de PipCode est tranchée
36
+ await writePending(root, [...pending, ...asks]);
37
+ return visible;
38
+ }
39
+ // MODULE_COMPLETE
@@ -0,0 +1,28 @@
1
+ // src/project-memory/qa-tags.js — Balises de dialogue que l'IA place dans sa réponse : question posée (ask), question tranchée par
2
+ // l'utilisateur (answered, balise vide), choix proposé par PipCode avec estimation de réussite (choice). Le moteur les lit puis les
3
+ // RETIRE du texte montré (le texte intérieur reste visible). Sortie du modèle = donnée non fiable : cahier connu, confiance 0–100.
4
+
5
+ import { CAHIERS } from "./cahiers.js";
6
+
7
+ const TAG = /\[\[vcto:(ask|answered|choice|validated|rejected)\s+cahier="([a-z-]+)"(?:\s+confidence="(\d{1,3})")?\s*\]\](?:([\s\S]*?)\[\[\/vcto:\1\]\])?/g;
8
+
9
+ /** @typedef {{ cahier: string, text: string, confidence?: number }} QaItem */
10
+
11
+ /** @param {string} raw @returns {{ visible: string, asks: QaItem[], answered: string[], choices: QaItem[], settled: Array<{ cahier: string, verdict: "confirme" | "refuse" }> }} */
12
+ export function parseQaTags(raw) {
13
+ /** @type {QaItem[]} */ const asks = [];
14
+ /** @type {QaItem[]} */ const choices = [];
15
+ /** @type {string[]} */ const answered = [];
16
+ /** @type {Array<{ cahier: string, verdict: "confirme" | "refuse" }>} */ const settled = [];
17
+ const visible = String(raw).replace(TAG, (_m, kind, cahier, conf, inner = "") => {
18
+ const text = String(inner).trim();
19
+ if (!CAHIERS.includes(cahier)) return text;
20
+ if (kind === "ask" && text) asks.push({ cahier, text });
21
+ else if (kind === "answered") answered.push(cahier);
22
+ else if (kind === "validated" || kind === "rejected") settled.push({ cahier, verdict: kind === "validated" ? "confirme" : "refuse" });
23
+ else if (kind === "choice" && text) choices.push({ cahier, text, confidence: conf !== undefined && Number(conf) <= 100 ? Number(conf) : undefined });
24
+ return text;
25
+ });
26
+ return { visible: visible.replace(/\n{3,}/g, "\n\n").trim(), asks, answered, choices, settled };
27
+ }
28
+ // MODULE_COMPLETE
@@ -0,0 +1,38 @@
1
+ // src/project-memory/recall.js — Rappel de la mémoire pour le chat : sections des cahiers pertinentes pour la question (sources
2
+ // ouvertes, mots de la question), vision/cahier général toujours en tête. Sections ENTIÈRES (jamais coupées) dans un budget ;
3
+ // ce qui ne tient pas est listé, pas perdu. Une section périmée est marquée comme telle.
4
+
5
+ import { CAHIERS, parseSections, staleSections } from "./cahiers.js";
6
+ import { parseDecisions } from "./decisions.js";
7
+ import { readIndex, readMemoryFile } from "./store.js";
8
+
9
+ const ANCHORS = ["vision-objectifs", "cahier-general"];
10
+ /** @param {string} q @returns {string[]} */
11
+ const words = (q) => [...new Set(q.toLowerCase().split(/[^\p{L}\p{N}_]+/u).filter((w) => w.length >= 4))];
12
+
13
+ /** @param {string} root @param {string} question @param {string[]} open @param {number} budget @returns {Promise<{ text: string, omitted: string[] }>} */
14
+ export async function recallSections(root, question, open, budget) {
15
+ const index = await readIndex(root);
16
+ const kw = words(question);
17
+ const items = [];
18
+ for (const c of CAHIERS) {
19
+ const md = (await readMemoryFile(root, `${c}.md`)) ?? "";
20
+ for (const d of parseDecisions(md)) items.push({ score: 1000, label: d.source === "utilisateur" ? `Décision de l'utilisateur (${c})` : `Choix proposé par PipCode, à confirmer (${c}${d.confidence !== undefined ? `, ≈ ${d.confidence} % estimé` : ""})`, body: `Question : ${d.question}
21
+ Réponse : ${d.answer}` });
22
+ const sections = parseSections(md);
23
+ const stale = new Set(index ? staleSections(sections, index) : []);
24
+ for (const s of sections) {
25
+ const hay = `${s.id} ${s.body}`.toLowerCase();
26
+ const score = (ANCHORS.includes(c) ? 100 : 0) + s.sources.filter((x) => open.includes(x.path)).length * 5 + kw.filter((w) => hay.includes(w)).length;
27
+ if (score > 0) items.push({ score, label: `${s.id}${stale.has(s.id) ? " [PÉRIMÉE : un source a changé]" : ""}`, body: s.body });
28
+ }
29
+ }
30
+ const blocks = []; const omitted = []; let used = 0;
31
+ for (const it of items.sort((a, b) => b.score - a.score)) {
32
+ const block = `### Mémoire — ${it.label}\n${it.body}`;
33
+ if (used + block.length > budget) { omitted.push(it.label); continue; }
34
+ used += block.length; blocks.push(block);
35
+ }
36
+ return { text: blocks.join("\n\n"), omitted };
37
+ }
38
+ // MODULE_COMPLETE
@@ -0,0 +1,37 @@
1
+ // src/project-memory/store.js — Dossier mémoire du projet (.pipcode/memoire/) : lecture VALIDÉE (donnée non fiable : un fichier
2
+ // édité à la main ne doit jamais casser ni détourner) et écriture ATOMIQUE (fichier temporaire puis renommage). Seul ce dossier
3
+ // est écrit ; le nom de fichier est contrôlé (liste fermée / motif sûr), jamais un chemin reçu.
4
+
5
+ import fs from "node:fs/promises";
6
+ import path from "node:path";
7
+
8
+ const SAFE_NAME = /^[a-z0-9][a-z0-9-]{0,40}\.(md|json)$/;
9
+
10
+ /** @param {string} root @returns {string} */
11
+ export const memoryDir = (root) => path.join(root, ".pipcode", "memoire");
12
+
13
+ /** Lecture d'un fichier mémoire ; null si absent/illisible/nom refusé. @param {string} root @param {string} name @returns {Promise<string | null>} */
14
+ export async function readMemoryFile(root, name) {
15
+ if (!SAFE_NAME.test(name)) return null;
16
+ return fs.readFile(path.join(memoryDir(root), name), "utf8").catch(() => null);
17
+ }
18
+
19
+ /** Écriture atomique ; nom invalide = erreur typée. @param {string} root @param {string} name @param {string} content @returns {Promise<void>} */
20
+ export async function writeMemoryFile(root, name, content) {
21
+ if (!SAFE_NAME.test(name)) throw new Error(`Nom de fichier mémoire refusé : ${name}`);
22
+ const dir = memoryDir(root);
23
+ await fs.mkdir(dir, { recursive: true });
24
+ const tmp = path.join(dir, `.${name}.${process.pid}.tmp`);
25
+ await fs.writeFile(tmp, content, "utf8");
26
+ await fs.rename(tmp, path.join(dir, name));
27
+ }
28
+
29
+ /** Carte lue et validée (forme minimale) ; null sinon. @param {string} root @returns {Promise<import("./index-build.js").MemoryIndex | null>} */
30
+ export async function readIndex(root) {
31
+ const raw = await readMemoryFile(root, "index.json");
32
+ try {
33
+ const v = JSON.parse(raw ?? "");
34
+ return v && v.version === 1 && v.files && typeof v.files === "object" && !Array.isArray(v.files) ? v : null;
35
+ } catch { return null; } // JSON corrompu : carte considérée absente (reconstruite)
36
+ }
37
+ // MODULE_COMPLETE
@@ -0,0 +1,13 @@
1
+ // src/project-memory/window.js — Taille de la fenêtre de contexte (en caractères) : PLUGCTO_CONTEXT_CHARS (donnée non fiable :
2
+ // entier borné), sinon valeur par défaut. Le contexte du chat se place dans cette fenêtre (rien n'est coupé en silence).
3
+
4
+ export const DEFAULT_CONTEXT_CHARS = 120_000;
5
+ const MIN = 4_000; const MAX = 2_000_000;
6
+
7
+ /** @param {Record<string, string | undefined>} [env] @returns {number} */
8
+ export function contextChars(env = process.env) {
9
+ const n = Number.parseInt(env.PLUGCTO_CONTEXT_CHARS || "", 10);
10
+ return Number.isFinite(n) && n >= MIN && n <= MAX ? n : DEFAULT_CONTEXT_CHARS;
11
+ }
12
+
13
+ // MODULE_COMPLETE
@@ -0,0 +1,17 @@
1
+ // src/rules/rules-advisor.js — Bloc <ADVISOR_RULES> du lot « pro » servi par le serveur : périmètres que l'utilisateur oublie
2
+ // (comptes, données, argent, sécurité, légal, fiabilité, exploitation, expérience, croissance). Extraction par les balises
3
+ // (structure, aucun texte de règle ici). Non chargé (serveur injoignable, compte gratuit) → "" : jamais de texte inventé.
4
+ import { getRules } from "./rules-registry.js";
5
+
6
+ const OPEN = "<ADVISOR_RULES>";
7
+ const CLOSE = "</ADVISOR_RULES>";
8
+
9
+ /** @returns {string} */
10
+ export function advisorRules() {
11
+ let decomp;
12
+ try { decomp = getRules().prompts.decomp; } catch { return ""; }
13
+ const a = decomp.indexOf(OPEN);
14
+ const b = decomp.indexOf(CLOSE);
15
+ return a >= 0 && b > a ? decomp.slice(a, b + CLOSE.length) : "";
16
+ }
17
+ // MODULE_COMPLETE
@@ -0,0 +1,17 @@
1
+ // src/rules/rules-frugality.js — Bloc <FRUGALITY_RULES> (principes de réalisation, en langage clair) du gabarit maître servi par le serveur (lot de base, tous les
2
+ // comptes) : injecté dans le prompt système du chat. Extraction par les balises (structure, pas de texte de règle ici).
3
+ // Règles non chargées (serveur injoignable) → "" : le chat répond sans (jamais de texte de frugalité inventé).
4
+ import { getRules } from "./rules-registry.js";
5
+
6
+ const OPEN = "<FRUGALITY_RULES>";
7
+ const CLOSE = "</FRUGALITY_RULES>";
8
+
9
+ /** @returns {string} */
10
+ export function frugalityRules() {
11
+ let master;
12
+ try { master = getRules().prompts.master; } catch { return ""; }
13
+ const a = master.indexOf(OPEN);
14
+ const b = master.indexOf(CLOSE);
15
+ return a >= 0 && b > a ? master.slice(a, b + CLOSE.length) : "";
16
+ }
17
+ // MODULE_COMPLETE
@@ -0,0 +1,56 @@
1
+ // src/rules/rules-load.js — Chargement des règles métier depuis le serveur PipCode (fiches signées Ed25519,
2
+ // même chemin que la doctrine : essai/licence tranchés par le serveur). Lot « base » obligatoire (comptes
3
+ // gratuits inclus) ; lot « pro » (UX, prompt de patch) : refus 402 = lot absent (le plan reste utilisable),
4
+ // toute autre panne = échec honnête. Mémoire seulement, requête dédupliquée (single-flight).
5
+
6
+ import { fetchSignedSkill } from "../core/doctrine-fetch.js";
7
+ import { verifySignature } from "../core/crypto.js";
8
+ import { setRules, rulesAreFresh } from "./rules-registry.js";
9
+ import { parseBaseRules, parseProRules } from "./rules-validate.js";
10
+
11
+ export class RulesUnavailableError extends Error {
12
+ /** @param {string} message @param {boolean} denied */
13
+ constructor(message, denied) { super(message); this.name = "RulesUnavailableError"; this.denied = denied; }
14
+ }
15
+
16
+ /** @param {string} raw @returns {string} */
17
+ const explain = (raw) => (/HTTP 404/.test(raw)
18
+ ? "Les règles PipCode ne sont pas encore disponibles sur le serveur."
19
+ : `Le serveur PipCode est injoignable (${raw.slice(0, 140)}). Une connexion est requise.`) + " Aucune requête n'a été envoyée au modèle.";
20
+
21
+ /** @param {string} slug @param {string} tier @param {PlugConfig} cfg @param {{ fetchSkill: typeof fetchSignedSkill, verify: typeof verifySignature }} d @returns {Promise<string>} */
22
+ async function readBundle(slug, tier, cfg, d) {
23
+ let payload;
24
+ try { payload = await d.fetchSkill(slug, tier, cfg); }
25
+ catch (e) { const m = /** @type {Error} */ (e).message; throw new RulesUnavailableError(explain(m), /HTTP 402/.test(m)); }
26
+ if (!d.verify(payload.data, payload.sig)) throw new RulesUnavailableError("Signature des règles PipCode INVALIDE : contenu rejeté.", false);
27
+ /** @type {{ slug?: unknown, content?: unknown }} */ let p = {};
28
+ try { p = JSON.parse(payload.data); } catch { /* corps illisible : rejeté juste dessous */ }
29
+ if (p.slug !== slug || typeof p.content !== "string") throw new RulesUnavailableError("Règles PipCode inattendues (fiche vide ou différente).", false);
30
+ return p.content;
31
+ }
32
+
33
+ /** @param {PlugConfig} cfg @param {{ fetchSkill: typeof fetchSignedSkill, verify: typeof verifySignature }} d */
34
+ async function load(cfg, d) {
35
+ // Les deux lots sont demandés EN MÊME TEMPS (avant : l'un après l'autre = deux allers-retours cumulés à chaque message).
36
+ const baseText = readBundle("rules_base", "lite", cfg, d);
37
+ const proText = readBundle("rules_pro", "pro", cfg, d);
38
+ proText.catch(() => undefined); // l'échec du lot pro est traité plus bas ; jamais de rejet non observé si le lot de base échoue d'abord
39
+ const base = parseBaseRules(await baseText);
40
+ /** @type {{ ux: EngineRules["ux"], patch: string, decomp: string, control: string, prefix: Record<string, string> | null, scan: ScanRules | null }} */
41
+ let pro = { ux: { design: {}, universal: [] }, patch: "", decomp: "", control: "", prefix: null, scan: null };
42
+ try { pro = parseProRules(await proText); }
43
+ catch (e) { if (!(e instanceof RulesUnavailableError && e.denied)) throw e; }
44
+ setRules({ domains: base.domains, patterns: base.patterns, glossary: base.glossary, ux: pro.ux, prompts: { analysis: base.analysis, mission: base.mission, patch: pro.patch, master: base.master, decomp: pro.decomp, control: pro.control }, plan: base.plan, pricing: base.pricing, prefix: pro.prefix, scan: pro.scan });
45
+ }
46
+
47
+ /** @type {Promise<void> | null} */
48
+ let inflight = null;
49
+
50
+ /** Garantit des règles fraîches en mémoire (réseau seulement si absentes/périmées). @param {PlugConfig} cfg @param {{ fetchSkill?: typeof fetchSignedSkill, verify?: typeof verifySignature }} [deps] @returns {Promise<void>} */
51
+ export function ensureRules(cfg, { fetchSkill = fetchSignedSkill, verify = verifySignature } = {}) {
52
+ if (rulesAreFresh()) return Promise.resolve();
53
+ inflight ??= load(cfg, { fetchSkill, verify }).finally(() => { inflight = null; });
54
+ return inflight;
55
+ }
56
+ // MODULE_COMPLETE
@@ -0,0 +1,16 @@
1
+ // src/rules/rules-project.js — Bloc <PROJECT_RULES> du lot « pro » servi par le serveur : type de projet, lieu de mise en ligne (proposé, jamais imposé) et suivi des erreurs en production.
2
+ // Extraction par les balises (structure, aucun texte de règle ici). Non chargé (serveur injoignable, compte gratuit) → "" : jamais de texte inventé.
3
+ import { getRules } from "./rules-registry.js";
4
+
5
+ const OPEN = "<PROJECT_RULES>";
6
+ const CLOSE = "</PROJECT_RULES>";
7
+
8
+ /** @returns {string} */
9
+ export function projectRules() {
10
+ let decomp;
11
+ try { decomp = getRules().prompts.decomp; } catch { return ""; }
12
+ const a = decomp.indexOf(OPEN);
13
+ const b = decomp.indexOf(CLOSE);
14
+ return a >= 0 && b > a ? decomp.slice(a, b + CLOSE.length) : "";
15
+ }
16
+ // MODULE_COMPLETE
@@ -0,0 +1,32 @@
1
+ // src/rules/rules-registry.js — Registre MÉMOIRE des règles métier servies par le serveur (domaines, UX,
2
+ // prompts d'analyse). Jamais écrit sur disque, jamais embarqué dans le paquet : sans chargement, tout
3
+ // accès échoue par une erreur typée honnête (le moteur ne devine rien). TTL : une licence qui change
4
+ // (essai → Pro) est reprise au rechargement suivant.
5
+
6
+ export const RULES_TTL_MS = 3_600_000;
7
+
8
+ export class RulesNotLoadedError extends Error {
9
+ constructor() {
10
+ super("Les règles PipCode ne sont pas chargées (connexion au serveur PipCode requise). Aucune requête n'a été envoyée au modèle.");
11
+ this.name = "RulesNotLoadedError";
12
+ }
13
+ }
14
+
15
+ /** @type {EngineRules | null} */
16
+ let current = null;
17
+ let loadedAt = 0;
18
+
19
+ /** @param {EngineRules} rules @param {number} [now] */
20
+ export function setRules(rules, now = Date.now()) { current = rules; loadedAt = now; }
21
+
22
+ /** @returns {EngineRules} */
23
+ export function getRules() {
24
+ if (!current) throw new RulesNotLoadedError();
25
+ return current;
26
+ }
27
+
28
+ /** @param {number} [now] @returns {boolean} */
29
+ export const rulesAreFresh = (now = Date.now()) => current !== null && now - loadedAt < RULES_TTL_MS;
30
+
31
+ export function resetRules() { current = null; loadedAt = 0; }
32
+ // MODULE_COMPLETE
@@ -0,0 +1,22 @@
1
+ // src/rules/rules-validate-pricing.js — Grille tarifaire servie par le serveur (USD / million de tokens) : forme stricte,
2
+ // nombres finis ≥ 0, taille bornée. Absente → null (le coût reste alors inconnu, jamais supposé). Erreur typée sinon.
3
+
4
+ const isObj = (/** @type {unknown} */ v) => !!v && typeof v === "object" && !Array.isArray(v);
5
+ const price = (/** @type {unknown} */ v) => typeof v === "number" && Number.isFinite(v) && v >= 0 && v < 100_000;
6
+
7
+ /** @param {unknown} raw @returns {PricingGrid | null} */
8
+ export function parsePricing(raw) {
9
+ if (raw === undefined) return null;
10
+ const g = /** @type {{ asOf?: unknown, currency?: unknown, models?: unknown }} */ (raw);
11
+ const bad = (/** @type {string} */ m) => new Error(`Règles PipCode invalides (grille tarifaire : ${m}).`);
12
+ if (!isObj(raw) || typeof g.asOf !== "string" || g.asOf.length > 40 || g.currency !== "USD" || !isObj(g.models)) throw bad("forme");
13
+ const models = /** @type {Record<string, { in?: unknown, out?: unknown, cachedIn?: unknown, cacheWriteIn?: unknown, note?: unknown }>} */ (g.models);
14
+ const ids = Object.keys(models);
15
+ if (ids.length > 2000) throw bad("taille");
16
+ for (const id of ids) {
17
+ const p = models[id];
18
+ if (id.length > 200 || !isObj(p) || !price(p.in) || !price(p.out) || (p.cachedIn !== undefined && !price(p.cachedIn)) || (p.cacheWriteIn !== undefined && !price(p.cacheWriteIn)) || (p.note !== undefined && (typeof p.note !== "string" || p.note.length > 60))) throw bad(`modèle ${id.slice(0, 40)}`);
19
+ }
20
+ return /** @type {PricingGrid} */ (raw);
21
+ }
22
+ // MODULE_COMPLETE