@daniellins/power-claude 0.16.3

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.

Potentially problematic release.


This version of @daniellins/power-claude might be problematic. Click here for more details.

Files changed (467) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +121 -0
  3. package/bin/cli.js +124 -0
  4. package/installer/__tests__/doctor-ready-oracles.test.cjs +140 -0
  5. package/installer/__tests__/merge-settings.test.cjs +330 -0
  6. package/installer/adopt.js +161 -0
  7. package/installer/copy.js +203 -0
  8. package/installer/doctor.js +266 -0
  9. package/installer/install.js +155 -0
  10. package/installer/manifest.js +102 -0
  11. package/installer/motor-scripts.js +50 -0
  12. package/installer/motor-scripts.json +39 -0
  13. package/installer/uninstall.js +99 -0
  14. package/installer/update.js +376 -0
  15. package/installer/util.js +113 -0
  16. package/installer/wizard.js +64 -0
  17. package/package.json +26 -0
  18. package/scripts/__fixtures__/anti-path-demo/agents/ap-writer.md +13 -0
  19. package/scripts/__fixtures__/anti-path-demo/templates/write-tmpl.md +7 -0
  20. package/scripts/__fixtures__/anti-path-demo/workflows/wf-ap.smoke.json +10 -0
  21. package/scripts/__fixtures__/anti-path-demo/workflows/wf-ap.yaml +25 -0
  22. package/scripts/__fixtures__/assess-tier/retrofit-leve/pod/agents/content-chief.md +1 -0
  23. package/scripts/__fixtures__/assess-tier/retrofit-leve/pod/workflows/wf-criacao-redteam.yaml +3 -0
  24. package/scripts/__fixtures__/assess-tier/retrofit-leve/pod-spec.json +13 -0
  25. package/scripts/__fixtures__/fidelity/fake-green-average-in-band.yaml +21 -0
  26. package/scripts/__fixtures__/fidelity/fake-green-average.yaml +18 -0
  27. package/scripts/__fixtures__/fidelity/fake-green-denominator.yaml +19 -0
  28. package/scripts/__fixtures__/fidelity/fake-green-missing-evidence.yaml +18 -0
  29. package/scripts/__fixtures__/fidelity/fake-green-threshold.yaml +18 -0
  30. package/scripts/__fixtures__/fidelity/fake-green-weight.yaml +18 -0
  31. package/scripts/__fixtures__/fidelity/golden.yaml +31 -0
  32. package/scripts/__fixtures__/fidelity/trindade-incoherent.yaml +15 -0
  33. package/scripts/__fixtures__/forge-canonical/pod-spec-example.json +37 -0
  34. package/scripts/__fixtures__/migrate-squad/authored/agents/offer-engineer.md +17 -0
  35. package/scripts/__fixtures__/migrate-squad/authored/knowledge/no-source.md +7 -0
  36. package/scripts/__fixtures__/migrate-squad/authored/knowledge/value-stack-method.md +8 -0
  37. package/scripts/__fixtures__/migrate-squad/authored/specs/wf-sample.smoke.json +24 -0
  38. package/scripts/__fixtures__/migrate-squad/authored/specs/wf-sample.spec.json +45 -0
  39. package/scripts/__fixtures__/migrate-squad/legacy-squad/HEADLINE.md +3 -0
  40. package/scripts/__fixtures__/migrate-squad/legacy-squad/README.md +4 -0
  41. package/scripts/__fixtures__/migrate-squad/legacy-squad/agents/hormozi-offer.md +14 -0
  42. package/scripts/__fixtures__/migrate-squad/legacy-squad/checklists/dead-redundant.md +4 -0
  43. package/scripts/__fixtures__/migrate-squad/legacy-squad/checklists/method-guide.md +6 -0
  44. package/scripts/__fixtures__/migrate-squad/legacy-squad/checklists/veto-screen.md +4 -0
  45. package/scripts/__fixtures__/migrate-squad/legacy-squad/config/config.yaml +2 -0
  46. package/scripts/__fixtures__/migrate-squad/legacy-squad/data/case-library.md +3 -0
  47. package/scripts/__fixtures__/migrate-squad/legacy-squad/scripts/validate-mvn.py +16 -0
  48. package/scripts/__fixtures__/migrate-squad/legacy-squad/sops/sop-sample.md +3 -0
  49. package/scripts/__fixtures__/migrate-squad/legacy-squad/squad-io.yaml +14 -0
  50. package/scripts/__fixtures__/migrate-squad/legacy-squad/tasks/gather-context.md +7 -0
  51. package/scripts/__fixtures__/migrate-squad/legacy-squad/templates/output-tmpl.md +5 -0
  52. package/scripts/__fixtures__/migrate-squad/legacy-squad/workflows/wf-sample.yaml +24 -0
  53. package/scripts/__fixtures__/pipeline/pipelines/pipeline-checkpoint.yaml +21 -0
  54. package/scripts/__fixtures__/pipeline/pipelines/pipeline-demo.yaml +22 -0
  55. package/scripts/__fixtures__/pipeline/pods/pipe-a/agents/maker.md +13 -0
  56. package/scripts/__fixtures__/pipeline/pods/pipe-a/memory/learnings.consolidated.md +8 -0
  57. package/scripts/__fixtures__/pipeline/pods/pipe-a/memory/learnings.jsonl +83 -0
  58. package/scripts/__fixtures__/pipeline/pods/pipe-a/templates/make-tmpl.md +8 -0
  59. package/scripts/__fixtures__/pipeline/pods/pipe-a/workflows/wf-a.yaml +16 -0
  60. package/scripts/__fixtures__/pipeline/pods/pipe-b/agents/user.md +13 -0
  61. package/scripts/__fixtures__/pipeline/pods/pipe-b/memory/learnings.consolidated.md +8 -0
  62. package/scripts/__fixtures__/pipeline/pods/pipe-b/memory/learnings.jsonl +14 -0
  63. package/scripts/__fixtures__/pipeline/pods/pipe-b/templates/use-tmpl.md +6 -0
  64. package/scripts/__fixtures__/pipeline/pods/pipe-b/workflows/wf-b.yaml +16 -0
  65. package/scripts/__fixtures__/pod-run/agents/forge-chief.md +13 -0
  66. package/scripts/__fixtures__/pod-run/agents/pod-critic.md +13 -0
  67. package/scripts/__fixtures__/pod-run/templates/build-tmpl.md +8 -0
  68. package/scripts/__fixtures__/pod-run/templates/gate-tmpl.md +6 -0
  69. package/scripts/__fixtures__/pod-run/workflows/wf-min.yaml +43 -0
  70. package/scripts/__fixtures__/pod-run/workflows/wf-skip-modes.yaml +50 -0
  71. package/scripts/__fixtures__/pod-workflow/invalid-checkpoint-all-skippable.yaml +30 -0
  72. package/scripts/__fixtures__/pod-workflow/invalid-checkpoint-criteria-escalar-de-bloco.yaml +40 -0
  73. package/scripts/__fixtures__/pod-workflow/invalid-checkpoint-skip-mode-desconhecido.yaml +30 -0
  74. package/scripts/__fixtures__/pod-workflow/invalid-checkpoint-unknown-key.yaml +29 -0
  75. package/scripts/__fixtures__/pod-workflow/invalid-cycle.yaml +26 -0
  76. package/scripts/__fixtures__/pod-workflow/invalid-fix-loop-backedge.yaml +29 -0
  77. package/scripts/__fixtures__/pod-workflow/invalid-fix-loop-forward.yaml +29 -0
  78. package/scripts/__fixtures__/pod-workflow/invalid-handoff-open.yaml +27 -0
  79. package/scripts/__fixtures__/pod-workflow/invalid-knowledge-shape.yaml +18 -0
  80. package/scripts/__fixtures__/pod-workflow/invalid-missing-agent.yaml +15 -0
  81. package/scripts/__fixtures__/pod-workflow/invalid-missing-outputs.yaml +15 -0
  82. package/scripts/__fixtures__/pod-workflow/invalid-missing-template.yaml +15 -0
  83. package/scripts/__fixtures__/pod-workflow/invalid-missing-veto.yaml +13 -0
  84. package/scripts/__fixtures__/pod-workflow/invalid-template-not-on-disk.yaml +16 -0
  85. package/scripts/__fixtures__/pod-workflow/invalid-veto-prose.yaml +17 -0
  86. package/scripts/__fixtures__/pod-workflow/knowledge/a.md +4 -0
  87. package/scripts/__fixtures__/pod-workflow/knowledge/b.md +4 -0
  88. package/scripts/__fixtures__/pod-workflow/knowledge/c.md +4 -0
  89. package/scripts/__fixtures__/pod-workflow/templates/step-tmpl.md +7 -0
  90. package/scripts/__fixtures__/pod-workflow/valid-checkpoint-skip-modes.yaml +29 -0
  91. package/scripts/__fixtures__/pod-workflow/valid-fix-loop.yaml +30 -0
  92. package/scripts/__fixtures__/pod-workflow/valid-knowledge.yaml +30 -0
  93. package/scripts/__fixtures__/pod-workflow/valid-veto-always-true.yaml +20 -0
  94. package/scripts/__fixtures__/pod-workflow/valid.yaml +27 -0
  95. package/scripts/__fixtures__/smoke-canary/agents/canary-reporter.md +13 -0
  96. package/scripts/__fixtures__/smoke-canary/agents/canary-researcher.md +13 -0
  97. package/scripts/__fixtures__/smoke-canary/agents/canary-synth.md +13 -0
  98. package/scripts/__fixtures__/smoke-canary/memory/learnings.consolidated.md +10 -0
  99. package/scripts/__fixtures__/smoke-canary/memory/learnings.jsonl +448 -0
  100. package/scripts/__fixtures__/smoke-canary/templates/report-tmpl.md +9 -0
  101. package/scripts/__fixtures__/smoke-canary/templates/research-tmpl.md +7 -0
  102. package/scripts/__fixtures__/smoke-canary/templates/synth-tmpl.md +9 -0
  103. package/scripts/__fixtures__/smoke-canary/workflows/wf-canary.smoke.json +11 -0
  104. package/scripts/__fixtures__/smoke-canary/workflows/wf-canary.yaml +53 -0
  105. package/scripts/__fixtures__/source-audit/fake-green-bronze-unsignaled.yaml +10 -0
  106. package/scripts/__fixtures__/source-audit/fake-green-counts.yaml +9 -0
  107. package/scripts/__fixtures__/source-audit/fake-green-missing-evidence.yaml +9 -0
  108. package/scripts/__fixtures__/source-audit/fake-green-unclassified.yaml +10 -0
  109. package/scripts/__fixtures__/source-audit/fake-green-verdict.yaml +10 -0
  110. package/scripts/__fixtures__/source-audit/golden-blocked.yaml +10 -0
  111. package/scripts/__fixtures__/source-audit/golden.yaml +11 -0
  112. package/scripts/__fixtures__/sweep-forms/agents/forms-count.md +13 -0
  113. package/scripts/__fixtures__/sweep-forms/agents/forms-field.md +13 -0
  114. package/scripts/__fixtures__/sweep-forms/agents/forms-fileref.md +13 -0
  115. package/scripts/__fixtures__/sweep-forms/agents/forms-json.md +13 -0
  116. package/scripts/__fixtures__/sweep-forms/agents/forms-section.md +13 -0
  117. package/scripts/__fixtures__/sweep-forms/specs/wf-forms.spec.json +5 -0
  118. package/scripts/__fixtures__/sweep-forms/templates/t_count.md +5 -0
  119. package/scripts/__fixtures__/sweep-forms/templates/t_field.md +3 -0
  120. package/scripts/__fixtures__/sweep-forms/templates/t_fileref.md +3 -0
  121. package/scripts/__fixtures__/sweep-forms/templates/t_json.md +0 -0
  122. package/scripts/__fixtures__/sweep-forms/templates/t_section.md +3 -0
  123. package/scripts/__fixtures__/sweep-forms/workflows/wf-forms.smoke.json +13 -0
  124. package/scripts/__fixtures__/sweep-forms/workflows/wf-forms.yaml +67 -0
  125. package/scripts/__fixtures__/validate-story-acs-corpus/corpus-1-noop-state-json.fixture.md +18 -0
  126. package/scripts/__fixtures__/validate-story-acs-corpus/corpus-2-crash-buildworkflowyaml.fixture.md +26 -0
  127. package/scripts/__fixtures__/validate-story-acs-corpus/corpus-3-nasce-verde-skip-modes.fixture.md +29 -0
  128. package/scripts/__fixtures__/validate-story-acs-corpus/corpus-4-guarda-sem-irmao.fixture.md +17 -0
  129. package/scripts/__fixtures__/validate-story-acs-corpus/corpus-5a-diff-stat-sem-guarda.fixture.md +17 -0
  130. package/scripts/__fixtures__/validate-story-acs-corpus/corpus-5b-test-f-sobre-diretorio.fixture.md +20 -0
  131. package/scripts/__fixtures__/veto-checks/copy-clean.md +4 -0
  132. package/scripts/__fixtures__/veto-checks/copy-dashes.md +4 -0
  133. package/scripts/__fixtures__/veto-checks/n8n-bad.md +18 -0
  134. package/scripts/__fixtures__/veto-checks/n8n-ok.md +22 -0
  135. package/scripts/__fixtures__/veto-checks/page-bad.html +7 -0
  136. package/scripts/__fixtures__/veto-checks/phash/copy.png +0 -0
  137. package/scripts/__fixtures__/veto-checks/phash/distinct.png +0 -0
  138. package/scripts/__fixtures__/veto-checks/phash/distinct2.png +0 -0
  139. package/scripts/__fixtures__/veto-checks/phash/make-fixtures.cjs +203 -0
  140. package/scripts/__fixtures__/veto-checks/phash/ref.png +0 -0
  141. package/scripts/__fixtures__/veto-checks/slide-ok.html +15 -0
  142. package/scripts/__fixtures__/veto-checks/slide-overflow.html +14 -0
  143. package/scripts/__fixtures__/veto-checks/sweep-pod/agents/vc-checker.md +13 -0
  144. package/scripts/__fixtures__/veto-checks/sweep-pod/templates/check-tmpl.md +6 -0
  145. package/scripts/__fixtures__/veto-checks/sweep-pod/workflows/wf-vc.smoke.json +10 -0
  146. package/scripts/__fixtures__/veto-checks/sweep-pod/workflows/wf-vc.yaml +19 -0
  147. package/scripts/__fixtures__/voz-do-dono/README.md +23 -0
  148. package/scripts/__fixtures__/voz-do-dono/dono-escalar.yaml +9 -0
  149. package/scripts/__fixtures__/voz-do-dono/dono-guia.yaml +10 -0
  150. package/scripts/__fixtures__/voz-do-dono/peca-3a-pessoa.md +6 -0
  151. package/scripts/__fixtures__/voz-do-dono/peca-com-lexico-proibido.md +6 -0
  152. package/scripts/__fixtures__/voz-do-dono/peca-limpa-1a-pessoa.md +6 -0
  153. package/scripts/__tests__/derive-ready-transitions.test.cjs +269 -0
  154. package/scripts/__tests__/enforce-cycle-authority.test.cjs +869 -0
  155. package/scripts/__tests__/enforce-git-push-authority.test.cjs +701 -0
  156. package/scripts/__tests__/enforce-ready-oracles.test.cjs +745 -0
  157. package/scripts/__tests__/motor-scripts.test.cjs +339 -0
  158. package/scripts/__tests__/pc-run-pod-counter.test.cjs +58 -0
  159. package/scripts/__tests__/pc-run-pod.test.cjs +578 -0
  160. package/scripts/__tests__/validate-pod-workflow.test.cjs +90 -0
  161. package/scripts/__tests__/validate-story-acs.test.cjs +607 -0
  162. package/scripts/anti-path.test.cjs +163 -0
  163. package/scripts/arena.cjs +514 -0
  164. package/scripts/arena.test.cjs +213 -0
  165. package/scripts/brief-leitura-obrigatoria.test.cjs +364 -0
  166. package/scripts/consolidate-knowledge-runner.test.cjs +249 -0
  167. package/scripts/default-funcional.test.cjs +153 -0
  168. package/scripts/derive-ready-transitions.cjs +325 -0
  169. package/scripts/forge-additive.test.cjs +158 -0
  170. package/scripts/forge-assess-tier.cjs +205 -0
  171. package/scripts/forge-assess-tier.test.cjs +114 -0
  172. package/scripts/forge-canonical.test.cjs +624 -0
  173. package/scripts/forge-generate.cjs +1303 -0
  174. package/scripts/forge-harden-learning.cjs +921 -0
  175. package/scripts/forge-harden-learning.test.cjs +242 -0
  176. package/scripts/forge-ingest-memory.cjs +136 -0
  177. package/scripts/forge-upgrade-pod.cjs +368 -0
  178. package/scripts/forge.test.cjs +609 -0
  179. package/scripts/harden-knowledge.test.cjs +612 -0
  180. package/scripts/harness-g5-lite.cjs +243 -0
  181. package/scripts/harness-g5-lite.test.cjs +134 -0
  182. package/scripts/hn-carousel-contrato-entrada.test.cjs +96 -0
  183. package/scripts/migrate-squad-orchestration.test.cjs +146 -0
  184. package/scripts/migration-manifest-sf3.2.test.cjs +382 -0
  185. package/scripts/migration-manifest-sf3.3.test.cjs +170 -0
  186. package/scripts/migration-manifest-sf5.5.test.cjs +189 -0
  187. package/scripts/migration-manifest.cjs +1146 -0
  188. package/scripts/migration-manifest.test.cjs +186 -0
  189. package/scripts/npm-scope-invocation.test.cjs +194 -0
  190. package/scripts/owner-layer.cjs +442 -0
  191. package/scripts/owner-layer.test.cjs +358 -0
  192. package/scripts/pc-run-pipeline.cjs +289 -0
  193. package/scripts/pc-run-pipeline.test.cjs +256 -0
  194. package/scripts/pc-run-pod-countgte.test.cjs +66 -0
  195. package/scripts/pc-run-pod.cjs +1355 -0
  196. package/scripts/phash-gate.test.cjs +433 -0
  197. package/scripts/pod-export-compile.cjs +487 -0
  198. package/scripts/pod-export-compile.test.cjs +312 -0
  199. package/scripts/pod-export-executor.cjs +678 -0
  200. package/scripts/pod-export-executor.test.cjs +1341 -0
  201. package/scripts/pod-export-hardening.test.cjs +531 -0
  202. package/scripts/pod-export-postprocess.cjs +427 -0
  203. package/scripts/pod-export-postprocess.test.cjs +558 -0
  204. package/scripts/pod-export-queue-race.test.cjs +865 -0
  205. package/scripts/pod-export-release.cjs +473 -0
  206. package/scripts/pod-export-release.test.cjs +267 -0
  207. package/scripts/pod-export-service.cjs +1297 -0
  208. package/scripts/pod-export-service.test.cjs +489 -0
  209. package/scripts/pod-export-tools.cjs +470 -0
  210. package/scripts/pod-export-ui.cjs +805 -0
  211. package/scripts/pod-export-ui.test.cjs +301 -0
  212. package/scripts/pod-memory-budget.test.cjs +309 -0
  213. package/scripts/pod-memory-note.cjs +357 -0
  214. package/scripts/pod-memory-note.test.cjs +452 -0
  215. package/scripts/pod-memory.cjs +1252 -0
  216. package/scripts/pod-memory.test.cjs +951 -0
  217. package/scripts/render-png.test.cjs +307 -0
  218. package/scripts/smoke-isolation.test.cjs +100 -0
  219. package/scripts/smoke-pod-workflow.cjs +755 -0
  220. package/scripts/smoke-sweep.test.cjs +170 -0
  221. package/scripts/validate-adopt.js +235 -0
  222. package/scripts/validate-branding.js +84 -0
  223. package/scripts/validate-budgets.js +140 -0
  224. package/scripts/validate-fidelity-output.cjs +267 -0
  225. package/scripts/validate-fidelity-output.test.cjs +101 -0
  226. package/scripts/validate-install.js +230 -0
  227. package/scripts/validate-no-clone.cjs +220 -0
  228. package/scripts/validate-no-clone.test.cjs +77 -0
  229. package/scripts/validate-pipeline.cjs +145 -0
  230. package/scripts/validate-pod-workflow.cjs +878 -0
  231. package/scripts/validate-source-audit.cjs +170 -0
  232. package/scripts/validate-source-audit.test.cjs +90 -0
  233. package/scripts/validate-story-acs.cjs +862 -0
  234. package/scripts/validate-update-scope-motor.js +421 -0
  235. package/scripts/veto-checks/html-valid.cjs +92 -0
  236. package/scripts/veto-checks/lexical-check.cjs +62 -0
  237. package/scripts/veto-checks/phash-gate.cjs +589 -0
  238. package/scripts/veto-checks/render-gate.cjs +165 -0
  239. package/scripts/veto-checks/render-png.cjs +358 -0
  240. package/scripts/veto-checks.test.cjs +219 -0
  241. package/scripts/watcher-audit.cjs +178 -0
  242. package/template/.claude/CLAUDE.md +103 -0
  243. package/template/.claude/agents/pc-analyst.md +100 -0
  244. package/template/.claude/agents/pc-architect.md +32 -0
  245. package/template/.claude/agents/pc-dev.md +27 -0
  246. package/template/.claude/agents/pc-devops.md +45 -0
  247. package/template/.claude/agents/pc-planner.md +32 -0
  248. package/template/.claude/agents/pc-po.md +25 -0
  249. package/template/.claude/agents/pc-qa.md +25 -0
  250. package/template/.claude/commands/power/academy.md +9 -0
  251. package/template/.claude/commands/power/automation.md +9 -0
  252. package/template/.claude/commands/power/content-carousel.md +267 -0
  253. package/template/.claude/commands/power/content.md +9 -0
  254. package/template/.claude/commands/power/dev.md +9 -0
  255. package/template/.claude/commands/power/forge.md +224 -0
  256. package/template/.claude/commands/power/hormozi.md +221 -0
  257. package/template/.claude/commands/power/modo.md +57 -0
  258. package/template/.claude/hooks/README.md +114 -0
  259. package/template/.claude/hooks/context-budget.cjs +64 -0
  260. package/template/.claude/hooks/enforce-cycle-authority.cjs +457 -0
  261. package/template/.claude/hooks/enforce-git-push-authority.cjs +925 -0
  262. package/template/.claude/hooks/enforce-ready-oracles.cjs +502 -0
  263. package/template/.claude/rules/authority.md +76 -0
  264. package/template/.claude/rules/curadoria-de-conhecimento.md +30 -0
  265. package/template/.claude/rules/model-matrix.md +34 -0
  266. package/template/.claude/rules/orchestration.md +133 -0
  267. package/template/.claude/settings.json +50 -0
  268. package/template/.claude/skills/pc-apply-qa-fixes/SKILL.md +108 -0
  269. package/template/.claude/skills/pc-apply-qa-fixes/references/fix-loop.md +40 -0
  270. package/template/.claude/skills/pc-apply-qa-fixes/references/fix-request-template.md +70 -0
  271. package/template/.claude/skills/pc-arena/SKILL.md +96 -0
  272. package/template/.claude/skills/pc-close-story/SKILL.md +105 -0
  273. package/template/.claude/skills/pc-commit/SKILL.md +108 -0
  274. package/template/.claude/skills/pc-develop-story/SKILL.md +116 -0
  275. package/template/.claude/skills/pc-full-cycle/SKILL.md +150 -0
  276. package/template/.claude/skills/pc-pipeline/SKILL.md +63 -0
  277. package/template/.claude/skills/pc-pipeline/references/gate-templates.md +46 -0
  278. package/template/.claude/skills/pc-review-story/SKILL.md +129 -0
  279. package/template/.claude/skills/pc-review-story/references/false-positive-detection.md +50 -0
  280. package/template/.claude/skills/pc-review-story/references/nfr-assess.md +47 -0
  281. package/template/.claude/skills/pc-review-story/references/risk-profile.md +75 -0
  282. package/template/.claude/skills/pc-review-story/references/security-checklist.md +69 -0
  283. package/template/.claude/skills/pc-review-story/references/test-design.md +37 -0
  284. package/template/.claude/skills/pc-review-story/references/trace-requirements.md +46 -0
  285. package/template/.claude/skills/pc-run-pod/SKILL.md +123 -0
  286. package/template/.claude/skills/pc-validate-story/SKILL.md +114 -0
  287. package/template/.claude/skills/pc-wave/SKILL.md +133 -0
  288. package/template/.env.example +10 -0
  289. package/template/knowledge/analyst/brainstorming.md +40 -0
  290. package/template/knowledge/analyst/brief-skeleton.md +32 -0
  291. package/template/knowledge/analyst/competitor-skeleton.md +39 -0
  292. package/template/knowledge/analyst/elicitation-menu.md +49 -0
  293. package/template/knowledge/analyst/market-research-skeleton.md +36 -0
  294. package/template/knowledge/analyst/research-prompt-structure.md +48 -0
  295. package/template/knowledge/analyst/roi-skeleton.md +51 -0
  296. package/template/knowledge/voice-dna/README.md +99 -0
  297. package/template/knowledge/voice-dna/voice-dna.example.yaml +157 -0
  298. package/template/pods/README.md +64 -0
  299. package/template/pods/academy/README.md +37 -0
  300. package/template/pods/academy/agents/academy-chief.md +27 -0
  301. package/template/pods/academy/examples/palestra.example.md +142 -0
  302. package/template/pods/academy/pod-spec.json +44 -0
  303. package/template/pods/academy/templates/materiais-tmpl.md +17 -0
  304. package/template/pods/academy/templates/revisao-didatica-tmpl.md +20 -0
  305. package/template/pods/academy/workflows/wf-materiais-revisao.yaml +30 -0
  306. package/template/pods/authority-content/README.md +43 -0
  307. package/template/pods/authority-content/agents/content-chief.md +28 -0
  308. package/template/pods/authority-content/pod-spec.json +45 -0
  309. package/template/pods/authority-content/templates/criacao-na-voz-tmpl.md +17 -0
  310. package/template/pods/authority-content/templates/red-team-factual-tmpl.md +23 -0
  311. package/template/pods/authority-content/workflows/wf-criacao-redteam.yaml +30 -0
  312. package/template/pods/automation/README.md +37 -0
  313. package/template/pods/automation/agents/langgraph-architect.md +30 -0
  314. package/template/pods/automation/agents/n8n-architect.md +30 -0
  315. package/template/pods/automation/examples/README.md +46 -0
  316. package/template/pods/automation/examples/n8n-content-radar.example.json +380 -0
  317. package/template/pods/automation/knowledge/langgraph-patterns.md +126 -0
  318. package/template/pods/automation/knowledge/n8n-patterns.md +131 -0
  319. package/template/pods/automation/memory/README.md +16 -0
  320. package/template/pods/automation/memory/learnings.consolidated.md +8 -0
  321. package/template/pods/automation/memory/learnings.jsonl +0 -0
  322. package/template/pods/automation/pod-spec.json +109 -0
  323. package/template/pods/automation/specs/wf-langgraph-build.spec.json +107 -0
  324. package/template/pods/automation/squad-io.yaml +28 -0
  325. package/template/pods/automation/templates/lg-build-tmpl.md +17 -0
  326. package/template/pods/automation/templates/lg-design-tmpl.md +23 -0
  327. package/template/pods/automation/templates/lg-discover-tmpl.md +20 -0
  328. package/template/pods/automation/templates/lg-handoff-tmpl.md +20 -0
  329. package/template/pods/automation/templates/lg-test-tmpl.md +20 -0
  330. package/template/pods/automation/templates/n8n-build-tmpl.md +14 -0
  331. package/template/pods/automation/templates/n8n-design-tmpl.md +23 -0
  332. package/template/pods/automation/templates/n8n-discover-tmpl.md +20 -0
  333. package/template/pods/automation/templates/n8n-handoff-tmpl.md +20 -0
  334. package/template/pods/automation/templates/n8n-test-tmpl.md +20 -0
  335. package/template/pods/automation/workflows/wf-langgraph-build.smoke.json +11 -0
  336. package/template/pods/automation/workflows/wf-langgraph-build.yaml +64 -0
  337. package/template/pods/automation/workflows/wf-n8n-build.smoke.json +11 -0
  338. package/template/pods/automation/workflows/wf-n8n-build.yaml +65 -0
  339. package/template/pods/content-carousel/README.md +91 -0
  340. package/template/pods/content-carousel/agents/carousel-builder.md +28 -0
  341. package/template/pods/content-carousel/agents/carousel-visual.md +24 -0
  342. package/template/pods/content-carousel/agents/hn-curator.md +74 -0
  343. package/template/pods/content-carousel/config/heuristic-watcher-rubric.yaml +59 -0
  344. package/template/pods/content-carousel/knowledge/aquisicao-ancora.md +443 -0
  345. package/template/pods/content-carousel/knowledge/cta-brief.md +25 -0
  346. package/template/pods/content-carousel/knowledge/dono.example.yaml +45 -0
  347. package/template/pods/content-carousel/knowledge/identidade-visual-mascote.md +62 -0
  348. package/template/pods/content-carousel/knowledge/image-generation.md +62 -0
  349. package/template/pods/content-carousel/knowledge/selecao-de-ancora.md +60 -0
  350. package/template/pods/content-carousel/knowledge/slides-html.md +56 -0
  351. package/template/pods/content-carousel/memory/README.md +18 -0
  352. package/template/pods/content-carousel/memory/learnings.consolidated.md +6 -0
  353. package/template/pods/content-carousel/memory/learnings.jsonl +0 -0
  354. package/template/pods/content-carousel/pod-spec.json +100 -0
  355. package/template/pods/content-carousel/squad-io.yaml +10 -0
  356. package/template/pods/content-carousel/templates/assembly-tmpl.md +23 -0
  357. package/template/pods/content-carousel/templates/digest-tmpl.md +29 -0
  358. package/template/pods/content-carousel/templates/visuals-tmpl.md +20 -0
  359. package/template/pods/content-carousel/workflows/wf-content-carousel.smoke.json +23 -0
  360. package/template/pods/content-carousel/workflows/wf-content-carousel.yaml +80 -0
  361. package/template/pods/dev-ia/README.md +45 -0
  362. package/template/pods/dev-ia/agents/db-architect.md +33 -0
  363. package/template/pods/dev-ia/agents/ux-architect.md +29 -0
  364. package/template/pods/dev-ia/knowledge/db/migration-protocol.md +95 -0
  365. package/template/pods/dev-ia/knowledge/db/rls-patterns.md +101 -0
  366. package/template/pods/dev-ia/knowledge/db/schema-review-checklist.md +76 -0
  367. package/template/pods/dev-ia/memory/README.md +16 -0
  368. package/template/pods/dev-ia/memory/learnings.consolidated.md +8 -0
  369. package/template/pods/dev-ia/memory/learnings.jsonl +0 -0
  370. package/template/pods/dev-ia/pod-spec.json +34 -0
  371. package/template/pods/dev-ia/templates/schema-review-tmpl.md +23 -0
  372. package/template/pods/dev-ia/templates/ux-flow-review-tmpl.md +23 -0
  373. package/template/pods/dev-ia/tools/db-ops/backup-manager.sh +321 -0
  374. package/template/pods/dev-ia/tools/db-ops/best-practices-auditor.sh +534 -0
  375. package/template/pods/dev-ia/tools/db-ops/bootstrap-runner.sh +255 -0
  376. package/template/pods/dev-ia/tools/db-ops/data-loader.sh +616 -0
  377. package/template/pods/dev-ia/tools/db-ops/detect-database.sh +113 -0
  378. package/template/pods/dev-ia/tools/db-ops/explain-analyzer.sh +285 -0
  379. package/template/pods/dev-ia/tools/db-ops/health-checker.sh +323 -0
  380. package/template/pods/dev-ia/tools/db-ops/migration-runner.sh +308 -0
  381. package/template/pods/dev-ia/tools/db-ops/postgresql-schema-loader.sh +334 -0
  382. package/template/pods/dev-ia/tools/db-ops/query-runner.sh +171 -0
  383. package/template/pods/dev-ia/tools/db-ops/rls-policy-installer.sh +480 -0
  384. package/template/pods/dev-ia/tools/db-ops/rls-test-suite.sh +172 -0
  385. package/template/pods/dev-ia/tools/db-ops/schema-context-loader.sh +176 -0
  386. package/template/pods/hormozi-method/README.md +52 -0
  387. package/template/pods/hormozi-method/agents/acquisition-strategist.md +33 -0
  388. package/template/pods/hormozi-method/agents/conversion-copywriter.md +34 -0
  389. package/template/pods/hormozi-method/agents/monetization-architect.md +35 -0
  390. package/template/pods/hormozi-method/agents/offer-engineer.md +37 -0
  391. package/template/pods/hormozi-method/knowledge/core-four-leads.md +51 -0
  392. package/template/pods/hormozi-method/knowledge/grand-slam-offer.md +66 -0
  393. package/template/pods/hormozi-method/knowledge/growth-decision.md +38 -0
  394. package/template/pods/hormozi-method/knowledge/hooks-and-ads.md +56 -0
  395. package/template/pods/hormozi-method/knowledge/launch-email.md +31 -0
  396. package/template/pods/hormozi-method/knowledge/money-model.md +46 -0
  397. package/template/pods/hormozi-method/knowledge/opportunity-screening.md +44 -0
  398. package/template/pods/hormozi-method/knowledge/pages.md +25 -0
  399. package/template/pods/hormozi-method/knowledge/pos-lancamento-leads.md +56 -0
  400. package/template/pods/hormozi-method/knowledge/pricing.md +36 -0
  401. package/template/pods/hormozi-method/knowledge/retention-ltv.md +37 -0
  402. package/template/pods/hormozi-method/knowledge/scale-roadmap.md +35 -0
  403. package/template/pods/hormozi-method/knowledge/swipefiles-curados.md +44 -0
  404. package/template/pods/hormozi-method/knowledge/value-equation-diagnostic.md +77 -0
  405. package/template/pods/hormozi-method/memory/README.md +16 -0
  406. package/template/pods/hormozi-method/memory/learnings.consolidated.md +6 -0
  407. package/template/pods/hormozi-method/memory/learnings.jsonl +0 -0
  408. package/template/pods/hormozi-method/pod-spec.json +50 -0
  409. package/template/pods/hormozi-method/specs/wf-full-launch-sequence.spec.json +149 -0
  410. package/template/pods/hormozi-method/specs/wf-lead-magnet-pipeline.spec.json +125 -0
  411. package/template/pods/hormozi-method/specs/wf-money-model-design.spec.json +117 -0
  412. package/template/pods/hormozi-method/specs/wf-paid-ads-campaign.spec.json +132 -0
  413. package/template/pods/hormozi-method/specs/wf-scaling-diagnostic.spec.json +121 -0
  414. package/template/pods/hormozi-method/squad-io.yaml +43 -0
  415. package/template/pods/hormozi-method/templates/conversion-copywriter-tmpl.md +20 -0
  416. package/template/pods/hormozi-method/templates/launch-execucao-tmpl.md +17 -0
  417. package/template/pods/hormozi-method/templates/launch-fechamento-tmpl.md +17 -0
  418. package/template/pods/hormozi-method/templates/launch-pos-tmpl.md +17 -0
  419. package/template/pods/hormozi-method/templates/launch-preparacao-tmpl.md +17 -0
  420. package/template/pods/hormozi-method/templates/lm-copy-tmpl.md +17 -0
  421. package/template/pods/hormozi-method/templates/lm-distribuicao-tmpl.md +18 -0
  422. package/template/pods/hormozi-method/templates/lm-estrategia-tmpl.md +17 -0
  423. package/template/pods/hormozi-method/templates/mm-arquitetura-tmpl.md +18 -0
  424. package/template/pods/hormozi-method/templates/mm-copy-tmpl.md +17 -0
  425. package/template/pods/hormozi-method/templates/mm-diagnostico-tmpl.md +14 -0
  426. package/template/pods/hormozi-method/templates/monetization-architect-tmpl.md +17 -0
  427. package/template/pods/hormozi-method/templates/offer-engineer-tmpl.md +20 -0
  428. package/template/pods/hormozi-method/templates/paid-criativos-tmpl.md +19 -0
  429. package/template/pods/hormozi-method/templates/paid-fundacao-tmpl.md +17 -0
  430. package/template/pods/hormozi-method/templates/paid-teste-escala-tmpl.md +17 -0
  431. package/template/pods/hormozi-method/templates/scaling-diagnostico-tmpl.md +17 -0
  432. package/template/pods/hormozi-method/templates/scaling-priorizacao-tmpl.md +17 -0
  433. package/template/pods/hormozi-method/templates/scaling-roadmap-tmpl.md +17 -0
  434. package/template/pods/hormozi-method/workflows/wf-full-launch-sequence.smoke.json +12 -0
  435. package/template/pods/hormozi-method/workflows/wf-full-launch-sequence.yaml +56 -0
  436. package/template/pods/hormozi-method/workflows/wf-grand-slam-offer.smoke.json +11 -0
  437. package/template/pods/hormozi-method/workflows/wf-grand-slam-offer.yaml +45 -0
  438. package/template/pods/hormozi-method/workflows/wf-lead-magnet-pipeline.smoke.json +9 -0
  439. package/template/pods/hormozi-method/workflows/wf-lead-magnet-pipeline.yaml +45 -0
  440. package/template/pods/hormozi-method/workflows/wf-money-model-design.smoke.json +11 -0
  441. package/template/pods/hormozi-method/workflows/wf-money-model-design.yaml +45 -0
  442. package/template/pods/hormozi-method/workflows/wf-paid-ads-campaign.smoke.json +9 -0
  443. package/template/pods/hormozi-method/workflows/wf-paid-ads-campaign.yaml +46 -0
  444. package/template/pods/hormozi-method/workflows/wf-scaling-diagnostic.smoke.json +11 -0
  445. package/template/pods/hormozi-method/workflows/wf-scaling-diagnostic.yaml +44 -0
  446. package/template/pods/squad-forge/README.md +53 -0
  447. package/template/pods/squad-forge/agents/forge-chief.md +31 -0
  448. package/template/pods/squad-forge/agents/mind-extractor.md +29 -0
  449. package/template/pods/squad-forge/agents/pod-critic.md +26 -0
  450. package/template/pods/squad-forge/config/heuristic-watcher-rubric.yaml +74 -0
  451. package/template/pods/squad-forge/knowledge/fidelity-rubric.yaml +176 -0
  452. package/template/pods/squad-forge/knowledge/g6-memory-check.md +17 -0
  453. package/template/pods/squad-forge/knowledge/memory-ingest.md +16 -0
  454. package/template/pods/squad-forge/knowledge/source-audit-rubric.yaml +60 -0
  455. package/template/pods/squad-forge/memory/README.md +16 -0
  456. package/template/pods/squad-forge/memory/learnings.consolidated.md +8 -0
  457. package/template/pods/squad-forge/memory/learnings.jsonl +0 -0
  458. package/template/pods/squad-forge/pod-spec.json +166 -0
  459. package/template/pods/squad-forge/squad-io.yaml +13 -0
  460. package/template/pods/squad-forge/tasks/pipeline.md +158 -0
  461. package/template/pods/squad-forge/templates/discovery-tmpl.md +40 -0
  462. package/template/pods/squad-forge/templates/generation-tmpl.md +18 -0
  463. package/template/pods/squad-forge/templates/red-team-tmpl.md +17 -0
  464. package/template/pods/squad-forge/templates/register-tmpl.md +17 -0
  465. package/template/pods/squad-forge/templates/workflow-tmpl.yaml +101 -0
  466. package/template/pods/squad-forge/workflows/wf-forge.smoke.json +12 -0
  467. package/template/pods/squad-forge/workflows/wf-forge.yaml +60 -0
