Ir al contenido

Migración desde pi-mono: Una guía práctica de merge

Esta guía es una lista de verificación repetible para portar cambios desde pi-mono a este repositorio. Úsela para cualquier merge: un solo archivo, rama de funcionalidad o sincronización de versión completa.

Commit: b21b42d032919de2f2e6920a76fa9a37c3920c0a Fecha: 2026-03-22

Actualice esta sección después de cada sincronización; no reutilice el rango anterior.

Al iniciar una nueva sincronización, genere los parches desde este commit en adelante:

Ventana de terminal
git format-patch b21b42d032919de2f2e6920a76fa9a37c3920c0a..HEAD --stdout > changes.patch
  • Identifique la referencia upstream (commit, tag o PR).
  • Liste los paquetes o carpetas que planea modificar.
  • Decida qué funcionalidades están dentro del alcance y cuáles se omiten intencionalmente.
  • Prefiera un diff limpio y enfocado en lugar de una copia al por mayor.
  • Evite copiar artefactos compilados o archivos generados.
  • Si upstream agregó nuevos archivos, agréguelos explícitamente y revise su contenido.

2) Seguir las convenciones de extensión en imports

Sección titulada «2) Seguir las convenciones de extensión en imports»

La mayoría de los archivos TypeScript de tiempo de ejecución omiten .js en los imports internos, pero algunos entrypoints de test/bench mantienen .js para compatibilidad con ESM en tiempo de ejecución. Siga el estilo existente del paquete local; no elimine extensiones de forma indiscriminada.

  • En los archivos de tiempo de ejecución de packages/coding-agent, mantenga los imports internos sin extensión a menos que importe recursos no-TS.
  • En packages/tui/test y packages/natives/bench, mantenga .js donde los archivos circundantes ya lo usan.
  • Mantenga las extensiones de archivo reales cuando las herramientas lo requieran (por ejemplo, .json, .css, embeds de texto .md).
  • Ejemplo: import { x } from "./foo.js";import { x } from "./foo"; (solo cuando la convención del paquete es sin extensión).

Upstream usa diferentes scopes de paquetes. Reemplácelos de manera consistente.

  • Reemplace los scopes antiguos con el scope local usado aquí.
  • Ejemplos (ajuste para que coincidan con los paquetes reales que está portando):
    • @mariozechner/pi-coding-agent@f5-sales-demo/xcsh
    • @mariozechner/pi-agent-core@f5-sales-demo/pi-agent-core
    • @mariozechner/pi-tui@f5-sales-demo/pi-tui
    • @mariozechner/pi-ai@f5-sales-demo/pi-ai

4) Usar APIs de Bun cuando mejoren respecto a Node

Sección titulada «4) Usar APIs de Bun cuando mejoren respecto a Node»

Ejecutamos sobre Bun. Reemplace las APIs de Node solo cuando Bun proporcione una alternativa mejor.

SÍ reemplazar:

  • Ejecución de procesos: child_process.spawn → Bun Shell $ para comandos simples, Bun.spawn/Bun.spawnSync para streaming o trabajo de larga duración
  • E/S de archivos: fs.readFileSyncBun.file().text() / Bun.write()
  • Clientes HTTP: node-fetch, axiosfetch nativo
  • Hashing criptográfico: node:crypto → Web Crypto o Bun.hash
  • SQLite: better-sqlite3bun:sqlite
  • Carga de variables de entorno: dotenv → Bun carga .env automáticamente

NO reemplazar (estos funcionan correctamente en Bun):

  • os.homedir() — NO reemplazar con Bun.env.HOME, Bun.env.HOME o el literal "~"
  • os.tmpdir() — NO reemplazar con Bun.env.TMPDIR || "/tmp" o rutas hardcodeadas
  • fs.mkdtempSync() — NO reemplazar con construcción manual de rutas
  • path.join(), path.resolve(), etc. — estos están bien

Estilo de import: Use el prefijo node: con imports de namespace únicamente (sin imports con nombre desde node:fs o node:path).

Convenciones adicionales de Bun:

  • Prefiera Bun Shell $ para comandos cortos sin streaming; use Bun.spawn solo cuando necesite streaming de E/S o control de procesos.
  • Use Bun.file()/Bun.write() para archivos y node:fs/promises para directorios.
  • Evite verificaciones con Bun.file().exists(); use manejo con isEnoent en try/catch.
  • Prefiera Bun.sleep(ms) sobre wrappers de setTimeout.

Incorrecto:

// BROKEN: env vars may be undefined, "~" is not expanded
const home = Bun.env.HOME || "~";
const tmp = Bun.env.TMPDIR || "/tmp";

Correcto:

