@fourteensystems/shipguard 0.1.0

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.
Files changed (139) hide show
  1. package/README.md +213 -0
  2. package/bin/shipguard.mjs +2 -0
  3. package/dist/cli/commands/baseline.d.ts +7 -0
  4. package/dist/cli/commands/baseline.d.ts.map +1 -0
  5. package/dist/cli/commands/baseline.js +22 -0
  6. package/dist/cli/commands/baseline.js.map +1 -0
  7. package/dist/cli/commands/ci.d.ts +13 -0
  8. package/dist/cli/commands/ci.d.ts.map +1 -0
  9. package/dist/cli/commands/ci.js +91 -0
  10. package/dist/cli/commands/ci.js.map +1 -0
  11. package/dist/cli/commands/explain.d.ts +2 -0
  12. package/dist/cli/commands/explain.d.ts.map +1 -0
  13. package/dist/cli/commands/explain.js +20 -0
  14. package/dist/cli/commands/explain.js.map +1 -0
  15. package/dist/cli/commands/init.d.ts +7 -0
  16. package/dist/cli/commands/init.d.ts.map +1 -0
  17. package/dist/cli/commands/init.js +91 -0
  18. package/dist/cli/commands/init.js.map +1 -0
  19. package/dist/cli/commands/rules.d.ts +2 -0
  20. package/dist/cli/commands/rules.d.ts.map +1 -0
  21. package/dist/cli/commands/rules.js +13 -0
  22. package/dist/cli/commands/rules.js.map +1 -0
  23. package/dist/cli/commands/scan.d.ts +10 -0
  24. package/dist/cli/commands/scan.d.ts.map +1 -0
  25. package/dist/cli/commands/scan.js +55 -0
  26. package/dist/cli/commands/scan.js.map +1 -0
  27. package/dist/cli/commands/waive.d.ts +8 -0
  28. package/dist/cli/commands/waive.d.ts.map +1 -0
  29. package/dist/cli/commands/waive.js +34 -0
  30. package/dist/cli/commands/waive.js.map +1 -0
  31. package/dist/cli/index.d.ts +2 -0
  32. package/dist/cli/index.d.ts.map +1 -0
  33. package/dist/cli/index.js +63 -0
  34. package/dist/cli/index.js.map +1 -0
  35. package/dist/engine/baseline.d.ts +11 -0
  36. package/dist/engine/baseline.d.ts.map +1 -0
  37. package/dist/engine/baseline.js +39 -0
  38. package/dist/engine/baseline.js.map +1 -0
  39. package/dist/engine/config.d.ts +8 -0
  40. package/dist/engine/config.d.ts.map +1 -0
  41. package/dist/engine/config.js +130 -0
  42. package/dist/engine/config.js.map +1 -0
  43. package/dist/engine/extensions/load.d.ts +11 -0
  44. package/dist/engine/extensions/load.d.ts.map +1 -0
  45. package/dist/engine/extensions/load.js +26 -0
  46. package/dist/engine/extensions/load.js.map +1 -0
  47. package/dist/engine/extensions/registry.d.ts +5 -0
  48. package/dist/engine/extensions/registry.d.ts.map +1 -0
  49. package/dist/engine/extensions/registry.js +11 -0
  50. package/dist/engine/extensions/registry.js.map +1 -0
  51. package/dist/engine/extensions/types.d.ts +51 -0
  52. package/dist/engine/extensions/types.d.ts.map +1 -0
  53. package/dist/engine/extensions/types.js +2 -0
  54. package/dist/engine/extensions/types.js.map +1 -0
  55. package/dist/engine/report.d.ts +5 -0
  56. package/dist/engine/report.d.ts.map +1 -0
  57. package/dist/engine/report.js +88 -0
  58. package/dist/engine/report.js.map +1 -0
  59. package/dist/engine/run.d.ts +9 -0
  60. package/dist/engine/run.d.ts.map +1 -0
  61. package/dist/engine/run.js +101 -0
  62. package/dist/engine/run.js.map +1 -0
  63. package/dist/engine/sarif.d.ts +3 -0
  64. package/dist/engine/sarif.d.ts.map +1 -0
  65. package/dist/engine/sarif.js +58 -0
  66. package/dist/engine/sarif.js.map +1 -0
  67. package/dist/engine/score.d.ts +13 -0
  68. package/dist/engine/score.d.ts.map +1 -0
  69. package/dist/engine/score.js +97 -0
  70. package/dist/engine/score.js.map +1 -0
  71. package/dist/engine/types.d.ts +119 -0
  72. package/dist/engine/types.d.ts.map +1 -0
  73. package/dist/engine/types.js +2 -0
  74. package/dist/engine/types.js.map +1 -0
  75. package/dist/engine/version.d.ts +5 -0
  76. package/dist/engine/version.d.ts.map +1 -0
  77. package/dist/engine/version.js +15 -0
  78. package/dist/engine/version.js.map +1 -0
  79. package/dist/engine/waivers.d.ts +9 -0
  80. package/dist/engine/waivers.d.ts.map +1 -0
  81. package/dist/engine/waivers.js +55 -0
  82. package/dist/engine/waivers.js.map +1 -0
  83. package/dist/index.d.ts +12 -0
  84. package/dist/index.d.ts.map +1 -0
  85. package/dist/index.js +11 -0
  86. package/dist/index.js.map +1 -0
  87. package/dist/next/deps.d.ts +4 -0
  88. package/dist/next/deps.d.ts.map +1 -0
  89. package/dist/next/deps.js +102 -0
  90. package/dist/next/deps.js.map +1 -0
  91. package/dist/next/detect.d.ts +10 -0
  92. package/dist/next/detect.d.ts.map +1 -0
  93. package/dist/next/detect.js +57 -0
  94. package/dist/next/detect.js.map +1 -0
  95. package/dist/next/index.d.ts +5 -0
  96. package/dist/next/index.d.ts.map +1 -0
  97. package/dist/next/index.js +41 -0
  98. package/dist/next/index.js.map +1 -0
  99. package/dist/next/middleware.d.ts +3 -0
  100. package/dist/next/middleware.d.ts.map +1 -0
  101. package/dist/next/middleware.js +33 -0
  102. package/dist/next/middleware.js.map +1 -0
  103. package/dist/next/routes.d.ts +5 -0
  104. package/dist/next/routes.d.ts.map +1 -0
  105. package/dist/next/routes.js +125 -0
  106. package/dist/next/routes.js.map +1 -0
  107. package/dist/next/server-actions.d.ts +4 -0
  108. package/dist/next/server-actions.d.ts.map +1 -0
  109. package/dist/next/server-actions.js +107 -0
  110. package/dist/next/server-actions.js.map +1 -0
  111. package/dist/next/trpc.d.ts +3 -0
  112. package/dist/next/trpc.d.ts.map +1 -0
  113. package/dist/next/trpc.js +339 -0
  114. package/dist/next/trpc.js.map +1 -0
  115. package/dist/next/types.d.ts +100 -0
  116. package/dist/next/types.d.ts.map +1 -0
  117. package/dist/next/types.js +2 -0
  118. package/dist/next/types.js.map +1 -0
  119. package/dist/rules/auth-boundary-missing.d.ts +5 -0
  120. package/dist/rules/auth-boundary-missing.d.ts.map +1 -0
  121. package/dist/rules/auth-boundary-missing.js +278 -0
  122. package/dist/rules/auth-boundary-missing.js.map +1 -0
  123. package/dist/rules/index.d.ts +12 -0
  124. package/dist/rules/index.d.ts.map +1 -0
  125. package/dist/rules/index.js +41 -0
  126. package/dist/rules/index.js.map +1 -0
  127. package/dist/rules/rate-limit-missing.d.ts +5 -0
  128. package/dist/rules/rate-limit-missing.d.ts.map +1 -0
  129. package/dist/rules/rate-limit-missing.js +230 -0
  130. package/dist/rules/rate-limit-missing.js.map +1 -0
  131. package/dist/rules/tenancy-scope-missing.d.ts +5 -0
  132. package/dist/rules/tenancy-scope-missing.d.ts.map +1 -0
  133. package/dist/rules/tenancy-scope-missing.js +149 -0
  134. package/dist/rules/tenancy-scope-missing.js.map +1 -0
  135. package/dist/util/paths.d.ts +6 -0
  136. package/dist/util/paths.d.ts.map +1 -0
  137. package/dist/util/paths.js +18 -0
  138. package/dist/util/paths.js.map +1 -0
  139. package/package.json +55 -0
