For the complete documentation index, see llms.txt. Markdown versions of documentation pages are available by appending .md to the page URL.
Navegación principal
Theme
Codex

Codex use case

Convierte un issue en un pull request con cambios probados

Delega la implementación de una funcionalidad pequeña con criterios de aceptación y revisa la evidencia.

Difficulty Intermediate
Time horizon Long-running

Dale a Codex un issue de alcance definido, un entorno en la nube ya preparado y una lista explícita de verificaciones. Obtén un cambio listo para revisar, con la implementación, las pruebas, la documentación y la evidencia de que se cumple cada criterio de aceptación.

Best for

  • Ingenieros de startups que implementan cambios en una API a partir de un issue de alcance definido.
  • Revisores que necesitan examinar la implementación junto con la evidencia de validación.
  • Equipos que cuentan con criterios de aceptación y un entorno de pruebas operativo.

Contents

    ← All use cases

    Convierte un issue en un pull request con cambios probados

    Delega la implementación de una funcionalidad pequeña con criterios de aceptación y revisa la evidencia.

    Dale a Codex un issue de alcance definido, un entorno en la nube ya preparado y una lista explícita de verificaciones. Obtén un cambio listo para revisar, con la implementación, las pruebas, la documentación y la evidencia de que se cumple cada criterio de aceptación.

    Intermediate
    Long-running

    Dale a Codex un issue de alcance definido, un entorno en la nube ya preparado y una lista explícita de verificaciones. Obtén un cambio listo para revisar, con la implementación, las pruebas, la documentación y la evidencia de que se cumple cada criterio de aceptación.

    Intermediate
    Long-running

    Best for

    • Ingenieros de startups que implementan cambios en una API a partir de un issue de alcance definido.
    • Revisores que necesitan examinar la implementación junto con la evidencia de validación.
    • Equipos que cuentan con criterios de aceptación y un entorno de pruebas operativo.

    Starter prompt

    Implementa [issue ID and title] en [repository] a partir de [base branch and commit]. Usa el entorno en la nube ya preparado que seleccioné para esta tarea. Material de entrada: [paste or attach the issue, acceptance criteria, relevant files, public success/error schemas, authorization and pagination contract, test fixtures, and approved validation commands]. Instrucciones del repositorio: [AGENTS.md and any relevant nested instructions]. Alcance: [files or component boundaries]. Fuera del alcance: [explicit exclusions]. Verificaciones obligatorias: [focused tests, relevant regression tests, type check, lint, and any required CI checks]. Entrega: [create a draft PR in this repository if the available integration supports it and I authorize it here / return a diff and draft PR description]. Primero, lee las instrucciones y el código pertinente. Confirma el commit inicial y ejecuta las verificaciones existentes con las herramientas de ejecución disponibles en este entorno para establecer un punto de referencia. Registra como pendientes de creación los archivos de prueba nuevos. Si falla alguna verificación inicial, te falta acceso o no puedes ejecutar una verificación obligatoria, indica exactamente qué te impide continuar antes de empezar la implementación. Describe brevemente tu plan y luego implementa el cambio mínimo que cumpla los criterios de aceptación. Sigue los patrones existentes de control de acceso, manejo de errores, paginación y pruebas. Agrega pruebas que distingan el comportamiento requerido del anterior. Para este ejemplo de paginación, exige pruebas con registros que tengan la misma marca de tiempo a ambos lados de un límite de página. Incluye pruebas de recorrido acotado y de repetición del recorrido con datos de prueba fijos, así como casos en que la última página esté parcialmente llena o contenga exactamente el máximo de registros. Verifica también que la autorización se compruebe antes de validar el cursor, cubre los casos de cursores no válidos y comprueba los esquemas de las respuestas de éxito y de error. Actualiza la documentación afectada de la API para que coincida exactamente con la respuesta pública implementada. Después de la implementación, vuelve a ejecutar las verificaciones obligatorias. Para cada criterio de aceptación, indica qué prueba u otra evidencia confirma su cumplimiento. Registra los comandos, los resultados, el commit o el estado del árbol de trabajo que probaste y las verificaciones que no ejecutaste. Distingue las fallas preexistentes de las nuevas. Nunca des por aprobada una verificación que no ejecutaste. Entrega el diff o el PR en borrador autorizado, un resumen breve del cambio, una tabla con la evidencia de cada criterio de aceptación, los riesgos pendientes y las acciones que debe realizar quien revise el cambio. No fusiones ni despliegues los cambios, no uses datos de producción, no modifiques permisos, no agregues dependencias ni amplíes el alcance acordado. Si el contrato entra en conflicto con el repositorio o el trabajo requiere modificar un esquema, detente y pide una decisión.
    Implementa [issue ID and title] en [repository] a partir de [base branch and commit]. Usa el entorno en la nube ya preparado que seleccioné para esta tarea. Material de entrada: [paste or attach the issue, acceptance criteria, relevant files, public success/error schemas, authorization and pagination contract, test fixtures, and approved validation commands]. Instrucciones del repositorio: [AGENTS.md and any relevant nested instructions]. Alcance: [files or component boundaries]. Fuera del alcance: [explicit exclusions]. Verificaciones obligatorias: [focused tests, relevant regression tests, type check, lint, and any required CI checks]. Entrega: [create a draft PR in this repository if the available integration supports it and I authorize it here / return a diff and draft PR description]. Primero, lee las instrucciones y el código pertinente. Confirma el commit inicial y ejecuta las verificaciones existentes con las herramientas de ejecución disponibles en este entorno para establecer un punto de referencia. Registra como pendientes de creación los archivos de prueba nuevos. Si falla alguna verificación inicial, te falta acceso o no puedes ejecutar una verificación obligatoria, indica exactamente qué te impide continuar antes de empezar la implementación. Describe brevemente tu plan y luego implementa el cambio mínimo que cumpla los criterios de aceptación. Sigue los patrones existentes de control de acceso, manejo de errores, paginación y pruebas. Agrega pruebas que distingan el comportamiento requerido del anterior. Para este ejemplo de paginación, exige pruebas con registros que tengan la misma marca de tiempo a ambos lados de un límite de página. Incluye pruebas de recorrido acotado y de repetición del recorrido con datos de prueba fijos, así como casos en que la última página esté parcialmente llena o contenga exactamente el máximo de registros. Verifica también que la autorización se compruebe antes de validar el cursor, cubre los casos de cursores no válidos y comprueba los esquemas de las respuestas de éxito y de error. Actualiza la documentación afectada de la API para que coincida exactamente con la respuesta pública implementada. Después de la implementación, vuelve a ejecutar las verificaciones obligatorias. Para cada criterio de aceptación, indica qué prueba u otra evidencia confirma su cumplimiento. Registra los comandos, los resultados, el commit o el estado del árbol de trabajo que probaste y las verificaciones que no ejecutaste. Distingue las fallas preexistentes de las nuevas. Nunca des por aprobada una verificación que no ejecutaste. Entrega el diff o el PR en borrador autorizado, un resumen breve del cambio, una tabla con la evidencia de cada criterio de aceptación, los riesgos pendientes y las acciones que debe realizar quien revise el cambio. No fusiones ni despliegues los cambios, no uses datos de producción, no modifiques permisos, no agregues dependencias ni amplíes el alcance acordado. Si el contrato entra en conflicto con el repositorio o el trabajo requiere modificar un esquema, detente y pide una decisión.

    Asigna a Codex un issue pequeño y bien definido. Pídele que entregue un cambio que alguien de tu equipo pueda revisar, con la implementación, las pruebas, la documentación y la evidencia de que funciona como se pidió. Realiza toda la tarea en un mismo entorno en la nube ya preparado para que el desarrollo y la validación usen la misma configuración del repositorio.

    1. En la aplicación web o de escritorio, elige Trabajar en > Nube y selecciona un entorno publicado.

    2. Para crear uno, selecciona Crear entorno, elige los repositorios de GitHub y pide a Codex que prepare el entorno de ejecución, las dependencias y los servicios de prueba aislados.

    3. Revisa el informe de configuración, corrige los problemas que hicieron fallar las verificaciones y selecciona Publicar antes de iniciar una tarea nueva.

    Si cambian las dependencias o la configuración de los servicios, edita el entorno reutilizable, pruébalo y selecciona Volver a publicar. Luego verifica los cambios en una tarea nueva.

    Al actualizar el repositorio, no se vuelven a ejecutar los comandos de instalación ni de inicio. Las tareas existentes conservan su propio estado.

    Configura por separado los destinos de red necesarios y las credenciales de prueba. Permitir un destino no concede acceso al servicio.

    Si no puedes ejecutar las verificaciones obligatorias, completa la configuración antes de usar este flujo de trabajo.

    Cuándo usarlo

    Elige una funcionalidad con un contrato por escrito y un criterio claro para darla por terminada, como un nuevo punto de acceso de la API, una regla de validación o una pequeña ampliación de un servicio existente. El issue debe poder resolverse en un solo pull request y tener verificaciones que se puedan ejecutar sin acceso a producción.

    El ejemplo agrega paginación al historial de actividad de un proyecto. El ejemplo ejecutable en Node.js incluye autenticación, acceso a proyectos, cursores firmados y una ruta existente para consultar los detalles de un proyecto. Usa registros de prueba fijos en memoria, así que puedes verificar el contrato de paginación sin una base de datos ni un servicio externo.

    Prueba el ejemplo ejecutable

    Descarga el ejemplo, descomprímelo y abre la carpeta issue-to-tested-pr. Usa Node 22.6 o posterior. El ejemplo usa la biblioteca estándar y no requiere instalar paquetes ni tener una clave de API.

    node --version
    node api.mjs
    node test.mjs --seeded
    node test.mjs
    node reproduce.mjs ./evidence

    node api.mjs inicia un servidor HTTP local, solicita la primera página, imprime los ID públicos de sus eventos y cierra el servidor.

    La ejecución de las pruebas con --seeded termina deliberadamente con un código distinto de cero. Al continuar solo a partir de la marca de tiempo, se omiten dos eventos que tienen la misma marca de tiempo que el último evento de la primera página. Las mismas pruebas pasan con la corrección, que usa el par de marca de tiempo e ID para continuar.

    El comando de reproducción solo termina con éxito si las pruebas fallan en el estado con el defecto introducido y pasan en el estado corregido.

    Con limit=2, el recorrido corregido devuelve estas páginas en tres solicitudes:

    [evt_105, evt_104]
    [evt_103, evt_102]
    [evt_101]

    Las dos primeras páginas tienen un cursor de continuación. La última tiene next_cursor: null.

    El estado con el defecto devuelve [evt_105, evt_104] y luego [evt_101], por lo que se pierden evt_103 y evt_102.

    En una ejecución exitosa, las 36 pruebas pasan en el estado corregido. En el estado con el defecto fallan tres aserciones que ponen de manifiesto el problema de paginación.

    Para verificar tu entorno, coloca los archivos en el repositorio que elegiste y prepara y publica un entorno con una versión compatible de Node. Luego ejecuta los comandos en una tarea nueva en la nube y registra los resultados.

    reproduce.mjs guarda red.log, green.log, observed-http.json y run-manifest.json en el directorio indicado para la evidencia. El archivo de manifiesto identifica el entorno de ejecución, los comandos, los códigos de salida, el defecto introducido, la corrección y los hashes SHA-256 de los archivos fuente.

    No registra tokens de sesión ni la clave de firma. Consulta README.md y openapi.json para conocer la configuración del ejemplo y su contrato público.

    Examina el ejemplo ejecutable

    Estos fragmentos provienen de los archivos de la descarga. Ejecuta los archivos completos después de descomprimir el ZIP.

    Implementación del cursor y del orden de los eventos
    export function createApi({
      sessions,
      projects,
      events,
      signingKey,
      seededBug = false,
    }) {
      return createServer((request, response) => {
        const send = (status, body) => {
          response.writeHead(status, { "content-type": "application/json" });
          response.end(JSON.stringify(body));
        };
        const fail = (code) => {
          const [status, message] = errors[code];
          send(status, { error: { code, message } });
        };
        // Authenticate, then authorize. Malformed parameters must not change these results.
        const authorization = request.headers.authorization ?? "";
        const bearer = /^Bearer ([A-Za-z0-9_-]+)$/.exec(authorization);
        const tenantId = bearer ? sessions.get(bearer[1]) : undefined;
        if (!tenantId) return fail("unauthenticated");
    
        const url = new URL(request.url ?? "/", "http://127.0.0.1");
        const route = /^\/v1\/projects\/([^/]+)(\/events)?$/.exec(url.pathname);
        let projectId;
        try {
          projectId = route ? decodeURIComponent(route[1]) : undefined;
        } catch {
          return fail("not_found");
        }
        const project = projectId ? projects.get(projectId) : undefined;
        if (
          request.method !== "GET" ||
          !project ||
          project.tenant_id !== tenantId
        ) {
          return fail("not_found");
        }
        // Existing project-detail behavior remains independent of pagination parameters.
        if (!route[2]) return send(200, { id: projectId, name: project.name });
    
        const rawLimit = url.searchParams.get("limit");
        const limit = rawLimit === null ? 25 : Number(rawLimit);
        if (
          url.searchParams.getAll("limit").length > 1 ||
          (rawLimit !== null && !/^\d+$/.test(rawLimit)) ||
          !Number.isInteger(limit) ||
          limit < 1 ||
          limit > 100
        ) {
          return fail("invalid_limit");
        }
        let cursor;
        if (url.searchParams.has("cursor")) {
          try {
            if (url.searchParams.getAll("cursor").length !== 1)
              throw new Error("Invalid cursor");
            cursor = decodeCursor(
              url.searchParams.get("cursor"),
              tenantId,
              projectId,
              signingKey
            );
          } catch {
            return fail("invalid_cursor");
          }
        }
    
        const matching = events
          .filter(
            (event) =>
              event.tenant_id === tenantId && event.project_id === projectId
          )
          .sort(compareEvents)
          .filter((event) => {
            if (!cursor) return true;
            // Deliberate baseline defect: timestamp-only continuation loses tied rows.
            if (seededBug)
              return (
                timestampValue(event.created_at) < timestampValue(cursor.created_at)
              );
            return compareEvents(event, cursor) > 0;
          });
        const selected = matching.slice(0, limit);
        const items = selected.map(({ id, created_at, type, summary }) => ({
          id,
          created_at,
          type,
          summary,
        }));
        const nextCursor =
          matching.length > limit
            ? encodeCursor(selected.at(-1), tenantId, projectId, signingKey)
            : null;
        return send(200, { items, next_cursor: nextCursor });
      });
    }
    
    export async function startApi(fixtures, options = {}) {
      const server = createApi({ ...fixtures, ...options });
      await new Promise((resolve, reject) => {
        server.once("error", reject);
        server.listen(0, "127.0.0.1", () => resolve(undefined));
      });
      const address = server.address();
      if (!address || typeof address === "string")
        throw new Error("No listening address");
      return {
        baseUrl: `http://127.0.0.1:${address.port}`,
        close: () =>
          new Promise((resolve, reject) =>
            server.close((error) => (error ? reject(error) : resolve(undefined)))
          ),
      };
    }
    Registros precargados y datos de prueba para el acceso a proyectos
    export function createFixtures() {
      const tokenA = randomBytes(24).toString("base64url");
      const tokenB = randomBytes(24).toString("base64url");
      const sessions = new Map([
        [tokenA, "tenant_a"],
        [tokenB, "tenant_b"],
      ]);
      const projects = new Map([
        ["feed", { tenant_id: "tenant_a", name: "Activity feed" }],
        ["empty", { tenant_id: "tenant_a", name: "Empty feed" }],
        ["exact", { tenant_id: "tenant_a", name: "Four events" }],
        ["large", { tenant_id: "tenant_a", name: "Large feed" }],
        ["precision", { tenant_id: "tenant_a", name: "Precise timestamps" }],
        ["other", { tenant_id: "tenant_a", name: "Another project" }],
        ["private", { tenant_id: "tenant_b", name: "Private project" }],
      ]);
      const event = (id, created_at, project_id, tenant_id = "tenant_a") => ({
        id,
        created_at,
        project_id,
        tenant_id,
        type: "deployment",
        summary: `Synthetic event ${id}`,
        internal_note: "This field must never enter the HTTP response.",
        raw_payload: { synthetic: true },
      });
      const feed = [
        event("evt_105", "2026-01-15T12:00:03.123456Z", "feed"),
        event("evt_104", "2026-01-15T12:00:02.123456Z", "feed"),
        event("evt_103", "2026-01-15T12:00:02.123456Z", "feed"),
        event("evt_102", "2026-01-15T12:00:02.123456Z", "feed"),
        event("evt_101", "2026-01-15T12:00:01.123456Z", "feed"),
      ];
      const events = [
        ...feed,
        ...feed.slice(0, 4).map((item) => ({ ...item, project_id: "exact" })),
        ...Array.from({ length: 105 }, (_, index) =>
          event(
            `large_${String(index).padStart(3, "0")}`,
            new Date(Date.UTC(2026, 0, 15, 12, 0, index))
              .toISOString()
              .replace(".000Z", ".123456Z"),
            "large"
          )
        ),
        // Reverse ID order makes timestamp precision observable in the tests.
        event("precise_a", "2026-01-15T12:00:00.123999Z", "precision"),
        event("precise_z", "2026-01-15T12:00:00.123001Z", "precision"),
        event("other_1", "2026-01-15T12:00:00.123456Z", "other"),
        event("other_2", "2026-01-15T11:59:59.123456Z", "other"),
        event("private_1", "2026-01-15T12:00:00.123456Z", "private", "tenant_b"),
        event("private_2", "2026-01-15T11:59:59.123456Z", "private", "tenant_b"),
      ];
      return {
        sessions,
        projects,
        events,
        signingKey: randomBytes(32),
        tokenA,
        tokenB,
      };
    }
    Contrato HTTP y pruebas de regresión
    test("five-event traversal crosses a timestamp tie and replays the same continuation", async () => {
      const expected = ["evt_105", "evt_104", "evt_103", "evt_102", "evt_101"];
      const pages = await traverse("feed", 2, expected);
      assert.deepEqual(pages.map(ids), [
        ["evt_105", "evt_104"],
        ["evt_103", "evt_102"],
        ["evt_101"],
      ]);
      assert.equal(pages.length, 3);
      assert.ok(pages[0].next_cursor);
      assert.ok(pages[1].next_cursor);
      assert.equal(pages[2].next_cursor, null);
      const replay = await request("feed", {
        limit: "2",
        cursor: pages[0].next_cursor,
      });
      assert.deepEqual(replay.body.items, pages[1].items);
      const next = await request("feed", {
        limit: "2",
        cursor: replay.body.next_cursor,
      });
      assert.deepEqual(next.body.items, pages[2].items);
      assert.equal(next.body.next_cursor, null);
      console.log(
        JSON.stringify({
          fixture: "five-event-tie",
          pages: pages.map(ids),
          requests: pages.length,
          replay: ids(replay.body),
        })
      );
    });
    
    test("the same tie group traverses correctly with limit three", async () => {
      const pages = await traverse("feed", 3, [
        "evt_105",
        "evt_104",
        "evt_103",
        "evt_102",
        "evt_101",
      ]);
      assert.deepEqual(pages.map(ids), [
        ["evt_105", "evt_104", "evt_103"],
        ["evt_102", "evt_101"],
      ]);
    });
    
    test("timestamp ordering and continuation preserve submillisecond precision", async () => {
      const pages = await traverse("precision", 1, ["precise_a", "precise_z"]);
      assert.equal(pages[0].items[0].created_at, "2026-01-15T12:00:00.123999Z");
      assert.equal(pages[1].items[0].created_at, "2026-01-15T12:00:00.123001Z");
    });
    
    test("omitted, minimum, and maximum limits return 25, 1, and 100 of 105 events", async () => {
      for (const [parameters, expected] of [
        [{}, 25],
        [{ limit: "1" }, 1],
        [{ limit: "100" }, 100],
      ]) {
        const result = await request("large", parameters);
        assert.equal(result.status, 200);
        assert.equal(result.body.items.length, expected);
        assert.ok(result.body.next_cursor);
      }
      const pages = await traverse(
        "large",
        100,
        Array.from(
          { length: 105 },
          (_, index) => `large_${String(104 - index).padStart(3, "0")}`
        )
      );
      assert.deepEqual(
        pages.map((page) => page.items.length),
        [100, 5]
      );
    });
    
    test("invalid and repeated limits return the documented generic error", async (context) => {
      for (const limit of [
        "0",
        "101",
        "-1",
        "1.5",
        "many",
        "",
        "1e2",
        "Infinity",
      ]) {
        await context.test(`limit ${JSON.stringify(limit)}`, async () => {
          const result = await request("feed", { limit });
          assert.equal(result.status, 400);
          assert.deepEqual(
            result.body,
            expectedError(
              "invalid_limit",
              "Limit must be an integer from 1 through 100."
            )
          );
        });
      }
      const repeated = await request("feed", [
        ["limit", "1"],
        ["limit", "2"],
      ]);
      assert.equal(repeated.status, 400);
    });
    
    test("empty, partial, and exactly full final pages have no continuation", async () => {
      assert.deepEqual((await request("empty")).body, {
        items: [],
        next_cursor: null,
      });
      const partial = await traverse("feed", 2, [
        "evt_105",
        "evt_104",
        "evt_103",
        "evt_102",
        "evt_101",
      ]);
      assert.equal(partial.at(-1).items.length, 1);
      const exact = await traverse("exact", 2, [
        "evt_105",
        "evt_104",
        "evt_103",
        "evt_102",
      ]);
      assert.deepEqual(
        exact.map((page) => page.items.length),
        [2, 2]
      );
      assert.equal(exact[1].next_cursor, null);
    });
    
    test("authentication precedes malformed and wrong-scope cursor or limit validation", async () => {
      const privateCursor = (
        await request("private", { limit: "1" }, fixtures.tokenB)
      ).body.next_cursor;
      for (const token of [null, "invalid_session"]) {
        for (const parameters of [
          { cursor: "broken" },
          { cursor: privateCursor },
          { limit: "0", cursor: "broken" },
        ]) {
          const result = await request("feed", parameters, token);
          assert.equal(result.status, 401);
          assert.deepEqual(result.body, unauthenticated);
        }
      }
    });
    
    test("inaccessible and nonexistent projects return identical 404 before cursor validation", async () => {
      const ownCursor = (await request("feed", { limit: "1" })).body.next_cursor;
      for (const project of ["private", "missing"]) {
        for (const parameters of [
          {},
          { cursor: "broken" },
          { cursor: ownCursor },
          { limit: "0", cursor: "broken" },
        ]) {
          const result = await request(project, parameters);
          assert.equal(result.status, 404);
          assert.deepEqual(result.body, notFound);
        }
      }
    });
    
    test("a valid cursor from another project in the same tenant is invalid on an authorized feed", async () => {
      const cursor = (await request("other", { limit: "1" })).body.next_cursor;
      assert.ok(cursor);
      const result = await request("feed", { cursor });
      assert.equal(result.status, 400);
      assert.deepEqual(result.body, invalidCursor);
    });
    
    test("a valid cursor from another tenant is invalid on an authorized feed", async () => {
      const cursor = (await request("private", { limit: "1" }, fixtures.tokenB))
        .body.next_cursor;
      assert.ok(cursor);
      const result = await request("feed", { cursor });
      assert.equal(result.status, 400);
      assert.deepEqual(result.body, invalidCursor);
    });
    
    test("undecodable, empty, tampered, and repeated cursor tokens return the same closed error", async () => {
      const cursor = (await request("feed", { limit: "1" })).body.next_cursor;
      const [payload, signature] = cursor.split(".");
      const tampered = `${payload}.${signature[0] === "A" ? "B" : "A"}${signature.slice(1)}`;
      for (const token of [
        "broken",
        "",
        "%%%.___",
        "a.b.c",
        "x".repeat(2049),
        tampered,
      ]) {
        const result = await request("feed", { cursor: token });
        assert.equal(result.status, 400);
        assert.deepEqual(result.body, invalidCursor);
      }
      const result = await request("feed", [
        ["cursor", cursor],
        ["cursor", cursor],
      ]);
      assert.equal(result.status, 400);
      assert.deepEqual(result.body, invalidCursor);
    });
    
    test("signed structurally invalid payloads and unsupported versions are rejected", async (context) => {
      const valid = {
        version: 1,
        tenant_id: "tenant_a",
        project_id: "feed",
        created_at: "2026-01-15T12:00:02.123456Z",
        id: "evt_104",
      };
      const missing = { ...valid };
      delete missing.id;
      const cases = [
        ["missing ID", missing],
        ["number ID", { ...valid, id: 104 }],
        ["empty ID", { ...valid, id: "" }],
        ["number timestamp", { ...valid, created_at: 0 }],
        ["invalid date", { ...valid, created_at: "2026-02-30T12:00:00Z" }],
        ["unsupported version", { ...valid, version: 2 }],
        ["extra field", { ...valid, extra: true }],
        ["wrong tenant type", { ...valid, tenant_id: 1 }],
        ["wrong project type", { ...valid, project_id: null }],
        ["null payload", null],
        ["array payload", []],
        ["invalid JSON", "not-json"],
      ];
      for (const [name, payload] of cases) {
        await context.test(name, async () => {
          const result = await request("feed", { cursor: signFixture(payload) });
          assert.equal(result.status, 400);
          assert.deepEqual(result.body, invalidCursor);
        });
      }
    });
    
    test("every serialized event omits stored tenant, project, and raw payload fields", async () => {
      const result = await request("feed", { limit: "100" });
      assert.equal(result.status, 200);
      for (const item of result.body.items) {
        assert.deepEqual(Object.keys(item).sort(), [
          "created_at",
          "id",
          "summary",
          "type",
        ]);
      }
      assert.ok(
        fixtures.events.some((event) => event.internal_note && event.raw_payload)
      );
    });
    
    test("the schema oracle rejects extra, missing, incorrectly typed, and malformed public fields", () => {
      const schema = specification.components.schemas.EventPage;
      const page = {
        items: [
          {
            id: "evt_1",
            created_at: "2026-01-15T12:00:00.123456Z",
            type: "deployment",
            summary: "Synthetic",
          },
        ],
        next_cursor: null,
      };
      assert.ok(conforms(page, schema));
      assert.equal(conforms({ ...page, tenant_id: "tenant_a" }, schema), false);
      assert.equal(
        conforms(
          { ...page, items: [{ ...page.items[0], raw_payload: {} }] },
          schema
        ),
        false
      );
      assert.equal(conforms({ items: page.items }, schema), false);
      assert.equal(conforms({ ...page, next_cursor: "" }, schema), false);
      assert.equal(
        conforms({ ...page, items: [{ ...page.items[0], id: 1 }] }, schema),
        false
      );
      assert.equal(
        conforms(
          { ...page, items: [{ ...page.items[0], created_at: "yesterday" }] },
          schema
        ),
        false
      );
      const errorSchema = specification.components.schemas.InvalidCursor;
      assert.equal(
        conforms(
          { error: { ...invalidCursor.error, stack: "internal" } },
          errorSchema
        ),
        false
      );
      assert.equal(
        conforms(
          { error: { ...invalidCursor.error, message: "tenant_a" } },
          errorSchema
        ),
        false
      );
    });
    
    test("the existing project-detail route preserves access and ignores pagination parameters", async () => {
      const own = await request(
        "feed",
        { cursor: "broken", limit: "0" },
        fixtures.tokenA,
        true
      );
      assert.equal(own.status, 200);
      assert.deepEqual(own.body, { id: "feed", name: "Activity feed" });
      const inaccessible = await request("private", {}, fixtures.tokenA, true);
      assert.equal(inaccessible.status, 404);
      assert.deepEqual(inaccessible.body, notFound);
      const unauthenticatedResult = await request("feed", {}, null, true);
      assert.equal(unauthenticatedResult.status, 401);
      assert.deepEqual(unauthenticatedResult.body, unauthenticated);
    });
    
    test("OpenAPI declares operation, limit boundaries, signed cursor, and response statuses", () => {
      assert.equal(specification.openapi, "3.1.0");
      const operation = specification.paths["/v1/projects/{project_id}/events"].get;
      assert.equal(operation.operationId, "listProjectEvents");
      const limit = operation.parameters.find(
        (parameter) => parameter.name === "limit"
      ).schema;
      assert.deepEqual(limit, {
        type: "integer",
        minimum: 1,
        maximum: 100,
        default: 25,
      });
      assert.deepEqual(Object.keys(operation.responses).sort(), [
        "200",
        "400",
        "401",
        "404",
      ]);
      assert.equal(
        specification.components.securitySchemes.session.scheme,
        "bearer"
      );
    });
    Recopilación de evidencia
    mkdirSync(output, { recursive: true });
    const states = [];
    for (const [name, arguments_] of [
      ["red", ["--seeded"]],
      ["green", []],
    ]) {
      const result = spawnSync(
        process.execPath,
        [resolve(directory, "test.mjs"), ...arguments_],
        {
          cwd: directory,
          encoding: "utf8",
          timeout: 30_000,
        }
      );
      writeFileSync(
        resolve(output, `${name}.log`),
        `${result.stdout ?? ""}${result.stderr ?? ""}`
      );
      states.push({
        name,
        command: `node test.mjs${arguments_.length ? " --seeded" : ""}`,
        exit_code: result.status,
        signal: result.signal,
        error: result.error?.message ?? null,
      });
    }
    const sources = Object.fromEntries(
      readdirSync(directory, { withFileTypes: true })
        .filter((entry) => entry.isFile())
        .map((entry) => [
          entry.name,
          createHash("sha256")
            .update(readFileSync(resolve(directory, entry.name)))
            .digest("hex"),
        ])
    );
    const observed = { red: await observe(true), green: await observe(false) };
    writeJson("observed-http.json", observed);
    writeJson("run-manifest.json", {
      execution: "Synthetic loopback HTTP fixture; no external service",
      recorded_at: new Date().toISOString(),
      node: process.version,
      minimum_node: "22.6.0",
      dependencies: "Node standard library only; no install step",
      baseline:
        "Deliberately seeded timestamp-only continuation; not a historical customer defect",
      correction:
        "Descending timestamp-and-ID tuple continuation using full fractional precision",
      source_sha256: sources,
      states,
    });
    if (
      states[0].exit_code === null ||
      states[0].exit_code === 0 ||
      states[1].exit_code !== 0
    ) {
      throw new Error(
        "Expected the unchanged tests to fail on the seeded baseline and pass on the corrected implementation. Inspect red.log and green.log."
      );
    }
    console.log(
      JSON.stringify(
        {
          execution: "synthetic-loopback-http",
          red_exit: states[0].exit_code,
          green_exit: states[1].exit_code,
          green_pages: observed.green.pages.map((page) =>
            page.body.items.map((item) => item.id)
          ),
          evidence_directory: output,
        },
        null,
        2
      )
    );

    Prepara la información de entrada

    Un enlace al issue solo sirve si Codex puede leerlo. Si la fuente no está disponible, pega o adjunta el contenido pertinente. Incluye la siguiente información junto con el prompt inicial.

    Información Issue de ejemplo
    Issue y resultado esperado API-142: agrega GET /v1/projects/{project_id}/events para que los clientes puedan recorrer el historial de actividad por páginas.
    Repositorio y base Coloca la carpeta del ejemplo en el repositorio que elegiste. Luego registra la rama y el commit exacto que seleccionaste para la tarea.
    Código pertinente Los archivos api.mjs, fixtures.mjs, test.mjs y openapi.json del ejemplo, además del archivo AGENTS.md de tu repositorio.
    Patrones existentes Mantén la autenticación y la autorización de acceso al proyecto antes de la validación de la paginación. Usa cursores firmados y la estructura pública documentada para los errores. Al adaptar el issue, sigue los patrones de las funciones auxiliares de tu repositorio.
    Configuración de las pruebas Proyectos de prueba fijos en memoria para dos tenants, seis proyectos del tenant A, eventos con la misma marca de tiempo repartidos entre dos páginas, marcas de tiempo con precisión fraccionaria, páginas finales vacías y páginas finales con exactamente el máximo de elementos, y 105 eventos para verificar los límites.
    Validación node test.mjs --seeded (fallo esperado), node test.mjs (estado corregido) y node reproduce.mjs ./evidence. Ejecuta node --check para cada archivo .mjs. Para tu propio servicio, agrega los comandos que usa para la verificación de tipos, el lint, las pruebas de regresión y la CI.
    Alcance El punto de acceso, sus pruebas específicas y su documentación de OpenAPI. Quedan fuera las dependencias nuevas, la migración de la base de datos, el frontend, el despliegue y el rediseño de la autenticación o la autorización.
    Entrega Un borrador de PR si la integración disponible con el repositorio lo permite y autorizaste su creación. En caso contrario, un diff y un borrador de la descripción del PR.

    Para el ejemplo, prepara Node 22.6 o posterior. Las pruebas crean sus propias sesiones de prueba y su propia clave para firmar cursores, y vinculan un servidor HTTP temporal a la interfaz de loopback. Para tu propio servicio, prepara también sus dependencias, su configuración de pruebas y sus servicios aislados. Antes de asignar este issue, verifica que la función auxiliar de cursores pueda vincular un cursor con el tenant autenticado y el proyecto solicitado.

    Define el contrato de la API

    Antes de implementar el cambio, confirma que el siguiente contrato del ejemplo coincide con el de tu repositorio. Una respuesta exitosa tiene el estado HTTP 200 y contiene exactamente { items, next_cursor }.

    Cada elemento tiene exactamente cuatro campos públicos obligatorios: id (cadena no vacía), created_at (cadena con una marca de tiempo UTC en formato RFC 3339), type (cadena no vacía) y summary (cadena). items es un arreglo de estos objetos. next_cursor es una cadena opaca no vacía o null.

    Las claves internas del tenant y del proyecto, los metadatos de la base de datos, el contenido sin procesar de los eventos y las credenciales no forman parte de la respuesta. Especifica en OpenAPI los tipos de los campos de respuesta, cuáles son obligatorios, cuáles admiten null y la estructura de los objetos que no admiten propiedades adicionales. Verifica que los cuerpos HTTP serializados cumplan ese esquema.

    Los eventos se ordenan por created_at DESC, id DESC. Se conserva la precisión fraccionaria de la marca de tiempo y los ID siguen un orden lexicográfico descendente. El cursor reanuda el recorrido estrictamente después de la última tupla devuelta según ese orden.

    El tamaño de página predeterminado es 25. Los límites válidos son números enteros decimales del 1 al 100, ambos inclusive. No se puede repetir el parámetro de límite ni el de cursor. Devuelve un cursor no nulo solo si existe otro evento que cumpla los criterios.

    Tanto una página final parcial como una que contiene exactamente el máximo de elementos devuelven next_cursor: null. Un proyecto vacío al que se tiene acceso devuelve items: [] y next_cursor: null.

    El ejemplo firma cada cursor y lo vincula con el tenant autenticado y el proyecto solicitado. Verifica la autenticación y autoriza el acceso al proyecto solicitado antes de validar el límite o el cursor. Así, los errores de parámetros no pueden revelar si existe un proyecto de otro tenant.

    Si la sesión falta o no es válida, la respuesta es 401. Si el proyecto es inaccesible o no existe, la respuesta es el mismo error 404 genérico. Después de confirmar el acceso, verifica el límite antes que el cursor.

    Si el cursor está mal formado, fue manipulado, tiene una estructura no válida o no corresponde al tenant o al proyecto, la respuesta es HTTP 400 con el código invalid_cursor. Si se envía a un proyecto autorizado un cursor válido para otro proyecto del mismo tenant o para otro tenant, ese cursor se considera no válido.

    Enviar un cursor no debe cambiar el resultado de la autorización para un proyecto inaccesible. El cursor de la versión 1 del ejemplo no caduca. Las versiones no compatibles se consideran no válidas.

    En el ejemplo, cada cuerpo de error contiene exactamente { error: { code, message } }. Ambos campos son obligatorios y de tipo cadena, y no se admiten campos adicionales.

    Usa estas respuestas de error genéricas:

    • 400 invalid_cursor / Invalid cursor.
    • 400 invalid_limit / Limit must be an integer from 1 through 100.
    • 401 unauthenticated / Authentication required.
    • 404 not_found / Project not found.

    Excluye de todos los cuerpos de error el contenido decodificado del cursor, los identificadores de tenant y proyecto, los errores sin procesar y las trazas de pila. Si el repositorio real usa otra estructura u otros códigos aprobados, usa los de ese repositorio. Resuelve esa diferencia de contrato antes de cambiar las funciones auxiliares compartidas.

    Las aserciones de recorrido y repetición usan datos de prueba fijos sin escrituras concurrentes. Al seguir los cursores, cada evento se devuelve una sola vez en el orden acordado. El recorrido termina en un máximo de ceil(event_count / limit) solicitudes si el historial contiene eventos, o en una solicitud si está vacío.

    Mientras los datos de prueba permanezcan sin cambios, volver a usar un cursor válido con los mismos parámetros de solicitud devuelve los mismos elementos y la misma posición de continuación. Los tokens firmados que se generan no tienen que ser idénticos byte por byte.

    Este contrato no garantiza cursores de un solo uso, paginación sobre una instantánea ni resultados idénticos después de que cambie el historial. Documenta la política de consistencia que el repositorio ya aplica al historial. Si no existe una política y el issue requiere paginación sobre una instantánea, detente hasta que se tome una decisión de producto.

    Cómo usar este flujo de trabajo

    1. Elige el issue y prepara el entorno. Confirma que el contrato anterior coincide con el del repositorio real. Selecciona el entorno en la nube que preparaste y el commit base previsto. Incluye la información del issue directamente en la tarea. Para iniciar este flujo no necesitas un disparador automático del sistema de seguimiento de issues.

    2. Ejecuta el prompt inicial. Reemplaza los campos entre corchetes con los datos del repositorio, la información del issue, los límites del alcance y las verificaciones. Elige si Codex debe devolver un diff o crear un borrador de PR con tu autorización. El prompt inicial de esta página contiene las instrucciones completas para la entrega.

    3. Verifica el estado inicial. Antes de editar, Codex debe leer las instrucciones del repositorio y ejecutar las verificaciones iniciales aprobadas.

      Para el ejemplo, ejecuta node test.mjs y las verificaciones de sintaxis de README.md. La opción --seeded sirve para demostrar por separado un fallo deliberado.

      Para tu propio servicio, ejecuta las verificaciones existentes de regresión, tipos y lint. Es posible que aún no exista el archivo de pruebas específicas del nuevo punto de acceso. En ese caso, registra la prueba como pendiente de creación. Resuelve los fallos en las verificaciones existentes o los problemas de disponibilidad de los servicios de prueba antes de atribuir resultados posteriores a la nueva funcionalidad.

    4. Implementa y verifica el contrato. Pide a Codex que use las funciones auxiliares existentes para el acceso y los cursores, implemente el punto de acceso, agregue las pruebas de aceptación que se indican más adelante y actualice la documentación de la API. Cuando sea viable, las pruebas nuevas deben fallar con el código original y pasar después del cambio. Guarda esos resultados con el registro de verificación final. En el ejemplo descargable, --seeded activa un defecto deliberado para hacer esta comparación. Esto es independiente de los posibles fallos de las verificaciones iniciales en tu repositorio.

    5. Revisa el cambio y la evidencia. Revisa en conjunto el diff, las verificaciones de aceptación, sus resultados y los riesgos pendientes. Si Codex crea un borrador de PR autorizado, verifica que tanto la base como el origen de los cambios sean correctos. Si las herramientas de PR no están disponibles, usa el diff y la descripción que recibiste para preparar el PR según tu proceso habitual. Enumera en el borrador de PR las verificaciones pendientes y mantenlo como borrador mientras sigan sin resolverse.

    6. Atiende los comentarios de la revisión e integra el cambio según tu proceso habitual. En la misma tarea, indica a Codex qué comentarios debe atender. Luego vuelve a ejecutar las verificaciones afectadas. Antes de integrar el cambio, la persona que lo revisa confirma que se cumplan los requisitos de CI y aprueba el commit final.

    Pruebas de aceptación obligatorias

    Estas pruebas delimitan el issue para que puedas delegarlo y definen con precisión lo que se debe revisar. Adapta los nombres a tu herramienta de pruebas y conserva el comportamiento que demuestra cada prueba.

    Recorrido estable

    Carga cinco eventos fijos: evt_105 tiene la marca de tiempo más reciente, evt_104, evt_103 y evt_102 comparten la marca de tiempo intermedia, y evt_101 tiene la más antigua.

    Con limit=2, exige exactamente estas páginas: [evt_105, evt_104], [evt_103, evt_102], [evt_101]. Los eventos con la misma marca de tiempo deben quedar repartidos entre la primera página y la segunda.

    Comprueba que el orden sea exacto, que no falten ni se repitan ID, que los cursores de las dos primeras páginas no sean nulos y que el de la tercera sí lo sea. Haz que la prueba falle si el recorrido supera las tres solicitudes.

    Vuelve a usar el primer cursor de continuación con el mismo límite y sin cambiar los datos de prueba. Comprueba que la segunda página tenga los mismos elementos y la misma posición de continuación.

    Tamaño predeterminado y valores límite

    Carga más de 100 eventos. Comprueba que se devuelvan 25 elementos sin el parámetro limit, uno con limit=1 y 100 con limit=100.

    Límites no válidos

    Comprueba que se devuelva HTTP 400 para 0, 101, -1, un valor fraccionario y una cadena como many, con la estructura existente para los errores de validación.

    Páginas vacías y finales

    Comprueba que un proyecto vacío al que se tiene acceso devuelva items: [] y next_cursor: null. Con limit=2, comprueba que cinco eventos produzcan una tercera página con un elemento y un cursor nulo, y que cuatro eventos produzcan una segunda página con dos elementos y un cursor nulo. Cuando la última página esté llena, no debe hacer falta otra solicitud para obtener una página vacía.

    Autenticación

    Comprueba que las solicitudes sin sesión o con una sesión no válida reciban un 401 con el esquema acordado, incluso cuando incluyan un cursor mal formado o de otro ámbito. La validación del cursor no debe prevalecer sobre la autenticación.

    Aislamiento entre tenants

    Autentícate como el tenant A y solicita un proyecto del tenant B o un proyecto inexistente. Comprueba que en ambos casos se devuelva el mismo cuerpo genérico acordado para el error 404, sin datos de eventos, incluso si la solicitud incluye un cursor mal formado o de otro ámbito. La autorización debe preceder a la validación del cursor.

    Ámbito del cursor

    Solicita un proyecto del tenant A al que tengas acceso con (1) un cursor firmado válido de otro proyecto del tenant A y (2) un cursor firmado válido emitido para el tenant B.

    Comprueba que ambas solicitudes devuelvan HTTP 400 con el mismo cuerpo de error invalid_cursor, sin datos de eventos ni detalles sobre el ámbito. Prueba estos casos por separado de las solicitudes a un proyecto inaccesible, que deben devolver 404.

    Cursores mal formados o manipulados

    En un proyecto al que tengas acceso, comprueba que se devuelva HTTP 400 con invalid_cursor para los tokens que la función auxiliar no puede decodificar, los tokens con firmas modificadas y los tokens firmados que omiten campos obligatorios del cursor o que asignan a esos campos valores de un tipo incorrecto. Usa datos de firma aislados para las pruebas.

    Incluye versiones no compatibles o tokens caducados si la función auxiliar compartida admite esas características. Comprueba que las respuestas sigan el esquema genérico de error acordado y que no contengan datos decodificados, identificadores, errores sin procesar ni trazas de pila.

    Comportamiento existente

    El ejemplo comprueba la ruta existente de detalles del proyecto con solicitudes autorizadas, solicitudes no autorizadas y solicitudes a proyectos inaccesibles. Ejecuta las verificaciones de sintaxis de sus archivos .mjs. En tu servicio, ejecuta el conjunto habitual de pruebas de regresión, la verificación de tipos y el lint.

    Esquema de la respuesta pública

    Valida los cuerpos reales de las respuestas HTTP 200, 400, 401 y 404 con los esquemas de OpenAPI acordados. Comprueba las claves obligatorias, los tipos de los campos de cada elemento, el formato de la marca de tiempo, si el cursor admite null y la ausencia de propiedades adicionales en todos los niveles de los objetos.

    Carga eventos que incluyan campos internos y verifica que esos campos no aparezcan en los elementos serializados. Verifica que cada respuesta de error contenga únicamente el código y el mensaje genéricos aprobados.

    Documentación

    Verifica que la operación de OpenAPI y sus ejemplos coincidan con los parámetros implementados, los esquemas exactos de las respuestas exitosas y de error, la prioridad de la autorización, el orden de los eventos, las reglas para la última página y los límites de repetición y consistencia con datos de prueba fijos. Si el repositorio tiene una validación de la especificación de la API, ejecútala.

    Qué debe incluir la entrega

    Registro compartido de evidencia

    Usa este formato para registrar la evidencia de cada criterio de aceptación. Complétalo con los resultados observados. Si una prueba nueva aún no existe en el estado inicial, regístrala como “aún no existe”. Reserva el estado “fallida” para una verificación que se ejecutó y falló.

    Criterio / prueba: [comportamiento específico y ubicación de la prueba]
    Entorno: [referencia a la configuración publicada y versiones del entorno de ejecución, de la base de datos y de las dependencias]
    Antes: [revisión original e ID del diff que solo modifica las pruebas, comando, código de salida, resultado observado de la aserción y ruta del registro]
    Después: [revisión final o revisión base más el ID del diff, comando, código de salida, resultado observado de la aserción y ruta del registro]
    Estado: [aprobada / fallida / bloqueada / sin ejecutar]
    Limitaciones / acción de quien revisa: [comportamiento sin probar, impedimento o decisión necesaria]

    Vincula las ejecuciones fallidas y exitosas con los estados exactos que se probaron, incluidos los cambios limitados a las pruebas que se aplicaron a la revisión original. Incluye los registros, los datos de prueba de las respuestas y el diff final en la entrega. Los nombres de los archivos y de las pruebas por sí solos no demuestran que se hayan ejecutado. Actualiza la evidencia cuando cambie la revisión probada. Las verificaciones bloqueadas o que no se hayan ejecutado no cuentan como aprobadas.

    Una descripción de PR útil vincula cada afirmación con la evidencia que la respalda. Usa esta plantilla de entrega y reemplaza todos los campos entre corchetes con los resultados observados.

    Título: agregar paginación con cursores a los eventos del proyecto
    Issue: API-142
    Base: [rama y commit]
    Estado probado: [commit de la rama de origen o commit base más el identificador del diff adjunto]
    
    Cambios
    - Agrega el punto de acceso para los eventos del proyecto con la función auxiliar existente de acceso al proyecto.
    - Reutiliza cursores firmados vinculados al tenant y al proyecto.
    - Agrega pruebas del contrato y actualiza la operación de OpenAPI.
    
    Evidencia de aceptación
    - [Una fila por cada criterio anterior con la prueba, el resultado observado y la referencia al registro.]
    
    Validación
    - Antes de los cambios: [comando, resultado y referencia al registro de cada verificación inicial].
    - Pruebas nuevas con el código original: [resultado o indicación de que no se ejecutaron y el motivo].
    - Después de los cambios: [comando, resultado y referencia al registro de cada verificación obligatoria].
    - CI: [enlaces a las verificaciones y sus estados reales, o indicación de que la CI no está disponible].
    - Verificaciones pendientes: [verificaciones concretas y qué impide completarlas, o evidencia de que no queda ninguna pendiente].
    
    Prioridades de la revisión
    - [Comportamiento de mayor riesgo y criterios pendientes de resolución.]
    
    Riesgos pendientes y reversión
    - [Limitaciones observadas. Distínguelas de los supuestos que aún no se han comprobado.]
    - [Cómo revertir el cambio y si hay código que dependa de él.]

    Ajusta el cambio a partir de los comentarios de la revisión

    Da una instrucción de seguimiento centrada en un hallazgo concreto de la revisión. Por ejemplo:

    Atiende este comentario de la revisión en el cambio actual:
    “Repite el recorrido de los cinco eventos con limit=3. Comprueba que la primera página contenga
    [evt_105, evt_104, evt_103], que la segunda contenga [evt_102, evt_101] y que el cursor de continuación de la segunda sea nulo.”
    
    Agrega la prueba de regresión. Modifica la implementación solo si esa prueba revela un fallo.
    Vuelve a ejecutar las pruebas específicas del punto de acceso y las demás verificaciones obligatorias
    que se vean afectadas por el cambio. Actualiza la tabla de evidencia con el estado probado y los resultados observados.
    Mantén el mismo alcance. No integres ni despliegues el cambio.

    Si Codex no puede acceder directamente a la revisión, proporciona el texto del comentario. Quien revise el cambio debe comprobar que se haya corregido el comportamiento señalado, aunque la respuesta ya cumpla al pie de la letra lo que dice el comentario.

    Cuándo detenerte

    • Las verificaciones iniciales fallan o no se pueden ejecutar. Devuelve la verificación que falló y los detalles de la configuración. Corrige el entorno o acuerda y documenta un estado inicial antes de continuar.
    • El contrato entra en conflicto con el comportamiento existente. Identifica el conflicto, el código afectado que usa esa funcionalidad y la decisión mínima necesaria. No cambies el contrato sin señalarlo.
    • El cambio requiere una migración, una dependencia nueva o un rediseño de la autenticación o la autorización. Explica el motivo y divide el issue o redefine su alcance antes de ampliarlo.
    • Las pruebas fallan después de la implementación. No des el cambio por listo para integrarse. Informa qué casos fallaron. No elimines aserciones ni debilites la autorización para que pasen las verificaciones.
    • No está disponible la integración que permite escribir en el repositorio. Devuelve el diff, el borrador de la descripción del PR y la evidencia de validación. Alguien de tu equipo puede completar la entrega en el repositorio.

    Reutiliza el flujo de trabajo

    Una vez que la persona que revisa el cambio acepte el primer resultado, guarda la información del issue y la lista de verificación de los criterios de aceptación en la plantilla de issues de tu equipo. Registra en AGENTS.md o en la guía de contribución existente las instrucciones del repositorio que se mantienen estables, como los comandos de prueba y la ubicación de los contratos de la API.

    Related use cases