@ramon-sg/issue-flow 0.1.0 → 0.2.1

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 CHANGED
@@ -2,11 +2,11 @@
2
2
 
3
3
  Flujo de trabajo de issue a PR para cualquier repo, en local y en las sesiones de Claude en la nube: nombra la rama de cada issue, crea su worktree con puertos y Docker propios, abre el PR en draft y limpia cuando se mergea. Trae las skills que usan ese flujo.
4
4
 
5
- > **Instalar en un repo:** `npx @ramon-sg/issue-flow init` (ver [Instalar en un repo](#instalar-en-un-repo-issue-flow-init)). El paquete es `@ramon-sg/issue-flow` y el comando, `issue-flow`; sin el scope, `issue-flow` en npm es otro paquete.
5
+ > **Instalar en un repo:** `npx agent-kit`, que instala este flujo con sus skills, las skills generales, `claude-cloud` y `claude-repo-guard`, y los actualiza ([agent-kit](https://github.com/ramon-sg/agent-kit)). El paquete es `@ramon-sg/issue-flow` y el comando, `issue-flow`; sin el scope, `issue-flow` en npm es otro paquete.
6
6
 
7
7
  ## Qué va a tener
8
8
 
9
- - **`issue-flow init`:** escribe `.agents/flow.json` con las recetas que sugieren los archivos del repo, las dependencias y los hooks, y después instala `claude-cloud`, `claude-repo-guard` y las skills. `doctor` revisa que todo esté a esta versión y `upgrade` lo mueve a la siguiente.
9
+ - **`issue-flow init|doctor|upgrade`:** escribe, revisa y mueve a su versión lo que el flujo deja en el repo: `.agents/flow.json` con sus recetas, las dependencias y los hooks. Los corre `agent-kit`.
10
10
  - **`issue-flow issue branch <n>`:** propone la rama de una issue (con Claude por defecto, o desde el título). Con `--missing`, agrega el slug de rama a todas las issues abiertas que no lo tienen.
11
11
  - **`issue-flow issue start <n>`:** crea la rama y el worktree, hace el commit inicial, abre el PR en draft, asigna la issue y abre Claude. Con `--cloud`, lo hace en una sesión en la nube.
12
12
  - **`issue-flow worktree setup|teardown|ports|cleanup|prune`:** prepara y limpia cada worktree con los pasos del repo, le da un bloque de puertos y borra los worktrees con el PR cerrado.
@@ -178,13 +178,15 @@ Es el hook `SessionStart` de Claude Code. Lo que imprime le llega a Claude como
178
178
  4. Borra el Docker de los worktrees que ya no existen: Claude Code no llama al hook `WorktreeRemove` de los worktrees que no hizo un hook `WorktreeCreate`.
179
179
  5. Si hay `.envrc` y `direnv`, agrega su entorno a `CLAUDE_ENV_FILE`, para que los comandos de la sesión lo tengan.
180
180
 
181
- ## Instalar en un repo: `issue-flow init`
181
+ ## Lo que el flujo deja en el repo: `issue-flow init`
182
+
183
+ `npx agent-kit` corre estos tres comandos en la versión que corresponde. Correrlos a mano sirve para probar el flujo en un repo sin lo demás.
182
184
 
183
185
  ```sh
184
- npx @ramon-sg/issue-flow init # pregunta cada paso
186
+ npx @ramon-sg/issue-flow init # pregunta cada receta y pide el OK
185
187
  npx @ramon-sg/issue-flow init --yes # sin preguntas, con las recetas que sugiere el repo
186
- npx @ramon-sg/issue-flow doctor # revisa que el repo esté como lo deja init
187
- npx @ramon-sg/issue-flow upgrade # pasa todo a la última versión (o --to 0.2.0)
188
+ npx @ramon-sg/issue-flow doctor # revisa que el repo esté como lo deja init (--json para agent-kit)
189
+ npx @ramon-sg/issue-flow@<versión> upgrade # mueve el repo a esa versión (--dry-run)
188
190
  ```
189
191
 
190
192
  `init` muestra lo que va a escribir y pide el OK; correrlo otra vez solo completa lo que falta.
@@ -192,15 +194,10 @@ npx @ramon-sg/issue-flow upgrade # pasa todo a la última versión (o --t
192
194
  1. **`.agents/flow.json`:** si no existe, lo crea con el `$schema` de esta versión y, si el repo tiene `paseo.json`, con su plantilla de rama y sus tipos. Suma las recetas que elijas.
193
195
  2. **`package.json`:** `@ramon-sg/issue-flow`, `@ramon-sg/github-rest` y `@ramon-sg/skills-manager` como dependencias de desarrollo con versión exacta, y los scripts `issue:start`, `worktree:prune` y `pr:worktree`. Lo que el repo ya tiene con otro valor se queda, con un aviso.
194
196
  3. **`.claude/settings.json`:** los hooks `SessionStart`, `WorktreeCreate` y `WorktreeRemove`, fijados a esta versión y con 600 segundos. El de `SessionStart` corre `claude-cloud session` en la nube, así que un hook propio de `claude-cloud` se quita para que no corra dos veces.
195
- 4. **`claude-cloud init`**, después de preguntar (`--no-cloud` lo salta).
196
- 5. **`claude-repo-guard`**, después de preguntar (`--no-guard`). Antes escribe en sus opciones que abrir el PR en draft, asignar la issue y agregar el "Slug de rama" son parte del flujo; la `baseBranch` como `integrationBranch`, si el repo tiene una; y en macOS `weakerNetworkIsolation`, que `gh` necesita dentro del sandbox.
197
- 6. **Las skills**, con el menú de `skills-manager` y los dos catálogos: el de este repo en la tag de esta versión y el de `agent-skills` (`--no-skills`). Sin terminal o con `--yes`, elige las del flujo y `code-audit`.
198
-
199
- Si una de las herramientas falla, `init` sigue con las demás, dice cómo reintentar y sale con 1.
200
197
 
201
- **`doctor`** no cambia nada. Revisa la config, las dependencias, que los hooks y las skills del flujo estén en esta versión, y `claude-repo-guard` (su propio `doctor` y, en macOS, `weakerNetworkIsolation`). Cada problema dice qué lo arregla.
198
+ **`doctor`** no cambia nada. Revisa la config, las dependencias y que los hooks estén en esta versión. Cada problema dice qué lo arregla.
202
199
 
203
- **`upgrade`** mueve juntas la CLI y las skills del flujo, porque las skills llaman a la CLI: las dependencias a las que fija esa versión (las lee de npm), los hooks, el `$schema` y el catálogo del flujo en `.agents/skills.json`, y corre `skills-manager update` solo con las skills del flujo. Las de `agent-skills` se actualizan aparte. `--dry-run` solo dice qué cambiaría.
200
+ **`upgrade`** mueve el repo a la versión que corre: las dependencias a las que fija, los hooks y el `$schema`. No consulta npm, así que se corre con la versión: `npx @ramon-sg/issue-flow@<versión> upgrade`. Nunca baja de versión: si el repo ya tiene una más nueva (por ejemplo, una copia local vieja en `node_modules`), se detiene y dice qué correr. Las skills del flujo llaman a la CLI, así que tienen que ir a la misma tag: `agent-kit` las mueve después. `--dry-run` solo dice qué cambiaría.
204
201
 
205
202
  ### Recetas
206
203
 
@@ -213,7 +210,7 @@ Piezas de `flow.json` para lo que muchos repos comparten. `init` ofrece las que
213
210
  | `direnv` | `.envrc` | `direnv allow` en cada worktree, para que carguen su entorno y sus secretos (Infisical, por ejemplo) |
214
211
 
215
212
  Los pasos de preparación de las recetas son `trusted`: corren el código del worktree (el lockfile, el compose, el `.envrc`), así que en un worktree con código que no está en la rama base, como el de `pr worktree`, se saltan y el comando dice qué correr cuando lo revisaste.
216
- | `staging-main` | Nada: se pide | `baseBranch: "staging"`; `init` deja a `claude-repo-guard` mergear a `staging` |
213
+ | `staging-main` | Nada: se pide | `baseBranch: "staging"`; `agent-kit` deja a `claude-repo-guard` mergear a `staging` |
217
214
 
218
215
  ## Skills del flujo
219
216
 
@@ -227,11 +224,7 @@ Viven en `skills/` y `skills-catalog.json` las lista. Se instalan con [`skills-m
227
224
  | `pr-review-comments` | PRs | Verifica cada comentario y check contra el código, propone un plan, aplica lo aprobado y responde los hilos | `github-issues` |
228
225
  | `pr-worktree` | PRs | Revisa un PR en su worktree con `issue-flow pr worktree`, sin tocar el checkout principal | `close-pr` |
229
226
 
230
- ```sh
231
- npx @ramon-sg/skills-manager@1 add --catalog ramon-sg/issue-flow
232
- ```
233
-
234
- En un repo sin `.agents/skills.json`, `init` en vez de `add`. `close-pr` y `pr-worktree` usan `code-audit`, del catálogo [`agent-skills`](https://github.com/ramon-sg/agent-skills): instala los dos.
227
+ `agent-kit` las instala con el catálogo en la tag de la versión del flujo y las mueve con él. `close-pr` y `pr-worktree` usan `code-audit`, del catálogo [`agent-skills`](https://github.com/ramon-sg/agent-skills): `agent-kit` ofrece los dos.
235
228
 
236
229
  **Lo propio de cada repo va en `project.md`**, que cada skill (salvo `pr-worktree`) crea desde su plantilla al instalarse: los labels y el assignee, qué flujo sigue cada issue, qué corre sin preguntar al cerrar, qué bots revisan y qué cuenta como review aprobado. Ese archivo es del repo: `check` no lo marca y las actualizaciones lo conservan.
237
230
 
@@ -285,7 +278,7 @@ Usa [`github-rest`](https://github.com/ramon-sg/github-rest) para hablar con Git
285
278
 
286
279
  ## Publicar una versión
287
280
 
288
- Una versión es una release `vX.Y.Z` en GitHub, con el mismo número que `package.json`. Al publicarla, GitHub Actions corre los tests y publica en npm con procedencia (`.github/workflows/publish.yml`). La tag es también la versión de las skills del flujo: `init` instala el catálogo en la tag de su propia versión, así que la CLI y sus skills van siempre juntas.
281
+ Una versión es una release `vX.Y.Z` en GitHub, con el mismo número que `package.json`. Al publicarla, GitHub Actions corre los tests y publica en npm con procedencia (`.github/workflows/publish.yml`). La tag es también la versión de las skills del flujo: `agent-kit` instala el catálogo en la tag de la versión del flujo, así que la CLI y sus skills van siempre juntas.
289
282
 
290
283
  1. Un PR que sube `version` en `package.json`.
291
284
  2. Después del merge, una release `v<versión>` sobre ese commit de `main`.
@@ -296,7 +289,7 @@ El workflow se detiene si la tag no coincide con `package.json`.
296
289
 
297
290
  1. Desde `main`, con sesión en npm (`npm login`): `npm publish --provenance=false`. La procedencia solo se genera dentro de un CI.
298
291
  2. En npmjs.com, configurar la publicación desde GitHub (trusted publishing) para `publish.yml`.
299
- 3. Pushear la tag `v0.1.0` sin crear una release: la necesitan `init` y el `$schema`, y una release volvería a publicar la misma versión y fallaría.
292
+ 3. Pushear la tag `v0.1.0` sin crear una release: la necesitan las skills y el `$schema`, y una release volvería a publicar la misma versión y fallaría.
300
293
 
301
294
  Desde 0.2.0, cada versión es una release y la publica el workflow.
302
295
 
package/dist/cli-setup.js CHANGED
@@ -18,9 +18,9 @@ export async function runSetupCommand(command, flags, rest, run) {
18
18
  if (rest.length > 0)
19
19
  throw new UsageError(`${command} takes no arguments.`);
20
20
  if (command === "doctor")
21
- return doctor(run.cwd, run.out);
21
+ return doctor(run.cwd, flags.json ?? false, run.out);
22
22
  if (command === "upgrade")
23
- return upgrade({ cwd: run.cwd, to: flags.to ?? null, dryRun: flags["dry-run"] ?? false }, run.out);
23
+ return upgrade({ cwd: run.cwd, dryRun: flags["dry-run"] ?? false }, run.out);
24
24
  const recipes = flags.recipe === undefined ? null : flags.recipe.split(",").map((name) => name.trim()).filter((name) => name !== "");
25
- return init({ cwd: run.cwd, yes: flags.yes ?? false, recipes, skills: !(flags["no-skills"] ?? false), guard: !(flags["no-guard"] ?? false), cloud: !(flags["no-cloud"] ?? false) }, run.out);
25
+ return init({ cwd: run.cwd, yes: flags.yes ?? false, recipes }, run.out);
26
26
  }
package/dist/cli.d.ts CHANGED
@@ -84,18 +84,6 @@ declare const OPTIONS: {
84
84
  readonly recipe: {
85
85
  readonly type: "string";
86
86
  };
87
- readonly "no-cloud": {
88
- readonly type: "boolean";
89
- };
90
- readonly "no-guard": {
91
- readonly type: "boolean";
92
- };
93
- readonly "no-skills": {
94
- readonly type: "boolean";
95
- };
96
- readonly to: {
97
- readonly type: "string";
98
- };
99
87
  };
100
88
  /** The parsed flags. */
101
89
  export type Flags = ReturnType<typeof parseArgs<{
package/dist/cli.js CHANGED
@@ -41,10 +41,6 @@ const OPTIONS = {
41
41
  next: { type: "boolean" },
42
42
  "no-setup": { type: "boolean" },
43
43
  recipe: { type: "string" },
44
- "no-cloud": { type: "boolean" },
45
- "no-guard": { type: "boolean" },
46
- "no-skills": { type: "boolean" },
47
- to: { type: "string" },
48
44
  };
49
45
  /** Flags each command takes, besides `--repo`, `--help` and `--namer`. */
50
46
  const COMMAND_FLAGS = {
@@ -76,9 +72,9 @@ const COMMAND_FLAGS = {
76
72
  "adapter archive": [],
77
73
  "hook worktree-create": [],
78
74
  "hook worktree-remove": [],
79
- init: ["yes", "recipe", "no-cloud", "no-guard", "no-skills"],
80
- doctor: [],
81
- upgrade: ["to", "dry-run"],
75
+ init: ["yes", "recipe"],
76
+ doctor: ["json"],
77
+ upgrade: ["dry-run"],
82
78
  };
83
79
  /** An issue number: digits only. */
84
80
  const NUMBER = /^\d+$/;
@@ -2,9 +2,10 @@ import type { Output } from "../output.js";
2
2
  /**
3
3
  * Checks the repo; see the file's header.
4
4
  * @param cwd - Where it runs.
5
+ * @param json - Whether to print the checks as JSON.
5
6
  * @param out - Where to write.
6
7
  * @returns 0 if everything passes, 1 if not.
7
- * @throws SetupError outside a checkout.
8
- * @example doctor(process.cwd(), out)
8
+ * @throws SetupError outside a checkout; a broken file is a failed check.
9
+ * @example doctor(process.cwd(), false, out)
9
10
  */
10
- export declare function doctor(cwd: string, out: Output): number;
11
+ export declare function doctor(cwd: string, json: boolean, out: Output): number;
@@ -1,18 +1,17 @@
1
1
  /**
2
- * @file `issue-flow doctor`: whether the repo is set up as `init` leaves it, at this
3
- * version. It checks the config, the dev dependencies, the hooks, the tag of the flow's
4
- * skills, and `claude-repo-guard` (its own doctor and, on macOS, `weakerNetworkIsolation`,
5
- * which `gh` needs inside the sandbox). It changes nothing; each problem says what fixes it.
2
+ * @file `issue-flow doctor [--json]`: whether the repo's flow files are as `init` leaves
3
+ * them, at this version: the config, the dev dependencies and the hooks. It changes nothing;
4
+ * each problem says what fixes it. `--json` prints the checks for `agent-kit`,
5
+ * which adds those of the skills and the other tools.
6
6
  */
7
- import { ConfigError, isObject } from "../config-base.js";
7
+ import { ConfigError } from "../config-base.js";
8
8
  import { loadConfig } from "../config.js";
9
9
  import { locateRepo } from "../git.js";
10
- import { WEAKER_NETWORK } from "../guard-config.js";
11
10
  import { dependencyVersion } from "../package-edits.js";
12
11
  import { hookVersions } from "../settings-hooks.js";
13
- import { captureNpx, flowSkills, GUARD_CONFIG, GUARD_SPEC, ownVersions, PACKAGE, readJson, SETTINGS_FILE, SetupError, SKILLS_LOCK, } from "../setup-files.js";
14
- /** What fixes most problems. */
15
- const RUN_INIT = "run issue-flow init";
12
+ import { ownVersions, PACKAGE, readJson, SETTINGS_FILE, SetupError } from "../setup-files.js";
13
+ /** What fixes every problem: it runs init or upgrade at the right version. */
14
+ const RUN_AGENT_KIT = "run npx agent-kit";
16
15
  /**
17
16
  * Checks the config.
18
17
  * @param root - The repo.
@@ -41,8 +40,8 @@ function packageChecks(root, version) {
41
40
  const flow = dependencyVersion(pkg, PACKAGE);
42
41
  const rest = dependencyVersion(pkg, "@ramon-sg/github-rest");
43
42
  return [
44
- { name: PACKAGE, ok: flow === version, detail: flow === null ? `not in package.json: ${RUN_INIT}` : flow === version ? version : `package.json has ${flow}, this is ${version}: run issue-flow upgrade` },
45
- { name: "@ramon-sg/github-rest", ok: rest !== null, detail: rest ?? `not in package.json, and the skills call it: ${RUN_INIT}` },
43
+ { name: PACKAGE, ok: flow === version, detail: flow === null ? `not in package.json: ${RUN_AGENT_KIT}` : flow === version ? version : `package.json has ${flow}, this is ${version}: ${RUN_AGENT_KIT}` },
44
+ { name: "@ramon-sg/github-rest", ok: rest !== null, detail: rest ?? `not in package.json, and the skills call it: ${RUN_AGENT_KIT}` },
46
45
  ];
47
46
  }
48
47
  /**
@@ -55,70 +54,37 @@ function hookChecks(root, version) {
55
54
  return Object.entries(hookVersions(readJson(root, SETTINGS_FILE))).map(([event, found]) => ({
56
55
  name: `${event} hook`,
57
56
  ok: found === version,
58
- detail: found === null ? `not in ${SETTINGS_FILE}: ${RUN_INIT}` : found === version ? version : `at ${found}, this is ${version}: run issue-flow upgrade`,
57
+ detail: found === null ? `not in ${SETTINGS_FILE}: ${RUN_AGENT_KIT}` : found === version ? version : `at ${found}, this is ${version}: ${RUN_AGENT_KIT}`,
59
58
  }));
60
59
  }
61
- /**
62
- * Checks that the flow's skills are at this version's tag: they call this CLI, so they go
63
- * together.
64
- * @param root - The repo.
65
- * @param version - This package's version.
66
- * @returns The check.
67
- */
68
- function skillsCheck(root, version) {
69
- const skills = flowSkills(readJson(root, SKILLS_LOCK));
70
- const tag = `v${version}`;
71
- const off = skills.filter((skill) => skill.tag !== tag);
72
- if (skills.length === 0)
73
- return { name: "flow skills", ok: false, detail: `none in ${SKILLS_LOCK}: ${RUN_INIT}` };
74
- if (off.length === 0)
75
- return { name: "flow skills", ok: true, detail: `${skills.length} at ${tag}` };
76
- return { name: "flow skills", ok: false, detail: `${off.map((skill) => `${skill.name} at ${skill.tag ?? "no tag"}`).join(", ")}, this is ${tag}: run issue-flow upgrade` };
77
- }
78
- /**
79
- * Checks `claude-repo-guard`, if the repo has it: its own doctor and, on macOS, the option
80
- * `gh` needs.
81
- * @param root - The repo.
82
- * @returns The checks.
83
- */
84
- function guardChecks(root) {
85
- const options = readJson(root, GUARD_CONFIG);
86
- if (options === null)
87
- return [{ name: "claude-repo-guard", ok: true, detail: "not installed (Claude opens with your own permissions)" }];
88
- const checks = [];
89
- if (process.platform === "darwin" && options[WEAKER_NETWORK] !== true)
90
- checks.push({ name: WEAKER_NETWORK, ok: false, detail: `not true in ${GUARD_CONFIG}, so gh fails inside the macOS sandbox: ${RUN_INIT}` });
91
- const result = captureNpx(root, [GUARD_SPEC, "doctor", "--json"]);
92
- let found;
93
- try {
94
- found = JSON.parse(result?.out ?? "");
95
- }
96
- catch {
97
- found = null;
98
- }
99
- if (!Array.isArray(found))
100
- return [...checks, { name: "claude-repo-guard", ok: false, detail: `could not run its doctor; run 'npx ${GUARD_SPEC} doctor'` }];
101
- const failed = found.filter((check) => !isObject(check) || check.ok !== true).map((check) => (isObject(check) ? check : { name: "?", detail: String(check) }));
102
- if (failed.length === 0)
103
- return [...checks, { name: "claude-repo-guard", ok: true, detail: "its doctor passes" }];
104
- return [...checks, ...failed.map((check) => ({ name: `claude-repo-guard: ${String(check.name)}`, ok: false, detail: String(check.detail) }))];
105
- }
106
60
  /**
107
61
  * Checks the repo; see the file's header.
108
62
  * @param cwd - Where it runs.
63
+ * @param json - Whether to print the checks as JSON.
109
64
  * @param out - Where to write.
110
65
  * @returns 0 if everything passes, 1 if not.
111
- * @throws SetupError outside a checkout.
112
- * @example doctor(process.cwd(), out)
66
+ * @throws SetupError outside a checkout; a broken file is a failed check.
67
+ * @example doctor(process.cwd(), false, out)
113
68
  */
114
- export function doctor(cwd, out) {
69
+ export function doctor(cwd, json, out) {
115
70
  const located = locateRepo(cwd);
116
71
  if (located === null)
117
72
  throw new SetupError("run doctor inside a checkout of the repo.");
118
73
  const root = located.mainRoot;
119
74
  const { issueFlow } = ownVersions();
120
- const checks = [configCheck(root), ...packageChecks(root, issueFlow), ...hookChecks(root, issueFlow), skillsCheck(root, issueFlow), ...guardChecks(root)];
121
- for (const check of checks)
122
- out.stdout(`${check.ok ? "✓" : "✗"} ${check.name}: ${check.detail}\n`);
75
+ let checks;
76
+ try {
77
+ checks = [configCheck(root), ...packageChecks(root, issueFlow), ...hookChecks(root, issueFlow)];
78
+ }
79
+ catch (error) {
80
+ if (!(error instanceof SetupError))
81
+ throw error;
82
+ checks = [{ name: "files", ok: false, detail: error.message }];
83
+ }
84
+ if (json)
85
+ out.stdout(`${JSON.stringify(checks)}\n`);
86
+ else
87
+ for (const check of checks)
88
+ out.stdout(`${check.ok ? "✓" : "✗"} ${check.name}: ${check.detail}\n`);
123
89
  return checks.every((check) => check.ok) ? 0 : 1;
124
90
  }
@@ -4,9 +4,6 @@ export interface InitOptions {
4
4
  cwd: string;
5
5
  yes: boolean;
6
6
  recipes: string[] | null;
7
- skills: boolean;
8
- guard: boolean;
9
- cloud: boolean;
10
7
  }
11
8
  /**
12
9
  * The URL of the config's schema at a version.
@@ -19,9 +16,9 @@ export declare function schemaUrl(version: string): string;
19
16
  * Sets the repo up for the flow; see the file's header.
20
17
  * @param options - The options.
21
18
  * @param out - Where to write and ask.
22
- * @returns 0, or 1 if a file was not written or another tool failed.
19
+ * @returns 0, or 1 if the files were not written.
23
20
  * @throws SetupError outside the main checkout or with a broken file; ConfigError if the
24
21
  * config would be invalid; UsageError for an unknown recipe.
25
- * @example await init({ cwd, yes: true, recipes: null, skills: true, guard: true, cloud: true }, out)
22
+ * @example await init({ cwd, yes: true, recipes: null }, out)
26
23
  */
27
24
  export declare function init(options: InitOptions, out: Output): Promise<number>;
@@ -2,27 +2,19 @@
2
2
  * @file `issue-flow init`: sets a repo up for the flow, and can run again to fill in what
3
3
  * is missing. It writes `.agents/flow.json` (from `paseo.json` if the repo has one, plus the
4
4
  * recipes it picks), the flow's dev dependencies and scripts in `package.json`, and the
5
- * `SessionStart`, `WorktreeCreate` and `WorktreeRemove` hooks. Then, each after asking, it
6
- * runs `claude-cloud init`, installs `claude-repo-guard` with what the flow needs in its
7
- * options, and opens the skills menu of `skills-manager` with the two catalogs (`init` if
8
- * the repo has no manifest, `add` if it has one, because `init` stops then). A tool that
9
- * fails is reported and the rest goes on.
5
+ * `SessionStart`, `WorktreeCreate` and `WorktreeRemove` hooks. Only the flow's own files:
6
+ * the skills, `claude-cloud` and `claude-repo-guard` are installed by `agent-kit`,
7
+ * which runs this command.
10
8
  */
11
9
  import { confirm } from "../ask.js";
12
10
  import { CONFIG_PATH, isObject } from "../config-base.js";
13
11
  import { parseConfig } from "../config.js";
14
12
  import { checkoutRoot, locateRepo } from "../git.js";
15
- import { guardOptions } from "../guard-config.js";
16
13
  import { UsageError, warn } from "../output.js";
17
14
  import { withFlowPackages } from "../package-edits.js";
18
15
  import { applyRecipe, detectedRecipes, loadRecipes, projectPrefix } from "../recipes.js";
19
16
  import { registerHooks } from "../settings-hooks.js";
20
- import { CLOUD_SPEC, FLOW_CATALOG, GENERAL_CATALOG, GUARD_CONFIG, GUARD_SPEC, ownVersions, packageManager, readJson, runNpx, SETTINGS_FILE, SetupError, SKILLS_MANIFEST, writeJson, } from "../setup-files.js";
21
- /** The skills picked without a terminal: the flow's, and the audit `close-pr` uses. */
22
- const DEFAULT_SKILLS = [
23
- ...["github-issues", "start-issue", "close-pr", "pr-review-comments", "pr-worktree"].map((name) => `catalog:${FLOW_CATALOG}/${name}`),
24
- `catalog:${GENERAL_CATALOG}/code-audit`,
25
- ];
17
+ import { FLOW_REPO, ownVersions, packageManager, readJson, SETTINGS_FILE, SetupError, writeJson } from "../setup-files.js";
26
18
  /**
27
19
  * The URL of the config's schema at a version.
28
20
  * @param version - This package's version.
@@ -30,7 +22,7 @@ const DEFAULT_SKILLS = [
30
22
  * @example schemaUrl("0.1.0") // "https://raw.githubusercontent.com/ramon-sg/issue-flow/v0.1.0/schema.json"
31
23
  */
32
24
  export function schemaUrl(version) {
33
- return `https://raw.githubusercontent.com/${FLOW_CATALOG}/v${version}/schema.json`;
25
+ return `https://raw.githubusercontent.com/${FLOW_REPO}/v${version}/schema.json`;
34
26
  }
35
27
  /**
36
28
  * A new config: the schema, and the branch rules of `paseo.json` if the repo has one.
@@ -107,78 +99,14 @@ function plan(root, versions, recipes) {
107
99
  });
108
100
  return [file(CONFIG_PATH, raw, configChanges), file("package.json", pkg.pkg, pkg.changes), file(SETTINGS_FILE, hooks.settings, hooks.changes)];
109
101
  }
110
- /**
111
- * Runs one of the other tools after asking, and says if it failed.
112
- * @param step - What it is and how it runs.
113
- * @param options - The options.
114
- * @param out - Where to ask and write.
115
- * @returns False if it ran and failed.
116
- */
117
- async function external(step, options, out) {
118
- if (!options.yes && !(await confirm(out, step.question)))
119
- return true;
120
- if (step.run())
121
- return true;
122
- warn(out, step.failed);
123
- return false;
124
- }
125
- /**
126
- * Installs `claude-repo-guard`, after writing in its options what the flow needs.
127
- * @param root - The repo.
128
- * @param out - Where to write.
129
- * @returns Whether it installed.
130
- */
131
- function installGuard(root, out) {
132
- const flow = readJson(root, CONFIG_PATH);
133
- const baseBranch = typeof flow?.baseBranch === "string" ? flow.baseBranch : null;
134
- const { options, changes } = guardOptions(readJson(root, GUARD_CONFIG), baseBranch, process.platform);
135
- if (changes.length > 0)
136
- writeJson(root, GUARD_CONFIG, options);
137
- for (const change of changes)
138
- out.stdout(` ${change}\n`);
139
- return runNpx(root, [GUARD_SPEC, "install", "--yes"]);
140
- }
141
- /**
142
- * The ids of the default skills the manifest does not take yet: `skills-manager` leaves the
143
- * ones it has out of the candidates, and fails if `--select` names one.
144
- * @param manifest - The skills manifest, or null.
145
- * @returns The ids to pick.
146
- */
147
- function missingDefaults(manifest) {
148
- const sources = Array.isArray(manifest?.sources) ? manifest.sources.filter(isObject) : [];
149
- const taken = new Set(sources.flatMap((source) => (typeof source.catalog === "string" && Array.isArray(source.skills) ? source.skills.map((name) => `catalog:${String(source.catalog)}/${String(name)}`) : [])));
150
- return DEFAULT_SKILLS.filter((id) => !taken.has(id));
151
- }
152
- /**
153
- * Opens the skills menu with the flow's catalog, at this version, and the general one; without
154
- * a terminal or with `--yes`, it picks the flow's skills and `code-audit` that are not there yet.
155
- * @param root - The repo.
156
- * @param versions - This package's versions.
157
- * @param pick - Whether to pick without the menu.
158
- * @param out - Where to say there is nothing to add.
159
- * @returns Whether `skills-manager` worked, or true if there was nothing to pick.
160
- */
161
- function installSkills(root, versions, pick, out) {
162
- const manifest = readJson(root, SKILLS_MANIFEST);
163
- const command = manifest === null ? "init" : "add";
164
- const args = [`@ramon-sg/skills-manager@${versions.skillsManager}`, command, "--catalog", `${FLOW_CATALOG}#v${versions.issueFlow}`, "--catalog", GENERAL_CATALOG];
165
- if (!pick)
166
- return runNpx(root, args);
167
- const missing = missingDefaults(manifest);
168
- if (missing.length === 0) {
169
- out.stdout(`The flow's skills and code-audit are already in ${SKILLS_MANIFEST}.\n`);
170
- return true;
171
- }
172
- return runNpx(root, [...args, "--select", missing.join(",")]);
173
- }
174
102
  /**
175
103
  * Sets the repo up for the flow; see the file's header.
176
104
  * @param options - The options.
177
105
  * @param out - Where to write and ask.
178
- * @returns 0, or 1 if a file was not written or another tool failed.
106
+ * @returns 0, or 1 if the files were not written.
179
107
  * @throws SetupError outside the main checkout or with a broken file; ConfigError if the
180
108
  * config would be invalid; UsageError for an unknown recipe.
181
- * @example await init({ cwd, yes: true, recipes: null, skills: true, guard: true, cloud: true }, out)
109
+ * @example await init({ cwd, yes: true, recipes: null }, out)
182
110
  */
183
111
  export async function init(options, out) {
184
112
  const located = locateRepo(options.cwd);
@@ -202,12 +130,7 @@ export async function init(options, out) {
202
130
  }
203
131
  for (const file of files.filter((item) => item.changes.length > 0))
204
132
  writeJson(root, file.path, file.value);
205
- const results = [
206
- !options.cloud || await external({ question: "Set up Claude cloud sessions with claude-cloud init?", run: () => runNpx(root, [CLOUD_SPEC, "init"]), failed: `claude-cloud init failed; run 'npx ${CLOUD_SPEC} init' to retry.` }, options, out),
207
- !options.guard || await external({ question: "Install claude-repo-guard (auto mode, with the sandbox)?", run: () => installGuard(root, out), failed: `claude-repo-guard did not install; run 'npx ${GUARD_SPEC} install' to retry.` }, options, out),
208
- !options.skills || await external({ question: "Pick the skills now?", run: () => installSkills(root, versions, options.yes || !out.interactive, out), failed: "skills-manager failed; run issue-flow init again to retry." }, options, out),
209
- ];
210
133
  const manager = packageManager(root, readJson(root, "package.json"));
211
134
  out.stdout(`Next: run '${manager} install', and commit .agents/, .claude/ and package.json.\n`);
212
- return results.every(Boolean) ? 0 : 1;
135
+ return 0;
213
136
  }
@@ -2,15 +2,15 @@ import type { Output } from "../output.js";
2
2
  /** Options of `issue-flow upgrade`. */
3
3
  export interface UpgradeOptions {
4
4
  cwd: string;
5
- to: string | null;
6
5
  dryRun: boolean;
7
6
  }
8
7
  /**
9
- * Moves the repo to another version of the flow; see the file's header.
8
+ * Moves the repo's flow files to this version; see the file's header.
10
9
  * @param options - The options.
11
10
  * @param out - Where to write.
12
- * @returns 0, or 1 if `skills-manager` failed.
13
- * @throws SetupError outside the main checkout, if npm cannot tell the version or a file is broken.
14
- * @example upgrade({ cwd, to: null, dryRun: false }, out)
11
+ * @returns 0.
12
+ * @throws SetupError outside the main checkout, if a file is broken, or if the repo is
13
+ * already at a newer version (a stale local copy must not move it back).
14
+ * @example upgrade({ cwd, dryRun: false }, out)
15
15
  */
16
16
  export declare function upgrade(options: UpgradeOptions, out: Output): number;
@@ -1,72 +1,53 @@
1
1
  /**
2
- * @file `issue-flow upgrade [--to <version>] [--dry-run]`: moves the repo to another version
3
- * of the flow, the latest on npm by default. The CLI and the flow's skills go together,
4
- * because the skills call the CLI: it moves the dev dependencies to the ones that version
5
- * pins, the hooks and the config's schema to it, and the flow's catalog in the skills
6
- * manifest to its tag, and then runs `skills-manager update` on the flow's skills only (the
7
- * general ones keep their own versions). The versions come from npm, not from the CLI
8
- * that runs, which may be the old one.
2
+ * @file `issue-flow upgrade [--dry-run]`: moves the repo's flow files to the version that
3
+ * runs: the dev dependencies to the ones it pins, and the hooks and the config's schema to
4
+ * it. `agent-kit` runs it as `npx @ramon-sg/issue-flow@<version> upgrade` and then
5
+ * moves the flow's skills to the same tag, because the skills call this CLI.
9
6
  */
10
- import { spawnSync } from "node:child_process";
11
- import { CONFIG_PATH, isObject } from "../config-base.js";
7
+ import { CONFIG_PATH } from "../config-base.js";
12
8
  import { checkoutRoot, locateRepo } from "../git.js";
13
- import { withFlowPackages } from "../package-edits.js";
14
- import { registerHooks } from "../settings-hooks.js";
15
- import { FLOW_CATALOG, flowSkills, PACKAGE, packageManager, readJson, runNpx, SETTINGS_FILE, SetupError, SKILLS_LOCK, SKILLS_MANIFEST, writeJson, } from "../setup-files.js";
9
+ import { dependencyVersion, withFlowPackages } from "../package-edits.js";
10
+ import { hookVersions, registerHooks } from "../settings-hooks.js";
11
+ import { FLOW_REPO, ownVersions, PACKAGE, packageManager, readJson, SETTINGS_FILE, SetupError, writeJson } from "../setup-files.js";
16
12
  import { schemaUrl } from "./init.js";
17
- /** A release version. */
18
- const VERSION = /^\d+\.\d+\.\d+$/;
19
13
  /** The start of the schema URL `init` writes, at any version. */
20
- const SCHEMA_PREFIX = `https://raw.githubusercontent.com/${FLOW_CATALOG}/`;
14
+ const SCHEMA_PREFIX = `https://raw.githubusercontent.com/${FLOW_REPO}/`;
15
+ /** A release version, after an optional range sign. */
16
+ const RELEASE = /^[\^~=]?(\d+)\.(\d+)\.(\d+)$/;
21
17
  /**
22
- * The versions a release of this package pins, from npm.
23
- * @param to - The version, or null for the latest.
24
- * @returns The versions.
25
- * @throws SetupError if npm cannot tell, or the release lacks them.
18
+ * Whether a version is newer than another; false if either is not a release.
19
+ * @param version - The version, maybe with a range sign.
20
+ * @param than - The other.
21
+ * @returns True if it is newer.
26
22
  */
27
- function releaseVersions(to) {
28
- const spec = `${PACKAGE}@${to ?? "latest"}`;
29
- const result = spawnSync("npm", ["view", spec, "version", "dependencies", "devDependencies", "--json"], { encoding: "utf8" });
30
- let info;
31
- try {
32
- info = JSON.parse(result.stdout);
23
+ function isNewer(version, than) {
24
+ const [left, right] = [RELEASE.exec(version), RELEASE.exec(than)];
25
+ if (left === null || right === null)
26
+ return false;
27
+ for (let index = 1; index <= 3; index += 1) {
28
+ const diff = Number(left[index]) - Number(right[index]);
29
+ if (diff !== 0)
30
+ return diff > 0;
33
31
  }
34
- catch {
35
- info = null;
36
- }
37
- if (result.status !== 0 || !isObject(info))
38
- throw new SetupError(`npm could not find ${spec}${result.stderr ? `: ${result.stderr.trim()}` : "."}`);
39
- const deps = isObject(info.dependencies) ? info.dependencies : {};
40
- const devDeps = isObject(info.devDependencies) ? info.devDependencies : {};
41
- const githubRest = deps["@ramon-sg/github-rest"];
42
- const skillsManager = devDeps["@ramon-sg/skills-manager"];
43
- if (typeof info.version !== "string" || !VERSION.test(info.version) || typeof githubRest !== "string" || typeof skillsManager !== "string")
44
- throw new SetupError(`${spec} on npm lacks its version or those of github-rest and skills-manager.`);
45
- return { issueFlow: info.version, githubRest, skillsManager };
32
+ return false;
46
33
  }
47
34
  /**
48
- * The skills manifest with the flow's catalog at a tag.
49
- * @param manifest - The manifest, or null.
50
- * @param tag - The tag.
51
- * @returns The new manifest and whether it changed, or null if the flow's catalog is not in it.
35
+ * The newest version of the flow the repo already has, in package.json or a hook.
36
+ * @param root - The repo.
37
+ * @returns The version, or null if it has none.
52
38
  */
53
- function movedManifest(manifest, tag) {
54
- const sources = Array.isArray(manifest?.sources) ? manifest.sources : [];
55
- const index = sources.findIndex((source) => isObject(source) && source.catalog === FLOW_CATALOG);
56
- const source = sources[index];
57
- if (manifest === null || !isObject(source))
58
- return null;
59
- if (source.ref === tag)
60
- return { manifest, changed: false };
61
- return { manifest: { ...manifest, sources: sources.map((item, at) => (at === index ? { ...source, ref: tag } : item)) }, changed: true };
39
+ function repoVersion(root) {
40
+ const found = [dependencyVersion(readJson(root, "package.json"), PACKAGE), ...Object.values(hookVersions(readJson(root, SETTINGS_FILE)))];
41
+ return found.filter((version) => version !== null).reduce((newest, version) => (newest === null || isNewer(version, newest) ? version : newest), null);
62
42
  }
63
43
  /**
64
- * Moves the repo to another version of the flow; see the file's header.
44
+ * Moves the repo's flow files to this version; see the file's header.
65
45
  * @param options - The options.
66
46
  * @param out - Where to write.
67
- * @returns 0, or 1 if `skills-manager` failed.
68
- * @throws SetupError outside the main checkout, if npm cannot tell the version or a file is broken.
69
- * @example upgrade({ cwd, to: null, dryRun: false }, out)
47
+ * @returns 0.
48
+ * @throws SetupError outside the main checkout, if a file is broken, or if the repo is
49
+ * already at a newer version (a stale local copy must not move it back).
50
+ * @example upgrade({ cwd, dryRun: false }, out)
70
51
  */
71
52
  export function upgrade(options, out) {
72
53
  const located = locateRepo(options.cwd);
@@ -75,10 +56,10 @@ export function upgrade(options, out) {
75
56
  const root = located.mainRoot;
76
57
  if (checkoutRoot(options.cwd) !== root)
77
58
  throw new SetupError(`run upgrade in the main checkout, ${root}, not in a worktree.`);
78
- if (options.to !== null && !VERSION.test(options.to))
79
- throw new SetupError(`--to expects a version like 0.2.0: ${options.to}`);
80
- const versions = releaseVersions(options.to);
81
- const tag = `v${versions.issueFlow}`;
59
+ const versions = ownVersions();
60
+ const current = repoVersion(root);
61
+ if (current !== null && isNewer(current, versions.issueFlow))
62
+ throw new SetupError(`the repo is at issue-flow ${current}, newer than this ${versions.issueFlow}; run 'npx ${PACKAGE}@${current.replace(/^[\^~=]/, "")} upgrade' or 'npx agent-kit'.`);
82
63
  const writes = [];
83
64
  const lines = [];
84
65
  const pkg = withFlowPackages(readJson(root, "package.json"), versions, true);
@@ -92,24 +73,13 @@ export function upgrade(options, out) {
92
73
  const config = readJson(root, CONFIG_PATH);
93
74
  if (config !== null && typeof config.$schema === "string" && config.$schema.startsWith(SCHEMA_PREFIX) && config.$schema !== schemaUrl(versions.issueFlow)) {
94
75
  writes.push({ path: CONFIG_PATH, value: { ...config, $schema: schemaUrl(versions.issueFlow) } });
95
- lines.push(`${CONFIG_PATH}: $schema at ${tag}`);
96
- }
97
- const manifest = movedManifest(readJson(root, SKILLS_MANIFEST), tag);
98
- if (manifest?.changed === true) {
99
- writes.push({ path: SKILLS_MANIFEST, value: manifest.manifest });
100
- lines.push(`${SKILLS_MANIFEST}: ${FLOW_CATALOG} at ${tag}`);
76
+ lines.push(`${CONFIG_PATH}: $schema at v${versions.issueFlow}`);
101
77
  }
102
- const skills = flowSkills(readJson(root, SKILLS_LOCK)).map((skill) => skill.name);
103
78
  out.stdout(lines.length === 0 ? `Already at issue-flow ${versions.issueFlow}.\n` : `To issue-flow ${versions.issueFlow}:\n${lines.map((line) => ` ${line}\n`).join("")}`);
104
- if (manifest === null)
105
- out.stdout(` the flow's skills are not in ${SKILLS_MANIFEST}: issue-flow init installs them\n`);
106
- if (options.dryRun)
79
+ if (options.dryRun || writes.length === 0)
107
80
  return 0;
108
81
  for (const write of writes)
109
82
  writeJson(root, write.path, write.value);
110
- const updated = manifest === null || skills.length === 0 || runNpx(root, [`@ramon-sg/skills-manager@${versions.skillsManager}`, "update", ...skills]);
111
- if (!updated)
112
- out.stderr(`issue-flow: skills-manager update failed; run 'npx @ramon-sg/skills-manager@${versions.skillsManager} update ${skills.join(" ")}' to retry.\n`);
113
83
  out.stdout(`Next: run '${packageManager(root, readJson(root, "package.json"))} install', review the diff and commit it.\n`);
114
- return updated ? 0 : 1;
84
+ return 0;
115
85
  }
@@ -1,21 +1,9 @@
1
1
  /** This package's name on npm. */
2
2
  export declare const PACKAGE = "@ramon-sg/issue-flow";
3
- /** The GitHub repo of this package, which is also the catalog of the flow skills. */
4
- export declare const FLOW_CATALOG = "ramon-sg/issue-flow";
5
- /** The catalog of the general skills. */
6
- export declare const GENERAL_CATALOG = "ramon-sg/agent-skills";
7
- /** Where `skills-manager` keeps the repo's skills manifest. */
8
- export declare const SKILLS_MANIFEST = ".agents/skills.json";
9
- /** Where `skills-manager` keeps the repo's skills lock. */
10
- export declare const SKILLS_LOCK = ".agents/skills.lock.json";
3
+ /** The GitHub repo of this package, whose tags hold the config's schema of each version. */
4
+ export declare const FLOW_REPO = "ramon-sg/issue-flow";
11
5
  /** Claude Code's settings of the repo. */
12
6
  export declare const SETTINGS_FILE = ".claude/settings.json";
13
- /** The options of `claude-repo-guard` in the repo. */
14
- export declare const GUARD_CONFIG = ".claude/repo-guard.config.json";
15
- /** How `claude-repo-guard` runs: it is not on npm. */
16
- export declare const GUARD_SPEC = "github:ramon-sg/claude-repo-guard";
17
- /** How `claude-cloud` runs. */
18
- export declare const CLOUD_SPEC = "@ramon-sg/claude-cloud@1";
19
7
  /** Error of `init`, `doctor` or `upgrade`: exits 1 with the message. */
20
8
  export declare class SetupError extends Error {
21
9
  }
@@ -58,32 +46,3 @@ export declare function writeJson(root: string, path: string, value: unknown): v
58
46
  * @example packageManager(root, { packageManager: "pnpm@9.0.0" }) // "pnpm"
59
47
  */
60
48
  export declare function packageManager(root: string, pkg: Record<string, unknown> | null): string;
61
- /**
62
- * Runs `npx <args>` in the repo, its output on this terminal.
63
- * @param root - The repo.
64
- * @param args - The package and its arguments.
65
- * @returns Whether it exited 0.
66
- * @example runNpx(root, ["@ramon-sg/claude-cloud@1", "init"])
67
- */
68
- export declare function runNpx(root: string, args: string[]): boolean;
69
- /**
70
- * Runs `npx <args>` in the repo and returns what it printed.
71
- * @param root - The repo.
72
- * @param args - The package and its arguments.
73
- * @returns Its stdout and exit code, or null if npx could not start.
74
- * @example captureNpx(root, ["github:ramon-sg/claude-repo-guard", "doctor", "--json"])
75
- */
76
- export declare function captureNpx(root: string, args: string[]): {
77
- out: string;
78
- status: number | null;
79
- } | null;
80
- /**
81
- * The flow's skills in the repo's skills lock, with the tag each one is at.
82
- * @param lock - The lock, or null.
83
- * @returns Name and tag of each skill of the flow's catalog.
84
- * @example flowSkills(lock) // [{ name: "close-pr", tag: "v0.1.0" }]
85
- */
86
- export declare function flowSkills(lock: Record<string, unknown> | null): {
87
- name: string;
88
- tag: string | null;
89
- }[];
@@ -1,32 +1,19 @@
1
1
  /**
2
2
  * @file What `init`, `doctor` and `upgrade` share: this package's own versions, the JSON
3
- * files of the repo they read and write, its package manager, and running another tool
4
- * with `npx`. Every package goes by its scoped name: the unscoped `issue-flow`,
5
- * `github-rest` and `skills-manager` on npm belong to other people.
3
+ * files of the repo they read and write, and its package manager. Every package goes by its
4
+ * scoped name: the unscoped `issue-flow`, `github-rest` and `skills-manager` on npm belong
5
+ * to other people.
6
6
  */
7
- import { spawnSync } from "node:child_process";
8
7
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
9
8
  import { dirname, join } from "node:path";
10
9
  import { fileURLToPath } from "node:url";
11
10
  import { isObject } from "./config-base.js";
12
11
  /** This package's name on npm. */
13
12
  export const PACKAGE = "@ramon-sg/issue-flow";
14
- /** The GitHub repo of this package, which is also the catalog of the flow skills. */
15
- export const FLOW_CATALOG = "ramon-sg/issue-flow";
16
- /** The catalog of the general skills. */
17
- export const GENERAL_CATALOG = "ramon-sg/agent-skills";
18
- /** Where `skills-manager` keeps the repo's skills manifest. */
19
- export const SKILLS_MANIFEST = ".agents/skills.json";
20
- /** Where `skills-manager` keeps the repo's skills lock. */
21
- export const SKILLS_LOCK = ".agents/skills.lock.json";
13
+ /** The GitHub repo of this package, whose tags hold the config's schema of each version. */
14
+ export const FLOW_REPO = "ramon-sg/issue-flow";
22
15
  /** Claude Code's settings of the repo. */
23
16
  export const SETTINGS_FILE = ".claude/settings.json";
24
- /** The options of `claude-repo-guard` in the repo. */
25
- export const GUARD_CONFIG = ".claude/repo-guard.config.json";
26
- /** How `claude-repo-guard` runs: it is not on npm. */
27
- export const GUARD_SPEC = "github:ramon-sg/claude-repo-guard";
28
- /** How `claude-cloud` runs. */
29
- export const CLOUD_SPEC = "@ramon-sg/claude-cloud@1";
30
17
  /** Error of `init`, `doctor` or `upgrade`: exits 1 with the message. */
31
18
  export class SetupError extends Error {
32
19
  }
@@ -102,37 +89,3 @@ export function packageManager(root, pkg) {
102
89
  return field;
103
90
  return LOCKFILES.find(([file]) => existsSync(join(root, file)))?.[1] ?? "npm";
104
91
  }
105
- /**
106
- * Runs `npx <args>` in the repo, its output on this terminal.
107
- * @param root - The repo.
108
- * @param args - The package and its arguments.
109
- * @returns Whether it exited 0.
110
- * @example runNpx(root, ["@ramon-sg/claude-cloud@1", "init"])
111
- */
112
- export function runNpx(root, args) {
113
- const result = spawnSync("npx", ["--yes", ...args], { cwd: root, stdio: "inherit" });
114
- return result.status === 0;
115
- }
116
- /**
117
- * Runs `npx <args>` in the repo and returns what it printed.
118
- * @param root - The repo.
119
- * @param args - The package and its arguments.
120
- * @returns Its stdout and exit code, or null if npx could not start.
121
- * @example captureNpx(root, ["github:ramon-sg/claude-repo-guard", "doctor", "--json"])
122
- */
123
- export function captureNpx(root, args) {
124
- const result = spawnSync("npx", ["--yes", ...args], { cwd: root, encoding: "utf8", stdio: ["ignore", "pipe", "inherit"] });
125
- return result.error === undefined ? { out: result.stdout, status: result.status } : null;
126
- }
127
- /**
128
- * The flow's skills in the repo's skills lock, with the tag each one is at.
129
- * @param lock - The lock, or null.
130
- * @returns Name and tag of each skill of the flow's catalog.
131
- * @example flowSkills(lock) // [{ name: "close-pr", tag: "v0.1.0" }]
132
- */
133
- export function flowSkills(lock) {
134
- const skills = isObject(lock?.skills) ? lock.skills : {};
135
- return Object.entries(skills)
136
- .filter(([, entry]) => isObject(entry) && entry.source === `catalog:${FLOW_CATALOG}`)
137
- .map(([name, entry]) => ({ name, tag: isObject(entry) && typeof entry.tag === "string" ? entry.tag : null }));
138
- }
package/dist/usage.d.ts CHANGED
@@ -2,4 +2,4 @@
2
2
  * @file The usage text of the CLI, printed with `--help` and after a usage error.
3
3
  */
4
4
  /** Usage text. */
5
- export declare const USAGE = "Usage:\n issue-flow issue branch <n>... [--namer claude|title] [--json] [-R owner/repo]\n issue-flow issue branch --missing [--limit <n>] [--namer claude|title] [--dry-run | --yes] [--json] [-R owner/repo]\n issue-flow issue start <n> [--branch] [--dry-run | --yes] [--resume <branch> | --new] [--no-launch]\n [--ask-permissions | --free] [--namer claude|title] [-R owner/repo]\n issue-flow issue start <n> --plan [--ask-permissions | --free] [--namer claude|title] [-R owner/repo]\n issue-flow issue start <n> --cloud | --here | --teleport | --adopt [...]\n issue-flow worktree setup [--only <step>]\n issue-flow worktree teardown [--path <dir>]\n issue-flow worktree ports [--next]\n issue-flow worktree cleanup [--path <dir>]\n issue-flow worktree prune [--only <dir>] [--yes] [-R owner/repo]\n issue-flow pr worktree <n> [--no-setup] [-R owner/repo] [-- [<command>...]]\n issue-flow adapter setup [--only <step>] | issue [<url>] | archive [-R owner/repo]\n issue-flow session\n issue-flow hook worktree-create | worktree-remove\n issue-flow init [--yes] [--recipe <name>,...] [--no-cloud] [--no-guard] [--no-skills]\n issue-flow doctor\n issue-flow upgrade [--to <version>] [--dry-run]\n\nThe slug comes from the issue's \"Slug de rama\" block, then from Claude, then from the title,\nand the branch from the repo's template (an older \"Rama sugerida\" block is used if it fits).\n--missing adds the slug block to every open issue without a block, after confirming.\n\nissue start makes the branch in a worktree (or, with --branch, in this checkout), the\nstart commit, the push, the draft PR and the assignment, after confirming, and opens\nClaude: in auto mode if the repo has claude-repo-guard, with your settings with\n--ask-permissions, and with no sandbox nor questions with --free. --plan prints the branch\nand the prompt as JSON and changes nothing.\n\n--cloud confirms here and opens a Claude cloud session that starts the issue there.\n--here, inside a cloud session, starts it in this checkout and opens nothing. --teleport\nbrings the cloud session noted in the PR to the worktree. --adopt starts it in the\nworktree another tool (Orca, Paseo) just made, without asking nor opening Claude.\n\nworktree runs the steps of \"worktrees\" in .agents/flow.json: setup prepares this worktree,\nteardown cleans it before it goes (steps, ports, Docker), ports prints or moves its block\nof ports, cleanup deletes the Docker of deleted worktrees, and prune deletes the worktrees\nwhose PR was merged or closed. session is the SessionStart hook of Claude Code.\n\npr worktree makes (or reuses) a worktree on a PR's own branch, to review and fix it; with\n--, it runs the command there (Claude if none). adapter is what Orca and Paseo run: setup\nwhen they make a worktree (its steps and, if it came from an issue, issue start --adopt),\nissue to start one by hand, and archive before they delete it. hook implements Claude\nCode's WorktreeCreate and WorktreeRemove for claude --worktree.\n\ninit sets the repo up: .agents/flow.json (with the recipes its files suggest), the dev\ndependencies and scripts, and the hooks; then, after asking, claude-cloud init,\nclaude-repo-guard and the skills menu. doctor checks it is set up at this version, and\nupgrade moves the CLI, the hooks and the flow's skills to the latest version (or --to).";
5
+ export declare const USAGE = "Usage:\n issue-flow issue branch <n>... [--namer claude|title] [--json] [-R owner/repo]\n issue-flow issue branch --missing [--limit <n>] [--namer claude|title] [--dry-run | --yes] [--json] [-R owner/repo]\n issue-flow issue start <n> [--branch] [--dry-run | --yes] [--resume <branch> | --new] [--no-launch]\n [--ask-permissions | --free] [--namer claude|title] [-R owner/repo]\n issue-flow issue start <n> --plan [--ask-permissions | --free] [--namer claude|title] [-R owner/repo]\n issue-flow issue start <n> --cloud | --here | --teleport | --adopt [...]\n issue-flow worktree setup [--only <step>]\n issue-flow worktree teardown [--path <dir>]\n issue-flow worktree ports [--next]\n issue-flow worktree cleanup [--path <dir>]\n issue-flow worktree prune [--only <dir>] [--yes] [-R owner/repo]\n issue-flow pr worktree <n> [--no-setup] [-R owner/repo] [-- [<command>...]]\n issue-flow adapter setup [--only <step>] | issue [<url>] | archive [-R owner/repo]\n issue-flow session\n issue-flow hook worktree-create | worktree-remove\n issue-flow init [--yes] [--recipe <name>,...]\n issue-flow doctor [--json]\n issue-flow upgrade [--dry-run]\n\nThe slug comes from the issue's \"Slug de rama\" block, then from Claude, then from the title,\nand the branch from the repo's template (an older \"Rama sugerida\" block is used if it fits).\n--missing adds the slug block to every open issue without a block, after confirming.\n\nissue start makes the branch in a worktree (or, with --branch, in this checkout), the\nstart commit, the push, the draft PR and the assignment, after confirming, and opens\nClaude: in auto mode if the repo has claude-repo-guard, with your settings with\n--ask-permissions, and with no sandbox nor questions with --free. --plan prints the branch\nand the prompt as JSON and changes nothing.\n\n--cloud confirms here and opens a Claude cloud session that starts the issue there.\n--here, inside a cloud session, starts it in this checkout and opens nothing. --teleport\nbrings the cloud session noted in the PR to the worktree. --adopt starts it in the\nworktree another tool (Orca, Paseo) just made, without asking nor opening Claude.\n\nworktree runs the steps of \"worktrees\" in .agents/flow.json: setup prepares this worktree,\nteardown cleans it before it goes (steps, ports, Docker), ports prints or moves its block\nof ports, cleanup deletes the Docker of deleted worktrees, and prune deletes the worktrees\nwhose PR was merged or closed. session is the SessionStart hook of Claude Code.\n\npr worktree makes (or reuses) a worktree on a PR's own branch, to review and fix it; with\n--, it runs the command there (Claude if none). adapter is what Orca and Paseo run: setup\nwhen they make a worktree (its steps and, if it came from an issue, issue start --adopt),\nissue to start one by hand, and archive before they delete it. hook implements Claude\nCode's WorktreeCreate and WorktreeRemove for claude --worktree.\n\ninit sets the flow up in the repo: .agents/flow.json (with the recipes its files suggest),\nthe dev dependencies and scripts, and the hooks. doctor checks them at this version, and\nupgrade moves them to this version. npx agent-kit runs the three, along with the\nskills, claude-cloud and claude-repo-guard.";
package/dist/usage.js CHANGED
@@ -18,9 +18,9 @@ export const USAGE = `Usage:
18
18
  issue-flow adapter setup [--only <step>] | issue [<url>] | archive [-R owner/repo]
19
19
  issue-flow session
20
20
  issue-flow hook worktree-create | worktree-remove
21
- issue-flow init [--yes] [--recipe <name>,...] [--no-cloud] [--no-guard] [--no-skills]
22
- issue-flow doctor
23
- issue-flow upgrade [--to <version>] [--dry-run]
21
+ issue-flow init [--yes] [--recipe <name>,...]
22
+ issue-flow doctor [--json]
23
+ issue-flow upgrade [--dry-run]
24
24
 
25
25
  The slug comes from the issue's "Slug de rama" block, then from Claude, then from the title,
26
26
  and the branch from the repo's template (an older "Rama sugerida" block is used if it fits).
@@ -48,7 +48,7 @@ when they make a worktree (its steps and, if it came from an issue, issue start
48
48
  issue to start one by hand, and archive before they delete it. hook implements Claude
49
49
  Code's WorktreeCreate and WorktreeRemove for claude --worktree.
50
50
 
51
- init sets the repo up: .agents/flow.json (with the recipes its files suggest), the dev
52
- dependencies and scripts, and the hooks; then, after asking, claude-cloud init,
53
- claude-repo-guard and the skills menu. doctor checks it is set up at this version, and
54
- upgrade moves the CLI, the hooks and the flow's skills to the latest version (or --to).`;
51
+ init sets the flow up in the repo: .agents/flow.json (with the recipes its files suggest),
52
+ the dev dependencies and scripts, and the hooks. doctor checks them at this version, and
53
+ upgrade moves them to this version. npx agent-kit runs the three, along with the
54
+ skills, claude-cloud and claude-repo-guard.`;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ramon-sg/issue-flow",
3
- "version": "0.1.0",
3
+ "version": "0.2.1",
4
4
  "description": "Issue-to-PR workflow for any repo: names each issue's branch, creates its worktree, opens the draft PR and launches Claude Code, locally and in Claude's cloud.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -18,5 +18,5 @@
18
18
  "prepublishOnly": "npm test"
19
19
  },
20
20
  "dependencies": { "@ramon-sg/github-rest": "0.2.1" },
21
- "devDependencies": { "@ramon-sg/skills-manager": "1.1.0", "@types/node": "^24.0.0", "typescript": "^5.9.3" }
21
+ "devDependencies": { "@ramon-sg/skills-manager": "1.2.0", "@types/node": "^24.0.0", "typescript": "^5.9.3" }
22
22
  }
@@ -1,5 +1,5 @@
1
1
  {
2
- "description": "Work starts from staging and PRs go there; main only gets releases. Also lets claude-repo-guard's auto mode merge into staging.",
2
+ "description": "Work starts from staging and PRs go there; main only gets releases.",
3
3
  "detect": [],
4
4
  "config": { "baseBranch": "staging" }
5
5
  }
@@ -1,16 +0,0 @@
1
- /** What the auto mode is told about the flow. */
2
- export declare const GUARD_ENVIRONMENT = "issue-flow is this repo's issue workflow: making the branch and worktree of an issue, its empty start commit, pushing it, opening its draft PR, assigning the issue and adding its \"Slug de rama\" block are normal steps of the work, not risky actions.";
3
- /** The option that lets `gh` verify TLS inside the macOS sandbox. */
4
- export declare const WEAKER_NETWORK = "weakerNetworkIsolation";
5
- /**
6
- * The guard's options with what the flow needs added; what the repo set stays.
7
- * @param current - The options, or null if there is no file.
8
- * @param baseBranch - The flow's `baseBranch`, or null.
9
- * @param platform - The operating system (`process.platform`).
10
- * @returns The new options and what changed.
11
- * @example guardOptions(null, "staging", "darwin").options.integrationBranch // "staging"
12
- */
13
- export declare function guardOptions(current: Record<string, unknown> | null, baseBranch: string | null, platform: string): {
14
- options: Record<string, unknown>;
15
- changes: string[];
16
- };
@@ -1,38 +0,0 @@
1
- /**
2
- * @file What `issue-flow` writes in `claude-repo-guard`'s options before installing it. The
3
- * auto mode's classifier gets told that the flow's own writes (the start commit, the push,
4
- * the draft PR, the assignment, the "Slug de rama" block) are the normal workflow. A repo
5
- * with its own base branch (`staging`) gets it as `integrationBranch`, so merging into it is
6
- * part of the flow too. On macOS `gh` needs `weakerNetworkIsolation` inside the sandbox.
7
- */
8
- import { GUARD_CONFIG } from "./setup-files.js";
9
- /** What the auto mode is told about the flow. */
10
- export const GUARD_ENVIRONMENT = "issue-flow is this repo's issue workflow: making the branch and worktree of an issue, its empty start commit, pushing it, opening its draft PR, assigning the issue and adding its \"Slug de rama\" block are normal steps of the work, not risky actions.";
11
- /** The option that lets `gh` verify TLS inside the macOS sandbox. */
12
- export const WEAKER_NETWORK = "weakerNetworkIsolation";
13
- /**
14
- * The guard's options with what the flow needs added; what the repo set stays.
15
- * @param current - The options, or null if there is no file.
16
- * @param baseBranch - The flow's `baseBranch`, or null.
17
- * @param platform - The operating system (`process.platform`).
18
- * @returns The new options and what changed.
19
- * @example guardOptions(null, "staging", "darwin").options.integrationBranch // "staging"
20
- */
21
- export function guardOptions(current, baseBranch, platform) {
22
- const options = { ...(current ?? {}) };
23
- const changes = [];
24
- const environment = Array.isArray(options.environment) ? options.environment.map(String) : [];
25
- if (!environment.includes(GUARD_ENVIRONMENT)) {
26
- options.environment = [...environment, GUARD_ENVIRONMENT];
27
- changes.push(`${GUARD_CONFIG}: tells the auto mode about the issue flow`);
28
- }
29
- if (baseBranch !== null && (options.integrationBranch === undefined || options.integrationBranch === "")) {
30
- options.integrationBranch = baseBranch;
31
- changes.push(`${GUARD_CONFIG}: integrationBranch ${baseBranch}`);
32
- }
33
- if (platform === "darwin" && options[WEAKER_NETWORK] === undefined) {
34
- options[WEAKER_NETWORK] = true;
35
- changes.push(`${GUARD_CONFIG}: ${WEAKER_NETWORK} true, so gh works inside the macOS sandbox`);
36
- }
37
- return { options, changes };
38
- }