package/README.md ADDED
@@ -0,0 +1,213 @@
1
+ # Shipguard
2
+
3
+ CI guardrail that blocks unprotected mutation routes in Next.js SaaS.
4
+
5
+ Shipguard statically analyzes your Next.js App Router codebase and flags mutation endpoints missing auth boundaries, rate limiting, or tenant scoping. It understands your stack — Auth.js, Clerk, Supabase, tRPC, Prisma — and stays quiet when protections are in place.
6
+
7
+ ## Quick Start
8
+
9
+ ```bash
10
+ npx shipguard init
11
+ ```
12
+
13
+ Detects your framework and dependencies, generates a config, and runs your first scan.
14
+
15
+ ```
16
+ Shipguard 0.1.0
17
+ Detected: next-app-router · clerk · prisma · trpc · middleware
18
+ Score: 85 PASS
19
+ ```
20
+
21
+ ## Usage
22
+
23
+ ```bash
24
+ # Scan and print report
25
+ shipguard
26
+
27
+ # Only run specific rules
28
+ shipguard scan --only AUTH-BOUNDARY-MISSING,RATE-LIMIT-MISSING
29
+
30
+ # Exclude paths
31
+ shipguard scan --exclude "app/api/internal/**"
32
+
33
+ # JSON or SARIF output
34
+ shipguard scan --format json
35
+ shipguard scan --format sarif --output report.sarif
36
+
37
+ # CI mode (fail on critical findings)
38
+ shipguard ci --fail-on critical --min-confidence high
39
+
40
+ # Save baseline for regression detection
41
+ shipguard baseline --write
42
+
43
+ # Waive a finding
44
+ shipguard waive RATE-LIMIT-MISSING --file app/api/foo/route.ts --reason "Handled by Cloudflare WAF"
45
+
46
+ # List rules
47
+ shipguard rules
48
+
49
+ # Explain a rule
50
+ shipguard explain AUTH-BOUNDARY-MISSING
51
+ ```
52
+
53
+ ## What It Detects
54
+
55
+ ### Rules
56
+
57
+ | Rule | Severity | What it catches |
58
+ |------|----------|----------------|
59
+ | AUTH-BOUNDARY-MISSING | critical | Mutation endpoints without auth checks |
60
+ | RATE-LIMIT-MISSING | critical | Public API routes without rate limiting |
61
+ | TENANCY-SCOPE-MISSING | critical | Prisma queries without tenant scoping |
62
+
63
+ ### Stack Support
64
+
65
+ Shipguard auto-detects your stack and adjusts detection accordingly:
66
+
67
+ | Stack | What Shipguard understands |
68
+ |-------|---------------------------|
69
+ | **Auth.js / NextAuth** | `auth()`, `getServerSession()`, `withAuth()`, middleware auth |
70
+ | **Clerk** | `auth()`, `currentUser()`, `clerkMiddleware()` |
71
+ | **Supabase** | `.auth.getUser()`, `.auth.getSession()` (call-based, not import-based) |
72
+ | **Kinde** | `getKindeServerSession()` |
73
+ | **WorkOS / AuthKit** | `withAuth()`, `getUser()`, `authkitMiddleware()` |
74
+ | **Better Auth** | `auth()` |
75
+ | **Lucia** | `validateRequest()`, `validateSession()` |
76
+ | **Auth0** | `getSession()`, `withApiAuthRequired()` |
77
+ | **iron-session** | `getIronSession()` |
78
+ | **Firebase Auth** | `verifyIdToken()`, `getTokens()`, `verifySessionCookie()` |
79
+ | **tRPC** | `protectedProcedure` vs `publicProcedure`, `.mutation()` surfaces |
80
+ | **Prisma** | `.create()`, `.update()`, `.delete()` as mutation evidence, tenant scoping |
81
+ | **Drizzle** | Detected but gracefully degraded (tenancy rule skips) |
82
+ | **Upstash** | `Ratelimit`, `ratelimit.limit()` as rate-limit evidence |
83
+ | **Arcjet** | `fixedWindow()`, `slidingWindow()`, `tokenBucket()` |
84
+ | **Unkey** | `withUnkey()`, `verifyKey()` |
85
+
86
+ ### What It Skips
87
+
88
+ - Webhook routes (`/api/webhooks/*`) — exempt from rate-limit
89
+ - Cron routes (`/api/cron/*`) — exempt from rate-limit
90
+ - `GET`-only route handlers — not mutation surfaces
91
+ - Routes covered by `middleware.ts` auth — no double-flagging
92
+ - HOF-wrapped handlers (`withAuth(handler)`) — detected as auth boundary
93
+
94
+ See [PATTERNS.md](PATTERNS.md) for full detection logic.
95
+
96
+ ## GitHub Action
97
+
98
+ ```yaml
99
+ name: Shipguard
100
+ on: [pull_request]
101
+
102
+ permissions:
103
+ contents: read
104
+ pull-requests: write
105
+
106
+ jobs:
107
+ shipguard:
108
+ runs-on: ubuntu-latest
109
+ steps:
110
+ - uses: actions/checkout@v4
111
+ - uses: actions/setup-node@v4
112
+ with:
113
+ node-version: 20
114
+ - run: npm ci
115
+ - uses: shipguard/action@v1
116
+ env:
117
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
118
+ with:
119
+ min-score: 70
120
+ fail-on: critical
121
+ min-confidence: high
122
+ ```
123
+
124
+ The action:
125
+ - Comments on PRs with findings, score, and detected stack
126
+ - Adds inline annotations on flagged files
127
+ - Shows score delta when a baseline is provided
128
+ - Updates the same comment on re-runs (no spam)
129
+
130
+ ### Action Inputs
131
+
132
+ | Input | Default | Description |
133
+ |-------|---------|-------------|
134
+ | `fail-on` | `critical` | Minimum severity to fail the check |
135
+ | `min-confidence` | `high` | Minimum confidence to include |
136
+ | `min-score` | `70` | Minimum passing score |
137
+ | `baseline` | — | Path to baseline file for regression detection |
138
+ | `max-new-critical` | `0` | Max new critical findings allowed |
139
+ | `max-new-high` | — | Max new high findings allowed |
140
+ | `comment` | `true` | Post a PR comment with findings |
141
+ | `annotations` | `true` | Add inline file annotations |
142
+
143
+ ### Action Outputs
144
+
145
+ | Output | Description |
146
+ |--------|-------------|
147
+ | `score` | Shipguard score (0-100) |
148
+ | `findings` | Total number of findings |
149
+ | `result` | `PASS`, `WARN`, or `FAIL` |
150
+
151
+ ## Configuration
152
+
153
+ Most teams do not need to configure Shipguard. Run `shipguard init` and commit the generated config.
154
+
155
+ For advanced use cases, create `shipguard.config.json`:
156
+
157
+ ```json
158
+ {
159
+ "framework": "next-app-router",
160
+ "include": ["app/**", "src/**"],
161
+ "exclude": ["**/*.test.*", "**/*.spec.*"],
162
+ "ci": {
163
+ "failOn": "critical",
164
+ "minConfidence": "high",
165
+ "minScore": 70,
166
+ "maxNewCritical": 0
167
+ },
168
+ "hints": {
169
+ "auth": {
170
+ "functions": ["auth", "getServerSession", "currentUser"],
171
+ "middlewareFiles": ["middleware.ts"],
172
+ "allowlistPaths": ["app/api/public/**"]
173
+ },
174
+ "rateLimit": {
175
+ "wrappers": ["rateLimit", "withRateLimit"],
176
+ "allowlistPaths": ["app/api/webhooks/**"]
177
+ },
178
+ "tenancy": {
179
+ "orgFieldNames": ["orgId", "tenantId", "workspaceId"]
180
+ }
181
+ }
182
+ }
183
+ ```
184
+
185
+ ### Hints
186
+
187
+ Hints tell Shipguard about your codebase-specific patterns. If you use a custom auth wrapper like `requireAuth()` or a rate limiting function like `withRateLimit()`, add it to hints so Shipguard recognizes it and doesn't flag protected routes.
188
+
189
+ Most built-in patterns (Auth.js, Clerk, Supabase, Kinde, WorkOS, Lucia, Auth0, Firebase, tRPC, Upstash, Arcjet, Unkey) are detected automatically — hints are for your custom wrappers.
190
+
191
+ ## Confidence Levels
192
+
193
+ Every finding has a confidence level:
194
+
195
+ - **high** — strong evidence (e.g., `publicProcedure.mutation()` with `prisma.create`)
196
+ - **med** — likely but uncertain (e.g., unrecognized procedure type)
197
+ - **low** — possible issue, may be false positive
198
+
199
+ Use `--min-confidence` in CI to control noise:
200
+
201
+ ```bash
202
+ shipguard ci --min-confidence high
203
+ ```
204
+
205
+ ## Compatibility
206
+
207
+ Shipguard has no runtime dependency on Next.js, but it tracks evolving ecosystem patterns. Updates primarily add new detectors and improve confidence — not compatibility fixes.
208
+
209
+ Requires Next.js App Router (13.4+). Pages Router is not supported.
210
+
211
+ ## License
212
+
213
+ MIT — see [LICENSE](LICENSE)
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ import "../dist/cli/index.js";
@@ -0,0 +1,7 @@
1
+ interface BaselineOptions {
2
+ write?: boolean;
3
+ output?: string;
4
+ }
5
+ export declare function cmdBaseline(opts: BaselineOptions): Promise<void>;
6
+ export {};
7
+ //# sourceMappingURL=baseline.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"baseline.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/baseline.ts"],"names":[],"mappings":"AAIA,UAAU,eAAe;IACvB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,wBAAsB,WAAW,CAAC,IAAI,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CAkBtE"}
@@ -0,0 +1,22 @@
1
+ import pc from "picocolors";
2
+ import { runScan } from "../../engine/run.js";
3
+ import { writeBaseline } from "../../engine/baseline.js";
4
+ export async function cmdBaseline(opts) {
5
+ if (!opts.write) {
6
+ console.log(pc.dim(" Use --write to save a baseline snapshot."));
7
+ console.log(pc.dim(" Example: shipguard baseline --write"));
8
+ return;
9
+ }
10
+ try {
11
+ const rootDir = process.cwd();
12
+ const result = await runScan({ rootDir });
13
+ const dest = writeBaseline(rootDir, result, opts.output);
14
+ console.log(pc.green(` Baseline written to ${dest}`));
15
+ console.log(pc.dim(` Score: ${result.score} | Findings: ${result.findings.length}`));
16
+ }
17
+ catch (err) {
18
+ console.error(pc.red(` Error: ${err instanceof Error ? err.message : String(err)}`));
19
+ process.exit(1);
20
+ }
21
+ }
22
+ //# sourceMappingURL=baseline.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"baseline.js","sourceRoot":"","sources":["../../../src/cli/commands/baseline.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,YAAY,CAAC;AAC5B,OAAO,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAC;AAC9C,OAAO,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAOzD,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,IAAqB;IACrD,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC;QAChB,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,4CAA4C,CAAC,CAAC,CAAC;QAClE,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,uCAAuC,CAAC,CAAC,CAAC;QAC7D,OAAO;IACT,CAAC;IAED,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC;QAC9B,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC;QAC1C,MAAM,IAAI,GAAG,aAAa,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC;QAEzD,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,KAAK,CAAC,yBAAyB,IAAI,EAAE,CAAC,CAAC,CAAC;QACvD,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,YAAY,MAAM,CAAC,KAAK,gBAAgB,MAAM,CAAC,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;IACxF,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,GAAG,CAAC,YAAY,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC;QACtF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;AACH,CAAC"}
@@ -0,0 +1,13 @@
1
+ interface CiOptions {
2
+ failOn: string;
3
+ minConfidence: string;
4
+ minScore: string;
5
+ baseline?: string;
6
+ maxNewCritical: string;
7
+ maxNewHigh?: string;
8
+ format: string;
9
+ output?: string;
10
+ }
11
+ export declare function cmdCi(opts: CiOptions): Promise<void>;
12
+ export {};
13
+ //# sourceMappingURL=ci.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ci.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/ci.ts"],"names":[],"mappings":"AAQA,UAAU,SAAS;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE,MAAM,CAAC;IACvB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,wBAAsB,KAAK,CAAC,IAAI,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,CAiG1D"}
@@ -0,0 +1,91 @@
1
+ import { writeFileSync } from "node:fs";
2
+ import pc from "picocolors";
3
+ import { runScan } from "../../engine/run.js";
4
+ import { formatPretty, formatJson } from "../../engine/report.js";
5
+ import { formatSarif } from "../../engine/sarif.js";
6
+ import { loadBaseline, diffBaseline } from "../../engine/baseline.js";
7
+ import { confidenceLevel, severityLevel, parseConfidence, parseSeverity, parseIntOrThrow } from "../../engine/score.js";
8
+ export async function cmdCi(opts) {
9
+ try {
10
+ const rootDir = process.cwd();
11
+ const result = await runScan({ rootDir });
12
+ const minConf = parseConfidence(opts.minConfidence ?? "high");
13
+ const failOnSeverity = parseSeverity(opts.failOn ?? "critical");
14
+ const minScore = parseIntOrThrow(opts.minScore ?? "70", "min-score");
15
+ const maxNewCritical = parseIntOrThrow(opts.maxNewCritical ?? "0", "max-new-critical");
16
+ const maxNewHigh = opts.maxNewHigh !== undefined ? parseIntOrThrow(opts.maxNewHigh, "max-new-high") : undefined;
17
+ // Filter findings by confidence for failure evaluation
18
+ const gatedFindings = result.findings.filter((f) => confidenceLevel(f.confidence) >= confidenceLevel(minConf));
19
+ // Check baseline
20
+ let diff;
21
+ if (opts.baseline) {
22
+ const baseline = loadBaseline(opts.baseline);
23
+ if (baseline) {
24
+ diff = diffBaseline(baseline, result);
25
+ }
26
+ }
27
+ // Output report
28
+ let output;
29
+ switch (opts.format) {
30
+ case "json":
31
+ output = formatJson(result);
32
+ break;
33
+ case "sarif":
34
+ output = formatSarif(result);
35
+ break;
36
+ default:
37
+ output = formatPretty(result, diff);
38
+ }
39
+ if (opts.output) {
40
+ writeFileSync(opts.output, output);
41
+ }
42
+ console.log(output);
43
+ // Evaluate gates
44
+ const failures = [];
45
+ // Score gate
46
+ if (result.score < minScore) {
47
+ failures.push(`Score ${result.score} is below minimum ${minScore}`);
48
+ }
49
+ // Severity gate: any findings at or above fail-on severity with sufficient confidence
50
+ const failingSeverities = gatedFindings.filter((f) => severityLevel(f.severity) >= severityLevel(failOnSeverity));
51
+ if (failingSeverities.length > 0) {
52
+ failures.push(`${failingSeverities.length} finding(s) at ${failOnSeverity} or above (${minConf}+ confidence)`);
53
+ }
54
+ // New findings gate (baseline)
55
+ if (diff) {
56
+ const newCritical = diff.newFindings.filter((f) => f.severity === "critical").length;
57
+ const newHigh = diff.newFindings.filter((f) => f.severity === "high").length;
58
+ if (newCritical > maxNewCritical) {
59
+ failures.push(`${newCritical} new critical finding(s) exceeds max ${maxNewCritical}`);
60
+ }
61
+ if (maxNewHigh !== undefined && newHigh > maxNewHigh) {
62
+ failures.push(`${newHigh} new high finding(s) exceeds max ${maxNewHigh}`);
63
+ }
64
+ }
65
+ if (failures.length > 0) {
66
+ console.log(pc.red("\n CI FAILED:"));
67
+ for (const f of failures) {
68
+ console.log(pc.red(` - ${f}`));
69
+ }
70
+ // Show specific rule IDs + files that triggered the failure
71
+ if (failingSeverities.length > 0) {
72
+ console.log("");
73
+ console.log(pc.dim(" Failing findings:"));
74
+ for (const f of failingSeverities) {
75
+ const loc = f.line ? `:${f.line}` : "";
76
+ console.log(` ${pc.red(f.ruleId)} ${pc.dim(`(${f.severity})`)} ${pc.dim(f.file + loc)}`);
77
+ }
78
+ }
79
+ console.log("");
80
+ process.exit(1);
81
+ }
82
+ else {
83
+ console.log(pc.green("\n CI PASSED\n"));
84
+ }
85
+ }
86
+ catch (err) {
87
+ console.error(pc.red(` Error: ${err instanceof Error ? err.message : String(err)}`));
88
+ process.exit(1);
89
+ }
90
+ }
91
+ //# sourceMappingURL=ci.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ci.js","sourceRoot":"","sources":["../../../src/cli/commands/ci.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AACxC,OAAO,EAAE,MAAM,YAAY,CAAC;AAC5B,OAAO,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAC;AAC9C,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AAClE,OAAO,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AACpD,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AACtE,OAAO,EAAE,eAAe,EAAE,aAAa,EAAE,eAAe,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAaxH,MAAM,CAAC,KAAK,UAAU,KAAK,CAAC,IAAe;IACzC,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC;QAC9B,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC;QAE1C,MAAM,OAAO,GAAG,eAAe,CAAC,IAAI,CAAC,aAAa,IAAI,MAAM,CAAC,CAAC;QAC9D,MAAM,cAAc,GAAG,aAAa,CAAC,IAAI,CAAC,MAAM,IAAI,UAAU,CAAC,CAAC;QAChE,MAAM,QAAQ,GAAG,eAAe,CAAC,IAAI,CAAC,QAAQ,IAAI,IAAI,EAAE,WAAW,CAAC,CAAC;QACrE,MAAM,cAAc,GAAG,eAAe,CAAC,IAAI,CAAC,cAAc,IAAI,GAAG,EAAE,kBAAkB,CAAC,CAAC;QACvF,MAAM,UAAU,GAAG,IAAI,CAAC,UAAU,KAAK,SAAS,CAAC,CAAC,CAAC,eAAe,CAAC,IAAI,CAAC,UAAU,EAAE,cAAc,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;QAEhH,uDAAuD;QACvD,MAAM,aAAa,GAAG,MAAM,CAAC,QAAQ,CAAC,MAAM,CAC1C,CAAC,CAAC,EAAE,EAAE,CAAC,eAAe,CAAC,CAAC,CAAC,UAAU,CAAC,IAAI,eAAe,CAAC,OAAO,CAAC,CACjE,CAAC;QAEF,iBAAiB;QACjB,IAAI,IAAI,CAAC;QACT,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;YAClB,MAAM,QAAQ,GAAG,YAAY,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;YAC7C,IAAI,QAAQ,EAAE,CAAC;gBACb,IAAI,GAAG,YAAY,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;YACxC,CAAC;QACH,CAAC;QAED,gBAAgB;QAChB,IAAI,MAAc,CAAC;QACnB,QAAQ,IAAI,CAAC,MAAM,EAAE,CAAC;YACpB,KAAK,MAAM;gBACT,MAAM,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC;gBAC5B,MAAM;YACR,KAAK,OAAO;gBACV,MAAM,GAAG,WAAW,CAAC,MAAM,CAAC,CAAC;gBAC7B,MAAM;YACR;gBACE,MAAM,GAAG,YAAY,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;QACxC,CAAC;QAED,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;YAChB,aAAa,CAAC,IAAI,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QACrC,CAAC;QACD,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QAEpB,iBAAiB;QACjB,MAAM,QAAQ,GAAa,EAAE,CAAC;QAE9B,aAAa;QACb,IAAI,MAAM,CAAC,KAAK,GAAG,QAAQ,EAAE,CAAC;YAC5B,QAAQ,CAAC,IAAI,CAAC,SAAS,MAAM,CAAC,KAAK,qBAAqB,QAAQ,EAAE,CAAC,CAAC;QACtE,CAAC;QAED,sFAAsF;QACtF,MAAM,iBAAiB,GAAG,aAAa,CAAC,MAAM,CAC5C,CAAC,CAAC,EAAE,EAAE,CAAC,aAAa,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,aAAa,CAAC,cAAc,CAAC,CAClE,CAAC;QACF,IAAI,iBAAiB,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACjC,QAAQ,CAAC,IAAI,CAAC,GAAG,iBAAiB,CAAC,MAAM,kBAAkB,cAAc,cAAc,OAAO,eAAe,CAAC,CAAC;QACjH,CAAC;QAED,+BAA+B;QAC/B,IAAI,IAAI,EAAE,CAAC;YACT,MAAM,WAAW,GAAG,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,UAAU,CAAC,CAAC,MAAM,CAAC;YACrF,MAAM,OAAO,GAAG,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,MAAM,CAAC,CAAC,MAAM,CAAC;YAE7E,IAAI,WAAW,GAAG,cAAc,EAAE,CAAC;gBACjC,QAAQ,CAAC,IAAI,CAAC,GAAG,WAAW,wCAAwC,cAAc,EAAE,CAAC,CAAC;YACxF,CAAC;YACD,IAAI,UAAU,KAAK,SAAS,IAAI,OAAO,GAAG,UAAU,EAAE,CAAC;gBACrD,QAAQ,CAAC,IAAI,CAAC,GAAG,OAAO,oCAAoC,UAAU,EAAE,CAAC,CAAC;YAC5E,CAAC;QACH,CAAC;QAED,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACxB,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAC,CAAC;YACtC,KAAK,MAAM,CAAC,IAAI,QAAQ,EAAE,CAAC;gBACzB,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC,CAAC;YACpC,CAAC;YAED,4DAA4D;YAC5D,IAAI,iBAAiB,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBACjC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;gBAChB,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,qBAAqB,CAAC,CAAC,CAAC;gBAC3C,KAAK,MAAM,CAAC,IAAI,iBAAiB,EAAE,CAAC;oBAClC,MAAM,GAAG,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;oBACvC,OAAO,CAAC,GAAG,CAAC,OAAO,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,QAAQ,GAAG,CAAC,IAAI,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,GAAG,GAAG,CAAC,EAAE,CAAC,CAAC;gBAC9F,CAAC;YACH,CAAC;YAED,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;YAChB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;aAAM,CAAC;YACN,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,KAAK,CAAC,iBAAiB,CAAC,CAAC,CAAC;QAC3C,CAAC;IACH,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,GAAG,CAAC,YAAY,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC;QACtF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;AACH,CAAC"}
@@ -0,0 +1,2 @@
1
+ export declare function cmdExplain(ruleId: string): Promise<void>;
2
+ //# sourceMappingURL=explain.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"explain.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/explain.ts"],"names":[],"mappings":"AAGA,wBAAsB,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAoB9D"}
@@ -0,0 +1,20 @@
1
+ import pc from "picocolors";
2
+ import { RULE_REGISTRY } from "../../rules/index.js";
3
+ export async function cmdExplain(ruleId) {
4
+ const rule = RULE_REGISTRY.find((r) => r.id === ruleId || r.id === ruleId.toUpperCase());
5
+ if (!rule) {
6
+ console.error(pc.red(` Unknown rule: ${ruleId}`));
7
+ console.error(pc.dim(` Run \`shipguard rules\` to see available rules.`));
8
+ process.exit(1);
9
+ }
10
+ console.log(`\n ${pc.bold(rule.id)}`);
11
+ console.log(` ${rule.name}`);
12
+ console.log(` Default severity: ${rule.defaultSeverity}`);
13
+ console.log("");
14
+ console.log(` ${rule.description}`);
15
+ console.log("");
16
+ console.log(` ${pc.dim("How it works:")}`);
17
+ console.log(` ${rule.docs}`);
18
+ console.log("");
19
+ }
20
+ //# sourceMappingURL=explain.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"explain.js","sourceRoot":"","sources":["../../../src/cli/commands/explain.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,YAAY,CAAC;AAC5B,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAErD,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,MAAc;IAC7C,MAAM,IAAI,GAAG,aAAa,CAAC,IAAI,CAC7B,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,MAAM,IAAI,CAAC,CAAC,EAAE,KAAK,MAAM,CAAC,WAAW,EAAE,CACxD,CAAC;IAEF,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,GAAG,CAAC,mBAAmB,MAAM,EAAE,CAAC,CAAC,CAAC;QACnD,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,GAAG,CAAC,mDAAmD,CAAC,CAAC,CAAC;QAC3E,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,OAAO,CAAC,GAAG,CAAC,OAAO,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;IACvC,OAAO,CAAC,GAAG,CAAC,KAAK,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC;IAC9B,OAAO,CAAC,GAAG,CAAC,uBAAuB,IAAI,CAAC,eAAe,EAAE,CAAC,CAAC;IAC3D,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAChB,OAAO,CAAC,GAAG,CAAC,KAAK,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC;IACrC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAChB,OAAO,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC,GAAG,CAAC,eAAe,CAAC,EAAE,CAAC,CAAC;IAC5C,OAAO,CAAC,GAAG,CAAC,KAAK,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC;IAC9B,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;AAClB,CAAC"}
@@ -0,0 +1,7 @@
1
+ interface InitOptions {
2
+ force?: boolean;
3
+ dryRun?: boolean;
4
+ }
5
+ export declare function cmdInit(opts: InitOptions): Promise<void>;
6
+ export {};
7
+ //# sourceMappingURL=init.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/init.ts"],"names":[],"mappings":"AASA,UAAU,WAAW;IACnB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,wBAAsB,OAAO,CAAC,IAAI,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CA8E9D"}
@@ -0,0 +1,91 @@
1
+ import pc from "picocolors";
2
+ import { findConfigFile, writeDefaultConfig } from "../../engine/config.js";
3
+ import { runScan } from "../../engine/run.js";
4
+ import { scoreStatus } from "../../engine/score.js";
5
+ import { readDeps } from "../../next/deps.js";
6
+ import { detectNextAppRouter } from "../../next/detect.js";
7
+ import { existsSync } from "node:fs";
8
+ import path from "node:path";
9
+ export async function cmdInit(opts) {
10
+ const rootDir = process.cwd();
11
+ // 1. Detect framework
12
+ const det = detectNextAppRouter(rootDir);
13
+ if (!det.ok) {
14
+ console.error(pc.red(`\n Shipguard v1 requires a Next.js App Router project.`));
15
+ console.error(pc.dim(` Reason: ${det.reason}`));
16
+ console.error(pc.dim(` Make sure you're in the project root with package.json and app/ directory.\n`));
17
+ process.exit(1);
18
+ }
19
+ console.log(pc.green(" Detected Next.js App Router"));
20
+ // 2. Detect dependencies and print what we found
21
+ const deps = readDeps(rootDir);
22
+ const detected = ["next-app-router"];
23
+ if (deps.hasNextAuth)
24
+ detected.push("next-auth");
25
+ if (deps.hasClerk)
26
+ detected.push("clerk");
27
+ if (deps.hasSupabase)
28
+ detected.push("supabase");
29
+ if (deps.hasPrisma)
30
+ detected.push("prisma");
31
+ if (deps.hasDrizzle)
32
+ detected.push("drizzle");
33
+ if (deps.hasTrpc)
34
+ detected.push("trpc");
35
+ if (deps.hasUpstashRatelimit)
36
+ detected.push("upstash-ratelimit");
37
+ // Check for middleware
38
+ const hasMiddleware = existsSync(path.join(rootDir, "middleware.ts"))
39
+ || existsSync(path.join(rootDir, "middleware.js"))
40
+ || existsSync(path.join(rootDir, "src/middleware.ts"))
41
+ || existsSync(path.join(rootDir, "src/middleware.js"));
42
+ if (hasMiddleware)
43
+ detected.push("middleware.ts");
44
+ console.log(pc.green(` Detected: ${detected.join(" · ")}`));
45
+ // 3. Write config (idempotent)
46
+ const existingConfig = findConfigFile(rootDir);
47
+ if (existingConfig && !opts.force) {
48
+ console.log(pc.dim(` Found existing config → skipping generation (${path.basename(existingConfig)})`));
49
+ }
50
+ else if (opts.dryRun) {
51
+ console.log(pc.dim(" Would create shipguard.config.json (--dry-run)"));
52
+ }
53
+ else {
54
+ writeDefaultConfig(rootDir, { force: Boolean(opts.force) });
55
+ console.log(pc.green(" Created shipguard.config.json"));
56
+ }
57
+ // 4. Run scan
58
+ console.log(pc.dim("\n Running scan..."));
59
+ try {
60
+ const result = await runScan({ rootDir });
61
+ const status = scoreStatus(result.score);
62
+ const scoreColor = status === "PASS" ? pc.green : status === "WARN" ? pc.yellow : pc.red;
63
+ console.log(`\n Shipguard Score: ${scoreColor(String(result.score))} ${scoreColor(status)}`);
64
+ if (result.findings.length === 0) {
65
+ console.log(pc.green(" No findings — looking good!"));
66
+ }
67
+ else {
68
+ // Show top 5 findings
69
+ const top = result.findings.slice(0, 5);
70
+ for (const f of top) {
71
+ const loc = f.line ? `:${f.line}` : "";
72
+ const conf = pc.dim(`(${f.confidence})`);
73
+ console.log(` ${pc.red(f.ruleId)} ${conf} ${pc.dim(f.file + loc)}`);
74
+ }
75
+ if (result.findings.length > 5) {
76
+ console.log(pc.dim(` ... and ${result.findings.length - 5} more`));
77
+ }
78
+ }
79
+ // Next steps
80
+ console.log(pc.dim("\n Next:"));
81
+ console.log(pc.dim(" shipguard baseline --write Save current state as baseline"));
82
+ console.log(pc.dim(" shipguard explain <RULE> Learn about a specific rule"));
83
+ console.log(pc.dim(" shipguard ci Run in CI mode"));
84
+ console.log("");
85
+ }
86
+ catch (err) {
87
+ console.error(pc.red(` Scan failed: ${err instanceof Error ? err.message : String(err)}`));
88
+ process.exit(1);
89
+ }
90
+ }
91
+ //# sourceMappingURL=init.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"init.js","sourceRoot":"","sources":["../../../src/cli/commands/init.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export declare function cmdRules(): Promise<void>;
2
+ //# sourceMappingURL=rules.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rules.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/rules.ts"],"names":[],"mappings":"AAGA,wBAAsB,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC,CAW9C"}
@@ -0,0 +1,13 @@
1
+ import pc from "picocolors";
2
+ import { RULE_REGISTRY } from "../../rules/index.js";
3
+ export async function cmdRules() {
4
+ console.log("\n Shipguard Rules (v1)\n");
5
+ for (const rule of RULE_REGISTRY) {
6
+ const severityColor = rule.defaultSeverity === "critical" ? pc.red : pc.yellow;
7
+ console.log(` ${pc.bold(rule.id)} ${severityColor(`[${rule.defaultSeverity}]`)}`);
8
+ console.log(` ${pc.dim(rule.description)}`);
9
+ console.log("");
10
+ }
11
+ console.log(pc.dim(" Run `shipguard explain <RULE>` for full details.\n"));
12
+ }
13
+ //# sourceMappingURL=rules.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rules.js","sourceRoot":"","sources":["../../../src/cli/commands/rules.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,YAAY,CAAC;AAC5B,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAErD,MAAM,CAAC,KAAK,UAAU,QAAQ;IAC5B,OAAO,CAAC,GAAG,CAAC,4BAA4B,CAAC,CAAC;IAE1C,KAAK,MAAM,IAAI,IAAI,aAAa,EAAE,CAAC;QACjC,MAAM,aAAa,GAAG,IAAI,CAAC,eAAe,KAAK,UAAU,CAAC,CAAC,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,MAAM,CAAC;QAC/E,OAAO,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,aAAa,CAAC,IAAI,IAAI,CAAC,eAAe,GAAG,CAAC,EAAE,CAAC,CAAC;QACnF,OAAO,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC;QAC7C,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAClB,CAAC;IAED,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,sDAAsD,CAAC,CAAC,CAAC;AAC9E,CAAC"}
@@ -0,0 +1,10 @@
1
+ interface ScanOptions {
2
+ format: string;
3
+ output?: string;
4
+ only?: string;
5
+ exclude?: string;
6
+ minConfidence?: string;
7
+ }
8
+ export declare function cmdScan(opts: ScanOptions): Promise<void>;
9
+ export {};
10
+ //# sourceMappingURL=scan.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scan.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/scan.ts"],"names":[],"mappings":"AASA,UAAU,WAAW;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,wBAAsB,OAAO,CAAC,IAAI,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAsD9D"}
@@ -0,0 +1,55 @@
1
+ import { writeFileSync } from "node:fs";
2
+ import pc from "picocolors";
3
+ import { runScan } from "../../engine/run.js";
4
+ import { formatPretty, formatJson } from "../../engine/report.js";
5
+ import { formatSarif } from "../../engine/sarif.js";
6
+ import { computeScore, summarizeFindings, confidenceLevel, parseConfidence } from "../../engine/score.js";
7
+ export async function cmdScan(opts) {
8
+ try {
9
+ const rootDir = process.cwd();
10
+ // Build config overrides from CLI flags
11
+ const configOverrides = {};
12
+ if (opts.only) {
13
+ const onlyRules = opts.only.split(",").map((r) => r.trim().toUpperCase());
14
+ const rules = {};
15
+ for (const ruleId of onlyRules) {
16
+ rules[ruleId] = { severity: "critical" };
17
+ }
18
+ configOverrides.rules = rules;
19
+ }
20
+ const additionalExclude = opts.exclude
21
+ ? opts.exclude.split(",").map((g) => g.trim())
22
+ : undefined;
23
+ const result = await runScan({ rootDir, configOverrides, additionalExclude });
24
+ // Filter by confidence if specified, recalculate score and summary
25
+ if (opts.minConfidence) {
26
+ const minConf = parseConfidence(opts.minConfidence);
27
+ result.findings = result.findings.filter((f) => confidenceLevel(f.confidence) >= confidenceLevel(minConf));
28
+ result.score = computeScore(result.findings);
29
+ const counts = summarizeFindings(result.findings);
30
+ result.summary = { total: result.findings.length, ...counts, waived: result.summary.waived };
31
+ }
32
+ let output;
33
+ switch (opts.format) {
34
+ case "json":
35
+ output = formatJson(result);
36
+ break;
37
+ case "sarif":
38
+ output = formatSarif(result);
39
+ break;
40
+ default:
41
+ output = formatPretty(result);
42
+ }
43
+ if (opts.output) {
44
+ writeFileSync(opts.output, output);
45
+ }
46
+ else {
47
+ console.log(output);
48
+ }
49
+ }
50
+ catch (err) {
51
+ console.error(pc.red(` Error: ${err instanceof Error ? err.message : String(err)}`));
52
+ process.exit(1);
53
+ }
54
+ }
55
+ //# sourceMappingURL=scan.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scan.js","sourceRoot":"","sources":["../../../src/cli/commands/scan.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ interface WaiveOptions {
2
+ file: string;
3
+ reason: string;
4
+ expiry?: string;
5
+ }
6
+ export declare function cmdWaive(ruleId: string, opts: WaiveOptions): Promise<void>;
7
+ export {};
8
+ //# sourceMappingURL=waive.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"waive.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/waive.ts"],"names":[],"mappings":"AAIA,UAAU,YAAY;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,wBAAsB,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CA+BhF"}