@@ -0,0 +1,951 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+ /**
4
+ * pod-memory.test.cjs — suíte própria NÃO-FAKE-GREEN do motor de memória (Story SF2.1).
5
+ *
6
+ * Prova em código, com pods sintéticos em diretório temporário, cada garantia
7
+ * categórica do ADR-003 / SPEC §G1 — não "relata sucesso", exercita o pior caso:
8
+ * AC1 append grava JSON válido append-only (não reescreve linhas anteriores).
9
+ * AC2 append REJEITA `tier: "critical"` sem `attributed_by` válido / enum inválido.
10
+ * AC3 consolidate NUNCA excede top-N=30 (centenas de entradas).
11
+ * AC4 consolidate NUNCA excede o budget de ~1.500 tokens (poda por score).
12
+ * AC5 CRITICAL NUNCA despejado — sobrevive a >M rotação + poda do top-N.
13
+ * AC6 teto de pins ≤10 força needsCuration; 11º não aceito silenciosamente.
14
+ * AC7 conflito determinístico e reprodutível (mesmo input → mesmo vencedor).
15
+ * AC8 promoção por contagem + obsolete rebaixa/arquiva.
16
+ * AC9 rotação além de M=500 arquiva (não deleta) sem remover fonte de pin.
17
+ * AC10 getConsolidatedForBrief só lê o consolidado — nunca o .jsonl (inspeção do
18
+ * código-fonte da função + prova de comportamento).
19
+ * + idempotência de consolidate (rodar 2x → mesmo resultado).
20
+ *
21
+ * Node >= 18, sem dependências externas. Padrão de forge.test.cjs/pc-run-pod.test.cjs.
22
+ */
23
+
24
+ const fs = require('node:fs');
25
+ const os = require('node:os');
26
+ const path = require('node:path');
27
+
28
+ const mem = require('../scripts/pod-memory.cjs');
29
+ const {
30
+ append, consolidate, rotate, getConsolidatedForBrief,
31
+ memoryPaths, detectConflicts, mergeEntries,
32
+ TOP_N, ROTATE_M, TOKEN_BUDGET, PIN_CAP,
33
+ } = mem;
34
+
35
+ const results = [];
36
+ function check(label, cond, detail) {
37
+ results.push({ label, ok: Boolean(cond), detail: detail || '' });
38
+ }
39
+
40
+ function tmpRoot(name) {
41
+ return fs.mkdtempSync(path.join(os.tmpdir(), `podmem-${name}-`));
42
+ }
43
+
44
+ // entrada normal sintética com ts explícito (evita colisão de Date.now())
45
+ function normalEntry(i, extra = {}) {
46
+ return {
47
+ id: `n${i}`,
48
+ ts: 1_700_000_000_000 + i * 1000,
49
+ trigger: `trigger-${i}`,
50
+ recommendation: `recomendação número ${i}`,
51
+ evidence: { count: 1 },
52
+ tier: 'normal',
53
+ source_run_id: `run-${i}`,
54
+ ...extra,
55
+ };
56
+ }
57
+
58
+ // ───────────────────────────────────────────────────────────────────────────
59
+ // AC1 — append grava JSON válido, append-only.
60
+ // ───────────────────────────────────────────────────────────────────────────
61
+ {
62
+ const root = tmpRoot('ac1');
63
+ append('p', normalEntry(1), { root });
64
+ const rawPath = memoryPaths('p', { root }).raw;
65
+ const firstLine = fs.readFileSync(rawPath, 'utf8').trim();
66
+ append('p', normalEntry(2), { root });
67
+ const lines = fs.readFileSync(rawPath, 'utf8').split('\n').filter(Boolean);
68
+ check('AC1 append grava 1 linha JSON parseável', (() => { try { JSON.parse(firstLine); return true; } catch { return false; } })());
69
+ check('AC1 append-only: 2 linhas após 2 appends', lines.length === 2, `${lines.length} linhas`);
70
+ check('AC1 append-only: 1ª linha inalterada após 2º append', lines[0] === firstLine);
71
+ fs.rmSync(root, { recursive: true, force: true });
72
+ }
73
+
74
+ // ───────────────────────────────────────────────────────────────────────────
75
+ // AC2 — append REJEITA critical sem atribuição válida (trava ii).
76
+ // ───────────────────────────────────────────────────────────────────────────
77
+ {
78
+ const root = tmpRoot('ac2');
79
+ const rawPath = memoryPaths('p', { root }).raw;
80
+ let rejectedNoAttr = false;
81
+ try { append('p', { trigger: 't', recommendation: 'r', tier: 'critical' }, { root }); }
82
+ catch { rejectedNoAttr = true; }
83
+ let rejectedBadEnum = false;
84
+ try { append('p', { trigger: 't', recommendation: 'r', tier: 'critical', attributed_by: 'agent' }, { root }); }
85
+ catch { rejectedBadEnum = true; }
86
+ // controle positivo: critical COM atribuição válida é aceito
87
+ const okHuman = append('p', { trigger: 't', recommendation: 'r', tier: 'critical', attributed_by: 'human' }, { root });
88
+ check('AC2 rejeita critical sem attributed_by', rejectedNoAttr);
89
+ check('AC2 rejeita critical com enum inválido ("agent")', rejectedBadEnum);
90
+ check('AC2 nada gravado nas 2 rejeições (só a válida)', fs.existsSync(rawPath) && fs.readFileSync(rawPath, 'utf8').split('\n').filter(Boolean).length === 1);
91
+ check('AC2 aceita critical com attributed_by="human"', okHuman && okHuman.ok === true);
92
+ fs.rmSync(root, { recursive: true, force: true });
93
+ }
94
+
95
+ // ───────────────────────────────────────────────────────────────────────────
96
+ // AC3 — consolidate nunca excede top-N=30 (centenas de entradas).
97
+ // ───────────────────────────────────────────────────────────────────────────
98
+ {
99
+ const root = tmpRoot('ac3');
100
+ for (let i = 0; i < 300; i++) append('p', normalEntry(i), { root });
101
+ const r = consolidate('p', { root });
102
+ check('AC3 topNCount ≤ 30 com 300 entradas', r.topNCount <= TOP_N, `topNCount=${r.topNCount}`);
103
+ // conta bullets da seção top-N no .md gerado
104
+ const md = fs.readFileSync(memoryPaths('p', { root }).consolidated, 'utf8');
105
+ const topSection = md.split('## Heurísticas (top-N)')[1] || '';
106
+ const bullets = topSection.split('\n').filter((l) => l.startsWith('- ')).length;
107
+ check('AC3 bullets do consolidado ≤ 30', bullets <= TOP_N, `${bullets} bullets`);
108
+ fs.rmSync(root, { recursive: true, force: true });
109
+ }
110
+
111
+ // ───────────────────────────────────────────────────────────────────────────
112
+ // AC4 — consolidate nunca excede o budget de tokens (~1.500), poda por score.
113
+ // ───────────────────────────────────────────────────────────────────────────
114
+ {
115
+ const root = tmpRoot('ac4');
116
+ // recomendações longas: 30 * ~400 chars ≈ 12k chars ≈ 3k tokens >> 1500.
117
+ const longText = 'x'.repeat(400);
118
+ for (let i = 0; i < 40; i++) {
119
+ append('p', normalEntry(i, { recommendation: `${longText}-${i}` }), { root });
120
+ }
121
+ const r = consolidate('p', { root });
122
+ const md = fs.readFileSync(memoryPaths('p', { root }).consolidated, 'utf8');
123
+ const estTokens = Math.ceil(Buffer.byteLength(md, 'utf8') / 4);
124
+ check('AC4 tokens do .md ≤ budget (~1500)', estTokens <= TOKEN_BUDGET, `~${estTokens} tokens`);
125
+ check('AC4 poda por budget reduziu abaixo do top-N (< 30)', r.topNCount < TOP_N, `topNCount=${r.topNCount}`);
126
+ check('AC4 r.tokens reportado ≤ budget', r.tokens <= TOKEN_BUDGET, `r.tokens=${r.tokens}`);
127
+ fs.rmSync(root, { recursive: true, force: true });
128
+ }
129
+
130
+ // ───────────────────────────────────────────────────────────────────────────
131
+ // AC5 — CRITICAL nunca despejado por rotação (>M) nem por poda do top-N.
132
+ // ───────────────────────────────────────────────────────────────────────────
133
+ {
134
+ const root = tmpRoot('ac5');
135
+ // 1 CRITICAL ANTIGO (ts mais baixo de todos) + M+50 normais mais novos.
136
+ append('p', {
137
+ id: 'crit-old', ts: 1, trigger: 'operação destrutiva',
138
+ recommendation: 'nunca rode rm -rf sem confirmação humana',
139
+ tier: 'critical', attributed_by: 'human', evidence: { count: 1 },
140
+ }, { root });
141
+ const total = ROTATE_M + 50;
142
+ for (let i = 0; i < total; i++) append('p', normalEntry(i), { root });
143
+
144
+ const c1 = consolidate('p', { root });
145
+ const rot = rotate('p', { root });
146
+ const c2 = consolidate('p', { root });
147
+ const md = fs.readFileSync(memoryPaths('p', { root }).consolidated, 'utf8');
148
+ const rawAfter = fs.readFileSync(memoryPaths('p', { root }).raw, 'utf8');
149
+
150
+ check('AC5 rotação ocorreu (>M)', rot.rotated > 0, `rotated=${rot.rotated}`);
151
+ check('AC5 CRITICAL pinado presente no .md pós rotação+poda', md.includes('rm -rf sem confirmação'));
152
+ check('AC5 seção "## CRITICAL (pinado)" existe', md.includes('## CRITICAL (pinado)'));
153
+ check('AC5 entrada-fonte do CRITICAL NÃO removida do log cru por rotação', rawAfter.includes('crit-old'));
154
+ check('AC5 pinnedCount=1 antes e depois', c1.pinnedCount === 1 && c2.pinnedCount === 1, `${c1.pinnedCount}/${c2.pinnedCount}`);
155
+ // e o CRITICAL não conta contra o teto do top-N: 30 normais ainda cabem.
156
+ check('AC5 top-N normal preenchido em paralelo ao pin', c2.topNCount === TOP_N, `topNCount=${c2.topNCount}`);
157
+ fs.rmSync(root, { recursive: true, force: true });
158
+ }
159
+
160
+ // ───────────────────────────────────────────────────────────────────────────
161
+ // AC6 — teto de pins ≤10 força needsCuration; 11º não aceito silenciosamente.
162
+ // ───────────────────────────────────────────────────────────────────────────
163
+ {
164
+ const root = tmpRoot('ac6');
165
+ for (let i = 0; i < 11; i++) {
166
+ append('p', {
167
+ id: `crit-${i}`, ts: 1000 + i, trigger: `dano-irreversível-${i}`,
168
+ recommendation: `trava crítica ${i}`, tier: 'critical',
169
+ attributed_by: i % 2 === 0 ? 'human' : 'deterministic_rule', evidence: { count: 1 },
170
+ }, { root });
171
+ }
172
+ const r = consolidate('p', { root });
173
+ const md = fs.readFileSync(memoryPaths('p', { root }).consolidated, 'utf8');
174
+ // L2 fix: asserção LIMPA se a seção de pins sumir (não crash/TypeError).
175
+ const critParts = md.split('## CRITICAL (pinado)');
176
+ check('AC6 seção "## CRITICAL (pinado)" presente (não sumiu)', critParts.length >= 2, `partes=${critParts.length}`);
177
+ const critSection = critParts.length >= 2 ? critParts[1].split('## Heurísticas')[0] : '';
178
+ const pinBullets = critSection.split('\n').filter((l) => l.startsWith('- ')).length;
179
+ check('AC6 needsCuration=true com 11 pins', r.needsCuration === true);
180
+ check('AC6 pinnedCount travado em 10 (não 11)', r.pinnedCount === PIN_CAP, `pinnedCount=${r.pinnedCount}`);
181
+ check('AC6 overflow tem exatamente o excedente (1)', r.overflow.length === 1, `overflow=${r.overflow.length}`);
182
+ check('AC6 .md escreve no máximo 10 pins', pinBullets === PIN_CAP, `${pinBullets} pins no .md`);
183
+ // o 11º (overflow) NÃO está escrito como pin, mas SEGUE no log cru (não descartado)
184
+ const overflowId = r.overflow[0].id;
185
+ const overflowRec = r.overflow[0].recommendation;
186
+ check('AC6 11º pin não escrito no .md como pin permanente', !md.includes(overflowRec) || md.split(overflowRec).length - 1 === 0);
187
+ check('AC6 11º pin retido no log cru (não descartado)', fs.readFileSync(memoryPaths('p', { root }).raw, 'utf8').includes(overflowId));
188
+ fs.rmSync(root, { recursive: true, force: true });
189
+ }
190
+
191
+ // ───────────────────────────────────────────────────────────────────────────
192
+ // AC7 — conflito determinístico e reprodutível.
193
+ // ───────────────────────────────────────────────────────────────────────────
194
+ {
195
+ const root = tmpRoot('ac7');
196
+ // par de negação limpo, mesmo trigger, evidência decide o vencedor.
197
+ const A = { id: 'A', ts: 100, trigger: 'deploy sexta', recommendation: 'usar cache', evidence: { count: 5 }, tier: 'normal' };
198
+ const B = { id: 'B', ts: 200, trigger: 'deploy sexta', recommendation: 'não usar cache', evidence: { count: 2 }, tier: 'normal' };
199
+ append('p', A, { root });
200
+ append('p', B, { root });
201
+
202
+ // reprodutibilidade: detectConflicts direto, 2x, mesmo vencedor/perdedor.
203
+ const merged = mergeEntries([A, B]);
204
+ const d1 = detectConflicts(merged);
205
+ const d2 = detectConflicts(mergeEntries([A, B]));
206
+ check('AC7 exatamente 1 conflito detectado', d1.conflicts.length === 1, `${d1.conflicts.length}`);
207
+ check('AC7 vencedor = maior evidência (A, count=5)', d1.conflicts[0].winner_id === 'A', d1.conflicts[0].winner_id);
208
+ check('AC7 perdedor = B', d1.conflicts[0].loser_id === 'B', d1.conflicts[0].loser_id);
209
+ check('AC7 reprodutível: run 2 = mesmo vencedor/perdedor', d2.conflicts[0].winner_id === d1.conflicts[0].winner_id && d2.conflicts[0].loser_id === d1.conflicts[0].loser_id);
210
+
211
+ // via consolidate: perdedora fora do consolidado, conflito registrado auditável.
212
+ consolidate('p', { root });
213
+ const md = fs.readFileSync(memoryPaths('p', { root }).consolidated, 'utf8');
214
+ const conflictsFile = memoryPaths('p', { root }).conflicts;
215
+ check('AC7 perdedora rebaixada (fora do consolidado)', !md.includes('não usar cache'));
216
+ check('AC7 vencedora presente no consolidado', md.includes('usar cache'));
217
+ check('AC7 conflito registrado de forma auditável (conflicts.jsonl)', fs.existsSync(conflictsFile) && fs.readFileSync(conflictsFile, 'utf8').includes('"loser_id":"B"'));
218
+
219
+ // desempate por contradicts EXPLÍCITO cruzando cores diferentes.
220
+ const C = { id: 'C', ts: 300, trigger: 'x', recommendation: 'faça alpha', evidence: { count: 1 }, tier: 'normal' };
221
+ const D = { id: 'D', ts: 400, trigger: 'y', recommendation: 'faça beta', evidence: { count: 9 }, tier: 'normal', contradicts: 'C' };
222
+ const dExplicit = detectConflicts(mergeEntries([C, D]));
223
+ check('AC7 contradicts explícito detecta conflito cross-trigger', dExplicit.conflicts.length === 1 && dExplicit.conflicts[0].winner_id === 'D');
224
+ fs.rmSync(root, { recursive: true, force: true });
225
+ }
226
+
227
+ // ───────────────────────────────────────────────────────────────────────────
228
+ // H1 (regressão do gate QA) — CRITICAL NÃO é despejado por conflito com NORMAL.
229
+ // Reproduz o ATAQUE1 do gate: 1 CRITICAL curado por humano ('nunca fazer deploy
230
+ // sexta') + 1 NORMAL oposto de MAIOR evidência ('fazer deploy sexta', count=10).
231
+ // SPEC §G1(e): CRITICAL só perde para OUTRO CRITICAL; aqui o pin PERMANECE, o
232
+ // NORMAL é rebaixado, o conflito é registrado e needsCuration é sinalizado.
233
+ // ───────────────────────────────────────────────────────────────────────────
234
+ {
235
+ const root = tmpRoot('h1');
236
+ append('p', {
237
+ id: 'crit-deploy', ts: 100, trigger: 'deploy sexta',
238
+ recommendation: 'nunca fazer deploy sexta', tier: 'critical',
239
+ attributed_by: 'human', evidence: { count: 1 },
240
+ }, { root });
241
+ append('p', {
242
+ id: 'norm-deploy', ts: 200, trigger: 'deploy sexta',
243
+ recommendation: 'fazer deploy sexta', tier: 'normal', evidence: { count: 10 },
244
+ }, { root });
245
+ const r = consolidate('p', { root });
246
+ const md = fs.readFileSync(memoryPaths('p', { root }).consolidated, 'utf8');
247
+ const conflictsFile = memoryPaths('p', { root }).conflicts;
248
+ const conflictsTxt = fs.existsSync(conflictsFile) ? fs.readFileSync(conflictsFile, 'utf8') : '';
249
+
250
+ check('H1 CRITICAL NÃO despejado por NORMAL de maior evidência (pinnedCount=1)', r.pinnedCount === 1, `pinnedCount=${r.pinnedCount}`);
251
+ check('H1 seção "## CRITICAL (pinado)" presente no .md', md.includes('## CRITICAL (pinado)'));
252
+ check('H1 recomendação crítica permanece no consolidado', md.includes('nunca fazer deploy sexta'));
253
+ check('H1 NORMAL oposto é rebaixado (fora do consolidado)', !md.includes('- **[deploy sexta]** fazer deploy sexta'));
254
+ check('H1 conflito registrado (both_critical=false, resolution critical-protected)', conflictsTxt.includes('"both_critical":false') && conflictsTxt.includes('critical-protected'));
255
+ check('H1 needsCuration sinalizado (resolução é curadoria humana, não automática)', r.needsCuration === true);
256
+ check('H1 NORMAL perdedor NÃO removido do log cru (arquivado, nunca deletado)', fs.readFileSync(memoryPaths('p', { root }).raw, 'utf8').includes('norm-deploy'));
257
+
258
+ // controle: crit-vs-crit CONTINUA resolvendo por evidência (via legítima da SPEC).
259
+ // par de negação limpo (mesmo núcleo, polaridade oposta) → conflito real.
260
+ const cc1 = { id: 'c1', ts: 100, trigger: 'g', recommendation: 'usar cache', tier: 'critical', attributed_by: 'human', evidence: { count: 2 } };
261
+ const cc2 = { id: 'c2', ts: 200, trigger: 'g', recommendation: 'não usar cache', tier: 'critical', attributed_by: 'human', evidence: { count: 9 } };
262
+ const root2 = tmpRoot('h1cc');
263
+ append('p', cc1, { root: root2 });
264
+ append('p', cc2, { root: root2 });
265
+ const rcc = consolidate('p', { root: root2 });
266
+ const dcc = detectConflicts(mergeEntries([cc1, cc2]));
267
+ check('H1 crit-vs-crit resolve por evidência (winner=c2, both_critical=true)', dcc.conflicts.length === 1 && dcc.conflicts[0].winner_id === 'c2' && dcc.conflicts[0].both_critical === true);
268
+ check('H1 crit-vs-crit deixa 1 pin sobrevivente', rcc.pinnedCount === 1, `pinnedCount=${rcc.pinnedCount}`);
269
+ fs.rmSync(root, { recursive: true, force: true });
270
+ fs.rmSync(root2, { recursive: true, force: true });
271
+ }
272
+
273
+ // ───────────────────────────────────────────────────────────────────────────
274
+ // AC8 — promoção por contagem + obsolete rebaixa/arquiva.
275
+ // ───────────────────────────────────────────────────────────────────────────
276
+ {
277
+ const root = tmpRoot('ac8');
278
+ // heurística FREQUENTE (3 ocorrências) deve ranquear acima de uma rara, mesmo
279
+ // com ts mais antigo — freq domina.
280
+ for (let k = 0; k < 3; k++) {
281
+ append('p', { id: `freq-${k}`, ts: 100 + k, trigger: 'padrão-A', recommendation: 'aplique A', evidence: { count: 1 }, tier: 'normal' }, { root });
282
+ }
283
+ append('p', { id: 'rare', ts: 9999, trigger: 'padrão-B', recommendation: 'aplique B raro', evidence: { count: 1 }, tier: 'normal' }, { root });
284
+ consolidate('p', { root });
285
+ const md = fs.readFileSync(memoryPaths('p', { root }).consolidated, 'utf8');
286
+ const idxFreq = md.indexOf('aplique A');
287
+ const idxRare = md.indexOf('aplique B raro');
288
+ check('AC8 heurística confirmada 3x aparece', idxFreq >= 0);
289
+ check('AC8 promoção por contagem: frequente ranqueia acima da rara', idxFreq >= 0 && idxFreq < idxRare, `freq@${idxFreq} rare@${idxRare}`);
290
+
291
+ // obsolete: mesma heurística marcada obsolete (ts mais novo) some do consolidado.
292
+ const root2 = tmpRoot('ac8b');
293
+ append('p', { id: 'o1', ts: 100, trigger: 'padrão-X', recommendation: 'faça X', evidence: { count: 1 }, tier: 'normal' }, { root: root2 });
294
+ append('p', { id: 'o2', ts: 200, trigger: 'padrão-X', recommendation: 'faça X', evidence: { count: 1 }, tier: 'normal', obsolete: true }, { root: root2 });
295
+ consolidate('p', { root: root2 });
296
+ const md2 = fs.readFileSync(memoryPaths('p', { root: root2 }).consolidated, 'utf8');
297
+ check('AC8 entrada obsolete rebaixada (fora do consolidado)', !md2.includes('faça X'));
298
+ fs.rmSync(root, { recursive: true, force: true });
299
+ fs.rmSync(root2, { recursive: true, force: true });
300
+ }
301
+
302
+ // ───────────────────────────────────────────────────────────────────────────
303
+ // AC9 — rotação além de M=500 arquiva (não deleta) sem remover fonte de pin.
304
+ // ───────────────────────────────────────────────────────────────────────────
305
+ {
306
+ const root = tmpRoot('ac9');
307
+ const total = ROTATE_M + 30;
308
+ for (let i = 0; i < total; i++) append('p', normalEntry(i), { root });
309
+ const rawBefore = fs.readFileSync(memoryPaths('p', { root }).raw, 'utf8').split('\n').filter(Boolean).length;
310
+ const rot = rotate('p', { root });
311
+ const paths = memoryPaths('p', { root });
312
+ const rawAfter = fs.readFileSync(paths.raw, 'utf8').split('\n').filter(Boolean).length;
313
+ const archiveFiles = fs.existsSync(paths.archiveDir) ? fs.readdirSync(paths.archiveDir).filter((f) => /^learnings-.*\.jsonl$/.test(f)) : [];
314
+ const archivedCount = archiveFiles.length ? fs.readFileSync(path.join(paths.archiveDir, archiveFiles[0]), 'utf8').split('\n').filter(Boolean).length : 0;
315
+ check('AC9 log cru truncado a M=500', rawAfter === ROTATE_M, `rawAfter=${rawAfter}`);
316
+ check('AC9 excedente arquivado (não deletado): before = after + arquivado', rawBefore === rawAfter + archivedCount, `${rawBefore} = ${rawAfter} + ${archivedCount}`);
317
+ check('AC9 arquivo de archive criado', archiveFiles.length === 1, archiveFiles.join(','));
318
+ // idempotência da rotação: rodar de novo com ≤M não rotaciona.
319
+ const rot2 = rotate('p', { root });
320
+ check('AC9 rotação idempotente (2ª chamada não rotaciona)', rot2.rotated === 0, `rotated2=${rot2.rotated}`);
321
+ void rot;
322
+ fs.rmSync(root, { recursive: true, force: true });
323
+ }
324
+
325
+ // ───────────────────────────────────────────────────────────────────────────
326
+ // AC10 — getConsolidatedForBrief só lê o consolidado, nunca o .jsonl.
327
+ // ───────────────────────────────────────────────────────────────────────────
328
+ {
329
+ const src = getConsolidatedForBrief.toString();
330
+ check('AC10 corpo de getConsolidatedForBrief NÃO referencia .jsonl', !/jsonl/i.test(src));
331
+ check('AC10 corpo referencia o consolidado', /consolidated/i.test(src));
332
+
333
+ // prova de comportamento: com log cru cheio e consolidado curado distinto,
334
+ // a função retorna o CONSOLIDADO, não o conteúdo do cru.
335
+ const root = tmpRoot('ac10');
336
+ append('p', normalEntry(1, { recommendation: 'SEGREDO-DO-CRU' }), { root });
337
+ consolidate('p', { root });
338
+ const brief = getConsolidatedForBrief('p', { root });
339
+ check('AC10 retorna conteúdo do consolidado (.md header)', brief.includes('Memória consolidada'));
340
+ // (a recomendação aparece no consolidado por curadoria — mas via .md, não via
341
+ // leitura do .jsonl; provamos que a ausência do consolidado retorna vazio)
342
+ const root2 = tmpRoot('ac10b');
343
+ append('p', normalEntry(1), { root: root2 }); // só cru, sem consolidar
344
+ check('AC10 sem consolidado gerado → retorna "" (não lê o cru)', getConsolidatedForBrief('p', { root: root2 }) === '');
345
+ fs.rmSync(root, { recursive: true, force: true });
346
+ fs.rmSync(root2, { recursive: true, force: true });
347
+ }
348
+
349
+ // ───────────────────────────────────────────────────────────────────────────
350
+ // IDEMPOTÊNCIA — consolidate 2x sem novas entradas produz o mesmo .md.
351
+ // ───────────────────────────────────────────────────────────────────────────
352
+ {
353
+ const root = tmpRoot('idem');
354
+ for (let i = 0; i < 50; i++) append('p', normalEntry(i), { root });
355
+ append('p', { id: 'A', ts: 100, trigger: 'g', recommendation: 'usar cache', evidence: { count: 5 }, tier: 'normal' }, { root });
356
+ append('p', { id: 'B', ts: 200, trigger: 'g', recommendation: 'não usar cache', evidence: { count: 2 }, tier: 'normal' }, { root });
357
+ consolidate('p', { root });
358
+ const md1 = fs.readFileSync(memoryPaths('p', { root }).consolidated, 'utf8');
359
+ // M1 fix: comparar BYTES de conflicts.jsonl, não só contagem de linhas.
360
+ const conf1 = fs.readFileSync(memoryPaths('p', { root }).conflicts);
361
+ consolidate('p', { root });
362
+ const md2 = fs.readFileSync(memoryPaths('p', { root }).consolidated, 'utf8');
363
+ const conf2 = fs.readFileSync(memoryPaths('p', { root }).conflicts);
364
+ const conf1Lines = conf1.toString('utf8').split('\n').filter(Boolean).length;
365
+ check('IDEM consolidado idêntico em 2 execuções', md1 === md2);
366
+ check('IDEM conflicts.jsonl byte-idêntico em 2 execuções (M1: ts determinístico)', conf1.equals(conf2), `sha1=${require('node:crypto').createHash('sha1').update(conf1).digest('hex').slice(0, 8)} vs ${require('node:crypto').createHash('sha1').update(conf2).digest('hex').slice(0, 8)}`);
367
+ check('IDEM conflicts.jsonl não duplica (1 conflito)', conf1Lines === 1, `${conf1Lines} linhas`);
368
+ fs.rmSync(root, { recursive: true, force: true });
369
+ }
370
+
371
+ // ───────────────────────────────────────────────────────────────────────────
372
+ // L3 (regressão do gate QA) — conflicts.jsonl PRESERVA histórico após as fontes
373
+ // de um conflito antigo saírem do log cru (rotação). Um consolidate posterior com
374
+ // OUTRO conflito não pode sobrescrever/perder o registro antigo (§G1 "registrado").
375
+ // ───────────────────────────────────────────────────────────────────────────
376
+ {
377
+ const root = tmpRoot('l3');
378
+ const paths = memoryPaths('p', { root });
379
+ append('p', { id: 'A', ts: 100, trigger: 'g1', recommendation: 'usar cache', evidence: { count: 5 }, tier: 'normal' }, { root });
380
+ append('p', { id: 'B', ts: 200, trigger: 'g1', recommendation: 'não usar cache', evidence: { count: 2 }, tier: 'normal' }, { root });
381
+ consolidate('p', { root });
382
+ const afterFirst = fs.readFileSync(paths.conflicts, 'utf8');
383
+ check('L3 conflito A/B registrado', afterFirst.includes('"loser_id":"B"'));
384
+
385
+ // simula rotação: as fontes A e B saem do log cru; entra um NOVO conflito C/D.
386
+ fs.writeFileSync(paths.raw,
387
+ `${JSON.stringify({ id: 'C', ts: 300, trigger: 'g2', recommendation: 'fazer deploy', evidence: { count: 3 }, tier: 'normal' })}\n` +
388
+ `${JSON.stringify({ id: 'D', ts: 400, trigger: 'g2', recommendation: 'não fazer deploy', evidence: { count: 1 }, tier: 'normal' })}\n`);
389
+ consolidate('p', { root });
390
+ const afterSecond = fs.readFileSync(paths.conflicts, 'utf8');
391
+ check('L3 registro antigo A/B PRESERVADO pós-rotação das fontes', afterSecond.includes('"loser_id":"B"'));
392
+ check('L3 novo conflito C/D também registrado', afterSecond.includes('"loser_id":"D"'));
393
+ fs.rmSync(root, { recursive: true, force: true });
394
+ }
395
+
396
+ // ───────────────────────────────────────────────────────────────────────────
397
+ // SF7.6 / QA CONCERNS-1 — heurística ENDURECIDA sobrevive à rotação (>M flood).
398
+ // AC3 "nunca removida do log": a heurística-fonte E o registro type:"hardening"
399
+ // são protegidos da rotação; sem eles a anotação 🔒 do consolidado desaparece.
400
+ // Kill-test coberto: sabotar isProtected (TYPE_HARDENING/hardenedRefKeys) na
401
+ // rotate() torna estas verificações VERMELHAS.
402
+ // ───────────────────────────────────────────────────────────────────────────
403
+ {
404
+ const root = tmpRoot('hardrot');
405
+ // heurística com ts MUITO ANTIGO = candidata natural nº 1 da rotação; o registro
406
+ // de hardening não tem `ts` (ordena como 0) = candidato ainda mais antigo — ambos
407
+ // só sobrevivem se a PROTEÇÃO existir.
408
+ const hardSrc = {
409
+ id: 'HARD1',
410
+ ts: 1_600_000_000_000,
411
+ trigger: 'travessoes no texto final',
412
+ recommendation: 'nunca usar travessao; prefira virgula ou dois-pontos',
413
+ evidence: { count: 2 },
414
+ tier: 'normal',
415
+ };
416
+ append('p', hardSrc, { root });
417
+ mem.appendHardening('p', {
418
+ ref: mem.dedupKey(hardSrc),
419
+ target: {
420
+ workflowId: 'wf-x',
421
+ step: 's1',
422
+ assertion: { type: 'command_check', command: 'lexical-check.cjs', args: ['--max', '0'] },
423
+ },
424
+ approved_by: 'daniel',
425
+ timestamp: '2026-07-29T00:00:00Z',
426
+ }, { root });
427
+ // flood: > ROTATE_M entradas mais novas forçam rotação real
428
+ const total = ROTATE_M + 40;
429
+ for (let i = 0; i < total; i++) append('p', normalEntry(i), { root });
430
+ const rot = rotate('p', { root });
431
+ check('SF7.6 flood > M dispara rotação real (excedente arquivado)', rot.rotated > 0, `rotated=${rot.rotated}`);
432
+ const rawAfter = fs.readFileSync(memoryPaths('p', { root }).raw, 'utf8');
433
+ check('SF7.6 heurística-fonte ENDURECIDA sobrevive à rotação (nunca removida do log)',
434
+ rawAfter.includes('"id":"HARD1"'));
435
+ check('SF7.6 registro type:"hardening" sobrevive à rotação (marca de auditoria)',
436
+ rawAfter.includes('"type":"hardening"') && rawAfter.includes(`"ref":${JSON.stringify(mem.dedupKey(hardSrc))}`));
437
+ consolidate('p', { root });
438
+ const md = getConsolidatedForBrief('p', { root });
439
+ // SF11.4/ADR-022 (DF-SF11.4-1): o consolidado mantém a REFERÊNCIA AGREGADA; o detalhe
440
+ // íntegro (asserção, aprovador, momento) migrou para memory/learnings.hardened.md.
441
+ // A garantia de SF7.6 não muda — a trilha continua intacta pós-rotação, mudou ONDE.
442
+ const detail = fs.readFileSync(memoryPaths('p', { root }).hardenedDetail, 'utf8');
443
+ check('SF7.6 🔒 segue no consolidado PÓS-rotação (referência agregada intacta)',
444
+ md.includes('## Endurecidas (🔒 → enforcement)') &&
445
+ md.includes('veto command_check em s1 (1)') &&
446
+ md.includes('memory/learnings.hardened.md'));
447
+ check('SF11.4 detalhe íntegro da endurecida vive na trilha auditável PÓS-rotação',
448
+ detail.includes('🔒 **[travessoes no texto final]**') &&
449
+ detail.includes('endurecida → veto command_check em s1') &&
450
+ detail.includes('por daniel, 2026-07-29T00:00:00Z'));
451
+ check('SF11.4 o artefato INJETADO não carrega mais o texto da endurecida (só a referência)',
452
+ !md.includes('nunca usar travessao; prefira virgula ou dois-pontos'));
453
+ check('SF7.6 endurecida segue FORA do top-N ativo pós-rotação',
454
+ !md.split('## Endurecidas')[0].includes('**[travessoes no texto final]**'));
455
+ fs.rmSync(root, { recursive: true, force: true });
456
+ }
457
+
458
+ // ───────────────────────────────────────────────────────────────────────────
459
+ // SF8.3 — ok_pattern FORA do ranking do top-N; log cru intacto; kill-test
460
+ // automatizado (cópia sabotada do motor prova que a fixture exercita a exclusão).
461
+ // Evidência de campo (triagem 2026-07-29): 26/122 entradas, slot #1 (x10/x9/x7).
462
+ // ───────────────────────────────────────────────────────────────────────────
463
+ {
464
+ const root = tmpRoot('okpat');
465
+ const T0 = 1_700_000_000_000;
466
+ // heurísticas REAIS (ocorrências menores que o ok_pattern — o caso de campo)
467
+ append('p', { id: 'real1a', ts: T0 + 1000, trigger: 'falha de veto no editorial', recommendation: 'sempre validar a contagem de secoes antes de entregar', evidence: { count: 2 } }, { root });
468
+ append('p', { id: 'real1b', ts: T0 + 2000, trigger: 'falha de veto no editorial', recommendation: 'sempre validar a contagem de secoes antes de entregar', evidence: { count: 1 } }, { root });
469
+ append('p', { id: 'real2', ts: T0 + 1500, trigger: 'timeout de api externa', recommendation: 'configurar retry com backoff exponencial', evidence: { count: 1 } }, { root });
470
+ // ok_pattern TIPADO na origem (buildOkLearning pós-SF8.3) ×10 — mesmo dedupKey,
471
+ // ts MAIS RECENTES: sem a exclusão, freq×recency o colocaria disparado em 1º.
472
+ for (let i = 0; i < 10; i++) {
473
+ append('p', { id: `ok-t${i}`, ts: T0 + 10_000 + i * 1000, type: mem.TYPE_OK_PATTERN, trigger: 'workflow "wf-x" completou sem bloqueio', recommendation: 'caminho seguido (3 etapa(s) verificadas em disco) é válido; repetir a sequência', evidence: { steps_done: 3 }, tier: 'normal' }, { root });
474
+ }
475
+ // ok_pattern LEGADO (entrada de campo pré-SF8.3: SEM `type`, assinatura do produtor) ×5
476
+ for (let i = 0; i < 5; i++) {
477
+ append('p', { id: `ok-l${i}`, ts: T0 + 30_000 + i * 1000, trigger: 'workflow "wf-y" completou sem bloqueio', recommendation: 'caminho seguido (2 etapa(s) verificadas em disco) é válido; repetir a sequência', evidence: { steps_done: 2 }, tier: 'normal' }, { root });
478
+ }
479
+ const rawPath = memoryPaths('p', { root }).raw;
480
+ const linesBefore = fs.readFileSync(rawPath, 'utf8').split('\n').filter(Boolean);
481
+ check('SF8.3 log cru recebeu TODAS as entradas (18: 3 reais + 10 tipadas + 5 legadas)', linesBefore.length === 18, `linhas=${linesBefore.length}`);
482
+ check('SF8.3 append preservou o campo type na linha crua', linesBefore.some((l) => l.includes('"type":"ok_pattern"')));
483
+
484
+ const res = consolidate('p', { root });
485
+ const md = getConsolidatedForBrief('p', { root });
486
+ const topSection = md.split('## Heurísticas (top-N)')[1] || '';
487
+ const firstBullet = (topSection.split('\n').find((l) => l.startsWith('- ')) || '');
488
+ check('SF8.3 consolidado SEM ok_pattern (tipado E legado) no top-N', !md.includes('completou sem bloqueio'));
489
+ check('SF8.3 slot #1 do top-N é heurística REAL (a que o ok_pattern ocupava)', firstBullet.includes('falha de veto no editorial'), firstBullet);
490
+ check('SF8.3 heurísticas reais presentes (nada além de ok_pattern excluído)', md.includes('timeout de api externa') && res.topNCount === 2);
491
+ check('SF8.3 okPatternCount reportado pela consolidação', res.okPatternCount === 15, `okPatternCount=${res.okPatternCount}`);
492
+ const linesAfter = fs.readFileSync(rawPath, 'utf8').split('\n').filter(Boolean);
493
+ check('SF8.3 log cru INTACTO após consolidação (18 linhas, byte-idênticas)', linesAfter.length === 18 && linesAfter.join('\n') === linesBefore.join('\n'));
494
+
495
+ // ── KILL-TEST AUTOMATIZADO (AC5): cópia SABOTADA do motor, exclusão desligada ──
496
+ // Prova que a fixture exercita o MECANISMO: sem a exclusão, a MESMA memória
497
+ // consolida com o ok_pattern de volta ao slot #1 (as asserções acima ficariam
498
+ // vermelhas). Sabota as DUAS vias (type e assinatura legada).
499
+ const srcPath = path.join(__dirname, 'pod-memory.cjs');
500
+ const src = fs.readFileSync(srcPath, 'utf8');
501
+ const sab1 = src.replace("const TYPE_OK_PATTERN = 'ok_pattern';", "const TYPE_OK_PATTERN = '__sabotado__';");
502
+ check('SF8.3 kill-test: sabotagem da via `type` aplicou', sab1 !== src);
503
+ const sab2 = sab1.replace('const OK_PATTERN_LEGACY_RE = /^workflow ".+" completou sem bloqueio$/;', 'const OK_PATTERN_LEGACY_RE = /$^/;');
504
+ check('SF8.3 kill-test: sabotagem da via legada aplicou', sab2 !== sab1);
505
+ const sabPath = path.join(root, 'pod-memory-sabotada.cjs');
506
+ fs.writeFileSync(sabPath, sab2);
507
+ const sabotada = require(sabPath);
508
+ // mesma memória, pod-cópia isolado (não sobrescreve o consolidado correto de 'p')
509
+ const p2mem = memoryPaths('p2', { root });
510
+ fs.mkdirSync(p2mem.dir, { recursive: true });
511
+ fs.copyFileSync(rawPath, p2mem.raw);
512
+ sabotada.consolidate('p2', { root });
513
+ const mdSab = fs.readFileSync(p2mem.consolidated, 'utf8');
514
+ const topSab = mdSab.split('## Heurísticas (top-N)')[1] || '';
515
+ const firstSab = (topSab.split('\n').find((l) => l.startsWith('- ')) || '');
516
+ check('SF8.3 kill-test: SEM a exclusão o ok_pattern VOLTA ao consolidado', mdSab.includes('completou sem bloqueio'));
517
+ check('SF8.3 kill-test: e retoma o slot #1 (x10) — a fixture ficaria vermelha', firstSab.includes('completou sem bloqueio') && firstSab.includes('(x10)'), firstSab);
518
+ fs.rmSync(root, { recursive: true, force: true });
519
+ }
520
+
521
+ // ───────────────────────────────────────────────────────────────────────────
522
+ // SF10.10 / ADR-012 Adendo (DF-SF10.10-1) — UNIÃO DISCRIMINADA por `target.kind`.
523
+ // AC1 alvo `knowledge` = {pod,kind,file,sha_before,sha_after}, SEM diff embutido;
524
+ // renderConsolidated ganha ramo com linha fixa e curta.
525
+ // AC11 retrocompat: registro LEGADO sem `kind` continua tratado como `veto` —
526
+ // mesma validação byte a byte, mesma linha no consolidado, mesma demoção,
527
+ // sem migração e sem reescrita do log.
528
+ // + `kind` desconhecido FALHA LOUD (nunca degrada para o ramo `veto`).
529
+ // ───────────────────────────────────────────────────────────────────────────
530
+ {
531
+ const root = tmpRoot('kind');
532
+ const kSrc = {
533
+ id: 'K1', ts: 1_700_000_100_000,
534
+ trigger: 'slides sem riqueza grafica',
535
+ recommendation: 'usar blocos visuais com hierarquia tipografica',
536
+ evidence: { count: 2 }, tier: 'normal',
537
+ };
538
+ append('p', kSrc, { root });
539
+ const SHA_A = 'a'.repeat(64);
540
+ const SHA_B = 'b'.repeat(64);
541
+
542
+ // ── validação por RAMO ────────────────────────────────────────────────────
543
+ const throwsWith = (fn, re) => {
544
+ try { fn(); return false; } catch (e) { return re.test(e.message); }
545
+ };
546
+ check('SF10.10 kind desconhecido ⇒ THROW fail-loud (nunca cai no ramo veto)',
547
+ throwsWith(() => mem.appendHardening('p', {
548
+ ref: mem.dedupKey(kSrc), approved_by: 'daniel',
549
+ target: { kind: 'assertion_magica', file: 'x.md', sha_before: SHA_A, sha_after: SHA_B },
550
+ }, { root }), /kind desconhecido/));
551
+ check('SF10.10 kind "knowledge" sem `file` ⇒ THROW',
552
+ throwsWith(() => mem.appendHardening('p', {
553
+ ref: mem.dedupKey(kSrc), approved_by: 'daniel',
554
+ target: { kind: 'knowledge', sha_before: SHA_A, sha_after: SHA_B },
555
+ }, { root }), /exige `file`/));
556
+ check('SF10.10 kind "knowledge" com path traversal em `file` ⇒ THROW (marca de auditoria, não caminho)',
557
+ throwsWith(() => mem.appendHardening('p', {
558
+ ref: mem.dedupKey(kSrc), approved_by: 'daniel',
559
+ target: { kind: 'knowledge', file: '../../etc/passwd.md', sha_before: SHA_A, sha_after: SHA_B },
560
+ }, { root }), /nome SIMPLES sob knowledge\//));
561
+ check('SF10.10 kind "knowledge" com sha inválido ⇒ THROW (sha256 hex de 64)',
562
+ throwsWith(() => mem.appendHardening('p', {
563
+ ref: mem.dedupKey(kSrc), approved_by: 'daniel',
564
+ target: { kind: 'knowledge', file: 'slides-html.md', sha_before: 'nope', sha_after: SHA_B },
565
+ }, { root }), /sha256 hex de 64/));
566
+ check('SF10.10 kind "knowledge" sem approved_by ⇒ THROW (aprovação humana obrigatória nos DOIS alvos)',
567
+ throwsWith(() => mem.appendHardening('p', {
568
+ ref: mem.dedupKey(kSrc),
569
+ target: { kind: 'knowledge', file: 'slides-html.md', sha_before: SHA_A, sha_after: SHA_B },
570
+ }, { root }), /`approved_by` é obrigatório/));
571
+ check('SF10.10 ramo veto INALTERADO: target sem workflowId/step/assertion ⇒ THROW com a mensagem de ADR-012',
572
+ throwsWith(() => mem.appendHardening('p', {
573
+ ref: mem.dedupKey(kSrc), approved_by: 'daniel', target: { pod: 'p' },
574
+ }, { root }), /exige \{ workflowId, step, assertion:\{type,\.\.\.\} \}/));
575
+
576
+ // ── registro do alvo novo: forma FIXA, sem diff ───────────────────────────
577
+ const { record } = mem.appendHardening('p', {
578
+ ref: mem.dedupKey(kSrc),
579
+ target: { kind: 'knowledge', file: 'slides-html.md', sha_before: SHA_A, sha_after: SHA_B },
580
+ approved_by: 'daniel',
581
+ timestamp: '2026-08-01T00:00:00.000Z',
582
+ }, { root });
583
+ check('SF10.10 registro knowledge = {pod,kind,file,sha_before,sha_after} e NADA além (sem diff embutido)',
584
+ record.target.kind === 'knowledge' && record.target.pod === 'p' &&
585
+ record.target.file === 'slides-html.md' &&
586
+ record.target.sha_before === SHA_A && record.target.sha_after === SHA_B &&
587
+ Object.keys(record.target).sort().join(',') === 'file,kind,pod,sha_after,sha_before',
588
+ JSON.stringify(record.target));
589
+ check('SF10.10 registro knowledge não carrega `diff` nem trecho de prosa',
590
+ !JSON.stringify(record).includes('diff'));
591
+
592
+ const res = consolidate('p', { root });
593
+ const md = getConsolidatedForBrief('p', { root });
594
+ check('SF10.10 consolidate NÃO lança com registro kind:"knowledge" (o defeito de maior raio de dano)',
595
+ res.hardenedCount === 1);
596
+ // SF11.4/DF-SF11.4-1: a linha detalhada continua existindo, BYTE A BYTE — só mudou de
597
+ // artefato (saiu do injetado, foi para a trilha auditável). No consolidado fica a
598
+ // referência agregada com o alvo nomeado e a contagem.
599
+ const detailK = fs.readFileSync(memoryPaths('p', { root }).hardenedDetail, 'utf8');
600
+ check('SF10.10 linha 🔒 do alvo knowledge é fixa e curta (sem diff/trecho/contagem) — agora na trilha auditável',
601
+ detailK.includes('- 🔒 **[slides sem riqueza grafica]** usar blocos visuais com hierarquia tipografica — endurecida → knowledge/slides-html.md _(por daniel, 2026-08-01T00:00:00.000Z)_'),
602
+ detailK);
603
+ check('SF11.4 consolidado cita o alvo AGREGADO e a trilha, sem o texto da endurecida',
604
+ md.includes('knowledge/slides-html.md (1)') &&
605
+ md.includes('memory/learnings.hardened.md') &&
606
+ !md.includes('usar blocos visuais com hierarquia tipografica'),
607
+ md);
608
+ check('SF10.10 heurística com alvo knowledge DEMOVIDA do top-N ativo (mesma regra do alvo veto)',
609
+ !md.split('## Endurecidas')[0].includes('**[slides sem riqueza grafica]**'));
610
+
611
+ // ── QA/F3: fail-loud SIMÉTRICO — `knowledge` malformado LANÇA, como o `veto` ──
612
+ // Antes: um registro editado à mão sem `file` renderizava calado "knowledge/undefined"
613
+ // num artefato ORÇADO que entra no bundle do export, enquanto o ramo irmão lançava.
614
+ {
615
+ const r2 = tmpRoot('kindmal');
616
+ const src = {
617
+ id: 'M1', ts: 1_700_000_400_000, trigger: 'trigger malformado',
618
+ recommendation: 'recomendacao malformada', evidence: { count: 1 }, tier: 'normal',
619
+ };
620
+ append('p', src, { root: r2 });
621
+ const plant = (target) => {
622
+ fs.appendFileSync(memoryPaths('p', { root: r2 }).raw, `${JSON.stringify({
623
+ type: 'hardening', ref: mem.dedupKey(src), target, approved_by: 'daniel',
624
+ timestamp: '2026-08-01T00:00:00.000Z',
625
+ })}\n`);
626
+ };
627
+ const consolidateThrows = (re) => {
628
+ try { consolidate('p', { root: r2 }); return false; } catch (e) { return re.test(e.message); }
629
+ };
630
+ const rawBase = fs.readFileSync(memoryPaths('p', { root: r2 }).raw, 'utf8');
631
+ for (const [label, target] of [
632
+ ['sem `file`', { kind: 'knowledge' }],
633
+ ['`file` não-string', { kind: 'knowledge', file: 42 }],
634
+ ['`file` com traversal', { kind: 'knowledge', file: '../../../etc/passwd' }],
635
+ ]) {
636
+ fs.writeFileSync(memoryPaths('p', { root: r2 }).raw, rawBase);
637
+ plant(target);
638
+ check(`SF10.10/F3 kind "knowledge" ${label} ⇒ consolidate LANÇA (nunca renderiza calado)`,
639
+ consolidateThrows(/kind "knowledge" malformado/));
640
+ }
641
+ fs.writeFileSync(memoryPaths('p', { root: r2 }).raw, rawBase);
642
+ plant({ kind: 'knowledge', file: 'ok.md', sha_before: 'a'.repeat(64), sha_after: 'b'.repeat(64) });
643
+ check('SF10.10/F3 registro BEM-FORMADO segue renderizando normalmente (a simetria não quebra o caminho feliz)',
644
+ consolidate('p', { root: r2 }).hardenedCount === 1 &&
645
+ getConsolidatedForBrief('p', { root: r2 }).includes('knowledge/ok.md (1)') &&
646
+ fs.readFileSync(memoryPaths('p', { root: r2 }).hardenedDetail, 'utf8')
647
+ .includes('endurecida → knowledge/ok.md'));
648
+ fs.rmSync(r2, { recursive: true, force: true });
649
+ }
650
+
651
+ // ── AC11: registro LEGADO (sem `kind`) segue tratado como `veto` ───────────
652
+ const legacySrc = {
653
+ id: 'L1', ts: 1_700_000_200_000,
654
+ trigger: 'travessoes no texto final',
655
+ recommendation: 'nunca usar travessao; prefira virgula',
656
+ evidence: { count: 3 }, tier: 'normal',
657
+ };
658
+ append('p', legacySrc, { root });
659
+ // escrito DIRETO no log (sem passar por appendHardening), no formato pré-SF10.10
660
+ fs.appendFileSync(memoryPaths('p', { root }).raw, `${JSON.stringify({
661
+ type: 'hardening',
662
+ ref: mem.dedupKey(legacySrc),
663
+ target: { pod: 'p', workflowId: 'wf-x', step: 's1', assertion: { type: 'command_check', command: 'lexical-check.cjs' } },
664
+ approved_by: 'daniel',
665
+ timestamp: '2026-07-28T00:00:00.000Z',
666
+ })}\n`);
667
+ const res2 = consolidate('p', { root });
668
+ const md2 = getConsolidatedForBrief('p', { root });
669
+ const detail2 = fs.readFileSync(memoryPaths('p', { root }).hardenedDetail, 'utf8');
670
+ check('SF10.10/AC11 registro LEGADO sem `kind` é tratado como veto (linha idêntica à de SF7.6, na trilha auditável)',
671
+ detail2.includes('- 🔒 **[travessoes no texto final]** nunca usar travessao; prefira virgula — endurecida → veto command_check em s1 _(por daniel, 2026-07-28T00:00:00.000Z)_'),
672
+ detail2);
673
+ check('SF10.10/AC11 legado e alvo novo COEXISTEM no mesmo consolidado (sem migração do log)',
674
+ res2.hardenedCount === 2 && md2.includes('knowledge/slides-html.md (1)') &&
675
+ md2.includes('veto command_check em s1 (1)'));
676
+ check('SF10.10/AC11 legado também DEMOVIDO do top-N (comportamento de SF7.6 preservado)',
677
+ !md2.split('## Endurecidas')[0].includes('**[travessoes no texto final]**'));
678
+ check('SF10.10/AC11 log cru NÃO foi reescrito (registro legado permanece sem `kind`)',
679
+ fs.readFileSync(memoryPaths('p', { root }).raw, 'utf8')
680
+ .split('\n').filter(Boolean)
681
+ .some((l) => l.includes('"type":"hardening"') && l.includes('"workflowId"') && !l.includes('"kind"')));
682
+
683
+ // ── DF-SF10.10-3: listCandidates = diferença de conjuntos, READ-ONLY ──────
684
+ append('p', {
685
+ id: 'C1', ts: 1_700_000_300_000, trigger: 'api externa lenta',
686
+ recommendation: 'usar timeout explicito', evidence: { count: 1 }, tier: 'normal',
687
+ }, { root });
688
+ consolidate('p', { root });
689
+ const rawBytes = fs.readFileSync(memoryPaths('p', { root }).raw, 'utf8');
690
+ const mdBytes = fs.readFileSync(memoryPaths('p', { root }).consolidated, 'utf8');
691
+ const cands = mem.listCandidates('p', { root });
692
+ check('SF10.10/AC4 listCandidates lista SÓ as consolidadas sem registro de hardening',
693
+ cands.length === 1 && cands[0].trigger === 'api externa lenta',
694
+ JSON.stringify(cands));
695
+ check('SF10.10/AC4 candidato traz occurrences e o id da última entrada-fonte (colável em --learning)',
696
+ cands[0].occurrences === 1 && cands[0].last_id === 'C1' && cands[0].section === 'top-n');
697
+ check('SF10.10/AC4 listCandidates é READ-ONLY (log e consolidado byte-idênticos após a chamada)',
698
+ fs.readFileSync(memoryPaths('p', { root }).raw, 'utf8') === rawBytes &&
699
+ fs.readFileSync(memoryPaths('p', { root }).consolidated, 'utf8') === mdBytes);
700
+ check('SF10.10/AC4 listCandidates é determinística (2 chamadas ⇒ mesmo resultado)',
701
+ JSON.stringify(mem.listCandidates('p', { root })) === JSON.stringify(cands));
702
+ check('SF10.10/AC4 listCandidates NÃO classifica conteúdo vs mecanizável (sem oráculo — DF-SF10.10-3)',
703
+ !Object.keys(cands[0]).some((k) => /kind|tipo|class|mecaniz|conteudo|conteúdo/i.test(k)),
704
+ Object.keys(cands[0]).join(','));
705
+
706
+ fs.rmSync(root, { recursive: true, force: true });
707
+ }
708
+
709
+ // ═══════════════════════════════════════════════════════════════════════════
710
+ // SF11.4b / ADR-023 — ADMISSÃO ÚNICA e ALVO DE CUSTO ACEITO PELO DONO
711
+ //
712
+ // O defeito que estas verificações fecham (ADR-023 §C2, medido no pod real): com os
713
+ // DOIS canos ativos, 13 heurísticas ficavam fora do brief e o mecanismo anunciava 1.
714
+ // Por isso NENHUMA verificação aqui aceita "evicted > 0": o oráculo é IGUALDADE EXATA
715
+ // contra uma contagem INDEPENDENTE, lida do ARTEFATO renderizado (quais triggers
716
+ // entraram no consolidado), nunca do próprio `evicted`. Um teste que só exigisse
717
+ // "anunciou algo" passaria com o defeito dentro.
718
+ // ═══════════════════════════════════════════════════════════════════════════
719
+
720
+ /** Heurística de tamanho REAL (distribuição medida do hn-carousel: ~150-230 B). */
721
+ function sizedEntry(i) {
722
+ return {
723
+ id: `S${i}`,
724
+ ts: 1_700_000_000_000 + i * 1000,
725
+ trigger: `situacao de campo numero ${i} do pod em producao`,
726
+ recommendation: `recomendacao verificavel numero ${i} com tamanho realista de heuristica ` +
727
+ 'de campo produzida por rodada real do pod, com objeto e acao explicitos no texto',
728
+ evidence: { count: 1 },
729
+ tier: 'normal',
730
+ };
731
+ }
732
+
733
+ /** Contagem INDEPENDENTE: quantas do pool NÃO aparecem no artefato renderizado. */
734
+ function absentFromArtifact(mdText, poolSize) {
735
+ let absent = 0;
736
+ for (let i = 0; i < poolSize; i++) {
737
+ if (!mdText.includes(`**[${sizedEntry(i).trigger}]**`)) absent += 1;
738
+ }
739
+ return absent;
740
+ }
741
+
742
+ function writePodSpec(root, pod, spec) {
743
+ const dir = path.join(root, pod);
744
+ fs.mkdirSync(dir, { recursive: true });
745
+ fs.writeFileSync(path.join(dir, 'pod-spec.json'), JSON.stringify(spec, null, 2));
746
+ }
747
+
748
+ // ── AC2 — réplica do C2: pool 42, os DOIS cortes ativos, o número BATE ──────
749
+ {
750
+ const root = tmpRoot('sf114b-c2');
751
+ const POOL = 42;
752
+ for (let i = 0; i < POOL; i++) append('c2', sizedEntry(i), { root });
753
+ const r = consolidate('c2', { root });
754
+ const md = fs.readFileSync(memoryPaths('c2', { root }).consolidated, 'utf8');
755
+ const absent = absentFromArtifact(md, POOL);
756
+ const byTopN = r.evicted.filter((e) => e.reason === 'top_n').length;
757
+ const byBudget = r.evicted.filter((e) => e.reason === 'budget').length;
758
+
759
+ check('SF11.4b/AC2 os DOIS cortes estão ativos na fixture (senão o teste não exercita C2)',
760
+ byTopN > 0 && byBudget > 0, `top_n=${byTopN} budget=${byBudget} ranked=${r.topNCount}`);
761
+ check(`SF11.4b/AC2 evicted.length (${r.evicted.length}) == ausentes do consolidado (${absent}) — IGUALDADE EXATA, contagem independente do artefato`,
762
+ r.evicted.length === absent, `ranked=${r.topNCount} pool=${POOL}`);
763
+ check('SF11.4b/AC2 conservação: ranked + evicted == pool ativo (nada fora da contabilidade)',
764
+ r.topNCount + r.evicted.length === POOL, `${r.topNCount}+${r.evicted.length} vs ${POOL}`);
765
+ check('SF11.4b/AC2 pré-story o número seria SUB-RELATADO: só o cano do orçamento anunciaria',
766
+ byBudget < r.evicted.length, `anunciaria ${byBudget} com ${r.evicted.length} fora`);
767
+ check('SF11.4b/AC1 toda não-admissão tem reason ∈ {top_n, budget} e trigger/recommendation/score',
768
+ r.evicted.every((e) => (e.reason === 'top_n' || e.reason === 'budget') &&
769
+ typeof e.trigger === 'string' && typeof e.recommendation === 'string' && typeof e.score === 'number'),
770
+ JSON.stringify(r.evicted[0]));
771
+ check('SF11.4b/AC1 nenhuma evicted está no artefato e nenhuma ranked está fora dele (reason descreve o REAL)',
772
+ r.evicted.every((e) => !md.includes(`**[${e.trigger}]**`)));
773
+ // invariante de ORDEM entre as duas vias: o corte por custo tira a CAUDA de `ranked`
774
+ // (scores mais altos que os do excedente do teto de atenção). Se um dia isso inverter,
775
+ // a admissão deixou de ser única — passou a haver duas ordenações.
776
+ check('SF11.4b/AC1 ordem: todo score cortado por custo ≥ todo score cortado por atenção',
777
+ Math.min(...r.evicted.filter((e) => e.reason === 'budget').map((e) => e.score)) >=
778
+ Math.max(...r.evicted.filter((e) => e.reason === 'top_n').map((e) => e.score)),
779
+ JSON.stringify(r.evicted.map((e) => [e.reason, e.score])));
780
+ check('SF11.4b/AC2 substância preservada: TODA evicted permanece no log cru (poda ≠ deleção)',
781
+ (() => { const raw = fs.readFileSync(memoryPaths('c2', { root }).raw, 'utf8'); return r.evicted.every((e) => raw.includes(e.trigger)); })());
782
+ const ledger = mem.formatEvictionLedger('c2', r);
783
+ check(`SF11.4b/AC4 ledger reporta o TOTAL (${r.evicted.length}) antes da quebra, e a quebra soma o total`,
784
+ ledger.startsWith(`[memory] AVISO: ${r.evicted.length} heurística(s) NÃO entraram`) &&
785
+ ledger.includes(`${byTopN} por teto de atenção TOP_N=${TOP_N}`) &&
786
+ ledger.includes(`${byBudget} por teto de custo ${TOKEN_BUDGET} tok`), ledger);
787
+ check('SF11.4b/AC4 ledger declara a unidade do custo (ceil(bytes UTF-8/4)) — lição UTF-16×UTF-8 da SF11.2',
788
+ /tokens = ceil\(bytes UTF-8 \/ 4\)/.test(ledger), ledger);
789
+ fs.rmSync(root, { recursive: true, force: true });
790
+ }
791
+
792
+ // ── AC3 — needsCuration liga TAMBÉM pela via do teto de atenção ─────────────
793
+ {
794
+ const root = tmpRoot('sf114b-topn');
795
+ // entradas CURTAS: o pool passa de TOP_N sem chegar perto do orçamento — o cenário em
796
+ // que, pré-story, 6 heurísticas sumiam com needsCuration=false e folga de teto ociosa.
797
+ for (let i = 0; i < 36; i++) {
798
+ append('t', { id: `C${i}`, ts: 1_700_000_000_000 + i * 1000, trigger: `gatilho curto ${i}`, recommendation: `faca ${i}`, evidence: { count: 1 }, tier: 'normal' }, { root });
799
+ }
800
+ const r = consolidate('t', { root });
801
+ check('SF11.4b/AC3 needsCuration=true por TOP_N puro (pré-story era false — ADR-023 §C1)',
802
+ r.needsCuration === true && r.evicted.length === 6 &&
803
+ r.evicted.every((e) => e.reason === 'top_n'), JSON.stringify({ n: r.needsCuration, ev: r.evicted.length }));
804
+ check('SF11.4b/AC3 e a folga de orçamento ficava OCIOSA enquanto isso (corte não era de custo)',
805
+ r.tokens < TOKEN_BUDGET, `tokens=${r.tokens} teto=${TOKEN_BUDGET}`);
806
+ fs.rmSync(root, { recursive: true, force: true });
807
+ }
808
+
809
+ // ── AC6/AC9 — memoryCost efetivo = min(alvo, TOKEN_BUDGET); ausência inalterada
810
+ {
811
+ const root = tmpRoot('sf114b-alvo');
812
+ const POOL = 42;
813
+ for (let i = 0; i < POOL; i++) append('alvo', sizedEntry(i), { root });
814
+ const semAlvo = consolidate('alvo', { root });
815
+ check('SF11.4b/AC9 pod SEM memoryCost: teto efetivo = TOKEN_BUDGET (opt-in, ausência não é defeito)',
816
+ semAlvo.tokenBudget === TOKEN_BUDGET && semAlvo.memoryCost === null, JSON.stringify({ b: semAlvo.tokenBudget, m: semAlvo.memoryCost }));
817
+
818
+ writePodSpec(root, 'alvo', { pod: 'alvo', memoryCost: { artifactTokens: 900, perRunTokens: 2700, steps: 3, acceptedBy: 'daniel', acceptedAt: '2026-08-03' } });
819
+ const comAlvo = consolidate('alvo', { root });
820
+ const mdAlvo = fs.readFileSync(memoryPaths('alvo', { root }).consolidated, 'utf8');
821
+ check('SF11.4b/AC6 teto efetivo = min(900, 1500) = 900 — MEDIDO por execução, não lido do JSON',
822
+ comAlvo.tokenBudget === 900 && comAlvo.memoryCost.artifactTokens === 900, JSON.stringify(comAlvo.tokenBudget));
823
+ check(`SF11.4b/AC6 o artefato real respeita o alvo (${comAlvo.tokens} ≤ 900) e é MENOR que sem alvo (${semAlvo.tokens})`,
824
+ comAlvo.tokens <= 900 && comAlvo.tokens < semAlvo.tokens);
825
+ check('SF11.4b/AC6 apertar o alvo aumenta o que sai — e TODO o aumento tem causa registrada',
826
+ comAlvo.evicted.length > semAlvo.evicted.length &&
827
+ comAlvo.topNCount + comAlvo.evicted.length === POOL &&
828
+ comAlvo.evicted.length === absentFromArtifact(mdAlvo, POOL),
829
+ `${comAlvo.evicted.length} vs ${semAlvo.evicted.length}`);
830
+ const ledgerAlvo = mem.formatEvictionLedger('alvo', comAlvo);
831
+ check('SF11.4b/AC4 com alvo declarado o ledger nomeia "alvo de custo" e o custo/rodada (steps do manifesto)',
832
+ /por alvo de custo 900 tok/.test(ledgerAlvo) && /× 3 etapa\(s\) =/.test(ledgerAlvo), ledgerAlvo);
833
+ fs.rmSync(root, { recursive: true, force: true });
834
+ }
835
+
836
+ // ── AC8 — fail-loud de forma: malformado LANÇA, nunca degrada para 1.500 ────
837
+ {
838
+ const root = tmpRoot('sf114b-failloud');
839
+ for (let i = 0; i < 5; i++) append('f', sizedEntry(i), { root });
840
+ const malformados = [
841
+ ['artifactTokens string', { artifactTokens: '900', acceptedBy: 'daniel' }],
842
+ ['artifactTokens zero', { artifactTokens: 0, acceptedBy: 'daniel' }],
843
+ ['artifactTokens negativo', { artifactTokens: -5, acceptedBy: 'daniel' }],
844
+ ['artifactTokens fracionário', { artifactTokens: 900.5, acceptedBy: 'daniel' }],
845
+ ['artifactTokens acima do teto do framework', { artifactTokens: 1501, acceptedBy: 'daniel' }],
846
+ ['artifactTokens ausente', { acceptedBy: 'daniel' }],
847
+ ['null como valor', { artifactTokens: null, acceptedBy: 'daniel' }],
848
+ ['memoryCost não-objeto', 900],
849
+ ['memoryCost array', [900]],
850
+ ['acceptedBy ausente (alvo sem dono = limiar inventado, DF4-5)', { artifactTokens: 900 }],
851
+ ['acceptedBy vazio', { artifactTokens: 900, acceptedBy: ' ' }],
852
+ ['steps inválido', { artifactTokens: 900, acceptedBy: 'daniel', steps: 0 }],
853
+ ];
854
+ for (const [label, memoryCost] of malformados) {
855
+ writePodSpec(root, 'f', { pod: 'f', memoryCost });
856
+ let threw = false; let msg = '';
857
+ try { consolidate('f', { root }); } catch (e) { threw = true; msg = e.message; }
858
+ check(`SF11.4b/AC8 fail-loud: ${label} ⇒ LANÇA com mensagem acionável (nunca cai para ${TOKEN_BUDGET})`,
859
+ threw && /malformado/.test(msg) && /pod-spec\.json/.test(msg) && /ADR-023/.test(msg), msg.slice(0, 200));
860
+ }
861
+ // JSON quebrado no manifesto também é ruidoso — nunca silencioso
862
+ fs.writeFileSync(path.join(root, 'f', 'pod-spec.json'), '{ "pod": "f", ');
863
+ let threwJson = false;
864
+ try { consolidate('f', { root }); } catch { threwJson = true; }
865
+ check('SF11.4b/AC8 pod-spec.json ilegível como JSON ⇒ LANÇA (manifesto corrompido não silencia a trava)', threwJson);
866
+
867
+ // ── DF-SF11.4b-7 (Emenda 1 ao ADR-023) — "tolerante à ausência" tem UMA só forma ──
868
+ // O par abaixo é UM contrato, não dois casos soltos: manifesto ILEGÍVEL (aqui o path
869
+ // virou diretório ⇒ EISDIR, o vetor 30/30 do harness do pc-qa) é alvo DESCONHECIDO e
870
+ // LANÇA; manifesto INEXISTENTE (ENOENT) continua sendo ausência legítima e devolve o
871
+ // teto do framework sem ruído. Se só o primeiro fosse testado, o fix poderia ter
872
+ // matado o opt-in dos 13 pods sem manifesto e a suíte não veria.
873
+ const specPathF = path.join(root, 'f', 'pod-spec.json');
874
+ fs.rmSync(specPathF, { force: true });
875
+ fs.mkdirSync(specPathF, { recursive: true });
876
+ let threwDir = false; let msgDir = '';
877
+ try { consolidate('f', { root }); } catch (e) { threwDir = true; msgDir = e.message; }
878
+ check('SF11.4b/DF-7 pod-spec.json ILEGÍVEL (path é diretório ⇒ EISDIR) ⇒ LANÇA citando path e errno — NÃO degrada para 1.500',
879
+ threwDir && /EISDIR/.test(msgDir) && /pod-spec\.json/.test(msgDir) && /ADR-023 DF-SF11\.4b-7/.test(msgDir), msgDir.slice(0, 240));
880
+ check('SF11.4b/DF-7 e é falha de LEITURA, classe própria — não se disfarça de parse nem de "malformado"',
881
+ threwDir && /não pôde ser lido/.test(msgDir) && !/malformado/.test(msgDir), msgDir.slice(0, 160));
882
+ fs.rmSync(specPathF, { recursive: true, force: true });
883
+
884
+ let semSpec = null; let threwAbsent = false;
885
+ try { semSpec = consolidate('f', { root }); } catch { threwAbsent = true; }
886
+ check('SF11.4b/DF-7 pod-spec.json INEXISTENTE (ENOENT) segue sendo ausência legítima: teto 1.500, sem lançar (opt-in intacto)',
887
+ !threwAbsent && semSpec && semSpec.memoryCost === null && semSpec.tokenBudget === TOKEN_BUDGET,
888
+ JSON.stringify({ threwAbsent, mc: semSpec && semSpec.memoryCost, b: semSpec && semSpec.tokenBudget }));
889
+ check('SF11.4b/DF-7 readMemoryCost direto: manifesto ausente ⇒ null (a única forma de ausência)',
890
+ mem.readMemoryCost('f', { root }) === null);
891
+
892
+ // e o caminho feliz segue funcionando (a rigidez não quebra quem declara certo)
893
+ writePodSpec(root, 'f', { pod: 'f', memoryCost: { artifactTokens: 1500, acceptedBy: 'daniel' } });
894
+ const okRes = consolidate('f', { root });
895
+ check('SF11.4b/AC8 alvo bem-formado no limite (=TOKEN_BUDGET) é aceito', okRes.tokenBudget === 1500);
896
+ fs.rmSync(root, { recursive: true, force: true });
897
+ }
898
+
899
+ // ── AC9 — os pods do repositório sem memoryCost seguem no teto de hoje ──────
900
+ {
901
+ const repoPods = path.join(__dirname, '..', 'template', 'pods');
902
+ if (fs.existsSync(repoPods)) {
903
+ const semCampo = fs.readdirSync(repoPods, { withFileTypes: true })
904
+ .filter((d) => d.isDirectory())
905
+ .filter((d) => fs.existsSync(path.join(repoPods, d.name, 'pod-spec.json')))
906
+ .filter((d) => {
907
+ const spec = JSON.parse(fs.readFileSync(path.join(repoPods, d.name, 'pod-spec.json'), 'utf8'));
908
+ return spec.memoryCost == null;
909
+ })
910
+ .map((d) => d.name);
911
+ const comCampo = fs.readdirSync(repoPods, { withFileTypes: true })
912
+ .filter((d) => d.isDirectory() && fs.existsSync(path.join(repoPods, d.name, 'pod-spec.json')))
913
+ .filter((d) => JSON.parse(fs.readFileSync(path.join(repoPods, d.name, 'pod-spec.json'), 'utf8')).memoryCost != null);
914
+ check(`SF11.4b/AC9 nenhum pod-template ganhou memoryCost nesta story (opt-in por pod) — ${semCampo.length} sem campo`,
915
+ comCampo.length === 0, comCampo.map((d) => d.name).join(','));
916
+ for (const p of semCampo) {
917
+ check(`SF11.4b/AC9 ${p}: teto efetivo permanece ${TOKEN_BUDGET} (comportamento de hoje, inalterado)`,
918
+ mem.effectiveTokenBudget(p, { podDir: path.join(repoPods, p) }).tokens === TOKEN_BUDGET);
919
+ }
920
+ }
921
+ }
922
+
923
+ // ── AC10 — contrato preservado: assinatura e getConsolidatedForBrief ────────
924
+ {
925
+ const root = tmpRoot('sf114b-contrato');
926
+ for (let i = 0; i < 42; i++) append('k', sizedEntry(i), { root });
927
+ const r = consolidate('k', { root });
928
+ check('SF11.4b/AC10 consolidate() mantém a assinatura (pod, opts com default) e todos os campos de SF11.4',
929
+ consolidate.length === 1 &&
930
+ ['pinnedCount', 'topNCount', 'tokens', 'needsCuration', 'overflow', 'evicted', 'conflicts', 'hardenedCount', 'okPatternCount']
931
+ .every((k) => k in r), `arity=${consolidate.length} keys=${Object.keys(r).join(',')}`);
932
+ check('SF11.4b/AC10 getConsolidatedForBrief continua lendo UM arquivo (o consolidado), sem novo parâmetro',
933
+ getConsolidatedForBrief.length === 1 &&
934
+ getConsolidatedForBrief('k', { root }) === fs.readFileSync(memoryPaths('k', { root }).consolidated, 'utf8'));
935
+ fs.rmSync(root, { recursive: true, force: true });
936
+ }
937
+
938
+ // ───────────────────────────────────────────────────────────────────────────
939
+ // Relatório.
940
+ // ───────────────────────────────────────────────────────────────────────────
941
+ console.log('POWER CLAUDE — pod-memory (motor de memória do pod, SF2.1)\n');
942
+ const failures = results.filter((r) => !r.ok);
943
+ for (const r of results) {
944
+ console.log(`[${r.ok ? 'OK ' : 'FALHA'}] ${r.label}${r.ok ? '' : ` — ${r.detail}`}`);
945
+ }
946
+ console.log(`\n${results.length - failures.length}/${results.length} verificações passaram.`);
947
+ if (failures.length > 0) {
948
+ console.error('\nSUÍTE FALHOU.');
949
+ process.exit(1);
950
+ }
951
+ process.exit(0);