Pick any line to see why it is the way it is: the commit, the pull request and issue it came from, and what the agent was thinking.
| Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar | 1 | import { FileSearch } from "lucide-react"; |
| 2 | import { Form, useNavigation, useSearchParams } from "react-router"; | |
| 3 | ||
| 4 | import type { AlertState, Severity } from "@g1t/contracts"; | |
| 5 | ||
| 6 | import type { Route } from "./+types/security-code"; | |
| 7 | import { page } from "../../lib/meta"; | |
| 8 | import { StateFilter } from "../../components/security"; | |
| 9 | import { ActivationPrompt, CARD, CodeAlertItem, FilterSelect, LIST, SectionHeader } from "../../components/security-suite"; | |
| 10 | import { TimeAgo } from "../../components/ui"; | |
| The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after. | 11 | import { Button } from "../../components/ui/button"; |
| 12 | import { Card } from "../../components/ui/card"; | |
| 13 | import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "../../components/ui/table"; | |
| Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar | 14 | import { securitySuite } from "../../lib/services.server"; |
| Merge main (membership, two-factor, GitHub repo roles) into tokens | 15 | import { getViewer, managesSecurity, requireUser, unwrap } from "../../lib/session.server"; |
| Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar | 16 | import { requireInsider } from "../../lib/access.server"; |
| Pricing says it plainly: models at the provider's price, the agent rate for what g1t runs around every model call (the gateway, secrets, routing, context and pass-through to your own provider, so your own keys too), everything else at cost plus 20%, your own runners free, no seats; Security and quality comes with the plan with no separate fee, and live activations end. Each agent has an effort setting, Auto to Max, with what a typical task has cost at each level, and Spend's Spend less, keep quality suggests a lower level only when the agent's own past work shows quality held, to apply or dismiss. The pricing, spend and agents guides say how. | 17 | import { planPrice } from "../../lib/security-suite.server"; |
| Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar | 18 | import { codeFilters, countStates, keepCode } from "../../lib/security-suite"; |
| 19 | ||
| 20 | export function meta({ params, ...args }: Route.MetaArgs) { | |
| 21 | return page(args, { title: `Code scanning · ${params.owner}/${params.repo} · g1t` }); | |
| 22 | } | |
| 23 | ||
| 24 | export async function loader({ params, context, request }: Route.LoaderArgs) { | |
| 25 | const viewer = getViewer(context) ?? requireUser(context, request); | |
| Merge main (membership, two-factor, GitHub repo roles) into tokens | 26 | const { access } = await requireInsider(context, params, "security_alerts"); |
| Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar | 27 | const repo = { namespace: params.owner, name: params.repo }; |
| Pricing says it plainly: models at the provider's price, the agent rate for what g1t runs around every model call (the gateway, secrets, routing, context and pass-through to your own provider, so your own keys too), everything else at cost plus 20%, your own runners free, no seats; Security and quality comes with the plan with no separate fee, and live activations end. Each agent has an effort setting, Auto to Max, with what a typical task has cost at each level, and Spend's Spend less, keep quality suggests a lower level only when the agent's own past work shows quality held, to apply or dismiss. The pricing, spend and agents guides say how. | 28 | const [scanning, price] = await Promise.all([securitySuite.codeScanning(repo, viewer), planPrice(params.owner, viewer)]); |
| Merge main (membership, two-factor, GitHub repo roles) into tokens | 29 | return { scanning: unwrap(scanning), price, can: access.can, owner: managesSecurity(viewer, params.owner) }; |
| Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar | 30 | } |
| 31 | ||
| 32 | const SEVERITY_OPTIONS: [string, string][] = [ | |
| 33 | ["all", "Any severity"], | |
| 34 | ["critical", "Critical"], | |
| 35 | ["high", "High"], | |
| 36 | ["medium", "Medium"], | |
| 37 | ["low", "Low"], | |
| 38 | ]; | |
| 39 | ||
| 40 | export default function CodeScanning({ loaderData, params }: Route.ComponentProps) { | |
| 41 | const { scanning, price, can, owner } = loaderData; | |
| 42 | const base = `/${params.owner}/${params.repo}`; | |
| 43 | const [search, setSearch] = useSearchParams(); | |
| 44 | const navigation = useNavigation(); | |
| 45 | const filters = codeFilters(search); | |
| 46 | const tools = [...new Set(scanning.alerts.map((alert) => alert.tool))].sort(); | |
| 47 | const counts = countStates(scanning.alerts.filter((alert) => keepCode(alert, { ...filters, state: alert.state }))); | |
| 48 | const shown = scanning.alerts.filter((alert) => keepCode(alert, filters)); | |
| 49 | const set = (key: string, value: string | null) => { | |
| 50 | const next = new URLSearchParams(search); | |
| 51 | if (value) next.set(key, value); | |
| 52 | else next.delete(key); | |
| 53 | setSearch(next, { replace: true, preventScrollReset: true }); | |
| 54 | }; | |
| 55 | const empty = scanning.alerts.length === 0 && scanning.analyses.length === 0; | |
| 56 | const settingUp = navigation.state !== "idle" && navigation.formAction?.endsWith("/code-scanning/setup"); | |
| 57 | return ( | |
| 58 | <div className="max-w-5xl space-y-8"> | |
| 59 | <SectionHeader | |
| 60 | title="Code scanning" | |
| 61 | about="Results from your static analysis tools, uploaded as SARIF. On the default branch each problem is an alert, fixed when a later analysis no longer reports it. On a pull request, new results on the lines it changes become review comments and the Code scanning check." | |
| 62 | actions={ | |
| 63 | scanning.entitled && can.manage_settings && !scanning.configured ? ( | |
| 64 | <Form method="post" action={`${base}/security/code-scanning/setup`}> | |
| The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after. | 65 | <Button type="submit" disabled={settingUp}> |
| Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar | 66 | {settingUp ? "Opening a pull request…" : "Set up code scanning"} |
| The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after. | 67 | </Button> |
| Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar | 68 | </Form> |
| 69 | ) : null | |
| 70 | } | |
| 71 | /> | |
| 72 | {!scanning.entitled ? ( | |
| 73 | <ActivationPrompt workspace={params.owner} feature="Code scanning" monthlyCents={price} isOwner={owner} /> | |
| 74 | ) : empty ? ( | |
| 75 | <div className={`${CARD} px-6 py-10 text-center`}> | |
| 76 | <FileSearch size={22} className="mx-auto text-accent" /> | |
| 77 | <p className="mt-2 font-medium">No analyses yet</p> | |
| 78 | <p className="mx-auto mt-1 max-w-xl text-sm text-muted"> | |
| 79 | {scanning.configured | |
| 80 | ? "The code scanning workflow is on the default branch; its first run's results show here." | |
| 81 | : "Set up code scanning to open a pull request adding a workflow that scans each language the repository has (Bandit, gosec, ESLint, Clippy) and uploads the results, or upload SARIF from any tool with POST /repos/{owner}/{name}/code-scanning/sarifs."} | |
| 82 | </p> | |
| 83 | </div> | |
| 84 | ) : ( | |
| 85 | <section className="space-y-3"> | |
| 86 | <div className="flex flex-wrap items-end justify-between gap-3"> | |
| 87 | <StateFilter counts={counts} value={filters.state} onChange={(state: AlertState) => set("state", state === "open" ? null : state)} /> | |
| 88 | <div className="flex flex-wrap gap-2"> | |
| 89 | <FilterSelect label="Severity" value={filters.severity ?? "all"} options={SEVERITY_OPTIONS} onChange={(value) => set("severity", value === "all" ? null : (value as Severity))} /> | |
| 90 | <FilterSelect label="Tool" value={filters.tool ?? "all"} options={[["all", "Every tool"], ...tools.map((tool): [string, string] => [tool, tool])]} onChange={(value) => set("tool", value === "all" ? null : value)} /> | |
| 91 | </div> | |
| 92 | </div> | |
| 93 | {shown.length === 0 ? ( | |
| The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after. | 94 | <Card asChild tone="plain" className="border-dashed px-4 py-6 text-sm text-muted"> |
| 95 | <p>No {filters.state} alerts match.</p> | |
| 96 | </Card> | |
| Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar | 97 | ) : ( |
| 98 | <ul className={LIST}> | |
| 99 | {shown.map((alert) => ( | |
| 100 | <CodeAlertItem key={alert.id} alert={alert} base={base} /> | |
| 101 | ))} | |
| 102 | </ul> | |
| 103 | )} | |
| 104 | </section> | |
| 105 | )} | |
| 106 | {scanning.analyses.length > 0 && ( | |
| 107 | <section> | |
| 108 | <h3 className="text-base font-semibold tracking-tight">Recent analyses</h3> | |
| The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after. | 109 | <div className={`${CARD} mt-2`}> |
| 110 | <Table className="min-w-[36rem]"> | |
| 111 | <TableHeader> | |
| 112 | <TableRow> | |
| 113 | <TableHead className="px-4">Tool</TableHead> | |
| 114 | <TableHead>Ref</TableHead> | |
| 115 | <TableHead>Commit</TableHead> | |
| 116 | <TableHead className="text-right">Results</TableHead> | |
| 117 | <TableHead className="text-right">New</TableHead> | |
| 118 | <TableHead className="text-right">Fixed</TableHead> | |
| 119 | <TableHead>When</TableHead> | |
| 120 | </TableRow> | |
| 121 | </TableHeader> | |
| 122 | <TableBody className="tabular-nums"> | |
| Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar | 123 | {scanning.analyses.slice(0, 15).map((analysis) => ( |
| The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after. | 124 | <TableRow key={analysis.id}> |
| 125 | <TableCell className="px-4">{analysis.tool}</TableCell> | |
| 126 | <TableCell className="font-mono text-xs"> | |
| Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar | 127 | {analysis.pull != null ? ( |
| 128 | <a href={`${base}/security/pulls/${analysis.pull}`} className="hover:underline"> | |
| 129 | #{analysis.pull} | |
| 130 | </a> | |
| 131 | ) : ( | |
| 132 | analysis.gitRef.replace("refs/heads/", "") | |
| 133 | )} | |
| The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after. | 134 | </TableCell> |
| 135 | <TableCell className="font-mono text-xs">{analysis.commitSha.slice(0, 7)}</TableCell> | |
| 136 | <TableCell className="text-right">{analysis.results}</TableCell> | |
| 137 | <TableCell className="text-right">{analysis.newAlerts}</TableCell> | |
| 138 | <TableCell className="text-right">{analysis.fixedAlerts}</TableCell> | |
| 139 | <TableCell className="text-xs text-muted"> | |
| Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar | 140 | <TimeAgo at={analysis.createdAt} /> |
| The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after. | 141 | </TableCell> |
| 142 | </TableRow> | |
| Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar | 143 | ))} |
| The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after. | 144 | </TableBody> |
| 145 | </Table> | |
| Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar | 146 | </div> |
| 147 | </section> | |
| 148 | )} | |
| 149 | </div> | |
| 150 | ); | |
| 151 | } |
This file's history is long; its oldest lines are credited to the oldest commit read.