# Advice for building `pi-extension-times`

## 1. Scope the tool correctly

The key architectural fact is:

- Pi loads extensions through its extension loader
- `jiti.import(extensionPath)` happens before extension code gets control
- therefore a normal extension cannot fully profile the loader path that loaded it

So if you want accurate startup timings for **all** extensions, prefer one of these designs:

### Best options

1. **Loader instrumentation patch**
   - patch Pi's extension loader to record timings around:
     - `createJiti(...)`
     - `await jiti.import(...)`
     - `await factory(api)`
   - output JSON/Markdown diagnostics

2. **External wrapper profiler**
   - run Pi from a helper CLI
   - patch or monkey-patch the loader module at runtime
   - collect timings from outside the Pi process or from an instrumented child process

3. **Hybrid package**
   - external profiler for startup timing
   - Pi extension for viewing reports, triggering re-runs, and rendering advice

A pure extension is still useful for commands/UI, but it is not the right place for first-principles startup measurement.

---

## 2. What to measure

At minimum, capture per-extension:

- extension path
- `createJiti` time
- `jiti.import` time
- `factory(api)` time
- total load time
- success/error state

Also capture aggregate metrics:

- total extension load time
- top N slowest extensions
- totals by phase
- count of loaded extension entrypoints

Useful extras:

- local file count in the extension import graph
- external dependency count
- number of cached jiti files generated
- syscall counts from optional deep profiling mode
- whether extension package ships TS sources or prebuilt JS

---

## 3. What we learned from profiling real Pi extensions

### Main finding

For slow extensions, the dominant cost was usually **`jiti.import(...)`**, not:

- `createJiti(...)`
- `factory(api)`

That means the main bottleneck is often:

- module resolution
- many file probes in `node_modules`
- repeated loading/evaluation of many modules
- eager import of optional code paths

### Practical interpretation

Even when jiti has filesystem cache, startup can still be slow because cached transpilation does **not** eliminate:

- `package.json` / `exports` resolution
- `stat/open` probing across many candidate files
- execution of a large module graph

---

## 4. Heuristics for explaining a slow extension

Your tool should emit likely causes, not just numbers.

### A. Large import graph

Symptoms:

- many local modules
- many cached `.mjs` files for the extension
- large `jiti.import` time

Likely advice:

- reduce top-level imports
- bundle the package to a smaller number of JS files
- split optional features behind dynamic `import()`

### B. Eagerly loaded optional branches

Examples:

- async execution support loaded at startup even if unused
- TUI editor/manager code loaded even if user never opens it
- chain execution code loaded even for single-task mode
- provider-specific implementations loaded even if only one provider is active

Likely advice:

- keep a thin bootstrap entrypoint
- move execution/UI branches into lazy imports inside handlers

### C. Eager provider registry

A common smell:

- registry imports all provider implementations at module top level

Likely advice:

- lazy-load provider implementations by provider name
- separate cheap detection/credential checks from heavy fetch implementations

### D. Heavy top-level UI imports

Examples:

- importing all TUI components/settings screens at startup
- importing editor widgets that are only used by a command or overlay

Likely advice:

- lazy-load UI screens on command execution
- keep startup path free of optional visual components

### E. Sync filesystem work on startup

Examples:

- recursive directory scans
- `readFileSync`, `readdirSync`, `statSync`
- cleanup jobs
- watchers started immediately

Likely advice:

- delay work until first use
- move cleanup/watch setup to background after session start
- cache discovered metadata

### F. Heavy third-party dependency imports

Examples:

- tokenizers
- SDKs
- large parser stacks
- provider-specific client libraries

Likely advice:

- import only when feature is used
- prefer prebuilt and tree-shaken output
- avoid top-level imports of large libraries where possible

---

## 5. Recommendations your tool should generate

For each slow extension, try to produce advice like:

- **bundle/import-graph advice**
  - "This extension imports 35 local modules at startup. Consider bundling or reducing top-level imports."
