SigilixSigilix

Examples

Six reviews. Real findings. Zero hallucinations.

Every example below is reproduced from a real Sigilix review on internal and shadow-mode repositories. Names and identifiers are anonymized. Findings are verbatim. Read each one for the same density a Sigilix review delivers on your PRs.

01TypeScript / Postgres · case study

Catching a SQL injection a single-agent missed

A pull request adds a search endpoint. The author tested manually with safe inputs. A single-agent reviewer flagged style. Warden flagged the actual vulnerability.

diff
// src/api/search.ts
+ app.get("/search", async (req, res) => {
+   const term = req.query.q as string
+   const sql = `SELECT id, title FROM docs WHERE title LIKE '%${term}%'`
+   const { rows } = await pg.query(sql)
+   res.json(rows)
+ })
Warden·Security · SQL injectioncritical
req.query.q is concatenated directly into the SQL string on line 3. Any client can pass '%' OR 1=1; -- and dump the table. Use the parameterized form already established in authService.ts:pg.query(`SELECT id, title FROM docs WHERE title LIKE $1`, [`%${term}%`])
Spark·Performance · indexwarning
Even after parameterizing, this LIKE '%term%' with a leading wildcard can't use a B-tree index. For the docs table's ~120K rows this is fine; if it grows past 1M, switch to a pg_trgm GIN index or a dedicated FTS column.

Takeaway

A single-agent reviewer caught nothing critical here. Warden owns security; the bug surfaces in the first pass — not the third revision.

02TypeScript / Prisma · case study

An N+1 hiding behind a clean async/await

Looks idiomatic. Reads cleanly. Ships a 200-row API request as 201 round-trips to the database.

diff
// src/services/billing.ts
+ async function getInvoiceLines(invoices: Invoice[]) {
+   return Promise.all(
+     invoices.map(async (inv) => ({
+       ...inv,
+       lines: await prisma.invoiceLine.findMany({
+         where: { invoiceId: inv.id },
+       }),
+     })),
+   )
+ }
Spark·Performance · N+1 querycritical
Each invoice triggers its own findMany. For 200 invoices that's 200 round-trips on top of the parent fetch. Promise.all parallelizes, but the database still serializes them through the connection pool. Replace with a single batched query:const ids = invoices.map((i) => i.id) const lines = await prisma.invoiceLine.findMany({ where: { invoiceId: { in: ids } }, }) const byInvoice = groupBy(lines, "invoiceId")
Weave·Semantics · naminginfo
getInvoiceLines implies it returns lines, but it returns invoices with an embedded lines field. Rename to attachLinesToInvoices to match the return shape.

Takeaway

The diff looks ‘clean,’ which is exactly why generalist reviewers miss it. Spark's only job is performance — N+1s are its bread and butter.

03TypeScript · case study

Spotting dead code that the type system endorses

A refactor moves auth logic into a new module. The old helper is still exported, still type-checks, and is now invoked nowhere. Glyph notices.

diff
// src/auth/legacy.ts (left in place)
  export function verifySessionLegacy(token: string) {
    /* 40 lines, no callers in the new build */
  }

// src/auth/index.ts
  export { verifySession } from "./session"
Glyph·Architecture · dead codewarning
verifySessionLegacy is exported from src/auth/legacy.ts but has no remaining callers in the repo (verified across src/, tests/, and scripts/). The new verifySession implements an incompatible session shape — leaving the legacy helper around guarantees a future contributor will re-import it by accident. Delete the file and remove the export.
Warden·Security · attack surfaceinfo
Dead auth code is also dead-but-reachable attack surface in any future bundle that mistakenly tree-shakes loosely. Removing it shrinks the worry list.

Takeaway

Type checkers and linters keep this code green. Glyph reads the dependency graph and asks ‘who actually calls this?’ — a question single-file linters can't.

04TypeScript · case study

A broken interface contract no test covered

A shared type was widened by one author, but a downstream consumer assumes the narrow form. The compiler is happy. Production at 3am is not.

diff
// packages/types/src/billing.ts
- export type Plan = "free" | "pro" | "max"
+ export type Plan = string

