@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,869 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
/**
|
|
4
|
+
* enforce-cycle-authority.test.cjs — suíte real do hook `PreToolUse` das 2
|
|
5
|
+
* garantias de autoridade do ciclo (Blocos C+D+E, story SF12.3).
|
|
6
|
+
*
|
|
7
|
+
* Cobre: os 2 predicados (Done / gate file / Dev×Draft) × os 3 níveis de
|
|
8
|
+
* identidade do AC19 (papel exigido, papel do roster errado, ambíguo) ×
|
|
9
|
+
* controles negativos (AC11/AC17) × a mutação dos AC14/AC18 (mutante gerado
|
|
10
|
+
* em tmp + executado como subprocesso Node com o mesmo payload de um caso
|
|
11
|
+
* que deveria negar/perguntar — o teste falha se a mutante permitir).
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
const fs = require('node:fs');
|
|
15
|
+
const os = require('node:os');
|
|
16
|
+
const path = require('node:path');
|
|
17
|
+
const { spawnSync } = require('node:child_process');
|
|
18
|
+
|
|
19
|
+
const REPO_ROOT = path.resolve(__dirname, '..', '..');
|
|
20
|
+
const HOOK_PATH = path.join(REPO_ROOT, 'template', '.claude', 'hooks', 'enforce-cycle-authority.cjs');
|
|
21
|
+
|
|
22
|
+
let failures = 0;
|
|
23
|
+
function assert(ok, msg) {
|
|
24
|
+
if (ok) {
|
|
25
|
+
console.log(`[OK ] ${msg}`);
|
|
26
|
+
} else {
|
|
27
|
+
console.error(`[FALHA] ${msg}`);
|
|
28
|
+
failures++;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// ---------------------------------------------------------------------------
|
|
33
|
+
// Fixture: repositório fake com branch resolvível + stories Draft/Ready/Done
|
|
34
|
+
// ---------------------------------------------------------------------------
|
|
35
|
+
|
|
36
|
+
function makeFixtureRepo() {
|
|
37
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'eca-fixture-'));
|
|
38
|
+
fs.mkdirSync(path.join(dir, '.git'), { recursive: true });
|
|
39
|
+
fs.mkdirSync(path.join(dir, 'docs', 'stories'), { recursive: true });
|
|
40
|
+
fs.mkdirSync(path.join(dir, 'docs', 'qa', 'gates'), { recursive: true });
|
|
41
|
+
fs.mkdirSync(path.join(dir, 'src'), { recursive: true });
|
|
42
|
+
|
|
43
|
+
setBranch(dir, 'feat/9.9-teste');
|
|
44
|
+
|
|
45
|
+
fs.writeFileSync(
|
|
46
|
+
path.join(dir, 'docs', 'stories', '9.9.story.md'),
|
|
47
|
+
'# Story 9.9\n\n## Status\n\nDraft\n\n## Executor Assignment\n',
|
|
48
|
+
);
|
|
49
|
+
fs.writeFileSync(
|
|
50
|
+
path.join(dir, 'docs', 'stories', '9.8.story.md'),
|
|
51
|
+
'# Story 9.8\n\n## Status\n\nReady\n\n## Executor Assignment\n',
|
|
52
|
+
);
|
|
53
|
+
fs.writeFileSync(
|
|
54
|
+
path.join(dir, 'docs', 'stories', '9.7.story.md'),
|
|
55
|
+
'# Story 9.7\n\n## Status\n\nDone\n\n## Dev Agent Record\n\n[AUTO-DECISION] algo\n',
|
|
56
|
+
);
|
|
57
|
+
fs.writeFileSync(path.join(dir, 'src', 'foo.js'), 'const a = 1;\n');
|
|
58
|
+
return dir;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function setBranch(dir, branch) {
|
|
62
|
+
fs.writeFileSync(path.join(dir, '.git', 'HEAD'), `ref: refs/heads/${branch}\n`);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// ---------------------------------------------------------------------------
|
|
66
|
+
// Execução do hook (real ou mutante) como subprocesso Node, via stdin
|
|
67
|
+
// ---------------------------------------------------------------------------
|
|
68
|
+
|
|
69
|
+
function runHookAtPath(hookPath, rawInput) {
|
|
70
|
+
const result = spawnSync(process.execPath, [hookPath], {
|
|
71
|
+
input: rawInput,
|
|
72
|
+
encoding: 'utf8',
|
|
73
|
+
timeout: 10000,
|
|
74
|
+
});
|
|
75
|
+
if (result.error) throw result.error;
|
|
76
|
+
let decision = null;
|
|
77
|
+
const out = (result.stdout || '').trim();
|
|
78
|
+
if (out) {
|
|
79
|
+
try {
|
|
80
|
+
const parsed = JSON.parse(out);
|
|
81
|
+
decision = parsed?.hookSpecificOutput?.permissionDecision || null;
|
|
82
|
+
} catch {
|
|
83
|
+
decision = 'UNPARSEABLE_OUTPUT';
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return { decision, stdout: out, stderr: result.stderr, status: result.status };
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function runHook(payload) {
|
|
90
|
+
return runHookAtPath(HOOK_PATH, JSON.stringify(payload));
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function payload({ cwd, agentType, agentId = 's-1', toolName, toolInput }) {
|
|
94
|
+
const p = { cwd, tool_name: toolName, tool_input: toolInput };
|
|
95
|
+
if (agentId !== undefined) p.agent_id = agentId;
|
|
96
|
+
if (agentType !== undefined) p.agent_type = agentType;
|
|
97
|
+
return p;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// ---------------------------------------------------------------------------
|
|
101
|
+
// Predicado 1 — só QA marca Status: Done (Edit/Write em *.story.md)
|
|
102
|
+
// ---------------------------------------------------------------------------
|
|
103
|
+
|
|
104
|
+
function testPredicate1(fixtureDir) {
|
|
105
|
+
console.log('\n-- Predicado 1: Status: Done --');
|
|
106
|
+
const doneEdit = {
|
|
107
|
+
cwd: fixtureDir,
|
|
108
|
+
toolName: 'Edit',
|
|
109
|
+
toolInput: {
|
|
110
|
+
file_path: path.join(fixtureDir, 'docs', 'stories', '9.9.story.md'),
|
|
111
|
+
old_string: 'Draft',
|
|
112
|
+
new_string: 'Done',
|
|
113
|
+
},
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
assert(
|
|
117
|
+
runHook(payload({ ...doneEdit, agentType: 'pc-qa' })).decision === null,
|
|
118
|
+
'pc-qa marca Done → allow (silêncio)',
|
|
119
|
+
);
|
|
120
|
+
assert(
|
|
121
|
+
runHook(payload({ ...doneEdit, agentType: 'pc-dev' })).decision === 'deny',
|
|
122
|
+
'pc-dev marca Done → deny (papel do roster errado)',
|
|
123
|
+
);
|
|
124
|
+
assert(
|
|
125
|
+
runHook({ cwd: fixtureDir, tool_name: 'Edit', tool_input: doneEdit.toolInput }).decision === 'ask',
|
|
126
|
+
'main thread (agent_id ausente) marca Done → ask',
|
|
127
|
+
);
|
|
128
|
+
assert(
|
|
129
|
+
runHook(payload({ ...doneEdit, agentType: 'Explore' })).decision === 'ask',
|
|
130
|
+
'agent_type fora do roster fechado marca Done → ask',
|
|
131
|
+
);
|
|
132
|
+
|
|
133
|
+
// AC10: honra replace_all e Write de arquivo novo (∅ → Done)
|
|
134
|
+
const newStoryPath = path.join(fixtureDir, 'docs', 'stories', '9.6.story.md');
|
|
135
|
+
assert(
|
|
136
|
+
runHook(
|
|
137
|
+
payload({
|
|
138
|
+
cwd: fixtureDir,
|
|
139
|
+
agentType: 'pc-dev',
|
|
140
|
+
toolName: 'Write',
|
|
141
|
+
toolInput: { file_path: newStoryPath, content: '# Story 9.6\n\n## Status\n\nDone\n' },
|
|
142
|
+
}),
|
|
143
|
+
).decision === 'deny',
|
|
144
|
+
'Write de story NOVA (∅ → Done) dispara o predicado — pc-dev nega',
|
|
145
|
+
);
|
|
146
|
+
assert(!fs.existsSync(newStoryPath), 'fixture não deveria ter sido escrita de verdade (hook não grava)');
|
|
147
|
+
|
|
148
|
+
// AC11a: outra seção de story já Done não dispara
|
|
149
|
+
assert(
|
|
150
|
+
runHook(
|
|
151
|
+
payload({
|
|
152
|
+
cwd: fixtureDir,
|
|
153
|
+
agentType: 'pc-dev',
|
|
154
|
+
toolName: 'Edit',
|
|
155
|
+
toolInput: {
|
|
156
|
+
file_path: path.join(fixtureDir, 'docs', 'stories', '9.7.story.md'),
|
|
157
|
+
old_string: '[AUTO-DECISION] algo',
|
|
158
|
+
new_string: '[AUTO-DECISION] algo editado',
|
|
159
|
+
},
|
|
160
|
+
}),
|
|
161
|
+
).decision === null,
|
|
162
|
+
'AC11a: editar OUTRA seção de story já Done não dispara o predicado',
|
|
163
|
+
);
|
|
164
|
+
|
|
165
|
+
// AC11b: palavra "Done" em prosa fora da seção de Status não dispara
|
|
166
|
+
fs.writeFileSync(
|
|
167
|
+
path.join(fixtureDir, 'docs', 'stories', '9.5.story.md'),
|
|
168
|
+
'# Story 9.5\n\n## Status\n\nDraft\n\n## Contexto\n\na SF12.2, mencionada aqui\n',
|
|
169
|
+
);
|
|
170
|
+
assert(
|
|
171
|
+
runHook(
|
|
172
|
+
payload({
|
|
173
|
+
cwd: fixtureDir,
|
|
174
|
+
agentType: 'pc-dev',
|
|
175
|
+
toolName: 'Edit',
|
|
176
|
+
toolInput: {
|
|
177
|
+
file_path: path.join(fixtureDir, 'docs', 'stories', '9.5.story.md'),
|
|
178
|
+
old_string: 'a SF12.2, mencionada aqui',
|
|
179
|
+
new_string: 'a SF12.2 (já Done), mencionada aqui',
|
|
180
|
+
},
|
|
181
|
+
}),
|
|
182
|
+
).decision === null,
|
|
183
|
+
'AC11b: "Done" em prosa fora da seção de Status não dispara o predicado',
|
|
184
|
+
);
|
|
185
|
+
|
|
186
|
+
// replace_all
|
|
187
|
+
fs.writeFileSync(
|
|
188
|
+
path.join(fixtureDir, 'docs', 'stories', '9.4.story.md'),
|
|
189
|
+
'# Story 9.4\n\n## Status\n\nInReview\n\n## X\n',
|
|
190
|
+
);
|
|
191
|
+
assert(
|
|
192
|
+
runHook(
|
|
193
|
+
payload({
|
|
194
|
+
cwd: fixtureDir,
|
|
195
|
+
agentType: 'pc-qa',
|
|
196
|
+
toolName: 'Edit',
|
|
197
|
+
toolInput: {
|
|
198
|
+
file_path: path.join(fixtureDir, 'docs', 'stories', '9.4.story.md'),
|
|
199
|
+
old_string: 'InReview',
|
|
200
|
+
new_string: 'Done',
|
|
201
|
+
replace_all: true,
|
|
202
|
+
},
|
|
203
|
+
}),
|
|
204
|
+
).decision === null,
|
|
205
|
+
'replace_all honrado na simulação (pc-qa permitido, sem erro)',
|
|
206
|
+
);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// ---------------------------------------------------------------------------
|
|
210
|
+
// Predicado 2 — só QA escreve docs/qa/gates/*.yaml
|
|
211
|
+
// ---------------------------------------------------------------------------
|
|
212
|
+
|
|
213
|
+
function testPredicate2(fixtureDir) {
|
|
214
|
+
console.log('\n-- Predicado 2: gate file --');
|
|
215
|
+
const gatePath = path.join(fixtureDir, 'docs', 'qa', 'gates', '9.9.yaml');
|
|
216
|
+
const base = { cwd: fixtureDir, toolName: 'Write', toolInput: { file_path: gatePath, content: 'gate: PASS\n' } };
|
|
217
|
+
|
|
218
|
+
assert(runHook(payload({ ...base, agentType: 'pc-qa' })).decision === null, 'pc-qa escreve gate file → allow');
|
|
219
|
+
assert(runHook(payload({ ...base, agentType: 'pc-po' })).decision === 'deny', 'pc-po escreve gate file → deny');
|
|
220
|
+
assert(
|
|
221
|
+
runHook({ cwd: fixtureDir, tool_name: 'Write', tool_input: base.toolInput }).decision === 'ask',
|
|
222
|
+
'main thread escreve gate file → ask',
|
|
223
|
+
);
|
|
224
|
+
assert(
|
|
225
|
+
runHook(payload({ ...base, agentType: 'Explore' })).decision === 'ask',
|
|
226
|
+
'agent_type fora do roster escreve gate file → ask',
|
|
227
|
+
);
|
|
228
|
+
// sem exceção de conteúdo (AC12): dispara mesmo sem "Status"/"Done" no conteúdo
|
|
229
|
+
assert(
|
|
230
|
+
runHook(
|
|
231
|
+
payload({
|
|
232
|
+
cwd: fixtureDir,
|
|
233
|
+
agentType: 'pc-dev',
|
|
234
|
+
toolName: 'Edit',
|
|
235
|
+
toolInput: { file_path: gatePath, old_string: 'PASS', new_string: 'FAIL' },
|
|
236
|
+
}),
|
|
237
|
+
).decision === 'deny',
|
|
238
|
+
'Edit em gate file (qualquer conteúdo) dispara igual — superfície inteira é exclusiva do QA',
|
|
239
|
+
);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// ---------------------------------------------------------------------------
|
|
243
|
+
// Predicado 3 — Dev não edita código com a story associada em Draft
|
|
244
|
+
// ---------------------------------------------------------------------------
|
|
245
|
+
|
|
246
|
+
function testPredicate3(fixtureDir) {
|
|
247
|
+
console.log('\n-- Predicado 3: Dev × Draft --');
|
|
248
|
+
setBranch(fixtureDir, 'feat/9.9-teste'); // story 9.9 = Draft
|
|
249
|
+
const codeEdit = {
|
|
250
|
+
cwd: fixtureDir,
|
|
251
|
+
toolName: 'Edit',
|
|
252
|
+
toolInput: { file_path: path.join(fixtureDir, 'src', 'foo.js'), old_string: 'a', new_string: 'b' },
|
|
253
|
+
};
|
|
254
|
+
|
|
255
|
+
assert(runHook(payload({ ...codeEdit, agentType: 'pc-dev' })).decision === 'ask', 'pc-dev edita código com story Draft → ask (nunca deny)');
|
|
256
|
+
assert(runHook(payload({ ...codeEdit, agentType: 'pc-qa' })).decision === null, 'AC17: pc-qa edita código com story Draft → allow (predicado é só do Dev)');
|
|
257
|
+
assert(runHook(payload({ ...codeEdit, agentType: 'pc-po' })).decision === null, 'AC17: pc-po edita código com story Draft → allow');
|
|
258
|
+
assert(
|
|
259
|
+
runHook({ cwd: fixtureDir, tool_name: 'Edit', tool_input: codeEdit.toolInput }).decision === 'ask',
|
|
260
|
+
'main thread edita código com story Draft → ask',
|
|
261
|
+
);
|
|
262
|
+
assert(
|
|
263
|
+
runHook(payload({ ...codeEdit, agentType: 'Explore' })).decision === 'ask',
|
|
264
|
+
'agent_type fora do roster edita código com story Draft → ask',
|
|
265
|
+
);
|
|
266
|
+
|
|
267
|
+
// AC17: story Ready/InProgress/InReview/Done → Dev não interferido
|
|
268
|
+
setBranch(fixtureDir, 'feat/9.8-teste'); // story 9.8 = Ready
|
|
269
|
+
assert(
|
|
270
|
+
runHook(payload({ ...codeEdit, agentType: 'pc-dev' })).decision === null,
|
|
271
|
+
'AC17: pc-dev edita código com story Ready → allow (sem interferência)',
|
|
272
|
+
);
|
|
273
|
+
|
|
274
|
+
// AC16: branch fora do padrão → pass-through
|
|
275
|
+
setBranch(fixtureDir, 'nonsense-branch-sem-barra');
|
|
276
|
+
assert(
|
|
277
|
+
runHook(payload({ ...codeEdit, agentType: 'pc-dev' })).decision === null,
|
|
278
|
+
'AC16: branch fora do padrão → pass-through silencioso',
|
|
279
|
+
);
|
|
280
|
+
|
|
281
|
+
// AC16: token resolvido mas story inexistente em disco → pass-through
|
|
282
|
+
setBranch(fixtureDir, 'feat/9.999-inexistente');
|
|
283
|
+
assert(
|
|
284
|
+
runHook(payload({ ...codeEdit, agentType: 'pc-dev' })).decision === null,
|
|
285
|
+
'AC16: story inexistente em disco → pass-through silencioso',
|
|
286
|
+
);
|
|
287
|
+
|
|
288
|
+
// AC16: captura sem prefixo fixo — branch docs/ (contraexemplo vivo desta própria story)
|
|
289
|
+
setBranch(fixtureDir, 'docs/9.9-nome-qualquer');
|
|
290
|
+
assert(
|
|
291
|
+
runHook(payload({ ...codeEdit, agentType: 'pc-dev' })).decision === 'ask',
|
|
292
|
+
'AC16: captura ^[a-z]+/([^/-]+)- resolve story mesmo em branch docs/ (não só feat/)',
|
|
293
|
+
);
|
|
294
|
+
setBranch(fixtureDir, 'feat/9.9-teste');
|
|
295
|
+
|
|
296
|
+
// Exclusão: editar a própria story (mesmo com story Draft) cai fora do predicado 3
|
|
297
|
+
assert(
|
|
298
|
+
runHook(
|
|
299
|
+
payload({
|
|
300
|
+
cwd: fixtureDir,
|
|
301
|
+
agentType: 'pc-dev',
|
|
302
|
+
toolName: 'Edit',
|
|
303
|
+
toolInput: {
|
|
304
|
+
file_path: path.join(fixtureDir, 'docs', 'stories', '9.9.story.md'),
|
|
305
|
+
old_string: '## Executor Assignment',
|
|
306
|
+
new_string: '## Executor Assignment\n\nnota',
|
|
307
|
+
},
|
|
308
|
+
}),
|
|
309
|
+
).decision === null,
|
|
310
|
+
'editar a própria story (sem tocar Status) não dispara nem predicado 1 nem 3',
|
|
311
|
+
);
|
|
312
|
+
|
|
313
|
+
// Exclusão: qualquer arquivo sob docs/ cai fora do predicado 3
|
|
314
|
+
fs.mkdirSync(path.join(fixtureDir, 'docs', 'mecanismos'), { recursive: true });
|
|
315
|
+
fs.writeFileSync(path.join(fixtureDir, 'docs', 'mecanismos', 'x.md'), 'nota\n');
|
|
316
|
+
assert(
|
|
317
|
+
runHook(
|
|
318
|
+
payload({
|
|
319
|
+
cwd: fixtureDir,
|
|
320
|
+
agentType: 'pc-dev',
|
|
321
|
+
toolName: 'Edit',
|
|
322
|
+
toolInput: {
|
|
323
|
+
file_path: path.join(fixtureDir, 'docs', 'mecanismos', 'x.md'),
|
|
324
|
+
old_string: 'nota',
|
|
325
|
+
new_string: 'nota editada',
|
|
326
|
+
},
|
|
327
|
+
}),
|
|
328
|
+
).decision === null,
|
|
329
|
+
'editar qualquer arquivo sob docs/ (não a story) não dispara o predicado 3',
|
|
330
|
+
);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// ---------------------------------------------------------------------------
|
|
334
|
+
// Alcance declarado (Edit/Write apenas) + input malformado (AC22)
|
|
335
|
+
// ---------------------------------------------------------------------------
|
|
336
|
+
|
|
337
|
+
function testAlcanceEInputMalformado(fixtureDir) {
|
|
338
|
+
console.log('\n-- Alcance (Edit/Write) + input malformado --');
|
|
339
|
+
assert(
|
|
340
|
+
runHook({
|
|
341
|
+
cwd: fixtureDir,
|
|
342
|
+
agent_id: 's',
|
|
343
|
+
agent_type: 'pc-dev',
|
|
344
|
+
tool_name: 'Bash',
|
|
345
|
+
tool_input: { command: `sed -i s/Draft/Done/ ${path.join(fixtureDir, 'docs', 'stories', '9.9.story.md')}` },
|
|
346
|
+
}).decision === null,
|
|
347
|
+
'Bash fora do alcance declarado (AC10/AC12/AC15) → silêncio, mesmo tentando marcar Done',
|
|
348
|
+
);
|
|
349
|
+
assert(
|
|
350
|
+
runHook({
|
|
351
|
+
cwd: fixtureDir,
|
|
352
|
+
agent_id: 's',
|
|
353
|
+
agent_type: 'pc-dev',
|
|
354
|
+
tool_name: 'MultiEdit',
|
|
355
|
+
tool_input: { edits: [{ file_path: 'x', old_string: 'a', new_string: 'b' }] },
|
|
356
|
+
}).decision === null,
|
|
357
|
+
'MultiEdit fora do alcance declarado → silêncio',
|
|
358
|
+
);
|
|
359
|
+
|
|
360
|
+
assert(
|
|
361
|
+
runHookAtPath(HOOK_PATH, 'isto não é json { Status Done quebrado').decision === 'ask',
|
|
362
|
+
'AC22: JSON ilegível + texto sugere Status/Done → ask (nunca deny)',
|
|
363
|
+
);
|
|
364
|
+
assert(
|
|
365
|
+
runHookAtPath(HOOK_PATH, 'isto não é json docs/qa/gates/x.yaml quebrado').decision === 'ask',
|
|
366
|
+
'AC22: JSON ilegível + texto sugere gate file → ask',
|
|
367
|
+
);
|
|
368
|
+
assert(
|
|
369
|
+
runHookAtPath(HOOK_PATH, 'lixo totalmente não relacionado').decision === null,
|
|
370
|
+
'AC22: JSON ilegível sem sugestão de predicado 1/2 → silêncio total (pred. 3 sempre silêncio no ilegível)',
|
|
371
|
+
);
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
// ---------------------------------------------------------------------------
|
|
375
|
+
// AC14/AC18 — instrumento de mutação: gera cópia mutante em tmp, executa
|
|
376
|
+
// como subprocesso com payload que deveria negar/perguntar; o teste FALHA
|
|
377
|
+
// se a mutante permitir a operação.
|
|
378
|
+
// ---------------------------------------------------------------------------
|
|
379
|
+
|
|
380
|
+
function buildMutant(tmpDir) {
|
|
381
|
+
const source = fs.readFileSync(HOOK_PATH, 'utf8');
|
|
382
|
+
const target =
|
|
383
|
+
`function decide(agentType, agentId, { requiredRole, forbiddenRole, denyOnMismatch }) {\n` +
|
|
384
|
+
` if (agentId === undefined) return 'ask'; // main thread — sem identidade de subagente\n` +
|
|
385
|
+
` if (typeof agentType !== 'string' || !ROSTER.has(agentType)) return 'ask'; // fora do roster fechado\n\n` +
|
|
386
|
+
` const isMatch = requiredRole ? agentType === requiredRole : agentType !== forbiddenRole;\n` +
|
|
387
|
+
` if (isMatch) return 'allow';\n` +
|
|
388
|
+
` return denyOnMismatch ? 'deny' : 'ask';\n` +
|
|
389
|
+
`}`;
|
|
390
|
+
const mutated = `function decide(agentType, agentId, { requiredRole, forbiddenRole, denyOnMismatch }) {\n return 'allow'; // MUTANTE — checagem de identidade removida (AC14/AC18)\n}`;
|
|
391
|
+
|
|
392
|
+
if (!source.includes(target)) {
|
|
393
|
+
throw new Error('Instrumento de mutação desalinhado com o fonte real do hook — AC14/AC18 não são prova válida.');
|
|
394
|
+
}
|
|
395
|
+
const mutantSource = source.replace(target, mutated);
|
|
396
|
+
const mutantPath = path.join(tmpDir, 'enforce-cycle-authority.mutant.cjs');
|
|
397
|
+
fs.writeFileSync(mutantPath, mutantSource);
|
|
398
|
+
return mutantPath;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
function testMutacao(fixtureDir) {
|
|
402
|
+
console.log('\n-- AC14/AC18: instrumento de mutação (mutante deve permitir; original deve negar/perguntar) --');
|
|
403
|
+
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'eca-mutant-'));
|
|
404
|
+
const mutantPath = buildMutant(tmpDir);
|
|
405
|
+
|
|
406
|
+
setBranch(fixtureDir, 'feat/9.9-teste');
|
|
407
|
+
|
|
408
|
+
// AC14 — predicado 1 (Done): payload que o hook REAL nega (pc-dev)
|
|
409
|
+
const doneDenyPayload = JSON.stringify(
|
|
410
|
+
payload({
|
|
411
|
+
cwd: fixtureDir,
|
|
412
|
+
agentType: 'pc-dev',
|
|
413
|
+
toolName: 'Edit',
|
|
414
|
+
toolInput: { file_path: path.join(fixtureDir, 'docs', 'stories', '9.9.story.md'), old_string: 'Draft', new_string: 'Done' },
|
|
415
|
+
}),
|
|
416
|
+
);
|
|
417
|
+
assert(runHookAtPath(HOOK_PATH, doneDenyPayload).decision === 'deny', 'controle: hook REAL nega pc-dev marcando Done (pré-condição da prova)');
|
|
418
|
+
assert(
|
|
419
|
+
runHookAtPath(mutantPath, doneDenyPayload).decision === null,
|
|
420
|
+
'AC14: mutante (checagem de identidade removida) PERMITE pc-dev marcar Done — prova que a checagem real é o que trava',
|
|
421
|
+
);
|
|
422
|
+
|
|
423
|
+
// AC18 — predicado 3 (Dev × Draft): payload que o hook REAL pergunta (pc-dev editando código com story Draft)
|
|
424
|
+
const draftAskPayload = JSON.stringify(
|
|
425
|
+
payload({
|
|
426
|
+
cwd: fixtureDir,
|
|
427
|
+
agentType: 'pc-dev',
|
|
428
|
+
toolName: 'Edit',
|
|
429
|
+
toolInput: { file_path: path.join(fixtureDir, 'src', 'foo.js'), old_string: 'a', new_string: 'b' },
|
|
430
|
+
}),
|
|
431
|
+
);
|
|
432
|
+
assert(runHookAtPath(HOOK_PATH, draftAskPayload).decision === 'ask', 'controle: hook REAL pergunta para pc-dev editando código com story Draft (pré-condição da prova)');
|
|
433
|
+
assert(
|
|
434
|
+
runHookAtPath(mutantPath, draftAskPayload).decision === null,
|
|
435
|
+
'AC18: mutante (checagem removida) PERMITE pc-dev editar código com story Draft — prova que a checagem real é o que trava',
|
|
436
|
+
);
|
|
437
|
+
|
|
438
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
// ---------------------------------------------------------------------------
|
|
442
|
+
// SF13.9 — `statusToken`/`doneEvidence` e a ESCADA DE 3 DEGRAUS do predicado 1
|
|
443
|
+
// (`ADR-032` Emenda 3, E3.2/E3.9, com a correção factual da Emenda 4).
|
|
444
|
+
//
|
|
445
|
+
// Duas classes de defeito OPOSTAS, no MESMO `.includes('done')` da linha que
|
|
446
|
+
// decidia o predicado, ambas medidas contra o hook antigo:
|
|
447
|
+
// (a) falso positivo — `InReview — não está Done ainda` negava uma NEGAÇÃO;
|
|
448
|
+
// (b) bypass em dois passos — `Ready (aguardando o Done do QA)` → `Done`
|
|
449
|
+
// passava em silêncio, porque o "antes" já mencionava "done" em prosa.
|
|
450
|
+
// Elas têm testes SEPARADOS de propósito: consertar só o lado `depois` mata
|
|
451
|
+
// (a) e deixa (b) trivial, e o mutante M2 abaixo existe para provar que a
|
|
452
|
+
// suíte percebe exatamente esse conserto parcial.
|
|
453
|
+
// ---------------------------------------------------------------------------
|
|
454
|
+
|
|
455
|
+
const hookMod = require(HOOK_PATH);
|
|
456
|
+
|
|
457
|
+
// ---------------------------------------------------------------------------
|
|
458
|
+
// SF13.10 — TRIPWIRE do roster de status (`ADR-032` Emenda 5, E5.5).
|
|
459
|
+
//
|
|
460
|
+
// `template/.claude/rules/authority.md` é a FONTE DA VERDADE do roster. A parte
|
|
461
|
+
// load-bearing, e a razão de "as cópias citam a fonte em prosa" não bastar:
|
|
462
|
+
// **DERIVAR NÃO É TRIPWIRE**. Um teste que usa o conjunto derivado do documento
|
|
463
|
+
// COMO SE FOSSE a expectativa fica verde para QUALQUER documento — inclusive um
|
|
464
|
+
// errado (é a classe "AC nasce verde", medida antes de ser prescrita). O
|
|
465
|
+
// tripwire é a IGUALDADE entre um literal escrito à mão AQUI e o conjunto
|
|
466
|
+
// derivado de LÁ: a divergência entre os dois, nos DOIS sentidos, é o que ele
|
|
467
|
+
// detecta. Esta duplicação é deliberada e É o mecanismo, não o defeito — um
|
|
468
|
+
// teste não lê prosa (E5.5 item 3).
|
|
469
|
+
// ---------------------------------------------------------------------------
|
|
470
|
+
|
|
471
|
+
const AUTHORITY_PATH = path.join(REPO_ROOT, 'template', '.claude', 'rules', 'authority.md');
|
|
472
|
+
|
|
473
|
+
// Parser ANCORADO por marcador, NUNCA contagem. Marcador ausente devolve
|
|
474
|
+
// `null` — ignorância declarada, jamais conjunto vazio (que seria indistinguível
|
|
475
|
+
// de "um roster de zero status" e deixaria o controle passar em silêncio).
|
|
476
|
+
function deriveRoster(texto) {
|
|
477
|
+
const m = /^<!--\s*roster:status\s*-->\s*\n(.+)$/m.exec(String(texto || ''));
|
|
478
|
+
if (!m) return null;
|
|
479
|
+
return new Set((m[1].match(/`([^`]+)`/g) || []).map((s) => s.slice(1, -1).toLowerCase()));
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
function setEq(a, b) {
|
|
483
|
+
if (!a || !b) return false;
|
|
484
|
+
if (a.size !== b.size) return false;
|
|
485
|
+
for (const x of a) if (!b.has(x)) return false;
|
|
486
|
+
return true;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
function fmtSet(s) {
|
|
490
|
+
return s ? `{${[...s].sort().join(', ')}}` : 'null (âncora ausente)';
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
function testStatusToken() {
|
|
494
|
+
console.log('\n-- SF13.9: statusToken (token do `## Status`, positivo e negativo) --');
|
|
495
|
+
|
|
496
|
+
// POSITIVOS — os 3 passos, cada um com o caso que responde por ele.
|
|
497
|
+
const positivos = [
|
|
498
|
+
['Ready', 'caso base'],
|
|
499
|
+
['Ready (validado pelo PO, 10/10)', 'anotação depois do token — o defeito de FINDING-8'],
|
|
500
|
+
['Ready — aguardando dev', 'travessão'],
|
|
501
|
+
['**Ready**', 'normalizeStatusText (inalterada) responde por este'],
|
|
502
|
+
['Ready.', 'pontuação não é parte do token'],
|
|
503
|
+
['`Ready`', 'crase é decoração'],
|
|
504
|
+
];
|
|
505
|
+
for (const [texto, porque] of positivos) {
|
|
506
|
+
assert(hookMod.statusToken(texto) === 'ready', `statusToken(${JSON.stringify(texto)}) === "ready" — ${porque}`);
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
// NEGATIVOS — pareados na MESMA rodada. Sem eles, `() => 'ready'` passaria.
|
|
510
|
+
const negativos = [
|
|
511
|
+
['~~Ready~~', '', '`~` fora da decoração: riscado é ambíguo (a mesma função alimenta um deny)'],
|
|
512
|
+
['Readyish', 'readyish', 'não é o token'],
|
|
513
|
+
['Not Ready', 'not', 'negação NUNCA dispara — régua da casa aplicada a uma máquina'],
|
|
514
|
+
['ainda nao esta Ready', 'ainda', 'idem'],
|
|
515
|
+
['', '', 'string vazia'],
|
|
516
|
+
['Draft', 'draft', 'outro token do roster'],
|
|
517
|
+
];
|
|
518
|
+
for (const [texto, esperado, porque] of negativos) {
|
|
519
|
+
const t = hookMod.statusToken(texto);
|
|
520
|
+
assert(t === esperado && t !== 'ready', `statusToken(${JSON.stringify(texto)}) === ${JSON.stringify(esperado)} (≠ "ready") — ${porque}`);
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
// `InProgress` — o status MANDATÓRIO que faltava nas tabelas de fronteira
|
|
524
|
+
// (SF13.10; `ADR-032` Emenda 5, E5.1/E5.6 item 3). É o caso alcançável que
|
|
525
|
+
// teria pego o defeito do roster antes de ele ser publicado 3 vezes: uma
|
|
526
|
+
// story fica `InProgress` exatamente ENQUANTO um dev trabalha nela, e o
|
|
527
|
+
// corpus é fotografado no repouso — um controle que só roda no repouso não
|
|
528
|
+
// observa os estados que só existem em movimento. Duas rotas de entrada:
|
|
529
|
+
// o dev começando, e cada `FAIL` do gate devolvendo a story ao loop de fix.
|
|
530
|
+
const emAndamento = [
|
|
531
|
+
['InProgress', 'caso base'],
|
|
532
|
+
['**InProgress**', 'normalizeStatusText responde por este'],
|
|
533
|
+
['`InProgress`', 'crase é decoração'],
|
|
534
|
+
['InProgress (AC-3 em curso)', 'anotação depois do token'],
|
|
535
|
+
['InProgress — dev trabalhando', 'travessão'],
|
|
536
|
+
];
|
|
537
|
+
for (const [texto, porque] of emAndamento) {
|
|
538
|
+
assert(hookMod.statusToken(texto) === 'inprogress', `statusToken(${JSON.stringify(texto)}) === "inprogress" — ${porque}`);
|
|
539
|
+
}
|
|
540
|
+
// Negativo pareado na MESMA rodada — sem ele, `() => 'inprogress'` passaria.
|
|
541
|
+
assert(
|
|
542
|
+
hookMod.statusToken('Not InProgress') === 'not' && hookMod.statusToken('Not InProgress') !== 'inprogress',
|
|
543
|
+
'statusToken("Not InProgress") === "not" (≠ "inprogress") — negação NUNCA vira o token, também aqui',
|
|
544
|
+
);
|
|
545
|
+
|
|
546
|
+
// `#` FORA da decoração — restrição NORMATIVA (E3.2 + Emenda 4 E4.1/E4.2).
|
|
547
|
+
// A fixture é `## Done — …` e não `## QA Results — Done`: medido, com `#` no
|
|
548
|
+
// strip o segundo caso dá token `qa`, não `done`, e o mutante SOBREVIVERIA.
|
|
549
|
+
assert(
|
|
550
|
+
hookMod.statusToken('## Done — selado pelo QA') === '',
|
|
551
|
+
'`#` fora do conjunto de decoração: statusToken("## Done — selado pelo QA") === "" (contém o vazamento de extractStatusSection estruturalmente)',
|
|
552
|
+
);
|
|
553
|
+
assert(
|
|
554
|
+
hookMod.doneEvidence('## Done — selado pelo QA') === 'mencao',
|
|
555
|
+
'seção VAZADA vira evidência `mencao` (⇒ ask), nunca `token` (⇒ deny) — é esta a contenção do gatilho T-E3-EXTRATOR',
|
|
556
|
+
);
|
|
557
|
+
|
|
558
|
+
// Corpus REAL — guard que sempre dispara — e o LITERAL do tripwire.
|
|
559
|
+
//
|
|
560
|
+
// O `roster` abaixo é escrito à mão e serve a DUAS funções: é o conjunto
|
|
561
|
+
// aceito pelo controle de corpus, e é o lado INDEPENDENTE da igualdade que
|
|
562
|
+
// detecta divergência contra `authority.md` (o tripwire, logo abaixo).
|
|
563
|
+
//
|
|
564
|
+
// FECHAMENTO do achado que a SF13.9 registrou aqui como "NÃO corrigido"
|
|
565
|
+
// (SF13.10; `ADR-032` Emenda 5, E5.1/E5.3/E5.5) — o comentário anterior
|
|
566
|
+
// envelheceu e está REMOVIDO, não só substituído: a enumeração de `ADR-032`
|
|
567
|
+
// `E3.2`/`E3.12` estava errada nos DOIS sentidos. Omitia `inprogress`, que é
|
|
568
|
+
// MANDATÓRIO no ciclo, e incluía `blocked`, que nenhum documento do framework
|
|
569
|
+
// define como status de story — o `blocked` do motor de pod/pipeline e o do
|
|
570
|
+
// `state.json` de `pc-wave` são OUTRO namespace (fase de execução, não
|
|
571
|
+
// lifecycle), e nunca devem ser unificados por semelhança de nome. Leitura
|
|
572
|
+
// SUPERADA, não repetir. O roster vigente tem CINCO status e a sua fonte
|
|
573
|
+
// deixou de ser o ADR: é `template/.claude/rules/authority.md`.
|
|
574
|
+
//
|
|
575
|
+
// Custo declarado de tirar `blocked` daqui: se alguém marcar uma story como
|
|
576
|
+
// `Blocked`, este controle fica VERMELHO. Esse vermelho é o sinal PRETENDIDO,
|
|
577
|
+
// não um falso positivo — um status sem dono de transição é exatamente o
|
|
578
|
+
// evento que este guard existe para detectar (gatilho `T-E5-BLOCKED`: se
|
|
579
|
+
// `Blocked` for desejado, ele entra em `authority.md` PRIMEIRO).
|
|
580
|
+
const dir = path.join(REPO_ROOT, 'docs', 'stories');
|
|
581
|
+
const roster = new Set(['draft', 'ready', 'inprogress', 'inreview', 'done']);
|
|
582
|
+
const files = fs.readdirSync(dir).filter((f) => f.endsWith('.story.md'));
|
|
583
|
+
const fora = files.filter((f) => !roster.has(hookMod.statusToken(hookMod.extractStatusSection(fs.readFileSync(path.join(dir, f), 'utf8')))));
|
|
584
|
+
assert(fora.length === 0, `corpus real (${files.length} stories): todo token cai dentro do roster fechado de 5 (fora: ${fora.length}${fora.length ? ` — ${fora.join(', ')}` : ''})`);
|
|
585
|
+
|
|
586
|
+
// TRIPWIRE — POSITIVO: o documento REAL bate com o literal, nos dois sentidos.
|
|
587
|
+
// É esta asserção que quebra se `authority.md` e este teste divergirem, em
|
|
588
|
+
// QUALQUER direção (status a mais, a menos, renomeado, ou âncora removida).
|
|
589
|
+
const derivadoReal = deriveRoster(fs.readFileSync(AUTHORITY_PATH, 'utf8'));
|
|
590
|
+
assert(
|
|
591
|
+
setEq(derivadoReal, roster),
|
|
592
|
+
`TRIPWIRE (positivo): roster derivado de authority.md === literal deste teste — derivado=${fmtSet(derivadoReal)}, literal=${fmtSet(roster)}`,
|
|
593
|
+
);
|
|
594
|
+
|
|
595
|
+
// TRIPWIRE — NEGATIVOS: os mutantes APLICADOS, não apenas nomeados (`Emenda
|
|
596
|
+
// 4`, E4.4). Sem eles um `assert(true)` disfarçado passaria por tripwire.
|
|
597
|
+
const DOC_OK = '<!-- roster:status -->\n`Draft` · `Ready` · `InProgress` · `InReview` · `Done`\n';
|
|
598
|
+
const ROSTER_VELHO_DO_ADR = new Set(['draft', 'ready', 'inreview', 'done', 'blocked']);
|
|
599
|
+
const mutantes = [
|
|
600
|
+
['M1 (doc SEM `InProgress` — o defeito real, publicado 3×)', '<!-- roster:status -->\n`Draft` · `Ready` · `InReview` · `Done`\n', roster],
|
|
601
|
+
['M2 (doc COM `Blocked` a mais — o defeito oposto)', '<!-- roster:status -->\n`Draft` · `Ready` · `InProgress` · `InReview` · `Done` · `Blocked`\n', roster],
|
|
602
|
+
['M3 (âncora AUSENTE — o estado de authority.md antes da SF13.10)', '# Matriz de Autoridade\n\n`Draft` · `Ready` · `InProgress` · `InReview` · `Done`\n', roster],
|
|
603
|
+
['M4 (doc correto × literal VELHO do ADR)', DOC_OK, ROSTER_VELHO_DO_ADR],
|
|
604
|
+
['M5 (doc renomeia `InProgress` → `WIP`)', '<!-- roster:status -->\n`Draft` · `Ready` · `WIP` · `InReview` · `Done`\n', roster],
|
|
605
|
+
];
|
|
606
|
+
for (const [nome, doc, literal] of mutantes) {
|
|
607
|
+
const d = deriveRoster(doc);
|
|
608
|
+
assert(setEq(d, literal) === false, `TRIPWIRE (negativo) ${nome} ⇒ VERMELHO — derivado=${fmtSet(d)}`);
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
// CASO BASE, pareado com os 5 negativos na MESMA rodada: sem ele, um `setEq`
|
|
612
|
+
// que devolvesse `false` sempre passaria nos 5 acima e o tripwire seria vácuo.
|
|
613
|
+
assert(
|
|
614
|
+
setEq(deriveRoster(DOC_OK), roster) === true,
|
|
615
|
+
'TRIPWIRE (caso base): doc correto × literal correto ⇒ VERDE — prova que os 5 negativos DISCRIMINAM, não que setEq sempre reprova',
|
|
616
|
+
);
|
|
617
|
+
|
|
618
|
+
// ANTI-PADRÃO, medido e não só afirmado: um checador INGÊNUO (que só verifica
|
|
619
|
+
// "a âncora existe") fica verde no doc correto E no mutante M1. É a prova de
|
|
620
|
+
// que DERIVAR SOZINHO NÃO É TRIPWIRE — e a razão de o literal independente
|
|
621
|
+
// acima ser obrigatório, não decorativo (`ADR-032` Emenda 5, E5.5).
|
|
622
|
+
const ingenuo = (doc) => deriveRoster(doc) !== null;
|
|
623
|
+
assert(
|
|
624
|
+
ingenuo(DOC_OK) === true && ingenuo(mutantes[0][1]) === true,
|
|
625
|
+
'ANTI-PADRÃO: checador "a âncora existe" NÃO discrimina (verde no doc correto E no M1) — derivar sozinho não é tripwire',
|
|
626
|
+
);
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
function testDoneEvidence() {
|
|
630
|
+
console.log('\n-- SF13.9: doneEvidence (3 valores, cada um com caso alcançável) --');
|
|
631
|
+
|
|
632
|
+
for (const t of ['Done', 'Done (gate CONCERNS)', '**Done**', '`Done`', 'Done — selado pelo QA']) {
|
|
633
|
+
assert(hookMod.doneEvidence(t) === 'token', `doneEvidence(${JSON.stringify(t)}) === "token" (evidência FIRME ⇒ deny-capaz)`);
|
|
634
|
+
}
|
|
635
|
+
for (const t of ['Not Done', '~~Done~~', 'QA Done', 'Ready (o QA ainda nao marcou Done)', 'Undone', 'Concluida (Done)']) {
|
|
636
|
+
assert(hookMod.doneEvidence(t) === 'mencao', `doneEvidence(${JSON.stringify(t)}) === "mencao" (ambígua ⇒ ask, NUNCA deny e NUNCA silêncio)`);
|
|
637
|
+
}
|
|
638
|
+
// `InProgress` entra aqui (SF13.10, `ADR-032` Emenda 5, E5.6 item 3): uma
|
|
639
|
+
// story em andamento NÃO está sendo selada, logo o P1 tem de SILENCIAR sobre
|
|
640
|
+
// ela — inclusive sob decoração e anotação, que é como o dev de fato escreve.
|
|
641
|
+
for (const t of ['Ready', 'Draft', '', 'InReview', 'InProgress', '**InProgress**', 'InProgress (AC-3 em curso)', 'InProgress — dev trabalhando']) {
|
|
642
|
+
assert(hookMod.doneEvidence(t) === 'nenhuma', `doneEvidence(${JSON.stringify(t)}) === "nenhuma" (⇒ silêncio)`);
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
// Corpus REAL: o degrau do meio não acrescenta UMA interrupção sequer sobre
|
|
646
|
+
// material existente — é o controle de ruído do degrau novo.
|
|
647
|
+
const dir = path.join(REPO_ROOT, 'docs', 'stories');
|
|
648
|
+
const files = fs.readdirSync(dir).filter((f) => f.endsWith('.story.md'));
|
|
649
|
+
const contagem = { token: 0, mencao: 0, nenhuma: 0, outro: 0 };
|
|
650
|
+
for (const f of files) {
|
|
651
|
+
const ev = hookMod.doneEvidence(hookMod.extractStatusSection(fs.readFileSync(path.join(dir, f), 'utf8')));
|
|
652
|
+
if (contagem[ev] === undefined) contagem.outro += 1;
|
|
653
|
+
else contagem[ev] += 1;
|
|
654
|
+
}
|
|
655
|
+
assert(
|
|
656
|
+
contagem.mencao === 0 && contagem.outro === 0 && contagem.token + contagem.nenhuma === files.length,
|
|
657
|
+
`corpus real (${files.length} stories): mencao=${contagem.mencao}, fora dos 3 valores=${contagem.outro} — o degrau novo não gera ruído sobre material existente`,
|
|
658
|
+
);
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
// Payload de transição de `## Status` por Write, com "antes" real em disco.
|
|
662
|
+
function transicao(dir, antes, depois, agentType) {
|
|
663
|
+
const p = path.join(dir, 'w.story.md');
|
|
664
|
+
fs.writeFileSync(p, `## Status\n\n${antes}\n`);
|
|
665
|
+
const input = { tool_name: 'Write', tool_input: { file_path: p, content: `## Status\n\n${depois}\n` }, cwd: dir };
|
|
666
|
+
if (agentType !== undefined) {
|
|
667
|
+
input.agent_type = agentType;
|
|
668
|
+
input.agent_id = 'sub-1';
|
|
669
|
+
}
|
|
670
|
+
return input;
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
function testEscadaDoPredicado1() {
|
|
674
|
+
console.log('\n-- SF13.9: predicado 1, a escada de 3 degraus (os 4 desfechos) --');
|
|
675
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'eca-escada-'));
|
|
676
|
+
|
|
677
|
+
// Degrau 1 — evidência FIRME ⇒ deny-capaz.
|
|
678
|
+
assert(hookMod.evaluatePredicate1(transicao(dir, 'Draft', 'Done', 'pc-dev')) === 'deny', 'Draft→Done com pc-dev ⇒ deny (evidência firme + papel positivamente errado)');
|
|
679
|
+
assert(hookMod.evaluatePredicate1(transicao(dir, 'Draft', 'Done', 'pc-qa')) === 'allow', 'Draft→Done com pc-qa ⇒ allow (silêncio) — o papel exigido não é interrompido');
|
|
680
|
+
assert(hookMod.evaluatePredicate1(transicao(dir, 'Draft', 'Done', undefined)) === 'ask', 'Draft→Done sem identidade (main thread) ⇒ ask — identidade ambígua nunca vira deny');
|
|
681
|
+
|
|
682
|
+
// Degrau 2 — evidência AMBÍGUA ⇒ ask. Os 2 casos do dono.
|
|
683
|
+
assert(hookMod.evaluatePredicate1(transicao(dir, 'Draft', 'InReview — nao esta Done ainda', 'pc-dev')) === 'ask', 'FALSO POSITIVO FECHADO: Draft→"InReview — nao esta Done ainda" ⇒ ask, não mais deny');
|
|
684
|
+
assert(hookMod.evaluatePredicate1(transicao(dir, 'Draft', 'Ready (o QA ainda nao marcou Done)', 'pc-dev')) === 'ask', 'Draft→"Ready (o QA ainda nao marcou Done)" ⇒ ask (menção, não token)');
|
|
685
|
+
assert(hookMod.evaluatePredicate1(transicao(dir, 'Draft', '~~Done~~', 'pc-dev')) === 'ask', 'Draft→"~~Done~~" ⇒ ask — riscado é ambíguo, nunca deny');
|
|
686
|
+
assert(hookMod.evaluatePredicate1(transicao(dir, 'Draft', 'QA Done', 'pc-dev')) === 'ask', 'Draft→"QA Done" ⇒ ask — NÃO negar, mas também NUNCA calar (é o bypass que o token puro abriria)');
|
|
687
|
+
|
|
688
|
+
// Degrau 3 — ausência de evidência ⇒ silêncio.
|
|
689
|
+
assert(hookMod.evaluatePredicate1(transicao(dir, 'Draft', 'Ready', 'pc-dev')) === null, 'Draft→Ready puro com pc-dev ⇒ silêncio (o predicado 1 não é sobre Ready)');
|
|
690
|
+
assert(hookMod.evaluatePredicate1(transicao(dir, 'Draft', 'InReview', 'pc-dev')) === null, 'Draft→InReview ⇒ silêncio');
|
|
691
|
+
|
|
692
|
+
// "Já estava Done" ⇒ silêncio (token no `antes`, e SÓ token).
|
|
693
|
+
assert(hookMod.evaluatePredicate1(transicao(dir, 'Done', 'Done (gate CONCERNS)', 'pc-dev')) === null, 'Done→Done (gate CONCERNS) ⇒ silêncio — já estava Done, não é transição');
|
|
694
|
+
|
|
695
|
+
// O BYPASS EM DOIS PASSOS, fechado. Menção no "antes" NÃO é "já estava Done".
|
|
696
|
+
assert(
|
|
697
|
+
hookMod.evaluatePredicate1(transicao(dir, 'Ready (aguardando o Done do QA)', 'Done', 'pc-dev')) === 'deny',
|
|
698
|
+
'BYPASS FECHADO: "Ready (aguardando o Done do QA)"→Done com pc-dev ⇒ deny (antes era `mencao`, não `token`)',
|
|
699
|
+
);
|
|
700
|
+
assert(
|
|
701
|
+
hookMod.evaluatePredicate1(transicao(dir, 'Not Done', 'Done', 'pc-dev')) === 'deny',
|
|
702
|
+
'BYPASS FECHADO (2ª forma): "Not Done"→Done com pc-dev ⇒ deny',
|
|
703
|
+
);
|
|
704
|
+
|
|
705
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
function testMotivoNomeado() {
|
|
709
|
+
console.log('\n-- SF13.9: o ask por EVIDÊNCIA ambígua é distinguível do ask por IDENTIDADE ambígua --');
|
|
710
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'eca-motivo-'));
|
|
711
|
+
const RE_AMBIGUA = /evid[eê]ncia amb[ií]gua/i;
|
|
712
|
+
|
|
713
|
+
const porEvidencia = runHookAtPath(HOOK_PATH, JSON.stringify(transicao(dir, 'Draft', 'Ready (o QA ainda nao marcou Done)', 'pc-dev')));
|
|
714
|
+
assert(porEvidencia.decision === 'ask', 'rota real: evidência ambígua ⇒ ask');
|
|
715
|
+
assert(RE_AMBIGUA.test(porEvidencia.stdout), 'rota real: a razão NOMEIA "evidência ambígua" — decisão nua não distingue os dois motivos');
|
|
716
|
+
|
|
717
|
+
const porIdentidade = runHookAtPath(HOOK_PATH, JSON.stringify(transicao(dir, 'Draft', 'Done', undefined)));
|
|
718
|
+
assert(porIdentidade.decision === 'ask', 'rota real: identidade ambígua (evidência FIRME) ⇒ ask');
|
|
719
|
+
assert(
|
|
720
|
+
!RE_AMBIGUA.test(porIdentidade.stdout),
|
|
721
|
+
'CONTROLE NEGATIVO: o ask por identidade ambígua NÃO diz "evidência ambígua" — senão rotular todo ask passaria',
|
|
722
|
+
);
|
|
723
|
+
|
|
724
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
function testPredicate3PorToken(fixtureDir) {
|
|
728
|
+
console.log('\n-- SF13.9: predicado 3 decide por token (prosa no Status não o cega) --');
|
|
729
|
+
const storyPath = path.join(fixtureDir, 'docs', 'stories', '9.9.story.md');
|
|
730
|
+
const original = fs.readFileSync(storyPath, 'utf8');
|
|
731
|
+
setBranch(fixtureDir, 'feat/9.9-teste');
|
|
732
|
+
const codigo = { cwd: fixtureDir, tool_name: 'Edit', tool_input: { file_path: path.join(fixtureDir, 'src', 'foo.js') }, agent_type: 'pc-dev', agent_id: 'sub-1' };
|
|
733
|
+
|
|
734
|
+
fs.writeFileSync(storyPath, '# Story 9.9\n\n## Status\n\nDraft (aguardando PO)\n');
|
|
735
|
+
assert(hookMod.evaluatePredicate3(codigo) === 'ask', 'P3: story em "Draft (aguardando PO)" ⇒ ask (a igualdade estrita ficava MUDA aqui)');
|
|
736
|
+
|
|
737
|
+
fs.writeFileSync(storyPath, '# Story 9.9\n\n## Status\n\nReady (validado pelo PO)\n');
|
|
738
|
+
assert(hookMod.evaluatePredicate3(codigo) === null, 'P3: story em "Ready (validado pelo PO)" ⇒ silêncio (controle negativo pareado)');
|
|
739
|
+
|
|
740
|
+
fs.writeFileSync(storyPath, '# Story 9.9\n\n## Status\n\nNot Draft\n');
|
|
741
|
+
assert(hookMod.evaluatePredicate3(codigo) === null, 'P3: "Not Draft" ⇒ silêncio — negação nunca dispara');
|
|
742
|
+
|
|
743
|
+
fs.writeFileSync(storyPath, original);
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
// ---------------------------------------------------------------------------
|
|
747
|
+
// Mutantes de CLASSE da escada. `E3.12` item 12 é explícito: revertido
|
|
748
|
+
// `doneEvidence` para `.includes('done')` nos dois lados, DOIS testes têm de
|
|
749
|
+
// ficar vermelhos — um o falso positivo, outro o bypass. Se só um cair, a
|
|
750
|
+
// escada não está completa.
|
|
751
|
+
// ---------------------------------------------------------------------------
|
|
752
|
+
|
|
753
|
+
const MARCADOR_EVIDENCIA = [
|
|
754
|
+
' const antes = doneEvidence(extractStatusSection(sim.before));',
|
|
755
|
+
' const depois = doneEvidence(extractStatusSection(sim.after));',
|
|
756
|
+
].join('\n');
|
|
757
|
+
|
|
758
|
+
const INCLUDES_ANTES = " const antes = normalizeStatusText(extractStatusSection(sim.before)).includes('done') ? 'token' : 'nenhuma';";
|
|
759
|
+
const INCLUDES_DEPOIS = " const depois = normalizeStatusText(extractStatusSection(sim.after)).includes('done') ? 'token' : 'nenhuma';";
|
|
760
|
+
|
|
761
|
+
function buildVariante(tmpDir, nome, marcador, substituto) {
|
|
762
|
+
const source = fs.readFileSync(HOOK_PATH, 'utf8');
|
|
763
|
+
if (!source.includes(marcador)) {
|
|
764
|
+
throw new Error(`Instrumento de mutação desalinhado com o fonte real (${nome}) — este teste não é prova válida.`);
|
|
765
|
+
}
|
|
766
|
+
const p = path.join(tmpDir, `enforce-cycle-authority.${nome}.cjs`);
|
|
767
|
+
fs.writeFileSync(p, source.split(marcador).join(substituto));
|
|
768
|
+
return require(p);
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
function testMutantesDaEscada() {
|
|
772
|
+
console.log('\n-- SF13.9: mutantes de classe da escada (as 2 direções de erro, separadas) --');
|
|
773
|
+
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'eca-escada-mut-'));
|
|
774
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'eca-escada-fix-'));
|
|
775
|
+
|
|
776
|
+
const falsoPositivo = () => transicao(dir, 'Draft', 'InReview — nao esta Done ainda', 'pc-dev');
|
|
777
|
+
const bypass = () => transicao(dir, 'Ready (aguardando o Done do QA)', 'Done', 'pc-dev');
|
|
778
|
+
|
|
779
|
+
// M1 — `.includes('done')` nos DOIS lados (o predicado superado, inteiro).
|
|
780
|
+
const m1 = buildVariante(tmpDir, 'M1', MARCADOR_EVIDENCIA, `${INCLUDES_ANTES}\n${INCLUDES_DEPOIS}`);
|
|
781
|
+
assert(m1.evaluatePredicate1(falsoPositivo()) === 'deny', 'MUTANTE M1 (includes nos 2 lados): ressuscita o FALSO POSITIVO (deny sobre uma negação) — logo o teste do degrau do meio o derruba');
|
|
782
|
+
assert(m1.evaluatePredicate1(bypass()) === null, 'MUTANTE M1: ressuscita o BYPASS (silêncio) — logo o teste do bypass o derruba. São 2 testes distintos, como o ADR exige');
|
|
783
|
+
|
|
784
|
+
// M2 — SÓ o lado `depois` consertado: o conserto parcial que ABRE o bypass.
|
|
785
|
+
// É o mutante mais importante da story: hoje o passo 1 do bypass é barrado
|
|
786
|
+
// pelo próprio falso positivo; conserte só o `depois` e os 2 passos ficam
|
|
787
|
+
// triviais. O falso positivo fica CERTO aqui — só o bypass o denuncia.
|
|
788
|
+
const m2 = buildVariante(tmpDir, 'M2', MARCADOR_EVIDENCIA, `${INCLUDES_ANTES}\n const depois = doneEvidence(extractStatusSection(sim.after));`);
|
|
789
|
+
assert(m2.evaluatePredicate1(falsoPositivo()) === 'ask', 'MUTANTE M2 (só o `depois` consertado): o falso positivo fica CERTO — este teste NÃO o pega, de propósito');
|
|
790
|
+
assert(m2.evaluatePredicate1(bypass()) === null, 'MUTANTE M2: o BYPASS continua ABERTO (silêncio) — quem o mata é o teste do bypass, e só ele');
|
|
791
|
+
|
|
792
|
+
// M3 — só o lado `antes` consertado (a direção oposta de M2).
|
|
793
|
+
const m3 = buildVariante(tmpDir, 'M3', MARCADOR_EVIDENCIA, ` const antes = doneEvidence(extractStatusSection(sim.before));\n${INCLUDES_DEPOIS}`);
|
|
794
|
+
assert(m3.evaluatePredicate1(falsoPositivo()) === 'deny', 'MUTANTE M3 (só o `antes` consertado): o FALSO POSITIVO volta — distinto de M2, como o ADR exige');
|
|
795
|
+
|
|
796
|
+
// M4 — `#` acrescentado ao strip de decoração (a restrição normativa).
|
|
797
|
+
const m4 = buildVariante(tmpDir, 'M4', "norm.replace(/^[\\s*_`]+/, '')", "norm.replace(/^[\\s*_`#]+/, '')");
|
|
798
|
+
assert(m4.statusToken('## Done — selado pelo QA') === 'done', 'MUTANTE M4 (`#` no strip): seção VAZADA passa a produzir token `done` ⇒ deny — logo a asserção de `#`-fora-da-decoração o derruba');
|
|
799
|
+
|
|
800
|
+
// M5 — `~` acrescentado ao strip: `~~Done~~` viraria deny sobre riscado.
|
|
801
|
+
const m5 = buildVariante(tmpDir, 'M5', "norm.replace(/^[\\s*_`]+/, '')", "norm.replace(/^[\\s*_~`]+/, '')");
|
|
802
|
+
assert(m5.doneEvidence('~~Done~~') === 'token', 'MUTANTE M5 (`~` no strip): `~~Done~~` viraria evidência FIRME ⇒ deny sobre status riscado');
|
|
803
|
+
|
|
804
|
+
// M6 — degrau do meio arrancado: menção volta a virar silêncio.
|
|
805
|
+
const m6 = buildVariante(
|
|
806
|
+
tmpDir,
|
|
807
|
+
'M6',
|
|
808
|
+
" if (depois === 'mencao' && antes === 'nenhuma') {",
|
|
809
|
+
' if (false) {',
|
|
810
|
+
);
|
|
811
|
+
assert(m6.evaluatePredicate1(falsoPositivo()) === null, 'MUTANTE M6 (degrau do meio removido): a menção vira SILÊNCIO — trocaria um falso positivo caro por um falso negativo silencioso');
|
|
812
|
+
|
|
813
|
+
// M7 — P3 de volta à igualdade estrita.
|
|
814
|
+
const m7 = buildVariante(tmpDir, 'M7', ' const status = statusToken(extractStatusSection(storyContent));', ' const status = normalizeStatusText(extractStatusSection(storyContent));');
|
|
815
|
+
const fx = makeFixtureRepo();
|
|
816
|
+
setBranch(fx, 'feat/9.9-teste');
|
|
817
|
+
fs.writeFileSync(path.join(fx, 'docs', 'stories', '9.9.story.md'), '## Status\n\nDraft (aguardando PO)\n');
|
|
818
|
+
assert(
|
|
819
|
+
m7.evaluatePredicate3({ cwd: fx, tool_name: 'Edit', tool_input: { file_path: path.join(fx, 'src', 'foo.js') }, agent_type: 'pc-dev', agent_id: 'sub-1' }) === null,
|
|
820
|
+
'MUTANTE M7 (P3 sem token): prosa no Status volta a CEGAR o predicado 3',
|
|
821
|
+
);
|
|
822
|
+
fs.rmSync(fx, { recursive: true, force: true });
|
|
823
|
+
|
|
824
|
+
// M8 — `buildMessage` nomeia "evidência ambígua" em TODO ask do predicado 1.
|
|
825
|
+
const m8src = fs.readFileSync(HOOK_PATH, 'utf8');
|
|
826
|
+
const marcadorMsg = " if (ev === 'mencao') {";
|
|
827
|
+
if (!m8src.includes(marcadorMsg)) throw new Error('Instrumento de mutação desalinhado (M8).');
|
|
828
|
+
const m8path = path.join(tmpDir, 'enforce-cycle-authority.M8.cjs');
|
|
829
|
+
fs.writeFileSync(m8path, m8src.split(marcadorMsg).join(' if (true) {'));
|
|
830
|
+
const m8out = runHookAtPath(m8path, JSON.stringify(transicao(dir, 'Draft', 'Done', undefined)));
|
|
831
|
+
assert(
|
|
832
|
+
/evid[eê]ncia amb[ií]gua/i.test(m8out.stdout),
|
|
833
|
+
'MUTANTE M8 (rótulo em todo ask): o ask por IDENTIDADE ambígua passa a dizer "evidência ambígua" — logo o controle negativo o derruba',
|
|
834
|
+
);
|
|
835
|
+
|
|
836
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
837
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
// ---------------------------------------------------------------------------
|
|
841
|
+
|
|
842
|
+
function main() {
|
|
843
|
+
console.log('POWER CLAUDE — suíte do hook enforce-cycle-authority.cjs (SF12.3, Blocos C+D+E+L; SF13.9, escada de 3 degraus)\n');
|
|
844
|
+
const fixtureDir = makeFixtureRepo();
|
|
845
|
+
try {
|
|
846
|
+
testPredicate1(fixtureDir);
|
|
847
|
+
testPredicate2(fixtureDir);
|
|
848
|
+
testPredicate3(fixtureDir);
|
|
849
|
+
testAlcanceEInputMalformado(fixtureDir);
|
|
850
|
+
testMutacao(fixtureDir);
|
|
851
|
+
testStatusToken();
|
|
852
|
+
testDoneEvidence();
|
|
853
|
+
testEscadaDoPredicado1();
|
|
854
|
+
testMotivoNomeado();
|
|
855
|
+
testPredicate3PorToken(fixtureDir);
|
|
856
|
+
testMutantesDaEscada();
|
|
857
|
+
} finally {
|
|
858
|
+
fs.rmSync(fixtureDir, { recursive: true, force: true });
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
if (failures > 0) {
|
|
862
|
+
console.error(`\nSuíte FALHOU: ${failures} violação(ões).`);
|
|
863
|
+
process.exit(1);
|
|
864
|
+
}
|
|
865
|
+
console.log('\nSuíte verde.');
|
|
866
|
+
process.exit(0);
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
main();
|