- **lazy-loading advice**
  - "`agent-manager` UI is imported at startup but appears to be used only in management mode. Move behind dynamic import."
- **provider advice**
  - "Registry imports all provider implementations eagerly. Load provider modules on demand."
- **runtime work advice**
  - "Startup performs synchronous directory cleanup and watcher setup. Delay until after session start or first use."
- **publish advice**
  - "Ship prebuilt JS instead of raw TS where possible."

Try to keep advice concrete and path-based.

---

## 6. Suggested output format

The tool should support:

### Summary

- total startup load time
- top 5 slowest extensions
- total time by phase

### Per-extension report

For each extension:

- path
- phase timings
- local module count
- likely cause categories
- actionable recommendations

### Machine-readable output

Produce JSON as well:

```json
{
  "extensions": [
    {
      "path": "...",
      "createJitiMs": 2,
      "importMs": 983,
      "factoryMs": 3,
      "localModuleCount": 35,
      "causes": ["large-import-graph", "eager-ui"],
      "advice": [
        "Move manager UI behind dynamic import",
        "Bundle the package to prebuilt JS"
      ]
    }
  ]
}
```

---

## 7. Suggested implementation plan

### Phase 1: instrumentation

- patch or wrap Pi loader
- capture phase timings per extension
- emit JSON report

### Phase 2: graph analysis

- parse top-level imports from the extension entrypoint
- recursively estimate local module graph size
- count external dependencies
- flag known heavy patterns

### Phase 3: heuristics/advice

- detect eager provider registries
- detect optional UI imported at top level
- detect sync FS startup work
- detect TS-source package vs prebuilt-JS package
- map findings to human advice

### Phase 4: Pi UX

- add `/extension-times` command
- render a compact startup report
- optionally open a rich HTML report

---

## 8. Advice for optimizing `pi-extension-times` itself

Do **not** let the profiler become one of the slowest extensions.

### Keep startup tiny

At startup, only register commands/tools/events. Avoid importing:

- HTML/report renderers
- AST parsers
- graph analyzers
- syscall analyzers
- visualization code
- GitHub API helpers

Load those only when the user requests profiling or a deep report.

### Prefer this shape

```ts
export default function (pi: ExtensionAPI) {
  pi.registerCommand("extension-times", {
    description: "Profile Pi extension startup",
    handler: async (args, ctx) => {
      const { runProfile } = await import("./profile.js")
      await runProfile(args, ctx)
    }
  })
}
```

### Keep analysis modes separate

Use separate modules for:

- fast summary
- deep graph analysis
- deep syscall analysis
- HTML report generation
- GitHub publishing helpers

### Avoid top-level heavy dependencies

If you need:

- TypeScript AST libraries
- graph tools
- HTML generators
- visualization libraries

load them only in the code path that needs them.

### Consider bundling this package

Since the profiler's purpose is startup performance, it should model good behavior:

- ship prebuilt JS
- keep entrypoint minimal
- use lazy imports for optional functionality

---

## 9. Concrete Pi-specific guidance

Relevant Pi extension facts:

- extensions export a default factory function
- Pi loads extensions via jiti
- extensions can register tools, commands, handlers, and UI
- startup-sensitive code is whatever runs before or during `factory(api)` and whatever is pulled in by top-level imports

Therefore the profiling/advice tool should distinguish:

- **loader/import cost**
- **factory/bootstrap cost**
- **runtime/event cost**

These are different problems and need different recommendations.

---

## 10. High-value advice rules to implement first

If time is limited, implement these rules first:

1. `importMs` dominates total load time and local module count is high
   - advise bundling / reducing import graph

2. provider registry imports many implementations eagerly
   - advise lazy provider loading

3. TUI/manager/editor modules imported at top level but only used by commands
   - advise dynamic import in handlers

4. sync FS work discovered in entrypoint or immediate dependencies
   - advise delay/background init

5. package ships raw TS entrypoints
   - advise prebuilt JS for published package

Those five rules should already generate useful feedback on many real extensions.
