---
name: code-profile
description: >
  Profile existing code to surface concrete optimization opportunities. Three
  phases: execute the code under a real profiler (cProfile / py-spy / pyinstrument
  for Python, --prof / clinic / 0x for Node, chrome-devtools MCP for webapps),
  analyze the profiler output to rank the actual runtime bottlenecks, then
  propose targeted code changes for each. Profiling can run on an existing test,
  a freshly-written micro-test, or the running application — never via inline
  prints, logs, or timers in the source code. Use when the user says
  /code-profile, /profile, 'profile this code', 'find slow paths',
  'optimization opportunities', 'profile this test', 'profile this endpoint',
  'profile la page', 'identifie les bottlenecks', 'identify the bottlenecks',
  'pourquoi est-ce lent', 'why is this slow', 'fais du profiling',
  'do some profiling', 'analyse les performances', 'analyze the performance',
  'rends ce code plus rapide', 'make this code faster', 'optimise ce code',
  'optimize this code', 'trouve les goulots', or otherwise asks to investigate
  or improve runtime performance of a specific code path. Distinct from
  /infra-perf (which monitors live API health via Datadog) — /code-profile
  runs a profiler on the code itself.
argument-hint: "[optional: target — file/path, test name, endpoint, URL, or 'app'] [--mode test|new-test|app]"
allowed-tools:
  - Read
  - Write
  - Glob
  - Grep
  - Bash
  - AskUserQuestion
---

Find optimization opportunities by running the code under a real profiler, reading the profiler's output, and turning the observed bottlenecks into concrete code changes. **Never** add `print`, `logger.info`, `console.log`, `time.perf_counter()`, `Date.now()`, custom decorators, or any other in-source timing — the whole point is to observe the program from the outside with a profiler that already attributes time to each frame.

The skill runs in three phases, in this order:

1. **Execute** — pick the right profiler for the language/runtime, drive a realistic workload through it, and capture the raw profile artefact.
2. **Analyze** — load that artefact, rank the hotspots by their actual runtime cost (not by intuition), and bucket each one (CPU / I/O / allocation / rendering / network).
3. **Propose** — for each top hotspot, formulate a concrete code-level optimization with file:line, expected impact, and effort.

## Required tools

- `chrome-mcp` — **only** for profiling a webapp (browser trace). Python/Node code profiling uses local profilers (cProfile, py-spy, pyinstrument, `--prof`, clinic, 0x) that are not managed by `pysae-ai-tools`.

When (and only when) the target is a webapp/URL, run `pysae-ai-tools tools require chrome-mcp` before starting; if it exits non-zero, abort and report the install command from stderr to the user. Skip this check for non-webapp targets.

## Phase 1 — Execute

### Identify target and mode

Parse `$ARGUMENTS` to determine:

- **Target** — file/module path, test name, HTTP endpoint, URL, or "the app" (a running process).
- **Mode** — one of:
  - `test` — profile an existing test (pytest node id, jest test, …).
  - `new-test` — write a focused profiling harness in a temp file (NOT committed) that exercises the target with realistic input.
  - `app` — attach to / launch the running application (Python service, Node server, browser page).

If ambiguous, use `AskUserQuestion` once to confirm both target and mode. Don't guess between "the slow endpoint" and "the slow function" — they need different profilers and different drivers.

### Detect language and pick the profiler

Read the project's manifest (`pyproject.toml`, `package.json`, `go.mod`, `Cargo.toml`, …) and the target file. Pick from this priority list — never fall back to ad-hoc instrumentation.

#### Python

| Mode | First choice | Why |
|------|--------------|-----|
| `test` / `new-test` (CPU-bound) | `cProfile` + `pstats` (stdlib) | Reproducible, exact call counts, no install |
| `test` / `new-test` (mixed I/O) | `pyinstrument` (sampling, low overhead) | Wall-clock view, easier to read for I/O-heavy code |
| `app` (live process) | `py-spy record` (sampling, attaches by PID) | Zero-instrumentation, works on running services |
| Async-heavy code | `pyinstrument --async-mode strict` | Correctly attributes time across awaits |
| Memory profiling (only if user asks) | `memray run` then `memray flamegraph` | Modern, low-overhead heap profiler |

Install on demand with `uv tool install pyinstrument py-spy memray` if not already available.

#### Node.js / TypeScript backend

