@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,612 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
/**
|
|
4
|
+
* harden-knowledge.test.cjs — fixture PONTA-A-PONTA do 2º alvo de promoção do
|
|
5
|
+
* `*harden-learning`: KNOWLEDGE CANÔNICO (Story SF10.10, ADR-012 Adendo SF10.10).
|
|
6
|
+
*
|
|
7
|
+
* Pod-fixture: `template/pods/content-carousel` (EXPORT-CAPAZ — tem pins em
|
|
8
|
+
* exports/content-carousel/pins.json), copiado para tmp. Nada do repo é mutado.
|
|
9
|
+
*
|
|
10
|
+
* O que esta suíte prova (e o que ela NÃO prova):
|
|
11
|
+
* PROVA (máquina): aprovação obrigatória; precondição de knowledge DECLARADO no wf;
|
|
12
|
+
* recusa de path traversal e de criação de arquivo novo; CONFINAMENTO de escrita a
|
|
13
|
+
* 3 paths com rollback; scanClone verde sobre o alvo; paridade de `per_file_sha`
|
|
14
|
+
* divergindo SÓ nas 2 chaves esperadas; `export_check:"n/a"` DECLARADO em pod sem
|
|
15
|
+
* pins; recusa+rollback por estouro de TOKEN_BUDGET; marcação append-only + 🔒.
|
|
16
|
+
* NÃO PROVA (e diz isso na cara): que a prosa promovida ficou MELHOR. Não existe
|
|
17
|
+
* oráculo de máquina para isso — o único portão de qualidade é `--approved-by`
|
|
18
|
+
* (DF-SF10.10-2 item 6). A suíte verifica que essa frase aparece no --help e na
|
|
19
|
+
* saída do comando, não que ela seja dispensável.
|
|
20
|
+
*
|
|
21
|
+
* Node >= 18, sem dependências externas. Exit 0/1.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
const fs = require('node:fs');
|
|
25
|
+
const os = require('node:os');
|
|
26
|
+
const path = require('node:path');
|
|
27
|
+
const crypto = require('node:crypto');
|
|
28
|
+
const { spawnSync } = require('node:child_process');
|
|
29
|
+
|
|
30
|
+
const podMemory = require('./pod-memory.cjs');
|
|
31
|
+
const { confinementViolations, perFileShaDiff, snapshotDir, declaredKnowledge, QUALITY_NOTICE } =
|
|
32
|
+
require('./forge-harden-learning.cjs');
|
|
33
|
+
|
|
34
|
+
const REPO = path.resolve(__dirname, '..');
|
|
35
|
+
const HARDEN = path.join(REPO, 'scripts', 'forge-harden-learning.cjs');
|
|
36
|
+
const FIXTURE_POD = path.join(REPO, 'template', 'pods', 'content-carousel');
|
|
37
|
+
const POD = 'content-carousel';
|
|
38
|
+
const KFILE = 'slides-html.md';
|
|
39
|
+
|
|
40
|
+
let passed = 0;
|
|
41
|
+
const failures = [];
|
|
42
|
+
function ok(label, cond, extra) {
|
|
43
|
+
if (cond) { passed++; console.log(`[OK ] ${label}`); }
|
|
44
|
+
else { failures.push(label); console.log(`[FALHA] ${label}${extra ? `\n ${String(extra).slice(0, 500)}` : ''}`); }
|
|
45
|
+
}
|
|
46
|
+
function sh(args) {
|
|
47
|
+
return spawnSync(process.execPath, args, { encoding: 'utf8', cwd: REPO });
|
|
48
|
+
}
|
|
49
|
+
function sha256(buf) {
|
|
50
|
+
return crypto.createHash('sha256').update(buf).digest('hex');
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const TRIGGER = 'slides internos sem riqueza grafica';
|
|
54
|
+
const RECOMMENDATION = 'usar blocos visuais com hierarquia tipografica em vez de texto corrido';
|
|
55
|
+
|
|
56
|
+
/** Pod temporário + memória com a heurística consolidada. Devolve o id da entrada. */
|
|
57
|
+
function makePod(label, { seed = true } = {}) {
|
|
58
|
+
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), `sf10.10-${label}-`));
|
|
59
|
+
const podDir = path.join(tmp, POD);
|
|
60
|
+
fs.cpSync(FIXTURE_POD, podDir, { recursive: true });
|
|
61
|
+
fs.rmSync(path.join(podDir, 'memory'), { recursive: true, force: true });
|
|
62
|
+
fs.mkdirSync(path.join(podDir, 'memory'), { recursive: true });
|
|
63
|
+
let learningId = null;
|
|
64
|
+
if (seed) {
|
|
65
|
+
const e = podMemory.append(POD, {
|
|
66
|
+
id: 'SRC-1', ts: 1_700_000_000_000, trigger: TRIGGER, recommendation: RECOMMENDATION,
|
|
67
|
+
evidence: { count: 2 }, tier: 'normal',
|
|
68
|
+
}, { podDir });
|
|
69
|
+
podMemory.consolidate(POD, { podDir });
|
|
70
|
+
learningId = e.entry.id;
|
|
71
|
+
}
|
|
72
|
+
const newContent = path.join(tmp, 'novo-knowledge.md');
|
|
73
|
+
fs.writeFileSync(newContent,
|
|
74
|
+
'# Slides HTML (promovido)\n\n## Hierarquia\n\nBlocos visuais com hierarquia tipografica, ' +
|
|
75
|
+
'numero-heroi por slide e respiro entre secoes.\n');
|
|
76
|
+
return { tmp, podDir, learningId, newContent };
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const promoteArgs = (podDir, learningId, contentPath, extra = []) => [
|
|
80
|
+
HARDEN, podDir, '--target-type', 'knowledge',
|
|
81
|
+
'--learning', learningId, '--file', KFILE, '--content', contentPath,
|
|
82
|
+
...extra,
|
|
83
|
+
];
|
|
84
|
+
|
|
85
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
86
|
+
// 1. Invariantes PUROS (testáveis sem tocar disco de pod)
|
|
87
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
88
|
+
console.log('\n--- 1. Invariantes puros (confinamento, paridade de compilação, wf) ---');
|
|
89
|
+
{
|
|
90
|
+
const before = new Map([
|
|
91
|
+
['knowledge/slides-html.md', Buffer.from('a')],
|
|
92
|
+
['workflows/wf-x.yaml', Buffer.from('wf')],
|
|
93
|
+
['memory/learnings.jsonl', Buffer.from('{}')],
|
|
94
|
+
['memory/learnings.consolidated.md', Buffer.from('# md')],
|
|
95
|
+
]);
|
|
96
|
+
const ALLOWED = ['knowledge/slides-html.md', 'memory/learnings.jsonl', 'memory/learnings.consolidated.md'];
|
|
97
|
+
|
|
98
|
+
const okCase = new Map(before);
|
|
99
|
+
okCase.set('knowledge/slides-html.md', Buffer.from('b'));
|
|
100
|
+
okCase.set('memory/learnings.jsonl', Buffer.from('{}{}'));
|
|
101
|
+
okCase.set('memory/learnings.consolidated.md', Buffer.from('# md2'));
|
|
102
|
+
ok('confinamento: mudança nos 3 paths permitidos ⇒ ZERO violação',
|
|
103
|
+
confinementViolations(before, okCase, ALLOWED).length === 0);
|
|
104
|
+
|
|
105
|
+
const badEdit = new Map(okCase);
|
|
106
|
+
badEdit.set('workflows/wf-x.yaml', Buffer.from('wf-alterado'));
|
|
107
|
+
ok('confinamento: escrita no wf (fora do trio) ⇒ violação nomeada',
|
|
108
|
+
confinementViolations(before, badEdit, ALLOWED).join() === '~ workflows/wf-x.yaml');
|
|
109
|
+
|
|
110
|
+
const badNew = new Map(okCase);
|
|
111
|
+
badNew.set('agents/novo.md', Buffer.from('x'));
|
|
112
|
+
ok('confinamento: arquivo CRIADO fora do trio ⇒ violação',
|
|
113
|
+
confinementViolations(before, badNew, ALLOWED).join() === '+ agents/novo.md');
|
|
114
|
+
|
|
115
|
+
const badDel = new Map(okCase);
|
|
116
|
+
badDel.delete('workflows/wf-x.yaml');
|
|
117
|
+
ok('confinamento: arquivo REMOVIDO fora do trio ⇒ violação',
|
|
118
|
+
confinementViolations(before, badDel, ALLOWED).join() === '- workflows/wf-x.yaml');
|
|
119
|
+
|
|
120
|
+
ok('paridade: per_file_sha idêntico ⇒ nenhuma chave divergente',
|
|
121
|
+
perFileShaDiff({ per_file_sha: { a: '1' } }, { per_file_sha: { a: '1' } }).length === 0);
|
|
122
|
+
ok('paridade: chave alterada + chave nova ⇒ ambas listadas, ordenadas',
|
|
123
|
+
perFileShaDiff({ per_file_sha: { a: '1', b: '2' } }, { per_file_sha: { a: '9', b: '2', c: '3' } }).join() === 'a,c');
|
|
124
|
+
|
|
125
|
+
const declared = declaredKnowledge(path.join(FIXTURE_POD, 'workflows', `wf-${POD}.yaml`));
|
|
126
|
+
ok('wf: declaredKnowledge lê os knowledge declarados nas etapas do workflow real',
|
|
127
|
+
declared.has(KFILE) && declared.has('image-generation.md') && !declared.has('inexistente.md'),
|
|
128
|
+
[...declared].join(','));
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
132
|
+
// 2. Recusas (nada aplicado, exit 2) — cada uma com o pod BYTE-IDÊNTICO depois
|
|
133
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
134
|
+
console.log('\n--- 2. Recusas: aprovação, declaração no wf, traversal, criação, no-op ---');
|
|
135
|
+
{
|
|
136
|
+
const { tmp, podDir, learningId, newContent } = makePod('recusas');
|
|
137
|
+
const snapBefore = snapshotDir(podDir);
|
|
138
|
+
const intact = (label) => {
|
|
139
|
+
const now = snapshotDir(podDir);
|
|
140
|
+
ok(`${label}: pod BYTE-IDÊNTICO (nada aplicado)`,
|
|
141
|
+
confinementViolations(snapBefore, now, []).length === 0,
|
|
142
|
+
confinementViolations(snapBefore, now, []).join(', '));
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
const noApproval = sh(promoteArgs(podDir, learningId, newContent));
|
|
146
|
+
ok('sem --approved-by: recusa exit 2', noApproval.status === 2, noApproval.stderr);
|
|
147
|
+
ok('sem --approved-by: a recusa REPETE que o único portão de qualidade é a revisão humana',
|
|
148
|
+
noApproval.stderr.includes('--approved-by') && noApproval.stderr.includes(QUALITY_NOTICE), noApproval.stderr);
|
|
149
|
+
intact('sem --approved-by');
|
|
150
|
+
|
|
151
|
+
const notDeclared = sh([HARDEN, podDir, '--target-type', 'knowledge', '--learning', learningId,
|
|
152
|
+
'--file', 'dono.example.yaml.md', '--content', newContent, '--approved-by', 'daniel']);
|
|
153
|
+
ok('AC9 knowledge NÃO declarado no wf: recusa exit 2 citando os declarados',
|
|
154
|
+
notDeclared.status === 2 && /não está declarado em knowledge:/.test(notDeclared.stderr) &&
|
|
155
|
+
notDeclared.stderr.includes('DEBT-SF10.10-C'), notDeclared.stderr);
|
|
156
|
+
intact('knowledge não declarado');
|
|
157
|
+
|
|
158
|
+
for (const evil of ['../../../etc/passwd.md', 'knowledge/../../fora.md', '/tmp/absoluto.md', 'sub/dir.md']) {
|
|
159
|
+
const trav = sh([HARDEN, podDir, '--target-type', 'knowledge', '--learning', learningId,
|
|
160
|
+
'--file', evil, '--content', newContent, '--approved-by', 'daniel']);
|
|
161
|
+
ok(`path traversal em --file (${evil}): recusa exit 2 antes de qualquer escrita`,
|
|
162
|
+
trav.status === 2 && /nome SIMPLES sob knowledge\/|resolveu para fora/.test(trav.stderr), trav.stderr);
|
|
163
|
+
}
|
|
164
|
+
intact('path traversal');
|
|
165
|
+
|
|
166
|
+
const create = sh([HARDEN, podDir, '--target-type', 'knowledge', '--learning', learningId,
|
|
167
|
+
'--file', 'ainda-nao-existe.md', '--content', newContent, '--approved-by', 'daniel']);
|
|
168
|
+
ok('criar knowledge NOVO (não declarado/inexistente): recusa exit 2 (Escopo OUT, DEBT-SF10.10-C)',
|
|
169
|
+
create.status === 2, create.stderr);
|
|
170
|
+
|
|
171
|
+
const same = path.join(tmp, 'igual.md');
|
|
172
|
+
fs.copyFileSync(path.join(podDir, 'knowledge', KFILE), same);
|
|
173
|
+
const noop = sh(promoteArgs(podDir, learningId, same, ['--approved-by', 'daniel']));
|
|
174
|
+
ok('--content byte-idêntico ao alvo: recusa exit 2 (não há mudança a registrar)',
|
|
175
|
+
noop.status === 2 && /BYTE-IDÊNTICO/.test(noop.stderr), noop.stderr);
|
|
176
|
+
intact('--content idêntico');
|
|
177
|
+
|
|
178
|
+
const badLearning = sh(promoteArgs(podDir, 'nao-existe', newContent, ['--approved-by', 'daniel']));
|
|
179
|
+
ok('--learning inexistente: recusa exit 2', badLearning.status === 2, badLearning.stderr);
|
|
180
|
+
intact('--learning inexistente');
|
|
181
|
+
|
|
182
|
+
const badType = sh([HARDEN, podDir, '--target-type', 'assertion_magica', '--approved-by', 'daniel']);
|
|
183
|
+
ok('--target-type desconhecido: erro de uso exit 1 (nunca silencioso, nunca vira "veto")',
|
|
184
|
+
badType.status === 1 && /desconhecido/.test(badType.stderr), badType.stderr);
|
|
185
|
+
|
|
186
|
+
fs.rmSync(tmp, { recursive: true, force: true });
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
190
|
+
// 3. Promoção aprovada em pod EXPORT-CAPAZ (o caminho completo, AC2/AC3/AC6)
|
|
191
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
192
|
+
console.log('\n--- 3. Promoção aprovada (pod export-capaz): confinamento + scanClone + per_file_sha ---');
|
|
193
|
+
{
|
|
194
|
+
const { tmp, podDir, learningId, newContent } = makePod('promocao');
|
|
195
|
+
const memPaths = podMemory.memoryPaths(POD, { podDir });
|
|
196
|
+
const rawBefore = fs.readFileSync(memPaths.raw, 'utf8').split('\n').filter(Boolean);
|
|
197
|
+
const snapBefore = snapshotDir(podDir);
|
|
198
|
+
const shaBefore = sha256(fs.readFileSync(path.join(podDir, 'knowledge', KFILE)));
|
|
199
|
+
const shaAfter = sha256(fs.readFileSync(newContent));
|
|
200
|
+
|
|
201
|
+
const r = sh(promoteArgs(podDir, learningId, newContent, ['--approved-by', 'daniel']));
|
|
202
|
+
ok(`promoção aprovada: exit 0 (${r.status})`, r.status === 0, `${r.stdout}${r.stderr}`);
|
|
203
|
+
|
|
204
|
+
ok('knowledge-alvo recebeu EXATAMENTE o conteúdo proposto pelo humano',
|
|
205
|
+
fs.readFileSync(path.join(podDir, 'knowledge', KFILE)).equals(fs.readFileSync(newContent)));
|
|
206
|
+
|
|
207
|
+
// SF11.4/DF-SF11.4-1: a trilha auditável (memory/learnings.hardened.md) é escrita por
|
|
208
|
+
// `consolidate` dentro da promoção — entra no confinamento permitido (sem isso a
|
|
209
|
+
// promoção reprovaria contra si mesma).
|
|
210
|
+
const violations = confinementViolations(snapBefore, snapshotDir(podDir),
|
|
211
|
+
[`knowledge/${KFILE}`, 'memory/learnings.jsonl', 'memory/learnings.consolidated.md',
|
|
212
|
+
podMemory.HARDENED_DETAIL_REL]);
|
|
213
|
+
ok('AC3 CONFINAMENTO real: só os 4 paths permitidos mudaram sob o podDir',
|
|
214
|
+
violations.length === 0, violations.join(', '));
|
|
215
|
+
|
|
216
|
+
const raw = fs.readFileSync(memPaths.raw, 'utf8').split('\n').filter(Boolean);
|
|
217
|
+
ok('AC6 append-only: linhas anteriores INTACTAS + 1 registro de hardening',
|
|
218
|
+
raw.length === rawBefore.length + 1 && raw.slice(0, rawBefore.length).join('\n') === rawBefore.join('\n'));
|
|
219
|
+
const rec = JSON.parse(raw[raw.length - 1]);
|
|
220
|
+
ok('AC1 registro = {type:hardening, ref, target:{pod,kind,file,sha_before,sha_after}, approved_by, timestamp}',
|
|
221
|
+
rec.type === 'hardening' && rec.ref === `${podMemory.normalizeText(TRIGGER)}||${podMemory.normalizeText(RECOMMENDATION)}` &&
|
|
222
|
+
rec.target.kind === 'knowledge' && rec.target.file === KFILE &&
|
|
223
|
+
rec.target.sha_before === shaBefore && rec.target.sha_after === shaAfter &&
|
|
224
|
+
rec.approved_by === 'daniel' && typeof rec.timestamp === 'string',
|
|
225
|
+
JSON.stringify(rec));
|
|
226
|
+
// O `ref` é a dedupKey da HEURÍSTICA (contrato de SF7.6) — o que não pode aparecer
|
|
227
|
+
// é o CONTEÚDO do knowledge: registro de tamanho fixo, diff no git (DF-SF10.10-1).
|
|
228
|
+
ok('AC1 registro SEM diff e SEM trecho do knowledge (tamanho fixo: 2 shas + 1 nome)',
|
|
229
|
+
!JSON.stringify(rec).includes('diff') && !JSON.stringify(rec).includes('numero-heroi') &&
|
|
230
|
+
JSON.stringify(rec).length <= rec.ref.length + 350, // overhead FIXO: 2 shas + nome + aprovador + ts
|
|
231
|
+
JSON.stringify(rec));
|
|
232
|
+
|
|
233
|
+
const md = fs.readFileSync(memPaths.consolidated, 'utf8');
|
|
234
|
+
const detail = fs.readFileSync(memPaths.hardenedDetail, 'utf8');
|
|
235
|
+
ok('SF11.4 trilha auditável: 🔒 endurecida → knowledge/<file>, linha fixa e curta (fora do brief)',
|
|
236
|
+
detail.includes(`🔒 **[${TRIGGER}]**`) && detail.includes(`endurecida → knowledge/${KFILE}`) &&
|
|
237
|
+
detail.includes('(por daniel,'), detail);
|
|
238
|
+
ok('SF11.4 consolidado injetado cita só a REFERÊNCIA agregada (alvo + contagem + trilha)',
|
|
239
|
+
md.includes(`knowledge/${KFILE} (1)`) && md.includes(podMemory.HARDENED_DETAIL_REL) &&
|
|
240
|
+
!md.includes(RECOMMENDATION), md);
|
|
241
|
+
ok('consolidado: heurística DEMOVIDA do top-N ativo',
|
|
242
|
+
!md.split('## Endurecidas')[0].includes(`**[${TRIGGER}]**`));
|
|
243
|
+
|
|
244
|
+
ok('AC3(v) paridade de compilação: per_file_sha divergiu SOMENTE nas 2 chaves esperadas',
|
|
245
|
+
r.stdout.includes(`per_file_sha divergiu SOMENTE em knowledge/${KFILE}, memory/learnings.consolidated.md`),
|
|
246
|
+
r.stdout);
|
|
247
|
+
ok('AC3(v) export_check declarado como "ok" no relatório', /export_check: ok/.test(r.stdout), r.stdout);
|
|
248
|
+
ok('AC3(ii) a AUSÊNCIA do smoke "depois" é DECLARADA com o motivo (verde por construção)',
|
|
249
|
+
/sem smoke "depois": omitido POR DECISÃO/.test(r.stdout) && /VERDE POR CONSTRUÇÃO/.test(r.stdout), r.stdout);
|
|
250
|
+
ok('AC3(vi) a saída diz, sem eufemismo, que o único portão de qualidade é --approved-by',
|
|
251
|
+
r.stdout.includes(QUALITY_NOTICE), r.stdout);
|
|
252
|
+
ok('relatório aponta o git para o diff (registro não o embute)',
|
|
253
|
+
/git diff --/.test(r.stdout), r.stdout);
|
|
254
|
+
|
|
255
|
+
// idempotência: repetir recusa (já endurecida)
|
|
256
|
+
const again = sh(promoteArgs(podDir, learningId, newContent, ['--approved-by', 'daniel']));
|
|
257
|
+
ok('re-promover a mesma heurística: recusa exit 2 (já endurecida 🔒)', again.status === 2, again.stderr);
|
|
258
|
+
|
|
259
|
+
fs.rmSync(tmp, { recursive: true, force: true });
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
263
|
+
// 4. scanClone como PÓS-CONDIÇÃO: prosa que encarna um mind ⇒ rollback
|
|
264
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
265
|
+
console.log('\n--- 4. Pós-condição de proveniência (validate-no-clone regras 1-3) ---');
|
|
266
|
+
{
|
|
267
|
+
const { tmp, podDir, learningId } = makePod('clone');
|
|
268
|
+
const snapBefore = snapshotDir(podDir);
|
|
269
|
+
const evil = path.join(tmp, 'clone.md');
|
|
270
|
+
// Regra 2 (persona com token da denylist derivada de validate-branding): assumir a
|
|
271
|
+
// persona de um mind é FAIL mesmo com citação — "citar ≠ encarnar".
|
|
272
|
+
fs.writeFileSync(evil, '# Slides\n\nVocê é o alan nicolas e escreve exatamente como ele.\n');
|
|
273
|
+
const r = sh(promoteArgs(podDir, learningId, evil, ['--approved-by', 'daniel']));
|
|
274
|
+
ok('scanClone vermelho no knowledge promovido ⇒ recusa exit 2 + ROLLBACK',
|
|
275
|
+
r.status === 2 && /validate-no-clone/.test(r.stderr) && /ROLLBACK aplicado/.test(r.stderr), r.stderr);
|
|
276
|
+
const after = snapshotDir(podDir);
|
|
277
|
+
ok('rollback do scanClone: pod BYTE-IDÊNTICO (knowledge, log e consolidado restaurados)',
|
|
278
|
+
confinementViolations(snapBefore, after, []).length === 0,
|
|
279
|
+
confinementViolations(snapBefore, after, []).join(', '));
|
|
280
|
+
ok('rollback do scanClone: NENHUM registro de hardening ficou no log',
|
|
281
|
+
!fs.readFileSync(podMemory.memoryPaths(POD, { podDir }).raw, 'utf8').includes('"type":"hardening"'));
|
|
282
|
+
fs.rmSync(tmp, { recursive: true, force: true });
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
286
|
+
// 5. (SF11.4/DF-SF11.4-4, SUPERSEDE AC10/DF-SF10.10-6) o gate mede EVICTION,
|
|
287
|
+
// não `tokens > TOKEN_BUDGET`. Motivo: `computeConsolidation` normaliza `tokens`
|
|
288
|
+
// ANTES de qualquer chamador — o gate antigo só era alcançável com `ranked` já
|
|
289
|
+
// ZERADO, isto é, depois que 100% da memória ativa tinha sido apagada em silêncio
|
|
290
|
+
// (ADR-022 §C4). O gate novo recusa quando a consolidação DESCARTA heurística.
|
|
291
|
+
// ALCANÇABILIDADE PROVADA AQUI: a fixture monta o estado e o comando recusa.
|
|
292
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
293
|
+
console.log('\n--- 5. DF-SF11.4-4: promoção que descartaria heurística ⇒ recusa + rollback ---');
|
|
294
|
+
{
|
|
295
|
+
const { tmp, podDir, learningId, newContent } = makePod('budget');
|
|
296
|
+
const memPaths = podMemory.memoryPaths(POD, { podDir });
|
|
297
|
+
const baseLog = fs.readFileSync(memPaths.raw, 'utf8');
|
|
298
|
+
const B = podMemory.TOKEN_BUDGET;
|
|
299
|
+
|
|
300
|
+
// Enche o POOL ATIVO (heurísticas NÃO endurecidas, que é o que a poda por orçamento
|
|
301
|
+
// corta) até o consolidado não caber no teto. `ts` mais ANTIGO que o da heurística-alvo
|
|
302
|
+
// ⇒ recência menor ⇒ a cauda podada são as de enchimento, e o alvo segue curado.
|
|
303
|
+
const lines = [baseLog.trim()];
|
|
304
|
+
for (let i = 0; i < 34; i++) {
|
|
305
|
+
lines.push(JSON.stringify({
|
|
306
|
+
id: `F${i}`,
|
|
307
|
+
ts: 1_600_000_000_000 + i * 1000,
|
|
308
|
+
trigger: `heuristica de enchimento numero ${i} do consolidado`,
|
|
309
|
+
recommendation: `recomendacao de enchimento ${i} — ${'texto de enchimento com tamanho realista '.repeat(4)}`,
|
|
310
|
+
evidence: { count: 1 },
|
|
311
|
+
tier: 'normal',
|
|
312
|
+
}));
|
|
313
|
+
}
|
|
314
|
+
fs.writeFileSync(memPaths.raw, `${lines.join('\n')}\n`);
|
|
315
|
+
const pre = podMemory.consolidate(POD, { podDir });
|
|
316
|
+
ok(`fixture: estado com poda por orçamento ATIVA (${pre.evicted.length} descartada(s), ${pre.tokens}/${B})`,
|
|
317
|
+
pre.evicted.length > 0 && pre.tokens <= B, JSON.stringify({ evicted: pre.evicted.length, tokens: pre.tokens }));
|
|
318
|
+
ok('fixture: a poda FALA — needsCuration ligado pelo descarte (não fica false como antes)',
|
|
319
|
+
pre.needsCuration === true);
|
|
320
|
+
ok('fixture: heurística-alvo ainda presente no consolidado curado',
|
|
321
|
+
fs.readFileSync(memPaths.consolidated, 'utf8').includes(`**[${TRIGGER}]**`));
|
|
322
|
+
|
|
323
|
+
const snapBefore = snapshotDir(podDir);
|
|
324
|
+
const r = sh(promoteArgs(podDir, learningId, newContent, ['--approved-by', 'daniel']));
|
|
325
|
+
ok('DF-SF11.4-4 promoção que descartaria heurística: recusa exit 2 NOMEANDO o que sairia',
|
|
326
|
+
r.status === 2 && /DESCARTARIA \d+ heurística\(s\) do consolidado por (alvo|teto) de custo/.test(r.stderr) &&
|
|
327
|
+
/heuristica de enchimento numero/.test(r.stderr) && /cure o consolidado/.test(r.stderr), r.stderr);
|
|
328
|
+
// SF11.4b/DF-SF11.4b-1: recusar por CUSTO e relatar por custo apenas seria o
|
|
329
|
+
// sub-relato de C2 pela porta dos fundos — a mesma saída traz o LEDGER com o TOTAL das
|
|
330
|
+
// duas vias. (O gate continua medindo só `reason:'budget'`: promover nunca CAUSA corte
|
|
331
|
+
// por teto de atenção, porque a endurecida sai do pool que compete por slot.)
|
|
332
|
+
ok('SF11.4b/AC4 a mesma recusa traz o LEDGER com o TOTAL não-admitido e a quebra por causa',
|
|
333
|
+
/\[memory\] AVISO: \d+ heurística\(s\) NÃO entraram no consolidado/.test(r.stderr) &&
|
|
334
|
+
/por teto de atenção TOP_N=30/.test(r.stderr) && /de custo \d+ tok/.test(r.stderr), r.stderr);
|
|
335
|
+
ok('SF11.4b/AC4 o total do ledger ≥ o número que motivou a recusa (nunca um subconjunto anunciado como tudo)',
|
|
336
|
+
Number((r.stderr.match(/AVISO: (\d+) heurística/) || [])[1]) >=
|
|
337
|
+
Number((r.stderr.match(/DESCARTARIA (\d+) heurística/) || [])[1]), r.stderr.slice(0, 300));
|
|
338
|
+
const after = snapshotDir(podDir);
|
|
339
|
+
ok('DF-SF11.4-4 ROLLBACK completo: knowledge, log cru e consolidado byte-idênticos',
|
|
340
|
+
confinementViolations(snapBefore, after, []).length === 0,
|
|
341
|
+
confinementViolations(snapBefore, after, []).join(', '));
|
|
342
|
+
ok('DF-SF11.4-4 nenhum registro do alvo knowledge sobrou no log',
|
|
343
|
+
!fs.readFileSync(memPaths.raw, 'utf8').includes('"kind":"knowledge"'));
|
|
344
|
+
ok('DF-SF11.4-4 o gate antigo (tokens > BUDGET) NÃO teria disparado neste estado — por isso ele foi supersedido',
|
|
345
|
+
pre.tokens <= B);
|
|
346
|
+
|
|
347
|
+
fs.rmSync(tmp, { recursive: true, force: true });
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
351
|
+
// 5-bis. (SF11.4/DF-SF11.4-5) unicidade de hardening por `dedupKey`, não por string
|
|
352
|
+
// do render nem por `trigger` apenas. Entregue JUNTO de DF-SF11.4-1 por
|
|
353
|
+
// obrigação: aquele remove do consolidado exatamente a string que a checagem
|
|
354
|
+
// antiga procurava (`🔒 **[trigger]**`) — separar as duas criaria REGRESSÃO
|
|
355
|
+
// SILENCIOSA (dupla promoção da mesma heurística, sem recusa).
|
|
356
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
357
|
+
console.log('\n--- 5-bis. DF-SF11.4-5: unicidade por dedupKey (trigger + recommendation) ---');
|
|
358
|
+
{
|
|
359
|
+
const { tmp, podDir, learningId, newContent } = makePod('dedupkey');
|
|
360
|
+
const memPaths = podMemory.memoryPaths(POD, { podDir });
|
|
361
|
+
|
|
362
|
+
// (i) NÃO-REGRESSÃO: re-promover a MESMA heurística continua recusado — agora por
|
|
363
|
+
// `hardenedRefs.has(dedupKey(entry))`, e não por string do artefato renderizado.
|
|
364
|
+
const first = sh(promoteArgs(podDir, learningId, newContent, ['--approved-by', 'daniel']));
|
|
365
|
+
ok('DF-SF11.4-5 (i) 1ª promoção aplica (exit 0)', first.status === 0, `${first.stdout}${first.stderr}`);
|
|
366
|
+
// conteúdo DIFERENTE do já aplicado: isola a recusa por "já endurecida" (com o mesmo
|
|
367
|
+
// conteúdo, a recusa viria antes, por --content byte-idêntico).
|
|
368
|
+
const outroContent = path.join(tmp, 'outro-knowledge.md');
|
|
369
|
+
fs.writeFileSync(outroContent,
|
|
370
|
+
'# Slides HTML (promovido de novo)\n\n## Hierarquia\n\nBlocos visuais com hierarquia tipografica e respiro.\n');
|
|
371
|
+
const again = sh(promoteArgs(podDir, learningId, outroContent, ['--approved-by', 'daniel']));
|
|
372
|
+
ok('DF-SF11.4-5 (i) re-promover a MESMA heurística: recusa exit 2 citando a ref (dedupKey)',
|
|
373
|
+
again.status === 2 && /já está endurecido/.test(again.stderr) && /ref "/.test(again.stderr), again.stderr);
|
|
374
|
+
|
|
375
|
+
// (ii) CASO REAL DA SESSÃO 2026-08-01/02: duas heurísticas DISTINTAS com o MESMO
|
|
376
|
+
// trigger. Antes, a segunda era recusada por "já endurecido" (checagem por
|
|
377
|
+
// trigger apenas) e o contorno em produção foi RENOMEAR o trigger
|
|
378
|
+
// ("compor a capa" → "titular capa"). Agora ambas podem ser promovidas.
|
|
379
|
+
const irma = podMemory.append(POD, {
|
|
380
|
+
id: 'SRC-2', ts: 1_700_000_500_000,
|
|
381
|
+
trigger: TRIGGER, // MESMO trigger da já endurecida
|
|
382
|
+
recommendation: 'reservar respiro no topo da peca para o titulo nao encostar na borda',
|
|
383
|
+
evidence: { count: 2 }, tier: 'normal',
|
|
384
|
+
}, { podDir });
|
|
385
|
+
podMemory.consolidate(POD, { podDir });
|
|
386
|
+
ok('DF-SF11.4-5 (ii) fixture: a irmã (mesmo trigger, recommendation distinta) está curada',
|
|
387
|
+
fs.readFileSync(memPaths.consolidated, 'utf8').includes('reservar respiro no topo da peca'));
|
|
388
|
+
const content2 = path.join(tmp, 'novo-knowledge-2.md');
|
|
389
|
+
fs.writeFileSync(content2,
|
|
390
|
+
'# Slides HTML (promovido 2)\n\n## Hierarquia\n\nBlocos visuais com hierarquia tipografica.\n\n' +
|
|
391
|
+
'## Respiro\n\nReservar respiro no topo da peca para o titulo nao encostar na borda.\n');
|
|
392
|
+
const second = sh(promoteArgs(podDir, irma.entry.id, content2, ['--approved-by', 'daniel']));
|
|
393
|
+
ok('DF-SF11.4-5 (ii) heurística DISTINTA com o MESMO trigger é promovida (exit 0) — sem renomear nada',
|
|
394
|
+
second.status === 0, `${second.stdout}${second.stderr}`);
|
|
395
|
+
const detail = fs.readFileSync(memPaths.hardenedDetail, 'utf8');
|
|
396
|
+
ok('DF-SF11.4-5 (ii) as DUAS endurecidas convivem na trilha, com o mesmo trigger',
|
|
397
|
+
detail.split('\n').filter((l) => l.includes(`🔒 **[${TRIGGER}]**`)).length === 2, detail);
|
|
398
|
+
ok('DF-SF11.4-5 (ii) a referência agregada contabiliza as duas',
|
|
399
|
+
fs.readFileSync(memPaths.consolidated, 'utf8').includes(`knowledge/${KFILE} (2)`));
|
|
400
|
+
|
|
401
|
+
fs.rmSync(tmp, { recursive: true, force: true });
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
405
|
+
// 6. Pod SEM pins ⇒ export_check "n/a" DECLARADO (nunca pulado em silêncio)
|
|
406
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
407
|
+
console.log('\n--- 6. Pod não export-capaz: export_check "n/a" declarado (DEBT-SF10.10-E) ---');
|
|
408
|
+
{
|
|
409
|
+
const { tmp, podDir, learningId, newContent } = makePod('nopins');
|
|
410
|
+
// renomeia o pod (spec + wf + smoke) para um nome SEM pins em exports/
|
|
411
|
+
const alias = 'carousel-sem-pins';
|
|
412
|
+
const spec = JSON.parse(fs.readFileSync(path.join(podDir, 'pod-spec.json'), 'utf8'));
|
|
413
|
+
spec.pod = alias;
|
|
414
|
+
fs.writeFileSync(path.join(podDir, 'pod-spec.json'), `${JSON.stringify(spec, null, 2)}\n`);
|
|
415
|
+
fs.renameSync(path.join(podDir, 'memory'), path.join(podDir, 'memory-old'));
|
|
416
|
+
fs.renameSync(path.join(podDir, 'memory-old'), path.join(podDir, 'memory')); // no-op: memória é por podDir
|
|
417
|
+
ok('fixture: pod renomeado sem pins em exports/',
|
|
418
|
+
!fs.existsSync(path.join(REPO, 'exports', alias, 'pins.json')) && !fs.existsSync(path.join(podDir, 'pins.json')));
|
|
419
|
+
|
|
420
|
+
const r = sh([HARDEN, podDir, '--target-type', 'knowledge', '--learning', learningId,
|
|
421
|
+
'--file', KFILE, '--content', newContent, '--approved-by', 'daniel']);
|
|
422
|
+
ok(`promoção em pod sem pins: exit 0 (${r.status})`, r.status === 0, `${r.stdout}${r.stderr}`);
|
|
423
|
+
ok('AC3(v) export_check "n/a" DECLARADO no relatório, com o motivo e o débito',
|
|
424
|
+
/export_check: n\/a/.test(r.stdout) && /pins\.json/.test(r.stdout) && /DEBT-SF10\.10-E/.test(r.stdout), r.stdout);
|
|
425
|
+
ok('pod sem pins: promoção ainda registra 🔒 (referência no consolidado + detalhe na trilha)',
|
|
426
|
+
fs.readFileSync(path.join(podDir, 'memory', 'learnings.consolidated.md'), 'utf8')
|
|
427
|
+
.includes(`knowledge/${KFILE} (1)`) &&
|
|
428
|
+
fs.readFileSync(path.join(podDir, 'memory', podMemory.HARDENED_DETAIL_FILE), 'utf8')
|
|
429
|
+
.includes(`endurecida → knowledge/${KFILE}`));
|
|
430
|
+
fs.rmSync(tmp, { recursive: true, force: true });
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
434
|
+
// 7. --list-candidates (AC4): read-only, exit 0, nunca promove, nunca classifica
|
|
435
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
436
|
+
console.log('\n--- 7. --list-candidates: detecção read-only (DF-SF10.10-3) ---');
|
|
437
|
+
{
|
|
438
|
+
const { tmp, podDir, learningId, newContent } = makePod('list');
|
|
439
|
+
podMemory.append(POD, {
|
|
440
|
+
id: 'SRC-2', ts: 1_700_000_500_000, trigger: 'api do hn instavel',
|
|
441
|
+
recommendation: 'usar timeout explicito e retry', evidence: { count: 1 }, tier: 'normal',
|
|
442
|
+
}, { podDir });
|
|
443
|
+
podMemory.consolidate(POD, { podDir });
|
|
444
|
+
const snapBefore = snapshotDir(podDir);
|
|
445
|
+
|
|
446
|
+
const l1 = sh([HARDEN, podDir, '--list-candidates']);
|
|
447
|
+
ok('--list-candidates: exit 0', l1.status === 0, `${l1.stdout}${l1.stderr}`);
|
|
448
|
+
ok('--list-candidates: lista as 2 heurísticas consolidadas com o id colável em --learning',
|
|
449
|
+
l1.stdout.includes(`**[${TRIGGER}]**`) && l1.stdout.includes('**[api do hn instavel]**') &&
|
|
450
|
+
l1.stdout.includes('--learning SRC-1') && l1.stdout.includes('--learning SRC-2'), l1.stdout);
|
|
451
|
+
ok('--list-candidates: declara que NÃO classifica conteúdo vs mecanizável (sem oráculo)',
|
|
452
|
+
/não há oráculo|NÃO classifica/i.test(l1.stdout), l1.stdout);
|
|
453
|
+
ok('--list-candidates: READ-ONLY (pod byte-idêntico depois)',
|
|
454
|
+
confinementViolations(snapBefore, snapshotDir(podDir), []).length === 0);
|
|
455
|
+
|
|
456
|
+
// depois de promover uma, ela SAI da lista (diferença de conjuntos, verificável)
|
|
457
|
+
const p = sh(promoteArgs(podDir, learningId, newContent, ['--approved-by', 'daniel']));
|
|
458
|
+
ok('setup: promoção auxiliar exit 0', p.status === 0, `${p.stdout}${p.stderr}`);
|
|
459
|
+
const l2 = sh([HARDEN, podDir, '--list-candidates']);
|
|
460
|
+
ok('--list-candidates: heurística ENDURECIDA sai da lista; a outra permanece',
|
|
461
|
+
l2.status === 0 && !l2.stdout.includes(`**[${TRIGGER}]**`) && l2.stdout.includes('**[api do hn instavel]**'),
|
|
462
|
+
l2.stdout);
|
|
463
|
+
ok('--list-candidates: exit 0 mesmo sem memória (nunca falha)',
|
|
464
|
+
sh([HARDEN, path.join(REPO, 'template', 'pods', 'content-carousel'), '--list-candidates']).status === 0);
|
|
465
|
+
|
|
466
|
+
fs.rmSync(tmp, { recursive: true, force: true });
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
470
|
+
// 9. QA/F1 — SYMLINK não fura a prova de confinamento
|
|
471
|
+
// O guard lexical (`path.resolve`) não enxerga link: sem o guard de realpath, um
|
|
472
|
+
// knowledge/<file> apontando para fora do pod fazia a promoção sair exit 0 dizendo
|
|
473
|
+
// "confinamento OK" e SOBRESCREVER o arquivo externo — o diff é cego a links
|
|
474
|
+
// (Dirent de symlink não é isFile()). Mesmo padrão de guard do Guard B de
|
|
475
|
+
// command_check (pc-run-pod.cjs) e do confineToRunDir (pod-export-ui.cjs, SF10.9).
|
|
476
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
477
|
+
console.log('\n--- 9. QA/F1: symlink no knowledge-alvo ⇒ recusa antes de escrever ---');
|
|
478
|
+
{
|
|
479
|
+
const { tmp, podDir, learningId, newContent } = makePod('symlink');
|
|
480
|
+
const outside = path.join(tmp, 'FORA-DO-POD.md');
|
|
481
|
+
const target = path.join(podDir, 'knowledge', KFILE);
|
|
482
|
+
fs.copyFileSync(target, outside); // o arquivo externo guarda o conteúdo real
|
|
483
|
+
const outsideBefore = fs.readFileSync(outside);
|
|
484
|
+
fs.rmSync(target);
|
|
485
|
+
fs.symlinkSync(outside, target); // knowledge/slides-html.md → fora do pod
|
|
486
|
+
ok('fixture: knowledge-alvo é um symlink para fora do podDir', fs.lstatSync(target).isSymbolicLink());
|
|
487
|
+
|
|
488
|
+
const snapBefore = snapshotDir(podDir);
|
|
489
|
+
const r = sh(promoteArgs(podDir, learningId, newContent, ['--approved-by', 'daniel']));
|
|
490
|
+
ok('F1 promoção através de symlink: RECUSA exit 2 (não exit 0 com "confinamento OK")',
|
|
491
|
+
r.status === 2, `status=${r.status}\n${r.stdout}${r.stderr}`);
|
|
492
|
+
ok('F1 a recusa nomeia o realpath e o motivo (escrita cairia fora do pod / diff cego)',
|
|
493
|
+
/realpath/.test(r.stderr) && /FORA/i.test(r.stderr), r.stderr);
|
|
494
|
+
ok('F1 o arquivo FORA do pod continua BYTE-IDÊNTICO (nada vazou para fora do podDir)',
|
|
495
|
+
fs.readFileSync(outside).equals(outsideBefore));
|
|
496
|
+
ok('F1 nenhum registro de hardening foi gravado',
|
|
497
|
+
!fs.readFileSync(podMemory.memoryPaths(POD, { podDir }).raw, 'utf8').includes('"type":"hardening"'));
|
|
498
|
+
ok('F1 pod inalterado (o symlink segue lá, nada foi escrito)',
|
|
499
|
+
confinementViolations(snapBefore, snapshotDir(podDir), []).length === 0);
|
|
500
|
+
ok('F1 snapshotDir REGISTRA o symlink (antes era invisível: Dirent de link não é isFile())',
|
|
501
|
+
[...snapBefore.keys()].includes(`knowledge/${KFILE}`) &&
|
|
502
|
+
snapBefore.get(`knowledge/${KFILE}`).toString().startsWith('symlink → '),
|
|
503
|
+
[...snapBefore.keys()].filter((k) => k.startsWith('knowledge/')).join(','));
|
|
504
|
+
|
|
505
|
+
// link criado DENTRO do pod agora conta como mudança para o diff de confinamento
|
|
506
|
+
const snapA = snapshotDir(podDir);
|
|
507
|
+
fs.symlinkSync(outside, path.join(podDir, 'knowledge', 'LINK-NOVO.md'));
|
|
508
|
+
ok('F1 symlink CRIADO sob o podDir vira violação de confinamento (antes: invisível)',
|
|
509
|
+
confinementViolations(snapA, snapshotDir(podDir), []).join() === '+ knowledge/LINK-NOVO.md',
|
|
510
|
+
confinementViolations(snapA, snapshotDir(podDir), []).join());
|
|
511
|
+
|
|
512
|
+
fs.rmSync(tmp, { recursive: true, force: true });
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
516
|
+
// 10. QA/F2 — a GUARDA de confinamento (o wiring, não a função pura) tem que MORRER
|
|
517
|
+
// se removida. Padrão de kill-test já usado no repo (pod-memory.test.cjs SF8.3):
|
|
518
|
+
// roda uma cópia do motor REAL com UMA escrita extra injetada fora do trio. Como a
|
|
519
|
+
// cópia é derivada do arquivo em disco a cada execução, remover a guarda do motor
|
|
520
|
+
// real deixa esta verificação VERMELHA.
|
|
521
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
522
|
+
console.log('\n--- 10. QA/F2: guarda de confinamento morre sob mutação (teste negativo real) ---');
|
|
523
|
+
{
|
|
524
|
+
const { tmp, podDir, learningId, newContent } = makePod('confin');
|
|
525
|
+
const src = fs.readFileSync(HARDEN, 'utf8');
|
|
526
|
+
const anchor = ' fs.writeFileSync(targetAbs, newContent);';
|
|
527
|
+
ok('F2 âncora da aplicação do conteúdo encontrada no motor', src.includes(anchor));
|
|
528
|
+
// injeta UMA escrita fora do trio permitido (o vetor que o QA usou por injeção)
|
|
529
|
+
const injected = src.replace(anchor,
|
|
530
|
+
`${anchor}\n fs.writeFileSync(path.join(podDir, 'INTRUSO.md'), 'escrita fora do trio permitido\\n');`);
|
|
531
|
+
ok('F2 injeção aplicada (motor com escrita fora do trio)', injected !== src);
|
|
532
|
+
const sabPath = path.join(tmp, 'forge-harden-learning-intruso.cjs');
|
|
533
|
+
// A cópia vive em tmp: reaponta os require relativos E o REPO_ROOT para o repo real —
|
|
534
|
+
// sem isso a cópia recusaria por "baseline vermelho" (motivo ERRADO) e a verificação de
|
|
535
|
+
// exit 2 seria VACUOSA. A asserção seguinte (mensagem do confinamento) é o que impede isso.
|
|
536
|
+
const rooted = injected
|
|
537
|
+
.replace(/require\('\.\//g, `require('${path.join(REPO, 'scripts')}/`)
|
|
538
|
+
.replace("const REPO_ROOT = path.resolve(__dirname, '..');", `const REPO_ROOT = ${JSON.stringify(REPO)};`);
|
|
539
|
+
ok('F2 cópia reancorada no repo real (senão o exit 2 seria por baseline, não pela guarda)',
|
|
540
|
+
rooted.includes(`const REPO_ROOT = ${JSON.stringify(REPO)};`));
|
|
541
|
+
fs.writeFileSync(sabPath, rooted);
|
|
542
|
+
|
|
543
|
+
const snapBefore = snapshotDir(podDir);
|
|
544
|
+
const r = spawnSync(process.execPath, [sabPath, podDir, '--target-type', 'knowledge',
|
|
545
|
+
'--learning', learningId, '--file', KFILE, '--content', newContent, '--approved-by', 'daniel'],
|
|
546
|
+
{ encoding: 'utf8', cwd: REPO });
|
|
547
|
+
ok('F2 escrita fora do trio ⇒ a GUARDA dispara: exit 2 (se a guarda for removida, isto vira exit 0)',
|
|
548
|
+
r.status === 2, `status=${r.status}\n${r.stdout}${r.stderr}`);
|
|
549
|
+
ok('F2 a recusa nomeia o confinamento e o path intruso, e diz ROLLBACK',
|
|
550
|
+
/confinamento permitido/.test(r.stderr) && /INTRUSO\.md/.test(r.stderr) && /ROLLBACK aplicado/.test(r.stderr),
|
|
551
|
+
r.stderr);
|
|
552
|
+
const after = snapshotDir(podDir);
|
|
553
|
+
ok('F2 ROLLBACK: o arquivo intruso foi REMOVIDO e o pod voltou byte a byte',
|
|
554
|
+
!fs.existsSync(path.join(podDir, 'INTRUSO.md')) &&
|
|
555
|
+
confinementViolations(snapBefore, after, []).length === 0,
|
|
556
|
+
confinementViolations(snapBefore, after, []).join(', '));
|
|
557
|
+
ok('F2 ROLLBACK: nenhum registro de hardening ficou no log',
|
|
558
|
+
!fs.readFileSync(podMemory.memoryPaths(POD, { podDir }).raw, 'utf8').includes('"type":"hardening"'));
|
|
559
|
+
|
|
560
|
+
fs.rmSync(tmp, { recursive: true, force: true });
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
564
|
+
// 11. QA/F2 — a GUARDA de paridade de compilação também tem rota negativa REAL:
|
|
565
|
+
// knowledge declarado em `knowledge_exclude:` é editável (AC9 aceita), mas o
|
|
566
|
+
// compilador NÃO o inclui em per_file_sha ⇒ a promoção muda um arquivo que o
|
|
567
|
+
// bundle do export não vê. A guarda pega isso e faz rollback.
|
|
568
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
569
|
+
console.log('\n--- 11. QA/F2: paridade per_file_sha — knowledge excluído do bundle ⇒ rollback ---');
|
|
570
|
+
{
|
|
571
|
+
const { tmp, podDir, learningId, newContent } = makePod('paridade');
|
|
572
|
+
const excluded = 'nota-interna.md';
|
|
573
|
+
fs.writeFileSync(path.join(podDir, 'knowledge', excluded), '# Nota interna\n\nTexto original.\n');
|
|
574
|
+
const wfPath = path.join(podDir, 'workflows', `wf-${POD}.yaml`);
|
|
575
|
+
fs.writeFileSync(wfPath, `knowledge_exclude: [${excluded}]\n${fs.readFileSync(wfPath, 'utf8')}`);
|
|
576
|
+
ok('fixture: knowledge declarado só em knowledge_exclude (fora do bundle do export)',
|
|
577
|
+
declaredKnowledge(wfPath).has(excluded));
|
|
578
|
+
|
|
579
|
+
const snapBefore = snapshotDir(podDir);
|
|
580
|
+
const r = sh([HARDEN, podDir, '--target-type', 'knowledge', '--learning', learningId,
|
|
581
|
+
'--file', excluded, '--content', newContent, '--approved-by', 'daniel']);
|
|
582
|
+
ok('F2 paridade: arquivo ausente do per_file_sha ⇒ recusa exit 2 + ROLLBACK',
|
|
583
|
+
r.status === 2 && /per_file_sha NÃO registrou mudança/.test(r.stderr) && /ROLLBACK aplicado/.test(r.stderr),
|
|
584
|
+
`status=${r.status}\n${r.stderr}`);
|
|
585
|
+
ok('F2 paridade: rollback byte a byte (knowledge excluído restaurado, log limpo)',
|
|
586
|
+
confinementViolations(snapBefore, snapshotDir(podDir), []).length === 0 &&
|
|
587
|
+
!fs.readFileSync(podMemory.memoryPaths(POD, { podDir }).raw, 'utf8').includes('"type":"hardening"'),
|
|
588
|
+
confinementViolations(snapBefore, snapshotDir(podDir), []).join(', '));
|
|
589
|
+
|
|
590
|
+
fs.rmSync(tmp, { recursive: true, force: true });
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
594
|
+
// 8. --help carrega a fronteira de qualidade (AC3 vi)
|
|
595
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
596
|
+
console.log('\n--- 8. --help ---');
|
|
597
|
+
{
|
|
598
|
+
const h = sh([HARDEN, '--help']);
|
|
599
|
+
ok('--help: exit 0 e documenta os dois alvos + a detecção',
|
|
600
|
+
h.status === 0 && /--target-type knowledge/.test(h.stdout) && /--list-candidates/.test(h.stdout), h.stdout);
|
|
601
|
+
ok('--help: declara literalmente que o único portão de qualidade é --approved-by',
|
|
602
|
+
h.stdout.includes(QUALITY_NOTICE), h.stdout);
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
console.log(`\n${passed}/${passed + failures.length} verificações passaram.`);
|
|
606
|
+
if (failures.length) {
|
|
607
|
+
console.error('\nSUÍTE SF10.10 (alvo knowledge) FALHOU:');
|
|
608
|
+
for (const f of failures) console.error(` - ${f}`);
|
|
609
|
+
process.exit(1);
|
|
610
|
+
}
|
|
611
|
+
console.log('Alvo knowledge verde: confinamento + proveniência + paridade de compilação provados; qualidade da prosa segue sendo humana (--approved-by).');
|
|
612
|
+
process.exit(0);
|