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,56 @@
1
+ // src/auto/step-loop.js — BOUCLE FERMÉE d'une tâche du Mode Auto (jusqu'à MAX_EXEC_TURNS tours, sans repasser par l'utilisateur) :
2
+ // AGIR (patch généré puis appliqué) → OBSERVER (erreur d'application, sortie de tsc / des tests) → ÉVALUER (vert ? même panne qu'au tour d'avant ?)
3
+ // → CORRIGER (la sortie réelle est réinjectée dans le prompt du tour suivant) → ARRÊT : tâche validée, ou PAUSE (question à l'utilisateur) /
4
+ // échec si le blocage ne se résout pas. Le patch d'un tour raté est ANNULÉ avant le suivant : chaque tour repart d'un dépôt propre.
5
+ import { runQaLoop } from "./step-qa.js";
6
+ import { applyTaskDiff, checkpointTask } from "./step-commit.js";
7
+ import { markGreen } from "./step-green.js";
8
+ import { failTask, stopReport } from "./stop-options.js";
9
+ import { askExit } from "./ask-pause.js";
10
+ import { canAsk } from "./ask-user.js";
11
+ import { MAX_EXEC_TURNS } from "./constants.js";
12
+ import { failureSignature, feedbackBlock, headTail } from "./observe.js";
13
+ import { emitProgress } from "../core/progress-emit.js";
14
+ import { cancelExit } from "./cancel.js";
15
+ import { isRunAborted } from "../core/abort-signal.js";
16
+
17
+ /** Événement de progression d'une tâche (affiché dans le chat) : début, terminée, non terminée. @param {StepCtx} ctx @param {"start" | "done" | "failed"} state */
18
+ const taskEvent = (ctx, state) => emitProgress({ k: "task", state, id: ctx.task.id, title: ctx.task.title, n: ctx.idx + 1, total: ctx.state.tasks.length });
19
+
20
+ /** Blocage non résolu : question posée à l'utilisateur (PAUSE) si une interface peut répondre, sinon échec classique. @param {StepCtx} ctx @param {string} observed @param {number} turn @returns {Promise<AutoResult>} */
21
+ async function halt(ctx, observed, turn) {
22
+ const cause = headTail(observed, 300).replace(/\s+/g, " ");
23
+ if (canAsk(ctx.state)) return askExit(ctx, `La tâche « ${ctx.task.title} » reste bloquée après ${turn} tour(s) de correction automatique : ${cause} Indiquez comment la corriger, ou répondez « passer » pour l'ignorer.`);
24
+ const exit = await failTask(ctx, cause, turn);
25
+ taskEvent(ctx, "failed");
26
+ exit.stop = await stopReport(ctx, `Échec après ${turn} tour(s) de correction automatique : ${cause}`);
27
+ return exit;
28
+ }
29
+
30
+ /** @param {StepCtx} ctx @returns {Promise<AutoResult>} */
31
+ export async function runExecLoop(ctx) {
32
+ let feedback = "";
33
+ let previous = "";
34
+ taskEvent(ctx, "start");
35
+ for (let turn = 1; turn <= MAX_EXEC_TURNS; turn++) {
36
+ if (isRunAborted()) return cancelExit(ctx); // Stop entre deux tours : PAUSE
37
+ const qa = await runQaLoop(ctx, feedback); // AGIR : le patch (garde-fous statiques inclus)
38
+ if (qa.exit) return qa.exit; // question du modèle (pause), panne du fournisseur, garde-fou épuisé
39
+ const result = /** @type {OrchestratorResult} */ (qa.result);
40
+ const tried = (qa.attempts ?? 0) + turn - 1; // tentatives déjà échouées (re-attaques des garde-fous + tours de correction)
41
+ const applied = await applyTaskDiff(ctx, result, tried, true); // AGIR : écriture
42
+ let observed = applied.error ?? ""; // OBSERVER : erreur d'application
43
+ if (applied.applied) {
44
+ const green = await markGreen(ctx, applied.applied, tried, true); // OBSERVER : sortie de tsc / tests
45
+ if ("exit" in green) return green.exit;
46
+ if ("green" in green) { taskEvent(ctx, "done"); return checkpointTask(ctx, result, tried, applied.applied); } // ÉVALUER : vert → ARRÊT
47
+ observed = green.error;
48
+ }
49
+ if (isRunAborted()) return cancelExit(ctx); // Stop pendant la vérification : patch déjà annulé, PAUSE (pas de correction à tenter)
50
+ if (turn === MAX_EXEC_TURNS || (previous !== "" && failureSignature(observed) === previous)) return halt(ctx, observed, turn); // dernier tour, ou même panne : inutile de recommencer
51
+ previous = failureSignature(observed);
52
+ feedback = feedbackBlock(turn, observed); // CORRIGER : réinjection dans le prompt du tour suivant
53
+ }
54
+ return halt(ctx, "boucle terminée sans résultat", MAX_EXEC_TURNS);
55
+ }
56
+ // MODULE_COMPLETE
@@ -0,0 +1,60 @@
1
+ // src/auto/step-prep.js — Préparation d'une étape runAutoStep (porte + lecture + doctrine).
2
+
3
+ import fs from "node:fs/promises";
4
+ import path from "node:path";
5
+ import { loadAutoState, saveAutoState, progressOf } from "./state.js";
6
+ import { formatQuote } from "./quote-format.js";
7
+ import { missionPathOf } from "./paths.js";
8
+ import { redactError } from "./amnesia.js";
9
+
10
+ // Charge state + gate consentement + done. Retourne { state } ou { result } (early exit).
11
+ /** @param {string} projectPath @param {boolean} confirmed @returns {Promise<{ state?: AutoState, result?: AutoResult }>} */
12
+ export async function stepGate(projectPath, confirmed) {
13
+ const state = await loadAutoState(projectPath);
14
+ if (!state) return { result: { isError: true, text: "Aucun plan Auto trouvé. Établissez d'abord le plan du Mode Auto." } };
15
+ if (state.status === "consent_pending") {
16
+ if (!confirmed) return { result: { needsConsent: true, quote: formatQuote(state.tasks, state.budget), status: "consent_pending" } };
17
+ state.status = "running";
18
+ }
19
+ if (state.status === "done") return { result: { done: true, progress: progressOf(state), status: "done" } };
20
+ return { state };
21
+ }
22
+
23
+ /** Trouve la prochaine tâche pending à/après cursor. @param {AutoState} state @param {string} projectPath @returns {Promise<{ idx?: number, task?: AutoTask, result?: AutoResult }>} */
24
+ export async function findNextPending(state, projectPath) {
25
+ let idx = -1;
26
+ for (let i = state.cursor; i < state.tasks.length; i++) {
27
+ if (state.tasks[i].status === "pending") { idx = i; break; }
28
+ }
29
+ if (idx < 0) {
30
+ state.status = "done"; state.updatedAt = new Date().toISOString();
31
+ await saveAutoState(projectPath, state);
32
+ return { result: { done: true, progress: progressOf(state), status: "done" } };
33
+ }
34
+ return { idx, task: state.tasks[idx] };
35
+ }
36
+
37
+ /** Lit le fichier cible. @param {string} projectPath @param {AutoTask} task @param {AutoState} state @param {number} idx @returns {Promise<{ content?: string, result?: AutoResult }>} */
38
+ export async function readTaskContent(projectPath, task, state, idx) {
39
+ if (task.isNew) return { content: "" };
40
+ try { return { content: await fs.readFile(path.join(projectPath, task.file), "utf8") }; }
41
+ catch (e) {
42
+ const ee = /** @type {Error} */ (e);
43
+ task.status = "failed"; task.error = redactError(`fichier introuvable : ${ee.message}`);
44
+ state.cursor = idx + 1; state.updatedAt = new Date().toISOString();
45
+ await saveAutoState(projectPath, state);
46
+ return { result: { failed: true, task, progress: progressOf(state), status: state.status } };
47
+ }
48
+ }
49
+
50
+ // Charge mission + doctrine complète (19 fiches). Retourne { mission, missionContext, doctrine } ou { result }.
51
+ /** @param {AutoDeps} deps @param {PlugConfig} cfg @param {string} projectPath @returns {Promise<{ mission?: MissionPlan | null, missionContext?: string, doctrine?: string, result?: AutoResult }>} */
52
+ export async function loadStepMission(deps, cfg, projectPath) {
53
+ const mission = await deps.readMissionContext(missionPathOf(projectPath));
54
+ const missionContext = deps.missionContextPrefix(mission);
55
+ let doctrine = "";
56
+ try { doctrine = await deps.loadAllDoctrine(cfg); }
57
+ catch (e) { const ee = /** @type {Error} */ (e); return { result: { isError: true, text: `🛑 Doctrine (19 fiches) indisponible ou signature invalide. ${ee.message}` } }; }
58
+ return { mission, missionContext, doctrine };
59
+ }
60
+ // MODULE_COMPLETE
@@ -0,0 +1,53 @@
1
+ // src/auto/step-qa.js — Boucle de re-attaque QA bornée (MAX_QA_ATTEMPTS=3) de
2
+ // runAutoStep. Alimente le sous-agent via amnésie (doctrine + lastDiff +
3
+ // correction), retry transient orthogonal, checkMissionGuard. À l'épuisement :
4
+ // échec persisté (garde-fou) + cause de boucle (violations N vs N-1) + rapport
5
+ // d'arrêt avec les 2 options de restauration (.pipcode/cache/snapshots).
6
+
7
+ import { buildAmnesiaContext, buildCorrectionSignal, redactError } from "./amnesia.js";
8
+ import { withTransientRetry } from "./retry.js";
9
+ import { MAX_QA_ATTEMPTS } from "./constants.js";
10
+ import { failTask, stopReport, loopCause } from "./stop-options.js";
11
+ import { answersBlock, canAsk } from "./ask-user.js";
12
+ import { askExit } from "./ask-pause.js";
13
+ import { cancelExit } from "./cancel.js";
14
+ import { isRunAborted } from "../core/abort-signal.js";
15
+
16
+ // Boucle QA : re-attaque bornée tant que le guard bloque. Retry transient orthogonal.
17
+ /** @param {StepCtx} ctx @param {string} [feedback] sortie réelle du tour précédent (boucle auto-corrective) @returns {Promise<{ result?: OrchestratorResult, attempts?: number, lastGuard?: GuardResult | null, exit?: AutoResult }>} */
18
+ export async function runQaLoop(ctx, feedback = "") {
19
+ const { deps, state, doctrine, missionContext, mission, task, fileContent, pcfg } = ctx;
20
+ let attempts = 0;
21
+ let lastGuard = null;
22
+ let prevGuard = null; // garde-fou : comparaison violations N vs N-1 (cause de boucle)
23
+ let result = /** @type {OrchestratorResult} */ (/** @type {unknown} */ (null));
24
+ while (attempts < MAX_QA_ATTEMPTS) {
25
+ const amn = buildAmnesiaContext(state, doctrine, missionContext + answersBlock(state)); // + réponses déjà données à ses questions
26
+ const correction = [feedback, lastGuard ? buildCorrectionSignal(lastGuard.violations) : ""].filter(Boolean).join("\n\n");
27
+ try {
28
+ result = await withTransientRetry(() => deps.orchestrateLLM(
29
+ { code: fileContent, file_path: task.file, missionContext: amn.missionContext, doctrine: amn.doctrine, lastDiff: amn.lastDiff, correction, canAsk: canAsk(state) },
30
+ pcfg
31
+ ), `task:${task.id}`);
32
+ } catch (e) {
33
+ if (isRunAborted()) return { exit: await cancelExit(ctx) }; // Stop : PAUSE, pas une panne
34
+ const ee = /** @type {Error} */ (e);
35
+ state.status = "failed"; // comportement historique : panne provider → run failed
36
+ const exit = await failTask(ctx, redactError(ee.message), attempts);
37
+ exit.stop = await stopReport(ctx, `Le sous-agent LLM a échoué (${redactError(ee.message)}).`);
38
+ return { exit };
39
+ }
40
+ if (result.asking) return { exit: await askExit(ctx, result.asking) }; // question : PAUSE (tâche en attente), ni garde-fou ni échec
41
+ const guard = deps.checkMissionGuard(result, mission);
42
+ if (!guard.blocked) break;
43
+ prevGuard = lastGuard; lastGuard = guard; attempts++;
44
+ }
45
+ if (attempts >= MAX_QA_ATTEMPTS && lastGuard && lastGuard.blocked) {
46
+ const exit = await failTask(ctx, lastGuard.message || "", attempts);
47
+ exit.violations = lastGuard.violations;
48
+ exit.stop = await stopReport(ctx, loopCause(prevGuard ? prevGuard.violations : null, lastGuard.violations));
49
+ return { exit };
50
+ }
51
+ return { result, attempts, lastGuard };
52
+ }
53
+ // MODULE_COMPLETE
@@ -0,0 +1,30 @@
1
+ // src/auto/step.js — Pilier 2 : runAutoStep (avance UNE tâche, orchestrateur).
2
+ // step-prep (porte+lecture+doctrine) → snapshot state_pre_gen (garde-fou) →
3
+ // step-loop (boucle fermée : patch, apply, gates, correction par la sortie réelle ≤ 3 tours) →
4
+ // checkpoint. deps injecté (closure).
5
+
6
+ import { stepGate, findNextPending, readTaskContent, loadStepMission } from "./step-prep.js";
7
+ import { runExecLoop } from "./step-loop.js";
8
+ import { takeSnapshot } from "./snapshot-take.js";
9
+ import { SNAP_PRE } from "./snapshot-paths.js";
10
+ import { missionPathOf } from "./paths.js";
11
+
12
+ // Avance d'UNE tâche : porte → prochaine tâche → lecture → doctrine → QA → apply → checkpoint.
13
+ /** @param {AutoStepArgs} arg @param {AutoDeps} deps @returns {Promise<AutoResult>} */
14
+ export async function runAutoStep({ projectPath, confirmed, pcfg, cfg }, deps) {
15
+ const gated = await stepGate(projectPath, !!confirmed);
16
+ if (gated.result) return gated.result;
17
+ const state = /** @type {AutoState} */ (gated.state);
18
+ const found = await findNextPending(state, projectPath);
19
+ if (found.result) return found.result;
20
+ const idx = /** @type {number} */ (found.idx);
21
+ const task = /** @type {AutoTask} */ (found.task);
22
+ const read = await readTaskContent(projectPath, task, state, idx);
23
+ if (read.result) return read.result;
24
+ const loaded = await loadStepMission(deps, cfg, projectPath);
25
+ if (loaded.result) return loaded.result;
26
+ /** @type {StepCtx} */ const ctx = { deps, state, doctrine: /** @type {string} */ (loaded.doctrine), missionContext: /** @type {string} */ (loaded.missionContext), mission: /** @type {MissionPlan} */ (loaded.mission), task, fileContent: /** @type {string} */ (read.content), pcfg, idx, projectPath, missionPath: missionPathOf(projectPath) };
27
+ await takeSnapshot(projectPath, SNAP_PRE); // garde-fou : état AVANT génération (fail-safe)
28
+ return runExecLoop(ctx); // boucle fermée : agir → observer → évaluer → corriger (≤ 3 tours) → arrêt ou pause
29
+ }
30
+ // MODULE_COMPLETE
@@ -0,0 +1,34 @@
1
+ // src/auto/stop-options.js — Garde-fou d'arrêt : échec de tâche persisté (compteur
2
+ // d'échecs consécutifs, cap run-loop) + cause de boucle (violations identiques
3
+ // N vs N-1). Le rapport d'arrêt 2 options vit dans stop-report.js (SRP).
4
+
5
+ import { saveAutoState, progressOf } from "./state.js";
6
+ import { redactError } from "./amnesia.js";
7
+
8
+ // Le rapport d'arrêt 2 options vit dans stop-report.js (SRP) ; re-exporté ici
9
+ // pour un point d'entrée unique du garde-fou.
10
+ export { stopReport } from "./stop-report.js";
11
+
12
+ // Cause de boucle : les violations sont-elles identiques à la tentative précédente ?
13
+ /** @param {GuardViolation[] | null | undefined} prev @param {GuardViolation[] | null | undefined} cur @returns {string} */
14
+ export function loopCause(prev, cur) {
15
+ /** @type {(v: GuardViolation[] | null | undefined) => string} */
16
+ const key = (v) => (Array.isArray(v) ? v.map((x) => String((x && (x.rule || x.kind)) || "")).sort().join(",") : "");
17
+ if (key(prev) && key(prev) === key(cur)) {
18
+ return `Boucle détectée : les mêmes violations reviennent à chaque re-attaque (${key(cur)}). Le modèle ignore le signal de correction — arrêt immédiat pour éviter une boucle infinie.`;
19
+ }
20
+ return "Échec après 3 re-attaques (violations persistantes malgré le signal de correction).";
21
+ }
22
+
23
+ // Marque la tâche failed, incrémente le compteur d'échecs consécutifs, persiste.
24
+ /** @param {StepCtx} ctx @param {string} errText @param {number} attempts @returns {Promise<AutoResult>} */
25
+ export async function failTask(ctx, errText, attempts) {
26
+ const { state, task, idx, projectPath } = ctx;
27
+ task.status = "failed"; task.attempts = attempts; task.error = redactError(errText);
28
+ state.cursor = idx + 1;
29
+ state.failureStreak = (state.failureStreak || 0) + 1;
30
+ state.updatedAt = new Date().toISOString();
31
+ await saveAutoState(projectPath, state);
32
+ return { failed: true, task, error: task.error || "", progress: progressOf(state), status: state.status };
33
+ }
34
+ // MODULE_COMPLETE
@@ -0,0 +1,17 @@
1
+ // src/auto/stop-report.js — Rapport d'arrêt du garde-fou : cause + progression conservée + où retrouver les originaux (dossier de sauvegarde de l'exécution, s'il est fourni
2
+ // par l'extension) + indice de délégation multi-agent (si l'agent hôte en a la capacité, c'est lui qui déploie).
3
+
4
+ import path from "node:path";
5
+ import { progressOf } from "./state.js";
6
+ import { multiAgentHint } from "../mcp/client-info.js";
7
+
8
+ // Rapport d'arrêt : aucune restauration automatique, l'état est préservé ; les originaux des fichiers modifiés restent dans la sauvegarde.
9
+ /** @param {{ projectPath: string, state: AutoState }} ctx @param {string} cause @returns {Promise<string>} */
10
+ export async function stopReport(ctx, cause) {
11
+ const saves = process.env.PLUGCTO_BACKUP_DIR;
12
+ const hint = multiAgentHint();
13
+ return `STOP — ${cause}\nProgression conservée : ${progressOf(ctx.state)}. Aucun retour en arrière automatique : l'état est préservé.` +
14
+ (saves && path.isAbsolute(saves) ? `\nOriginaux des fichiers modifiés : ${saves}` : "") +
15
+ (hint ? `\n\n${hint}` : "");
16
+ }
17
+ // MODULE_COMPLETE
@@ -0,0 +1,44 @@
1
+ // src/auto/tasks.js — Décomposition : normalisation des micro-tâches + collecte tailles.
2
+ // normalizeTasks : valide les champs, rejette paths absolus/.., cap 40, isNew.
3
+ // collectFileSizes : { [file]: bytes } pour le devis (0 pour les nouveaux fichiers).
4
+
5
+ import fs from "node:fs/promises";
6
+ import path from "node:path";
7
+
8
+ // Normalise la sortie LLM en AutoTask[] : valide champs, anti path traversal, cap 40.
9
+ /** @param {({ tasks?: Record<string, unknown>[] } | Record<string, unknown>[])} parsed @param {string[]} repoFiles @returns {AutoTask[]} */
10
+ export function normalizeTasks(parsed, repoFiles) {
11
+ let arr = parsed;
12
+ if (arr && typeof arr === "object" && !Array.isArray(arr) && Array.isArray(arr.tasks)) arr = arr.tasks;
13
+ if (!Array.isArray(arr)) throw new Error("Décomposition : le LLM n'a pas retourné un tableau de tâches.");
14
+ const repo = new Set(repoFiles);
15
+ /** @type {AutoTask[]} */ const out = [];
16
+ let n = 0;
17
+ for (const raw of arr) {
18
+ if (!raw || typeof raw !== "object") continue;
19
+ n += 1;
20
+ const id = String(raw.id || `t${String(n).padStart(2, "0")}`);
21
+ const file = String(raw.file || "").trim();
22
+ const title = String(raw.title || "").trim();
23
+ const objective = String(raw.objective || "").trim();
24
+ const acceptance = String(raw.acceptance || "").trim();
25
+ if (!file || !title || !objective || !acceptance) continue; // tâche incomplète → écartée
26
+ if (path.isAbsolute(file) || file.includes("..")) continue; // anti path traversal hors projet
27
+ out.push({ id, title, file, objective, acceptance, status: "pending", attempts: 0, commit: null, error: null, isNew: !repo.has(file) });
28
+ }
29
+ if (out.length === 0) throw new Error("Décomposition : aucune micro-tâche valide produite.");
30
+ return out.slice(0, 40); // cap doctrine (prompt max 40).
31
+ }
32
+
33
+ // Collecte les tailles de fichiers (bytes) pour le devis. 0 pour les nouveaux fichiers.
34
+ /** @param {string} projectPath @param {AutoTask[]} tasks @returns {Promise<Record<string, number>>} */
35
+ export async function collectFileSizes(projectPath, tasks) {
36
+ /** @type {Record<string, number>} */ const sizes = {};
37
+ for (const t of tasks) {
38
+ if (t.isNew) { sizes[t.file] = 0; continue; }
39
+ try { const st = await fs.stat(path.join(projectPath, t.file)); sizes[t.file] = st.size; }
40
+ catch { sizes[t.file] = 0; }
41
+ }
42
+ return sizes;
43
+ }
44
+ // MODULE_COMPLETE
@@ -0,0 +1,29 @@
1
+ // src/cli/apply-loop.js — Boucle d'application du setup sur chaque cible IDE.
2
+ // applySetup par cible, log ✓/✗, collecte des résultats (succès ou erreur).
3
+ // Une erreur sur une cible n'interrompt pas les autres (best-effort).
4
+
5
+ import { applySetup } from "./apply.js";
6
+
7
+ /** @typedef {{ licenseKey: string, email: string }} CollectedConfig */
8
+ /** @typedef {{ id: string, name: string, path: string, parentExists: boolean, configExists: boolean, schema?: "mcpServers" | "servers" | "context_servers" | "toml" }} IdeConfigEntry */
9
+ /** @typedef {{ path: string, created?: boolean, wrote?: boolean, error?: string }} ApplyResult */
10
+
11
+ // Applique le setup à chaque cible ; retourne le tableau des résultats (succès/erreur).
12
+ /** @param {IdeConfigEntry[]} targets @param {CollectedConfig} collected @param {string} indexAbsPath @returns {Promise<ApplyResult[]>} */
13
+ export async function applyToTargets(targets, collected, indexAbsPath) {
14
+ console.log("");
15
+ /** @type {ApplyResult[]} */ const results = [];
16
+ for (const t of targets) {
17
+ try {
18
+ const r = await applySetup(t.path, collected, indexAbsPath, t.schema || "mcpServers");
19
+ results.push(r);
20
+ console.log(` ✓ ${t.name.padEnd(16)} → ${r.path} ${r.created ? "(créé)" : "(mis à jour)"}`);
21
+ } catch (err) {
22
+ const e = /** @type {Error} */ (err);
23
+ console.error(` ✗ ${t.name.padEnd(16)} → ÉCHEC : ${e.message}`);
24
+ results.push({ path: t.path, error: e.message });
25
+ }
26
+ }
27
+ return results;
28
+ }
29
+ // MODULE_COMPLETE
@@ -0,0 +1,26 @@
1
+ // src/cli/apply-toml.js — Application du setup au config.toml de Codex (partagé
2
+ // avec l'app ChatGPT desktop et l'extension IDE de Codex). Lecture → fusion
3
+ // prudente de NOS tables seulement (toml-merge) → sauvegarde unique du fichier
4
+ // d'origine → écriture atomique. Retourne { path, created, wrote }.
5
+
6
+ import { existsSync } from "node:fs";
7
+ import fs from "node:fs/promises";
8
+ import { buildServerEntry, SERVER_KEY } from "./server-entry.js";
9
+ import { mergeToml, removeToml } from "./toml-merge.js";
10
+ import { LEGACY_SERVER_KEYS } from "./legacy-entry.js";
11
+ import { toEnvVars } from "../engine/collect.js";
12
+ import { writeTextAtomic } from "../fs/atomic-text.js";
13
+ import { backupOnce } from "../fs/backup-once.js";
14
+
15
+ /** @typedef {{ licenseKey: string, email: string }} CollectedConfig */
16
+
17
+ /** @param {string} filePath @param {CollectedConfig} collected @param {string} indexAbsPath @returns {Promise<{ path: string, created: boolean, wrote: boolean }>} */
18
+ export async function applyTomlSetup(filePath, collected, indexAbsPath) {
19
+ const existed = existsSync(filePath);
20
+ const text = existed ? await fs.readFile(filePath, "utf8") : "";
21
+ const next = mergeToml(LEGACY_SERVER_KEYS.reduce((t, k) => removeToml(t, k).text, text), SERVER_KEY, buildServerEntry(indexAbsPath, toEnvVars(collected)));
22
+ await backupOnce(filePath);
23
+ await writeTextAtomic(filePath, next);
24
+ return { path: filePath, created: !existed, wrote: true };
25
+ }
26
+ // MODULE_COMPLETE
@@ -0,0 +1,45 @@
1
+ // src/cli/apply.js — Application de la config collectée à un fichier IDE.
2
+ // Lit → sauvegarde unique de l'original → merge idempotent → écriture atomique.
3
+ // Retourne { path, created, wrote }. `schema` sélectionne la clé racine + la forme
4
+ // de l'entrée (cf. mergeConfig) ; "toml" = config.toml de Codex (apply-toml.js).
5
+
6
+ import { existsSync } from "node:fs";
7
+ import path from "node:path";
8
+ import { loadConfig } from "../fs/load-config.js";
9
+ import { writeConfigAtomic } from "../fs/atomic-write.js";
10
+ import { backupOnce } from "../fs/backup-once.js";
11
+ import { applyTomlSetup } from "./apply-toml.js";
12
+ import { buildServerEntry, mergeConfig, SERVER_KEY } from "./server-entry.js";
13
+ import { toEnvVars } from "../engine/collect.js";
14
+
15
+ /** @typedef {{ licenseKey: string, email: string }} CollectedConfig */
16
+ /** @typedef {{ path: string, created: boolean, wrote: boolean }} ApplySetupResult */
17
+ /** @typedef {"mcpServers" | "servers" | "context_servers" | "toml"} IdeSchema */
18
+
19
+ // Chemin absolu vers index.js (racine projet, à côté de bin/ et src/).
20
+ const INDEX_JS = path.resolve(import.meta.dirname, "..", "..", "index.js");
21
+
22
+ // Zed : settings.json contient presque toujours des commentaires — les réécrire en
23
+ // JSON les détruirait. On refuse (jamais d'écrasement) et on guide l'utilisateur.
24
+ const ZED_HINT = " Zed : ce fichier contient des commentaires, PipCode ne le réécrit pas pour ne pas les perdre. Ajoutez le serveur dans Zed (Paramètres → IA → Serveurs MCP).";
25
+
26
+ /**
27
+ * @param {string} filePath
28
+ * @param {CollectedConfig} collected
29
+ * @param {string} [indexAbsPath]
30
+ * @param {IdeSchema} [schema]
31
+ * @returns {Promise<ApplySetupResult>}
32
+ */
33
+ export async function applySetup(filePath, collected, indexAbsPath = INDEX_JS, schema = "mcpServers") {
34
+ if (schema === "toml") return applyTomlSetup(filePath, collected, indexAbsPath);
35
+ const existed = existsSync(filePath);
36
+ const config = await loadConfig(filePath).catch((err) => {
37
+ throw new Error(err.message + (schema === "context_servers" ? ZED_HINT : ""));
38
+ });
39
+ const entry = buildServerEntry(indexAbsPath, toEnvVars(collected));
40
+ const next = mergeConfig(config, entry, SERVER_KEY, schema);
41
+ await backupOnce(filePath);
42
+ await writeConfigAtomic(filePath, next);
43
+ return { path: filePath, created: !existed, wrote: true };
44
+ }
45
+ // MODULE_COMPLETE
@@ -0,0 +1,37 @@
1
+ // src/cli/ask-prepare.js — Préparation d'un message du chat : règles du serveur ET lecture locale du projet EN PARALLÈLE (avant : l'une après l'autre), puis fiches métier du sujet.
2
+ // Chaque étape est annoncée à l'interface (événement « stage ») : l'utilisateur voit tout de suite ce qui se passe. Aucune règle de contenu ici : assemblage seulement.
3
+ import { ensureRules } from "../rules/rules-load.js";
4
+ import { emitProgress } from "../core/progress-emit.js";
5
+ import { buildProjectContext } from "../engine/project-context.js";
6
+ import { parseOpenFiles } from "../engine/context-files.js";
7
+ import { indexProject } from "../project-memory/memory-build.js";
8
+ import { pendingBlock } from "../project-memory/qa-apply.js";
9
+ import { projectInstructions } from "../engine/project-instructions.js";
10
+ import { chatDoctrine } from "../engine/chat-doctrine.js";
11
+ import { missionLine } from "../auto/mission-line.js";
12
+ import { contextChars } from "../project-memory/window.js";
13
+ import { isOllamaBase, ollamaPromptChars } from "../core/ollama.js";
14
+
15
+ /** Lecture locale du projet : carte mise à jour, contexte, consignes (CLAUDE.md + mission en cours), questions en attente. @param {string} project @param {string} need @param {number} W @returns {Promise<{ ctx: string, instructions: string, pending: string }>} */
16
+ async function readProject(project, need, W) {
17
+ await indexProject(project).catch((e) => console.error(`ℹ Carte du projet non mise à jour : ${e.message}`)); // mémoire alimentée à chaque message
18
+ const ctx = await buildProjectContext(project, parseOpenFiles(process.env.PLUGCTO_ASK_FILES), need, W);
19
+ const instructions = [await projectInstructions(project, W * 0.2), await missionLine(project)].filter(Boolean).join("\n\n");
20
+ return { ctx, instructions, pending: await pendingBlock(project) };
21
+ }
22
+
23
+ /** @param {AutoGateOk} g @param {AutoFlags} flags @returns {Promise<{ local: boolean, projectCtx: string, instructions: string, doctrine: string }>} */
24
+ export async function prepareAsk(g, flags) {
25
+ const project = flags.project.trim();
26
+ const local = isOllamaBase(g.pcfg.primary.base); // Ollama : prompt réduit au strict utile
27
+ emitProgress({ k: "stage", s: "rules" });
28
+ // Règles du serveur (tous les comptes) : si le serveur est injoignable, le chat répond quand même et le signale sur stderr.
29
+ const rules = ensureRules(g.cfg).catch(() => console.error("ℹ Principes de réalisation indisponibles (serveur PipCode injoignable) : réponse sans eux."));
30
+ emitProgress({ k: "stage", s: "project" });
31
+ const [p] = await Promise.all([project ? readProject(project, flags.need, local ? ollamaPromptChars() : contextChars()) : { ctx: "", instructions: "", pending: "" }, rules]);
32
+ emitProgress({ k: "stage", s: "sheets" });
33
+ const doctrine = local ? "" : await chatDoctrine(flags.need, g.cfg);
34
+ emitProgress({ k: "stage", s: "model" });
35
+ return { local, projectCtx: [p.ctx, p.pending].filter(Boolean).join("\n\n"), instructions: p.instructions, doctrine };
36
+ }
37
+ // MODULE_COMPLETE
@@ -0,0 +1,53 @@
1
+ // src/cli/auth-branches.js — Branches d'authentification du flux interactif.
2
+ // Trois voies : navigateur (callback local), email (lookup Worker puis saisie
3
+ // manuelle de la clé Polar), essai (confirmation avant écrasement d'une licence
4
+ // Pro existante — U1). Chaque branche retourne { licenseKey, email }.
5
+
6
+ import { AUTH_CALLBACK_PORT } from "../net/auth-endpoints.js";
7
+
8
+ /** @typedef {{ ask: (q: string) => Promise<string>, askDefault: (q: string, def: string | undefined) => Promise<string>, askRequired: (q: string) => Promise<string> }} Prompts */
9
+ /** @typedef {{ licenseKey: string, email: string }} AuthResult */
10
+ /** @typedef {{ key: string, email: string, source: "browser", error?: string }} AuthBrowserResult */
11
+ /** @typedef {{ found: boolean, tier: string | null, email: string, source: "email", error?: string }} AuthEmailResult */
12
+
13
+ /** @param {Prompts} _p @param {() => Promise<AuthBrowserResult>} browser @param {string} existingLicense @param {string} existingEmail @returns {Promise<AuthResult>} */
14
+ export async function authBrowserBranch(_p, browser, existingLicense, existingEmail) {
15
+ console.log(" 🌐 Ouverture du navigateur… (en attente du callback sur 127.0.0.1:" + AUTH_CALLBACK_PORT + ")");
16
+ const r = await browser();
17
+ if (r && r.key) return { licenseKey: r.key, email: r.email || "" };
18
+ // U1 : auth navigateur échouée — on conserve une licence Pro existante.
19
+ if (existingLicense) {
20
+ console.error(" ⚠️ Auth navigateur échouée (" + (r && r.error) + "). Conservation de votre licence existante.");
21
+ return { licenseKey: existingLicense, email: existingEmail };
22
+ }
23
+ console.error(" ⚠️ Auth navigateur échouée (" + (r && r.error) + "). Bascule en essai 5 jours.");
24
+ return { licenseKey: "", email: "" };
25
+ }
26
+
27
+ /** @param {Prompts} p @param {(email: string) => Promise<AuthEmailResult>} emailLookup @returns {Promise<AuthResult>} */
28
+ export async function authEmailBranch(p, emailLookup) {
29
+ const email = await p.askRequired(" Entrez votre email : ");
30
+ const r = await emailLookup(email);
31
+ if (r && r.found) {
32
+ console.log(" ✅ Licence " + (r.tier ? r.tier.toUpperCase() : "") + " trouvée pour " + email + ".");
33
+ console.log(" Saisissez la clé de licence reçue par email (Polar.sh).");
34
+ const licenseKey = await p.askRequired(" Clé de licence : ");
35
+ return { licenseKey, email };
36
+ }
37
+ console.error(" ⚠️ Aucune licence trouvée pour cet email (" + (r && r.error) + "). Vous pouvez saisir une clé manuellement ou passer en essai.");
38
+ return { licenseKey: "", email };
39
+ }
40
+
41
+ // Essai [4] : confirmation explicite avant écrasement d'une licence Pro (U1).
42
+ /** @param {Prompts} p @param {string} existingLicense @param {string} existingEmail @returns {Promise<AuthResult>} */
43
+ export async function authTrialBranch(p, existingLicense, existingEmail) {
44
+ if (!existingLicense) return { licenseKey: "", email: "" };
45
+ console.log(" ⚠️ Vous avez une licence Pro existante. Le mode Essai (sans licence) l'écrasera.");
46
+ const confirm = await p.askDefault(" Confirmer le remplacement par un essai sans licence ? (o/N) : ", "N");
47
+ if (!/^(o|oui|y|yes)$/i.test(confirm)) {
48
+ console.log(" ✅ Licence existante conservée.");
49
+ return { licenseKey: existingLicense, email: existingEmail };
50
+ }
51
+ return { licenseKey: "", email: "" };
52
+ }
53
+ // MODULE_COMPLETE
@@ -0,0 +1,46 @@
1
+ // src/cli/auth-interactive.js — Étape 1 du flux interactif : authentification.
2
+ // Affiche le menu [1-4] (Navigateur / Email / Clé manuelle / Essai) et dispatche
3
+ // vers la branche correspondante. Détecte une licence Pro existante (U1) pour
4
+ // empêcher son écrasement accidentel.
5
+
6
+ import { authBrowserBranch, authEmailBranch, authTrialBranch } from "./auth-branches.js";
7
+
8
+ /** @typedef {{ ask: (q: string) => Promise<string>, askDefault: (q: string, def: string | undefined) => Promise<string>, askRequired: (q: string) => Promise<string> }} Prompts */
9
+ /** @typedef {{ licenseKey: string, email: string }} AuthResult */
10
+ /** @typedef {{ installedAt?: string, clientId?: string, licenseKey?: string, email?: string, trialExpiredNotified?: boolean }} SharedConfig */
11
+ /** @typedef {{ key: string, email: string, source: "browser", error?: string }} AuthBrowserResult */
12
+ /** @typedef {{ found: boolean, tier: string | null, email: string, source: "email", error?: string }} AuthEmailResult */
13
+ /** @typedef {{ browser?: () => Promise<AuthBrowserResult>, emailLookup?: (email: string) => Promise<AuthEmailResult>, existingConfig?: SharedConfig }} CollectAuthOpts */
14
+
15
+ // Extrait licence + email existants (U1 : anti-écrasement de licence Pro).
16
+ /** @param {SharedConfig | undefined} existingConfig @returns {{ existingLicense: string, existingEmail: string }} */
17
+ export function existingLicenseInfo(existingConfig) {
18
+ const existingLicense = (existingConfig && existingConfig.licenseKey) ? String(existingConfig.licenseKey) : "";
19
+ const existingEmail = (existingConfig && existingConfig.email) ? String(existingConfig.email) : "";
20
+ return { existingLicense, existingEmail };
21
+ }
22
+
23
+ /**
24
+ * @param {Prompts} p
25
+ * @param {CollectAuthOpts} opts
26
+ * @returns {Promise<AuthResult>}
27
+ */
28
+ export async function collectAuth(p, { browser, emailLookup, existingConfig }) {
29
+ const { existingLicense, existingEmail } = existingLicenseInfo(existingConfig);
30
+ console.log("\n🔑 Configuration PipCode — auto-correction en direct (BYOK)\n");
31
+ if (existingLicense) {
32
+ console.log(" 🔑 Licence existante détectée dans ~/.pipcode/config.json" + (existingEmail ? " (" + existingEmail + ")" : "") + ".");
33
+ console.log(" Elle sera conservée sauf si vous choisissez explicitement de la remplacer.\n");
34
+ }
35
+ console.log(" Authentification PipCode :");
36
+ console.log(" [1] Navigateur (connexion via navigateur) ← défaut (Entrée)");
37
+ console.log(" [2] Email (recherche de licence par email)");
38
+ console.log(" [3] Clé de licence Polar.sh (saisie manuelle)");
39
+ console.log(" [4] Essai 5 jours (sans licence)");
40
+ const authChoice = await p.askDefault(" Choix [1-4] (Entrée = 1) : ", "1");
41
+ if (authChoice === "1") return authBrowserBranch(p, /** @type {() => Promise<AuthBrowserResult>} */ (browser), existingLicense, existingEmail);
42
+ if (authChoice === "2") return authEmailBranch(p, /** @type {(email: string) => Promise<AuthEmailResult>} */ (emailLookup));
43
+ if (authChoice === "3") return { licenseKey: await p.ask(" Entrez votre clé de licence Polar.sh : "), email: "" };
44
+ return authTrialBranch(p, existingLicense, existingEmail);
45
+ }
46
+ // MODULE_COMPLETE
@@ -0,0 +1,33 @@
1
+ // src/cli/auto-ask.js — Handler `auto ask` : conversation pure (ChatView v2).
2
+ // Répond par UN appel LLM BYOK — aucun plan, aucun état, aucun fichier de code écrit (seule la mémoire du projet,
3
+ // sous .pipcode/memoire, est alimentée). Gate héritée du dispatcher auto-cli.js : gratuit à vie (Lite inclus, zéro fiche
4
+ // injectée sauf les règles de frugalité servies par le serveur), BYOK exigé.
5
+
6
+ import { askLLM } from "../engine/ask-llm.js";
7
+ import { formatAskHistory } from "../engine/ask-history.js";
8
+ import { applyQa, stripTags } from "../project-memory/qa-apply.js";
9
+ import { splitThinking, stepsBlock } from "../engine/steps-block.js";
10
+ import { keepRecentLines } from "../core/ollama.js";
11
+ import { prepareAsk } from "./ask-prepare.js";
12
+
13
+ // Handler `auto ask --need "<question>" --project <chemin>`.
14
+ /** @param {AutoGateOk} g @param {AutoFlags} flags @returns {Promise<void>} */
15
+ export async function handleAutoAsk(g, flags) {
16
+ if (!flags.need.trim()) {
17
+ console.error('✗ --need "<question>" requis pour `auto ask`.');
18
+ process.exit(2);
19
+ }
20
+ const project = flags.project.trim();
21
+ try {
22
+ const { local, projectCtx, instructions, doctrine } = await prepareAsk(g, flags);
23
+ const raw = await askLLM({ primary: g.pcfg.primary }, flags.need, "", ((h) => (local ? keepRecentLines(h, 3000) : h))(formatAskHistory(process.env.PLUGCTO_ASK_HISTORY)), projectCtx, instructions, doctrine);
24
+ const { thinking, answer } = splitThinking(raw); // raisonnement du modèle : bloc repliable, jamais mêlé à la réponse ni aux balises
25
+ const shown = project ? await applyQa(project, flags.need, answer).catch((e) => { console.error(`ℹ Décisions non enregistrées : ${e.message}`); return stripTags(answer); }) : stripTags(answer);
26
+ console.log("\n" + shown + "\n");
27
+ } catch (err) {
28
+ const e = /** @type {NodeErr} */ (err);
29
+ console.error("\n ✗ Conversation échouée :", e.message);
30
+ process.exit(1);
31
+ }
32
+ }
33
+ // MODULE_COMPLETE
@@ -0,0 +1,16 @@
1
+ // src/cli/auto-audit.js — Handler `auto audit` : rapport de diagnostic en lecture seule (aucun plan, aucun auto-state.json, aucune écriture).
2
+ // Protocole serveur indisponible → code 5 sans appel modèle. Pas de process.exit après du réseau (crash Windows) : exitCode positionné.
3
+ import { runAudit } from "../engine/audit-report.js";
4
+ import { splitThinking } from "../engine/steps-block.js";
5
+
6
+ /** @param {AutoGateOk} g @param {string} projectPath @returns {Promise<void>} */
7
+ export async function handleAutoAudit(g, projectPath) {
8
+ try {
9
+ const { answer } = splitThinking(await runAudit({ primary: g.pcfg.primary }, g.cfg, projectPath));
10
+ console.log("\n" + answer.trim() + "\n");
11
+ } catch (err) {
12
+ console.error("\n ✗ Audit échoué :", /** @type {Error} */ (err).message);
13
+ process.exitCode = /protocole|serveur|Audit approfondi|essai/i.test(/** @type {Error} */ (err).message) ? 5 : 1;
14
+ }
15
+ }
16
+ // MODULE_COMPLETE