agent-kitchen 0.3.0 → 0.4.0
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/README.md +15 -10
- package/dist/cli.js +7 -5
- package/dist/commands/doctor.d.ts +2 -1
- package/dist/commands/doctor.js +2 -1
- package/dist/commands/sync.d.ts +1 -1
- package/dist/commands/sync.js +17 -11
- package/dist/piece.d.ts +5 -4
- package/dist/piece.js +5 -4
- package/dist/pieces/agents-md.d.ts +20 -0
- package/dist/pieces/agents-md.js +124 -0
- package/dist/pieces/cloud.js +1 -1
- package/dist/pieces/guard.d.ts +20 -0
- package/dist/pieces/guard.js +34 -3
- package/dist/pieces/issue-flow.js +3 -3
- package/dist/pieces/skills.js +1 -1
- package/package.json +22 -8
package/README.md
CHANGED
|
@@ -3,10 +3,12 @@
|
|
|
3
3
|
Un solo comando para instalar, actualizar y revisar lo que un repo usa para trabajar con agentes: el flujo de issues con sus skills, las skills generales, las sesiones de Claude en la nube y las barandas de seguridad.
|
|
4
4
|
|
|
5
5
|
```sh
|
|
6
|
-
npx agent-kitchen
|
|
6
|
+
npx agent-kitchen apply # muestra qué hay, qué falta y qué está atrasado, y pregunta
|
|
7
7
|
npx agent-kitchen doctor # revisa que todo funcione, sin cambiar nada
|
|
8
8
|
```
|
|
9
9
|
|
|
10
|
+
Qué hace cada herramienta y cada skill, y dónde están: [docs/README.md](docs/README.md) para personas y [docs/agent-guide.md](docs/agent-guide.md) para agentes.
|
|
11
|
+
|
|
10
12
|
Al arrancar revisa si npm tiene una versión más nueva de sí mismo. Si la hay, corre esa con los mismos argumentos, así que `npx` nunca usa una copia vieja de su caché.
|
|
11
13
|
|
|
12
14
|
## En simple
|
|
@@ -15,7 +17,7 @@ Al arrancar revisa si npm tiene una versión más nueva de sí mismo. Si la hay,
|
|
|
15
17
|
- Pregunta una vez: **todo lo sugerido** (Enter), **elegir** los pasos y las skills, o **salir**. Todas las preguntas van antes de instalar; después corre solo.
|
|
16
18
|
- Cada paso es una línea con ✓ o ✗. La salida de cada herramienta solo aparece si falla, o con `--verbose`.
|
|
17
19
|
- Es rápido: las versiones se consultan todas a la vez y las herramientas se descargan mientras respondes.
|
|
18
|
-
- Cada pieza la instala su propia herramienta. `agent-kitchen` solo sabe cuáles hay, en qué versión están y cómo se conectan entre sí.
|
|
20
|
+
- Cada pieza la instala su propia herramienta. `agent-kitchen` solo sabe cuáles hay, en qué versión están y cómo se conectan entre sí. La excepción es una línea de `AGENTS.md`, que escribe él mismo.
|
|
19
21
|
- Correrlo de nuevo no rompe nada: si todo está al día, lo dice y termina.
|
|
20
22
|
|
|
21
23
|
## Las piezas
|
|
@@ -24,8 +26,9 @@ Al arrancar revisa si npm tiene una versión más nueva de sí mismo. Si la hay,
|
|
|
24
26
|
|---|---|---|---|---|---|
|
|
25
27
|
| `issue-flow` | Flujo de issues ([`@ramon-sg/issue-flow`](https://github.com/ramon-sg/issue-flow)): rama, worktree y PR en draft por issue, y sus 5 skills | `package.json` | npm | `issue-flow init` | `issue-flow upgrade` en la versión nueva, y sus skills a la misma tag |
|
|
26
28
|
| `claude-cloud` | Sesiones en la nube ([`@ramon-sg/claude-cloud`](https://github.com/ramon-sg/claude-cloud)): herramientas, Docker y el clone completo | `.claude/cloud.json` | npm | `claude-cloud init` | Cambia la versión que fija `.claude/cloud.json` |
|
|
27
|
-
| `claude-repo-guard` | Barandas ([`claude-repo-guard`](https://github.com/ramon-sg/claude-repo-guard)): modo auto con sandbox, escribe solo en su worktree | `.claude/repo-guard.manifest.json` | Tags `vX.Y.Z` de GitHub | `
|
|
29
|
+
| `claude-repo-guard` | Barandas ([`claude-repo-guard`](https://github.com/ramon-sg/claude-repo-guard)): modo auto con sandbox, escribe solo en su worktree | `.claude/repo-guard.manifest.json` | Tags `vX.Y.Z` de GitHub | `apply` en esa tag (`install` antes de 0.5.0) | `apply` en la tag nueva |
|
|
28
30
|
| `skills` | Skills con [`skills-manager`](https://github.com/ramon-sg/skills-manager): las del flujo y las de [`agent-skills`](https://github.com/ramon-sg/agent-skills) (planes, auditoría de código y de tests) | `.agents/skills.lock.json` | Tags del catálogo | `skills-manager init --select` con las elegidas | `skills-manager update` |
|
|
31
|
+
| `agents-md` | Una línea en `AGENTS.md` que le pide al agente mirar la versión instalada de una biblioteca antes de usarla. Si el repo tiene `CLAUDE.md`, le agrega `@AGENTS.md`, porque si no Claude Code no lee `AGENTS.md`. Solo en repos con `package.json` | `AGENTS.md` | — | La escribe `agent-kitchen` | — |
|
|
29
32
|
|
|
30
33
|
Si npm o GitHub no responden, la pieza se muestra pero no se ofrece actualizarla. Si un paso falla, los demás siguen y el comando dice cómo reintentarlo.
|
|
31
34
|
|
|
@@ -40,19 +43,21 @@ Si npm o GitHub no responden, la pieza se muestra pero no se ofrece actualizarla
|
|
|
40
43
|
## Uso
|
|
41
44
|
|
|
42
45
|
```sh
|
|
43
|
-
npx agent-kitchen # pregunta, en una terminal (Enter: todo lo sugerido)
|
|
44
|
-
npx agent-kitchen --dry-run # solo muestra qué haría
|
|
45
|
-
npx agent-kitchen --yes # hace lo sugerido sin preguntar
|
|
46
|
-
npx agent-kitchen --verbose # muestra la salida de cada herramienta
|
|
47
|
-
npx agent-kitchen --skip claude-cloud # deja fuera una pieza (o varias, con comas)
|
|
48
|
-
npx agent-kitchen doctor
|
|
46
|
+
npx agent-kitchen apply # pregunta, en una terminal (Enter: todo lo sugerido)
|
|
47
|
+
npx agent-kitchen apply --dry-run # solo muestra qué haría
|
|
48
|
+
npx agent-kitchen apply --yes # hace lo sugerido sin preguntar
|
|
49
|
+
npx agent-kitchen apply --verbose # muestra la salida de cada herramienta
|
|
50
|
+
npx agent-kitchen apply --skip claude-cloud # deja fuera una pieza (o varias, con comas)
|
|
51
|
+
npx agent-kitchen doctor # revisa cada pieza con su propio doctor
|
|
49
52
|
```
|
|
50
53
|
|
|
54
|
+
- **Sin subcomando** (`npx agent-kitchen`) hace lo mismo que `apply`, por ahora, y avisa que se use `apply`. En una versión mayor pasará a solo mostrar el estado.
|
|
55
|
+
|
|
51
56
|
- **Sin terminal y sin `--yes`** (por ejemplo, desde un agente), solo muestra lo que ofrecería.
|
|
52
57
|
- **Corre en el checkout principal,** no en un worktree, y desde tu terminal. Dentro de una sesión de Claude, el sandbox de `claude-repo-guard` no deja escribir `.claude/settings.json`.
|
|
53
58
|
- **Al terminar,** corre el `install` de tu gestor de paquetes, revisa el diff y haz commit.
|
|
54
59
|
|
|
55
|
-
**`doctor`** corre el doctor de cada herramienta en la versión del repo: `issue-flow doctor --json`, `skills-manager check` y `claude-repo-guard doctor --json`. Además revisa que las skills del flujo estén en la tag de su CLI, que `.claude/cloud.json` fije una versión exacta y, en macOS, `weakerNetworkIsolation`. Lo que no está instalado o tiene una versión nueva solo se anota con `·`. Sale con 1 si algo falla (`✗`).
|
|
60
|
+
**`doctor`** corre el doctor de cada herramienta en la versión del repo: `issue-flow doctor --json`, `skills-manager check` y `claude-repo-guard doctor --json`. Además revisa que las skills del flujo estén en la tag de su CLI, que `.claude/cloud.json` fije una versión exacta y, en macOS, `weakerNetworkIsolation`. En un repo con `package.json`, también la línea de `AGENTS.md` y, si hay `CLAUDE.md`, su `@AGENTS.md`. Lo que no está instalado o tiene una versión nueva solo se anota con `·`. Sale con 1 si algo falla (`✗`).
|
|
56
61
|
|
|
57
62
|
## Sumar una pieza
|
|
58
63
|
|
package/dist/cli.js
CHANGED
|
@@ -21,15 +21,15 @@ const PACKAGE = "agent-kitchen";
|
|
|
21
21
|
const HANDED_OVER = "AGENT_KITCHEN_HANDED_OVER";
|
|
22
22
|
/** Usage text. */
|
|
23
23
|
const USAGE = `Uso:
|
|
24
|
-
agent-kitchen [--yes | --dry-run] [--verbose] [--skip <pieza>,...]
|
|
24
|
+
agent-kitchen apply [--yes | --dry-run] [--verbose] [--skip <pieza>,...]
|
|
25
25
|
agent-kitchen doctor
|
|
26
26
|
|
|
27
27
|
agent-kitchen revisa cada pieza de las herramientas de agentes del repo (${pieceNames().join(", ")}),
|
|
28
|
-
muestra qué tiene el repo y la última versión de cada una, y ofrece instalar lo que falta
|
|
29
|
-
actualizar lo que está atrasado. En una terminal pregunta una vez: hacer todo lo sugerido,
|
|
28
|
+
muestra qué tiene el repo y la última versión de cada una, y apply ofrece instalar lo que falta
|
|
29
|
+
y actualizar lo que está atrasado. En una terminal pregunta una vez: hacer todo lo sugerido,
|
|
30
30
|
o elegir los pasos y las skills. --yes hace lo sugerido sin preguntar; sin terminal solo lo
|
|
31
31
|
muestra. --verbose muestra la salida de cada herramienta. Córrelo en la copia principal del
|
|
32
|
-
repo, desde tu terminal.
|
|
32
|
+
repo, desde tu terminal. Sin subcomando hace lo mismo que apply, por ahora.
|
|
33
33
|
|
|
34
34
|
doctor corre las revisiones de cada pieza en la versión del repo, sin cambiar nada.`;
|
|
35
35
|
/**
|
|
@@ -92,13 +92,15 @@ async function main(argv) {
|
|
|
92
92
|
if (handed !== null)
|
|
93
93
|
return handed;
|
|
94
94
|
const [command, ...rest] = positionals;
|
|
95
|
-
if (rest.length > 0 || (command !== undefined && command !== "doctor"))
|
|
95
|
+
if (rest.length > 0 || (command !== undefined && command !== "doctor" && command !== "apply"))
|
|
96
96
|
throw new UsageError(`no existe el comando ${positionals.join(" ")}.`);
|
|
97
97
|
if (command === "doctor") {
|
|
98
98
|
if (values.yes !== undefined || values["dry-run"] !== undefined || values.skip !== undefined || values.verbose !== undefined)
|
|
99
99
|
throw new UsageError("doctor no lleva opciones.");
|
|
100
100
|
return doctor(process.cwd(), out);
|
|
101
101
|
}
|
|
102
|
+
if (command === undefined)
|
|
103
|
+
out.stderr("agent-kitchen: usa 'agent-kitchen apply'; sin subcomando dejará de instalar en una versión mayor.\n");
|
|
102
104
|
const skip = (values.skip ?? "").split(",").map((name) => name.trim()).filter((name) => name !== "");
|
|
103
105
|
return sync({ cwd: process.cwd(), yes: values.yes ?? false, dryRun: values["dry-run"] ?? false, verbose: values.verbose ?? false, skip }, out);
|
|
104
106
|
}
|
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
* @file `agent-kitchen doctor`: whether each piece the repo has works, with the doctor of each
|
|
3
3
|
* tool at the repo's own version: the issue flow (and its skills at its tag),
|
|
4
4
|
* `skills-manager check`, the version `.claude/cloud.json` pins, and claude-repo-guard. It
|
|
5
|
-
* changes nothing. A piece the repo does not have, or a newer release, is only noted
|
|
5
|
+
* changes nothing. A piece the repo does not have, or a newer release, is only noted, and so is
|
|
6
|
+
* a missing rule in AGENTS.md.
|
|
6
7
|
*/
|
|
7
8
|
import type { Output } from "../output.js";
|
|
8
9
|
/**
|
package/dist/commands/doctor.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { agentsMdChecks } from "../pieces/agents-md.js";
|
|
1
2
|
import { cloudChecks } from "../pieces/cloud.js";
|
|
2
3
|
import { guardChecks } from "../pieces/guard.js";
|
|
3
4
|
import { issueFlowChecks } from "../pieces/issue-flow.js";
|
|
@@ -15,7 +16,7 @@ const MARKS = { ok: "✓", fail: "✗", info: "·" };
|
|
|
15
16
|
*/
|
|
16
17
|
export async function doctor(cwd, out) {
|
|
17
18
|
const root = mainCheckout(cwd, "agent-kitchen doctor");
|
|
18
|
-
const checks = (await Promise.all([issueFlowChecks(root), skillsChecks(root), cloudChecks(root), guardChecks(root)])).flat();
|
|
19
|
+
const checks = (await Promise.all([issueFlowChecks(root), skillsChecks(root), cloudChecks(root), guardChecks(root), agentsMdChecks(root)])).flat();
|
|
19
20
|
for (const check of checks)
|
|
20
21
|
out.stdout(`${MARKS[check.status]} ${check.name}: ${check.detail}\n`);
|
|
21
22
|
return checks.some((check) => check.status === "fail") ? 1 : 0;
|
package/dist/commands/sync.d.ts
CHANGED
|
@@ -10,7 +10,7 @@ export interface SyncOptions {
|
|
|
10
10
|
/**
|
|
11
11
|
* The names `--skip` takes.
|
|
12
12
|
* @returns The pieces' names.
|
|
13
|
-
* @example pieceNames() // ["issue-flow", "claude-cloud", "claude-repo-guard", "skills"]
|
|
13
|
+
* @example pieceNames() // ["issue-flow", "claude-cloud", "claude-repo-guard", "skills", "agents-md"]
|
|
14
14
|
*/
|
|
15
15
|
export declare function pieceNames(): string[];
|
|
16
16
|
/**
|
package/dist/commands/sync.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @file `agent-kitchen [--yes] [--dry-run] [--verbose] [--skip <piece>,...]`: the one command
|
|
3
|
-
*
|
|
2
|
+
* @file `agent-kitchen apply [--yes] [--dry-run] [--verbose] [--skip <piece>,...]`: the one command
|
|
3
|
+
* that changes the repo (and, for now, also the bare `agent-kitchen`).
|
|
4
|
+
* It looks at each piece (the issue flow, claude-cloud, claude-repo-guard, the skills, the
|
|
5
|
+
* rule in AGENTS.md) with
|
|
4
6
|
* every lookup at once, shows what the repo has, and offers the steps that install what is
|
|
5
7
|
* missing or update what is behind. In a terminal it asks first, all in one go: everything
|
|
6
8
|
* suggested, or pick the steps and the skills; whether to replace the repo's own skills.
|
|
@@ -17,21 +19,23 @@ import { cloudStatus, NAME as CLOUD } from "../pieces/cloud.js";
|
|
|
17
19
|
import { guardStatus, NAME as GUARD } from "../pieces/guard.js";
|
|
18
20
|
import { issueFlowStatus, NAME as FLOW } from "../pieces/issue-flow.js";
|
|
19
21
|
import { keepOwn, NAME as SKILLS, ownInPick, skillsStatus } from "../pieces/skills.js";
|
|
22
|
+
import { agentsMdStatus, NAME as AGENTS_MD } from "../pieces/agents-md.js";
|
|
20
23
|
import { mainCheckout, packageManager } from "../repo.js";
|
|
21
24
|
import { download, failedOutput, setVerbose } from "../tools.js";
|
|
22
25
|
/** Every piece, in the order its steps run. */
|
|
23
|
-
const PIECES = [FLOW, CLOUD, GUARD, SKILLS];
|
|
26
|
+
const PIECES = [FLOW, CLOUD, GUARD, SKILLS, AGENTS_MD];
|
|
24
27
|
/**
|
|
25
28
|
* The names `--skip` takes.
|
|
26
29
|
* @returns The pieces' names.
|
|
27
|
-
* @example pieceNames() // ["issue-flow", "claude-cloud", "claude-repo-guard", "skills"]
|
|
30
|
+
* @example pieceNames() // ["issue-flow", "claude-cloud", "claude-repo-guard", "skills", "agents-md"]
|
|
28
31
|
*/
|
|
29
32
|
export function pieceNames() {
|
|
30
33
|
return [...PIECES];
|
|
31
34
|
}
|
|
32
35
|
/**
|
|
33
|
-
* Each piece's status, with every lookup at once. The guard's step
|
|
34
|
-
* flow is about to be installed
|
|
36
|
+
* Each piece's status, with every lookup at once. The guard's step and the rule in AGENTS.md
|
|
37
|
+
* depend on whether the flow is about to be installed (it writes package.json), so they wait
|
|
38
|
+
* for the flow's status.
|
|
35
39
|
* @param root - The repo.
|
|
36
40
|
* @param skip - The pieces left out.
|
|
37
41
|
* @returns The statuses, in the pieces' order.
|
|
@@ -40,8 +44,10 @@ async function statusesOf(root, skip) {
|
|
|
40
44
|
const flow = skip.includes(FLOW) ? null : issueFlowStatus(root);
|
|
41
45
|
const cloud = skip.includes(CLOUD) ? null : cloudStatus(root);
|
|
42
46
|
const skills = skip.includes(SKILLS) ? null : skillsStatus(root, !skip.includes(FLOW));
|
|
43
|
-
const
|
|
44
|
-
const
|
|
47
|
+
const flowComing = (flow ?? Promise.resolve(null)).then((status) => status !== null && !status.installed && status.steps.length > 0);
|
|
48
|
+
const guard = skip.includes(GUARD) ? null : flowComing.then((coming) => guardStatus(root, coming));
|
|
49
|
+
const agentsMd = skip.includes(AGENTS_MD) ? null : flowComing.then((coming) => agentsMdStatus(root, coming));
|
|
50
|
+
const all = await Promise.all([flow, cloud, guard, skills, agentsMd]);
|
|
45
51
|
return all.flatMap((status) => status ?? []);
|
|
46
52
|
}
|
|
47
53
|
/**
|
|
@@ -70,7 +76,7 @@ function keepOwnSkills(root, step, out) {
|
|
|
70
76
|
if (own.length === 0)
|
|
71
77
|
return;
|
|
72
78
|
Object.assign(step.skills, keepOwn(step.skills, own));
|
|
73
|
-
out.stdout(`Se dejan fuera las skills que el repo ya tiene como propias: ${own.join(", ")}. Para reemplazarlas, corre agent-kitchen en una terminal.\n`);
|
|
79
|
+
out.stdout(`Se dejan fuera las skills que el repo ya tiene como propias: ${own.join(", ")}. Para reemplazarlas, corre agent-kitchen apply en una terminal.\n`);
|
|
74
80
|
}
|
|
75
81
|
/**
|
|
76
82
|
* Asks, for a step that installs skills, which ones, and whether to replace the repo's own.
|
|
@@ -120,7 +126,7 @@ async function chooseSteps(root, steps, options, out) {
|
|
|
120
126
|
return suggested;
|
|
121
127
|
}
|
|
122
128
|
if (options.dryRun || !out.interactive) {
|
|
123
|
-
const hint = options.dryRun ? "" : " (para hacerlos, corre agent-kitchen en una terminal, o con --yes)";
|
|
129
|
+
const hint = options.dryRun ? "" : " (para hacerlos, corre agent-kitchen apply en una terminal, o con --yes)";
|
|
124
130
|
out.stdout(`Pasos sugeridos${hint}:\n${steps.map((step) => ` [${step.checked ? "x" : " "}] ${step.label}\n`).join("")}`);
|
|
125
131
|
return null;
|
|
126
132
|
}
|
|
@@ -205,7 +211,7 @@ export async function sync(options, out) {
|
|
|
205
211
|
throw new UsageError(`no existe la pieza ${unknown}; son ${pieceNames().join(", ")}.`);
|
|
206
212
|
if (options.yes && options.dryRun)
|
|
207
213
|
throw new UsageError("usa --yes o --dry-run, no los dos.");
|
|
208
|
-
const root = mainCheckout(options.cwd, "agent-kitchen");
|
|
214
|
+
const root = mainCheckout(options.cwd, "agent-kitchen apply");
|
|
209
215
|
setVerbose(options.verbose);
|
|
210
216
|
if (out.interactive)
|
|
211
217
|
out.stdout("Revisando el repo y las últimas versiones…\n");
|
package/dist/piece.d.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @file What agent-kitchen knows of each piece it manages, and the names they share. A piece
|
|
3
|
-
* (the issue flow, claude-cloud, claude-repo-guard, the skills
|
|
4
|
-
* the steps that would install or update it; agent-kitchen shows them,
|
|
5
|
-
* and runs the picked ones in order. Every step runs the piece's own
|
|
6
|
-
* files: agent-kitchen only writes the few lines that join two pieces
|
|
3
|
+
* (the issue flow, claude-cloud, claude-repo-guard, the skills, the rule in AGENTS.md) tells
|
|
4
|
+
* what the repo has and the steps that would install or update it; agent-kitchen shows them,
|
|
5
|
+
* lets the person pick, and runs the picked ones in order. Every step runs the piece's own
|
|
6
|
+
* tool, which owns its files: agent-kitchen only writes the few lines that join two pieces,
|
|
7
|
+
* and the one line of AGENTS.md, which has no tool.
|
|
7
8
|
*/
|
|
8
9
|
/** The issue flow on npm. */
|
|
9
10
|
export declare const FLOW_PACKAGE = "@ramon-sg/issue-flow";
|
package/dist/piece.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @file What agent-kitchen knows of each piece it manages, and the names they share. A piece
|
|
3
|
-
* (the issue flow, claude-cloud, claude-repo-guard, the skills
|
|
4
|
-
* the steps that would install or update it; agent-kitchen shows them,
|
|
5
|
-
* and runs the picked ones in order. Every step runs the piece's own
|
|
6
|
-
* files: agent-kitchen only writes the few lines that join two pieces
|
|
3
|
+
* (the issue flow, claude-cloud, claude-repo-guard, the skills, the rule in AGENTS.md) tells
|
|
4
|
+
* what the repo has and the steps that would install or update it; agent-kitchen shows them,
|
|
5
|
+
* lets the person pick, and runs the picked ones in order. Every step runs the piece's own
|
|
6
|
+
* tool, which owns its files: agent-kitchen only writes the few lines that join two pieces,
|
|
7
|
+
* and the one line of AGENTS.md, which has no tool.
|
|
7
8
|
*/
|
|
8
9
|
/** The issue flow on npm. */
|
|
9
10
|
export const FLOW_PACKAGE = "@ramon-sg/issue-flow";
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { Check, PieceStatus } from "../piece.js";
|
|
2
|
+
/** The piece's name, as `--skip` takes it. */
|
|
3
|
+
export declare const NAME = "agents-md";
|
|
4
|
+
/** The line, as AGENTS.md has it. */
|
|
5
|
+
export declare const VERSION_RULE = "Antes de usar una biblioteca, mira su versi\u00F3n instalada en `node_modules/<biblioteca>/package.json` y lee sus tipos o su documentaci\u00F3n ah\u00ED. Si no alcanza, busca la documentaci\u00F3n de esa versi\u00F3n, no la \u00FAltima.";
|
|
6
|
+
/**
|
|
7
|
+
* What the repo has of the rule, and the step that adds it.
|
|
8
|
+
* @param root - The repo.
|
|
9
|
+
* @param flowComing - Whether this run installs the flow, which writes package.json.
|
|
10
|
+
* @returns The piece's status, or null if the repo has no package.json and gets none.
|
|
11
|
+
* @example (await agentsMdStatus(root, false))?.state // "falta la regla"
|
|
12
|
+
*/
|
|
13
|
+
export declare function agentsMdStatus(root: string, flowComing: boolean): Promise<PieceStatus | null>;
|
|
14
|
+
/**
|
|
15
|
+
* Whether AGENTS.md has the rule and CLAUDE.md, if there is one, imports it. Only noted.
|
|
16
|
+
* @param root - The repo.
|
|
17
|
+
* @returns The checks; none without package.json.
|
|
18
|
+
* @example (await agentsMdChecks(root))[0].status // "ok"
|
|
19
|
+
*/
|
|
20
|
+
export declare function agentsMdChecks(root: string): Promise<Check[]>;
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file The rule in AGENTS.md that makes the agent check the installed version of a library
|
|
3
|
+
* before using it, instead of the one it remembers. It is one fixed line, so AGENTS.md stays
|
|
4
|
+
* small: it loads in every session. This piece has no tool of its own: agent-kitchen writes
|
|
5
|
+
* the line, as it writes the guard's options. Claude Code does not read AGENTS.md when the
|
|
6
|
+
* repo has a CLAUDE.md, so that file gets `@AGENTS.md` too. Only for repos with package.json.
|
|
7
|
+
*/
|
|
8
|
+
import { existsSync, readFileSync, realpathSync, writeFileSync } from "node:fs";
|
|
9
|
+
import { join } from "node:path";
|
|
10
|
+
/** The piece's name, as `--skip` takes it. */
|
|
11
|
+
export const NAME = "agents-md";
|
|
12
|
+
/** The line, as AGENTS.md has it. */
|
|
13
|
+
export const VERSION_RULE = "Antes de usar una biblioteca, mira su versión instalada en `node_modules/<biblioteca>/package.json` y lee sus tipos o su documentación ahí. Si no alcanza, busca la documentación de esa versión, no la última.";
|
|
14
|
+
/** The line that makes Claude Code read AGENTS.md from CLAUDE.md. */
|
|
15
|
+
const IMPORT = "@AGENTS.md";
|
|
16
|
+
/**
|
|
17
|
+
* A file's text, or null if it does not exist.
|
|
18
|
+
* @param root - The repo.
|
|
19
|
+
* @param name - The file.
|
|
20
|
+
* @returns Its text, or null.
|
|
21
|
+
*/
|
|
22
|
+
function readText(root, name) {
|
|
23
|
+
const path = join(root, name);
|
|
24
|
+
return existsSync(path) ? readFileSync(path, "utf8") : null;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Whether a text has a line, exactly.
|
|
28
|
+
* @param text - The text, or null.
|
|
29
|
+
* @param line - The line.
|
|
30
|
+
* @returns Whether it has it.
|
|
31
|
+
*/
|
|
32
|
+
function hasLine(text, line) {
|
|
33
|
+
return text !== null && text.split("\n").some((each) => each.trim() === line);
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Whether CLAUDE.md and AGENTS.md are one file through a link (`ln -s AGENTS.md CLAUDE.md`):
|
|
37
|
+
* then Claude Code already reads the rule, and an import would make AGENTS.md import itself.
|
|
38
|
+
* @param root - The repo.
|
|
39
|
+
* @returns Whether they are the same file.
|
|
40
|
+
*/
|
|
41
|
+
function sameFile(root) {
|
|
42
|
+
try {
|
|
43
|
+
return realpathSync(join(root, "CLAUDE.md")) === realpathSync(join(root, "AGENTS.md"));
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* What the repo lacks: the rule in AGENTS.md, and the import in a CLAUDE.md that exists and
|
|
51
|
+
* is not AGENTS.md itself.
|
|
52
|
+
* @param root - The repo.
|
|
53
|
+
* @returns Whether each one is missing.
|
|
54
|
+
*/
|
|
55
|
+
function missing(root) {
|
|
56
|
+
const claude = readText(root, "CLAUDE.md");
|
|
57
|
+
return { rule: !hasLine(readText(root, "AGENTS.md"), VERSION_RULE), import: claude !== null && !hasLine(claude, IMPORT) && !sameFile(root) };
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Adds a line at the end of a file, after a blank line, with the file's own line endings, or
|
|
61
|
+
* creates the file with it. Nothing the file had is lost but its trailing blank lines.
|
|
62
|
+
* @param root - The repo.
|
|
63
|
+
* @param name - The file.
|
|
64
|
+
* @param line - The line.
|
|
65
|
+
*/
|
|
66
|
+
function append(root, name, line) {
|
|
67
|
+
const text = readText(root, name) ?? "";
|
|
68
|
+
const eol = text.includes("\r\n") ? "\r\n" : "\n";
|
|
69
|
+
const kept = text.replace(/(?:\r?\n)+$/, "");
|
|
70
|
+
writeFileSync(join(root, name), `${kept.trim() === "" ? "" : `${kept}${eol}${eol}`}${line}${eol}`);
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Writes what is missing, if the repo has package.json by now.
|
|
74
|
+
* @param context - Where it runs.
|
|
75
|
+
* @returns Always true: a repo without package.json gets nothing, and that is no failure.
|
|
76
|
+
*/
|
|
77
|
+
function write(context) {
|
|
78
|
+
if (!existsSync(join(context.root, "package.json"))) {
|
|
79
|
+
context.note("sin package.json: no se agregó nada");
|
|
80
|
+
return true;
|
|
81
|
+
}
|
|
82
|
+
const lack = missing(context.root);
|
|
83
|
+
if (lack.rule)
|
|
84
|
+
append(context.root, "AGENTS.md", VERSION_RULE);
|
|
85
|
+
if (lack.import)
|
|
86
|
+
append(context.root, "CLAUDE.md", IMPORT);
|
|
87
|
+
return true;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* What the repo has of the rule, and the step that adds it.
|
|
91
|
+
* @param root - The repo.
|
|
92
|
+
* @param flowComing - Whether this run installs the flow, which writes package.json.
|
|
93
|
+
* @returns The piece's status, or null if the repo has no package.json and gets none.
|
|
94
|
+
* @example (await agentsMdStatus(root, false))?.state // "falta la regla"
|
|
95
|
+
*/
|
|
96
|
+
export async function agentsMdStatus(root, flowComing) {
|
|
97
|
+
if (!existsSync(join(root, "package.json")) && !flowComing)
|
|
98
|
+
return null;
|
|
99
|
+
const lack = missing(root);
|
|
100
|
+
const summary = "regla de versiones instaladas en AGENTS.md";
|
|
101
|
+
if (!lack.rule && !lack.import)
|
|
102
|
+
return { name: NAME, summary, installed: true, state: "al día", steps: [] };
|
|
103
|
+
const files = [...(lack.rule ? ["AGENTS.md"] : []), ...(lack.import ? ["CLAUDE.md"] : [])];
|
|
104
|
+
const label = lack.rule ? `Agregar a AGENTS.md la regla de versiones instaladas${lack.import ? " (y @AGENTS.md en CLAUDE.md)" : ""}` : "Agregar @AGENTS.md a CLAUDE.md, para que Claude Code lea AGENTS.md";
|
|
105
|
+
const state = lack.rule ? "falta la regla" : "CLAUDE.md no importa AGENTS.md";
|
|
106
|
+
return { name: NAME, summary, installed: !lack.rule, state, steps: [{ piece: NAME, checked: true, downloads: [], label: `${label}: ${files.join(", ")}`, run: async (context) => write(context) }] };
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Whether AGENTS.md has the rule and CLAUDE.md, if there is one, imports it. Only noted.
|
|
110
|
+
* @param root - The repo.
|
|
111
|
+
* @returns The checks; none without package.json.
|
|
112
|
+
* @example (await agentsMdChecks(root))[0].status // "ok"
|
|
113
|
+
*/
|
|
114
|
+
export async function agentsMdChecks(root) {
|
|
115
|
+
if (!existsSync(join(root, "package.json")))
|
|
116
|
+
return [];
|
|
117
|
+
const lack = missing(root);
|
|
118
|
+
const checks = [];
|
|
119
|
+
if (lack.rule)
|
|
120
|
+
checks.push({ name: NAME, status: "info", detail: "falta la regla de versiones en AGENTS.md; corre npx agent-kitchen apply" });
|
|
121
|
+
if (lack.import)
|
|
122
|
+
checks.push({ name: NAME, status: "info", detail: "CLAUDE.md no importa AGENTS.md, así que Claude Code no lo lee; corre npx agent-kitchen apply" });
|
|
123
|
+
return checks.length > 0 ? checks : [{ name: NAME, status: "ok", detail: "regla de versiones en AGENTS.md" }];
|
|
124
|
+
}
|
package/dist/pieces/cloud.js
CHANGED
|
@@ -83,6 +83,6 @@ export async function cloudChecks(root) {
|
|
|
83
83
|
if (version === null)
|
|
84
84
|
return [{ name: NAME, status: "fail", detail: `${CONFIG} no fija una versión exacta: pon en "version" la que se usa, como "${latest ?? "1.0.0"}"` }];
|
|
85
85
|
if (latest !== null && compareVersions(latest, version) > 0)
|
|
86
|
-
return [{ name: NAME, status: "info", detail: `${version}, hay ${latest}: corre npx agent-kitchen` }];
|
|
86
|
+
return [{ name: NAME, status: "info", detail: `${version}, hay ${latest}: corre npx agent-kitchen apply` }];
|
|
87
87
|
return [{ name: NAME, status: "ok", detail: version }];
|
|
88
88
|
}
|
package/dist/pieces/guard.d.ts
CHANGED
|
@@ -7,6 +7,11 @@
|
|
|
7
7
|
* macOS `weakerNetworkIsolation` lets `gh` work inside the sandbox (the guard's own default
|
|
8
8
|
* is false, so false counts as missing; the step can be unticked). When the flow is
|
|
9
9
|
* installed in the same run, the guard gets those options in that run too.
|
|
10
|
+
*
|
|
11
|
+
* Its command that installs or updates is `apply` since 0.5.0 and `install` before, so the
|
|
12
|
+
* command follows the version being run. If it fails, its own output is shown; the hint about
|
|
13
|
+
* Claude's sandbox is added only when that output is a permission error, which is how the
|
|
14
|
+
* sandbox's block on `.claude/settings.json` shows up.
|
|
10
15
|
*/
|
|
11
16
|
import { type Check, type PieceStatus } from "../piece.js";
|
|
12
17
|
/** The piece's name, as `--skip` takes it. */
|
|
@@ -24,6 +29,21 @@ export declare const WEAKER_NETWORK = "weakerNetworkIsolation";
|
|
|
24
29
|
* @example guardSpec("0.2.0") // "github:ramon-sg/claude-repo-guard#v0.2.0"
|
|
25
30
|
*/
|
|
26
31
|
export declare function guardSpec(version: string | null): string;
|
|
32
|
+
/**
|
|
33
|
+
* The guard's command that installs or updates, at a version.
|
|
34
|
+
* @param version - The version, or null for the default branch.
|
|
35
|
+
* @returns `apply` from 0.5.0 on and on the default branch, `install` before.
|
|
36
|
+
* @example guardCommand("0.4.1") // "install"
|
|
37
|
+
*/
|
|
38
|
+
export declare function guardCommand(version: string | null): string;
|
|
39
|
+
/**
|
|
40
|
+
* What to say when the guard did not install.
|
|
41
|
+
* @param version - The version it ran.
|
|
42
|
+
* @param output - What it printed.
|
|
43
|
+
* @returns One line: the command to run by hand, and the sandbox hint only if the output is a permission error.
|
|
44
|
+
* @example guardFailure("0.5.1", "Error: EPERM: operation not permitted") // "…sandbox…"
|
|
45
|
+
*/
|
|
46
|
+
export declare function guardFailure(version: string | null, output: string): string;
|
|
27
47
|
/**
|
|
28
48
|
* The guard's options with what the flow needs added; what the repo set stays.
|
|
29
49
|
* @param root - The repo.
|
package/dist/pieces/guard.js
CHANGED
|
@@ -7,6 +7,11 @@
|
|
|
7
7
|
* macOS `weakerNetworkIsolation` lets `gh` work inside the sandbox (the guard's own default
|
|
8
8
|
* is false, so false counts as missing; the step can be unticked). When the flow is
|
|
9
9
|
* installed in the same run, the guard gets those options in that run too.
|
|
10
|
+
*
|
|
11
|
+
* Its command that installs or updates is `apply` since 0.5.0 and `install` before, so the
|
|
12
|
+
* command follows the version being run. If it fails, its own output is shown; the hint about
|
|
13
|
+
* Claude's sandbox is added only when that output is a permission error, which is how the
|
|
14
|
+
* sandbox's block on `.claude/settings.json` shows up.
|
|
10
15
|
*/
|
|
11
16
|
import { FLOW_CONFIG } from "../piece.js";
|
|
12
17
|
import { readJson, writeJson } from "../repo.js";
|
|
@@ -32,6 +37,32 @@ export const WEAKER_NETWORK = "weakerNetworkIsolation";
|
|
|
32
37
|
export function guardSpec(version) {
|
|
33
38
|
return version === null ? `github:${REPO}` : `github:${REPO}#v${version}`;
|
|
34
39
|
}
|
|
40
|
+
/** The first version whose install command is `apply`. */
|
|
41
|
+
const APPLY_SINCE = "0.5.0";
|
|
42
|
+
/** What a write blocked by a sandbox or by the file's permissions prints. */
|
|
43
|
+
const PERMISSION_ERROR = /\b(EPERM|EACCES|EROFS)\b|operation not permitted|permission denied|read-only file system/i;
|
|
44
|
+
/**
|
|
45
|
+
* The guard's command that installs or updates, at a version.
|
|
46
|
+
* @param version - The version, or null for the default branch.
|
|
47
|
+
* @returns `apply` from 0.5.0 on and on the default branch, `install` before.
|
|
48
|
+
* @example guardCommand("0.4.1") // "install"
|
|
49
|
+
*/
|
|
50
|
+
export function guardCommand(version) {
|
|
51
|
+
return version === null || compareVersions(version, APPLY_SINCE) >= 0 ? "apply" : "install";
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* What to say when the guard did not install.
|
|
55
|
+
* @param version - The version it ran.
|
|
56
|
+
* @param output - What it printed.
|
|
57
|
+
* @returns One line: the command to run by hand, and the sandbox hint only if the output is a permission error.
|
|
58
|
+
* @example guardFailure("0.5.1", "Error: EPERM: operation not permitted") // "…sandbox…"
|
|
59
|
+
*/
|
|
60
|
+
export function guardFailure(version, output) {
|
|
61
|
+
const command = `npx ${guardSpec(version)} ${guardCommand(version)}`;
|
|
62
|
+
if (PERMISSION_ERROR.test(output))
|
|
63
|
+
return `claude-repo-guard no se instaló: no tuvo permiso para escribir en el repo. Si corres agent-kitchen dentro de una sesión de Claude, su sandbox bloquea .claude/settings.json; corre '${command}' desde tu terminal.`;
|
|
64
|
+
return `claude-repo-guard no se instaló; su error está abajo. Para reintentar a mano: '${command}'.`;
|
|
65
|
+
}
|
|
35
66
|
/**
|
|
36
67
|
* The guard's options with what the flow needs added; what the repo set stays.
|
|
37
68
|
* @param root - The repo.
|
|
@@ -71,10 +102,10 @@ async function install(context, version) {
|
|
|
71
102
|
writeJson(context.root, CONFIG, options);
|
|
72
103
|
for (const change of changes)
|
|
73
104
|
context.note(change);
|
|
74
|
-
const run = await runNpx(context.root, [guardSpec(version),
|
|
105
|
+
const run = await runNpx(context.root, [guardSpec(version), guardCommand(version), "--yes"]);
|
|
75
106
|
if (run.ok)
|
|
76
107
|
return true;
|
|
77
|
-
context.fail(
|
|
108
|
+
context.fail(guardFailure(version, run.output), run.output);
|
|
78
109
|
return false;
|
|
79
110
|
}
|
|
80
111
|
/**
|
|
@@ -144,7 +175,7 @@ export async function guardChecks(root) {
|
|
|
144
175
|
return [{ name: NAME, status: "info", detail: "no instalado (Claude se abre con tus propios permisos)" }];
|
|
145
176
|
const checks = [];
|
|
146
177
|
if (process.platform === "darwin" && readJson(root, CONFIG)?.[WEAKER_NETWORK] !== true)
|
|
147
|
-
checks.push({ name: `${NAME}: ${WEAKER_NETWORK}`, status: "fail", detail: `no está activado en ${CONFIG}, así que gh falla en el sandbox de macOS: corre npx agent-kitchen` });
|
|
178
|
+
checks.push({ name: `${NAME}: ${WEAKER_NETWORK}`, status: "fail", detail: `no está activado en ${CONFIG}, así que gh falla en el sandbox de macOS: corre npx agent-kitchen apply` });
|
|
148
179
|
const result = await captureNpx(root, [guardSpec(installed), "doctor", "--json"]);
|
|
149
180
|
let found;
|
|
150
181
|
try {
|
|
@@ -63,7 +63,7 @@ async function moveSkills(context, version) {
|
|
|
63
63
|
context.note(`skills del flujo en v${version}: ${names.join(", ")}`);
|
|
64
64
|
return true;
|
|
65
65
|
}
|
|
66
|
-
context.fail(`skills-manager no actualizó las skills del flujo; corre '${failed.command}' y luego agent-kitchen otra vez.`, failed.run.output);
|
|
66
|
+
context.fail(`skills-manager no actualizó las skills del flujo; corre '${failed.command}' y luego agent-kitchen apply otra vez.`, failed.run.output);
|
|
67
67
|
return false;
|
|
68
68
|
}
|
|
69
69
|
/**
|
|
@@ -128,7 +128,7 @@ export async function issueFlowChecks(root) {
|
|
|
128
128
|
if (installed === null)
|
|
129
129
|
return [{ name: NAME, status: "info", detail: `package.json tiene ${spec}, no una versión exacta` }];
|
|
130
130
|
if (compareVersions(installed, JSON_DOCTOR) < 0)
|
|
131
|
-
return [{ name: NAME, status: "info", detail: `${installed} no tiene doctor --json: corre npx agent-kitchen para actualizarlo` }];
|
|
131
|
+
return [{ name: NAME, status: "info", detail: `${installed} no tiene doctor --json: corre npx agent-kitchen apply para actualizarlo` }];
|
|
132
132
|
const result = await captureNpx(root, [`${FLOW_PACKAGE}@${installed}`, "doctor", "--json"]);
|
|
133
133
|
let found;
|
|
134
134
|
try {
|
|
@@ -146,6 +146,6 @@ export async function issueFlowChecks(root) {
|
|
|
146
146
|
? { name: `${NAME}: skills`, status: "info", detail: "ninguna instalada" }
|
|
147
147
|
: off.length === 0
|
|
148
148
|
? { name: `${NAME}: skills`, status: "ok", detail: `${count} en v${installed}` }
|
|
149
|
-
: { name: `${NAME}: skills`, status: "fail", detail: `${off.join(", ")}; issue-flow está en ${installed}: corre npx agent-kitchen` };
|
|
149
|
+
: { name: `${NAME}: skills`, status: "fail", detail: `${off.join(", ")}; issue-flow está en ${installed}: corre npx agent-kitchen apply` };
|
|
150
150
|
return [...own, skills];
|
|
151
151
|
}
|
package/dist/pieces/skills.js
CHANGED
|
@@ -181,7 +181,7 @@ async function updateStep(root, catalog, ref) {
|
|
|
181
181
|
const failed = await updateSkills(context.root, behind.map((skill) => skill.name));
|
|
182
182
|
if (failed === null)
|
|
183
183
|
return true;
|
|
184
|
-
context.fail(`skills-manager no actualizó ${catalog}; corre '${failed.command}' y luego agent-kitchen otra vez.`, failed.run.output);
|
|
184
|
+
context.fail(`skills-manager no actualizó ${catalog}; corre '${failed.command}' y luego agent-kitchen apply otra vez.`, failed.run.output);
|
|
185
185
|
return false;
|
|
186
186
|
},
|
|
187
187
|
};
|
package/package.json
CHANGED
|
@@ -1,13 +1,22 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agent-kitchen",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "One command to install, update and check a repo's agent tooling: the issue flow and its skills, the general skills, Claude cloud sessions
|
|
3
|
+
"version": "0.4.0",
|
|
4
|
+
"description": "One command to install, update and check a repo's agent tooling: the issue flow and its skills, the general skills, Claude cloud sessions, claude-repo-guard and the installed-versions rule in AGENTS.md.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
7
|
-
"bin": {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
"
|
|
7
|
+
"bin": {
|
|
8
|
+
"agent-kitchen": "dist/cli.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"dist"
|
|
12
|
+
],
|
|
13
|
+
"engines": {
|
|
14
|
+
"node": ">=20"
|
|
15
|
+
},
|
|
16
|
+
"publishConfig": {
|
|
17
|
+
"access": "public",
|
|
18
|
+
"provenance": true
|
|
19
|
+
},
|
|
11
20
|
"repository": {
|
|
12
21
|
"type": "git",
|
|
13
22
|
"url": "git+https://github.com/ramon-sg/agent-kitchen.git"
|
|
@@ -17,6 +26,11 @@
|
|
|
17
26
|
"test": "tsc && node --test test/*.test.mjs",
|
|
18
27
|
"prepublishOnly": "npm test"
|
|
19
28
|
},
|
|
20
|
-
"dependencies": {
|
|
21
|
-
|
|
29
|
+
"dependencies": {
|
|
30
|
+
"@clack/prompts": "1.8.1"
|
|
31
|
+
},
|
|
32
|
+
"devDependencies": {
|
|
33
|
+
"@types/node": "^24.0.0",
|
|
34
|
+
"typescript": "^5.9.3"
|
|
35
|
+
}
|
|
22
36
|
}
|