@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,358 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
/**
|
|
4
|
+
* owner-layer.test.cjs — ORÁCULO DA FRONTEIRA DO PUBLICÁVEL (Story SF12.1).
|
|
5
|
+
* AC1 · AC2 · AC3 · AC4 · AC9 · AC12. Fonte de desenho: ADR-027 (Decisões 1, 3, 8).
|
|
6
|
+
*
|
|
7
|
+
* O QUE ESTE TESTE PROVA — e o que ele NÃO prova. Declarado aqui porque um AC (ou
|
|
8
|
+
* um teste) que promete mais que o instrumento é a classe de defeito que o SF11
|
|
9
|
+
* pagou nove vezes:
|
|
10
|
+
*
|
|
11
|
+
* ✓ CONTENÇÃO (AC12): nenhum arquivo do TARBALL REAL dentro da camada declarada
|
|
12
|
+
* em `owner-layer.cjs` é publicado sem ser um placeholder `*.example.*` com
|
|
13
|
+
* sentinela — ou um não-`.example.` explicitamente permitido por path, com a
|
|
14
|
+
* sua regra verificada. Pega dado NÃO ENUMERADO dentro da camada, inclusive
|
|
15
|
+
* DNA de terceiro extraído pelo `mind-extractor`.
|
|
16
|
+
* ✗ NÃO pega nada FORA da camada.
|
|
17
|
+
*
|
|
18
|
+
* ✓ ENUMERAÇÃO (AC1/AC9): nenhum dos padrões JÁ CLASSIFICADOS em
|
|
19
|
+
* `owner-layer.cjs` aparece em qualquer arquivo do tarball fora da allowlist
|
|
20
|
+
* por path exato.
|
|
21
|
+
* ✗ NÃO prova ausência de "qualquer dado pessoal": a régua do dono é
|
|
22
|
+
* ENUMERÁVEL POR DESENHO. Dado pessoal não tem gramática sintática (um nome
|
|
23
|
+
* próprio, um cargo ou uma tese editorial não têm forma reconhecível, ao
|
|
24
|
+
* contrário de `sk-`/`AKIA`/`ghp_`). Uma varredura heurística ("parece nome
|
|
25
|
+
* próprio?") foi REJEITADA por doutrina no ADR-027 Decisão 3, por ser
|
|
26
|
+
* impossível declarar seu alcance honestamente.
|
|
27
|
+
*
|
|
28
|
+
* ✗ LACUNA RESIDUAL, SEM INSTRUMENTO: dado pessoal NÃO ENUMERADO e FORA da
|
|
29
|
+
* camada. `DEBT-SF12.1-B`. Nenhum AC da SF12.1 afirma cobri-la. Só leitura
|
|
30
|
+
* humana a pega; centralizar (mover o dado para dentro da camada) é o que a
|
|
31
|
+
* encolhe.
|
|
32
|
+
*
|
|
33
|
+
* MÉTODO: `npm pack` de VERDADE (nunca `--dry-run` — precisamos do CONTEÚDO de
|
|
34
|
+
* cada arquivo, não da lista de nomes), extração para diretório temporário,
|
|
35
|
+
* varredura do conteúdo extraído. É o tarball que vai para o registry, não uma
|
|
36
|
+
* simulação da árvore de trabalho.
|
|
37
|
+
*
|
|
38
|
+
* KILL-TESTS (AC2 e AC12): um oráculo verde só no caminho feliz não prova nada.
|
|
39
|
+
* As injeções abaixo passam pelas MESMAS funções (`scanEnumeration`/
|
|
40
|
+
* `scanContainment`) e sobre os MESMOS arquivos reais do tarball, acrescidos de
|
|
41
|
+
* uma entrada violadora — não sobre um conjunto sintético que poderia divergir do
|
|
42
|
+
* caminho real.
|
|
43
|
+
*
|
|
44
|
+
* Node >= 18, zero dependências. Exit 0/1.
|
|
45
|
+
*/
|
|
46
|
+
|
|
47
|
+
const fs = require('node:fs');
|
|
48
|
+
const os = require('node:os');
|
|
49
|
+
const path = require('node:path');
|
|
50
|
+
const { execFileSync } = require('node:child_process');
|
|
51
|
+
|
|
52
|
+
const OL = require('./owner-layer.cjs');
|
|
53
|
+
const { parseYaml } = require('./validate-pod-workflow.cjs');
|
|
54
|
+
|
|
55
|
+
const REPO_ROOT = path.resolve(__dirname, '..');
|
|
56
|
+
|
|
57
|
+
// ---------------------------------------------------------------------------
|
|
58
|
+
// Este arquivo de teste é PUBLICADO (`scripts/` está em package.json.files) e NÃO
|
|
59
|
+
// está na allowlist do AC2 — por desenho: acrescentá-lo alargaria a fronteira que
|
|
60
|
+
// a story existe para estreitar. Consequência: o teste NUNCA escreve os literais
|
|
61
|
+
// perigosos. Ele os obtém por PAPEL (`patternByRole`) e verifica o manifesto
|
|
62
|
+
// contra uma fonte independente e já isenta — o campo `author` de `package.json`.
|
|
63
|
+
// Efeito colateral desejado: se o manifesto divergir do dono real do pacote, o
|
|
64
|
+
// teste falha alto em vez de proteger a pessoa errada em silêncio.
|
|
65
|
+
// ---------------------------------------------------------------------------
|
|
66
|
+
const PKG = require('../package.json');
|
|
67
|
+
const NOME_CIVIL = PKG.author;
|
|
68
|
+
const SLUG = String(PKG.author).toLowerCase().replace(/\s+/g, '-');
|
|
69
|
+
|
|
70
|
+
let passed = 0;
|
|
71
|
+
const failures = [];
|
|
72
|
+
function ok(cond, label) {
|
|
73
|
+
if (cond) { passed++; console.log(`[OK ] ${label}`); }
|
|
74
|
+
else { failures.push(label); console.log(`[FALHA] ${label}`); }
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function fmtEnum(v) {
|
|
78
|
+
return v.map((x) => ` ${x.path}:${x.line} → "${x.pattern}" | ${x.excerpt}`).join('\n');
|
|
79
|
+
}
|
|
80
|
+
function fmtCont(v) {
|
|
81
|
+
return v.map((x) => ` ${x.path} [camada ${x.layer}] → ${x.reason}`).join('\n');
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// ===========================================================================
|
|
85
|
+
// Setup — `npm pack` real + extração.
|
|
86
|
+
// ===========================================================================
|
|
87
|
+
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'pc-owner-layer-'));
|
|
88
|
+
let tarball;
|
|
89
|
+
try {
|
|
90
|
+
const out = execFileSync('npm', ['pack', '--pack-destination', tmp, '--silent'], {
|
|
91
|
+
cwd: REPO_ROOT,
|
|
92
|
+
encoding: 'utf8',
|
|
93
|
+
stdio: ['ignore', 'pipe', 'inherit'],
|
|
94
|
+
});
|
|
95
|
+
tarball = path.join(tmp, out.trim().split(/\r?\n/).filter(Boolean).pop());
|
|
96
|
+
} catch (e) {
|
|
97
|
+
console.error('[FATAL] `npm pack` falhou — sem tarball não há oráculo.', e.message);
|
|
98
|
+
process.exit(1);
|
|
99
|
+
}
|
|
100
|
+
const extractDir = path.join(tmp, 'x');
|
|
101
|
+
fs.mkdirSync(extractDir);
|
|
102
|
+
execFileSync('tar', ['-xzf', tarball, '-C', extractDir], { stdio: 'inherit' });
|
|
103
|
+
|
|
104
|
+
/** Lê todo arquivo extraído como { path (lógico, sem `package/`), content }. */
|
|
105
|
+
function collect(dir, base = '') {
|
|
106
|
+
const out = [];
|
|
107
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
108
|
+
const abs = path.join(dir, entry.name);
|
|
109
|
+
const rel = base ? `${base}/${entry.name}` : entry.name;
|
|
110
|
+
if (entry.isDirectory()) out.push(...collect(abs, rel));
|
|
111
|
+
else if (entry.isFile()) {
|
|
112
|
+
const buf = fs.readFileSync(abs);
|
|
113
|
+
// Guarda de binário: um arquivo com NUL não é varrível por string.
|
|
114
|
+
const binary = buf.subarray(0, 8000).includes(0);
|
|
115
|
+
out.push({ path: OL.stripPackagePrefix(rel), content: binary ? '' : buf.toString('utf8'), binary });
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return out;
|
|
119
|
+
}
|
|
120
|
+
const files = collect(extractDir);
|
|
121
|
+
console.log(`\n[info ] tarball: ${path.basename(tarball)} — ${files.length} arquivos publicados`);
|
|
122
|
+
console.log(`[info ] binários (não varridos por string): ${files.filter((f) => f.binary).length}`);
|
|
123
|
+
|
|
124
|
+
ok(files.length > 100, `tarball extraído tem conteúdo real (${files.length} arquivos)`);
|
|
125
|
+
|
|
126
|
+
// ===========================================================================
|
|
127
|
+
// AC1 + AC9 — ENUMERAÇÃO sobre o tarball real: zero fora da allowlist.
|
|
128
|
+
// ===========================================================================
|
|
129
|
+
const enumViolations = OL.scanEnumeration(files);
|
|
130
|
+
if (enumViolations.length > 0) {
|
|
131
|
+
console.log(' Ocorrências de dado pessoal ENUMERADO fora da allowlist:');
|
|
132
|
+
console.log(fmtEnum(enumViolations));
|
|
133
|
+
}
|
|
134
|
+
ok(enumViolations.length === 0,
|
|
135
|
+
`AC1/AC9 — enumeração: zero ocorrências dos ${OL.patterns.length} padrões fora da allowlist de ${OL.enumerationAllowlist.length} paths`);
|
|
136
|
+
|
|
137
|
+
// Cada padrão declarado tem os 3 campos exigidos pelo AC1.
|
|
138
|
+
ok(OL.patterns.every((p) => p.pattern && p.description && p.origin),
|
|
139
|
+
'AC1 — todo padrão declara pattern/description/origin (proveniência do achado)');
|
|
140
|
+
// Os 3 padrões mínimos do AC1, verificados SEM escrever os literais:
|
|
141
|
+
// • nome civil → tem de bater com `package.json.author` (fonte independente);
|
|
142
|
+
// • slug → tem de ser a slugificação desse mesmo nome;
|
|
143
|
+
// • path de máquina → tem de ser um path `/Users/<usuário>` não-degenerado.
|
|
144
|
+
ok(OL.patternByRole('nome-civil') === NOME_CIVIL,
|
|
145
|
+
'AC1 — padrão de NOME CIVIL declarado e igual ao `author` de package.json (fonte independente)');
|
|
146
|
+
ok(OL.patternByRole('slug') === SLUG,
|
|
147
|
+
'AC1 — padrão de SLUG hifenizado declarado e derivado do mesmo nome (cobre nome de arquivo)');
|
|
148
|
+
const pathPat = OL.patternByRole('path-maquina');
|
|
149
|
+
ok(pathPat.startsWith('/Users/') && pathPat.length > '/Users/'.length + 3,
|
|
150
|
+
'AC1 — padrão de PATH DE MÁQUINA pessoal declarado (`/Users/<usuário>`, não-degenerado)');
|
|
151
|
+
|
|
152
|
+
// ===========================================================================
|
|
153
|
+
// AC2 — KILL-TEST da enumeração: a allowlist é restrita, não escotilha.
|
|
154
|
+
// ===========================================================================
|
|
155
|
+
const killEnumFiles = files.concat([
|
|
156
|
+
{ path: 'scripts/__killtest__.md', content: `Escrito por ${NOME_CIVIL} em 2026.\n` },
|
|
157
|
+
]);
|
|
158
|
+
// Como nos kill-tests de contenção: a asserção é sobre a INJEÇÃO, não sobre a
|
|
159
|
+
// contagem global — a limpeza da linha de base é a asserção AC1/AC9 acima.
|
|
160
|
+
const killEnum = OL.scanEnumeration(killEnumFiles);
|
|
161
|
+
ok(killEnum.some((v) => v.path === 'scripts/__killtest__.md'),
|
|
162
|
+
'AC2 kill-test — arquivo NOVO fora da allowlist contendo o nome civil do dono é PEGO (oráculo não é decorativo)');
|
|
163
|
+
|
|
164
|
+
// A allowlist casa por path EXATO: um vizinho de nome parecido não herda a isenção.
|
|
165
|
+
const killNear = OL.scanEnumeration(files.concat([
|
|
166
|
+
{ path: 'scripts/owner-layer.cjs.bak', content: `pattern: "${NOME_CIVIL}"\n` },
|
|
167
|
+
{ path: 'template/README.md', content: `Copyright ${NOME_CIVIL}\n` },
|
|
168
|
+
]));
|
|
169
|
+
ok(['scripts/owner-layer.cjs.bak', 'template/README.md'].every((p) => killNear.some((v) => v.path === p)),
|
|
170
|
+
'AC2 kill-test — allowlist é por PATH EXATO: `scripts/owner-layer.cjs.bak` e `template/README.md` NÃO herdam isenção');
|
|
171
|
+
|
|
172
|
+
// E o oráculo se protege de si mesmo: o arquivo de declaração É publicado e ESTÁ isento.
|
|
173
|
+
const declInTarball = files.find((f) => f.path === 'scripts/owner-layer.cjs');
|
|
174
|
+
ok(!!declInTarball, 'AC2 — o manifesto `scripts/owner-layer.cjs` é publicado (está em package.json.files)');
|
|
175
|
+
ok(!!declInTarball && OL.patterns.every((p) => declInTarball.content.includes(p.pattern)),
|
|
176
|
+
'AC2 — o manifesto contém, de fato, todos os literais perigosos (é a lista deles) — a isenção não é teórica');
|
|
177
|
+
|
|
178
|
+
// ===========================================================================
|
|
179
|
+
// AC12 — CONTENÇÃO sobre o tarball real.
|
|
180
|
+
// ===========================================================================
|
|
181
|
+
const inLayer = files.filter((f) => OL.layerOf(f.path));
|
|
182
|
+
console.log(`[info ] arquivos do tarball dentro da camada do dono: ${inLayer.length}`);
|
|
183
|
+
for (const f of inLayer) console.log(` · ${f.path} [${OL.layerOf(f.path).id}]`);
|
|
184
|
+
|
|
185
|
+
const contViolations = OL.scanContainment(files);
|
|
186
|
+
if (contViolations.length > 0) {
|
|
187
|
+
console.log(' Violações de contenção:');
|
|
188
|
+
console.log(fmtCont(contViolations));
|
|
189
|
+
}
|
|
190
|
+
ok(contViolations.length === 0,
|
|
191
|
+
`AC12 — contenção: os ${inLayer.length} arquivos da camada são placeholder com sentinela ou não-example permitido com regra verificada`);
|
|
192
|
+
ok(inLayer.length > 0, 'AC12 — a camada declarada de fato alcança arquivos do tarball (manifesto não é letra morta)');
|
|
193
|
+
// GUARDA DE REGRESSÃO DO MANIFESTO (AC17c). Igualdade de CONJUNTO, não `>=`: uma
|
|
194
|
+
// camada removida do manifesto reabriria o canal correspondente **com a suíte verde**,
|
|
195
|
+
// e os kill-tests abaixo não pegariam isso — eles provam o estado atual, não que a
|
|
196
|
+
// declaração continua existindo. O acoplamento é de propósito nos DOIS sentidos:
|
|
197
|
+
// remover uma camada falha aqui, e ACRESCENTAR uma também — camada nova sem kill-test
|
|
198
|
+
// próprio é exatamente o defeito que esta asserção existe para impedir.
|
|
199
|
+
const CAMADAS_EXIGIDAS = ['voice-dna', 'dono-do-pod', 'exemplos-do-pod', 'memoria-do-pod'];
|
|
200
|
+
const idsDeclarados = OL.layers.map((l) => l.id).sort();
|
|
201
|
+
const idsExigidos = CAMADAS_EXIGIDAS.slice().sort();
|
|
202
|
+
ok(idsDeclarados.length === idsExigidos.length && idsDeclarados.every((id, i) => id === idsExigidos[i]),
|
|
203
|
+
`AC12/AC17c — o manifesto declara EXATAMENTE as ${CAMADAS_EXIGIDAS.length} camadas com kill-test próprio\n` +
|
|
204
|
+
` exigidas: ${idsExigidos.join(', ')}\n declaradas: ${idsDeclarados.join(', ')}`);
|
|
205
|
+
// E cada camada exigida precisa ter kill-test — conferido por construção logo abaixo.
|
|
206
|
+
|
|
207
|
+
// KILL-TESTS da contenção — cada um sobre os arquivos REAIS + uma injeção.
|
|
208
|
+
const OLD_DNA_PATH = `template/knowledge/voice-dna/${SLUG}-voice-dna.yaml`;
|
|
209
|
+
const killCont = [
|
|
210
|
+
{
|
|
211
|
+
label: 'AC12 kill-test (a) — DNA real do dono de volta na camada (sem `.example.`) é PEGO',
|
|
212
|
+
inject: { path: OLD_DNA_PATH, content: 'meta:\n owner: "alguem"\n' },
|
|
213
|
+
},
|
|
214
|
+
{
|
|
215
|
+
label: 'AC12 kill-test (b) — DNA de TERCEIRO extraído pelo mind-extractor é PEGO sem enumerar nada',
|
|
216
|
+
inject: { path: 'template/knowledge/voice-dna/fulano-de-tal-dna.yaml', content: 'meta:\n owner: "Fulano de Tal"\n' },
|
|
217
|
+
},
|
|
218
|
+
{
|
|
219
|
+
label: 'AC12 kill-test (c) — `.example.` SEM sentinela é PEGO (a sentinela é exigida, não sugerida)',
|
|
220
|
+
inject: { path: 'template/knowledge/voice-dna/outro.example.yaml', content: 'meta:\n owner: "x"\n' },
|
|
221
|
+
},
|
|
222
|
+
{
|
|
223
|
+
label: 'AC12 kill-test (d) — `learnings.jsonl` NÃO vazio (memória de instância no pacote) é PEGO',
|
|
224
|
+
inject: { path: 'template/pods/dev-ia/memory/learnings.jsonl', content: '{"trigger":"x","recommendation":"y"}\n' },
|
|
225
|
+
},
|
|
226
|
+
{
|
|
227
|
+
label: 'AC12 kill-test (e) — consolidado COM heurística (conhecimento de instância) é PEGO',
|
|
228
|
+
inject: {
|
|
229
|
+
path: 'template/pods/dev-ia/memory/learnings.consolidated.md',
|
|
230
|
+
content: '# Memória consolidada — dev-ia\n\n## Heurísticas (top-N)\n- **[ao migrar]** faça assim _(x3)_\n',
|
|
231
|
+
},
|
|
232
|
+
},
|
|
233
|
+
{
|
|
234
|
+
label: 'AC12 kill-test (f) — `learnings.hardened.md` real chegando ao template é PEGO',
|
|
235
|
+
inject: { path: 'template/pods/dev-ia/memory/learnings.hardened.md', content: '# Endurecidas\n' },
|
|
236
|
+
},
|
|
237
|
+
{
|
|
238
|
+
// AC17c — achado da PRÓPRIA guarda de regressão nesta rodada: a camada `dono-do-pod`
|
|
239
|
+
// também estava sem kill-test. O vazamento realista é o `dono.yaml` de INSTÂNCIA
|
|
240
|
+
// (já preenchido) sendo publicado no lugar do `.example.`.
|
|
241
|
+
label: 'AC12/AC17c kill-test (h) — `dono.yaml` de INSTÂNCIA (preenchido) no template é PEGO',
|
|
242
|
+
inject: {
|
|
243
|
+
path: 'template/pods/content-carousel/knowledge/dono.yaml',
|
|
244
|
+
content: 'dono:\n nome: "Nome Real"\n handle: "@handle.real"\n',
|
|
245
|
+
},
|
|
246
|
+
},
|
|
247
|
+
{
|
|
248
|
+
// AC17c — o canal `exemplos-do-pod`. Um `examples/` AUTORADO é por onde opinião e
|
|
249
|
+
// posicionamento do dono chegavam à instalação de terceiros: o agente lê o exemplo
|
|
250
|
+
// como referência de padrão e imita o ASSUNTO junto com a FORMA.
|
|
251
|
+
label: 'AC12/AC17c kill-test (g) — exemplo AUTORADO (peça pronta, não esqueleto) em `examples/` é PEGO',
|
|
252
|
+
inject: {
|
|
253
|
+
path: 'template/pods/academy/examples/palestra-tema-do-dono.md',
|
|
254
|
+
camada: 'exemplos-do-pod',
|
|
255
|
+
content: '# Palestra: "<tese do dono sobre o assunto dele>"\n\nTese: <posicionamento autoral>.\n',
|
|
256
|
+
},
|
|
257
|
+
},
|
|
258
|
+
];
|
|
259
|
+
// Cada kill-test afirma que a INJEÇÃO é pega — não que ela é a única violação do
|
|
260
|
+
// conjunto. A limpeza da linha de base já é uma asserção independente (acima);
|
|
261
|
+
// acoplar as duas produziria 6 falhas em cascata para um único defeito plantado,
|
|
262
|
+
// que é ruído de diagnóstico, não sinal.
|
|
263
|
+
const camadasComKillTest = new Set();
|
|
264
|
+
for (const t of killCont) {
|
|
265
|
+
const base = files.filter((f) => f.path !== t.inject.path);
|
|
266
|
+
const v = OL.scanContainment(base.concat([t.inject]));
|
|
267
|
+
const hit = v.filter((x) => x.path === t.inject.path);
|
|
268
|
+
ok(hit.length === 1, `${t.label}${hit.length ? ` → ${hit[0].reason.slice(0, 70)}…` : ''}`);
|
|
269
|
+
if (hit.length === 1) camadasComKillTest.add(hit[0].layer);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// AC17c — nenhuma camada declarada pode ficar sem kill-test. Sem esta asserção, uma
|
|
273
|
+
// camada nova entra no manifesto sem prova de que reprova o que promete reprovar, e a
|
|
274
|
+
// suíte segue verde: foi exatamente o buraco que `exemplos-do-pod` abriu ao nascer.
|
|
275
|
+
const semKillTest = CAMADAS_EXIGIDAS.filter((id) => !camadasComKillTest.has(id));
|
|
276
|
+
ok(semKillTest.length === 0,
|
|
277
|
+
`AC17c — toda camada declarada é exercida por kill-test próprio${semKillTest.length ? ` — SEM prova: ${semKillTest.join(', ')}` : ` (${[...camadasComKillTest].sort().join(', ')})`}`);
|
|
278
|
+
|
|
279
|
+
// AC17c(iii) — A ASSIMETRIA VIRA ASSERÇÃO, não comentário. A MESMA injeção que a
|
|
280
|
+
// contenção pega precisa dar ZERO na enumeração. É isto que sustenta, por execução, a
|
|
281
|
+
// frase que o AC17 declara: o mecanismo **fecha o canal**, ele **não detecta opinião**
|
|
282
|
+
// (opinião não tem gramática sintática). Se algum dia a enumeração passar a pegar esta
|
|
283
|
+
// injeção, a frase do AC virou mentira e esta asserção falha antes de alguém publicá-la.
|
|
284
|
+
const injecaoOpiniao = killCont.find((t) => t.inject.camada === 'exemplos-do-pod').inject;
|
|
285
|
+
const contOpiniao = OL.scanContainment(files.concat([injecaoOpiniao])).filter((x) => x.path === injecaoOpiniao.path);
|
|
286
|
+
const enumOpiniao = OL.scanEnumeration([injecaoOpiniao]);
|
|
287
|
+
ok(contOpiniao.length === 1 && enumOpiniao.length === 0,
|
|
288
|
+
`AC17c(iii) — alcance DISJUNTO na mesma injeção: contenção ${contOpiniao.length} (pega) × enumeração ${enumOpiniao.length} (não pega) — fecha o canal, não detecta opinião`);
|
|
289
|
+
|
|
290
|
+
// Contraprova de simetria: injeção CONFORME não gera violação PRÓPRIA (o oráculo
|
|
291
|
+
// discrimina em vez de reprovar por atacado).
|
|
292
|
+
const CONFORME_PATH = 'template/knowledge/voice-dna/outro.example.yaml';
|
|
293
|
+
const conforme = OL.scanContainment(files.concat([
|
|
294
|
+
{ path: CONFORME_PATH, content: '# pc:placeholder\nmeta:\n owner: "<preencha>"\n' },
|
|
295
|
+
]));
|
|
296
|
+
ok(conforme.filter((x) => x.path === CONFORME_PATH).length === 0,
|
|
297
|
+
'AC12 contraprova — `.example.` COM sentinela passa: o oráculo discrimina, não reprova por atacado');
|
|
298
|
+
|
|
299
|
+
// ===========================================================================
|
|
300
|
+
// AC3 — o dado real do dono não existe mais no pacote publicado.
|
|
301
|
+
// ===========================================================================
|
|
302
|
+
ok(!files.some((f) => f.path === OLD_DNA_PATH),
|
|
303
|
+
`AC3 — o DNA autoral (\`${OLD_DNA_PATH}\`) NÃO está no tarball`);
|
|
304
|
+
ok(!fs.existsSync(path.join(REPO_ROOT, OLD_DNA_PATH)),
|
|
305
|
+
'AC3 — o arquivo também não existe mais no repositório (test -f falso)');
|
|
306
|
+
|
|
307
|
+
// ===========================================================================
|
|
308
|
+
// AC4 — `voice-dna.example.yaml`: paridade de FORMA + sentinela.
|
|
309
|
+
// ===========================================================================
|
|
310
|
+
const EXPECTED_TOP_KEYS = [
|
|
311
|
+
'meta', 'identity', 'tone', 'structures', 'hooks',
|
|
312
|
+
'vocabulary', 'argumentation', 'signatures', 'anti_patterns', 'gaps_e_limitacoes',
|
|
313
|
+
];
|
|
314
|
+
const example = files.find((f) => f.path === 'template/knowledge/voice-dna/voice-dna.example.yaml');
|
|
315
|
+
ok(!!example, 'AC4 — `voice-dna.example.yaml` está no tarball');
|
|
316
|
+
if (example) {
|
|
317
|
+
let parsed = null;
|
|
318
|
+
let parseErr = null;
|
|
319
|
+
try { parsed = parseYaml(example.content); } catch (e) { parseErr = e; }
|
|
320
|
+
ok(parsed && typeof parsed === 'object', `AC4 — parse YAML válido${parseErr ? ` (erro: ${parseErr.message})` : ''}`);
|
|
321
|
+
if (parsed) {
|
|
322
|
+
const got = Object.keys(parsed).sort();
|
|
323
|
+
const want = EXPECTED_TOP_KEYS.slice().sort();
|
|
324
|
+
ok(got.length === want.length && got.every((k, i) => k === want[i]),
|
|
325
|
+
`AC4 — paridade de chaves de topo (igualdade de conjunto, 10 chaves)\n esperado: ${want.join(', ')}\n obtido: ${got.join(', ')}`);
|
|
326
|
+
ok(parsed.meta && parsed.meta.placeholder === true,
|
|
327
|
+
'AC4 — sentinela `meta.placeholder: true` presente (consumida pelo oráculo E pelo agente)');
|
|
328
|
+
}
|
|
329
|
+
// Alcance declarado: isto prova FORMA. Que os placeholders façam sentido
|
|
330
|
+
// semântico como exemplo é revisão humana — sem oráculo de máquina (ADR-002).
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// ===========================================================================
|
|
334
|
+
// AC6 — nenhum ponteiro para o nome de arquivo antigo sobrou no pacote.
|
|
335
|
+
// ===========================================================================
|
|
336
|
+
const OLD_DNA_BASENAME = `${SLUG}-voice-dna`;
|
|
337
|
+
const oldRefs = files.filter((f) => f.content.includes(OLD_DNA_BASENAME));
|
|
338
|
+
ok(oldRefs.length === 0,
|
|
339
|
+
`AC6 — zero referências ao nome de arquivo antigo no tarball${oldRefs.length ? ` (${oldRefs.map((f) => f.path).join(', ')})` : ''}`);
|
|
340
|
+
|
|
341
|
+
// A instância declarada pelo manifesto é a que as referências cruzadas usam.
|
|
342
|
+
const voiceLayer = OL.layers.find((l) => l.id === 'voice-dna');
|
|
343
|
+
ok(voiceLayer.instancePath === 'knowledge/voice-dna/voice-dna.yaml',
|
|
344
|
+
'AC6 — o manifesto declara o path de INSTÂNCIA (ADR-027 Decisão 8), fonte única das referências cruzadas');
|
|
345
|
+
|
|
346
|
+
// ===========================================================================
|
|
347
|
+
// AC8 — a identidade fictícia canônica é única e está declarada no manifesto.
|
|
348
|
+
// ===========================================================================
|
|
349
|
+
ok(OL.FICTIONAL_IDENTITY.nome === 'Alexa Exemplo' && OL.FICTIONAL_IDENTITY.handle === '@alexa.exemplo',
|
|
350
|
+
'AC8 — identidade fictícia canônica declarada no manifesto (uma só no repo, DF-SF12.1-6)');
|
|
351
|
+
|
|
352
|
+
// ===========================================================================
|
|
353
|
+
console.log(`\n${failures.length === 0 ? 'PASS' : 'FAIL'} — ${passed} asserções OK, ${failures.length} falha(s)`);
|
|
354
|
+
if (failures.length > 0) {
|
|
355
|
+
for (const f of failures) console.error(` ✗ ${f}`);
|
|
356
|
+
}
|
|
357
|
+
try { fs.rmSync(tmp, { recursive: true, force: true }); } catch { /* tmp */ }
|
|
358
|
+
process.exit(failures.length === 0 ? 0 : 1);
|
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
/**
|
|
4
|
+
* pc-run-pipeline.cjs — MOTOR SUPERVISOR do maestro cross-pod (Story SF9.1/ADR-017).
|
|
5
|
+
*
|
|
6
|
+
* DUAS máquinas de estado. A do run-filho (`pc-run-pod`) é dirigida pelo OPERADOR (executa
|
|
7
|
+
* os steps, agente a agente). Esta — a do PIPELINE — é 100% determinística e SUPERVISORA:
|
|
8
|
+
* dá `start` no run-filho da fase, DETECTA `done` LENDO o state.json do filho, impõe o
|
|
9
|
+
* BASELINE (run done + artefato de output existente EM DISCO), aplica o PORTÃO binário
|
|
10
|
+
* GO/NO-GO (veto de máquina reusando evalVeto; checkpoint humano = PARADA explícita), e só
|
|
11
|
+
* então libera a fase seguinte — passando o output da fase N por REFERÊNCIA (path resolvido,
|
|
12
|
+
* verificado em disco, via --task-ref; sem cópia). NUNCA executa LLM/etapa. NUNCA pula fase.
|
|
13
|
+
* NUNCA avança sem evidência em disco. "O eletricista não começa antes da parede aprovada."
|
|
14
|
+
*
|
|
15
|
+
* Comandos: init <pipeline.yaml> | status <id> | advance <id> | approve <id> [--note ..] | reject <id>
|
|
16
|
+
* Flags: --pods-root <dir> (onde vivem os pods; default pods/), --state-root <dir> (default .power-claude).
|
|
17
|
+
* Exit 0 = ação concluída; exit 1 = fail-loud (fase sem run, run não-done, artefato ausente,
|
|
18
|
+
* portão NO-GO, contrato inválido). Node >= 18, zero-dep.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
const fs = require('node:fs');
|
|
22
|
+
const path = require('node:path');
|
|
23
|
+
const os = require('node:os');
|
|
24
|
+
const { parseYaml } = require('./validate-pod-workflow.cjs');
|
|
25
|
+
const { evalVeto, startRun, readState, runDirFor, statePathFor } = require('./pc-run-pod.cjs');
|
|
26
|
+
const { validatePipeline } = require('./validate-pipeline.cjs');
|
|
27
|
+
|
|
28
|
+
const REPO_ROOT = path.resolve(__dirname, '..');
|
|
29
|
+
|
|
30
|
+
class PipelineError extends Error {}
|
|
31
|
+
function fail(msg) { throw new PipelineError(msg); }
|
|
32
|
+
function nowISO() { return new Date().toISOString(); }
|
|
33
|
+
|
|
34
|
+
function podsRootFor(o) { return o ? path.resolve(o) : path.join(process.cwd(), 'pods'); }
|
|
35
|
+
function stateRootFor(o) { return o ? path.resolve(o) : path.join(process.cwd(), '.power-claude'); }
|
|
36
|
+
function pipelineDir(stateRoot, id) { return path.join(stateRoot, 'pipeline', id); }
|
|
37
|
+
function pipelineStatePath(stateRoot, id) { return path.join(pipelineDir(stateRoot, id), 'state.json'); }
|
|
38
|
+
|
|
39
|
+
function saveState(sp, state) {
|
|
40
|
+
state.updated_at = nowISO();
|
|
41
|
+
fs.mkdirSync(path.dirname(sp), { recursive: true });
|
|
42
|
+
const tmp = `${sp}.tmp`;
|
|
43
|
+
fs.writeFileSync(tmp, `${JSON.stringify(state, null, 2)}\n`, 'utf8');
|
|
44
|
+
fs.renameSync(tmp, sp);
|
|
45
|
+
}
|
|
46
|
+
function loadPipelineState(stateRoot, id) {
|
|
47
|
+
const sp = pipelineStatePath(stateRoot, id);
|
|
48
|
+
if (!fs.existsSync(sp)) fail(`pipeline "${id}" não inicializado (${path.relative(REPO_ROOT, sp)} não existe) — rode 'init'`);
|
|
49
|
+
try { return JSON.parse(fs.readFileSync(sp, 'utf8')); }
|
|
50
|
+
catch (e) { fail(`state.json do pipeline "${id}" corrompido: ${e.message}`); }
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// ---------------------------------------------------------------------------
|
|
54
|
+
// init — valida o contrato e cria o state (todas as fases pending).
|
|
55
|
+
// ---------------------------------------------------------------------------
|
|
56
|
+
function init(pipelinePath, { podsRoot, stateRoot } = {}) {
|
|
57
|
+
const pr = podsRootFor(podsRoot);
|
|
58
|
+
validatePipeline(pipelinePath, pr); // fail-loud se o contrato for inválido (Fail → propaga)
|
|
59
|
+
const doc = parseYaml(fs.readFileSync(pipelinePath, 'utf8'));
|
|
60
|
+
const sr = stateRootFor(stateRoot);
|
|
61
|
+
const sp = pipelineStatePath(sr, doc.id);
|
|
62
|
+
if (fs.existsSync(sp)) fail(`pipeline "${doc.id}" já iniciado (${path.relative(REPO_ROOT, sp)}) — use 'status'/'advance' (retomada) ou remova o state para recomeçar`);
|
|
63
|
+
const phases = {};
|
|
64
|
+
const order = [];
|
|
65
|
+
for (const ph of doc.phases) {
|
|
66
|
+
order.push(ph.id);
|
|
67
|
+
phases[ph.id] = {
|
|
68
|
+
status: 'pending', pod: ph.pod, workflow: ph.workflow,
|
|
69
|
+
outputs: ph.outputs, inputs: ph.inputs || null, gate: ph.gate || null,
|
|
70
|
+
run_id: null, run_dir: null, outputs_resolved: null, note: null,
|
|
71
|
+
started_at: null, completed_at: null,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
const state = {
|
|
75
|
+
pipeline: doc.id, deliverable: doc.deliverable,
|
|
76
|
+
contract_path: path.resolve(pipelinePath), pods_root: pr,
|
|
77
|
+
runs_root: path.join(pipelineDir(sr, doc.id), 'runs'),
|
|
78
|
+
order, phases, updated_at: nowISO(),
|
|
79
|
+
};
|
|
80
|
+
saveState(sp, state);
|
|
81
|
+
return state;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// ---------------------------------------------------------------------------
|
|
85
|
+
// Helpers do supervisor.
|
|
86
|
+
// ---------------------------------------------------------------------------
|
|
87
|
+
function currentPhaseId(state) {
|
|
88
|
+
return state.order.find((id) => state.phases[id].status !== 'completed') || null;
|
|
89
|
+
}
|
|
90
|
+
function childStatePathOf(state, ph) {
|
|
91
|
+
return statePathFor(state.runs_root, ph.pod, ph.workflow, ph.run_id);
|
|
92
|
+
}
|
|
93
|
+
function childDone(childState) {
|
|
94
|
+
const steps = childState && childState.steps ? Object.values(childState.steps) : [];
|
|
95
|
+
return steps.length > 0 && steps.every((s) => s.status === 'done');
|
|
96
|
+
}
|
|
97
|
+
function resolveOutputs(state, ph) {
|
|
98
|
+
const runDir = ph.run_dir;
|
|
99
|
+
const out = {};
|
|
100
|
+
for (const [name, rel] of Object.entries(ph.outputs)) out[name] = path.resolve(runDir, rel);
|
|
101
|
+
return out;
|
|
102
|
+
}
|
|
103
|
+
/** Resolve os inputs da fase a partir dos outputs JÁ RESOLVIDOS de fases anteriores, verificando EM DISCO. */
|
|
104
|
+
function resolveInputs(state, ph) {
|
|
105
|
+
const resolved = {};
|
|
106
|
+
if (!ph.inputs) return resolved;
|
|
107
|
+
for (const [local, refRaw] of Object.entries(ph.inputs)) {
|
|
108
|
+
const ref = String(refRaw).replace(/^\s*<-\s*/, '').trim();
|
|
109
|
+
const dot = ref.indexOf('.');
|
|
110
|
+
const srcPhase = ref.slice(0, dot);
|
|
111
|
+
const srcOut = ref.slice(dot + 1);
|
|
112
|
+
const src = state.phases[srcPhase];
|
|
113
|
+
if (!src || src.status !== 'completed' || !src.outputs_resolved || !(srcOut in src.outputs_resolved)) {
|
|
114
|
+
fail(`input "${local}": fase-fonte "${srcPhase}" não concluída ou output "${srcOut}" não resolvido`);
|
|
115
|
+
}
|
|
116
|
+
const abs = src.outputs_resolved[srcOut];
|
|
117
|
+
if (!fs.existsSync(abs)) fail(`input "${local}": artefato de referência ausente EM DISCO — ${abs} (nunca por relato)`);
|
|
118
|
+
resolved[local] = abs;
|
|
119
|
+
}
|
|
120
|
+
return resolved;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** Avalia o portão de máquina da fase sobre o PRIMEIRO output declarado. GO/NO-GO. */
|
|
124
|
+
function evalGateVeto(state, ph) {
|
|
125
|
+
const g = ph.gate;
|
|
126
|
+
if (!g || !Array.isArray(g.veto) || g.veto.length === 0) return { go: true };
|
|
127
|
+
const [firstName] = Object.keys(ph.outputs);
|
|
128
|
+
const artPath = ph.outputs_resolved[firstName];
|
|
129
|
+
const text = fs.readFileSync(artPath, 'utf8');
|
|
130
|
+
const roots = { podRoot: path.join(state.pods_root, ph.pod), runDir: ph.run_dir };
|
|
131
|
+
for (const a of g.veto) {
|
|
132
|
+
const r = evalVeto(a, text, artPath, roots);
|
|
133
|
+
if (!r.ok) return { go: false, reason: `${a.type}: ${r.reason}` };
|
|
134
|
+
}
|
|
135
|
+
return { go: true };
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// ---------------------------------------------------------------------------
|
|
139
|
+
// advance — UMA transição do supervisor. Nunca executa a fase; só supervisiona.
|
|
140
|
+
// ---------------------------------------------------------------------------
|
|
141
|
+
function advance(id, { stateRoot } = {}) {
|
|
142
|
+
const sr = stateRootFor(stateRoot);
|
|
143
|
+
const sp = pipelineStatePath(sr, id);
|
|
144
|
+
const state = loadPipelineState(sr, id);
|
|
145
|
+
const cur = currentPhaseId(state);
|
|
146
|
+
if (cur === null) return { status: 'complete', message: `pipeline "${id}" COMPLETO — todas as fases concluídas.` };
|
|
147
|
+
const ph = state.phases[cur];
|
|
148
|
+
|
|
149
|
+
if (ph.status === 'failed') fail(`fase "${cur}" FALHOU — pipeline travado (fail-loud; sem pular fase). Corrija e reinicie a fase.`);
|
|
150
|
+
if (ph.status === 'blocked') fail(`fase "${cur}" aguarda decisão HUMANA do checkpoint — rode 'approve ${id}' (GO) ou 'reject ${id}' (NO-GO).`);
|
|
151
|
+
|
|
152
|
+
if (ph.status === 'pending') {
|
|
153
|
+
// START do run-filho (o SUPERVISOR só cria o run; o OPERADOR executa os steps depois).
|
|
154
|
+
const inputs = resolveInputs(state, ph); // verifica os inputs EM DISCO (fail-loud)
|
|
155
|
+
const taskRef = Object.keys(inputs).length
|
|
156
|
+
? `Inputs cross-pod (por REFERÊNCIA, verificados em disco): ${Object.entries(inputs).map(([k, v]) => `${k}=${v}`).join('; ')}`
|
|
157
|
+
: null;
|
|
158
|
+
const podRoot = path.join(state.pods_root, ph.pod);
|
|
159
|
+
const workflowPath = path.join(podRoot, 'workflows', `${ph.workflow}.yaml`);
|
|
160
|
+
// Roteamento de memória do run gravado no state do filho (SF9.2 CONCERNS-1): o start
|
|
161
|
+
// isola em tmp E o `next` do operador honra o mesmo dir (lido do state) — sem vazar
|
|
162
|
+
// learnings do pipeline para o memory REAL do pod.
|
|
163
|
+
const { result } = startRun({
|
|
164
|
+
workflowPath, podRootOverride: podRoot, pod: ph.pod, workflow: ph.workflow,
|
|
165
|
+
taskRef, runsRoot: state.runs_root, runId: cur, memoryPodDir: fs.mkdtempSync(path.join(os.tmpdir(), 'pc-pipe-mem-')),
|
|
166
|
+
});
|
|
167
|
+
ph.run_id = cur;
|
|
168
|
+
ph.run_dir = runDirFor(state.runs_root, ph.pod, ph.workflow, cur);
|
|
169
|
+
ph.status = 'running';
|
|
170
|
+
ph.started_at = nowISO();
|
|
171
|
+
ph.task_ref = taskRef;
|
|
172
|
+
saveState(sp, state);
|
|
173
|
+
// Aspereza #1: expõe o BRIEF da 1ª etapa (o operador precisa dele para executar a fase).
|
|
174
|
+
const firstBrief = result && result.brief ? result.brief : null;
|
|
175
|
+
return { status: 'started', phase: cur, run_dir: ph.run_dir, task_ref: taskRef, brief: firstBrief,
|
|
176
|
+
message: `fase "${cur}" iniciada (run-filho em ${ph.run_dir}).\n` + // aspereza #3: path absoluto, sem ../../.. cosmético
|
|
177
|
+
(firstBrief ? `--- BRIEF DA 1ª ETAPA (execute-a via pc-run-pod; o pipeline NÃO executa) ---\n${firstBrief}\n--- fim do brief ---\n` : '') +
|
|
178
|
+
`O OPERADOR completa o run via 'pc-run-pod next/approve'; depois rode 'advance ${id}' de novo.` };
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
if (ph.status === 'running') {
|
|
182
|
+
// DETECTA done LENDO o state do filho — nunca executa os steps.
|
|
183
|
+
const csp = childStatePathOf(state, ph);
|
|
184
|
+
if (!fs.existsSync(csp)) fail(`fase "${cur}": state.json do run-filho ausente (${path.relative(REPO_ROOT, csp)})`);
|
|
185
|
+
const child = readState(csp);
|
|
186
|
+
if (!childDone(child)) {
|
|
187
|
+
const pend = Object.entries(child.steps).filter(([, s]) => s.status !== 'done').map(([k, s]) => `${k}:${s.status}`);
|
|
188
|
+
fail(`fase "${cur}": run-filho NÃO está done (steps: ${pend.join(', ')}). O pipeline é SUPERVISOR — quem executa a fase é o operador via pc-run-pod. Complete o run e rode advance de novo.`);
|
|
189
|
+
}
|
|
190
|
+
// BASELINE: outputs declarados existem EM DISCO (nunca por relato do state.json).
|
|
191
|
+
ph.outputs_resolved = resolveOutputs(state, ph);
|
|
192
|
+
for (const [name, abs] of Object.entries(ph.outputs_resolved)) {
|
|
193
|
+
if (!fs.existsSync(abs)) { ph.status = 'failed'; saveState(sp, state); fail(`fase "${cur}": output "${name}" declarado mas AUSENTE em disco (${abs}) — baseline reprovado.`); }
|
|
194
|
+
}
|
|
195
|
+
// PORTÃO de máquina (binário GO/NO-GO).
|
|
196
|
+
const gv = evalGateVeto(state, ph);
|
|
197
|
+
if (!gv.go) { ph.status = 'failed'; saveState(sp, state); fail(`fase "${cur}": PORTÃO NO-GO (veto de máquina) — ${gv.reason}. Fase seguinte NÃO dispara.`); }
|
|
198
|
+
// PORTÃO humano = PARADA explícita (nunca auto-avanço).
|
|
199
|
+
if (ph.gate && ph.gate.checkpoint) {
|
|
200
|
+
ph.status = 'blocked';
|
|
201
|
+
saveState(sp, state);
|
|
202
|
+
return { status: 'blocked', phase: cur, message: `fase "${cur}": run done + baseline + veto GO, MAS há checkpoint HUMANO (${ph.gate.checkpoint.criteria}). Decisão explícita: 'approve ${id}' (GO) ou 'reject ${id}' (NO-GO). Sem auto-avanço.` };
|
|
203
|
+
}
|
|
204
|
+
// GO automático (só baseline/veto).
|
|
205
|
+
ph.status = 'completed';
|
|
206
|
+
ph.completed_at = nowISO();
|
|
207
|
+
saveState(sp, state);
|
|
208
|
+
return { status: 'phase-complete', phase: cur, message: `fase "${cur}" CONCLUÍDA (GO). Rode 'advance ${id}' para iniciar a próxima.` };
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
fail(`fase "${cur}": status desconhecido "${ph.status}"`);
|
|
212
|
+
return undefined;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function approve(id, { stateRoot, note } = {}) {
|
|
216
|
+
const sr = stateRootFor(stateRoot);
|
|
217
|
+
const sp = pipelineStatePath(sr, id);
|
|
218
|
+
const state = loadPipelineState(sr, id);
|
|
219
|
+
const cur = currentPhaseId(state);
|
|
220
|
+
const ph = cur && state.phases[cur];
|
|
221
|
+
if (!ph || ph.status !== 'blocked') fail(`nenhuma fase aguardando decisão humana em "${id}" (use advance).`);
|
|
222
|
+
ph.status = 'completed';
|
|
223
|
+
ph.completed_at = nowISO();
|
|
224
|
+
ph.note = note || null; // ressalvas como TEXTO LIVRE num GO (nunca 3º estado de fluxo)
|
|
225
|
+
saveState(sp, state);
|
|
226
|
+
return { status: 'phase-complete', phase: cur, message: `fase "${cur}" GO humano${note ? ` (ressalva: ${note})` : ''}. Rode 'advance ${id}'.` };
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function reject(id, { stateRoot } = {}) {
|
|
230
|
+
const sr = stateRootFor(stateRoot);
|
|
231
|
+
const sp = pipelineStatePath(sr, id);
|
|
232
|
+
const state = loadPipelineState(sr, id);
|
|
233
|
+
const cur = currentPhaseId(state);
|
|
234
|
+
const ph = cur && state.phases[cur];
|
|
235
|
+
if (!ph || ph.status !== 'blocked') fail(`nenhuma fase aguardando decisão humana em "${id}".`);
|
|
236
|
+
ph.status = 'failed';
|
|
237
|
+
saveState(sp, state);
|
|
238
|
+
fail(`fase "${cur}": NO-GO humano — pipeline travado. Fase seguinte NÃO dispara.`);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function statusSummary(id, { stateRoot } = {}) {
|
|
242
|
+
const state = loadPipelineState(stateRootFor(stateRoot), id);
|
|
243
|
+
const lines = state.order.map((pid) => ` ${pid}: ${state.phases[pid].status}`);
|
|
244
|
+
return `pipeline "${id}" (${state.deliverable})\n${lines.join('\n')}`;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// ---------------------------------------------------------------------------
|
|
248
|
+
// CLI
|
|
249
|
+
// ---------------------------------------------------------------------------
|
|
250
|
+
function argVal(args, flag) { const i = args.indexOf(flag); return i >= 0 ? args[i + 1] : null; }
|
|
251
|
+
|
|
252
|
+
function main() {
|
|
253
|
+
const args = process.argv.slice(2);
|
|
254
|
+
const cmd = args[0];
|
|
255
|
+
const podsRoot = argVal(args, '--pods-root');
|
|
256
|
+
const stateRoot = argVal(args, '--state-root');
|
|
257
|
+
const note = argVal(args, '--note');
|
|
258
|
+
try {
|
|
259
|
+
if (cmd === 'init') {
|
|
260
|
+
const s = init(args[1], { podsRoot, stateRoot });
|
|
261
|
+
console.log(`[OK] pipeline "${s.pipeline}" iniciado — ${s.order.length} fase(s): ${s.order.join(' → ')}`);
|
|
262
|
+
} else if (cmd === 'status') {
|
|
263
|
+
console.log(statusSummary(args[1], { stateRoot }));
|
|
264
|
+
} else if (cmd === 'advance') {
|
|
265
|
+
const r = advance(args[1], { stateRoot });
|
|
266
|
+
console.log(`[${r.status}] ${r.message}`);
|
|
267
|
+
} else if (cmd === 'approve') {
|
|
268
|
+
const r = approve(args[1], { stateRoot, note });
|
|
269
|
+
console.log(`[${r.status}] ${r.message}`);
|
|
270
|
+
} else if (cmd === 'reject') {
|
|
271
|
+
reject(args[1], { stateRoot });
|
|
272
|
+
} else {
|
|
273
|
+
console.error('Uso: node scripts/pc-run-pipeline.cjs <init|status|advance|approve|reject> <pipeline.yaml|id> [--pods-root <dir>] [--state-root <dir>] [--note "..."]');
|
|
274
|
+
process.exit(2);
|
|
275
|
+
}
|
|
276
|
+
process.exit(0);
|
|
277
|
+
} catch (e) {
|
|
278
|
+
if (e instanceof PipelineError || e.constructor.name === 'Fail') { console.error(`[FALHA] ${e.message}`); process.exit(1); }
|
|
279
|
+
throw e;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
if (require.main === module) main();
|
|
284
|
+
|
|
285
|
+
module.exports = {
|
|
286
|
+
init, advance, approve, reject, statusSummary, loadPipelineState,
|
|
287
|
+
currentPhaseId, childDone, resolveInputs, evalGateVeto,
|
|
288
|
+
pipelineStatePath, pipelineDir, PipelineError,
|
|
289
|
+
};
|