pipcode 0.0.0-stage → 2.1.1

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,37 @@
1
+ // src/engine/json-balance.js — Récupération d'un JSON TRONQUÉ (flux coupé par la limite de sortie) par équilibrage de pile.
2
+ // 1) coupure juste après une valeur complète : on ferme les délimiteurs ouverts (] }) ; 2) coupure en pleine valeur : on revient au dernier
3
+ // séparateur du niveau le plus haut et on ABANDONNE l'élément incomplet (jamais de guillemet refermé sur un texte coupé : une tâche au titre
4
+ // tronqué serait fausse sans que personne le voie). La valeur rendue porte la marque TRUNCATED pour que l'appelant prévienne l'utilisateur.
5
+
6
+ export const TRUNCATED = Symbol.for("vcto.json.truncated");
7
+
8
+ /** @param {string} stack @returns {string} fermetures dans l'ordre inverse */
9
+ const closers = (stack) => [...stack].reverse().map((c) => (c === "{" ? "}" : "]")).join("");
10
+
11
+ /** @param {unknown} v @returns {unknown} */
12
+ function mark(v) { if (v && typeof v === "object") Object.defineProperty(v, TRUNCATED, { value: true }); return v; }
13
+
14
+ /** @param {string} s texte commençant par { ou [ @returns {unknown} valeur récupérée (marquée) ou null si rien d'exploitable */
15
+ export function recoverTruncated(s) {
16
+ let stack = ""; let inStr = false; let esc = false;
17
+ /** @type {{ at: number, stack: string }[]} */ const commas = [];
18
+ for (let i = 0; i < s.length; i++) {
19
+ const c = s[i];
20
+ if (inStr) { if (esc) esc = false; else if (c === "\\") esc = true; else if (c === '"') inStr = false; continue; }
21
+ if (c === '"') inStr = true;
22
+ else if (c === "{" || c === "[") stack += c;
23
+ else if (c === "}" || c === "]") stack = stack.slice(0, -1);
24
+ else if (c === ",") commas.push({ at: i, stack });
25
+ }
26
+ /** @type {string[]} */ const tries = [];
27
+ const trimmed = s.replace(/[\s,]+$/, "");
28
+ if (!inStr && /[}\]"]$/.test(trimmed)) tries.push(trimmed + closers(stack));
29
+ if (commas.length > 0) {
30
+ const top = Math.min(...commas.map((c) => c.stack.length));
31
+ const cut = /** @type {{ at: number, stack: string }} */ (commas.filter((c) => c.stack.length === top).at(-1));
32
+ tries.push(s.slice(0, cut.at) + closers(cut.stack));
33
+ }
34
+ for (const t of tries) { try { return mark(JSON.parse(t)); } catch { /* essai suivant */ } }
35
+ return null;
36
+ }
37
+ // MODULE_COMPLETE
@@ -0,0 +1,13 @@
1
+ // src/engine/json-safe.js — Lecture JSON défensive : toute donnée de fichier est non fiable, rien n'est supposé (objet ou tableau vide plutôt qu'une exception).
2
+
3
+ /** @param {unknown} v @returns {Record<string, unknown>} */
4
+ export const asObj = (v) => (v && typeof v === "object" && !Array.isArray(v) ? /** @type {Record<string, unknown>} */ (v) : {});
5
+
6
+ /** @param {unknown} v @returns {unknown[]} */
7
+ export const asArr = (v) => (Array.isArray(v) ? v : []);
8
+
9
+ /** @param {string} text @returns {Record<string, unknown>} objet JSON ({} si illisible ou si ce n'est pas un objet) */
10
+ export function jsonObject(text) {
11
+ try { return asObj(JSON.parse(text)); } catch { return {}; } // fichier corrompu : traité comme vide
12
+ }
13
+ // MODULE_COMPLETE
@@ -0,0 +1,16 @@
1
+ // src/engine/line-numbers.js — Lignes d'un texte : décompte exact (un saut de ligne final ne crée pas de ligne en plus) et numérotation « 042│ code » pour que le modèle cite les lignes
2
+ // d'après des numéros fournis, jamais comptés de tête.
3
+
4
+ /** @param {string} text @returns {string[]} lignes sans la ligne vide fantôme d'un saut de ligne final */
5
+ const linesOf = (text) => { const l = text.split("\n"); if (l.length > 1 && l[l.length - 1] === "") l.pop(); return l; };
6
+
7
+ /** @param {string} text @returns {number} nombre de lignes (0 pour un texte vide) */
8
+ export const countLines = (text) => (text === "" ? 0 : linesOf(text).length);
9
+
10
+ /** @param {string} text @returns {string} le texte avec le numéro de chaque ligne en tête, aligné */
11
+ export function numberLines(text) {
12
+ const lines = linesOf(text);
13
+ const w = String(lines.length).length;
14
+ return lines.map((l, i) => `${String(i + 1).padStart(w, "0")}│ ${l}`).join("\n");
15
+ }
16
+ // MODULE_COMPLETE
@@ -0,0 +1,35 @@
1
+ // src/engine/md-build-mid.js — Builders markdown sections 5-7 (UX, design, glossaire).
2
+ /** @param {string[]} uxDirectives @param {DesignSystem} designSystem @returns {string[]} */
3
+ export function mdSection5(uxDirectives, designSystem) {
4
+ const L = ["## 5. DIRECTIVES UX & ERGONOMIE MÉTIER"];
5
+ for (const r of (uxDirectives || [])) { L.push(`- ${r}`); }
6
+ L.push(`> Accessibilité : ${(designSystem && designSystem.wcag) || "WCAG 2.1 AA"} — focus visible, couleur jamais seule, ≥ 16px.`);
7
+ L.push("");
8
+ return L;
9
+ }
10
+
11
+ // Section 6 — Charte visuelle & Design System contextuel.
12
+ /** @param {DesignSystem} designSystem @returns {string[]} */
13
+ export function mdSection6(designSystem) {
14
+ const ds = designSystem || {};
15
+ const L = ["## 6. CHARTE VISUELLE & DESIGN SYSTEM CONTEXTUEL"];
16
+ L.push(`- Famille : ${ds.family || "—"}`);
17
+ L.push(`- Style : ${ds.style || "—"}`);
18
+ L.push(`- Densité : ${ds.density || "—"}`);
19
+ L.push(`- Palette : ${ds.palette || "—"}`);
20
+ L.push(`- Micro-interactions : ${ds.microInteractions || "—"}`);
21
+ L.push(`- Accessibilité : ${ds.wcag || "WCAG 2.1 AA"}`);
22
+ for (const v of (ds.violations || [])) { L.push(`- ❌ HORS CHARTE : ${v.rule}`); }
23
+ L.push("");
24
+ return L;
25
+ }
26
+
27
+ // Section 7 — Glossaire & lexique métier.
28
+ /** @param {GlossaryEntry[]} glossary @returns {string[]} */
29
+ export function mdSection7(glossary) {
30
+ const L = ["## 7. GLOSSAIRE & LEXIQUE MÉTIER"];
31
+ for (const g of (glossary || [])) { L.push(`- \`${g.term}\` : ${g.definition}`); }
32
+ L.push("");
33
+ return L;
34
+ }
35
+ // MODULE_COMPLETE
@@ -0,0 +1,58 @@
1
+ // src/engine/md-build-top.js — Builders markdown sections 1-4 + en-tete.
2
+ /** @param {MissionPlan} plan @returns {string[]} */
3
+ export function mdHeader(plan) {
4
+ const now = new Date().toISOString();
5
+ const { projectName, need, autoMode, createdAt, validatedAt, iterations } = plan;
6
+ return [
7
+ `# MISSION SCRATCHPAD — ${projectName || "Projet"}`,
8
+ `> Mode Autonome : ${autoMode ? "ACTIF" : "INACTIF"} | Validé par l'utilisateur le ${validatedAt ? validatedAt.slice(0, 10) : "—"}`,
9
+ `> Besoin : ${need || "_(non renseigné)_"}`,
10
+ `> Créé le ${createdAt ? createdAt.slice(0, 10) : now.slice(0, 10)} — Itérations : ${iterations || 0}`,
11
+ "",
12
+ ];
13
+ }
14
+
15
+ // Section 1 — Personas & garde-fous.
16
+ /** @param {{ role_name: string, scope: string, mandate: string }[]} personas @returns {string[]} */
17
+ export function mdSection1(personas) {
18
+ const L = ["## 1. PERSONAS MÉTIERS & GARDE-FOUS"];
19
+ for (const p of (personas || [])) {
20
+ L.push(`- **${p.role_name}** : ${p.mandate}`);
21
+ if (p.scope) L.push(` - _Périmètre_ : ${p.scope}`);
22
+ }
23
+ L.push("");
24
+ return L;
25
+ }
26
+
27
+ // Section 2 — Norme & invariants réglementaires (NON NEGO).
28
+ /** @param {DomainConstraint[]} constraints @returns {string[]} */
29
+ export function mdSection2(constraints) {
30
+ const L = ["## 2. NORME & INVARIANTS RÉGLEMENTAIRES (NON NEGO)"];
31
+ for (const c of (constraints || [])) {
32
+ L.push(`- [ ] ${c.text}${c.invariant ? ` — Invariant : ${c.invariant}` : ""}`);
33
+ }
34
+ L.push("");
35
+ return L;
36
+ }
37
+
38
+ // Section 3 — Failles métiers à bloquer.
39
+ /** @param {DomainPitfall[]} pitfalls @returns {string[]} */
40
+ export function mdSection3(pitfalls) {
41
+ const L = ["## 3. FAILLES MÉTIERS À BLOQUER"];
42
+ for (const f of (pitfalls || [])) {
43
+ L.push(`- ❌ NE JAMAIS : ${f.never}`);
44
+ L.push(` ✅ TOUJOURS : ${f.always}`);
45
+ }
46
+ L.push("");
47
+ return L;
48
+ }
49
+
50
+ // Section 4 — Checklist d'exécution technique (cochée par ultra_orchestrate).
51
+ /** @param {string[]} steps @param {boolean[]} checked @returns {string[]} */
52
+ export function mdSection4(steps, checked) {
53
+ const L = ["## 4. CHECKLIST D'EXÉCUTION TECHNIQUE"];
54
+ steps.forEach((s, i) => { L.push(`- [${checked[i] ? "x" : " "}] ${s}`); });
55
+ L.push("");
56
+ return L;
57
+ }
58
+ // MODULE_COMPLETE
@@ -0,0 +1,36 @@
1
+ // src/engine/md-build.js — Sections 8-9 + orchestrateur buildMissionMarkdown.
2
+ import { mdHeader, mdSection1, mdSection2, mdSection3, mdSection4 } from "./md-build-top.js";
3
+ import { mdSection5, mdSection6, mdSection7 } from "./md-build-mid.js";
4
+
5
+ // Section 8 — Estimation & impact.
6
+ /** @param {{ files: string[], tokens: number, risk: string }} impact @param {string[]} steps @param {string[]} [fiches] @returns {string[]} */
7
+ export function mdSection8(impact, steps, fiches) {
8
+ const L = ["## 8. ESTIMATION & IMPACT"];
9
+ L.push(`- Fichiers prévus : ${(impact.files || []).join(", ") || "—"}`);
10
+ L.push(`- Étapes de livraison : ${steps.length}`);
11
+ L.push(`- Coût approximatif en tokens : ~${impact.tokens}`);
12
+ L.push(`- Niveau de risque métier : ${impact.risk}`);
13
+ L.push(`- Fiches de doctrine à charger : ${(fiches || []).join(", ") || "—"}`);
14
+ L.push("");
15
+ return L;
16
+ }
17
+
18
+ // Section 9 — Log d'itérations.
19
+ /** @returns {string[]} */
20
+ export function mdSection9() {
21
+ return ["## 9. LOG D'ITÉRATIONS ultra_orchestrate", "<!-- Une ligne par appel ultra_orchestrate (horodaté) ; checklist §4 cochée progressivement -->", ""];
22
+ }
23
+
24
+ // Génère le markdown du scratchpad mission.md selon le template strict.
25
+ /** @param {MissionPlan} plan @returns {string} */
26
+ export function buildMissionMarkdown(plan) {
27
+ const { personas, constraints, pitfalls, glossary, impact, uxDirectives, designSystem, fiches } = plan;
28
+ const steps = impact.steps || [];
29
+ const checked = Array.isArray(plan.checkedSteps) && plan.checkedSteps.length === steps.length ? plan.checkedSteps : steps.map(() => false);
30
+ return [
31
+ ...mdHeader(plan), ...mdSection1(personas), ...mdSection2(constraints), ...mdSection3(pitfalls),
32
+ ...mdSection4(steps, checked), ...mdSection5(uxDirectives, designSystem), ...mdSection6(designSystem),
33
+ ...mdSection7(glossary), ...mdSection8(impact, steps, fiches), ...mdSection9(),
34
+ ].join("\n");
35
+ }
36
+ // MODULE_COMPLETE
@@ -0,0 +1,7 @@
1
+ // src/engine/md-parse-helpers.js — Helper d'extraction de bloc markdown (section).
2
+ /** @param {string} raw @param {string} re @returns {string} */
3
+ export const blockOf = (raw, re) => {
4
+ const m = [...raw.matchAll(new RegExp(re, "g"))][0];
5
+ return m ? m[1] : "";
6
+ };
7
+ // MODULE_COMPLETE
@@ -0,0 +1,38 @@
1
+ // src/engine/md-parse-pillars.js — Parse markdown : personas/contraintes/failles/glossaire.
2
+ import { blockOf } from "./md-parse-helpers.js";
3
+
4
+ /** @param {string} raw @returns {{ role_name: string, mandate: string, scope: string }[]} */
5
+ export function parsePersonas(raw) {
6
+ return blockOf(raw, "## 1\\. PERSONAS MÉTIERS & GARDE-FOUS\\s*\\n([\\s\\S]*?)(?:\\n## )")
7
+ .split("\n").map((l) => l.match(/^- \*\*(.+?)\*\*\s*:\s*(.+?)$/))
8
+ .filter((m) => m !== null)
9
+ .map((m) => ({ role_name: m[1].trim(), mandate: m[2].trim(), scope: "" }));
10
+ }
11
+
12
+ /** @param {string} raw @returns {DomainConstraint[]} */
13
+ export function parseConstraints(raw) {
14
+ return blockOf(raw, "## 2\\. NORME & INVARIANTS RÉGLEMENTAIRES \\(NON NEGO\\)\\s*\\n([\\s\\S]*?)(?:\\n## )")
15
+ .split("\n").map((l) => l.match(/^- \[[ x]\] (.+?)(?:\s*— Invariant : (.+))?$/))
16
+ .filter((m) => m !== null)
17
+ .map((m) => ({ text: m[1].trim(), invariant: (m[2] || "").trim(), patterns: [] }));
18
+ }
19
+
20
+ /** @param {string} raw @returns {DomainPitfall[]} */
21
+ export function parsePitfalls(raw) {
22
+ const pitfalls = blockOf(raw, "## 3\\. FAILLES MÉTIERS À BLOQUER\\s*\\n([\\s\\S]*?)(?:\\n## )")
23
+ .split("\n").map((l) => l.match(/^- ❌ NE JAMAIS : (.+?)$/))
24
+ .filter((m) => m !== null)
25
+ .map((m) => ({ never: m[1].trim(), always: "", patterns: [] }));
26
+ const alwaysLines = [...raw.matchAll(/✅ TOUJOURS : (.+?)$/gm)].map((m) => m[1].trim());
27
+ pitfalls.forEach((p, i) => { if (alwaysLines[i]) p.always = alwaysLines[i]; });
28
+ return pitfalls;
29
+ }
30
+
31
+ /** @param {string} raw @returns {GlossaryEntry[]} */
32
+ export function parseGlossary(raw) {
33
+ return blockOf(raw, "## 7\\. GLOSSAIRE & LEXIQUE MÉTIER\\s*\\n([\\s\\S]*?)(?:\\n## )")
34
+ .split("\n").map((l) => l.match(/^- `(.*?)` : (.+?)$/))
35
+ .filter((m) => m !== null)
36
+ .map((m) => ({ term: m[1].trim(), definition: m[2].trim() }));
37
+ }
38
+ // MODULE_COMPLETE
@@ -0,0 +1,31 @@
1
+ // src/engine/md-parse-ux-design.js — Parse markdown : directives UX + design system.
2
+ import { blockOf } from "./md-parse-helpers.js";
3
+ import { ensureDataLeakGuard } from "./ux-violations-leak.js";
4
+
5
+ // Section 5 — Directives UX (tolérant : lignes `- …` hors blockquote, hors ❌).
6
+ /** @param {string} raw @returns {string[]} */
7
+ export function parseUxDirectives(raw) {
8
+ return blockOf(raw, "## 5\\. DIRECTIVES UX & ERGONOMIE MÉTIER\\s*\\n([\\s\\S]*?)(?:\\n## )")
9
+ .split("\n").map((l) => l.match(/^- (.+?)$/))
10
+ .filter((m) => m !== null)
11
+ .map((m) => m[1].trim())
12
+ .filter((s) => !s.startsWith("❌"));
13
+ }
14
+
15
+ // Section 6 — Charte visuelle & Design System.
16
+ /** @param {string} raw @returns {{ family: string, style: string, density: string, palette: string, microInteractions: string, wcag: string, violations: DesignViolation[] }} */
17
+ export function parseDesignSystem(raw) {
18
+ const dsBlock = blockOf(raw, "## 6\\. CHARTE VISUELLE & DESIGN SYSTEM CONTEXTUEL\\s*\\n([\\s\\S]*?)(?:\\n## )");
19
+ /** @param {string} label @returns {string} */
20
+ const dsLine = (label) => {
21
+ const m = dsBlock.match(new RegExp(`- ${label}\\s*:\\s*(.+?)$`, "m"));
22
+ return m ? m[1].trim() : "";
23
+ };
24
+ const violations = [...dsBlock.matchAll(/^- ❌ HORS CHARTE : (.+?)$/gm)].map((m) => ({ rule: m[1].trim(), patterns: [] }));
25
+ return {
26
+ family: dsLine("Famille"), style: dsLine("Style"), density: dsLine("Densité"),
27
+ palette: dsLine("Palette"), microInteractions: dsLine("Micro-interactions"),
28
+ wcag: dsLine("Accessibilité") || "WCAG 2.1 AA", violations: ensureDataLeakGuard(violations),
29
+ };
30
+ }
31
+ // MODULE_COMPLETE
@@ -0,0 +1,33 @@
1
+ // src/engine/md-parse.js — parseMissionMarkdown : header + steps/impact + assembly.
2
+ import { parsePersonas, parseConstraints, parsePitfalls, parseGlossary } from "./md-parse-pillars.js";
3
+ import { parseUxDirectives, parseDesignSystem } from "./md-parse-ux-design.js";
4
+
5
+ // Parse un mission.md (template strict) → plan structuré. Tolérant (null si vide/corrompu).
6
+ // Les `patterns` (détection de violations) ne sont pas dans le markdown : lus depuis
7
+ // le sidecar JSON par readMissionContext. Ce parser couvre l'absence de sidecar.
8
+ /** @param {string} raw @returns {MissionPlan | null} */
9
+ export function parseMissionMarkdown(raw) {
10
+ if (!raw || !raw.trim()) return null;
11
+ const autoMode = /Mode Autonome\s*:\s*ACTIF/i.test(raw);
12
+ const projectName = (raw.match(/# MISSION SCRATCHPAD\s*[—-]\s*(.+?)\s*$/m) || [])[1] || "Projet";
13
+ const need = (raw.match(/Besoin\s*:\s*(.+?)\s*$/m) || [])[1] || "";
14
+ const steps = [...raw.matchAll(/^- \[([ x])\] (.+?)$/gm)]
15
+ .filter((m) => m[2].startsWith("Etape "))
16
+ .map((m) => ({ label: m[2].trim(), checked: m[1] === "x" }));
17
+ const risk = (raw.match(/Niveau de risque métier\s*:\s*(Élevé|Moyen|Bas)/) || [])[1] || "Moyen";
18
+ const tokens = parseInt((raw.match(/Coût approximatif en tokens\s*:\s*~?(\d+)/) || [])[1] || "0", 10);
19
+ return {
20
+ autoMode, projectName, need,
21
+ personas: parsePersonas(raw),
22
+ constraints: parseConstraints(raw),
23
+ pitfalls: parsePitfalls(raw),
24
+ glossary: parseGlossary(raw),
25
+ uxDirectives: parseUxDirectives(raw),
26
+ designSystem: parseDesignSystem(raw),
27
+ impact: { steps: steps.map((s) => s.label), tokens, risk, files: [] },
28
+ checkedSteps: steps.map((s) => s.checked),
29
+ patternsLoaded: false, // false → scanner inactif (sidecar requis pour les patterns)
30
+ raw,
31
+ };
32
+ }
33
+ // MODULE_COMPLETE
@@ -0,0 +1,30 @@
1
+ // src/engine/mission-context-prefix.js — Préfixe de contexte métier injecté dans ultra_orchestrate (Phase 1 +
2
+ // Phase 2). Les GABARITS (intitulés, formulations d'instruction) sont servis par le serveur (lot Pro) : ici
3
+ // seulement l'assemblage. Retourne "" si pas de mission, ou si les gabarits sont absents (compte gratuit).
4
+ import { getRules } from "../rules/rules-registry.js";
5
+ import { fmt } from "./rule-text.js";
6
+
7
+ /** @param {MissionPlan | null} m @returns {string} */
8
+ export function missionContextPrefix(m) {
9
+ const t = getRules().prefix;
10
+ if (!m || !t) return "";
11
+ const L = [t.open];
12
+ /** @template T @param {string} title @param {T[] | undefined} items @param {(x: T) => string} line */
13
+ const section = (title, items, line) => { if (items && items.length) { L.push(title); for (const x of items) L.push(line(x)); } };
14
+ if (m.projectName) L.push(fmt(t.project, { v: m.projectName }));
15
+ if (m.need) L.push(fmt(t.need, { v: m.need }));
16
+ section(t.personas, m.personas, (p) => fmt(t.persona, { role: p.role_name, mandate: p.mandate }));
17
+ section(t.constraints, m.constraints, (c) => fmt(c.invariant ? t.constraintInv : t.constraint, { text: c.text, inv: c.invariant || "" }));
18
+ section(t.pitfalls, m.pitfalls, (f) => fmt(t.pitfall, { never: f.never, always: f.always }));
19
+ section(t.glossary, m.glossary, (g) => fmt(t.term, { term: g.term, def: g.definition }));
20
+ section(t.ux, m.uxDirectives, (r) => fmt(t.uxRule, { v: r }));
21
+ if (m.designSystem) {
22
+ const ds = m.designSystem;
23
+ L.push(fmt(t.design, { style: ds.style || "", family: ds.family || "", density: ds.density || "", wcag: ds.wcag || "WCAG 2.1 AA" }));
24
+ section(t.violations, ds.violations, (v) => fmt(t.violation, { v: v.rule }));
25
+ }
26
+ if (m.impact) L.push(fmt(t.impact, { risk: String(m.impact.risk), tokens: String(m.impact.tokens) }));
27
+ L.push(t.close, "");
28
+ return L.join("\n");
29
+ }
30
+ // MODULE_COMPLETE
@@ -0,0 +1,36 @@
1
+ // src/engine/mission-guard.js — Garde-fou Phase 0 (Auto Mode) : bloque le patch
2
+ // Unified Diff d'une itération ultra_orchestrate non conforme au scratchpad
3
+ // mission (anti-patterns §3, invariants réglo §2, Design System §6 sur patches
4
+ // UI). Un patch non conforme n'est JAMAIS délivré : on renvoie le diagnostic
5
+ // conservé + le rapport de violations + l'alternative conforme (pas
6
+ // d'auto-retry — l'utilisateur re-soumet un patch corrigé). Fonction PURE
7
+ // exportée : capture la logique testable du garde-fou (le handler
8
+ // ultra_orchestrate vit dans src/mcp/handle-engine.js, invoquable isolément en test).
9
+ import { scanPatchForViolations } from "./patch-scan.js";
10
+
11
+ // Retourne { blocked, violations, message } — message null si non bloqué
12
+ // (le caller journalise alors l'itération).
13
+ /** @param {OrchestratorResult | null} result @param {MissionPlan | null} mission @returns {{ blocked: boolean, violations: Violation[], message: string | null }} */
14
+ export function checkMissionGuard(result, mission) {
15
+ if (!mission) return { blocked: false, violations: [], message: null };
16
+ const violations = scanPatchForViolations(result ? result.patch : "", mission);
17
+ if (violations.length === 0) return { blocked: false, violations: [], message: null };
18
+ /** @param {string} k @returns {string} */
19
+ const kindLabel = (k) =>
20
+ k === "constraint" ? "Invariant réglo"
21
+ : k === "design" ? "Hors charte Design"
22
+ : k === "ast" ? "Sécurité (AST)"
23
+ : "Anti-pattern";
24
+ const vLines = violations.map((v) =>
25
+ ` • [${kindLabel(v.kind)}] ${v.rule}\n` +
26
+ ` → Alternative conforme : ${v.alternative || "—"}`
27
+ ).join("\n");
28
+ const message =
29
+ `🚫 **Mission : patch bloqué par Phase 0** (${violations.length} violation(s)).\n` +
30
+ `Le patch viole une règle du scratchpad \`.pipcode/mission.md\` — il n'est pas délivré.\n\n` +
31
+ `### Diagnostic conservé\n${(result && result.analysis) || ""}\n\n` +
32
+ `### Violations détectées dans le Unified Diff\n${vLines}\n\n` +
33
+ `Corrigez le patch selon les alternatives ci-dessus, puis régénérez le patch.`;
34
+ return { blocked: true, violations, message };
35
+ }
36
+ // MODULE_COMPLETE
@@ -0,0 +1,23 @@
1
+ // src/engine/mission-helpers.js — Utilitaires mission : niveau de risque + nom de projet.
2
+
3
+ // Niveau de risque métier selon les domaines détectés.
4
+ /** @param {string[]} domainKeys @returns {string} */
5
+ export function riskLevel(domainKeys) {
6
+ const high = ["sante", "finance_bancaire", "juridique_conformite"];
7
+ const mid = ["ecommerce", "cloud_archi", "securite_appsec", "data_ml"];
8
+ if (domainKeys.some((k) => high.includes(k))) return "Élevé";
9
+ if (domainKeys.some((k) => mid.includes(k))) return "Moyen";
10
+ return "Bas";
11
+ }
12
+
13
+ // Déduit un nom de projet court depuis le besoin.
14
+ /** @param {string} need @returns {string} */
15
+ export function deriveProjectName(need) {
16
+ const t = String(need || "").trim();
17
+ if (!t) return "Projet";
18
+ const words = t.split(/\s+/).filter((w) => w.replace(/[.:;,()]+$/, "").length > 2).slice(0, 5);
19
+ const name = words.join(" ").replace(/[.:;,]+$/, "").trim();
20
+ if (!name) return "Projet";
21
+ return name.charAt(0).toUpperCase() + name.slice(1);
22
+ }
23
+ // MODULE_COMPLETE
@@ -0,0 +1,56 @@
1
+ // src/engine/mission-log.js — Journalise une itération ultra_orchestrate (best-effort).
2
+ import fs from "node:fs/promises";
3
+
4
+ // Ajoute une ligne au log §9 (après le header de section, ou en crée un nouveau).
5
+ /** @param {string} raw @param {string} line @returns {string} */
6
+ function appendIterationLine(raw, line) {
7
+ const m = raw.match(/(## 9\. LOG D'ITÉRATIONS ultra_orchestrate\s*\n(?:<!--.*?-->\s*\n)?)/s);
8
+ if (m) {
9
+ const idx = /** @type {number} */ (m.index);
10
+ return raw.slice(0, idx + m[1].length) + line + "\n" + raw.slice(idx + m[1].length);
11
+ }
12
+ return raw + "\n## 9. LOG D'ITÉRATIONS ultra_orchestrate\n" + line + "\n";
13
+ }
14
+
15
+ // Coche la prochaine case « Etape » non cochée de la checklist §4.
16
+ /** @param {string} next @returns {string} */
17
+ function tickNextStep(next) {
18
+ let ticked = false;
19
+ return next.replace(/^- \[ \] (Etape .+?)$/m, (full, label) => {
20
+ if (ticked) return full;
21
+ ticked = true;
22
+ return `- [x] ${label}`;
23
+ });
24
+ }
25
+
26
+ // Met à jour le sidecar JSON (checkedSteps + iterations) pour garder scanner et
27
+ // markdown cohérents. Best-effort (échec non bloquant).
28
+ /** @param {MissionPlan | null} plan @param {string} planPath @returns {Promise<void>} */
29
+ async function updateSidecar(plan, planPath) {
30
+ if (!plan) return;
31
+ plan.iterations = (plan.iterations || 0) + 1;
32
+ const steps = (plan.impact && plan.impact.steps) || [];
33
+ if (Array.isArray(plan.checkedSteps) && plan.checkedSteps.length === steps.length) {
34
+ const idx = plan.checkedSteps.findIndex((c) => !c);
35
+ if (idx >= 0) plan.checkedSteps[idx] = true;
36
+ }
37
+ try { await fs.writeFile(planPath, JSON.stringify(plan, null, 2), { encoding: "utf8", mode: 0o600 }); } catch { /* best-effort : échec non bloquant */ }
38
+ }
39
+
40
+ // Journalise une itération : ligne au log §9, coche la prochaine case non cochée
41
+ // de la checklist §4, incrémente le compteur. Best-effort (échec non bloquant).
42
+ /** @param {string} missionPath @param {string} summary @returns {Promise<void>} */
43
+ export async function logIteration(missionPath, summary) {
44
+ const planPath = missionPath + ".plan.json";
45
+ /** @type {MissionPlan | null} */ let plan = null;
46
+ try { plan = /** @type {MissionPlan} */ (JSON.parse(await fs.readFile(planPath, "utf8"))); } catch { /* plan absent/corrompu : plan reste null */ }
47
+ let raw;
48
+ try { raw = await fs.readFile(missionPath, "utf8"); } catch { return; }
49
+ const line = `- [${new Date().toISOString()}] ultra_orchestrate : ${summary}`;
50
+ let next = appendIterationLine(raw, line);
51
+ next = tickNextStep(next);
52
+ next = next.replace(/(Itérations\s*:\s*)(\d+)/, (_, p, n) => p + (parseInt(n, 10) + 1));
53
+ try { await fs.writeFile(missionPath, next, { encoding: "utf8", mode: 0o600 }); } catch { /* best-effort : échec non bloquant */ }
54
+ await updateSidecar(plan, planPath);
55
+ }
56
+ // MODULE_COMPLETE
@@ -0,0 +1,7 @@
1
+ // src/engine/mission-paths.js — Chemins du scratchpad mission.md + sidecar JSON (surchargeables via env).
2
+ import path from "node:path";
3
+
4
+ export const MISSION_PATH =
5
+ process.env.PLUGCTO_MISSION_PATH || path.join(process.cwd(), ".pipcode", "mission.md");
6
+ export const MISSION_PLAN_PATH = MISSION_PATH + ".plan.json";
7
+ // MODULE_COMPLETE
@@ -0,0 +1,51 @@
1
+ // src/engine/mission-store.js — Persistance du scratchpad mission.md + sidecar JSON.
2
+ import fs from "node:fs/promises";
3
+ import path from "node:path";
4
+ import { MISSION_PATH } from "./mission-paths.js";
5
+ import { parseMissionMarkdown } from "./md-parse.js";
6
+ import { buildMissionMarkdown } from "./md-build.js";
7
+
8
+ // Lit le contexte mission : priorise le sidecar JSON (porte les patterns), sinon
9
+ // parse le markdown. Retourne null si absent.
10
+ /** @param {string} [missionPath] @returns {Promise<MissionPlan | null>} */
11
+ export async function readMissionContext(missionPath = MISSION_PATH) {
12
+ const planPath = missionPath + ".plan.json";
13
+ try {
14
+ const raw = await fs.readFile(planPath, "utf8");
15
+ const plan = /** @type {MissionPlan} */ (JSON.parse(raw));
16
+ plan.patternsLoaded = true;
17
+ return plan;
18
+ } catch {
19
+ // Pas de sidecar : on parse le markdown (sans patterns → scanner inactif).
20
+ }
21
+ try {
22
+ const raw = await fs.readFile(missionPath, "utf8");
23
+ return parseMissionMarkdown(raw);
24
+ } catch {
25
+ return null;
26
+ }
27
+ }
28
+
29
+ // Écriture atomique : temp + rename, repli sur écriture directe si rename échoue.
30
+ /** @param {string} filePath @param {string} data @returns {Promise<void>} */
31
+ async function writeFileAtomic(filePath, data) {
32
+ const tmp = filePath + ".tmp-" + process.pid;
33
+ await fs.writeFile(tmp, data, { encoding: "utf8", mode: 0o600 });
34
+ try {
35
+ await fs.rename(tmp, filePath);
36
+ } catch {
37
+ await fs.writeFile(filePath, data, { encoding: "utf8", mode: 0o600 });
38
+ await fs.rm(tmp, { force: true });
39
+ }
40
+ }
41
+
42
+ // Écrit le scratchpad mission.md (template strict, atomic, 0600) + sidecar JSON
43
+ // du plan structuré (porte les patterns de détection, 0600).
44
+ /** @param {MissionPlan} plan @param {string} [missionPath] @returns {Promise<void>} */
45
+ export async function writeMission(plan, missionPath = MISSION_PATH) {
46
+ const dir = path.dirname(missionPath);
47
+ await fs.mkdir(dir, { recursive: true });
48
+ await writeFileAtomic(missionPath, buildMissionMarkdown(plan));
49
+ await writeFileAtomic(missionPath + ".plan.json", JSON.stringify(plan, null, 2));
50
+ }
51
+ // MODULE_COMPLETE
@@ -0,0 +1,35 @@
1
+ // src/engine/normalize-extraction.js — Normalise/valide la sortie JSON du LLM vers un MissionPlan.
2
+ import { arr, str } from "./coerce.js";
3
+ import { extractMissionDeterministic } from "./extract-deterministic.js";
4
+ import { normalizePersonas, normalizeConstraints, normalizePitfalls, normalizeGlossary } from "./normalize-pillars.js";
5
+ import { normalizeImpact, normalizeDesignSystem } from "./normalize-impact-design.js";
6
+
7
+ // Tolérant : comble les clés manquantes avec l'extracteur déterministe, coerce les types.
8
+ /** @param {unknown} raw @param {string} needText @returns {MissionPlan} */
9
+ export function normalizeExtraction(raw, needText) {
10
+ const need = String(needText || "").trim();
11
+ const fallback = extractMissionDeterministic(need);
12
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) return fallback;
13
+ const r = /** @type {Record<string, unknown>} */ (raw);
14
+ const projectName = str(r.project_name) || fallback.projectName;
15
+ const personas = normalizePersonas(r);
16
+ const constraints = normalizeConstraints(r);
17
+ const pitfalls = normalizePitfalls(r);
18
+ const glossary = normalizeGlossary(r);
19
+ const impact = normalizeImpact(r, fallback.impact);
20
+ const uxDirectives = arr(r.ux_directives).map(str).filter(Boolean);
21
+ const designSystem = normalizeDesignSystem(r, fallback.designSystem);
22
+ return {
23
+ projectName, need, domainKeys: fallback.domainKeys,
24
+ personas: personas.length ? personas : fallback.personas,
25
+ constraints: constraints.length ? constraints : fallback.constraints,
26
+ pitfalls: pitfalls.length ? pitfalls : fallback.pitfalls,
27
+ glossary: glossary.length ? glossary : fallback.glossary,
28
+ impact,
29
+ uxDirectives: uxDirectives.length ? uxDirectives : fallback.uxDirectives,
30
+ designSystem, fiches: fallback.fiches,
31
+ autoMode: false, createdAt: new Date().toISOString(), validatedAt: null,
32
+ iterations: 0, checkedSteps: impact.steps.map(() => false),
33
+ };
34
+ }
35
+ // MODULE_COMPLETE
@@ -0,0 +1,39 @@
1
+ // src/engine/normalize-impact-design.js — Normalisation LLM : impact + design system.
2
+ import { str, arr, obj } from "./coerce.js";
3
+ import { sanitizePatternsLLM } from "./pattern-sanitize.js";
4
+ import { ensureDataLeakGuard } from "./ux-violations-leak.js";
5
+
6
+ // Normalise la section impact (files/steps/tokens/risk) avec fallback déterministe.
7
+ /** @param {Record<string, unknown>} r @param {{ files: string[], steps: string[], tokens: number, risk: string }} fb @returns {{ files: string[], steps: string[], tokens: number, risk: string }} */
8
+ export function normalizeImpact(r, fb) {
9
+ const rawImpact = (r.impact && typeof r.impact === "object" && !Array.isArray(r.impact)) ? /** @type {Record<string, unknown>} */ (r.impact) : /** @type {Record<string, unknown>} */ ({});
10
+ const risk = ["Élevé", "Moyen", "Bas"].includes(/** @type {string} */ (rawImpact.risk)) ? /** @type {string} */ (rawImpact.risk) : fb.risk;
11
+ const tokens = Number.isFinite(rawImpact.tokens) ? Math.max(0, Math.round(/** @type {number} */ (rawImpact.tokens))) : fb.tokens;
12
+ const steps = arr(rawImpact.steps).map(str).filter(Boolean);
13
+ const files = arr(rawImpact.files).map(str).filter(Boolean);
14
+ return { files: files.length ? files : fb.files, steps: steps.length ? steps : fb.steps, tokens, risk };
15
+ }
16
+
17
+ // Normalise le design system ; violations de charte non négociables (fallback si omises).
18
+ /** @param {Record<string, unknown>} r @param {{ family: string, style: string, density: string, palette: string, microInteractions: string, wcag: string, violations: DesignViolation[] }} fbDs @returns {{ family: string, style: string, density: string, palette: string, microInteractions: string, wcag: string, violations: DesignViolation[] }} */
19
+ export function normalizeDesignSystem(r, fbDs) {
20
+ const rawDs = (r.design_system && typeof r.design_system === "object" && !Array.isArray(r.design_system)) ? /** @type {Record<string, unknown>} */ (r.design_system) : null;
21
+ if (!rawDs) return fbDs;
22
+ const designSystem = {
23
+ family: str(rawDs.family) || fbDs.family || "A",
24
+ style: str(rawDs.style) || fbDs.style || "",
25
+ density: str(rawDs.density) || fbDs.density || "",
26
+ palette: str(rawDs.palette) || fbDs.palette || "",
27
+ microInteractions: str(rawDs.microInteractions) || fbDs.microInteractions || "",
28
+ wcag: str(rawDs.wcag) || "WCAG 2.1 AA",
29
+ violations: ensureDataLeakGuard(arr(rawDs.violations).filter((v) => v && typeof v === "object").map((v) => {
30
+ const o = obj(v);
31
+ return { rule: str(o.rule), patterns: sanitizePatternsLLM(o.patterns) };
32
+ }).filter((v) => v.rule)),
33
+ };
34
+ if ((!designSystem.violations || designSystem.violations.length === 0) && fbDs.violations) {
35
+ designSystem.violations = fbDs.violations.map((v) => ({ rule: v.rule, patterns: [...(v.patterns || [])] }));
36
+ }
37
+ return designSystem;
38
+ }
39
+ // MODULE_COMPLETE
@@ -0,0 +1,36 @@
1
+ // src/engine/normalize-pillars.js — Normalisation des piliers LLM : personas/contraintes/failles/glossaire.
2
+ import { str, arr, obj } from "./coerce.js";
3
+ import { sanitizePatternsLLM } from "./pattern-sanitize.js";
4
+
5
+ /** @param {Record<string, unknown>} r @returns {{ role_name: string, scope: string, mandate: string }[]} */
6
+ export function normalizePersonas(r) {
7
+ return arr(r.personas).filter((p) => p && typeof p === "object").map((p) => {
8
+ const o = obj(p);
9
+ return { role_name: str(o.role_name) || "Expert métier", scope: str(o.scope), mandate: str(o.mandate) };
10
+ });
11
+ }
12
+
13
+ /** @param {Record<string, unknown>} r @returns {DomainConstraint[]} */
14
+ export function normalizeConstraints(r) {
15
+ return arr(r.constraints).filter((p) => p && typeof p === "object").map((p) => {
16
+ const o = obj(p);
17
+ return { text: str(o.text), invariant: str(o.invariant), patterns: sanitizePatternsLLM(o.patterns) };
18
+ });
19
+ }
20
+
21
+ /** @param {Record<string, unknown>} r @returns {DomainPitfall[]} */
22
+ export function normalizePitfalls(r) {
23
+ return arr(r.pitfalls).filter((p) => p && typeof p === "object").map((p) => {
24
+ const o = obj(p);
25
+ return { never: str(o.never), always: str(o.always), patterns: sanitizePatternsLLM(o.patterns) };
26
+ });
27
+ }
28
+
29
+ /** @param {Record<string, unknown>} r @returns {GlossaryEntry[]} */
30
+ export function normalizeGlossary(r) {
31
+ return arr(r.glossary).filter((p) => p && typeof p === "object").map((p) => {
32
+ const o = obj(p);
33
+ return { term: str(o.term), definition: str(o.definition) };
34
+ });
35
+ }
36
+ // MODULE_COMPLETE