| Mode | First choice |
|------|--------------|
| `test` / `new-test` | `clinic doctor -- node <runner>` (diagnoses bottleneck class) then `clinic flame` for CPU |
| `app` (live) | `0x -- node server.js` for flamegraph, or `node --inspect` + Chrome DevTools |
| Heap | `clinic heapprofiler` |

Run `npx --yes clinic` / `npx --yes 0x` if not installed. Reports land in `./.clinic/` or `./<pid>.0x/`.

#### Frontend webapp (Vue, React, …)

Always use the **chrome-devtools MCP** — it gives a real browser trace (CPU, layout, paint, network, scripting attribution) that no instrumentation can replicate.

#### Other runtimes

- **Go** — `go test -cpuprofile cpu.prof -bench .` then `go tool pprof -top cpu.prof`
- **Rust** — `cargo flamegraph` (release build mandatory)
- **Java/Kotlin** — `async-profiler` attached to PID

If the language is exotic and no first-class profiler is available, stop and ask the user — never add manual timing as a substitute.

### Drive a realistic workload

A profile of a toy workload is worthless. Before starting capture, make sure:

- Input size is **representative** (production-like row counts, payload sizes, list lengths). Load a fixture, generate at scale, or hit the live dev environment.
- The build is **optimized** for compiled / bundled code (release mode for Rust/Go, production build for the webapp). Profiling a debug build produces misleading numbers.
- Caches are warmed if the scenario is "steady state", or explicitly cold if the scenario is "first hit".
- The driven scenario runs long enough to amortize startup — **≥ 1 s** of useful work for sampling profilers, **≥ a few thousand iterations** for tight CPU loops.

For `new-test` mode, write the harness to a path under the system temp dir or `tests/_profile_<slug>.py` (track the path for deletion in Phase 4 — Hand off). The harness MUST:

- Import the real production code — no copies, no mocks of the function under test.
- Use realistic data.
- Run enough iterations to amortize startup.

### Run the profiler and capture the artefact

Default invocations:

**Python — `cProfile` on a single test**
```bash
python -m cProfile -o /tmp/profile.pstats -m pytest <node_id> -x -q
```

**Python — `pyinstrument`**
```bash
pyinstrument -r html -o /tmp/profile.html -m pytest <node_id> -x -q
# or for an arbitrary script
pyinstrument -r html -o /tmp/profile.html path/to/script.py
```

**Python — `py-spy` on a running process**
```bash
PID=$(pgrep -f '<process pattern>' | head -1)
py-spy record -o /tmp/profile.svg -d 30 -p "$PID"          # flamegraph, 30s
py-spy top -p "$PID"                                        # live top-functions view
```

**Node — `clinic`**
```bash
clinic doctor -- node ./node_modules/.bin/jest <pattern>
clinic flame -- node ./node_modules/.bin/jest <pattern>
```

**Frontend — chrome-devtools MCP**
1. `mcp__chrome-devtools__new_page` (or `select_page` if the dev server is already open) on the target URL.
2. `mcp__chrome-devtools__performance_start_trace` with `reload: true` for cold load, or `reload: false` then drive interactions via `click` / `type_text` / `navigate_page` for a warm scenario.
3. Execute the user scenario (or `wait_for` on a stable selector).
4. `mcp__chrome-devtools__performance_stop_trace`.
5. Optional: `lighthouse_audit` for a holistic score, `list_network_requests` to spot waterfalls, `take_memory_snapshot` for leak hunts.

If the run produces unusable data (sub-millisecond total, profiler crash, sampling rate too low, all time in `<frozen importlib>` or `node:internal`), increase the workload or duration and re-run **once** before bailing out.

## Phase 2 — Analyze the profiler output

Do not paraphrase intuition: every claim in the analysis must come from the profiler artefact captured in Phase 1.

### Load the data and extract the top-N

**Python — `cProfile` / `pstats`**
```bash
python - <<'EOF'
import pstats
p = pstats.Stats('/tmp/profile.pstats').strip_dirs()
print("=== by cumulative time ===")
p.sort_stats('cumulative').print_stats(40)
print("=== by total (self) time ===")
p.sort_stats('tottime').print_stats(40)
print("=== top callers of hotspot ===")
p.print_callers(20)
EOF
```

**Python — `pyinstrument`** : open the captured `profile.html` (`Read` the JSON sibling if present, or parse the rendered tree from the HTML output). Look at percent-of-total wall time per frame, `[await]` markers for async, and `[self]` for direct cost.

