parche-cli 0.6.6 → 0.6.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,6 +1,41 @@
1
1
  # Cambios
2
2
 
3
- Todas las versiones de `parche-cli` (npm) y de la Action `Pepo08/parche@v0`.
3
+ Los cambios de cada versión de `parche-cli` y de la Action `Pepo08/parche@v0`. En npm están la 0.1.1, la 0.5.1 y de la 0.6.0 en adelante. La 0.1.0 y de la 0.2.0 a la 0.5.0 no se publicaron en npm; lo que traían está incluido en la 0.5.1. La fecha de cada versión es la del cambio; la de publicación figura en npm y en los [Releases](https://github.com/Pepo08/parche/releases).
4
+
5
+ ## 0.6.7 — 2026-10-21
6
+
7
+ Pack de IA con la API version `2026-10-21` de Anthropic, que salió el 21/10 junto con el SDK de TypeScript 1.0.0 y el de Python 2.0.0: elimina `temperature`, `top_p` y `top_k` para cualquier modelo, y un pedido que los manda devuelve un error 400 aunque tengan su valor por defecto (guía "Migrating to API version 2026-10-21" y página de deprecaciones, verificadas el 21/10). Los SDK anteriores siguen mandando `2023-06-01`, así que hoy casi nada se rompe: se rompe con la próxima suba del SDK. Como siempre, Parche avisa y no toca el código. Esta versión trae además las bases de confianza del repo (al final).
8
+
9
+ ### Nuevo
10
+ - **Los tres parámetros con cualquier modelo,** también Claude Sonnet 4.6, Opus 4.6 y Haiku 4.5, que en `2023-06-01` los aceptan. El aviso dice en cuál de tres casos está el pedido:
11
+ - **ya falla:** el archivo manda `anthropic-version: 2026-10-21`, o el SDK instalado ya es el nuevo (`@anthropic-ai/sdk` 1.0.0 o posterior; `anthropic` 2.0.0 o posterior por `extra_body`). En Python, el parámetro directo ya tira `TypeError` desde la 1.0.0 (MIGRATION.md del SDK);
12
+ - **falla en una instalación nueva:** el rango declarado no tiene tope (`>=0.30.0`, `>=0.40`) y ya deja instalar la versión nueva;
13
+ - **va a fallar con la suba:** el SDK instalado es anterior; el aviso pide sacar el parámetro antes de aceptar esa suba.
14
+ - **Sin repetir avisos:** lo que ya marca la regla de Claude 4.7 (un valor fuera de lo aceptado) sale una sola vez. En un modelo que todavía acepta otros valores, el aviso aclara que sacarlo cambia cómo responde y que la alternativa es dejar ese pedido en `2023-06-01`. Un archivo que fija `anthropic-version: 2023-06-01` no se avisa.
15
+ - **Versión de Python desde `requirements.txt`:** el contexto del repo ahora también trae las versiones declaradas de Python y el rango tal como está escrito.
16
+ - **PRs abiertos con el `GITHUB_TOKEN`:** GitHub deja sus workflows esperando aprobación. Cuando la Action corre con el token por defecto, el cuerpo del PR lo dice y explica cómo usar un token de GitHub App o uno personal para que la CI corra sola (README, "En GitHub Actions").
17
+ - **La Action se llama "Parche API Changes"** (antes, "Parche — Dependabot para APIs"), para publicarla en el GitHub Marketplace. Sus reglas no aceptan un nombre igual a una función de GitHub (Dependabot, que queda solo en la descripción) ni a un usuario de GitHub (existe la cuenta `parche`).
18
+
19
+ ### Cambios
20
+ - La fuente del `TypeError` del SDK de Python pasa a ser su MIGRATION.md de la 1.0.0: la guía de migración a Opus 5.5 ya no lo menciona.
21
+
22
+ ### Todavía no cubre
23
+ - La versión del SDK de Python desde `pyproject.toml`, `poetry.lock` o `uv.lock`, y los rangos sin número (`latest`, `*`), que cuentan como versión desconocida.
24
+ - Los SDKs de Go (v2), Java (3.0.0), .NET (13.0.0), Ruby (2.0.0) y PHP (1.0.0), que también pasan a `2026-10-21`.
25
+ - Los otros cambios de esa API version: bloques `document` en mensajes de `assistant`, `citations` en mensajes de `user` y los campos renombrados de la Admin API.
26
+
27
+ ### Bases de confianza del repo
28
+
29
+ Qué hace Parche con tu código, cómo reportar una vulnerabilidad y cómo contribuir.
30
+
31
+ - **`SECURITY.md`:** cómo reportar una vulnerabilidad (en privado, primera respuesta en 14 días como máximo) y qué hace Parche en tu máquina o en tu runner: a qué hosts se conecta y con qué comando, que no tiene telemetría, qué ejecuta (tus tests, solo con `--verify`) y qué archivos escribe.
32
+ - **README:** un bloque en inglés al principio, la sección "Seguridad y datos" y qué puede cambiar dentro de `v0`.
33
+ - **`CONTRIBUTING.md`** (con el proceso para sacar una versión, que sale del README), **código de conducta**, plantillas de issue (falso positivo o aviso que falta, pedido de pack, error) y de PR.
34
+ - **`parche --version`.**
35
+ - **"¿Por qué recibí un PR de Parche?"** ([`docs/por-que-recibi-este-pr.md`](docs/por-que-recibi-este-pr.md)): para quien recibe un PR o un issue de Parche. Explica por qué su repo, cómo se revisó el PR, qué se verificó y qué no, y cómo pedir que no le manden más. El pie de cada PR y de cada issue la enlaza.
36
+ - **La plantilla del workflow pide solo lectura por defecto** y da `contents: write` y `pull-requests: write` solo al job de Parche. Explica cómo fijar la Action a un commit en vez de `v0`.
37
+ - **Las Actions de la CI, del release y de la plantilla quedan fijadas por SHA** (`actions/checkout` v7.0.1, `actions/setup-node` v7.1.0), con `dependabot.yml` para mantenerlas al día.
38
+ - `package.json`: la descripción dice "cualquier API", la página es getparche.com y suma keywords de IA.
4
39
 
5
40
  ## 0.6.6 — 2026-10-20
6
41
 
package/README.md CHANGED
@@ -1,6 +1,12 @@
1
1
  # Parche — Dependabot para APIs
2
2
 
3
3
  [![CI](https://github.com/Pepo08/parche/actions/workflows/ci.yml/badge.svg)](https://github.com/Pepo08/parche/actions/workflows/ci.yml)
4
+ [![npm](https://img.shields.io/npm/v/parche-cli)](https://www.npmjs.com/package/parche-cli)
5
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue)](LICENSE)
6
+
7
+ > **In English.** Parche reads your code and inventories every third-party API it uses: SDKs, direct HTTP calls and AI model IDs. When a provider ships a dated change that affects you, it tells you the file and the line, or opens a small PR with a deterministic fix. Every changed file is parsed before the PR, and with `--verify` your own tests run too. What can't be inferred is never made up: the PR asks for it. Today's packs cover Argentina's ARCA e-invoicing change (due December 1, 2026), Mercado Pago Payments → Orders, and OpenAI and Anthropic model and parameter deprecations.
8
+ >
9
+ > Try it with `npx parche-cli scan .` (Node 22+): it runs locally, with no account and no telemetry ([security and data](SECURITY.md)). GitHub Action: `uses: Pepo08/parche@v0`. The rest of this README is in Spanish for now · [getparche.com](https://getparche.com) · hola@getparche.com
4
10
 
5
11
  Parche lee tu código y arma el inventario de **todas** las APIs de terceros que usás: SDKs (Stripe, Twilio, OpenAI, AWS, Mercado Pago, ARCA…), llamadas HTTP directas y modelos de IA. Lo cruza contra una base de **cambios de API verificados** y, cuando un proveedor cambia algo que te afecta, te avisa o abre un PR con el arreglo, explica qué cambió y por qué, y marca lo que necesita una persona. Ningún PR sale sin pasar la verificación. Las APIs de pagos y facturación de Latinoamérica son el fuerte, pero el inventario cubre cualquier API.
6
12
 
@@ -87,6 +93,14 @@ Para que corra todas las semanas, copiá [`templates/github-workflow.yml`](templ
87
93
 
88
94
  El workflow necesita `permissions: contents: write` y `pull-requests: write`.
89
95
 
96
+ Con el `GITHUB_TOKEN` por defecto, GitHub deja los workflows del PR que abre Parche esperando aprobación: alguien con permiso de escritura tiene que tocar **Approve workflows to run** (el cuerpo del PR lo avisa). Para que tu CI corra sola sobre esos PRs, pasale a la Action un token de una GitHub App o un token personal con permiso de escritura en `contents` y `pull-requests`:
97
+
98
+ ```yaml
99
+ - uses: Pepo08/parche@v0
100
+ with:
101
+ token: ${{ secrets.PARCHE_TOKEN }}
102
+ ```
103
+
90
104
  ### Desarrollo
91
105
 
92
106
  ```bash
@@ -98,22 +112,7 @@ node bin/parche.js scan examples/saas-demo # SaaS con Stripe, Twilio, OpenAI
98
112
  node bin/parche.js scan examples/legacy-ia --upgrades # OpenAI v3: qué se rompe al actualizar (usa la red)
99
113
  ```
100
114
 
101
- #### Sacar una versión
102
-
103
- Las versiones se publican solas desde GitHub Actions ([`release.yml`](.github/workflows/release.yml)) con [trusted publishing](https://docs.npmjs.com/trusted-publishers): npm confía en este repo y en ese workflow, sin tokens ni 2FA a mano. Desde `main` actualizado:
104
-
105
- ```bash
106
- npm version 0.1.2 --no-git-tag-version # cambia package.json y package-lock.json
107
- git commit -am "Parche 0.1.2"
108
- git tag -a v0.1.2 -m "Parche 0.1.2: qué cambió" # el mensaje del tag es el texto del GitHub Release
109
- git push origin main v0.1.2
110
- ```
111
-
112
- Con el tag, el workflow corre los tests, controla que la versión de `package.json` coincida con el tag y que el commit esté en `main`, publica `parche-cli` en npm (con provenance), mueve `v0` a esta versión (es la que usan los workflows con `Pepo08/parche@v0`) y crea el GitHub Release. Para notas largas, `git tag -a v0.1.2 -F notas.md`.
113
-
114
- - Solo dispara con versiones estables (`vX.Y.Z`); un tag como `v0.2.0-beta.1` no hace nada.
115
- - Si falla un paso después de publicar, "Re-run failed jobs" retoma: lo que ya está hecho se saltea.
116
- - Requiere, una sola vez, el Trusted Publisher en npmjs.com: `parche-cli` → Settings → Trusted publishing → GitHub Actions, con `Pepo08` / `parche` / `release.yml` y permiso para `npm publish`.
115
+ Cómo contribuir, las reglas para el código y cómo se saca una versión: [`CONTRIBUTING.md`](CONTRIBUTING.md).
117
116
 
118
117
  ## Estados de cada hallazgo
119
118
 
@@ -131,7 +130,7 @@ Con el tag, el workflow corre los tests, controla que la versión de `package.js
131
130
  |---|---|---|---|---|
132
131
  | `arca.wsfe.condicion-iva-receptor` | ARCA | rompe | 2026-12-01 | sí, en JS (`afip.js`) y PHP (`afipsdk/afip.php` y forks): consumidor final y DNI B/C; con `DocTipo` en una variable, consumidor final al ejecutarse (PR parcial) |
133
132
  | `mercadopago.payments-to-orders` | Mercado Pago | migración | — | sí (creación de pagos en SDK Node v2) |
134
- | `ai.model-retirements` | Anthropic, OpenAI | retiro | varias | solo detección (también modelos fine-tuned de OpenAI, `ft:…`). De OpenAI, los modelos que su página de deprecaciones apaga desde 2026: texto, código, imagen, video, voz, transcripción y realtime; y los productos que cierran: Assistants API, prompts reutilizables (`pmpt_…`, `v1/prompts`), la plataforma de Evals, ChatKit con un workflow de Agent Builder y la Videos API (cerrada el 24/9/2026). De Anthropic, los modelos retirados y `temperature`, `top_p` y `top_k` con un valor que no es el de por defecto en Claude 4.7 y posteriores, que dan error 400 (SDK de JS/TS y de Python, y HTTP directo) |
133
+ | `ai.model-retirements` | Anthropic, OpenAI | retiro | varias | solo detección (también modelos fine-tuned de OpenAI, `ft:…`). De OpenAI, los modelos que su página de deprecaciones apaga desde 2026: texto, código, imagen, video, voz, transcripción y realtime; y los productos que cierran: Assistants API, prompts reutilizables (`pmpt_…`, `v1/prompts`), la plataforma de Evals, ChatKit con un workflow de Agent Builder y la Videos API (cerrada el 24/9/2026). De Anthropic, los modelos retirados, `temperature`, `top_p` y `top_k` con un valor que no es el de por defecto en Claude 4.7 y posteriores, que dan error 400 (SDK de JS/TS y de Python, y HTTP directo), y los mismos tres parámetros con cualquier modelo en la API version `2026-10-21`, que usan el SDK de TypeScript 1.0.0 y el de Python 2.0.0 (del 21/10/2026): dice si el pedido ya falla, si falla en una instalación nueva o si va a fallar cuando suban el SDK |
135
134
 
136
135
  ## Verificación
137
136
 
@@ -141,6 +140,21 @@ Antes de commitear, Parche parsea **siempre** cada archivo modificado (JS/TS o P
141
140
 
142
141
  **Copias de respaldo:** Parche no lee ni edita carpetas `BAK/`, `backup/`, `old/`, `_old/` y `deprecated/`, ni archivos como `factura copy.php`, `factura - Copy.js`, `factura.bak.php` o `factura-old.php`. El scan dice cuáles saltó, por si alguno es código que corre.
143
142
 
143
+ ## Seguridad y datos
144
+
145
+ Parche corre en tu máquina o en tu runner. Tu código no sale de ahí, salvo el PR que abre en tu propio repo.
146
+
147
+ | | |
148
+ |---|---|
149
+ | **Red** | `scan`, `fix` y `packs` no usan la red. `pr` llama a `api.github.com` para abrir el PR en tu repo y pushea su rama `parche/<pack>` (con `--force`, así solo reemplaza su propio PR anterior). `diff` y `scan --upgrades` bajan metadatos y paquetes de `registry.npmjs.org`, que ve qué paquetes y versiones consultás. Nada más. |
150
+ | **Telemetría** | Ninguna. `parche feedback` escribe en `.parche/ledger.jsonl` de tu repo y queda ahí. |
151
+ | **Qué ejecuta** | `git` y, solo con `--verify`, los tests de tu repo (`npm test`, `composer test` o `vendor/bin/phpunit`). |
152
+ | **Qué escribe** | Tu working copy (solo `fix` y `pr`), `.parche/` (se agrega a `.git/info/exclude`) y, para `diff` y `--upgrades`, un caché en `~/.parche/cache`. |
153
+
154
+ Para reportar una vulnerabilidad, y el detalle de todo esto: [`SECURITY.md`](SECURITY.md). Si te llegó un PR de Parche: [por qué lo recibiste](docs/por-que-recibi-este-pr.md).
155
+
156
+ **Qué puede cambiar dentro de `v0`:** mientras Parche esté en 0.x, `Pepo08/parche@v0` y `npx parche-cli` reciben cada versión nueva. Una versión 0.x puede sumar packs, avisos y opciones. No cambia sin aviso en el CHANGELOG el formato de `.parche/inventory.json` (tiene `schemaVersion`), los nombres de los comandos ni el de los inputs de la Action. Si tu organización exige fijar las Actions, usá el commit de un release: `Pepo08/parche@<sha> # v0.6.7`.
157
+
144
158
  ## ARCA en PHP
145
159
 
146
160
  Para `afipsdk/afip.php` y sus forks (`fertandil87/afip.php`, `ivanalemunioz/afip.php`, `eesnaola/afip.php`), Parche busca `$afip->ElectronicBilling->CreateVoucher($data)` y `CreateNextVoucher($data)` (también en minúsculas: PHP no distingue) y agrega una línea por factura:
@@ -189,6 +203,7 @@ Cuando `DocTipo` no es un número fijo (`DocTipo: docTipo`), Parche no puede sab
189
203
  - JS/TS (ESM y CommonJS) y, para ARCA, PHP. Siguen Python y Java.
190
204
  - ARCA: cubre `@afipsdk/afip.js` y `afipsdk/afip.php` (con sus forks). Las integraciones SOAP propias (en cualquier lenguaje) se detectan pero no se reescriben.
191
205
  - IA, parámetros de Claude 4.7 y posteriores: avisa solo si el modelo del pedido se lee en el mismo archivo (un literal, una constante asignada una vez o el valor por defecto de una variable de entorno) y el parámetro es un número escrito en el código. Si el modelo llega de otro archivo o de la configuración, no avisa: no se puede saber si el pedido va a fallar.
206
+ - IA, API version `2026-10-21` de Anthropic: la versión del SDK sale de `package-lock.json`, `node_modules` o el rango de `package.json`, y en Python solo de `requirements.txt` (todavía no de `pyproject.toml`, `poetry.lock` ni `uv.lock`). Un rango sin número (`latest`, `*`) cuenta como versión desconocida. Por HTTP directo avisa solo si el archivo manda `anthropic-version: 2026-10-21`. Los SDKs de Go, Java, .NET, Ruby y PHP no se leen.
192
207
  - Mercado Pago: migra la creación de pagos con el SDK v2. Las consultas, capturas y reembolsos de pagos existentes se dejan a propósito en Payments API, como indica la guía oficial.
193
208
  - Se distribuye como CLI (`npx parche-cli`) + GitHub Action (`Pepo08/parche@v0`). La GitHub App (instalación en un click, PRs automáticos al publicarse un pack nuevo) es el próximo paso.
194
209
  - La capa de LLM (redactar packs desde changelogs y resolver casos "falta un dato" leyendo el modelo de datos del cliente) todavía no está incluida; cuando entre, va a pasar por la misma verificación.
package/bin/parche.js CHANGED
@@ -27,6 +27,7 @@ Uso:
27
27
  pushea a ese remote (tu fork) y abre el PR contra origin.
28
28
  Con --author, firma con esa identidad y no con la de git
29
29
  parche feedback <dir> --pack <id> --outcome merged|edited|rejected [--note "..."]
30
+ parche --version La versión instalada (para reportar un error)
30
31
  `;
31
32
 
32
33
  function parseArgs(argv) {
@@ -140,6 +141,11 @@ function printFindings(result) {
140
141
  async function main() {
141
142
  const args = parseArgs(process.argv.slice(2));
142
143
  const [cmd, dirArg] = args._;
144
+ if (args.version || cmd === '-v') {
145
+ const pkg = JSON.parse(fs.readFileSync(new URL('../package.json', import.meta.url), 'utf8'));
146
+ console.log(pkg.version);
147
+ return 0;
148
+ }
143
149
  if (!cmd || args.help) { console.log(HELP); return 0; }
144
150
 
145
151
  if (cmd === 'packs') {
@@ -243,7 +249,9 @@ async function main() {
243
249
  }
244
250
  // Si solo cambiarían tests o mocks no hay PR: el texto queda como issue, sin "Modifica 1 archivo" (frente B, punto 20).
245
251
  const reportOnly = !changedFiles.length || changedFiles.every(isTestFile);
246
- fs.writeFileSync(bodyPath, prBody({ pack, findings: result.findings, changed: reportOnly ? [] : result.changed, verification: reportOnly ? null : verification }));
252
+ // La Action avisa si usa el GITHUB_TOKEN por defecto (PARCHE_DEFAULT_TOKEN): los workflows del PR esperan aprobación.
253
+ const ciApproval = process.env.GITHUB_ACTIONS === 'true' && process.env.PARCHE_DEFAULT_TOKEN === 'true';
254
+ fs.writeFileSync(bodyPath, prBody({ pack, findings: result.findings, changed: reportOnly ? [] : result.changed, verification: reportOnly ? null : verification, ciApproval }));
247
255
  if (reportOnly) {
248
256
  const why = changedFiles.length ? 'solo cambiaría tests o mocks: no se abre un PR' : 'sin cambios de código';
249
257
  git.discardChanges(root);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "parche-cli",
3
- "version": "0.6.6",
4
- "description": "Dependabot para APIs: detecta cambios de APIs de terceros en tu código y abre el PR con el arreglo.",
3
+ "version": "0.6.7",
4
+ "description": "Dependabot para APIs: detecta cambios de cualquier API de terceros en tu código (ARCA, Mercado Pago, OpenAI, Anthropic) y abre el PR con el arreglo.",
5
5
  "keywords": [
6
6
  "api",
7
7
  "migration",
@@ -11,9 +11,15 @@
11
11
  "afip",
12
12
  "wsfe",
13
13
  "mercadopago",
14
- "github-action"
14
+ "github-action",
15
+ "openai",
16
+ "anthropic",
17
+ "claude",
18
+ "api-migration",
19
+ "deprecation",
20
+ "breaking-changes"
15
21
  ],
16
- "homepage": "https://github.com/Pepo08/parche#readme",
22
+ "homepage": "https://getparche.com",
17
23
  "bugs": "https://github.com/Pepo08/parche/issues",
18
24
  "repository": {
19
25
  "type": "git",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "id": "ai.model-retirements",
3
- "version": "2026.10.20",
3
+ "version": "2026.10.21",
4
4
  "provider": "Modelos de IA",
5
5
  "title": "Modelos, endpoints y parámetros de IA retirados o por retirarse",
6
6
  "kind": "retirement",
@@ -14,7 +14,12 @@
14
14
  { "title": "Anthropic: Model deprecations, API parameter deprecations (temperature, top_p y top_k en Claude 4.7 y posteriores)", "url": "https://platform.claude.com/docs/en/about-claude/model-deprecations#api-parameter-deprecations" },
15
15
  { "title": "Anthropic: API reference, Messages (valores que se aceptan: temperature 1, top_p desde 0.99, top_k ninguno)", "url": "https://platform.claude.com/docs/en/api/messages/create" },
16
16
  { "title": "Anthropic: Claude Haiku 5.5 migration guide (top_p solo 0.99; temperature y top_p juntos dan 400)", "url": "https://platform.claude.com/docs/en/models/haiku-5-5/migration-guide#remove-sampling-parameters" },
17
- { "title": "Anthropic: Claude Opus 5.5 migration guide (Opus 4.7 y posteriores, Sonnet 5; SDK de Python 1.0 y TypeError)", "url": "https://platform.claude.com/docs/en/models/opus-5-5/migration-guide" },
17
+ { "title": "Anthropic: Claude Opus 5.5 migration guide (Opus 4.7 y posteriores, Sonnet 5)", "url": "https://platform.claude.com/docs/en/models/opus-5-5/migration-guide" },
18
+ { "title": "Anthropic: Migrating to API version 2026-10-21 (temperature, top_p y top_k dan 400 con cualquier modelo; primera versión de cada SDK que la usa)", "url": "https://platform.claude.com/docs/en/api/migration-2026-10-21" },
19
+ { "title": "Anthropic: Versions (anthropic-version; historial de la API version 2026-10-21)", "url": "https://platform.claude.com/docs/en/api/versioning" },
20
+ { "title": "anthropic-sdk-python: MIGRATION.md de la 1.0.0 (temperature, top_p y top_k salen de los métodos: TypeError)", "url": "https://github.com/anthropics/anthropic-sdk-python/blob/v1.0.0/MIGRATION.md" },
21
+ { "title": "anthropic-sdk-python: release v2.0.0 del 21/10/2026 (API version 2026-10-21)", "url": "https://github.com/anthropics/anthropic-sdk-python/releases/tag/v2.0.0" },
22
+ { "title": "anthropic-sdk-typescript: release sdk-v1.0.0 del 21/10/2026 (API version 2026-10-21)", "url": "https://github.com/anthropics/anthropic-sdk-typescript/releases/tag/sdk-v1.0.0" },
18
23
  { "title": "Anthropic: Claude Sonnet 5.5 migration guide (Sonnet 4.6 y anteriores y Haiku 4.5 aceptan los tres)", "url": "https://platform.claude.com/docs/en/models/sonnet-5-5/migration-guide" },
19
24
  { "title": "Anthropic: Claude Fable 5.1 and Claude Mythos 5.1 migration guide (la restricción de muestreo se mantiene)", "url": "https://platform.claude.com/docs/en/models/fable-5-1/migration-guide" },
20
25
  { "title": "OpenAI: Deprecations", "url": "https://developers.openai.com/api/docs/deprecations" },
@@ -189,7 +194,21 @@
189
194
  },
190
195
  "recommendation_es": "sacar {params} del pedido y guiar el comportamiento del modelo con el prompt",
191
196
  "temperature_zero_es": "Si estaba en 0 para tener respuestas determinísticas: según Anthropic, eso nunca garantizó respuestas idénticas.",
192
- "python_sdk_es": "Además, el SDK de Python 1.0 o posterior ya no tiene `temperature`, `top_p` ni `top_k`: pasarlos tira `TypeError`."
197
+ "python_sdk_es": "Además, el SDK de Python 1.0 o posterior ya no tiene `temperature`, `top_p` ni `top_k`: pasarlos tira `TypeError`.",
198
+ "api_version": {
199
+ "version": "2026-10-21",
200
+ "last_verified": "2026-10-21",
201
+ "released_es": "21/10/2026",
202
+ "sdks": {
203
+ "npm": { "package": "@anthropic-ai/sdk", "first": "1.0.0" },
204
+ "pypi": { "package": "anthropic", "first": "2.0.0", "typeerror_from": "1.0.0" }
205
+ },
206
+ "removed_es": "esa versión de la API eliminó `temperature`, `top_p` y `top_k`: con cualquier modelo devuelven un error 400, aunque tengan su valor por defecto",
207
+ "recommendation_es": "sacá {param} del pedido y guiá el comportamiento del modelo con el prompt",
208
+ "typescript_es": "En TypeScript, además, el SDK 1.0 ya no tiene el parámetro en sus tipos.",
209
+ "older_models": ["claude-sonnet-4-6", "claude-sonnet-4-5", "claude-sonnet-4-2", "claude-opus-4-6", "claude-opus-4-5", "claude-opus-4-1", "claude-opus-4-2", "claude-haiku-4-5", "claude-3"],
210
+ "older_model_es": "Con {model}, que todavía acepta otros valores, sacarlo cambia cómo responde y ningún otro ajuste lo reemplaza: si dependés de ese valor, ese pedido tiene que quedarse en la API version `2023-06-01`."
211
+ }
193
212
  },
194
213
  "retired_endpoints": [
195
214
  { "pattern": ".beta.assistants", "retired": "2026-08-26", "api_es": "Assistants API", "replacement_es": "Responses API + Conversations API", "provider": "openai" },
package/src/context.js CHANGED
@@ -1,7 +1,8 @@
1
1
  // Contexto del repo para los packs: lo que un archivo solo no puede saber.
2
2
  // Lo arma el motor una vez, antes de correr los packs, y cada transform lo recibe como `context`.
3
3
  //
4
- // versions: versión instalada de cada dependencia (de los lockfiles, de node_modules o del rango declarado)
4
+ // versions: versión instalada de cada dependencia (de los lockfiles, de node_modules o del rango declarado;
5
+ // en Python, solo el rango de requirements.txt). Si sale del rango, `range` trae el texto declarado.
5
6
  // fieldFiles: archivos que ya usan CondicionIVAReceptorId (el repo ya empezó a migrar)
6
7
  // staleWsdl: WSDL de WSFEv1 del repo que no tienen CondicionIVAReceptorId
7
8
  // arcaLibs: librerías de ARCA del repo con lo que se sabe de si el campo llega a viajar
@@ -20,8 +21,10 @@ const clean = (v) => String(v || '').replace(/^v/, '');
20
21
 
21
22
  // Versiones instaladas por ecosistema: lockfiles (en cualquier carpeta) → node_modules de la raíz → rango declarado.
22
23
  export function readVersions(root, manifests) {
23
- const out = { npm: {}, composer: {} };
24
- const set = (eco, name, version, source) => { if (name && version && !out[eco][name]) out[eco][name] = { version: clean(version), source }; };
24
+ const out = { npm: {}, composer: {}, pypi: {} };
25
+ const set = (eco, name, version, source, range) => {
26
+ if (name && version && !out[eco][name]) out[eco][name] = { version: clean(version), source, ...(range ? { range } : {}) };
27
+ };
25
28
  for (const rel of findFiles(root, (n) => n === 'package-lock.json' || n === 'composer.lock')) {
26
29
  const lock = readJson(path.join(root, rel));
27
30
  if (!lock) continue;
@@ -39,15 +42,19 @@ export function readVersions(root, manifests) {
39
42
  const pkg = readJson(path.join(root, 'node_modules', name, 'package.json'));
40
43
  if (pkg?.version) set('npm', name, pkg.version, 'node_modules');
41
44
  }
42
- for (const eco of ['npm', 'composer']) {
45
+ for (const eco of ['npm', 'composer', 'pypi']) {
43
46
  for (const [name, range] of Object.entries(manifests?.[eco] || {})) {
44
47
  const m = String(range).match(/(\d+)(?:\.(\d+))?(?:\.(\d+))?/);
45
- if (m) set(eco, name, `${m[1]}.${m[2] || 0}.${m[3] || 0}`, 'declarada');
48
+ if (m) set(eco, name, `${m[1]}.${m[2] || 0}.${m[3] || 0}`, 'declarada', String(range));
46
49
  }
47
50
  }
48
51
  return out;
49
52
  }
50
53
 
54
+ // ¿Un rango declarado deja instalar versiones mayores nuevas? `>=0.40`, `>0.3`, `*`, `x` o `latest` sin tope (`<`):
55
+ // una instalación nueva trae la última versión. `^0.137.0`, `~1.2`, `==1.4.0` o `~=1.2` no.
56
+ export const openRange = (range) => /^\s*(?:>=?|\*|x|latest)/i.test(range || '') && !/</.test(range || '');
57
+
51
58
  // Qué se sabe, por librería, de si CondicionIVAReceptorId llega a ARCA (verificado en su código el 16/10/2026):
52
59
  // afip.js / afip.php ≥ 1.0: mandan JSON a la API de Afip SDK → sí.
53
60
  // 0.x: arman el SOAP con un WSDL de 2021 incluido en la librería, sin el campo → no (PHP) o sin confirmar (JS).
@@ -90,4 +97,4 @@ export function buildContext(root, files, manifests) {
90
97
  return { versions, fieldFiles, staleWsdl, arcaLibs: arcaLibraries(versions) };
91
98
  }
92
99
 
93
- export const emptyContext = () => ({ versions: { npm: {}, composer: {} }, fieldFiles: [], staleWsdl: [], arcaLibs: [] });
100
+ export const emptyContext = () => ({ versions: { npm: {}, composer: {}, pypi: {} }, fieldFiles: [], staleWsdl: [], arcaLibs: [] });
@@ -38,7 +38,7 @@ const endpointHit = (ep, l) => {
38
38
  const milestonesText = (ep, today) => (ep.milestones || [])
39
39
  .map((m) => (m.date <= today ? ` Desde el ${m.date} ${m.past_es}.` : ` El ${m.date} ${m.future_es}.`)).join('');
40
40
 
41
- export default function detect({ source, file, pack, today = new Date().toISOString().slice(0, 10) }) {
41
+ export default function detect({ source, file, pack, today = new Date().toISOString().slice(0, 10), context }) {
42
42
  const findings = [];
43
43
  const usages = [];
44
44
  const { retired_models: retired, retirement_floors: floors, retired_endpoints: endpoints } = pack.detect;
@@ -94,9 +94,9 @@ export default function detect({ source, file, pack, today = new Date().toISOStr
94
94
  usages.push({ provider: 'ia', op: `OpenAI ${ep.api_es}`, line });
95
95
  }
96
96
  });
97
- // `temperature`, `top_p` y `top_k` en Claude 4.7 y posteriores: el modelo y el parámetro pueden estar en líneas
97
+ // `temperature`, `top_p` y `top_k` (Claude 4.7 y posteriores, API version 2026-10-21): el modelo y el parámetro pueden estar en líneas
98
98
  // distintas del pedido, así que va aparte (anthropic-sampling-params.js).
99
- const sampling = samplingParams({ source, file, pack });
99
+ const sampling = samplingParams({ source, file, pack, context });
100
100
  findings.push(...sampling.findings);
101
101
  usages.push(...sampling.usages);
102
102
  return { output: null, findings, usages };
@@ -16,6 +16,16 @@
16
16
  // Se avisa solo si el modelo del pedido se puede leer y es uno de los afectados, y si el parámetro es un número fuera
17
17
  // de lo aceptado. El modelo se lee si es un literal, una constante asignada una sola vez en el archivo o el valor por
18
18
  // defecto de una variable (`process.env.X || '…'`, `os.getenv("X", "…")`). Si no se puede saber, no se avisa (D64).
19
+ //
20
+ // API version `2026-10-21` (D65): elimina los tres parámetros para cualquier modelo (400 aunque tengan su valor por
21
+ // defecto). La manda el archivo (`anthropic-version: 2026-10-21`) o el SDK desde su versión mayor nueva (TypeScript 1.0.0
22
+ // y Python 2.0.0, del 21/10/2026); los anteriores mandan `2023-06-01`. Corre sobre los mismos pedidos, sin importar el
23
+ // modelo, si el archivo es de Anthropic (importa el SDK, nombra un `claude-…` o va por HTTP a api.anthropic.com), y dice
24
+ // si ya falla (header, SDK instalado o, en Python, `TypeError` desde la 1.0), si falla en una instalación nueva (rango
25
+ // sin tope) o si va a fallar cuando suban el SDK. Lo que ya avisó la regla de Claude 4.7 no se repite. Por HTTP sin el
26
+ // header nuevo no se avisa: la versión la elige el archivo y no cambia sola.
27
+ import { openRange } from '../context.js';
28
+
19
29
  const PACK = 'ai.model-retirements';
20
30
  const JS = /\.[cm]?[jt]sx?$/i;
21
31
  const PY = /\.py$/i;
@@ -29,6 +39,17 @@ const NAMES = /[A-Za-z_$][\w$]*/g;
29
39
  // Antes de una `/` que abre una regex de JS (y no es una división) hay un operador, un separador o una palabra clave.
30
40
  const REGEX_BEFORE = /(?:^|[(,=:[!&|?{};+\-*%<>~^]|\b(?:return|typeof|case|in|of|yield|await|void|delete|throw|else|do))\s*$/;
31
41
  const CONTROL = /\b(?:if|for|while|switch|catch|with)\s*$/;
42
+ const ANTHROPIC = /@anthropic-ai\/|["'`]claude-|^\s*(?:from\s+anthropic(?:\.\w+)*\s+import|import\s+anthropic)\b/m;
43
+ const TS = /\.[cm]?tsx?$/i;
44
+ const EMPTY = /^(?:undefined|null|None)$/;
45
+
46
+ // a ≥ b, comparando "mayor.menor.parche".
47
+ const atLeast = (a, b) => {
48
+ const x = String(a).split('.').map(Number);
49
+ const y = String(b).split('.').map(Number);
50
+ for (let i = 0; i < 3; i++) if ((x[i] || 0) !== (y[i] || 0)) return (x[i] || 0) > (y[i] || 0);
51
+ return true;
52
+ };
32
53
 
33
54
  // Copia del código con los comentarios y el interior de los strings en blanco (mismas posiciones y saltos de línea),
34
55
  // para buscar llaves, comas y claves sin tropezar con texto. Los strings quedan aparte, con su valor.
@@ -119,12 +140,15 @@ function separator(code, a, b, sep) {
119
140
  return -1;
120
141
  }
121
142
 
122
- export default function detectSamplingParams({ source, file, pack }) {
143
+ export default function detectSamplingParams({ source, file, pack, context }) {
123
144
  const rules = pack.detect.sampling_params;
124
145
  const python = PY.test(file);
125
146
  if (!rules || (!python && !JS.test(file))) return { findings: [], usages: [] };
126
- // La mayoría de los archivos no tiene ni el parámetro ni un modelo afectado: no hace falta tokenizarlos.
127
- if (!PARAMS.some((p) => source.includes(p)) || !rules.models.some((m) => source.includes(m))) return { findings: [], usages: [] };
147
+ // La mayoría de los archivos no tiene el parámetro, ni un modelo afectado ni nada de Anthropic: no hace falta
148
+ // tokenizarlos.
149
+ const api = rules.api_version;
150
+ const anthropic = Boolean(api) && (ANTHROPIC.test(source) || (/api\.anthropic\.com/.test(source) && /v1\/messages/.test(source)));
151
+ if (!PARAMS.some((p) => source.includes(p)) || (!anthropic && !rules.models.some((m) => source.includes(m)))) return { findings: [], usages: [] };
128
152
 
129
153
  const { code, strings } = tokenize(source, python);
130
154
  const { nodes, depth } = brackets(code);
@@ -283,12 +307,77 @@ export default function detectSamplingParams({ source, file, pack }) {
283
307
  findings.push({ pack: PACK, file, line: lineOf(at), status: 'detect-only', message });
284
308
  usages.push({ provider: 'ia', op, line: lineOf(at) });
285
309
  };
310
+ const shownOf = (param, e) => (e.sep === '=' ? `${param}=${e.value.text}` : e.sep ? `${param}: ${e.value.text}` : param);
311
+
312
+ // API version `2026-10-21`: la que manda el archivo (`new`, `old` o null si no la nombra) y el SDK instalado.
313
+ const header = (() => {
314
+ if (!anthropic || !strings.some((s) => s.value.toLowerCase() === 'anthropic-version')) return null;
315
+ if (strings.some((s) => s.value === api.version)) return 'new';
316
+ return strings.some((s) => /^2023-0[16]-01$/.test(s.value)) ? 'old' : null;
317
+ })();
318
+ const sdk = api?.sdks[python ? 'pypi' : 'npm'];
319
+ const installed = sdk ? context?.versions?.[python ? 'pypi' : 'npm']?.[sdk.package] || null : null;
320
+ const whereOf = (v) => (v.source === 'declarada' ? `declarado como \`${v.range}\`` : v.source === 'node_modules' ? 'instalado en node_modules' : `según ${v.source}`);
321
+ const cap = (s) => s[0].toUpperCase() + s.slice(1);
322
+ const apiVersion = (via, byKey, model, flagged) => {
323
+ // Un archivo que fija `2023-06-01` eligió quedarse; por HTTP, la versión la elige el archivo.
324
+ if (!anthropic || header === 'old' || (via === 'http' && header !== 'new')) return;
325
+ const usable = (e) => !EMPTY.test(e.value.text);
326
+ const candidates = PARAMS.filter((p) => byKey.has(p) && !flagged.has(p) && usable(byKey.get(p))).map((param) => ({ param, e: byKey.get(param), extra: false }));
327
+ // Python: lo que va en `extra_body={...}` llega a la API sin pasar por la firma del método.
328
+ const xb = python && byKey.get('extra_body');
329
+ const xn = xb && code[xb.value.at] === '{' ? nodeAt(xb.value.at) : null;
330
+ if (xn) for (const e of entriesOf(xn)) if (PARAMS.includes(e.key) && !flagged.has(e.key) && usable(e)) candidates.push({ param: e.key, e, extra: true });
331
+ const older = model && api.older_models.some((m) => model.id.startsWith(m)) ? model.id : null;
332
+ const pkg = `\`${sdk.package}\``;
333
+ for (const { param, e, extra } of candidates) {
334
+ // En Python, un parámetro directo falla antes de llegar a la API: el SDK 1.0 lo sacó de la firma.
335
+ const direct = python && !extra && via === 'sdk';
336
+ const target = direct ? sdk.typeerror_from : sdk.first;
337
+ const reason = direct
338
+ ? `desde la ${sdk.typeerror_from}, el SDK de Python ya no tiene \`temperature\`, \`top_p\` ni \`top_k\` y pasarlos tira \`TypeError\` con cualquier modelo`
339
+ : `desde la ${sdk.first}, el SDK usa la API version \`${api.version}\`, y ${api.removed_es}`;
340
+ const rec = api.recommendation_es.replace('{param}', `\`${param}\``);
341
+ const shown = `\`${shownOf(param, e)}\`${extra ? ' (en `extra_body`)' : ''}`;
342
+ let message;
343
+ if (header === 'new') {
344
+ message = `${shown} ya no funciona: el archivo manda \`anthropic-version: ${api.version}\`, y ${api.removed_es}. ${cap(rec)}.`;
345
+ } else if (installed && atLeast(installed.version, target)) {
346
+ message = `${shown} ya no funciona con ${pkg} ${installed.version} (${whereOf(installed)}): ${reason}.`
347
+ + (direct && !atLeast(installed.version, sdk.first) ? ` Tampoco sirve pasarlo por \`extra_body\`: con la ${sdk.first} (del ${api.released_es}) devuelve un error 400.` : '')
348
+ + ` ${cap(rec)}.`;
349
+ } else if (installed?.source === 'declarada' && openRange(installed.range)) {
350
+ message = `${shown} falla en una instalación nueva: ${pkg} está declarado como \`${installed.range}\`, que ya deja instalar la ${sdk.first} (del ${api.released_es}). ${cap(reason)}. ${cap(rec)}, o fijá el SDK debajo de la ${target}.`;
351
+ } else {
352
+ const when = `${target}${target === sdk.first ? ` (del ${api.released_es})` : ''}`;
353
+ const alsoExtra = direct ? ` Y desde la ${sdk.first} (del ${api.released_es}) tampoco sirve pasarlo por \`extra_body\`: la API version \`${api.version}\` lo rechaza.` : '';
354
+ // Sin la versión no se puede decir que hoy funciona: puede que ya falle.
355
+ message = installed
356
+ ? `${shown} funciona con ${pkg} ${installed.version} (${whereOf(installed)}), pero va a fallar cuando lo subas a la ${when}: ${reason}.${alsoExtra} Antes de aceptar esa suba, ${rec}.`
357
+ : `${shown} falla con ${pkg} ${target} o posterior: ${reason}.${alsoExtra} Parche no encontró qué versión del SDK usa este repo: si ya es la ${target} o una posterior, el pedido ya falla. ${cap(rec)}.`;
358
+ }
359
+ if (!python && via === 'sdk' && header !== 'new' && TS.test(file)) message += ` ${api.typescript_es}`;
360
+ if (param === 'temperature' && NUMBER.test(e.value.text) && Number(e.value.text) === 0) message += ` ${rules.temperature_zero_es}`;
361
+ if (older) message += ` ${api.older_model_es.replace('{model}', `\`${older}\``)}`;
362
+ report(e.keyAt, message, `Anthropic ${param} (API version ${api.version})`);
363
+ }
364
+ };
365
+
286
366
  for (const [node, via] of requests) {
287
367
  if (!node) continue;
288
368
  const byKey = new Map(entriesOf(node).map((e) => [e.key, e]));
289
369
  if (via === 'http' && !byKey.has('messages') && !byKey.has('max_tokens')) continue;
290
370
  const model = byKey.has('model') ? modelOf(byKey.get('model').value) : null;
291
- if (!model || !rules.models.includes(model.id)) continue;
371
+ const flagged = new Set(); // lo que ya avisó la regla de Claude 4.7: no se repite
372
+ if (model && rules.models.includes(model.id)) claude47(byKey, via, model, flagged);
373
+ apiVersion(via, byKey, model, flagged);
374
+ }
375
+ // Los pedidos salen en el orden del árbol (de afuera hacia adentro); el reporte va en el orden del archivo.
376
+ const byLine = (a, b) => a.line - b.line;
377
+ return { findings: findings.sort(byLine), usages: usages.sort(byLine) };
378
+
379
+ // Claude 4.7 y posteriores: el parámetro con un valor fuera de lo aceptado (y, en Haiku 5.5, los dos juntos).
380
+ function claude47(byKey, via, model, flagged) {
292
381
  const override = rules.overrides?.[model.id] || {};
293
382
  const scope = override.scope_es || rules.scope_es;
294
383
  const accepted = { ...rules.accepted, ...override.accepted };
@@ -296,7 +385,6 @@ export default function detectSamplingParams({ source, file, pack }) {
296
385
  const tail = (params, zero) => `Recomendación oficial de Anthropic: ${rules.recommendation_es.replace('{params}', params.map((p) => `\`${p}\``).join(' y '))}.`
297
386
  + (zero ? ` ${rules.temperature_zero_es}` : '') + (python && via === 'sdk' ? ` ${rules.python_sdk_es}` : '');
298
387
  const present = [];
299
- let flagged = 0;
300
388
  for (const param of PARAMS) {
301
389
  const e = byKey.get(param);
302
390
  const num = e && numberOf(e.value);
@@ -304,19 +392,17 @@ export default function detectSamplingParams({ source, file, pack }) {
304
392
  present.push({ param, e });
305
393
  const ok = accepted[param];
306
394
  if (ok.min !== undefined && num.value >= ok.min && num.value <= ok.max) continue;
307
- flagged++;
308
- const shown = (e.sep === '=' ? `${param}=${e.value.text}` : e.sep ? `${param}: ${e.value.text}` : param) + (num.via ? ` (${num.value})` : '');
395
+ flagged.add(param);
396
+ const shown = shownOf(param, e) + (num.via ? ` (${num.value})` : '');
309
397
  report(e.keyAt, `\`${shown}\` con el modelo ${modelText} da un error 400: ${scope}, \`${param}\` ${ok.es}. ${tail([param], param === 'temperature' && num.value === 0)}`,
310
398
  `Anthropic ${param} (Claude 4.7+)`);
311
399
  }
312
400
  const together = override.exclusive || [];
313
- if (together.length && !flagged && together.every((p) => present.some((x) => x.param === p))) {
401
+ if (together.length && !flagged.size && together.every((p) => present.some((x) => x.param === p))) {
314
402
  const last = present.filter((x) => together.includes(x.param)).at(-1);
315
403
  report(last.e.keyAt, `${together.map((p) => `\`${p}\``).join(' y ')} juntos con el modelo ${modelText} dan un error 400: ${override.exclusive_es}. ${tail(together, false)}`,
316
404
  `Anthropic ${together.join(' + ')} (Claude 4.7+)`);
405
+ for (const p of together) flagged.add(p);
317
406
  }
318
407
  }
319
- // Los pedidos salen en el orden del árbol (de afuera hacia adentro); el reporte va en el orden del archivo.
320
- const byLine = (a, b) => a.line - b.line;
321
- return { findings: findings.sort(byLine), usages: usages.sort(byLine) };
322
408
  }
package/src/report.js CHANGED
@@ -102,7 +102,9 @@ export function partialCovers(findings) {
102
102
  return [...new Set(findings.filter((f) => f.partial).map((f) => f.partial.covers))];
103
103
  }
104
104
 
105
- export function prBody({ pack, findings, changed, verification }, today = new Date()) {
105
+ // `ciApproval`: el PR lo abre GitHub Actions con el GITHUB_TOKEN por defecto. GitHub deja los workflows de ese PR
106
+ // esperando aprobación (docs de GitHub, "GITHUB_TOKEN"): sin avisarlo, el PR parece roto.
107
+ export function prBody({ pack, findings, changed, verification, ciApproval = false }, today = new Date()) {
106
108
  const fs = findings.filter((f) => f.pack === pack.id);
107
109
  const c = counts(fs);
108
110
  const out = [];
@@ -152,10 +154,13 @@ export function prBody({ pack, findings, changed, verification }, today = new Da
152
154
  } else {
153
155
  out.push('- Sin verificar (corré `parche pr --verify`).');
154
156
  }
157
+ if (ciApproval) {
158
+ out.push('- CI de este PR: lo abrió GitHub Actions con el `GITHUB_TOKEN`, así que GitHub deja sus workflows esperando aprobación. Alguien con permiso de escritura tiene que tocar **Approve workflows to run**. Para que corran solos, pasale a la Action un token de una GitHub App o un token personal (input `token`).');
159
+ }
155
160
  }
156
161
  out.push('', asIssue ? '### Checklist' : '### Checklist antes de mergear', '', ...pack.review_checklist_es.map((x) => `- [ ] ${x}`), '');
157
162
  out.push('### Fuentes', '', ...pack.sources.map((s) => `- [${s.title}](${s.url})`), '');
158
- out.push(`<sub>Pack \`${pack.id}\` v${pack.version}, verificado el ${pack.last_verified}. Resumen: ${Object.entries(c).map(([k, v]) => `${LABEL[k]} ${v}`).join(' · ')}. ¿Este PR te sirvió? Mergealo, editalo o cerralo: Parche aprende de cada resultado.</sub>`, '');
163
+ out.push(`<sub>Pack \`${pack.id}\` v${pack.version}, verificado el ${pack.last_verified}. Resumen: ${Object.entries(c).map(([k, v]) => `${LABEL[k]} ${v}`).join(' · ')}. ¿Este PR te sirvió? Mergealo, editalo o cerralo: Parche aprende de cada resultado. [¿Qué es Parche y por qué recibiste esto?](https://github.com/Pepo08/parche/blob/main/docs/por-que-recibi-este-pr.md)</sub>`, '');
159
164
  return out.join('\n');
160
165
  }
161
166
 
@@ -8,20 +8,25 @@ on:
8
8
  - cron: "17 11 * * 1" # lunes 8:17 (hora de Argentina)
9
9
  workflow_dispatch:
10
10
 
11
+ # Por defecto, solo lectura. La escritura la pide el único job que la necesita.
11
12
  permissions:
12
- contents: write
13
- pull-requests: write
13
+ contents: read
14
14
 
15
15
  jobs:
16
16
  parche:
17
17
  runs-on: ubuntu-latest
18
+ permissions:
19
+ contents: write # pushear la rama parche/<pack>
20
+ pull-requests: write # abrir el PR
18
21
  steps:
19
- - uses: actions/checkout@v7
22
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
20
23
  with:
21
24
  fetch-depth: 0
22
- - uses: actions/setup-node@v7
25
+ - uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
23
26
  with:
24
27
  node-version: 24
25
28
  - name: Instalar dependencias del proyecto (para correr sus tests antes de cada PR)
26
29
  run: npm ci
30
+ # v0 se mueve con cada release 0.x. Para fijar una versión, cambiá v0 por el commit de ese release
31
+ # (https://github.com/Pepo08/parche/releases), por ejemplo: Pepo08/parche@<sha> # v0.6.7
27
32
  - uses: Pepo08/parche@v0