| name | new-format |
| description | Implement a new profile format end-to-end: research, input generation,
converter, origin decision, registration, tests, docs, and examples. Use when
asked to support, add, or implement a new format.
|
| argument-hint | [format name, issue link, or guidance] |
Implement a new profile format end-to-end.
Format
$ARGUMENTS
Principles
-
Trust real profiler output over the spec. Real files may violate it. Consider
parsing correct only after a generated real input validates it, not just
hand-written tests
-
Verify against ground truth you compute independently. Before trusting the
Markdown output for a real input, write a throwaway script that tallies
checkable invariants straight from the input bytes (e.g. summed self costs
must equal the file's summary:; per-function totals from an independent
parse) and compare against the formatted tables
-
Follow the @../../dimensions.md principles for every behavior an input shows
Workflow
Research
-
Read the format's official spec online
-
Read one or two reference implementations (e.g. the canonical viewer's
parser, speedscope's importer). Note where they deviate from or extend the
spec
-
Enumerate the tools and runtimes that emit the format (the canonical tool,
other profilers that export it, tools we already support), then decide the
origin question:
- Note which existing origins need their
formats expanded
- Register each emitting tool or runtime we don't have yet as its own origin,
even when its inputs carry no detectable markers (a markerless spec,
isMarkerEntry: () => false); sharing another origin's spec would make a
later behavioral split a breaking change
- Decide the format's
fallbackOrigin: the origin of the format's defining
tool or runtime for single-runtime formats, else unknown
-
Confirm every modality the format captures is supported. If any is
unsupported, STOP: explain the new modality and how it differs from the
supported ones, and ask whether to implement it first by loading
/new-modality and following its workflow
Generate inputs first
-
Load /new-input and follow its workflow through generation and inspection
for every emitting language; return here at its converter checkpoint, since
the converter doesn't exist yet
-
Reconcile the generated bytes with the research: the parser plan must account
for every construct in the real file without overfitting to it
Implement
-
Create src/formats/<name>/:
-
parse.ts: typed data types and parsing into the modality's uniform parsed
type; aggregation, origin detection, and categorization then run uniformly
in the modality module
- A sampling profile parses into a
Profile (frames, metrics,
lazily-generated samples; see src/modalities/profile/type.ts). Map the
format's units to metrics via determineMetric; a lone sample-count
metric should populate Sample.sampleCount with no metrics instead
- A snapshot parses into a
HeapSnapshot (node adjacency graph,
lazily-classified nodes; see src/modalities/snapshot/type.ts)
parse returns a list of them (ParsedInput[]), which may mix
modalities when one input contains both profiles and snapshots
-
matches.ts: the matches<Name> auto-detection check. Keep it cheap and
strict enough not to claim other text/JSON
-
index.ts: the <name>Converter
(as const satisfies JsonFormatConverter/BinaryFormatConverter):
- Registration metadata:
title (the display name in the readme matrix),
extension (the examples/input/ filename extension), languages (the
languages whose profilers emit the format), and fallbackOrigin (decided
in step 3)
matches from matches.ts
parse is authoritative: it must throw on non-instances so
auto-detection can move on
- Binary converters also implement the streaming
parseAsync
-
index.test.ts: test:
matches accept/reject and parse rejections
- Conversion end-to-end via
convertJsonToMd/convertBytesToMd (and the
streaming convertToMdAsync for binary), asserting complete tables with
the helpers in src/testing/markdown.ts (extend them if the new metric
noun has no table accessor)
- The tricky behaviors real inputs exposed, not just the happy path
-
testing.ts for format-specific test utilities
-
Implement the origin decision from step 3: for each new emitting tool or
runtime, load /new-origin and follow its workflow.
Then set up the format's origin detection:
- List the format in the
formats of every origin that emits it
- A missing per-origin
formats entry fails silently (detection falls back).
The detected-input-origins test in src/origins/index.test.ts requires
every committed input to resolve to the origin in its filename. NEVER
commit an input that resolves elsewhere: give the origin a marker or a
parser origin hint, or make the workload realistic enough to carry the
origin's evidence
Register
-
Add the converter to formatConverters in src/formats/registry.ts
-
src/cli/languages.ts: add a languageMetas entry (name, aliases,
extensions) for any emitting language we don't list yet. An origin's display
name comes from its OriginSpec.title; if a new config token needs a
display label beyond title-casing, add it to configNames in
src/cli/examples.ts
Validate against real inputs
-
Convert each generated input with node src/cli/index.ts <input> and verify
against independently computed ground truth. Also convert each base/current
pair as a diff. Expect this step to send you back to step 7; that's the
point
-
pnpm bench <input> to confirm conversion time is comparable to existing
formats on similar-size inputs
Document and finish
-
docs/formats/<name>.md for --help <format>; generation instructions in
each emitting language's docs/languages/<language>.md (a new file for a
new language). Verify the content renders with
node src/cli/index.ts --help <name> for the format and each language
-
pnpm update-examples (re-run after ANY later converter change), then
pnpm update-readme
-
pnpm format, pnpm lint, pnpm typecheck, pnpm knip, pnpm test