Skip to content

Pattern engine (structural layer)

Source: packages/core/src/patterns/, packages/cli/src/commands/patterns.ts, packages/cli/src/render-patterns.ts · Spec: §8.1

beetl patterns [--since <iso>] [--format md|json] [--no-cache]

Section titled “beetl patterns [--since <iso>] [--format md|json] [--no-cache]”

Pure computation over session fields — no LLM (synthesis is M3 §8.2). Output is deliberately authorless: it mines code and category patterns, not people (author analysis is beetl blame).

Report sections:

  • Hotspot files/modules — sessions per file (from diagnosis.filesImplicated, line refs stripped, plus fix.filesChanged) and per containing directory. Each hotspot carries a weight: sum of severity weight (blocker 2 / high 1.5 / medium 1 / low 0.5, default 1) × recency decay (90-day half-life on report.reportedAt). Top 20.
  • Category × surface heat map — session counts per cell; missing values bucket as (uncategorized) / (unspecified).
  • Refix chainsfix.refixOf lineage walked leaf → root; “fix of X re-fixed N times” is the strongest single root-cause signal. Chains survive a root record missing from the window.
  • Tempo — sessions per yyyy-mm month of report time.
  • Threshold nudges — config rules (config.nudges.rules, default 3+ sessions same file in 60d) evaluated over the report window. They also fire on the create path (beetl new and file-complete), filtered to keys the new session contributes: warnings on stderr for humans, a nudges array in the --json payload for agents.

beetl log [--module <path>] [--author <who>] [--refixes]

Section titled “beetl log [--module <path>] [--author <who>] [--refixes]”

Failure-history views (spec §5), oldest first, from packages/core/src/patterns/log.ts + packages/cli/src/commands/log.ts:

  • --module filters by path prefix over file keys and surface (boundary-aware: src/ui matches src/ui/login.tsx, not src/ui2).
  • --author is a case-insensitive substring over formatted actors (reportedBy, verifiedBy, event actors) and attribution.introducedBy/fixedBy authors.
  • --refixes switches to refix lineage views: each fix.refixOf chain with resolved titles (null when a chain member is missing from the store), filtered to chains touching the --module/--author selection.

JSON shapes: { count, entries } / { count, chains } — snapshot-tested.

  • Analytics edge handling (M-12): nudge relevance is scope-typed (file:/surface:/category:-qualified keys — a surface named like a file path can’t satisfy a file rule); since windows compare parsed instants so timezone offsets filter correctly; file keys are normalized at ingestion (\/, case-folded on case-insensitive platforms).
  • All markdown surfaces (pattern tables, cluster packs, GitHub issue bodies, recorded blame reports) escape session-derived text via the shared escapeMd/mdTableCell helpers (core markdown.ts, L-02/M-12) — pipes, backticks, emphasis, links, and HTML are neutralized; snapshot-tested with adversarial strings.
  • --format md (default for humans) renders a markdown report; --json / --format json emits { fromCache, report } — snapshot-tested contract.
  • Reports cache under .beetl/.cache/patterns/structural.json. Validity is a sha256 digest of actual session file bytes (not size/mtime) plus a key covering the option set, the nudge config, and a UTC-day expiry bucket — so config flips, same-size replacements, and window/recency aging all invalidate (H-02). Cache reads/writes are best-effort; --no-cache bypasses.

The structural layer selects clusters worth attention — top hotspot files (≥2 sessions), refix chains, densest category×surface cells (≥2), up to 3 per kind, 8 sessions per pack (packages/core/src/patterns/synthesize.ts). Each pack carries member diagnoses (root cause, mechanism, files, rejected hypotheses, refix links) — no author data.

  • With a runner: one synthesis call over all packs → root-cause hypothesis report written to .beetl/reports/<date>-patterns.md (committed; -2, -3… suffixes avoid clobbering) and printed. JSON: { kind: "report", clusters, path, markdown }.
  • No runner: the packs are emitted as a markdown prompt document to paste into any model. JSON: { kind: "prompt", clusters, prompt }.
  • Nothing clusters yet: { kind: "empty", clusters: 0 }.

beetl diagnose <ref> [--runner claude|codex|api|none] [--dry-run] (§9)

Section titled “beetl diagnose <ref> [--runner claude|codex|api|none] [--dry-run] (§9)”

Runner-driven diagnosis (packages/cli/src/commands/diagnose.ts): builds a prompt from the session report + a git ls-files repo context, asks the runner for structured findings (reproduction, diagnosis with required rootCause + filesImplicated), then records them through the same core mutations a human would use (applyUpdates → attribution capture → gated move to diagnosing, staying put if gates are unmet). Terminal sessions are refused. --dry-run prints the exact prompt; with no runner the prompt is the outcome. JSON kinds: dry-run | prompt | findings.

"nudges": {
"enabled": true,
"rules": [{ "scope": "file", "count": 3, "days": 60 }] // scope: file | surface | category
}