@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.
- package/LICENSE +21 -0
- package/README.md +121 -0
- package/bin/cli.js +124 -0
- package/installer/__tests__/doctor-ready-oracles.test.cjs +140 -0
- package/installer/__tests__/merge-settings.test.cjs +330 -0
- package/installer/adopt.js +161 -0
- package/installer/copy.js +203 -0
- package/installer/doctor.js +266 -0
- package/installer/install.js +155 -0
- package/installer/manifest.js +102 -0
- package/installer/motor-scripts.js +50 -0
- package/installer/motor-scripts.json +39 -0
- package/installer/uninstall.js +99 -0
- package/installer/update.js +376 -0
- package/installer/util.js +113 -0
- package/installer/wizard.js +64 -0
- package/package.json +26 -0
- package/scripts/__fixtures__/anti-path-demo/agents/ap-writer.md +13 -0
- package/scripts/__fixtures__/anti-path-demo/templates/write-tmpl.md +7 -0
- package/scripts/__fixtures__/anti-path-demo/workflows/wf-ap.smoke.json +10 -0
- package/scripts/__fixtures__/anti-path-demo/workflows/wf-ap.yaml +25 -0
- package/scripts/__fixtures__/assess-tier/retrofit-leve/pod/agents/content-chief.md +1 -0
- package/scripts/__fixtures__/assess-tier/retrofit-leve/pod/workflows/wf-criacao-redteam.yaml +3 -0
- package/scripts/__fixtures__/assess-tier/retrofit-leve/pod-spec.json +13 -0
- package/scripts/__fixtures__/fidelity/fake-green-average-in-band.yaml +21 -0
- package/scripts/__fixtures__/fidelity/fake-green-average.yaml +18 -0
- package/scripts/__fixtures__/fidelity/fake-green-denominator.yaml +19 -0
- package/scripts/__fixtures__/fidelity/fake-green-missing-evidence.yaml +18 -0
- package/scripts/__fixtures__/fidelity/fake-green-threshold.yaml +18 -0
- package/scripts/__fixtures__/fidelity/fake-green-weight.yaml +18 -0
- package/scripts/__fixtures__/fidelity/golden.yaml +31 -0
- package/scripts/__fixtures__/fidelity/trindade-incoherent.yaml +15 -0
- package/scripts/__fixtures__/forge-canonical/pod-spec-example.json +37 -0
- package/scripts/__fixtures__/migrate-squad/authored/agents/offer-engineer.md +17 -0
- package/scripts/__fixtures__/migrate-squad/authored/knowledge/no-source.md +7 -0
- package/scripts/__fixtures__/migrate-squad/authored/knowledge/value-stack-method.md +8 -0
- package/scripts/__fixtures__/migrate-squad/authored/specs/wf-sample.smoke.json +24 -0
- package/scripts/__fixtures__/migrate-squad/authored/specs/wf-sample.spec.json +45 -0
- package/scripts/__fixtures__/migrate-squad/legacy-squad/HEADLINE.md +3 -0
- package/scripts/__fixtures__/migrate-squad/legacy-squad/README.md +4 -0
- package/scripts/__fixtures__/migrate-squad/legacy-squad/agents/hormozi-offer.md +14 -0
- package/scripts/__fixtures__/migrate-squad/legacy-squad/checklists/dead-redundant.md +4 -0
- package/scripts/__fixtures__/migrate-squad/legacy-squad/checklists/method-guide.md +6 -0
- package/scripts/__fixtures__/migrate-squad/legacy-squad/checklists/veto-screen.md +4 -0
- package/scripts/__fixtures__/migrate-squad/legacy-squad/config/config.yaml +2 -0
- package/scripts/__fixtures__/migrate-squad/legacy-squad/data/case-library.md +3 -0
- package/scripts/__fixtures__/migrate-squad/legacy-squad/scripts/validate-mvn.py +16 -0
- package/scripts/__fixtures__/migrate-squad/legacy-squad/sops/sop-sample.md +3 -0
- package/scripts/__fixtures__/migrate-squad/legacy-squad/squad-io.yaml +14 -0
- package/scripts/__fixtures__/migrate-squad/legacy-squad/tasks/gather-context.md +7 -0
- package/scripts/__fixtures__/migrate-squad/legacy-squad/templates/output-tmpl.md +5 -0
- package/scripts/__fixtures__/migrate-squad/legacy-squad/workflows/wf-sample.yaml +24 -0
- package/scripts/__fixtures__/pipeline/pipelines/pipeline-checkpoint.yaml +21 -0
- package/scripts/__fixtures__/pipeline/pipelines/pipeline-demo.yaml +22 -0
- package/scripts/__fixtures__/pipeline/pods/pipe-a/agents/maker.md +13 -0
- package/scripts/__fixtures__/pipeline/pods/pipe-a/memory/learnings.consolidated.md +8 -0
- package/scripts/__fixtures__/pipeline/pods/pipe-a/memory/learnings.jsonl +83 -0
- package/scripts/__fixtures__/pipeline/pods/pipe-a/templates/make-tmpl.md +8 -0
- package/scripts/__fixtures__/pipeline/pods/pipe-a/workflows/wf-a.yaml +16 -0
- package/scripts/__fixtures__/pipeline/pods/pipe-b/agents/user.md +13 -0
- package/scripts/__fixtures__/pipeline/pods/pipe-b/memory/learnings.consolidated.md +8 -0
- package/scripts/__fixtures__/pipeline/pods/pipe-b/memory/learnings.jsonl +14 -0
- package/scripts/__fixtures__/pipeline/pods/pipe-b/templates/use-tmpl.md +6 -0
- package/scripts/__fixtures__/pipeline/pods/pipe-b/workflows/wf-b.yaml +16 -0
- package/scripts/__fixtures__/pod-run/agents/forge-chief.md +13 -0
- package/scripts/__fixtures__/pod-run/agents/pod-critic.md +13 -0
- package/scripts/__fixtures__/pod-run/templates/build-tmpl.md +8 -0
- package/scripts/__fixtures__/pod-run/templates/gate-tmpl.md +6 -0
- package/scripts/__fixtures__/pod-run/workflows/wf-min.yaml +43 -0
- package/scripts/__fixtures__/pod-run/workflows/wf-skip-modes.yaml +50 -0
- package/scripts/__fixtures__/pod-workflow/invalid-checkpoint-all-skippable.yaml +30 -0
- package/scripts/__fixtures__/pod-workflow/invalid-checkpoint-criteria-escalar-de-bloco.yaml +40 -0
- package/scripts/__fixtures__/pod-workflow/invalid-checkpoint-skip-mode-desconhecido.yaml +30 -0
- package/scripts/__fixtures__/pod-workflow/invalid-checkpoint-unknown-key.yaml +29 -0
- package/scripts/__fixtures__/pod-workflow/invalid-cycle.yaml +26 -0
- package/scripts/__fixtures__/pod-workflow/invalid-fix-loop-backedge.yaml +29 -0
- package/scripts/__fixtures__/pod-workflow/invalid-fix-loop-forward.yaml +29 -0
- package/scripts/__fixtures__/pod-workflow/invalid-handoff-open.yaml +27 -0
- package/scripts/__fixtures__/pod-workflow/invalid-knowledge-shape.yaml +18 -0
- package/scripts/__fixtures__/pod-workflow/invalid-missing-agent.yaml +15 -0
- package/scripts/__fixtures__/pod-workflow/invalid-missing-outputs.yaml +15 -0
- package/scripts/__fixtures__/pod-workflow/invalid-missing-template.yaml +15 -0
- package/scripts/__fixtures__/pod-workflow/invalid-missing-veto.yaml +13 -0
- package/scripts/__fixtures__/pod-workflow/invalid-template-not-on-disk.yaml +16 -0
- package/scripts/__fixtures__/pod-workflow/invalid-veto-prose.yaml +17 -0
- package/scripts/__fixtures__/pod-workflow/knowledge/a.md +4 -0
- package/scripts/__fixtures__/pod-workflow/knowledge/b.md +4 -0
- package/scripts/__fixtures__/pod-workflow/knowledge/c.md +4 -0
- package/scripts/__fixtures__/pod-workflow/templates/step-tmpl.md +7 -0
- package/scripts/__fixtures__/pod-workflow/valid-checkpoint-skip-modes.yaml +29 -0
- package/scripts/__fixtures__/pod-workflow/valid-fix-loop.yaml +30 -0
- package/scripts/__fixtures__/pod-workflow/valid-knowledge.yaml +30 -0
- package/scripts/__fixtures__/pod-workflow/valid-veto-always-true.yaml +20 -0
- package/scripts/__fixtures__/pod-workflow/valid.yaml +27 -0
- package/scripts/__fixtures__/smoke-canary/agents/canary-reporter.md +13 -0
- package/scripts/__fixtures__/smoke-canary/agents/canary-researcher.md +13 -0
- package/scripts/__fixtures__/smoke-canary/agents/canary-synth.md +13 -0
- package/scripts/__fixtures__/smoke-canary/memory/learnings.consolidated.md +10 -0
- package/scripts/__fixtures__/smoke-canary/memory/learnings.jsonl +448 -0
- package/scripts/__fixtures__/smoke-canary/templates/report-tmpl.md +9 -0
- package/scripts/__fixtures__/smoke-canary/templates/research-tmpl.md +7 -0
- package/scripts/__fixtures__/smoke-canary/templates/synth-tmpl.md +9 -0
- package/scripts/__fixtures__/smoke-canary/workflows/wf-canary.smoke.json +11 -0
- package/scripts/__fixtures__/smoke-canary/workflows/wf-canary.yaml +53 -0
- package/scripts/__fixtures__/source-audit/fake-green-bronze-unsignaled.yaml +10 -0
- package/scripts/__fixtures__/source-audit/fake-green-counts.yaml +9 -0
- package/scripts/__fixtures__/source-audit/fake-green-missing-evidence.yaml +9 -0
- package/scripts/__fixtures__/source-audit/fake-green-unclassified.yaml +10 -0
- package/scripts/__fixtures__/source-audit/fake-green-verdict.yaml +10 -0
- package/scripts/__fixtures__/source-audit/golden-blocked.yaml +10 -0
- package/scripts/__fixtures__/source-audit/golden.yaml +11 -0
- package/scripts/__fixtures__/sweep-forms/agents/forms-count.md +13 -0
- package/scripts/__fixtures__/sweep-forms/agents/forms-field.md +13 -0
- package/scripts/__fixtures__/sweep-forms/agents/forms-fileref.md +13 -0
- package/scripts/__fixtures__/sweep-forms/agents/forms-json.md +13 -0
- package/scripts/__fixtures__/sweep-forms/agents/forms-section.md +13 -0
- package/scripts/__fixtures__/sweep-forms/specs/wf-forms.spec.json +5 -0
- package/scripts/__fixtures__/sweep-forms/templates/t_count.md +5 -0
- package/scripts/__fixtures__/sweep-forms/templates/t_field.md +3 -0
- package/scripts/__fixtures__/sweep-forms/templates/t_fileref.md +3 -0
- package/scripts/__fixtures__/sweep-forms/templates/t_json.md +0 -0
- package/scripts/__fixtures__/sweep-forms/templates/t_section.md +3 -0
- package/scripts/__fixtures__/sweep-forms/workflows/wf-forms.smoke.json +13 -0
- package/scripts/__fixtures__/sweep-forms/workflows/wf-forms.yaml +67 -0
- package/scripts/__fixtures__/validate-story-acs-corpus/corpus-1-noop-state-json.fixture.md +18 -0
- package/scripts/__fixtures__/validate-story-acs-corpus/corpus-2-crash-buildworkflowyaml.fixture.md +26 -0
- package/scripts/__fixtures__/validate-story-acs-corpus/corpus-3-nasce-verde-skip-modes.fixture.md +29 -0
- package/scripts/__fixtures__/validate-story-acs-corpus/corpus-4-guarda-sem-irmao.fixture.md +17 -0
- package/scripts/__fixtures__/validate-story-acs-corpus/corpus-5a-diff-stat-sem-guarda.fixture.md +17 -0
- package/scripts/__fixtures__/validate-story-acs-corpus/corpus-5b-test-f-sobre-diretorio.fixture.md +20 -0
- package/scripts/__fixtures__/veto-checks/copy-clean.md +4 -0
- package/scripts/__fixtures__/veto-checks/copy-dashes.md +4 -0
- package/scripts/__fixtures__/veto-checks/n8n-bad.md +18 -0
- package/scripts/__fixtures__/veto-checks/n8n-ok.md +22 -0
- package/scripts/__fixtures__/veto-checks/page-bad.html +7 -0
- package/scripts/__fixtures__/veto-checks/phash/copy.png +0 -0
- package/scripts/__fixtures__/veto-checks/phash/distinct.png +0 -0
- package/scripts/__fixtures__/veto-checks/phash/distinct2.png +0 -0
- package/scripts/__fixtures__/veto-checks/phash/make-fixtures.cjs +203 -0
- package/scripts/__fixtures__/veto-checks/phash/ref.png +0 -0
- package/scripts/__fixtures__/veto-checks/slide-ok.html +15 -0
- package/scripts/__fixtures__/veto-checks/slide-overflow.html +14 -0
- package/scripts/__fixtures__/veto-checks/sweep-pod/agents/vc-checker.md +13 -0
- package/scripts/__fixtures__/veto-checks/sweep-pod/templates/check-tmpl.md +6 -0
- package/scripts/__fixtures__/veto-checks/sweep-pod/workflows/wf-vc.smoke.json +10 -0
- package/scripts/__fixtures__/veto-checks/sweep-pod/workflows/wf-vc.yaml +19 -0
- package/scripts/__fixtures__/voz-do-dono/README.md +23 -0
- package/scripts/__fixtures__/voz-do-dono/dono-escalar.yaml +9 -0
- package/scripts/__fixtures__/voz-do-dono/dono-guia.yaml +10 -0
- package/scripts/__fixtures__/voz-do-dono/peca-3a-pessoa.md +6 -0
- package/scripts/__fixtures__/voz-do-dono/peca-com-lexico-proibido.md +6 -0
- package/scripts/__fixtures__/voz-do-dono/peca-limpa-1a-pessoa.md +6 -0
- package/scripts/__tests__/derive-ready-transitions.test.cjs +269 -0
- package/scripts/__tests__/enforce-cycle-authority.test.cjs +869 -0
- package/scripts/__tests__/enforce-git-push-authority.test.cjs +701 -0
- package/scripts/__tests__/enforce-ready-oracles.test.cjs +745 -0
- package/scripts/__tests__/motor-scripts.test.cjs +339 -0
- package/scripts/__tests__/pc-run-pod-counter.test.cjs +58 -0
- package/scripts/__tests__/pc-run-pod.test.cjs +578 -0
- package/scripts/__tests__/validate-pod-workflow.test.cjs +90 -0
- package/scripts/__tests__/validate-story-acs.test.cjs +607 -0
- package/scripts/anti-path.test.cjs +163 -0
- package/scripts/arena.cjs +514 -0
- package/scripts/arena.test.cjs +213 -0
- package/scripts/brief-leitura-obrigatoria.test.cjs +364 -0
- package/scripts/consolidate-knowledge-runner.test.cjs +249 -0
- package/scripts/default-funcional.test.cjs +153 -0
- package/scripts/derive-ready-transitions.cjs +325 -0
- package/scripts/forge-additive.test.cjs +158 -0
- package/scripts/forge-assess-tier.cjs +205 -0
- package/scripts/forge-assess-tier.test.cjs +114 -0
- package/scripts/forge-canonical.test.cjs +624 -0
- package/scripts/forge-generate.cjs +1303 -0
- package/scripts/forge-harden-learning.cjs +921 -0
- package/scripts/forge-harden-learning.test.cjs +242 -0
- package/scripts/forge-ingest-memory.cjs +136 -0
- package/scripts/forge-upgrade-pod.cjs +368 -0
- package/scripts/forge.test.cjs +609 -0
- package/scripts/harden-knowledge.test.cjs +612 -0
- package/scripts/harness-g5-lite.cjs +243 -0
- package/scripts/harness-g5-lite.test.cjs +134 -0
- package/scripts/hn-carousel-contrato-entrada.test.cjs +96 -0
- package/scripts/migrate-squad-orchestration.test.cjs +146 -0
- package/scripts/migration-manifest-sf3.2.test.cjs +382 -0
- package/scripts/migration-manifest-sf3.3.test.cjs +170 -0
- package/scripts/migration-manifest-sf5.5.test.cjs +189 -0
- package/scripts/migration-manifest.cjs +1146 -0
- package/scripts/migration-manifest.test.cjs +186 -0
- package/scripts/npm-scope-invocation.test.cjs +194 -0
- package/scripts/owner-layer.cjs +442 -0
- package/scripts/owner-layer.test.cjs +358 -0
- package/scripts/pc-run-pipeline.cjs +289 -0
- package/scripts/pc-run-pipeline.test.cjs +256 -0
- package/scripts/pc-run-pod-countgte.test.cjs +66 -0
- package/scripts/pc-run-pod.cjs +1355 -0
- package/scripts/phash-gate.test.cjs +433 -0
- package/scripts/pod-export-compile.cjs +487 -0
- package/scripts/pod-export-compile.test.cjs +312 -0
- package/scripts/pod-export-executor.cjs +678 -0
- package/scripts/pod-export-executor.test.cjs +1341 -0
- package/scripts/pod-export-hardening.test.cjs +531 -0
- package/scripts/pod-export-postprocess.cjs +427 -0
- package/scripts/pod-export-postprocess.test.cjs +558 -0
- package/scripts/pod-export-queue-race.test.cjs +865 -0
- package/scripts/pod-export-release.cjs +473 -0
- package/scripts/pod-export-release.test.cjs +267 -0
- package/scripts/pod-export-service.cjs +1297 -0
- package/scripts/pod-export-service.test.cjs +489 -0
- package/scripts/pod-export-tools.cjs +470 -0
- package/scripts/pod-export-ui.cjs +805 -0
- package/scripts/pod-export-ui.test.cjs +301 -0
- package/scripts/pod-memory-budget.test.cjs +309 -0
- package/scripts/pod-memory-note.cjs +357 -0
- package/scripts/pod-memory-note.test.cjs +452 -0
- package/scripts/pod-memory.cjs +1252 -0
- package/scripts/pod-memory.test.cjs +951 -0
- package/scripts/render-png.test.cjs +307 -0
- package/scripts/smoke-isolation.test.cjs +100 -0
- package/scripts/smoke-pod-workflow.cjs +755 -0
- package/scripts/smoke-sweep.test.cjs +170 -0
- package/scripts/validate-adopt.js +235 -0
- package/scripts/validate-branding.js +84 -0
- package/scripts/validate-budgets.js +140 -0
- package/scripts/validate-fidelity-output.cjs +267 -0
- package/scripts/validate-fidelity-output.test.cjs +101 -0
- package/scripts/validate-install.js +230 -0
- package/scripts/validate-no-clone.cjs +220 -0
- package/scripts/validate-no-clone.test.cjs +77 -0
- package/scripts/validate-pipeline.cjs +145 -0
- package/scripts/validate-pod-workflow.cjs +878 -0
- package/scripts/validate-source-audit.cjs +170 -0
- package/scripts/validate-source-audit.test.cjs +90 -0
- package/scripts/validate-story-acs.cjs +862 -0
- package/scripts/validate-update-scope-motor.js +421 -0
- package/scripts/veto-checks/html-valid.cjs +92 -0
- package/scripts/veto-checks/lexical-check.cjs +62 -0
- package/scripts/veto-checks/phash-gate.cjs +589 -0
- package/scripts/veto-checks/render-gate.cjs +165 -0
- package/scripts/veto-checks/render-png.cjs +358 -0
- package/scripts/veto-checks.test.cjs +219 -0
- package/scripts/watcher-audit.cjs +178 -0
- package/template/.claude/CLAUDE.md +103 -0
- package/template/.claude/agents/pc-analyst.md +100 -0
- package/template/.claude/agents/pc-architect.md +32 -0
- package/template/.claude/agents/pc-dev.md +27 -0
- package/template/.claude/agents/pc-devops.md +45 -0
- package/template/.claude/agents/pc-planner.md +32 -0
- package/template/.claude/agents/pc-po.md +25 -0
- package/template/.claude/agents/pc-qa.md +25 -0
- package/template/.claude/commands/power/academy.md +9 -0
- package/template/.claude/commands/power/automation.md +9 -0
- package/template/.claude/commands/power/content-carousel.md +267 -0
- package/template/.claude/commands/power/content.md +9 -0
- package/template/.claude/commands/power/dev.md +9 -0
- package/template/.claude/commands/power/forge.md +224 -0
- package/template/.claude/commands/power/hormozi.md +221 -0
- package/template/.claude/commands/power/modo.md +57 -0
- package/template/.claude/hooks/README.md +114 -0
- package/template/.claude/hooks/context-budget.cjs +64 -0
- package/template/.claude/hooks/enforce-cycle-authority.cjs +457 -0
- package/template/.claude/hooks/enforce-git-push-authority.cjs +925 -0
- package/template/.claude/hooks/enforce-ready-oracles.cjs +502 -0
- package/template/.claude/rules/authority.md +76 -0
- package/template/.claude/rules/curadoria-de-conhecimento.md +30 -0
- package/template/.claude/rules/model-matrix.md +34 -0
- package/template/.claude/rules/orchestration.md +133 -0
- package/template/.claude/settings.json +50 -0
- package/template/.claude/skills/pc-apply-qa-fixes/SKILL.md +108 -0
- package/template/.claude/skills/pc-apply-qa-fixes/references/fix-loop.md +40 -0
- package/template/.claude/skills/pc-apply-qa-fixes/references/fix-request-template.md +70 -0
- package/template/.claude/skills/pc-arena/SKILL.md +96 -0
- package/template/.claude/skills/pc-close-story/SKILL.md +105 -0
- package/template/.claude/skills/pc-commit/SKILL.md +108 -0
- package/template/.claude/skills/pc-develop-story/SKILL.md +116 -0
- package/template/.claude/skills/pc-full-cycle/SKILL.md +150 -0
- package/template/.claude/skills/pc-pipeline/SKILL.md +63 -0
- package/template/.claude/skills/pc-pipeline/references/gate-templates.md +46 -0
- package/template/.claude/skills/pc-review-story/SKILL.md +129 -0
- package/template/.claude/skills/pc-review-story/references/false-positive-detection.md +50 -0
- package/template/.claude/skills/pc-review-story/references/nfr-assess.md +47 -0
- package/template/.claude/skills/pc-review-story/references/risk-profile.md +75 -0
- package/template/.claude/skills/pc-review-story/references/security-checklist.md +69 -0
- package/template/.claude/skills/pc-review-story/references/test-design.md +37 -0
- package/template/.claude/skills/pc-review-story/references/trace-requirements.md +46 -0
- package/template/.claude/skills/pc-run-pod/SKILL.md +123 -0
- package/template/.claude/skills/pc-validate-story/SKILL.md +114 -0
- package/template/.claude/skills/pc-wave/SKILL.md +133 -0
- package/template/.env.example +10 -0
- package/template/knowledge/analyst/brainstorming.md +40 -0
- package/template/knowledge/analyst/brief-skeleton.md +32 -0
- package/template/knowledge/analyst/competitor-skeleton.md +39 -0
- package/template/knowledge/analyst/elicitation-menu.md +49 -0
- package/template/knowledge/analyst/market-research-skeleton.md +36 -0
- package/template/knowledge/analyst/research-prompt-structure.md +48 -0
- package/template/knowledge/analyst/roi-skeleton.md +51 -0
- package/template/knowledge/voice-dna/README.md +99 -0
- package/template/knowledge/voice-dna/voice-dna.example.yaml +157 -0
- package/template/pods/README.md +64 -0
- package/template/pods/academy/README.md +37 -0
- package/template/pods/academy/agents/academy-chief.md +27 -0
- package/template/pods/academy/examples/palestra.example.md +142 -0
- package/template/pods/academy/pod-spec.json +44 -0
- package/template/pods/academy/templates/materiais-tmpl.md +17 -0
- package/template/pods/academy/templates/revisao-didatica-tmpl.md +20 -0
- package/template/pods/academy/workflows/wf-materiais-revisao.yaml +30 -0
- package/template/pods/authority-content/README.md +43 -0
- package/template/pods/authority-content/agents/content-chief.md +28 -0
- package/template/pods/authority-content/pod-spec.json +45 -0
- package/template/pods/authority-content/templates/criacao-na-voz-tmpl.md +17 -0
- package/template/pods/authority-content/templates/red-team-factual-tmpl.md +23 -0
- package/template/pods/authority-content/workflows/wf-criacao-redteam.yaml +30 -0
- package/template/pods/automation/README.md +37 -0
- package/template/pods/automation/agents/langgraph-architect.md +30 -0
- package/template/pods/automation/agents/n8n-architect.md +30 -0
- package/template/pods/automation/examples/README.md +46 -0
- package/template/pods/automation/examples/n8n-content-radar.example.json +380 -0
- package/template/pods/automation/knowledge/langgraph-patterns.md +126 -0
- package/template/pods/automation/knowledge/n8n-patterns.md +131 -0
- package/template/pods/automation/memory/README.md +16 -0
- package/template/pods/automation/memory/learnings.consolidated.md +8 -0
- package/template/pods/automation/memory/learnings.jsonl +0 -0
- package/template/pods/automation/pod-spec.json +109 -0
- package/template/pods/automation/specs/wf-langgraph-build.spec.json +107 -0
- package/template/pods/automation/squad-io.yaml +28 -0
- package/template/pods/automation/templates/lg-build-tmpl.md +17 -0
- package/template/pods/automation/templates/lg-design-tmpl.md +23 -0
- package/template/pods/automation/templates/lg-discover-tmpl.md +20 -0
- package/template/pods/automation/templates/lg-handoff-tmpl.md +20 -0
- package/template/pods/automation/templates/lg-test-tmpl.md +20 -0
- package/template/pods/automation/templates/n8n-build-tmpl.md +14 -0
- package/template/pods/automation/templates/n8n-design-tmpl.md +23 -0
- package/template/pods/automation/templates/n8n-discover-tmpl.md +20 -0
- package/template/pods/automation/templates/n8n-handoff-tmpl.md +20 -0
- package/template/pods/automation/templates/n8n-test-tmpl.md +20 -0
- package/template/pods/automation/workflows/wf-langgraph-build.smoke.json +11 -0
- package/template/pods/automation/workflows/wf-langgraph-build.yaml +64 -0
- package/template/pods/automation/workflows/wf-n8n-build.smoke.json +11 -0
- package/template/pods/automation/workflows/wf-n8n-build.yaml +65 -0
- package/template/pods/content-carousel/README.md +91 -0
- package/template/pods/content-carousel/agents/carousel-builder.md +28 -0
- package/template/pods/content-carousel/agents/carousel-visual.md +24 -0
- package/template/pods/content-carousel/agents/hn-curator.md +74 -0
- package/template/pods/content-carousel/config/heuristic-watcher-rubric.yaml +59 -0
- package/template/pods/content-carousel/knowledge/aquisicao-ancora.md +443 -0
- package/template/pods/content-carousel/knowledge/cta-brief.md +25 -0
- package/template/pods/content-carousel/knowledge/dono.example.yaml +45 -0
- package/template/pods/content-carousel/knowledge/identidade-visual-mascote.md +62 -0
- package/template/pods/content-carousel/knowledge/image-generation.md +62 -0
- package/template/pods/content-carousel/knowledge/selecao-de-ancora.md +60 -0
- package/template/pods/content-carousel/knowledge/slides-html.md +56 -0
- package/template/pods/content-carousel/memory/README.md +18 -0
- package/template/pods/content-carousel/memory/learnings.consolidated.md +6 -0
- package/template/pods/content-carousel/memory/learnings.jsonl +0 -0
- package/template/pods/content-carousel/pod-spec.json +100 -0
- package/template/pods/content-carousel/squad-io.yaml +10 -0
- package/template/pods/content-carousel/templates/assembly-tmpl.md +23 -0
- package/template/pods/content-carousel/templates/digest-tmpl.md +29 -0
- package/template/pods/content-carousel/templates/visuals-tmpl.md +20 -0
- package/template/pods/content-carousel/workflows/wf-content-carousel.smoke.json +23 -0
- package/template/pods/content-carousel/workflows/wf-content-carousel.yaml +80 -0
- package/template/pods/dev-ia/README.md +45 -0
- package/template/pods/dev-ia/agents/db-architect.md +33 -0
- package/template/pods/dev-ia/agents/ux-architect.md +29 -0
- package/template/pods/dev-ia/knowledge/db/migration-protocol.md +95 -0
- package/template/pods/dev-ia/knowledge/db/rls-patterns.md +101 -0
- package/template/pods/dev-ia/knowledge/db/schema-review-checklist.md +76 -0
- package/template/pods/dev-ia/memory/README.md +16 -0
- package/template/pods/dev-ia/memory/learnings.consolidated.md +8 -0
- package/template/pods/dev-ia/memory/learnings.jsonl +0 -0
- package/template/pods/dev-ia/pod-spec.json +34 -0
- package/template/pods/dev-ia/templates/schema-review-tmpl.md +23 -0
- package/template/pods/dev-ia/templates/ux-flow-review-tmpl.md +23 -0
- package/template/pods/dev-ia/tools/db-ops/backup-manager.sh +321 -0
- package/template/pods/dev-ia/tools/db-ops/best-practices-auditor.sh +534 -0
- package/template/pods/dev-ia/tools/db-ops/bootstrap-runner.sh +255 -0
- package/template/pods/dev-ia/tools/db-ops/data-loader.sh +616 -0
- package/template/pods/dev-ia/tools/db-ops/detect-database.sh +113 -0
- package/template/pods/dev-ia/tools/db-ops/explain-analyzer.sh +285 -0
- package/template/pods/dev-ia/tools/db-ops/health-checker.sh +323 -0
- package/template/pods/dev-ia/tools/db-ops/migration-runner.sh +308 -0
- package/template/pods/dev-ia/tools/db-ops/postgresql-schema-loader.sh +334 -0
- package/template/pods/dev-ia/tools/db-ops/query-runner.sh +171 -0
- package/template/pods/dev-ia/tools/db-ops/rls-policy-installer.sh +480 -0
- package/template/pods/dev-ia/tools/db-ops/rls-test-suite.sh +172 -0
- package/template/pods/dev-ia/tools/db-ops/schema-context-loader.sh +176 -0
- package/template/pods/hormozi-method/README.md +52 -0
- package/template/pods/hormozi-method/agents/acquisition-strategist.md +33 -0
- package/template/pods/hormozi-method/agents/conversion-copywriter.md +34 -0
- package/template/pods/hormozi-method/agents/monetization-architect.md +35 -0
- package/template/pods/hormozi-method/agents/offer-engineer.md +37 -0
- package/template/pods/hormozi-method/knowledge/core-four-leads.md +51 -0
- package/template/pods/hormozi-method/knowledge/grand-slam-offer.md +66 -0
- package/template/pods/hormozi-method/knowledge/growth-decision.md +38 -0
- package/template/pods/hormozi-method/knowledge/hooks-and-ads.md +56 -0
- package/template/pods/hormozi-method/knowledge/launch-email.md +31 -0
- package/template/pods/hormozi-method/knowledge/money-model.md +46 -0
- package/template/pods/hormozi-method/knowledge/opportunity-screening.md +44 -0
- package/template/pods/hormozi-method/knowledge/pages.md +25 -0
- package/template/pods/hormozi-method/knowledge/pos-lancamento-leads.md +56 -0
- package/template/pods/hormozi-method/knowledge/pricing.md +36 -0
- package/template/pods/hormozi-method/knowledge/retention-ltv.md +37 -0
- package/template/pods/hormozi-method/knowledge/scale-roadmap.md +35 -0
- package/template/pods/hormozi-method/knowledge/swipefiles-curados.md +44 -0
- package/template/pods/hormozi-method/knowledge/value-equation-diagnostic.md +77 -0
- package/template/pods/hormozi-method/memory/README.md +16 -0
- package/template/pods/hormozi-method/memory/learnings.consolidated.md +6 -0
- package/template/pods/hormozi-method/memory/learnings.jsonl +0 -0
- package/template/pods/hormozi-method/pod-spec.json +50 -0
- package/template/pods/hormozi-method/specs/wf-full-launch-sequence.spec.json +149 -0
- package/template/pods/hormozi-method/specs/wf-lead-magnet-pipeline.spec.json +125 -0
- package/template/pods/hormozi-method/specs/wf-money-model-design.spec.json +117 -0
- package/template/pods/hormozi-method/specs/wf-paid-ads-campaign.spec.json +132 -0
- package/template/pods/hormozi-method/specs/wf-scaling-diagnostic.spec.json +121 -0
- package/template/pods/hormozi-method/squad-io.yaml +43 -0
- package/template/pods/hormozi-method/templates/conversion-copywriter-tmpl.md +20 -0
- package/template/pods/hormozi-method/templates/launch-execucao-tmpl.md +17 -0
- package/template/pods/hormozi-method/templates/launch-fechamento-tmpl.md +17 -0
- package/template/pods/hormozi-method/templates/launch-pos-tmpl.md +17 -0
- package/template/pods/hormozi-method/templates/launch-preparacao-tmpl.md +17 -0
- package/template/pods/hormozi-method/templates/lm-copy-tmpl.md +17 -0
- package/template/pods/hormozi-method/templates/lm-distribuicao-tmpl.md +18 -0
- package/template/pods/hormozi-method/templates/lm-estrategia-tmpl.md +17 -0
- package/template/pods/hormozi-method/templates/mm-arquitetura-tmpl.md +18 -0
- package/template/pods/hormozi-method/templates/mm-copy-tmpl.md +17 -0
- package/template/pods/hormozi-method/templates/mm-diagnostico-tmpl.md +14 -0
- package/template/pods/hormozi-method/templates/monetization-architect-tmpl.md +17 -0
- package/template/pods/hormozi-method/templates/offer-engineer-tmpl.md +20 -0
- package/template/pods/hormozi-method/templates/paid-criativos-tmpl.md +19 -0
- package/template/pods/hormozi-method/templates/paid-fundacao-tmpl.md +17 -0
- package/template/pods/hormozi-method/templates/paid-teste-escala-tmpl.md +17 -0
- package/template/pods/hormozi-method/templates/scaling-diagnostico-tmpl.md +17 -0
- package/template/pods/hormozi-method/templates/scaling-priorizacao-tmpl.md +17 -0
- package/template/pods/hormozi-method/templates/scaling-roadmap-tmpl.md +17 -0
- package/template/pods/hormozi-method/workflows/wf-full-launch-sequence.smoke.json +12 -0
- package/template/pods/hormozi-method/workflows/wf-full-launch-sequence.yaml +56 -0
- package/template/pods/hormozi-method/workflows/wf-grand-slam-offer.smoke.json +11 -0
- package/template/pods/hormozi-method/workflows/wf-grand-slam-offer.yaml +45 -0
- package/template/pods/hormozi-method/workflows/wf-lead-magnet-pipeline.smoke.json +9 -0
- package/template/pods/hormozi-method/workflows/wf-lead-magnet-pipeline.yaml +45 -0
- package/template/pods/hormozi-method/workflows/wf-money-model-design.smoke.json +11 -0
- package/template/pods/hormozi-method/workflows/wf-money-model-design.yaml +45 -0
- package/template/pods/hormozi-method/workflows/wf-paid-ads-campaign.smoke.json +9 -0
- package/template/pods/hormozi-method/workflows/wf-paid-ads-campaign.yaml +46 -0
- package/template/pods/hormozi-method/workflows/wf-scaling-diagnostic.smoke.json +11 -0
- package/template/pods/hormozi-method/workflows/wf-scaling-diagnostic.yaml +44 -0
- package/template/pods/squad-forge/README.md +53 -0
- package/template/pods/squad-forge/agents/forge-chief.md +31 -0
- package/template/pods/squad-forge/agents/mind-extractor.md +29 -0
- package/template/pods/squad-forge/agents/pod-critic.md +26 -0
- package/template/pods/squad-forge/config/heuristic-watcher-rubric.yaml +74 -0
- package/template/pods/squad-forge/knowledge/fidelity-rubric.yaml +176 -0
- package/template/pods/squad-forge/knowledge/g6-memory-check.md +17 -0
- package/template/pods/squad-forge/knowledge/memory-ingest.md +16 -0
- package/template/pods/squad-forge/knowledge/source-audit-rubric.yaml +60 -0
- package/template/pods/squad-forge/memory/README.md +16 -0
- package/template/pods/squad-forge/memory/learnings.consolidated.md +8 -0
- package/template/pods/squad-forge/memory/learnings.jsonl +0 -0
- package/template/pods/squad-forge/pod-spec.json +166 -0
- package/template/pods/squad-forge/squad-io.yaml +13 -0
- package/template/pods/squad-forge/tasks/pipeline.md +158 -0
- package/template/pods/squad-forge/templates/discovery-tmpl.md +40 -0
- package/template/pods/squad-forge/templates/generation-tmpl.md +18 -0
- package/template/pods/squad-forge/templates/red-team-tmpl.md +17 -0
- package/template/pods/squad-forge/templates/register-tmpl.md +17 -0
- package/template/pods/squad-forge/templates/workflow-tmpl.yaml +101 -0
- package/template/pods/squad-forge/workflows/wf-forge.smoke.json +12 -0
- 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
|
+
};
|