import * as os from "node:os";
import * as fs from "node:fs";
import * as path from "node:path";
const configDir = path.join(os.homedir(), ".config", "myapp");
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "myapp-"));

No copie assets de tiempo de ejecución ni archivos de vendor en tiempo de compilación.

  • Si upstream copia assets a una carpeta dist, reemplace con embeds compatibles con Bun.
  • Los prompts son archivos .md estáticos; use imports de texto de Bun (with { type: "text" }) y Handlebars en lugar de cadenas de prompt inline.
  • Use import.meta.dir + Bun.file para cargar recursos adyacentes que no sean texto.
  • Mantenga los assets en el repositorio y deje que el bundler los incluya.
  • Elimine los scripts de copia a menos que el usuario los solicite explícitamente.
  • Si upstream lee un archivo fallback empaquetado en tiempo de ejecución, reemplace las lecturas del sistema de archivos con un import de texto embed de Bun.
    • Ejemplo (fallback de instrucciones de Codex):
      • const FALLBACK_PROMPT_PATH = join(import.meta.dir, "codex-instructions.md"); -> eliminado
      • import FALLBACK_INSTRUCTIONS from "./codex-instructions.md" with { type: "text" };
      • Use return FALLBACK_INSTRUCTIONS; en lugar de readFileSync(FALLBACK_PROMPT_PATH, "utf8")

Trate package.json como un contrato. Haga el merge de forma intencional.

  • Mantenga los valores existentes de name, version, type, exports y bin a menos que el port lo requiera.
  • Reemplace los scripts de npm/node con equivalentes de Bun (por ejemplo, bun check, bun test).
  • Asegúrese de que las dependencias usen el scope correcto.
  • No rebaje versiones de dependencias para corregir errores de tipos; actualice en su lugar.
  • Valide los enlaces de paquetes del workspace y peerDependencies.
  • Mantenga las convenciones de formato existentes.
  • No introduzca any a menos que sea necesario.
  • Evite imports dinámicos e imports de tipos inline; use solo imports de nivel superior.
  • Nunca construya prompts en código; los prompts son archivos .md estáticos renderizados con Handlebars.
  • En coding-agent, nunca use console.log/console.warn/console.error; use logger de @f5-sales-demo/pi-utils.
  • Use Promise.withResolvers() en lugar de new Promise((resolve, reject) => ...).
  • No use las palabras clave private/protected/public en campos o métodos de clase. Use campos privados ES # para encapsulación; deje los miembros accesibles sin palabra clave. La única excepción son las propiedades de parámetros del constructor (constructor(private readonly x: T)), donde la palabra clave es requerida por TypeScript. Al portar código upstream que usa private foo o protected bar, convierta a #foo (privado) o bar sin modificador (accesible).
  • Prefiera helpers y utilidades existentes sobre código ad-hoc nuevo.
  • Preserve los cambios de infraestructura Bun-first ya realizados en este repositorio:
    • El runtime es Bun (sin entry points de Node).
    • El gestor de paquetes es Bun (sin lockfiles de npm).
    • Las APIs pesadas de Node (child_process, readline) están reemplazadas con equivalentes de Bun.
    • Las APIs ligeras de Node (os.homedir, os.tmpdir, fs.mkdtempSync, path.*) se mantienen.
    • Los shebangs de CLI usan bun (no node, no tsx).
    • Los paquetes usan archivos fuente directamente (sin paso de compilación de TypeScript).
    • Los workflows de CI ejecutan Bun para install/check/test.

8) Eliminar capas de compatibilidad antiguas

Sección titulada «8) Eliminar capas de compatibilidad antiguas»

A menos que se solicite, elimine los shims de compatibilidad de upstream.

  • Elimine las APIs antiguas que fueron reemplazadas.
  • Actualice todos los sitios de llamada a la nueva API directamente.
  • No mantenga versiones *_v2 ni paralelas.
  • Reemplace los enlaces al repositorio pi-mono donde sea apropiado.
  • Actualice los ejemplos para usar Bun y los scopes de paquetes correctos.
  • Asegúrese de que las instrucciones del README aún coincidan con el comportamiento actual del repositorio.

Ejecute las verificaciones estándar después de los cambios:

  • bun check

Si el repositorio ya tiene verificaciones fallando no relacionadas con sus cambios, repórtelo. Las pruebas usan el runner de Bun (no Vitest), pero solo ejecute bun test cuando se solicite explícitamente.

11) Proteger funcionalidades mejoradas (lista de trampas de regresión)

Sección titulada «11) Proteger funcionalidades mejoradas (lista de trampas de regresión)»

