@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,330 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
/**
|
|
4
|
+
* merge-settings.test.cjs — regressão do merge aditivo de `hooks` e
|
|
5
|
+
* `permissions` no instalador (story SF13.13).
|
|
6
|
+
*
|
|
7
|
+
* O DEFEITO QUE ESTE TESTE EXISTE PARA IMPEDIR: `shallowMergeSettings` era
|
|
8
|
+
* `{ ...template, ...usuário }`. `hooks` é chave de TOPO, então toda
|
|
9
|
+
* instalação que já tinha `hooks` descartava o bloco INTEIRO do template —
|
|
10
|
+
* hook novo do framework não alcançava instalação nenhuma, nunca.
|
|
11
|
+
*
|
|
12
|
+
* POR QUE ELE INSTALA SOBRE UM `settings.json` PRÉ-EXISTENTE: o ramo de merge
|
|
13
|
+
* (`copy.js`, `rel === SETTINGS_REL`) só dispara com destino EXISTENTE. O
|
|
14
|
+
* único E2E do repo (`scripts/validate-install.js`) instala em `mkdtempSync`
|
|
15
|
+
* vazio e por isso nunca exerceu esta função — cobertura zero foi a razão de o
|
|
16
|
+
* bug sobreviver desde o início. Teste que instalasse em diretório vazio
|
|
17
|
+
* repetiria o mesmo ponto cego.
|
|
18
|
+
*
|
|
19
|
+
* CONTROLES NA MESMA EXECUÇÃO (o conjunto não pode ser vácuo): cada bateria
|
|
20
|
+
* roda também contra 2 mutantes de referência — `noop` (devolve o objeto do
|
|
21
|
+
* usuário: o comportamento efetivo de ANTES do conserto) e `tw` (template
|
|
22
|
+
* vence). Uma bateria que não reprovasse os dois não estaria medindo nada.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
const fs = require('node:fs');
|
|
26
|
+
const os = require('node:os');
|
|
27
|
+
const path = require('node:path');
|
|
28
|
+
|
|
29
|
+
const copy = require(path.resolve(__dirname, '..', 'copy.js'));
|
|
30
|
+
const { shallowMergeSettings, copyTemplate } = copy;
|
|
31
|
+
|
|
32
|
+
const ROOT = path.resolve(__dirname, '..', '..');
|
|
33
|
+
const TEMPLATE_SETTINGS = path.join(ROOT, 'template', '.claude', 'settings.json');
|
|
34
|
+
const T = JSON.parse(fs.readFileSync(TEMPLATE_SETTINGS, 'utf8'));
|
|
35
|
+
|
|
36
|
+
const C = (x) => JSON.parse(JSON.stringify(x));
|
|
37
|
+
const S = (x) => JSON.stringify(x);
|
|
38
|
+
|
|
39
|
+
let failures = 0;
|
|
40
|
+
const tmpDirs = [];
|
|
41
|
+
|
|
42
|
+
function assert(ok, msg) {
|
|
43
|
+
if (ok) {
|
|
44
|
+
console.log(`[OK ] ${msg}`);
|
|
45
|
+
} else {
|
|
46
|
+
console.error(`[FALHA] ${msg}`);
|
|
47
|
+
failures++;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* O `settings.json` de usuário do caso real (SF13.12): igual ao template
|
|
53
|
+
* MENOS a entrada `enforce-ready-oracles.cjs`, MAIS coisas só dele — 1 entrada
|
|
54
|
+
* própria no mesmo bloco (com `timeout` próprio), 1 bloco próprio (`Task`),
|
|
55
|
+
* `permissions` próprias, `language` próprio, e NENHUM `PostToolUse`.
|
|
56
|
+
*/
|
|
57
|
+
function usuario() {
|
|
58
|
+
const u = C(T);
|
|
59
|
+
u.language = 'english';
|
|
60
|
+
const ew = u.hooks.PreToolUse.find((b) => b.matcher === 'Edit|Write');
|
|
61
|
+
ew.hooks = ew.hooks.filter((h) => String(h.command).indexOf('enforce-ready-oracles') < 0);
|
|
62
|
+
ew.hooks.push({ type: 'command', command: 'node meu-hook.cjs', timeout: 5 });
|
|
63
|
+
u.hooks.PreToolUse.push({
|
|
64
|
+
matcher: 'Task',
|
|
65
|
+
hooks: [{ type: 'command', command: 'node meu-task.cjs', timeout: 5 }],
|
|
66
|
+
});
|
|
67
|
+
delete u.hooks.PostToolUse;
|
|
68
|
+
u.permissions.deny = ['Read(.legado-core/**)'];
|
|
69
|
+
u.permissions.allow = ['Bash(ls:*)'];
|
|
70
|
+
return u;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// --- mutantes de referência -------------------------------------------------
|
|
74
|
+
const real = shallowMergeSettings;
|
|
75
|
+
const noop = (t, u) => C(u); // comportamento efetivo de antes do conserto
|
|
76
|
+
const tw = (t, u) => Object.assign({}, C(u), C(t)); // template vence
|
|
77
|
+
|
|
78
|
+
/** Roda a bateria contra real/noop/tw e assere real verde + os 2 mutantes vermelhos. */
|
|
79
|
+
function bateria(nome, fn) {
|
|
80
|
+
const v = { real: fn(real), noop: fn(noop), tw: fn(tw) };
|
|
81
|
+
assert(v.real === null, `${nome}: implementação real passa (motivo: ${v.real})`);
|
|
82
|
+
assert(v.noop !== null, `${nome}: controle "noop" (merge raso de antes) REPROVA — bateria não é vácua`);
|
|
83
|
+
assert(v.tw !== null, `${nome}: controle "template vence" REPROVA — bateria não é vácua`);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// --- AC-1: hooks ------------------------------------------------------------
|
|
87
|
+
function batHooks(m) {
|
|
88
|
+
let r;
|
|
89
|
+
try {
|
|
90
|
+
r = m(C(T), usuario());
|
|
91
|
+
} catch (e) {
|
|
92
|
+
return 'lancou:' + e.message;
|
|
93
|
+
}
|
|
94
|
+
const pre = ((r.hooks || {}).PreToolUse) || [];
|
|
95
|
+
const ew = pre.filter((b) => b.matcher === 'Edit|Write');
|
|
96
|
+
if (ew.length !== 1) return 'bloco-Edit|Write-nao-unico:' + ew.length;
|
|
97
|
+
const cmds = (ew[0].hooks || []).map((h) => String(h.command || ''));
|
|
98
|
+
const n = (s) => cmds.filter((c) => c.indexOf(s) >= 0).length;
|
|
99
|
+
if (n('enforce-ready-oracles') !== 1) return 'entrada-do-template-ausente-ou-duplicada:' + n('enforce-ready-oracles');
|
|
100
|
+
if (n('enforce-cycle-authority') !== 1) return 'entrada-preexistente-perdida-ou-duplicada:' + n('enforce-cycle-authority');
|
|
101
|
+
if (n('meu-hook') !== 1) return 'entrada-so-do-usuario-perdida:' + n('meu-hook');
|
|
102
|
+
const meu = (ew[0].hooks || []).find((h) => String(h.command).indexOf('meu-hook') >= 0);
|
|
103
|
+
if (!meu || meu.timeout !== 5) return 'timeout-do-usuario-sobrescrito:' + (meu && meu.timeout);
|
|
104
|
+
if (pre.filter((b) => b.matcher === 'Task').length !== 1) return 'bloco-so-do-usuario-perdido';
|
|
105
|
+
if (!r.hooks.PostToolUse) return 'evento-so-do-template-ausente';
|
|
106
|
+
let r2;
|
|
107
|
+
try {
|
|
108
|
+
r2 = m(C(T), C(r));
|
|
109
|
+
} catch (e) {
|
|
110
|
+
return '2a-passada-lancou:' + e.message;
|
|
111
|
+
}
|
|
112
|
+
if (S(r2) !== S(r)) return 'nao-idempotente';
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// --- AC-2: permissions ------------------------------------------------------
|
|
117
|
+
function batPermissions(m) {
|
|
118
|
+
let r;
|
|
119
|
+
try {
|
|
120
|
+
r = m(C(T), usuario());
|
|
121
|
+
} catch (e) {
|
|
122
|
+
return 'lancou:' + e.message;
|
|
123
|
+
}
|
|
124
|
+
const d = ((r.permissions || {}).deny) || [];
|
|
125
|
+
for (const x of ['Read(.env)', 'Read(.env.*)', 'Read(.legado-core/**)']) {
|
|
126
|
+
if (d.indexOf(x) < 0) return 'deny-sem:' + x;
|
|
127
|
+
}
|
|
128
|
+
if (d.length !== 3) return 'deny-com-duplicata-ou-lixo:' + d.length;
|
|
129
|
+
if (d[0] !== 'Read(.legado-core/**)') return 'ordem-do-usuario-nao-preservada';
|
|
130
|
+
const a = ((r.permissions || {}).allow) || [];
|
|
131
|
+
if (a.length !== 1 || a[0] !== 'Bash(ls:*)') return 'allow-so-do-usuario-alterado';
|
|
132
|
+
let r2;
|
|
133
|
+
try {
|
|
134
|
+
r2 = m(C(T), C(r));
|
|
135
|
+
} catch (e) {
|
|
136
|
+
return '2a-passada-lancou:' + e.message;
|
|
137
|
+
}
|
|
138
|
+
if (S(r2) !== S(r)) return 'nao-idempotente';
|
|
139
|
+
return null;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// --- AC-3: não-dano universal (comparação estrutural, insensível a ordem) ----
|
|
143
|
+
function cmp(a, d, cam, prob, acr) {
|
|
144
|
+
const tp = (x) => (Array.isArray(x) ? 'arr' : x && typeof x === 'object' ? 'obj' : 'prim');
|
|
145
|
+
if (tp(a) !== tp(d)) {
|
|
146
|
+
prob.push(cam + ':tipo');
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
if (tp(a) === 'prim') {
|
|
150
|
+
if (a !== d) prob.push(cam + ':alterado');
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
if (tp(a) === 'obj') {
|
|
154
|
+
for (const k of Object.keys(a)) {
|
|
155
|
+
if (!(k in d)) {
|
|
156
|
+
prob.push(cam + '.' + k + ':removido');
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
cmp(a[k], d[k], cam + '.' + k, prob, acr);
|
|
160
|
+
}
|
|
161
|
+
for (const k of Object.keys(d)) if (!(k in a)) acr.push(cam + '.' + k);
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
const usado = d.map(() => false);
|
|
165
|
+
const pend = [];
|
|
166
|
+
for (const el of a) {
|
|
167
|
+
const i = d.findIndex((x, j) => !usado[j] && S(x) === S(el));
|
|
168
|
+
if (i >= 0) usado[i] = true;
|
|
169
|
+
else pend.push(el);
|
|
170
|
+
}
|
|
171
|
+
for (const el of pend) {
|
|
172
|
+
let ok = false;
|
|
173
|
+
for (let j = 0; j < d.length && !ok; j++) {
|
|
174
|
+
if (usado[j]) continue;
|
|
175
|
+
const p2 = [];
|
|
176
|
+
const a2 = [];
|
|
177
|
+
cmp(el, d[j], cam + '[]', p2, a2);
|
|
178
|
+
if (p2.length === 0) {
|
|
179
|
+
usado[j] = true;
|
|
180
|
+
a2.forEach((x) => acr.push(x));
|
|
181
|
+
ok = true;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
if (!ok) prob.push(cam + '[]:removido-ou-alterado');
|
|
185
|
+
}
|
|
186
|
+
for (let j = 0; j < d.length; j++) if (!usado[j]) acr.push(cam + '[]');
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/** (a) nada removido/alterado; (b) todo acréscimo sob hooks/permissions; (c) ao menos 1 acréscimo. */
|
|
190
|
+
function dano(antes, depois) {
|
|
191
|
+
const prob = [];
|
|
192
|
+
const acr = [];
|
|
193
|
+
cmp(antes, depois, '', prob, acr);
|
|
194
|
+
const fora = acr.filter((c) => !(c.indexOf('.hooks') === 0 || c.indexOf('.permissions') === 0));
|
|
195
|
+
if (prob.length) return 'danos:' + S(prob);
|
|
196
|
+
if (fora.length) return 'acrescimo-fora:' + S(fora);
|
|
197
|
+
if (acr.length < 1) return 'nenhum-acrescimo';
|
|
198
|
+
return null;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function batNaoDano(m) {
|
|
202
|
+
const u = usuario();
|
|
203
|
+
let r;
|
|
204
|
+
try {
|
|
205
|
+
r = m(C(T), C(u));
|
|
206
|
+
} catch (e) {
|
|
207
|
+
return 'lancou:' + e.message;
|
|
208
|
+
}
|
|
209
|
+
return dano(u, r);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// --- AC-4: guarda — chave não-aditiva mantém o override raso ----------------
|
|
213
|
+
function batOverrideRaso(m) {
|
|
214
|
+
let r;
|
|
215
|
+
try {
|
|
216
|
+
const u = usuario();
|
|
217
|
+
r = m(C(T), C(u));
|
|
218
|
+
const T2 = C(T);
|
|
219
|
+
T2.statusLine = { type: 'command', command: 'x' };
|
|
220
|
+
const r2 = m(T2, C(u));
|
|
221
|
+
const u3 = C(u);
|
|
222
|
+
u3.model = 'opus';
|
|
223
|
+
const r3 = m(C(T), u3);
|
|
224
|
+
if (r.language !== 'english') return 'language-do-usuario-perdido:' + r.language;
|
|
225
|
+
if (!r2.statusLine) return 'chave-nova-do-template-nao-entrou';
|
|
226
|
+
if (r3.model !== 'opus') return 'chave-so-do-usuario-perdida:' + r3.model;
|
|
227
|
+
return null;
|
|
228
|
+
} catch (e) {
|
|
229
|
+
return 'lancou:' + e.message;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// --- AC-5: ponta a ponta, em disco, nos DOIS pontos de chamada --------------
|
|
234
|
+
async function e2eEmDisco() {
|
|
235
|
+
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'sf1313-test-'));
|
|
236
|
+
tmpDirs.push(tmp);
|
|
237
|
+
const tdir = path.join(tmp, 'tpl');
|
|
238
|
+
const ddir = path.join(tmp, 'dst');
|
|
239
|
+
fs.mkdirSync(path.join(tdir, '.claude'), { recursive: true });
|
|
240
|
+
fs.mkdirSync(path.join(ddir, '.claude'), { recursive: true });
|
|
241
|
+
fs.copyFileSync(TEMPLATE_SETTINGS, path.join(tdir, '.claude', 'settings.json'));
|
|
242
|
+
const src = path.join(tdir, '.claude', 'settings.json');
|
|
243
|
+
const alvo = path.join(ddir, '.claude', 'settings.json');
|
|
244
|
+
// Pré-existente: o ramo de merge só dispara com destino EXISTENTE.
|
|
245
|
+
const escreve = () => fs.writeFileSync(alvo, S(usuario()) + '\n', 'utf8');
|
|
246
|
+
const antes = C(usuario());
|
|
247
|
+
|
|
248
|
+
const inst = () => copyTemplate({ templateDir: tdir, destDir: ddir, yes: true, selectedPods: [], prompter: null });
|
|
249
|
+
|
|
250
|
+
escreve();
|
|
251
|
+
await inst();
|
|
252
|
+
assert(dano(antes, JSON.parse(fs.readFileSync(alvo, 'utf8'))) === null, 'AC-5(a) install: arquivo em disco satisfaz o não-dano (acrescenta, não remove)');
|
|
253
|
+
const b1 = fs.readFileSync(alvo, 'utf8');
|
|
254
|
+
await inst();
|
|
255
|
+
assert(fs.readFileSync(alvo, 'utf8') === b1, 'AC-5(a) install: 2ª passada devolve o arquivo BYTE-IDÊNTICO (idempotente)');
|
|
256
|
+
|
|
257
|
+
let rm = null;
|
|
258
|
+
try {
|
|
259
|
+
rm = require(path.resolve(__dirname, '..', 'update.js')).remergeSettings;
|
|
260
|
+
} catch (e) {
|
|
261
|
+
rm = null;
|
|
262
|
+
}
|
|
263
|
+
assert(typeof rm === 'function', 'AC-5(b) `remergeSettings` é exportado por installer/update.js (o caminho de update é observável)');
|
|
264
|
+
if (typeof rm !== 'function') return;
|
|
265
|
+
|
|
266
|
+
escreve();
|
|
267
|
+
const antesTexto = fs.readFileSync(alvo, 'utf8');
|
|
268
|
+
rm(src, alvo);
|
|
269
|
+
assert(dano(antes, JSON.parse(fs.readFileSync(alvo, 'utf8'))) === null, 'AC-5(b) update: arquivo em disco satisfaz o não-dano');
|
|
270
|
+
const b2 = fs.readFileSync(alvo, 'utf8');
|
|
271
|
+
assert(b2 !== antesTexto, 'AC-5(d) update MUDA o arquivo — controle contra a versão degenerada que "não faz dano" por não fazer nada');
|
|
272
|
+
rm(src, alvo);
|
|
273
|
+
assert(fs.readFileSync(alvo, 'utf8') === b2, 'AC-5(b) update: 2ª passada devolve o arquivo BYTE-IDÊNTICO (idempotente)');
|
|
274
|
+
assert(b2.indexOf('enforce-ready-oracles') >= 0, 'AC-5(c) a entrada nova está NO ARQUIVO EM DISCO, não só no objeto em memória');
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
async function main() {
|
|
278
|
+
bateria('AC-1 hooks (entrada que falta entra, o que existe fica, 2× não duplica)', batHooks);
|
|
279
|
+
bateria('AC-2 permissions (união aditiva, ordem do usuário, sem duplicata)', batPermissions);
|
|
280
|
+
bateria('AC-3 não-dano universal (nada removido/alterado; acréscimo só sob hooks/permissions)', batNaoDano);
|
|
281
|
+
|
|
282
|
+
// AC-3, controles extras: reordenação NÃO é dano; chave de topo nova É.
|
|
283
|
+
const u = usuario();
|
|
284
|
+
const r = real(C(T), C(u));
|
|
285
|
+
const reord = (() => {
|
|
286
|
+
const x = C(r);
|
|
287
|
+
x.hooks.PreToolUse.reverse();
|
|
288
|
+
return { hooks: x.hooks, permissions: x.permissions, language: x.language };
|
|
289
|
+
})();
|
|
290
|
+
const lixo = (() => {
|
|
291
|
+
const x = C(r);
|
|
292
|
+
x.tudoNovo = 1;
|
|
293
|
+
return x;
|
|
294
|
+
})();
|
|
295
|
+
assert(dano(u, reord) === null, 'AC-3 controle: resultado REORDENADO passa — reserialização não é dano');
|
|
296
|
+
assert(dano(u, lixo) !== null, 'AC-3 controle: chave de topo nova REPROVA — acréscimo fora de hooks/permissions é dano');
|
|
297
|
+
|
|
298
|
+
// AC-4 é guarda (preserva comportamento já correto): só `tw` precisa reprovar.
|
|
299
|
+
assert(batOverrideRaso(real) === null, 'AC-4 guarda: chave não-aditiva mantém o override raso (language do usuário vence; statusLine do template entra; model só do usuário fica)');
|
|
300
|
+
assert(batOverrideRaso(tw) !== null, 'AC-4 guarda: controle "template vence" REPROVA (o language do usuário sumiria)');
|
|
301
|
+
|
|
302
|
+
// R4: tipo divergente no settings.json do usuário não pode lançar.
|
|
303
|
+
let lancou = false;
|
|
304
|
+
let divergente = null;
|
|
305
|
+
try {
|
|
306
|
+
const ud = usuario();
|
|
307
|
+
ud.hooks = 'nao-sou-objeto';
|
|
308
|
+
divergente = real(C(T), ud);
|
|
309
|
+
} catch (e) {
|
|
310
|
+
lancou = true;
|
|
311
|
+
}
|
|
312
|
+
assert(!lancou, 'R4: `hooks` com tipo divergente (string) não lança — o update não quebra');
|
|
313
|
+
assert(divergente && divergente.hooks === 'nao-sou-objeto', 'R4: em tipo divergente o valor do USUÁRIO vence, sem alteração');
|
|
314
|
+
|
|
315
|
+
await e2eEmDisco();
|
|
316
|
+
|
|
317
|
+
for (const d of tmpDirs) fs.rmSync(d, { recursive: true, force: true });
|
|
318
|
+
|
|
319
|
+
if (failures > 0) {
|
|
320
|
+
console.error(`\nSuíte FALHOU: ${failures} violação(ões).`);
|
|
321
|
+
process.exit(1);
|
|
322
|
+
}
|
|
323
|
+
console.log('\nSuíte verde.');
|
|
324
|
+
process.exit(0);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
main().catch((e) => {
|
|
328
|
+
console.error('[FALHA] suíte não completou: ' + (e && e.stack));
|
|
329
|
+
process.exit(1);
|
|
330
|
+
});
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
/**
|
|
3
|
+
* adopt.js — adota uma instalação POWER CLAUDE montada à mão (sem manifest),
|
|
4
|
+
* gerando `.power-claude/manifest.json` a partir do estado atual do disco para
|
|
5
|
+
* que `power-claude update` passe a alcançá-la.
|
|
6
|
+
*
|
|
7
|
+
* Contrato (compõe com install/update — não altera nenhum dos dois):
|
|
8
|
+
* - Fonte de verdade = os MESMOS arquivos que o update itera:
|
|
9
|
+
* template/ inteiro + scripts/validate-budgets.js do pacote.
|
|
10
|
+
* - Cada arquivo do template é classificado contra o disco:
|
|
11
|
+
* idêntico → rastreado {path, sha256} (igual ao install).
|
|
12
|
+
* modificado→ rastreado {path, sha256: <baseline do template>, origin:
|
|
13
|
+
* 'modified'}. O baseline divergente faz o update PRESERVAR o
|
|
14
|
+
* arquivo (mesmo mecanismo de hash-mismatch de hoje) e o doctor
|
|
15
|
+
* reportá-lo como modificado — sem tocar no contrato do update.
|
|
16
|
+
* ausente → perguntado item a item (interativo: instalar agora?) ou, em
|
|
17
|
+
* --yes, registrado como não-instalado SEM escrita.
|
|
18
|
+
* - NUNCA sobrescreve nada: adopt só LÊ o projeto e escreve APENAS o manifest
|
|
19
|
+
* (+ instalação opcional dos ausentes confirmados item a item no interativo).
|
|
20
|
+
* - Arquivos do usuário fora do template são ignorados (nunca tocados).
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
const fs = require('node:fs');
|
|
24
|
+
const path = require('node:path');
|
|
25
|
+
|
|
26
|
+
const { readManifest, writeManifest, sha256OfFile } = require('./manifest');
|
|
27
|
+
const { walkFiles, createPrompter, ensureParentDir } = require('./util');
|
|
28
|
+
const { motorEntries, motorRels } = require('./motor-scripts');
|
|
29
|
+
|
|
30
|
+
const MOTOR_RELS = new Set(motorRels());
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Classifica cada arquivo-fonte (template + budget script) contra o disco.
|
|
34
|
+
* Puro/sem efeitos colaterais — reutilizável em teste.
|
|
35
|
+
* @returns {{identical: string[], modified: string[], absent: string[],
|
|
36
|
+
* entries: Array<{path: string, sha256: string, origin?: string}>}}
|
|
37
|
+
*/
|
|
38
|
+
function classify({ templateDir, pkgRoot, destDir }) {
|
|
39
|
+
const sources = walkFiles(templateDir).map((rel) => ({
|
|
40
|
+
rel,
|
|
41
|
+
src: path.join(templateDir, ...rel.split('/')),
|
|
42
|
+
}));
|
|
43
|
+
for (const { path: rel } of motorEntries()) {
|
|
44
|
+
const src = path.join(pkgRoot, ...rel.split('/'));
|
|
45
|
+
if (fs.existsSync(src)) sources.push({ rel, src });
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const identical = [];
|
|
49
|
+
const modified = [];
|
|
50
|
+
const absent = [];
|
|
51
|
+
const entries = [];
|
|
52
|
+
|
|
53
|
+
for (const { rel, src } of sources) {
|
|
54
|
+
const destPath = path.join(destDir, ...rel.split('/'));
|
|
55
|
+
const srcHash = sha256OfFile(src);
|
|
56
|
+
if (!fs.existsSync(destPath)) {
|
|
57
|
+
absent.push(rel);
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
const destHash = sha256OfFile(destPath);
|
|
61
|
+
if (destHash === srcHash) {
|
|
62
|
+
identical.push(rel);
|
|
63
|
+
entries.push({ path: rel, sha256: destHash });
|
|
64
|
+
} else {
|
|
65
|
+
modified.push(rel);
|
|
66
|
+
// Baseline = hash do template: faz o update tratar como "modificado pelo
|
|
67
|
+
// usuário" (destHash !== entry.sha256) e preservar, sem alterar o update.
|
|
68
|
+
entries.push({ path: rel, sha256: srcHash, origin: 'modified' });
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return { identical, modified, absent, entries };
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
async function adopt({ yes = false } = {}) {
|
|
76
|
+
const destDir = process.cwd();
|
|
77
|
+
const pkgRoot = path.resolve(__dirname, '..');
|
|
78
|
+
const templateDir = path.join(pkgRoot, 'template');
|
|
79
|
+
const pkg = require(path.join(pkgRoot, 'package.json'));
|
|
80
|
+
|
|
81
|
+
if (path.resolve(destDir) === path.resolve(pkgRoot)) {
|
|
82
|
+
throw new Error('Não rode adopt dentro do próprio repositório do framework. Rode no diretório do seu projeto.');
|
|
83
|
+
}
|
|
84
|
+
if (!fs.existsSync(templateDir)) {
|
|
85
|
+
throw new Error(`Template não encontrado em ${templateDir}. Pacote corrompido?`);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const existing = readManifest(destDir);
|
|
89
|
+
if (existing) {
|
|
90
|
+
console.log(`Esta instalação já é rastreada (POWER CLAUDE ${existing.version}, manifest presente).`);
|
|
91
|
+
console.log('Nada a adotar. Use "power-claude update" para atualizar.');
|
|
92
|
+
return 0;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
console.log(`POWER CLAUDE adopt — ${destDir}\n`);
|
|
96
|
+
|
|
97
|
+
const { identical, modified, absent, entries } = classify({ templateDir, pkgRoot, destDir });
|
|
98
|
+
|
|
99
|
+
if (identical.length + modified.length === 0) {
|
|
100
|
+
console.log('Nenhum arquivo do POWER CLAUDE encontrado no disco — não há instalação para adotar.');
|
|
101
|
+
console.log('Rode "power-claude install" para instalar do zero.');
|
|
102
|
+
return 0;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Ausentes: interativo pergunta item a item; --yes registra como não-instalados.
|
|
106
|
+
const installedAbsent = [];
|
|
107
|
+
const declinedAbsent = [];
|
|
108
|
+
if (absent.length > 0) {
|
|
109
|
+
if (yes) {
|
|
110
|
+
declinedAbsent.push(...absent);
|
|
111
|
+
} else {
|
|
112
|
+
const prompter = createPrompter();
|
|
113
|
+
try {
|
|
114
|
+
console.log(`\n${absent.length} arquivo(s) do template ausente(s) no disco:`);
|
|
115
|
+
for (const rel of absent) {
|
|
116
|
+
const install = await prompter.confirm(` Instalar agora "${rel}"?`, false);
|
|
117
|
+
if (install) {
|
|
118
|
+
const src = MOTOR_RELS.has(rel)
|
|
119
|
+
? path.join(pkgRoot, ...rel.split('/'))
|
|
120
|
+
: path.join(templateDir, ...rel.split('/'));
|
|
121
|
+
const destPath = path.join(destDir, ...rel.split('/'));
|
|
122
|
+
ensureParentDir(destPath);
|
|
123
|
+
fs.copyFileSync(src, destPath);
|
|
124
|
+
entries.push({ path: rel, sha256: sha256OfFile(destPath) });
|
|
125
|
+
installedAbsent.push(rel);
|
|
126
|
+
} else {
|
|
127
|
+
declinedAbsent.push(rel);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
} finally {
|
|
131
|
+
prompter.close();
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
writeManifest(destDir, { version: pkg.version, files: entries });
|
|
137
|
+
|
|
138
|
+
printReport({ identical, modified, installedAbsent, declinedAbsent, total: entries.length });
|
|
139
|
+
|
|
140
|
+
console.log('\nInstalação adotada. Manifesto em .power-claude/manifest.json');
|
|
141
|
+
console.log('Próximo passo: rode "power-claude update" para propagar as correções do template.');
|
|
142
|
+
return 0;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function printReport({ identical, modified, installedAbsent, declinedAbsent, total }) {
|
|
146
|
+
console.log('\n--- Relatório de adoção ---');
|
|
147
|
+
console.log(`Idênticos ao template (rastreados): ${identical.length}`);
|
|
148
|
+
console.log(`Modificados por você (rastreados, preservados pelo update): ${modified.length}`);
|
|
149
|
+
for (const f of modified) console.log(` ~ ${f}`);
|
|
150
|
+
if (installedAbsent.length > 0) {
|
|
151
|
+
console.log(`Ausentes instalados agora (sua confirmação): ${installedAbsent.length}`);
|
|
152
|
+
for (const f of installedAbsent) console.log(` + ${f}`);
|
|
153
|
+
}
|
|
154
|
+
if (declinedAbsent.length > 0) {
|
|
155
|
+
console.log(`Ausentes NÃO instalados (registrados como não-instalados): ${declinedAbsent.length}`);
|
|
156
|
+
for (const f of declinedAbsent) console.log(` - ${f}`);
|
|
157
|
+
}
|
|
158
|
+
console.log(`Total de arquivos gerenciados no manifest: ${total}`);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
module.exports = { adopt, classify };
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
/**
|
|
3
|
+
* copy.js — cópia do template/ para o projeto destino.
|
|
4
|
+
*
|
|
5
|
+
* Regras:
|
|
6
|
+
* - NUNCA sobrescreve arquivo existente modificado sem decisão:
|
|
7
|
+
* - modo --yes: mantém o arquivo do usuário e registra no relatório;
|
|
8
|
+
* - modo interativo: pergunta (readline).
|
|
9
|
+
* - Exceção: `.claude/settings.json` recebe MERGE de chaves — chaves do
|
|
10
|
+
* usuário são preservadas; chaves do template que faltam são adicionadas.
|
|
11
|
+
* `hooks` e `permissions` são ADITIVAS (ver `shallowMergeSettings`):
|
|
12
|
+
* NÃO MAIS "entram só se a chave de topo estiver ausente" — essa leitura
|
|
13
|
+
* está superada e não deve ser repetida; ela descrevia o defeito da
|
|
14
|
+
* `SF13.13` (instalação com `hooks` descartava o bloco INTEIRO do
|
|
15
|
+
* template, e hook novo do framework nunca chegava) como se fosse a
|
|
16
|
+
* garantia. Hoje o acréscimo desce ao nível da ENTRADA.
|
|
17
|
+
* Consequência aceita e declarada (`SF13.13` R2): entrada do template que
|
|
18
|
+
* o usuário removeu de propósito VOLTA no próximo install/update — é o
|
|
19
|
+
* preço de o framework entregar o que distribui, sem deriva.
|
|
20
|
+
* - Filtro de pods: arquivos sob `pods/<pod>/` só são copiados se o pod
|
|
21
|
+
* estiver na seleção (pods/README.md e demais arquivos sempre entram).
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
const fs = require('node:fs');
|
|
25
|
+
const path = require('node:path');
|
|
26
|
+
const { sha256OfFile, sha256OfBuffer } = require('./manifest');
|
|
27
|
+
const { walkFiles, ensureParentDir } = require('./util');
|
|
28
|
+
|
|
29
|
+
const SETTINGS_REL = '.claude/settings.json';
|
|
30
|
+
|
|
31
|
+
/** Lista os nomes dos pods disponíveis no template. */
|
|
32
|
+
function listPods(templateDir) {
|
|
33
|
+
const podsDir = path.join(templateDir, 'pods');
|
|
34
|
+
if (!fs.existsSync(podsDir)) return [];
|
|
35
|
+
return fs
|
|
36
|
+
.readdirSync(podsDir, { withFileTypes: true })
|
|
37
|
+
.filter((e) => e.isDirectory())
|
|
38
|
+
.map((e) => e.name)
|
|
39
|
+
.sort();
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** Retorna o nome do pod dono de um path relativo, ou null. */
|
|
43
|
+
function podOf(relPath) {
|
|
44
|
+
const match = relPath.match(/^pods\/([^/]+)\//);
|
|
45
|
+
return match ? match[1] : null;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** Chaves que a plataforma trata como aditivas — as únicas com merge profundo. */
|
|
49
|
+
const ADDITIVE_KEYS = ['hooks', 'permissions'];
|
|
50
|
+
/** Identidade de elemento de array: bloco de hook por `matcher`, entrada por `command`. */
|
|
51
|
+
const IDENTITY_KEYS = ['matcher', 'command'];
|
|
52
|
+
|
|
53
|
+
const isPlainObject = (v) => v !== null && typeof v === 'object' && !Array.isArray(v);
|
|
54
|
+
|
|
55
|
+
function sameIdentity(a, b) {
|
|
56
|
+
if (isPlainObject(a) && isPlainObject(b)) {
|
|
57
|
+
for (const k of IDENTITY_KEYS) {
|
|
58
|
+
if (typeof a[k] === 'string' && typeof b[k] === 'string') return a[k] === b[k];
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return JSON.stringify(a) === JSON.stringify(b);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Acrescenta ao valor do USUÁRIO o que só existe no template. Nunca remove,
|
|
66
|
+
* nunca altera, idempotente (2ª passada devolve estrutura idêntica).
|
|
67
|
+
*/
|
|
68
|
+
function additiveMerge(t, u) {
|
|
69
|
+
if (Array.isArray(t) && Array.isArray(u)) {
|
|
70
|
+
const out = u.slice();
|
|
71
|
+
for (const el of t) {
|
|
72
|
+
const i = out.findIndex((x) => sameIdentity(el, x));
|
|
73
|
+
if (i === -1) {
|
|
74
|
+
out.push(el);
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
if (isPlainObject(el) && isPlainObject(out[i])) out[i] = additiveMerge(el, out[i]);
|
|
78
|
+
}
|
|
79
|
+
return out;
|
|
80
|
+
}
|
|
81
|
+
if (isPlainObject(t) && isPlainObject(u)) {
|
|
82
|
+
const out = { ...u };
|
|
83
|
+
for (const k of Object.keys(t)) {
|
|
84
|
+
if (!(k in out)) {
|
|
85
|
+
out[k] = t[k];
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
out[k] = additiveMerge(t[k], out[k]);
|
|
89
|
+
}
|
|
90
|
+
return out;
|
|
91
|
+
}
|
|
92
|
+
return u; // primitivo ou tipos divergentes: o do usuário vence, e nada é alterado
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Merge do settings.json.
|
|
97
|
+
*
|
|
98
|
+
* - Chaves NÃO-aditivas: override raso — a do usuário vence; a que só o
|
|
99
|
+
* template tem entra.
|
|
100
|
+
* - `hooks` e `permissions` (ADDITIVE_KEYS): merge ADITIVO em profundidade —
|
|
101
|
+
* acrescenta a entrada que falta sem remover nem alterar o que o usuário
|
|
102
|
+
* tem (inclusive `timeout` ajustado por ele). NÃO é mais o merge raso que
|
|
103
|
+
* descartava o bloco inteiro do template quando o usuário tinha a chave.
|
|
104
|
+
*/
|
|
105
|
+
function shallowMergeSettings(templateObj, userObj) {
|
|
106
|
+
const merged = { ...templateObj, ...userObj };
|
|
107
|
+
if (!isPlainObject(templateObj) || !isPlainObject(userObj)) return merged;
|
|
108
|
+
for (const key of ADDITIVE_KEYS) {
|
|
109
|
+
if (!(key in templateObj) || !(key in userObj)) continue;
|
|
110
|
+
merged[key] = additiveMerge(templateObj[key], userObj[key]);
|
|
111
|
+
}
|
|
112
|
+
return merged;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Copia o template para destDir.
|
|
117
|
+
*
|
|
118
|
+
* @param {object} opts
|
|
119
|
+
* @param {string} opts.templateDir raiz do template no pacote
|
|
120
|
+
* @param {string} opts.destDir raiz do projeto destino (cwd)
|
|
121
|
+
* @param {boolean} opts.yes modo não-interativo
|
|
122
|
+
* @param {string[]|null} opts.selectedPods pods a instalar (null = todos)
|
|
123
|
+
* @param {object|null} opts.prompter prompter interativo (null em --yes)
|
|
124
|
+
* @returns {Promise<{entries: Array, report: object}>}
|
|
125
|
+
*/
|
|
126
|
+
async function copyTemplate({ templateDir, destDir, yes, selectedPods, prompter }) {
|
|
127
|
+
const allFiles = walkFiles(templateDir);
|
|
128
|
+
const entries = [];
|
|
129
|
+
const report = { installed: [], merged: [], kept: [], overwritten: [], skippedPods: new Set() };
|
|
130
|
+
|
|
131
|
+
for (const rel of allFiles) {
|
|
132
|
+
const pod = podOf(rel);
|
|
133
|
+
if (pod && selectedPods && !selectedPods.includes(pod)) {
|
|
134
|
+
report.skippedPods.add(pod);
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const srcPath = path.join(templateDir, ...rel.split('/'));
|
|
139
|
+
const destPath = path.join(destDir, ...rel.split('/'));
|
|
140
|
+
|
|
141
|
+
if (!fs.existsSync(destPath)) {
|
|
142
|
+
ensureParentDir(destPath);
|
|
143
|
+
fs.copyFileSync(srcPath, destPath);
|
|
144
|
+
entries.push({ path: rel, sha256: sha256OfFile(destPath) });
|
|
145
|
+
report.installed.push(rel);
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Destino já existe.
|
|
150
|
+
if (rel === SETTINGS_REL) {
|
|
151
|
+
const result = mergeSettingsFile(srcPath, destPath);
|
|
152
|
+
if (result.merged) {
|
|
153
|
+
entries.push({ path: rel, sha256: result.sha256, origin: 'merged' });
|
|
154
|
+
report.merged.push(rel);
|
|
155
|
+
} else {
|
|
156
|
+
// settings.json do usuário não parseia — preserva intacto.
|
|
157
|
+
report.kept.push(`${rel} (JSON inválido no destino; mantido sem merge)`);
|
|
158
|
+
}
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const srcHash = sha256OfFile(srcPath);
|
|
163
|
+
const destHash = sha256OfFile(destPath);
|
|
164
|
+
if (srcHash === destHash) {
|
|
165
|
+
// Conteúdo idêntico — apenas registra como gerenciado.
|
|
166
|
+
entries.push({ path: rel, sha256: destHash });
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Conflito: destino existe e difere do template.
|
|
171
|
+
let overwrite = false;
|
|
172
|
+
if (!yes && prompter) {
|
|
173
|
+
overwrite = await prompter.confirm(` Arquivo já existe e difere do template: ${rel}. Sobrescrever?`, false);
|
|
174
|
+
}
|
|
175
|
+
if (overwrite) {
|
|
176
|
+
fs.copyFileSync(srcPath, destPath);
|
|
177
|
+
entries.push({ path: rel, sha256: srcHash });
|
|
178
|
+
report.overwritten.push(rel);
|
|
179
|
+
} else {
|
|
180
|
+
report.kept.push(rel);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
report.skippedPods = [...report.skippedPods];
|
|
185
|
+
return { entries, report };
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/** Faz o merge (aditivo em hooks/permissions, override raso nas demais chaves) do settings.json e grava no destino. */
|
|
189
|
+
function mergeSettingsFile(srcPath, destPath) {
|
|
190
|
+
const templateObj = JSON.parse(fs.readFileSync(srcPath, 'utf8'));
|
|
191
|
+
let userObj;
|
|
192
|
+
try {
|
|
193
|
+
userObj = JSON.parse(fs.readFileSync(destPath, 'utf8'));
|
|
194
|
+
} catch {
|
|
195
|
+
return { merged: false };
|
|
196
|
+
}
|
|
197
|
+
const mergedObj = shallowMergeSettings(templateObj, userObj);
|
|
198
|
+
const content = JSON.stringify(mergedObj, null, 2) + '\n';
|
|
199
|
+
fs.writeFileSync(destPath, content, 'utf8');
|
|
200
|
+
return { merged: true, sha256: sha256OfBuffer(Buffer.from(content, 'utf8')) };
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
module.exports = { copyTemplate, listPods, podOf, shallowMergeSettings, SETTINGS_REL };
|