// apps/dashboard/src/PlanBadge.tsx
  function PlanBadge({ plan }: { plan: Plan }) {
    return BADGES[plan].element  // BADGES is keyed only on the literal union
  }
Weave·Semantics · contract violationcritical
The Plan type was widened from a string-literal union to string in packages/types/src/billing.ts. Two downstream consumers — PlanBadge.tsx and pricingMatrix.ts — assume the narrow form and do unsafe lookups against a record keyed only on the literals. They pass the compiler, but BADGES[plan] returns undefined for any new plan name and crashes the page. Either keep the union and add new variants explicitly, or update both consumers to handle the wider type.
Glyph·Architecture · type ownershipwarning
Loosening a public type in a shared packages/types module is a coordination break, not a refactor. Add a deprecation comment on the old shape and bump the package version. Future maintainers need a migration trail.

Takeaway

Type-aware reviewers catch this; pattern-match generalists don't. Weave traces the contract; Glyph weighs the blast radius.

05TypeScript · case study

A race condition only Core could synthesize

Spark finds a missed cache invalidation. Warden finds a permission check on a stale read. Neither, alone, recognizes the production-class bug. Core does.

diff
// src/services/orders.ts
  async function applyDiscount(orderId: string, code: string) {
    const order = await cache.get(`order:${orderId}`)
+   const discount = await db.discount.findFirst({ where: { code } })
+   if (canApply(order.userId, discount)) {
+     await db.order.update({
+       where: { id: orderId },
+       data: { totalCents: order.totalCents - discount.amount },
+     })
+   }
  }
Spark·Performance · cache stalenesswarning
cache.geton line 2 returns a cached order that may be many seconds old. There's no compare-and-set when writing back on line 7 — the update lands on whatever the row says now, not what the cached value implies.
Warden·Security · stale authorizationwarning
canApply(order.userId, ...) on line 4 trusts the cached userId. If ownership of the order changed since the cache was written, this discount applies under the previous owner's permissions.
Core·Synthesis · race conditioncritical
Spark and Warden each found a partial flaw. Together they describe a real CVE-class race: a transferred order, with a stale cache entry, can have a discount applied under the previous owner's permissions and against the previous total. Either the cache read must be invalidated on transfer or the update must be done in a single transaction that re-checks ownership and balance against the live row. Recommended: drop the cache for write paths and use a SELECT ... FOR UPDATE on orders inside the same transaction as the write.

Takeaway

Single-agent reviewers report symptoms. Core synthesizes the disease. The Critical only exists once two specialists' partial findings are stitched together.

06package.json + lockfile · case study

A supply-chain bump that quietly downgraded a dep

A bot opens a routine `lodash` patch bump. The lockfile churn looks minor. Warden reads what actually resolves.

diff
// package.json
- "lodash": "4.17.20",
+ "lodash": "4.17.21",

// package-lock.json (excerpt)
  "lodash.merge": {
-   "version": "4.17.21"
+   "version": "3.6.1"  // pulled by some-legacy-tool@2.1.0
  },
- "minimatch": "9.0.3",  // explicit resolution removed
+ // (now resolved transitively to 7.4.6 via glob@8)
Warden·Security · CVE re-introducedcritical
lodash bumped to 4.17.21, but the lockfile now resolves lodash.merge@3.6.1 via a transitive of some-legacy-tool@2.1.0. CVE-2021-23337 applies to lodash.merge<4.17.21. The bump did not propagate to the transitive tree. Pin lodash.merge explicitly to >=4.17.21 or replace some-legacy-tool.
Warden·Security · resolution driftwarning
Removed the explicit minimatch@9.0.3 resolution, which was previously forcing a safe version over glob@8's vulnerable range. The updated lockfile now allows minimatch@7.4.6 (affected). Re-add the explicit resolution until glob is upgraded.

Takeaway

‘Routine bump’ is the most dangerous PR title in any monorepo. Warden reads the resolved tree, not the package.json delta.

Sigilix doesn't just find bugs. It finds the interaction between bugs.

These six reviews are representative density. Most PRs yield 3–6 actionable findings and zero hallucinations after Core filters.

Last updated 2026-05-04