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, plusfix.filesChanged) and per containing directory. Each hotspot carries aweight: sum of severity weight (blocker 2 / high 1.5 / medium 1 / low 0.5, default 1) × recency decay (90-day half-life onreport.reportedAt). Top 20. - Category × surface heat map — session counts per cell; missing values
bucket as
(uncategorized)/(unspecified). - Refix chains —
fix.refixOflineage 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-mmmonth of report time. - Threshold nudges — config rules (
config.nudges.rules, default3+ sessions same file in 60d) evaluated over the report window. They also fire on the create path (beetl newandfile-complete), filtered to keys the new session contributes: warnings on stderr for humans, anudgesarray in the--jsonpayload 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:
--modulefilters by path prefix over file keys andsurface(boundary-aware:src/uimatchessrc/ui/login.tsx, notsrc/ui2).--authoris a case-insensitive substring over formatted actors (reportedBy,verifiedBy, event actors) andattribution.introducedBy/fixedByauthors.--refixesswitches to refix lineage views: eachfix.refixOfchain with resolved titles (nullwhen a chain member is missing from the store), filtered to chains touching the--module/--authorselection.
JSON shapes: { count, entries } / { count, chains } — snapshot-tested.
Output & cache
Section titled “Output & cache”- 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);sincewindows 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/mdTableCellhelpers (coremarkdown.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 jsonemits{ 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-cachebypasses.
beetl patterns --synthesize (§8.2)
Section titled “beetl patterns --synthesize (§8.2)”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.
Config
Section titled “Config”"nudges": { "enabled": true, "rules": [{ "scope": "file", "count": 3, "days": 60 }] // scope: file | surface | category}