**Node — `clinic`** : open the generated `.html` report; extract the flagged "main thread blocked" / "I/O" / "GC" verdict from `clinic doctor` and the dominant flame stacks from `clinic flame`.

**Frontend — chrome-devtools** : iterate the insights returned by `performance_stop_trace`; for each, call `mcp__chrome-devtools__performance_analyze_insight` to drill in. Read `list_console_messages` for runtime warnings and `list_network_requests` for waterfalls.

### Bucket each hotspot

For every candidate hotspot, classify the time into one bucket — the optimization strategy depends on it:

- **CPU** — pure compute, dominated by `[self]` time in user code.
- **I/O wait** — blocked on network/disk/db (visible as `_socket.recv`, `pymongo`, `aiohttp`, `fs.read`, …). Async profilers attribute this clearly.
- **Allocation / GC** — high `tottime` in allocators, frequent GC pauses, large retained-heap deltas.
- **Rendering / layout / paint** — frontend only, from DevTools insight names.
- **Network** — frontend only, from `list_network_requests` (waterfalls, oversized assets, missing cache).

Quote the actual numbers from the profiler. If a frame is 32 % of total time over 6.4 s of trace, write **"32 % / 2.0 s"**, not "slow".

### Filter to real bottlenecks

Discard noise before reporting:

- Frames < 3 % of total time individually and not part of a heavy aggregate.
- Framework internals you can't change (`asyncio.events.run_forever`, `eventLoop.tick`, …) unless the call pattern *into* them is the cause.
- Setup/teardown overhead from the test harness itself — re-run with more iterations rather than reporting it.

Keep at most 8 findings — beyond that the report becomes noise.

## Phase 3 — Propose optimizations

For each retained hotspot, formulate a concrete, code-level fix.

### Per-finding structure

```
### #<n> — <short title> (<impact: high / medium / low>)

**Hotspot** : `path/to/file.py:<line>` — `<function or expression>`
**Coût observé** : <X % du temps total / Y ms / Z appels — chiffres tirés du profil>
**Bucket** : CPU | I/O | allocation | rendering | network
**Cause** : <what the profile shows — algorithmic, N+1, blocking call, layout thrash, oversized bundle, …>
**Piste** : <concrete change — e.g. "remplacer le `O(n²)` par un set lookup", "batcher la requête Mongo via `$in`", "ajouter l'index `{route_id: 1, ts: -1}`", "déplacer le calcul hors du render loop", "code-split la route /op/dashboard">
**Effort estimé** : XS | S | M | L
```

Order findings by `impact / effort` ratio (highest ROI first). If nothing significant shows up: **"Aucun goulot évident — code dominé par <X> qui semble incompressible."**

### Rules for the proposals

- **Language** — French only, with proper accents. Apply [../references/french-writing-rules.md](../references/french-writing-rules.md).
- **No emojis.**
- File references use the `path:line` pattern so the user can jump to source.
- The proposal must address what the profile actually showed. Don't suggest "add a cache" if the hotspot is CPU-bound; don't suggest "parallelize" if the trace shows pure I/O wait.
- Prefer **algorithmic / data-structure / query-shape** changes when the profile points at them — they almost always beat micro-optimizations.
- Quote the actionable numbers (request count, latency, % of total) so the user can prioritize.

## Phase 4 — Hand off

1. Print the report to the user.
2. Mention the raw profile artefacts (e.g. `/tmp/profile.html`, `flamegraph.svg`, DevTools trace) so they can open them.
3. Clean up any `new-test` harness file created in Phase 1 unless the user asked to keep it.
4. Suggest the follow-up:
   > Pour appliquer une de ces pistes, lance `/code-implement` avec le ticket correspondant ou crée-en un avec `/glab-issue-create`.

Do **not** modify production code, do **not** create commits, do **not** open MRs — `code-profile` is read-only on the codebase.

## Hard rules

- **Never** add `print`, `logger.info`, `console.log`, `time.perf_counter`, `Date.now()`, custom decorators, or any other in-source timing to measure performance. The whole point is to use a real profiler.
- **Never** profile a debug / development build for compiled or bundled code when a release / optimized build is available — the numbers are misleading.
- **Never** report findings without a concrete hotspot location (file + line or DevTools insight name) and the numbers from the profile.
- **Never** keep `new-test` profiling harnesses in the repo — they are throwaway scaffolding.

$ARGUMENTS
