@justmpm/firebase-audit 0.4.0 → 0.4.2

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.
@@ -783,6 +783,10 @@ async function runChecks(model, rootDir, opts = {}) {
783
783
  }
784
784
  }
785
785
  const adminHits = await collectAdminImports(rootDir);
786
+ const isTestPath = (f) => {
787
+ const n = f.replace(/\\/g, "/");
788
+ return /\.test\.[tj]sx?$/.test(n) || /\.spec\.[tj]sx?$/.test(n) || /(^|\/)__tests__\//.test(n) || /(^|\/)(test|tests|testing)\//.test(n);
789
+ };
786
790
  for (const hit of adminHits) {
787
791
  let origin = "UNKNOWN";
788
792
  try {
@@ -791,7 +795,7 @@ async function runChecks(model, rootDir, opts = {}) {
791
795
  } catch {
792
796
  origin = "UNKNOWN";
793
797
  }
794
- if (origin === "CLIENT") {
798
+ if (origin === "CLIENT" && !isTestPath(hit.file)) {
795
799
  findings.push(
796
800
  make(
797
801
  "FBA003",
@@ -1329,16 +1333,17 @@ async function scan(rootDir, opts = {}) {
1329
1333
  });
1330
1334
  }
1331
1335
  } else {
1336
+ const details = validated.error.issues.slice(0, 5).map((i) => `${String(i.path.join(".")) || "(raiz)"}: ${i.message}`).join("; ");
1332
1337
  findings_pre.push({
1333
1338
  rule: "CONTRACT_INVALID",
1334
1339
  severity: "WARNING",
1335
1340
  confidence: "CONFIRMED",
1336
- message: `firebase-audit.yaml existe mas \xE9 inv\xE1lido: ${validated.error.issues[0]?.message ?? "schema"} \u2014 contrato ignorado neste scan.`,
1341
+ message: `firebase-audit.yaml inv\xE1lido (${validated.error.issues.length} problema(s): ${details}) \u2014 contrato ignorado neste scan.`,
1337
1342
  fingerprint: "CONTRACT_INVALID",
1338
1343
  evidence: [
1339
- { id: "ev-contract-invalid", kind: "contract-parse", summary: "YAML inv\xE1lido", confidence: "CONFIRMED" }
1344
+ { id: "ev-contract-invalid", kind: "contract-parse", summary: details.slice(0, 200), confidence: "CONFIRMED" }
1340
1345
  ],
1341
- fix: "Valide contra templates/firebase-audit.yaml."
1346
+ fix: "Valide contra templates/firebase-audit.yaml (min\xFAsculas, sem chaves extras)."
1342
1347
  });
1343
1348
  }
1344
1349
  } catch (err) {
@@ -7,7 +7,7 @@ function collectVisitCounts(node, out) {
7
7
  return;
8
8
  }
9
9
  const rec = node;
10
- for (const k of ["visitCount", "visit_count", "count", "hits", "visits", "evaluations"]) {
10
+ for (const k of ["visitCount", "visit_count"]) {
11
11
  const v = rec[k];
12
12
  if (typeof v === "number" && Number.isFinite(v)) {
13
13
  out.push(v);
@@ -26,6 +26,7 @@ function planVerify(model) {
26
26
  };
27
27
  const cases = [];
28
28
  for (const rule of model.rules) {
29
+ if (rule.path === "/(unknown)") continue;
29
30
  for (const role of roles) {
30
31
  for (const op of rule.operations) {
31
32
  for (const mapped of expand(op)) {
@@ -105,6 +106,7 @@ function verify(model, opts = {}) {
105
106
  return { cases, uncovered: [], findings, coveragePct };
106
107
  }
107
108
  for (const rule of model.rules) {
109
+ if (rule.path === "/(unknown)") continue;
108
110
  uncovered.push(rule.path);
109
111
  }
110
112
  return { cases, uncovered, findings, coveragePct };
@@ -1,9 +1,9 @@
1
1
  import {
2
2
  scan
3
- } from "./chunk-5PVQB3ZI.js";
3
+ } from "./chunk-3SEVYYCX.js";
4
4
  import {
5
5
  verify
6
- } from "./chunk-TVEZQU7C.js";
6
+ } from "./chunk-PSSMYRRU.js";
7
7
  import {
8
8
  drift
9
9
  } from "./chunk-NJBCSW7E.js";
@@ -13,13 +13,58 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
13
13
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
14
14
  import { z } from "zod";
15
15
  import { createRequire } from "module";
16
+ import { readFileSync, existsSync } from "fs";
17
+ import { dirname, join } from "path";
18
+ import { fileURLToPath } from "url";
16
19
  var require2 = createRequire(import.meta.url);
17
20
  var pkg = require2("../package.json");
21
+ var pkgDir = join(dirname(fileURLToPath(import.meta.url)), "..");
22
+ function readPkgText(rel) {
23
+ try {
24
+ const p = join(pkgDir, rel);
25
+ if (!existsSync(p)) return null;
26
+ return readFileSync(p, "utf-8");
27
+ } catch {
28
+ return null;
29
+ }
30
+ }
31
+ var FALLBACK_SKILL = [
32
+ "# firebase-audit-skill (fallback)",
33
+ "",
34
+ "1. Descobrir projeto (firebase.json, firestore.rules, firestore.indexes.json).",
35
+ "2. Executar scan (strict no CI). Sem adapter resolvido \u2192 UNKNOWN, nunca adivinhar.",
36
+ "3. CONFIRMED corrige agora; PROBABLE investiga; NOT_OBSERVED/UNKNOWN n\xE3o afirma.",
37
+ "4. Corrigir c\xF3digo, nunca o contrato para esconder finding."
38
+ ].join("\n");
18
39
  function createMcpServer() {
19
40
  const server = new McpServer({
20
41
  name: "firebase-audit-mcp-server",
21
42
  version: pkg.version
22
43
  });
44
+ server.registerTool(
45
+ "firebase_audit_skill",
46
+ {
47
+ title: "Firebase Audit Skill (guia do agente)",
48
+ description: "LEIA ANTES de auditar: sequ\xEAncia obrigat\xF3ria, travas (nunca mude o contrato para esconder finding, nunca afirme sem evid\xEAncia, nunca diga 'unused') e Emulador. Retorna o guia oficial da vers\xE3o instalada + modelo de contrato. Sempre sincronizado \u2014 dispensa skill copiada.",
49
+ inputSchema: {}
50
+ },
51
+ async () => {
52
+ const skill = readPkgText(join("skill", "SKILL.md")) ?? FALLBACK_SKILL;
53
+ const template = readPkgText(join("templates", "firebase-audit.yaml")) ?? "";
54
+ const text = [
55
+ `# firebase-audit v${pkg.version} \u2014 guia do agente`,
56
+ "",
57
+ skill,
58
+ "",
59
+ "## Modelo de contrato (templates/firebase-audit.yaml)",
60
+ "",
61
+ "```yaml",
62
+ template,
63
+ "```"
64
+ ].join("\n");
65
+ return { content: [{ type: "text", text }] };
66
+ }
67
+ );
23
68
  server.registerTool(
24
69
  "firebase_audit_scan",
25
70
  {
@@ -120,8 +165,8 @@ function createMcpServer() {
120
165
  title: "Firebase Audit Drift (\xEDndices)",
121
166
  description: "Compara \xEDndices locais vs implantados. Retorna MATCHED/LOCAL_ONLY/REMOTE_ONLY. Nunca diz 'unused', apenas NOT_OBSERVED.",
122
167
  inputSchema: {
123
- local: z.string().nullable().describe("Path do firestore.indexes.json local (ou null). Relativo resolve contra cwd."),
124
- remote: z.string().nullable().describe("Path do JSON remoto (gcloud/REST) (ou null). Relativo resolve contra cwd."),
168
+ local: z.string().nullable().optional().describe("Path do firestore.indexes.json local (ou null). Relativo resolve contra cwd."),
169
+ remote: z.string().nullable().optional().describe("Path do JSON remoto (gcloud/REST) (ou null). Relativo resolve contra cwd."),
125
170
  cwd: z.string().optional().describe("Diret\xF3rio base para paths relativos (default: cwd do servidor).")
126
171
  }
127
172
  },
@@ -130,7 +175,7 @@ function createMcpServer() {
130
175
  const { resolve: resolveP, isAbsolute } = await import("path");
131
176
  const base = cwd && cwd.length > 0 ? cwd : process.cwd();
132
177
  const resPath = (p) => {
133
- if (p === null) return null;
178
+ if (p === null || p === void 0) return null;
134
179
  return isAbsolute(p) ? p : resolveP(base, p);
135
180
  };
136
181
  const res = drift(resPath(local), resPath(remote));
package/dist/cli.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  scan
4
- } from "./chunk-5PVQB3ZI.js";
4
+ } from "./chunk-3SEVYYCX.js";
5
5
 
6
6
  // src/cli.ts
7
7
  import { createRequire } from "module";
@@ -64,8 +64,8 @@ Uso: firebase-audit scan|check|verify|drift [--json] ...`);
64
64
  const { resolve: resolveRoot } = await import("path");
65
65
  const rootDir = cwdArg ? resolveRoot(process.cwd(), cwdArg) : process.cwd();
66
66
  if (cmd === "verify") {
67
- const { scan: scanForVerify } = await import("./scan-6FRUE5BE.js");
68
- const { verify } = await import("./verify-R6LI2KNZ.js");
67
+ const { scan: scanForVerify } = await import("./scan-VREY2FGO.js");
68
+ const { verify } = await import("./verify-O722ZZBV.js");
69
69
  const { resolve: resolveVerify } = await import("path");
70
70
  const coverageArg = getArg("coverage");
71
71
  const coveragePath = coverageArg ? resolveVerify(rootDir, coverageArg) : void 0;
@@ -107,6 +107,15 @@ ${icon} ${f.rule} [${f.confidence}]
107
107
  const remotePath = remoteArg ? resolveDrift(rootDir, remoteArg) : null;
108
108
  for (const [label, p] of [["--local", localPath], ["--remote", remotePath]]) {
109
109
  if (p && !existsDrift(p)) {
110
+ if (asJson) {
111
+ const { drift: driftMissing } = await import("./drift-KU7HWL7M.js");
112
+ const res2 = driftMissing(
113
+ localPath && existsDrift(localPath) ? localPath : null,
114
+ remotePath && existsDrift(remotePath) ? remotePath : null
115
+ );
116
+ console.log(JSON.stringify({ version: pkg.version, entries: res2.entries, findings: res2.findings }, null, 2));
117
+ process.exit(1);
118
+ }
110
119
  console.error(`Arquivo ${label} n\xE3o encontrado: ${p}`);
111
120
  process.exit(1);
112
121
  }
package/dist/index.d.ts CHANGED
@@ -643,6 +643,10 @@ declare function enrichWithGraph(rootDir: string, d: DiscoveryResult): Promise<v
643
643
  /**
644
644
  * Rules extractor — estrutura + referências + locations (sem avaliar).
645
645
  * Parser leve e intencionalmente incompleto: avaliação real é do Emulator (V3).
646
+ *
647
+ * Limitação conhecida: `ALLOW_RE` corta a condição no primeiro `;` mesmo dentro
648
+ * de string literal (ex: `x != "a;b"`). Raríssimo em Rules reais; documentado aqui
649
+ * em vez de silêncio — Emulator acusa a regra de verdade.
646
650
  */
647
651
 
648
652
  type Rule = z$1.infer<typeof RuleSchema>;
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  createMcpServer,
3
3
  startMcpServer
4
- } from "./chunk-YVV7YRF3.js";
4
+ } from "./chunk-SKD4TKFU.js";
5
5
  import {
6
6
  AccessOriginSchema,
7
7
  AuditYamlSchema,
@@ -53,11 +53,11 @@ import {
53
53
  stripOuterParens,
54
54
  stripRuleComments,
55
55
  validateClaimSamples
56
- } from "./chunk-5PVQB3ZI.js";
56
+ } from "./chunk-3SEVYYCX.js";
57
57
  import {
58
58
  planVerify,
59
59
  verify
60
- } from "./chunk-TVEZQU7C.js";
60
+ } from "./chunk-PSSMYRRU.js";
61
61
  import {
62
62
  drift
63
63
  } from "./chunk-NJBCSW7E.js";
package/dist/mcp-cli.js CHANGED
@@ -1,9 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  startMcpServer
4
- } from "./chunk-YVV7YRF3.js";
5
- import "./chunk-5PVQB3ZI.js";
6
- import "./chunk-TVEZQU7C.js";
4
+ } from "./chunk-SKD4TKFU.js";
5
+ import "./chunk-3SEVYYCX.js";
6
+ import "./chunk-PSSMYRRU.js";
7
7
  import "./chunk-NJBCSW7E.js";
8
8
 
9
9
  // src/mcp-cli.ts
package/dist/mcp.d.ts CHANGED
@@ -3,7 +3,7 @@ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
3
3
  /**
4
4
  * MCP Server do firebase-audit.
5
5
  *
6
- * Expõe scan/check/verify/drift via protocolo MCP (stdio).
6
+ * Expõe skill + scan/check/verify/drift via protocolo MCP (stdio).
7
7
  * Mesma lógica da biblioteca — sem duplicação (chama scan/verify/drift direto).
8
8
  *
9
9
  * Uso:
package/dist/mcp.js CHANGED
@@ -1,9 +1,9 @@
1
1
  import {
2
2
  createMcpServer,
3
3
  startMcpServer
4
- } from "./chunk-YVV7YRF3.js";
5
- import "./chunk-5PVQB3ZI.js";
6
- import "./chunk-TVEZQU7C.js";
4
+ } from "./chunk-SKD4TKFU.js";
5
+ import "./chunk-3SEVYYCX.js";
6
+ import "./chunk-PSSMYRRU.js";
7
7
  import "./chunk-NJBCSW7E.js";
8
8
  export {
9
9
  createMcpServer,
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  scan
3
- } from "./chunk-5PVQB3ZI.js";
3
+ } from "./chunk-3SEVYYCX.js";
4
4
  export {
5
5
  scan
6
6
  };
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  planVerify,
3
3
  verify
4
- } from "./chunk-TVEZQU7C.js";
4
+ } from "./chunk-PSSMYRRU.js";
5
5
  export {
6
6
  planVerify,
7
7
  verify
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@justmpm/firebase-audit",
3
- "version": "0.4.0",
3
+ "version": "0.4.2",
4
4
  "description": "Auditor de consistência e segurança para projetos Firebase: código + Rules + índices + contrato vs comportamento. Static-first, nunca inventa certeza.",
5
5
  "keywords": [
6
6
  "firebase",