@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,1355 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+ /**
4
+ * pc-run-pod.cjs — motor determinístico da skill `pc-run-pod` (Story SF1.2).
5
+ *
6
+ * Executa um `wf-*.yaml` de pod POR ETAPA, forçando o processo por CÓDIGO — nunca
7
+ * pela confiança no relato do subagente (mesma regra "do not fake green" do
8
+ * `pc-full-cycle`). É irmão de `pc-full-cycle`: a story é dado / a skill é motor;
9
+ * aqui o pod é dado / este runner é motor, na granularidade "passo do DAG".
10
+ *
11
+ * Garantia central (SPEC §B1, ADR-001, AC4/DoD 1b): uma etapa só vira `done` quando
12
+ * a VERIFICAÇÃO EM DISCO passa — o artefato existe, casa estruturalmente com o
13
+ * template, e TODAS as `veto.assertions` passam. Enquanto isso não ocorre, o runner
14
+ * RECUSA avançar: a etapa seguinte nunca roda. O agente não escreve o `state.json`.
15
+ *
16
+ * Fonte única do contrato (Risco 2): parse/schema/DAG/fix_cycle vêm de
17
+ * `validate-pod-workflow.cjs` (SF1.1) — o preflight do runner NÃO reimplementa o
18
+ * schema; reusa `validateWorkflow` + `parseYaml` + `topoOrder`.
19
+ *
20
+ * State durável (retomável): `.power-claude/pod-runs/{pod}/{workflow}/{run-id}/state.json`,
21
+ * schema exato da SPEC §B1. Comandos: start/resume/next/status/approve/revise/abort.
22
+ *
23
+ * Status de etapa: `pending` → `running` → `done` (SPEC). Extensões documentadas do
24
+ * runner: `awaiting_approval` (checkpoint.blocking verificado, aguardando gate humano),
25
+ * `failed` (aborto). O relato do subagente nunca decide a transição.
26
+ *
27
+ * SF11.10/ADR-024: o gate é condicionado ao RITMO (`checkpoint.skip_modes`, conjunto fechado
28
+ * `RUN_MODES`). Um ritmo listado PULA o gate — e pular NÃO é aprovar: `approve()` não é
29
+ * chamado, a etapa nunca entra em `awaiting_approval` e o `state.json` grava `checkpoint:
30
+ * null` ali. A proibição do ADR-020 (o modo autônomo JAMAIS aprova) segue inteira.
31
+ *
32
+ * Node >= 18, sem dependências externas. Exporta a API para a suíte própria (DoD 1b).
33
+ */
34
+
35
+ const fs = require('node:fs');
36
+ const path = require('node:path');
37
+ const { execFileSync } = require('node:child_process'); // SF7.2 — command_check (execução blessed)
38
+
39
+ const {
40
+ parseYaml,
41
+ validateWorkflow,
42
+ podRootFor,
43
+ topoOrder,
44
+ VETO_TYPES,
45
+ // SF11.10/ADR-024 — conjunto fechado de ritmos e chaves conhecidas de `checkpoint`. Vêm do
46
+ // validador (fonte única do contrato, Risco 2 da SF1.2): estático e runtime NUNCA divergem.
47
+ RUN_MODES,
48
+ CHECKPOINT_KEYS,
49
+ } = require('./validate-pod-workflow.cjs');
50
+
51
+ // SF7.2/ADR-011 — raiz BLESSED de execução, ANCORADA no diretório do runner (robusto ao
52
+ // path de instalação). Defesa em profundidade: o runner re-valida esta raiz em runtime,
53
+ // nunca confia que o validador estático rodou.
54
+ const VETO_CHECKS_ROOT = path.join(__dirname, 'veto-checks');
55
+ const COMMAND_CHECK_TIMEOUT_MS = 15000;
56
+
57
+ // Conjunto BLESSED de runtime (paridade com o estático): repo-rel paths sob
58
+ // scripts/veto-checks/ classificados em installer/motor-scripts.json (ADR-007). Cacheado.
59
+ let _blessedVetoRels = null;
60
+ function blessedVetoRels() {
61
+ if (_blessedVetoRels) return _blessedVetoRels;
62
+ _blessedVetoRels = new Set();
63
+ try {
64
+ const { motorRels } = require(path.join(__dirname, '..', 'installer', 'motor-scripts.js'));
65
+ for (const rel of motorRels()) if (rel.startsWith('scripts/veto-checks/')) _blessedVetoRels.add(rel);
66
+ } catch { /* manifesto indisponível — realpath + existência ainda barram o vetor externo */ }
67
+ return _blessedVetoRels;
68
+ }
69
+
70
+ /** Extrai o conteúdo do 1º bloco cercado ```<fence>``` sob uma seção declarada. */
71
+ function extractFencedBlock(text, section, fence) {
72
+ const lines = text.split(/\r?\n/);
73
+ const want = String(section).trim();
74
+ let i = 0;
75
+ if (want) {
76
+ i = lines.findIndex((l) => l.trim() === want);
77
+ if (i < 0) return null;
78
+ i += 1;
79
+ }
80
+ const open = new RegExp('^\\s*```' + (fence ? fence : '[A-Za-z0-9_-]*') + '\\s*$');
81
+ let start = -1;
82
+ for (; i < lines.length; i++) {
83
+ if (open.test(lines[i])) { start = i + 1; break; }
84
+ // Não atravessa para a próxima seção (heading ##) se estávamos ancorados numa seção.
85
+ if (want && /^#{1,6}\s+\S/.test(lines[i].trim())) return null;
86
+ }
87
+ if (start < 0) return null;
88
+ const body = [];
89
+ for (let j = start; j < lines.length; j++) {
90
+ if (/^\s*```\s*$/.test(lines[j])) return body.join('\n');
91
+ body.push(lines[j]);
92
+ }
93
+ return null; // sem fechamento
94
+ }
95
+
96
+ // Motor de memória do pod (Story SF2.1) — biblioteca já testada (suíte própria).
97
+ // SF2.2 CONSOME esta API (append/consolidate/getConsolidatedForBrief); nunca
98
+ // reimplementa retenção/curadoria. O runner é o CALLER da cadência de consolidação.
99
+ const podMemory = require('./pod-memory.cjs');
100
+
101
+ // ---------------------------------------------------------------------------
102
+ // Utilidades de estado e tempo.
103
+ // ---------------------------------------------------------------------------
104
+
105
+ function nowISO() {
106
+ return new Date().toISOString();
107
+ }
108
+
109
+ let __runIdSeq = 0;
110
+ /**
111
+ * run-id = timestamp ISO sanitizado (prefixo legível, padrão da SPEC
112
+ * 2026-07-24T18-00-00Z) + sufixo de unicidade `-<pid>-<seq>`.
113
+ *
114
+ * [SF1.2 finding #1 — débito resolvido na SF1.3] O timestamp tem granularidade de
115
+ * segundo: dois `start` no MESMO segundo (ex.: um harness que dispara vários runs)
116
+ * gerariam o mesmo run-id → o mesmo path de state.json → um run sobrescrevendo o
117
+ * outro. O sufixo `<pid>-<seq>` garante unicidade dentro do processo (seq monotônico)
118
+ * e entre processos (pid), sem perder a legibilidade do timestamp. Callers que
119
+ * passam `--run-id`/`runId` explícito continuam no controle (não passam por aqui).
120
+ */
121
+ function makeRunId(date) {
122
+ const iso = (date || new Date()).toISOString(); // 2026-07-24T18:00:00.000Z
123
+ const base = iso.replace(/\.\d+Z$/, 'Z').replace(/:/g, '-');
124
+ const suffix = `${process.pid.toString(36)}-${(__runIdSeq++).toString(36)}`;
125
+ return `${base}-${suffix}`;
126
+ }
127
+
128
+ function runDirFor(runsRoot, pod, workflow, runId) {
129
+ return path.join(runsRoot, pod, workflow, runId);
130
+ }
131
+
132
+ function statePathFor(runsRoot, pod, workflow, runId) {
133
+ return path.join(runDirFor(runsRoot, pod, workflow, runId), 'state.json');
134
+ }
135
+
136
+ function readState(statePath) {
137
+ let raw;
138
+ try {
139
+ raw = fs.readFileSync(statePath, 'utf8');
140
+ } catch (e) {
141
+ throw new Error(`state.json não encontrado: ${statePath}`);
142
+ }
143
+ let state;
144
+ try {
145
+ state = JSON.parse(raw);
146
+ } catch (e) {
147
+ // Risco 4: state corrompido nunca é "consertado" silenciosamente — HALT explícito.
148
+ throw new Error(`state.json corrompido (JSON inválido) em ${statePath}: ${e.message}`);
149
+ }
150
+ validateStateSchema(state, statePath);
151
+ return state;
152
+ }
153
+
154
+ function validateStateSchema(state, where) {
155
+ // Risco 4: valida o schema antes de qualquer decisão de retomada.
156
+ const req = ['pod', 'workflow', 'run_id', 'mode', 'steps', 'updated_at'];
157
+ for (const k of req) {
158
+ if (!(k in state)) throw new Error(`state.json inválido (campo "${k}" ausente) em ${where}`);
159
+ }
160
+ if (!state.steps || typeof state.steps !== 'object' || Array.isArray(state.steps)) {
161
+ throw new Error(`state.json inválido (steps não é um mapa) em ${where}`);
162
+ }
163
+ const VALID = new Set(['pending', 'running', 'awaiting_approval', 'done', 'failed']);
164
+ for (const [id, s] of Object.entries(state.steps)) {
165
+ if (!VALID.has(s.status)) {
166
+ throw new Error(`state.json inválido: etapa "${id}" com status desconhecido "${s.status}" em ${where}`);
167
+ }
168
+ }
169
+ }
170
+
171
+ /** Escrita atômica: grava em tmp e renomeia (nunca deixa um state.json parcial). */
172
+ function writeState(statePath, state) {
173
+ state.updated_at = nowISO();
174
+ fs.mkdirSync(path.dirname(statePath), { recursive: true });
175
+ const tmp = `${statePath}.tmp-${process.pid}`;
176
+ fs.writeFileSync(tmp, `${JSON.stringify(state, null, 2)}\n`, 'utf8');
177
+ fs.renameSync(tmp, statePath);
178
+ return state;
179
+ }
180
+
181
+ // ---------------------------------------------------------------------------
182
+ // Carga do contrato (fonte única — SF1.1).
183
+ // ---------------------------------------------------------------------------
184
+
185
+ /**
186
+ * SF10.20/ADR-019 `DF-SF10.20-2` — deriva, do `wf` JÁ ESTRUTURADO, as CAPACIDADES que
187
+ * ele exige e que ESTE motor (o do repo em que este arquivo está instalado) não tem.
188
+ * Não há campo novo no `wf` e não há comparação de versão: a capacidade é o FATO,
189
+ * a versão seria só um proxy dela (e um proxy que mente exatamente quando alguém
190
+ * copia scripts à mão — que é a causa-raiz desta família de defeito).
191
+ *
192
+ * Três classes, todas derivadas de campos que o schema já tem:
193
+ * (a) `veto.assertions[].type` que não consta em VETO_TYPES deste motor;
194
+ * (b) `command_check.command` cujo script blessed não existe sob scripts/veto-checks/;
195
+ * (c) [SF11.10/ADR-024 `DF-SF11.10-6`] chave sob `step.checkpoint` que não consta em
196
+ * CHECKPOINT_KEYS deste motor. ADR-021 `DF-SF11.2-7` registrou como PERDA que o gate
197
+ * de capacidade não sabia expressar a capacidade da vez; esta sabe, e barato. Sem ela,
198
+ * motor antigo + `wf` novo degrada SEGURO (o gate bloqueia também no ritmo autônomo,
199
+ * nada é aprovado sem humano) porém OBSCURO: a run estaciona sem causa nomeada.
200
+ *
201
+ * Best-effort e SEM efeito colateral: nunca lança, nunca decide se algo falha —
202
+ * só explica uma falha que o preflight já produziu.
203
+ */
204
+ function capabilityGaps(workflowPath) {
205
+ const gaps = [];
206
+ let doc;
207
+ try {
208
+ doc = parseYaml(fs.readFileSync(workflowPath, 'utf8'));
209
+ } catch {
210
+ return gaps; // wf ilegível: a falha é outra, não de capacidade.
211
+ }
212
+ const steps = doc && Array.isArray(doc.steps) ? doc.steps : [];
213
+ for (const step of steps) {
214
+ // (c) SF11.10 — chave de `checkpoint` que este motor não conhece.
215
+ const cp = step && step.checkpoint;
216
+ if (cp && typeof cp === 'object' && !Array.isArray(cp)) {
217
+ for (const k of Object.keys(cp)) {
218
+ if (!CHECKPOINT_KEYS.includes(k)) {
219
+ gaps.push(`etapa "${step.id}" · checkpoint.${k}: chave de checkpoint "${k}" não existe neste motor`);
220
+ }
221
+ }
222
+ }
223
+ const assertions = step && step.veto && Array.isArray(step.veto.assertions) ? step.veto.assertions : [];
224
+ for (let i = 0; i < assertions.length; i++) {
225
+ const a = assertions[i];
226
+ if (!a || typeof a.type !== 'string') continue;
227
+ const where = `etapa "${step.id}" · veto.assertions[${i}]`;
228
+ if (!Object.prototype.hasOwnProperty.call(VETO_TYPES, a.type)) {
229
+ gaps.push(`${where}: tipo de veto "${a.type}" não existe neste motor`);
230
+ continue;
231
+ }
232
+ if (a.type === 'command_check' && typeof a.command === 'string' && /^[\w.-]+$/.test(a.command)) {
233
+ if (!fs.existsSync(path.join(VETO_CHECKS_ROOT, a.command))) {
234
+ gaps.push(`${where}: script blessed "scripts/veto-checks/${a.command}" não está instalado neste motor`);
235
+ }
236
+ }
237
+ }
238
+ }
239
+ return gaps;
240
+ }
241
+
242
+ /** Manifest da instalação, ou null. Best-effort: nunca lança, nunca é condição de falha. */
243
+ function readInstallManifest() {
244
+ try {
245
+ return JSON.parse(fs.readFileSync(path.join(__dirname, '..', '.power-claude', 'manifest.json'), 'utf8'));
246
+ } catch {
247
+ return null;
248
+ }
249
+ }
250
+
251
+ /**
252
+ * SF10.20/ADR-019 `DF-SF10.20-3` — única frase honesta disponível sobre a instalação.
253
+ * O manifest é registro de distribuição/integridade, NÃO o oráculo do bloqueio.
254
+ */
255
+ function installationLabel() {
256
+ const m = readInstallManifest();
257
+ if (!m) return 'SEM manifest em .power-claude/ — instalação não rastreada (nunca passou por install/adopt/update)';
258
+ return `versão ${m.version} registrada em .power-claude/manifest.json`;
259
+ }
260
+
261
+ /**
262
+ * SF10.20 — o comando de correção, VERIFICADO antes de ser impresso.
263
+ *
264
+ * Por que não `npx power-claude`: uma instalação não tem `package.json` nem
265
+ * `node_modules` do framework, e o binário não está no PATH — `npx` resolveria pelo
266
+ * REGISTRY PÚBLICO, onde o nome `power-claude` pertence a um pacote de TERCEIRO. A
267
+ * mensagem mandaria o dono, já em estado quebrado, baixar e executar código de
268
+ * estranho dentro do repo de uso diário. Um comando que não corrige e ainda abre
269
+ * superfície de supply-chain é pior do que não imprimir comando nenhum.
270
+ *
271
+ * O caminho do framework vem do `frameworkRoot` que o próprio instalador carimbou no
272
+ * manifest (ele roda a partir do framework, então sabe). Como é registro e não
273
+ * oráculo — o framework pode ter sido movido ou apagado depois —, aqui se CONFERE a
274
+ * existência do `bin/cli.js` antes de imprimir. Sem confirmação, degrada para
275
+ * instrução com placeholder explícito: menos cômodo, mas nunca perigoso e nunca falso.
276
+ *
277
+ * @returns {{lines: string[], resolved: boolean}}
278
+ */
279
+ function correctionCommand() {
280
+ const m = readInstallManifest();
281
+ const root = m && typeof m.frameworkRoot === 'string' ? m.frameworkRoot : null;
282
+ const cli = root ? path.join(root, 'bin', 'cli.js') : null;
283
+ if (cli && fs.existsSync(cli)) {
284
+ return {
285
+ resolved: true,
286
+ lines: [
287
+ ` Corrija: node ${cli} adopt \\`,
288
+ ` && node ${cli} update --scope motor`,
289
+ ],
290
+ };
291
+ }
292
+ return {
293
+ resolved: false,
294
+ lines: [
295
+ ' Corrija a partir do repositório do POWER CLAUDE (troque o caminho pelo real):',
296
+ ' node <repo-power-claude>/bin/cli.js adopt \\',
297
+ ' && node <repo-power-claude>/bin/cli.js update --scope motor',
298
+ root
299
+ ? ` (o manifest aponta "${root}", mas não há bin/cli.js lá — o framework foi movido?)`
300
+ : ' (esta instalação não registra o caminho do framework; rode install/adopt de lá)',
301
+ ' NÃO use "npx power-claude": esse nome no registry público é de terceiro.',
302
+ ],
303
+ };
304
+ }
305
+
306
+ /**
307
+ * SF10.20/ADR-019 `DF-SF10.20-4` — ATRIBUIÇÃO. O preflight já falhava alto e cedo; o que
308
+ * faltava era dizer de QUEM é a culpa. A mensagem do validador acusa o `wf` ("type
309
+ * inválido", "proibido por ADR-002") — e o `wf` pode estar perfeitamente certo, com a
310
+ * INSTALAÇÃO velha. Aqui a exceção original é reaproveitada (mesma classe, mesmo exit
311
+ * code, zero mudança de comportamento — só texto), acrescida da hipótese de skew.
312
+ *
313
+ * Fica em `loadWorkflow` e NÃO no validador de propósito: `validate-pod-workflow.cjs`
314
+ * também é linter estático puro (CI, forge-*, pod-export-compile), onde "sua instalação
315
+ * está defasada" seria ruído falso. A hipótese só é verdadeira quando alguém tenta RODAR.
316
+ *
317
+ * Declara DUAS causas porque, de dentro do motor defasado, é logicamente impossível
318
+ * decidir a direção do skew: um `type` desconhecido é indistinguível de um `type`
319
+ * inventado. Apontar uma causa só seria adivinhação com cara de diagnóstico.
320
+ */
321
+ function annotateCapabilitySkew(error, workflowPath) {
322
+ try {
323
+ const gaps = capabilityGaps(workflowPath);
324
+ if (gaps.length === 0) return error; // falha de contrato comum: mensagem intacta.
325
+ error.capabilityGaps = gaps;
326
+ error.message = [
327
+ error.message,
328
+ '',
329
+ ' ↑ ATRIBUIÇÃO: esta falha é da INSTALAÇÃO, não necessariamente do wf.',
330
+ ` Motor: ${path.join(__dirname, path.basename(__filename))}`,
331
+ ` Instalação: ${installationLabel()}`,
332
+ ' Capacidade(s) que o wf exige e este motor NÃO tem:',
333
+ ...gaps.map((g) => ` - ${g}`),
334
+ ' Duas causas possíveis, nesta ordem de probabilidade:',
335
+ ' 1. INSTALAÇÃO DEFASADA — o wf usa capacidade de um motor mais novo que o instalado.',
336
+ ...correctionCommand().lines,
337
+ ' 2. o wf declara capacidade que não existe em nenhuma versão do motor',
338
+ ' (só nesse caso o defeito é do wf).',
339
+ ].join('\n');
340
+ } catch { /* atribuição é enfeite: jamais pode substituir a falha original */ }
341
+ return error;
342
+ }
343
+
344
+ function loadWorkflow(workflowPath, podRootOverride) {
345
+ // Preflight de contrato reusando o validador estático da SF1.1 (Risco 2): se o
346
+ // wf-*.yaml não é bem-formado, o runner nem começa. Lança em caso de contrato inválido.
347
+ // SF10.20: a falha ganha ATRIBUIÇÃO quando é skew de capacidade (ADR-019 DF-SF10.20-4).
348
+ try {
349
+ validateWorkflow(workflowPath, podRootOverride);
350
+ } catch (error) {
351
+ throw annotateCapabilitySkew(error, workflowPath);
352
+ }
353
+ const doc = parseYaml(fs.readFileSync(workflowPath, 'utf8'));
354
+ const podRoot = podRootFor(workflowPath, podRootOverride);
355
+ const order = topoOrder(doc);
356
+ return { doc, podRoot, order };
357
+ }
358
+
359
+ function stepById(doc, id) {
360
+ return doc.steps.find((s) => s.id === id);
361
+ }
362
+
363
+ /**
364
+ * SF11.10/ADR-024 `DF-SF11.10-2` — o checkpoint bloqueia NESTE ritmo?
365
+ *
366
+ * `skip_modes` é enumeração NEGATIVA de propósito: o modo passado só escapa do gate se
367
+ * estiver LISTADO. Ausente, `undefined`, typo, modo futuro ⇒ **bloqueia** (fail-closed).
368
+ * A enumeração positiva (`blocking_in: [interactive]`) foi avaliada e recusada: um modo
369
+ * desconhecido cairia em "não bloqueia" — falha ABERTA no único gate humano do sistema.
370
+ *
371
+ * PULAR NÃO É APROVAR: quando esta função devolve `false`, o motor segue o caminho comum
372
+ * (`done`) e o `state.json` grava `checkpoint: null` para a etapa — ninguém, em lugar
373
+ * nenhum, chama `approve()` em nome do dono. A proibição inegociável do ADR-020 fica
374
+ * INTEIRA; o que muda é ONDE existe gate, nunca QUEM o resolve.
375
+ */
376
+ function isBlocking(step, mode) {
377
+ if (!step || !step.checkpoint || step.checkpoint.blocking !== true) return false;
378
+ const skip = Array.isArray(step.checkpoint.skip_modes) ? step.checkpoint.skip_modes : [];
379
+ return !skip.includes(mode);
380
+ }
381
+
382
+ // ---------------------------------------------------------------------------
383
+ // Verificação pós-etapa EM DISCO (o coração do não-fake-green).
384
+ // ---------------------------------------------------------------------------
385
+
386
+ function extractHeadings(text) {
387
+ return text
388
+ .split(/\r?\n/)
389
+ .map((l) => l.trim())
390
+ .filter((l) => /^#{1,6}\s+\S/.test(l));
391
+ }
392
+
393
+ function extractField(text, field) {
394
+ // Aceita `field: valor`, `**field**: valor`, `- field: valor`, `field = valor`.
395
+ const esc = field.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
396
+ const re = new RegExp(`^\\s*(?:[-*]\\s+)?(?:\\*\\*)?${esc}(?:\\*\\*)?\\s*[:=]\\s*(.*)$`, 'i');
397
+ for (const line of text.split(/\r?\n/)) {
398
+ const m = line.match(re);
399
+ if (m) return m[1].trim();
400
+ }
401
+ return null;
402
+ }
403
+
404
+ // Débito #2 do gate sf1.5-piloto-hormozi-g5.yaml (finding id:1, segunda metade):
405
+ // o contador só reconhecia bullets `-`/`*` — 2/5 runs do piloto formataram o Value
406
+ // Stack como TABELA e count_gte contou 0 (< n) → fix evitável (~40% das runs). Aqui o
407
+ // contador passa a reconhecer as 3 formas contáveis de item de lista sob um heading:
408
+ // (a) bullets `- item` / `* item` (comportamento SF1.1 — INALTERADO)
409
+ // (b) lista numerada `1. item` / `2. item`
410
+ // (c) linha de tabela `| ... |` (EXCLUINDO a linha separadora `|---|---|`)
411
+ // Prosa corrida NUNCA conta (nenhuma das 3 formas casa uma linha de prosa). É ADIÇÃO
412
+ // pura: bullets contam exatamente como antes; numeradas/tabela só SOMAM (nunca reduzem
413
+ // a contagem de um artefato pré-existente) — sem afrouxar a semântica do veto.
414
+ const RE_BULLET = /^\s*[-*]\s+\S/;
415
+ const RE_NUMBERED = /^\s*\d+\.\s+\S/;
416
+ const RE_TABLE_ROW = /^\s*\|.+\|\s*$/;
417
+ // separador de tabela markdown: célula só com `-`, `:`, espaços (ex.: `|---|:--:|`).
418
+ const RE_TABLE_SEPARATOR = /^\s*\|[\s:|-]+\|\s*$/;
419
+
420
+ function countListItemsInSection(text, sectionField) {
421
+ // Conta itens de lista sob a seção cujo heading CONTÉM `sectionField`.
422
+ const lines = text.split(/\r?\n/);
423
+ let inSection = false;
424
+ let count = 0;
425
+ for (const line of lines) {
426
+ if (/^#{1,6}\s+\S/.test(line.trim())) {
427
+ inSection = line.toLowerCase().includes(String(sectionField).toLowerCase());
428
+ continue;
429
+ }
430
+ if (!inSection) continue;
431
+ if (RE_BULLET.test(line)) count++; // (a) bullets — inalterado
432
+ else if (RE_NUMBERED.test(line)) count++; // (b) lista numerada
433
+ else if (RE_TABLE_ROW.test(line) && !RE_TABLE_SEPARATOR.test(line)) count++; // (c) tabela (exclui separador)
434
+ }
435
+ return count;
436
+ }
437
+
438
+ /**
439
+ * Avalia UMA veto.assertion contra o texto do artefato. Resultado binário e
440
+ * determinístico (SPEC §B1: veto é avaliável por máquina, proibido julgamento LLM).
441
+ * As 5 formas são as mesmas validadas na FORMA pela SF1.1 (VETO_TYPES).
442
+ */
443
+ function evalVeto(assertion, text, artifactPath, roots) {
444
+ const ok = () => ({ ok: true });
445
+ const no = (reason) => ({ ok: false, reason });
446
+ switch (assertion.type) {
447
+ case 'section_exists': {
448
+ const want = String(assertion.section).trim();
449
+ const has = text.split(/\r?\n/).some((l) => l.trim() === want);
450
+ return has ? ok() : no(`seção "${want}" ausente`);
451
+ }
452
+ case 'field_matches': {
453
+ const val = extractField(text, assertion.field);
454
+ if (val === null) return no(`campo "${assertion.field}" ausente`);
455
+ let re;
456
+ try {
457
+ re = new RegExp(String(assertion.pattern));
458
+ } catch (e) {
459
+ return no(`regex inválida: ${e.message}`);
460
+ }
461
+ return re.test(val) ? ok() : no(`campo "${assertion.field}"="${val}" não casa /${assertion.pattern}/`);
462
+ }
463
+ case 'file_referenced_exists': {
464
+ const ref = extractField(text, assertion.field);
465
+ if (ref === null) return no(`campo "${assertion.field}" ausente`);
466
+ const bases = [path.dirname(artifactPath), roots.runDir, roots.podRoot].filter(Boolean);
467
+ const found = bases.some((b) => fs.existsSync(path.resolve(b, ref)));
468
+ return found ? ok() : no(`arquivo referenciado por "${assertion.field}" ("${ref}") não existe`);
469
+ }
470
+ case 'count_gte': {
471
+ // M-r2-5 (SF3.1): campo auto-declarado NUNCA satisfaz count_gte — a contagem
472
+ // REAL de itens de lista sob o heading é a ÚNICA fonte. O caminho `extractField`
473
+ // (número declarado) foi REMOVIDO de propósito: era um bypass de green ("Value
474
+ // Stack: 99" sem nenhum item passava o veto). Agora `Value Stack: 99` sem itens
475
+ // bloqueia (declaração não compra prova); heading ausente ⇒ contagem 0 ⇒ bloqueia.
476
+ const n = Number(assertion.n);
477
+ const c = countListItemsInSection(text, assertion.field);
478
+ return c >= n ? ok() : no(`contagem ${c} < ${n} para "${assertion.field}"`);
479
+ }
480
+ case 'json_schema_valid': {
481
+ // SF7.2/ADR-011 Decisão 1: com `extract:{section,fence}`, valida o BLOCO fenced de uma
482
+ // seção declarada (não o artefato inteiro) — fecha o CONCERNS-1 de SF6.3 (JSON.parse do
483
+ // .md colidia com checkTemplateConformance). Sem `extract`, comportamento legado (artefato = JSON).
484
+ let jsonText = text;
485
+ if (assertion.extract && typeof assertion.extract === 'object') {
486
+ const fence = assertion.extract.fence || 'json';
487
+ const block = extractFencedBlock(text, assertion.extract.section, fence);
488
+ if (block === null) {
489
+ return no(`bloco \`\`\`${fence}\`\`\` não encontrado na seção "${assertion.extract.section}"`);
490
+ }
491
+ jsonText = block;
492
+ }
493
+ let obj;
494
+ try {
495
+ obj = JSON.parse(jsonText);
496
+ } catch (e) {
497
+ return no(`${assertion.extract ? 'bloco extraído' : 'artefato'} não é JSON válido: ${e.message}`);
498
+ }
499
+ let required = [];
500
+ const sc = assertion.schema;
501
+ if (Array.isArray(sc)) required = sc;
502
+ else if (sc && typeof sc === 'object' && Array.isArray(sc.required)) required = sc.required;
503
+ else if (typeof sc === 'string') {
504
+ const scPath = path.resolve(roots.podRoot, sc);
505
+ if (fs.existsSync(scPath)) {
506
+ try {
507
+ const parsed = JSON.parse(fs.readFileSync(scPath, 'utf8'));
508
+ required = Array.isArray(parsed) ? parsed : (parsed.required || []);
509
+ } catch (e) {
510
+ return no(`schema "${sc}" ilegível: ${e.message}`);
511
+ }
512
+ } else {
513
+ required = String(sc).split(',').map((s) => s.trim()).filter(Boolean);
514
+ }
515
+ }
516
+ const missing = required.filter((k) => !(k in obj));
517
+ return missing.length === 0 ? ok() : no(`chaves obrigatórias ausentes: ${missing.join(', ')}`);
518
+ }
519
+ case 'command_check': {
520
+ // SF7.2/ADR-011 Decisão 2 — executa SÓ script BLESSED sob scripts/veto-checks/.
521
+ // Defesa em profundidade: re-valida a raiz blessed em runtime (nunca confia que o
522
+ // validador estático rodou). execFileSync SEM shell, args literais, timeout. Exit 0 =
523
+ // ok; não-zero (inclui fail-loud de dependência ausente) = no() → etapa BLOQUEADA.
524
+ const cmd = String(assertion.command || '');
525
+ if (/[\\/]/.test(cmd) || cmd.includes('..') || !/\.(cjs|js)$/.test(cmd)) {
526
+ return no(`command_check recusado: "${cmd}" não é nome simples de script blessed`);
527
+ }
528
+ // Guard B (defesa em profundidade endurecida, SF7.2 CONCERNS-1): o nome DEVE ser blessed
529
+ // no manifesto (paridade com o estático — barra arquivo estranho no dir), E o path
530
+ // RESOLVIDO POR realpathSync tem que ficar sob o realpath da raiz blessed (um symlink
531
+ // dentro de veto-checks/ apontando para fora NÃO fura mais: o check lexical anterior
532
+ // seguia o link; o realpath expõe o alvo externo e RECUSA sem executar).
533
+ const blessed = blessedVetoRels();
534
+ if (blessed.size > 0 && !blessed.has(`scripts/veto-checks/${cmd}`)) {
535
+ return no(`command_check recusado: "${cmd}" não é blessed no manifesto (ADR-007)`);
536
+ }
537
+ const scriptPath = path.resolve(VETO_CHECKS_ROOT, cmd);
538
+ let realScript;
539
+ let realRoot;
540
+ try {
541
+ realRoot = fs.realpathSync(VETO_CHECKS_ROOT);
542
+ realScript = fs.realpathSync(scriptPath);
543
+ } catch {
544
+ return no(`command_check recusado: "${cmd}" não resolve em disco sob scripts/veto-checks/`);
545
+ }
546
+ if (path.dirname(realScript) !== realRoot) {
547
+ return no(`command_check recusado: "${cmd}" resolve (realpath) FORA de scripts/veto-checks/ — symlink/traversal para "${realScript}"`);
548
+ }
549
+ const extraArgs = Array.isArray(assertion.args) ? assertion.args.map(String) : [];
550
+ const timeout = Number.isInteger(assertion.timeout_ms) && assertion.timeout_ms > 0
551
+ ? assertion.timeout_ms : COMMAND_CHECK_TIMEOUT_MS;
552
+ try {
553
+ execFileSync(process.execPath, [scriptPath, artifactPath, ...extraArgs], {
554
+ timeout, encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'],
555
+ });
556
+ return ok();
557
+ } catch (e) {
558
+ if (e.code === 'ETIMEDOUT' || e.signal === 'SIGTERM') {
559
+ return no(`command_check "${cmd}" excedeu o timeout de ${timeout}ms`);
560
+ }
561
+ const out = `${e.stderr || ''}${e.stdout || ''}`.trim().split('\n').pop() || e.message;
562
+ return no(`command_check "${cmd}" bloqueou (exit ${e.status ?? '?'}): ${out.slice(0, 200)}`);
563
+ }
564
+ }
565
+ default:
566
+ return no(`tipo de veto desconhecido: ${assertion.type}`);
567
+ }
568
+ }
569
+
570
+ /**
571
+ * Conformidade estrutural ao template: o artefato contém toda SEÇÃO do template.
572
+ * "Seção" = heading de nível 2+ (`##`…); o título H1 do template é descritivo, não
573
+ * uma seção obrigatória do artefato produzido. Regra determinística e documentada.
574
+ */
575
+ function checkTemplateConformance(ctx, step, artifactText) {
576
+ const tmplPath = path.resolve(ctx.podRoot, step.template);
577
+ if (!fs.existsSync(tmplPath)) {
578
+ return { ok: false, failures: [`template ausente em disco: ${step.template}`] };
579
+ }
580
+ const sections = (t) => extractHeadings(t).filter((h) => /^#{2,6}\s+\S/.test(h));
581
+ const wanted = sections(fs.readFileSync(tmplPath, 'utf8'));
582
+ const have = new Set(sections(artifactText));
583
+ const failures = [];
584
+ for (const h of wanted) {
585
+ if (!have.has(h)) failures.push(`seção obrigatória do template ausente no artefato: "${h}"`);
586
+ }
587
+ return { ok: failures.length === 0, failures };
588
+ }
589
+
590
+ /**
591
+ * Verificação pós-etapa completa. Retorna { ok, failures[] }. NÃO muta o state — a
592
+ * decisão de transição é do `advance`. Ordem: artefato existe → template conforme →
593
+ * TODAS as veto.assertions. Reúne todas as falhas (diagnóstico rico no relatório).
594
+ */
595
+ function verifyStep(ctx, stepId) {
596
+ const step = stepById(ctx.doc, stepId);
597
+ const artRel = ctx.state.steps[stepId].artifact;
598
+ if (!artRel) return { ok: false, failures: ['artefato não atribuído à etapa'] };
599
+ const artPath = path.resolve(ctx.runDir, artRel);
600
+ if (!fs.existsSync(artPath)) {
601
+ return { ok: false, failures: [`artefato ausente em disco: ${artRel}`] };
602
+ }
603
+ const text = fs.readFileSync(artPath, 'utf8');
604
+ const failures = [];
605
+ const tconf = checkTemplateConformance(ctx, step, text);
606
+ if (!tconf.ok) failures.push(...tconf.failures);
607
+ const assertions = (step.veto && Array.isArray(step.veto.assertions)) ? step.veto.assertions : [];
608
+ assertions.forEach((a, i) => {
609
+ const r = evalVeto(a, text, artPath, ctx);
610
+ if (!r.ok) failures.push(`veto.assertions[${i}] (${a.type}) falhou: ${r.reason}`);
611
+ });
612
+ return { ok: failures.length === 0, failures };
613
+ }
614
+
615
+ // ---------------------------------------------------------------------------
616
+ // Brief do subagente FRESCO (AC3): leitura obrigatória (SF11.2), paths, contrato de saída,
617
+ // veto (sem `_sources`, SF11.2), retorno ≤15.
618
+ // ---------------------------------------------------------------------------
619
+
620
+ function artifactRelFor(stepId) {
621
+ return path.join('artifacts', `${stepId}.md`);
622
+ }
623
+
624
+ /**
625
+ * SF7.3/ADR-016 — linhas do brief para o `anti_path` da etapa. Separa ENFORÇADOS (item
626
+ * com `mechanized_by` → aponta o veto pareado que MORDE) de ADVISORY (`mechanized_by: null`
627
+ * → orientação rotulada NÃO-enforcement). Etapa sem anti_path = array vazio (nada injetado).
628
+ */
629
+ function antiPathBriefLines(step) {
630
+ const ap = Array.isArray(step.anti_path) ? step.anti_path : [];
631
+ if (ap.length === 0) return [];
632
+ const enforced = ap.filter((it) => it && it.mechanized_by != null);
633
+ const advisory = ap.filter((it) => it && it.mechanized_by == null);
634
+ const out = [];
635
+ if (enforced.length > 0) {
636
+ out.push('', '## Anti-padrões desta etapa — ENFORÇADOS por veto (bloqueiam de verdade)');
637
+ for (const it of enforced) out.push(`- ${it.rule} → enforçado pela veto.assertion id="${it.mechanized_by}" (o runner bloqueia se violado).`);
638
+ }
639
+ if (advisory.length > 0) {
640
+ out.push('', '## Anti-padrões desta etapa (orientação — NÃO enforcement)');
641
+ out.push('_Sugestões escopo-de-etapa; o runner NÃO as verifica em disco — não são gate. Siga-as, mas nada bloqueia se não seguir._');
642
+ for (const it of advisory) out.push(`- ${it.rule}`);
643
+ }
644
+ return out;
645
+ }
646
+
647
+ /**
648
+ * SF11.2/ADR-021 `DF-SF11.2-2` — o MESMO motor roda sobre dois transportes do mesmo `wf`:
649
+ * a raiz de POD em disco (driver de dev) e a raiz de BUNDLE COMPILADO (export server-side,
650
+ * `pod-export-executor.cjs` dirige `advance` com `podRootOverride: bundleRoot`). No bundle
651
+ * NÃO existe `agents/<a>.md`: o agente + golden rules + knowledge (na ordem declarada) já
652
+ * foram compilados POR VALOR em `phases/<step>/system.md` (`pod-export-compile.cjs:249-262,298`)
653
+ * e o brief nem é consumido nesse caminho (`pod-export-executor.cjs:241,339`).
654
+ *
655
+ * Marcador POSITIVO do transporte (nunca a AUSÊNCIA de `agents/`, que é justamente o defeito
656
+ * que o gate existe para pegar): `bundle.json` na raiz — escrito só pelo compilador de export
657
+ * (`pod-export-compile.cjs:355`) — mais o diretório `phases/`.
658
+ */
659
+ function isExportBundle(podRoot) {
660
+ return fs.existsSync(path.join(podRoot, 'bundle.json')) && fs.existsSync(path.join(podRoot, 'phases'));
661
+ }
662
+
663
+ /**
664
+ * SF11.2/ADR-021 `DF-SF11.2-1`/`-4` — LEITURA OBRIGATÓRIA da etapa, resolvida a partir do
665
+ * `wf`: `agents/<step.agent>.md` seguido de cada `knowledge/<k>` NA ORDEM DECLARADA. Mesma
666
+ * convenção de path do OUTRO runtime do mesmo `wf` (`pod-export-compile.cjs:213,234`), que já
667
+ * compila esses arquivos POR VALOR e já falha quando um deles não existe em disco — aqui a
668
+ * renderização é POR REFERÊNCIA VERIFICADA (`DF-SF11.2-2`): o motor **não abre nem lê** o
669
+ * conteúdo, só resolve o path, confere existência e exibe.
670
+ *
671
+ * Etapa sem `knowledge:` → lista SÓ o arquivo do agente (o campo é OPCIONAL no schema,
672
+ * `validate-pod-workflow.cjs:451`; retrocompat explícita, `DF-SF11.2-10`). Raiz de BUNDLE de
673
+ * export → a leitura obrigatória da fase é o `phases/<id>/system.md` compilado (ver
674
+ * `isExportBundle`): a MESMA fonte de verdade do `wf`, renderizada POR VALOR (`DF-SF11.2-2`).
675
+ *
676
+ * Confinamento (`DF-SF11.2` / segurança do ADR): todo path é resolvido sob `ctx.podRoot`; o
677
+ * que escapar da raiz do pod é tratado como AUSENTE (fail-closed), nunca exibido no brief.
678
+ *
679
+ * Retorna `{ files: [{ kind, rel, abs }], missing: [string] }` — `missing` traz a descrição
680
+ * legível do que falta (path relativo ao pod + causa), pronta para virar `failures`.
681
+ */
682
+ function requiredReading(ctx, step) {
683
+ const podRoot = path.resolve(ctx.podRoot);
684
+ const declared = isExportBundle(podRoot)
685
+ ? [{ kind: 'phase-system', rel: path.posix.join('phases', String(step.id), 'system.md') }]
686
+ : [
687
+ { kind: 'agent', rel: path.posix.join('agents', `${step.agent}.md`) },
688
+ ...(Array.isArray(step.knowledge) ? step.knowledge : []).map((k) => ({
689
+ kind: 'knowledge',
690
+ rel: path.posix.join('knowledge', String(k)),
691
+ })),
692
+ ];
693
+ const files = [];
694
+ const missing = [];
695
+ for (const d of declared) {
696
+ const abs = path.resolve(podRoot, d.rel);
697
+ const inside = abs === podRoot || abs.startsWith(podRoot + path.sep);
698
+ if (!inside) {
699
+ missing.push(`${d.rel} (leitura obrigatória ${d.kind}: path escapa da raiz do pod ${podRoot})`);
700
+ continue;
701
+ }
702
+ if (!fs.existsSync(abs)) {
703
+ missing.push(`${d.rel} (leitura obrigatória ${d.kind}: inexistente em disco — ${abs})`);
704
+ continue;
705
+ }
706
+ files.push({ kind: d.kind, rel: d.rel, abs });
707
+ }
708
+ return { files, missing };
709
+ }
710
+
711
+ /**
712
+ * SF11.2/ADR-021 `DF-SF11.2-1`/`-6` — seção de leitura obrigatória do brief. Vem ANTES de
713
+ * `## Veto` (na verdade, antes de tudo que não é cabeçalho): a precedência de layout é parte
714
+ * da decisão, não estética — o agravante medido pelo QA (gate SF10.21, finding 2) era a
715
+ * ASSIMETRIA "a resposta que faz passar aparece, a regra que manda recusar não".
716
+ *
717
+ * Honestidade do rótulo (mesma família de `antiPathBriefLines`, ADR-016): o motor declara o
718
+ * ESTATUTO do que injeta. Aqui: os arquivos existem (verificado em disco) e a `## Veto` é
719
+ * verificação POSTERIOR. O ato de LER e OBEDECER continua sendo disciplina do agente — este
720
+ * canal garante alcançabilidade e declaração, não obediência (`DF-SF11.2-3`).
721
+ */
722
+ function requiredReadingBriefLines(files) {
723
+ return [
724
+ '## Leitura obrigatória ANTES de agir (declarada no workflow desta etapa)',
725
+ '_O runner verificou que cada arquivo abaixo existe em disco. Leia-os na ordem, integralmente, antes de qualquer ação. Se algum não abrir, HALTE e reporte._',
726
+ ...files.map((f) => `- ${f.abs}`),
727
+ '_A seção "Veto" abaixo é a verificação POSTERIOR do runner sobre o artefato — não é a especificação da tarefa e não substitui a leitura acima._',
728
+ '',
729
+ ];
730
+ }
731
+
732
+ /**
733
+ * SF11.2/ADR-021 `DF-SF11.2-5` — projeção da `veto.assertion` para o brief: tudo, MENOS
734
+ * `_sources`. `_sources` é metadado de MANUTENÇÃO por inspeção (cita IDs de ADR/story,
735
+ * obrigações de sidecar/smoke, achados de `extractField`) — endereçado a quem mantém o `wf`,
736
+ * nada nele é acionável pelo agente, e ele era 85% do bloco de veto da `digest`.
737
+ *
738
+ * Omissão GENÉRICA por construção: vale para toda assertion de todo pod. Sem classificação
739
+ * por `type`/`field`/`id`, sem lista de exceções, sem nome de pod, sem campo novo no `wf`.
740
+ * Todo o resto (`type`, `field`, `pattern`, `n`, `command`, `args`, `timeout_ms`, `id`,
741
+ * `section`, ...) segue no brief verbatim — o caso honesto não perde nada.
742
+ */
743
+ function vetoAssertionForBrief(a) {
744
+ if (!a || typeof a !== 'object' || Array.isArray(a)) return a;
745
+ const out = {};
746
+ for (const k of Object.keys(a)) {
747
+ if (k === '_sources') continue;
748
+ out[k] = a[k];
749
+ }
750
+ return out;
751
+ }
752
+
753
+ function buildBrief(ctx, stepId) {
754
+ const step = stepById(ctx.doc, stepId);
755
+ const artRel = ctx.state.steps[stepId].artifact || artifactRelFor(stepId);
756
+ const artAbs = path.resolve(ctx.runDir, artRel);
757
+ const tmplAbs = path.resolve(ctx.podRoot, step.template);
758
+ // SF11.2/DF-SF11.2-4: nenhum brief é emitido com referência morta. `advance` já bloqueia
759
+ // antes de chegar aqui; este throw fecha o invariante para QUALQUER outro chamador.
760
+ const rr = requiredReading(ctx, step);
761
+ if (rr.missing.length > 0) {
762
+ throw new Error(`leitura_obrigatoria_ausente na etapa "${stepId}": ${rr.missing.join(' | ')}`);
763
+ }
764
+ const assertions = (step.veto && Array.isArray(step.veto.assertions)) ? step.veto.assertions : [];
765
+ const vetoLines = assertions.map((a, i) => ` ${i + 1}. ${JSON.stringify(vetoAssertionForBrief(a))}`).join('\n');
766
+ const mem = memorySection(ctx); // ADR-003: SOMENTE o consolidado, nunca o log cru.
767
+ return [
768
+ `# Brief — etapa "${stepId}" (agente: ${step.agent})`,
769
+ `Pod: ${ctx.state.pod} · Workflow: ${ctx.state.workflow} · Run: ${ctx.state.run_id}`,
770
+ ctx.state.task_ref ? `Tarefa: ${ctx.state.task_ref}` : null,
771
+ '',
772
+ ...requiredReadingBriefLines(rr.files),
773
+ '## Contrato de saída (obrigatório)',
774
+ `- Escreva o artefato EXATAMENTE em: ${artAbs}`,
775
+ `- Siga o template: ${tmplAbs} (todas as seções/headings do template devem constar).`,
776
+ '',
777
+ '## Veto (o runner verifica em disco; nenhuma passa por confiança no relato)',
778
+ vetoLines || ' (sem asserções declaradas)',
779
+ // SF7.3/ADR-016 — anti_path da etapa: os mecanizados são ENFORÇADOS pelo veto pareado
780
+ // (bloqueiam de verdade); os não-mecanizados são ADVISORY, rotulados explicitamente como
781
+ // NÃO-enforcement (honestidade SF7.6: injeção é sugestão, nunca gate).
782
+ ...antiPathBriefLines(step),
783
+ mem ? '' : null,
784
+ mem, // bloco condicional: omitido inteiramente quando não há memória consolidada real.
785
+ '',
786
+ '## Autoridade e retorno',
787
+ '- Você NÃO marca a etapa concluída; o runner decide pela verificação em disco.',
788
+ '- Sem git push / PR (exclusivo devops).',
789
+ '- Relatório final ≤15 linhas: o que produziu e onde; detalhes vão no artefato.',
790
+ ].filter((l) => l !== null).join('\n');
791
+ }
792
+
793
+ // ---------------------------------------------------------------------------
794
+ // Lifecycle de memória do pod (SF2.2): appending determinístico + injeção do
795
+ // consolidado + cadência de consolidação. Consome o motor SF2.1 via `podMemory`.
796
+ // Contrato inegociável (ADR-003 / AC3-AC4): o agente lê SOMENTE o consolidado
797
+ // curado; NENHUM caminho do brief lê nem referencia o log cru `learnings.jsonl`.
798
+ // ---------------------------------------------------------------------------
799
+
800
+ function memOptsFor(ctx) {
801
+ // A memória vive em `<pod>/memory/` (ADR-003). O runner conhece o dir do pod
802
+ // (ctx.podRoot); `memoryPodDir` permite redirecionar em teste sem tocar produção.
803
+ return { podDir: ctx.memoryPodDir || ctx.podRoot };
804
+ }
805
+
806
+ /**
807
+ * Extrai APENAS o corpo substantivo do consolidado (das seções `##` em diante:
808
+ * CRITICAL pinado + Heurísticas top-N), descartando o título H1 e o cabeçalho de
809
+ * disclaimer que o motor gera. [AUTO-DECISION SF2.2] Esse disclaimer contém a string
810
+ * literal "learnings.jsonl" (pod-memory.cjs renderConsolidated) — injetá-lo cru
811
+ * violaria AC4 ("brief nunca menciona o log cru"). Descartar o cabeçalho preserva a
812
+ * arquitetura (injeta só o consolidado) e satisfaz o teste negativo. É formatação de
813
+ * injeção, não duplicação da lógica de consolidação (que segue inteira na SF2.1).
814
+ */
815
+ function extractInjectable(md) {
816
+ if (!md) return '';
817
+ const lines = md.split(/\r?\n/);
818
+ const start = lines.findIndex((l) => /^##\s+\S/.test(l));
819
+ if (start === -1) return '';
820
+ return lines.slice(start).join('\n').trim();
821
+ }
822
+
823
+ /**
824
+ * Bloco de memória para o brief (AC3/AC4). Retorna a seção pronta SOMENTE se o
825
+ * consolidado existe e tem conteúdo real (ao menos uma heurística/pin, não só
826
+ * cabeçalho); caso contrário `null` (seção omitida, sem placeholder vazio).
827
+ */
828
+ function memorySection(ctx) {
829
+ let md = '';
830
+ try {
831
+ md = podMemory.getConsolidatedForBrief(ctx.state.pod, memOptsFor(ctx));
832
+ } catch {
833
+ return null; // ausência/erro de leitura do consolidado nunca quebra o brief.
834
+ }
835
+ const body = extractInjectable(md);
836
+ // conteúdo real = ao menos um item de heurística/pin (`- **[...]**`). Scaffold
837
+ // vazio ("_Nenhuma heurística consolidada ainda._") não casa → seção omitida.
838
+ if (!/^\s*-\s+\*\*\[/m.test(body)) return null;
839
+ return `## Memória consolidada (aplique estas heurísticas)\n${body}`;
840
+ }
841
+
842
+ /** Conta appends acumulados no log cru (fonte única de verdade da cadência K). */
843
+ function rawAppendCount(ctx) {
844
+ const rawPath = podMemory.memoryPaths(ctx.state.pod, memOptsFor(ctx)).raw;
845
+ if (!fs.existsSync(rawPath)) return 0;
846
+ return fs.readFileSync(rawPath, 'utf8').split('\n').filter((l) => l.trim()).length;
847
+ }
848
+
849
+ /**
850
+ * Appenda UMA entrada de aprendizado ao motor (SF2.1) e decide a cadência de
851
+ * consolidação (AC5/AC6): consolida SEMPRE no fechamento (`forceConsolidate`) e/ou a
852
+ * cada K=20 appends acumulados no log cru — o que ocorrer primeiro. Memória é auxiliar
853
+ * ao enforcement: um erro de I/O de memória é reportado (nunca suprimido) mas NÃO
854
+ * derruba a transição da máquina de estados (o núcleo não-fake-green é intocado).
855
+ */
856
+ function recordLearning(ctx, entry, { forceConsolidate = false } = {}) {
857
+ const memOpts = memOptsFor(ctx);
858
+ const pod = ctx.state.pod;
859
+ try {
860
+ podMemory.append(pod, entry, memOpts);
861
+ const n = rawAppendCount(ctx);
862
+ if (forceConsolidate || (n > 0 && n % podMemory.CONSOLIDATE_K === 0)) {
863
+ // SF11.4/ADR-022 (DF-SF11.4-2): o retorno de consolidate() PARA DE SER DESCARTADO.
864
+ // Poda nunca é silenciosa — mesmo padrão "surface, não suprime" do catch abaixo.
865
+ // O aviso nomeia o que saiu: quem lê o consolidado da próxima rodada precisa saber
866
+ // que uma heurística real deixou de ser injetada (a substância continua no log cru,
867
+ // que o agente nunca lê — só curadoria a recupera).
868
+ //
869
+ // SF11.4b/ADR-023 (DF-SF11.4b-1): a mensagem vem do LEDGER do motor, fonte única —
870
+ // este emissor não monta contador próprio. Era exatamente assim que o número
871
+ // errado nascia: a mensagem falava só do cano do orçamento e omitia o corte por
872
+ // TOP_N, anunciando "1" com 13 heurísticas fora do brief (ADR-023 §C2).
873
+ const res = podMemory.consolidate(pod, memOpts);
874
+ const ledger = podMemory.formatEvictionLedger(pod, res);
875
+ if (ledger) console.error(ledger);
876
+ }
877
+ } catch (e) {
878
+ // Surface, não suprime: aviso em stderr, run continua (memória ≠ enforcement).
879
+ console.error(`[memory] aviso: falha ao registrar aprendizado do pod "${pod}": ${e.message}`);
880
+ }
881
+ }
882
+
883
+ /**
884
+ * Candidato a aprendizado a partir de um bloqueio `verification_failed` (AC1). O
885
+ * `trigger`/`recommendation`/`evidence` são montados DETERMINISTICAMENTE a partir dos
886
+ * campos estruturados de `failures` (retornados por verifyStep/evalVeto) — nunca por
887
+ * julgamento de LLM. `tier: 'normal'` SEMPRE (o runner jamais decide critical: trava ii).
888
+ */
889
+ function buildFailureLearning(ctx, stepId, failures) {
890
+ const list = Array.isArray(failures) ? failures : [];
891
+ return {
892
+ trigger: `etapa "${stepId}" bloqueada por verification_failed`,
893
+ recommendation: `corrigir antes de reexecutar: ${list.join(' | ')}`,
894
+ evidence: { step: stepId, veto: list, causa: `${list.length} verificação(ões) de veto/template falharam` },
895
+ tier: 'normal',
896
+ source_run_id: ctx.state.run_id,
897
+ };
898
+ }
899
+
900
+ /**
901
+ * Registro de padrão-ok a partir de um fechamento limpo (`complete`, AC2). Registra
902
+ * que a sequência completou sem bloqueio. `tier: 'normal'` SEMPRE.
903
+ * SF8.3: marcado NA ORIGEM com `type: ok_pattern` (constante do pod-memory) — vai
904
+ * ao log cru como trilha de auditoria de estabilidade, mas a consolidação o exclui
905
+ * do ranking do top-N (auto-referência não é heurística; ver splitRaw/isOkPattern).
906
+ */
907
+ function buildOkLearning(ctx) {
908
+ const done = ctx.order.filter((id) => ctx.state.steps[id].status === 'done').length;
909
+ return {
910
+ type: podMemory.TYPE_OK_PATTERN,
911
+ trigger: `workflow "${ctx.state.workflow}" completou sem bloqueio`,
912
+ recommendation: `caminho seguido (${done} etapa(s) verificadas em disco) é válido; repetir a sequência`,
913
+ evidence: { steps_done: done, run_id: ctx.state.run_id },
914
+ tier: 'normal',
915
+ source_run_id: ctx.state.run_id,
916
+ };
917
+ }
918
+
919
+ // ---------------------------------------------------------------------------
920
+ // Máquina de estados: transições decididas por CÓDIGO.
921
+ // ---------------------------------------------------------------------------
922
+
923
+ function depsDone(ctx, stepId) {
924
+ const step = stepById(ctx.doc, stepId);
925
+ const deps = Array.isArray(step.depends_on) ? step.depends_on : [];
926
+ return deps.every((d) => ctx.state.steps[d].status === 'done');
927
+ }
928
+
929
+ function firstNonDone(ctx) {
930
+ return ctx.order.find((id) => ctx.state.steps[id].status !== 'done') || null;
931
+ }
932
+
933
+ function dispatchStep(ctx, stepId) {
934
+ const st = ctx.state.steps[stepId];
935
+ st.status = 'running';
936
+ st.started_at = nowISO();
937
+ st.verified_at = null;
938
+ st.artifact = artifactRelFor(stepId);
939
+ fs.mkdirSync(path.resolve(ctx.runDir, 'artifacts'), { recursive: true });
940
+ }
941
+
942
+ function resetStep(ctx, stepId) {
943
+ const st = ctx.state.steps[stepId];
944
+ st.status = 'pending';
945
+ st.started_at = null;
946
+ st.verified_at = null;
947
+ st.artifact = null;
948
+ }
949
+
950
+ /**
951
+ * Avança a máquina por EXATAMENTE uma transição determinística. Nunca "fabrica verde":
952
+ * um passo running só vira done se verifyStep passar. Persiste o state a cada mudança.
953
+ * Retorna { action, step?, brief?, reason?, failures? }.
954
+ * dispatch | blocked | awaiting_approval | complete
955
+ */
956
+ function advance(ctx) {
957
+ const { state, order } = ctx;
958
+
959
+ // (1) Há passo running? → verifica em disco antes de qualquer avanço.
960
+ const running = order.find((id) => state.steps[id].status === 'running');
961
+ if (running) {
962
+ const v = verifyStep(ctx, running);
963
+ if (!v.ok) {
964
+ // AC1: todo bloqueio por verification_failed vira candidato a aprendizado
965
+ // (etapa + veto violado + causa), montado dos campos estruturados de v.failures.
966
+ recordLearning(ctx, buildFailureLearning(ctx, running, v.failures));
967
+ // NÃO avança: a etapa seguinte não roda. O passo permanece running.
968
+ return { action: 'blocked', step: running, reason: 'verification_failed', failures: v.failures };
969
+ }
970
+ state.steps[running].verified_at = nowISO();
971
+ const step = stepById(ctx.doc, running);
972
+ // SF11.10: o gate é condicionado ao RITMO da run, lido do state (nunca re-derivado).
973
+ if (isBlocking(step, state.mode)) {
974
+ state.steps[running].status = 'awaiting_approval';
975
+ writeState(ctx.statePath, state);
976
+ return { action: 'awaiting_approval', step: running };
977
+ }
978
+ state.steps[running].status = 'done';
979
+ writeState(ctx.statePath, state);
980
+ // segue para despachar o próximo (abaixo)
981
+ }
982
+
983
+ // (2) Há passo aguardando aprovação? → só approve/revise destravam.
984
+ const awaiting = order.find((id) => state.steps[id].status === 'awaiting_approval');
985
+ if (awaiting) {
986
+ return { action: 'awaiting_approval', step: awaiting };
987
+ }
988
+
989
+ // (3) Próximo pendente na ordem topológica com dependências satisfeitas.
990
+ const next = order.find((id) => state.steps[id].status === 'pending' && depsDone(ctx, id));
991
+ if (!next) {
992
+ const anyPending = order.some((id) => state.steps[id].status === 'pending');
993
+ if (!anyPending) {
994
+ writeState(ctx.statePath, state);
995
+ // AC2 + AC5: fechamento limpo appenda padrão-ok e SEMPRE dispara consolidate
996
+ // (após o append), consolidando o aprendizado acumulado do run.
997
+ recordLearning(ctx, buildOkLearning(ctx), { forceConsolidate: true });
998
+ return { action: 'complete' };
999
+ }
1000
+ return { action: 'blocked', reason: 'deps_unmet' };
1001
+ }
1002
+ // (3b) SF11.2/ADR-021 `DF-SF11.2-4` — leitura obrigatória da etapa resolvida e VERIFICADA
1003
+ // em disco ANTES do despacho. Referência morta (agente ou knowledge declarado que não
1004
+ // existe, ou path que escapa da raiz do pod) BLOQUEIA: a etapa NÃO é despachada (segue
1005
+ // `pending`) e NENHUM brief é emitido. Paridade com o compilador de export, que já falha
1006
+ // pela mesma causa (`pod-export-compile.cjs:214,236`) sobre o MESMO `wf`.
1007
+ // Não gera aprendizado de memória de propósito: isto é defeito de CONFIGURAÇÃO do pod em
1008
+ // disco (o operador conserta o `wf`/arquivo), não aprendizado de execução do run.
1009
+ const rr = requiredReading(ctx, stepById(ctx.doc, next));
1010
+ if (rr.missing.length > 0) {
1011
+ return { action: 'blocked', step: next, reason: 'leitura_obrigatoria_ausente', failures: rr.missing };
1012
+ }
1013
+ dispatchStep(ctx, next);
1014
+ const brief = buildBrief(ctx, next);
1015
+ writeState(ctx.statePath, state);
1016
+ return { action: 'dispatch', step: next, brief };
1017
+ }
1018
+
1019
+ function approve(ctx, stepId) {
1020
+ const { state, order } = ctx;
1021
+ const target = stepId || order.find((id) => state.steps[id].status === 'awaiting_approval');
1022
+ if (!target || state.steps[target].status !== 'awaiting_approval') {
1023
+ return { action: 'noop', reason: 'nenhuma etapa aguardando aprovação' };
1024
+ }
1025
+ state.steps[target].status = 'done';
1026
+ writeState(ctx.statePath, state);
1027
+ // libera o próximo passo
1028
+ const nxt = advance(ctx);
1029
+ return { action: 'approved', step: target, next: nxt };
1030
+ }
1031
+
1032
+ function revise(ctx, stepId) {
1033
+ const { state, order, doc } = ctx;
1034
+ const target = stepId || order.find((id) => state.steps[id].status === 'awaiting_approval');
1035
+ if (!target) return { action: 'noop', reason: 'nenhuma etapa para revisar' };
1036
+ const step = stepById(doc, target);
1037
+
1038
+ // Fix-loop explícito (SPEC §B2a): revise honra `fix_cycle` devolvendo o alcance
1039
+ // [fix_cycle.to .. target] a pending para reexecução, sob max_iterations.
1040
+ if (step.fix_cycle) {
1041
+ const to = step.fix_cycle.to;
1042
+ const max = step.fix_cycle.max_iterations || Infinity;
1043
+ const st = state.steps[target];
1044
+ st.fix_iterations = (st.fix_iterations || 0) + 1;
1045
+ if (st.fix_iterations > max) {
1046
+ state.steps[target].status = 'failed';
1047
+ writeState(ctx.statePath, state);
1048
+ return { action: 'halt', step: target, reason: `fix_cycle max_iterations (${max}) excedido` };
1049
+ }
1050
+ const iTo = order.indexOf(to);
1051
+ const iTarget = order.indexOf(target);
1052
+ for (let k = iTo; k <= iTarget; k++) {
1053
+ const keep = state.steps[order[k]].fix_iterations; // preserva contador do owner
1054
+ resetStep(ctx, order[k]);
1055
+ if (order[k] === target) state.steps[target].fix_iterations = keep;
1056
+ }
1057
+ writeState(ctx.statePath, state);
1058
+ const nxt = advance(ctx);
1059
+ return { action: 'revised', step: target, loop_to: to, iteration: st.fix_iterations, next: nxt };
1060
+ }
1061
+
1062
+ // Sem fix_cycle: apenas a própria etapa volta a pending para reexecução.
1063
+ resetStep(ctx, target);
1064
+ writeState(ctx.statePath, state);
1065
+ const nxt = advance(ctx);
1066
+ return { action: 'revised', step: target, next: nxt };
1067
+ }
1068
+
1069
+ function abort(ctx) {
1070
+ ctx.state.mode_aborted = true;
1071
+ for (const id of ctx.order) {
1072
+ if (['pending', 'running', 'awaiting_approval'].includes(ctx.state.steps[id].status)) {
1073
+ ctx.state.steps[id].status = 'failed';
1074
+ }
1075
+ }
1076
+ writeState(ctx.statePath, ctx.state);
1077
+ return { action: 'aborted' };
1078
+ }
1079
+
1080
+ function statusSummary(ctx) {
1081
+ const rows = ctx.order.map((id) => ` ${id}: ${ctx.state.steps[id].status}`);
1082
+ const next = firstNonDone(ctx);
1083
+ return [
1084
+ `Run ${ctx.state.run_id} · ${ctx.state.pod}/${ctx.state.workflow} · mode=${ctx.state.mode}`,
1085
+ ...rows,
1086
+ `próximo não-done: ${next || '(nenhum — completo)'}`,
1087
+ ].join('\n');
1088
+ }
1089
+
1090
+ // ---------------------------------------------------------------------------
1091
+ // Construção de contexto e start.
1092
+ // ---------------------------------------------------------------------------
1093
+
1094
+ function buildContext({ workflowPath, podRootOverride, statePath, state, memoryPodDir }) {
1095
+ const { doc, podRoot, order } = loadWorkflow(workflowPath, podRootOverride);
1096
+ return {
1097
+ doc,
1098
+ podRoot,
1099
+ order,
1100
+ statePath,
1101
+ runDir: path.dirname(statePath),
1102
+ workflowPath,
1103
+ state,
1104
+ // Dir do pod p/ o lifecycle de memória (SF2.2). Precedência (SF9.2 CONCERNS-1):
1105
+ // (1) override explícito; (2) roteamento GRAVADO no state do run (`memory_pod_dir`,
1106
+ // escrito no start — o operador que dirige next/approve o honra sem re-derivar);
1107
+ // (3) default = raiz do pod (produção grava em `<pod>/memory/`). Ausente = retrocompat.
1108
+ memoryPodDir: memoryPodDir || (state && state.memory_pod_dir) || podRoot,
1109
+ };
1110
+ }
1111
+
1112
+ /**
1113
+ * start: cria o state.json (schema exato SPEC §B1, todos os passos pending) e faz o
1114
+ * primeiro dispatch. Retorna { ctx, result }.
1115
+ */
1116
+ function startRun(opts) {
1117
+ const {
1118
+ workflowPath,
1119
+ podRootOverride,
1120
+ pod,
1121
+ workflow,
1122
+ mode = 'interactive',
1123
+ taskRef = null,
1124
+ runsRoot,
1125
+ runId = makeRunId(),
1126
+ date,
1127
+ } = opts;
1128
+ // SF11.10/ADR-024 `DF-SF11.10-5` — `mode` deixa de ser rótulo inerte (emenda ao ADR-020
1129
+ // `DF-SF10.21-12`) e passa a ser CONJUNTO FECHADO, validado aqui, no único ponto de entrada
1130
+ // que o cria. Sem este fechamento, `--mode YOLO` (maiúscula) não casaria `skip_modes` e
1131
+ // travaria a rodada autônoma em `awaiting_approval` esperando um humano que a proibição do
1132
+ // ADR-020 impede o comando de substituir — deadlock obscuro. Aqui vira erro imediato e
1133
+ // legível. Breaking change DECLARADO: antes qualquer string era aceita.
1134
+ if (!RUN_MODES.includes(mode)) {
1135
+ throw new Error(`modo de run inválido: ${JSON.stringify(mode)} — valores aceitos: ${RUN_MODES.join(', ')} ` +
1136
+ '(o modo agora decide quais checkpoints bloqueiam, via checkpoint.skip_modes; um rótulo livre ' +
1137
+ 'travaria a run num gate que ninguém pode destravar)');
1138
+ }
1139
+ const rid = opts.runId || makeRunId(date);
1140
+ const { doc, podRoot, order } = loadWorkflow(workflowPath, podRootOverride);
1141
+ const statePath = statePathFor(runsRoot, pod, workflow, rid);
1142
+
1143
+ const steps = {};
1144
+ for (const step of doc.steps) {
1145
+ steps[step.id] = {
1146
+ status: 'pending',
1147
+ agent: step.agent,
1148
+ artifact: null,
1149
+ template: step.template,
1150
+ // SF11.10/DF-SF11.10-2: materialização HONESTA. Numa run que pula o gate, isto grava
1151
+ // `null` — a auditoria diz "esta rodada não teve gate humano aqui", que é a verdade.
1152
+ // Auto-aprovar escreveria a mentira oposta (done por veredito humano inexistente).
1153
+ checkpoint: isBlocking(step, mode) ? 'blocking' : null,
1154
+ started_at: null,
1155
+ verified_at: null,
1156
+ };
1157
+ }
1158
+ const state = {
1159
+ pod,
1160
+ workflow,
1161
+ run_id: rid,
1162
+ mode,
1163
+ task_ref: taskRef,
1164
+ // SF9.2 CONCERNS-1: grava o ROTEAMENTO de memória do run UMA vez, no start. Todo comando
1165
+ // subsequente (next/approve/revise via openRun) o HONRA lendo do state — nunca re-deriva
1166
+ // por default. Sem isso, o start do pipeline isolava em tmp mas o `next` do operador
1167
+ // escrevia no memory REAL do pod (contaminação ativa). Ausente ⇒ retrocompat (podRoot).
1168
+ ...(opts.memoryPodDir ? { memory_pod_dir: opts.memoryPodDir } : {}),
1169
+ steps,
1170
+ updated_at: nowISO(),
1171
+ };
1172
+ writeState(statePath, state);
1173
+
1174
+ const ctx = {
1175
+ doc, podRoot, order, statePath, runDir: path.dirname(statePath), workflowPath, state,
1176
+ memoryPodDir: opts.memoryPodDir || podRoot,
1177
+ };
1178
+ const result = advance(ctx); // primeiro dispatch
1179
+ return { ctx, result };
1180
+ }
1181
+
1182
+ /** Localiza o state.json de um run-id sob runsRoot (`.power-claude/pod-runs/**`). */
1183
+ function findStateByRunId(runsRoot, runId) {
1184
+ const stack = [runsRoot];
1185
+ while (stack.length > 0) {
1186
+ const dir = stack.pop();
1187
+ if (!fs.existsSync(dir)) continue;
1188
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
1189
+ const p = path.join(dir, entry.name);
1190
+ if (entry.isDirectory()) {
1191
+ if (entry.name === runId && fs.existsSync(path.join(p, 'state.json'))) {
1192
+ return path.join(p, 'state.json');
1193
+ }
1194
+ stack.push(p);
1195
+ }
1196
+ }
1197
+ }
1198
+ return null;
1199
+ }
1200
+
1201
+ /**
1202
+ * resume/openRun: reconstrói o ctx a partir de um state.json em disco. SEMPRE lê o
1203
+ * state primeiro (Sequence Lock / Risco 4). O workflowPath é derivado ou informado.
1204
+ */
1205
+ function openRun({ statePath, workflowPath, podRootOverride, memoryPodDir }) {
1206
+ const state = readState(statePath);
1207
+ return buildContext({ workflowPath, podRootOverride, statePath, state, memoryPodDir });
1208
+ }
1209
+
1210
+ // ---------------------------------------------------------------------------
1211
+ // CLI (a skill invoca; a suíte própria importa as funções acima).
1212
+ // ---------------------------------------------------------------------------
1213
+
1214
+ function argVal(args, flag) {
1215
+ const i = args.indexOf(flag);
1216
+ return i >= 0 ? args[i + 1] : null;
1217
+ }
1218
+
1219
+ function deriveWorkflowPath(args, pod, workflow, runsRoot, statePath) {
1220
+ const explicit = argVal(args, '--workflow-file');
1221
+ if (explicit) return explicit;
1222
+ const podRoot = argVal(args, '--pod-root');
1223
+ if (podRoot && workflow) return path.join(podRoot, 'workflows', `${workflow}.yaml`);
1224
+ throw new Error('informe --workflow-file <path> (ou --pod-root <dir> + workflow)');
1225
+ }
1226
+
1227
+ function main() {
1228
+ const args = process.argv.slice(2);
1229
+ const cmd = args[0];
1230
+ const runsRoot = argVal(args, '--runs-root') || path.join(process.cwd(), '.power-claude', 'pod-runs');
1231
+ const podRootOverride = argVal(args, '--pod-root');
1232
+
1233
+ try {
1234
+ if (cmd === 'start') {
1235
+ const pod = args[1];
1236
+ const workflow = args[2];
1237
+ const taskRef = args[3] && !args[3].startsWith('--') ? args[3] : null;
1238
+ const workflowPath = deriveWorkflowPath(args, pod, workflow);
1239
+ const { ctx, result } = startRun({
1240
+ workflowPath,
1241
+ podRootOverride,
1242
+ pod,
1243
+ workflow,
1244
+ mode: argVal(args, '--mode') || 'interactive',
1245
+ taskRef: argVal(args, '--task-ref') || taskRef,
1246
+ runsRoot,
1247
+ runId: argVal(args, '--run-id'),
1248
+ });
1249
+ console.log(`[start] run ${ctx.state.run_id} → ${ctx.statePath}`);
1250
+ printResult(result);
1251
+ process.exit(0);
1252
+ }
1253
+
1254
+ if (['resume', 'next', 'status', 'approve', 'revise', 'abort'].includes(cmd)) {
1255
+ let statePath = argVal(args, '--state');
1256
+ if (!statePath && cmd === 'resume') {
1257
+ const runId = args[1];
1258
+ statePath = findStateByRunId(runsRoot, runId);
1259
+ if (!statePath) throw new Error(`run-id "${runId}" não encontrado sob ${runsRoot}`);
1260
+ }
1261
+ if (!statePath) throw new Error(`comando "${cmd}" exige --state <path> (ou resume <run-id>)`);
1262
+ const workflowPath = deriveWorkflowPath(args);
1263
+ const ctx = openRun({ statePath, workflowPath, podRootOverride });
1264
+
1265
+ if (cmd === 'status') {
1266
+ console.log(statusSummary(ctx));
1267
+ process.exit(0);
1268
+ }
1269
+ if (cmd === 'abort') {
1270
+ printResult(abort(ctx));
1271
+ process.exit(0);
1272
+ }
1273
+ if (cmd === 'approve') {
1274
+ printResult(approve(ctx, argVal(args, '--step')));
1275
+ process.exit(0);
1276
+ }
1277
+ if (cmd === 'revise') {
1278
+ printResult(revise(ctx, argVal(args, '--step')));
1279
+ process.exit(0);
1280
+ }
1281
+ // resume | next → uma transição
1282
+ printResult(advance(ctx));
1283
+ process.exit(0);
1284
+ }
1285
+
1286
+ console.error('Uso: pc-run-pod.cjs <start|resume|next|status|approve|revise|abort> [args]');
1287
+ console.error(' start <pod> <workflow> [task-ref] --workflow-file <wf.yaml> [--pod-root d] [--mode m] [--run-id id] [--runs-root d]');
1288
+ console.error(' next|status|approve|revise|abort --state <state.json> --workflow-file <wf.yaml> [--pod-root d] [--step id]');
1289
+ console.error(' resume <run-id> --workflow-file <wf.yaml> [--pod-root d] [--runs-root d]');
1290
+ process.exit(2);
1291
+ } catch (e) {
1292
+ console.error(`[erro] ${e.message}`);
1293
+ process.exit(1);
1294
+ }
1295
+ }
1296
+
1297
+ function printResult(r) {
1298
+ console.log(`[${r.action}]${r.step ? ` etapa=${r.step}` : ''}${r.reason ? ` motivo=${r.reason}` : ''}`);
1299
+ if (r.failures && r.failures.length) {
1300
+ for (const f of r.failures) console.log(` - ${f}`);
1301
+ }
1302
+ if (r.brief) console.log(`\n--- BRIEF ---\n${r.brief}\n--- /BRIEF ---`);
1303
+ }
1304
+
1305
+ if (require.main === module) {
1306
+ main();
1307
+ }
1308
+
1309
+ module.exports = {
1310
+ makeRunId,
1311
+ runDirFor,
1312
+ statePathFor,
1313
+ readState,
1314
+ writeState,
1315
+ validateStateSchema,
1316
+ loadWorkflow,
1317
+ capabilityGaps,
1318
+ annotateCapabilitySkew,
1319
+ correctionCommand,
1320
+ stepById,
1321
+ isBlocking,
1322
+ RUN_MODES, // SF11.10 — re-exportado do validador (fonte única) para consumidores do motor.
1323
+ extractHeadings,
1324
+ extractField,
1325
+ countListItemsInSection,
1326
+ evalVeto,
1327
+ extractFencedBlock,
1328
+ checkTemplateConformance,
1329
+ verifyStep,
1330
+ buildBrief,
1331
+ antiPathBriefLines,
1332
+ requiredReading,
1333
+ requiredReadingBriefLines,
1334
+ vetoAssertionForBrief,
1335
+ memorySection,
1336
+ extractInjectable,
1337
+ recordLearning,
1338
+ buildFailureLearning,
1339
+ buildOkLearning,
1340
+ rawAppendCount,
1341
+ artifactRelFor,
1342
+ depsDone,
1343
+ firstNonDone,
1344
+ dispatchStep,
1345
+ resetStep,
1346
+ advance,
1347
+ approve,
1348
+ revise,
1349
+ abort,
1350
+ statusSummary,
1351
+ buildContext,
1352
+ startRun,
1353
+ findStateByRunId,
1354
+ openRun,
1355
+ };