Si ya mejoró el comportamiento localmente, trate esas mejoras como no negociables. Antes de portar, documente las mejoras y agregue verificaciones explícitas para que no se pierdan en el merge.

  • Congele el comportamiento esperado: agregue una nota breve de “antes/después” para cada mejora (entradas, salidas, valores por defecto, casos límite). Esto previene reversiones silenciosas.
  • Mapee APIs antiguas → nuevas: si upstream renombró conceptos (hooks → extensions, custom tools → tools, etc.), asegúrese de que cada punto de entrada antiguo aún se conecte correctamente. Un flag o export omitido equivale a funcionalidad perdida.
  • Verifique los exports: revise los exports de package.json, tipos públicos y archivos barrel. Los ports de upstream a menudo olvidan re-exportar adiciones locales.
  • Cubra los caminos no felices: si corrigió manejo de errores, timeouts o lógica de fallback, agregue una prueba o al menos una lista de verificación manual que ejercite esos caminos.
  • Verifique valores por defecto y orden de merge de configuración: las mejoras a menudo residen en los valores por defecto. Confirme que los nuevos valores por defecto no se revirtieron (por ejemplo, nueva precedencia de configuración, funcionalidades deshabilitadas, listas de herramientas).
  • Audite el comportamiento de env/shell: si corrigió la ejecución o el sandboxing, verifique que el nuevo camino aún use su env sanitizado y no reintroduzca overrides de alias/funciones.
  • Re-ejecute muestras específicas: mantenga un conjunto mínimo de ejemplos “known good” y ejecútelos después del port (flags de CLI, registro de extensiones, ejecución de herramientas).

12) Detectar y manejar código refactorizado

Sección titulada «12) Detectar y manejar código refactorizado»

Antes de portar un archivo, verifique si upstream lo refactorizó significativamente:

Ventana de terminal
# Compare the file you're about to port against what you have locally
git diff HEAD upstream/main -- path/to/file.ts

Si el diff muestra que el archivo fue refactorizado (no solo parcheado):

  • Nuevas abstracciones, conceptos renombrados, módulos fusionados, flujo de datos cambiado

Entonces debe leer la nueva implementación completamente antes de portar. El merge a ciegas de código refactorizado pierde funcionalidad porque:

Nota: el modo interactivo fue recientemente dividido en controllers/utils/types. Al retroportar cambios relacionados, porte las actualizaciones a los archivos individuales que creamos y asegúrese de que el cableado de interactive-mode.ts se mantenga sincronizado.

  1. Los valores por defecto cambian silenciosamente - Una nueva variable defaultFoo = [a, b] puede reemplazar un antiguo getAllFoo() que retornaba [a, b, c, d, e].

  2. Las opciones de API se eliminan - Cuando los sistemas se fusionan (por ejemplo, hooks + customToolsextensions), las opciones antiguas pueden no conectarse a la nueva implementación.

  3. Los caminos de código quedan obsoletos - Un concepto renombrado (por ejemplo, hookMessagecustom) necesita actualizaciones en cada switch statement, type guard y handler, no solo en la definición.

  4. El contexto/capacidades se reducen - Las APIs antiguas pueden haber expuesto { logger, typebox, pi } que las nuevas APIs olvidaron incluir.

Cuando upstream refactorizó un módulo:

  1. Lea la implementación antigua - Entienda qué hacía, qué opciones aceptaba, qué exponía.

  2. Lea la implementación nueva - Entienda las nuevas abstracciones y cómo se mapean al comportamiento anterior.

  3. Verifique la paridad de funcionalidades - Para cada capacidad del código antiguo, confirme que el código nuevo la preserva o la elimina explícitamente.

  4. Busque remanentes - Busque nombres/conceptos antiguos que puedan haberse omitido en switch statements, handlers, componentes de UI.

  5. Pruebe los límites - Flags de CLI, opciones del SDK, event handlers, valores por defecto: aquí es donde se esconden las regresiones.

Ventana de terminal
# Find all uses of an old concept that may need updating
rg "oldConceptName" --type ts
# Compare default values between versions
git show upstream/main:path/to/file.ts | rg "default|DEFAULT"
# Check if all enum/union values have handlers
rg "case \"" path/to/file.ts

13) Lista de verificación de auditoría rápida

Sección titulada «13) Lista de verificación de auditoría rápida»

Use esto como una pasada final antes de terminar:

Al hacer commit de un backport, siga el formato del repositorio <type>(scope): <descripción en pasado> y mantenga el rango de commits en el título.

