agent-kitchen 0.5.0 → 0.6.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 +21 -1
- package/dist/commands/sync.js +211 -23
- 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 +253 -0
- package/dist/menu.d.ts +33 -0
- package/dist/menu.js +46 -0
- package/dist/pieces/issue-flow.d.ts +12 -6
- package/dist/pieces/issue-flow.js +149 -27
- package/dist/pieces/skills-manager.d.ts +6 -3
- package/dist/pieces/skills-manager.js +31 -10
- 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
|
@@ -6,6 +6,9 @@ export interface SyncOptions {
|
|
|
6
6
|
dryRun: boolean;
|
|
7
7
|
verbose: boolean;
|
|
8
8
|
skip: string[];
|
|
9
|
+
commit: boolean;
|
|
10
|
+
/** Ask which pieces the repo uses, save the answer, then go on as usual. */
|
|
11
|
+
pieces: boolean;
|
|
9
12
|
}
|
|
10
13
|
/**
|
|
11
14
|
* The names `--skip` takes.
|
|
@@ -13,12 +16,29 @@ export interface SyncOptions {
|
|
|
13
16
|
* @example pieceNames() // ["skills-manager", "issue-flow", "claude-cloud", "claude-repo-guard", "skills", "agents-md"]
|
|
14
17
|
*/
|
|
15
18
|
export declare function pieceNames(): string[];
|
|
19
|
+
/**
|
|
20
|
+
* The closing line when no step ran, which must not contradict a file written in this run.
|
|
21
|
+
* @param why - "leave" if the person left without changing anything, "none" if they picked no steps.
|
|
22
|
+
* @param wrote - Whether the decisions file was written in this run.
|
|
23
|
+
* @returns The line, with its newline.
|
|
24
|
+
* @example noStepsMessage("none", true) // "No elegiste pasos; solo guardé qué piezas usa el repo.\n"
|
|
25
|
+
*/
|
|
26
|
+
export declare function noStepsMessage(why: "leave" | "none", wrote: boolean): string;
|
|
27
|
+
/**
|
|
28
|
+
* What the commit of `--commit` lists: the steps that worked, and the decisions file if this
|
|
29
|
+
* run wrote it.
|
|
30
|
+
* @param done - The labels of the steps that worked.
|
|
31
|
+
* @param wrote - Whether the decisions file was written in this run.
|
|
32
|
+
* @returns The labels.
|
|
33
|
+
* @example commitLabels([], true) // ["Guardar qué piezas usa el repo en .agents/kitchen.json"]
|
|
34
|
+
*/
|
|
35
|
+
export declare function commitLabels(done: string[], wrote: boolean): string[];
|
|
16
36
|
/**
|
|
17
37
|
* Runs the command; see the file's header.
|
|
18
38
|
* @param options - The options.
|
|
19
39
|
* @param out - Where to write and ask.
|
|
20
40
|
* @returns 0, or 1 if a step failed or the person cancelled.
|
|
21
41
|
* @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)
|
|
42
|
+
* @example await sync({ cwd, yes: false, dryRun: true, verbose: false, skip: [], commit: false, pieces: false }, out)
|
|
23
43
|
*/
|
|
24
44
|
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,22 @@
|
|
|
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
26
|
import { spinner } from "@clack/prompts";
|
|
17
|
-
import {
|
|
27
|
+
import { DECIDABLE, decide, DECISIONS, off, readDecisions, undecided, writeDecisions } from "../decisions.js";
|
|
28
|
+
import { baseBranch, branchName, changedPaths, checkReady, finish } from "../git-finish.js";
|
|
29
|
+
import { askFinish, askPath, askPieces, askReplace, askSkills, askSteps } from "../menu.js";
|
|
18
30
|
import { KitError, UsageError, warn } from "../output.js";
|
|
19
31
|
import { cloudStatus, NAME as CLOUD } from "../pieces/cloud.js";
|
|
20
32
|
import { guardStatus, NAME as GUARD } from "../pieces/guard.js";
|
|
@@ -23,7 +35,7 @@ import { keepOwn, NAME as SKILLS, ownInPick, skillsStatus } from "../pieces/skil
|
|
|
23
35
|
import { agentsMdStatus, NAME as AGENTS_MD } from "../pieces/agents-md.js";
|
|
24
36
|
import { NAME as SKILLS_MANAGER, skillsManagerStatus } from "../pieces/skills-manager.js";
|
|
25
37
|
import { mainCheckout, packageManager } from "../repo.js";
|
|
26
|
-
import { download, failedOutput, setVerbose } from "../tools.js";
|
|
38
|
+
import { download, failedOutput, runInstall, setVerbose } from "../tools.js";
|
|
27
39
|
/** Every piece, in the order its steps run. */
|
|
28
40
|
const PIECES = [SKILLS_MANAGER, FLOW, CLOUD, GUARD, SKILLS, AGENTS_MD];
|
|
29
41
|
/**
|
|
@@ -35,37 +47,119 @@ export function pieceNames() {
|
|
|
35
47
|
return [...PIECES];
|
|
36
48
|
}
|
|
37
49
|
/**
|
|
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.
|
|
50
|
+
* Each piece's status, with every lookup at once. skills-manager, the guard's step and the
|
|
51
|
+
* rule in AGENTS.md depend on whether the flow is about to be installed (it writes
|
|
52
|
+
* package.json), so they wait for the flow's status.
|
|
41
53
|
* @param root - The repo.
|
|
42
54
|
* @param skip - The pieces left out.
|
|
43
55
|
* @returns The statuses, in the pieces' order.
|
|
44
56
|
*/
|
|
45
57
|
async function statusesOf(root, skip) {
|
|
46
|
-
const skillsManager = skip.includes(SKILLS_MANAGER) ? null : skillsManagerStatus(root, !skip.includes(SKILLS));
|
|
47
58
|
const flow = skip.includes(FLOW) ? null : issueFlowStatus(root);
|
|
59
|
+
const flowComing = (flow ?? Promise.resolve(null)).then((status) => status !== null && !status.installed && status.steps.length > 0);
|
|
60
|
+
const skillsManager = skip.includes(SKILLS_MANAGER) ? null : flowComing.then((coming) => skillsManagerStatus(root, !skip.includes(SKILLS), coming));
|
|
48
61
|
const cloud = skip.includes(CLOUD) ? null : cloudStatus(root);
|
|
49
62
|
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
63
|
const guard = skip.includes(GUARD) ? null : flowComing.then((coming) => guardStatus(root, coming));
|
|
52
64
|
const agentsMd = skip.includes(AGENTS_MD) ? null : flowComing.then((coming) => agentsMdStatus(root, coming));
|
|
53
65
|
const all = await Promise.all([skillsManager, flow, cloud, guard, skills, agentsMd]);
|
|
54
66
|
return all.flatMap((status) => status ?? []);
|
|
55
67
|
}
|
|
56
68
|
/**
|
|
57
|
-
* Prints what the repo has of each piece
|
|
69
|
+
* Prints what the repo has of each piece, in the pieces' order, and the pieces the repo
|
|
70
|
+
* decided not to have, with how to change that.
|
|
58
71
|
* @param statuses - The pieces' statuses.
|
|
72
|
+
* @param decidedOff - The pieces left out by the repo's decision.
|
|
59
73
|
* @param out - Where to write.
|
|
60
74
|
*/
|
|
61
|
-
function printStatus(statuses, out) {
|
|
62
|
-
const width = Math.max(...statuses.map((status) => status.name.length));
|
|
75
|
+
function printStatus(statuses, decidedOff, out) {
|
|
76
|
+
const width = Math.max(...statuses.map((status) => status.name.length), ...decidedOff.map((name) => name.length));
|
|
63
77
|
const stateWidth = Math.max(...statuses.map((status) => status.state.length));
|
|
64
78
|
out.stdout("\nEste repo tiene:\n");
|
|
65
|
-
for (const
|
|
66
|
-
|
|
79
|
+
for (const name of PIECES) {
|
|
80
|
+
for (const status of statuses.filter((each) => each.name === name))
|
|
81
|
+
out.stdout(` ${status.name.padEnd(width)} ${status.state.padEnd(stateWidth)} · ${status.summary}\n`);
|
|
82
|
+
if (decidedOff.includes(name))
|
|
83
|
+
out.stdout(` ${name.padEnd(width)} fuera por decisión del repo\n`);
|
|
84
|
+
}
|
|
85
|
+
if (decidedOff.length > 0)
|
|
86
|
+
out.stdout(`\nPara cambiar qué piezas usa el repo: agent-kitchen apply --pieces (o edita ${DECISIONS}).\n`);
|
|
67
87
|
out.stdout("\n");
|
|
68
88
|
}
|
|
89
|
+
/**
|
|
90
|
+
* Settles the pieces without a decision and writes the file, unless nothing changed or
|
|
91
|
+
* nothing was decided.
|
|
92
|
+
* @param root - The repo.
|
|
93
|
+
* @param saved - What was read, or null if there is no file.
|
|
94
|
+
* @param statuses - What each piece found.
|
|
95
|
+
* @param chosen - The steps the person picked.
|
|
96
|
+
* @param commits - Whether `--commit` asks about the changes at the end.
|
|
97
|
+
* @param out - Where to write.
|
|
98
|
+
* @returns True if the file was written.
|
|
99
|
+
*/
|
|
100
|
+
function saveDecisions(root, saved, statuses, chosen, commits, out) {
|
|
101
|
+
const previous = saved?.pieces ?? {};
|
|
102
|
+
const pieces = decide(statuses, chosen, previous);
|
|
103
|
+
const same = Object.keys(pieces).length === Object.keys(previous).length && Object.entries(pieces).every(([name, value]) => previous[name] === value);
|
|
104
|
+
if (Object.keys(pieces).length === 0 || (saved !== null && same))
|
|
105
|
+
return false;
|
|
106
|
+
writeDecisions(root, pieces, saved?.raw ?? {});
|
|
107
|
+
out.stdout(savedMessage(commits));
|
|
108
|
+
return true;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* The closing line when no step ran, which must not contradict a file written in this run.
|
|
112
|
+
* @param why - "leave" if the person left without changing anything, "none" if they picked no steps.
|
|
113
|
+
* @param wrote - Whether the decisions file was written in this run.
|
|
114
|
+
* @returns The line, with its newline.
|
|
115
|
+
* @example noStepsMessage("none", true) // "No elegiste pasos; solo guardé qué piezas usa el repo.\n"
|
|
116
|
+
*/
|
|
117
|
+
export function noStepsMessage(why, wrote) {
|
|
118
|
+
if (why === "leave")
|
|
119
|
+
return wrote ? "No corrí ningún paso; solo guardé qué piezas usa el repo.\n" : "No se cambió nada.\n";
|
|
120
|
+
return wrote ? "No elegiste pasos; solo guardé qué piezas usa el repo.\n" : "No elegiste nada; no se cambió nada.\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 `Guardé qué piezas usa el repo en ${DECISIONS}${commits ? "" : "; commitéalo"}.\n`;
|
|
129
|
+
}
|
|
130
|
+
/** The line the commit of `--commit` adds for the decisions file. */
|
|
131
|
+
const DECISIONS_STEP = `Guardar qué piezas usa el repo en ${DECISIONS}`;
|
|
132
|
+
/**
|
|
133
|
+
* What the commit of `--commit` lists: the steps that worked, and the decisions file if this
|
|
134
|
+
* run wrote it.
|
|
135
|
+
* @param done - The labels of the steps that worked.
|
|
136
|
+
* @param wrote - Whether the decisions file was written in this run.
|
|
137
|
+
* @returns The labels.
|
|
138
|
+
* @example commitLabels([], true) // ["Guardar qué piezas usa el repo en .agents/kitchen.json"]
|
|
139
|
+
*/
|
|
140
|
+
export function commitLabels(done, wrote) {
|
|
141
|
+
return wrote ? [...done, DECISIONS_STEP] : done;
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Asks which pieces the repo uses and writes the answer for every decidable piece.
|
|
145
|
+
* @param root - The repo.
|
|
146
|
+
* @param saved - What was read, or null if there is no file.
|
|
147
|
+
* @param commits - Whether `--commit` asks about the changes at the end.
|
|
148
|
+
* @param out - Where to write.
|
|
149
|
+
* @returns False if the person cancelled.
|
|
150
|
+
*/
|
|
151
|
+
async function choosePieces(root, saved, commits, out) {
|
|
152
|
+
out.stdout("Revisando el repo y las últimas versiones…\n");
|
|
153
|
+
// No skip here: an installed, undecided piece passed in --skip still starts ticked.
|
|
154
|
+
const statuses = await statusesOf(root, []);
|
|
155
|
+
const ticked = DECIDABLE.filter((name) => saved?.pieces[name] === true || (saved?.pieces[name] === undefined && statuses.some((status) => status.name === name && status.installed)));
|
|
156
|
+
const picked = await askPieces(DECIDABLE, ticked);
|
|
157
|
+
if (picked === null)
|
|
158
|
+
return false;
|
|
159
|
+
writeDecisions(root, Object.fromEntries(DECIDABLE.map((name) => [name, picked.includes(name)])), saved?.raw ?? {});
|
|
160
|
+
out.stdout(savedMessage(commits));
|
|
161
|
+
return true;
|
|
162
|
+
}
|
|
69
163
|
/**
|
|
70
164
|
* Leaves the repo's own skills out of a step's pick, saying so.
|
|
71
165
|
* @param root - The repo.
|
|
@@ -206,7 +300,7 @@ async function runStep(root, step, progress, out) {
|
|
|
206
300
|
* @param out - Where to write and ask.
|
|
207
301
|
* @returns 0, or 1 if a step failed or the person cancelled.
|
|
208
302
|
* @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)
|
|
303
|
+
* @example await sync({ cwd, yes: false, dryRun: true, verbose: false, skip: [], commit: false, pieces: false }, out)
|
|
210
304
|
*/
|
|
211
305
|
export async function sync(options, out) {
|
|
212
306
|
const unknown = options.skip.find((name) => !pieceNames().includes(name));
|
|
@@ -214,39 +308,133 @@ export async function sync(options, out) {
|
|
|
214
308
|
throw new UsageError(`no existe la pieza ${unknown}; son ${pieceNames().join(", ")}.`);
|
|
215
309
|
if (options.yes && options.dryRun)
|
|
216
310
|
throw new UsageError("usa --yes o --dry-run, no los dos.");
|
|
311
|
+
if (options.pieces && (options.yes || options.dryRun))
|
|
312
|
+
throw new UsageError("usa --pieces solo, sin --yes ni --dry-run.");
|
|
313
|
+
if (options.pieces && !out.interactive)
|
|
314
|
+
throw new UsageError("--pieces pregunta qué piezas usa el repo; córrelo en una terminal.");
|
|
217
315
|
const root = mainCheckout(options.cwd, "agent-kitchen apply");
|
|
316
|
+
const base = options.commit ? baseBranch(root) : null;
|
|
317
|
+
if (base !== null)
|
|
318
|
+
checkReady(root, base);
|
|
218
319
|
setVerbose(options.verbose);
|
|
320
|
+
let saved = readDecisions(root);
|
|
321
|
+
let wrote = false;
|
|
322
|
+
for (const warning of saved?.warnings ?? [])
|
|
323
|
+
warn(out, warning);
|
|
324
|
+
if (options.pieces) {
|
|
325
|
+
if (!(await choosePieces(root, saved, base !== null, out))) {
|
|
326
|
+
out.stdout("No se cambió nada.\n");
|
|
327
|
+
return 0;
|
|
328
|
+
}
|
|
329
|
+
saved = readDecisions(root);
|
|
330
|
+
wrote = true;
|
|
331
|
+
}
|
|
332
|
+
const skip = [...options.skip, ...off(saved)];
|
|
333
|
+
const asks = out.interactive && !options.yes && !options.dryRun;
|
|
334
|
+
const saves = asks && options.skip.length === 0;
|
|
219
335
|
if (out.interactive)
|
|
220
336
|
out.stdout("Revisando el repo y las últimas versiones…\n");
|
|
221
|
-
|
|
222
|
-
|
|
337
|
+
let statuses = await statusesOf(root, skip);
|
|
338
|
+
if (saved !== null && !asks && !options.dryRun) {
|
|
339
|
+
const left = undecided(saved).filter((name) => statuses.some((status) => status.name === name && !status.installed));
|
|
340
|
+
if (left.length > 0) {
|
|
341
|
+
skip.push(...left);
|
|
342
|
+
statuses = await statusesOf(root, skip);
|
|
343
|
+
out.stdout(`Pieza nueva sin decisión: ${left.join(", ")}. Corre agent-kitchen apply en una terminal para elegir.\n`);
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
if (saved !== null) {
|
|
347
|
+
const fresh = undecided(saved);
|
|
348
|
+
for (const status of statuses.filter((each) => fresh.includes(each.name))) {
|
|
349
|
+
status.state += " (nueva)";
|
|
350
|
+
for (const step of status.steps)
|
|
351
|
+
step.label += " (nueva)";
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
printStatus(statuses, off(saved).filter((name) => !options.skip.includes(name)), out);
|
|
223
355
|
const steps = statuses.flatMap((status) => status.steps);
|
|
356
|
+
// With --commit, a run that only wrote the decisions file still ends with the question.
|
|
357
|
+
const endWithoutSteps = async () => (base !== null && wrote ? commitChanges(root, base, commitLabels([], true), [], options, out) : 0);
|
|
224
358
|
if (steps.length === 0) {
|
|
359
|
+
if (saves)
|
|
360
|
+
wrote = saveDecisions(root, saved, statuses, [], base !== null, out) || wrote;
|
|
225
361
|
out.stdout("Todo al día: no hay nada que instalar ni actualizar.\n");
|
|
226
|
-
return
|
|
362
|
+
return endWithoutSteps();
|
|
227
363
|
}
|
|
228
364
|
const chosen = await chooseSteps(root, steps, options, out);
|
|
365
|
+
if (saves && Array.isArray(chosen))
|
|
366
|
+
wrote = saveDecisions(root, saved, statuses, chosen, base !== null, out) || wrote;
|
|
229
367
|
if (chosen === "leave") {
|
|
230
|
-
out.stdout("
|
|
231
|
-
return
|
|
368
|
+
out.stdout(noStepsMessage("leave", wrote));
|
|
369
|
+
return endWithoutSteps();
|
|
232
370
|
}
|
|
233
371
|
if (chosen === null) {
|
|
372
|
+
if (wrote) {
|
|
373
|
+
out.stdout(noStepsMessage("leave", wrote));
|
|
374
|
+
return endWithoutSteps();
|
|
375
|
+
}
|
|
234
376
|
if (!options.dryRun && out.interactive)
|
|
235
377
|
warn(out, "no se cambió nada.");
|
|
236
378
|
return options.dryRun || !out.interactive ? 0 : 1;
|
|
237
379
|
}
|
|
238
380
|
if (chosen.length === 0) {
|
|
239
381
|
if (!options.yes)
|
|
240
|
-
out.stdout("
|
|
241
|
-
return
|
|
382
|
+
out.stdout(noStepsMessage("none", wrote));
|
|
383
|
+
return endWithoutSteps();
|
|
242
384
|
}
|
|
243
|
-
const progress = progressFor(out, options.verbose);
|
|
385
|
+
const progress = progressFor(out, options.verbose || base !== null);
|
|
244
386
|
const failed = [];
|
|
245
387
|
for (const step of chosen) {
|
|
246
388
|
if (!(await runStep(root, step, progress, out)))
|
|
247
389
|
failed.push(step.label);
|
|
248
390
|
}
|
|
391
|
+
if (base !== null)
|
|
392
|
+
return commitChanges(root, base, commitLabels(chosen.filter((step) => !failed.includes(step.label)).map((step) => step.label), wrote), failed, options, out);
|
|
249
393
|
const next = `Falta: corre '${packageManager(root)} install', revisa el diff y haz commit.`;
|
|
250
394
|
out.stdout(`\n${failed.length === 0 ? "Listo." : `Terminó, pero fallaron: ${failed.join("; ")}.`} ${next}\n`);
|
|
251
395
|
return failed.length === 0 ? 0 : 1;
|
|
252
396
|
}
|
|
397
|
+
/**
|
|
398
|
+
* The end of `--commit`: the install if package.json changed, the changes, the question (or
|
|
399
|
+
* the branch with a PR under --yes) and what git did.
|
|
400
|
+
* @param root - The repo.
|
|
401
|
+
* @param base - The base branch.
|
|
402
|
+
* @param done - What the commit lists: the labels of the steps that worked, and the decisions file if written.
|
|
403
|
+
* @param failed - The labels of the steps that failed.
|
|
404
|
+
* @param options - The options.
|
|
405
|
+
* @param out - Where to write and ask.
|
|
406
|
+
* @returns 0, or 1 if a step, the install or git failed.
|
|
407
|
+
*/
|
|
408
|
+
async function commitChanges(root, base, done, failed, options, out) {
|
|
409
|
+
const summary = failed.length === 0 ? "Listo." : `Terminó, pero fallaron: ${failed.join("; ")}.`;
|
|
410
|
+
const changed = changedPaths(root);
|
|
411
|
+
if (changed.length === 0) {
|
|
412
|
+
out.stdout(`\n${summary} No cambió ningún archivo: no hay nada que commitear.\n`);
|
|
413
|
+
return failed.length === 0 ? 0 : 1;
|
|
414
|
+
}
|
|
415
|
+
const manager = packageManager(root);
|
|
416
|
+
const packageChanged = changed.includes("package.json");
|
|
417
|
+
if (packageChanged) {
|
|
418
|
+
const label = `Instalar las dependencias con ${manager}`;
|
|
419
|
+
out.stdout(`→ ${label}\n`);
|
|
420
|
+
const installed = await runInstall(root, manager);
|
|
421
|
+
out.stdout(`${installed.ok ? "✓" : "✗"} ${label}\n`);
|
|
422
|
+
if (!installed.ok) {
|
|
423
|
+
out.stderr(failedOutput(installed.output));
|
|
424
|
+
out.stdout(`\n${summary} No commiteé: el install falló y los cambios quedan sin commit.\n`);
|
|
425
|
+
return 1;
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
const paths = changedPaths(root);
|
|
429
|
+
out.stdout(`\n${summary} Cambios:\n${paths.map((path) => ` ${path}`).join("\n")}\n`);
|
|
430
|
+
if (failed.length > 0 && options.yes) {
|
|
431
|
+
out.stdout("No commiteé porque algún paso falló; los cambios quedan sin commit.\n");
|
|
432
|
+
return 1;
|
|
433
|
+
}
|
|
434
|
+
const branch = branchName(root, new Date());
|
|
435
|
+
const choice = options.yes ? "pr" : await askFinish(base, branch, failed.length > 0);
|
|
436
|
+
const result = await finish(root, choice, { base, branch, steps: done, paths, manager, packageChanged });
|
|
437
|
+
for (const line of result.lines)
|
|
438
|
+
out.stdout(`${line}\n`);
|
|
439
|
+
return result.ok && failed.length === 0 ? 0 : 1;
|
|
440
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import type { PieceStatus, Step } from "./piece.js";
|
|
2
|
+
/** The file with the decisions, relative to the repo. */
|
|
3
|
+
export declare const DECISIONS = ".agents/kitchen.json";
|
|
4
|
+
/** The pieces a repo can decide on, in the order sync runs them. */
|
|
5
|
+
export declare const DECIDABLE: string[];
|
|
6
|
+
/** What was read from the decisions file. */
|
|
7
|
+
export interface Saved {
|
|
8
|
+
/** The valid decisions of the decidable pieces. */
|
|
9
|
+
pieces: Record<string, boolean>;
|
|
10
|
+
/** The whole file, to write back without losing what is not ours. */
|
|
11
|
+
raw: Record<string, unknown>;
|
|
12
|
+
/** What was wrong in the file, in Spanish, for the user. */
|
|
13
|
+
warnings: string[];
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Reads the decisions of a repo.
|
|
17
|
+
* @param root - The repo.
|
|
18
|
+
* @returns The decisions, or null if the file does not exist.
|
|
19
|
+
* @throws KitError if the file is not valid JSON.
|
|
20
|
+
* @example readDecisions(root)?.pieces // { "claude-repo-guard": false }
|
|
21
|
+
*/
|
|
22
|
+
export declare function readDecisions(root: string): Saved | null;
|
|
23
|
+
/**
|
|
24
|
+
* The decidable pieces with no decision yet.
|
|
25
|
+
* @param saved - What was read, or null if there is no file.
|
|
26
|
+
* @returns Their names; all of them without a file.
|
|
27
|
+
* @example undecided(null) // ["issue-flow", "claude-cloud", ...]
|
|
28
|
+
*/
|
|
29
|
+
export declare function undecided(saved: Saved | null): string[];
|
|
30
|
+
/**
|
|
31
|
+
* The pieces the repo decided not to have.
|
|
32
|
+
* @param saved - What was read, or null if there is no file.
|
|
33
|
+
* @returns Their names.
|
|
34
|
+
* @example off({ pieces: { skills: false }, raw: {}, warnings: [] }) // ["skills"]
|
|
35
|
+
*/
|
|
36
|
+
export declare function off(saved: Saved | null): string[];
|
|
37
|
+
/**
|
|
38
|
+
* Settles the pieces that have no decision from what the repo has and what was chosen.
|
|
39
|
+
* @param statuses - What each piece found.
|
|
40
|
+
* @param chosen - The steps the user picked.
|
|
41
|
+
* @param previous - The decisions already made; they never change here.
|
|
42
|
+
* @returns `previous` plus a decision for each decidable piece without one: installed or
|
|
43
|
+
* with a chosen step is true, offered steps all unchosen is false, no steps leaves it open.
|
|
44
|
+
* @example decide([{ name: "skills", installed: true, summary: "", state: "", steps: [] }], [], {}) // { skills: true }
|
|
45
|
+
*/
|
|
46
|
+
export declare function decide(statuses: PieceStatus[], chosen: Step[], previous: Record<string, boolean>): Record<string, boolean>;
|
|
47
|
+
/**
|
|
48
|
+
* Writes the decisions, keeping every other key of the file.
|
|
49
|
+
* @param root - The repo.
|
|
50
|
+
* @param pieces - The decisions to write.
|
|
51
|
+
* @param raw - The whole file as it was read (empty if there was none).
|
|
52
|
+
* @example writeDecisions(root, { "issue-flow": true }, {})
|
|
53
|
+
*/
|
|
54
|
+
export declare function writeDecisions(root: string, pieces: Record<string, boolean>, raw: Record<string, unknown>): void;
|