Repository navigation
v0.0.6
Added
- (views) scoped views — process / thread / time_range pre-filter on create_view
- (summary) hints field nudges callers toward create_view
- (views) keep_only_frames / keep_only_modules inverse-hide filter (#89)
- (views) strip_type_params preset transform (#88)
- (views) regex backreferences in rename replacements
- (views) rule hit counts on create_view and describe_view tool
- (views) stack views by composing transforms across the chain
- (views) collapse_recursion folds multi-function cycles up to length 8
- (tools) list_profiles reports view base id
- (tools) create_view MCP tool for derived profiles
- (registry) create_view derives a session from a base
- (profile) resolved_chain helper applies view transforms
- (profile) Arc-share raw tables and add transforms field
- (profile) introduce Transforms value type for views
- (query) compare_functions side-by-side asm diff (#8)
- (call_tree) surface cross-process aggregation on inverted trees
- (summary) accept process / thread / time_range filter
- (filter) warn when bare-name process= matches multiple pids
- (call_tree) surface child names in pruning markers
- (summary) add top_processes and top_threads breakdown
- (describe) cap describe_profile output and drop idle entries
Fixed
- rustfmt — missing newline before DescribeViewResult struct
- (registry) create_view caches identical views; harden tests
- (error) cap available_* lists, demangle nearest_matches, add module_not_found, route event/regex through invalid_value
- (symbolicate) count hex frames as unsymbolicated; surface per-lib outcomes
- (query) make time_range filter and summary.time_range_ms share a frame
- (query) reject empty function pattern for required/narrowing args
Other
- (release-plz) use action outputs to find release PR branch
- (changelog) record plugin bundle, cookbook, summary hints, regex backrefs
- (readme) document plugin install path alongside MCP-direct install
- extract version via cargo metadata, not grep/sed
- skill description voice, doc cleanup, manifest version guard
- --version, --help, and unknown-arg handling
- prerequisites + pollard-doctor health-check skill
- sync plugin manifest versions in release-plz PR
- marketplace.json and tag-triggered release workflow
- bundle pollard as a Claude Code plugin with skills
- surface cookbook through view_presets MCP tool (may be reverted)
- (views) cookbook of canonical hide_modules / hide_frames regex sets
- (transforms) name the keep-only placeholder inline
- cargo fmt + fix clippy::shadow_unrelated in keep_only test
- (view_stats) cargo fmt
- (matching) cargo fmt
- (view_stats) drop unneeded allow(dead_code)
- (view_stats) cargo fmt
- (spec) tighten create_view cost and lifetime wording
- (spec) document profile views
- (views) end-to-end view re-attributes hidden leaf
- (tools) clarify create_view syntax and collapse_recursion guidance
- (query) folded_stacks consumes resolved_chain
- (query) stacks_containing consumes resolved_chain
- (query) call_tree consumes resolved_chain
- (query) top_functions consumes resolved_chain
- (plan) profile views implementation plan
- Merge pull request #58 from antiguru/measure-rss
- (spec) bring design doc back in sync with shipped surface
- (query) [breaking] unify percentage field naming on {kind}_pct
- Merge pull request #77 from antiguru/claude/fix-issue-68-oPTKI
- (tools) document
re:regex prefix on every function-pattern arg - (call_tree) apply cargo fmt
- Merge pull request #54 from antiguru/issue-52-marker-no-stack
- Merge pull request #53 from antiguru/issue-51-empty-pattern
Fixed
- (symbolicate) don't enable macOS Spotlight on Linux (#56).
SymbolManagerConfig::use_spotlight(true)was unconditional. On Linux that pushed wholesym into a macOS-shaped resolution path that ended in adyld_shared_cache_x86_64read, so every Linux.sofailed symbolication with a stderr line likecould not load symbols for "/usr/lib64/libc-2.28.so": ... /System/Library/dyld/dyld_shared_cache_x86_64 ... No such file or directory. Profiles imported fromperf.datashowed up degraded (top_functions,call_tree,source_for_functionlost names) and the stderr volume drowned other diagnostics. Spotlight is now gated behindcfg!(target_os = "macos"), leaving the macOS dSYM-bundle lookup intact and skipping it entirely on other targets.
Changed
-
breaking (query) unify percentage field naming on
{kind}_pct(#65).
summary.dominant_thread.samples_pct,summary.top_processes[].samples_pct,summary.top_threads[].samples_pct, andsource_for_function[].samples_pctare renamed toself_pctso every percentage field across the surface uses the same{kind}_pctshape (self_pct/total_pct;samplesreserved for cases where there's no self/total distinction at all). "Samples on this thread / process" is self time for that thread / process, and per-line source samples are self time for that line, soself_pctis the natural name and matches whattop_functionsalready exposes. Wire-format break for callers that destructured the old name. The convention is documented next toProcessEntry::self_pctand in the design doc. -
(tools) document the
re:(?i)case-insensitive convention on every function-pattern arg (#67).
Substring matching is case-sensitive (memcpy≠MEMCPY), which surprises during exploration. The regex form already supports the standard(?i)inline flag; the per-arg docs and thesrc/matching.rsmodule header now spell that out so callers don't have to discover it from theregexcrate. The canonical one-liner is nowSubstring match by default; prefix withre:for a regex (usere:(?i)foofor case-insensitive).— same wording at every site so it stays grep-able alongside the #66 line. Default behavior is unchanged. -
(tools) document the
re:regex prefix on every function-pattern arg (#66).
The prefix that flips a function pattern from substring match to regex was discoverable through the skill description, not through per-arg docs — callers without the skill missed the feature, and callers with it had to re-find which args supported it. Every function-pattern arg now carries the same one-line "Substring match by default; prefix withre:for a regex." in itsdescription:top_functions.filter,top_groups.filter,compare_profiles.filter,call_tree.root_function,call_tree.paths_to,folded_stacks.function_filter,stacks_containing.function,source_for_function.function,asm_for_function.function. The wording is identical at every site so it's grep-able.
Added
-
(plugin) bundle pollard as a Claude Code plugin with skills (#92).
.claude-plugin/plugin.jsonregisters the MCP server and.claude-plugin/marketplace.jsonmakes the bundle installable via/plugin marketplace add antiguru/pollard. Three skills ship alongside:profile-recording(samply record /samply importforperf.data/load_profileworkflow with troubleshooting for empty profiles, unsymbolicated frames, andperf.datarecorded without-g),view-presets(cookbook regex sets for tracing-subscriber, tokio internals, and Rust stdlib glue, plus a stacked-view recipe), andpollard-doctor(dependency-ordered diagnostic for binary-not-on-PATH, MCP-server-not-reloaded, empty-profile, and unsymbolicated-frames failure modes with exact remediation per step). Skills surface as/pollard:profile-recording,/pollard:view-presets, and/pollard:pollard-doctorand solve the discoverability gap a plain MCP tool leaves: the cookbook would otherwise require the agent to think to look it up. The plugin bundle does not ship the binary —cargo install pollardis still a prerequisite. README documents both the plugin install path and the legacyclaude mcp addserver-only path. -
(docs) view-presets cookbook of canonical
hide_modules/hide_framesregex sets (#92).
docs/superpowers/specs/2026-05-06-view-presets-cookbook.mdcollects copy-paste regex sets for the three filters users keep re-deriving by hand: tracing-subscriber walls, tokio runtime internals, Rust stdlib glue. Ships as docs (and the mirroredview-presetsskill) rather than apresets=argument oncreate_view, so curated content can drift with upstream crates without baking yesterday's noise filters into the binary. The skill mirrors the cookbook's regex blocks; the design spec records the convention that cookbook is the source of truth and reviewers reject one-sided regex changes. -
(cli)
--version,--help, and unknown-arg handling.
pollard --versionandpollard --helppreviously hung on stdio waiting for an MCP client. A tiny hand-rolled arg parser insrc/main.rs(no new dep) now prints and exits before tokio starts; unknown args fail with exit code 2 and a hint. The MCP server path is unchanged when no args are passed.pollard-doctor's binary check usespollard --versioninstead ofcommand -v pollardso stale installs whose version doesn't match what the user expects are caught. -
(query)
summaryreturnshintsnudging callers towardcreate_view(#93).
Three cheap heuristics fire on the summary response: dominant function appears recursively in stacks (suggestscollapse_recursion), a single module covers >40% of stacks (suggestshide_modules), or top function names carry type parameters (suggestsstrip_type_params). Thehintsarray is omitted when no heuristic fires, so a clean profile stays silent. -
(views) regex backreferences in
renamereplacements (#87).
renameapplication now goes through a newFunctionMatcher::replacehelper that delegates toregex::Regex::replace, so capture references ($1,${name}) interpolate in the replacement string. A single rule can fold a trait-vs-inherent monomorphisation pair likere:<(.*) as .*::Schedule>::schedule => ${1}::schedulewhere users previously had to enumerate one rule per concrete type. -
(views) scoped views —
process/thread/time_rangepre-filter oncreate_view(#90).
Pinning a hot pid/tid or a benchmark window once at view creation is now possible: the scope is baked into the view's stored state and applied alongside any per-callCommonFilterArgsat query time. Per-call filters must be a sub-slice of the view's scope — equality (or, forpid:, the bare-vs-suffixed strict-narrowing case) forprocess/thread, and[start, end] ⊆ [scope_start, scope_end]fortime_range. Conflicting per-call filters are rejected withinvalid_value, so a misuse surfaces as one structured error rather than a silently-ignored argument. Stacked views inherit the parent scope and are only allowed to narrow it further, by the same sub-slice rule. The composed scope round-trips throughdescribe_viewunder a newscopefield (thread/process/time_range, omitted when nothing is set). View ids hash the scope alongside the transforms, so two views differing only in scope register as distinct. -
(views)
keep_only_frames/keep_only_modulesinverse-hide transforms oncreate_view(#89).
Inverse ofhide_frames/hide_modules: only frames whose function name (or module name) matches at least one pattern survive, and each maximal run of non-matching frames collapses into a single<hidden>placeholder frame. The two lists OR together — a frame is kept if it matches anykeep_only_*rule. Applied beforehide_*, so a frame matching both akeep_onlyand ahiderule is dropped (hidealways wins).describe_view'stransformsview echoes the patterns, andrule_statsgainskeep_only_frames/keep_only_modulesentries whoseframes_matchedcounts every kept frame and0is the same typo signal as for the other rule kinds. Composes throughextend_fromby appending lists (union), matchinghide_*. The placeholder name is static for v1. -
(views)
strip_type_paramspreset transform oncreate_view(#88).
Whenstrip_type_params: true, balanced<…>segments are removed from each frame's function name during transform application —OrdValBatch<RowRowLayout<((Row, Row), Ts, i64)>>collapses toOrdValBatch. The strip runs afterhide_*and beforerename, so user rules can target the normalized name without each rule encoding a bracket-tolerant pattern. Implemented as a hand-rolled depth counter (regex can't match balanced delimiters reliably); unmatched>outside any open bracket is preserved verbatim.describe_view'stransformsview echoes the flag, andrule_statsgains astrip_type_paramsentry whoseframes_matchedcounts every frame whose name actually changed —0is the same typo signal as for the other rule kinds. Composes throughextend_fromas a logical OR, matchingcollapse_recursion. -
(views) per-rule hit counts on
create_view, plus a newdescribe_viewtool (#85, #86).
create_viewnow returnsrule_stats: [{rule_index, kind, pattern, frames_matched, samples_affected}]andtotal_base_samples, computed by replaying everyhide_frames/hide_modules/rename/collapse_recursionrule against the base's samples once at view-create time. A rule withframes_matched: 0is the typo signal — without it, mistyped patterns failed silently and only surfaced when a downstream tool produced unchanged output.describe_view(profile_id)is symmetric withdescribe_profilefor views: returns the immediate parent's id, the full composedtransformsshape, and the same per-rule counts so callers can re-fetch them later without re-creating the view. Stats are cached on the view'sProfileSession, sodescribe_viewis a hash-map lookup. -
(query)
compare_functions(profile_id_a, function_a, function_b, profile_id_b?, ...): side-by-side asm diff with per-instruction sample counts on both sides (#8).
Twoasm_for_functioncalls plus an LCS pass over a normalized instruction key (registers collapsed toR, numeric immediates toIMM, mnemonic + operand shape preserved). The displayed asm text is unchanged; normalization is alignment-only — without it, register renames and differing displacements (xmm0/xmm1,rdi+rcx*8-0x38/rdi+rcx*1-0x6000) would split nominally-equal instructions onto separate rows and the per-row sample columns would no longer line up.profile_id_bdefaults toprofile_id_a, so the natural "why issimd_rows_1stfaster thansimd_cols_1st" workflow stays a one-profile call; pass a differentprofile_id_bfor before/after refactor diffs across two recordings. Output rows carryOption<offset|asm|samples>per side so LCS gaps (instructions only on one side, e.g. an unrolled loop with moreaddsds) remain visible at their original offsets. The two sides must agree onarch; a mismatch errors out rather than producing a meaningless alignment. -
(query) surface cross-process aggregation on inverted
call_tree(#68).
An inverted (inverted=true)call_treewithout aprocess=filter pulls callers from every pid that hits a given leaf. Reasonable for "where ismemcpycalled from anywhere", but the same caller chain can mix time from two different processes via different code paths and the result was indistinguishable from a single-process tree. The response now carriescross_process: trueandprocesses_in_tree: [{pid, name, samples, pct}](biggest first;pidmatches theprocess=pid:wire form) when the inverted aggregation crossed >1 distinct pid; single-process and process-filtered results stay silent. Top-down trees never emit the signal because their root structure already separates per-process callees. -
(query)
summaryaccepts the standardprocess/thread/time_rangefilter (#63).
Previously profile-wide only, so the natural follow-up after spotting a hot pid intop_processeswas a hand-composedtop_functions+top_modules+ dominant-thread reasoning. The filter now flows through every sample-count surface in the response (total_samples,time_range_ms,duration_ms,top_processes,top_threads,top_modules, both top-functions lists,dominant_thread); recording-level fields (interval_ms,sample_rate_hz,unsymbolicated_pct,profile_start_ms) stay profile-wide because they describe the recording, not the slice. Default args reproduce the prior profile-wide behavior. -
(query) surface child names on
call_treepruning markers (#61).
Omitted-children markers now carrytop_omitted(up to 3 entries of{function, pct}, biggest first) and truncated-subtree markers carry the cutoff frame'sfunction, so a caller can tell what was dropped and decide whether wideningmin_pct/max_breadth/max_depthis worth a second call. -
(query) warn when bare-name
process=aggregates across multiple pids (#62).
top_functions,call_tree,top_groups,stacks_containing, andcompare_profilesnow exposematched_processes: [{pid, name}](per-sidea_/b_forcompare_profiles) when a bare-name filter resolves to more than one distinct pid, listing the pids it absorbed so the caller can disambiguate viapid:N/pid:N.M. Bare-name filters that resolve to a single pid stay silent.
Fixed
- (query) align
summary.time_range_mswith thetime_rangefilter contract (#64).
summary.time_range_msreported absolute (boot-relative) timestamps whiletime_rangefilter args are documented as profile-relative; pasting one into the other returned no samples.
time_range_msis now offset by the first-sample anchor (matching the filter's reference frame), the newsummary.profile_start_msexposes that anchor so the absolute frame is one addition away, andProfile::stack_indicessubtracts the anchor before gating so boot-relative samply timestamps no longer leak through.
Other
- (release) tag-triggered
plugin-release.ymlworkflow zips.claude-plugin/,skills/, README, and LICENSE-* intopollard.skilland attaches it to the GitHub release that release-plz already cuts; a verify step rejects the tag if Cargo.toml's version doesn't match.claude-plugin/plugin.jsonandmarketplace.json. - (release)
release-plz.ymlsplits into release-pr → sync → release so every release-pr run is followed by a commit that mirrorsCargo.toml's version into.claude-plugin/plugin.jsonand.claude-plugin/marketplace.json. release-plz force-pushes its release branch each run, so the sync commit is reapplied rather than persisting. - (ci) new
plugin-manifestsjob intest.ymlassertsCargo.tomlversion equals.claude-plugin/plugin.jsonandmarketplace.json, catching manifest drift on regular PRs instead of waiting for the tag-time check. - (ci) extract version via
cargo metadata --no-deps --format-version 1 | jqinstead ofgrep/sed, robust against Cargo.toml formatting changes (workspace inheritance, quoting, comments).