@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,1146 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+ /**
4
+ * migration-manifest.cjs — ESCRITOR ÚNICO do estado de migração squad→pod v0.3
5
+ * (Story SF3.1; Decisão A.1 do design `*migrate-squad`, ADR-006 ACEITO).
6
+ *
7
+ * `docs/migration/MIGRATION-<squad>.yaml` sobrevive à deleção do squad e é o
8
+ * estado durável da migração (a linha carrega `phase`; não há state file paralelo).
9
+ * SÓ este script escreve o arquivo — o forge-chief invoca subcomandos, nunca edita
10
+ * o YAML. Espelha a tese do runner↔state.json: o relato do agente nunca é evidência.
11
+ *
12
+ * TRÊS camadas de garantia, com o limite de cada uma DECLARADO:
13
+ * 1. integrity hash (sha256 do corpo canônico) — tamper-EVIDENTE contra edição
14
+ * ACIDENTAL; declaradamente NÃO anti-recomputação (o algoritmo é público). Por
15
+ * isso NÃO é a raiz de confiança. Manifest com hash divergente → exit 3.
16
+ * 2. RAIZ DE CONFIANÇA EM CÓDIGO (fecha G-r2-1.1): os checks de fechamento são
17
+ * DERIVADOS pelo helper do TIPO da linha e aplicados aos CAMPOS DE EVIDÊNCIA —
18
+ * nunca a comandos gravados no YAML. `verify[]` é só CACHE DE AUDITORIA (o que
19
+ * rodou, quando, com que exit); close-line/verify NUNCA o executam como fonte.
20
+ * Consequência: `verify: [{cmd: true}]` não compra nada — para passar seria
21
+ * preciso fabricar os artefatos em disco (state.json com binding, wf válido).
22
+ * 3. BINDING de evidência à linha (fecha G-r2-1.2/1.3): run_ids só valem se
23
+ * `state.json.workflow`+`.pod` == os da linha; spec_hash conferido contra o
24
+ * disco; wf_path RE-EXECUTA validate-pod-workflow.cjs (exit 0 exigido), não
25
+ * confere só existência. O barato e determinístico se RE-EXECUTA (close e F3).
26
+ *
27
+ * ESCOPO SF3.1: `advance`/`close-line` recebem o contrato de fases e a verificação
28
+ * de binding/evidência, mas SEM a chamada ao SWEEP (que só existe a partir de SF3.2)
29
+ * e SEM feature-set/motor_files[]/exit 4 (SF3.2). A assinatura pública é estável o
30
+ * bastante para SF3.2 apenas ESTENDER (ver Dev Notes da story: hook 'pre-gates-check').
31
+ *
32
+ * Subcomandos: init · triage-set · triage-approve · command-close · dispense-close ·
33
+ * address-set · open-line · reopen-line · advance · close-line · set-anchor · waiver-set ·
34
+ * coverage · verify
35
+ * Exit: 0 ok · 2 recusa (colisão/binding/spec_hash/fase/evidência forjada/wf inválido)
36
+ * · 3 integrity divergente (manifest adulterado) · 1 erro de uso/interno OU
37
+ * stale detectado no `verify` (fail-loud: nunca stale silencioso em exit 0).
38
+ *
39
+ * Node >= 18, sem dependências externas (reusa parseYaml de validate-pod-workflow.cjs).
40
+ */
41
+
42
+ const fs = require('node:fs');
43
+ const path = require('node:path');
44
+ const crypto = require('node:crypto');
45
+ const { spawnSync } = require('node:child_process');
46
+ const { parseYaml } = require('./validate-pod-workflow.cjs');
47
+
48
+ const PHASES = ['spec', 'generated', 'distilled', 'linted', 'gates', 'closed'];
49
+ const TRIAGE_DISPOSITIONS = new Set(['migrate', 'dispense', 'command', 'knowledge']);
50
+ const TRIAGE_BASES = new Set(['dado', 'julgamento']);
51
+
52
+ // Motor pinado (M-r2-4): lista DEFAULT dos arquivos self-contained que o motor de
53
+ // verificação usa (evalVeto/checkTemplateConformance/countListItemsInSection vivem
54
+ // TODOS em pc-run-pod.cjs — conferido no design D.G2). O motor_hash é sha256 sobre a
55
+ // lista; a lista é DADO versionado do manifest, alterável SÓ por decisão de re-âncora
56
+ // (set-anchor). pc-run-pod.cjs aqui é o PÓS-fix da SF3.1 (evalVeto count_gte corrigido).
57
+ const DEFAULT_MOTOR_FILES = [
58
+ 'scripts/pc-run-pod.cjs',
59
+ 'scripts/pod-memory.cjs',
60
+ 'scripts/validate-pod-workflow.cjs',
61
+ 'scripts/smoke-pod-workflow.cjs',
62
+ ];
63
+
64
+ // ---------------------------------------------------------------------------
65
+ // Erros com código de saída dedicado (fail-loud; nunca "conserta" silenciosamente).
66
+ // ---------------------------------------------------------------------------
67
+ class ManifestError extends Error {
68
+ constructor(message, exitCode) {
69
+ super(message);
70
+ this.name = 'ManifestError';
71
+ this.exitCode = exitCode;
72
+ }
73
+ }
74
+ const refuse = (m) => { throw new ManifestError(m, 2); }; // recusa de contrato
75
+ const tamper = (m) => { throw new ManifestError(m, 3); }; // integrity/adulteração
76
+ const usage = (m) => { throw new ManifestError(m, 1); }; // uso/interno
77
+ const reanchor = (m) => { throw new ManifestError(m, 4); }; // re-âncora obrigatória (feature/motor)
78
+
79
+ function sha256(buf) {
80
+ return crypto.createHash('sha256').update(buf).digest('hex');
81
+ }
82
+ function sha256File(p) {
83
+ return sha256(fs.readFileSync(p));
84
+ }
85
+
86
+ // ---------------------------------------------------------------------------
87
+ // Serializador CANÔNICO (subconjunto emitido = subconjunto lido por parseYaml).
88
+ // Chaves ORDENADAS; strings SEMPRE entre aspas (round-trip de tipo garantido);
89
+ // números/booleanos crus. O integrity hash é sobre o TEXTO do corpo (sem depender
90
+ // de round-trip do parser) — robusto contra qualquer quirk de leitura.
91
+ // ---------------------------------------------------------------------------
92
+ function q(s) {
93
+ return `"${String(s).replace(/\\/g, '\\\\').replace(/"/g, '\\"')}"`;
94
+ }
95
+ function scalar(v) {
96
+ if (v === null || v === undefined) return '""';
97
+ if (typeof v === 'number' || typeof v === 'boolean') return String(v);
98
+ const s = String(v);
99
+ if (/[\r\n]/.test(s)) refuse(`valor de campo com quebra de linha não é permitido no manifest: ${JSON.stringify(s)}`);
100
+ return q(s);
101
+ }
102
+ function isPlainObject(v) {
103
+ return v !== null && typeof v === 'object' && !Array.isArray(v);
104
+ }
105
+ function serMap(obj, sp) {
106
+ const pad = ' '.repeat(sp);
107
+ const out = [];
108
+ for (const k of Object.keys(obj).sort()) {
109
+ const v = obj[k];
110
+ if (Array.isArray(v)) {
111
+ if (v.length === 0) { out.push(`${pad}${k}: []`); continue; }
112
+ out.push(`${pad}${k}:`);
113
+ for (const item of v) {
114
+ if (isPlainObject(item)) {
115
+ const inner = serMap(item, sp + 4).split('\n');
116
+ out.push(`${' '.repeat(sp + 2)}- ${inner[0].slice(sp + 4)}`);
117
+ for (let j = 1; j < inner.length; j++) out.push(inner[j]);
118
+ } else {
119
+ out.push(`${' '.repeat(sp + 2)}- ${scalar(item)}`);
120
+ }
121
+ }
122
+ } else if (isPlainObject(v)) {
123
+ if (Object.keys(v).length === 0) { out.push(`${pad}${k}: {}`); continue; }
124
+ out.push(`${pad}${k}:`);
125
+ out.push(serMap(v, sp + 2));
126
+ } else {
127
+ out.push(`${pad}${k}: ${scalar(v)}`);
128
+ }
129
+ }
130
+ return out.join('\n');
131
+ }
132
+
133
+ function serializeBody(doc) {
134
+ const rest = {};
135
+ for (const k of Object.keys(doc)) if (k !== 'integrity') rest[k] = doc[k];
136
+ return `${serMap(rest, 0)}\n`;
137
+ }
138
+
139
+ // ---------------------------------------------------------------------------
140
+ // Leitura/escrita do manifest. O integrity hash é o sha256 do CORPO (tudo menos
141
+ // a última linha `integrity:`). Hand-edit em QUALQUER linha do corpo muda o texto
142
+ // ⇒ hash diverge ⇒ exit 3 (antes mesmo de parsear).
143
+ // ---------------------------------------------------------------------------
144
+ function writeManifest(mfPath, doc) {
145
+ const body = serializeBody(doc);
146
+ const integrity = sha256(Buffer.from(body, 'utf8'));
147
+ fs.mkdirSync(path.dirname(mfPath), { recursive: true });
148
+ fs.writeFileSync(mfPath, `${body}integrity: ${q(integrity)}\n`, 'utf8');
149
+ doc.integrity = integrity;
150
+ return doc;
151
+ }
152
+
153
+ function readManifest(mfPath) {
154
+ if (!fs.existsSync(mfPath)) refuse(`manifest inexistente: ${rel(mfPath)} — rode 'init' primeiro`);
155
+ const text = fs.readFileSync(mfPath, 'utf8');
156
+ const lines = text.split('\n');
157
+ let idx = -1;
158
+ for (let i = lines.length - 1; i >= 0; i--) {
159
+ if (/^integrity:\s*"[0-9a-f]{64}"\s*$/.test(lines[i])) { idx = i; break; }
160
+ }
161
+ if (idx < 0) tamper(`manifest sem campo integrity válido — não foi escrito pelo escritor único (${rel(mfPath)})`);
162
+ const stored = lines[idx].match(/"([0-9a-f]{64})"/)[1];
163
+ const body = `${lines.slice(0, idx).join('\n')}\n`;
164
+ const recomputed = sha256(Buffer.from(body, 'utf8'));
165
+ if (stored !== recomputed) {
166
+ tamper(`integrity divergente (stored=${stored.slice(0, 12)}… recomputed=${recomputed.slice(0, 12)}…) — ` +
167
+ `manifest editado fora do escritor único (${rel(mfPath)}). Nenhuma operação prossegue.`);
168
+ }
169
+ let doc;
170
+ try { doc = parseYaml(body); } catch (e) { tamper(`manifest ilegível: ${e.message}`); }
171
+ if (!isPlainObject(doc)) tamper('manifest com estrutura de topo inválida');
172
+ doc.triage = Array.isArray(doc.triage) ? doc.triage : [];
173
+ doc.lines = Array.isArray(doc.lines) ? doc.lines : [];
174
+ return doc;
175
+ }
176
+
177
+ function rel(p) {
178
+ return path.relative(process.cwd(), p) || p;
179
+ }
180
+
181
+ // ---------------------------------------------------------------------------
182
+ // Localização da linha / fases.
183
+ // ---------------------------------------------------------------------------
184
+ function findLine(doc, workflow) {
185
+ return doc.lines.find((l) => l.workflow === workflow) || null;
186
+ }
187
+ function phaseIndex(p) {
188
+ const i = PHASES.indexOf(p);
189
+ if (i < 0) usage(`fase desconhecida: "${p}" (esperado: ${PHASES.join('|')})`);
190
+ return i;
191
+ }
192
+
193
+ // ---------------------------------------------------------------------------
194
+ // Baseline snapshot (fecha M-r2-2): listagem recursiva + sha256 por arquivo.
195
+ // ---------------------------------------------------------------------------
196
+ function snapshotTree(rootDir) {
197
+ const out = [];
198
+ if (!fs.existsSync(rootDir)) return out;
199
+ const walk = (dir) => {
200
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
201
+ const abs = path.join(dir, entry.name);
202
+ if (entry.isDirectory()) walk(abs);
203
+ else if (entry.isFile()) out.push({ path: path.relative(rootDir, abs), sha256: sha256File(abs) });
204
+ }
205
+ };
206
+ walk(rootDir);
207
+ out.sort((a, b) => a.path.localeCompare(b.path));
208
+ return out;
209
+ }
210
+
211
+ // ---------------------------------------------------------------------------
212
+ // RAIZ DE CONFIANÇA EM CÓDIGO — verificação de UMA linha fechada. Os checks são
213
+ // DERIVADOS aqui (código), aplicados aos campos de evidência da linha. `verify[]`
214
+ // NÃO é consultado como fonte — só existe como cache de auditoria. Retorna
215
+ // { ok, failures[] }. RE-EXECUTA o determinístico (validate-pod-workflow.cjs).
216
+ // ---------------------------------------------------------------------------
217
+ function verifyLineEvidence(line, opts = {}) {
218
+ const failures = [];
219
+ const baseDir = opts.baseDir || process.cwd();
220
+ const resolve = (p) => (path.isAbsolute(p) ? p : path.resolve(baseDir, p));
221
+
222
+ // (1) spec_hash — conferido contra o disco (o que está em specs/ AGORA).
223
+ if (!line.spec_path || !line.spec_hash) {
224
+ failures.push('linha sem spec_path/spec_hash — evidência de spec ausente');
225
+ } else {
226
+ const specAbs = resolve(line.spec_path);
227
+ if (!fs.existsSync(specAbs)) failures.push(`spec inexistente em disco: ${line.spec_path}`);
228
+ else {
229
+ const now = sha256File(specAbs);
230
+ if (now !== line.spec_hash) {
231
+ failures.push(`spec_hash divergente: linha=${String(line.spec_hash).slice(0, 12)}… disco=${now.slice(0, 12)}… (spec mudou após open-line)`);
232
+ }
233
+ }
234
+ }
235
+
236
+ // (2) wf_path — RE-EXECUTA validate-pod-workflow.cjs (exit 0), não confere só existência.
237
+ if (!line.wf_path) {
238
+ failures.push('linha sem wf_path — evidência de workflow ausente');
239
+ } else {
240
+ const wfAbs = resolve(line.wf_path);
241
+ if (!fs.existsSync(wfAbs)) failures.push(`wf_path inexistente em disco: ${line.wf_path}`);
242
+ else {
243
+ const args = [path.join(__dirname, 'validate-pod-workflow.cjs'), wfAbs];
244
+ if (line.pod_dir) { args.push('--pod-root', resolve(line.pod_dir)); }
245
+ const r = spawnSync(process.execPath, args, { encoding: 'utf8' });
246
+ if (r.status !== 0) {
247
+ failures.push(`validate-pod-workflow re-executado FALHOU (exit ${r.status}) em ${line.wf_path}: ${(r.stderr || r.stdout || '').trim().split('\n').pop()}`);
248
+ }
249
+ }
250
+ }
251
+
252
+ // (3) run_ids — existência + BINDING (state.json.workflow+.pod == linha).
253
+ const runIds = Array.isArray(line.run_ids) ? line.run_ids : [];
254
+ if (runIds.length === 0) {
255
+ failures.push('linha sem run_ids — evidência dinâmica (G5) ausente');
256
+ }
257
+ for (const rid of runIds) {
258
+ const ridAbs = resolve(rid);
259
+ if (!fs.existsSync(ridAbs)) { failures.push(`run_id inexistente (forjado): ${rid}`); continue; }
260
+ let st;
261
+ try { st = JSON.parse(fs.readFileSync(ridAbs, 'utf8')); } catch (e) { failures.push(`run_id ilegível: ${rid} (${e.message})`); continue; }
262
+ if (st.workflow !== line.workflow || st.pod !== line.pod) {
263
+ failures.push(`run_id sem binding: ${rid} tem workflow="${st.workflow}"/pod="${st.pod}", linha exige "${line.workflow}"/"${line.pod}" (evidência de OUTRA linha)`);
264
+ } else if (st.spec_hash != null && line.spec_hash != null && st.spec_hash !== line.spec_hash) {
265
+ // Binding de spec_hash é conferido QUANDO presente no state (forward-compat SF3.2);
266
+ // o runner atual não emite spec_hash, então ausência não reprova (não se inventa prova).
267
+ failures.push(`run_id com spec_hash divergente: ${rid} (${String(st.spec_hash).slice(0, 12)}… ≠ ${String(line.spec_hash).slice(0, 12)}…)`);
268
+ }
269
+ }
270
+
271
+ return { ok: failures.length === 0, failures };
272
+ }
273
+
274
+ // ---------------------------------------------------------------------------
275
+ // FEATURE-SET + MOTOR PINADO (SF3.2 — Decisão D.G2 / M-r2-1 / M-r2-4).
276
+ // Decisão de MÁQUINA (não lista de palpites): o feature-set é computado
277
+ // deterministicamente do wf-*.yaml; o diff features(wf) ⊄ features_covered dispara
278
+ // re-âncora (exit 4). O motor é pinado por sha256 sobre a lista versionada.
279
+ // ---------------------------------------------------------------------------
280
+ // CALIBRAÇÃO 2026-07-25 (ADR-006 §Consequências, F2 real do wf-money-model-design):
281
+ // `agents_shared` SAIU do tuple de re-âncora. Verificação código-verificada do runner
282
+ // (f3 @ f9b49b7) provou que o motor é AGENT-AGNÓSTICO — `dispatchStep`/`state.steps{}`
283
+ // são keyed por stepId (subagente fresco por etapa), `step.agent` é só rótulo do brief
284
+ // (pc-run-pod.cjs:339) e campo informativo do state (:672); verifyStep/fix_cycle/memória
285
+ // não ramificam por identidade de agente. Logo reuso de agente NÃO exercita caminho de
286
+ // motor não coberto pela âncora — a via correta é calibrar o tuple, não override. Se um
287
+ // refactor futuro fizer o runner ramificar por agente, o `motor_hash` dispara a decisão
288
+ // (a proteção não depende da lista). Tuple remanescente: assertion_types[], dag_shape,
289
+ // checkpoint_positions, fix_loop. Daniel aprovou o LITE com este fundamento.
290
+ function computeFeatureSet(wfDoc) {
291
+ const steps = Array.isArray(wfDoc.steps) ? wfDoc.steps : [];
292
+ const assertionTypes = new Set();
293
+ let fixLoop = false;
294
+ const fanIn = new Map(); // id -> nº de deps
295
+ const fanOut = new Map(); // id -> nº de dependentes
296
+ for (const s of steps) {
297
+ const as = (s.veto && Array.isArray(s.veto.assertions)) ? s.veto.assertions : [];
298
+ for (const a of as) if (a && a.type) assertionTypes.add(a.type);
299
+ const deps = Array.isArray(s.depends_on) ? s.depends_on : [];
300
+ fanIn.set(s.id, deps.length);
301
+ for (const d of deps) fanOut.set(d, (fanOut.get(d) || 0) + 1);
302
+ if (s.fix_loop === true || (s.checkpoint && s.checkpoint.loop === true)) fixLoop = true;
303
+ }
304
+ let dagShape = 'linear';
305
+ for (const s of steps) {
306
+ if ((fanOut.get(s.id) || 0) > 1 || (fanIn.get(s.id) || 0) > 1) { dagShape = 'branch'; break; }
307
+ }
308
+ const positions = new Set();
309
+ for (const s of steps) {
310
+ if (s.checkpoint && s.checkpoint.blocking === true) {
311
+ positions.add((fanOut.get(s.id) || 0) > 0 ? 'mid' : 'final');
312
+ }
313
+ }
314
+ return {
315
+ assertion_types: [...assertionTypes].sort(),
316
+ dag_shape: dagShape,
317
+ checkpoint_positions: [...positions].sort(),
318
+ fix_loop: fixLoop,
319
+ };
320
+ }
321
+
322
+ /** Retorna a lista de features do wf NÃO cobertas pela âncora (vazia ⇒ LITE liberado). */
323
+ function featureDiff(feat, covered) {
324
+ const c = covered || {};
325
+ const cAssert = c.assertion_types || [];
326
+ const cShapes = c.dag_shapes || [];
327
+ const cPos = c.checkpoint_positions || [];
328
+ const uncovered = [];
329
+ for (const t of feat.assertion_types) if (!cAssert.includes(t)) uncovered.push(`assertion_type:${t}`);
330
+ if (!cShapes.includes(feat.dag_shape)) uncovered.push(`dag_shape:${feat.dag_shape}`);
331
+ for (const p of feat.checkpoint_positions) if (!cPos.includes(p)) uncovered.push(`checkpoint:${p}`);
332
+ if (feat.fix_loop && !c.fix_loop) uncovered.push('fix_loop');
333
+ return uncovered;
334
+ }
335
+
336
+ /** motor_hash = sha256 sobre "path:sha256(conteúdo)" de cada arquivo, ordenado. */
337
+ function computeMotorHash(files, baseDir) {
338
+ const parts = [...files].sort().map((f) => {
339
+ const abs = path.isAbsolute(f) ? f : path.resolve(baseDir, f);
340
+ if (!fs.existsSync(abs)) refuse(`motor_files[] referencia arquivo inexistente: ${f}`);
341
+ return `${f}:${sha256File(abs)}`;
342
+ });
343
+ return sha256(Buffer.from(parts.join('\n'), 'utf8'));
344
+ }
345
+
346
+ /** Localiza o wf em disco para uma linha (flag explícita > wf_path da linha > convenção). */
347
+ function resolveWfPath(line, opts, baseDir) {
348
+ const cand = opts.wfPath || line.wf_path ||
349
+ (line.pod_dir ? path.join(line.pod_dir, 'workflows', `${line.workflow}.yaml`) : null);
350
+ if (!cand) return null;
351
+ const abs = path.isAbsolute(cand) ? cand : path.resolve(baseDir, cand);
352
+ return fs.existsSync(abs) ? abs : null;
353
+ }
354
+
355
+ /**
356
+ * Porta de re-âncora (D.G2): roda no advance→gates (ANTES de pagar runs) e como
357
+ * BACKSTOP no close-line. Sem `doc.anchor` gravado, é no-op (compat SF3.1). Com âncora:
358
+ * - features(wf) ⊄ features_covered ⇒ exit 4 (re-âncora obrigatória);
359
+ * - motor_hash atual ≠ pinado ⇒ exit 4, salvo --approved-by (aceitação registrada).
360
+ */
361
+ function checkReanchorGate(doc, line, opts, baseDir) {
362
+ const anchor = doc.anchor;
363
+ if (!anchor) return { skipped: true };
364
+ const wfAbs = resolveWfPath(line, opts, baseDir);
365
+ if (!wfAbs) refuse(`re-âncora: wf do workflow "${line.workflow}" não localizado em disco (passe --wf-path) — não se libera LITE sem computar o feature-set`);
366
+ const wfDoc = parseYaml(fs.readFileSync(wfAbs, 'utf8'));
367
+ const feat = computeFeatureSet(wfDoc);
368
+ const uncovered = featureDiff(feat, anchor.features_covered);
369
+ if (uncovered.length) {
370
+ reanchor(`RE-ÂNCORA OBRIGATÓRIA (feature-diff) — "${line.workflow}" exercita feature(s) fora de features_covered: ${uncovered.join(', ')}. ` +
371
+ `Rode este workflow em G5-FULL (não LITE) e atualize features_covered via set-anchor.`);
372
+ }
373
+ if (anchor.motor && anchor.motor.files) {
374
+ const nowHash = computeMotorHash(anchor.motor.files, baseDir);
375
+ if (nowHash !== anchor.motor.hash) {
376
+ if (opts.approvedBy) {
377
+ line.motor_approval = { approved_by: opts.approvedBy, prev_hash: anchor.motor.hash, now_hash: nowHash, at: opts.now || new Date().toISOString() };
378
+ } else {
379
+ reanchor(`RE-ÂNCORA OBRIGATÓRIA (motor mudou) — motor_hash atual (${nowHash.slice(0, 12)}…) ≠ pinado (${String(anchor.motor.hash).slice(0, 12)}…). ` +
380
+ `Qualquer diff recusa LITE: (a) re-âncora de motor (1 FULL, set-anchor) OU (b) --approved-by "<humano>: <justificativa>".`);
381
+ }
382
+ }
383
+ }
384
+ return { skipped: false, feature_set: feat };
385
+ }
386
+
387
+ /**
388
+ * RE-EXECUTA o sweep de injeção (nunca relê o report — A.1.3). Determinístico, ~zero
389
+ * custo. Só roda se houver cenário sidecar `<wf>.smoke.json` (linha de migração real);
390
+ * linhas sem cenário (fixtures de contrato SF3.1) são no-op. Report adulterado em disco
391
+ * NÃO muda o veredito porque o sweep é re-executado, não lido.
392
+ */
393
+ function reExecuteSweep(line, opts, baseDir) {
394
+ const wfAbs = resolveWfPath(line, opts, baseDir);
395
+ if (!wfAbs) return { ran: false };
396
+ const scenarioPath = wfAbs.replace(/\.ya?ml$/, '.smoke.json');
397
+ if (!fs.existsSync(scenarioPath)) return { ran: false };
398
+ const smoke = require('./smoke-pod-workflow.cjs');
399
+ const scenario = JSON.parse(fs.readFileSync(scenarioPath, 'utf8'));
400
+ const podRoot = line.pod_dir ? (path.isAbsolute(line.pod_dir) ? line.pod_dir : path.resolve(baseDir, line.pod_dir)) : path.dirname(path.dirname(wfAbs));
401
+ const specPath = line.spec_path ? (path.isAbsolute(line.spec_path) ? line.spec_path : path.resolve(baseDir, line.spec_path)) : null;
402
+ const sweep = smoke.runSweep({ workflowPath: wfAbs, podRoot, scenario, specPath });
403
+ const failures = [];
404
+ if (sweep.underivable) failures.push(`sweep underivável: ${sweep.underivable.reason}`);
405
+ else if (!sweep.ok) failures.push(`sweep vazou (blocked:false): ${(sweep.leaked || []).map((l) => `${l.step}/${l.assertion}`).join(', ')}`);
406
+ // Binding spec_hash (fecha o forward-compat da SF3.1): o report re-executado embute
407
+ // spec_hash; ele DEVE conferir com o spec_hash da linha.
408
+ if (!failures.length && line.spec_hash && sweep.report.spec_hash !== line.spec_hash) {
409
+ failures.push(`sweep spec_hash divergente: report=${sweep.report.spec_hash.slice(0, 12)}… linha=${String(line.spec_hash).slice(0, 12)}…`);
410
+ }
411
+ return { ran: true, ok: failures.length === 0, failures, proofs: sweep.report.proofs.length };
412
+ }
413
+
414
+ // ---------------------------------------------------------------------------
415
+ // SUBCOMANDOS.
416
+ // ---------------------------------------------------------------------------
417
+ // ---------------------------------------------------------------------------
418
+ // F0 — INVENTÁRIO determinístico (Decisão B.4/B.5; fecha G5). Scan RECURSIVO do
419
+ // squad INTEIRO (readdir recursivo via snapshotTree, NÃO lista fixa de dirs):
420
+ // classifica cada arquivo numa FAMÍLIA por seu diretório de topo; tudo fora das
421
+ // famílias conhecidas é 'loose'. Grep de COMANDOS embutidos (`\*[a-z-]+`) em cada
422
+ // arquivo de agente legado (B.5 — cada comando legado tem de ter endereço). O
423
+ // inventário é DADO do manifest, escrito pelo escritor único (nunca pelo agente).
424
+ // ---------------------------------------------------------------------------
425
+ const FAMILY_DIRS = ['workflows', 'agents', 'tasks', 'checklists', 'data', 'scripts', 'templates', 'sops', 'config'];
426
+
427
+ function buildInventory(squadDir, now) {
428
+ const abs = path.resolve(squadDir);
429
+ if (!fs.existsSync(abs)) refuse(`squad-dir inexistente para inventário F0: ${squadDir}`);
430
+ const tree = snapshotTree(abs); // recursivo: [{ path, sha256 }]
431
+ const items = [];
432
+ const famSet = new Set();
433
+ for (const e of tree) {
434
+ const top = e.path.split(/[\\/]/)[0];
435
+ const family = FAMILY_DIRS.includes(top) && e.path.includes('/') ? top : 'loose';
436
+ items.push({ family, path: e.path });
437
+ famSet.add(family);
438
+ }
439
+ // Grep de comandos embutidos nos agentes legados (B.5): padrão `*comando`.
440
+ const commands = [];
441
+ for (const it of items) {
442
+ if (it.family !== 'agents') continue;
443
+ const content = fs.readFileSync(path.join(abs, it.path), 'utf8');
444
+ const found = new Set();
445
+ for (const m of content.matchAll(/(?:^|\s|`)(\*[a-z][a-z-]*)\b/g)) found.add(m[1]);
446
+ for (const c of [...found].sort()) commands.push({ agent: it.path, command: c });
447
+ }
448
+ return {
449
+ scanned_from: squadDir,
450
+ scanned_at: now || new Date().toISOString(),
451
+ families: [...famSet].sort(),
452
+ items,
453
+ commands,
454
+ };
455
+ }
456
+
457
+ function cmdInit(mfPath, opts) {
458
+ if (!opts.squad) usage("init exige --squad <nome>");
459
+ // F0 OBRIGATÓRIO (fix do finding GRAVE do QA sf3.3): --squad-dir é exigido para
460
+ // que o inventário SEMPRE nasça — `inventory` NUNCA é null. Sem ele não há como
461
+ // criar o "caminho dormente" que o QA explorou (init sem inventário → gate F1
462
+ // adormecido). Todo manifest de migração nasce inventariado. Exit 2 (contrato).
463
+ if (!opts.squadDir) refuse('init exige --squad-dir <dir> — todo manifest de migração nasce inventariado (F0); sem inventário não há gate F1 dormente.');
464
+ if (fs.existsSync(mfPath)) refuse(`manifest já existe: ${rel(mfPath)} (init não sobrescreve)`);
465
+ const doc = { squad: opts.squad, created_at: opts.now || new Date().toISOString(), triage: [], lines: [] };
466
+ doc.inventory = buildInventory(opts.squadDir, opts.now);
467
+ writeManifest(mfPath, doc);
468
+ return {
469
+ action: 'init',
470
+ squad: opts.squad,
471
+ path: mfPath,
472
+ inventory: { families: doc.inventory.families, items: doc.inventory.items.length, commands: doc.inventory.commands.length },
473
+ };
474
+ }
475
+
476
+ function cmdTriageSet(mfPath, opts) {
477
+ const doc = readManifest(mfPath);
478
+ if (!opts.workflow) usage('triage-set exige --workflow <id>');
479
+ // Colunas de evidência OBRIGATÓRIAS (A.2/M5): sem elas o helper RECUSA.
480
+ if (!opts.disposition || !TRIAGE_DISPOSITIONS.has(opts.disposition)) {
481
+ refuse(`triage-set exige --disposition ∈ {${[...TRIAGE_DISPOSITIONS].join(',')}}`);
482
+ }
483
+ if (!opts.purpose) refuse('triage-set exige --purpose <citação path:linha> (propósito com evidência)');
484
+ if (!opts.basis || !TRIAGE_BASES.has(opts.basis)) {
485
+ refuse(`triage-set exige --basis ∈ {${[...TRIAGE_BASES].join(',')}} (dado citado vs julgamento explícito)`);
486
+ }
487
+ const entry = {
488
+ workflow: opts.workflow,
489
+ disposition: opts.disposition,
490
+ purpose: opts.purpose,
491
+ overlap: opts.overlap || '',
492
+ basis: opts.basis,
493
+ set_at: opts.now || new Date().toISOString(),
494
+ };
495
+ const i = doc.triage.findIndex((t) => t.workflow === opts.workflow);
496
+ // Reclassificar preserva a proveniência mínima, mas ZERA aprovação/comando
497
+ // (mudar de disposição é decisão nova — não herda um "aprovado" antigo).
498
+ if (i >= 0) doc.triage[i] = entry; else doc.triage.push(entry);
499
+ writeManifest(mfPath, doc);
500
+ return { action: 'triage-set', workflow: opts.workflow, disposition: opts.disposition };
501
+ }
502
+
503
+ // F1 — aprovação humana da triagem (regra 5 do ledger; fecha M5/AC2). SÓ o Daniel
504
+ // aprova; sem esta marca o open-line RECUSA num manifest de migração. Escritor
505
+ // único: a aprovação é DADO do manifest, gravada por este subcomando.
506
+ function cmdTriageApprove(mfPath, opts) {
507
+ const doc = readManifest(mfPath);
508
+ if (!opts.workflow) usage('triage-approve exige --workflow <id>');
509
+ if (!opts.approvedBy) refuse('triage-approve exige --approved-by <nome> (aprovação humana registrada — regra 5)');
510
+ const t = doc.triage.find((x) => x.workflow === opts.workflow);
511
+ if (!t) refuse(`triage-approve: não há triagem para "${opts.workflow}" — rode triage-set primeiro`);
512
+ t.approved_by = opts.approvedBy;
513
+ t.approved_at = opts.now || new Date().toISOString();
514
+ writeManifest(mfPath, doc);
515
+ return { action: 'triage-approve', workflow: opts.workflow, disposition: t.disposition, approved_by: t.approved_by };
516
+ }
517
+
518
+ // VIRAR-COMANDO — regra de fechamento (fecha G6-comando/AC9). A linha de disposição
519
+ // 'command' fica `pendente-comando` e SÓ fecha com os TRÊS, conferidos por CÓDIGO:
520
+ // (a) path do comando em disco; (b) smoke próprio registrado (arquivo de output
521
+ // conferível e não-vazio — regra 1 do ledger, teste real); (c) approved_by. Sem os
522
+ // três, a ÚNICA saída é reclassificar como `dispensar` (triage-set --disposition
523
+ // dispense). "comando" nunca é dispensa com outro nome.
524
+ function cmdCommandClose(mfPath, opts) {
525
+ const doc = readManifest(mfPath);
526
+ if (!opts.workflow) usage('command-close exige --workflow <id>');
527
+ const t = doc.triage.find((x) => x.workflow === opts.workflow);
528
+ if (!t) refuse(`command-close: não há triagem para "${opts.workflow}"`);
529
+ if (t.disposition !== 'command') refuse(`command-close só aplica a disposição 'command' (atual: '${t.disposition}')`);
530
+ const missing = [];
531
+ if (!opts.commandPath) missing.push('--command-path <path do comando em disco>');
532
+ else if (!fs.existsSync(path.resolve(opts.commandPath))) missing.push(`comando não existe em disco: ${opts.commandPath}`);
533
+ if (!opts.smoke) missing.push('--smoke <path do output do smoke próprio>');
534
+ else {
535
+ const sAbs = path.resolve(opts.smoke);
536
+ if (!fs.existsSync(sAbs)) missing.push(`smoke próprio não registrado (arquivo inexistente): ${opts.smoke}`);
537
+ else if (fs.statSync(sAbs).size === 0) missing.push(`smoke próprio vazio (sem output conferível): ${opts.smoke}`);
538
+ }
539
+ if (!opts.approvedBy) missing.push('--approved-by <nome> (aprovação do Daniel)');
540
+ if (missing.length) {
541
+ refuse(`VIRAR-COMANDO de "${opts.workflow}" permanece pendente-comando — faltam: ${missing.join('; ')}. ` +
542
+ `Única saída sem os três: reclassificar com triage-set --disposition dispense (regra 5).`);
543
+ }
544
+ t.command_status = 'closed';
545
+ t.command_path = opts.commandPath;
546
+ t.command_smoke = opts.smoke;
547
+ // FIX ADR-006 §Consequências (SF5.5): a aprovação do close vai para um campo PRÓPRIO
548
+ // (`close_approved_by`), NUNCA `t.approved_by = t.approved_by || opts.approvedBy` — esse
549
+ // `||` deixava a aprovação da F1 (2026-07-24) persistir NO LUGAR da confirmação fresca
550
+ // da regra 5 exigida no ATO do close. Agora as duas aprovações ficam separadas na trilha:
551
+ // `approved_by` (F1, disposição) e `close_approved_by` (regra 5, fechamento).
552
+ t.close_approved_by = opts.approvedBy;
553
+ t.command_closed_at = opts.now || new Date().toISOString();
554
+ writeManifest(mfPath, doc);
555
+ return { action: 'command-close', workflow: opts.workflow, command_status: 'closed', command_path: opts.commandPath };
556
+ }
557
+
558
+ // DISPENSAR — fechamento AUDITÁVEL da disposição 'dispense' (ADR-006 §Consequências, SF5.5;
559
+ // mesma classe do episódio reopen-line: dispensa era beco sem fechamento re-verificável — o
560
+ // DISPATCH não tinha subcomando dedicado). A dispensa é ADJUDICAÇÃO, não ausência de trabalho:
561
+ // fecha SÓ com (a) triagem 'dispense' aprovada na F1 (regra 5), (b) aprovação FRESCA do close
562
+ // em campo PRÓPRIO (`dispense_close_approved_by`, NUNCA `||` — mesma lição do command-close:544)
563
+ // e (c) `--evidence-ref` (a cobertura que fundamenta a dispensa, path[:linha] existente em disco
564
+ // NO ATO). `verify` re-checa que essa evidência ainda existe — cobertura que sumiu → stale
565
+ // ruidoso, re-adjudicar. Espelha a tese: nenhuma disposição fecha por ausência de mecanismo.
566
+ function cmdDispenseClose(mfPath, opts) {
567
+ const doc = readManifest(mfPath);
568
+ if (!opts.workflow) usage('dispense-close exige --workflow <id>');
569
+ const t = doc.triage.find((x) => x.workflow === opts.workflow);
570
+ if (!t) refuse(`dispense-close: não há triagem para "${opts.workflow}"`);
571
+ if (t.disposition !== 'dispense') refuse(`dispense-close só aplica a disposição 'dispense' (atual: '${t.disposition}')`);
572
+ if (!t.approved_by) refuse(`dispense-close: triagem 'dispense' de "${opts.workflow}" sem approved_by — rode triage-approve (F1, regra 5) antes de fechar a dispensa.`);
573
+ const missing = [];
574
+ if (!opts.approvedBy) missing.push('--approved-by <nome> (confirmação FRESCA da regra 5 no ato do close)');
575
+ if (!opts.evidenceRef) missing.push('--evidence-ref <path[:linha]> (a cobertura que fundamenta a dispensa)');
576
+ else if (!fs.existsSync(path.resolve(String(opts.evidenceRef).split(':')[0]))) {
577
+ missing.push(`evidência de cobertura inexistente em disco: ${opts.evidenceRef} (a dispensa exige um substituto REAL)`);
578
+ }
579
+ if (missing.length) {
580
+ refuse(`DISPENSA de "${opts.workflow}" permanece pendente — faltam: ${missing.join('; ')}.`);
581
+ }
582
+ t.dispense_status = 'closed';
583
+ // Campo PRÓPRIO (nunca `||`): a confirmação do close não herda nem é herdada pela F1.
584
+ t.dispense_close_approved_by = opts.approvedBy;
585
+ t.dispense_evidence_ref = opts.evidenceRef;
586
+ t.dispense_closed_at = opts.now || new Date().toISOString();
587
+ writeManifest(mfPath, doc);
588
+ return { action: 'dispense-close', workflow: opts.workflow, dispense_status: 'closed', evidence_ref: opts.evidenceRef };
589
+ }
590
+
591
+ // F1 STOP (Decisão A.2; fecha M5/AC2; ENDURECIDO pós-QA sf3.3): o loop F2 NÃO
592
+ // abre linha sem a triagem do workflow ter sido APROVADA pelo Daniel (regra 5 do
593
+ // ledger). A garantia é INCONDICIONAL — não há caminho dormente. O finding GRAVE
594
+ // do QA foi um proxy leaky: a versão anterior condicionava o gate à presença de
595
+ // `inventory`, e um `init` sem `--squad-dir` produzia `inventory:null` → gate
596
+ // adormecido → open-line sem aprovação. Agora: (1) init exige --squad-dir
597
+ // (inventory nunca null); (2) ESTE gate roda SEMPRE, mesmo que `inventory` esteja
598
+ // ausente/null — sem triagem `migrate` aprovada, open-line RECUSA. Nenhuma flag
599
+ // desliga a parada. Os unit tests SF3.1/3.2 usam triagem aprovada SINTÉTICA nos
600
+ // fixtures (nunca gate dormente).
601
+ function assertF1Approved(doc, workflow) {
602
+ const t = doc.triage.find((x) => x.workflow === workflow);
603
+ if (!t) refuse(`F1 não executada para "${workflow}": rode triage-set + triage-approve antes de open-line (PARA no Daniel, regra 5).`);
604
+ if (t.disposition !== 'migrate') refuse(`triagem de "${workflow}" é '${t.disposition}', não 'migrate' — só workflows aprovados para migração abrem linha F2.`);
605
+ if (!t.approved_by) refuse(`F1 não aprovada para "${workflow}": disposição 'migrate' sem approved_by — rode triage-approve --workflow ${workflow} --approved-by <daniel> (PARA no Daniel).`);
606
+ }
607
+
608
+ function cmdOpenLine(mfPath, opts) {
609
+ const doc = readManifest(mfPath);
610
+ if (!opts.workflow) usage('open-line exige --workflow <id>');
611
+ if (!opts.spec) refuse('open-line exige --spec <specPath>');
612
+ if (!opts.podDir) refuse('open-line exige --pod-dir <dir>');
613
+ assertF1Approved(doc, opts.workflow);
614
+ const specAbs = path.resolve(opts.spec);
615
+ if (!fs.existsSync(specAbs)) refuse(`spec inexistente: ${opts.spec}`);
616
+ const specHash = sha256File(specAbs);
617
+ const owns = (opts.owns || []).map((p) => p.trim()).filter(Boolean);
618
+
619
+ const existing = findLine(doc, opts.workflow);
620
+ if (existing) {
621
+ // RETOMADA IDEMPOTENTE (fecha G4): mesmo spec_hash + linha não fechada → prossegue
622
+ // da fase registrada; spec_hash diferente → exige --amend; linha fechada → recusa.
623
+ if (existing.phase === 'closed') refuse(`linha "${opts.workflow}" já está closed — reabertura só via 'reopen-line --reason reanchor' (decisão de re-âncora registrada, histórico preservado em superseded[])`);
624
+ if (existing.spec_hash === specHash) {
625
+ return { action: 'open-line', workflow: opts.workflow, resumed: true, phase: existing.phase };
626
+ }
627
+ if (!opts.amend) {
628
+ refuse(`spec_hash divergente para a linha aberta "${opts.workflow}" ` +
629
+ `(linha=${existing.spec_hash.slice(0, 12)}… nova=${specHash.slice(0, 12)}…). ` +
630
+ `Use --amend para reabrir da fase 'spec' e regenerar owns[].`);
631
+ }
632
+ // --amend: reabre da fase 'spec' e regenera owns/baseline com a spec nova.
633
+ }
634
+
635
+ // Unicidade de namespace POD-WIDE: owns[] não pode colidir com owns[] de OUTRA linha aberta.
636
+ for (const other of doc.lines) {
637
+ if (other.workflow === opts.workflow) continue;
638
+ if (other.phase === 'closed') continue;
639
+ const clash = (other.owns || []).filter((p) => owns.includes(p));
640
+ if (clash.length) {
641
+ refuse(`colisão de namespace: owns[] {${clash.join(', ')}} já reservado pela linha aberta "${other.workflow}"`);
642
+ }
643
+ }
644
+
645
+ const podDirAbs = path.resolve(opts.podDir);
646
+ const line = {
647
+ workflow: opts.workflow,
648
+ pod: opts.pod || path.basename(podDirAbs),
649
+ pod_dir: opts.podDir,
650
+ phase: 'spec',
651
+ spec_path: opts.spec,
652
+ spec_hash: specHash,
653
+ owns,
654
+ baseline: snapshotTree(podDirAbs),
655
+ opened_at: opts.now || new Date().toISOString(),
656
+ run_ids: [],
657
+ verify: [],
658
+ };
659
+ const i = doc.lines.findIndex((l) => l.workflow === opts.workflow);
660
+ if (i >= 0) doc.lines[i] = line; else doc.lines.push(line);
661
+ writeManifest(mfPath, doc);
662
+ return { action: 'open-line', workflow: opts.workflow, phase: 'spec', spec_hash: specHash, owns: owns.length, baseline: line.baseline.length };
663
+ }
664
+
665
+ /**
666
+ * advance — avança a fase da linha (spec→generated→distilled→linted→gates), um passo
667
+ * por vez, monotônico. 'closed' é exclusivo do close-line.
668
+ *
669
+ * CONTRATO ESTÁVEL PARA SF3.2 (Risco 1 da story): o retorno inclui `hook`. Ao avançar
670
+ * PARA 'gates', hook='pre-gates-check' — este é o ponto EXATO onde SF3.2 insere a
671
+ * decisão LITE vs FULL (feature-diff → exit 4 ANTES de pagar as runs) e o check de
672
+ * motor_hash, SEM alterar a assinatura pública de advance (só o corpo do hook).
673
+ */
674
+ function cmdAdvance(mfPath, opts) {
675
+ const doc = readManifest(mfPath);
676
+ if (!opts.workflow) usage('advance exige --workflow <id>');
677
+ if (!opts.to) usage(`advance exige --to <${PHASES.slice(1, -1).join('|')}>`);
678
+ const line = findLine(doc, opts.workflow);
679
+ if (!line) refuse(`linha inexistente: "${opts.workflow}" — rode open-line primeiro`);
680
+ if (line.phase === 'closed') refuse(`linha "${opts.workflow}" está closed — imutável`);
681
+ const from = phaseIndex(line.phase);
682
+ const to = phaseIndex(opts.to);
683
+ if (opts.to === 'closed') refuse("advance não fecha a linha — use close-line (exige binding/re-execução)");
684
+ if (to <= from) refuse(`avanço não-monotônico: de '${line.phase}' para '${opts.to}' (fase atual ≥ pedida)`);
685
+ if (to !== from + 1) refuse(`avanço fora de ordem: de '${line.phase}' só se avança para '${PHASES[from + 1]}', não '${opts.to}'`);
686
+ const hook = opts.to === 'gates' ? 'pre-gates-check' : null;
687
+ // HOOK pre-gates-check (SF3.2, D.G2/M-r2-1): a decisão LITE vs FULL roda AQUI, ANTES
688
+ // de pagar as ~570k tokens das runs. feature-diff → exit 4; motor mudou → exit 4
689
+ // (salvo --approved-by). Sem âncora gravada, é no-op (compat SF3.1).
690
+ let gate = null;
691
+ if (hook === 'pre-gates-check') {
692
+ gate = checkReanchorGate(doc, line, opts, process.cwd());
693
+ if (gate && gate.feature_set) line.feature_set = gate.feature_set;
694
+ }
695
+ line.phase = opts.to;
696
+ line.advanced_at = opts.now || new Date().toISOString();
697
+ writeManifest(mfPath, doc);
698
+ return { action: 'advance', workflow: opts.workflow, from: PHASES[from], to: opts.to, hook };
699
+ }
700
+
701
+ /**
702
+ * reopen-line — RE-ADJUDICAÇÃO de uma linha `closed` (ADR-006 §Consequências, gap SF3.4).
703
+ * Linha fechada era beco sem saída: `open-line` recusa reopen, `advance` recusa closed, e
704
+ * o `verify` não via drift de feature-set em linha fechada. Reabertura SÓ sob re-âncora,
705
+ * com justificativa+aprovação REGISTRADAS. A adjudicação anterior NUNCA é apagada — vai
706
+ * para `superseded[]` (trilha de auditoria). A linha volta à fase pré-gates ('linted') para
707
+ * o próximo `advance` re-rodar o `pre-gates-check` (feature drift → exit 4 → FULL).
708
+ * Preserva a identidade da linha (spec_hash/owns/baseline); limpa só a adjudicação viva.
709
+ */
710
+ function cmdReopenLine(mfPath, opts) {
711
+ const doc = readManifest(mfPath);
712
+ if (!opts.workflow) usage('reopen-line exige --workflow <id>');
713
+ const line = findLine(doc, opts.workflow);
714
+ if (!line) refuse(`linha inexistente: "${opts.workflow}"`);
715
+ if (line.phase !== 'closed') refuse(`reopen-line exige a linha 'closed' (está em '${line.phase}') — só se re-adjudica o que já foi fechado`);
716
+ // reanchor é o ÚNICO reason válido; os 4 flags são OBRIGATÓRIOS (decisão rastreável).
717
+ if (opts.reason !== 'reanchor') refuse("reopen-line exige --reason reanchor (único motivo válido — reabertura é decisão de re-âncora)");
718
+ if (!opts.feature) refuse('reopen-line exige --feature <feat> (o feature descoberto que obsoleta a adjudicação LITE)');
719
+ if (!opts.approvedBy) refuse('reopen-line exige --approved-by "<quem>: <justificativa>"');
720
+ if (!opts.decisionRef) refuse('reopen-line exige --decision-ref <path> (referência à decisão de re-âncora registrada)');
721
+
722
+ // SNAPSHOT da adjudicação atual → superseded[] (append; nunca apaga).
723
+ const ADJUDICATION_FIELDS = ['wf_path', 'run_ids', 'pair', 'closed_at', 'verify', 'feature_set', 'sweep', 'motor_approval', 'advanced_at', 'stale', 'stale_at'];
724
+ const snapshot = {};
725
+ for (const f of ADJUDICATION_FIELDS) if (f in line) snapshot[f] = line[f];
726
+ line.superseded = Array.isArray(line.superseded) ? line.superseded : [];
727
+ line.superseded.push({
728
+ superseded_at: opts.now || new Date().toISOString(),
729
+ reason: opts.reason,
730
+ feature: opts.feature,
731
+ approved_by: opts.approvedBy,
732
+ decision_ref: opts.decisionRef,
733
+ snapshot,
734
+ });
735
+ // Limpa a adjudicação da linha VIVA, preservando identidade (spec_hash/owns/baseline/pod).
736
+ for (const f of ADJUDICATION_FIELDS) delete line[f];
737
+ line.run_ids = [];
738
+ line.verify = [];
739
+ line.phase = 'linted'; // próximo advance→gates re-roda o pre-gates-check → exit 4 → FULL.
740
+ line.reopened_at = opts.now || new Date().toISOString();
741
+ writeManifest(mfPath, doc);
742
+ return { action: 'reopen-line', workflow: opts.workflow, phase: 'linted', superseded: line.superseded.length };
743
+ }
744
+
745
+ function cmdCloseLine(mfPath, opts) {
746
+ const doc = readManifest(mfPath);
747
+ if (!opts.workflow) usage('close-line exige --workflow <id>');
748
+ const line = findLine(doc, opts.workflow);
749
+ if (!line) refuse(`linha inexistente: "${opts.workflow}"`);
750
+ if (line.phase === 'closed') refuse(`linha "${opts.workflow}" já está closed`);
751
+ if (line.phase !== 'gates') refuse(`close-line exige a linha em 'gates' (está em '${line.phase}') — avance pelos gates primeiro`);
752
+
753
+ // Anexa a evidência declarada pelo orquestrador ANTES de verificar por código.
754
+ if (opts.wfPath) line.wf_path = opts.wfPath;
755
+ if (opts.runIds && opts.runIds.length) line.run_ids = opts.runIds;
756
+ if (opts.pair) line.pair = opts.pair;
757
+ // verify[] é CACHE DE AUDITORIA — registrado, NUNCA executado como fonte (G-r2-1.1).
758
+ if (opts.verifyNote) {
759
+ line.verify = Array.isArray(line.verify) ? line.verify : [];
760
+ line.verify.push({ note: opts.verifyNote, at: opts.now || new Date().toISOString() });
761
+ }
762
+
763
+ // RAIZ DE CONFIANÇA EM CÓDIGO: deriva e aplica os checks aos campos de evidência.
764
+ const res = verifyLineEvidence(line, { baseDir: process.cwd() });
765
+ if (!res.ok) {
766
+ refuse(`close-line RECUSA "${opts.workflow}" — evidência não verifica por código:\n` +
767
+ res.failures.map((f) => ` - ${f}`).join('\n'));
768
+ }
769
+
770
+ // BACKSTOP de re-âncora (D.G2): nunca fecha LITE com feature descoberto/motor mudado.
771
+ checkReanchorGate(doc, line, opts, process.cwd());
772
+
773
+ // RE-EXECUTA o sweep (A.1.3) — nunca relê o report. Report adulterado em disco não
774
+ // muda o veredito. Só roda para linha de migração com cenário sidecar.
775
+ const sw = reExecuteSweep(line, opts, process.cwd());
776
+ if (sw.ran && !sw.ok) {
777
+ refuse(`close-line RECUSA "${opts.workflow}" — sweep re-executado FALHOU:\n` +
778
+ sw.failures.map((f) => ` - ${f}`).join('\n'));
779
+ }
780
+ if (sw.ran) line.sweep = { proofs: sw.proofs, reexec_at: opts.now || new Date().toISOString() };
781
+
782
+ line.phase = 'closed';
783
+ line.closed_at = opts.now || new Date().toISOString();
784
+ writeManifest(mfPath, doc);
785
+ return { action: 'close-line', workflow: opts.workflow, phase: 'closed' };
786
+ }
787
+
788
+ /**
789
+ * set-anchor — grava a decisão de RE-ÂNCORA registrada (M-r2-4): features_covered +
790
+ * motor pinado (motor_files[] + motor_hash). É o ÚNICO ponto que escreve `motor_files[]`
791
+ * — alterar a lista fora desta decisão é impossível (só o escritor único grava; hand-edit
792
+ * do YAML cai no integrity → exit 3). Exige --decision (justificativa da re-âncora).
793
+ */
794
+ function cmdSetAnchor(mfPath, opts) {
795
+ const doc = readManifest(mfPath);
796
+ if (!opts.decision) refuse('set-anchor exige --decision "<justificativa da re-âncora>" (M-r2-4: a lista de motor só muda em decisão registrada)');
797
+ const baseDir = process.cwd();
798
+
799
+ let covered = {
800
+ assertion_types: [...(opts.assertionTypes || [])],
801
+ dag_shapes: [...(opts.dagShapes || [])],
802
+ checkpoint_positions: [...(opts.checkpointPositions || [])],
803
+ fix_loop: opts.fixLoopCovered === true,
804
+ };
805
+ // --from-wf: semeia features_covered com o feature-set de um wf real (a âncora paga).
806
+ if (opts.fromWf) {
807
+ const wfAbs = path.resolve(opts.fromWf);
808
+ if (!fs.existsSync(wfAbs)) refuse(`set-anchor --from-wf inexistente: ${opts.fromWf}`);
809
+ const feat = computeFeatureSet(parseYaml(fs.readFileSync(wfAbs, 'utf8')));
810
+ covered.assertion_types = [...new Set([...covered.assertion_types, ...feat.assertion_types])].sort();
811
+ covered.dag_shapes = [...new Set([...covered.dag_shapes, feat.dag_shape])].sort();
812
+ covered.checkpoint_positions = [...new Set([...covered.checkpoint_positions, ...feat.checkpoint_positions])].sort();
813
+ covered.fix_loop = covered.fix_loop || feat.fix_loop;
814
+ } else {
815
+ covered.assertion_types.sort(); covered.dag_shapes.sort(); covered.checkpoint_positions.sort();
816
+ }
817
+
818
+ const motorFiles = (opts.motorFiles && opts.motorFiles.length) ? opts.motorFiles : DEFAULT_MOTOR_FILES;
819
+ const motorHash = computeMotorHash(motorFiles, baseDir);
820
+ doc.anchor = {
821
+ features_covered: covered,
822
+ motor: { files: [...motorFiles], hash: motorHash },
823
+ decision: opts.decision,
824
+ set_at: opts.now || new Date().toISOString(),
825
+ };
826
+ writeManifest(mfPath, doc);
827
+ return { action: 'set-anchor', motor_hash: motorHash, motor_files: motorFiles.length, features_covered: covered };
828
+ }
829
+
830
+ /**
831
+ * waiver-set — registra o waiver de LEGADO INEXECUTÁVEL (M-r2-3). "Não rodei" não é
832
+ * evidência de "não roda": exige --transcript de uma TENTATIVA REAL (arquivo em disco)
833
+ * + --approved-by (Daniel) + --declared (artefatos DECLARADOS do workflow legado, contra
834
+ * os quais a completude passa a ser comparada quando o par não completa).
835
+ */
836
+ function cmdWaiverSet(mfPath, opts) {
837
+ const doc = readManifest(mfPath);
838
+ if (!opts.workflow) usage('waiver-set exige --workflow <id>');
839
+ const line = findLine(doc, opts.workflow);
840
+ if (!line) refuse(`linha inexistente: "${opts.workflow}" — rode open-line primeiro`);
841
+ if (!opts.transcript) refuse('waiver-set exige --transcript <path> (tentativa real registrada — sem tentativa não há waiver)');
842
+ const trAbs = path.resolve(opts.transcript);
843
+ if (!fs.existsSync(trAbs)) refuse(`transcript inexistente: ${opts.transcript} — "não rodei" não é evidência de "não roda" (M-r2-3)`);
844
+ if (!opts.approvedBy) refuse('waiver-set exige --approved-by "<Daniel>: <justificativa>"');
845
+ const declared = (opts.declared || []).map((s) => s.trim()).filter(Boolean);
846
+ if (!declared.length) refuse('waiver-set exige ao menos um --declared <artefato> (a completude compara contra os artefatos DECLARADOS do legado)');
847
+ line.legacy_waiver = {
848
+ transcript: opts.transcript,
849
+ partial_artifacts: [...(opts.partial || [])],
850
+ approved_by: opts.approvedBy,
851
+ declared_artifacts: declared,
852
+ at: opts.now || new Date().toISOString(),
853
+ };
854
+ writeManifest(mfPath, doc);
855
+ return { action: 'waiver-set', workflow: opts.workflow, declared: declared.length };
856
+ }
857
+
858
+ // address-set — endereço AUDITÁVEL de itens NÃO-workflow (ADR-006 §Consequências, SF5.5).
859
+ // O `coverage` só enxergava owns/spec/wf/triagem — os ~184 itens dos baldes B do design
860
+ // (checklists/tasks/data/scripts/templates) não tinham via de endereço e ficavam eternamente
861
+ // "sem endereço". Escritor único, registro `{path|glob, disposition, endereco, decision_ref,
862
+ // evidence}` em `doc.addresses[]`. Disposições:
863
+ // destilado|veto|script-do-pod → EXIGEM --endereco existente em disco (destino REAL da
864
+ // capacidade; `verify` re-checa que ele ainda existe);
865
+ // obito → o item MORRE: --endereco é PROIBIDO. Óbito em massa por
866
+ // balde aprovado = UMA entrada com --glob (nunca lista solta).
867
+ // `coverage` SUBTRAI o registrado (F3 = coverage vazio por endereços auditáveis). --glob só
868
+ // vale para óbito-por-balde; endereços individuais usam --path.
869
+ const ADDRESS_DISPOSITIONS = new Set(['destilado', 'veto', 'script-do-pod', 'obito']);
870
+
871
+ function globToRegex(glob) {
872
+ let re = String(glob).replace(/[.+^${}()|[\]\\]/g, '\\$&');
873
+ re = re.replace(/\*\*\//g, '').replace(/\*\*/g, ''); // ** = qualquer profundidade
874
+ re = re.replace(/\*/g, '[^/]*'); // * = dentro do segmento
875
+ re = re.replace(//g, '.*');
876
+ return new RegExp(`^${re}$`);
877
+ }
878
+
879
+ function cmdAddressSet(mfPath, opts) {
880
+ const doc = readManifest(mfPath);
881
+ if (opts.addrPath && opts.glob) refuse('address-set: --path e --glob são mutuamente exclusivos');
882
+ const target = opts.addrPath || opts.glob;
883
+ if (!target) usage('address-set exige --path <arquivo> OU --glob <padrão> (óbito-por-balde)');
884
+ if (!opts.disposition || !ADDRESS_DISPOSITIONS.has(opts.disposition)) {
885
+ refuse(`address-set exige --disposition ∈ {${[...ADDRESS_DISPOSITIONS].join(',')}}`);
886
+ }
887
+ if (opts.glob && opts.disposition !== 'obito') {
888
+ refuse("address-set: --glob só se aplica a óbito-por-balde (--disposition obito) — destino de destilado/veto/script é individual (--path)");
889
+ }
890
+ if (!opts.decisionRef) refuse('address-set exige --decision-ref <path> (a decisão de balde aprovada — TRIAGE Parte B)');
891
+ if (opts.disposition === 'obito') {
892
+ if (opts.endereco) refuse('address-set: óbito NÃO tem endereço (o item morre) — --endereco é proibido para obito');
893
+ } else {
894
+ if (!opts.endereco) refuse(`address-set: disposição '${opts.disposition}' exige --endereco <path existente> (destino REAL da capacidade)`);
895
+ if (!fs.existsSync(path.resolve(opts.endereco))) {
896
+ refuse(`address-set: endereço inexistente em disco: ${opts.endereco} — destilado/veto/script devem apontar a um destino que EXISTE (senão é fabricação).`);
897
+ }
898
+ }
899
+ const entry = {
900
+ target_path: opts.addrPath || '',
901
+ target_glob: opts.glob || '',
902
+ disposition: opts.disposition,
903
+ endereco: opts.endereco || '',
904
+ decision_ref: opts.decisionRef,
905
+ evidence: opts.evidence || '',
906
+ set_at: opts.now || new Date().toISOString(),
907
+ };
908
+ doc.addresses = Array.isArray(doc.addresses) ? doc.addresses : [];
909
+ const key = opts.glob ? `glob:${opts.glob}` : `path:${opts.addrPath}`;
910
+ const idx = doc.addresses.findIndex((a) => (a.target_glob ? `glob:${a.target_glob}` : `path:${a.target_path}`) === key);
911
+ if (idx >= 0) doc.addresses[idx] = entry; else doc.addresses.push(entry);
912
+ writeManifest(mfPath, doc);
913
+ return { action: 'address-set', target: key, disposition: opts.disposition };
914
+ }
915
+
916
+ function cmdCoverage(mfPath, opts) {
917
+ const doc = readManifest(mfPath);
918
+ if (!opts.squadDir) usage('coverage exige --squad-dir <dir>');
919
+ const squadAbs = path.resolve(opts.squadDir);
920
+ const files = snapshotTree(squadAbs).map((e) => e.path);
921
+ // Endereço = arquivo citado em owns[]/spec_path/wf_path de alguma linha, OU em triage.
922
+ const addressed = new Set();
923
+ for (const l of doc.lines) {
924
+ for (const p of l.owns || []) addressed.add(path.basename(p));
925
+ if (l.spec_path) addressed.add(path.basename(l.spec_path));
926
+ if (l.wf_path) addressed.add(path.basename(l.wf_path));
927
+ }
928
+ for (const t of doc.triage) addressed.add(t.workflow);
929
+ // + itens endereçados por address-set (SF5.5): --path exato (relativo ao squad-dir) e --glob.
930
+ const addrPaths = new Set();
931
+ const addrGlobs = [];
932
+ for (const a of (doc.addresses || [])) {
933
+ if (a.target_path) addrPaths.add(a.target_path);
934
+ if (a.target_glob) addrGlobs.push(globToRegex(a.target_glob));
935
+ }
936
+ const unaddressed = files.filter((f) => {
937
+ const base = path.basename(f);
938
+ if (addressed.has(base)) return false;
939
+ // um workflow legado wf-<id>.yaml conta como endereçado se há triage do <id>.
940
+ const wfId = base.replace(/\.ya?ml$/, '');
941
+ if (addressed.has(wfId)) return false;
942
+ if (addrPaths.has(f)) return false;
943
+ if (addrGlobs.some((re) => re.test(f))) return false;
944
+ return true;
945
+ });
946
+ return { action: 'coverage', total: files.length, unaddressed, clean: unaddressed.length === 0 };
947
+ }
948
+
949
+ function cmdVerify(mfPath) {
950
+ const doc = readManifest(mfPath);
951
+ const closed = doc.lines.filter((l) => l.phase === 'closed');
952
+ const report = [];
953
+ let staleFound = false;
954
+ for (const line of closed) {
955
+ const res = verifyLineEvidence(line, { baseDir: process.cwd() });
956
+ const failures = [...res.failures];
957
+ // F3 RE-EXECUTA o sweep (A.1.3) — determinístico, não relê o report.
958
+ const sw = reExecuteSweep(line, {}, process.cwd());
959
+ if (sw.ran && !sw.ok) failures.push(...sw.failures.map((f) => `sweep: ${f}`));
960
+ // F3 pergunta "esta adjudicação vale HOJE?": feature-set do wf ATUAL em disco vs
961
+ // features_covered ATUAL da âncora vigente (ADR-006 §Consequências, gap SF3.4). Drift
962
+ // além do coberto → adjudicação LITE obsoleta → stale ruidoso (mesma via existente).
963
+ // Comparar contra o covered vigente (não o "da época") evita falso pos/neg: âncora
964
+ // ampliada depois SANA o drift sem reopen; wf driftado além do coberto o expõe.
965
+ try {
966
+ checkReanchorGate(doc, line, {}, process.cwd());
967
+ } catch (e) {
968
+ if (e instanceof ManifestError && e.exitCode === 4) {
969
+ failures.push(`feature drift: ${e.message.split(' — ')[0].replace(/^RE-ÂNCORA OBRIGATÓRIA\s*/, '').trim()} — reabra via reopen-line --reason reanchor`);
970
+ } else if (e instanceof ManifestError) {
971
+ failures.push(`re-âncora não verificável: ${e.message}`);
972
+ } else { throw e; }
973
+ }
974
+ const okAll = failures.length === 0;
975
+ report.push({ workflow: line.workflow, ok: okAll, failures });
976
+ if (!okAll) { line.phase = 'gates'; line.stale = true; line.stale_at = new Date().toISOString(); staleFound = true; }
977
+ }
978
+ // Re-checa as DISPENSAS fechadas (ADR-006 §Consequências, SF5.5): a evidência de cobertura
979
+ // que fundamentou a dispensa ainda existe em disco? Sumiu → stale ruidoso, re-adjudicar.
980
+ const dispense = [];
981
+ for (const t of doc.triage) {
982
+ if (t.dispense_status !== 'closed') continue;
983
+ const failures = [];
984
+ const ev = t.dispense_evidence_ref ? String(t.dispense_evidence_ref).split(':')[0] : null;
985
+ if (!ev) failures.push('dispensa fechada sem dispense_evidence_ref');
986
+ else if (!fs.existsSync(path.resolve(ev))) failures.push(`evidência de cobertura sumiu do disco: ${t.dispense_evidence_ref} — re-adjudicar`);
987
+ const okAll = failures.length === 0;
988
+ dispense.push({ workflow: t.workflow, ok: okAll, failures });
989
+ if (!okAll) { t.dispense_status = 'stale'; t.dispense_stale_at = new Date().toISOString(); staleFound = true; }
990
+ }
991
+ // Re-checa os ENDEREÇOS dos itens endereçados (SF5.5): destilado/veto/script apontam a um
992
+ // destino REAL. óbito não tem endereço (nada a re-checar). Endereço sumiu → stale ruidoso.
993
+ const addresses = [];
994
+ for (const a of (doc.addresses || [])) {
995
+ if (a.disposition === 'obito') continue;
996
+ const failures = [];
997
+ if (!a.endereco) failures.push('endereço ausente');
998
+ else if (!fs.existsSync(path.resolve(a.endereco))) failures.push(`endereço sumiu do disco: ${a.endereco} — re-adjudicar`);
999
+ const target = a.target_glob || a.target_path;
1000
+ const okAll = failures.length === 0;
1001
+ addresses.push({ target, ok: okAll, failures });
1002
+ if (!okAll) { a.stale = true; a.stale_at = new Date().toISOString(); staleFound = true; }
1003
+ }
1004
+ // Re-checa as linhas COMMAND fechadas (ADR-006 §Consequências, SF5.5 QA finding): o comando
1005
+ // em disco (command_path) ainda existe E o smoke próprio (command_smoke) existe e é NÃO-VAZIO
1006
+ // — mesmo padrão de re-verificação do evidence-ref da dispensa. Um close de VIRAR-COMANDO cujo
1007
+ // comando/smoke sumiu vira stale ruidoso, nunca silencioso (fail-loud também em exit code).
1008
+ const command = [];
1009
+ for (const t of doc.triage) {
1010
+ if (t.command_status !== 'closed') continue;
1011
+ const failures = [];
1012
+ if (!t.command_path) failures.push('comando fechado sem command_path');
1013
+ else if (!fs.existsSync(path.resolve(t.command_path))) failures.push(`comando sumiu do disco: ${t.command_path} — re-adjudicar`);
1014
+ if (!t.command_smoke) failures.push('comando fechado sem command_smoke');
1015
+ else {
1016
+ const sAbs = path.resolve(t.command_smoke);
1017
+ if (!fs.existsSync(sAbs)) failures.push(`smoke do comando sumiu do disco: ${t.command_smoke} — re-adjudicar`);
1018
+ else if (fs.statSync(sAbs).size === 0) failures.push(`smoke do comando vazio: ${t.command_smoke} — re-adjudicar`);
1019
+ }
1020
+ const okAll = failures.length === 0;
1021
+ command.push({ workflow: t.workflow, ok: okAll, failures });
1022
+ if (!okAll) { t.command_status = 'stale'; t.command_stale_at = new Date().toISOString(); staleFound = true; }
1023
+ }
1024
+ if (staleFound) writeManifest(mfPath, doc);
1025
+ const staleTotal = report.filter((r) => !r.ok).length + dispense.filter((r) => !r.ok).length
1026
+ + addresses.filter((r) => !r.ok).length + command.filter((r) => !r.ok).length;
1027
+ return { action: 'verify', checked: closed.length, stale: report.filter((r) => !r.ok), report, dispense, addresses, command, staleTotal };
1028
+ }
1029
+
1030
+ // ---------------------------------------------------------------------------
1031
+ // CLI.
1032
+ // ---------------------------------------------------------------------------
1033
+ function parseArgs(argv) {
1034
+ const opts = { owns: [], runIds: [] };
1035
+ const rest = [];
1036
+ for (let i = 0; i < argv.length; i++) {
1037
+ const a = argv[i];
1038
+ switch (a) {
1039
+ case '--squad': opts.squad = argv[++i]; break;
1040
+ case '--workflow': opts.workflow = argv[++i]; break;
1041
+ case '--disposition': opts.disposition = argv[++i]; break;
1042
+ case '--purpose': opts.purpose = argv[++i]; break;
1043
+ case '--overlap': opts.overlap = argv[++i]; break;
1044
+ case '--basis': opts.basis = argv[++i]; break;
1045
+ case '--spec': opts.spec = argv[++i]; break;
1046
+ case '--pod-dir': opts.podDir = argv[++i]; break;
1047
+ case '--pod': opts.pod = argv[++i]; break;
1048
+ case '--owns': opts.owns.push(...String(argv[++i]).split(',')); break;
1049
+ case '--amend': opts.amend = true; break;
1050
+ case '--to': opts.to = argv[++i]; break;
1051
+ case '--wf-path': opts.wfPath = argv[++i]; break;
1052
+ case '--run-id': opts.runIds.push(argv[++i]); break;
1053
+ case '--pair': opts.pair = argv[++i]; break;
1054
+ case '--verify-note': opts.verifyNote = argv[++i]; break;
1055
+ case '--squad-dir': opts.squadDir = argv[++i]; break;
1056
+ case '--now': opts.now = argv[++i]; break;
1057
+ // SF3.2 — re-âncora / motor / feature-set / waiver
1058
+ case '--decision': opts.decision = argv[++i]; break;
1059
+ case '--from-wf': opts.fromWf = argv[++i]; break;
1060
+ case '--motor-file': (opts.motorFiles = opts.motorFiles || []).push(argv[++i]); break;
1061
+ case '--assertion-type': (opts.assertionTypes = opts.assertionTypes || []).push(argv[++i]); break;
1062
+ case '--dag-shape': (opts.dagShapes = opts.dagShapes || []).push(argv[++i]); break;
1063
+ case '--checkpoint-pos': (opts.checkpointPositions = opts.checkpointPositions || []).push(argv[++i]); break;
1064
+ case '--fix-loop-covered': opts.fixLoopCovered = argv[++i] === 'true'; break;
1065
+ case '--approved-by': opts.approvedBy = argv[++i]; break;
1066
+ case '--command-path': opts.commandPath = argv[++i]; break;
1067
+ case '--smoke': opts.smoke = argv[++i]; break;
1068
+ case '--transcript': opts.transcript = argv[++i]; break;
1069
+ case '--declared': (opts.declared = opts.declared || []).push(argv[++i]); break;
1070
+ case '--partial': (opts.partial = opts.partial || []).push(argv[++i]); break;
1071
+ // SF3.4 (ADR-006 §Consequências) — re-adjudicação de linha closed
1072
+ case '--reason': opts.reason = argv[++i]; break;
1073
+ case '--feature': opts.feature = argv[++i]; break;
1074
+ case '--decision-ref': opts.decisionRef = argv[++i]; break;
1075
+ // SF5.5 (ADR-006 §Consequências) — dispense-close / address-set
1076
+ case '--evidence-ref': opts.evidenceRef = argv[++i]; break;
1077
+ case '--path': opts.addrPath = argv[++i]; break;
1078
+ case '--glob': opts.glob = argv[++i]; break;
1079
+ case '--endereco': opts.endereco = argv[++i]; break;
1080
+ case '--evidence': opts.evidence = argv[++i]; break;
1081
+ default: rest.push(a);
1082
+ }
1083
+ }
1084
+ return { opts, rest };
1085
+ }
1086
+
1087
+ const DISPATCH = {
1088
+ init: cmdInit,
1089
+ 'triage-set': cmdTriageSet,
1090
+ 'triage-approve': cmdTriageApprove,
1091
+ 'command-close': cmdCommandClose,
1092
+ 'dispense-close': cmdDispenseClose,
1093
+ 'address-set': cmdAddressSet,
1094
+ 'open-line': cmdOpenLine,
1095
+ 'reopen-line': cmdReopenLine,
1096
+ advance: cmdAdvance,
1097
+ 'close-line': cmdCloseLine,
1098
+ 'set-anchor': cmdSetAnchor,
1099
+ 'waiver-set': cmdWaiverSet,
1100
+ coverage: cmdCoverage,
1101
+ verify: (mfPath) => cmdVerify(mfPath),
1102
+ };
1103
+
1104
+ function main() {
1105
+ const [sub, mfPath, ...rest] = process.argv.slice(2);
1106
+ if (!sub || !mfPath || !DISPATCH[sub]) {
1107
+ console.error('Uso: node scripts/migration-manifest.cjs <subcomando> <manifest.yaml> [flags]');
1108
+ console.error(`Subcomandos: ${Object.keys(DISPATCH).join(' · ')}`);
1109
+ process.exit(1);
1110
+ }
1111
+ const { opts } = parseArgs(rest);
1112
+ try {
1113
+ const out = DISPATCH[sub](path.resolve(mfPath), opts);
1114
+ if (sub === 'coverage') {
1115
+ console.log(`coverage: ${out.total} arquivo(s); ${out.unaddressed.length} sem endereço.`);
1116
+ for (const u of out.unaddressed) console.log(` ! ${u}`);
1117
+ process.exit(out.clean ? 0 : 2);
1118
+ }
1119
+ if (sub === 'verify') {
1120
+ console.log(`verify: ${out.checked} linha(s) fechada(s) + ${out.dispense.length} dispensa(s) + ${out.command.length} comando(s) + ${out.addresses.length} endereço(s) re-verificado(s) por código.`);
1121
+ for (const r of out.report) console.log(` ${r.ok ? '✓' : '✗'} line:${r.workflow}${r.ok ? '' : ' — ' + r.failures.join('; ')}`);
1122
+ for (const d of out.dispense) console.log(` ${d.ok ? '✓' : '✗'} dispense:${d.workflow}${d.ok ? '' : ' — ' + d.failures.join('; ')}`);
1123
+ for (const c of out.command) console.log(` ${c.ok ? '✓' : '✗'} command:${c.workflow}${c.ok ? '' : ' — ' + c.failures.join('; ')}`);
1124
+ for (const a of out.addresses) console.log(` ${a.ok ? '✓' : '✗'} address:${a.target}${a.ok ? '' : ' — ' + a.failures.join('; ')}`);
1125
+ // FAIL-LOUD (SF5.5 QA): qualquer stale marcado → exit NÃO-ZERO (1). Stale silencioso em
1126
+ // exit code contradiz a tese fail-loud do helper — o verde de exit 0 é reservado a "tudo íntegro".
1127
+ process.exit(out.staleTotal ? 1 : 0);
1128
+ }
1129
+ console.log(JSON.stringify(out));
1130
+ process.exit(0);
1131
+ } catch (e) {
1132
+ console.error(`[migration-manifest:${sub}] ${e.message}`);
1133
+ process.exit(e instanceof ManifestError ? e.exitCode : 1);
1134
+ }
1135
+ }
1136
+
1137
+ if (require.main === module) main();
1138
+
1139
+ module.exports = {
1140
+ cmdInit, cmdTriageSet, cmdTriageApprove, cmdCommandClose, cmdDispenseClose, cmdAddressSet,
1141
+ cmdOpenLine, cmdReopenLine, cmdAdvance, cmdCloseLine,
1142
+ cmdSetAnchor, cmdWaiverSet, cmdCoverage, cmdVerify,
1143
+ readManifest, writeManifest, serializeBody, snapshotTree, verifyLineEvidence, buildInventory,
1144
+ computeFeatureSet, featureDiff, computeMotorHash, checkReanchorGate, reExecuteSweep, globToRegex,
1145
+ ManifestError, PHASES, DEFAULT_MOTOR_FILES, FAMILY_DIRS, ADDRESS_DISPOSITIONS,
1146
+ };