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,30 @@
1
+ // src/auto/backup-touched.js — Avant qu'un diff modifie des fichiers, copie leur état ACTUEL sous PLUGCTO_BACKUP_DIR (dossier absolu
2
+ // fourni par l'extension : « sauvegarde du <date> » dans le dossier des sauvegardes PipCode). Sans cette variable : aucune sauvegarde. Chemins du diff = non fiables :
3
+ // tout chemin qui sortirait du projet est refusé. Une sauvegarde déjà prise dans ce dossier n'est jamais écrasée (l'original du run).
4
+ import fs from "node:fs/promises";
5
+ import path from "node:path";
6
+ import { touchedFilesFromDiff } from "./diff.js";
7
+
8
+ /** @param {string} root @param {string} rel @returns {string | null} chemin absolu dans root, sinon null */
9
+ function inside(root, rel) {
10
+ const abs = path.resolve(root, rel);
11
+ const back = path.relative(root, abs);
12
+ return back && !back.startsWith("..") && !path.isAbsolute(back) ? abs : null;
13
+ }
14
+
15
+ /** @param {string} projectPath @param {string} diff @param {string | undefined} [dir] @returns {Promise<string[]>} fichiers sauvegardés */
16
+ export async function backupTouched(projectPath, diff, dir = process.env.PLUGCTO_BACKUP_DIR) {
17
+ if (!dir || !path.isAbsolute(dir)) return [];
18
+ /** @type {string[]} */ const saved = [];
19
+ for (const rel of touchedFilesFromDiff(diff)) {
20
+ const src = inside(projectPath, rel);
21
+ const dst = inside(dir, rel);
22
+ if (!src || !dst) throw new Error(`Chemin refusé (hors du projet) : ${rel}`);
23
+ const exists = await fs.stat(src).then((s) => s.isFile(), () => false); // fichier nouveau : rien à sauvegarder
24
+ if (!exists) continue;
25
+ await fs.mkdir(path.dirname(dst), { recursive: true });
26
+ await fs.copyFile(src, dst, fs.constants.COPYFILE_EXCL).then(() => saved.push(rel), (e) => { if (/** @type {NodeJS.ErrnoException} */ (e).code !== "EEXIST") throw e; });
27
+ }
28
+ return saved;
29
+ }
30
+ // MODULE_COMPLETE
@@ -0,0 +1,52 @@
1
+ // src/auto/budget.js — Pilier 1 : Budget / Devis (pur, 0 appel LLM).
2
+ // estimateTaskTokens : tokens d'une tâche (analysis + patch). estimateRelayCost :
3
+ // coût des relais de contexte (fichier > zone de confort → agents supplémentaires).
4
+ // estimateRunBudget : coût d'exécution (N appels + relais) + headroom ×1.3.
5
+
6
+ import { lookupPrice } from "./pricing.js";
7
+ import { localCapacity, readLocalWindow } from "../engine/context-margin.js";
8
+ import { countRelays, fragmentBudget } from "../engine/context-relay.js";
9
+ import { readSecondaryProvider } from "../core/llm-config-secondary.js";
10
+
11
+ // Tokens du passage de contrôle duo (Phase 3) : le contrôle peut réémettre un
12
+ // diff complet → même budget que le patch, si et seulement si un provider
13
+ // SECONDAIRE est configuré (le moteur ne facture que ce qu'il exécute).
14
+ /** @param {AutoDeps} deps @param {number} fileBytes @param {Record<string, string | undefined>} [env] @returns {number} */
15
+ export function estimateControlTokens(deps, fileBytes, env = process.env) {
16
+ const { secondary } = readSecondaryProvider(env);
17
+ if (!secondary) return 0;
18
+ return deps.patchMaxTokens(Number(fileBytes) || 0);
19
+ }
20
+
21
+ // Tokens d'une tâche (analysis max + patch max + contrôle duo éventuel).
22
+ /** @param {AutoDeps} deps @param {number} fileBytes @returns {number} */
23
+ export function estimateTaskTokens(deps, fileBytes) {
24
+ return deps.ANALYSIS_MAX_TOKENS + deps.patchMaxTokens(Number(fileBytes) || 0) + estimateControlTokens(deps, fileBytes);
25
+ }
26
+
27
+ // Coût des relais d'UNE tâche : ~4 chars/token, chaque agent relais refacture
28
+ // une analyse complète (ANALYSIS_MAX_TOKENS). Fenêtre injectable pour les tests.
29
+ /** @param {AutoDeps} deps @param {number} fileBytes @param {number} [windowTokens] @returns {{ relays: number, tokens: number }} */
30
+ export function estimateRelayCost(deps, fileBytes, windowTokens = readLocalWindow()) {
31
+ const frag = fragmentBudget(localCapacity(windowTokens));
32
+ const relays = countRelays(Math.ceil((Number(fileBytes) || 0) / 4), frag);
33
+ return { relays, tokens: relays * (deps && deps.ANALYSIS_MAX_TOKENS || 0) };
34
+ }
35
+
36
+ // Estime le coût d'exécution (N appels + relais) + headroom re-attaque ×1.3 (MAX_QA_ATTEMPTS=3).
37
+ /** @param {AutoTask[]} tasks @param {MissionPlan} _mission @param {Record<string, number>} fileSizes @param {AutoDeps} deps @param {{ primary: ProviderPrimary }} pcfg @returns {AutoBudgetResult} */
38
+ export function estimateRunBudget(tasks, _mission, fileSizes, deps, pcfg) {
39
+ let runTokens = 0, relayTokens = 0, relaysEst = 0;
40
+ const perTask = tasks.map((t) => {
41
+ const size = (fileSizes && fileSizes[t.file]) || 0;
42
+ const tTokens = estimateTaskTokens(deps, size);
43
+ const relay = estimateRelayCost(deps, size);
44
+ runTokens += tTokens + relay.tokens; relayTokens += relay.tokens; relaysEst += relay.relays;
45
+ return { id: t.id, file: t.file, tokens: tTokens, relayTokens: relay.tokens };
46
+ });
47
+ runTokens = Math.round(runTokens * 1.3); // Headroom re-attaque (moyenne prudente).
48
+ const price = lookupPrice(pcfg && pcfg.primary && pcfg.primary.provider, pcfg && pcfg.primary && pcfg.primary.model);
49
+ const costUsdEst = Math.round((runTokens / 1_000_000) * price.out * 10000) / 10000; // prix output (conservateur).
50
+ return { tokensEst: runTokens, costUsdEst, perTask, relaysEst, relayTokensEst: Math.round(relayTokens * 1.3) };
51
+ }
52
+ // MODULE_COMPLETE
@@ -0,0 +1,20 @@
1
+ // src/auto/cancel.js — Arrêt demandé par l'utilisateur pendant le Mode Auto : le plan est sauvegardé en PAUSE (statut « paused »), la tâche en cours
2
+ // reste « pending » (aucun échec compté), rien n'est perdu ; une reprise (auto resume) repart de cette tâche.
3
+ import { loadAutoState, saveAutoState, progressOf } from "./state.js";
4
+
5
+ export const CANCEL_EXIT_CODE = 4;
6
+
7
+ /** @param {StepCtx} ctx @returns {Promise<AutoResult>} */
8
+ export async function cancelExit({ state, projectPath, task }) {
9
+ state.status = "paused"; state.updatedAt = new Date().toISOString();
10
+ await saveAutoState(projectPath, state);
11
+ return { cancelled: true, status: "paused", task, progress: progressOf(state) };
12
+ }
13
+
14
+ /** Pause du plan sans tâche en cours (arrêt entre deux tâches). @param {string} projectPath @returns {Promise<AutoResult>} */
15
+ export async function pauseRun(projectPath) {
16
+ const state = await loadAutoState(projectPath);
17
+ if (state && state.status === "running") { state.status = "paused"; state.updatedAt = new Date().toISOString(); await saveAutoState(projectPath, state); }
18
+ return { cancelled: true, status: "paused", progress: state ? progressOf(state) : undefined };
19
+ }
20
+ // MODULE_COMPLETE
@@ -0,0 +1,30 @@
1
+ // src/auto/constants.js — Constantes du Mode Auto (timings, caps, état, retry).
2
+ // Aucune dépendance. LLM_RETRY est mutable/exporté pour raccourcir les délais en test.
3
+
4
+ // Status HTTP retryables (429 rate-limit, 5xx serveur, 408/409/425 timeout/conflit).
5
+ export const LLM_RETRYABLE_STATUS = new Set([408, 409, 425, 429, 500, 502, 503, 504]);
6
+
7
+ // Extensions de fichiers source reconnues (borne anti-monospace sur git ls-files).
8
+ export const SRC_EXT =
9
+ /\.(js|mjs|cjs|ts|mts|cts|tsx|jsx|py|go|rs|java|kt|rb|php|cs|c|cpp|h|hpp|vue|svelte|sql|sh|bash|ps1|yaml|yml|json|toml)$/i;
10
+
11
+ // --- Constantes ---------------------------------------------------------------
12
+ export const AUTO_STATE_VERSION = 1;
13
+ export const AUTO_STATE_FILENAME = "auto-state.json";
14
+ export const MAX_QA_ATTEMPTS = 3; // doctrine orchestration_pipeline.md (Dev↔QA loop).
15
+ export const MAX_LS_FILES = 300; // borne anti-monospace sur `git ls-files`.
16
+ export const TASK_DECOMP_MAX_TOKENS = 4096; // large : tableau JSON de micro-tâches.
17
+ export const GIT_TIMEOUT_MS = 30_000; // défensive_engineering : tout I/O borné.
18
+ export const NODE_CHECK_TIMEOUT_MS = 15_000;
19
+ export const MAX_EXEC_TURNS = 3; // boucle auto-corrective : tours AGIR → OBSERVER → ÉVALUER → CORRIGER avant la pause.
20
+ export const MAX_CONSECUTIVE_FAILURES = 3; // garde-fou : cap d'échecs consécutifs Auto.
21
+ export const GREEN_TSC_TIMEOUT_MS = 180_000; // gate tsc --noEmit du projet client.
22
+ export const GREEN_TEST_TIMEOUT_MS = 600_000; // gate npm test du projet client.
23
+
24
+ // U3 — Retry/backoff sur erreurs provider transient. Borné pour ne pas bloquer le
25
+ // Mode Auto indéfiniment. Backoff exponentiel + jitter (anti thundering herd).
26
+ // Config mutable (exporté) pour raccourcir les délais en test sans toucher au
27
+ // timing de production.
28
+ /** @type {RetryConfig} */
29
+ export const LLM_RETRY = { maxRetries: 3, baseMs: 800 }; // 0.8s, 1.6s, 3.2s (±jitter).
30
+ // MODULE_COMPLETE
@@ -0,0 +1,17 @@
1
+ // src/auto/crypto-util.js — Empreintes cryptographiques (sha8, sha256).
2
+ // Utilisées pour le runId, le missionHash et le nonce des fichiers temporaires.
3
+
4
+ import crypto from "node:crypto";
5
+
6
+ // sha256 tronqué à 8 hex (runId, identifiants courts).
7
+ /** @param {unknown} input @returns {string} */
8
+ export function sha8(input) {
9
+ return crypto.createHash("sha256").update(String(input), "utf8").digest("hex").slice(0, 8);
10
+ }
11
+
12
+ // sha256 complet (missionHash, ancrage d'intégrité).
13
+ /** @param {unknown} input @returns {string} */
14
+ export function sha256(input) {
15
+ return crypto.createHash("sha256").update(String(input), "utf8").digest("hex");
16
+ }
17
+ // MODULE_COMPLETE
@@ -0,0 +1,47 @@
1
+ // src/auto/decompose.js — Décomposition LLM d'un cahier des charges en micro-tâches.
2
+ // decomposeAutoTasks : git ls-files + doctrine complète + 1 appel LLM (smart_model)
3
+ // avec retry transient, puis normalizeTasks. Best-effort doctrine (trial sans quota).
4
+
5
+ import { listProjectFiles } from "./project-files.js";
6
+ import { normalizeTasks } from "./tasks.js";
7
+ import { withTransientRetry } from "./retry.js";
8
+ import { TASK_DECOMP_MAX_TOKENS } from "./constants.js";
9
+ import { TRUNCATED } from "../engine/json-balance.js";
10
+ import { taskDecompPrompt } from "./prompts.js";
11
+ import { adjustBlock, feasibilityNotice, dropImpossible } from "./adjust.js";
12
+ import { buildGraph } from "../engine/code-graph.js";
13
+ import { formatRelations } from "../engine/project-context.js";
14
+ import { projectBrief } from "../engine/project-brief.js";
15
+
16
+ // Décompose le besoin en AutoTask[] via LLM. Retourne { tasks } ou { error }.
17
+ /** @param {string} need @param {string} projectPath @param {{ primary: ProviderPrimary }} pcfg @param {PlugConfig} cfg @param {AutoDeps} deps @param {string} missionCtx @param {string} [adjust] @returns {Promise<{ notice?: string, tasks?: AutoTask[], error?: string }>} */
18
+ export async function decomposeAutoTasks(need, projectPath, pcfg, cfg, deps, missionCtx, adjust) {
19
+ const repoFiles = await listProjectFiles(projectPath);
20
+ let doctrine = "";
21
+ try { doctrine = await deps.loadAllDoctrine(cfg); } catch { /* best-effort : trial sans quota Pro */ }
22
+ const doctrineBlock = doctrine ? `\n\nDoctrine (règles absolues à respecter dans la décomposition):\n${doctrine}\n` : "";
23
+ const relations = formatRelations(await buildGraph(projectPath, repoFiles)); // graphe : qui importe quoi (une seule fois)
24
+ const instructions = await projectBrief(projectPath); // CLAUDE.md du client + décisions validées de la mémoire : complétés par les règles PipCode, jamais écrasés
25
+ const userText =
26
+ (instructions ? `${instructions}\n\n` : "") + `${missionCtx}\n\nFichiers du dépôt (${repoFiles.length} fichiers source retenus) :\n${repoFiles.join("\n")}\n\n` +
27
+ (relations ? `Relations entre fichiers (imports, symboles déclarés) :\n${relations}\n\n` : "") +
28
+ `Cahier des charges : ${need}${adjustBlock(adjust)}${doctrineBlock}`;
29
+ const smartModel = (pcfg.primary.smartModel || pcfg.primary.model);
30
+ let raw;
31
+ try {
32
+ raw = await withTransientRetry(() => deps.callLLM({
33
+ provider: pcfg.primary.provider, key: pcfg.primary.key,
34
+ model: smartModel, base: pcfg.primary.base,
35
+ systemPrompt: taskDecompPrompt(), userText, maxTokens: TASK_DECOMP_MAX_TOKENS,
36
+ }), "decomp");
37
+ } catch (e) { const ee = /** @type {Error} */ (e); return { error: `Décomposition impossible (LLM) : ${ee.message}` }; }
38
+ try {
39
+ const parsed = /** @type {Record<string, unknown>[] | { tasks?: Record<string, unknown>[] }} */ (deps.parseJsonLoose(raw, { arrays: true, recover: true })); // tableau racine accepté ; JSON tronqué rattrapé
40
+ const kept = dropImpossible(parsed); // une tâche qui ne sert qu'un ajustement impossible est retirée par le code, même si le modèle l'a écrite
41
+ const tasks = normalizeTasks(/** @type {Record<string, unknown>[] | { tasks?: Record<string, unknown>[] }} */ (kept.parsed), repoFiles);
42
+ const cut = parsed && /** @type {Record<symbol, unknown>} */ (parsed)[TRUNCATED];
43
+ const notice = cut ? `⚠ Réponse du modèle coupée : ${tasks.length} tâche(s) complète(s) récupérée(s), la dernière (incomplète) est abandonnée. Relancez si la liste semble incomplète.\n` : "";
44
+ return { tasks, notice: feasibilityNotice(parsed, adjust) + notice };
45
+ } catch (e) { const ee = /** @type {Error} */ (e); return { error: `Décomposition invalide : ${ee.message}` }; }
46
+ }
47
+ // MODULE_COMPLETE
@@ -0,0 +1,19 @@
1
+ // src/auto/diff.js — Parseur des chemins `b/...` d'un diff Unified (fichiers touchés).
2
+ // Pur. Extrait les fichiers modifiés/créés depuis les en-têtes `+++ b/…`.
3
+
4
+ // Retourne les chemins touchés par un diff (en-têtes +++ b/…). Pur, déterministe.
5
+ /** @param {string} diff @returns {string[]} */
6
+ export function touchedFilesFromDiff(diff) {
7
+ const set = new Set();
8
+ for (const line of String(diff || "").split("\n")) {
9
+ if (line.startsWith("+++ b/")) {
10
+ const f = line.slice(6).trim();
11
+ if (f) set.add(f);
12
+ } else if (line.startsWith("+++ ") && !line.startsWith("+++ /dev/null")) {
13
+ const rest = line.slice(4).trim();
14
+ if (rest && rest !== "/dev/null" && !rest.startsWith("b/")) set.add(rest);
15
+ }
16
+ }
17
+ return [...set];
18
+ }
19
+ // MODULE_COMPLETE
@@ -0,0 +1,34 @@
1
+ // src/auto/fs-list.js — Liste des fichiers source par lecture du disque (projet SANS git). Bornée (profondeur,
2
+ // nombre de fichiers), sans lien symbolique, dossiers lourds et fichiers sensibles (.env, clés) exclus.
3
+
4
+ import fs from "node:fs/promises";
5
+ import path from "node:path";
6
+ import { MAX_LS_FILES, SRC_EXT } from "./constants.js";
7
+
8
+ export const SKIP_DIRS = new Set(["node_modules", ".git", ".pipcode", "dist", "build", "out", "coverage", ".venv", "venv", "__pycache__", ".next", "target"]);
9
+ const SECRET_FILE = /(^|\/)(\.env.*|.*\.(pem|key|p12|pfx)|id_(rsa|ed25519).*|.*(secret|credential|token)s?\.(json|ya?ml|toml))$/i;
10
+ const MAX_DEPTH = 6;
11
+
12
+ /** @param {string} rel @returns {boolean} */
13
+ export const isSecretPath = (rel) => SECRET_FILE.test(rel.replace(/\\/g, "/"));
14
+
15
+ /** @param {string} root @param {string} rel @param {number} depth @param {string[]} out @returns {Promise<void>} */
16
+ async function walk(root, rel, depth, out) {
17
+ let entries;
18
+ try { entries = await fs.readdir(path.join(root, rel), { withFileTypes: true }); }
19
+ catch { return; } // dossier illisible : ignoré (liste partielle, jamais d'échec)
20
+ for (const e of entries.sort((a, b) => a.name.localeCompare(b.name))) {
21
+ if (out.length >= MAX_LS_FILES) return;
22
+ const r = rel ? `${rel}/${e.name}` : e.name;
23
+ if (e.isDirectory() && depth < MAX_DEPTH && !SKIP_DIRS.has(e.name)) await walk(root, r, depth + 1, out);
24
+ else if (e.isFile() && SRC_EXT.test(e.name) && !isSecretPath(r)) out.push(r);
25
+ }
26
+ }
27
+
28
+ /** Fichiers source du dossier (chemins relatifs « / »), au plus MAX_LS_FILES. @param {string} projectPath @returns {Promise<string[]>} */
29
+ export async function fsListSourceFiles(projectPath) {
30
+ /** @type {string[]} */ const out = [];
31
+ await walk(projectPath, "", 0, out);
32
+ return out;
33
+ }
34
+ // MODULE_COMPLETE
@@ -0,0 +1,16 @@
1
+ // src/auto/git-base.js — Point de retour : indexe les fichiers déjà suivis (modifiés ou supprimés) et les sources non sensibles, puis commite.
2
+ // Jamais .env ni clés. Identité du commit en option (config git de l'utilisateur jamais modifiée).
3
+ import { gitExec } from "./git.js";
4
+ import { fsListSourceFiles } from "./fs-list.js";
5
+
6
+ export const IDENT = ["-c", "user.name=PipCode", "-c", "user.email=pipcode@localhost", "-c", "commit.gpgsign=false"];
7
+ const CHUNK = 100;
8
+
9
+ /** @param {string} projectPath @param {string} message @returns {Promise<void>} */
10
+ export async function commitBase(projectPath, message) {
11
+ await gitExec(projectPath, ["add", "-u"]);
12
+ const files = await fsListSourceFiles(projectPath);
13
+ for (let i = 0; i < files.length; i += CHUNK) await gitExec(projectPath, ["add", "--", ...files.slice(i, i + CHUNK)]);
14
+ await gitExec(projectPath, [...IDENT, "commit", "--allow-empty", "-m", message]);
15
+ }
16
+ // MODULE_COMPLETE
@@ -0,0 +1,39 @@
1
+ // src/auto/git-init.js — Projet sans dépôt git : initialisation + commit de base (état initial), pour que le Mode Auto
2
+ // puisse appliquer et annuler ses modifications. Appelée APRÈS le consentement au devis. Seuls les fichiers source non
3
+ // sensibles sont indexés (jamais .env, clés). Avec PLUGCTO_GIT_DIR, l'historique vit HORS du projet (jamais de .git créé chez lui) et
4
+ // chaque exécution y ajoute un point de retour.
5
+
6
+ import fs from "node:fs/promises";
7
+ import { gitExec } from "./git.js";
8
+ import { gitScope } from "./git-scope.js";
9
+ import { commitBase } from "./git-base.js";
10
+
11
+ export const NO_GIT_NOTICE = "ℹ️ Aucun dépôt Git dans ce dossier : au lancement (après votre accord), PipCode en initialisera un avec un commit de base de vos fichiers source (hors .env et clés).\n\n";
12
+ const BASE_MSG = "chore(pipcode): état initial avant Mode Auto";
13
+ const RUN_MSG = "chore(pipcode): état avant exécution";
14
+
15
+ /** Dossier de travail isolé (PLUGCTO_OWN_REPO=1) : il lui faut SON PROPRE dépôt — jamais celui du projet qui le contient. @param {string} projectPath @returns {Promise<boolean>} */
16
+ export async function isGitRepo(projectPath) {
17
+ try {
18
+ if (process.env.PLUGCTO_OWN_REPO !== "1" || gitScope(projectPath).length > 0) return (await gitExec(projectPath, ["rev-parse", "--is-inside-work-tree"])) === "true";
19
+ const [top, here] = await Promise.all([gitExec(projectPath, ["rev-parse", "--show-toplevel"]), fs.realpath(projectPath)]);
20
+ return (await fs.realpath(top)) === here;
21
+ } catch { return false; } // pas de dépôt (ou git absent) : traité par l'appelant
22
+ }
23
+
24
+ /** Initialise git + commit de base si absent ; renvoie true si initialisé. Historique hors du projet : un point de retour par exécution. @param {string} projectPath @returns {Promise<boolean>} */
25
+ export async function ensureGitRepo(projectPath) {
26
+ const apart = gitScope(projectPath).length > 0;
27
+ try {
28
+ if (await isGitRepo(projectPath)) { if (apart) await commitBase(projectPath, RUN_MSG); return false; }
29
+ if (apart) await fs.mkdir(String(process.env.PLUGCTO_GIT_DIR), { recursive: true }); // git ne crée pas les dossiers parents de l'historique
30
+ await gitExec(projectPath, ["init"]);
31
+ if (apart) for (const [k, v] of [["user.name", "PipCode"], ["user.email", "pipcode@localhost"], ["commit.gpgsign", "false"], ["core.autocrlf", "false"]]) await gitExec(projectPath, ["config", k, v]); // réglages propres à ce dépôt (identité, fins de ligne conservées à l'octet près) : la config de l'utilisateur n'est jamais touchée
32
+ await commitBase(projectPath, BASE_MSG);
33
+ return true;
34
+ } catch (e) {
35
+ const err = /** @type {Error & { stderr?: string }} */ (e);
36
+ throw new Error(`Initialisation Git impossible dans ${projectPath} (git est-il installé ?) : ${(err.stderr || err.message || "").slice(0, 200)}`);
37
+ }
38
+ }
39
+ // MODULE_COMPLETE
@@ -0,0 +1,34 @@
1
+ // src/auto/git-restore.js — Restitue les fichiers touchés à l'état HEAD (SAFE_SURGERY).
2
+ // unstage + restore (checkout) pour les fichiers présents dans HEAD ; rm pour les
3
+ // nouveaux fichiers non présents dans HEAD. Rollback propre après apply/commit échoué.
4
+
5
+ import fs from "node:fs/promises";
6
+ import path from "node:path";
7
+ import { gitExec } from "./git.js";
8
+
9
+ // Fichier absent de HEAD : s'il existait avant le travail (html, texte, config… non suivis), son ORIGINAL sauvegardé est remis ; sinon il est nouveau → retiré.
10
+ /** @param {string} projectPath @param {string} f @returns {Promise<void>} */
11
+ async function dropOrRestoreOriginal(projectPath, f) {
12
+ const dir = process.env.PLUGCTO_BACKUP_DIR;
13
+ const saved = dir && path.isAbsolute(dir) ? path.resolve(dir, f) : "";
14
+ const dst = path.resolve(projectPath, f);
15
+ const sane = saved && !path.relative(dir ?? "", saved).startsWith("..") && !path.relative(projectPath, dst).startsWith("..");
16
+ if (sane && await fs.stat(saved).then((s) => s.isFile(), () => false)) await fs.copyFile(saved, dst);
17
+ else await fs.rm(dst, { force: true });
18
+ }
19
+
20
+ // Restitue les fichiers à l'état HEAD (unstage + restore/rm). Best-effort par fichier.
21
+ /** @param {string} projectPath @param {string[]} files @returns {Promise<void>} */
22
+ export async function gitRestore(projectPath, files) {
23
+ for (const f of files) {
24
+ let inHead = true;
25
+ try { await gitExec(projectPath, ["cat-file", "-e", `HEAD:${f}`]); } catch { inHead = false; }
26
+ try { await gitExec(projectPath, ["reset", "-q", "HEAD", "--", f]); } catch { /* best-effort par fichier */ }
27
+ if (inHead) {
28
+ try { await gitExec(projectPath, ["checkout", "--", f]); } catch { /* best-effort par fichier */ }
29
+ } else {
30
+ try { await dropOrRestoreOriginal(projectPath, f); } catch { /* best-effort par fichier */ }
31
+ }
32
+ }
33
+ }
34
+ // MODULE_COMPLETE
@@ -0,0 +1,10 @@
1
+ // src/auto/git-scope.js — Dépôt d'historique HORS du projet : PLUGCTO_GIT_DIR (chemin absolu fourni par l'extension) fait pointer git sur ce dossier
2
+ // avec le projet pour arbre de travail. Le .git du projet n'est jamais créé ni touché ; sans la variable, ou si elle n'est pas absolue : comportement normal.
3
+ import path from "node:path";
4
+
5
+ /** @param {string} projectPath @param {NodeJS.ProcessEnv} [env] @returns {string[]} options git à placer avant la commande ([] = dépôt du projet) */
6
+ export function gitScope(projectPath, env = process.env) {
7
+ const dir = env.PLUGCTO_GIT_DIR;
8
+ return dir && path.isAbsolute(dir) ? [`--git-dir=${dir}`, `--work-tree=${projectPath}`] : [];
9
+ }
10
+ // MODULE_COMPLETE
@@ -0,0 +1,38 @@
1
+ // src/auto/git.js — Primitives git (execFile sans shell — anti-injection shell).
2
+ // gitExec : promisifié, borné. assertGitRepo : garde Auto Mode (apply+commit).
3
+ // gitListSourceFiles : liste des fichiers source du dépôt (cap MAX_LS_FILES).
4
+
5
+ import { execFile } from "node:child_process";
6
+ import { GIT_TIMEOUT_MS, MAX_LS_FILES, SRC_EXT } from "./constants.js";
7
+ import { gitScope } from "./git-scope.js";
8
+
9
+ // execFile git promisifié (stdout trimé). Rejette avec err.stderr rempli.
10
+ /** @param {string} projectPath @param {string[]} args @param {number} [timeoutMs] @returns {Promise<string>} */
11
+ export function gitExec(projectPath, args, timeoutMs = GIT_TIMEOUT_MS) {
12
+ return new Promise((resolve, reject) => {
13
+ execFile("git", [...gitScope(projectPath), ...args], { cwd: projectPath, windowsHide: true, timeout: timeoutMs, maxBuffer: 8 * 1024 * 1024 }, (err, stdout, stderr) => {
14
+ if (err) { err.stderr = (stderr || "").toString().trim(); reject(err); }
15
+ else resolve((stdout || "").toString().trim());
16
+ });
17
+ });
18
+ }
19
+
20
+ // Vérifie que projectPath est un dépôt git (Auto Mode exige apply+commit).
21
+ /** @param {string} projectPath @returns {Promise<void>} */
22
+ export async function assertGitRepo(projectPath) {
23
+ let out;
24
+ try { out = await gitExec(projectPath, ["rev-parse", "--is-inside-work-tree"]); }
25
+ catch { throw new Error(`Le chemin n'est pas un dépôt git : ${projectPath}. L'Auto Mode (apply+commit) exige un dépôt git.`); }
26
+ if (out !== "true") throw new Error(`Le chemin n'est pas un dépôt git : ${projectPath}.`);
27
+ }
28
+
29
+ // Liste les fichiers source du dépôt (cap MAX_LS_FILES, exclut .pipcode/).
30
+ /** @param {string} projectPath @returns {Promise<string[]>} */
31
+ export async function gitListSourceFiles(projectPath) {
32
+ /** @type {string} */ let raw = "";
33
+ try { raw = await gitExec(projectPath, ["ls-files"]); } catch { return []; }
34
+ const all = raw.split("\n").map((s) => s.trim()).filter(Boolean);
35
+ const src = all.filter((f) => SRC_EXT.test(f) && !f.startsWith(".pipcode/"));
36
+ return src.slice(0, MAX_LS_FILES);
37
+ }
38
+ // MODULE_COMPLETE
@@ -0,0 +1,29 @@
1
+ // src/auto/mission-docs.js — Déclenchement du trio documentaire client en fin de
2
+ // mission Auto (README + GUIDE_ARCHITECTURE_CODE + RUNBOOK, racine du projet).
3
+ // Coexistence garantie par construction : chaque générateur n'écrit QUE son fichier.
4
+ import { generateReadme } from "../engine/generate-readme.js";
5
+ import { generateArchitectureGuide } from "../engine/generate-architecture-guide.js";
6
+ import { generateRunbook } from "../engine/generate-runbook.js";
7
+
8
+ // Mode de mission : au moins la moitié des fichiers nouveaux → création, sinon refactoring.
9
+ /** @param {AutoTask[]} tasks @returns {DocMode} */
10
+ export function detectDocMode(tasks) {
11
+ const created = tasks.filter((t) => t.isNew).length;
12
+ return created * 2 >= tasks.length ? "creation" : "refactoring";
13
+ }
14
+
15
+ // Génère/met à jour les 3 documents. Best-effort PAR document : l'échec de l'un
16
+ // n'interrompt pas les autres et n'échoue JAMAIS la mission (stderr uniquement).
17
+ /** @param {{ projectPath: string, state: AutoState, mission: MissionPlan | null }} arg @returns {Promise<void>} */
18
+ export async function generateClientDocs({ projectPath, state, mission }) {
19
+ const mode = detectDocMode(state.tasks);
20
+ const generators = [generateReadme, generateArchitectureGuide, generateRunbook];
21
+ for (const gen of generators) {
22
+ try { await gen(projectPath, state, mission, mode); }
23
+ catch (e) {
24
+ const ee = /** @type {Error} */ (e);
25
+ console.error(`[pipcode auto] Documentation client (${gen.name}) : échec best-effort — ${ee.message}`);
26
+ }
27
+ }
28
+ }
29
+ // MODULE_COMPLETE
@@ -0,0 +1,22 @@
1
+ // src/auto/mission-line.js — Reprise de mission après /clear : si un plan autonome est en cours (auto-state.json actif), UNE phrase courte pour le prompt
2
+ // système (mission, tâche active, consigne). Lecture seule : ne modifie jamais l'état. Texte de mission nettoyé (une ligne, borné) avant injection.
3
+ import fs from "node:fs/promises";
4
+ import { loadAutoState } from "./state.js";
5
+ import { missionPathOf } from "./paths.js";
6
+
7
+ const ACTIVE = new Set(["running", "awaiting_user", "paused"]);
8
+
9
+ /** @param {string} s @returns {string} */
10
+ const oneLine = (s) => s.replace(/[\u0000-\u001f\u007f]+/g, " ").replace(/^[#>*\-\s]+/, "").trim().slice(0, 120);
11
+
12
+ /** @param {string} projectPath @returns {Promise<string>} "" si aucun plan en cours */
13
+ export async function missionLine(projectPath) {
14
+ const state = await loadAutoState(projectPath);
15
+ if (!state || !ACTIVE.has(state.status) || !Array.isArray(state.tasks) || !state.tasks.length) return "";
16
+ const idx = Math.min(Math.max(Number.isInteger(state.cursor) ? state.cursor : 0, 0), state.tasks.length - 1);
17
+ const task = state.tasks[idx];
18
+ const raw = await fs.readFile(missionPathOf(projectPath), "utf8").catch(() => "");
19
+ const title = oneLine(raw.split("\n").find((l) => l.trim()) || "") || "plan autonome";
20
+ return `Mission en cours : ${title}. Tâche active : ${idx + 1}/${state.tasks.length} — ${oneLine(String(task?.title || ""))}. Poursuis l'exécution sur le code présent dans le workspace.`;
21
+ }
22
+ // MODULE_COMPLETE
@@ -0,0 +1,30 @@
1
+ // src/auto/node-check.js — Validation syntaxique JS family via `node --check`.
2
+ // Authentique (exécuté après git apply, avant commit). Rejette la syntaxe invalide.
3
+ // Un .js écrit en ESM (import/export) sans package.json « type: module » passe `node --check` même cassé : on le revérifie en module.
4
+
5
+ import { execFile } from "node:child_process";
6
+ import fs from "node:fs/promises";
7
+ import { NODE_CHECK_TIMEOUT_MS } from "./constants.js";
8
+
9
+ const ESM = /^\s*(?:import|export)\s/m;
10
+
11
+ /** @param {string[]} args @param {string} [input] @returns {Promise<void>} */
12
+ function run(args, input) {
13
+ return new Promise((resolve, reject) => {
14
+ const child = execFile("node", args, { windowsHide: true, timeout: NODE_CHECK_TIMEOUT_MS }, (err, _o, stderr) => {
15
+ if (err) reject(new Error((stderr || err.message || "syntaxe invalide").toString().trim()));
16
+ else resolve();
17
+ });
18
+ if (input !== undefined) child.stdin?.end(input);
19
+ });
20
+ }
21
+
22
+ // Vérifie la syntaxe d'un fichier JS/MJS/CJS via `node --check`. Reject si invalide.
23
+ /** @param {string} file @returns {Promise<void>} */
24
+ export async function nodeCheck(file) {
25
+ await run(["--check", file]);
26
+ if (!/\.js$/i.test(file)) return;
27
+ const source = await fs.readFile(file, "utf8");
28
+ if (ESM.test(source)) await run(["--input-type=module", "--check"], source);
29
+ }
30
+ // MODULE_COMPLETE
@@ -0,0 +1,23 @@
1
+ // src/auto/observe.js — OBSERVER : mise en forme de ce que l'exécution a réellement produit (erreur d'application du patch, sortie de tsc / des tests) pour
2
+ // la boucle auto-corrective. Sortie volumineuse → tête + queue (la cause est à la fin) ; secrets masqués (redactError) ; signature pour reconnaître
3
+ // la MÊME panne d'un tour à l'autre (inutile de recommencer une correction qui ne change rien). Pur.
4
+ import { redactError } from "./amnesia.js";
5
+
6
+ export const OBSERVE_MAX_CHARS = 2000;
7
+
8
+ /** @param {string} text @param {number} [max] @returns {string} */
9
+ export function headTail(text, max = OBSERVE_MAX_CHARS) {
10
+ const s = redactError(String(text ?? "")).trim();
11
+ if (s.length <= max) return s;
12
+ const head = Math.floor(max * 0.35);
13
+ return `${s.slice(0, head)}\n[… ${s.length - max} caractères omis …]\n${s.slice(-(max - head))}`;
14
+ }
15
+
16
+ /** Fin du log nettoyée : deux tours qui échouent pareil ont la même signature. @param {string} text @returns {string} */
17
+ export const failureSignature = (text) => redactError(String(text ?? "")).replace(/\s+/g, " ").trim().slice(-200);
18
+
19
+ /** Bloc réinjecté dans le prompt du tour suivant (sans validation de l'utilisateur). @param {number} turn @param {string} observed @returns {string} */
20
+ export function feedbackBlock(turn, observed) {
21
+ return `Le tour ${turn} a échoué à l'exécution (le patch a été annulé). Sortie réelle ci-dessous : corrige la cause, sans réintroduire le problème.\n${headTail(observed)}`;
22
+ }
23
+ // MODULE_COMPLETE
@@ -0,0 +1,30 @@
1
+ // src/auto/orchestrator.js — Factory createAutoOrchestrator(deps) (DI, anti-import circulaire).
2
+ // Lie les méthodes (plan/step/run/resume) en passant deps explicitement (extraction closure).
3
+ // Expose aussi les helpers purs (amnesia, state, budget) pour les tests directs.
4
+
5
+ import { buildAutoPlan } from "./plan.js";
6
+ import { runAutoStep } from "./step.js";
7
+ import { runAutoRun, resumeAuto } from "./run.js";
8
+ import { applyDiffSafe } from "./apply-diff.js";
9
+ import { estimateRunBudget } from "./budget.js";
10
+ import { buildAmnesiaContext, buildCorrectionSignal } from "./amnesia.js";
11
+ import { loadAutoState, saveAutoState } from "./state.js";
12
+
13
+ // Factory : retourne l'orchestrateur Auto avec deps lié à chaque méthode.
14
+ /** @param {AutoDeps} deps @returns {AutoOrchestrator} */
15
+ export function createAutoOrchestrator(deps) {
16
+ if (!deps || typeof deps !== "object") throw new Error("createAutoOrchestrator : deps requis.");
17
+ return {
18
+ buildAutoPlan: (arg) => buildAutoPlan(arg, deps),
19
+ runAutoStep: (arg) => runAutoStep(arg, deps),
20
+ runAutoRun: (arg) => runAutoRun(arg, deps),
21
+ resumeAuto: (arg) => resumeAuto(arg, deps),
22
+ applyDiffSafe: (arg) => applyDiffSafe(arg),
23
+ estimateRunBudget: (tasks, mission, fileSizes, pcfg) => estimateRunBudget(tasks, mission, fileSizes, deps, pcfg),
24
+ buildAmnesiaContext,
25
+ buildCorrectionSignal,
26
+ loadAutoState,
27
+ saveAutoState,
28
+ };
29
+ }
30
+ // MODULE_COMPLETE
@@ -0,0 +1,16 @@
1
+ // src/auto/patch-ask.js — Phase patch avec question possible : une vraie question du modèle met la tâche en pause ; une question qui vise la doctrine interne
2
+ // (ou des fichiers qui n'existent pas dans le projet) n'arrête JAMAIS le plan — la phase est relancée UNE fois avec l'interdiction de poser une question.
3
+ import { ASK_NOTE, parseAskUser } from "./ask-user.js";
4
+ import { guardQuestion } from "./ask-guard.js";
5
+
6
+ export const NO_ASK_NOTE = "Ne pose AUCUNE question. Travaille uniquement avec les fichiers du projet fournis ; tes instructions et fiches internes ne sont pas des fichiers du projet : ne les cite pas, ne les modifie pas. Produis directement le résultat de la tâche.";
7
+
8
+ /** @param {(note: string) => Promise<string>} runPatch @param {boolean} canAsk @param {string} doctrine @returns {Promise<{ patch: string, asking: string | null }>} */
9
+ export async function patchOrAsk(runPatch, canAsk, doctrine) {
10
+ const patch = await runPatch(canAsk ? ASK_NOTE : "");
11
+ const question = canAsk ? parseAskUser(patch) : null;
12
+ if (!question) return { patch, asking: null };
13
+ if (guardQuestion(question, doctrine) === question) return { patch, asking: question };
14
+ return { patch: await runPatch(NO_ASK_NOTE), asking: null };
15
+ }
16
+ // MODULE_COMPLETE
@@ -0,0 +1,18 @@
1
+ // src/auto/paths.js — Chemins du dossier .pipcode/ (mission, state).
2
+ // PipCode confine ses artefacts dans .pipcode/ à la racine du projet.
3
+
4
+ import path from "node:path";
5
+ import { AUTO_STATE_FILENAME } from "./constants.js";
6
+
7
+ // Dossier plugcto à la racine du projet (<projectPath>/.pipcode).
8
+ /** @param {string} projectPath @returns {string} */
9
+ export function plugDirOf(projectPath) { return path.join(projectPath, ".pipcode"); }
10
+
11
+ // Chemin du scratchpad mission (.pipcode/mission.md).
12
+ /** @param {string} projectPath @returns {string} */
13
+ export function missionPathOf(projectPath) { return path.join(plugDirOf(projectPath), "mission.md"); }
14
+
15
+ // Chemin du state Auto (.pipcode/auto-state.json).
16
+ /** @param {string} projectPath @returns {string} */
17
+ export function statePathOf(projectPath) { return path.join(plugDirOf(projectPath), AUTO_STATE_FILENAME); }
18
+ // MODULE_COMPLETE
@@ -0,0 +1,42 @@
1
+ // src/auto/plan.js — Pilier 1 : buildAutoPlan (Plan + Devis, Dry-Run).
2
+ // Valide → charge/crée mission → décompose LLM → budget + state consent_pending.
3
+ // Aucun appel orchestrateLLM tant que l'utilisateur n'a pas confirmé.
4
+
5
+ import path from "node:path";
6
+ import { gitExec } from "./git.js";
7
+ import { isGitRepo, NO_GIT_NOTICE } from "./git-init.js";
8
+ import { gitScope } from "./git-scope.js";
9
+ import { missionPathOf } from "./paths.js";
10
+ import { sha256, sha8 } from "./crypto-util.js";
11
+ import { collectFileSizes } from "./tasks.js";
12
+ import { estimateRunBudget } from "./budget.js";
13
+ import { formatQuote } from "./quote-format.js";
14
+ import { saveAutoState } from "./state.js";
15
+ import { decomposeAutoTasks } from "./decompose.js";
16
+ import { anonymizeTasks } from "./anonymize-tasks.js";
17
+ import { AUTO_STATE_VERSION } from "./constants.js";
18
+
19
+ // Construit le plan Auto : mission, décomposition, devis, state consent_pending.
20
+ /** @param {AutoPlanArgs} arg @param {AutoDeps} deps @returns {Promise<AutoResult>} */
21
+ export async function buildAutoPlan({ need, projectPath, pcfg, cfg, instructions, adjust }, deps) {
22
+ need = String(need || "").trim(); projectPath = String(projectPath || "").trim();
23
+ if (!need) return { isError: true, text: "Paramètre `need` manquant pour le plan Auto." };
24
+ if (!projectPath) return { isError: true, text: "Paramètre `project_path` manquant pour le plan Auto." };
25
+ const noGit = gitScope(projectPath).length === 0 && !(await isGitRepo(projectPath)); // projet brut : accepté, git initialisé après consentement (run-loop)
26
+ const missionPath = missionPathOf(projectPath);
27
+ let mission = await deps.readMissionContext(missionPath);
28
+ if (!mission) { mission = await deps.extractMission(need, pcfg); mission.need = need; await deps.writeMission(mission, missionPath); }
29
+ const missionHash = sha256(JSON.stringify(mission));
30
+ const missionCtx = deps.missionContextPrefix(mission);
31
+ const decomp = await decomposeAutoTasks(need, projectPath, pcfg, cfg, deps, instructions ? `${missionCtx}\n\n${instructions}` : missionCtx, adjust); // instructions confidentielles : décomposition SEULE, jamais persistées.
32
+ if (decomp.error) return { isError: true, text: decomp.error };
33
+ const tasks = instructions ? anonymizeTasks(/** @type {AutoTask[]} */ (decomp.tasks)) : /** @type {AutoTask[]} */ (decomp.tasks); // plan d'audit : titres génériques
34
+ const fileSizes = await collectFileSizes(projectPath, tasks);
35
+ const budget = estimateRunBudget(tasks, mission, fileSizes, deps, pcfg);
36
+ const runId = sha8(need + projectPath);
37
+ const gitHead = await gitExec(projectPath, ["rev-parse", "HEAD"]).catch(() => "");
38
+ const state = { version: AUTO_STATE_VERSION, runId, projectPath: path.resolve(projectPath), missionHash, gitHead, tasks, cursor: 0, lastDiff: "", budget: { tokensEst: budget.tokensEst, tokensUsed: 0, costUsdEst: budget.costUsdEst, costUsdUsed: 0 }, status: "consent_pending", startedAt: new Date().toISOString(), updatedAt: new Date().toISOString() };
39
+ await saveAutoState(projectPath, /** @type {AutoState} */ (/** @type {unknown} */ (state)));
40
+ return { runId, tasks, budget, quote: (noGit ? NO_GIT_NOTICE : "") + (decomp.notice ?? "") + formatQuote(tasks, budget), consent: false, status: "consent_pending" };
41
+ }
42
+ // MODULE_COMPLETE