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,49 @@
1
+ // src/auto/pricing.js — Table publique de prix modèles (USD / 1M tokens, in/out).
2
+ // Surchargeable via env PLUGCTO_PRICING_JSON (JSON string). Devis = estimation honnête,
3
+ // pas une garantie (cf. computeImpact index.js).
4
+
5
+ // Table de prix par provider/modèle. Fallback conservatif si modèle inconnu.
6
+ /** @returns {PricingTable} */
7
+ export function loadPricing() {
8
+ /** @type {PricingTable} */
9
+ const def = {
10
+ openai: {
11
+ "gpt-5": { in: 1.25, out: 10 }, "gpt-5-mini": { in: 0.25, out: 2 },
12
+ "gpt-4.1": { in: 2, out: 8 }, "gpt-4.1-mini": { in: 0.4, out: 1.6 },
13
+ "gpt-4.1-nano": { in: 0.1, out: 0.4 }, "gpt-4o": { in: 2.5, out: 10 },
14
+ "gpt-4o-mini": { in: 0.15, out: 0.6 },
15
+ },
16
+ anthropic: {
17
+ "claude-3-opus-latest": { in: 15, out: 75 }, "claude-3-5-sonnet-latest": { in: 3, out: 15 },
18
+ "claude-sonnet-5": { in: 3, out: 15 }, "claude-opus-5": { in: 15, out: 75 },
19
+ "claude-haiku-4-5": { in: 1, out: 5 },
20
+ },
21
+ gemini: {
22
+ "gemini-1.5-pro": { in: 1.25, out: 5 }, "gemini-1.5-flash": { in: 0.075, out: 0.3 },
23
+ "gemini-2.5-flash": { in: 0.15, out: 0.6 }, "gemini-2.5-pro": { in: 1.25, out: 5 },
24
+ "gemini-2.0-flash": { in: 0.1, out: 0.4 },
25
+ },
26
+ zhipu: { "glm-4.6": { in: 0.6, out: 2.2 }, "glm-4-flash": { in: 0.1, out: 0.3 } },
27
+ };
28
+ // Famille par défaut (fallback si modèle inconnu) — conservative.
29
+ const famDef = { openai: { in: 1, out: 5 }, anthropic: { in: 3, out: 15 }, gemini: { in: 0.5, out: 2 }, zhipu: { in: 0.6, out: 2.2 } };
30
+ if (process.env.PLUGCTO_PRICING_JSON) {
31
+ try { return /** @type {PricingTable} */ (JSON.parse(process.env.PLUGCTO_PRICING_JSON)); } catch { /* ignore, garde défaut */ }
32
+ }
33
+ def.__default = famDef;
34
+ return def;
35
+ }
36
+
37
+ /** @type {PricingTable} */
38
+ export const PROVIDER_PRICING = loadPricing();
39
+
40
+ // Prix d'un modèle (ou fallback famille/défaut absolu { in: 1, out: 5 }).
41
+ /** @param {string} provider @param {string} model @returns {PricePair} */
42
+ export function lookupPrice(provider, model) {
43
+ const table = PROVIDER_PRICING[provider];
44
+ if (table && table[model]) return table[model];
45
+ const famDef = PROVIDER_PRICING.__default;
46
+ if (famDef && famDef[provider]) return famDef[provider];
47
+ return { in: 1, out: 5 }; // garde-fou absolu.
48
+ }
49
+ // MODULE_COMPLETE
@@ -0,0 +1,12 @@
1
+ // src/auto/project-files.js — Fichiers source du projet pour le LLM : git ls-files si dépôt, sinon lecture du disque
2
+ // (projet brut non versionné : jamais bloquant, jamais liste vide à tort).
3
+
4
+ import { gitListSourceFiles } from "./git.js";
5
+ import { fsListSourceFiles } from "./fs-list.js";
6
+
7
+ /** @param {string} projectPath @returns {Promise<string[]>} */
8
+ export async function listProjectFiles(projectPath) {
9
+ const tracked = await gitListSourceFiles(projectPath);
10
+ return tracked.length > 0 ? tracked : fsListSourceFiles(projectPath);
11
+ }
12
+ // MODULE_COMPLETE
@@ -0,0 +1,24 @@
1
+ // src/auto/prompts.js — Prompt + schéma de décomposition des micro-tâches.
2
+ // Byte-stable (aucune interpolation runtime) : le contexte variable va dans le bloc user (callLLM),
3
+ // jamais dans le system prompt (doctrine de prompt caching).
4
+
5
+ // Prompt de décomposition (CTO architecte senior → tableau JSON de micro-tâches) : TEXTE servi par le serveur
6
+ // (lot Pro) ; ici seulement l'accès au registre mémoire (jamais de prompt inventé sans règles chargées).
7
+ import { getRules } from "../rules/rules-registry.js";
8
+ /** @returns {string} */
9
+ export const taskDecompPrompt = () => getRules().prompts.decomp;
10
+
11
+ // Schéma JSON de validation des micro-tâches (champs requis + enum de status).
12
+ export const TASK_SCHEMA = {
13
+ type: "object",
14
+ required: ["id", "title", "file", "objective", "acceptance"],
15
+ properties: {
16
+ id: { type: "string" },
17
+ title: { type: "string" },
18
+ file: { type: "string" },
19
+ objective: { type: "string" },
20
+ acceptance: { type: "string" },
21
+ status: { type: "string", enum: ["pending", "done", "failed", "skipped"] },
22
+ },
23
+ };
24
+ // MODULE_COMPLETE
@@ -0,0 +1,33 @@
1
+ // src/auto/quote-format.js — Rendu du devis Auto Mode : durée estimée
2
+ // (débit LLM prudent, headroom re-attaque ×1.3 déjà inclus dans les tokens)
3
+ // + formatQuote (tâches, budget, coût, avertissement honnête).
4
+
5
+ // Durée d'exécution estimée : les tokens du devis (headroom inclus) divisés
6
+ // par un débit de sortie prudent 30-100 tok/s, + latence fixe par appel LLM.
7
+ /** @param {AutoTask[]} tasks @param {BudgetQuote} budget @returns {{ low: number, high: number }} */
8
+ export function estimateRunMinutes(tasks, budget) {
9
+ const calls = Math.max(1, tasks.length);
10
+ const tokens = budget.tokensEst || 0;
11
+ const low = Math.max(1, Math.ceil(tokens / 6000 + calls * 0.25));
12
+ const high = Math.max(low + 1, Math.ceil(tokens / 1800 + calls * 0.75));
13
+ return { low, high };
14
+ }
15
+
16
+ // Rendu lisible du devis (durée, budget, coût, avertissement estimation).
17
+ /** @param {AutoTask[]} tasks @param {BudgetQuote} budget @returns {string} */
18
+ export function formatQuote(tasks, budget) {
19
+ const d = estimateRunMinutes(tasks, budget);
20
+ const lines = [];
21
+ lines.push(`📋 Plan Auto Mode — ${tasks.length} micro-tâches`);
22
+ lines.push(` Durée estimée : ~${d.low} à ${d.high} min (estimation, latence variable selon fournisseur)`);
23
+ lines.push(` Budget estimé : ~${(budget.tokensEst || 0).toLocaleString()} tokens`);
24
+ lines.push(` Coût API estimé : ~$${(budget.costUsdEst || 0).toFixed(4)} (provider BYOK, prix output)`);
25
+ lines.push(` Tâches :`);
26
+ for (const t of tasks.slice(0, 10)) lines.push(` • [${t.id}] ${t.file} — ${t.title}`);
27
+ if (tasks.length > 10) lines.push(` • … (+${tasks.length - 10} autres)`);
28
+ lines.push("");
29
+ lines.push("⚠️ Estimation honnête, pas une garantie. Lancement bloqué jusqu'au consentement.");
30
+ lines.push(" Pour lancer : npx pipcode auto run (Y/n).");
31
+ return lines.join("\n");
32
+ }
33
+ // MODULE_COMPLETE
@@ -0,0 +1,40 @@
1
+ // src/auto/retry.js — U3 : retry/backoff exponentiel + jitter sur erreurs transient.
2
+ // isTransientLLMError : 429/5xx/408/409/425 + réseau sans status. On NE retente pas
3
+ // sur 400/401/403/404/422 (erreurs définitives). withTransientRetry : borné par
4
+ // LLM_RETRY.maxRetries. Orthogonal au QA-retry (MAX_QA_ATTEMPTS).
5
+
6
+ import { LLM_RETRY, LLM_RETRYABLE_STATUS } from "./constants.js";
7
+
8
+ // Erreur provider retryable ? (status HTTP retryable, ou réseau sans status).
9
+ /** @param {unknown} err @returns {boolean} */
10
+ export function isTransientLLMError(err) {
11
+ if (!err) return false;
12
+ const e = /** @type {Error & { status?: number, rateLimitExhausted?: boolean }} */ (err);
13
+ if (e.rateLimitExhausted) return false; // série de 429 déjà épuisée dans callLLM : on ne la recommence pas
14
+ const status = e.status;
15
+ if (Number.isFinite(status)) return LLM_RETRYABLE_STATUS.has(/** @type {number} */ (status));
16
+ // Pas de status HTTP → échec réseau/timeout (AbortError, fetch failed, ECONNRESET).
17
+ return true;
18
+ }
19
+
20
+ // Exécute fn avec retry/backoff exponentiel + jitter (±25%) sur erreurs transient.
21
+ // Renvoie le résultat de fn ou re-throw la dernière erreur. Borné par maxRetries.
22
+ /** @template T @param {() => Promise<T>} fn @param {string} [_label] @returns {Promise<T>} */
23
+ export async function withTransientRetry(fn, _label = "llm") {
24
+ const maxRetries = LLM_RETRY.maxRetries;
25
+ let lastErr;
26
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
27
+ try {
28
+ return await fn();
29
+ } catch (err) {
30
+ lastErr = err;
31
+ if (attempt === maxRetries || !isTransientLLMError(err)) throw err;
32
+ // Backoff exponentiel + jitter (±25%) : 0.8s, 1.6s, 3.2s… anti thundering herd.
33
+ const base = LLM_RETRY.baseMs * Math.pow(2, attempt);
34
+ const jitter = base * (0.75 + Math.random() * 0.5);
35
+ await new Promise((r) => setTimeout(r, jitter));
36
+ }
37
+ }
38
+ throw lastErr;
39
+ }
40
+ // MODULE_COMPLETE
@@ -0,0 +1,40 @@
1
+ // src/auto/run-lock.js — Verrou d'exécution mono-instance (audit hebdo, INFO).
2
+ // <plugDir>/run.lock contient le PID du détenteur ; un verrou dont le PID n'est
3
+ // plus vivant (crash/reboot) est déclaré périmé et écrasé sans bloquer la suite.
4
+
5
+ import fs from "node:fs/promises";
6
+ import path from "node:path";
7
+ import { plugDirOf } from "./paths.js";
8
+
9
+ const LOCK_FILE = "run.lock";
10
+
11
+ // PID vivant ? process.kill(pid, 0) : ESRCH = mort, EPERM = vivant (autre session).
12
+ // Résidu assumé (mono-utilisateur) : un PID mort réutilisé par l'OS donnerait un
13
+ // faux « déjà en cours » temporaire — la vérification lève la barre du trivial.
14
+ /** @param {number} pid @returns {boolean} */
15
+ function pidAlive(pid) {
16
+ try { process.kill(pid, 0); return true; }
17
+ catch (err) {
18
+ const e = /** @type {Error & { code?: string }} */ (err);
19
+ return e.code === "EPERM"; // EPERM = vivant (autre session) ; ESRCH = mort
20
+ }
21
+ }
22
+
23
+ // Acquiert le verrou du projet ; retourne release() (best-effort). Jette si une
24
+ // instance vivante le détient déjà. Verrou périmé (PID mort/illisible) → écrasé.
25
+ /** @param {string} projectPath @returns {Promise<() => Promise<void>>} */
26
+ export async function acquireRunLock(projectPath) {
27
+ const plugDir = plugDirOf(projectPath);
28
+ const lockFile = path.join(plugDir, LOCK_FILE);
29
+ let prev = null;
30
+ try { prev = Number((await fs.readFile(lockFile, "utf8")).trim()); } catch { /* absent → prise directe */ }
31
+ if (typeof prev === "number" && Number.isInteger(prev) && prev > 0 && pidAlive(prev)) {
32
+ throw new Error(`Une exécution pipcode auto est déjà en cours sur ce projet (PID ${prev}).`);
33
+ }
34
+ await fs.mkdir(plugDir, { recursive: true });
35
+ await fs.writeFile(lockFile, String(process.pid), "utf8");
36
+ return async () => {
37
+ try { await fs.rm(lockFile, { force: true }); } catch { /* best-effort : verrou périmé au prochain run */ }
38
+ };
39
+ }
40
+ // MODULE_COMPLETE
@@ -0,0 +1,38 @@
1
+ // src/auto/run-loop.js — Boucle d'exécution du Mode Auto + garde-fou : cap de
2
+ // MAX_CONSECUTIVE_FAILURES échecs consécutifs (persistés dans l'état) → arrêt
3
+ // immédiat, état préservé, rapport d'arrêt avec les 2 options de restauration.
4
+ // Le compteur est remis à zéro par toute tâche validée (step-green).
5
+
6
+ import { runAutoStep } from "./step.js";
7
+ import { loadAutoState, saveAutoState, progressOf } from "./state.js";
8
+ import { pauseRun } from "./cancel.js";
9
+ import { isRunAborted } from "../core/abort-signal.js";
10
+ import { MAX_CONSECUTIVE_FAILURES } from "./constants.js";
11
+ import { stopReport } from "./stop-options.js";
12
+ import { ensureGitRepo } from "./git-init.js";
13
+
14
+ // Boucle : exécute runAutoStep (confirmed:true) jusqu'à done/failed/isError ou maxSteps.
15
+ /** @param {{ projectPath: string, pcfg: { primary: ProviderPrimary }, cfg: PlugConfig, maxSteps: number }} arg @param {AutoDeps} deps @returns {Promise<AutoResult>} */
16
+ export async function runAutoLoop({ projectPath, pcfg, cfg, maxSteps }, deps) {
17
+ const state = await loadAutoState(projectPath);
18
+ if (state && (state.failureStreak || 0) >= MAX_CONSECUTIVE_FAILURES) {
19
+ // Garde-fou anti-boucle infinie : refus d'exécution, l'utilisateur arbitre.
20
+ const text = await stopReport({ projectPath, state },
21
+ `Garde-fou : ${MAX_CONSECUTIVE_FAILURES} échecs consécutifs — le Mode Auto refuse de relancer une boucle identique.`);
22
+ return { isError: true, text, progress: progressOf(state), status: state.status, last: { failed: true } };
23
+ }
24
+ if (state && state.status === "awaiting_user") return { asking: true, question: state.question, status: state.status, progress: progressOf(state) };
25
+ try { await ensureGitRepo(projectPath); } catch (e) { return { isError: true, text: /** @type {Error} */ (e).message }; }
26
+ /** @type {AutoResult[]} */ const steps = [];
27
+ let n = 0; let last;
28
+ if (state && state.status === "paused") { state.status = "running"; await saveAutoState(projectPath, state); } // reprise après un Stop
29
+ while (n < maxSteps) {
30
+ if (isRunAborted()) return pauseRun(projectPath); // Stop entre deux tâches
31
+ last = await runAutoStep({ projectPath, confirmed: true, pcfg, cfg }, deps);
32
+ steps.push(last);
33
+ if (last.done || last.failed || last.isError || last.asking || last.cancelled) break; // une question posée est une PAUSE, pas un échec
34
+ n++;
35
+ }
36
+ return { progress: last && last.progress, steps, status: last && last.status, summary: last && last.summary, last };
37
+ }
38
+ // MODULE_COMPLETE
@@ -0,0 +1,49 @@
1
+ // src/auto/run.js — Pilier 2 : runAutoRun + reprise sur crash resumeAuto.
2
+ // Boucle d'exécution extraite dans run-loop.js (garde-fou cap d'échecs
3
+ // consécutifs). Verrou mono-instance autour de l'exécution.
4
+
5
+ import { loadAutoState, saveAutoState, progressOf, formatRunSummary } from "./state.js";
6
+ import { formatQuote } from "./quote-format.js";
7
+ import { runAutoLoop } from "./run-loop.js";
8
+ import { acquireRunLock } from "./run-lock.js";
9
+ import { resumeFromPause } from "./ask-pause.js";
10
+
11
+ // Boucle : exécute runAutoStep (confirmed:true) jusqu'à done/failed/isError ou maxSteps.
12
+ /** @param {AutoRunArgs} arg @param {AutoDeps} deps @returns {Promise<AutoResult>} */
13
+ export async function runAutoRun({ projectPath, confirmed, maxSteps = 50, pcfg, cfg }, deps) {
14
+ const state = await loadAutoState(projectPath);
15
+ if (!state) return { isError: true, text: "Aucun plan Auto trouvé. Établissez d'abord le plan du Mode Auto." };
16
+ if (state.status === "consent_pending" && !confirmed) {
17
+ return { needsConsent: true, quote: formatQuote(state.tasks, state.budget), status: "consent_pending" };
18
+ }
19
+ if (state.status === "done") return { done: true, progress: progressOf(state), summary: formatRunSummary(state), status: "done" };
20
+ /** @type {() => Promise<void>} */
21
+ let release;
22
+ try { release = await acquireRunLock(projectPath); } // mono-instance (audit hebdo)
23
+ catch (err) {
24
+ const e = /** @type {Error & { message?: string }} */ (err);
25
+ return { isError: true, text: String((e && e.message) || err) };
26
+ }
27
+ try {
28
+ return await runAutoLoop({ projectPath, pcfg, cfg, maxSteps }, deps);
29
+ } finally {
30
+ await release(); // best-effort : verrou retiré quoi qu'il arrive
31
+ }
32
+ }
33
+
34
+ // Reprise sur crash : lit state, reprend à cursor (consent_pending → needsConsent, sinon running).
35
+ /** @param {ResumeArgs} arg @param {AutoDeps} deps @returns {Promise<AutoResult>} */
36
+ export async function resumeAuto({ projectPath, maxSteps = 50, answer, pcfg, cfg }, deps) {
37
+ const state = await loadAutoState(projectPath);
38
+ if (!state) return { isError: true, text: "Aucun plan Auto à reprendre (pas de .pipcode/auto-state.json)." };
39
+ if (state.status === "done") return { done: true, progress: progressOf(state), summary: formatRunSummary(state), status: "done" };
40
+ if (state.status === "consent_pending") {
41
+ return { needsConsent: true, quote: formatQuote(state.tasks, state.budget), status: "consent_pending" };
42
+ }
43
+ const paused = await resumeFromPause(projectPath, state, answer); // pause ASK_USER : question rendue, ou réponse enregistrée
44
+ if (paused) return paused;
45
+ state.status = "running";
46
+ await saveAutoState(projectPath, state);
47
+ return runAutoRun({ projectPath, confirmed: true, maxSteps, pcfg, cfg }, deps);
48
+ }
49
+ // MODULE_COMPLETE
@@ -0,0 +1,51 @@
1
+ // src/auto/snapshot-gate.js — checkGreen : détection + exécution des gates du
2
+ // projet client. tsconfig.json (avec typescript local installé) → tsc --noEmit ;
3
+ // scripts.test → npm test. Absents = "skipped" (le checkpoint git tient lieu de
4
+ // preuve verte). Commandes à arguments FIXES uniquement (aucune entrée utilisateur).
5
+ // OBSERVER : quand une gate est rouge, sa sortie (stdout + stderr) est CAPTURÉE : la boucle auto-corrective la relit.
6
+
7
+ import { execFile } from "node:child_process";
8
+ import fs from "node:fs/promises";
9
+ import path from "node:path";
10
+ import { GREEN_TSC_TIMEOUT_MS, GREEN_TEST_TIMEOUT_MS } from "./constants.js";
11
+ import { runSignal } from "../core/abort-signal.js";
12
+
13
+ // Exécute une commande fixe dans le projet : green/red + sortie capturée quand rouge (fail-safe).
14
+ /** @param {string} projectPath @param {string} file @param {string[]} args @param {number} timeoutMs @returns {Promise<{ status: "green" | "red", log: string }>} */
15
+ function runFixed(projectPath, file, args, timeoutMs) {
16
+ return new Promise((resolve) => {
17
+ execFile(file, args, { cwd: projectPath, windowsHide: true, timeout: timeoutMs, maxBuffer: 8 * 1024 * 1024, signal: runSignal, shell: process.platform === "win32" }, (err, stdout, stderr) => {
18
+ resolve(err ? { status: "red", log: `${stdout}\n${stderr}`.trim() || String(err.message) } : { status: "green", log: "" });
19
+ });
20
+ });
21
+ }
22
+
23
+ // Le projet a-t-il une gate tsc exécutable localement (tsconfig + typescript) ?
24
+ /** @param {string} projectPath @returns {Promise<boolean>} */
25
+ async function hasTscGate(projectPath) {
26
+ try {
27
+ await fs.access(path.join(projectPath, "tsconfig.json"));
28
+ await fs.access(path.join(projectPath, "node_modules", "typescript", "bin", "tsc"));
29
+ return true;
30
+ } catch { return false; }
31
+ }
32
+
33
+ // Le package.json déclare-t-il scripts.test ? (parse défensif, jamais de crash)
34
+ /** @param {string} projectPath @returns {Promise<boolean>} */
35
+ async function hasTestScript(projectPath) {
36
+ try {
37
+ const p = JSON.parse(await fs.readFile(path.join(projectPath, "package.json"), "utf8"));
38
+ return !!(p && typeof p === "object" && p.scripts && typeof p.scripts.test === "string" && p.scripts.test.trim());
39
+ } catch { return false; }
40
+ }
41
+
42
+ // État des gates du projet : tsc et tests (green/red/skipped chacun) + sortie des gates rouges (log).
43
+ /** @param {string} projectPath @returns {Promise<GreenReport>} */
44
+ export async function checkGreen(projectPath) {
45
+ if (typeof projectPath !== "string" || !projectPath.trim()) return { tsc: "skipped", tests: "skipped" };
46
+ const tsc = (await hasTscGate(projectPath)) ? await runFixed(projectPath, "npx", ["tsc", "--noEmit"], GREEN_TSC_TIMEOUT_MS) : null;
47
+ const tests = (await hasTestScript(projectPath)) ? await runFixed(projectPath, "npm", ["test"], GREEN_TEST_TIMEOUT_MS) : null;
48
+ const log = [tsc?.log && `tsc --noEmit :\n${tsc.log}`, tests?.log && `npm test :\n${tests.log}`].filter(Boolean).join("\n\n");
49
+ return { tsc: tsc?.status ?? "skipped", tests: tests?.status ?? "skipped", ...(log ? { log } : {}) };
50
+ }
51
+ // MODULE_COMPLETE
@@ -0,0 +1,33 @@
1
+ // src/auto/snapshot-paths.js — Chemins du cache de garde-fou du Mode Auto :
2
+ // .pipcode/cache/snapshots/ (repères state_pre_gen / state_last_green). Exclusion
3
+ // Git idempotente du dossier dans le projet client (jamais commité).
4
+
5
+ import fs from "node:fs/promises";
6
+ import path from "node:path";
7
+
8
+ // Repères de restauration (Option 1 = avant génération, Option 2 = dernier vert).
9
+ export const SNAP_PRE = "state_pre_gen";
10
+ export const SNAP_GREEN = "state_last_green";
11
+ export const SNAP_KINDS = [SNAP_PRE, SNAP_GREEN];
12
+
13
+ // Dossier snapshots à la racine du projet client (<projectPath>/.pipcode/cache/snapshots).
14
+ /** @param {string} projectPath @returns {string} */
15
+ export function snapDirOf(projectPath) { return path.join(projectPath, ".pipcode", "cache", "snapshots"); }
16
+
17
+ // Chemin du marqueur JSON d'un repère (<snapDir>/<kind>.json).
18
+ /** @param {string} projectPath @param {string} kind @returns {string} */
19
+ export function markerPathOf(projectPath, kind) { return path.join(snapDirOf(projectPath), `${kind}.json`); }
20
+
21
+ // Exclut .pipcode/cache/ du Git du projet client. Idempotent, best-effort (jamais de crash).
22
+ /** @param {string} projectPath @returns {Promise<void>} */
23
+ export async function ensureGitignored(projectPath) {
24
+ const line = ".pipcode/cache/";
25
+ const gi = path.join(projectPath, ".gitignore");
26
+ let cur = "";
27
+ try { cur = await fs.readFile(gi, "utf8"); } catch { /* absent → créé ci-dessous */ }
28
+ const norm = cur.replace(/\r\n/g, "\n");
29
+ if (norm.split("\n").some((l) => l.trim() === line)) return;
30
+ const data = norm + (norm.endsWith("\n") || norm === "" ? "" : "\n") + line + "\n";
31
+ await fs.writeFile(gi, data, { encoding: "utf8" });
32
+ }
33
+ // MODULE_COMPLETE
@@ -0,0 +1,59 @@
1
+ // src/auto/snapshot-restore.js — restoreSnapshot : restitue le projet client au
2
+ // repère demandé (Option 1 = state_pre_gen, Option 2 = state_last_green) :
3
+ // git reset --hard <head> du repère, purge des fichiers créés par le run annulé
4
+ // (nouveaux fichiers du patch, absents du HEAD du repère), puis ré-application
5
+ // du patch (état de travail exact au moment du repère ; les untracked présents
6
+ // au repère survivent et font partie de l'état restauré).
7
+
8
+ import fs from "node:fs/promises";
9
+ import path from "node:path";
10
+ import { gitExec } from "./git.js";
11
+ import { touchedFilesFromDiff } from "./diff.js";
12
+ import { markerPathOf, snapDirOf, SNAP_KINDS } from "./snapshot-paths.js";
13
+
14
+ // Lit et valide le marqueur du repère (Zero Trust : head contrôlé).
15
+ /** @param {string} projectPath @param {string} kind @returns {Promise<SnapshotMarker>} */
16
+ async function readMarker(projectPath, kind) {
17
+ let raw;
18
+ try { raw = await fs.readFile(markerPathOf(projectPath, kind), "utf8"); }
19
+ catch { throw new Error(`Aucun repère ${kind} (aucun snapshot pris avant la génération).`); }
20
+ const m = /** @type {SnapshotMarker} */ (JSON.parse(raw));
21
+ if (!m || typeof m !== "object" || typeof m.head !== "string" || !/^[0-9a-f]{7,40}$/.test(m.head)) {
22
+ throw new Error(`Repère ${kind} corrompu (head invalide) — supprimez .pipcode/cache/snapshots/.`);
23
+ }
24
+ return m;
25
+ }
26
+
27
+ // Purge les résidus du run annulé : fichiers touchés par le patch mais absents du
28
+ // HEAD du repère (nouveaux fichiers devenus untracked après reset --hard).
29
+ /** @param {string} projectPath @param {string} head @param {string} patchText @returns {Promise<void>} */
30
+ async function purgeRunLeftovers(projectPath, head, patchText) {
31
+ for (const f of touchedFilesFromDiff(patchText)) {
32
+ try { await gitExec(projectPath, ["cat-file", "-e", `${head}:${f}`]); } catch {
33
+ await fs.rm(path.join(projectPath, f), { force: true }).catch(() => { /* best-effort */ });
34
+ }
35
+ }
36
+ }
37
+
38
+ // Restitue l'état capturé par le repère. Option 1 → pre_gen, Option 2 → last_green.
39
+ /** @param {string} projectPath @param {number} option @returns {Promise<{ restored: string, head: string }>} */
40
+ export async function restoreSnapshot(projectPath, option) {
41
+ if (typeof projectPath !== "string" || !projectPath.trim()) throw new Error("Chemin de projet invalide.");
42
+ const kind = option === 1 ? SNAP_KINDS[0] : option === 2 ? SNAP_KINDS[1] : "";
43
+ if (!kind) throw new Error("Option invalide : 1 (state_pre_gen) ou 2 (state_last_green).");
44
+ const m = await readMarker(projectPath, kind);
45
+ // m.untracked = fichiers présents au repère : ils survivent à reset --hard et
46
+ // font partie de l'état restauré — jamais supprimés. Seuls les résidus du run
47
+ // annulé (nouveaux fichiers du patch, absents du HEAD du repère) sont purgés.
48
+ await gitExec(projectPath, ["reset", "--hard", m.head]);
49
+ if (typeof m.patch === "string" && m.patch.trim()) {
50
+ const patchPath = path.join(snapDirOf(projectPath), m.patch);
51
+ const patchText = await fs.readFile(patchPath, "utf8");
52
+ if (patchText.trim()) { // arbre propre au repère → patch vide → rien à ré-appliquer
53
+ await purgeRunLeftovers(projectPath, m.head, patchText);
54
+ await gitExec(projectPath, ["apply", patchPath]);
55
+ }
56
+ }
57
+ return { restored: kind, head: m.head };
58
+ }
59
+ // MODULE_COMPLETE
@@ -0,0 +1,30 @@
1
+ // src/auto/snapshot-take.js — takeSnapshot : capture légère (diff/patch) de l'état
2
+ // du code sous .pipcode/cache/snapshots/ et écrit le marqueur <kind>.json (head +
3
+ // patch + untracked). Fail-safe : toute erreur → null, le run ne plante jamais.
4
+
5
+ import fs from "node:fs/promises";
6
+ import path from "node:path";
7
+ import crypto from "node:crypto";
8
+ import { gitExec } from "./git.js";
9
+ import { snapDirOf, markerPathOf, ensureGitignored } from "./snapshot-paths.js";
10
+
11
+ // Prend un snapshot (diff HEAD + untracked + HEAD sha) et marque le repère.
12
+ /** @param {string} projectPath @param {string} kind @returns {Promise<SnapshotMarker | null>} */
13
+ export async function takeSnapshot(projectPath, kind) {
14
+ try {
15
+ const dir = snapDirOf(projectPath);
16
+ await fs.mkdir(dir, { recursive: true });
17
+ await ensureGitignored(projectPath);
18
+ const head = await gitExec(projectPath, ["rev-parse", "HEAD"]);
19
+ const diff = await gitExec(projectPath, ["diff", "HEAD"]);
20
+ const rawUntracked = await gitExec(projectPath, ["ls-files", "--others", "--exclude-standard"]);
21
+ const untracked = rawUntracked.split("\n").map((s) => s.trim()).filter(Boolean).filter((f) => !f.startsWith(".pipcode/"));
22
+ const patch = `snap-${kind}-${crypto.randomBytes(6).toString("hex")}.patch`;
23
+ await fs.writeFile(path.join(dir, patch), diff + (diff.endsWith("\n") || diff === "" ? "" : "\n"), { encoding: "utf8", mode: 0o600 });
24
+ /** @type {SnapshotMarker} */
25
+ const marker = { kind, head, patch, untracked, takenAt: new Date().toISOString() };
26
+ await fs.writeFile(markerPathOf(projectPath, kind), JSON.stringify(marker, null, 2) + "\n", { encoding: "utf8", mode: 0o600 });
27
+ return marker;
28
+ } catch { return null; } // fail-safe : un snapshot impossible n'arrête jamais le run
29
+ }
30
+ // MODULE_COMPLETE
@@ -0,0 +1,52 @@
1
+ // src/auto/state.js — État (checkpoint) Auto : lecture/écriture atomique 0600.
2
+ // loadAutoState : {} si absent/corrompu. saveAutoState : temp à entropie + rename.
3
+ // progressOf : rendu "done/total". Pur (hors I/O fs).
4
+
5
+ import fs from "node:fs/promises";
6
+ import path from "node:path";
7
+ import crypto from "node:crypto";
8
+ import { statePathOf } from "./paths.js";
9
+
10
+ // Lit le state Auto ; null si absent, non-objet ou corrompu (parse échoue).
11
+ /** @param {string} projectPath @returns {Promise<AutoState | null>} */
12
+ export async function loadAutoState(projectPath) {
13
+ try {
14
+ const raw = await fs.readFile(statePathOf(projectPath), "utf8");
15
+ const s = /** @type {unknown} */ (JSON.parse(raw));
16
+ if (s && typeof s === "object") return /** @type {AutoState} */ (s);
17
+ return null;
18
+ } catch { return null; }
19
+ }
20
+
21
+ // Écrit le state atomiquement (temp + rename, permissions 0600).
22
+ /** @param {string} projectPath @param {AutoState} state @returns {Promise<void>} */
23
+ export async function saveAutoState(projectPath, state) {
24
+ const p = statePathOf(projectPath);
25
+ await fs.mkdir(path.dirname(p), { recursive: true });
26
+ const data = JSON.stringify(state, null, 2) + "\n";
27
+ const tmp = `${p}.${crypto.randomBytes(6).toString("hex")}.tmp`;
28
+ await fs.writeFile(tmp, data, { encoding: "utf8", mode: 0o600 });
29
+ try { await fs.rename(tmp, p); }
30
+ catch { await fs.writeFile(p, data, { encoding: "utf8", mode: 0o600 }); await fs.rm(tmp, { force: true }); }
31
+ }
32
+
33
+ // Rendu de progression "done/total" depuis le state.
34
+ /** @param {AutoState} state @returns {string} */
35
+ export function progressOf(state) {
36
+ const total = state.tasks.length;
37
+ const done = state.tasks.filter((t) => t.status === "done").length;
38
+ return `${done}/${total}`;
39
+ }
40
+
41
+ // Bilan final du run Auto (exigence produit) : durée devis → fin, tokens
42
+ // consommés (estimation honnête par tâche, budget.js) et coût API dérivé.
43
+ // Pur — consomme uniquement l'état persisté (reprise sur crash incluse).
44
+ /** @param {AutoState} state @returns {string} */
45
+ export function formatRunSummary(state) {
46
+ const b = state.budget || { tokensUsed: 0, costUsdUsed: 0 };
47
+ const ms = Date.parse(state.updatedAt || "") - Date.parse(state.startedAt || "");
48
+ const dur = Number.isFinite(ms) && ms >= 0 ? `${Math.round(ms / 60000)} min` : "—";
49
+ const tokens = (b.tokensUsed || 0).toLocaleString();
50
+ return `⏱️ Durée : ${dur} • 🔋 Tokens consommés (estimation honnête) : ~${tokens} • 💰 Coût API : ~$${(b.costUsdUsed || 0).toFixed(4)}`;
51
+ }
52
+ // MODULE_COMPLETE
@@ -0,0 +1,46 @@
1
+ // src/auto/step-commit.js — Application sûre + checkpoint de runAutoStep (Pilier 2/3).
2
+ // applyTaskDiff : applyDiffSafe + rollback état si échec. checkpointTask : done, budget,
3
+ // amnésie lastDiff, journalise, trio documentaire client en fin de mission, retourne AutoResult.
4
+
5
+ import { applyDiffBacked } from "./apply-diff-backed.js";
6
+ import { saveAutoState, progressOf, formatRunSummary } from "./state.js";
7
+ import { estimateTaskTokens } from "./budget.js";
8
+ import { lookupPrice } from "./pricing.js";
9
+ import { redactError } from "./amnesia.js";
10
+ import { generateClientDocs } from "./mission-docs.js";
11
+
12
+ // Applique le diff (git apply + commit, SAFE_SURGERY). Retourne { applied }, { exit } (failed) ou — en mode « soft » de la boucle auto-corrective — { error } sans toucher à l'état de la tâche.
13
+ /** @param {StepCtx} ctx @param {OrchestratorResult} result @param {number} attempts @param {boolean} [soft] @returns {Promise<{ applied?: { commit: string, touched: string[] }, exit?: AutoResult, error?: string }>} */
14
+ export async function applyTaskDiff(ctx, result, attempts, soft = false) {
15
+ const { deps, projectPath, task, state, idx } = ctx;
16
+ try {
17
+ const applied = await applyDiffBacked({ projectPath, file: task.file, diff: result.patch, deps });
18
+ return { applied };
19
+ } catch (e) {
20
+ const ee = /** @type {Error} */ (e);
21
+ if (soft) return { error: redactError(ee.message) }; // le tour suivant corrige : la tâche n'est PAS encore en échec
22
+ task.status = "failed"; task.attempts = attempts + 1; task.error = redactError(ee.message);
23
+ state.cursor = idx + 1; state.updatedAt = new Date().toISOString();
24
+ await saveAutoState(projectPath, state);
25
+ return { exit: { failed: true, task, error: ee.message, progress: progressOf(state), status: state.status } };
26
+ }
27
+ }
28
+
29
+ // Checkpoint : marque done, budget utilisé, amnésie lastDiff, journalise, retourne AutoResult.
30
+ /** @param {StepCtx} ctx @param {OrchestratorResult} result @param {number} attempts @param {{ commit: string, touched: string[] }} applied @returns {Promise<AutoResult>} */
31
+ export async function checkpointTask(ctx, result, attempts, applied) {
32
+ const { deps, projectPath, task, state, idx, pcfg, missionPath, fileContent } = ctx;
33
+ task.status = "done"; task.attempts = attempts + 1; task.commit = applied.commit;
34
+ state.lastDiff = result.patch; // Pilier 3 : on ne stocke QUE lastDiff (pas analysis/code).
35
+ state.cursor = idx + 1;
36
+ state.budget.tokensUsed = (state.budget.tokensUsed || 0) + estimateTaskTokens(deps, fileContent.length);
37
+ state.budget.costUsdUsed = Math.round((state.budget.tokensUsed / 1_000_000) * lookupPrice(pcfg.primary.provider, pcfg.primary.model).out * 10000) / 10000;
38
+ if (state.cursor >= state.tasks.length) state.status = "done";
39
+ state.updatedAt = new Date().toISOString();
40
+ await saveAutoState(projectPath, state);
41
+ try { await deps.logIteration(missionPath, `auto ${task.id}: ${task.title} (commit ${applied.commit.slice(0, 7)})`); } catch { /* best-effort */ }
42
+ // Fin de mission : mise à jour du trio documentaire client (best-effort, n'échoue jamais le run).
43
+ if (state.status === "done") await generateClientDocs({ projectPath, state, mission: ctx.mission });
44
+ return { progress: progressOf(state), task, diff: result.patch, commit: applied.commit, status: state.status, budget: state.budget, summary: formatRunSummary(state) };
45
+ }
46
+ // MODULE_COMPLETE
@@ -0,0 +1,38 @@
1
+ // src/auto/step-green.js — markGreen : après apply, exécute les gates du projet
2
+ // client (tsc + tests). Vert → snapshot state_last_green + remise à zéro du
3
+ // compteur d'échecs. Rouge → annulation du commit auto (reset HEAD~1 + restore)
4
+ // et échec de tâche propre avec rapport d'arrêt (2 options de restauration).
5
+
6
+ import { checkGreen } from "./snapshot-gate.js";
7
+ import { takeSnapshot } from "./snapshot-take.js";
8
+ import { SNAP_GREEN } from "./snapshot-paths.js";
9
+ import { gitExec } from "./git.js";
10
+ import { gitRestore } from "./git-restore.js";
11
+ import { failTask, stopReport } from "./stop-options.js";
12
+
13
+ // Annule le dernier commit auto (reset HEAD~1) puis restitue les fichiers (best-effort).
14
+ /** @param {string} projectPath @param {string[]} touched @returns {Promise<void>} */
15
+ async function undoAutoCommit(projectPath, touched) {
16
+ try { await gitExec(projectPath, ["reset", "--hard", "HEAD~1"]); } catch { /* premier commit ou HEAD immobile : options de restauration dispo */ }
17
+ try { await gitRestore(projectPath, touched); } catch { /* best-effort */ }
18
+ }
19
+
20
+ // Gate verte post-apply. Retourne { green }, { exit } (échec + rapport d'arrêt) ou — mode « soft » de la boucle auto-corrective — { error } (sortie réelle des gates, commit auto annulé, tâche NON marquée en échec).
21
+ /** @param {StepCtx} ctx @param {{ commit: string, touched: string[] }} applied @param {number} attempts @param {boolean} [soft] @returns {Promise<{ green: boolean } | { exit: AutoResult } | { error: string }>} */
22
+ export async function markGreen(ctx, applied, attempts, soft = false) {
23
+ const { projectPath, state } = ctx;
24
+ const report = await checkGreen(projectPath);
25
+ if (report.tsc === "red" || report.tests === "red") {
26
+ await undoAutoCommit(projectPath, applied.touched);
27
+ if (soft) return { error: report.log || `tsc ${report.tsc} / tests ${report.tests}` };
28
+ const cause = `Génération invalidée par les gates du projet (tsc : ${report.tsc}, tests : ${report.tests}) — commit auto annulé.`;
29
+ const exit = await failTask(ctx, `tsc ${report.tsc} / tests ${report.tests}`, attempts);
30
+ exit.stop = await stopReport(ctx, cause);
31
+ return { exit };
32
+ }
33
+ // Vert (ou gates absentes : le checkpoint git tient lieu de preuve) → repère vert.
34
+ await takeSnapshot(projectPath, SNAP_GREEN); // best-effort fail-safe (null si impossible)
35
+ state.failureStreak = 0;
36
+ return { green: true };
37
+ }
38
+ // MODULE_COMPLETE