@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,921 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+ /**
4
+ * forge-harden-learning.cjs — `*harden-learning`: promove UMA heurística do consolidado
5
+ * de memória de um pod a FORMA CANÔNICA do pod — Story SF7.6, ADR-012 (Decisões 6 e 7);
6
+ * 2º alvo (knowledge) em SF10.10, ADR-012 Adendo SF10.10 (DF-SF10.10-1 a -6).
7
+ *
8
+ * DOIS ALVOS DE PROMOÇÃO (`--target-type`):
9
+ * veto (default) → veto.assertion no wf-*.yaml (enforcement do runner, SF7.6)
10
+ * knowledge (SF10.10) → knowledge canônico do pod (prosa que o export recebe cru)
11
+ * + modo read-only `--list-candidates` (detecção, DF-SF10.10-3).
12
+ *
13
+ * Caso motivador (benchmark 4-braços, R5): a heurística "evitar travessões" foi injetada
14
+ * no brief via memória evolutiva e AINDA ASSIM regrediu — memória é sugestão, não
15
+ * enforcement. Este motor fecha o degrau aprender→impor: a heurística vira bloqueio
16
+ * físico do runner (veto existente `count_gte`/`field_matches`/... ou `command_check`
17
+ * blessed da SF7.2, ex.: lexical-check para travessões).
18
+ *
19
+ * FRONTEIRAS (vinculantes):
20
+ * - A TRADUÇÃO heurística→forma de veto é JULGAMENTO HUMANO: a asserção entra como
21
+ * ARGUMENTO (--assertion), nunca derivada de prosa por LLM (Escopo OUT da story).
22
+ * - Aprovação do dono OBRIGATÓRIA: --approved-by <nome> (padrão migration-manifest);
23
+ * sem ela a promoção NÃO aplica (exit 2 explícito, nunca silencioso).
24
+ * - Regeneração via forge-generate `--additive` + owns (mecanismo provado por
25
+ * *migrate-squad F2b): edita pod-spec.json (fonte declarada da verdade, ADR-005) e
26
+ * reemite o workflow-alvo; `agents/` NUNCA é tocado (forge-generate não escreve lá).
27
+ * - Prova de preservação: validate-pod-workflow + smoke 4/4 ANTES e DEPOIS; diff do wf
28
+ * confinado à etapa-alvo — qualquer mudança fora dela ⇒ ROLLBACK + exit 2.
29
+ * - Marcação append-only na memória: {type:"hardening", ref, target, approved_by,
30
+ * timestamp} (pod-memory.appendHardening); consolidate anota 🔒 e DEMOVE do top-N
31
+ * ativo (enforcement substitui injeção) — a heurística NUNCA é removida do log.
32
+ *
33
+ * ALVO `knowledge` (SF10.10) — FRONTEIRAS ADICIONAIS (vinculantes):
34
+ * - A REDAÇÃO do knowledge é do dono: o conteúdo novo entra por `--content <arquivo>`
35
+ * (proposto/revisado por humano). Este motor NUNCA gera prosa.
36
+ * - Prova de PRESERVAÇÃO (DF-SF10.10-2): baseline antes (validate + smoke 4/4);
37
+ * CONFINAMENTO de escrita a 3 paths (knowledge/<file>, memory/learnings.jsonl,
38
+ * memory/learnings.consolidated.md) com rollback fora deles; scanClone (regras 1-3)
39
+ * verde sobre o alvo depois; paridade de `per_file_sha` via pod-export-compile
40
+ * quando o pod é export-capaz (senão `export_check: n/a`, declarado).
41
+ * - SEM smoke "depois": omitido POR DECISÃO e com o motivo registrado — nenhum
42
+ * caminho de runtime lê `knowledge/*.md` (zero ocorrência em smoke-pod-workflow.cjs
43
+ * e pc-run-pod.cjs), logo o smoke é VERDE POR CONSTRUÇÃO para este alvo; mantê-lo
44
+ * seria gate que não pode falhar (ilusão de cobertura).
45
+ * - Prova de QUALIDADE: NÃO EXISTE em máquina. Nenhum gate deste mecanismo julga se a
46
+ * prosa ficou melhor. O único portão de qualidade é `--approved-by`.
47
+ *
48
+ * Uso:
49
+ * # alvo veto (SF7.6)
50
+ * node scripts/forge-harden-learning.cjs <podDir> \
51
+ * --learning <id-da-entrada-no-learnings.jsonl> \
52
+ * --step <stepId-do-workflow> \
53
+ * --assertion '<json da veto.assertion>' \
54
+ * --approved-by <nome> \
55
+ * [--workflow <workflowId>] (default: workflowId do pod-spec.json)
56
+ *
57
+ * # alvo knowledge (SF10.10)
58
+ * node scripts/forge-harden-learning.cjs <podDir> --target-type knowledge \
59
+ * --learning <id> --file <knowledge/arquivo.md> --content <arquivo-novo.md> \
60
+ * --approved-by <nome> [--pins <pins.json>]
61
+ *
62
+ * # detecção (read-only, exit 0 sempre)
63
+ * node scripts/forge-harden-learning.cjs <podDir> --list-candidates
64
+ *
65
+ * Exit: 0 promoção aplicada e provada (ou listagem) · 2 recusa (aprovação ausente,
66
+ * learning/step/arquivo inexistente, asserção inválida, knowledge não declarado
67
+ * no wf, baseline vermelho, pós-gate vermelho→rollback, diff fora do alvo→
68
+ * rollback, budget estourado→rollback, já endurecida) · 1 erro de uso/interno.
69
+ *
70
+ * Node >= 18, sem dependências externas.
71
+ */
72
+
73
+ const fs = require('node:fs');
74
+ const os = require('node:os');
75
+ const path = require('node:path');
76
+ const crypto = require('node:crypto');
77
+ const { spawnSync } = require('node:child_process');
78
+
79
+ const podMemory = require('./pod-memory.cjs');
80
+ const { generatePod, stepsFromSpec, slug } = require('./forge-generate.cjs');
81
+ const { VETO_TYPES, parseYaml } = require('./validate-pod-workflow.cjs');
82
+ const { scanClone, deriveDenylist } = require('./validate-no-clone.cjs');
83
+
84
+ const REPO_ROOT = path.resolve(__dirname, '..');
85
+ const VETO_CHECKS_ROOT = path.join(REPO_ROOT, 'scripts', 'veto-checks');
86
+
87
+ // DF-SF10.10-2 item 6 — dito com todas as letras no --help, na SAÍDA do comando e no
88
+ // doc de mecanismo. Não há eufemismo aqui: a máquina prova confinamento, o humano
89
+ // prova qualidade, e nenhum dos dois finge ser o outro.
90
+ const QUALITY_NOTICE =
91
+ 'Nenhum gate deste mecanismo julga se a prosa ficou melhor. O único portão de qualidade ' +
92
+ 'é a revisão humana registrada em `--approved-by` sobre o diff proposto.';
93
+
94
+ // DF-SF10.10-2 item 2 — o motivo da AUSÊNCIA do smoke "depois", impresso no relatório
95
+ // (omissão declarada, nunca silenciosa).
96
+ const NO_SMOKE_AFTER_NOTICE =
97
+ 'sem smoke "depois": omitido POR DECISÃO (DF-SF10.10-2) — nenhum caminho de runtime lê ' +
98
+ 'knowledge/*.md (zero ocorrência em smoke-pod-workflow.cjs/pc-run-pod.cjs), logo seria ' +
99
+ 'VERDE POR CONSTRUÇÃO: prova vacuosa não é prova.';
100
+
101
+ const USAGE_LINES = [
102
+ 'Uso:',
103
+ ' # alvo veto (SF7.6) — promove heurística a enforcement físico do runner',
104
+ ' node scripts/forge-harden-learning.cjs <podDir> --learning <id> --step <stepId> \\',
105
+ " --assertion '<json>' --approved-by <nome> [--workflow <id>]",
106
+ '',
107
+ ' # alvo knowledge (SF10.10) — promove heurística a knowledge canônico do pod',
108
+ ' node scripts/forge-harden-learning.cjs <podDir> --target-type knowledge \\',
109
+ ' --learning <id> --file <knowledge/arquivo.md> --content <arquivo-com-o-conteudo-novo> \\',
110
+ ' --approved-by <nome> [--pins <pins.json>]',
111
+ '',
112
+ ' # detecção de candidatos (read-only, exit 0 sempre, nunca promove)',
113
+ ' node scripts/forge-harden-learning.cjs <podDir> --list-candidates',
114
+ '',
115
+ `QUALIDADE (alvo knowledge): ${QUALITY_NOTICE}`,
116
+ ];
117
+
118
+ function usage(msg) {
119
+ if (msg) console.error(`[erro] ${msg}`);
120
+ for (const l of USAGE_LINES) console.error(l);
121
+ process.exit(1);
122
+ }
123
+
124
+ function help() {
125
+ for (const l of USAGE_LINES) console.log(l);
126
+ }
127
+
128
+ function refuse(msg) {
129
+ console.error(`[recusa] ${msg}`);
130
+ process.exit(2);
131
+ }
132
+
133
+ function argVal(args, flag) {
134
+ const i = args.indexOf(flag);
135
+ return i >= 0 ? args[i + 1] : null;
136
+ }
137
+
138
+ function run(cmdArgs, label) {
139
+ // O 1º argumento é sempre um script DESTE repo: resolve contra REPO_ROOT em vez do
140
+ // cwd — o pod-alvo pode viver em outro repositório (cross-repo por path, padrão D6
141
+ // do ADR-018) e o motor precisa achar os próprios gates de qualquer cwd.
142
+ const [script, ...rest] = cmdArgs;
143
+ const abs = path.isAbsolute(script) ? script : path.join(REPO_ROOT, script);
144
+ const r = spawnSync(process.execPath, [abs, ...rest], { encoding: 'utf8' });
145
+ return { label, status: r.status, out: `${r.stdout || ''}${r.stderr || ''}` };
146
+ }
147
+
148
+ /** Valida a FORMA da asserção proposta (o conteúdo é julgamento humano revisado). */
149
+ function checkAssertionShape(assertion) {
150
+ if (!assertion || typeof assertion !== 'object' || Array.isArray(assertion)) {
151
+ refuse('--assertion deve ser um objeto JSON (a veto.assertion proposta pelo dono)');
152
+ }
153
+ const required = VETO_TYPES[assertion.type];
154
+ if (!required) {
155
+ refuse(
156
+ `--assertion type "${assertion.type}" não é uma forma suportada (${Object.keys(VETO_TYPES).join(', ')})`,
157
+ );
158
+ }
159
+ for (const k of required) {
160
+ if (!(k in assertion)) refuse(`--assertion (${assertion.type}) exige o campo "${k}"`);
161
+ }
162
+ if (assertion.type === 'command_check') {
163
+ const cmd = String(assertion.command || '');
164
+ if (/[\\/]/.test(cmd) || cmd.includes('..') || !/\.(cjs|js)$/.test(cmd)) {
165
+ refuse(`command_check: "${cmd}" não é nome simples de script blessed (.cjs/.js sob scripts/veto-checks/)`);
166
+ }
167
+ if (!fs.existsSync(path.join(VETO_CHECKS_ROOT, cmd))) {
168
+ refuse(`command_check: script blessed inexistente: scripts/veto-checks/${cmd}`);
169
+ }
170
+ if ('args' in assertion) {
171
+ if (!Array.isArray(assertion.args) || assertion.args.some((a) => typeof a !== 'string')) {
172
+ refuse('command_check: "args" deve ser um array de strings literais');
173
+ }
174
+ if (assertion.args.some((a) => /[`$]|\{\{|\$\(/.test(a))) {
175
+ refuse('command_check: "args" não pode conter template/interpolação/shell');
176
+ }
177
+ }
178
+ }
179
+ }
180
+
181
+ /** Assinatura estável de uma asserção (dedup — ignora _sources). */
182
+ function assertionSig(a) {
183
+ const clean = {};
184
+ for (const k of Object.keys(a).sort()) {
185
+ if (k.startsWith('_')) continue;
186
+ clean[k] = a[k];
187
+ }
188
+ return JSON.stringify(clean);
189
+ }
190
+
191
+ /** Quebra o texto do wf em { header, blocks: Map<stepId, texto> } (blocos `- id:`). */
192
+ function splitWfBlocks(text) {
193
+ const lines = String(text).split('\n');
194
+ const header = [];
195
+ const blocks = new Map();
196
+ let cur = null;
197
+ for (const line of lines) {
198
+ const m = line.match(/^ - id:\s*(\S+)\s*$/);
199
+ if (m) {
200
+ cur = m[1];
201
+ blocks.set(cur, [line]);
202
+ continue;
203
+ }
204
+ if (cur) blocks.get(cur).push(line);
205
+ else header.push(line);
206
+ }
207
+ return { header: header.join('\n'), blocks };
208
+ }
209
+
210
+ // ── comum aos dois alvos: localizar a heurística e exigi-la no consolidado ───
211
+ /**
212
+ * Acha a entrada-fonte no log cru, consolida ANTES (estado curado fresco) e recusa se
213
+ * a heurística já está endurecida (🔒) ou não está no consolidado curado. Idêntico
214
+ * para os dois alvos: promoção é sempre DE uma heurística consolidada.
215
+ */
216
+ function resolveLearning(pod, podDir, learningId) {
217
+ const memPaths = podMemory.memoryPaths(pod, { podDir });
218
+ if (!fs.existsSync(memPaths.raw)) refuse(`memória não encontrada: ${memPaths.raw}`);
219
+ const rawLines = fs.readFileSync(memPaths.raw, 'utf8').split('\n').filter(Boolean);
220
+ const entries = rawLines.map((l) => { try { return JSON.parse(l); } catch { return null; } }).filter(Boolean);
221
+ const entry = entries.find((e) => e.id === learningId && e.type !== podMemory.TYPE_HARDENING);
222
+ if (!entry) refuse(`learning "${learningId}" não encontrado em ${memPaths.raw}`);
223
+ const ref = podMemory.dedupKey(entry);
224
+
225
+ // consolida ANTES para decidir sobre o estado curado atual (top-N fresco)
226
+ podMemory.consolidate(pod, { podDir });
227
+ // SF11.4/ADR-022 (DF-SF11.4-5) — as duas checagens abaixo passam a ler DADO
228
+ // ESTRUTURADO (o mesmo conjunto curado que o motor acabou de escrever), não string do
229
+ // artefato RENDERIZADO. Duas razões, nesta ordem:
230
+ // 1. OBRIGATÓRIO junto de DF-SF11.4-1: a seção 🔒 virou referência AGREGADA, então
231
+ // a string antiga (`🔒 **[trigger]**`) não existe mais no consolidado — mantê-la
232
+ // seria regressão SILENCIOSA (a mesma heurística promovida duas vezes sem recusa).
233
+ // 2. RESTRIÇÃO NÃO-DOCUMENTADA REMOVIDA: a checagem antiga era por `trigger`
234
+ // APENAS, então DUAS heurísticas legítimas e distintas com o mesmo trigger não
235
+ // podiam ambas ser promovidas — a segunda era recusada mesmo sendo conteúdo novo.
236
+ // Isso bloqueou uma promoção real (2026-08-01/02) e foi contornado RENOMEANDO o
237
+ // trigger da segunda ("compor a capa" → "titular capa") — gambiarra de
238
+ // nomenclatura, registrada como achado lateral 1 da Story SF11.4. A chave correta
239
+ // é `dedupKey` (trigger + recommendation), a MESMA que o motor usa para dedup e
240
+ // para casar registro de hardening ↔ heurística.
241
+ const { pins, ranked, hardenedRefs } = podMemory.computeConsolidation(pod, { podDir });
242
+ if (hardenedRefs.has(ref)) {
243
+ refuse(`learning "${learningId}" já está endurecido (registro 🔒 com ref "${ref}") — nada a promover`);
244
+ }
245
+ const curated = new Set([...pins, ...ranked].map((m) => podMemory.dedupKey(m)));
246
+ if (!curated.has(ref)) {
247
+ refuse(`learning "${learningId}" não está no consolidado curado (top-N/pinado) — o hardening promove heurística DO CONSOLIDADO (story SF7.6); consolide/cure antes`);
248
+ }
249
+ return { entry, ref, memPaths };
250
+ }
251
+
252
+ // ── ALVO `knowledge` (SF10.10) — helpers ─────────────────────────────────────
253
+
254
+ function sha256(buf) {
255
+ return crypto.createHash('sha256').update(buf).digest('hex');
256
+ }
257
+
258
+ /** Identidade do pod para os dois alvos: spec quando existe, basename como fallback. */
259
+ function podIdentity(podDir, args) {
260
+ const specPath = path.join(podDir, 'pod-spec.json');
261
+ let spec = null;
262
+ if (fs.existsSync(specPath)) {
263
+ try {
264
+ spec = JSON.parse(fs.readFileSync(specPath, 'utf8'));
265
+ } catch (e) {
266
+ refuse(`pod-spec.json inválido: ${e.message}`);
267
+ }
268
+ }
269
+ const pod = (spec && spec.pod) || path.basename(path.resolve(podDir));
270
+ const workflowId = slug(argVal(args, '--workflow') || (spec && (spec.workflowId || spec.pod)) || pod);
271
+ return { spec, pod, workflowId };
272
+ }
273
+
274
+ /**
275
+ * Normaliza `--file` para o NOME SIMPLES sob `knowledge/` e recusa qualquer forma que
276
+ * escape do diretório (path traversal, path absoluto, subdiretório). 1ª barreira do
277
+ * confinamento — a 2ª é a verificação de escrita pós-aplicação.
278
+ */
279
+ function normalizeKnowledgeFile(raw) {
280
+ let f = String(raw == null ? '' : raw).trim().replace(/\\/g, '/');
281
+ if (f.startsWith('./')) f = f.slice(2);
282
+ if (f.startsWith('knowledge/')) f = f.slice('knowledge/'.length);
283
+ if (!f) refuse('--file <arquivo> é obrigatório no alvo knowledge (nome sob knowledge/, ex.: slides-html.md)');
284
+ if (path.isAbsolute(f) || f.includes('/') || f.includes('..')) {
285
+ refuse(
286
+ `--file ${JSON.stringify(String(raw))} deve ser um nome SIMPLES sob knowledge/ (sem path, sem "..") — ` +
287
+ 'o alvo knowledge escreve EXCLUSIVAMENTE dentro de <podDir>/knowledge/',
288
+ );
289
+ }
290
+ if (!/\.md$/i.test(f)) refuse(`--file "${f}": o alvo knowledge só edita arquivos .md declarados no wf`);
291
+ return f;
292
+ }
293
+
294
+ /** Nomes de knowledge DECLARADOS no wf (knowledge: de qualquer etapa + knowledge_exclude:). */
295
+ function declaredKnowledge(wfPath) {
296
+ let doc;
297
+ try {
298
+ doc = parseYaml(fs.readFileSync(wfPath, 'utf8'));
299
+ } catch (e) {
300
+ refuse(`workflow ilegível (${wfPath}): ${e.message}`);
301
+ }
302
+ const declared = new Set();
303
+ for (const k of Array.isArray(doc.knowledge_exclude) ? doc.knowledge_exclude : []) declared.add(String(k));
304
+ for (const step of Array.isArray(doc.steps) ? doc.steps : []) {
305
+ for (const k of Array.isArray(step.knowledge) ? step.knowledge : []) declared.add(String(k));
306
+ }
307
+ return declared;
308
+ }
309
+
310
+ /**
311
+ * CONTAINMENT POR REALPATH — mesmo padrão do Guard B de `command_check`
312
+ * (`pc-run-pod.cjs`) e do `confineToRunDir` de `pod-export-ui.cjs` (SF10.9): o guard
313
+ * lexical (`path.resolve`) NÃO enxerga symlink. Quando o caminho — ou o ancestral
314
+ * existente mais próximo — resolve em disco, o realpath TEM que permanecer sob o
315
+ * realpath da raiz; senão RECUSA antes de qualquer escrita.
316
+ */
317
+ function assertRealpathUnder(realRoot, abs, label) {
318
+ let probe = path.resolve(abs);
319
+ for (;;) {
320
+ let real;
321
+ try {
322
+ real = fs.realpathSync(probe);
323
+ } catch {
324
+ const parent = path.dirname(probe);
325
+ if (parent === probe || parent.length < realRoot.length) return; // nada existe ainda
326
+ probe = parent;
327
+ continue;
328
+ }
329
+ if (real !== realRoot && !real.startsWith(realRoot + path.sep)) {
330
+ refuse(
331
+ `${label}: "${abs}" resolve (realpath) para FORA de ${realRoot} — symlink/traversal para "${real}". ` +
332
+ 'A escrita cairia fora do pod e o diff de confinamento seria CEGO (Dirent de symlink não é isFile()).',
333
+ );
334
+ }
335
+ return;
336
+ }
337
+ }
338
+
339
+ /**
340
+ * Snapshot recursivo (relpath posix → conteúdo) — base do confinamento e do rollback.
341
+ * Symlinks são REGISTRADOS (tipo + destino), não ignorados: um Dirent de link não é
342
+ * `isFile()`, e pulá-lo tornava o diff cego a link criado/alterado/removido sob o
343
+ * podDir. Registrado, qualquer mexida em link vira violação como qualquer outra.
344
+ * (O guard que impede ESCRITA ATRAVÉS de um link pré-existente é `assertRealpathUnder`,
345
+ * aplicado aos 3 paths deste modo ANTES de escrever — este snapshot não substitui aquele.)
346
+ */
347
+ function snapshotDir(root) {
348
+ const out = new Map();
349
+ const walk = (dir) => {
350
+ const entries = fs.readdirSync(dir, { withFileTypes: true })
351
+ .sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0));
352
+ for (const e of entries) {
353
+ const abs = path.join(dir, e.name);
354
+ const rel = path.relative(root, abs).split(path.sep).join('/');
355
+ if (e.isSymbolicLink()) out.set(rel, Buffer.from(`symlink → ${fs.readlinkSync(abs)}`));
356
+ else if (e.isDirectory()) walk(abs);
357
+ else if (e.isFile()) out.set(rel, fs.readFileSync(abs));
358
+ }
359
+ };
360
+ walk(root);
361
+ return out;
362
+ }
363
+
364
+ /**
365
+ * PURA (testável isoladamente): caminhos criados/alterados/removidos entre dois
366
+ * snapshots que NÃO estão no conjunto permitido. Este é o análogo real, para o alvo
367
+ * knowledge, do "diff confinado à etapa-alvo" do alvo veto (DF-SF10.10-2 item 3).
368
+ * @returns {string[]} ex.: ['~ workflows/wf-x.yaml', '+ templates/novo.md']
369
+ */
370
+ function confinementViolations(before, after, allowedRels) {
371
+ const allowed = new Set(allowedRels);
372
+ const changed = [];
373
+ for (const [rel, buf] of after) {
374
+ const prev = before.get(rel);
375
+ if (prev == null) changed.push(`+ ${rel}`);
376
+ else if (!prev.equals(buf)) changed.push(`~ ${rel}`);
377
+ }
378
+ for (const rel of before.keys()) if (!after.has(rel)) changed.push(`- ${rel}`);
379
+ return changed.filter((c) => !allowed.has(c.slice(2))).sort();
380
+ }
381
+
382
+ /** Restaura o diretório ao estado do snapshot (bytes idênticos; extras removidos). */
383
+ function restoreSnapshot(root, snap) {
384
+ const now = snapshotDir(root);
385
+ for (const rel of now.keys()) {
386
+ if (!snap.has(rel)) fs.rmSync(path.join(root, rel), { force: true });
387
+ }
388
+ for (const [rel, buf] of snap) {
389
+ const abs = path.join(root, rel);
390
+ const cur = now.get(rel);
391
+ if (cur == null || !cur.equals(buf)) {
392
+ fs.mkdirSync(path.dirname(abs), { recursive: true });
393
+ fs.writeFileSync(abs, buf);
394
+ }
395
+ }
396
+ }
397
+
398
+ /**
399
+ * Pins do pod (export-capacidade). Ordem: `--pins` explícito, `<podDir>/pins.json`,
400
+ * `exports/<pod>/pins.json` (local canônico que o compilador usa). Ausente ⇒ null,
401
+ * e o relatório grava `export_check: "n/a"` — DECLARADO, nunca pulado em silêncio.
402
+ */
403
+ function findPins(podDir, podName, args) {
404
+ const explicit = argVal(args, '--pins');
405
+ if (explicit) {
406
+ const abs = path.resolve(explicit);
407
+ if (!fs.existsSync(abs)) refuse(`--pins não encontrado: ${abs}`);
408
+ return abs;
409
+ }
410
+ const inPod = path.join(podDir, 'pins.json');
411
+ if (fs.existsSync(inPod)) return inPod;
412
+ const canonical = path.join(REPO_ROOT, 'exports', podName, 'pins.json');
413
+ return fs.existsSync(canonical) ? canonical : null;
414
+ }
415
+
416
+ /** Compila o pod em diretório temporário e devolve o lock (sem tocar exports/ real). */
417
+ function compileLock(podDir, pinsPath, label) {
418
+ const outRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'harden-compile-'));
419
+ const lockPath = path.join(outRoot, 'export.lock.json');
420
+ const r = run([
421
+ 'scripts/pod-export-compile.cjs', 'compile', path.resolve(podDir),
422
+ '--no-endpoint-check', '--pins', pinsPath,
423
+ '--out', path.join(outRoot, 'bundle'), '--lock-out', lockPath,
424
+ ], label);
425
+ let lock = null;
426
+ if (fs.existsSync(lockPath)) lock = JSON.parse(fs.readFileSync(lockPath, 'utf8'));
427
+ fs.rmSync(outRoot, { recursive: true, force: true });
428
+ return { ...r, lock };
429
+ }
430
+
431
+ /** PURA: chaves de per_file_sha que diferem entre dois locks (criadas/removidas/alteradas). */
432
+ function perFileShaDiff(lockBefore, lockAfter) {
433
+ const a = (lockBefore && lockBefore.per_file_sha) || {};
434
+ const b = (lockAfter && lockAfter.per_file_sha) || {};
435
+ const keys = new Set([...Object.keys(a), ...Object.keys(b)]);
436
+ return [...keys].filter((k) => a[k] !== b[k]).sort();
437
+ }
438
+
439
+ // ── ALVO `knowledge` (SF10.10) — promoção ────────────────────────────────────
440
+ function hardenKnowledge(args) {
441
+ const podDir = args[0];
442
+ const learningId = argVal(args, '--learning');
443
+ const fileArg = argVal(args, '--file');
444
+ const contentArg = argVal(args, '--content');
445
+ const approvedBy = argVal(args, '--approved-by');
446
+
447
+ if (!learningId) usage('--learning <id> é obrigatório (id da entrada no learnings.jsonl)');
448
+ if (!fileArg || fileArg.startsWith('--')) usage('--file <arquivo> é obrigatório (knowledge-alvo, ex.: slides-html.md)');
449
+ if (!contentArg || contentArg.startsWith('--')) {
450
+ usage('--content <arquivo> é obrigatório — o conteúdo NOVO do knowledge é proposto/revisado pelo HUMANO; ' +
451
+ 'este motor nunca gera prosa (ADR-012 §Escopo OUT)');
452
+ }
453
+ // aprovação do dono OBRIGATÓRIA — recusa explícita, nunca aplica sem ela.
454
+ if (!approvedBy || approvedBy.startsWith('--')) {
455
+ refuse('promoção exige --approved-by <nome> (aprovação humana registrada — padrão migration-manifest); sem ela NADA é aplicado. ' +
456
+ QUALITY_NOTICE);
457
+ }
458
+
459
+ const file = normalizeKnowledgeFile(fileArg);
460
+ const { pod, workflowId } = podIdentity(podDir, args);
461
+ const wfPath = path.join(podDir, 'workflows', `wf-${workflowId}.yaml`);
462
+ if (!fs.existsSync(wfPath)) refuse(`workflow-alvo não encontrado: ${wfPath}`);
463
+ const smokePath = wfPath.replace(/\.ya?ml$/, '.smoke.json');
464
+ if (!fs.existsSync(smokePath)) {
465
+ refuse(`cenário de smoke não encontrado: ${smokePath} — o BASELINE do alvo knowledge é validate + smoke 4/4 verdes (guarda de pod quebrado, DF-SF10.10-2 item 1); autore o sidecar primeiro`);
466
+ }
467
+
468
+ // ── (AC9/DF-SF10.10-5) knowledge DECLARADO no wf — precondição barata, lida
469
+ // direto do wf. v1 só EDITA knowledge já declarado; CRIAR é outro raio de
470
+ // explosão (arrasta wf/spec + regeneração) e está fora de escopo (DEBT-SF10.10-C).
471
+ const declared = declaredKnowledge(wfPath);
472
+ if (!declared.has(file)) {
473
+ refuse(
474
+ `"${file}" não está declarado em knowledge:/knowledge_exclude: de nenhuma etapa de ${path.relative(process.cwd(), wfPath)} ` +
475
+ `(declarados: ${[...declared].sort().join(', ') || 'nenhum'}). O alvo knowledge v1 só EDITA knowledge já declarado — ` +
476
+ 'criar knowledge novo exige editar o wf/spec e regenerar (fora de escopo, DEBT-SF10.10-C).',
477
+ );
478
+ }
479
+
480
+ const targetAbs = path.join(podDir, 'knowledge', file);
481
+ // Guard LEXICAL (1ª camada): mesmo com --file já normalizado, o path resolvido tem que
482
+ // ser exatamente <podDir>/knowledge/<file>. Isto NÃO cobre symlink — `path.resolve`
483
+ // não resolve link; a 2ª camada (realpath, logo abaixo) é quem cobre.
484
+ const knowledgeRoot = path.resolve(podDir, 'knowledge');
485
+ if (path.resolve(targetAbs) !== path.join(knowledgeRoot, file)) {
486
+ refuse(`--file "${fileArg}" resolveu para fora de ${knowledgeRoot}`);
487
+ }
488
+ if (!fs.existsSync(targetAbs)) {
489
+ refuse(`knowledge-alvo inexistente: ${targetAbs} — o alvo v1 só EDITA arquivo existente (criar é DEBT-SF10.10-C)`);
490
+ }
491
+ // Guard REALPATH (2ª camada — QA SF10.10/F1): o alvo tem que SER o arquivo real sob
492
+ // knowledge/, e os outros 2 paths que este modo escreve têm que resolver sob a raiz
493
+ // real do pod. Sem isto, um `knowledge/<file>` que fosse symlink para fora do pod
494
+ // escreveria FORA do podDir com o diff de confinamento cego (Dirent de link não é
495
+ // isFile()) — e o relatório ainda diria "confinamento OK". Padrão idêntico ao Guard B
496
+ // de `command_check` (pc-run-pod.cjs) e ao `confineToRunDir` (pod-export-ui.cjs, SF10.9).
497
+ const realPodRoot = fs.realpathSync(path.resolve(podDir));
498
+ const realTarget = fs.realpathSync(targetAbs);
499
+ if (realTarget !== path.join(fs.realpathSync(knowledgeRoot), file)) {
500
+ refuse(
501
+ `knowledge/${file} resolve (realpath) para "${realTarget}" — não é o arquivo real sob ` +
502
+ `${fs.realpathSync(knowledgeRoot)}. Promover através de symlink escreveria FORA do pod e a prova de ` +
503
+ 'confinamento seria cega (Dirent de symlink não é isFile()); a promoção é RECUSADA antes de qualquer escrita.',
504
+ );
505
+ }
506
+ for (const rel of ['memory/learnings.jsonl', 'memory/learnings.consolidated.md']) {
507
+ assertRealpathUnder(realPodRoot, path.join(podDir, rel), `memória do pod (${rel})`);
508
+ }
509
+ const contentPath = path.resolve(contentArg);
510
+ if (!fs.existsSync(contentPath)) refuse(`--content não encontrado: ${contentPath}`);
511
+ const newContent = fs.readFileSync(contentPath);
512
+ const oldContent = fs.readFileSync(targetAbs);
513
+ if (oldContent.equals(newContent)) {
514
+ refuse(`--content é BYTE-IDÊNTICO a knowledge/${file} — não há promoção a registrar (registro de hardening marca uma MUDANÇA)`);
515
+ }
516
+ const shaBefore = sha256(oldContent);
517
+ const shaAfter = sha256(newContent);
518
+
519
+ // ── heurística-fonte: existe, está no consolidado, ainda não endurecida ─────
520
+ const { entry, ref, memPaths } = resolveLearning(pod, podDir, learningId);
521
+
522
+ // ── BASELINE (antes): estático + smoke 4/4 verdes (guarda de pod quebrado) ──
523
+ const staticBefore = run(['scripts/validate-pod-workflow.cjs', wfPath], 'validate-pod-workflow (antes)');
524
+ if (staticBefore.status !== 0) {
525
+ refuse(`baseline vermelho — ${staticBefore.label} exit ${staticBefore.status}:\n${staticBefore.out}`);
526
+ }
527
+ const smokeBefore = run(['scripts/smoke-pod-workflow.cjs', wfPath, '--scenario', smokePath], 'smoke 4/4 (antes)');
528
+ if (smokeBefore.status !== 0) {
529
+ refuse(`baseline vermelho — ${smokeBefore.label} exit ${smokeBefore.status}:\n${smokeBefore.out}`);
530
+ }
531
+
532
+ // ── PARIDADE DE COMPILAÇÃO (antes), quando o pod é export-capaz ─────────────
533
+ const pinsPath = findPins(podDir, pod, args);
534
+ let lockBefore = null;
535
+ if (pinsPath) {
536
+ const cb = compileLock(podDir, pinsPath, 'pod-export-compile (antes)');
537
+ if (cb.status !== 0 || !cb.lock) {
538
+ refuse(`baseline vermelho — ${cb.label} exit ${cb.status}:\n${cb.out}`);
539
+ }
540
+ lockBefore = cb.lock;
541
+ }
542
+
543
+ // ── snapshot p/ CONFINAMENTO + rollback (pós-consolidate do baseline) ───────
544
+ const snapshot = snapshotDir(path.resolve(podDir));
545
+ const ALLOWED = [
546
+ `knowledge/${file}`,
547
+ 'memory/learnings.jsonl',
548
+ 'memory/learnings.consolidated.md',
549
+ // SF11.4/ADR-022 (DF-SF11.4-1/-6): a trilha auditável das endurecidas é escrita por
550
+ // `consolidate` — sem ela no ALLOWED, a própria promoção reprovaria por violação de
551
+ // confinamento contra si mesma. Fica no repositório do pod; NÃO viaja no bundle.
552
+ podMemory.HARDENED_DETAIL_REL,
553
+ ];
554
+ const rollback = (why) => {
555
+ restoreSnapshot(path.resolve(podDir), snapshot);
556
+ refuse(`${why} — ROLLBACK aplicado (pod restaurado byte a byte ao estado anterior à promoção)`);
557
+ };
558
+
559
+ // ── aplicar o conteúdo proposto pelo humano ────────────────────────────────
560
+ const timestamp = new Date().toISOString();
561
+ fs.writeFileSync(targetAbs, newContent);
562
+
563
+ // ── marcação append-only + consolidação (demoção do top-N) ─────────────────
564
+ let record;
565
+ let consolidateResult;
566
+ try {
567
+ ({ record } = podMemory.appendHardening(pod, {
568
+ ref,
569
+ target: { pod, kind: 'knowledge', file, sha_before: shaBefore, sha_after: shaAfter },
570
+ approved_by: approvedBy,
571
+ timestamp,
572
+ }, { podDir }));
573
+ consolidateResult = podMemory.consolidate(pod, { podDir });
574
+ } catch (e) {
575
+ rollback(`marcação/consolidação falhou: ${e.message}`);
576
+ }
577
+
578
+ // ── (DF-SF11.4-4, SUPERSEDE DF-SF10.10-6) o gate mede a CAUSA, não o sintoma que a
579
+ // poda já apagou. O gate anterior comparava `consolidateResult.tokens > TOKEN_BUDGET`
580
+ // — grandeza que `computeConsolidation` NORMALIZA antes de qualquer chamador vê-la
581
+ // (o `while` de poda só termina com tokens ≤ BUDGET ou `ranked` VAZIO). Consequência
582
+ // provada em ADR-022 §C4: ele só era alcançável com `ranked.length === 0`, ou seja,
583
+ // depois que 100% da memória ativa do pod já tinha sido apagada em silêncio (8
584
+ // promoções seguidas zeram o `hn-carousel` sem um único sinal). Agora recusa quando a
585
+ // promoção CAUSOU eviction — fato binário observável, sem limiar novo (DF4-5).
586
+ //
587
+ // SF11.4b/ADR-023 (DF-SF11.4b-1): com a ADMISSÃO ÚNICA, `evicted` passou a carregar
588
+ // TAMBÉM o corte por teto de atenção (`reason: 'top_n'`). O GATE continua medindo só
589
+ // `reason: 'budget'` — e isso não é afrouxamento: promover REMOVE a heurística do pool
590
+ // que compete por slot (endurecida sai do top-N ativo), então uma promoção nunca CAUSA
591
+ // corte por TOP_N; recusar por ele puniria o dono por um fato pré-existente e
592
+ // independente do ato. O RELATO, ao contrário, é sempre o TOTAL (ledger do motor):
593
+ // recusar por uma causa e relatar por outra é o defeito de C2 pela porta dos fundos.
594
+ const evicted = Array.isArray(consolidateResult.evicted) ? consolidateResult.evicted : [];
595
+ const evictedByBudget = evicted.filter((e) => e.reason === 'budget');
596
+ const ledger = podMemory.formatEvictionLedger(pod, consolidateResult);
597
+ if (evictedByBudget.length > 0) {
598
+ rollback(
599
+ `promoção DESCARTARIA ${evictedByBudget.length} heurística(s) do consolidado por ` +
600
+ `${consolidateResult.memoryCost ? 'alvo' : 'teto'} de custo (${consolidateResult.tokenBudget} tok): ` +
601
+ `${evictedByBudget.map((e) => `"${e.trigger}"`).join(', ')}. ` +
602
+ 'Nenhum aprendizado sai da memória ativa em silêncio para abrir espaço a outro: cure o consolidado ' +
603
+ `(marque redundância como obsolete) e promova de novo.\n${ledger}`,
604
+ );
605
+ } else if (ledger) {
606
+ // sem eviction por custo, mas o pool excede o teto de ATENÇÃO: a promoção segue
607
+ // (ela não causou o corte), e o corte deixa de ser mudo mesmo neste caminho.
608
+ console.error(ledger);
609
+ }
610
+
611
+ // ── CONFINAMENTO: só os 3 paths permitidos mudaram sob o podDir ────────────
612
+ const after = snapshotDir(path.resolve(podDir));
613
+ const violations = confinementViolations(snapshot, after, ALLOWED);
614
+ if (violations.length > 0) {
615
+ rollback(`escrita FORA do confinamento permitido (${ALLOWED.join(', ')}): ${violations.join(', ')}`);
616
+ }
617
+
618
+ // ── PROVENIÊNCIA/IDENTIDADE: scanClone (regras 1-3) verde sobre o alvo ─────
619
+ // Determinístico, sem limiar: prosa nova que encarne um mind ou cite token de
620
+ // squad sem citação reprova. (A regra 4, knowledge-sem-fonte, fica FORA por
621
+ // decisão — DF-SF10.10-2.1: reprova 4/5 knowledge reais, inclusive o caso motivador.)
622
+ const cloneViolations = scanClone(targetAbs, deriveDenylist(null));
623
+ if (cloneViolations.length > 0) {
624
+ const detail = cloneViolations.slice(0, 5)
625
+ .map((v) => `${v.rule} (linha ${v.line}): "${v.term}" — ${v.text}`).join('; ');
626
+ rollback(`validate-no-clone: ${cloneViolations.length} violação(ões) no knowledge promovido: ${detail}`);
627
+ }
628
+
629
+ // ── PARIDADE DE COMPILAÇÃO (depois) ────────────────────────────────────────
630
+ let exportCheck = 'n/a';
631
+ let exportDetail = `nenhum pins.json encontrado (nem <podDir>/pins.json, nem exports/${pod}/pins.json) — pod NÃO é export-capaz (DEBT-SF10.10-E: prova mais fraca, declarada)`;
632
+ if (pinsPath) {
633
+ const ca = compileLock(podDir, pinsPath, 'pod-export-compile (depois)');
634
+ if (ca.status !== 0 || !ca.lock) {
635
+ rollback(`pós-gate vermelho — ${ca.label} exit ${ca.status}:\n${ca.out}`);
636
+ }
637
+ const diverged = perFileShaDiff(lockBefore, ca.lock);
638
+ const expected = [`knowledge/${file}`, 'memory/learnings.consolidated.md'].sort();
639
+ const unexpected = diverged.filter((k) => !expected.includes(k));
640
+ if (unexpected.length > 0) {
641
+ rollback(`per_file_sha divergiu FORA do alvo (esperado só ${expected.join(', ')}): ${unexpected.join(', ')}`);
642
+ }
643
+ if (!diverged.includes(`knowledge/${file}`)) {
644
+ rollback(`per_file_sha NÃO registrou mudança em knowledge/${file} — o compilador não vê o arquivo promovido (knowledge órfão/não anexado?)`);
645
+ }
646
+ exportCheck = 'ok';
647
+ exportDetail = `per_file_sha divergiu SOMENTE em ${diverged.join(', ')}; pod_sha ${lockBefore.pod_sha.slice(0, 12)} → ${ca.lock.pod_sha.slice(0, 12)} ` +
648
+ '(muda ⇒ pod-export-release --if-changed detecta-e-bloqueia o selo antigo, DF4-4)';
649
+ }
650
+
651
+ // ── relatório ──────────────────────────────────────────────────────────────
652
+ console.log('# forge-harden-learning — promoção de KNOWLEDGE aplicada e provada');
653
+ console.log(`pod: ${pod} · alvo: knowledge/${file}`);
654
+ console.log(`learning: ${learningId} ("${entry.trigger}", ref: ${ref})`);
655
+ console.log(`aprovado por: ${approvedBy} em ${timestamp}`);
656
+ console.log(`sha_before: ${shaBefore}`);
657
+ console.log(`sha_after : ${shaAfter}`);
658
+ console.log(`gates: baseline validate+smoke 4/4 exit 0 · confinamento OK (${ALLOWED.join(', ')}) · ` +
659
+ `scanClone 0 violação(ões) · export_check: ${exportCheck}`);
660
+ console.log(`export_check(${exportCheck}): ${exportDetail}`);
661
+ console.log(NO_SMOKE_AFTER_NOTICE);
662
+ console.log(`QUALIDADE: ${QUALITY_NOTICE}`);
663
+ console.log(`diff: NÃO embutido no registro (DF-SF10.10-1) — revise com \`git diff -- ${path.join(podDir, 'knowledge', file)}\``);
664
+ console.log(`memória: registro hardening append-only gravado (${record.timestamp}) em ${memPaths.raw}; ` +
665
+ `consolidado: ${consolidateResult.hardenedCount} endurecida(s) 🔒 (referência agregada; detalhe íntegro em ` +
666
+ `${podMemory.HARDENED_DETAIL_REL}), top-N ativo: ${consolidateResult.topNCount}, ` +
667
+ `${consolidateResult.tokens}/${podMemory.TOKEN_BUDGET} tokens, 0 heurística descartada por orçamento`);
668
+ process.exit(0);
669
+ }
670
+
671
+ // ── DETECÇÃO (DF-SF10.10-3) — read-only, exit 0 SEMPRE, nunca promove ────────
672
+ function listCandidatesMode(args) {
673
+ const podDir = args[0];
674
+ const { pod } = podIdentity(podDir, args);
675
+ const candidates = podMemory.listCandidates(pod, { podDir });
676
+
677
+ console.log(`# candidatos a endurecimento — pod "${pod}"`);
678
+ console.log('Heurísticas do consolidado CURADO sem registro de hardening (diferença de conjuntos).');
679
+ console.log('Esta lista NÃO classifica "de conteúdo" vs "mecanizável": não há oráculo para isso — a');
680
+ console.log('tradução heurística→forma (veto ou knowledge) é julgamento HUMANO (ADR-012 §Escopo OUT).');
681
+ console.log('');
682
+ if (candidates.length === 0) {
683
+ console.log('_Nenhum candidato: toda heurística do consolidado já tem registro de hardening (ou o consolidado está vazio)._');
684
+ } else {
685
+ for (const c of candidates) {
686
+ console.log(`- [${c.section}] **[${c.trigger}]** ${c.recommendation} _(x${c.occurrences})_`);
687
+ console.log(` --learning ${c.last_id}`);
688
+ }
689
+ }
690
+ console.log('');
691
+ console.log(`${candidates.length} candidato(s). Read-only: nada foi promovido, nada foi escrito.`);
692
+ console.log('Disciplina de PRÉ-RELEASE (nunca gate): rode antes de `pod-export-release.cjs` e decida o que promover.');
693
+ process.exit(0);
694
+ }
695
+
696
+ // ── ALVO `veto` (SF7.6) — fluxo original, inalterado ─────────────────────────
697
+ function hardenVeto(args) {
698
+ const podDir = args[0];
699
+
700
+ const learningId = argVal(args, '--learning');
701
+ const stepId = argVal(args, '--step');
702
+ const assertionJson = argVal(args, '--assertion');
703
+ const approvedBy = argVal(args, '--approved-by');
704
+
705
+ if (!learningId) usage('--learning <id> é obrigatório (id da entrada no learnings.jsonl)');
706
+ if (!stepId) usage('--step <stepId> é obrigatório (etapa-alvo do workflow)');
707
+ if (!assertionJson) usage("--assertion '<json>' é obrigatório (a forma de veto proposta — julgamento humano)");
708
+ // (b) aprovação do dono OBRIGATÓRIA — recusa explícita, nunca aplica sem ela.
709
+ if (!approvedBy || approvedBy.startsWith('--')) {
710
+ refuse('promoção exige --approved-by <nome> (aprovação humana registrada — padrão migration-manifest); sem ela NADA é aplicado');
711
+ }
712
+
713
+ let assertion;
714
+ try {
715
+ assertion = JSON.parse(assertionJson);
716
+ } catch (e) {
717
+ refuse(`--assertion não é JSON válido: ${e.message}`);
718
+ }
719
+ checkAssertionShape(assertion);
720
+
721
+ // ── pod-spec.json = fonte declarada da verdade (ADR-005) ────────────────────
722
+ const specPath = path.join(podDir, 'pod-spec.json');
723
+ if (!fs.existsSync(specPath)) {
724
+ refuse(`pod-spec.json não encontrado em ${podDir} — o hardening edita a SPEC (fonte da verdade, ADR-005) e regenera o wf; sem spec declarada não há o que promover`);
725
+ }
726
+ const specTextBefore = fs.readFileSync(specPath, 'utf8');
727
+ let spec;
728
+ try {
729
+ spec = JSON.parse(specTextBefore);
730
+ } catch (e) {
731
+ refuse(`pod-spec.json inválido: ${e.message}`);
732
+ }
733
+
734
+ const pod = spec.pod || path.basename(path.resolve(podDir));
735
+ const workflowId = slug(argVal(args, '--workflow') || spec.workflowId || spec.pod || 'main');
736
+ const wfRel = path.join('workflows', `wf-${workflowId}.yaml`);
737
+ const wfPath = path.join(podDir, wfRel);
738
+ if (!fs.existsSync(wfPath)) refuse(`workflow-alvo não encontrado: ${wfPath}`);
739
+ const smokePath = wfPath.replace(/\.ya?ml$/, '.smoke.json');
740
+ if (!fs.existsSync(smokePath)) {
741
+ refuse(`cenário de smoke não encontrado: ${smokePath} — sem ele não há prova 4/4 antes/depois (AC5); autore o sidecar primeiro`);
742
+ }
743
+
744
+ // ── localizar a heurística no log cru e exigir presença no consolidado ──────
745
+ const { entry, ref } = resolveLearning(pod, podDir, learningId);
746
+
747
+ // ── localizar a etapa-alvo na spec ──────────────────────────────────────────
748
+ const steps = stepsFromSpec(spec);
749
+ const stepIdx = steps.findIndex((s) => s.id === slug(stepId));
750
+ if (stepIdx < 0) {
751
+ refuse(`etapa "${stepId}" não existe na spec (etapas: ${steps.map((s) => s.id).join(', ')})`);
752
+ }
753
+ const agentEntry = spec.agents[stepIdx];
754
+ if (typeof agentEntry === 'string') {
755
+ refuse(`agents[${stepIdx}] da spec é string simples — hardening exige entrada-objeto para declarar veto[] (edite a spec para a forma objeto antes)`);
756
+ }
757
+
758
+ // dedup: mesma asserção já declarada na etapa ⇒ recusa (idempotência explícita)
759
+ const existing = Array.isArray(agentEntry.veto) ? agentEntry.veto : [];
760
+ const sig = assertionSig(assertion);
761
+ if (existing.some((a) => assertionSig(a) === sig)) {
762
+ refuse(`a etapa "${stepId}" já declara esta exata asserção — promoção repetida não aplica`);
763
+ }
764
+
765
+ // ── BASELINE (antes): estático + smoke 4/4 têm que estar verdes ─────────────
766
+ const staticBefore = run(['scripts/validate-pod-workflow.cjs', wfPath], 'validate-pod-workflow (antes)');
767
+ if (staticBefore.status !== 0) {
768
+ refuse(`baseline vermelho — ${staticBefore.label} exit ${staticBefore.status}:\n${staticBefore.out}`);
769
+ }
770
+ const smokeBefore = run(['scripts/smoke-pod-workflow.cjs', wfPath, '--scenario', smokePath], 'smoke 4/4 (antes)');
771
+ if (smokeBefore.status !== 0) {
772
+ refuse(`baseline vermelho — ${smokeBefore.label} exit ${smokeBefore.status}:\n${smokeBefore.out}`);
773
+ }
774
+
775
+ // snapshots p/ rollback e p/ diff de preservação
776
+ const wfBefore = fs.readFileSync(wfPath, 'utf8');
777
+ const ownedTemplates = steps.map((s) => path.join('templates', `${s.id}-tmpl.md`));
778
+ const tmplBefore = new Map(ownedTemplates.map((rel) => {
779
+ const abs = path.join(podDir, rel);
780
+ return [rel, fs.existsSync(abs) ? fs.readFileSync(abs, 'utf8') : null];
781
+ }));
782
+
783
+ // ── aplicar a asserção à SPEC (com rastreabilidade _sources) ────────────────
784
+ const timestamp = new Date().toISOString();
785
+ // sem veto declarado, o gerador emitia o default (confianca) — preservá-lo
786
+ // explicitamente antes de somar a nova asserção (nenhuma regressão de veto).
787
+ if (!Array.isArray(agentEntry.veto) || agentEntry.veto.length === 0) {
788
+ agentEntry.veto = [{ type: 'field_matches', field: 'confianca', pattern: '^(alta|media|baixa)$' }];
789
+ }
790
+ agentEntry.veto.push({
791
+ ...assertion,
792
+ _sources: `hardening SF7.6: learning ${learningId} ("${entry.trigger}") promovido a enforcement por ${approvedBy} em ${timestamp} (forge-harden-learning.cjs)`,
793
+ });
794
+
795
+ const rollback = (why) => {
796
+ fs.writeFileSync(specPath, specTextBefore);
797
+ fs.writeFileSync(wfPath, wfBefore);
798
+ for (const [rel, content] of tmplBefore) {
799
+ if (content != null) fs.writeFileSync(path.join(podDir, rel), content);
800
+ }
801
+ refuse(`${why} — ROLLBACK aplicado (spec, wf e templates restaurados; memória intocada)`);
802
+ };
803
+
804
+ fs.writeFileSync(specPath, `${JSON.stringify(spec, null, 2)}\n`);
805
+
806
+ // ── regenerar SÓ o alvo via mecanismo provado (--additive + owns) ───────────
807
+ try {
808
+ generatePod(spec, path.resolve(podDir), {
809
+ additive: true,
810
+ owns: [wfRel, ...ownedTemplates],
811
+ // SF12.4a/ADR-030 Bloco O (AC48/Emenda 1) — `*harden-learning` reemite a partir da
812
+ // spec PRÉ-EXISTENTE (re-emitida, não autorada agora): sempre 'derived'.
813
+ specOrigin: 'derived',
814
+ });
815
+ } catch (e) {
816
+ rollback(`forge-generate --additive falhou: ${e.message}`);
817
+ }
818
+
819
+ // ── prova de preservação: diff confinado à etapa-alvo ───────────────────────
820
+ const wfAfter = fs.readFileSync(wfPath, 'utf8');
821
+ const before = splitWfBlocks(wfBefore);
822
+ const after = splitWfBlocks(wfAfter);
823
+ if (before.header !== after.header) rollback('regeneração alterou o cabeçalho do wf (fora da etapa-alvo)');
824
+ const ids = new Set([...before.blocks.keys(), ...after.blocks.keys()]);
825
+ const changedSteps = [];
826
+ for (const id of ids) {
827
+ const b = (before.blocks.get(id) || []).join('\n');
828
+ const a = (after.blocks.get(id) || []).join('\n');
829
+ if (b !== a) changedSteps.push(id);
830
+ }
831
+ if (changedSteps.length !== 1 || changedSteps[0] !== slug(stepId)) {
832
+ rollback(`regeneração mudou etapas além da alvo (mudadas: ${changedSteps.join(', ') || 'nenhuma'})`);
833
+ }
834
+ const changedTemplates = ownedTemplates.filter((rel) => {
835
+ const abs = path.join(podDir, rel);
836
+ const now = fs.existsSync(abs) ? fs.readFileSync(abs, 'utf8') : null;
837
+ return now !== tmplBefore.get(rel);
838
+ });
839
+ if (changedTemplates.some((rel) => rel !== path.join('templates', `${slug(stepId)}-tmpl.md`))) {
840
+ rollback(`regeneração mudou template de etapa não-alvo (${changedTemplates.join(', ')})`);
841
+ }
842
+
843
+ // ── DEPOIS: estático + smoke 4/4 sobre o wf regenerado ──────────────────────
844
+ const staticAfter = run(['scripts/validate-pod-workflow.cjs', wfPath], 'validate-pod-workflow (depois)');
845
+ if (staticAfter.status !== 0) rollback(`pós-gate vermelho — ${staticAfter.label} exit ${staticAfter.status}:\n${staticAfter.out}`);
846
+ const smokeAfter = run(['scripts/smoke-pod-workflow.cjs', wfPath, '--scenario', smokePath], 'smoke 4/4 (depois)');
847
+ if (smokeAfter.status !== 0) rollback(`pós-gate vermelho — ${smokeAfter.label} exit ${smokeAfter.status}:\n${smokeAfter.out}`);
848
+
849
+ // ── marcação append-only + demoção do top-N (pod-memory, ADR-012) ───────────
850
+ const { record } = podMemory.appendHardening(pod, {
851
+ ref,
852
+ target: { pod, workflowId, step: slug(stepId), assertion },
853
+ approved_by: approvedBy,
854
+ timestamp,
855
+ }, { podDir });
856
+ const consolidateResult = podMemory.consolidate(pod, { podDir });
857
+
858
+ // diff textual do wf (linhas adicionadas na etapa-alvo) p/ o registro
859
+ const beforeLines = new Set(wfBefore.split('\n'));
860
+ const addedLines = wfAfter.split('\n').filter((l) => !beforeLines.has(l));
861
+
862
+ console.log(`# forge-harden-learning — promoção aplicada e provada`);
863
+ console.log(`pod: ${pod} · workflow: wf-${workflowId} · etapa-alvo: ${slug(stepId)}`);
864
+ console.log(`learning: ${learningId} (ref: ${ref})`);
865
+ console.log(`aprovado por: ${approvedBy} em ${timestamp}`);
866
+ console.log(`asserção promovida: ${assertionSig(assertion)}`);
867
+ console.log(`gates: estático antes/depois exit 0 · smoke 4/4 antes/depois exit 0`);
868
+ console.log(`diff do wf confinado à etapa-alvo (${addedLines.length} linha(s) adicionada(s)):`);
869
+ for (const l of addedLines) console.log(` + ${l}`);
870
+ // SF11.4/DF-SF11.4-2: poda nunca é silenciosa — o alvo `veto` não tem rollback de
871
+ // memória (a marcação é o último passo, depois dos gates), então o canal aqui é o
872
+ // LEDGER nomeado em stderr, nunca o silêncio.
873
+ // SF11.4b/DF-SF11.4b-1: a mensagem vem do motor (fonte única) e reporta o TOTAL das
874
+ // duas vias — este emissor não monta contador próprio nem cita só uma causa.
875
+ const evictedVeto = Array.isArray(consolidateResult.evicted) ? consolidateResult.evicted : [];
876
+ const ledgerVeto = podMemory.formatEvictionLedger(pod, consolidateResult);
877
+ if (ledgerVeto) console.error(ledgerVeto);
878
+ const evictedBreakdown = `${evictedVeto.length} NÃO admitida(s) ` +
879
+ `(${evictedVeto.filter((e) => e.reason === 'top_n').length} por teto de atenção · ` +
880
+ `${evictedVeto.filter((e) => e.reason === 'budget').length} por custo)`;
881
+ console.log(`memória: registro hardening append-only gravado (${record.timestamp}); ` +
882
+ `consolidado: ${consolidateResult.hardenedCount} endurecida(s) 🔒 (referência agregada; detalhe em ` +
883
+ `${podMemory.HARDENED_DETAIL_REL}), top-N ativo: ${consolidateResult.topNCount}, ` +
884
+ `${evictedBreakdown}`);
885
+ process.exit(0);
886
+ }
887
+
888
+ // ── despacho por MODO ────────────────────────────────────────────────────────
889
+ function main() {
890
+ const args = process.argv.slice(2);
891
+ if (args.includes('--help') || args.includes('-h')) {
892
+ help();
893
+ process.exit(0);
894
+ }
895
+ const podDir = args[0];
896
+ if (!podDir || podDir.startsWith('--')) usage('informe o <podDir>');
897
+ if (!fs.existsSync(podDir)) usage(`podDir não encontrado: ${podDir}`);
898
+
899
+ if (args.includes('--list-candidates')) return listCandidatesMode(args);
900
+
901
+ const targetType = argVal(args, '--target-type') || podMemory.HARDEN_KIND_VETO;
902
+ if (targetType === podMemory.HARDEN_KIND_KNOWLEDGE) return hardenKnowledge(args);
903
+ if (targetType !== podMemory.HARDEN_KIND_VETO) {
904
+ usage(`--target-type "${targetType}" desconhecido (use "veto" — default — ou "knowledge")`);
905
+ }
906
+ return hardenVeto(args);
907
+ }
908
+
909
+ if (require.main === module) main();
910
+
911
+ module.exports = {
912
+ splitWfBlocks,
913
+ assertionSig,
914
+ checkAssertionShape,
915
+ // SF10.10 — invariantes do alvo knowledge, exportados para teste hermético
916
+ confinementViolations,
917
+ perFileShaDiff,
918
+ snapshotDir,
919
+ declaredKnowledge,
920
+ QUALITY_NOTICE,
921
+ };