fix(coding-agent): backported pi-mono changes (<from>..<to>)
packages/<package>:
- <type>: <description>
- <type>: <description> (#<issue> by @<contributor>)
packages/<other-package>:
- <type>: <description>

Ejemplo:

fix(coding-agent): backported pi-mono changes (9f3eef65f..52532c7c0)
packages/ai:
- fix: handle "sensitive" stop reason from Anthropic API
- fix: normalize tool call IDs with special characters for Responses API
- fix: add overflow detection for Bedrock, MiniMax, Kimi providers
- fix: 429 status is rate limiting, not context overflow
packages/tui:
- fix: refactored autocomplete state tracking
- fix: file autocomplete should not trigger on empty text
- fix: configurable autocomplete max visible items
- fix: improved table column width calculation with word-aware wrapping
packages/coding-agent:
- fix: preserve external config.yml edits on save (#1046 by @nicobailonMD)
- fix: resolve macOS NFD and curly quote variants in file paths

Reglas:

  • Agrupe los cambios por paquete
  • Use tipos de commit convencionales (fix, feat, refactor, perf, docs)
  • Incluya números de issue/PR de upstream y atribución del contribuidor para contribuciones externas
  • El rango de commits en el título ayuda a rastrear los puntos de sincronización

Nuestro fork tiene decisiones arquitectónicas que difieren de upstream. No porte estos patrones de upstream:

UpstreamNuestro forkRazón
Clase FooterDataProviderStatusLineComponentLínea de estado más simple e integrada
ctx.ui.setHeader() / ctx.ui.setFooter()Stub en modos no-TUIImplementado en TUI, no-op en otros casos
ctx.ui.setEditorComponent()Stub en modos no-TUIImplementado en TUI, no-op en otros casos
Objeto de opciones InteractiveModeOptionsArgs posicionales en constructor (el tipo de opciones aún se exporta)Mantener la firma del constructor; actualizar el tipo cuando upstream agregue campos
UpstreamNuestro fork
extension-input.tshook-input.ts
extension-selector.tshook-selector.ts
ExtensionInputComponentHookInputComponent
ExtensionSelectorComponentHookSelectorComponent
UpstreamNuestro forkNotas
sessionManager.appendSessionInfo(name)sessionManager.setSessionName(name)Usamos sessionName en todo el código
sessionManager.getSessionName()sessionManager.getSessionName()Igual (unificamos para coincidir con el RPC de upstream)
agent.sessionName / setSessionName()agent.sessionName / setSessionName()Igual
UpstreamNuestro forkRazón
clipboard.ts + clipboard-image.ts (archivos de tool)Módulo clipboard de @f5-sales-demo/pi-nativesFusionado en implementación nativa N-API
UpstreamNuestro fork
vitest con vi.mock()bun:test con vi de bun
Aserciones de node:testMatchers expect()
UpstreamNuestro forkNotas
createTool(cwd: string, options?)createTools(session: ToolSession) vía registro BUILTIN_TOOLSLas factorías de tools aceptan ToolSession y pueden retornar null
Interfaces *Operations por toolLas interfaces por tool se mantienen (FindOperations, GrepOperations)Usadas para overrides SSH/remotos
fs/promises de Node.js en todos ladosBun.file()/Bun.write() para archivos; node:fs/promises para directoriosPreferir APIs de Bun cuando simplifiquen
UpstreamNuestro forkNotas
proper-lockfile + auth.jsonagent.db (bun:sqlite)Credenciales almacenadas exclusivamente en agent.db
Una sola credencial por proveedorMulti-credencial con selección round-robinLógica de afinidad de sesión y backoff preservada
UpstreamNuestro fork
jiti para carga de TypeScriptimport() nativo de Bun
Campo de manifiesto pkg.pipkg.xcsh ?? pkg.pi (preferir nuestro namespace)

Al portar, omita estos archivos/funcionalidades por completo:

  • footer-data-provider.ts — usamos StatusLineComponent
  • clipboard-image.ts — el clipboard está en el módulo N-API de @f5-sales-demo/pi-natives
  • Archivos de workflow de GitHub — tenemos nuestro propio CI
  • models.generated.ts — auto-generado, regenerar localmente (como models.json en su lugar)

Funcionalidades que agregamos (preservar estas)

Sección titulada «Funcionalidades que agregamos (preservar estas)»

Estas existen en nuestro fork pero no en upstream. Nunca sobrescribir:

  • StatusLineComponent en modo interactivo
  • Autenticación multi-credencial con afinidad de sesión
  • Sistema de descubrimiento basado en capacidades (defineCapability, registerProvider, loadCapability, skillCapability, etc.)
  • Integraciones MCP/Exa/SSH
  • Writethrough LSP para format-on-save
  • Intercepción de Bash (checkBashInterception)
  • Sugerencias de rutas difusas en la herramienta read