@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,862 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* POWER CLAUDE — validate-story-acs.cjs (Story SF13.1, `ADR-031`)
|
|
6
|
+
*
|
|
7
|
+
* Dado o texto de uma story, roda de fato os oráculos que ela declara (bloco
|
|
8
|
+
* ` ```oraculo `, convenção v1.5 — comando + `# espera:`) e classifica cada
|
|
9
|
+
* AC em: `ok` | `reprovados` (nasce verde / comando quebrado) | `alertas`
|
|
10
|
+
* (oráculo pode ser no-op) | `citacoes` (Guarda forma 3, não conta como
|
|
11
|
+
* cobertura) | `semOraculo` (AC sem bloco — fora do alcance) |
|
|
12
|
+
* `naoDeterminados` (recusa/timeout/reentrância/typo/pós-trabalho/fence
|
|
13
|
+
* vazia — "não consegui determinar", nunca "passou" nem "falhou").
|
|
14
|
+
*
|
|
15
|
+
* Execução SEM shell (`ADR-031` D2): `spawnSync` (mesma família de
|
|
16
|
+
* `execFile*` — nunca `exec`/`shell:true`) com argv TOKENIZADO (aspas
|
|
17
|
+
* simples/duplas respeitadas); allow-list de cabeçalho + sub-allow-list de
|
|
18
|
+
* `git`; metacaractere de shell FORA DE ASPAS ⇒ recusa sem executar,
|
|
19
|
+
* relatório fala. A admissibilidade é decidida por `wouldBeRefused`, função
|
|
20
|
+
* PURA (zero subprocesso — `ADR-031` Emenda 7) — `runOracle` delega a ela
|
|
21
|
+
* como fonte única (Emenda 10, `AC-10f`).
|
|
22
|
+
*
|
|
23
|
+
* "Nasce verde" é a asserção declarada já ser verdadeira hoje contra o
|
|
24
|
+
* disco — não o exit code puro (`ADR-031` D1). É classe DATADA (Emenda 2):
|
|
25
|
+
* `## Status` `Draft`/`Ready` decide; `InReview`/`Done` suspende só o
|
|
26
|
+
* veredito de nasce-verde (`naoDeterminados`, causa
|
|
27
|
+
* `nasce-verde-pos-trabalho`); sem `## Status` ⇒ pré-trabalho. Comando
|
|
28
|
+
* quebrado e no-op são independentes do tempo — nunca suspensos (Emenda 4).
|
|
29
|
+
*
|
|
30
|
+
* Guarda de reentrância (Emenda 7, obrigatória de qualquer modo — a SF13.2
|
|
31
|
+
* roda isto por hook): `VSA_DEPTH` propagado no `env` de cada subprocesso
|
|
32
|
+
* (produtor, `AC-12c`); se o PRÓPRIO processo que chama `validateStory` já
|
|
33
|
+
* tem `VSA_DEPTH >= 1`, nenhum oráculo executa (consumidor, `AC-12b`) —
|
|
34
|
+
* tudo cai em `naoDeterminados`, `causa: "reentrancia"`.
|
|
35
|
+
*
|
|
36
|
+
* `causa` (campo estrutural de `naoDeterminados`, enum FECHADO de 7 —
|
|
37
|
+
* Emendas 8/11/12 + N12): `recusa` | `timeout` | `nasce-verde-pos-trabalho`
|
|
38
|
+
* | `reentrancia` | `arquivo-sem-oraculo` | `assercao-desconhecida` |
|
|
39
|
+
* `oraculo-vazio`. Nenhum consumidor filtra `naoDeterminados` por regex
|
|
40
|
+
* sobre `motivo` (Emenda 8) — o campo estrutural é o contrato. `# espera:`
|
|
41
|
+
* presente e não reconhecido NUNCA cai no default `exit0` (Emenda 11,
|
|
42
|
+
* `AC-16e`) — vira `assercao-desconhecida`, falado.
|
|
43
|
+
*
|
|
44
|
+
* `[Guarda]` — 3 formas endurecidas (B8): `(prova: AC-M)` (AC-M existe e
|
|
45
|
+
* NÃO é ele mesmo nasce-verde — proíbe cadeia guarda→guarda, `AC-5`);
|
|
46
|
+
* `(sem irmão — ausência declarada)` (só quando a asserção do próprio AC é
|
|
47
|
+
* de ausência, `stdout-vazio`/`stdout == 0`, `AC-6`); `(citação de fato
|
|
48
|
+
* pré-existente, não prova de trabalho)` (sempre em `citacoes`, nunca conta
|
|
49
|
+
* como cobertura, `AC-6b`). Forma NÃO reconhecida ⇒ REPROVA "forma de
|
|
50
|
+
* guarda não reconhecida" — destino pinado (Emenda 11, `AC-5b`).
|
|
51
|
+
*
|
|
52
|
+
* Escopo do parser: só dentro de `## Acceptance Criteria`, sem descer em
|
|
53
|
+
* fence aninhado antes da seção (`ADR-031` D2 item 6, B5).
|
|
54
|
+
*
|
|
55
|
+
* O instrumento não é seu próprio primeiro cliente (`ADR-031` Emenda 5/D1)
|
|
56
|
+
* — não há AC nesta story que rode `validateStory` sobre o próprio texto
|
|
57
|
+
* dela (isso seria reentrância, não auditoria); a 1ª aplicação real do
|
|
58
|
+
* self-check nasce-verde é a validação do PO da SF13.2.
|
|
59
|
+
*/
|
|
60
|
+
|
|
61
|
+
const fs = require('node:fs');
|
|
62
|
+
const path = require('node:path');
|
|
63
|
+
const cp = require('node:child_process');
|
|
64
|
+
|
|
65
|
+
const DEFAULT_TIMEOUT_MS = 5000;
|
|
66
|
+
|
|
67
|
+
// `cwd` DECLARADO, nunca ambiente (`ADR-032` `DF-SF13.2-5` parte 2, SF13.2).
|
|
68
|
+
//
|
|
69
|
+
// A convenção documentada sempre disse "os oráculos são executados a partir
|
|
70
|
+
// da raiz do repositório", mas o código presumia `process.cwd()` — duas
|
|
71
|
+
// verdades diferentes. Medido antes do fix, mesma story, mesmo comando
|
|
72
|
+
// (`test -f package.json` sem `[Guarda]`): invocado da raiz ⇒ `reprovados=1`
|
|
73
|
+
// (nasce-verde corretamente detectado); invocado de `docs/` ⇒ `reprovados=0`
|
|
74
|
+
// (o comando roda relativo a `docs/`, `package.json` não existe ali, e o
|
|
75
|
+
// relatório sai limpo por engano). Um PO que abra o terminal na pasta do
|
|
76
|
+
// arquivo — comportamento default de vários editores — lê um relatório que
|
|
77
|
+
// PARECE verde e não é: exatamente a classe de falha silenciosa que a trava
|
|
78
|
+
// de `Ready` existe para matar, aqui no próprio instrumento.
|
|
79
|
+
//
|
|
80
|
+
// `scripts/` é sempre 1 nível abaixo da raiz, no framework e na instalação
|
|
81
|
+
// (ao contrário de `template/.claude/hooks/`, cuja profundidade muda) — por
|
|
82
|
+
// isso a âncora em `__dirname` é determinística. Todo ponto que antes caía
|
|
83
|
+
// em `process.cwd()` passa a cair aqui; quem quiser outra raiz continua
|
|
84
|
+
// podendo declará-la por `opts.cwd`.
|
|
85
|
+
const REPO_ROOT = path.resolve(__dirname, '..');
|
|
86
|
+
|
|
87
|
+
const HEADER_ALLOWLIST = new Set([
|
|
88
|
+
'test', 'grep', 'node', 'git', 'npm', 'ls', 'cat', 'wc', 'diff', 'head', 'tail', 'jq',
|
|
89
|
+
]);
|
|
90
|
+
|
|
91
|
+
const GIT_SUB_ALLOWLIST = new Set(['diff', 'log', 'status', 'ls-files', 'show']);
|
|
92
|
+
|
|
93
|
+
const NAO_DETERMINADOS_CAUSAS = new Set([
|
|
94
|
+
'recusa',
|
|
95
|
+
'timeout',
|
|
96
|
+
'nasce-verde-pos-trabalho',
|
|
97
|
+
'reentrancia',
|
|
98
|
+
'arquivo-sem-oraculo',
|
|
99
|
+
'assercao-desconhecida',
|
|
100
|
+
'oraculo-vazio',
|
|
101
|
+
]);
|
|
102
|
+
|
|
103
|
+
function stripAccents(text) {
|
|
104
|
+
return String(text || '').normalize('NFD').replace(/[\u0300-\u036f]/g, '');
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// ---------------------------------------------------------------------------
|
|
108
|
+
// `## Status` — mesmo precedente de `enforce-cycle-authority.cjs`
|
|
109
|
+
// ---------------------------------------------------------------------------
|
|
110
|
+
|
|
111
|
+
function normalizeStatusText(text) {
|
|
112
|
+
return String(text || '').replace(/\*\*/g, '').toLowerCase().trim();
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function extractStatusSection(content) {
|
|
116
|
+
const match = /^##\s*Status\s*\n([\s\S]*?)(?=\n#{1,2}\s|\s*$)/m.exec(String(content || ''));
|
|
117
|
+
return match ? match[1] : '';
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function readStoryStatus(content) {
|
|
121
|
+
const section = extractStatusSection(content);
|
|
122
|
+
if (!section) return null; // sem `## Status` — pré-trabalho (mutante/fixture isolado)
|
|
123
|
+
return normalizeStatusText(section);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function statusSuspendsNasceVerde(content) {
|
|
127
|
+
const status = readStoryStatus(content);
|
|
128
|
+
if (status === null) return false; // sem `## Status` ⇒ pré-trabalho
|
|
129
|
+
return status.includes('inreview') || status.includes('done');
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// ---------------------------------------------------------------------------
|
|
133
|
+
// Tokenização de argv (aspas simples/duplas respeitadas) + metacaractere
|
|
134
|
+
// FORA de aspas (`ADR-031` Emenda 1) — usadas por `wouldBeRefused` E `runOracle`.
|
|
135
|
+
// ---------------------------------------------------------------------------
|
|
136
|
+
|
|
137
|
+
function tokenizeCommand(cmd) {
|
|
138
|
+
const tokens = [];
|
|
139
|
+
let current = '';
|
|
140
|
+
let started = false;
|
|
141
|
+
let inSingle = false;
|
|
142
|
+
let inDouble = false;
|
|
143
|
+
const s = String(cmd || '');
|
|
144
|
+
for (let i = 0; i < s.length; i++) {
|
|
145
|
+
const ch = s[i];
|
|
146
|
+
if (inSingle) {
|
|
147
|
+
if (ch === "'") { inSingle = false; } else { current += ch; }
|
|
148
|
+
continue;
|
|
149
|
+
}
|
|
150
|
+
if (inDouble) {
|
|
151
|
+
if (ch === '"') { inDouble = false; } else { current += ch; }
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
if (ch === "'") { inSingle = true; started = true; continue; }
|
|
155
|
+
if (ch === '"') { inDouble = true; started = true; continue; }
|
|
156
|
+
// Backslash FORA de aspas escapa o PRÓXIMO caractere literalmente — é o
|
|
157
|
+
// que faz o idioma bash `'\''` (fechar aspa, aspa literal, reabrir aspa)
|
|
158
|
+
// funcionar como concatenação, em vez de inserir um backslash espúrio
|
|
159
|
+
// (achado do QA, gate `sf13.1-validador-de-acs`: AC-10c corrompido).
|
|
160
|
+
if (ch === '\\' && i + 1 < s.length) {
|
|
161
|
+
current += s[i + 1];
|
|
162
|
+
started = true;
|
|
163
|
+
i += 1;
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
166
|
+
if (/\s/.test(ch)) {
|
|
167
|
+
if (started) { tokens.push(current); current = ''; started = false; }
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
current += ch;
|
|
171
|
+
started = true;
|
|
172
|
+
}
|
|
173
|
+
if (started) tokens.push(current);
|
|
174
|
+
return tokens;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Metacaractere de shell (`|`,`;`,`&&`,`||`,`>`,`>>`,`` ` ``,`$(`,`&`) FORA de
|
|
178
|
+
// aspas — checar `|`,`;`,`&`,`>`,`` ` `` já cobre as formas duplicadas.
|
|
179
|
+
function hasUnquotedMetachar(cmd) {
|
|
180
|
+
const s = String(cmd || '');
|
|
181
|
+
let inSingle = false;
|
|
182
|
+
let inDouble = false;
|
|
183
|
+
for (let i = 0; i < s.length; i++) {
|
|
184
|
+
const ch = s[i];
|
|
185
|
+
if (inSingle) {
|
|
186
|
+
if (ch === "'") inSingle = false;
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
if (inDouble) {
|
|
190
|
+
if (ch === '"') inDouble = false;
|
|
191
|
+
continue;
|
|
192
|
+
}
|
|
193
|
+
if (ch === "'") { inSingle = true; continue; }
|
|
194
|
+
if (ch === '"') { inDouble = true; continue; }
|
|
195
|
+
// Mesmo escape de backslash do tokenizer: `\|`/`\&`/... FORA de aspas é
|
|
196
|
+
// o caractere literal escapado, não o metacaractere de shell.
|
|
197
|
+
if (ch === '\\' && i + 1 < s.length) { i += 1; continue; }
|
|
198
|
+
if (ch === '$' && s[i + 1] === '(') return true;
|
|
199
|
+
if (ch === '`' || ch === '|' || ch === ';' || ch === '&' || ch === '>') return true;
|
|
200
|
+
}
|
|
201
|
+
return false;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// ---------------------------------------------------------------------------
|
|
205
|
+
// `wouldBeRefused` — função PURA, zero subprocesso (`ADR-031` Emenda 7, E1).
|
|
206
|
+
// ---------------------------------------------------------------------------
|
|
207
|
+
|
|
208
|
+
function wouldBeRefused(cmd) {
|
|
209
|
+
const s = typeof cmd === 'string' ? cmd : '';
|
|
210
|
+
if (!s.trim()) {
|
|
211
|
+
return { refused: true, reason: 'não consegui determinar: comando vazio' };
|
|
212
|
+
}
|
|
213
|
+
if (hasUnquotedMetachar(s)) {
|
|
214
|
+
return { refused: true, reason: 'não consegui determinar: metacaractere de shell fora de aspas' };
|
|
215
|
+
}
|
|
216
|
+
const tokens = tokenizeCommand(s);
|
|
217
|
+
if (tokens.length === 0) {
|
|
218
|
+
return { refused: true, reason: 'não consegui determinar: comando vazio' };
|
|
219
|
+
}
|
|
220
|
+
const header = tokens[0];
|
|
221
|
+
if (!HEADER_ALLOWLIST.has(header)) {
|
|
222
|
+
return { refused: true, reason: 'não consegui determinar: comando não está na lista permitida' };
|
|
223
|
+
}
|
|
224
|
+
if (header === 'git') {
|
|
225
|
+
const sub = tokens[1];
|
|
226
|
+
if (!GIT_SUB_ALLOWLIST.has(sub)) {
|
|
227
|
+
return { refused: true, reason: 'não consegui determinar: subcomando git não está na lista permitida' };
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
return { refused: false, tokens };
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// ---------------------------------------------------------------------------
|
|
234
|
+
// `runOracle` — `spawnSync` (família `execFile*`, sem shell). Delega a
|
|
235
|
+
// `wouldBeRefused` (fonte única, Emenda 10). Propaga `VSA_DEPTH`
|
|
236
|
+
// incrementado no `env` do filho (produtor da guarda de reentrância, `AC-12c`).
|
|
237
|
+
// ---------------------------------------------------------------------------
|
|
238
|
+
|
|
239
|
+
function runOracle(cmd, options) {
|
|
240
|
+
const opts = options || {};
|
|
241
|
+
const cwd = opts.cwd || REPO_ROOT;
|
|
242
|
+
const timeoutMs = typeof opts.timeoutMs === 'number' ? opts.timeoutMs : DEFAULT_TIMEOUT_MS;
|
|
243
|
+
|
|
244
|
+
const wb = wouldBeRefused(cmd);
|
|
245
|
+
if (wb.refused) {
|
|
246
|
+
return {
|
|
247
|
+
refused: true,
|
|
248
|
+
refusalReason: wb.reason,
|
|
249
|
+
exitCode: null,
|
|
250
|
+
stdout: '',
|
|
251
|
+
stderr: '',
|
|
252
|
+
timedOut: false,
|
|
253
|
+
durationMs: 0,
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
const [bin, ...args] = wb.tokens;
|
|
258
|
+
const baseEnv = opts.env || process.env;
|
|
259
|
+
const currentDepth = parseInt(baseEnv.VSA_DEPTH, 10);
|
|
260
|
+
const nextDepth = Number.isFinite(currentDepth) ? currentDepth + 1 : 1;
|
|
261
|
+
const childEnv = Object.assign({}, baseEnv, { VSA_DEPTH: String(nextDepth) });
|
|
262
|
+
|
|
263
|
+
const start = Date.now();
|
|
264
|
+
const result = cp.spawnSync(bin, args, {
|
|
265
|
+
cwd,
|
|
266
|
+
timeout: timeoutMs,
|
|
267
|
+
encoding: 'utf8',
|
|
268
|
+
env: childEnv,
|
|
269
|
+
killSignal: 'SIGKILL',
|
|
270
|
+
});
|
|
271
|
+
const durationMs = Date.now() - start;
|
|
272
|
+
|
|
273
|
+
const timedOut = !!(result.error && result.error.code === 'ETIMEDOUT') || result.signal === 'SIGKILL';
|
|
274
|
+
const spawnEnoent = !!(result.error && result.error.code === 'ENOENT');
|
|
275
|
+
|
|
276
|
+
return {
|
|
277
|
+
refused: false,
|
|
278
|
+
refusalReason: null,
|
|
279
|
+
exitCode: typeof result.status === 'number' ? result.status : (spawnEnoent ? 127 : null),
|
|
280
|
+
stdout: typeof result.stdout === 'string' ? result.stdout : '',
|
|
281
|
+
stderr: typeof result.stderr === 'string' ? result.stderr : (spawnEnoent ? String(result.error && result.error.message) : ''),
|
|
282
|
+
timedOut,
|
|
283
|
+
durationMs,
|
|
284
|
+
spawnError: result.error ? { code: result.error.code } : null,
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// ---------------------------------------------------------------------------
|
|
289
|
+
// `evaluateAssertion` — as 5 formas de `# espera:` (`ADR-031` D1).
|
|
290
|
+
// ---------------------------------------------------------------------------
|
|
291
|
+
|
|
292
|
+
function evaluateAssertion(result, espera) {
|
|
293
|
+
const stdout = typeof result.stdout === 'string' ? result.stdout : '';
|
|
294
|
+
const type = espera && espera.type;
|
|
295
|
+
switch (type) {
|
|
296
|
+
case 'exit0':
|
|
297
|
+
return result.exitCode === 0;
|
|
298
|
+
case 'exit!=0':
|
|
299
|
+
return result.exitCode !== 0;
|
|
300
|
+
case 'stdout-vazio':
|
|
301
|
+
return stdout.trim() === '';
|
|
302
|
+
case 'stdout-nao-vazio':
|
|
303
|
+
return stdout.trim() !== '';
|
|
304
|
+
case 'stdout==':
|
|
305
|
+
return stdout.trim() === String(espera.value).trim();
|
|
306
|
+
default:
|
|
307
|
+
return false; // desconhecida — nunca chamado por classifyAc/validateStory
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
function parseEspera(rawText) {
|
|
312
|
+
const t = String(rawText || '').trim();
|
|
313
|
+
if (/^exit0$/i.test(t)) return { type: 'exit0' };
|
|
314
|
+
if (/^exit\s*!=\s*0$/i.test(t)) return { type: 'exit!=0' };
|
|
315
|
+
if (/^stdout-vazio$/i.test(t)) return { type: 'stdout-vazio' };
|
|
316
|
+
if (/^stdout-n(a|ã)o-vazio$/i.test(t)) return { type: 'stdout-nao-vazio' };
|
|
317
|
+
const eqMatch = /^stdout\s*==\s*(.+)$/.exec(t);
|
|
318
|
+
if (eqMatch) return { type: 'stdout==', value: eqMatch[1].trim() };
|
|
319
|
+
return { type: 'desconhecida', raw: t };
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// ---------------------------------------------------------------------------
|
|
323
|
+
// `isCrash` — precedência normativa (`ADR-031` D3).
|
|
324
|
+
// ---------------------------------------------------------------------------
|
|
325
|
+
|
|
326
|
+
function extractCrashSignature(stderr) {
|
|
327
|
+
const lines = String(stderr || '').split('\n');
|
|
328
|
+
for (const line of lines) {
|
|
329
|
+
if (/\b[A-Za-z]+Error:\s/.test(line) || /is not a function/.test(line)) {
|
|
330
|
+
return line.trim();
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
const first = String(stderr || '').trim().split('\n')[0];
|
|
334
|
+
return first || 'comando quebrado (crash)';
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
function isCrash(result) {
|
|
338
|
+
const stderr = typeof result.stderr === 'string' ? result.stderr : '';
|
|
339
|
+
const exitCode = result.exitCode;
|
|
340
|
+
|
|
341
|
+
// 1. ENOENT no spawn, exit 127/126 ⇒ estrutural.
|
|
342
|
+
if (result.spawnError && result.spawnError.code === 'ENOENT') {
|
|
343
|
+
return { crash: true, reason: 'comando não executa (ENOENT)' };
|
|
344
|
+
}
|
|
345
|
+
if (exitCode === 127 || exitCode === 126) {
|
|
346
|
+
return { crash: true, reason: 'comando não executa (exit ' + exitCode + ')' };
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
// 2. MODULE_NOT_FOUND / Cannot find module ⇒ falha pré-trabalho, NUNCA crash.
|
|
350
|
+
if (/MODULE_NOT_FOUND|Cannot find module/.test(stderr)) {
|
|
351
|
+
return { crash: false };
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
// 3. grep exit 2 + "No such file or directory" ⇒ alvo ausente, NUNCA crash.
|
|
355
|
+
if (exitCode === 2 && /No such file or directory/i.test(stderr)) {
|
|
356
|
+
return { crash: false };
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
// 4. Linha `<Nome>Error: ` / "is not a function" ⇒ crash.
|
|
360
|
+
if (/\b[A-Za-z]+Error:\s/.test(stderr) || /is not a function/.test(stderr)) {
|
|
361
|
+
return { crash: true, reason: extractCrashSignature(stderr) };
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// 5. Linha de stack isolada NÃO é assinatura (removida — falso-positivo medido).
|
|
365
|
+
return { crash: false };
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
// ---------------------------------------------------------------------------
|
|
369
|
+
// `looksLikeNaiveCounting` — padrão de contagem ingênua (`grep -c` sem
|
|
370
|
+
// `-w`/`-x`/âncora) sob `# espera: exit0` (default ou explícito).
|
|
371
|
+
// ---------------------------------------------------------------------------
|
|
372
|
+
|
|
373
|
+
function looksLikeNaiveCounting(oracleCmd) {
|
|
374
|
+
const tokens = tokenizeCommand(oracleCmd);
|
|
375
|
+
if (tokens[0] !== 'grep') return false;
|
|
376
|
+
const flagTokens = tokens.slice(1).filter((t) => t.startsWith('-') && !t.startsWith('--'));
|
|
377
|
+
const hasCountFlag = flagTokens.some((t) => t.includes('c'));
|
|
378
|
+
if (!hasCountFlag) return false;
|
|
379
|
+
const hasDiscriminating = flagTokens.some((t) => t.includes('w') || t.includes('x'));
|
|
380
|
+
if (hasDiscriminating) return false;
|
|
381
|
+
const nonFlag = tokens.slice(1).filter((t) => !t.startsWith('-'));
|
|
382
|
+
const pattern = nonFlag[0] || '';
|
|
383
|
+
if (/[\^$]/.test(pattern)) return false;
|
|
384
|
+
return true;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
// ---------------------------------------------------------------------------
|
|
388
|
+
// `parseAcs` — escopo restrito a `## Acceptance Criteria`, sem descer em
|
|
389
|
+
// fence aninhado (`ADR-031` D2 item 6, B5).
|
|
390
|
+
// ---------------------------------------------------------------------------
|
|
391
|
+
|
|
392
|
+
function extractAcSection(content) {
|
|
393
|
+
const text = String(content || '');
|
|
394
|
+
const startMatch = /^##[ \t]+Acceptance Criteria[ \t]*$/m.exec(text);
|
|
395
|
+
if (!startMatch) return '';
|
|
396
|
+
const startIdx = startMatch.index + startMatch[0].length;
|
|
397
|
+
const rest = text.slice(startIdx);
|
|
398
|
+
const endMatch = /^##[ \t]+\S/m.exec(rest);
|
|
399
|
+
const endIdx = endMatch ? endMatch.index : rest.length;
|
|
400
|
+
return rest.slice(0, endIdx);
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
// Ranges [start, end) de todo bloco fenced (``` ... ```) dentro de `text` —
|
|
404
|
+
// usado para que `parseAcs` NUNCA trate um marcador de AC que apareça DENTRO
|
|
405
|
+
// de um oráculo (ex.: string literal de fixture embutida em `node -e '...'`)
|
|
406
|
+
// como um cabeçalho real (achado do QA, gate `sf13.1-validador-de-acs.yaml`,
|
|
407
|
+
// finding 1b — "parseAcs não é fence-aware").
|
|
408
|
+
function computeFencedRanges(text) {
|
|
409
|
+
const ranges = [];
|
|
410
|
+
// A fence real (CommonMark) abre e fecha em INÍCIO DE LINHA (`^`) — isso
|
|
411
|
+
// distingue de trecho inline como "` ```oraculo `" (usado na prosa desta
|
|
412
|
+
// própria story para DESCREVER a sintaxe da convenção), que NÃO abre um
|
|
413
|
+
// fence real e não pode ser tratado como se abrisse.
|
|
414
|
+
const re = /^```[^\n]*\n[\s\S]*?^```[ \t]*$/gm;
|
|
415
|
+
let m;
|
|
416
|
+
while ((m = re.exec(text))) {
|
|
417
|
+
ranges.push([m.index, m.index + m[0].length]);
|
|
418
|
+
}
|
|
419
|
+
return ranges;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
function isWithinRanges(idx, ranges) {
|
|
423
|
+
return ranges.some(([start, end]) => idx >= start && idx < end);
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
// Ranges [start, end) de todo *code span* INLINE (crases) que NÃO esteja
|
|
427
|
+
// dentro de um bloco fenced — usado por `parseGuarda` para distinguir a
|
|
428
|
+
// etiqueta `[Guarda] (...)` CITADA como exemplo na prosa (entre crases,
|
|
429
|
+
// descrevendo o que o AC testa) do uso REAL (etiqueta aplicada ao próprio AC).
|
|
430
|
+
//
|
|
431
|
+
// Regra CommonMark aplicada: uma corrida de N crases abre um span que fecha na
|
|
432
|
+
// PRÓXIMA corrida de exatamente N crases — é isso que faz "` ```oraculo `"
|
|
433
|
+
// (crase simples envolvendo uma tripla) ser UM span, não três.
|
|
434
|
+
//
|
|
435
|
+
// Restrição deliberada: o par precisa ficar na MESMA linha — uma crase ÓRFÃ não
|
|
436
|
+
// pode "engolir" as linhas seguintes, onde o uso REAL costuma estar.
|
|
437
|
+
//
|
|
438
|
+
// LEITURA SUPERADA, NÃO REPETIR: `AC-29` de `sf13.1.story.md:425-426`
|
|
439
|
+
// NÃO exercita esta restrição — medido no gate `sf13.7-conserto-do-instrumento.yaml`
|
|
440
|
+
// (mutante M5: sem a restrição de mesma linha, `AC-29` continua `sem-irmao` e o
|
|
441
|
+
// AC-3 daquela story continua VERDE, ou seja, o mutante sobrevive). A
|
|
442
|
+
// justificativa anterior deste comentário, que citava `AC-29` como o caso
|
|
443
|
+
// protegido, era FALSA.
|
|
444
|
+
//
|
|
445
|
+
// O caso que a restrição de fato protege é outro, e está no teste que a
|
|
446
|
+
// acompanha (`scripts/__tests__/validate-story-acs.test.cjs`, "SF13.9/M5"):
|
|
447
|
+
// crase órfã numa linha + `[Guarda]` real na linha seguinte + outra crase
|
|
448
|
+
// depois ⇒ sem a restrição, as duas crases órfãs pareiam ATRAVÉS das linhas, o
|
|
449
|
+
// guarda REAL cai dentro do span e `parseGuarda` devolve `null`.
|
|
450
|
+
function computeInlineCodeRanges(text, fencedRanges) {
|
|
451
|
+
const runs = [];
|
|
452
|
+
const re = /`+/g;
|
|
453
|
+
let m;
|
|
454
|
+
while ((m = re.exec(text))) {
|
|
455
|
+
if (isWithinRanges(m.index, fencedRanges)) continue;
|
|
456
|
+
runs.push({ start: m.index, len: m[0].length });
|
|
457
|
+
}
|
|
458
|
+
const ranges = [];
|
|
459
|
+
let i = 0;
|
|
460
|
+
while (i < runs.length) {
|
|
461
|
+
let j = i + 1;
|
|
462
|
+
while (j < runs.length && runs[j].len !== runs[i].len) j++;
|
|
463
|
+
const sameLine = j < runs.length && !text.slice(runs[i].start, runs[j].start).includes('\n');
|
|
464
|
+
if (sameLine) {
|
|
465
|
+
ranges.push([runs[i].start, runs[j].start + runs[j].len]);
|
|
466
|
+
i = j + 1;
|
|
467
|
+
} else {
|
|
468
|
+
i += 1; // corrida sem fechamento na mesma linha ⇒ não abre span
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
return ranges;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
function parseGuarda(body) {
|
|
475
|
+
// Exclusão ESTRUTURAL (posição do match relativa a fence/code span), NUNCA
|
|
476
|
+
// heurística de prosa — `ADR-031` Emenda 12: regex sobre prosa para asserir
|
|
477
|
+
// ausência é proibida, porque uma reescrita de frase a esvaziaria em
|
|
478
|
+
// silêncio. Um `[Guarda] (...)` que caia (a) dentro de um bloco fenced
|
|
479
|
+
// (dado literal de fixture dentro de ```oraculo) ou (b) dentro de um code
|
|
480
|
+
// span inline (citação da convenção) NÃO é etiqueta aplicada.
|
|
481
|
+
//
|
|
482
|
+
// Vale o PRIMEIRO match que SOBRA, não o primeiro match textual — a
|
|
483
|
+
// diferença importa quando citação e uso coexistem com texto idêntico
|
|
484
|
+
// (`AC-29` de `sf13.1.story.md`), caso em que a regex ingênua acertava por
|
|
485
|
+
// acidente. (Story SF13.7, 7 falsos positivos em `sf13.1.story.md`.)
|
|
486
|
+
const src = String(body || '');
|
|
487
|
+
const fencedRanges = computeFencedRanges(src);
|
|
488
|
+
const excluded = fencedRanges.concat(computeInlineCodeRanges(src, fencedRanges));
|
|
489
|
+
const re = /\[Guarda\]\s*\(([^)]*)\)/g;
|
|
490
|
+
let m = null;
|
|
491
|
+
let candidate;
|
|
492
|
+
while ((candidate = re.exec(src))) {
|
|
493
|
+
if (isWithinRanges(candidate.index, excluded)) continue;
|
|
494
|
+
m = candidate;
|
|
495
|
+
break;
|
|
496
|
+
}
|
|
497
|
+
if (!m) return null;
|
|
498
|
+
const inner = m[1].trim();
|
|
499
|
+
const normalized = stripAccents(inner.toLowerCase());
|
|
500
|
+
const provaMatch = /prova:\s*(AC-[A-Za-z0-9]+)/.exec(inner);
|
|
501
|
+
if (provaMatch) {
|
|
502
|
+
return { forma: 'prova', provaId: provaMatch[1], raw: inner };
|
|
503
|
+
}
|
|
504
|
+
if (normalized.includes('sem irmao')) {
|
|
505
|
+
return { forma: 'sem-irmao', raw: inner };
|
|
506
|
+
}
|
|
507
|
+
if (normalized.includes('citacao de fato') || normalized.includes('citacao')) {
|
|
508
|
+
return { forma: 'citacao', raw: inner };
|
|
509
|
+
}
|
|
510
|
+
return { forma: 'desconhecida', raw: inner };
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
function parseAcBody(id, body) {
|
|
514
|
+
const guarda = parseGuarda(body);
|
|
515
|
+
// Fence-aware (mesmo achado do QA aplicado aqui): abertura E fechamento
|
|
516
|
+
// precisam estar em INÍCIO DE LINHA. Sem `^`/`m`, um AC cujo oráculo
|
|
517
|
+
// contém, como dado literal (ex.: string de fixture embutida em
|
|
518
|
+
// `node -e '...'`), as substrings "```oraculo"/"```" no MEIO da linha
|
|
519
|
+
// (não delimitando um fence real) fazia o match lazy fechar cedo demais,
|
|
520
|
+
// truncando o comando real (AC-3 e outros — gate `sf13.1-validador-de-acs`,
|
|
521
|
+
// finding 1b).
|
|
522
|
+
const fenceMatch = /^```oraculo[ \t]*\r?\n([\s\S]*?)^```[ \t]*$/m.exec(body);
|
|
523
|
+
if (!fenceMatch) {
|
|
524
|
+
return { id, guarda, hasOraculo: false, oracleCmd: null, oracleEmpty: false, espera: null };
|
|
525
|
+
}
|
|
526
|
+
const lines = fenceMatch[1].split('\n').map((l) => l.replace(/\r$/, ''));
|
|
527
|
+
let espera = { type: 'exit0' }; // default (`ADR-031` D1, retrocompatível)
|
|
528
|
+
let cmdLine = null;
|
|
529
|
+
for (const line of lines) {
|
|
530
|
+
const trimmed = line.trim();
|
|
531
|
+
if (trimmed === '') continue;
|
|
532
|
+
if (cmdLine === null) {
|
|
533
|
+
const esperaMatch = /^#\s*espera:\s*(.+)$/i.exec(trimmed);
|
|
534
|
+
if (esperaMatch) {
|
|
535
|
+
espera = parseEspera(esperaMatch[1]);
|
|
536
|
+
continue;
|
|
537
|
+
}
|
|
538
|
+
cmdLine = trimmed;
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
const oracleEmpty = cmdLine === null;
|
|
542
|
+
return {
|
|
543
|
+
id,
|
|
544
|
+
guarda,
|
|
545
|
+
hasOraculo: true,
|
|
546
|
+
oracleCmd: oracleEmpty ? null : cmdLine,
|
|
547
|
+
oracleEmpty,
|
|
548
|
+
espera: oracleEmpty ? null : espera,
|
|
549
|
+
};
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
// Marcador de AC real, `ADR-031` §(b) e convenção já em uso no projeto: SEMPRE
|
|
553
|
+
// no início de linha (`^`), `**AC-<id>` seguido de QUALQUER conteúdo em
|
|
554
|
+
// negrito na mesma linha até o `**` de fechamento — cobre `**AC-1.**` (forma
|
|
555
|
+
// simples), `**AC-2 [novo, ...].**` (anotação entre colchetes) e `**AC-17.
|
|
556
|
+
// Corpus 1 — ....**` (prosa adicional dentro do próprio negrito). O achado do
|
|
557
|
+
// QA (finding 1a): a forma anterior (`\*\*(AC-...)\.\*\*`) exigia `.**`
|
|
558
|
+
// IMEDIATAMENTE após o id e não casava nenhuma das duas últimas formas —
|
|
559
|
+
// 32 dos 43 cabeçalhos reais desta própria story usam uma delas.
|
|
560
|
+
const AC_HEADER_RE = /^\*\*(AC-[A-Za-z0-9]+)\b[^\n]*?\*\*/gm;
|
|
561
|
+
|
|
562
|
+
function parseAcs(content) {
|
|
563
|
+
const section = extractAcSection(content);
|
|
564
|
+
if (!section) return [];
|
|
565
|
+
const fencedRanges = computeFencedRanges(section);
|
|
566
|
+
const idRegex = new RegExp(AC_HEADER_RE.source, AC_HEADER_RE.flags);
|
|
567
|
+
const markers = [];
|
|
568
|
+
let m;
|
|
569
|
+
while ((m = idRegex.exec(section))) {
|
|
570
|
+
if (isWithinRanges(m.index, fencedRanges)) continue; // fence-aware (finding 1b)
|
|
571
|
+
markers.push({ id: m[1], headerEnd: m.index + m[0].length, headerStart: m.index });
|
|
572
|
+
}
|
|
573
|
+
const acs = [];
|
|
574
|
+
for (let i = 0; i < markers.length; i++) {
|
|
575
|
+
const start = markers[i].headerEnd;
|
|
576
|
+
const end = i + 1 < markers.length ? markers[i + 1].headerStart : section.length;
|
|
577
|
+
acs.push(parseAcBody(markers[i].id, section.slice(start, end)));
|
|
578
|
+
}
|
|
579
|
+
return acs;
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
// ---------------------------------------------------------------------------
|
|
583
|
+
// `classifyAc` / `validateStory` — orquestração completa.
|
|
584
|
+
// ---------------------------------------------------------------------------
|
|
585
|
+
|
|
586
|
+
// Núcleo de avaliação de UM AC, sem conhecimento de outros ACs (usado tanto
|
|
587
|
+
// para a classificação final quanto para o "veredito bruto" consultado pela
|
|
588
|
+
// checagem de cadeia guarda→guarda).
|
|
589
|
+
function computeCore(ac, ctx) {
|
|
590
|
+
if (!ac.hasOraculo) return { kind: 'semOraculo' };
|
|
591
|
+
if (ac.oracleEmpty) return { kind: 'oraculoVazio' };
|
|
592
|
+
if (ac.guarda && ac.guarda.forma === 'desconhecida') {
|
|
593
|
+
return { kind: 'guardaDesconhecida', detail: ac.guarda.raw };
|
|
594
|
+
}
|
|
595
|
+
if (ac.espera && ac.espera.type === 'desconhecida') {
|
|
596
|
+
return { kind: 'esperaDesconhecida', detail: ac.espera.raw };
|
|
597
|
+
}
|
|
598
|
+
if (ac.guarda && ac.guarda.forma === 'prova' && !ctx.acIds.has(ac.guarda.provaId)) {
|
|
599
|
+
return { kind: 'provaInexistente', detail: ac.guarda.provaId };
|
|
600
|
+
}
|
|
601
|
+
if (ac.guarda && ac.guarda.forma === 'sem-irmao') {
|
|
602
|
+
const t = ac.espera && ac.espera.type;
|
|
603
|
+
const isAusencia = t === 'stdout-vazio' || (t === 'stdout==' && String(ac.espera.value).trim() === '0');
|
|
604
|
+
if (!isAusencia) return { kind: 'formaAusenciaInvalida' };
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
if (ctx.reentrant) return { kind: 'reentrancia' };
|
|
608
|
+
|
|
609
|
+
const wb = wouldBeRefused(ac.oracleCmd);
|
|
610
|
+
if (wb.refused) return { kind: 'recusa', detail: wb.reason };
|
|
611
|
+
|
|
612
|
+
const result = runOracle(ac.oracleCmd, { cwd: ctx.cwd, timeoutMs: ctx.timeoutMs });
|
|
613
|
+
if (result.timedOut) return { kind: 'timeout' };
|
|
614
|
+
|
|
615
|
+
const crash = isCrash(result);
|
|
616
|
+
if (crash.crash) return { kind: 'crash', detail: crash.reason };
|
|
617
|
+
|
|
618
|
+
if (ac.espera.type === 'exit0' && looksLikeNaiveCounting(ac.oracleCmd)) {
|
|
619
|
+
return { kind: 'noop', detail: ac.oracleCmd };
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
const assertionTrue = evaluateAssertion(result, ac.espera);
|
|
623
|
+
return { kind: 'assertion', assertionTrue };
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
function finalizeAc(ac, core, ctx) {
|
|
627
|
+
switch (core.kind) {
|
|
628
|
+
case 'semOraculo':
|
|
629
|
+
return { bucket: 'semOraculo' };
|
|
630
|
+
case 'oraculoVazio':
|
|
631
|
+
return { bucket: 'naoDeterminados', causa: 'oraculo-vazio', motivo: 'declarou ```oraculo mas a fence está vazia — não pude medir' };
|
|
632
|
+
case 'guardaDesconhecida':
|
|
633
|
+
return { bucket: 'reprovados', motivo: 'forma de guarda não reconhecida: (' + core.detail + ')' };
|
|
634
|
+
case 'esperaDesconhecida':
|
|
635
|
+
return { bucket: 'naoDeterminados', causa: 'assercao-desconhecida', motivo: '`# espera:` presente e não reconhecido: "' + core.detail + '" — não decido por exit0 por omissão' };
|
|
636
|
+
case 'provaInexistente':
|
|
637
|
+
return { bucket: 'reprovados', motivo: 'prova nomeada não existe: ' + core.detail };
|
|
638
|
+
case 'formaAusenciaInvalida':
|
|
639
|
+
return { bucket: 'reprovados', motivo: 'forma 2 (sem irmão) exige asserção de ausência (stdout-vazio ou stdout == 0)' };
|
|
640
|
+
case 'reentrancia':
|
|
641
|
+
return { bucket: 'naoDeterminados', causa: 'reentrancia', motivo: 'execução aninhada recusada (VSA_DEPTH >= 1)' };
|
|
642
|
+
case 'recusa':
|
|
643
|
+
return { bucket: 'naoDeterminados', causa: 'recusa', motivo: core.detail };
|
|
644
|
+
case 'timeout':
|
|
645
|
+
return { bucket: 'naoDeterminados', causa: 'timeout', motivo: 'tempo limite excedido' };
|
|
646
|
+
case 'crash':
|
|
647
|
+
return { bucket: 'reprovados', motivo: 'comando quebrado (crash): ' + core.detail };
|
|
648
|
+
case 'noop':
|
|
649
|
+
return { bucket: 'alertas', motivo: 'oráculo pode ser no-op (contagem ingênua): ' + core.detail };
|
|
650
|
+
default:
|
|
651
|
+
break;
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
// core.kind === 'assertion'
|
|
655
|
+
if (ac.guarda && ac.guarda.forma === 'citacao') {
|
|
656
|
+
return { bucket: 'citacoes', motivo: 'citação de fato pré-existente, não prova de trabalho' };
|
|
657
|
+
}
|
|
658
|
+
if (ctx.statusSuspends && core.assertionTrue === true) {
|
|
659
|
+
return { bucket: 'naoDeterminados', causa: 'nasce-verde-pos-trabalho', motivo: 'não decido nasce-verde depois de o trabalho existir' };
|
|
660
|
+
}
|
|
661
|
+
if (core.assertionTrue === false) {
|
|
662
|
+
return { bucket: 'ok' };
|
|
663
|
+
}
|
|
664
|
+
// assertionTrue === true, status decidável
|
|
665
|
+
if (!ac.guarda) {
|
|
666
|
+
return { bucket: 'reprovados', motivo: 'nasce verde: a asserção já é verdadeira hoje, sem pareamento (prova de trabalho)' };
|
|
667
|
+
}
|
|
668
|
+
if (ac.guarda.forma === 'prova') {
|
|
669
|
+
const target = ctx.coreById[ac.guarda.provaId];
|
|
670
|
+
const targetRaw = target && target.kind === 'assertion' && target.assertionTrue === true;
|
|
671
|
+
if (targetRaw) {
|
|
672
|
+
return { bucket: 'reprovados', motivo: 'cadeia guarda→guarda proibida: prova (' + ac.guarda.provaId + ') também é nasce-verde' };
|
|
673
|
+
}
|
|
674
|
+
return { bucket: 'ok' };
|
|
675
|
+
}
|
|
676
|
+
// forma 'sem-irmao' (já validada estruturalmente)
|
|
677
|
+
return { bucket: 'ok' };
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
function classifyAc(ac, options) {
|
|
681
|
+
const opts = options || {};
|
|
682
|
+
const ctx = {
|
|
683
|
+
cwd: opts.cwd || REPO_ROOT,
|
|
684
|
+
timeoutMs: opts.timeoutMs,
|
|
685
|
+
reentrant: !!opts.reentrant,
|
|
686
|
+
statusSuspends: !!opts.statusSuspends,
|
|
687
|
+
acIds: opts.acIds || new Set(ac.guarda && ac.guarda.provaId ? [ac.guarda.provaId] : []),
|
|
688
|
+
coreById: opts.coreById || {},
|
|
689
|
+
};
|
|
690
|
+
const core = computeCore(ac, ctx);
|
|
691
|
+
return finalizeAc(ac, core, ctx);
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
function currentDepth() {
|
|
695
|
+
const n = parseInt(process.env.VSA_DEPTH, 10);
|
|
696
|
+
return Number.isFinite(n) ? n : 0;
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
function validateStory(content, options) {
|
|
700
|
+
const opts = options || {};
|
|
701
|
+
const cwd = opts.cwd || REPO_ROOT;
|
|
702
|
+
const timeoutMs = opts.timeoutMs;
|
|
703
|
+
const reentrant = currentDepth() >= 1;
|
|
704
|
+
const statusSuspends = statusSuspendsNasceVerde(content);
|
|
705
|
+
|
|
706
|
+
const acs = parseAcs(content);
|
|
707
|
+
const acIds = new Set(acs.map((a) => a.id));
|
|
708
|
+
|
|
709
|
+
const report = {
|
|
710
|
+
total: 0,
|
|
711
|
+
semOraculo: 0,
|
|
712
|
+
ok: [],
|
|
713
|
+
reprovados: [],
|
|
714
|
+
alertas: [],
|
|
715
|
+
citacoes: [],
|
|
716
|
+
naoDeterminados: [],
|
|
717
|
+
};
|
|
718
|
+
|
|
719
|
+
const ctxBase = { cwd, timeoutMs, reentrant, statusSuspends, acIds };
|
|
720
|
+
|
|
721
|
+
// Passo 1 — núcleo de cada AC (sem checagem de cadeia guarda→guarda ainda).
|
|
722
|
+
const coreById = {};
|
|
723
|
+
for (const ac of acs) {
|
|
724
|
+
coreById[ac.id] = computeCore(ac, ctxBase);
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
// Passo 2 — finalização (usa `coreById` para resolver `[Guarda] (prova: AC-M)`).
|
|
728
|
+
const ctx = Object.assign({}, ctxBase, { coreById });
|
|
729
|
+
let sawAnyOraculo = false;
|
|
730
|
+
for (const ac of acs) {
|
|
731
|
+
const core = coreById[ac.id];
|
|
732
|
+
const final = finalizeAc(ac, core, ctx);
|
|
733
|
+
if (final.bucket === 'semOraculo') {
|
|
734
|
+
report.semOraculo += 1;
|
|
735
|
+
continue;
|
|
736
|
+
}
|
|
737
|
+
sawAnyOraculo = true;
|
|
738
|
+
report.total += 1;
|
|
739
|
+
const entry = { id: ac.id, motivo: final.motivo };
|
|
740
|
+
if (final.bucket === 'naoDeterminados') entry.causa = final.causa;
|
|
741
|
+
report[final.bucket].push(entry);
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
// Arquivo com seção mas NENHUM AC declarou oráculo ⇒ o relatório FALA
|
|
745
|
+
// (B4/`ADR-031` Emenda 8 aplicada ao nível do arquivo) — "não consegui
|
|
746
|
+
// determinar", nunca silêncio de "tudo OK".
|
|
747
|
+
const hasSection = /^##[ \t]+Acceptance Criteria[ \t]*$/m.test(String(content || ''));
|
|
748
|
+
if (hasSection && !sawAnyOraculo) {
|
|
749
|
+
report.naoDeterminados.push({
|
|
750
|
+
id: null,
|
|
751
|
+
motivo: 'nenhum AC declarou oráculo — não pude medir',
|
|
752
|
+
causa: 'arquivo-sem-oraculo',
|
|
753
|
+
});
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
return report;
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
function validateStoryBatch(dirPath, options) {
|
|
760
|
+
const opts = options || {};
|
|
761
|
+
const files = fs.readdirSync(dirPath).filter((f) => f.endsWith('.story.md')).sort();
|
|
762
|
+
const aggregate = {
|
|
763
|
+
files: files.length,
|
|
764
|
+
total: 0,
|
|
765
|
+
semOraculo: 0,
|
|
766
|
+
ok: [],
|
|
767
|
+
reprovados: [],
|
|
768
|
+
alertas: [],
|
|
769
|
+
citacoes: [],
|
|
770
|
+
naoDeterminados: [],
|
|
771
|
+
perFile: {},
|
|
772
|
+
};
|
|
773
|
+
for (const f of files) {
|
|
774
|
+
const full = path.join(dirPath, f);
|
|
775
|
+
const content = fs.readFileSync(full, 'utf8');
|
|
776
|
+
const r = validateStory(content, opts);
|
|
777
|
+
aggregate.total += r.total;
|
|
778
|
+
aggregate.semOraculo += r.semOraculo;
|
|
779
|
+
aggregate.ok.push(...r.ok);
|
|
780
|
+
aggregate.reprovados.push(...r.reprovados);
|
|
781
|
+
aggregate.alertas.push(...r.alertas);
|
|
782
|
+
aggregate.citacoes.push(...r.citacoes);
|
|
783
|
+
aggregate.naoDeterminados.push(...r.naoDeterminados);
|
|
784
|
+
aggregate.perFile[f] = r;
|
|
785
|
+
}
|
|
786
|
+
return aggregate;
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
// ---------------------------------------------------------------------------
|
|
790
|
+
// CLI
|
|
791
|
+
// ---------------------------------------------------------------------------
|
|
792
|
+
|
|
793
|
+
function printReport(label, report) {
|
|
794
|
+
process.stdout.write('\n=== ' + label + ' ===\n');
|
|
795
|
+
process.stdout.write('total=' + report.total + ' semOraculo=' + report.semOraculo + '\n');
|
|
796
|
+
process.stdout.write('ok=' + report.ok.length + ' reprovados=' + report.reprovados.length + ' alertas=' + report.alertas.length + ' citacoes=' + report.citacoes.length + ' naoDeterminados=' + report.naoDeterminados.length + '\n');
|
|
797
|
+
for (const r of report.reprovados) process.stdout.write(' REPROVA ' + (r.id || '(arquivo)') + ': ' + r.motivo + '\n');
|
|
798
|
+
for (const a of report.alertas) process.stdout.write(' ALERTA ' + a.id + ': ' + a.motivo + '\n');
|
|
799
|
+
for (const n of report.naoDeterminados) process.stdout.write(' ND ' + (n.id || '(arquivo)') + ' [' + n.causa + ']: ' + n.motivo + '\n');
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
function main(argv) {
|
|
803
|
+
const args = (argv || process.argv).slice(2);
|
|
804
|
+
if (args.length === 0) {
|
|
805
|
+
process.stderr.write('uso: node validate-story-acs.cjs <story.md> | <diretorio de stories>\n');
|
|
806
|
+
process.exitCode = 2;
|
|
807
|
+
return;
|
|
808
|
+
}
|
|
809
|
+
const target = args[0];
|
|
810
|
+
let stat;
|
|
811
|
+
try {
|
|
812
|
+
stat = fs.statSync(target);
|
|
813
|
+
} catch (err) {
|
|
814
|
+
process.stderr.write('Erro: caminho não encontrado: ' + target + '\n');
|
|
815
|
+
process.exitCode = 2;
|
|
816
|
+
return;
|
|
817
|
+
}
|
|
818
|
+
try {
|
|
819
|
+
if (stat.isDirectory()) {
|
|
820
|
+
const report = validateStoryBatch(target, { cwd: REPO_ROOT });
|
|
821
|
+
printReport(target + ' (' + report.files + ' arquivos)', report);
|
|
822
|
+
process.exitCode = report.reprovados.length > 0 ? 1 : 0;
|
|
823
|
+
} else {
|
|
824
|
+
const content = fs.readFileSync(target, 'utf8');
|
|
825
|
+
const report = validateStory(content, { cwd: REPO_ROOT });
|
|
826
|
+
printReport(target, report);
|
|
827
|
+
process.exitCode = report.reprovados.length > 0 ? 1 : 0;
|
|
828
|
+
}
|
|
829
|
+
} catch (err) {
|
|
830
|
+
process.stderr.write('Erro ao validar ' + target + ': ' + (err && err.message ? err.message : String(err)) + '\n');
|
|
831
|
+
process.exitCode = 2;
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
if (require.main === module) {
|
|
836
|
+
main(process.argv);
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
module.exports = {
|
|
840
|
+
HEADER_ALLOWLIST,
|
|
841
|
+
GIT_SUB_ALLOWLIST,
|
|
842
|
+
NAO_DETERMINADOS_CAUSAS,
|
|
843
|
+
tokenizeCommand,
|
|
844
|
+
hasUnquotedMetachar,
|
|
845
|
+
wouldBeRefused,
|
|
846
|
+
runOracle,
|
|
847
|
+
evaluateAssertion,
|
|
848
|
+
parseEspera,
|
|
849
|
+
isCrash,
|
|
850
|
+
looksLikeNaiveCounting,
|
|
851
|
+
extractAcSection,
|
|
852
|
+
parseAcs,
|
|
853
|
+
parseGuarda,
|
|
854
|
+
classifyAc,
|
|
855
|
+
validateStory,
|
|
856
|
+
validateStoryBatch,
|
|
857
|
+
extractStatusSection,
|
|
858
|
+
normalizeStatusText,
|
|
859
|
+
readStoryStatus,
|
|
860
|
+
statusSuspendsNasceVerde,
|
|
861
|
+
main,
|
|
862
|
+
};
|