@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,1303 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
/**
|
|
4
|
+
* forge-generate.cjs — geração proporcional de artefatos de pod (Story SF1.4).
|
|
5
|
+
*
|
|
6
|
+
* Consome o veredito de `forge-assess-tier.cjs` (ADR-004) e gera, DISCO-FIRST
|
|
7
|
+
* (M2 — grava em disco antes de qualquer outra ação, sem rede/vault), os
|
|
8
|
+
* artefatos proporcionais ao tier:
|
|
9
|
+
*
|
|
10
|
+
* SIMPLE → no máximo 1 workflow mínimo + templates das 1–2 saídas.
|
|
11
|
+
* SEM squad-io.yaml, SEM veto-engine obrigatório, SEM memória.
|
|
12
|
+
* STANDARD/COMPLEX→ workflows/wf-<id>.yaml (DAG+checkpoints+veto de máquina, no
|
|
13
|
+
* schema da SF1.1) a partir de templates/workflow-tmpl.yaml,
|
|
14
|
+
* 1 template por etapa, squad-io.yaml (contrato de handoff) e
|
|
15
|
+
* scaffold de memória (learnings.jsonl vazio +
|
|
16
|
+
* learnings.consolidated.md só com cabeçalho — SEM lifecycle).
|
|
17
|
+
*
|
|
18
|
+
* O `wf-<id>.yaml` gerado passa `scripts/validate-pod-workflow.cjs` sem ajuste
|
|
19
|
+
* manual (AC4): DAG linear, cada etapa com agent+template(em disco)+outputs+veto,
|
|
20
|
+
* handoffs fechados (output de cada etapa consumido pela seguinte; última = sink).
|
|
21
|
+
*
|
|
22
|
+
* Uso:
|
|
23
|
+
* node scripts/forge-generate.cjs <pod-spec.json> --out <podDir>
|
|
24
|
+
*
|
|
25
|
+
* Spec: { pod, agents:[{name, step, output}], workflowId?, dna?, critical?,
|
|
26
|
+
* handoffs?, outputs?:[...] }. `agents` ordenados = etapas do DAG.
|
|
27
|
+
*
|
|
28
|
+
* Node >= 18, sem dependências externas.
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
const fs = require('node:fs');
|
|
32
|
+
const path = require('node:path');
|
|
33
|
+
const { classifyTier, TIER_SIMPLE } = require('./forge-assess-tier.cjs');
|
|
34
|
+
const podMemory = require('./pod-memory.cjs');
|
|
35
|
+
// SF12.6/ADR-030 Bloco C/D — a auto-validação (AC25) e o guard por classe (AC11-AC20) INVOCAM
|
|
36
|
+
// o validador estático já existente, nunca duplicam a regra (juiz único: checkCheckpoint).
|
|
37
|
+
const { parseYaml: parseWfYaml, checkCheckpoint } = require('./validate-pod-workflow.cjs');
|
|
38
|
+
|
|
39
|
+
function slug(s) {
|
|
40
|
+
return String(s).toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '');
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// ---------------------------------------------------------------------------
|
|
44
|
+
// MODO ADITIVO (Decisão C.2, SF3.1). Preserva o validado; adiciona o novo; RECUSA
|
|
45
|
+
// (exit 2) colisão de artefato validado fora de owns[]. `owns[]` = paths que a
|
|
46
|
+
// linha corrente É DONA (planejados no open-line do manifest) — regenerados
|
|
47
|
+
// deterministicamente (retomada idempotente), nunca tratados como colisão.
|
|
48
|
+
// ---------------------------------------------------------------------------
|
|
49
|
+
class AdditiveCollisionError extends Error {
|
|
50
|
+
constructor(relPath, kind) {
|
|
51
|
+
super(`colisão aditiva: ${kind} "${relPath}" já existe e NÃO pertence a esta linha (owns[]) — ` +
|
|
52
|
+
`modo aditivo recusa editar/sobrescrever artefato validado (isso é evolução do pod, ciclo próprio).`);
|
|
53
|
+
this.name = 'AdditiveCollisionError';
|
|
54
|
+
this.exitCode = 2;
|
|
55
|
+
this.collision = { path: relPath, kind };
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// ---------------------------------------------------------------------------
|
|
60
|
+
// Forma STANDARD-interface (ADR-004 adendo 2026-07-25, caso de fronteira SF6.2).
|
|
61
|
+
// `interface: true` suprime wf-*.yaml + squad-io.yaml (zero handoff artefato-a-artefato
|
|
62
|
+
// entre os agentes do pod). É VÁLIDA SOMENTE com `handoffs: 0` — com handoff declarado
|
|
63
|
+
// há processo real e o pod deve gerar workflow. Fail-loud (exit 2) contra fabricação.
|
|
64
|
+
// ---------------------------------------------------------------------------
|
|
65
|
+
class InterfaceContractError extends Error {
|
|
66
|
+
constructor(handoffs) {
|
|
67
|
+
super(`pod-interface inválido: interface:true exige handoffs:0 (recebido handoffs:${handoffs}). ` +
|
|
68
|
+
`A forma STANDARD-interface (ADR-004 adendo 2026-07-25) suprime wf/squad-io SOMENTE onde há zero ` +
|
|
69
|
+
`handoff interno; com handoff real o pod tem processo e deve gerar workflow/contrato — não pode declarar-se interface.`);
|
|
70
|
+
this.name = 'InterfaceContractError';
|
|
71
|
+
this.exitCode = 2;
|
|
72
|
+
this.handoffs = handoffs;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** Extrai os handoffs (from/to/artifact) de um texto squad-io.yaml. */
|
|
77
|
+
function extractHandoffTriples(text) {
|
|
78
|
+
const lines = String(text).split(/\r?\n/);
|
|
79
|
+
const triples = [];
|
|
80
|
+
let cur = null;
|
|
81
|
+
for (const line of lines) {
|
|
82
|
+
const from = line.match(/^\s*-\s*from:\s*(.+?)\s*$/);
|
|
83
|
+
if (from) { if (cur) triples.push(cur); cur = { from: from[1], to: '', artifact: '' }; continue; }
|
|
84
|
+
if (cur) {
|
|
85
|
+
const to = line.match(/^\s*to:\s*(.+?)\s*$/);
|
|
86
|
+
const art = line.match(/^\s*artifact:\s*(.+?)\s*$/);
|
|
87
|
+
if (to) cur.to = to[1];
|
|
88
|
+
else if (art) cur.artifact = art[1];
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
if (cur) triples.push(cur);
|
|
92
|
+
return triples.filter((t) => t.from && t.to);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** Quebra um squad-io.yaml em preâmbulo (comentários) + seções de topo (`key:`). */
|
|
96
|
+
function splitTopSections(text) {
|
|
97
|
+
const lines = String(text).split(/\r?\n/);
|
|
98
|
+
const preamble = [];
|
|
99
|
+
const sections = [];
|
|
100
|
+
let cur = null;
|
|
101
|
+
for (const line of lines) {
|
|
102
|
+
const isTop = /^[A-Za-z_][\w-]*:\s*(.*)$/.test(line) && !/^\s/.test(line);
|
|
103
|
+
if (isTop) { if (cur) sections.push(cur); cur = { key: line.match(/^([A-Za-z_][\w-]*):/)[1], lines: [line] }; }
|
|
104
|
+
else if (cur) cur.lines.push(line);
|
|
105
|
+
else preamble.push(line);
|
|
106
|
+
}
|
|
107
|
+
if (cur) sections.push(cur);
|
|
108
|
+
return { preamble, sections };
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* MERGE POR SEÇÃO do squad-io.yaml (Decisão C.2). Preserva TODAS as seções
|
|
113
|
+
* pré-existentes (inclusive customizadas fora do gerador) e faz UNIÃO dos handoffs
|
|
114
|
+
* (dedup por from|to|artifact). Não sobrescreve conteúdo validado — só adiciona.
|
|
115
|
+
*/
|
|
116
|
+
function mergeSquadIo(existing, generated) {
|
|
117
|
+
const ex = splitTopSections(existing);
|
|
118
|
+
const gen = splitTopSections(generated);
|
|
119
|
+
const exKeys = new Set(ex.sections.map((s) => s.key));
|
|
120
|
+
const union = [];
|
|
121
|
+
const seen = new Set();
|
|
122
|
+
for (const h of [...extractHandoffTriples(existing), ...extractHandoffTriples(generated)]) {
|
|
123
|
+
const k = `${h.from}|${h.to}|${h.artifact}`;
|
|
124
|
+
if (!seen.has(k)) { seen.add(k); union.push(h); }
|
|
125
|
+
}
|
|
126
|
+
const handoffLines = ['handoffs:'];
|
|
127
|
+
for (const h of union) handoffLines.push(` - from: ${h.from}`, ` to: ${h.to}`, ` artifact: ${h.artifact}`);
|
|
128
|
+
const out = [];
|
|
129
|
+
if (ex.preamble.some((l) => l.trim())) out.push(...ex.preamble);
|
|
130
|
+
for (const s of ex.sections) {
|
|
131
|
+
if (s.key === 'handoffs') out.push(...handoffLines);
|
|
132
|
+
else out.push(...s.lines);
|
|
133
|
+
}
|
|
134
|
+
for (const s of gen.sections) {
|
|
135
|
+
if (exKeys.has(s.key)) continue;
|
|
136
|
+
if (s.key === 'handoffs') { out.push(...handoffLines); continue; }
|
|
137
|
+
out.push(...s.lines);
|
|
138
|
+
}
|
|
139
|
+
return `${out.join('\n').replace(/\n{3,}/g, '\n\n').replace(/\s*$/, '')}\n`;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/** Escreve arquivo criando os diretórios pais. Disco-first, síncrono. */
|
|
143
|
+
function writeDisk(file, content) {
|
|
144
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
145
|
+
fs.writeFileSync(file, content);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// ---------------------------------------------------------------------------
|
|
149
|
+
// SF12.4a/ADR-030 Bloco O (`DF-SF12.4-11`, Emenda 1) — `opts.specOrigin`, OBRIGATÓRIO,
|
|
150
|
+
// SEM DEFAULT. Substitui a leitura ambígua de `opts.additive` (acerta 0 de 4 chamadores,
|
|
151
|
+
// anti-correlacionado em 2 — ver `*review` da SF12.4a). 3 call sites reais:
|
|
152
|
+
// forge-generate.cjs main()/CLI → 'authored' (incondicional — *new E
|
|
153
|
+
// *migrate-squad F2b, `--additive` NÃO muda isto)
|
|
154
|
+
// forge-upgrade-pod.cjs → 'derived'
|
|
155
|
+
// forge-harden-learning.cjs → 'derived'
|
|
156
|
+
// Sem default: um consumidor novo que esquecer o parâmetro PARA na hora, nomeando a
|
|
157
|
+
// ausência — a mesma doutrina da Decisão 3 (Bloco C) aplicada à própria API do gerador.
|
|
158
|
+
// ---------------------------------------------------------------------------
|
|
159
|
+
const SPEC_ORIGINS = new Set(['authored', 'derived']);
|
|
160
|
+
|
|
161
|
+
class SpecOriginError extends Error {
|
|
162
|
+
constructor(value) {
|
|
163
|
+
super(`generatePod: opts.specOrigin é OBRIGATÓRIO e deve ser "authored" ou "derived" (recebido: ${JSON.stringify(value)}). ` +
|
|
164
|
+
`"authored" = spec autorada fresca (*new, *migrate-squad F2b) — aciona a recusa fail-loud dos campos canônicos (Bloco C). ` +
|
|
165
|
+
`"derived" = spec sintetizada de um pod já em disco (*upgrade-pod, *harden-learning) — preserva o comportamento atual e reporta ` +
|
|
166
|
+
`os campos canônicos ausentes (ADR-030 Emenda 1, DF-SF12.4-11). Sem default: nomeie a escolha.`);
|
|
167
|
+
this.name = 'SpecOriginError';
|
|
168
|
+
this.exitCode = 2;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function validateSpecOrigin(value) {
|
|
173
|
+
if (!SPEC_ORIGINS.has(value)) throw new SpecOriginError(value);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// ---------------------------------------------------------------------------
|
|
177
|
+
// SF12.6/ADR-030 Bloco C (Decisão 7) — guard POR CLASSE de artefato. Escopo:
|
|
178
|
+
// `specOrigin: 'derived'` (regeneração a partir de um pod já em disco — *upgrade-pod,
|
|
179
|
+
// *harden-learning) E o path NÃO pertencer a `opts.owns[]` (harden-learning já usa
|
|
180
|
+
// --additive + owns para regenerar deterministicamente o SEU alvo; essa é uma
|
|
181
|
+
// autoridade de escrita PRÉ-EXISTENTE e mais estreita — o guard novo não a reabre,
|
|
182
|
+
// só protege o que NINGUÉM declarou possuir). Três classes:
|
|
183
|
+
// wf → recusa fail-loud (Decisão 7.1), leniência POR PATH (Decisão 7.2).
|
|
184
|
+
// templates/* → preserva-por-default (reporta em preserved[], nunca lança).
|
|
185
|
+
// squad-io.yaml → sempre-merge (mergeSquadIo, incondicional — ver bloco (3) abaixo).
|
|
186
|
+
// pod-spec.json / memory/ → FORA, explicitamente (AC16) — já são write-once/merge
|
|
187
|
+
// por outros mecanismos pré-existentes (guarda de existência, consolidate()).
|
|
188
|
+
// ---------------------------------------------------------------------------
|
|
189
|
+
class DivergentRegenerationError extends Error {
|
|
190
|
+
constructor(relPath, diskLen, genLen, firstDiffLine) {
|
|
191
|
+
super(
|
|
192
|
+
`recusa fail-loud (ADR-030 Decisão 7.1, guard por classe): "${relPath}" (classe wf — fail-loud) ` +
|
|
193
|
+
`diverge do que a spec regeneraria — bytes disco×gerado: ${diskLen}×${genLen}; 1ª linha divergente: ` +
|
|
194
|
+
`${JSON.stringify(firstDiffLine)}. A causa: a spec está atrás do artefato — alguém editou o wf ` +
|
|
195
|
+
'diretamente sem levar a mudança para pod-spec.json (ADR-030 Emenda 3: o wf é DERIVADO da spec, ' +
|
|
196
|
+
'nunca o contrário). Duas saídas, NESTA ordem: (1) declarar na spec o que falta, para a próxima ' +
|
|
197
|
+
'regeneração já bater — fecha o vão de verdade; (2) se a perda for aceitável, nomeie o path ' +
|
|
198
|
+
`explicitamente: --allow-divergent-overwrite ${relPath} (ADR-030 Decisão 7.2 — a leniência é ` +
|
|
199
|
+
'sempre POR PATH, nunca "sim para tudo").',
|
|
200
|
+
);
|
|
201
|
+
this.name = 'DivergentRegenerationError';
|
|
202
|
+
this.exitCode = 2;
|
|
203
|
+
this.path = relPath;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
class AllowDivergentOverwriteError extends Error {
|
|
208
|
+
constructor(value) {
|
|
209
|
+
super(`generatePod: opts.allowDivergentOverwrite deve ser um array de paths (recebido ` +
|
|
210
|
+
`${JSON.stringify(value)}) — a leniência é SEMPRE por path, nunca "sim para tudo" (ADR-030 ` +
|
|
211
|
+
'Decisão 7.2). Ex.: allowDivergentOverwrite: ["workflows/wf-x.yaml"].');
|
|
212
|
+
this.name = 'AllowDivergentOverwriteError';
|
|
213
|
+
this.exitCode = 2;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/** Forma nua (`true`/qualquer não-array) é RECUSADA (Decisão 7.2). Ausente ⇒ []. */
|
|
218
|
+
function validateAllowDivergentOverwrite(value) {
|
|
219
|
+
if (value === undefined) return [];
|
|
220
|
+
if (!Array.isArray(value)) throw new AllowDivergentOverwriteError(value);
|
|
221
|
+
return value.map(String);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/** 1ª linha em que `a`/`b` divergem (para a mensagem de recusa, Decisão 7.3). */
|
|
225
|
+
function firstDivergentLine(a, b) {
|
|
226
|
+
const la = String(a).split(/\r?\n/);
|
|
227
|
+
const lb = String(b).split(/\r?\n/);
|
|
228
|
+
const n = Math.max(la.length, lb.length);
|
|
229
|
+
for (let i = 0; i < n; i++) {
|
|
230
|
+
if (la[i] !== lb[i]) return lb[i] !== undefined ? lb[i] : la[i];
|
|
231
|
+
}
|
|
232
|
+
return '';
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* SF12.6/ADR-030 Bloco D (AC25/DEBT-SF12.6-E) — generatePod valida a PRÓPRIA saída ANTES
|
|
237
|
+
* de escrever: parseYaml(wfContent) → checkCheckpoint. INVOCA o validador estático já
|
|
238
|
+
* existente (validate-pod-workflow.cjs) — nunca duplica a regra de DF-SF11.10-3. Recusa
|
|
239
|
+
* (não escreve nada) se a saída violaria o invariante do gate humano.
|
|
240
|
+
*/
|
|
241
|
+
class CheckpointInvariantError extends Error {
|
|
242
|
+
constructor(wfRelPath, cause) {
|
|
243
|
+
super(`generatePod: recusa escrever "${wfRelPath}" — a saída violaria o invariante do gate ` +
|
|
244
|
+
'humano (ADR-024 DF-SF11.10-3), auto-validado via checkCheckpoint ANTES de qualquer escrita ' +
|
|
245
|
+
`(ADR-030 Bloco D, DEBT-SF12.6-E): ${cause}`);
|
|
246
|
+
this.name = 'CheckpointInvariantError';
|
|
247
|
+
this.exitCode = 2;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// ---------------------------------------------------------------------------
|
|
252
|
+
// SF12.4a/ADR-030 Bloco C (`DF-SF12.4-3`, Decisão 3) — os 3 padrões DECLARADOS-OU-
|
|
253
|
+
// RECUSADOS (`ritmo`, `contrato_entrada`, `entregavel`, Bloco A/AC2). Três estados,
|
|
254
|
+
// nunca dois: declarado com conteúdo (emite o mecanismo, Blocos E/G/H); recusado —
|
|
255
|
+
// `nao_se_aplica: true` + `razao` + `respondido_por` não-vazios (emite nada, registra a
|
|
256
|
+
// recusa); ausente (recusa fail-loud SOB `specOrigin: 'authored'` — Bloco O/AC48b). A
|
|
257
|
+
// máquina confere FORMA (campo existe? razão não-vazia? atribuição presente?), nunca
|
|
258
|
+
// julga se a razão é boa (fronteira ADR-002, `DEBT-SF12.4-A`).
|
|
259
|
+
// ---------------------------------------------------------------------------
|
|
260
|
+
const CANONICAL_FIELDS = ['ritmo', 'contrato_entrada', 'entregavel'];
|
|
261
|
+
|
|
262
|
+
class CanonicalFieldError extends Error {
|
|
263
|
+
constructor(field, reason) {
|
|
264
|
+
super(`campo canônico "${field}" ${reason} — declare com conteúdo ou registre ` +
|
|
265
|
+
`{ nao_se_aplica: true, razao: "<motivo>", respondido_por: "<quem>" } (ADR-030 Decisão 3, DF-SF12.4-3).`);
|
|
266
|
+
this.name = 'CanonicalFieldError';
|
|
267
|
+
this.exitCode = 2;
|
|
268
|
+
this.field = field;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/** Referência de etapa (AC21/AC24) — mesma disciplina de `anti_path.mechanized_by`. */
|
|
273
|
+
function validateCanonicalStepRef(field, stepId, steps) {
|
|
274
|
+
if (!steps.some((s) => s.id === stepId)) {
|
|
275
|
+
throw new CanonicalFieldError(field, `referencia a etapa "${stepId}" que não existe entre os ids de ` +
|
|
276
|
+
`stepsFromSpec(spec) — ids válidos: ${steps.map((s) => s.id).join(', ') || '(nenhuma etapa)'}`);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Lê e valida (FORMA, não semântica) UM campo canônico da spec — usado sob
|
|
282
|
+
* `specOrigin: 'authored'` (fail-loud, Bloco C/AC7-AC10/AC42-AC44). Retorna
|
|
283
|
+
* `{ mode: 'declared', value }` ou `{ mode: 'refused', razao, respondido_por }`;
|
|
284
|
+
* nunca retorna para o caso "ausente" — lança CanonicalFieldError.
|
|
285
|
+
*/
|
|
286
|
+
function readCanonicalField(spec, field, steps) {
|
|
287
|
+
const raw = spec[field];
|
|
288
|
+
if (raw == null) {
|
|
289
|
+
throw new CanonicalFieldError(field, 'ausente na spec');
|
|
290
|
+
}
|
|
291
|
+
if (typeof raw !== 'object' || Array.isArray(raw)) {
|
|
292
|
+
throw new CanonicalFieldError(field, 'deve ser um objeto — declarado com conteúdo OU recusa (ver AC2)');
|
|
293
|
+
}
|
|
294
|
+
if (raw.nao_se_aplica === true) {
|
|
295
|
+
// AC9 — a recusa TEM forma exigida: razão e atribuição não-vazias. Mensagem
|
|
296
|
+
// DIFERENTE da de "ausente" (AC44) — quem lê o erro sabe qual dos dois corrigir.
|
|
297
|
+
const razaoOk = typeof raw.razao === 'string' && raw.razao.trim() !== '';
|
|
298
|
+
const respOk = typeof raw.respondido_por === 'string' && raw.respondido_por.trim() !== '';
|
|
299
|
+
if (!razaoOk || !respOk) {
|
|
300
|
+
const falta = !razaoOk ? 'razao' : 'respondido_por';
|
|
301
|
+
throw new CanonicalFieldError(field, `tem recusa MALFORMADA — "${falta}" ausente ou vazio ` +
|
|
302
|
+
`(nao_se_aplica exige razao E respondido_por não-vazios)`);
|
|
303
|
+
}
|
|
304
|
+
return { mode: 'refused', razao: raw.razao, respondido_por: raw.respondido_por };
|
|
305
|
+
}
|
|
306
|
+
// Conteúdo declarado — forma exata por campo (AC2).
|
|
307
|
+
if (field === 'ritmo') {
|
|
308
|
+
if (typeof raw.step !== 'string' || raw.step.trim() === ''
|
|
309
|
+
|| !Array.isArray(raw.skip_modes) || raw.skip_modes.length === 0
|
|
310
|
+
|| !raw.skip_modes.every((m) => typeof m === 'string' && m.trim() !== '')) {
|
|
311
|
+
throw new CanonicalFieldError(field, 'tem conteúdo malformado — esperado { step: <id>, skip_modes: [<modo...>] } (≥1 modo)');
|
|
312
|
+
}
|
|
313
|
+
// SF12.6/ADR-030 AC21c — mesma disciplina que contrato_entrada/entregavel já aplicam:
|
|
314
|
+
// referência a etapa inexistente RECUSA, nunca retorna `declared` em silêncio.
|
|
315
|
+
validateCanonicalStepRef(field, raw.step, steps);
|
|
316
|
+
} else if (field === 'contrato_entrada') {
|
|
317
|
+
if (typeof raw.step !== 'string' || raw.step.trim() === ''
|
|
318
|
+
|| !Array.isArray(raw.valores) || raw.valores.length < 2
|
|
319
|
+
|| !raw.valores.every((v) => typeof v === 'string' && v.trim() !== '')) {
|
|
320
|
+
throw new CanonicalFieldError(field, 'tem conteúdo malformado — esperado { step: <id>, valores: [<≥2 strings>] }');
|
|
321
|
+
}
|
|
322
|
+
validateCanonicalStepRef(field, raw.step, steps);
|
|
323
|
+
} else if (field === 'entregavel') {
|
|
324
|
+
if (typeof raw.step !== 'string' || raw.step.trim() === '') {
|
|
325
|
+
throw new CanonicalFieldError(field, 'tem conteúdo malformado — esperado { step: <id> }');
|
|
326
|
+
}
|
|
327
|
+
validateCanonicalStepRef(field, raw.step, steps);
|
|
328
|
+
}
|
|
329
|
+
return { mode: 'declared', value: raw };
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/**
|
|
333
|
+
* Estado dos 3 campos canônicos, best-effort (nunca lança) — usado para persistência
|
|
334
|
+
* (Bloco F/AC18-19), relato do modo `derived` (Bloco O/AC48c) e o anúncio do ritmo
|
|
335
|
+
* (Bloco E/AC15), independente de `specOrigin`.
|
|
336
|
+
*/
|
|
337
|
+
function computeCanonicalStatus(spec) {
|
|
338
|
+
const out = {};
|
|
339
|
+
for (const field of CANONICAL_FIELDS) {
|
|
340
|
+
const raw = spec[field];
|
|
341
|
+
if (raw == null) {
|
|
342
|
+
out[field] = { mode: 'ausente' };
|
|
343
|
+
} else if (raw && typeof raw === 'object' && !Array.isArray(raw) && raw.nao_se_aplica === true) {
|
|
344
|
+
out[field] = { mode: 'refused', razao: raw.razao, respondido_por: raw.respondido_por };
|
|
345
|
+
} else if (raw && typeof raw === 'object' && !Array.isArray(raw)) {
|
|
346
|
+
out[field] = { mode: 'declared', ...raw };
|
|
347
|
+
} else {
|
|
348
|
+
out[field] = { mode: 'malformed', value: raw };
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
return out;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* Template de saída de uma etapa.
|
|
356
|
+
* FORMA = as seções `##` (verificadas por checkTemplateConformance no runner).
|
|
357
|
+
* VETO ORTOGONAL (ADR-005 Decisão A): o template SEMPRE termina com um campo de
|
|
358
|
+
* máquina NÃO-heading — a linha `confianca: <alta|media|baixa>`, cujo placeholder
|
|
359
|
+
* NÃO casa o pattern do veto default (força preenchimento real). Nunca se usa
|
|
360
|
+
* `section_exists` de uma seção do próprio template como veto (seria implicado pela
|
|
361
|
+
* conformidade de forma — a classe degenerada que o piloto expôs).
|
|
362
|
+
*/
|
|
363
|
+
/**
|
|
364
|
+
* Nota de semântica contável do veto `count_gte` (débito #1 do gate SF1.5, finding
|
|
365
|
+
* id:1). O piloto expôs que 2/5 runs formataram uma seção contável como TABELA e o
|
|
366
|
+
* contador (bullets-only, à época) contou 0 → fix evitável. Agora o template GERADO
|
|
367
|
+
* PRESCREVE a forma contável junto à seção-alvo, para o agente de etapa saber, ANTES
|
|
368
|
+
* de escrever, o que conta. Casa a semântica real de countListItemsInSection (SF2.3):
|
|
369
|
+
* bullets, lista numerada e linhas de tabela contam; prosa não.
|
|
370
|
+
*/
|
|
371
|
+
function countGteNote(field) {
|
|
372
|
+
return `<!-- count_gte "${field}": conte itens de lista sob este heading — bullets (\`- \`/\`* \`), ` +
|
|
373
|
+
`linhas numeradas (\`1.\` \`2.\` ...) OU linhas de tabela (\`| ... |\`, o separador \`|---|---|\` NÃO conta). ` +
|
|
374
|
+
`Prosa corrida NÃO conta. -->`;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
/** Índice case-insensitive dos vetos count_gte por campo (mesma regra de contains do contador). */
|
|
378
|
+
function countGteFields(veto) {
|
|
379
|
+
const out = [];
|
|
380
|
+
if (Array.isArray(veto)) {
|
|
381
|
+
for (const a of veto) {
|
|
382
|
+
if (a && a.type === 'count_gte' && a.field != null) out.push({ field: String(a.field), n: a.n });
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
return out;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
/**
|
|
389
|
+
* SF12.4a/ADR-030 Blocos G/H (AC20a/AC23a) — campos de máquina extras dos padrões
|
|
390
|
+
* `contrato_entrada`/`entregavel`, quando declarados. Emitidos ANTES de `confianca`
|
|
391
|
+
* (mesma zona ortogonal fora dos headings — ADR-005 Decisão A). `extraFields`:
|
|
392
|
+
* `[{ field, placeholder }]`.
|
|
393
|
+
*/
|
|
394
|
+
function stepTemplate(stepId, agent, output, sections, veto, extraFields) {
|
|
395
|
+
const secs = Array.isArray(sections) && sections.length ? sections : ['## Contexto', '## Output'];
|
|
396
|
+
const cg = countGteFields(veto);
|
|
397
|
+
const body = secs
|
|
398
|
+
.map((s) => {
|
|
399
|
+
const label = String(s).replace(/^#+\s*/, '').trim();
|
|
400
|
+
// Anexa a nota de forma contável quando a seção casa o field de um count_gte
|
|
401
|
+
// (mesma semântica CONTAINS/case-insensitive do contador do runner).
|
|
402
|
+
const match = cg.find((c) => label.toLowerCase().includes(c.field.toLowerCase())
|
|
403
|
+
|| c.field.toLowerCase().includes(label.toLowerCase()));
|
|
404
|
+
const note = match ? `${countGteNote(match.field)}\n` : '';
|
|
405
|
+
return `${s}\n${note}<!-- preencher: conteúdo de "${label}" -->\n`;
|
|
406
|
+
})
|
|
407
|
+
.join('\n');
|
|
408
|
+
const extras = (Array.isArray(extraFields) && extraFields.length)
|
|
409
|
+
? `${extraFields.map((e) => `${e.field}: <${e.placeholder}>`).join('\n')}\n`
|
|
410
|
+
: '';
|
|
411
|
+
return `# Template de saída — etapa \`${stepId}\` (agente \`${agent}\`, artefato \`${output}\`)
|
|
412
|
+
|
|
413
|
+
> Gerado por forge-generate.cjs (SF1.4 + ADR-005). Preencher com o artefato real da etapa.
|
|
414
|
+
> FORMA (seções \`##\`) verificada por checkTemplateConformance; VETO ORTOGONAL via o
|
|
415
|
+
> campo de máquina \`confianca\` abaixo (fora dos headings — ADR-005 Decisão A).
|
|
416
|
+
|
|
417
|
+
${body}
|
|
418
|
+
<!-- Campo(s) de máquina do(s) padrão(ões) declarado(s) (SF12.4a/ADR-030 Blocos G/H), quando aplicável — NÃO é heading. -->
|
|
419
|
+
${extras}<!-- Campo de máquina do veto default (ADR-005) — NÃO é heading. Troque o placeholder por alta|media|baixa. -->
|
|
420
|
+
confianca: <alta|media|baixa>
|
|
421
|
+
`;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
/** Serializa UMA veto.assertion para o flow-map inline do contrato wf-*.yaml. */
|
|
425
|
+
function serializeAssertion(a) {
|
|
426
|
+
// SF7.2: enc RECURSIVO — arrays viram flow-list `[..]` e objetos viram flow-map `{..}`,
|
|
427
|
+
// para emitir corretamente `command_check.args` (lista) e `json_schema_valid.extract`
|
|
428
|
+
// (mapa). Antes, String(array)="a,b" e String(obj)="[object Object]" corrompiam a emissão.
|
|
429
|
+
const enc = (v) => {
|
|
430
|
+
if (v === null || v === undefined) return 'null';
|
|
431
|
+
if (typeof v === 'number' || typeof v === 'boolean') return String(v);
|
|
432
|
+
if (Array.isArray(v)) return `[${v.map(enc).join(', ')}]`;
|
|
433
|
+
if (typeof v === 'object') {
|
|
434
|
+
return `{ ${Object.entries(v).map(([k, val]) => `${k}: ${enc(val)}`).join(', ')} }`;
|
|
435
|
+
}
|
|
436
|
+
const s = String(v);
|
|
437
|
+
// SF7.6: string PURAMENTE numérica é emitida QUOTADA — sem aspas o parser YAML-lite
|
|
438
|
+
// a releria como number e corromperia o tipo (ex.: command_check.args ["--max","0"]
|
|
439
|
+
// viraria [--max, 0] e o validador estático recusa "args" não-string). Round-trip
|
|
440
|
+
// fiel: string entra, string sai.
|
|
441
|
+
return /^[A-Za-z0-9_]+$/.test(s) && !/^\d+$/.test(s) ? s : JSON.stringify(s);
|
|
442
|
+
};
|
|
443
|
+
const parts = [`type: ${a.type}`];
|
|
444
|
+
for (const [k, v] of Object.entries(a)) {
|
|
445
|
+
if (k === 'type') continue;
|
|
446
|
+
parts.push(`${k}: ${enc(v)}`);
|
|
447
|
+
}
|
|
448
|
+
return `- { ${parts.join(', ')} }`;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
/** Constrói o texto YAML do wf-<id>.yaml a partir das etapas (schema SF1.1). */
|
|
452
|
+
/**
|
|
453
|
+
* Serializa um checkpoint por-etapa declarado na spec para o flow-map inline do
|
|
454
|
+
* wf-*.yaml. Preserva `blocking` e `criteria` (fonte do legado citada na spec).
|
|
455
|
+
* SF3.4/finding F2: sem isto o checkpoint mid-DAG autorado era DROPADO — degradando
|
|
456
|
+
* fidelidade E mascarando o gatilho checkpoint:mid do feature-diff (computeFeatureSet
|
|
457
|
+
* lê step.checkpoint.blocking). Newlines viram espaço (o flow-map é single-line).
|
|
458
|
+
*/
|
|
459
|
+
function serializeCheckpoint(cp) {
|
|
460
|
+
const blocking = cp.blocking === true ? 'true' : 'false';
|
|
461
|
+
const criteria = String(cp.criteria == null ? '' : cp.criteria)
|
|
462
|
+
.replace(/[\r\n]+/g, ' ')
|
|
463
|
+
.replace(/\\/g, '\\\\')
|
|
464
|
+
.replace(/"/g, '\\"');
|
|
465
|
+
// SF12.6/ADR-030 Bloco D (AC22) — skip_modes, quando presente (INJETADO por `ritmo`, nunca
|
|
466
|
+
// lido de agents[i].checkpoint.skip_modes diretamente — ver injeção em generatePod), é
|
|
467
|
+
// emitido ANTES de `criteria` (mesma ordem do wf hand-authored — fecha o AC23).
|
|
468
|
+
const skipModes = (Array.isArray(cp.skip_modes) && cp.skip_modes.length)
|
|
469
|
+
? `skip_modes: [${cp.skip_modes.join(', ')}], `
|
|
470
|
+
: '';
|
|
471
|
+
return `{ blocking: ${blocking}, ${skipModes}criteria: "${criteria}" }`;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
function buildWorkflowYaml(workflowId, steps) {
|
|
475
|
+
const lines = [];
|
|
476
|
+
lines.push(`# wf-${workflowId}.yaml — gerado por forge-generate.cjs (SF1.4).`);
|
|
477
|
+
lines.push(`# Contrato: templates/workflow-tmpl.yaml. Validador: scripts/validate-pod-workflow.cjs.`);
|
|
478
|
+
lines.push('');
|
|
479
|
+
lines.push('state_machine:');
|
|
480
|
+
lines.push(' states: [init, running, review, complete, failed]');
|
|
481
|
+
lines.push(' initial: init');
|
|
482
|
+
lines.push(' terminal: [complete, failed]');
|
|
483
|
+
lines.push('');
|
|
484
|
+
lines.push('steps:');
|
|
485
|
+
steps.forEach((step, i) => {
|
|
486
|
+
const inputs = i === 0 ? '[brief]' : `[${steps[i - 1].output}]`;
|
|
487
|
+
const dependsOn = i === 0 ? '[]' : `[${steps[i - 1].id}]`;
|
|
488
|
+
// Checkpoint por-etapa: se a spec declarou `step.checkpoint` (mid ou final),
|
|
489
|
+
// PROPAGA-o conforme declarado (blocking/criteria). Senão, mantém o default:
|
|
490
|
+
// última etapa ganha checkpoint bloqueante genérico; demais, null.
|
|
491
|
+
const isLast = i === steps.length - 1;
|
|
492
|
+
const checkpoint = (step.checkpoint && typeof step.checkpoint === 'object')
|
|
493
|
+
? serializeCheckpoint(step.checkpoint)
|
|
494
|
+
: (isLast ? '{ blocking: true, criteria: "revisão da etapa final aprovada" }' : 'null');
|
|
495
|
+
lines.push(` - id: ${step.id}`);
|
|
496
|
+
lines.push(` agent: ${step.agent}`);
|
|
497
|
+
lines.push(` template: templates/${step.id}-tmpl.md`);
|
|
498
|
+
lines.push(` inputs: ${inputs}`);
|
|
499
|
+
lines.push(` outputs: [${step.output}]`);
|
|
500
|
+
// SF12.4a/ADR-030 Bloco D (AC12) — mecanismo 1 (`requiredReading`, primitiva EMITIDA):
|
|
501
|
+
// `knowledge:` por etapa, MESMA forma já validada por validate-pod-workflow.cjs
|
|
502
|
+
// (bloco SF10.1/ADR-018 D2). Etapa sem `knowledge` na spec: nada emitido (AC14 —
|
|
503
|
+
// retrocompat byte-idêntica, não é caso de recusa do Bloco C).
|
|
504
|
+
if (Array.isArray(step.knowledge) && step.knowledge.length) {
|
|
505
|
+
lines.push(` knowledge: [${step.knowledge.join(', ')}]`);
|
|
506
|
+
}
|
|
507
|
+
lines.push(` depends_on: ${dependsOn}`);
|
|
508
|
+
lines.push(` checkpoint: ${checkpoint}`);
|
|
509
|
+
lines.push(' veto:');
|
|
510
|
+
lines.push(' assertions:');
|
|
511
|
+
// Veto ORTOGONAL ao template (ADR-005): default = asserção de conteúdo declarado
|
|
512
|
+
// (campo de máquina `confianca` fora dos headings). Vetos de domínio declarados na
|
|
513
|
+
// spec (step.veto[]) SUBSTITUEM o default (sempre ≥1). Nunca section_exists de
|
|
514
|
+
// seção do próprio template (implicado pela conformidade de forma).
|
|
515
|
+
const assertions = (Array.isArray(step.veto) && step.veto.length)
|
|
516
|
+
? step.veto
|
|
517
|
+
: [{ type: 'field_matches', field: 'confianca', pattern: '^(alta|media|baixa)$' }];
|
|
518
|
+
for (const a of assertions) lines.push(` ${serializeAssertion(a)}`);
|
|
519
|
+
// SF7.3/ADR-016 — emissão OPT-IN de anti_path: só quando a spec da etapa o declara
|
|
520
|
+
// (ausente por padrão — diferente de `confianca`, que é default por ortogonalidade).
|
|
521
|
+
if (Array.isArray(step.anti_path) && step.anti_path.length) {
|
|
522
|
+
lines.push(' anti_path:');
|
|
523
|
+
for (const it of step.anti_path) {
|
|
524
|
+
const mb = (it.mechanized_by == null) ? 'null' : encAntiScalar(it.mechanized_by);
|
|
525
|
+
lines.push(` - { rule: ${encAntiScalar(it.rule)}, mechanized_by: ${mb} }`);
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
lines.push('');
|
|
529
|
+
});
|
|
530
|
+
return lines.join('\n');
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
/** Escalar do anti_path para o flow-map inline (aspas só quando não é token simples). */
|
|
534
|
+
function encAntiScalar(v) {
|
|
535
|
+
const s = String(v);
|
|
536
|
+
return /^[A-Za-z0-9_-]+$/.test(s) ? s : JSON.stringify(s);
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
/** squad-io.yaml — contrato de handoff do pod (inputs↔outputs entre etapas). */
|
|
540
|
+
function buildSquadIo(pod, steps) {
|
|
541
|
+
const lines = [];
|
|
542
|
+
lines.push(`# squad-io.yaml — contrato de handoff do pod \`${pod}\` (SF1.4).`);
|
|
543
|
+
lines.push(`# Cada handoff liga o output de uma etapa ao input da etapa a jusante.`);
|
|
544
|
+
lines.push(`pod: ${pod}`);
|
|
545
|
+
lines.push('handoffs:');
|
|
546
|
+
for (let i = 1; i < steps.length; i++) {
|
|
547
|
+
lines.push(` - from: ${steps[i - 1].id}`);
|
|
548
|
+
lines.push(` to: ${steps[i].id}`);
|
|
549
|
+
lines.push(` artifact: ${steps[i - 1].output}`);
|
|
550
|
+
}
|
|
551
|
+
if (steps.length < 2) lines.push(' [] # pod de etapa única — sem handoff interno');
|
|
552
|
+
lines.push('');
|
|
553
|
+
return lines.join('\n');
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
/** Normaliza a lista de etapas a partir da spec (agents ordenados = etapas). */
|
|
557
|
+
function stepsFromSpec(spec) {
|
|
558
|
+
const agents = Array.isArray(spec.agents) ? spec.agents : [];
|
|
559
|
+
const steps = agents.map((a, i) => {
|
|
560
|
+
const name = typeof a === 'string' ? a : a.name;
|
|
561
|
+
const id = slug((typeof a === 'object' && a.step) || name || `step-${i + 1}`);
|
|
562
|
+
const output = (typeof a === 'object' && a.output) || `${id}-artifact`;
|
|
563
|
+
// ADR-005: propaga as seções do template e o veto de domínio declarados na spec.
|
|
564
|
+
const sections = (typeof a === 'object' && Array.isArray(a.sections)) ? a.sections : undefined;
|
|
565
|
+
const veto = (typeof a === 'object' && Array.isArray(a.veto)) ? a.veto : undefined;
|
|
566
|
+
// ADR-005/SF3.4: propaga o checkpoint por-etapa declarado na spec (mid ou final).
|
|
567
|
+
const checkpoint = (typeof a === 'object' && a.checkpoint && typeof a.checkpoint === 'object') ? a.checkpoint : undefined;
|
|
568
|
+
// SF7.3/ADR-016: propaga anti_path da etapa da spec (opt-in). A emissão pelo
|
|
569
|
+
// buildWorkflowYaml só é ALCANÇÁVEL pela rota real (generatePod→stepsFromSpec) se o
|
|
570
|
+
// campo for propagado aqui — antes era descartado (CONCERNS-1). Valida a forma na
|
|
571
|
+
// ENTRADA da spec (fail-loud, coerente com o estático): item malformado ou mechanized_by
|
|
572
|
+
// sem id correspondente no veto da etapa recusa a geração.
|
|
573
|
+
const anti_path = (typeof a === 'object' && Array.isArray(a.anti_path))
|
|
574
|
+
? validateSpecAntiPath(a.anti_path, veto, id)
|
|
575
|
+
: undefined;
|
|
576
|
+
// SF12.4a/ADR-030 Bloco D (AC11) — propaga agents[i].knowledge (opcional, retro-
|
|
577
|
+
// compat: ausente ⇒ undefined ⇒ nenhuma chave `knowledge:` emitida, AC14).
|
|
578
|
+
const knowledge = (typeof a === 'object' && Array.isArray(a.knowledge))
|
|
579
|
+
? a.knowledge.map(String)
|
|
580
|
+
: undefined;
|
|
581
|
+
return { id, agent: name, output, sections, veto, checkpoint, anti_path, knowledge };
|
|
582
|
+
});
|
|
583
|
+
return injectRitmoSkipModes(spec, steps);
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
/**
|
|
587
|
+
* SF12.6/ADR-030 Bloco D (Decisão 6.1-6.5) — `ritmo` INJETA checkpoint.skip_modes sobre um
|
|
588
|
+
* checkpoint JÁ AUTORADO (mesmo padrão de contrato_entrada/entregavel, Blocos G/H — mas
|
|
589
|
+
* aplicado AQUI, em `stepsFromSpec`, não em `generatePod`: a emissão precisa ser alcançável
|
|
590
|
+
* também pela rota direta `stepsFromSpec → buildWorkflowYaml`, sem passar por `generatePod`
|
|
591
|
+
* — AC23). NUNCA fabrica `criteria` (Decisão 1): RECUSA quando a etapa-alvo não tem
|
|
592
|
+
* checkpoint autorado — inclusive quando o único "checkpoint" seria só o DEFAULT sintetizado
|
|
593
|
+
* por `buildWorkflowYaml` na última etapa (nunca aparece aqui: esse default só é aplicado
|
|
594
|
+
* DEPOIS, dentro de `buildWorkflowYaml` — Decisão 6.5, `DEBT-SF12.6-F`, sem AC próprio).
|
|
595
|
+
* Muta `steps` em memória (array recém-construído por este mesmo `stepsFromSpec`) — nunca a
|
|
596
|
+
* spec de entrada. Mutação silenciosa quando `ritmo` está ausente/recusado/malformado: a
|
|
597
|
+
* disciplina de FORMA (fail-loud sob `specOrigin:'authored'`) é do Bloco C/`readCanonicalField`,
|
|
598
|
+
* não deste ponto — aqui só se decide SE a injeção é POSSÍVEL sobre o alvo referenciado.
|
|
599
|
+
*/
|
|
600
|
+
function injectRitmoSkipModes(spec, steps) {
|
|
601
|
+
const ritmo = spec && spec.ritmo;
|
|
602
|
+
const isCandidate = ritmo && typeof ritmo === 'object' && !Array.isArray(ritmo) && ritmo.nao_se_aplica !== true
|
|
603
|
+
&& typeof ritmo.step === 'string' && ritmo.step.trim() !== ''
|
|
604
|
+
&& Array.isArray(ritmo.skip_modes) && ritmo.skip_modes.length
|
|
605
|
+
&& ritmo.skip_modes.every((m) => typeof m === 'string' && m.trim() !== '');
|
|
606
|
+
if (!isCandidate) return steps;
|
|
607
|
+
validateCanonicalStepRef('ritmo', ritmo.step, steps); // AC21c — referência a etapa inexistente RECUSA.
|
|
608
|
+
const idx = steps.findIndex((s) => s.id === ritmo.step);
|
|
609
|
+
const targetCp = steps[idx].checkpoint;
|
|
610
|
+
if (!targetCp || typeof targetCp !== 'object' || typeof targetCp.criteria !== 'string' || targetCp.criteria.trim() === '') {
|
|
611
|
+
throw new CanonicalFieldError('ritmo', `aponta para a etapa "${ritmo.step}" que NÃO tem checkpoint ` +
|
|
612
|
+
'autorado (blocking+criteria escritos por humano) — "ritmo" só INJETA skip_modes sobre um gate que já ' +
|
|
613
|
+
'existe; a máquina NUNCA fabrica criteria (ADR-030 Decisão 1/6.1/6.5, AC21b/DEBT-SF12.6-F).');
|
|
614
|
+
}
|
|
615
|
+
steps[idx] = { ...steps[idx], checkpoint: { ...targetCp, skip_modes: ritmo.skip_modes } };
|
|
616
|
+
return steps;
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
/** Valida a forma do anti_path na entrada da spec (fail-loud). Retorna o array validado. */
|
|
620
|
+
function validateSpecAntiPath(ap, veto, stepId) {
|
|
621
|
+
const ids = new Set((Array.isArray(veto) ? veto : [])
|
|
622
|
+
.filter((a) => a && typeof a.id === 'string' && a.id.trim()).map((a) => a.id));
|
|
623
|
+
ap.forEach((item, i) => {
|
|
624
|
+
if (!item || typeof item !== 'object' || Array.isArray(item)) {
|
|
625
|
+
throw new Error(`anti_path[${i}] da etapa "${stepId}" deve ser um mapa { rule, mechanized_by }`);
|
|
626
|
+
}
|
|
627
|
+
if (typeof item.rule !== 'string' || item.rule.trim() === '') {
|
|
628
|
+
throw new Error(`anti_path[${i}].rule da etapa "${stepId}" ausente ou vazio`);
|
|
629
|
+
}
|
|
630
|
+
if (item.mechanized_by != null) {
|
|
631
|
+
if (typeof item.mechanized_by !== 'string' || !ids.has(item.mechanized_by)) {
|
|
632
|
+
throw new Error(`anti_path[${i}].mechanized_by "${item.mechanized_by}" da etapa "${stepId}" não referencia o id de nenhuma veto.assertion declarada na spec desta etapa`);
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
});
|
|
636
|
+
return ap;
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
/**
|
|
640
|
+
* Gera os artefatos do pod em `podDir`, proporcionais ao tier.
|
|
641
|
+
* Retorna { tier, written[], skipped[] } — written = paths criados (disco-first).
|
|
642
|
+
*/
|
|
643
|
+
// ---------------------------------------------------------------------------
|
|
644
|
+
// SF12.4a/ADR-030 Bloco I — o preço do L1: stub de agente + sidecar `.smoke.json`
|
|
645
|
+
// sintetizado. Síntese ESTRUTURAL (satisfaz a FORMA das veto.assertions declaradas),
|
|
646
|
+
// nunca redação de conteúdo — mesma fronteira ADR-002 do resto do framework.
|
|
647
|
+
// ---------------------------------------------------------------------------
|
|
648
|
+
|
|
649
|
+
class KnowledgeFileMissingError extends Error {
|
|
650
|
+
constructor(stepId, file) {
|
|
651
|
+
super(`etapa "${stepId}": knowledge declarado "${file}" não existe em <podDir>/knowledge/${file} — ` +
|
|
652
|
+
`o gerador CONFERE existência, não cria (pipeline.md Fase 3: knowledge é autorado ANTES de invocar ` +
|
|
653
|
+
`forge-generate.cjs). AC13/ADR-030 Bloco D.`);
|
|
654
|
+
this.name = 'KnowledgeFileMissingError';
|
|
655
|
+
this.exitCode = 2;
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
/** AC13 — pré-condição que o motor verifica no dispatch, conferida NA GERAÇÃO. */
|
|
660
|
+
function assertKnowledgeExists(steps, podDir) {
|
|
661
|
+
for (const step of steps) {
|
|
662
|
+
if (!Array.isArray(step.knowledge)) continue;
|
|
663
|
+
for (const k of step.knowledge) {
|
|
664
|
+
const abs = path.join(podDir, 'knowledge', k);
|
|
665
|
+
if (!fs.existsSync(abs)) throw new KnowledgeFileMissingError(step.id, k);
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
class UnsynthesizableVetoError extends Error {
|
|
671
|
+
constructor(stepId, assertion) {
|
|
672
|
+
const extra = assertion.type === 'field_matches'
|
|
673
|
+
? ` com pattern "${assertion.pattern}" (o sintetizador só deriva alternação simples "^(a|b|c)$" ou literal puro)`
|
|
674
|
+
: '';
|
|
675
|
+
super(`sidecar de smoke (Bloco I): a etapa "${stepId}" declara veto.assertions do tipo "${assertion.type}"${extra} ` +
|
|
676
|
+
`que o sintetizador não sabe satisfazer — RECUSA fail-loud (não gera .smoke.json/cenário inválido; ` +
|
|
677
|
+
`um sidecar que não prova nada seria pior que ausente — mesma doutrina do Bloco C, AC28).`);
|
|
678
|
+
this.name = 'UnsynthesizableVetoError';
|
|
679
|
+
this.exitCode = 2;
|
|
680
|
+
this.stepId = stepId;
|
|
681
|
+
this.assertionType = assertion.type;
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
const SYNTHESIZABLE_VETO_TYPES = new Set(['field_matches', 'count_gte', 'file_referenced_exists', 'section_exists']);
|
|
686
|
+
|
|
687
|
+
/** Deriva um valor literal que CASA o pattern — só alternação simples "^(a|b|c)$" ou literal puro. Senão, null. */
|
|
688
|
+
function deriveFieldMatchesValue(pattern) {
|
|
689
|
+
let p = String(pattern == null ? '' : pattern);
|
|
690
|
+
if (p.startsWith('^')) p = p.slice(1);
|
|
691
|
+
if (p.endsWith('$')) p = p.slice(0, -1);
|
|
692
|
+
const alt = p.match(/^\(([^()]+)\)$/);
|
|
693
|
+
if (alt) {
|
|
694
|
+
const first = alt[1].split('|')[0];
|
|
695
|
+
if (first && !/[\\.*+?[\]{}^$]/.test(first)) return first;
|
|
696
|
+
return null;
|
|
697
|
+
}
|
|
698
|
+
if (p && !/[\\.*+?()[\]{}^$|]/.test(p)) return p;
|
|
699
|
+
return null;
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
/** Escapa um literal para uso seguro dentro de uma alternação de regex `(a|b|c)`. */
|
|
703
|
+
function escapeRegexAlt(v) {
|
|
704
|
+
return String(v).replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
function stepAssertions(step) {
|
|
708
|
+
return (Array.isArray(step.veto) && step.veto.length)
|
|
709
|
+
? step.veto
|
|
710
|
+
: [{ type: 'field_matches', field: 'confianca', pattern: '^(alta|media|baixa)$' }];
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
function stepSections(step) {
|
|
714
|
+
return Array.isArray(step.sections) && step.sections.length ? step.sections : ['## Contexto', '## Output'];
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
/** AC28 — recusa fail-loud ANTES de qualquer escrita se algum veto declarado não é sintetizável. */
|
|
718
|
+
function assertSynthesizableVeto(steps) {
|
|
719
|
+
for (const step of steps) {
|
|
720
|
+
for (const a of stepAssertions(step)) {
|
|
721
|
+
if (!SYNTHESIZABLE_VETO_TYPES.has(a.type)) throw new UnsynthesizableVetoError(step.id, a);
|
|
722
|
+
if (a.type === 'field_matches' && deriveFieldMatchesValue(a.pattern) == null) {
|
|
723
|
+
throw new UnsynthesizableVetoError(step.id, a);
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
/**
|
|
730
|
+
* Sintetiza o texto de UM artefato de etapa: `{}` = conforme (caminho feliz);
|
|
731
|
+
* `{ dropFirstSection: true }` = quebra de FORMA (template_violation, AC27);
|
|
732
|
+
* `{ breakAssertion: <assertion> }` = quebra de UMA assertion nomeada (veto_violation,
|
|
733
|
+
* AC27/AC27b — nunca uma assertion genérica/arbitrária).
|
|
734
|
+
*/
|
|
735
|
+
function synthesizeArtifactText(step, opts = {}) {
|
|
736
|
+
const sections = stepSections(step);
|
|
737
|
+
const useSections = opts.dropFirstSection ? sections.slice(1) : sections;
|
|
738
|
+
const assertions = stepAssertions(step);
|
|
739
|
+
const countGte = assertions.filter((a) => a.type === 'count_gte');
|
|
740
|
+
|
|
741
|
+
const body = useSections.map((s) => {
|
|
742
|
+
const label = String(s).replace(/^#+\s*/, '').trim();
|
|
743
|
+
const cg = countGte.find((c) => label.toLowerCase().includes(String(c.field).toLowerCase())
|
|
744
|
+
|| String(c.field).toLowerCase().includes(label.toLowerCase()));
|
|
745
|
+
if (cg) {
|
|
746
|
+
const broken = opts.breakAssertion === cg;
|
|
747
|
+
const n = broken ? 0 : Math.max(1, Number(cg.n) || 1);
|
|
748
|
+
const items = Array.from({ length: n }, (_, i) => `- item de canário ${i + 1}`).join('\n');
|
|
749
|
+
return `${s}\n${items ? `${items}\n` : ''}`;
|
|
750
|
+
}
|
|
751
|
+
return `${s}\nConteúdo de canário (sintetizado por forge-generate.cjs, ADR-030 Bloco I — síntese ESTRUTURAL, não redação).\n`;
|
|
752
|
+
}).join('\n');
|
|
753
|
+
|
|
754
|
+
const fieldLines = [];
|
|
755
|
+
const seen = new Set();
|
|
756
|
+
for (const a of assertions) {
|
|
757
|
+
if (a.type === 'field_matches') {
|
|
758
|
+
const val = deriveFieldMatchesValue(a.pattern);
|
|
759
|
+
const broken = opts.breakAssertion === a;
|
|
760
|
+
fieldLines.push(`${a.field}: ${broken ? `${val}--quebrado-pelo-smoke` : val}`);
|
|
761
|
+
seen.add(a.field);
|
|
762
|
+
} else if (a.type === 'file_referenced_exists') {
|
|
763
|
+
const broken = opts.breakAssertion === a;
|
|
764
|
+
// O agente-stub do AC26 garante existência real de agents/<agent>.md sob podRoot
|
|
765
|
+
// (base de resolução confirmada pelo `*review` — pc-run-pod.cjs:466).
|
|
766
|
+
fieldLines.push(`${a.field}: ${broken ? 'arquivo-inexistente-smoke.md' : `agents/${step.agent}.md`}`);
|
|
767
|
+
seen.add(a.field);
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
if (!seen.has('confianca')) fieldLines.push('confianca: alta');
|
|
771
|
+
|
|
772
|
+
return `${body}\n${fieldLines.join('\n')}\n`;
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
/**
|
|
776
|
+
* Monta UM sidecar `.smoke.json` (schema medido em `wf-forge.smoke.json` — Contexto (f)
|
|
777
|
+
* da story): `artifacts` conforme por etapa (TODAS as etapas do wf); `template_violation`/
|
|
778
|
+
* `veto_violation` da etapa-alvo. `breakField`, quando dado, nomeia a assertion a quebrar
|
|
779
|
+
* (AC27b — mecanismo específico); default: a 1ª assertion da etapa-alvo (AC27 — base).
|
|
780
|
+
*/
|
|
781
|
+
function buildSmokeSidecar(steps, targetStepId, breakField) {
|
|
782
|
+
const artifacts = {};
|
|
783
|
+
for (const step of steps) artifacts[step.id] = synthesizeArtifactText(step);
|
|
784
|
+
const targetStep = steps.find((s) => s.id === targetStepId);
|
|
785
|
+
const templateViolation = synthesizeArtifactText(targetStep, { dropFirstSection: true });
|
|
786
|
+
const assertions = stepAssertions(targetStep);
|
|
787
|
+
const breakAssertion = (breakField && assertions.find((a) => a.field === breakField)) || assertions[0];
|
|
788
|
+
const vetoViolation = synthesizeArtifactText(targetStep, { breakAssertion });
|
|
789
|
+
return {
|
|
790
|
+
_comment: 'Cenário de smoke SINTETIZADO por forge-generate.cjs (SF12.4a/ADR-030 Bloco I) — canário ' +
|
|
791
|
+
'ESTRUTURAL (satisfaz a forma das veto.assertions declaradas), não redação de conteúdo (fronteira ADR-002). ' +
|
|
792
|
+
`target_step="${targetStepId}"${breakField ? ` — cenário do mecanismo "${breakField}" (AC27b)` : ' — sidecar BASE (AC27)'}.`,
|
|
793
|
+
target_step: targetStepId,
|
|
794
|
+
artifacts,
|
|
795
|
+
template_violation: templateViolation,
|
|
796
|
+
veto_violation: vetoViolation,
|
|
797
|
+
};
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
/** AC26 — frontmatter + 3 blocos mínimos (Papel/Comandos/Regras de ouro), dentro do teto de validate-budgets.js. */
|
|
801
|
+
function agentStubContent(name) {
|
|
802
|
+
return `---
|
|
803
|
+
name: ${name}
|
|
804
|
+
description: "Stub gerado por forge-generate.cjs (SF12.4a/ADR-030 Bloco I). Use para: pod executável até o forge-chief preencher o conteúdo real."
|
|
805
|
+
model: sonnet
|
|
806
|
+
budget_tokens: 400
|
|
807
|
+
---
|
|
808
|
+
|
|
809
|
+
# Papel
|
|
810
|
+
|
|
811
|
+
Stub gerado automaticamente para que o pod seja EXECUTÁVEL pelo motor real (o runner
|
|
812
|
+
exige \`agents/<nome>.md\` — ADR-030 M2/DF-SF12.4-1). Conteúdo REAL é autoria do
|
|
813
|
+
\`forge-chief\` em prosa (\`DEBT-SF12.4-F\`) — este stub não é bom, é estruturalmente válido.
|
|
814
|
+
|
|
815
|
+
## Comandos
|
|
816
|
+
|
|
817
|
+
- *comando-exemplo: preencher com os comandos reais deste agente.
|
|
818
|
+
|
|
819
|
+
## Regras de ouro
|
|
820
|
+
|
|
821
|
+
- Preencher com as regras de ouro reais deste agente antes de considerar o pod pronto para uso.
|
|
822
|
+
`;
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
// ---------------------------------------------------------------------------
|
|
826
|
+
// SF12.4a/ADR-030 Bloco J (`DF-SF12.4-5`/`-6`) — camada do dono, SÓ PLACEHOLDER. Nunca
|
|
827
|
+
// escreve instância (`dono.yaml`); emite `knowledge/dono.example.yaml` com sentinela
|
|
828
|
+
// `pc:placeholder`, no MESMO formato estrutural de `content-carousel/knowledge/dono.example.yaml`
|
|
829
|
+
// (generalizado — sem os campos específicos de carrossel/CTA).
|
|
830
|
+
// ---------------------------------------------------------------------------
|
|
831
|
+
const ESCADA_DE_VOZ_VALUES = new Set(['sem_voz', 'guia_de_estilo', 'dna_completo']);
|
|
832
|
+
|
|
833
|
+
function donoExampleContent(pod) {
|
|
834
|
+
return `# dono.example.yaml — forma declarada da configuração de dono do pod \`${pod}\` (ADR-010 + ADR-013).
|
|
835
|
+
# O template NUNCA distribui dado pessoal real: este arquivo é o placeholder.
|
|
836
|
+
# pc:placeholder <- SENTINELA (ADR-027): enquanto esta marca existir, o arquivo
|
|
837
|
+
# ainda NÃO foi personalizado. Apague-a ao preencher de verdade.
|
|
838
|
+
# É ela que o oráculo de contenção do framework exige de todo
|
|
839
|
+
# arquivo da camada do dono que entra no pacote.
|
|
840
|
+
# Na instância: copie para \`knowledge/dono.yaml\` e preencha. Se \`dono.yaml\` estiver
|
|
841
|
+
# ausente ou ainda igual a este placeholder, o agente ELICITA os dados na 1ª ativação.
|
|
842
|
+
#
|
|
843
|
+
# Gerado por forge-generate.cjs (SF12.4a/ADR-030 Bloco J) — SÓ emissão de placeholder,
|
|
844
|
+
# NUNCA escrita de instância (\`DF-SF12.4-6\`, vinculante).
|
|
845
|
+
dono:
|
|
846
|
+
nome: "<Nome do Dono>" # quem assina a peça (nome civil ou de marca)
|
|
847
|
+
handle: "<@handle>" # handle do perfil onde a peça é publicada
|
|
848
|
+
autoridade: "<uma linha factual de enquadramento de autoridade, sem persona nem promessa que o conteúdo não sustente>"
|
|
849
|
+
# Guia de estilo do dono (ADR-013) — degrau intermediário da escada de voz
|
|
850
|
+
# (sem voz / GUIA DE ESTILO / DNA completo do dono). Machine-readable.
|
|
851
|
+
voz:
|
|
852
|
+
pessoa: "primeira" # o dono é o AUTOR; NUNCA citado em 3ª pessoa como fonte externa
|
|
853
|
+
lexico_proibido: ["—", "–"] # PISO do framework (classe P) — subir é livre, descer exige atribuição explícita
|
|
854
|
+
tom: "<uma linha: registro e energia da voz do dono, ex.: direto, técnico sem jargão, sem hype>"
|
|
855
|
+
`;
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
function generatePod(spec, podDir, opts = {}) {
|
|
859
|
+
// SF12.4a/ADR-030 Bloco O (AC48) — PRIMEIRA checagem, antes de qualquer outra.
|
|
860
|
+
validateSpecOrigin(opts.specOrigin);
|
|
861
|
+
const specOrigin = opts.specOrigin;
|
|
862
|
+
// C.1/SF3.1 — tierOverride INTERCEPTA a classificação: com override presente,
|
|
863
|
+
// NÃO re-classifica pela spec isolada (que ignoraria o pod existente). O modo
|
|
864
|
+
// aditivo e o upgrade-in-place SEMPRE passam o tier AGREGADO do disco. Sem
|
|
865
|
+
// override, comportamento SF1.4 inalterado (classifica pela spec recebida).
|
|
866
|
+
const tier = opts.tierOverride != null ? opts.tierOverride : classifyTier(spec).tier;
|
|
867
|
+
const steps = stepsFromSpec(spec);
|
|
868
|
+
const workflowId = slug(spec.workflowId || spec.pod || 'main');
|
|
869
|
+
const written = [];
|
|
870
|
+
const skipped = [];
|
|
871
|
+
|
|
872
|
+
// dry-run (SF6.2): computa o PLANO (written[]/skipped[] preenchidos) sem tocar disco.
|
|
873
|
+
// Requisito da diretiva ADR-004 §2: forge-upgrade-pod em dry-run = zero escrita.
|
|
874
|
+
const dryRun = Boolean(opts.dryRun);
|
|
875
|
+
const emit = (file, content) => { if (!dryRun) writeDisk(file, content); };
|
|
876
|
+
|
|
877
|
+
// Forma STANDARD-interface (ADR-004 adendo): `interface:true` só é válido com zero
|
|
878
|
+
// handoff interno. Fail-loud ANTES de qualquer escrita (vale também em dry-run).
|
|
879
|
+
const isInterface = spec.interface === true;
|
|
880
|
+
if (isInterface && Number(spec.handoffs || 0) !== 0) {
|
|
881
|
+
throw new InterfaceContractError(Number(spec.handoffs || 0));
|
|
882
|
+
}
|
|
883
|
+
// Memória em SIMPLE é OPCIONAL (SPEC §G2 tabela — "memória opcional", não proibida):
|
|
884
|
+
// habilitada só sob pedido explícito via flag CLI `--with-memory` (opts.withMemory)
|
|
885
|
+
// ou campo `spec.memoryOpt: true`. STANDARD/COMPLEX sempre recebe.
|
|
886
|
+
const withMemory = Boolean(opts.withMemory) || spec.memoryOpt === true;
|
|
887
|
+
|
|
888
|
+
// Modo aditivo: `owns[]` (resolvidos a absoluto) são os paths que ESTA linha
|
|
889
|
+
// possui — regeneráveis; qualquer outro artefato pré-existente é intocável.
|
|
890
|
+
const additive = Boolean(opts.additive);
|
|
891
|
+
const ownsSet = new Set((opts.owns || []).map((p) => path.resolve(podDir, p)));
|
|
892
|
+
const merged = [];
|
|
893
|
+
|
|
894
|
+
// SF12.6/ADR-030 Bloco C — leniência POR PATH (Decisão 7.2), forma nua recusada.
|
|
895
|
+
const allowDivergentOverwrite = validateAllowDivergentOverwrite(opts.allowDivergentOverwrite);
|
|
896
|
+
const allowSet = new Set(allowDivergentOverwrite.map((p) => path.resolve(p)));
|
|
897
|
+
const divergent = [];
|
|
898
|
+
const preserved = [];
|
|
899
|
+
// Guarda aditiva: existe + não-owned ⇒ colisão (exit 2). Existe + owned ⇒ regenera.
|
|
900
|
+
const guardCollision = (abs, kind) => {
|
|
901
|
+
if (!additive) return;
|
|
902
|
+
if (!fs.existsSync(abs)) return;
|
|
903
|
+
if (ownsSet.has(path.resolve(abs))) return; // owned → retomada idempotente (regenera determinístico)
|
|
904
|
+
throw new AdditiveCollisionError(path.relative(podDir, abs), kind);
|
|
905
|
+
};
|
|
906
|
+
|
|
907
|
+
// SF12.4a/ADR-030 Bloco D (AC13) — pré-condição de knowledge ANTES de qualquer escrita.
|
|
908
|
+
assertKnowledgeExists(steps, podDir);
|
|
909
|
+
|
|
910
|
+
// SF12.4a/ADR-030 Blocos C/E/G/H/O (AC7-AC25, AC42-AC44, AC48b/AC48c) — os 3 campos
|
|
911
|
+
// canônicos (ritmo, contrato_entrada, entregavel). `specOrigin: 'authored'` ⇒ recusa
|
|
912
|
+
// fail-loud de campo AUSENTE ou malformado, ANTES de qualquer escrita; `'derived'` ⇒
|
|
913
|
+
// preserva o comportamento atual e reporta o que falta (leniência que FALA).
|
|
914
|
+
if (specOrigin === 'authored') {
|
|
915
|
+
for (const field of CANONICAL_FIELDS) readCanonicalField(spec, field, steps);
|
|
916
|
+
}
|
|
917
|
+
const canonicalStatus = computeCanonicalStatus(spec);
|
|
918
|
+
|
|
919
|
+
// stepExtraFields: stepId -> [{ field, placeholder }] — campos de máquina extras
|
|
920
|
+
// (Blocos G/H) a injetar no template da etapa-alvo, ANTES de `confianca`.
|
|
921
|
+
const stepExtraFields = new Map();
|
|
922
|
+
|
|
923
|
+
// Bloco G (AC20-22) — contrato_entrada declarado: campo de máquina + veto field_matches.
|
|
924
|
+
if (canonicalStatus.contrato_entrada.mode === 'declared'
|
|
925
|
+
&& typeof canonicalStatus.contrato_entrada.step === 'string'
|
|
926
|
+
&& Array.isArray(canonicalStatus.contrato_entrada.valores) && canonicalStatus.contrato_entrada.valores.length >= 2) {
|
|
927
|
+
const { step: targetId, valores } = canonicalStatus.contrato_entrada;
|
|
928
|
+
const idx = steps.findIndex((s) => s.id === targetId);
|
|
929
|
+
if (idx === -1) {
|
|
930
|
+
throw new CanonicalFieldError('contrato_entrada', `referencia a etapa "${targetId}" que não existe entre os ids de stepsFromSpec(spec)`);
|
|
931
|
+
}
|
|
932
|
+
const pattern = `^(${valores.map(escapeRegexAlt).join('|')})$`;
|
|
933
|
+
const baseVeto = (Array.isArray(steps[idx].veto) && steps[idx].veto.length)
|
|
934
|
+
? steps[idx].veto
|
|
935
|
+
: [{ type: 'field_matches', field: 'confianca', pattern: '^(alta|media|baixa)$' }];
|
|
936
|
+
steps[idx] = {
|
|
937
|
+
...steps[idx],
|
|
938
|
+
veto: [...baseVeto, {
|
|
939
|
+
type: 'field_matches', field: 'contrato_entrada', pattern,
|
|
940
|
+
_sources: 'ADR-030 Bloco G (DF-SF12.4-1) — âncora de MÁQUINA do padrão contrato_entrada, declarado na spec (mesma forma de content-carousel/workflows/wf-content-carousel.yaml).',
|
|
941
|
+
}],
|
|
942
|
+
};
|
|
943
|
+
stepExtraFields.set(targetId, [...(stepExtraFields.get(targetId) || []), { field: 'contrato_entrada', placeholder: valores.join('|') }]);
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
// Bloco H (AC23-25) — entregavel declarado: campo de máquina + veto file_referenced_exists.
|
|
947
|
+
if (canonicalStatus.entregavel.mode === 'declared' && typeof canonicalStatus.entregavel.step === 'string') {
|
|
948
|
+
const targetId = canonicalStatus.entregavel.step;
|
|
949
|
+
const idx = steps.findIndex((s) => s.id === targetId);
|
|
950
|
+
if (idx === -1) {
|
|
951
|
+
throw new CanonicalFieldError('entregavel', `referencia a etapa "${targetId}" que não existe entre os ids de stepsFromSpec(spec)`);
|
|
952
|
+
}
|
|
953
|
+
const baseVeto = (Array.isArray(steps[idx].veto) && steps[idx].veto.length)
|
|
954
|
+
? steps[idx].veto
|
|
955
|
+
: [{ type: 'field_matches', field: 'confianca', pattern: '^(alta|media|baixa)$' }];
|
|
956
|
+
steps[idx] = {
|
|
957
|
+
...steps[idx],
|
|
958
|
+
veto: [...baseVeto, {
|
|
959
|
+
type: 'file_referenced_exists', field: 'entregavel',
|
|
960
|
+
_sources: 'ADR-030 Bloco H (DF-SF12.4-1) — forma GENÉRICA já em produção (automation/wf-n8n-build.yaml:64), declarada na spec.',
|
|
961
|
+
}],
|
|
962
|
+
};
|
|
963
|
+
stepExtraFields.set(targetId, [...(stepExtraFields.get(targetId) || []), { field: 'entregavel', placeholder: 'path/relativo/ao/entregável' }]);
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
// wf — SIMPLE: no máx. 1 workflow mínimo (só se ≥2 etapas; comando/agente único ⇒
|
|
967
|
+
// nenhum). STANDARD/COMPLEX: sempre. EXCEÇÃO (ADR-004 adendo): pod-interface (zero
|
|
968
|
+
// handoff interno) NÃO fabrica workflow nem squad-io. Calculado AQUI (hoisted) porque
|
|
969
|
+
// o Bloco D (injeção de ritmo) e o Bloco I (AC28) precisam dele ANTES de qualquer escrita.
|
|
970
|
+
const wantsWorkflow = !isInterface && (tier !== TIER_SIMPLE || steps.length >= 2);
|
|
971
|
+
|
|
972
|
+
// SF12.6/ADR-030 Bloco D (Decisão 6.3) — a INJEÇÃO de fato (mutação de `steps[idx].checkpoint`
|
|
973
|
+
// + recusa quando a etapa-alvo não tem checkpoint autorado) já aconteceu dentro de
|
|
974
|
+
// `stepsFromSpec` (função `injectRitmoSkipModes`, acima) — precisa ser alcançável também
|
|
975
|
+
// pela rota direta `stepsFromSpec → buildWorkflowYaml`, sem passar por `generatePod` (AC23).
|
|
976
|
+
// Se chegamos aqui com `canonicalStatus.ritmo.mode === 'declared'` e forma válida, a injeção
|
|
977
|
+
// JÁ teve sucesso (senão `stepsFromSpec`, chamado no topo desta função, já teria lançado) —
|
|
978
|
+
// este bloco só ANUNCIA (nunca duplica a regra).
|
|
979
|
+
const ritmoAnnouncement = (canonicalStatus.ritmo.mode === 'declared'
|
|
980
|
+
&& typeof canonicalStatus.ritmo.step === 'string'
|
|
981
|
+
&& Array.isArray(canonicalStatus.ritmo.skip_modes) && canonicalStatus.ritmo.skip_modes.length)
|
|
982
|
+
? `[ritmo] etapa "${canonicalStatus.ritmo.step}" declarou checkpoint.skip_modes ` +
|
|
983
|
+
`${JSON.stringify(canonicalStatus.ritmo.skip_modes)} — EMITIDO no wf gerado, injetado sobre o ` +
|
|
984
|
+
'checkpoint JÁ AUTORADO (ADR-030 Bloco D, Decisão 6.3). Registrado em pod-spec.json._canonical_status.ritmo.'
|
|
985
|
+
: null;
|
|
986
|
+
if (ritmoAnnouncement) console.log(ritmoAnnouncement);
|
|
987
|
+
|
|
988
|
+
// Bloco O (AC48c) — modo `derived`: leniência que FALA, nos 2 modos (dry-run e --apply,
|
|
989
|
+
// ambos passam por aqui — a checagem não depende de `dryRun`) e mesmo sem pod-spec.json
|
|
990
|
+
// pré-existente (a spec RECEBIDA é a fonte, nunca leitura de arquivo em disco). SF12.6/
|
|
991
|
+
// AC27/`DEBT-SF12.5b-B`: quando `spec._<campo>_deferido` está presente (marcador irmão
|
|
992
|
+
// inerte, mesmo que o campo canônico fique ausente), a mensagem DISCRIMINA "deferido por X"
|
|
993
|
+
// de "ausente, legado" — não é a mesma frase genérica para os dois casos.
|
|
994
|
+
if (specOrigin === 'derived') {
|
|
995
|
+
for (const field of CANONICAL_FIELDS) {
|
|
996
|
+
if (canonicalStatus[field].mode === 'ausente') {
|
|
997
|
+
const deferido = spec[`_${field}_deferido`];
|
|
998
|
+
const razaoOk = deferido && typeof deferido === 'object' && typeof deferido.razao === 'string' && deferido.razao.trim() !== '';
|
|
999
|
+
if (razaoOk) {
|
|
1000
|
+
console.log(`[specOrigin:derived] campo canônico "${field}" AUSENTE, mas deferido-com-registro: ` +
|
|
1001
|
+
`${deferido.razao}` +
|
|
1002
|
+
(typeof deferido.respondido_por === 'string' && deferido.respondido_por.trim() ? ` (respondido por ${deferido.respondido_por})` : '') +
|
|
1003
|
+
' — não é esquecimento, é decisão registrada (ADR-030, paga DEBT-SF12.5b-B).');
|
|
1004
|
+
} else {
|
|
1005
|
+
console.log(`[specOrigin:derived] campo canônico ausente no pod pré-existente: "${field}" ` +
|
|
1006
|
+
'(DEBT-SF12.4-B — pod anterior à forma canônica; declare ou recuse quando levá-lo à forma canônica, ADR-030 Emenda 1).');
|
|
1007
|
+
}
|
|
1008
|
+
}
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
|
|
1012
|
+
// SF12.4a/ADR-030 Bloco I (AC28) — recusa fail-loud ANTES de qualquer escrita, se algum
|
|
1013
|
+
// veto declarado não é sintetizável (mesma disciplina do Bloco C: um artefato inválido
|
|
1014
|
+
// seria pior que ausente). Só quando o Bloco I de fato vai rodar (`specOrigin:'authored'`
|
|
1015
|
+
// + workflow) — nunca penaliza specs `derived`/sem workflow por um tipo de veto que o
|
|
1016
|
+
// sintetizador não precisa satisfazer.
|
|
1017
|
+
if (specOrigin === 'authored' && wantsWorkflow && steps.length >= 1) {
|
|
1018
|
+
assertSynthesizableVeto(steps);
|
|
1019
|
+
}
|
|
1020
|
+
|
|
1021
|
+
// SF12.6/ADR-030 Bloco C+D — o `wf` é computado UMA VEZ aqui (reusado no write real do
|
|
1022
|
+
// passo (2) abaixo) para poder: (i) auto-validar a PRÓPRIA saída via checkCheckpoint
|
|
1023
|
+
// (AC25/DEBT-SF12.6-E) e (ii) recusar fail-loud ANTES de escrever QUALQUER arquivo do pod
|
|
1024
|
+
// se divergir do disco (AC11) — nem os templates que não divergiam (por isso este bloco
|
|
1025
|
+
// roda ANTES do passo (1)/templates, não junto do passo (2)/workflow).
|
|
1026
|
+
let wfContent = null;
|
|
1027
|
+
const wfPath = (wantsWorkflow && steps.length >= 1)
|
|
1028
|
+
? path.join(podDir, 'workflows', `wf-${workflowId}.yaml`)
|
|
1029
|
+
: null;
|
|
1030
|
+
if (wfPath) {
|
|
1031
|
+
wfContent = buildWorkflowYaml(workflowId, steps);
|
|
1032
|
+
try {
|
|
1033
|
+
checkCheckpoint(parseWfYaml(wfContent));
|
|
1034
|
+
} catch (e) {
|
|
1035
|
+
throw new CheckpointInvariantError(path.relative(podDir, wfPath), e.message);
|
|
1036
|
+
}
|
|
1037
|
+
// Guard por classe (Decisão 7.1): só sob `specOrigin:'derived'` (regeneração de um pod
|
|
1038
|
+
// já em disco) E path NÃO owned (harden-learning já tem autoridade de escrita mais
|
|
1039
|
+
// estreita e pré-existente via --additive + owns — o guard novo não a reabre).
|
|
1040
|
+
if (specOrigin === 'derived' && !ownsSet.has(path.resolve(wfPath)) && fs.existsSync(wfPath)) {
|
|
1041
|
+
const diskWf = fs.readFileSync(wfPath, 'utf8');
|
|
1042
|
+
if (diskWf !== wfContent) {
|
|
1043
|
+
divergent.push(wfPath);
|
|
1044
|
+
if (!allowSet.has(path.resolve(wfPath)) && !dryRun) {
|
|
1045
|
+
const diskLen = Buffer.byteLength(diskWf, 'utf8');
|
|
1046
|
+
const genLen = Buffer.byteLength(wfContent, 'utf8');
|
|
1047
|
+
throw new DivergentRegenerationError(path.relative(podDir, wfPath), diskLen, genLen, firstDivergentLine(diskWf, wfContent));
|
|
1048
|
+
}
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1053
|
+
// (1) templates por etapa — SEMPRE (SIMPLE gera só das 1–2 saídas; a spec já limita a
|
|
1054
|
+
// contagem de agentes de um SIMPLE a ≤2). Disco-first. SF12.6/ADR-030 Bloco C: sob
|
|
1055
|
+
// `specOrigin:'derived'` (e path NÃO owned), um template pré-existente cujo conteúdo
|
|
1056
|
+
// diverge do que seria gerado é PRESERVADO por default (preserved[]) — nunca sobrescrito
|
|
1057
|
+
// numa regeneração (write-once, mesmo regime que generatePod já pratica em 5 outros
|
|
1058
|
+
// lugares — stub de agente, sidecar de smoke, sidecar de cenário, dono.example, README).
|
|
1059
|
+
for (const step of steps) {
|
|
1060
|
+
const tpl = path.join(podDir, 'templates', `${step.id}-tmpl.md`);
|
|
1061
|
+
const tplContent = stepTemplate(step.id, step.agent, step.output, step.sections, step.veto, stepExtraFields.get(step.id));
|
|
1062
|
+
if (specOrigin === 'derived' && !ownsSet.has(path.resolve(tpl)) && fs.existsSync(tpl)) {
|
|
1063
|
+
const diskTpl = fs.readFileSync(tpl, 'utf8');
|
|
1064
|
+
if (diskTpl !== tplContent) {
|
|
1065
|
+
preserved.push(tpl);
|
|
1066
|
+
continue; // preserva-por-default — não sobrescreve conteúdo curado.
|
|
1067
|
+
}
|
|
1068
|
+
}
|
|
1069
|
+
guardCollision(tpl, 'template/step-id'); // colisão de step-id/output fora de owns[] ⇒ exit 2
|
|
1070
|
+
emit(tpl, tplContent);
|
|
1071
|
+
written.push(tpl);
|
|
1072
|
+
}
|
|
1073
|
+
|
|
1074
|
+
// (2) workflow — `wfPath`/`wfContent` já computados acima (hoisted para o Bloco C/D/I).
|
|
1075
|
+
if (wfPath) {
|
|
1076
|
+
guardCollision(wfPath, 'workflowId'); // colisão de workflowId fora de owns[] ⇒ exit 2
|
|
1077
|
+
emit(wfPath, wfContent);
|
|
1078
|
+
written.push(wfPath);
|
|
1079
|
+
} else if (isInterface) {
|
|
1080
|
+
skipped.push('wf/squad-io (pod-interface: zero handoff interno; enforcement do crítico no ciclo pc-*)');
|
|
1081
|
+
} else {
|
|
1082
|
+
skipped.push('workflow (comando/agente único no caminho SIMPLE)');
|
|
1083
|
+
}
|
|
1084
|
+
|
|
1085
|
+
// SF12.4a/ADR-030 Bloco I — o preço do L1: stub de agente (AC26) + sidecar de smoke
|
|
1086
|
+
// sintetizado (AC27/AC27b/AC28). Só quando o pod TEM workflow (é o motor que consome
|
|
1087
|
+
// leitura obrigatória + smoke) E `specOrigin: 'authored'` (Decisão 7/Emenda 1: os
|
|
1088
|
+
// caminhos `derived` — `*upgrade-pod`/`*harden-learning` — PRESERVAM o comportamento
|
|
1089
|
+
// atual; impor a obrigação nova do L1 sobre um pod pré-existente regenerado é
|
|
1090
|
+
// exatamente o risco de regressão que a Decisão 7 existe para evitar — AC49/AC50).
|
|
1091
|
+
// Nunca sobrescreve artefato já existente (mesma guarda do README de memória) —
|
|
1092
|
+
// hand-authored (`*migrate-squad` F2a, `wf-forge.smoke.json`) ou já gerado antes,
|
|
1093
|
+
// preservado byte-a-byte.
|
|
1094
|
+
if (specOrigin === 'authored' && wantsWorkflow && steps.length >= 1) {
|
|
1095
|
+
const distinctAgents = [...new Set(steps.map((s) => s.agent))];
|
|
1096
|
+
for (const agentName of distinctAgents) {
|
|
1097
|
+
const agentPath = path.join(podDir, 'agents', `${agentName}.md`);
|
|
1098
|
+
if (!fs.existsSync(agentPath)) {
|
|
1099
|
+
emit(agentPath, agentStubContent(agentName));
|
|
1100
|
+
written.push(agentPath);
|
|
1101
|
+
}
|
|
1102
|
+
}
|
|
1103
|
+
|
|
1104
|
+
const lastStep = steps[steps.length - 1];
|
|
1105
|
+
const basePath = path.join(podDir, 'workflows', `wf-${workflowId}.smoke.json`);
|
|
1106
|
+
if (!fs.existsSync(basePath)) {
|
|
1107
|
+
// AC28 já foi checado ANTES de qualquer escrita (ver acima, hoisted com `wantsWorkflow`).
|
|
1108
|
+
emit(basePath, `${JSON.stringify(buildSmokeSidecar(steps, lastStep.id), null, 2)}\n`);
|
|
1109
|
+
written.push(basePath);
|
|
1110
|
+
}
|
|
1111
|
+
|
|
1112
|
+
// AC27b — 1 cenário adicional por mecanismo EMITIDO (contrato_entrada/entregavel),
|
|
1113
|
+
// SEMPRE — inclusive quando a etapa-alvo É a última: o sidecar BASE (AC27) quebra a
|
|
1114
|
+
// 1ª assertion da etapa por padrão (âncora L1 genérica), que pode NÃO ser a do
|
|
1115
|
+
// mecanismo; o cenário nomeado do AC27b é o único que GARANTE nomear a assertion do
|
|
1116
|
+
// mecanismo especificamente (AC39/AC40 dependem disso, `target_step` escalar único,
|
|
1117
|
+
// `smoke-pod-workflow.cjs:219`).
|
|
1118
|
+
const mechanismSteps = [];
|
|
1119
|
+
if (canonicalStatus.contrato_entrada.mode === 'declared' && canonicalStatus.contrato_entrada.step) {
|
|
1120
|
+
mechanismSteps.push({ field: 'contrato_entrada', stepId: canonicalStatus.contrato_entrada.step });
|
|
1121
|
+
}
|
|
1122
|
+
if (canonicalStatus.entregavel.mode === 'declared' && canonicalStatus.entregavel.step) {
|
|
1123
|
+
mechanismSteps.push({ field: 'entregavel', stepId: canonicalStatus.entregavel.step });
|
|
1124
|
+
}
|
|
1125
|
+
for (const m of mechanismSteps) {
|
|
1126
|
+
if (!steps.some((s) => s.id === m.stepId)) continue;
|
|
1127
|
+
const scenarioPath = path.join(podDir, 'workflows', `wf-${workflowId}.${m.stepId}.smoke.json`);
|
|
1128
|
+
if (!fs.existsSync(scenarioPath)) {
|
|
1129
|
+
emit(scenarioPath, `${JSON.stringify(buildSmokeSidecar(steps, m.stepId, m.field), null, 2)}\n`);
|
|
1130
|
+
written.push(scenarioPath);
|
|
1131
|
+
}
|
|
1132
|
+
}
|
|
1133
|
+
}
|
|
1134
|
+
|
|
1135
|
+
// (3) squad-io.yaml — SÓ STANDARD/COMPLEX NÃO-interface (SIMPLE e interface não
|
|
1136
|
+
// recebem contrato de handoff). SF12.6/ADR-030 Bloco C (Decisão 7.1, classe
|
|
1137
|
+
// squad-io.yaml → sempre-merge): squad-io pré-existente NUNCA é colisão nem
|
|
1138
|
+
// sobrescrita — SEMPRE faz MERGE por seção (C.2) quando o arquivo já existe, em
|
|
1139
|
+
// QUALQUER modo (a exigência de `additive:true` foi removida — `*upgrade-pod` nunca
|
|
1140
|
+
// passava `additive`, e por isso squad-io era sobrescrito incondicionalmente numa
|
|
1141
|
+
// regeneração, AC15).
|
|
1142
|
+
if (tier !== TIER_SIMPLE && !isInterface) {
|
|
1143
|
+
const io = path.join(podDir, 'squad-io.yaml');
|
|
1144
|
+
const content = buildSquadIo(spec.pod || workflowId, steps);
|
|
1145
|
+
// squad-io é POD-GLOBAL (agrega handoffs de TODOS os workflows). Merge é idempotente
|
|
1146
|
+
// (união dedup), seguro na retomada — nunca perde os handoffs/seções de outros workflows
|
|
1147
|
+
// nem conteúdo customizado hand-authored fora do gerador.
|
|
1148
|
+
if (fs.existsSync(io)) {
|
|
1149
|
+
emit(io, mergeSquadIo(fs.readFileSync(io, 'utf8'), content));
|
|
1150
|
+
merged.push(io);
|
|
1151
|
+
} else {
|
|
1152
|
+
emit(io, content);
|
|
1153
|
+
written.push(io);
|
|
1154
|
+
}
|
|
1155
|
+
} else if (!isInterface) {
|
|
1156
|
+
skipped.push('squad-io.yaml (SIMPLE não recebe contrato de handoff)');
|
|
1157
|
+
} // isInterface: wf+squad-io já cobertos pela mensagem combinada em (2).
|
|
1158
|
+
|
|
1159
|
+
// (4) MEMÓRIA OPERANTE proporcional ao tier (SF2.3, SPEC §G1/§G2). Substitui o
|
|
1160
|
+
// scaffold morto da SF1.4: a fábrica INSTALA a memória usando o MOTOR REAL
|
|
1161
|
+
// (pod-memory.cjs, SF2.1) — não hand-escreve um cabeçalho. O pod NASCE cabeado ao
|
|
1162
|
+
// mecanismo que o runner (SF2.2) consome: o consolidado fica no path canônico
|
|
1163
|
+
// (`memory/learnings.consolidated.md`, lido por getConsolidatedForBrief) e o log cru
|
|
1164
|
+
// (`memory/learnings.jsonl`) já existe para o append da run. "Operância" = o pod está
|
|
1165
|
+
// corretamente ligado ao motor pronto — nenhuma ação manual entre gerar e rodar.
|
|
1166
|
+
// STANDARD/COMPLEX → sempre. SIMPLE → só sob pedido explícito (withMemory).
|
|
1167
|
+
if (tier !== TIER_SIMPLE || withMemory) {
|
|
1168
|
+
written.push(...installMemory(spec.pod || workflowId, podDir, tier, { additive, dryRun }));
|
|
1169
|
+
} else {
|
|
1170
|
+
skipped.push('memória (SIMPLE sem --with-memory/spec.memoryOpt — opcional, não gerada)');
|
|
1171
|
+
}
|
|
1172
|
+
|
|
1173
|
+
// SF12.4a/ADR-030 Bloco J (AC29-32) — camada do dono, SÓ placeholder, quando
|
|
1174
|
+
// `escada_de_voz` indica pod de CONTEÚDO (qualquer valor ≠ "sem_voz"/ausente).
|
|
1175
|
+
// NUNCA escreve instância (`dono.yaml`) — `DF-SF12.4-6`, vinculante.
|
|
1176
|
+
const escadaDeVoz = ESCADA_DE_VOZ_VALUES.has(spec.escada_de_voz) ? spec.escada_de_voz : 'sem_voz';
|
|
1177
|
+
if (escadaDeVoz !== 'sem_voz') {
|
|
1178
|
+
const donoPath = path.join(podDir, 'knowledge', 'dono.example.yaml');
|
|
1179
|
+
if (!fs.existsSync(donoPath)) {
|
|
1180
|
+
emit(donoPath, donoExampleContent(spec.pod || workflowId));
|
|
1181
|
+
written.push(donoPath);
|
|
1182
|
+
}
|
|
1183
|
+
}
|
|
1184
|
+
|
|
1185
|
+
// SF12.4a/ADR-030 Bloco F (AC18-19) — pod-spec.json persistido na raiz do pod (fonte
|
|
1186
|
+
// da verdade, ADR-005; generalização da persistência que `*harden-learning` já fazia).
|
|
1187
|
+
// `_canonical_status` torna a recusa/anúncio/relato AUDITÁVEL por grep no artefato.
|
|
1188
|
+
if (!dryRun) {
|
|
1189
|
+
const specDest = path.join(podDir, 'pod-spec.json');
|
|
1190
|
+
const persisted = { ...spec, _canonical_status: canonicalStatus };
|
|
1191
|
+
emit(specDest, `${JSON.stringify(persisted, null, 2)}\n`);
|
|
1192
|
+
written.push(specDest);
|
|
1193
|
+
}
|
|
1194
|
+
|
|
1195
|
+
return { tier, workflowId, steps, written, skipped, merged, divergent, preserved, canonicalStatus, ritmoAnnouncement };
|
|
1196
|
+
}
|
|
1197
|
+
|
|
1198
|
+
/**
|
|
1199
|
+
* Instala a memória OPERANTE do pod usando o motor real pod-memory.cjs (SF2.1).
|
|
1200
|
+
* Cria o log cru vazio e materializa o consolidado canônico via `consolidate()` — o
|
|
1201
|
+
* mesmo caminho que o lifecycle usa em produção, provando que o pod está cabeado ao
|
|
1202
|
+
* motor (não a um cabeçalho morto). Idempotente. Retorna os paths gravados.
|
|
1203
|
+
*/
|
|
1204
|
+
function installMemory(pod, podDir, tier, opts = {}) {
|
|
1205
|
+
const additive = Boolean(opts.additive);
|
|
1206
|
+
const dryRun = Boolean(opts.dryRun);
|
|
1207
|
+
const paths = podMemory.memoryPaths(pod, { podDir });
|
|
1208
|
+
const readme = path.join(paths.dir, 'README.md');
|
|
1209
|
+
// dry-run (SF6.2): retorna o PLANO (paths que SERIAM gravados) sem tocar disco.
|
|
1210
|
+
if (dryRun) return [paths.raw, paths.consolidated, readme];
|
|
1211
|
+
fs.mkdirSync(paths.dir, { recursive: true });
|
|
1212
|
+
// Log cru vazio (append-only, alimentado pelo runner). Não sobrescreve se já existir
|
|
1213
|
+
// (upgrade-in-place preserva histórico da SF2.1 — o runner é o único escritor).
|
|
1214
|
+
if (!fs.existsSync(paths.raw)) fs.writeFileSync(paths.raw, '');
|
|
1215
|
+
// Consolidado canônico via o MOTOR. No modo aditivo, um consolidado JÁ EXISTENTE é
|
|
1216
|
+
// PRESERVADO byte-a-byte (a migração NUNCA escreve na memória do pod destino, ADR-006
|
|
1217
|
+
// §F; o critério de integridade compara sha256 de learnings.jsonl+.consolidated.md).
|
|
1218
|
+
if (!(additive && fs.existsSync(paths.consolidated))) podMemory.consolidate(pod, { podDir });
|
|
1219
|
+
// Marcador de cabeamento. GUARD DE EXISTÊNCIA (M2, fecha o furo do sobrescrever
|
|
1220
|
+
// incondicional): README pré-existente NÃO é sobrescrito — preserva conteúdo curado.
|
|
1221
|
+
if (!fs.existsSync(readme)) fs.writeFileSync(readme, memoryWiringNote(pod, tier));
|
|
1222
|
+
return [paths.raw, paths.consolidated, readme];
|
|
1223
|
+
}
|
|
1224
|
+
|
|
1225
|
+
/** Nota de cabeamento da memória — prova documental de operância (SF2.3). */
|
|
1226
|
+
function memoryWiringNote(pod, tier) {
|
|
1227
|
+
return `# Memória do pod \`${pod}\` — OPERANTE (SF2.3)
|
|
1228
|
+
|
|
1229
|
+
> Instalada por forge-generate.cjs via o motor \`scripts/pod-memory.cjs\` (SF2.1).
|
|
1230
|
+
> Tier: ${tier}. Esta memória NÃO é scaffold morto: nasce cabeada ao lifecycle.
|
|
1231
|
+
|
|
1232
|
+
## Cabeamento (nenhuma ação manual necessária)
|
|
1233
|
+
|
|
1234
|
+
- \`learnings.jsonl\` — log cru append-only. O runner (\`pc-run-pod\`, SF2.2) faz \`append()\`
|
|
1235
|
+
ao fim de cada etapa/run. O AGENTE NUNCA lê este arquivo (auditoria apenas).
|
|
1236
|
+
- \`learnings.consolidated.md\` — top-N curado (\`consolidate()\`), no path canônico que
|
|
1237
|
+
\`getConsolidatedForBrief()\` entrega ao brief da run. É o ÚNICO que o agente lê.
|
|
1238
|
+
- Auditoria de aderência por run: \`config/heuristic-watcher-rubric.yaml\` +
|
|
1239
|
+
\`scripts/watcher-audit.cjs\` (executado pelo \`pod-critic\`).
|
|
1240
|
+
|
|
1241
|
+
Assim que este pod rodar via \`pc-run-pod\`, injeção do consolidado no brief e append de
|
|
1242
|
+
learnings funcionam sem qualquer passo manual — a operância é o cabeamento ao motor pronto.
|
|
1243
|
+
`;
|
|
1244
|
+
}
|
|
1245
|
+
|
|
1246
|
+
function main() {
|
|
1247
|
+
const args = process.argv.slice(2);
|
|
1248
|
+
let out = null;
|
|
1249
|
+
let withMemory = false;
|
|
1250
|
+
let additive = false;
|
|
1251
|
+
const owns = [];
|
|
1252
|
+
const rest = [];
|
|
1253
|
+
for (let i = 0; i < args.length; i++) {
|
|
1254
|
+
if (args[i] === '--out') out = args[++i];
|
|
1255
|
+
else if (args[i] === '--with-memory') withMemory = true;
|
|
1256
|
+
else if (args[i] === '--additive') additive = true;
|
|
1257
|
+
else if (args[i] === '--owns') owns.push(...String(args[++i]).split(','));
|
|
1258
|
+
else rest.push(args[i]);
|
|
1259
|
+
}
|
|
1260
|
+
if (!rest[0] || !out) {
|
|
1261
|
+
console.error('Uso: node scripts/forge-generate.cjs <pod-spec.json> --out <podDir> [--with-memory] [--additive] [--owns p1,p2]');
|
|
1262
|
+
process.exit(2);
|
|
1263
|
+
}
|
|
1264
|
+
const spec = JSON.parse(fs.readFileSync(rest[0], 'utf8'));
|
|
1265
|
+
let r;
|
|
1266
|
+
try {
|
|
1267
|
+
// SF12.4a/ADR-030 Bloco O (AC48) — o CLI é SEMPRE 'authored', incondicional: serve
|
|
1268
|
+
// `*new` (sem flag) E `*migrate-squad` F2b (`--additive`). `--additive` governa só a
|
|
1269
|
+
// guarda de colisão (ortogonal) — NÃO muda `specOrigin`.
|
|
1270
|
+
r = generatePod(spec, path.resolve(out), { withMemory, additive, owns, specOrigin: 'authored' });
|
|
1271
|
+
} catch (e) {
|
|
1272
|
+
console.error(`[erro] ${e.message}`);
|
|
1273
|
+
process.exit(e && e.exitCode ? e.exitCode : 1);
|
|
1274
|
+
}
|
|
1275
|
+
console.log(`tier: ${r.tier}`);
|
|
1276
|
+
console.log('artefatos gravados (disco-first):');
|
|
1277
|
+
for (const w of r.written) console.log(` + ${path.relative(process.cwd(), w)}`);
|
|
1278
|
+
if (r.merged && r.merged.length) {
|
|
1279
|
+
console.log('merge aditivo (seções preservadas + novas):');
|
|
1280
|
+
for (const m of r.merged) console.log(` ~ ${path.relative(process.cwd(), m)}`);
|
|
1281
|
+
}
|
|
1282
|
+
if (r.skipped.length) {
|
|
1283
|
+
console.log('não gerados (proporcionalidade):');
|
|
1284
|
+
for (const s of r.skipped) console.log(` - ${s}`);
|
|
1285
|
+
}
|
|
1286
|
+
process.exit(0);
|
|
1287
|
+
}
|
|
1288
|
+
|
|
1289
|
+
if (require.main === module) main();
|
|
1290
|
+
|
|
1291
|
+
module.exports = {
|
|
1292
|
+
generatePod, installMemory, stepsFromSpec, buildWorkflowYaml, buildSquadIo, slug, stepTemplate,
|
|
1293
|
+
countGteNote, countGteFields, serializeAssertion, serializeCheckpoint, mergeSquadIo,
|
|
1294
|
+
extractHandoffTriples, splitTopSections, AdditiveCollisionError, InterfaceContractError,
|
|
1295
|
+
// SF12.4a/ADR-030
|
|
1296
|
+
SpecOriginError, CanonicalFieldError, KnowledgeFileMissingError, UnsynthesizableVetoError,
|
|
1297
|
+
validateSpecOrigin, readCanonicalField, computeCanonicalStatus, CANONICAL_FIELDS,
|
|
1298
|
+
deriveFieldMatchesValue, synthesizeArtifactText, buildSmokeSidecar, agentStubContent,
|
|
1299
|
+
donoExampleContent, ESCADA_DE_VOZ_VALUES, assertKnowledgeExists, assertSynthesizableVeto,
|
|
1300
|
+
// SF12.6/ADR-030 Bloco C/D
|
|
1301
|
+
DivergentRegenerationError, AllowDivergentOverwriteError, CheckpointInvariantError,
|
|
1302
|
+
validateAllowDivergentOverwrite, validateCanonicalStepRef, firstDivergentLine,
|
|
1303
|
+
};
|