agent-kitchen 0.5.0 → 0.7.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 +14 -4
- package/dist/cli.js +10 -5
- package/dist/commands/doctor.d.ts +1 -9
- package/dist/commands/doctor.js +22 -6
- package/dist/commands/sync.d.ts +22 -1
- package/dist/commands/sync.js +227 -54
- package/dist/commit-texts.d.ts +18 -0
- package/dist/commit-texts.js +29 -0
- package/dist/decisions.d.ts +54 -0
- package/dist/decisions.js +91 -0
- package/dist/git-finish.d.ts +53 -0
- package/dist/git-finish.js +252 -0
- package/dist/menu.d.ts +33 -0
- package/dist/menu.js +48 -1
- package/dist/piece.d.ts +8 -2
- package/dist/pieces/agents-md.js +1 -3
- package/dist/pieces/cloud.js +2 -2
- package/dist/pieces/guard.js +3 -3
- package/dist/pieces/issue-flow.d.ts +12 -6
- package/dist/pieces/issue-flow.js +151 -29
- package/dist/pieces/skills-manager.d.ts +6 -3
- package/dist/pieces/skills-manager.js +33 -12
- package/dist/pieces/skills.js +3 -4
- package/dist/progress.d.ts +29 -0
- package/dist/progress.js +75 -0
- package/dist/texts/catalog.d.ts +50 -0
- package/dist/texts/catalog.js +1 -0
- package/dist/texts/es.d.ts +7 -0
- package/dist/texts/es.js +139 -0
- package/dist/texts/index.d.ts +30 -0
- package/dist/texts/index.js +31 -0
- package/dist/texts/step-text.d.ts +85 -0
- package/dist/texts/step-text.js +7 -0
- package/dist/tools.d.ts +33 -2
- package/dist/tools.js +52 -5
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -17,15 +17,15 @@ Al arrancar revisa si npm tiene una versión más nueva de sí mismo. Si la hay,
|
|
|
17
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.
|
|
18
18
|
- Cada paso es una línea con ✓ o ✗. La salida de cada herramienta solo aparece si falla, o con `--verbose`.
|
|
19
19
|
- Es rápido: las versiones se consultan todas a la vez y las herramientas se descargan mientras respondes.
|
|
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í. Las excepciones son una línea de `AGENTS.md` y la versión de `skills-manager`
|
|
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í. Las excepciones son una línea de `AGENTS.md` y, en `package.json`, la versión de `skills-manager` y la de `github-rest` cuando queda bajo el mínimo de issue-flow, que escribe él mismo.
|
|
21
21
|
- Correrlo de nuevo no rompe nada: si todo está al día, lo dice y termina.
|
|
22
22
|
|
|
23
23
|
## Las piezas
|
|
24
24
|
|
|
25
25
|
| Pieza | Qué es | Versión del repo | Última | Instalar | Actualizar |
|
|
26
26
|
|---|---|---|---|---|---|
|
|
27
|
-
| `skills-manager` | La versión de [`@ramon-sg/skills-manager`](https://github.com/ramon-sg/skills-manager) que corre `agent-kitchen`: como mínimo 1.2.0. Si falta la agrega y si es menor la sube; nunca la baja. Un rango o `workspace:` no se toca, y avisa si la versión que corre queda bajo el mínimo. Solo en repos
|
|
28
|
-
| `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
|
|
27
|
+
| `skills-manager` | La versión de [`@ramon-sg/skills-manager`](https://github.com/ramon-sg/skills-manager) que corre `agent-kitchen`: como mínimo 1.2.0. Si falta la agrega y si es menor la sube; nunca la baja. Un rango o `workspace:` no se toca, y avisa si la versión que corre queda bajo el mínimo. Solo en repos que usan skills y tienen `package.json`, o donde issue-flow se instala en la misma corrida: entonces crea el `package.json` | `package.json` | — | La agrega `agent-kitchen` | La sube `agent-kitchen` |
|
|
28
|
+
| `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 apply` (`init` antes de 0.3.0), y sus skills a la misma tag | `issue-flow apply` en la versión nueva (`upgrade` antes de 0.3.0), y sus skills a la misma tag. Desde 0.3.0, si su doctor ve hooks o el `$schema` atrás, `apply` en la versión del repo. Desde 0.3.0, si `github-rest` es una versión exacta bajo el mínimo que pide esa versión del flujo, la sube. Desde 0.2.6, si a GitHub le faltan labels del flujo (`spike`, `P1`–`P4`…), `issue-flow labels sync` los crea |
|
|
29
29
|
| `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` |
|
|
30
30
|
| `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 |
|
|
31
31
|
| `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` |
|
|
@@ -39,6 +39,7 @@ Si npm o GitHub no responden, la pieza se muestra pero no se ofrece actualizarla
|
|
|
39
39
|
- **Qué skills:** la primera vez vienen todas las de los catálogos. Con "Elegir" aparece un menú agrupado por catálogo, con la descripción de cada una. Si un catálogo trae skills que el repo no tiene, se ofrecen como un paso sin marcar.
|
|
40
40
|
- **Skills propias del repo:** si el repo ya tiene una carpeta con el nombre de una skill del catálogo, pregunta una vez si reemplazarlas. Al reemplazar se conserva su `project.md`. Con `--yes` no se reemplazan: se dejan fuera y lo dice.
|
|
41
41
|
- **Seguridad y flujo:** si el repo tiene el flujo, antes de instalar `claude-repo-guard` se escribe en sus opciones que abrir el PR en draft, asignar la issue y agregar el "Slug de rama" son parte del trabajo normal. También se escribe la rama base del flujo como `integrationBranch`. En macOS se agrega `weakerNetworkIsolation`, que `gh` necesita dentro del sandbox. Si `claude-repo-guard` ya estaba al día pero le faltan esas opciones, se ofrecen como un paso.
|
|
42
|
+
- **Qué piezas usa el repo:** `.agents/kitchen.json` guarda por pieza si el repo la quiere (`{ "pieces": { "claude-repo-guard": false } }`). Se crea en la primera corrida de `apply` en una terminal: lo instalado cuenta como sí y lo que ofreció y no marcaste, como no. `false` deja la pieza fuera del menú y de `--yes`, sin desinstalarla; la tabla lo dice y `doctor` no corre sus revisiones. Una pieza que el archivo no menciona se ofrece una vez, marcada "(nueva)"; con `--yes` o sin terminal se deja fuera, si no está instalada, y lo dice (una ya instalada se actualiza como siempre). `--yes`, `--dry-run`, `--skip` y las corridas sin terminal nunca escriben el archivo. Se cambia con `apply --pieces` o a mano. `skills-manager` no tiene decisión: sus skills siguen en `.agents/skills.json`.
|
|
42
43
|
- **La nube no molesta en local:** el hook de sesión solo actúa en la nube. Un repo que nunca se abre ahí no pierde nada.
|
|
43
44
|
|
|
44
45
|
## Uso
|
|
@@ -47,8 +48,10 @@ Si npm o GitHub no responden, la pieza se muestra pero no se ofrece actualizarla
|
|
|
47
48
|
npx agent-kitchen apply # pregunta, en una terminal (Enter: todo lo sugerido)
|
|
48
49
|
npx agent-kitchen apply --dry-run # solo muestra qué haría
|
|
49
50
|
npx agent-kitchen apply --yes # hace lo sugerido sin preguntar
|
|
51
|
+
npx agent-kitchen apply --commit # al final, pregunta si commitear, en una rama o con PR
|
|
50
52
|
npx agent-kitchen apply --verbose # muestra la salida de cada herramienta
|
|
51
53
|
npx agent-kitchen apply --skip claude-cloud # deja fuera una pieza (o varias, con comas)
|
|
54
|
+
npx agent-kitchen apply --pieces # cambia qué piezas usa el repo (solo en una terminal)
|
|
52
55
|
npx agent-kitchen doctor # revisa cada pieza con su propio doctor
|
|
53
56
|
```
|
|
54
57
|
|
|
@@ -56,7 +59,14 @@ npx agent-kitchen doctor # revisa cada pieza con su propio
|
|
|
56
59
|
|
|
57
60
|
- **Sin terminal y sin `--yes`** (por ejemplo, desde un agente), solo muestra lo que ofrecería.
|
|
58
61
|
- **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`.
|
|
59
|
-
- **Al terminar,** corre el `install` de tu gestor de paquetes, revisa el diff y haz commit.
|
|
62
|
+
- **Al terminar,** corre el `install` de tu gestor de paquetes, revisa el diff y haz commit. O usa `--commit`.
|
|
63
|
+
|
|
64
|
+
**`--commit`** deja los cambios commiteados sin que lo haga un agente:
|
|
65
|
+
|
|
66
|
+
- **Antes de cualquier paso,** exige el repo sin cambios (también sin seguimiento; los worktrees de `.claude/worktrees/` no cuentan) y en la rama base: `baseBranch` de `.agents/flow.json`, si no la rama por defecto de `origin`, si no `main`. Con `--dry-run` hace solo esta revisión y lista los pasos.
|
|
67
|
+
- **Al terminar,** si cambió `package.json`, corre el `install` de tu gestor para que el lockfile entre en el commit. Muestra los archivos cambiados y pregunta: dejarlos sin commit (por defecto), commit en la base, commit en una rama nueva `chore/agent-kitchen-apply-<fecha>`, o rama nueva con push y PR (con `@ramon-sg/github-rest`).
|
|
68
|
+
- **Con rama,** la copia principal pasa por la rama solo mientras corre el comando: commitea ahí (los hooks de git corren, y uno que prohíbe commitear en la base no la frena) y vuelve a la base sin los cambios. Si el push o el PR fallan, la rama queda y la salida dice qué correr.
|
|
69
|
+
- **Con `--yes`,** elige la rama con PR. Si algún paso falló, no commitea.
|
|
60
70
|
|
|
61
71
|
**`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 `skills-manager` esté al menos en 1.2.0, que las skills del flujo estén en la tag de su CLI, qué labels del flujo le faltan a GitHub (con `·`), 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 (`✗`).
|
|
62
72
|
|
package/dist/cli.js
CHANGED
|
@@ -21,14 +21,19 @@ 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 apply [--yes | --dry-run] [--verbose] [--skip <pieza>,...]
|
|
24
|
+
agent-kitchen apply [--yes | --dry-run] [--commit] [--verbose] [--skip <pieza>,...]
|
|
25
|
+
agent-kitchen apply --pieces [--commit]
|
|
25
26
|
agent-kitchen doctor
|
|
26
27
|
|
|
27
28
|
agent-kitchen revisa cada pieza de las herramientas de agentes del repo (${pieceNames().join(", ")}),
|
|
28
29
|
muestra qué tiene el repo y la última versión de cada una, y apply ofrece instalar lo que falta
|
|
29
30
|
y actualizar lo que está atrasado. En una terminal pregunta una vez: hacer todo lo sugerido,
|
|
30
31
|
o elegir los pasos y las skills. --yes hace lo sugerido sin preguntar; sin terminal solo lo
|
|
31
|
-
muestra. --verbose muestra la salida de cada herramienta.
|
|
32
|
+
muestra. --verbose muestra la salida de cada herramienta. --commit exige el repo sin cambios
|
|
33
|
+
y en la rama base; al final instala si cambió package.json y pregunta si dejar los cambios,
|
|
34
|
+
commitearlos en la base, en una rama nueva, o en una rama nueva con push y PR (con --yes,
|
|
35
|
+
esto último). apply --pieces elige qué piezas usa el repo y lo guarda en .agents/kitchen.json
|
|
36
|
+
(en una terminal, sin --yes ni --dry-run). Córrelo en la copia principal del
|
|
32
37
|
repo, desde tu terminal. Sin subcomando hace lo mismo que apply, por ahora.
|
|
33
38
|
|
|
34
39
|
doctor corre las revisiones de cada pieza en la versión del repo, sin cambiar nada.`;
|
|
@@ -77,7 +82,7 @@ async function main(argv) {
|
|
|
77
82
|
const { values, positionals } = parseArgs({
|
|
78
83
|
args: argv,
|
|
79
84
|
allowPositionals: true,
|
|
80
|
-
options: { yes: { type: "boolean" }, "dry-run": { type: "boolean" }, verbose: { type: "boolean", short: "v" }, skip: { type: "string" }, help: { type: "boolean", short: "h" }, version: { type: "boolean" } },
|
|
85
|
+
options: { yes: { type: "boolean" }, "dry-run": { type: "boolean" }, commit: { type: "boolean" }, verbose: { type: "boolean", short: "v" }, skip: { type: "string" }, pieces: { type: "boolean" }, help: { type: "boolean", short: "h" }, version: { type: "boolean" } },
|
|
81
86
|
});
|
|
82
87
|
if (values.help === true) {
|
|
83
88
|
out.stdout(`${USAGE}\n`);
|
|
@@ -95,14 +100,14 @@ async function main(argv) {
|
|
|
95
100
|
if (rest.length > 0 || (command !== undefined && command !== "doctor" && command !== "apply"))
|
|
96
101
|
throw new UsageError(`no existe el comando ${positionals.join(" ")}.`);
|
|
97
102
|
if (command === "doctor") {
|
|
98
|
-
if (values.yes !== undefined || values["dry-run"] !== undefined || values.skip !== undefined || values.verbose !== undefined)
|
|
103
|
+
if (values.yes !== undefined || values["dry-run"] !== undefined || values.skip !== undefined || values.verbose !== undefined || values.commit !== undefined || values.pieces !== undefined)
|
|
99
104
|
throw new UsageError("doctor no lleva opciones.");
|
|
100
105
|
return doctor(process.cwd(), out);
|
|
101
106
|
}
|
|
102
107
|
if (command === undefined)
|
|
103
108
|
out.stderr("agent-kitchen: usa 'agent-kitchen apply'; sin subcomando dejará de instalar en una versión mayor.\n");
|
|
104
109
|
const skip = (values.skip ?? "").split(",").map((name) => name.trim()).filter((name) => name !== "");
|
|
105
|
-
return sync({ cwd: process.cwd(), yes: values.yes ?? false, dryRun: values["dry-run"] ?? false, verbose: values.verbose ?? false, skip }, out);
|
|
110
|
+
return sync({ cwd: process.cwd(), yes: values.yes ?? false, dryRun: values["dry-run"] ?? false, verbose: values.verbose ?? false, skip, commit: values.commit ?? false, pieces: values.pieces ?? false }, out);
|
|
106
111
|
}
|
|
107
112
|
try {
|
|
108
113
|
process.exitCode = await main(process.argv.slice(2));
|
|
@@ -1,12 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
* @file `agent-kitchen doctor`: whether each piece the repo has works, with the doctor of each
|
|
3
|
-
* tool at the repo's own version: the version of skills-manager agent-kitchen needs, the
|
|
4
|
-
* issue flow (and its skills at its tag),
|
|
5
|
-
* `skills-manager check`, the version `.claude/cloud.json` pins, and claude-repo-guard. It
|
|
6
|
-
* changes nothing. A piece the repo does not have, or a newer release, is only noted, and so is
|
|
7
|
-
* a missing rule in AGENTS.md.
|
|
8
|
-
*/
|
|
9
|
-
import type { Output } from "../output.js";
|
|
1
|
+
import { type Output } from "../output.js";
|
|
10
2
|
/**
|
|
11
3
|
* Checks the repo; see the file's header.
|
|
12
4
|
* @param cwd - Where it runs.
|
package/dist/commands/doctor.js
CHANGED
|
@@ -1,8 +1,19 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
/**
|
|
2
|
+
* @file `agent-kitchen doctor`: whether each piece the repo has works, with the doctor of each
|
|
3
|
+
* tool at the repo's own version: the version of skills-manager agent-kitchen needs, the
|
|
4
|
+
* issue flow (and its skills at its tag),
|
|
5
|
+
* `skills-manager check`, the version `.claude/cloud.json` pins, and claude-repo-guard. It
|
|
6
|
+
* changes nothing. A piece the repo does not have, or a newer release, is only noted, and so is
|
|
7
|
+
* a missing rule in AGENTS.md. A piece the repo decided against (`.agents/kitchen.json`) is
|
|
8
|
+
* only noted, and its tool is not asked.
|
|
9
|
+
*/
|
|
10
|
+
import { readDecisions } from "../decisions.js";
|
|
11
|
+
import { warn } from "../output.js";
|
|
12
|
+
import { agentsMdChecks, NAME as AGENTS_MD } from "../pieces/agents-md.js";
|
|
13
|
+
import { cloudChecks, NAME as CLOUD } from "../pieces/cloud.js";
|
|
14
|
+
import { guardChecks, NAME as GUARD } from "../pieces/guard.js";
|
|
15
|
+
import { issueFlowChecks, NAME as FLOW } from "../pieces/issue-flow.js";
|
|
16
|
+
import { NAME as SKILLS, skillsChecks } from "../pieces/skills.js";
|
|
6
17
|
import { skillsManagerChecks } from "../pieces/skills-manager.js";
|
|
7
18
|
import { mainCheckout } from "../repo.js";
|
|
8
19
|
/** The mark of each check's status. */
|
|
@@ -17,7 +28,12 @@ const MARKS = { ok: "✓", fail: "✗", info: "·" };
|
|
|
17
28
|
*/
|
|
18
29
|
export async function doctor(cwd, out) {
|
|
19
30
|
const root = mainCheckout(cwd, "agent-kitchen doctor");
|
|
20
|
-
const
|
|
31
|
+
const saved = readDecisions(root);
|
|
32
|
+
for (const warning of saved?.warnings ?? [])
|
|
33
|
+
warn(out, warning);
|
|
34
|
+
/** The checks of a piece, or a note if the repo left it out. */
|
|
35
|
+
const unless = (name, run) => saved?.pieces[name] === false ? Promise.resolve([{ name, status: "info", detail: "fuera por decisión del repo" }]) : run();
|
|
36
|
+
const checks = (await Promise.all([skillsManagerChecks(root), unless(FLOW, () => issueFlowChecks(root)), unless(SKILLS, () => skillsChecks(root)), unless(CLOUD, () => cloudChecks(root)), unless(GUARD, () => guardChecks(root)), unless(AGENTS_MD, () => agentsMdChecks(root))])).flat();
|
|
21
37
|
for (const check of checks)
|
|
22
38
|
out.stdout(`${MARKS[check.status]} ${check.name}: ${check.detail}\n`);
|
|
23
39
|
return checks.some((check) => check.status === "fail") ? 1 : 0;
|
package/dist/commands/sync.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type Output } from "../output.js";
|
|
2
|
+
import type { Step } from "../piece.js";
|
|
2
3
|
/** Options of the command. */
|
|
3
4
|
export interface SyncOptions {
|
|
4
5
|
cwd: string;
|
|
@@ -6,6 +7,9 @@ export interface SyncOptions {
|
|
|
6
7
|
dryRun: boolean;
|
|
7
8
|
verbose: boolean;
|
|
8
9
|
skip: string[];
|
|
10
|
+
commit: boolean;
|
|
11
|
+
/** Ask which pieces the repo uses, save the answer, then go on as usual. */
|
|
12
|
+
pieces: boolean;
|
|
9
13
|
}
|
|
10
14
|
/**
|
|
11
15
|
* The names `--skip` takes.
|
|
@@ -13,12 +17,29 @@ export interface SyncOptions {
|
|
|
13
17
|
* @example pieceNames() // ["skills-manager", "issue-flow", "claude-cloud", "claude-repo-guard", "skills", "agents-md"]
|
|
14
18
|
*/
|
|
15
19
|
export declare function pieceNames(): string[];
|
|
20
|
+
/**
|
|
21
|
+
* The closing line when no step ran, which must not contradict a file written in this run.
|
|
22
|
+
* @param why - "leave" if the person left without changing anything, "none" if they picked no steps.
|
|
23
|
+
* @param wrote - Whether the decisions file was written in this run.
|
|
24
|
+
* @returns The line, with its newline.
|
|
25
|
+
* @example noStepsMessage("none", true) // "No elegiste pasos; solo guardé qué piezas usa el repo.\n"
|
|
26
|
+
*/
|
|
27
|
+
export declare function noStepsMessage(why: "leave" | "none", wrote: boolean): string;
|
|
28
|
+
/**
|
|
29
|
+
* What the commit of `--commit` lists: the steps that worked, and the decisions file if this
|
|
30
|
+
* run wrote it, in the commit's texts (not the interface's).
|
|
31
|
+
* @param done - The steps that worked.
|
|
32
|
+
* @param wrote - Whether the decisions file was written in this run.
|
|
33
|
+
* @returns The lines.
|
|
34
|
+
* @example commitLabels([], true) // ["Guardar qué piezas usa el repo en .agents/kitchen.json"]
|
|
35
|
+
*/
|
|
36
|
+
export declare function commitLabels(done: Pick<Step, "text">[], wrote: boolean): string[];
|
|
16
37
|
/**
|
|
17
38
|
* Runs the command; see the file's header.
|
|
18
39
|
* @param options - The options.
|
|
19
40
|
* @param out - Where to write and ask.
|
|
20
41
|
* @returns 0, or 1 if a step failed or the person cancelled.
|
|
21
42
|
* @throws KitError outside the main checkout or with a broken file; UsageError for an unknown piece.
|
|
22
|
-
* @example await sync({ cwd, yes: false, dryRun: true, verbose: false, skip: [] }, out)
|
|
43
|
+
* @example await sync({ cwd, yes: false, dryRun: true, verbose: false, skip: [], commit: false, pieces: false }, out)
|
|
23
44
|
*/
|
|
24
45
|
export declare function sync(options: SyncOptions, out: Output): Promise<number>;
|
package/dist/commands/sync.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @file `agent-kitchen apply [--yes] [--dry-run] [--verbose] [--skip <piece>,...]`: the one command
|
|
2
|
+
* @file `agent-kitchen apply [--yes] [--dry-run] [--commit] [--verbose] [--skip <piece>,...]`: the one command
|
|
3
3
|
* that changes the repo (and, for now, also the bare `agent-kitchen`).
|
|
4
4
|
* It looks at each piece (the version of skills-manager, the issue flow, claude-cloud,
|
|
5
5
|
* claude-repo-guard, the skills, the rule in AGENTS.md) with
|
|
@@ -11,10 +11,21 @@
|
|
|
11
11
|
* flow's version), one line each, with the
|
|
12
12
|
* tools' output only when one fails; one that fails does not stop the rest. With `--yes`
|
|
13
13
|
* it does what is suggested without asking (and keeps the repo's own skills); without a
|
|
14
|
-
* terminal it only shows the steps.
|
|
14
|
+
* terminal it only shows the steps. With `--commit` it first checks the repo is clean and on
|
|
15
|
+
* its base branch, and at the end installs if package.json changed and asks what to do with
|
|
16
|
+
* the changes (see git-finish.ts), also when the only change is the decisions file below;
|
|
17
|
+
* the steps then show as plain lines, since a spinner before a question leaves it deaf.
|
|
18
|
+
* What the repo decided of each piece, in `.agents/kitchen.json`, is respected: a piece set to
|
|
19
|
+
* false is left out as with `--skip`, and one with no decision is marked as new. Without
|
|
20
|
+
* asking (`--yes` or no terminal), a new piece that is not installed is left out for the run.
|
|
21
|
+
* In a terminal, once the person has answered, the pieces without a decision get one and the
|
|
22
|
+
* file is written; `--yes`, `--dry-run` and `--skip` never write it.
|
|
23
|
+
* `--pieces` (terminal only, not with `--yes` or `--dry-run`) first asks which pieces the repo
|
|
24
|
+
* uses, writes all of them to the file, and then goes on as usual.
|
|
15
25
|
*/
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
26
|
+
import { DECIDABLE, decide, DECISIONS, off, readDecisions, undecided, writeDecisions } from "../decisions.js";
|
|
27
|
+
import { baseBranch, branchName, changedPaths, checkReady, finish } from "../git-finish.js";
|
|
28
|
+
import { askFinish, askPath, askPieces, askReplace, askSkills, askSteps } from "../menu.js";
|
|
18
29
|
import { KitError, UsageError, warn } from "../output.js";
|
|
19
30
|
import { cloudStatus, NAME as CLOUD } from "../pieces/cloud.js";
|
|
20
31
|
import { guardStatus, NAME as GUARD } from "../pieces/guard.js";
|
|
@@ -23,7 +34,10 @@ import { keepOwn, NAME as SKILLS, ownInPick, skillsStatus } from "../pieces/skil
|
|
|
23
34
|
import { agentsMdStatus, NAME as AGENTS_MD } from "../pieces/agents-md.js";
|
|
24
35
|
import { NAME as SKILLS_MANAGER, skillsManagerStatus } from "../pieces/skills-manager.js";
|
|
25
36
|
import { mainCheckout, packageManager } from "../repo.js";
|
|
26
|
-
import { download, failedOutput, setVerbose } from "../tools.js";
|
|
37
|
+
import { download, failedOutput, runInstall, setVerbose } from "../tools.js";
|
|
38
|
+
import { commitLine, DECISIONS_LINE } from "../commit-texts.js";
|
|
39
|
+
import { progressFor } from "../progress.js";
|
|
40
|
+
import { stepLabel, ui } from "../texts/index.js";
|
|
27
41
|
/** Every piece, in the order its steps run. */
|
|
28
42
|
const PIECES = [SKILLS_MANAGER, FLOW, CLOUD, GUARD, SKILLS, AGENTS_MD];
|
|
29
43
|
/**
|
|
@@ -35,37 +49,116 @@ export function pieceNames() {
|
|
|
35
49
|
return [...PIECES];
|
|
36
50
|
}
|
|
37
51
|
/**
|
|
38
|
-
* Each piece's status, with every lookup at once.
|
|
39
|
-
* depend on whether the flow is about to be installed (it writes
|
|
40
|
-
* for the flow's status.
|
|
52
|
+
* Each piece's status, with every lookup at once. skills-manager, the guard's step and the
|
|
53
|
+
* rule in AGENTS.md depend on whether the flow is about to be installed (it writes
|
|
54
|
+
* package.json), so they wait for the flow's status.
|
|
41
55
|
* @param root - The repo.
|
|
42
56
|
* @param skip - The pieces left out.
|
|
43
57
|
* @returns The statuses, in the pieces' order.
|
|
44
58
|
*/
|
|
45
59
|
async function statusesOf(root, skip) {
|
|
46
|
-
const skillsManager = skip.includes(SKILLS_MANAGER) ? null : skillsManagerStatus(root, !skip.includes(SKILLS));
|
|
47
60
|
const flow = skip.includes(FLOW) ? null : issueFlowStatus(root);
|
|
61
|
+
const flowComing = (flow ?? Promise.resolve(null)).then((status) => status !== null && !status.installed && status.steps.length > 0);
|
|
62
|
+
const skillsManager = skip.includes(SKILLS_MANAGER) ? null : flowComing.then((coming) => skillsManagerStatus(root, !skip.includes(SKILLS), coming));
|
|
48
63
|
const cloud = skip.includes(CLOUD) ? null : cloudStatus(root);
|
|
49
64
|
const skills = skip.includes(SKILLS) ? null : skillsStatus(root, !skip.includes(FLOW));
|
|
50
|
-
const flowComing = (flow ?? Promise.resolve(null)).then((status) => status !== null && !status.installed && status.steps.length > 0);
|
|
51
65
|
const guard = skip.includes(GUARD) ? null : flowComing.then((coming) => guardStatus(root, coming));
|
|
52
66
|
const agentsMd = skip.includes(AGENTS_MD) ? null : flowComing.then((coming) => agentsMdStatus(root, coming));
|
|
53
67
|
const all = await Promise.all([skillsManager, flow, cloud, guard, skills, agentsMd]);
|
|
54
68
|
return all.flatMap((status) => status ?? []);
|
|
55
69
|
}
|
|
56
70
|
/**
|
|
57
|
-
* Prints what the repo has of each piece
|
|
71
|
+
* Prints what the repo has of each piece, in the pieces' order, and the pieces the repo
|
|
72
|
+
* decided not to have, with how to change that.
|
|
58
73
|
* @param statuses - The pieces' statuses.
|
|
74
|
+
* @param decidedOff - The pieces left out by the repo's decision.
|
|
59
75
|
* @param out - Where to write.
|
|
60
76
|
*/
|
|
61
|
-
function printStatus(statuses, out) {
|
|
62
|
-
const width = Math.max(...statuses.map((status) => status.name.length));
|
|
77
|
+
function printStatus(statuses, decidedOff, out) {
|
|
78
|
+
const width = Math.max(...statuses.map((status) => status.name.length), ...decidedOff.map((name) => name.length));
|
|
63
79
|
const stateWidth = Math.max(...statuses.map((status) => status.state.length));
|
|
64
80
|
out.stdout("\nEste repo tiene:\n");
|
|
65
|
-
for (const
|
|
66
|
-
|
|
81
|
+
for (const name of PIECES) {
|
|
82
|
+
for (const status of statuses.filter((each) => each.name === name))
|
|
83
|
+
out.stdout(` ${status.name.padEnd(width)} ${status.state.padEnd(stateWidth)} · ${status.summary}\n`);
|
|
84
|
+
if (decidedOff.includes(name))
|
|
85
|
+
out.stdout(` ${name.padEnd(width)} fuera por decisión del repo\n`);
|
|
86
|
+
}
|
|
87
|
+
if (decidedOff.length > 0)
|
|
88
|
+
out.stdout(`\nPara cambiar qué piezas usa el repo: agent-kitchen apply --pieces (o edita ${DECISIONS}).\n`);
|
|
67
89
|
out.stdout("\n");
|
|
68
90
|
}
|
|
91
|
+
/**
|
|
92
|
+
* Settles the pieces without a decision and writes the file, unless nothing changed or
|
|
93
|
+
* nothing was decided.
|
|
94
|
+
* @param root - The repo.
|
|
95
|
+
* @param saved - What was read, or null if there is no file.
|
|
96
|
+
* @param statuses - What each piece found.
|
|
97
|
+
* @param chosen - The steps the person picked.
|
|
98
|
+
* @param commits - Whether `--commit` asks about the changes at the end.
|
|
99
|
+
* @param out - Where to write.
|
|
100
|
+
* @returns True if the file was written.
|
|
101
|
+
*/
|
|
102
|
+
function saveDecisions(root, saved, statuses, chosen, commits, out) {
|
|
103
|
+
const previous = saved?.pieces ?? {};
|
|
104
|
+
const pieces = decide(statuses, chosen, previous);
|
|
105
|
+
const same = Object.keys(pieces).length === Object.keys(previous).length && Object.entries(pieces).every(([name, value]) => previous[name] === value);
|
|
106
|
+
if (Object.keys(pieces).length === 0 || (saved !== null && same))
|
|
107
|
+
return false;
|
|
108
|
+
writeDecisions(root, pieces, saved?.raw ?? {});
|
|
109
|
+
out.stdout(savedMessage(commits));
|
|
110
|
+
return true;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* The closing line when no step ran, which must not contradict a file written in this run.
|
|
114
|
+
* @param why - "leave" if the person left without changing anything, "none" if they picked no steps.
|
|
115
|
+
* @param wrote - Whether the decisions file was written in this run.
|
|
116
|
+
* @returns The line, with its newline.
|
|
117
|
+
* @example noStepsMessage("none", true) // "No elegiste pasos; solo guardé qué piezas usa el repo.\n"
|
|
118
|
+
*/
|
|
119
|
+
export function noStepsMessage(why, wrote) {
|
|
120
|
+
return `${ui.noSteps(why, wrote)}\n`;
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* The line that says the decisions were saved.
|
|
124
|
+
* @param commits - Whether `--commit` asks about the changes at the end, so the file need not be committed by hand.
|
|
125
|
+
* @returns The line.
|
|
126
|
+
*/
|
|
127
|
+
function savedMessage(commits) {
|
|
128
|
+
return `${ui.saved(DECISIONS, commits)}\n`;
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* What the commit of `--commit` lists: the steps that worked, and the decisions file if this
|
|
132
|
+
* run wrote it, in the commit's texts (not the interface's).
|
|
133
|
+
* @param done - The steps that worked.
|
|
134
|
+
* @param wrote - Whether the decisions file was written in this run.
|
|
135
|
+
* @returns The lines.
|
|
136
|
+
* @example commitLabels([], true) // ["Guardar qué piezas usa el repo en .agents/kitchen.json"]
|
|
137
|
+
*/
|
|
138
|
+
export function commitLabels(done, wrote) {
|
|
139
|
+
const lines = done.map((step) => commitLine(step.text));
|
|
140
|
+
return wrote ? [...lines, DECISIONS_LINE] : lines;
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Asks which pieces the repo uses and writes the answer for every decidable piece.
|
|
144
|
+
* @param root - The repo.
|
|
145
|
+
* @param saved - What was read, or null if there is no file.
|
|
146
|
+
* @param commits - Whether `--commit` asks about the changes at the end.
|
|
147
|
+
* @param out - Where to write.
|
|
148
|
+
* @returns False if the person cancelled.
|
|
149
|
+
*/
|
|
150
|
+
async function choosePieces(root, saved, commits, out) {
|
|
151
|
+
out.stdout(`${ui.checking}\n`);
|
|
152
|
+
// No skip here: an installed, undecided piece passed in --skip still starts ticked.
|
|
153
|
+
const statuses = await statusesOf(root, []);
|
|
154
|
+
const ticked = DECIDABLE.filter((name) => saved?.pieces[name] === true || (saved?.pieces[name] === undefined && statuses.some((status) => status.name === name && status.installed)));
|
|
155
|
+
const picked = await askPieces(DECIDABLE, ticked);
|
|
156
|
+
if (picked === null)
|
|
157
|
+
return false;
|
|
158
|
+
writeDecisions(root, Object.fromEntries(DECIDABLE.map((name) => [name, picked.includes(name)])), saved?.raw ?? {});
|
|
159
|
+
out.stdout(savedMessage(commits));
|
|
160
|
+
return true;
|
|
161
|
+
}
|
|
69
162
|
/**
|
|
70
163
|
* Leaves the repo's own skills out of a step's pick, saying so.
|
|
71
164
|
* @param root - The repo.
|
|
@@ -79,7 +172,7 @@ function keepOwnSkills(root, step, out) {
|
|
|
79
172
|
if (own.length === 0)
|
|
80
173
|
return;
|
|
81
174
|
Object.assign(step.skills, keepOwn(step.skills, own));
|
|
82
|
-
out.stdout(
|
|
175
|
+
out.stdout(`${ui.ownSkillsKept(own)}\n`);
|
|
83
176
|
}
|
|
84
177
|
/**
|
|
85
178
|
* Asks, for a step that installs skills, which ones, and whether to replace the repo's own.
|
|
@@ -123,14 +216,13 @@ async function chooseSteps(root, steps, options, out) {
|
|
|
123
216
|
const suggested = steps.filter((step) => step.checked);
|
|
124
217
|
if (options.yes) {
|
|
125
218
|
if (suggested.length === 0)
|
|
126
|
-
out.stdout(
|
|
219
|
+
out.stdout(`${ui.onlyOptional(steps.map(stepLabel))}\n`);
|
|
127
220
|
for (const step of suggested)
|
|
128
221
|
keepOwnSkills(root, step, out);
|
|
129
222
|
return suggested;
|
|
130
223
|
}
|
|
131
224
|
if (options.dryRun || !out.interactive) {
|
|
132
|
-
|
|
133
|
-
out.stdout(`Pasos sugeridos${hint}:\n${steps.map((step) => ` [${step.checked ? "x" : " "}] ${step.label}\n`).join("")}`);
|
|
225
|
+
out.stdout(`${ui.suggested(options.dryRun)}\n${steps.map((step) => ` [${step.checked ? "x" : " "}] ${stepLabel(step)}\n`).join("")}`);
|
|
134
226
|
return null;
|
|
135
227
|
}
|
|
136
228
|
for (const step of suggested)
|
|
@@ -148,22 +240,6 @@ async function chooseSteps(root, steps, options, out) {
|
|
|
148
240
|
}
|
|
149
241
|
return chosen;
|
|
150
242
|
}
|
|
151
|
-
/**
|
|
152
|
-
* How progress is shown: a spinner in a terminal (unless the tools print with
|
|
153
|
-
* `--verbose`), plain lines otherwise. Only for the steps, after every question: a spinner
|
|
154
|
-
* shown before a question leaves the second question deaf to the keyboard (@clack/prompts
|
|
155
|
-
* 1.8.1).
|
|
156
|
-
* @param out - Where to write.
|
|
157
|
-
* @param verbose - Whether the tools print on the terminal.
|
|
158
|
-
* @returns The progress.
|
|
159
|
-
*/
|
|
160
|
-
function progressFor(out, verbose) {
|
|
161
|
-
if (out.interactive && !verbose) {
|
|
162
|
-
const spin = spinner();
|
|
163
|
-
return { start: (text) => spin.start(text), stop: (text, ok) => (ok ? spin.stop(text) : spin.error(text)) };
|
|
164
|
-
}
|
|
165
|
-
return { start: (text) => out.stdout(`→ ${text}\n`), stop: (text, ok) => out.stdout(`${ok ? "✓" : "✗"} ${text}\n`) };
|
|
166
|
-
}
|
|
167
243
|
/**
|
|
168
244
|
* Runs one step and shows how it went: its notes, and what to do if it failed.
|
|
169
245
|
* @param root - The repo.
|
|
@@ -181,19 +257,21 @@ async function runStep(root, step, progress, out) {
|
|
|
181
257
|
note: (text) => notes.push(text),
|
|
182
258
|
fail: (text, output) => failures.push(` ${text}\n${output === undefined ? "" : failedOutput(output)}`),
|
|
183
259
|
};
|
|
184
|
-
progress.start(step.
|
|
185
|
-
await Promise.all(step.downloads.map((spec) => download(root, spec)));
|
|
260
|
+
progress.start(step.text);
|
|
186
261
|
let ok;
|
|
187
262
|
try {
|
|
263
|
+
await Promise.all(step.downloads.map((spec) => download(root, spec)));
|
|
188
264
|
ok = await step.run(context);
|
|
189
265
|
}
|
|
190
266
|
catch (error) {
|
|
191
|
-
if (!(error instanceof KitError))
|
|
267
|
+
if (!(error instanceof KitError)) {
|
|
268
|
+
progress.stop(step.text, false);
|
|
192
269
|
throw error;
|
|
270
|
+
}
|
|
193
271
|
failures.push(` ${error.message}\n`);
|
|
194
272
|
ok = false;
|
|
195
273
|
}
|
|
196
|
-
progress.stop(step.
|
|
274
|
+
progress.stop(step.text, ok);
|
|
197
275
|
for (const note of notes)
|
|
198
276
|
out.stdout(` · ${note}\n`);
|
|
199
277
|
for (const failure of failures)
|
|
@@ -206,7 +284,7 @@ async function runStep(root, step, progress, out) {
|
|
|
206
284
|
* @param out - Where to write and ask.
|
|
207
285
|
* @returns 0, or 1 if a step failed or the person cancelled.
|
|
208
286
|
* @throws KitError outside the main checkout or with a broken file; UsageError for an unknown piece.
|
|
209
|
-
* @example await sync({ cwd, yes: false, dryRun: true, verbose: false, skip: [] }, out)
|
|
287
|
+
* @example await sync({ cwd, yes: false, dryRun: true, verbose: false, skip: [], commit: false, pieces: false }, out)
|
|
210
288
|
*/
|
|
211
289
|
export async function sync(options, out) {
|
|
212
290
|
const unknown = options.skip.find((name) => !pieceNames().includes(name));
|
|
@@ -214,39 +292,134 @@ export async function sync(options, out) {
|
|
|
214
292
|
throw new UsageError(`no existe la pieza ${unknown}; son ${pieceNames().join(", ")}.`);
|
|
215
293
|
if (options.yes && options.dryRun)
|
|
216
294
|
throw new UsageError("usa --yes o --dry-run, no los dos.");
|
|
295
|
+
if (options.pieces && (options.yes || options.dryRun))
|
|
296
|
+
throw new UsageError("usa --pieces solo, sin --yes ni --dry-run.");
|
|
297
|
+
if (options.pieces && !out.interactive)
|
|
298
|
+
throw new UsageError("--pieces pregunta qué piezas usa el repo; córrelo en una terminal.");
|
|
217
299
|
const root = mainCheckout(options.cwd, "agent-kitchen apply");
|
|
300
|
+
const base = options.commit ? baseBranch(root) : null;
|
|
301
|
+
if (base !== null)
|
|
302
|
+
checkReady(root, base);
|
|
218
303
|
setVerbose(options.verbose);
|
|
304
|
+
let saved = readDecisions(root);
|
|
305
|
+
let wrote = false;
|
|
306
|
+
for (const warning of saved?.warnings ?? [])
|
|
307
|
+
warn(out, warning);
|
|
308
|
+
if (options.pieces) {
|
|
309
|
+
if (!(await choosePieces(root, saved, base !== null, out))) {
|
|
310
|
+
out.stdout("No se cambió nada.\n");
|
|
311
|
+
return 0;
|
|
312
|
+
}
|
|
313
|
+
saved = readDecisions(root);
|
|
314
|
+
wrote = true;
|
|
315
|
+
}
|
|
316
|
+
const skip = [...options.skip, ...off(saved)];
|
|
317
|
+
const asks = out.interactive && !options.yes && !options.dryRun;
|
|
318
|
+
const saves = asks && options.skip.length === 0;
|
|
219
319
|
if (out.interactive)
|
|
220
|
-
out.stdout(
|
|
221
|
-
|
|
222
|
-
|
|
320
|
+
out.stdout(`${ui.checking}\n`);
|
|
321
|
+
let statuses = await statusesOf(root, skip);
|
|
322
|
+
if (saved !== null && !asks && !options.dryRun) {
|
|
323
|
+
const left = undecided(saved).filter((name) => statuses.some((status) => status.name === name && !status.installed));
|
|
324
|
+
if (left.length > 0) {
|
|
325
|
+
skip.push(...left);
|
|
326
|
+
statuses = await statusesOf(root, skip);
|
|
327
|
+
out.stdout(`${ui.undecided(left)}\n`);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
if (saved !== null) {
|
|
331
|
+
const fresh = undecided(saved);
|
|
332
|
+
for (const status of statuses.filter((each) => fresh.includes(each.name))) {
|
|
333
|
+
status.state += ui.fresh;
|
|
334
|
+
for (const step of status.steps)
|
|
335
|
+
step.fresh = true;
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
printStatus(statuses, off(saved).filter((name) => !options.skip.includes(name)), out);
|
|
223
339
|
const steps = statuses.flatMap((status) => status.steps);
|
|
340
|
+
// With --commit, a run that only wrote the decisions file still ends with the question.
|
|
341
|
+
const progress = progressFor(out, options.verbose, base !== null);
|
|
342
|
+
const endWithoutSteps = async () => (base !== null && wrote ? commitChanges(root, base, commitLabels([], true), [], progress, options, out) : 0);
|
|
224
343
|
if (steps.length === 0) {
|
|
225
|
-
|
|
226
|
-
|
|
344
|
+
if (saves)
|
|
345
|
+
wrote = saveDecisions(root, saved, statuses, [], base !== null, out) || wrote;
|
|
346
|
+
out.stdout(`${ui.upToDate}\n`);
|
|
347
|
+
return endWithoutSteps();
|
|
227
348
|
}
|
|
228
349
|
const chosen = await chooseSteps(root, steps, options, out);
|
|
350
|
+
if (saves && Array.isArray(chosen))
|
|
351
|
+
wrote = saveDecisions(root, saved, statuses, chosen, base !== null, out) || wrote;
|
|
229
352
|
if (chosen === "leave") {
|
|
230
|
-
out.stdout("
|
|
231
|
-
return
|
|
353
|
+
out.stdout(noStepsMessage("leave", wrote));
|
|
354
|
+
return endWithoutSteps();
|
|
232
355
|
}
|
|
233
356
|
if (chosen === null) {
|
|
357
|
+
if (wrote) {
|
|
358
|
+
out.stdout(noStepsMessage("leave", wrote));
|
|
359
|
+
return endWithoutSteps();
|
|
360
|
+
}
|
|
234
361
|
if (!options.dryRun && out.interactive)
|
|
235
362
|
warn(out, "no se cambió nada.");
|
|
236
363
|
return options.dryRun || !out.interactive ? 0 : 1;
|
|
237
364
|
}
|
|
238
365
|
if (chosen.length === 0) {
|
|
239
366
|
if (!options.yes)
|
|
240
|
-
out.stdout("
|
|
241
|
-
return
|
|
367
|
+
out.stdout(noStepsMessage("none", wrote));
|
|
368
|
+
return endWithoutSteps();
|
|
242
369
|
}
|
|
243
|
-
const
|
|
244
|
-
const failed = [];
|
|
370
|
+
const failedSteps = [];
|
|
245
371
|
for (const step of chosen) {
|
|
246
372
|
if (!(await runStep(root, step, progress, out)))
|
|
247
|
-
|
|
373
|
+
failedSteps.push(step);
|
|
248
374
|
}
|
|
249
|
-
const
|
|
250
|
-
|
|
375
|
+
const failed = failedSteps.map(stepLabel);
|
|
376
|
+
if (base !== null)
|
|
377
|
+
return commitChanges(root, base, commitLabels(chosen.filter((step) => !failedSteps.includes(step)), wrote), failed, progress, options, out);
|
|
378
|
+
out.stdout(`\n${ui.summary(failed)} ${ui.next(packageManager(root))}\n`);
|
|
251
379
|
return failed.length === 0 ? 0 : 1;
|
|
252
380
|
}
|
|
381
|
+
/**
|
|
382
|
+
* The end of `--commit`: the install if package.json changed, the changes, the question (or
|
|
383
|
+
* the branch with a PR under --yes) and what git did.
|
|
384
|
+
* @param root - The repo.
|
|
385
|
+
* @param base - The base branch.
|
|
386
|
+
* @param done - What the commit lists: the steps that worked, and the decisions file if written.
|
|
387
|
+
* @param failed - The steps that failed, as the interface names them.
|
|
388
|
+
* @param progress - How progress is shown, for the install.
|
|
389
|
+
* @param options - The options.
|
|
390
|
+
* @param out - Where to write and ask.
|
|
391
|
+
* @returns 0, or 1 if a step, the install or git failed.
|
|
392
|
+
*/
|
|
393
|
+
async function commitChanges(root, base, done, failed, progress, options, out) {
|
|
394
|
+
const summary = ui.summary(failed);
|
|
395
|
+
const changed = changedPaths(root);
|
|
396
|
+
if (changed.length === 0) {
|
|
397
|
+
out.stdout(`\n${summary} No cambió ningún archivo: no hay nada que commitear.\n`);
|
|
398
|
+
return failed.length === 0 ? 0 : 1;
|
|
399
|
+
}
|
|
400
|
+
const manager = packageManager(root);
|
|
401
|
+
const packageChanged = changed.includes("package.json");
|
|
402
|
+
if (packageChanged) {
|
|
403
|
+
const text = { kind: "install-deps", manager };
|
|
404
|
+
progress.start(text);
|
|
405
|
+
const installed = await runInstall(root, manager);
|
|
406
|
+
progress.stop(text, installed.ok);
|
|
407
|
+
if (!installed.ok) {
|
|
408
|
+
out.stderr(failedOutput(installed.output));
|
|
409
|
+
out.stdout(`\n${summary} No commiteé: el install falló y los cambios quedan sin commit.\n`);
|
|
410
|
+
return 1;
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
const paths = changedPaths(root);
|
|
414
|
+
out.stdout(`\n${summary} Cambios:\n${paths.map((path) => ` ${path}`).join("\n")}\n`);
|
|
415
|
+
if (failed.length > 0 && options.yes) {
|
|
416
|
+
out.stdout("No commiteé porque algún paso falló; los cambios quedan sin commit.\n");
|
|
417
|
+
return 1;
|
|
418
|
+
}
|
|
419
|
+
const branch = branchName(root, new Date());
|
|
420
|
+
const choice = options.yes ? "pr" : await askFinish(base, branch, failed.length > 0);
|
|
421
|
+
const result = await finish(root, choice, { base, branch, steps: done, paths, manager, packageChanged });
|
|
422
|
+
for (const line of result.lines)
|
|
423
|
+
out.stdout(`${line}\n`);
|
|
424
|
+
return result.ok && failed.length === 0 ? 0 : 1;
|
|
425
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { StepText } from "./texts/step-text.js";
|
|
2
|
+
/** The subject of the commit and the title of the PR. */
|
|
3
|
+
export declare const COMMIT_TITLE = "chore: agent-kitchen apply";
|
|
4
|
+
/** The line of the decisions file. */
|
|
5
|
+
export declare const DECISIONS_LINE = "Guardar qu\u00E9 piezas usa el repo en .agents/kitchen.json";
|
|
6
|
+
/**
|
|
7
|
+
* A step's line in the commit and the PR.
|
|
8
|
+
* @param text - What the step does.
|
|
9
|
+
* @returns The line, without the dash.
|
|
10
|
+
* @example commitLine({ kind: "flow-install", version: "0.2.0" }) // "Instalar issue-flow 0.2.0: config del flujo, hooks y scripts"
|
|
11
|
+
*/
|
|
12
|
+
export declare function commitLine(text: StepText): string;
|
|
13
|
+
/**
|
|
14
|
+
* The body of the PR.
|
|
15
|
+
* @param lines - What the commit lists.
|
|
16
|
+
* @returns The body.
|
|
17
|
+
*/
|
|
18
|
+
export declare function prBody(lines: string[]): string;
|