@orkastery/cli 0.5.6 → 0.6.1
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.
- package/adapters/README.md +18 -0
- package/adapters/claude-code/.claude-plugin/plugin.json +1 -0
- package/adapters/claude-code/README.md +37 -5
- package/adapters/claude-code/agents/ork-check.md +1 -0
- package/adapters/claude-code/agents/ork-go.md +1 -0
- package/adapters/claude-code/agents/ork-goal.md +1 -0
- package/adapters/claude-code/agents/ork-master.md +1 -0
- package/adapters/claude-code/agents/ork-plan.md +1 -0
- package/adapters/claude-code/agents/ork-ship.md +1 -0
- package/adapters/claude-code/hooks/hooks.json +2 -1
- package/adapters/claude-code/hooks/ork-guard.js +15 -1
- package/adapters/claude-code/hooks/ork-resposta.js +91 -0
- package/adapters/claude-code/hooks/ork-sensor.js +18 -2
- package/adapters/claude-code/hooks/quota-signal.js +14 -0
- package/adapters/claude-code/mods/ork-mensageiro/.claude-plugin/plugin.json +14 -0
- package/adapters/claude-code/mods/ork-mensageiro/hooks/caixa.ts +215 -0
- package/adapters/claude-code/mods/ork-mensageiro/hooks/hooks.json +3 -0
- package/adapters/claude-code/mods/ork-mensageiro/hooks/painel.tsx +165 -0
- package/adapters/claude-code/mods/ork-mensageiro/hooks/recepcao.ts +256 -0
- package/adapters/claude-code/mods/ork-mensageiro/hooks/register.tsx +481 -0
- package/adapters/claude-code/mods/ork-mensageiro/orkastery-mod.json +5 -0
- package/adapters/claude-code/mods/ork-mensageiro/types/index.d.ts +85 -0
- package/adapters/claude-code/mods/ork-pista/hooks/ci.ts +9 -5
- package/adapters/claude-code/mods/ork-ritual/.claude-plugin/plugin.json +14 -0
- package/adapters/claude-code/mods/ork-ritual/hooks/hooks.json +3 -0
- package/adapters/claude-code/mods/ork-ritual/hooks/nota.ts +88 -0
- package/adapters/claude-code/mods/ork-ritual/hooks/register.tsx +494 -0
- package/adapters/claude-code/mods/ork-ritual/hooks/replay.tsx +271 -0
- package/adapters/claude-code/mods/ork-ritual/hooks/ritual.ts +218 -0
- package/adapters/claude-code/mods/ork-ritual/orkastery-mod.json +5 -0
- package/adapters/claude-code/mods/ork-ritual/types/index.d.ts +106 -0
- package/adapters/claude-code/mods/ork-sentinela/hooks/vigia.ts +1 -1
- package/adapters/codex/skills/ork/SKILL.md +2 -0
- package/adapters/hermes/README.md +64 -1
- package/adapters/hermes/bin/ork-conhecimento-colecoes.sh +14 -0
- package/adapters/hermes/bin/ork-conhecimento-listar.sh +15 -0
- package/adapters/hermes/bin/ork-conhecimento-mostrar.sh +16 -0
- package/adapters/hermes/bin/ork-conhecimento-verificar.sh +15 -0
- package/adapters/hermes/bin/ork-vigia-batida.py +127 -0
- package/adapters/hermes/hermes.plugin.json +4 -0
- package/adapters/hermes/hitl-ingress/__init__.py +101 -8
- package/adapters/hermes/skills/orkastery-devmaster/SKILL.md +2 -2
- package/adapters/openclaw/README.md +81 -9
- package/adapters/openclaw/bin/ork-vigia-batida.js +86 -0
- package/adapters/openclaw/dist/hitl-ingress.js +106 -9
- package/adapters/openclaw/dist/index.js +86 -0
- package/adapters/openclaw/openclaw.plugin.json +34 -4
- package/adapters/openclaw/package.json +2 -2
- package/adapters/openclaw/src/hitl-ingress.ts +103 -9
- package/adapters/openclaw/src/index.ts +94 -0
- package/assets/adapters-en/claude-code/README.md +403 -0
- package/assets/adapters-en/claude-code/agents/ork-check.md +33 -0
- package/assets/adapters-en/claude-code/agents/ork-go.md +33 -0
- package/assets/adapters-en/claude-code/agents/ork-goal.md +34 -0
- package/assets/adapters-en/claude-code/agents/ork-master.md +35 -0
- package/assets/adapters-en/claude-code/agents/ork-plan.md +33 -0
- package/assets/adapters-en/claude-code/agents/ork-ship.md +33 -0
- package/assets/adapters-en/claude-code/commands/check.md +46 -0
- package/assets/adapters-en/claude-code/commands/go.md +46 -0
- package/assets/adapters-en/claude-code/commands/goal.md +55 -0
- package/assets/adapters-en/claude-code/commands/master.md +54 -0
- package/assets/adapters-en/claude-code/commands/onboarding.md +22 -0
- package/assets/adapters-en/claude-code/commands/ork.md +126 -0
- package/assets/adapters-en/claude-code/commands/plan.md +47 -0
- package/assets/adapters-en/claude-code/commands/ship.md +45 -0
- package/assets/adapters-en/codex/skills/ork/SKILL.md +85 -0
- package/assets/adapters-en/hermes/README.md +273 -0
- package/assets/adapters-en/hermes/skills/orkastery-devmaster/SKILL.md +169 -0
- package/assets/references-en/README.md +25 -0
- package/assets/references-en/code-review-axes.md +82 -0
- package/assets/references-en/definition-of-done.md +74 -0
- package/assets/references-en/performance-checklist.md +53 -0
- package/assets/references-en/security-checklist.md +101 -0
- package/assets/references-en/testing-patterns.md +51 -0
- package/assets/security-scan/defaults/.golangci.yml +2 -0
- package/assets/security-scan/defaults/eslint.config.mjs +11 -0
- package/assets/security-scan/defaults/gitleaks.toml +3 -0
- package/assets/security-scan/defaults/ruff.toml +1 -0
- package/assets/security-scan/tools-agil.json +87 -0
- package/assets/security-scan/tools.json +625 -0
- package/assets/skills-en/core/onboarding/SKILL.md +58 -0
- package/assets/skills-en/core/orkastery-bootstrap/SKILL.md +119 -0
- package/assets/skills-en/core/thread-state/SKILL.md +88 -0
- package/assets/skills-en/governance/decision-triage/SKILL.md +65 -0
- package/assets/skills-en/governance/narrative-guardian/SKILL.md +66 -0
- package/assets/skills-en/governance/roadmap-keeper/SKILL.md +60 -0
- package/assets/skills-en/governance/scope-check-capability-map/SKILL.md +71 -0
- package/assets/skills-en/observability/thread-tracing/SKILL.md +63 -0
- package/assets/skills-en/phases/check-quality/SKILL.md +103 -0
- package/assets/skills-en/phases/go-implementation/SKILL.md +78 -0
- package/assets/skills-en/phases/goal-definition/SKILL.md +69 -0
- package/assets/skills-en/phases/master-metrics/SKILL.md +72 -0
- package/assets/skills-en/phases/plan-specification/SKILL.md +67 -0
- package/assets/skills-en/phases/security-remediation/SKILL.md +70 -0
- package/assets/skills-en/phases/ship-release/SKILL.md +71 -0
- package/assets/skills-en/reviewers/code-reviewer/SKILL.md +61 -0
- package/assets/skills-en/reviewers/security-auditor/SKILL.md +93 -0
- package/assets/skills-en/reviewers/test-engineer/SKILL.md +66 -0
- package/assets/skills-en/reviewers/web-performance-auditor/SKILL.md +63 -0
- package/dist/adapters/claude-bg.js +123 -9
- package/dist/adapters/codex-controller-worker.js +12 -5
- package/dist/adapters/codex-events.js +16 -4
- package/dist/adapters/codex.js +3 -1
- package/dist/adapters/grok-events.js +227 -0
- package/dist/adapters/grok-mcp-servidor.js +612 -0
- package/dist/adapters/grok-mcp.js +178 -0
- package/dist/adapters/grok.js +686 -0
- package/dist/agendador-cpu.js +92 -0
- package/dist/atualizar-branch.js +245 -0
- package/dist/auditoria.js +13 -11
- package/dist/auditrun.js +51 -46
- package/dist/autorizacao-duravel.js +327 -0
- package/dist/board.js +94 -40
- package/dist/branch-de-estado.js +23 -10
- package/dist/busca-semantica.js +7 -5
- package/dist/catalogo.js +2 -1
- package/dist/changelog-reconciliacao.js +290 -0
- package/dist/check-agil.js +528 -0
- package/dist/check-modo.js +41 -0
- package/dist/check-paralelo.js +238 -0
- package/dist/ci.js +248 -45
- package/dist/ciclos.js +8 -6
- package/dist/claim-lint.js +12 -8
- package/dist/claims-conferir.js +47 -39
- package/dist/claims.js +10 -9
- package/dist/company-brain-client.js +10 -3
- package/dist/company-brain-migration.js +11 -5
- package/dist/conducao-batida.js +759 -0
- package/dist/conducao-sentinela.js +25 -8
- package/dist/conducao.js +89 -29
- package/dist/conhecimento-consulta.js +270 -0
- package/dist/conhecimento.js +111 -87
- package/dist/conta-esgotada.js +127 -0
- package/dist/dado-privado.js +816 -0
- package/dist/decisao-autonoma.js +5 -4
- package/dist/demo.js +14 -12
- package/dist/dependencias-da-worktree.js +25 -0
- package/dist/divida.js +38 -36
- package/dist/docs.js +89 -89
- package/dist/doctor-forja.js +343 -0
- package/dist/doctor-pulse-cron.js +113 -1
- package/dist/doctor.js +164 -6
- package/dist/dsn-da-memoria.js +417 -0
- package/dist/entrega-por-mod.js +503 -0
- package/dist/entrega-pr.js +154 -54
- package/dist/esgotamentos.js +13 -11
- package/dist/estado-thread.js +21 -18
- package/dist/evalrunner.js +35 -32
- package/dist/fabrica-batida.js +34 -21
- package/dist/fabrica-estado.js +33 -21
- package/dist/fix.js +21 -19
- package/dist/forja/adaptador.js +6 -0
- package/dist/forja/ambiente-de-ci.js +27 -0
- package/dist/forja/api.js +256 -0
- package/dist/forja/cache.js +89 -0
- package/dist/forja/cli.js +60 -0
- package/dist/forja/contrato.js +9 -0
- package/dist/forja/endereco.js +43 -0
- package/dist/forja/estado.js +22 -0
- package/dist/forja/executor.js +73 -0
- package/dist/forja/github/ci.js +95 -0
- package/dist/forja/github/dono-da-rede.js +18 -0
- package/dist/forja/github/metadados.js +15 -0
- package/dist/forja/github/pedidos.js +200 -0
- package/dist/forja/gitlab/ci.js +141 -0
- package/dist/forja/gitlab/consultas.js +59 -0
- package/dist/forja/gitlab/dono-da-rede.js +43 -0
- package/dist/forja/gitlab/metadados.js +15 -0
- package/dist/forja/gitlab/pedidos-abertos.js +158 -0
- package/dist/forja/gitlab/pedidos.js +141 -0
- package/dist/forja/produtor.js +32 -0
- package/dist/forja/registro.js +31 -0
- package/dist/forja/resolver.js +121 -0
- package/dist/forja/texto.js +13 -0
- package/dist/forja/versao.js +71 -0
- package/dist/forja.js +123 -80
- package/dist/handoff.js +23 -13
- package/dist/hitl-contract.js +46 -43
- package/dist/hitl-curto.js +37 -25
- package/dist/hitl-gates.js +13 -11
- package/dist/hitl-local-receipt.js +51 -0
- package/dist/hitl-local.js +11 -2
- package/dist/hitl-lock.js +5 -11
- package/dist/hitl-public-receipt.js +11 -0
- package/dist/hitl-resumo.js +44 -1
- package/dist/hitl-sessions.js +62 -9
- package/dist/hitl-texto-livre.js +7 -4
- package/dist/hitl.js +34 -31
- package/dist/horario.js +20 -1
- package/dist/hosts.js +104 -134
- package/dist/index.js +864 -554
- package/dist/indice-vetorial.js +14 -12
- package/dist/init.js +11 -2
- package/dist/integracoes-locais.js +4 -0
- package/dist/intelligence-graph-cli.js +123 -69
- package/dist/intelligence-graph-query.js +37 -22
- package/dist/leases.js +19 -18
- package/dist/ledger-stats.js +17 -16
- package/dist/ledger.js +31 -1
- package/dist/licoes.js +12 -11
- package/dist/locale-accounts.js +228 -0
- package/dist/locale-adapter.js +291 -0
- package/dist/locale-ajuda.js +86 -10
- package/dist/locale-audit.js +199 -0
- package/dist/locale-claims.js +213 -0
- package/dist/locale-conducao.js +138 -0
- package/dist/locale-conhecimento.js +181 -0
- package/dist/locale-decisao.js +205 -0
- package/dist/locale-docs.js +230 -0
- package/dist/locale-estado.js +101 -0
- package/dist/locale-forja.js +288 -0
- package/dist/locale-gerados.js +5 -0
- package/dist/locale-grafo.js +129 -0
- package/dist/locale-master.js +83 -0
- package/dist/locale-network.js +524 -0
- package/dist/locale-operacao-restante.js +931 -0
- package/dist/locale-projetos.js +100 -0
- package/dist/locale-prompts.js +194 -0
- package/dist/locale-pulse.js +140 -2
- package/dist/locale-reservas.js +87 -0
- package/dist/locale-retry.js +183 -6
- package/dist/locale-security-scan.js +69 -0
- package/dist/locale-setup.js +192 -0
- package/dist/locale-ship.js +177 -66
- package/dist/locale-thread.js +149 -5
- package/dist/locale-verify.js +80 -4
- package/dist/locale-worktree.js +246 -0
- package/dist/locale.js +553 -9
- package/dist/maestro-runtime.js +22 -3
- package/dist/manifest.js +293 -12
- package/dist/maquina-ociosa.js +211 -0
- package/dist/maquina.js +10 -5
- package/dist/master-nota-local.js +200 -0
- package/dist/master-nota.js +10 -2
- package/dist/master.js +118 -41
- package/dist/mcp-conhecimento.js +52 -0
- package/dist/mcp-experiencia.js +26 -5
- package/dist/mcp-install.js +90 -15
- package/dist/mcp-noctum.js +101 -0
- package/dist/mcp-server.js +116 -22
- package/dist/mcp-ship-transporte.js +977 -0
- package/dist/mcp-ship.js +68 -24
- package/dist/mcp-transportes.js +17 -0
- package/dist/memoria.js +63 -58
- package/dist/merge-da-entrega.js +85 -0
- package/dist/modos-migracao.js +8 -6
- package/dist/modos.js +17 -18
- package/dist/network-roadmap.js +94 -54
- package/dist/noctum-aceite.js +929 -0
- package/dist/noctum-arco.js +540 -33
- package/dist/noctum-capacidade.js +3 -3
- package/dist/noctum-changelog.js +399 -0
- package/dist/noctum-cli.js +539 -0
- package/dist/noctum-coordenacao.js +1225 -0
- package/dist/noctum-entrega.js +1217 -0
- package/dist/noctum-envelope.js +253 -17
- package/dist/noctum-execucao.js +100 -24
- package/dist/noctum-fechamento.js +416 -0
- package/dist/noctum-fila.js +49 -10
- package/dist/noctum-guarda.js +333 -0
- package/dist/noctum-integracao.js +507 -0
- package/dist/noctum-maquinas.js +387 -0
- package/dist/noctum-medicao.js +450 -0
- package/dist/noctum-modo.js +390 -0
- package/dist/noctum-pergunta-canal.js +96 -0
- package/dist/noctum-pergunta.js +115 -0
- package/dist/noctum-provas.js +838 -0
- package/dist/noctum-reconciliacao.js +594 -0
- package/dist/noctum-rede.js +102 -0
- package/dist/noctum-registro.js +215 -0
- package/dist/noctum-relatorio.js +861 -0
- package/dist/noctum-resposta.js +207 -0
- package/dist/noctum-retomada-rede.js +71 -0
- package/dist/noctum-sessoes-em-voo.js +93 -0
- package/dist/noctum-supervisao.js +649 -0
- package/dist/noctum-terminal.js +459 -0
- package/dist/noctum-transporte.js +655 -0
- package/dist/noctum-vigia.js +679 -0
- package/dist/ocupacao.js +3 -1
- package/dist/onboarding.js +16 -5
- package/dist/orkmind.js +39 -23
- package/dist/orquestracao.js +31 -35
- package/dist/parado-no-condutor.js +160 -139
- package/dist/phase.js +96 -42
- package/dist/policies.js +33 -0
- package/dist/postura-local.js +11 -6
- package/dist/preflight.js +2 -1
- package/dist/procedencia.js +9 -14
- package/dist/projeto-alvo.js +59 -20
- package/dist/prompts.js +136 -42
- package/dist/prova-minima.js +5 -6
- package/dist/pulse-cadencia.js +5 -4
- package/dist/pulse-consentimento.js +53 -1
- package/dist/pulse-delivery.js +143 -27
- package/dist/pulse-resposta.js +368 -4
- package/dist/pulse.js +136 -3
- package/dist/ratelimit.js +13 -4
- package/dist/recall.js +14 -13
- package/dist/rede-adesao.js +13 -8
- package/dist/rede-forja.js +108 -24
- package/dist/rede-status.js +63 -45
- package/dist/rede.js +86 -46
- package/dist/retomada-checkpoint.js +101 -0
- package/dist/retry.js +326 -78
- package/dist/roadmap-reservas.js +32 -32
- package/dist/runtime-ambiente.js +5 -0
- package/dist/runtime-context.js +279 -85
- package/dist/runtime-escada.js +429 -0
- package/dist/runtime-profiles.js +130 -63
- package/dist/runtimes.js +42 -5
- package/dist/saida-segura.js +3 -2
- package/dist/security-scan/agil.js +366 -0
- package/dist/security-scan/cli.js +76 -0
- package/dist/security-scan/compare.js +149 -0
- package/dist/security-scan/config.js +93 -0
- package/dist/security-scan/detect.js +97 -0
- package/dist/security-scan/executor.js +196 -0
- package/dist/security-scan/guard.js +180 -0
- package/dist/security-scan/imagem-de-lint.js +96 -0
- package/dist/security-scan/normalize/actionlint.js +12 -0
- package/dist/security-scan/normalize/eslint.js +12 -0
- package/dist/security-scan/normalize/gitleaks.js +13 -0
- package/dist/security-scan/normalize/golangci-lint.js +13 -0
- package/dist/security-scan/normalize/govulncheck.js +36 -0
- package/dist/security-scan/normalize/index.js +25 -0
- package/dist/security-scan/normalize/npm-audit.js +30 -0
- package/dist/security-scan/normalize/osv-scanner.js +32 -0
- package/dist/security-scan/normalize/oxlint.js +51 -0
- package/dist/security-scan/normalize/pip-audit.js +16 -0
- package/dist/security-scan/normalize/ruff.js +12 -0
- package/dist/security-scan/normalize/semgrep.js +20 -0
- package/dist/security-scan/normalize/trivy.js +33 -0
- package/dist/security-scan/normalize/util.js +204 -0
- package/dist/security-scan/normalize/zizmor.js +19 -0
- package/dist/security-scan/report.js +156 -0
- package/dist/security-scan/run.js +448 -0
- package/dist/security-scan/tools.js +62 -0
- package/dist/security-scan/trabalhador.js +74 -0
- package/dist/security-scan/types.js +15 -0
- package/dist/session-events.js +19 -2
- package/dist/session-watcher-claude.js +74 -19
- package/dist/session-watcher-grok.js +425 -0
- package/dist/session-watcher.js +174 -13
- package/dist/sessoes-contas.js +42 -5
- package/dist/sessoes-inventario.js +1 -1
- package/dist/sessoes.js +25 -23
- package/dist/setup.js +67 -66
- package/dist/ship-publicar.js +263 -92
- package/dist/ship.js +160 -17
- package/dist/superficie.js +14 -11
- package/dist/thread-close.js +9 -9
- package/dist/thread.js +121 -18
- package/dist/tokens.js +15 -11
- package/dist/trabalho-thread.js +297 -0
- package/dist/transporte-do-canal.js +189 -0
- package/dist/types.js +12 -1
- package/dist/util.js +5 -2
- package/dist/varredura-acoes.js +500 -0
- package/dist/verify.js +491 -32
- package/dist/worktree-local.js +12 -10
- package/dist/worktree.js +81 -85
- package/eval/casos/check-quality.json +77 -0
- package/eval/casos/security-auditor.json +148 -0
- package/eval/casos/security-remediation.json +161 -0
- package/eval/fixtures/security-scan/README.md +8 -0
- package/eval/fixtures/security-scan/go/.github/workflows/ci.yml +12 -0
- package/eval/fixtures/security-scan/go/Dockerfile +4 -0
- package/eval/fixtures/security-scan/go/README.md +4 -0
- package/eval/fixtures/security-scan/go/config.go +4 -0
- package/eval/fixtures/security-scan/go/go.mod +5 -0
- package/eval/fixtures/security-scan/go/go.sum +2 -0
- package/eval/fixtures/security-scan/go/main.go +31 -0
- package/eval/fixtures/security-scan/js/.github/workflows/ci.yml +12 -0
- package/eval/fixtures/security-scan/js/Dockerfile +3 -0
- package/eval/fixtures/security-scan/js/README.md +4 -0
- package/eval/fixtures/security-scan/js/package-lock.json +844 -0
- package/eval/fixtures/security-scan/js/package.json +10 -0
- package/eval/fixtures/security-scan/js/src/config.js +4 -0
- package/eval/fixtures/security-scan/js/src/server.js +17 -0
- package/eval/fixtures/security-scan/js/src/util.ts +4 -0
- package/eval/fixtures/security-scan/js/tsconfig.json +4 -0
- package/eval/fixtures/security-scan/python/.github/workflows/ci.yml +12 -0
- package/eval/fixtures/security-scan/python/Dockerfile +3 -0
- package/eval/fixtures/security-scan/python/README.md +4 -0
- package/eval/fixtures/security-scan/python/app/__init__.py +0 -0
- package/eval/fixtures/security-scan/python/app/config.py +2 -0
- package/eval/fixtures/security-scan/python/app/main.py +19 -0
- package/eval/fixtures/security-scan/python/requirements.txt +1 -0
- package/package.json +10 -5
- package/references/security-checklist.md +3 -3
- package/skills/README.md +1 -0
- package/skills/core/orkastery-bootstrap/SKILL.md +2 -2
- package/skills/phases/check-quality/SKILL.md +16 -0
- package/skills/phases/go-implementation/SKILL.md +6 -0
- package/skills/phases/security-remediation/SKILL.md +70 -0
- package/skills/reviewers/security-auditor/SKILL.md +25 -0
|
@@ -0,0 +1,816 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.PADROES_GENERICOS = exports.OMITIDO = exports.MENOR_TERMO = exports.VARIAVEL_DA_LISTA_LOCAL = exports.ARQUIVO_DA_LISTA_LOCAL = exports.ARQUIVO_DAS_EXCECOES = exports.CONTRATO_DA_VARREDURA = exports.CONTRATO_DAS_EXCECOES = void 0;
|
|
37
|
+
exports.lerExcecoes = lerExcecoes;
|
|
38
|
+
exports.lerListaLocal = lerListaLocal;
|
|
39
|
+
exports.contextoDaVarredura = contextoDaVarredura;
|
|
40
|
+
exports.varreduraComErro = varreduraComErro;
|
|
41
|
+
exports.varrerArvore = varrerArvore;
|
|
42
|
+
exports.varrerDiff = varrerDiff;
|
|
43
|
+
exports.varrerTexto = varrerTexto;
|
|
44
|
+
exports.juntarVarreduras = juntarVarreduras;
|
|
45
|
+
exports.mergeBase = mergeBase;
|
|
46
|
+
exports.baseDaThread = baseDaThread;
|
|
47
|
+
exports.linhaDoAchado = linhaDoAchado;
|
|
48
|
+
exports.textoDoEscopo = textoDoEscopo;
|
|
49
|
+
exports.textoDaListaLocal = textoDaListaLocal;
|
|
50
|
+
exports.relatorioDoDadoPrivado = relatorioDoDadoPrivado;
|
|
51
|
+
exports.ondeDoAchado = ondeDoAchado;
|
|
52
|
+
exports.fatosDaVarredura = fatosDaVarredura;
|
|
53
|
+
exports.resumoNoLedger = resumoNoLedger;
|
|
54
|
+
exports.barreiraDaThread = barreiraDaThread;
|
|
55
|
+
/**
|
|
56
|
+
* RM-062 (F1 e F2): a barreira de dado privado do repositório público.
|
|
57
|
+
*
|
|
58
|
+
* Nada privado do dono entra no repositório: nem em linha, nem em nome de arquivo, nem no bundle de CI. A varredura
|
|
59
|
+
* procura três padrões genéricos (caminho de home, e-mail fora da lista permitida e hostname no formato de máquina) e
|
|
60
|
+
* os termos de uma lista local que mora fora do git. O relatório diz arquivo, linha, coluna e o nome do padrão, nunca
|
|
61
|
+
* o valor casado: relatório que repete o termo é um segundo vazamento (a mesma regra de `procurarSegredos`).
|
|
62
|
+
*
|
|
63
|
+
* - O que é exemplo inventado entra nas exceções versionadas (`orkastery.privado.json`), revisadas em PR (D4).
|
|
64
|
+
* - A lista local (`termos-privados.txt`) fica na pasta do usuário do `ork` e na `.orkastery/` do projeto, ou no
|
|
65
|
+
* caminho de `ORK_TERMOS_PRIVADOS` (D5). Ausente, como no CI, os padrões genéricos continuam valendo, e a saída diz.
|
|
66
|
+
* - Exceção nunca vale para a lista local.
|
|
67
|
+
* - Nome de arquivo também é conteúdo: o caminho de um arquivo com achado no nome sai com o trecho trocado por
|
|
68
|
+
* `[omitido]` em todo lugar onde aparece (D8).
|
|
69
|
+
*/
|
|
70
|
+
const fs = __importStar(require("node:fs"));
|
|
71
|
+
const path = __importStar(require("node:path"));
|
|
72
|
+
const util_1 = require("./util");
|
|
73
|
+
const maquina_1 = require("./maquina");
|
|
74
|
+
const estado_thread_1 = require("./estado-thread");
|
|
75
|
+
const policies_1 = require("./policies");
|
|
76
|
+
exports.CONTRATO_DAS_EXCECOES = 'ork.privado/v1';
|
|
77
|
+
exports.CONTRATO_DA_VARREDURA = 'ork.privado-varredura/v1';
|
|
78
|
+
/** O arquivo versionado de exceções e e-mails permitidos, na raiz do repositório. */
|
|
79
|
+
exports.ARQUIVO_DAS_EXCECOES = 'orkastery.privado.json';
|
|
80
|
+
/** O nome do arquivo da lista local, na pasta do usuário do `ork` e na `.orkastery/` do projeto. */
|
|
81
|
+
exports.ARQUIVO_DA_LISTA_LOCAL = 'termos-privados.txt';
|
|
82
|
+
/** Com ela, a lista local é só o arquivo que ela aponta. */
|
|
83
|
+
exports.VARIAVEL_DA_LISTA_LOCAL = 'ORK_TERMOS_PRIVADOS';
|
|
84
|
+
/** Termo mais curto que isso casaria em quase toda linha: é ignorado e contado. */
|
|
85
|
+
exports.MENOR_TERMO = 3;
|
|
86
|
+
exports.OMITIDO = '[omitido]';
|
|
87
|
+
exports.PADROES_GENERICOS = ['home', 'email', 'hostname'];
|
|
88
|
+
// ---------------------------------------------------------------------------
|
|
89
|
+
// Os padrões genéricos (D9)
|
|
90
|
+
// ---------------------------------------------------------------------------
|
|
91
|
+
/**
|
|
92
|
+
* Caminho de home: `/home/<usuário>` e `/Users/<usuário>`, também escapado (`\/home\/x` numa regex, `\\/home\\/x` no
|
|
93
|
+
* JSON dela), `/var/home/<usuário>` e `C:\Users\<usuário>`. O usuário aceita letra com acento; o marcador `<usuário>`
|
|
94
|
+
* não casa. Colado a letra, dígito ou ponto (`https://site.com/home/pagina`, `app/home/page.tsx`, a casa falsa em
|
|
95
|
+
* `/tmp/ensaio/home/x`), é trecho de URL ou de outro caminho.
|
|
96
|
+
*/
|
|
97
|
+
const HOME = /(?:(?<=\/var)|(?<![\p{L}\p{N}._-]))(?:\\*\/)+(home|Users)(?:\\*\/)+([\p{L}\p{N}._-]+)|\b([A-Za-z]):\\{1,2}Users\\{1,2}([\p{L}\p{N}._-]+)/gu;
|
|
98
|
+
/** Usuários de sistema e marcadores que não dizem de quem é a máquina. */
|
|
99
|
+
const USUARIOS_GENERICOS = new Set(['user', 'usuario', 'usuário', 'username', 'runner', 'ubuntu', 'node', 'git']);
|
|
100
|
+
/**
|
|
101
|
+
* E-mail: o endereço inteiro, com domínio de letras. O lookbehind tira o que vem logo depois de `\` (o `\n@app.get`
|
|
102
|
+
* de um JSON), de `@` e do meio de outro endereço. `/` e `:` são prefixos válidos; credenciais de URL são
|
|
103
|
+
* reconhecidas pela autoridade completa, não pelo caractere anterior.
|
|
104
|
+
*/
|
|
105
|
+
const EMAIL = /(?<![A-Za-z0-9._%+\-\\@])([A-Za-z0-9._%+-]+)@((?:[A-Za-z0-9](?:[A-Za-z0-9-]*[A-Za-z0-9])?\.)+[A-Za-z]{2,63})(?![A-Za-z0-9-])/g;
|
|
106
|
+
/** RFC 2606 e RFC 6761: domínios reservados para exemplo e teste. */
|
|
107
|
+
const DOMINIOS_DE_EXEMPLO = ['example.com', 'example.net', 'example.org'];
|
|
108
|
+
const TLDS_DE_EXEMPLO = new Set(['example', 'invalid', 'test', 'localhost']);
|
|
109
|
+
/** Hostname no formato de máquina: o hostname que não segue formato nenhum é da lista local. */
|
|
110
|
+
const HOSTNAMES = [
|
|
111
|
+
// Nome de máquina pessoal: `<nome>-pc`, `<nome>-laptop`, `<nome>-macbook-pro`... (os exemplos daqui usam marcador,
|
|
112
|
+
// para o comentário não casar com a própria regra).
|
|
113
|
+
/\b[a-z0-9]+(?:-[a-z0-9]+)*-(?:pc|laptop|desktop|notebook|workstation|thinkpad|macbook(?:-pro|-air)?|imac|mac-mini|mac-studio)\b/gi,
|
|
114
|
+
// Padrões de fábrica do Windows.
|
|
115
|
+
/\b(?:DESKTOP|LAPTOP)-[A-Z0-9]{6,10}\b/g,
|
|
116
|
+
/\bWIN-[A-Z0-9]{8,15}\b/g,
|
|
117
|
+
// AWS (`ip-` com os quatro números do IP), VPS (`vps-` com 8 hexadecimais, `srv` ou `vmi` com 5 dígitos ou mais) e
|
|
118
|
+
// DigitalOcean (`ubuntu-s-` com o tamanho da máquina).
|
|
119
|
+
/\bip-(?:\d{1,3}-){3}\d{1,3}\b/g,
|
|
120
|
+
/\bvps-[0-9a-f]{8}\b/gi,
|
|
121
|
+
/\b(?:srv|vmi)\d{5,}\b/gi,
|
|
122
|
+
/\bubuntu-s-\d+vcpu-\d+gb-[a-z0-9-]+\b/gi,
|
|
123
|
+
// O formato `srv` + letras + dígitos (o nome de servidor com as iniciais de alguém).
|
|
124
|
+
/\bsrv[a-z]{2,10}\d{1,4}\b/gi,
|
|
125
|
+
];
|
|
126
|
+
function escaparRegex(texto) {
|
|
127
|
+
return texto.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
128
|
+
}
|
|
129
|
+
/** Glob de caminho: `**` cruza pastas, `*` e `?` não. */
|
|
130
|
+
function globCasa(glob, caminho) {
|
|
131
|
+
let re = '';
|
|
132
|
+
for (let i = 0; i < glob.length; i++) {
|
|
133
|
+
const c = glob[i];
|
|
134
|
+
if (c === '*' && glob[i + 1] === '*') {
|
|
135
|
+
if (glob[i + 2] === '/') {
|
|
136
|
+
re += '(?:.*/)?';
|
|
137
|
+
i += 2;
|
|
138
|
+
}
|
|
139
|
+
else {
|
|
140
|
+
re += '.*';
|
|
141
|
+
i += 1;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
else if (c === '*')
|
|
145
|
+
re += '[^/]*';
|
|
146
|
+
else if (c === '?')
|
|
147
|
+
re += '[^/]';
|
|
148
|
+
else
|
|
149
|
+
re += escaparRegex(c);
|
|
150
|
+
}
|
|
151
|
+
return new RegExp(`^${re}$`, 'u').test(caminho);
|
|
152
|
+
}
|
|
153
|
+
function caminhoEquivaleACuringaGlobal(caminho) {
|
|
154
|
+
return ['arquivo.txt', 'pasta/arquivo.txt', 'pasta/interna/arquivo.txt'].every((amostra) => globCasa(caminho, amostra));
|
|
155
|
+
}
|
|
156
|
+
function emailDeExemplo(dominio) {
|
|
157
|
+
const d = dominio.toLowerCase();
|
|
158
|
+
if (DOMINIOS_DE_EXEMPLO.some((e) => d === e || d.endsWith(`.${e}`)))
|
|
159
|
+
return true;
|
|
160
|
+
return TLDS_DE_EXEMPLO.has(d.slice(d.lastIndexOf('.') + 1));
|
|
161
|
+
}
|
|
162
|
+
/** O endereço começa na autoridade de uma URL, antes do caminho, query, fragmento ou delimitador do documento. */
|
|
163
|
+
function naAutoridadeDeUrl(texto, inicio) {
|
|
164
|
+
const antes = texto.slice(Math.max(0, inicio - 512), inicio);
|
|
165
|
+
const separador = antes.lastIndexOf('://');
|
|
166
|
+
if (separador < 0)
|
|
167
|
+
return false;
|
|
168
|
+
const prefixo = antes.slice(0, separador);
|
|
169
|
+
if (!/(?:^|[^a-z0-9+.-])[a-z][a-z0-9+.-]*$/i.test(prefixo))
|
|
170
|
+
return false;
|
|
171
|
+
const autoridade = antes.slice(separador + 3);
|
|
172
|
+
return !/[\s/?#"'<>\[\]{},;`()|]/u.test(autoridade);
|
|
173
|
+
}
|
|
174
|
+
/** Os casamentos dos padrões genéricos e da lista local num texto, antes das exceções. */
|
|
175
|
+
function casamentos(texto, termos) {
|
|
176
|
+
const achados = [];
|
|
177
|
+
for (const m of texto.matchAll(HOME)) {
|
|
178
|
+
const raizDoCaminho = m[1] ?? null;
|
|
179
|
+
const usuario = (m[2] ?? m[4] ?? '').replace(/[._-]+$/, '');
|
|
180
|
+
// Pasta oculta direto na raiz das casas (`/home/.cache`) não é usuário.
|
|
181
|
+
if (!usuario || usuario.startsWith('.') || USUARIOS_GENERICOS.has(usuario.toLowerCase()))
|
|
182
|
+
continue;
|
|
183
|
+
const valor = raizDoCaminho ? `/${raizDoCaminho}/${usuario}` : `${m[3]}:\\Users\\${usuario}`;
|
|
184
|
+
achados.push({ inicio: m.index, fim: m.index + m[0].length, padrao: 'home', valor });
|
|
185
|
+
}
|
|
186
|
+
for (const m of texto.matchAll(EMAIL)) {
|
|
187
|
+
const inicio = m.index;
|
|
188
|
+
// Na autoridade de uma URL (`ssh://x=u@host/`, `git+ssh://u:1#t@host/`), o `@` separa a credencial do host.
|
|
189
|
+
if (naAutoridadeDeUrl(texto, inicio))
|
|
190
|
+
continue;
|
|
191
|
+
// `git@host` e `usuario@host:caminho` são remotos SSH, não o endereço de alguém.
|
|
192
|
+
const depois = texto.slice(inicio + m[0].length, inicio + m[0].length + 2);
|
|
193
|
+
if (m[1].toLowerCase() === 'git' || /^:[^\s]/.test(depois))
|
|
194
|
+
continue;
|
|
195
|
+
if (emailDeExemplo(m[2]))
|
|
196
|
+
continue;
|
|
197
|
+
achados.push({ inicio, fim: inicio + m[0].length, padrao: 'email', valor: m[0] });
|
|
198
|
+
}
|
|
199
|
+
for (const re of HOSTNAMES) {
|
|
200
|
+
for (const m of texto.matchAll(re)) {
|
|
201
|
+
achados.push({ inicio: m.index, fim: m.index + m[0].length, padrao: 'hostname', valor: m[0] });
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
termos.forEach((termo, i) => {
|
|
205
|
+
for (const m of texto.matchAll(termo)) {
|
|
206
|
+
achados.push({ inicio: m.index, fim: m.index + m[0].length, padrao: 'lista-local', valor: m[0], termo: i + 1 });
|
|
207
|
+
}
|
|
208
|
+
});
|
|
209
|
+
// O mesmo trecho achado por dois padrões de hostname conta uma vez.
|
|
210
|
+
const vistos = new Set();
|
|
211
|
+
return achados.filter((a) => {
|
|
212
|
+
const chave = `${a.inicio}:${a.padrao}:${a.termo ?? ''}`;
|
|
213
|
+
if (vistos.has(chave))
|
|
214
|
+
return false;
|
|
215
|
+
vistos.add(chave);
|
|
216
|
+
return true;
|
|
217
|
+
}).sort((a, b) => a.inicio - b.inicio || a.fim - b.fim);
|
|
218
|
+
}
|
|
219
|
+
// ---------------------------------------------------------------------------
|
|
220
|
+
// As exceções versionadas (D4)
|
|
221
|
+
// ---------------------------------------------------------------------------
|
|
222
|
+
/** O uso das exceções numa varredura, para dizer as que não cobriram nada. */
|
|
223
|
+
class UsoDasExcecoes {
|
|
224
|
+
excecoes = new Set();
|
|
225
|
+
emails = new Set();
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Lê o `orkastery.privado.json` da raiz. Ausente, nenhuma exceção (só as regras do código). Fora do contrato, o erro
|
|
229
|
+
* com o motivo: a varredura não roda com exceção que ninguém sabe o que cobre.
|
|
230
|
+
*/
|
|
231
|
+
function lerExcecoes(raiz) {
|
|
232
|
+
const arquivo = path.join(raiz, exports.ARQUIVO_DAS_EXCECOES);
|
|
233
|
+
if (!fs.existsSync(arquivo))
|
|
234
|
+
return { ok: true, valor: { presente: false, emailsPermitidos: [], excecoes: [] } };
|
|
235
|
+
const fora = (motivo) => ({ ok: false, erro: `${exports.ARQUIVO_DAS_EXCECOES} fora do contrato ${exports.CONTRATO_DAS_EXCECOES}: ${motivo}` });
|
|
236
|
+
let bruto;
|
|
237
|
+
try {
|
|
238
|
+
bruto = JSON.parse(fs.readFileSync(arquivo, 'utf8'));
|
|
239
|
+
}
|
|
240
|
+
catch (e) {
|
|
241
|
+
return fora(`não é JSON (${e.message})`);
|
|
242
|
+
}
|
|
243
|
+
if (!bruto || typeof bruto !== 'object' || Array.isArray(bruto))
|
|
244
|
+
return fora('a raiz não é um objeto');
|
|
245
|
+
const objeto = bruto;
|
|
246
|
+
const estranhas = Object.keys(objeto).filter((k) => !['contrato', 'descricao', 'emailsPermitidos', 'excecoes'].includes(k));
|
|
247
|
+
if (estranhas.length)
|
|
248
|
+
return fora(`chave desconhecida: ${estranhas.join(', ')}`);
|
|
249
|
+
if (objeto.contrato !== exports.CONTRATO_DAS_EXCECOES)
|
|
250
|
+
return fora(`"contrato" tem de ser "${exports.CONTRATO_DAS_EXCECOES}"`);
|
|
251
|
+
if (objeto.descricao !== undefined && typeof objeto.descricao !== 'string')
|
|
252
|
+
return fora('"descricao" tem de ser texto');
|
|
253
|
+
const emails = objeto.emailsPermitidos ?? [];
|
|
254
|
+
if (!Array.isArray(emails))
|
|
255
|
+
return fora('"emailsPermitidos" tem de ser uma lista');
|
|
256
|
+
const emailsPermitidos = [];
|
|
257
|
+
for (const [i, e] of emails.entries()) {
|
|
258
|
+
if (!e || typeof e !== 'object' || Array.isArray(e))
|
|
259
|
+
return fora(`emailsPermitidos[${i}] não é um objeto`);
|
|
260
|
+
const x = e;
|
|
261
|
+
const outras = Object.keys(x).filter((k) => !['valor', 'motivo'].includes(k));
|
|
262
|
+
if (outras.length)
|
|
263
|
+
return fora(`emailsPermitidos[${i}] tem chave desconhecida: ${outras.join(', ')}`);
|
|
264
|
+
if (typeof x.valor !== 'string' || !/^[^\s@*]+@[^\s@*]+$/.test(x.valor)) {
|
|
265
|
+
return fora(`emailsPermitidos[${i}].valor tem de ser endereço exato, sem curinga`);
|
|
266
|
+
}
|
|
267
|
+
if (typeof x.motivo !== 'string' || !x.motivo.trim())
|
|
268
|
+
return fora(`emailsPermitidos[${i}] sem motivo`);
|
|
269
|
+
emailsPermitidos.push({ valor: x.valor, motivo: x.motivo.trim() });
|
|
270
|
+
}
|
|
271
|
+
const lista = objeto.excecoes ?? [];
|
|
272
|
+
if (!Array.isArray(lista))
|
|
273
|
+
return fora('"excecoes" tem de ser uma lista');
|
|
274
|
+
const excecoes = [];
|
|
275
|
+
for (const [i, e] of lista.entries()) {
|
|
276
|
+
if (!e || typeof e !== 'object' || Array.isArray(e))
|
|
277
|
+
return fora(`excecoes[${i}] não é um objeto`);
|
|
278
|
+
const x = e;
|
|
279
|
+
const outras = Object.keys(x).filter((k) => !['padrao', 'caminho', 'valor', 'motivo'].includes(k));
|
|
280
|
+
if (outras.length)
|
|
281
|
+
return fora(`excecoes[${i}] tem chave desconhecida: ${outras.join(', ')}`);
|
|
282
|
+
if (!exports.PADROES_GENERICOS.includes(x.padrao)) {
|
|
283
|
+
return fora(`excecoes[${i}].padrao tem de ser ${exports.PADROES_GENERICOS.join(', ')}`);
|
|
284
|
+
}
|
|
285
|
+
if (typeof x.motivo !== 'string' || !x.motivo.trim())
|
|
286
|
+
return fora(`excecoes[${i}] sem motivo`);
|
|
287
|
+
for (const campo of ['caminho', 'valor']) {
|
|
288
|
+
if (x[campo] !== undefined && (typeof x[campo] !== 'string' || !x[campo].trim())) {
|
|
289
|
+
return fora(`excecoes[${i}].${campo} tem de ser texto não vazio`);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
if (x.valor === undefined)
|
|
293
|
+
return fora(`excecoes[${i}] sem valor`);
|
|
294
|
+
if (x.valor.includes('*'))
|
|
295
|
+
return fora(`excecoes[${i}].valor tem de ser exato, sem curinga`);
|
|
296
|
+
if (x.caminho === '**')
|
|
297
|
+
return fora(`excecoes[${i}].caminho não pode ser curinga global`);
|
|
298
|
+
if (x.caminho !== undefined && caminhoEquivaleACuringaGlobal(x.caminho)) {
|
|
299
|
+
return fora(`excecoes[${i}].caminho equivale a curinga global`);
|
|
300
|
+
}
|
|
301
|
+
excecoes.push({ padrao: x.padrao, motivo: x.motivo.trim(), valor: x.valor,
|
|
302
|
+
...(x.caminho !== undefined ? { caminho: x.caminho } : {}) });
|
|
303
|
+
}
|
|
304
|
+
return { ok: true, valor: { presente: true, emailsPermitidos, excecoes } };
|
|
305
|
+
}
|
|
306
|
+
/** O casamento genérico coberto pela lista de e-mails permitidos ou por uma exceção; marca o uso. */
|
|
307
|
+
function coberto(c, arquivo, excecoes, uso) {
|
|
308
|
+
if (c.padrao === 'lista-local')
|
|
309
|
+
return false;
|
|
310
|
+
if (c.padrao === 'email') {
|
|
311
|
+
const i = excecoes.emailsPermitidos.findIndex((e) => e.valor.toLowerCase() === c.valor.toLowerCase());
|
|
312
|
+
if (i >= 0) {
|
|
313
|
+
uso.emails.add(i);
|
|
314
|
+
return true;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
const j = excecoes.excecoes.findIndex((e) => e.padrao === c.padrao
|
|
318
|
+
&& (e.caminho === undefined || globCasa(e.caminho, arquivo)) && e.valor.toLowerCase() === c.valor.toLowerCase());
|
|
319
|
+
if (j >= 0) {
|
|
320
|
+
uso.excecoes.add(j);
|
|
321
|
+
return true;
|
|
322
|
+
}
|
|
323
|
+
return false;
|
|
324
|
+
}
|
|
325
|
+
// ---------------------------------------------------------------------------
|
|
326
|
+
// A lista local (D5)
|
|
327
|
+
// ---------------------------------------------------------------------------
|
|
328
|
+
/** O arquivo `dentro` da raiz `raiz`, relativo a ela; `null` fora dela. */
|
|
329
|
+
function relativoDentro(raiz, dentro) {
|
|
330
|
+
let real;
|
|
331
|
+
let base;
|
|
332
|
+
try {
|
|
333
|
+
real = fs.realpathSync(dentro);
|
|
334
|
+
base = fs.realpathSync(raiz);
|
|
335
|
+
}
|
|
336
|
+
catch {
|
|
337
|
+
return null;
|
|
338
|
+
}
|
|
339
|
+
const rel = path.relative(base, real);
|
|
340
|
+
return rel && !rel.startsWith('..') && !path.isAbsolute(rel) ? rel.split(path.sep).join('/') : null;
|
|
341
|
+
}
|
|
342
|
+
/**
|
|
343
|
+
* Lê a lista local: com `ORK_TERMOS_PRIVADOS`, só o arquivo dela; sem ela, `termos-privados.txt` da pasta do usuário
|
|
344
|
+
* do `ork` (`~/.orkastery/`) e da `.orkastery/` da raiz do estado do projeto. Um termo por linha, `#` comenta, sem
|
|
345
|
+
* diferença de caixa. `raiz` é o repositório varrido: a lista que o git versiona nele volta em `versionados`.
|
|
346
|
+
*/
|
|
347
|
+
function lerListaLocal(raizDoEstado, raiz = null, env = process.env) {
|
|
348
|
+
const explicita = (env[exports.VARIAVEL_DA_LISTA_LOCAL] ?? '').trim();
|
|
349
|
+
const rotuloDaPastaDoUsuario = (env.ORK_USUARIO_DIR ?? '').trim()
|
|
350
|
+
? `ORK_USUARIO_DIR/${exports.ARQUIVO_DA_LISTA_LOCAL}`
|
|
351
|
+
: (env.ORKASTERY_HOME ?? '').trim()
|
|
352
|
+
? `ORKASTERY_HOME/${exports.ARQUIVO_DA_LISTA_LOCAL}`
|
|
353
|
+
: `~/.orkastery/${exports.ARQUIVO_DA_LISTA_LOCAL}`;
|
|
354
|
+
const candidatos = explicita
|
|
355
|
+
? [{ arquivo: path.resolve(explicita), rotulo: `arquivo indicado por ${exports.VARIAVEL_DA_LISTA_LOCAL}` }]
|
|
356
|
+
: [{ arquivo: path.join((0, maquina_1.pastaDoUsuario)(env), exports.ARQUIVO_DA_LISTA_LOCAL), rotulo: rotuloDaPastaDoUsuario },
|
|
357
|
+
...(raizDoEstado ? [{ arquivo: path.join(raizDoEstado, '.orkastery', exports.ARQUIVO_DA_LISTA_LOCAL),
|
|
358
|
+
rotulo: `.orkastery/${exports.ARQUIVO_DA_LISTA_LOCAL}` }] : [])];
|
|
359
|
+
const unicos = [...new Map(candidatos.map((c) => [c.arquivo, c])).values()];
|
|
360
|
+
const termos = [];
|
|
361
|
+
const lidos = [];
|
|
362
|
+
const versionados = [];
|
|
363
|
+
let recusada = null;
|
|
364
|
+
let ignorados = 0;
|
|
365
|
+
for (const candidato of unicos) {
|
|
366
|
+
const { arquivo, rotulo } = candidato;
|
|
367
|
+
let texto;
|
|
368
|
+
let fd = null;
|
|
369
|
+
try {
|
|
370
|
+
const antes = fs.lstatSync(arquivo);
|
|
371
|
+
if (antes.isSymbolicLink()) {
|
|
372
|
+
recusada = `${rotulo} foi recusado: link simbólico não é aceito`;
|
|
373
|
+
break;
|
|
374
|
+
}
|
|
375
|
+
if (!antes.isFile()) {
|
|
376
|
+
recusada = `${rotulo} foi recusado: não é arquivo comum`;
|
|
377
|
+
break;
|
|
378
|
+
}
|
|
379
|
+
fd = fs.openSync(arquivo, fs.constants.O_RDONLY | fs.constants.O_NOFOLLOW | fs.constants.O_NONBLOCK);
|
|
380
|
+
const st = fs.fstatSync(fd);
|
|
381
|
+
const uid = typeof process.getuid === 'function' ? process.getuid() : null;
|
|
382
|
+
if (!st.isFile()) {
|
|
383
|
+
recusada = `${rotulo} foi recusado: não é arquivo comum`;
|
|
384
|
+
break;
|
|
385
|
+
}
|
|
386
|
+
if (uid === null || st.uid !== uid) {
|
|
387
|
+
recusada = `${rotulo} foi recusado: o dono não é o usuário atual`;
|
|
388
|
+
break;
|
|
389
|
+
}
|
|
390
|
+
if ((st.mode & 0o777) !== 0o600) {
|
|
391
|
+
recusada = `${rotulo} foi recusado: a permissão tem de ser 0600`;
|
|
392
|
+
break;
|
|
393
|
+
}
|
|
394
|
+
texto = fs.readFileSync(fd, 'utf8');
|
|
395
|
+
}
|
|
396
|
+
catch (e) {
|
|
397
|
+
const code = e.code;
|
|
398
|
+
if (code === 'ENOENT')
|
|
399
|
+
continue;
|
|
400
|
+
recusada = `${rotulo} foi recusado: não foi possível abrir com segurança`;
|
|
401
|
+
break;
|
|
402
|
+
}
|
|
403
|
+
finally {
|
|
404
|
+
if (fd !== null)
|
|
405
|
+
fs.closeSync(fd);
|
|
406
|
+
}
|
|
407
|
+
lidos.push(rotulo);
|
|
408
|
+
const rel = raiz ? relativoDentro(raiz, arquivo) : null;
|
|
409
|
+
if (rel && (0, util_1.exec)('git', ['ls-files', '--error-unmatch', '--', rel], raiz).ok)
|
|
410
|
+
versionados.push(rel);
|
|
411
|
+
for (const bruta of texto.split(/\r?\n/)) {
|
|
412
|
+
const linha = bruta.trim();
|
|
413
|
+
if (!linha || linha.startsWith('#'))
|
|
414
|
+
continue;
|
|
415
|
+
if ([...linha].length < exports.MENOR_TERMO) {
|
|
416
|
+
ignorados += 1;
|
|
417
|
+
continue;
|
|
418
|
+
}
|
|
419
|
+
const termo = linha.toLowerCase();
|
|
420
|
+
if (!termos.includes(termo))
|
|
421
|
+
termos.push(termo);
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
return { presente: lidos.length > 0, procurada: unicos.map((c) => c.rotulo), lidos, termos,
|
|
425
|
+
ignorados, versionados, recusada };
|
|
426
|
+
}
|
|
427
|
+
// ---------------------------------------------------------------------------
|
|
428
|
+
// A varredura
|
|
429
|
+
// ---------------------------------------------------------------------------
|
|
430
|
+
/** O contexto de uma varredura: as exceções da raiz varrida e a lista local. */
|
|
431
|
+
function contextoDaVarredura(raiz, raizDoEstado, env = process.env) {
|
|
432
|
+
const lista = lerListaLocal(raizDoEstado, raiz, env);
|
|
433
|
+
const excecoes = lerExcecoes(raiz);
|
|
434
|
+
if (!excecoes.ok)
|
|
435
|
+
return { ok: false, erro: excecoes.erro, lista };
|
|
436
|
+
if (lista.recusada)
|
|
437
|
+
return { ok: false, erro: lista.recusada, lista };
|
|
438
|
+
if (lista.versionados.length) {
|
|
439
|
+
return { ok: false, lista, erro: `a lista local de termos privados está versionada (${lista.versionados.length} arquivo(s)): ` +
|
|
440
|
+
'tire-a do git; ela nunca vai ao repositório' };
|
|
441
|
+
}
|
|
442
|
+
return { ok: true, valor: { raiz, excecoes: excecoes.valor, lista } };
|
|
443
|
+
}
|
|
444
|
+
/** Acumula os achados de uma varredura, com o caminho mascarado e o uso das exceções. */
|
|
445
|
+
class Coletor {
|
|
446
|
+
contexto;
|
|
447
|
+
achados = [];
|
|
448
|
+
uso = new UsoDasExcecoes();
|
|
449
|
+
arquivos = 0;
|
|
450
|
+
linhas = 0;
|
|
451
|
+
nomes = 0;
|
|
452
|
+
/** Os termos da lista local como regex, compiladas uma vez por varredura. */
|
|
453
|
+
termos;
|
|
454
|
+
constructor(contexto) {
|
|
455
|
+
this.contexto = contexto;
|
|
456
|
+
this.termos = contexto.lista.termos.map((t) => new RegExp(escaparRegex(t), 'giu'));
|
|
457
|
+
}
|
|
458
|
+
/** Os casamentos que valem num texto do arquivo (o nome, uma linha ou o arquivo inteiro), depois das exceções. */
|
|
459
|
+
validos(texto, arquivo, comGenericos) {
|
|
460
|
+
return casamentos(texto, this.termos).filter((c) => c.padrao === 'lista-local'
|
|
461
|
+
|| (comGenericos && !coberto(c, arquivo, this.contexto.excecoes, this.uso)));
|
|
462
|
+
}
|
|
463
|
+
/**
|
|
464
|
+
* O caminho para o relatório: cada trecho casado no nome trocado por `[omitido]` (trechos que se encostam viram um
|
|
465
|
+
* só). Varre o nome uma vez.
|
|
466
|
+
*/
|
|
467
|
+
caminho(arquivo) {
|
|
468
|
+
const achados = this.validos(arquivo, arquivo, true);
|
|
469
|
+
let mostrado = '';
|
|
470
|
+
let ate = 0;
|
|
471
|
+
achados.forEach((c, i) => {
|
|
472
|
+
if (i === 0 || c.inicio > ate)
|
|
473
|
+
mostrado += arquivo.slice(ate, c.inicio) + exports.OMITIDO;
|
|
474
|
+
ate = Math.max(ate, c.fim);
|
|
475
|
+
});
|
|
476
|
+
return { mostrado: mostrado + arquivo.slice(ate), achados };
|
|
477
|
+
}
|
|
478
|
+
/** O nome do arquivo como achado. */
|
|
479
|
+
nome(arquivo) {
|
|
480
|
+
this.nomes += 1;
|
|
481
|
+
const { mostrado, achados } = this.caminho(arquivo);
|
|
482
|
+
for (const c of achados) {
|
|
483
|
+
this.achados.push({ arquivo: mostrado, linha: null, coluna: null, padrao: c.padrao, ...(c.termo ? { termo: c.termo } : {}) });
|
|
484
|
+
}
|
|
485
|
+
return mostrado;
|
|
486
|
+
}
|
|
487
|
+
/**
|
|
488
|
+
* As linhas de um arquivo: `numeradas` são pares (número da linha, texto). O arquivo de exceções não passa pelos
|
|
489
|
+
* padrões genéricos (ele lista o que é permitido), só pela lista local.
|
|
490
|
+
*/
|
|
491
|
+
linhasDe(arquivo, mostrado, numeradas) {
|
|
492
|
+
const comGenericos = arquivo !== exports.ARQUIVO_DAS_EXCECOES;
|
|
493
|
+
for (const [numero, texto] of numeradas) {
|
|
494
|
+
this.linhas += 1;
|
|
495
|
+
for (const c of this.validos(texto, arquivo, comGenericos))
|
|
496
|
+
this.registrar(mostrado, numero, texto, 0, c);
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
/**
|
|
500
|
+
* O arquivo inteiro de uma vez (a árvore e os não rastreados): nenhum padrão atravessa a quebra de linha, e a
|
|
501
|
+
* linha de cada casamento sai do índice dos inícios de linha.
|
|
502
|
+
*/
|
|
503
|
+
arquivoInteiro(arquivo, mostrado, texto) {
|
|
504
|
+
const inicios = [0];
|
|
505
|
+
for (let i = texto.indexOf('\n'); i >= 0; i = texto.indexOf('\n', i + 1))
|
|
506
|
+
inicios.push(i + 1);
|
|
507
|
+
// A quebra no fim do arquivo não abre linha nova.
|
|
508
|
+
this.linhas += texto.endsWith('\n') ? inicios.length - 1 : inicios.length;
|
|
509
|
+
for (const c of this.validos(texto, arquivo, arquivo !== exports.ARQUIVO_DAS_EXCECOES)) {
|
|
510
|
+
let baixo = 0;
|
|
511
|
+
let alto = inicios.length - 1;
|
|
512
|
+
while (baixo < alto) {
|
|
513
|
+
const meio = (baixo + alto + 1) >> 1;
|
|
514
|
+
if (inicios[meio] <= c.inicio)
|
|
515
|
+
baixo = meio;
|
|
516
|
+
else
|
|
517
|
+
alto = meio - 1;
|
|
518
|
+
}
|
|
519
|
+
this.registrar(mostrado, baixo + 1, texto, inicios[baixo], c);
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
registrar(mostrado, linha, texto, inicioDaLinha, c) {
|
|
523
|
+
const coluna = [...texto.slice(inicioDaLinha, c.inicio)].length + 1;
|
|
524
|
+
this.achados.push({ arquivo: mostrado, linha, coluna, padrao: c.padrao, ...(c.termo ? { termo: c.termo } : {}) });
|
|
525
|
+
}
|
|
526
|
+
resultado(escopo, erro = null) {
|
|
527
|
+
const { excecoes, lista } = this.contexto;
|
|
528
|
+
const semUso = [
|
|
529
|
+
...excecoes.excecoes.flatMap((e, i) => this.uso.excecoes.has(i) ? []
|
|
530
|
+
: [`excecoes[${i}] ${e.padrao}`]),
|
|
531
|
+
...excecoes.emailsPermitidos.flatMap((_e, i) => this.uso.emails.has(i) ? [] : [`emailsPermitidos[${i}] email`]),
|
|
532
|
+
];
|
|
533
|
+
return {
|
|
534
|
+
schema: exports.CONTRATO_DA_VARREDURA, escopo, arquivos: this.arquivos, linhas: this.linhas, nomes: this.nomes,
|
|
535
|
+
listaLocal: resumoDaLista(lista),
|
|
536
|
+
excecoes: { arquivo: excecoes.presente ? exports.ARQUIVO_DAS_EXCECOES : null, total: excecoes.excecoes.length,
|
|
537
|
+
emailsPermitidos: excecoes.emailsPermitidos.length, semUso },
|
|
538
|
+
achados: this.achados, erro,
|
|
539
|
+
};
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
function resumoDaLista(lista) {
|
|
543
|
+
return { presente: lista.presente, procurada: lista.procurada, lidos: lista.lidos, termos: lista.termos.length,
|
|
544
|
+
ignorados: lista.ignorados };
|
|
545
|
+
}
|
|
546
|
+
/** A varredura que não rodou: o motivo, sem achado. */
|
|
547
|
+
function varreduraComErro(erro, lista, escopo = []) {
|
|
548
|
+
return { schema: exports.CONTRATO_DA_VARREDURA, escopo, arquivos: 0, linhas: 0, nomes: 0, listaLocal: resumoDaLista(lista),
|
|
549
|
+
excecoes: { arquivo: null, total: 0, emailsPermitidos: 0, semUso: [] }, achados: [], erro };
|
|
550
|
+
}
|
|
551
|
+
/**
|
|
552
|
+
* O conteúdo de um arquivo do repositório como texto, inclusive quando contém bytes nulos ou UTF-8 inválido. O link
|
|
553
|
+
* simbólico é o alvo dele: o caminho para onde aponta também é conteúdo, e o link nunca é seguido.
|
|
554
|
+
*/
|
|
555
|
+
function conteudo(raiz, arquivo) {
|
|
556
|
+
const absoluto = path.join(raiz, arquivo);
|
|
557
|
+
let st;
|
|
558
|
+
try {
|
|
559
|
+
st = fs.lstatSync(absoluto);
|
|
560
|
+
}
|
|
561
|
+
catch {
|
|
562
|
+
return null;
|
|
563
|
+
}
|
|
564
|
+
if (st.isSymbolicLink())
|
|
565
|
+
return fs.readlinkSync(absoluto);
|
|
566
|
+
if (!st.isFile())
|
|
567
|
+
return null;
|
|
568
|
+
return fs.readFileSync(absoluto).toString('utf8');
|
|
569
|
+
}
|
|
570
|
+
function listaZ(saida) {
|
|
571
|
+
return saida.split('\0').filter(Boolean);
|
|
572
|
+
}
|
|
573
|
+
/** F1: os nomes e o conteúdo dos arquivos versionados (`git ls-files`) da raiz. */
|
|
574
|
+
function varrerArvore(contexto) {
|
|
575
|
+
const coletor = new Coletor(contexto);
|
|
576
|
+
const escopo = [{ tipo: 'arvore' }];
|
|
577
|
+
const r = (0, util_1.exec)('git', ['-c', 'core.quotePath=false', 'ls-files', '-z'], contexto.raiz);
|
|
578
|
+
if (!r.ok)
|
|
579
|
+
return coletor.resultado(escopo, `git ls-files não respondeu em ${contexto.raiz}: ${r.stderr.trim() || r.code}`);
|
|
580
|
+
for (const arquivo of listaZ(r.stdout)) {
|
|
581
|
+
const mostrado = coletor.nome(arquivo);
|
|
582
|
+
const texto = conteudo(contexto.raiz, arquivo);
|
|
583
|
+
if (texto === null)
|
|
584
|
+
continue;
|
|
585
|
+
coletor.arquivos += 1;
|
|
586
|
+
coletor.arquivoInteiro(arquivo, mostrado, texto);
|
|
587
|
+
}
|
|
588
|
+
return coletor.resultado(escopo);
|
|
589
|
+
}
|
|
590
|
+
/** O caminho do cabeçalho `+++ b/<caminho>` do diff, também entre aspas (o git as põe no nome com tab ou aspas). */
|
|
591
|
+
function caminhoDoCabecalho(resto) {
|
|
592
|
+
if (resto === '/dev/null')
|
|
593
|
+
return null;
|
|
594
|
+
if (resto.startsWith('"') && resto.endsWith('"')) {
|
|
595
|
+
try {
|
|
596
|
+
resto = JSON.parse(resto.replace(/\\([0-7]{3})/g, (_, o) => `\\u${Number.parseInt(o, 8).toString(16).padStart(4, '0')}`));
|
|
597
|
+
}
|
|
598
|
+
catch {
|
|
599
|
+
return null;
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
return resto.replace(/^b\//, '');
|
|
603
|
+
}
|
|
604
|
+
/** As linhas acrescentadas de um `git diff --unified=0`, por arquivo, com o número da linha nova. */
|
|
605
|
+
function linhasAcrescentadas(saida) {
|
|
606
|
+
const porArquivo = new Map();
|
|
607
|
+
let arquivo = null;
|
|
608
|
+
let cabecalho = false;
|
|
609
|
+
let nova = 0;
|
|
610
|
+
for (const l of saida.split('\n')) {
|
|
611
|
+
if (l.startsWith('diff --git ')) {
|
|
612
|
+
cabecalho = true;
|
|
613
|
+
arquivo = null;
|
|
614
|
+
continue;
|
|
615
|
+
}
|
|
616
|
+
// O `+++` só é o nome do arquivo no cabeçalho; dentro do hunk, é uma linha acrescentada que começa por "++".
|
|
617
|
+
if (cabecalho && l.startsWith('+++ ')) {
|
|
618
|
+
arquivo = caminhoDoCabecalho(l.slice(4));
|
|
619
|
+
continue;
|
|
620
|
+
}
|
|
621
|
+
const h = /^@@ -\d+(?:,\d+)? \+(\d+)(?:,\d+)? @@/.exec(l);
|
|
622
|
+
if (h) {
|
|
623
|
+
nova = Number(h[1]);
|
|
624
|
+
cabecalho = false;
|
|
625
|
+
continue;
|
|
626
|
+
}
|
|
627
|
+
if (!cabecalho && arquivo && l.startsWith('+')) {
|
|
628
|
+
if (!porArquivo.has(arquivo))
|
|
629
|
+
porArquivo.set(arquivo, []);
|
|
630
|
+
porArquivo.get(arquivo).push([nova, l.slice(1)]);
|
|
631
|
+
nova += 1;
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
return porArquivo;
|
|
635
|
+
}
|
|
636
|
+
/**
|
|
637
|
+
* F2: o diff da thread contra `base`: as linhas acrescentadas da árvore de trabalho (o que já foi commitado e o que
|
|
638
|
+
* ainda não foi), os arquivos não rastreados inteiros e os nomes dos arquivos acrescentados e dos não rastreados. O
|
|
639
|
+
* nome que já estava na base é da árvore, que o CI varre. `ignorar` tira caminhos (o bundle que o `ci prepare`
|
|
640
|
+
* troca e varre pelo texto novo).
|
|
641
|
+
*/
|
|
642
|
+
function varrerDiff(contexto, base, opcoes = {}) {
|
|
643
|
+
const coletor = new Coletor(contexto);
|
|
644
|
+
const escopo = [{ tipo: 'diff', base: base.sha, ref: base.ref }];
|
|
645
|
+
const ignorar = new Set(opcoes.ignorar ?? []);
|
|
646
|
+
const git = (args) => (0, util_1.exec)('git', ['-c', 'core.quotePath=false', ...args], contexto.raiz);
|
|
647
|
+
const diff = git(['diff', '--no-color', '--no-ext-diff', '--no-textconv', '--text', '--unified=0', '--no-renames', base.sha, '--']);
|
|
648
|
+
const novos = git(['diff', '--name-only', '-z', '--no-renames', '--diff-filter=A', base.sha, '--']);
|
|
649
|
+
const soltos = git(['ls-files', '--others', '--exclude-standard', '-z']);
|
|
650
|
+
const falhou = [diff, novos, soltos].find((r) => !r.ok);
|
|
651
|
+
if (falhou)
|
|
652
|
+
return coletor.resultado(escopo, `git não respondeu ao ler o diff contra ${base.sha.slice(0, 12)}: ${falhou.stderr.trim() || falhou.code}`);
|
|
653
|
+
const mostrados = new Map();
|
|
654
|
+
const mostrar = (arquivo, comNome) => {
|
|
655
|
+
if (!mostrados.has(arquivo))
|
|
656
|
+
mostrados.set(arquivo, comNome ? coletor.nome(arquivo) : coletor.caminho(arquivo).mostrado);
|
|
657
|
+
return mostrados.get(arquivo);
|
|
658
|
+
};
|
|
659
|
+
const naoRastreados = listaZ(soltos.stdout).filter((a) => !ignorar.has(a));
|
|
660
|
+
for (const arquivo of [...listaZ(novos.stdout).filter((a) => !ignorar.has(a)), ...naoRastreados])
|
|
661
|
+
mostrar(arquivo, true);
|
|
662
|
+
for (const [arquivo, linhas] of linhasAcrescentadas(diff.stdout)) {
|
|
663
|
+
if (ignorar.has(arquivo))
|
|
664
|
+
continue;
|
|
665
|
+
coletor.arquivos += 1;
|
|
666
|
+
coletor.linhasDe(arquivo, mostrar(arquivo, false), linhas);
|
|
667
|
+
}
|
|
668
|
+
for (const arquivo of naoRastreados) {
|
|
669
|
+
const texto = conteudo(contexto.raiz, arquivo);
|
|
670
|
+
if (texto === null)
|
|
671
|
+
continue;
|
|
672
|
+
coletor.arquivos += 1;
|
|
673
|
+
coletor.arquivoInteiro(arquivo, mostrar(arquivo, true), texto);
|
|
674
|
+
}
|
|
675
|
+
return coletor.resultado(escopo);
|
|
676
|
+
}
|
|
677
|
+
/** Um texto que ainda não está no disco (o bundle do `ci prepare`), varrido como o arquivo `nome`. */
|
|
678
|
+
function varrerTexto(contexto, nome, texto) {
|
|
679
|
+
const coletor = new Coletor(contexto);
|
|
680
|
+
coletor.arquivos += 1;
|
|
681
|
+
coletor.arquivoInteiro(nome, coletor.caminho(nome).mostrado, texto);
|
|
682
|
+
return coletor.resultado([{ tipo: 'texto', nome }]);
|
|
683
|
+
}
|
|
684
|
+
/** Duas varreduras do mesmo contexto numa só (o diff e o bundle do `ci prepare`). Exceção sem uso é a de nenhuma. */
|
|
685
|
+
function juntarVarreduras(a, b) {
|
|
686
|
+
const semUsoEmB = new Set(b.excecoes.semUso);
|
|
687
|
+
return { ...a, escopo: [...a.escopo, ...b.escopo], arquivos: a.arquivos + b.arquivos, linhas: a.linhas + b.linhas,
|
|
688
|
+
nomes: a.nomes + b.nomes, excecoes: { ...a.excecoes, semUso: a.excecoes.semUso.filter((s) => semUsoEmB.has(s)) },
|
|
689
|
+
achados: [...a.achados, ...b.achados], erro: a.erro ?? b.erro };
|
|
690
|
+
}
|
|
691
|
+
const SHA = /^[0-9a-f]{40,64}$/;
|
|
692
|
+
/** A merge-base de HEAD com `ref` (nome de branch ou de ref remota), ou `null` sem ela. */
|
|
693
|
+
function mergeBase(cwd, ref) {
|
|
694
|
+
if (!(0, util_1.exec)('git', ['rev-parse', '--verify', '--quiet', `${ref}^{commit}`], cwd).ok)
|
|
695
|
+
return null;
|
|
696
|
+
const r = (0, util_1.exec)('git', ['merge-base', 'HEAD', ref], cwd);
|
|
697
|
+
const sha = r.stdout.trim();
|
|
698
|
+
return r.ok && SHA.test(sha) ? { sha, ref } : null;
|
|
699
|
+
}
|
|
700
|
+
/**
|
|
701
|
+
* A base do diff da thread (D7): a merge-base de HEAD com `origin/<base>`, senão com a base local, senão o commit
|
|
702
|
+
* carimbado na thread, se for ancestral de HEAD. `null` sem nenhuma: quem chama varre a árvore inteira e diz.
|
|
703
|
+
*/
|
|
704
|
+
function baseDaThread(cwd, baseBranch, carimbada) {
|
|
705
|
+
for (const ref of [`origin/${baseBranch}`, baseBranch]) {
|
|
706
|
+
const qualificada = ref.startsWith('origin/') ? `refs/remotes/${ref}` : `refs/heads/${ref}`;
|
|
707
|
+
if (!(0, util_1.exec)('git', ['rev-parse', '--verify', '--quiet', qualificada], cwd).ok)
|
|
708
|
+
continue;
|
|
709
|
+
const base = mergeBase(cwd, qualificada);
|
|
710
|
+
if (base)
|
|
711
|
+
return { sha: base.sha, ref };
|
|
712
|
+
}
|
|
713
|
+
if (carimbada && SHA.test(carimbada) && (0, util_1.exec)('git', ['merge-base', '--is-ancestor', carimbada, 'HEAD'], cwd).ok) {
|
|
714
|
+
return { sha: carimbada, ref: null };
|
|
715
|
+
}
|
|
716
|
+
return null;
|
|
717
|
+
}
|
|
718
|
+
// ---------------------------------------------------------------------------
|
|
719
|
+
// O relatório (D8)
|
|
720
|
+
// ---------------------------------------------------------------------------
|
|
721
|
+
/** `arquivo:linha:coluna padrão`, ou `arquivo nome do arquivo: padrão`; o termo da lista sai pelo número. */
|
|
722
|
+
function linhaDoAchado(a) {
|
|
723
|
+
const padrao = a.padrao === 'lista-local' ? `lista-local (termo ${a.termo})` : a.padrao;
|
|
724
|
+
return a.linha === null ? `${a.arquivo} nome do arquivo: ${padrao}` : `${a.arquivo}:${a.linha}:${a.coluna} ${padrao}`;
|
|
725
|
+
}
|
|
726
|
+
/** O escopo em uma frase. */
|
|
727
|
+
function textoDoEscopo(v) {
|
|
728
|
+
const partes = v.escopo.map((e) => e.tipo === 'arvore' ? 'árvore versionada'
|
|
729
|
+
: e.tipo === 'diff' ? `diff contra ${e.base.slice(0, 12)} (${e.ref ? `merge-base com ${e.ref}` : 'base carimbada da thread'})`
|
|
730
|
+
: `texto de ${e.nome}`);
|
|
731
|
+
return `${partes.join(' e ') || 'nada'}: ${v.arquivos} arquivo(s), ${v.linhas} linha(s), ${v.nomes} nome(s)`;
|
|
732
|
+
}
|
|
733
|
+
/** A lista local em uma frase, sem termo: ausente (e onde foi procurada) ou quantos termos de onde. */
|
|
734
|
+
function textoDaListaLocal(v) {
|
|
735
|
+
const l = v.listaLocal;
|
|
736
|
+
const ignorados = l.ignorados ? `; ${l.ignorados} termo(s) curto(s) demais ignorado(s)` : '';
|
|
737
|
+
return l.presente
|
|
738
|
+
? `${l.termos} termo(s) de ${l.lidos.join(' e ')}${ignorados}`
|
|
739
|
+
: `ausente (procurada em ${l.procurada.join(' e ')}); só os padrões genéricos (${exports.PADROES_GENERICOS.join(', ')})`;
|
|
740
|
+
}
|
|
741
|
+
/** O relatório da varredura, sem valor casado nem termo. */
|
|
742
|
+
function relatorioDoDadoPrivado(v) {
|
|
743
|
+
const linhas = [`Varredura de dado privado: ${textoDoEscopo(v)}`, ` lista local ${textoDaListaLocal(v)}`];
|
|
744
|
+
linhas.push(` exceções ${v.excecoes.arquivo
|
|
745
|
+
? `${v.excecoes.arquivo}: ${v.excecoes.total} exceção(ões) e ${v.excecoes.emailsPermitidos} e-mail(s) permitido(s)`
|
|
746
|
+
: `sem ${exports.ARQUIVO_DAS_EXCECOES}: só as regras do código`}`);
|
|
747
|
+
if (v.erro) {
|
|
748
|
+
linhas.push(` erro ${v.erro}`);
|
|
749
|
+
return linhas;
|
|
750
|
+
}
|
|
751
|
+
// Exceção sem uso só diz alguma coisa na árvore inteira; no diff, ela cobre o que a thread não mexeu.
|
|
752
|
+
if (v.escopo.some((e) => e.tipo === 'arvore')) {
|
|
753
|
+
for (const s of v.excecoes.semUso)
|
|
754
|
+
linhas.push(` aviso exceção sem uso, revise no PR: ${s}`);
|
|
755
|
+
}
|
|
756
|
+
linhas.push(` achados ${v.achados.length}${v.achados.length ? ' (o valor casado nunca é impresso)' : ''}`);
|
|
757
|
+
for (const a of v.achados)
|
|
758
|
+
linhas.push(` ${linhaDoAchado(a)}`);
|
|
759
|
+
return linhas;
|
|
760
|
+
}
|
|
761
|
+
// ---------------------------------------------------------------------------
|
|
762
|
+
// A barreira da thread (F2): a policy `dado_privado` no `ork verify` e no `ork ci prepare`
|
|
763
|
+
// ---------------------------------------------------------------------------
|
|
764
|
+
/** Até quantos `arquivo:linha` vão ao detalhe da policy e ao ledger. */
|
|
765
|
+
const ONDE_NO_DETALHE = 10;
|
|
766
|
+
const ONDE_NO_LEDGER = 50;
|
|
767
|
+
/** O `arquivo:linha` de um achado, ou `arquivo (nome)`; o caminho já vem mascarado. */
|
|
768
|
+
function ondeDoAchado(a) {
|
|
769
|
+
return a.linha === null ? `${a.arquivo} (nome)` : `${a.arquivo}:${a.linha}`;
|
|
770
|
+
}
|
|
771
|
+
function padroesAchados(v) {
|
|
772
|
+
return [...new Set(v.achados.map((a) => a.padrao))];
|
|
773
|
+
}
|
|
774
|
+
/** Os fatos da varredura para a policy, sem valor casado nem termo. */
|
|
775
|
+
function fatosDaVarredura(v) {
|
|
776
|
+
return { achados: v.achados.length, padroes: padroesAchados(v), onde: v.achados.slice(0, ONDE_NO_DETALHE).map(ondeDoAchado),
|
|
777
|
+
listaLocal: v.listaLocal.presente, escopo: textoDoEscopo(v), erro: v.erro };
|
|
778
|
+
}
|
|
779
|
+
/** O que vai ao ledger: escopo, contagens, padrões e onde; nunca o valor casado nem o termo. */
|
|
780
|
+
function resumoNoLedger(b) {
|
|
781
|
+
const v = b.varredura;
|
|
782
|
+
return { policy: b.severidade, escopo: v.escopo, arquivos: v.arquivos, linhas: v.linhas, nomes: v.nomes,
|
|
783
|
+
listaLocal: { presente: v.listaLocal.presente, termos: v.listaLocal.termos }, achados: v.achados.length,
|
|
784
|
+
padroes: padroesAchados(v), onde: v.achados.slice(0, ONDE_NO_LEDGER).map(ondeDoAchado), erro: v.erro };
|
|
785
|
+
}
|
|
786
|
+
function estadoDoProjeto(raiz) {
|
|
787
|
+
try {
|
|
788
|
+
return (0, estado_thread_1.raizDoEstado)(raiz);
|
|
789
|
+
}
|
|
790
|
+
catch {
|
|
791
|
+
return null;
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
/**
|
|
795
|
+
* RM-062 (F2): a barreira no diretório de trabalho da thread. Com `policies.dado_privado` declarada fora de `off`,
|
|
796
|
+
* varre o diff da thread contra a base (D7; sem base, a árvore inteira), mais o que `extra` varrer (o bundle do `ci
|
|
797
|
+
* prepare`), e avalia a policy no gate. Fora do manifesto, `null`: nada roda.
|
|
798
|
+
*/
|
|
799
|
+
function barreiraDaThread(carregado, thread, cwd, gate, opcoes = {}) {
|
|
800
|
+
const severidade = (0, policies_1.severidadeDeclarada)(carregado.manifesto, 'dado_privado');
|
|
801
|
+
if (severidade === null || severidade === 'off')
|
|
802
|
+
return null;
|
|
803
|
+
const c = contextoDaVarredura(cwd, estadoDoProjeto(carregado.raiz), opcoes.env);
|
|
804
|
+
let varredura;
|
|
805
|
+
if (!c.ok)
|
|
806
|
+
varredura = varreduraComErro(c.erro, c.lista);
|
|
807
|
+
else {
|
|
808
|
+
const base = baseDaThread(cwd, carregado.manifesto.worktree.base_branch, thread.base?.commit);
|
|
809
|
+
varredura = base ? varrerDiff(c.valor, base, { ignorar: opcoes.ignorar }) : varrerArvore(c.valor);
|
|
810
|
+
if (opcoes.extra)
|
|
811
|
+
varredura = juntarVarreduras(varredura, opcoes.extra(c.valor));
|
|
812
|
+
}
|
|
813
|
+
const violacoes = (0, policies_1.avaliarPolicies)(carregado.manifesto, { gate, threadId: thread.id, modo: thread.modo,
|
|
814
|
+
dadoPrivado: fatosDaVarredura(varredura) });
|
|
815
|
+
return { severidade, varredura, violacoes };
|
|
816
|
+
}
|