@ramon-sg/issue-flow 0.3.1 → 0.3.3
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 +2 -2
- package/dist/cli-flags.d.ts +97 -0
- package/dist/cli-flags.js +66 -0
- package/dist/cli-setup.d.ts +1 -1
- package/dist/cli-worktree.d.ts +1 -1
- package/dist/cli.d.ts +14 -94
- package/dist/cli.js +49 -78
- package/dist/commands/issue-start.js +32 -0
- package/dist/launch.d.ts +16 -3
- package/dist/launch.js +21 -4
- package/dist/package-edits.js +10 -1
- package/dist/repo-deny.d.ts +54 -0
- package/dist/repo-deny.js +128 -0
- package/dist/usage.d.ts +10 -1
- package/dist/usage.js +73 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -75,7 +75,7 @@ Qué hace, en orden:
|
|
|
75
75
|
- Si el PR se mergeó y no queda trabajo sin pushear, ofrece borrar el worktree y la rama.
|
|
76
76
|
- Si no, dice cómo retomar.
|
|
77
77
|
|
|
78
|
-
**Permisos:** si el repo tiene [`claude-repo-guard`](https://github.com/ramon-sg/claude-repo-guard), Claude se abre en modo auto con su contexto (lo mismo que `claude:auto`). Con `--ask-permissions` se abre con tus settings,
|
|
78
|
+
**Permisos:** si el repo tiene [`claude-repo-guard`](https://github.com/ramon-sg/claude-repo-guard), Claude se abre en modo auto con su contexto (lo mismo que `claude:auto`). Con `--ask-permissions` se abre con tus settings, con `--free` sin sandbox ni preguntas (como `claude:free`), y con `--free --no-repo-deny` también sin los `deny` del `.claude/settings.json` del repo, que vuelven al salir ([qué quita cada modo](docs/flujos/terminal.md#opciones)). Sin `claude-repo-guard`, se abre con tus settings. `--plan` devuelve esos mismos argumentos en `claudeArgs`. El atajo es `npm run issue:start:free 42`. Con npm, las opciones van después de `--` (`npm run issue:start -- 42 --free`); con pnpm, bun o yarn no hace falta. La tabla de cada administrador está en [Flujos](docs/flujos.md#administradores). `issue-flow issue start --help` explica cada opción.
|
|
79
79
|
|
|
80
80
|
### Con la nube y con otras herramientas
|
|
81
81
|
|
|
@@ -196,7 +196,7 @@ npx @ramon-sg/issue-flow doctor # revisa que el repo esté com
|
|
|
196
196
|
`apply` deja el repo como lo pide la versión que corre, y correrlo otra vez no cambia nada. La versión la elige quien actualiza, al elegir cuál correr: `apply` no cambia la versión de ningún otro paquete.
|
|
197
197
|
|
|
198
198
|
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. Esa primera vez propone las recetas que sugieren los archivos del repo (con `--yes`, todas; sin terminal y sin `--yes`, no escribe nada y lo dice). Si ya existe, no pregunta: corre las migraciones de esta versión, mueve el `$schema` y suma las recetas de `--recipe`.
|
|
199
|
-
2. **`package.json`:** `@ramon-sg/issue-flow` en esta versión, exacta. `@ramon-sg/github-rest` y los scripts `issue:start`, `worktree:prune` y `pr:worktree` solo si faltan: un `github-rest` que el repo ya tiene se queda, con un aviso si no llega a la versión que necesitan las skills.
|
|
199
|
+
2. **`package.json`:** `@ramon-sg/issue-flow` en esta versión, exacta. `@ramon-sg/github-rest` y los scripts `issue:start`, `issue:start:free`, `worktree:prune` y `pr:worktree` solo si faltan (`issue:start:free` no se agrega si el repo tiene su propio `issue:start`, que se saltaría): un `github-rest` que el repo ya tiene se queda, con un aviso si no llega a la versión que necesitan las skills.
|
|
200
200
|
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.
|
|
201
201
|
|
|
202
202
|
No consulta npm, y nunca baja de versión: si el repo ya tiene una issue-flow más nueva (por ejemplo, `package.json` subido a mano antes del `install`), o un rango como `^0.2.6`, se detiene y dice qué correr. `--json` da `{ version, changes, kept, written, next }`. Sale con 0 si aplicó o no había nada que hacer, y con 1 si se negó o tenía que preguntar y no pudo.
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file The CLI's flags and which command takes each one, apart from cli.ts (which runs on
|
|
3
|
+
* import) so the tests can check the usage text against them.
|
|
4
|
+
*/
|
|
5
|
+
import type { parseArgs } from "node:util";
|
|
6
|
+
/** Options of the CLI. */
|
|
7
|
+
export declare const OPTIONS: {
|
|
8
|
+
readonly repo: {
|
|
9
|
+
readonly type: "string";
|
|
10
|
+
readonly short: "R";
|
|
11
|
+
};
|
|
12
|
+
readonly json: {
|
|
13
|
+
readonly type: "boolean";
|
|
14
|
+
};
|
|
15
|
+
readonly help: {
|
|
16
|
+
readonly type: "boolean";
|
|
17
|
+
readonly short: "h";
|
|
18
|
+
};
|
|
19
|
+
readonly missing: {
|
|
20
|
+
readonly type: "boolean";
|
|
21
|
+
};
|
|
22
|
+
readonly limit: {
|
|
23
|
+
readonly type: "string";
|
|
24
|
+
};
|
|
25
|
+
readonly namer: {
|
|
26
|
+
readonly type: "string";
|
|
27
|
+
};
|
|
28
|
+
readonly yes: {
|
|
29
|
+
readonly type: "boolean";
|
|
30
|
+
readonly short: "y";
|
|
31
|
+
};
|
|
32
|
+
readonly "dry-run": {
|
|
33
|
+
readonly type: "boolean";
|
|
34
|
+
};
|
|
35
|
+
readonly branch: {
|
|
36
|
+
readonly type: "boolean";
|
|
37
|
+
};
|
|
38
|
+
readonly resume: {
|
|
39
|
+
readonly type: "string";
|
|
40
|
+
};
|
|
41
|
+
readonly new: {
|
|
42
|
+
readonly type: "boolean";
|
|
43
|
+
};
|
|
44
|
+
readonly "no-launch": {
|
|
45
|
+
readonly type: "boolean";
|
|
46
|
+
};
|
|
47
|
+
readonly "ask-permissions": {
|
|
48
|
+
readonly type: "boolean";
|
|
49
|
+
};
|
|
50
|
+
readonly free: {
|
|
51
|
+
readonly type: "boolean";
|
|
52
|
+
};
|
|
53
|
+
readonly "no-repo-deny": {
|
|
54
|
+
readonly type: "boolean";
|
|
55
|
+
};
|
|
56
|
+
readonly plan: {
|
|
57
|
+
readonly type: "boolean";
|
|
58
|
+
};
|
|
59
|
+
readonly cloud: {
|
|
60
|
+
readonly type: "boolean";
|
|
61
|
+
};
|
|
62
|
+
readonly here: {
|
|
63
|
+
readonly type: "boolean";
|
|
64
|
+
};
|
|
65
|
+
readonly teleport: {
|
|
66
|
+
readonly type: "boolean";
|
|
67
|
+
};
|
|
68
|
+
readonly adopt: {
|
|
69
|
+
readonly type: "boolean";
|
|
70
|
+
};
|
|
71
|
+
readonly only: {
|
|
72
|
+
readonly type: "string";
|
|
73
|
+
};
|
|
74
|
+
readonly path: {
|
|
75
|
+
readonly type: "string";
|
|
76
|
+
};
|
|
77
|
+
readonly next: {
|
|
78
|
+
readonly type: "boolean";
|
|
79
|
+
};
|
|
80
|
+
readonly "no-setup": {
|
|
81
|
+
readonly type: "boolean";
|
|
82
|
+
};
|
|
83
|
+
readonly recipe: {
|
|
84
|
+
readonly type: "string";
|
|
85
|
+
};
|
|
86
|
+
readonly update: {
|
|
87
|
+
readonly type: "boolean";
|
|
88
|
+
};
|
|
89
|
+
};
|
|
90
|
+
/** The parsed flags. */
|
|
91
|
+
export type Flags = ReturnType<typeof parseArgs<{
|
|
92
|
+
options: typeof OPTIONS;
|
|
93
|
+
allowPositionals: true;
|
|
94
|
+
strict: true;
|
|
95
|
+
}>>["values"];
|
|
96
|
+
/** Flags each command takes, besides `--repo`, `--help` and `--namer`. */
|
|
97
|
+
export declare const COMMAND_FLAGS: Record<string, (keyof Flags)[]>;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/** Options of the CLI. */
|
|
2
|
+
export const OPTIONS = {
|
|
3
|
+
repo: { type: "string", short: "R" },
|
|
4
|
+
json: { type: "boolean" },
|
|
5
|
+
help: { type: "boolean", short: "h" },
|
|
6
|
+
missing: { type: "boolean" },
|
|
7
|
+
limit: { type: "string" },
|
|
8
|
+
namer: { type: "string" },
|
|
9
|
+
yes: { type: "boolean", short: "y" },
|
|
10
|
+
"dry-run": { type: "boolean" },
|
|
11
|
+
branch: { type: "boolean" },
|
|
12
|
+
resume: { type: "string" },
|
|
13
|
+
new: { type: "boolean" },
|
|
14
|
+
"no-launch": { type: "boolean" },
|
|
15
|
+
"ask-permissions": { type: "boolean" },
|
|
16
|
+
free: { type: "boolean" },
|
|
17
|
+
"no-repo-deny": { type: "boolean" },
|
|
18
|
+
plan: { type: "boolean" },
|
|
19
|
+
cloud: { type: "boolean" },
|
|
20
|
+
here: { type: "boolean" },
|
|
21
|
+
teleport: { type: "boolean" },
|
|
22
|
+
adopt: { type: "boolean" },
|
|
23
|
+
only: { type: "string" },
|
|
24
|
+
path: { type: "string" },
|
|
25
|
+
next: { type: "boolean" },
|
|
26
|
+
"no-setup": { type: "boolean" },
|
|
27
|
+
recipe: { type: "string" },
|
|
28
|
+
update: { type: "boolean" },
|
|
29
|
+
};
|
|
30
|
+
/** Flags each command takes, besides `--repo`, `--help` and `--namer`. */
|
|
31
|
+
export const COMMAND_FLAGS = {
|
|
32
|
+
branch: ["json", "missing", "limit", "yes", "dry-run"],
|
|
33
|
+
start: [
|
|
34
|
+
"yes",
|
|
35
|
+
"dry-run",
|
|
36
|
+
"branch",
|
|
37
|
+
"resume",
|
|
38
|
+
"new",
|
|
39
|
+
"no-launch",
|
|
40
|
+
"ask-permissions",
|
|
41
|
+
"free",
|
|
42
|
+
"no-repo-deny",
|
|
43
|
+
"plan",
|
|
44
|
+
"cloud",
|
|
45
|
+
"here",
|
|
46
|
+
"teleport",
|
|
47
|
+
"adopt",
|
|
48
|
+
],
|
|
49
|
+
"worktree setup": ["only"],
|
|
50
|
+
"worktree teardown": ["path"],
|
|
51
|
+
"worktree ports": ["next"],
|
|
52
|
+
"worktree cleanup": ["path"],
|
|
53
|
+
"worktree prune": ["only", "yes"],
|
|
54
|
+
session: [],
|
|
55
|
+
"pr worktree": ["no-setup"],
|
|
56
|
+
"adapter setup": ["only"],
|
|
57
|
+
"adapter issue": [],
|
|
58
|
+
"adapter archive": [],
|
|
59
|
+
"hook worktree-create": [],
|
|
60
|
+
"hook worktree-remove": [],
|
|
61
|
+
apply: ["yes", "dry-run", "json", "recipe"],
|
|
62
|
+
init: ["yes", "recipe"],
|
|
63
|
+
doctor: ["json"],
|
|
64
|
+
upgrade: ["dry-run"],
|
|
65
|
+
"labels sync": ["update", "dry-run", "json"],
|
|
66
|
+
};
|
package/dist/cli-setup.d.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* @file The `apply` and `doctor` commands of the CLI, and `init` and `upgrade`, which run
|
|
3
3
|
* `apply` now and say so: their flags turned into options.
|
|
4
4
|
*/
|
|
5
|
-
import type { Flags } from "./cli.js";
|
|
5
|
+
import type { Flags } from "./cli-flags.js";
|
|
6
6
|
import { type Output } from "./output.js";
|
|
7
7
|
/** The setup commands. */
|
|
8
8
|
export declare const SETUP_COMMANDS: string[];
|
package/dist/cli-worktree.d.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* @file The `worktree`, `session`, `adapter` and `hook` commands of the CLI: their flags
|
|
3
3
|
* turned into options.
|
|
4
4
|
*/
|
|
5
|
-
import type { Flags } from "./cli.js";
|
|
5
|
+
import type { Flags } from "./cli-flags.js";
|
|
6
6
|
import { type FlowConfig } from "./config.js";
|
|
7
7
|
import { type Output } from "./output.js";
|
|
8
8
|
/** The commands of `issue-flow worktree`. */
|
package/dist/cli.d.ts
CHANGED
|
@@ -1,99 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
* @file `issue-flow` CLI. Warnings on stderr; exits 0 on success, 1 if GitHub, an edit or
|
|
4
|
-
* the start of an issue failed (or was declined) and 2 on bad usage or a bad
|
|
5
|
-
* `.agents/flow.json`.
|
|
6
|
-
*/
|
|
7
|
-
import { parseArgs } from "node:util";
|
|
2
|
+
import { type Flags } from "./cli-flags.js";
|
|
8
3
|
import { type FlowConfig } from "./config.js";
|
|
9
4
|
import { type Output } from "./output.js";
|
|
10
|
-
/** Options of the CLI. */
|
|
11
|
-
declare const OPTIONS: {
|
|
12
|
-
readonly repo: {
|
|
13
|
-
readonly type: "string";
|
|
14
|
-
readonly short: "R";
|
|
15
|
-
};
|
|
16
|
-
readonly json: {
|
|
17
|
-
readonly type: "boolean";
|
|
18
|
-
};
|
|
19
|
-
readonly help: {
|
|
20
|
-
readonly type: "boolean";
|
|
21
|
-
readonly short: "h";
|
|
22
|
-
};
|
|
23
|
-
readonly missing: {
|
|
24
|
-
readonly type: "boolean";
|
|
25
|
-
};
|
|
26
|
-
readonly limit: {
|
|
27
|
-
readonly type: "string";
|
|
28
|
-
};
|
|
29
|
-
readonly namer: {
|
|
30
|
-
readonly type: "string";
|
|
31
|
-
};
|
|
32
|
-
readonly yes: {
|
|
33
|
-
readonly type: "boolean";
|
|
34
|
-
readonly short: "y";
|
|
35
|
-
};
|
|
36
|
-
readonly "dry-run": {
|
|
37
|
-
readonly type: "boolean";
|
|
38
|
-
};
|
|
39
|
-
readonly branch: {
|
|
40
|
-
readonly type: "boolean";
|
|
41
|
-
};
|
|
42
|
-
readonly resume: {
|
|
43
|
-
readonly type: "string";
|
|
44
|
-
};
|
|
45
|
-
readonly new: {
|
|
46
|
-
readonly type: "boolean";
|
|
47
|
-
};
|
|
48
|
-
readonly "no-launch": {
|
|
49
|
-
readonly type: "boolean";
|
|
50
|
-
};
|
|
51
|
-
readonly "ask-permissions": {
|
|
52
|
-
readonly type: "boolean";
|
|
53
|
-
};
|
|
54
|
-
readonly free: {
|
|
55
|
-
readonly type: "boolean";
|
|
56
|
-
};
|
|
57
|
-
readonly plan: {
|
|
58
|
-
readonly type: "boolean";
|
|
59
|
-
};
|
|
60
|
-
readonly cloud: {
|
|
61
|
-
readonly type: "boolean";
|
|
62
|
-
};
|
|
63
|
-
readonly here: {
|
|
64
|
-
readonly type: "boolean";
|
|
65
|
-
};
|
|
66
|
-
readonly teleport: {
|
|
67
|
-
readonly type: "boolean";
|
|
68
|
-
};
|
|
69
|
-
readonly adopt: {
|
|
70
|
-
readonly type: "boolean";
|
|
71
|
-
};
|
|
72
|
-
readonly only: {
|
|
73
|
-
readonly type: "string";
|
|
74
|
-
};
|
|
75
|
-
readonly path: {
|
|
76
|
-
readonly type: "string";
|
|
77
|
-
};
|
|
78
|
-
readonly next: {
|
|
79
|
-
readonly type: "boolean";
|
|
80
|
-
};
|
|
81
|
-
readonly "no-setup": {
|
|
82
|
-
readonly type: "boolean";
|
|
83
|
-
};
|
|
84
|
-
readonly recipe: {
|
|
85
|
-
readonly type: "string";
|
|
86
|
-
};
|
|
87
|
-
readonly update: {
|
|
88
|
-
readonly type: "boolean";
|
|
89
|
-
};
|
|
90
|
-
};
|
|
91
|
-
/** The parsed flags. */
|
|
92
|
-
export type Flags = ReturnType<typeof parseArgs<{
|
|
93
|
-
options: typeof OPTIONS;
|
|
94
|
-
allowPositionals: true;
|
|
95
|
-
strict: true;
|
|
96
|
-
}>>["values"];
|
|
97
5
|
/**
|
|
98
6
|
* The repo of the command: `-R`, then the config's `repo`, then `GH_REPO`, then origin.
|
|
99
7
|
* @param flag - The `-R` value.
|
|
@@ -111,6 +19,19 @@ export declare function repoOf(flag: string | undefined, config: FlowConfig, cwd
|
|
|
111
19
|
* @example ownFlags("worktree ports", { next: true })
|
|
112
20
|
*/
|
|
113
21
|
export declare function ownFlags(command: string, flags: Flags): void;
|
|
22
|
+
/**
|
|
23
|
+
* Splits the arguments from the command that `pr worktree` runs after `--`. Anywhere else
|
|
24
|
+
* a `--` is dropped, since pnpm passes it on as it is (`pnpm issue:start -- 42` runs
|
|
25
|
+
* `issue start -- 42`), and so is one right after `pr worktree` with no PR number before it
|
|
26
|
+
* (`pnpm pr:worktree -- 5`).
|
|
27
|
+
* @param argv - The arguments.
|
|
28
|
+
* @returns The arguments for issue-flow, and the command, or null if there is none.
|
|
29
|
+
* @example splitArgs(["pr", "worktree", "5", "--", "npm", "test"]) // { args: ["pr", "worktree", "5"], command: ["npm", "test"] }
|
|
30
|
+
*/
|
|
31
|
+
export declare function splitArgs(argv: string[]): {
|
|
32
|
+
args: string[];
|
|
33
|
+
command: string[] | null;
|
|
34
|
+
};
|
|
114
35
|
/**
|
|
115
36
|
* Runs the CLI.
|
|
116
37
|
* @param argv - Arguments without `node` or the script.
|
|
@@ -120,4 +41,3 @@ export declare function ownFlags(command: string, flags: Flags): void;
|
|
|
120
41
|
* @throws UsageError, ConfigError or GitHubError, which the entry point handles.
|
|
121
42
|
*/
|
|
122
43
|
export declare function main(argv: string[], out: Output, cwd: string): Promise<number>;
|
|
123
|
-
export {};
|
package/dist/cli.js
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* `.agents/flow.json`.
|
|
6
6
|
*/
|
|
7
7
|
import { parseArgs } from "node:util";
|
|
8
|
+
import { COMMAND_FLAGS, OPTIONS } from "./cli-flags.js";
|
|
8
9
|
import { GitHubError, InvalidRepoEnvError, resolveRepo } from "@ramon-sg/github-rest";
|
|
9
10
|
import { branchMissing, branchOf } from "./commands/issue-branch.js";
|
|
10
11
|
import { issueStart } from "./commands/issue-start.js";
|
|
@@ -15,71 +16,7 @@ import { prWorktree } from "./commands/pr-worktree.js";
|
|
|
15
16
|
import { ConfigError } from "./config-base.js";
|
|
16
17
|
import { loadConfig } from "./config.js";
|
|
17
18
|
import { processOutput, UsageError } from "./output.js";
|
|
18
|
-
import {
|
|
19
|
-
/** Options of the CLI. */
|
|
20
|
-
const OPTIONS = {
|
|
21
|
-
repo: { type: "string", short: "R" },
|
|
22
|
-
json: { type: "boolean" },
|
|
23
|
-
help: { type: "boolean", short: "h" },
|
|
24
|
-
missing: { type: "boolean" },
|
|
25
|
-
limit: { type: "string" },
|
|
26
|
-
namer: { type: "string" },
|
|
27
|
-
yes: { type: "boolean", short: "y" },
|
|
28
|
-
"dry-run": { type: "boolean" },
|
|
29
|
-
branch: { type: "boolean" },
|
|
30
|
-
resume: { type: "string" },
|
|
31
|
-
new: { type: "boolean" },
|
|
32
|
-
"no-launch": { type: "boolean" },
|
|
33
|
-
"ask-permissions": { type: "boolean" },
|
|
34
|
-
free: { type: "boolean" },
|
|
35
|
-
plan: { type: "boolean" },
|
|
36
|
-
cloud: { type: "boolean" },
|
|
37
|
-
here: { type: "boolean" },
|
|
38
|
-
teleport: { type: "boolean" },
|
|
39
|
-
adopt: { type: "boolean" },
|
|
40
|
-
only: { type: "string" },
|
|
41
|
-
path: { type: "string" },
|
|
42
|
-
next: { type: "boolean" },
|
|
43
|
-
"no-setup": { type: "boolean" },
|
|
44
|
-
recipe: { type: "string" },
|
|
45
|
-
update: { type: "boolean" },
|
|
46
|
-
};
|
|
47
|
-
/** Flags each command takes, besides `--repo`, `--help` and `--namer`. */
|
|
48
|
-
const COMMAND_FLAGS = {
|
|
49
|
-
branch: ["json", "missing", "limit", "yes", "dry-run"],
|
|
50
|
-
start: [
|
|
51
|
-
"yes",
|
|
52
|
-
"dry-run",
|
|
53
|
-
"branch",
|
|
54
|
-
"resume",
|
|
55
|
-
"new",
|
|
56
|
-
"no-launch",
|
|
57
|
-
"ask-permissions",
|
|
58
|
-
"free",
|
|
59
|
-
"plan",
|
|
60
|
-
"cloud",
|
|
61
|
-
"here",
|
|
62
|
-
"teleport",
|
|
63
|
-
"adopt",
|
|
64
|
-
],
|
|
65
|
-
"worktree setup": ["only"],
|
|
66
|
-
"worktree teardown": ["path"],
|
|
67
|
-
"worktree ports": ["next"],
|
|
68
|
-
"worktree cleanup": ["path"],
|
|
69
|
-
"worktree prune": ["only", "yes"],
|
|
70
|
-
session: [],
|
|
71
|
-
"pr worktree": ["no-setup"],
|
|
72
|
-
"adapter setup": ["only"],
|
|
73
|
-
"adapter issue": [],
|
|
74
|
-
"adapter archive": [],
|
|
75
|
-
"hook worktree-create": [],
|
|
76
|
-
"hook worktree-remove": [],
|
|
77
|
-
apply: ["yes", "dry-run", "json", "recipe"],
|
|
78
|
-
init: ["yes", "recipe"],
|
|
79
|
-
doctor: ["json"],
|
|
80
|
-
upgrade: ["dry-run"],
|
|
81
|
-
"labels sync": ["update", "dry-run", "json"],
|
|
82
|
-
};
|
|
19
|
+
import { usageOf } from "./usage.js";
|
|
83
20
|
/** An issue number: digits only. */
|
|
84
21
|
const NUMBER = /^\d+$/;
|
|
85
22
|
/** A positive whole number. */
|
|
@@ -145,8 +82,10 @@ export function ownFlags(command, flags) {
|
|
|
145
82
|
function permissionsOf(flags) {
|
|
146
83
|
if (flags["ask-permissions"] && flags.free)
|
|
147
84
|
throw new UsageError("use --ask-permissions or --free, not both.");
|
|
85
|
+
if (flags["no-repo-deny"] && !flags.free)
|
|
86
|
+
throw new UsageError("--no-repo-deny goes with --free.");
|
|
148
87
|
if (flags.free)
|
|
149
|
-
return "free";
|
|
88
|
+
return flags["no-repo-deny"] ? "free-no-repo-deny" : "free";
|
|
150
89
|
return flags["ask-permissions"] ? "ask" : "default";
|
|
151
90
|
}
|
|
152
91
|
/**
|
|
@@ -162,18 +101,18 @@ function startArgs(flags, rest) {
|
|
|
162
101
|
throw new UsageError("issue start needs one issue number.");
|
|
163
102
|
if (flags.resume !== undefined && flags.new)
|
|
164
103
|
throw new UsageError("--resume does not go with --new.");
|
|
165
|
-
const conflicting = flags.branch || flags["dry-run"] || flags.yes || flags.resume !== undefined || flags.new;
|
|
104
|
+
const conflicting = flags.branch || flags["dry-run"] || flags.yes || flags.resume !== undefined || flags.new || flags["no-repo-deny"];
|
|
166
105
|
if (flags.plan && conflicting)
|
|
167
|
-
throw new UsageError("--plan does not go with --branch, --dry-run, --yes, --resume nor --
|
|
106
|
+
throw new UsageError("--plan does not go with --branch, --dry-run, --yes, --resume, --new nor --no-repo-deny.");
|
|
168
107
|
cloudArgs(flags);
|
|
169
108
|
return Number(text);
|
|
170
109
|
}
|
|
171
110
|
/** The flags that do not go together with each way of starting, besides each other. */
|
|
172
111
|
const EXCLUSIVE = {
|
|
173
|
-
cloud: ["branch", "plan", "ask-permissions", "free"],
|
|
174
|
-
here: ["branch", "plan", "ask-permissions", "free"],
|
|
112
|
+
cloud: ["branch", "plan", "ask-permissions", "free", "no-repo-deny"],
|
|
113
|
+
here: ["branch", "plan", "ask-permissions", "free", "no-repo-deny"],
|
|
175
114
|
teleport: ["plan", "new", "no-launch"],
|
|
176
|
-
adopt: ["branch", "plan", "resume", "new", "ask-permissions", "free"],
|
|
115
|
+
adopt: ["branch", "plan", "resume", "new", "ask-permissions", "free", "no-repo-deny"],
|
|
177
116
|
};
|
|
178
117
|
/**
|
|
179
118
|
* Checks `--cloud`, `--here`, `--teleport` and `--adopt`: at most one, and none with the
|
|
@@ -192,6 +131,41 @@ function cloudArgs(flags) {
|
|
|
192
131
|
if (other !== undefined)
|
|
193
132
|
throw new UsageError(`--${way} does not go with --${other}.`);
|
|
194
133
|
}
|
|
134
|
+
/**
|
|
135
|
+
* The positionals of some arguments, with each flag's value left out (`-R o/r` is a flag).
|
|
136
|
+
* @param args - The arguments.
|
|
137
|
+
* @returns The positionals.
|
|
138
|
+
*/
|
|
139
|
+
function positionalsOf(args) {
|
|
140
|
+
return parseArgs({ args, options: OPTIONS, allowPositionals: true, strict: false }).positionals;
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Splits the arguments from the command that `pr worktree` runs after `--`. Anywhere else
|
|
144
|
+
* a `--` is dropped, since pnpm passes it on as it is (`pnpm issue:start -- 42` runs
|
|
145
|
+
* `issue start -- 42`), and so is one right after `pr worktree` with no PR number before it
|
|
146
|
+
* (`pnpm pr:worktree -- 5`).
|
|
147
|
+
* @param argv - The arguments.
|
|
148
|
+
* @returns The arguments for issue-flow, and the command, or null if there is none.
|
|
149
|
+
* @example splitArgs(["pr", "worktree", "5", "--", "npm", "test"]) // { args: ["pr", "worktree", "5"], command: ["npm", "test"] }
|
|
150
|
+
*/
|
|
151
|
+
export function splitArgs(argv) {
|
|
152
|
+
let args = argv;
|
|
153
|
+
for (let dashes = args.indexOf("--"); dashes !== -1; dashes = args.indexOf("--")) {
|
|
154
|
+
const words = positionalsOf(args.slice(0, dashes));
|
|
155
|
+
if (words[0] === "pr" && words[1] === "worktree" && words.length > 2)
|
|
156
|
+
return { args: args.slice(0, dashes), command: args.slice(dashes + 1) };
|
|
157
|
+
args = args.filter((_, index) => index !== dashes);
|
|
158
|
+
}
|
|
159
|
+
return { args, command: null };
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* The command's words, as `usageOf` takes them.
|
|
163
|
+
* @param argv - The arguments.
|
|
164
|
+
* @returns The positionals, without the flags nor their values.
|
|
165
|
+
*/
|
|
166
|
+
function commandWords(argv) {
|
|
167
|
+
return positionalsOf(splitArgs(argv).args);
|
|
168
|
+
}
|
|
195
169
|
/**
|
|
196
170
|
* Runs the CLI.
|
|
197
171
|
* @param argv - Arguments without `node` or the script.
|
|
@@ -201,23 +175,20 @@ function cloudArgs(flags) {
|
|
|
201
175
|
* @throws UsageError, ConfigError or GitHubError, which the entry point handles.
|
|
202
176
|
*/
|
|
203
177
|
export async function main(argv, out, cwd) {
|
|
204
|
-
const
|
|
205
|
-
const command = dashes === -1 ? null : argv.slice(dashes + 1);
|
|
178
|
+
const { args, command } = splitArgs(argv);
|
|
206
179
|
let parsed;
|
|
207
180
|
try {
|
|
208
|
-
parsed = parseArgs({ args
|
|
181
|
+
parsed = parseArgs({ args, options: OPTIONS, allowPositionals: true, strict: true });
|
|
209
182
|
}
|
|
210
183
|
catch (error) {
|
|
211
184
|
throw new UsageError(error instanceof Error ? error.message : String(error));
|
|
212
185
|
}
|
|
213
186
|
const { values: flags, positionals } = parsed;
|
|
214
187
|
if (flags.help) {
|
|
215
|
-
out.stdout(`${
|
|
188
|
+
out.stdout(`${usageOf(positionals)}\n`);
|
|
216
189
|
return 0;
|
|
217
190
|
}
|
|
218
191
|
const [group, sub, ...rest] = positionals;
|
|
219
|
-
if (command !== null && (group !== "pr" || sub !== "worktree"))
|
|
220
|
-
throw new UsageError("-- only goes with pr worktree.");
|
|
221
192
|
if (group === "pr")
|
|
222
193
|
return runPr(sub, rest, flags, { out, cwd, command });
|
|
223
194
|
if (group === "labels")
|
|
@@ -363,6 +334,6 @@ main(process.argv.slice(2), out, process.cwd()).then((code) => {
|
|
|
363
334
|
}, (error) => {
|
|
364
335
|
const usage = error instanceof UsageError;
|
|
365
336
|
const config = error instanceof ConfigError;
|
|
366
|
-
out.stderr(`issue-flow: ${error instanceof Error ? error.message : String(error)}\n${usage ? `${
|
|
337
|
+
out.stderr(`issue-flow: ${error instanceof Error ? error.message : String(error)}\n${usage ? `${usageOf(commandWords(process.argv.slice(2)))}\n` : ""}`);
|
|
367
338
|
process.exitCode = usage || config ? 2 : 1;
|
|
368
339
|
});
|
|
@@ -17,6 +17,7 @@ import { inPath, launchSession, permissionArgs, teleportSession } from "../launc
|
|
|
17
17
|
import { emitJson, warn } from "../output.js";
|
|
18
18
|
import { findOpenPlan, findPlan } from "../plan-doc.js";
|
|
19
19
|
import { proposeBranches } from "../propose.js";
|
|
20
|
+
import { liftRepoDeny, restoreRepoDeny } from "../repo-deny.js";
|
|
20
21
|
import { lockWorktree, unlockWorktree } from "../session-lock.js";
|
|
21
22
|
import { afterSession, choosePlace, copyWorktreeIncludes, prepareBranch, publish } from "../start-place.js";
|
|
22
23
|
import { chooseAdopted, fetchOrigin, finishHere, launchCloud, placeAdopted, placeCloud, placeHere, prCloudSession, prepareHere, readAdopted, renameAdopted, withoutFresh, } from "../start-modes.js";
|
|
@@ -245,6 +246,31 @@ function question(state) {
|
|
|
245
246
|
};
|
|
246
247
|
return { text: "Start the issue?", declined: "did nothing." };
|
|
247
248
|
}
|
|
249
|
+
/**
|
|
250
|
+
* Before opening Claude: puts back the repo's deny rules a session whose terminal was
|
|
251
|
+
* closed left out, and with `--free --no-repo-deny` takes them out for this one. With
|
|
252
|
+
* another session in the worktree it touches nothing, as it may be using them.
|
|
253
|
+
* @param state - The start.
|
|
254
|
+
* @param lock - The worktree lock.
|
|
255
|
+
* @returns What to put back, or null if nothing was taken out.
|
|
256
|
+
*/
|
|
257
|
+
function liftForSession(state, lock) {
|
|
258
|
+
const { out, commonDir, branch } = state;
|
|
259
|
+
const wanted = state.options.permissions === "free-no-repo-deny";
|
|
260
|
+
if (lock.shared) {
|
|
261
|
+
if (wanted)
|
|
262
|
+
warn(out, "another session uses this worktree: the repo's deny rules stay.");
|
|
263
|
+
return null;
|
|
264
|
+
}
|
|
265
|
+
const leftover = restoreRepoDeny(commonDir, branch, state.workDir, null);
|
|
266
|
+
if (leftover !== null)
|
|
267
|
+
warn(out, leftover);
|
|
268
|
+
if (!wanted)
|
|
269
|
+
return null;
|
|
270
|
+
const { message, saved } = liftRepoDeny(state.workDir, commonDir, branch);
|
|
271
|
+
out.stdout(`${message}\n`);
|
|
272
|
+
return saved;
|
|
273
|
+
}
|
|
248
274
|
/**
|
|
249
275
|
* Opens Claude in the place, locking a worktree meanwhile, and then offers the cleanup.
|
|
250
276
|
* Without `claude` or with `--no-launch`, says how to open it.
|
|
@@ -264,6 +290,7 @@ async function openSession(state) {
|
|
|
264
290
|
process.once("exit", release);
|
|
265
291
|
forgetTypedAhead();
|
|
266
292
|
const permissions = permissionArgs(options.permissions, state.workDir, state.mainRoot).args;
|
|
293
|
+
const lifted = liftForSession(state, lock);
|
|
267
294
|
if (options.teleport) {
|
|
268
295
|
out.stdout(`Bringing the cloud session${state.cloudSession === null ? "" : ` (${state.cloudSession})`}. To resume it later: claude --resume\n`);
|
|
269
296
|
teleportSession(state.workDir, permissions, state.cloudSession);
|
|
@@ -278,6 +305,11 @@ async function openSession(state) {
|
|
|
278
305
|
color: issueColor(state.labels, options.config.colors)?.color ?? null,
|
|
279
306
|
permissions,
|
|
280
307
|
}, (text) => warn(out, text));
|
|
308
|
+
if (lifted !== null) {
|
|
309
|
+
const problem = restoreRepoDeny(state.commonDir, state.branch, state.workDir, lifted);
|
|
310
|
+
if (problem !== null)
|
|
311
|
+
warn(out, problem);
|
|
312
|
+
}
|
|
281
313
|
release();
|
|
282
314
|
process.off("exit", release);
|
|
283
315
|
await afterSession(state, lock);
|
package/dist/launch.d.ts
CHANGED
|
@@ -3,8 +3,12 @@ import { type StartMode } from "./issue-text.js";
|
|
|
3
3
|
export declare const AUTO_MODE_FILE = ".claude/repo-guard.auto-mode.json";
|
|
4
4
|
/** Folder, inside the shared `.git`, with one file per branch holding its session id. */
|
|
5
5
|
export declare const SESSIONS_DIR = "issue-flow-sessions";
|
|
6
|
-
/**
|
|
7
|
-
|
|
6
|
+
/**
|
|
7
|
+
* How Claude asks for permissions: chosen by the flags, or the default. `free-no-repo-deny`
|
|
8
|
+
* is `--free --no-repo-deny`: the same arguments as `free`, and `issue start` also takes the
|
|
9
|
+
* repo's deny rules out while Claude runs (see repo-deny.ts).
|
|
10
|
+
*/
|
|
11
|
+
export type Permissions = "default" | "ask" | "free" | "free-no-repo-deny";
|
|
8
12
|
/** The arguments of the permission mode, and how the summary names it. */
|
|
9
13
|
export interface PermissionArgs {
|
|
10
14
|
args: string[];
|
|
@@ -53,7 +57,16 @@ export declare function sessionExists(id: string): boolean;
|
|
|
53
57
|
*/
|
|
54
58
|
export declare function savedSession(commonDir: string, branch: string): string | null;
|
|
55
59
|
/**
|
|
56
|
-
*
|
|
60
|
+
* The note `--no-repo-deny` keeps while the repo's deny rules are out, next to the
|
|
61
|
+
* branch's session id.
|
|
62
|
+
* @param commonDir - The shared `.git`.
|
|
63
|
+
* @param branch - The branch.
|
|
64
|
+
* @returns The path.
|
|
65
|
+
* @example noteFile("/repo/.git", "feat/42-x") // "/repo/.git/issue-flow-sessions/feat+42-x.repo-deny.json"
|
|
66
|
+
*/
|
|
67
|
+
export declare function noteFile(commonDir: string, branch: string): string;
|
|
68
|
+
/**
|
|
69
|
+
* Forgets a branch's session id (and its `--no-repo-deny` note), once its work is gone.
|
|
57
70
|
* @param commonDir - The shared `.git`.
|
|
58
71
|
* @param branch - The branch.
|
|
59
72
|
* @example forgetSession("/repo/.git", "feat/42-x")
|
package/dist/launch.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @file Opens Claude for an issue in the same terminal. The permission mode follows
|
|
3
3
|
* `claude-repo-guard` when the repo has it: auto mode with its classifier context by
|
|
4
|
-
* default, plain `claude` with `--ask-permissions`,
|
|
5
|
-
* `--free`. Each branch keeps its session id, so starting the issue again resumes it.
|
|
4
|
+
* default, plain `claude` with `--ask-permissions`, no sandbox nor questions with `--free`,
|
|
5
|
+
* and also without the repo's deny rules with `--free --no-repo-deny`. Each branch keeps its session id, so starting the issue again resumes it.
|
|
6
6
|
* Ported from `launch_session` and `run_claude` in Abastio's `scripts/start-issue.sh`.
|
|
7
7
|
*/
|
|
8
8
|
import { randomUUID } from "node:crypto";
|
|
@@ -32,7 +32,12 @@ export function permissionArgs(permissions, workDir, mainRoot) {
|
|
|
32
32
|
if (permissions === "free")
|
|
33
33
|
return {
|
|
34
34
|
args: ["--settings", NO_SANDBOX, "--dangerously-skip-permissions"],
|
|
35
|
-
label: "none: no sandbox and no questions (--free)",
|
|
35
|
+
label: "none: no sandbox and no questions; deny rules still apply (--free)",
|
|
36
|
+
};
|
|
37
|
+
if (permissions === "free-no-repo-deny")
|
|
38
|
+
return {
|
|
39
|
+
args: ["--settings", NO_SANDBOX, "--dangerously-skip-permissions"],
|
|
40
|
+
label: "none: no sandbox, no questions and no deny rules of the repo; yours still apply (--free --no-repo-deny)",
|
|
36
41
|
};
|
|
37
42
|
if (permissions === "ask")
|
|
38
43
|
return { args: [], label: "your settings (--ask-permissions)" };
|
|
@@ -96,13 +101,25 @@ export function savedSession(commonDir, branch) {
|
|
|
96
101
|
return id !== "" && sessionExists(id) ? id : null;
|
|
97
102
|
}
|
|
98
103
|
/**
|
|
99
|
-
*
|
|
104
|
+
* The note `--no-repo-deny` keeps while the repo's deny rules are out, next to the
|
|
105
|
+
* branch's session id.
|
|
106
|
+
* @param commonDir - The shared `.git`.
|
|
107
|
+
* @param branch - The branch.
|
|
108
|
+
* @returns The path.
|
|
109
|
+
* @example noteFile("/repo/.git", "feat/42-x") // "/repo/.git/issue-flow-sessions/feat+42-x.repo-deny.json"
|
|
110
|
+
*/
|
|
111
|
+
export function noteFile(commonDir, branch) {
|
|
112
|
+
return `${sessionFile(commonDir, branch)}.repo-deny.json`;
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Forgets a branch's session id (and its `--no-repo-deny` note), once its work is gone.
|
|
100
116
|
* @param commonDir - The shared `.git`.
|
|
101
117
|
* @param branch - The branch.
|
|
102
118
|
* @example forgetSession("/repo/.git", "feat/42-x")
|
|
103
119
|
*/
|
|
104
120
|
export function forgetSession(commonDir, branch) {
|
|
105
121
|
rmSync(sessionFile(commonDir, branch), { force: true });
|
|
122
|
+
rmSync(noteFile(commonDir, branch), { force: true });
|
|
106
123
|
}
|
|
107
124
|
/**
|
|
108
125
|
* Runs Claude in the terminal; its exit code does not matter, what follows runs anyway.
|
package/dist/package-edits.js
CHANGED
|
@@ -100,9 +100,13 @@ export function reaches(current, tested) {
|
|
|
100
100
|
export const KEPT = "kept ";
|
|
101
101
|
/** The package the skills call, which the repo gets if it lacks it. */
|
|
102
102
|
export const GITHUB_REST = "@ramon-sg/github-rest";
|
|
103
|
+
/** The script that starts an issue, and its shortcut with `--free`. */
|
|
104
|
+
const START_SCRIPT = "issue:start";
|
|
105
|
+
const FREE_SCRIPT = "issue:start:free";
|
|
103
106
|
/** The scripts `apply` adds. */
|
|
104
107
|
const FLOW_SCRIPTS = {
|
|
105
108
|
"issue:start": "issue-flow issue start",
|
|
109
|
+
"issue:start:free": "issue-flow issue start --free",
|
|
106
110
|
"worktree:prune": "issue-flow worktree prune",
|
|
107
111
|
"pr:worktree": "issue-flow pr worktree",
|
|
108
112
|
};
|
|
@@ -156,8 +160,13 @@ export function withFlowPackages(pkg, versions) {
|
|
|
156
160
|
pin(GITHUB_REST, versions.githubRest, "devDependencies", null);
|
|
157
161
|
else if (!reaches(rest.version, versions.githubRest))
|
|
158
162
|
changes.push(`${KEPT}${GITHUB_REST} ${rest.version} (the skills need ${versions.githubRest} or newer: raise it)`);
|
|
163
|
+
// NOTE: with its own issue:start (a wrapper), issue:start:free would skip it: it is not
|
|
164
|
+
// added, and the flow's one gets a note.
|
|
165
|
+
const ownStart = scripts[START_SCRIPT] !== undefined && scripts[START_SCRIPT] !== FLOW_SCRIPTS[START_SCRIPT];
|
|
166
|
+
if (ownStart && scripts[FREE_SCRIPT] === FLOW_SCRIPTS[FREE_SCRIPT])
|
|
167
|
+
changes.push(`${KEPT}scripts: ${FREE_SCRIPT}, which skips the repo's own ${START_SCRIPT}: make it run that one with --free, or delete it`);
|
|
159
168
|
for (const [name, command] of Object.entries(FLOW_SCRIPTS)) {
|
|
160
|
-
if (scripts[name] !== undefined)
|
|
169
|
+
if (scripts[name] !== undefined || (name === FREE_SCRIPT && ownStart))
|
|
161
170
|
continue;
|
|
162
171
|
scripts[name] = command;
|
|
163
172
|
changes.push(`scripts: ${name}`);
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/** The repo's Claude settings, relative to the checkout. */
|
|
2
|
+
export declare const REPO_SETTINGS = ".claude/settings.json";
|
|
3
|
+
/** What it takes to put the rules back. */
|
|
4
|
+
export interface Lifted {
|
|
5
|
+
/** The file as it was. */
|
|
6
|
+
original: string;
|
|
7
|
+
/** The file as issue-flow left it, without the rules. */
|
|
8
|
+
lifted: string;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* The settings without `permissions.deny` (and without `permissions` if nothing else is
|
|
12
|
+
* left in it), or null if they have no rules to take out.
|
|
13
|
+
* @param text - The settings file.
|
|
14
|
+
* @returns The new text, or null.
|
|
15
|
+
* @throws SyntaxError if the file is not JSON.
|
|
16
|
+
* @example withoutDeny('{"permissions":{"deny":["Edit(/x)"]}}') // "{}\n"
|
|
17
|
+
*/
|
|
18
|
+
export declare function withoutDeny(text: string): string | null;
|
|
19
|
+
/**
|
|
20
|
+
* The settings with the original's deny rules added back (keeping any the text has).
|
|
21
|
+
* @param text - The settings now.
|
|
22
|
+
* @param original - The settings with the rules.
|
|
23
|
+
* @returns The new text.
|
|
24
|
+
* @throws SyntaxError if either is not JSON.
|
|
25
|
+
* @example withDeny("{}", '{"permissions":{"deny":["Edit(/x)"]}}') // '{\n "permissions": {\n "deny": [\n "Edit(/x)"\n ]\n }\n}\n'
|
|
26
|
+
*/
|
|
27
|
+
export declare function withDeny(text: string, original: string): string;
|
|
28
|
+
/**
|
|
29
|
+
* Takes the `deny` rules out of the worktree's settings and hides the change from git. A
|
|
30
|
+
* note in the shared `.git` keeps the original in case the process dies before it puts
|
|
31
|
+
* them back; the caller keeps what it returns and puts them back from that.
|
|
32
|
+
* @param workDir - The worktree.
|
|
33
|
+
* @param commonDir - The shared `.git`.
|
|
34
|
+
* @param branch - The branch.
|
|
35
|
+
* @returns What happened, for the person, and what to put back (null if nothing was taken).
|
|
36
|
+
* @example liftRepoDeny(wt, "/repo/.git", "feat/42-x").message
|
|
37
|
+
*/
|
|
38
|
+
export declare function liftRepoDeny(workDir: string, commonDir: string, branch: string): {
|
|
39
|
+
message: string;
|
|
40
|
+
saved: Lifted | null;
|
|
41
|
+
};
|
|
42
|
+
/**
|
|
43
|
+
* Puts the rules back in the worktree's settings. From `saved` when the caller kept it (the
|
|
44
|
+
* note is in reach of the session, so it is only trusted after a process that died); else
|
|
45
|
+
* from the branch's note, if there is one. If Claude changed the file meanwhile, the rules
|
|
46
|
+
* go back on top of its change, with a warning to review it.
|
|
47
|
+
* @param commonDir - The shared `.git`.
|
|
48
|
+
* @param branch - The branch.
|
|
49
|
+
* @param workDir - The worktree, never taken from the note.
|
|
50
|
+
* @param saved - What liftRepoDeny returned, or null to read the note.
|
|
51
|
+
* @returns A warning, or null if there was nothing to say.
|
|
52
|
+
* @example restoreRepoDeny("/repo/.git", "feat/42-x", wt, null) // null
|
|
53
|
+
*/
|
|
54
|
+
export declare function restoreRepoDeny(commonDir: string, branch: string, workDir: string, saved: Lifted | null): string | null;
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file `--free --no-repo-deny`: takes the `deny` rules out of the worktree's
|
|
3
|
+
* `.claude/settings.json` while Claude runs, and puts them back when it exits. Claude adds
|
|
4
|
+
* the `deny` of every settings file it loads and `--dangerously-skip-permissions` respects
|
|
5
|
+
* them, so a flag cannot drop them; and `--setting-sources user,local` drops them but also
|
|
6
|
+
* the repo's `CLAUDE.md`, skills, agents and MCP servers. Editing the worktree's own copy
|
|
7
|
+
* keeps all that, and leaves the main checkout and the other worktrees alone.
|
|
8
|
+
*
|
|
9
|
+
* While the rules are out, `git update-index --skip-worktree` hides the change from
|
|
10
|
+
* `git status` and `git add`, so it never lands in a commit. A note in the shared `.git`
|
|
11
|
+
* keeps the original, so a session whose terminal was closed is put back on the next
|
|
12
|
+
* `issue start` of the branch.
|
|
13
|
+
*/
|
|
14
|
+
import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
15
|
+
import { dirname, join } from "node:path";
|
|
16
|
+
import { git } from "./git.js";
|
|
17
|
+
import { noteFile } from "./launch.js";
|
|
18
|
+
/** The repo's Claude settings, relative to the checkout. */
|
|
19
|
+
export const REPO_SETTINGS = ".claude/settings.json";
|
|
20
|
+
/**
|
|
21
|
+
* The settings without `permissions.deny` (and without `permissions` if nothing else is
|
|
22
|
+
* left in it), or null if they have no rules to take out.
|
|
23
|
+
* @param text - The settings file.
|
|
24
|
+
* @returns The new text, or null.
|
|
25
|
+
* @throws SyntaxError if the file is not JSON.
|
|
26
|
+
* @example withoutDeny('{"permissions":{"deny":["Edit(/x)"]}}') // "{}\n"
|
|
27
|
+
*/
|
|
28
|
+
export function withoutDeny(text) {
|
|
29
|
+
const settings = JSON.parse(text);
|
|
30
|
+
const deny = settings.permissions?.deny;
|
|
31
|
+
if (!Array.isArray(deny) || deny.length === 0)
|
|
32
|
+
return null;
|
|
33
|
+
const { deny: _dropped, ...permissions } = settings.permissions ?? {};
|
|
34
|
+
const { permissions: _old, ...rest } = settings;
|
|
35
|
+
const result = Object.keys(permissions).length === 0 ? rest : { ...rest, permissions };
|
|
36
|
+
return `${JSON.stringify(result, null, 2)}\n`;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* The settings with the original's deny rules added back (keeping any the text has).
|
|
40
|
+
* @param text - The settings now.
|
|
41
|
+
* @param original - The settings with the rules.
|
|
42
|
+
* @returns The new text.
|
|
43
|
+
* @throws SyntaxError if either is not JSON.
|
|
44
|
+
* @example withDeny("{}", '{"permissions":{"deny":["Edit(/x)"]}}') // '{\n "permissions": {\n "deny": [\n "Edit(/x)"\n ]\n }\n}\n'
|
|
45
|
+
*/
|
|
46
|
+
export function withDeny(text, original) {
|
|
47
|
+
const settings = JSON.parse(text);
|
|
48
|
+
const deny = JSON.parse(original).permissions?.deny ?? [];
|
|
49
|
+
const now = Array.isArray(settings.permissions?.deny) ? settings.permissions.deny : [];
|
|
50
|
+
const merged = [...deny, ...now.filter((rule) => !deny.includes(rule))];
|
|
51
|
+
return `${JSON.stringify({ ...settings, permissions: { ...settings.permissions, deny: merged } }, null, 2)}\n`;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Takes the `deny` rules out of the worktree's settings and hides the change from git. A
|
|
55
|
+
* note in the shared `.git` keeps the original in case the process dies before it puts
|
|
56
|
+
* them back; the caller keeps what it returns and puts them back from that.
|
|
57
|
+
* @param workDir - The worktree.
|
|
58
|
+
* @param commonDir - The shared `.git`.
|
|
59
|
+
* @param branch - The branch.
|
|
60
|
+
* @returns What happened, for the person, and what to put back (null if nothing was taken).
|
|
61
|
+
* @example liftRepoDeny(wt, "/repo/.git", "feat/42-x").message
|
|
62
|
+
*/
|
|
63
|
+
export function liftRepoDeny(workDir, commonDir, branch) {
|
|
64
|
+
const file = join(workDir, REPO_SETTINGS);
|
|
65
|
+
if (!existsSync(file))
|
|
66
|
+
return { message: `the repo has no ${REPO_SETTINGS}: there are no deny rules to take out.`, saved: null };
|
|
67
|
+
const original = readFileSync(file, "utf8");
|
|
68
|
+
let lifted;
|
|
69
|
+
try {
|
|
70
|
+
lifted = withoutDeny(original);
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
return { message: `${REPO_SETTINGS} is not valid JSON: its deny rules stay.`, saved: null };
|
|
74
|
+
}
|
|
75
|
+
if (lifted === null)
|
|
76
|
+
return { message: `${REPO_SETTINGS} has no deny rules: nothing to take out.`, saved: null };
|
|
77
|
+
const saved = { original, lifted };
|
|
78
|
+
const note = noteFile(commonDir, branch);
|
|
79
|
+
mkdirSync(dirname(note), { recursive: true });
|
|
80
|
+
writeFileSync(note, `${JSON.stringify(saved)}\n`);
|
|
81
|
+
writeFileSync(file, lifted);
|
|
82
|
+
git(workDir, ["update-index", "--skip-worktree", REPO_SETTINGS]);
|
|
83
|
+
return { message: `took out the deny rules of ${REPO_SETTINGS} for this session; they come back when Claude exits.`, saved };
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Puts the rules back in the worktree's settings. From `saved` when the caller kept it (the
|
|
87
|
+
* note is in reach of the session, so it is only trusted after a process that died); else
|
|
88
|
+
* from the branch's note, if there is one. If Claude changed the file meanwhile, the rules
|
|
89
|
+
* go back on top of its change, with a warning to review it.
|
|
90
|
+
* @param commonDir - The shared `.git`.
|
|
91
|
+
* @param branch - The branch.
|
|
92
|
+
* @param workDir - The worktree, never taken from the note.
|
|
93
|
+
* @param saved - What liftRepoDeny returned, or null to read the note.
|
|
94
|
+
* @returns A warning, or null if there was nothing to say.
|
|
95
|
+
* @example restoreRepoDeny("/repo/.git", "feat/42-x", wt, null) // null
|
|
96
|
+
*/
|
|
97
|
+
export function restoreRepoDeny(commonDir, branch, workDir, saved) {
|
|
98
|
+
const note = noteFile(commonDir, branch);
|
|
99
|
+
let from = saved;
|
|
100
|
+
if (from === null) {
|
|
101
|
+
if (!existsSync(note))
|
|
102
|
+
return null;
|
|
103
|
+
try {
|
|
104
|
+
from = JSON.parse(readFileSync(note, "utf8"));
|
|
105
|
+
}
|
|
106
|
+
catch {
|
|
107
|
+
rmSync(note, { force: true });
|
|
108
|
+
return `could not read ${note}; check the deny rules of ${REPO_SETTINGS} with git diff.`;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
rmSync(note, { force: true });
|
|
112
|
+
if (!existsSync(workDir))
|
|
113
|
+
return null;
|
|
114
|
+
const file = join(workDir, REPO_SETTINGS);
|
|
115
|
+
git(workDir, ["update-index", "--no-skip-worktree", REPO_SETTINGS]);
|
|
116
|
+
const now = existsSync(file) ? readFileSync(file, "utf8") : null;
|
|
117
|
+
if (now === from.lifted || now === null) {
|
|
118
|
+
writeFileSync(file, from.original);
|
|
119
|
+
return null;
|
|
120
|
+
}
|
|
121
|
+
try {
|
|
122
|
+
writeFileSync(file, withDeny(now, from.original));
|
|
123
|
+
}
|
|
124
|
+
catch {
|
|
125
|
+
return `Claude left ${REPO_SETTINGS} as invalid JSON, so its deny rules could not be put back: fix it and add them before committing (git diff ${REPO_SETTINGS}).`;
|
|
126
|
+
}
|
|
127
|
+
return `Claude changed ${REPO_SETTINGS} during the session: its deny rules are back on top of that change; review it with git diff ${REPO_SETTINGS}.`;
|
|
128
|
+
}
|
package/dist/usage.d.ts
CHANGED
|
@@ -2,4 +2,13 @@
|
|
|
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 apply [--yes] [--dry-run] [--json] [--recipe <name>,...]\n issue-flow doctor [--json]\n issue-flow labels sync [--update] [--dry-run] [--json] [-R owner/repo]\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,
|
|
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 [--no-repo-deny]] [--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 apply [--yes] [--dry-run] [--json] [--recipe <name>,...]\n issue-flow doctor [--json]\n issue-flow labels sync [--update] [--dry-run] [--json] [-R owner/repo]\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, with no sandbox nor questions with --free, and also without the repo's\ndeny rules with --free --no-repo-deny. --plan prints the branch and the prompt as JSON and\nchanges nothing. issue start --help explains each flag.\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\napply leaves the repo as this version needs it: .agents/flow.json (the first time, with\nthe recipes its files suggest), issue-flow at this version and github-rest and the scripts\nif missing in package.json, and the hooks; it changes no other version, and refuses a newer\nissue-flow or a range. doctor checks them. npx agent-kitchen apply runs apply at the version\nit picks, along with the skills, claude-cloud and claude-repo-guard. init and upgrade still\nrun apply, with a warning, until 0.4.0.\n\nlabels sync makes in GitHub the labels the flow wants (\"labels\" in .agents/flow.json: the\nissue types and P1 to P4 by default) that the repo lacks. Names ignore case and are never\nrenamed nor deleted; --update also fixes the color and description of those that differ.";
|
|
6
|
+
/** Usage text of `issue start`, printed with `issue start --help`. */
|
|
7
|
+
export declare const START_USAGE = "Usage: issue-flow issue start <n> [flags]\n\nStarts issue <n>: the branch (in a worktree, or in this checkout with --branch), the empty\nstart commit, the push, the draft PR and the assignment, after confirming; then it opens\nClaude there.\n\nFlags:\n --dry-run show what it would do and change nothing\n -y, --yes do it without asking\n --branch in this checkout instead of a worktree\n --resume <branch> take that branch of the issue\n --new make another branch, even if the issue has one\n --no-launch leave everything ready and do not open Claude\n --namer claude|title who names the branch when the issue has no slug block\n -R owner/repo another repo than the checkout's\n --plan print the branch and the prompt as JSON, and change nothing\n --cloud open a Claude cloud session that starts the issue there\n --here inside a cloud session, start it in this checkout\n --teleport bring the cloud session noted in the PR to the worktree\n --adopt start it in the worktree Orca or Paseo just made\n -h, --help this help\n\nPermissions of the Claude it opens (one at most):\n (none) auto mode if the repo has claude-repo-guard, else your settings\n --ask-permissions your settings: Claude asks before what they do not allow\n --free no sandbox and no questions, as claude:free\n --no-repo-deny with --free: also without the deny rules of the repo's settings\n\nWhat each one takes away, and what stays:\n (none), --ask-permissions\n take nothing away: the sandbox, the questions your settings ask\n and every deny rule stay.\n --free takes away the sandbox and the questions. Every deny rule stays:\n yours (~/.claude), the repo's (.claude/settings.json) and the\n local ones (.claude/settings.local.json).\n --free --no-repo-deny\n also takes the deny rules out of the worktree's\n .claude/settings.json while Claude runs, and puts them back when it\n exits; git does not see the change meanwhile. Your deny rules and the\n local ones stay, and so do the repo's hooks, attribution, CLAUDE.md,\n skills, agents and MCP servers. If Claude changes that file, the\n rules go back on top of its change, with a warning to review it. If\n the terminal closes first, the next issue start of the issue puts\n them back.\n\nFrom the repo's scripts (issue:start, and issue:start:free, which adds --free):\n npm run issue:start:free 42 npm passes a number on as it is\n npm run issue:start -- 42 --free but keeps the flags for itself, unless after --\n pnpm issue:start 42 --free pnpm, bun and yarn pass everything on\n bun run issue:start 42 --free\n yarn issue:start 42 --free\nA -- does no harm with any of them: issue-flow drops it.\n\nissue:start:free does not go with --ask-permissions, --cloud, --here nor --adopt: use\nissue:start for those. --no-repo-deny goes after it, as any flag:\n npm run issue:start:free -- 42 --no-repo-deny\n pnpm issue:start:free 42 --no-repo-deny";
|
|
8
|
+
/**
|
|
9
|
+
* The usage text for a command: its own if it has one, else the whole CLI's.
|
|
10
|
+
* @param words - The command's words, as given (`["issue", "start", "42"]`).
|
|
11
|
+
* @returns The text.
|
|
12
|
+
* @example usageOf(["issue", "start"]) === START_USAGE
|
|
13
|
+
*/
|
|
14
|
+
export declare function usageOf(words: string[]): string;
|
package/dist/usage.js
CHANGED
|
@@ -6,7 +6,7 @@ export const USAGE = `Usage:
|
|
|
6
6
|
issue-flow issue branch <n>... [--namer claude|title] [--json] [-R owner/repo]
|
|
7
7
|
issue-flow issue branch --missing [--limit <n>] [--namer claude|title] [--dry-run | --yes] [--json] [-R owner/repo]
|
|
8
8
|
issue-flow issue start <n> [--branch] [--dry-run | --yes] [--resume <branch> | --new] [--no-launch]
|
|
9
|
-
[--ask-permissions | --free] [--namer claude|title] [-R owner/repo]
|
|
9
|
+
[--ask-permissions | --free [--no-repo-deny]] [--namer claude|title] [-R owner/repo]
|
|
10
10
|
issue-flow issue start <n> --plan [--ask-permissions | --free] [--namer claude|title] [-R owner/repo]
|
|
11
11
|
issue-flow issue start <n> --cloud | --here | --teleport | --adopt [...]
|
|
12
12
|
issue-flow worktree setup [--only <step>]
|
|
@@ -29,8 +29,9 @@ and the branch from the repo's template (an older "Rama sugerida" block is used
|
|
|
29
29
|
issue start makes the branch in a worktree (or, with --branch, in this checkout), the
|
|
30
30
|
start commit, the push, the draft PR and the assignment, after confirming, and opens
|
|
31
31
|
Claude: in auto mode if the repo has claude-repo-guard, with your settings with
|
|
32
|
-
--ask-permissions,
|
|
33
|
-
and the prompt as JSON and
|
|
32
|
+
--ask-permissions, with no sandbox nor questions with --free, and also without the repo's
|
|
33
|
+
deny rules with --free --no-repo-deny. --plan prints the branch and the prompt as JSON and
|
|
34
|
+
changes nothing. issue start --help explains each flag.
|
|
34
35
|
|
|
35
36
|
--cloud confirms here and opens a Claude cloud session that starts the issue there.
|
|
36
37
|
--here, inside a cloud session, starts it in this checkout and opens nothing. --teleport
|
|
@@ -58,3 +59,72 @@ run apply, with a warning, until 0.4.0.
|
|
|
58
59
|
labels sync makes in GitHub the labels the flow wants ("labels" in .agents/flow.json: the
|
|
59
60
|
issue types and P1 to P4 by default) that the repo lacks. Names ignore case and are never
|
|
60
61
|
renamed nor deleted; --update also fixes the color and description of those that differ.`;
|
|
62
|
+
/** Usage text of `issue start`, printed with `issue start --help`. */
|
|
63
|
+
export const START_USAGE = `Usage: issue-flow issue start <n> [flags]
|
|
64
|
+
|
|
65
|
+
Starts issue <n>: the branch (in a worktree, or in this checkout with --branch), the empty
|
|
66
|
+
start commit, the push, the draft PR and the assignment, after confirming; then it opens
|
|
67
|
+
Claude there.
|
|
68
|
+
|
|
69
|
+
Flags:
|
|
70
|
+
--dry-run show what it would do and change nothing
|
|
71
|
+
-y, --yes do it without asking
|
|
72
|
+
--branch in this checkout instead of a worktree
|
|
73
|
+
--resume <branch> take that branch of the issue
|
|
74
|
+
--new make another branch, even if the issue has one
|
|
75
|
+
--no-launch leave everything ready and do not open Claude
|
|
76
|
+
--namer claude|title who names the branch when the issue has no slug block
|
|
77
|
+
-R owner/repo another repo than the checkout's
|
|
78
|
+
--plan print the branch and the prompt as JSON, and change nothing
|
|
79
|
+
--cloud open a Claude cloud session that starts the issue there
|
|
80
|
+
--here inside a cloud session, start it in this checkout
|
|
81
|
+
--teleport bring the cloud session noted in the PR to the worktree
|
|
82
|
+
--adopt start it in the worktree Orca or Paseo just made
|
|
83
|
+
-h, --help this help
|
|
84
|
+
|
|
85
|
+
Permissions of the Claude it opens (one at most):
|
|
86
|
+
(none) auto mode if the repo has claude-repo-guard, else your settings
|
|
87
|
+
--ask-permissions your settings: Claude asks before what they do not allow
|
|
88
|
+
--free no sandbox and no questions, as claude:free
|
|
89
|
+
--no-repo-deny with --free: also without the deny rules of the repo's settings
|
|
90
|
+
|
|
91
|
+
What each one takes away, and what stays:
|
|
92
|
+
(none), --ask-permissions
|
|
93
|
+
take nothing away: the sandbox, the questions your settings ask
|
|
94
|
+
and every deny rule stay.
|
|
95
|
+
--free takes away the sandbox and the questions. Every deny rule stays:
|
|
96
|
+
yours (~/.claude), the repo's (.claude/settings.json) and the
|
|
97
|
+
local ones (.claude/settings.local.json).
|
|
98
|
+
--free --no-repo-deny
|
|
99
|
+
also takes the deny rules out of the worktree's
|
|
100
|
+
.claude/settings.json while Claude runs, and puts them back when it
|
|
101
|
+
exits; git does not see the change meanwhile. Your deny rules and the
|
|
102
|
+
local ones stay, and so do the repo's hooks, attribution, CLAUDE.md,
|
|
103
|
+
skills, agents and MCP servers. If Claude changes that file, the
|
|
104
|
+
rules go back on top of its change, with a warning to review it. If
|
|
105
|
+
the terminal closes first, the next issue start of the issue puts
|
|
106
|
+
them back.
|
|
107
|
+
|
|
108
|
+
From the repo's scripts (issue:start, and issue:start:free, which adds --free):
|
|
109
|
+
npm run issue:start:free 42 npm passes a number on as it is
|
|
110
|
+
npm run issue:start -- 42 --free but keeps the flags for itself, unless after --
|
|
111
|
+
pnpm issue:start 42 --free pnpm, bun and yarn pass everything on
|
|
112
|
+
bun run issue:start 42 --free
|
|
113
|
+
yarn issue:start 42 --free
|
|
114
|
+
A -- does no harm with any of them: issue-flow drops it.
|
|
115
|
+
|
|
116
|
+
issue:start:free does not go with --ask-permissions, --cloud, --here nor --adopt: use
|
|
117
|
+
issue:start for those. --no-repo-deny goes after it, as any flag:
|
|
118
|
+
npm run issue:start:free -- 42 --no-repo-deny
|
|
119
|
+
pnpm issue:start:free 42 --no-repo-deny`;
|
|
120
|
+
/** The usage text of each command that has its own, by its words. */
|
|
121
|
+
const OWN_USAGE = { "issue start": START_USAGE };
|
|
122
|
+
/**
|
|
123
|
+
* The usage text for a command: its own if it has one, else the whole CLI's.
|
|
124
|
+
* @param words - The command's words, as given (`["issue", "start", "42"]`).
|
|
125
|
+
* @returns The text.
|
|
126
|
+
* @example usageOf(["issue", "start"]) === START_USAGE
|
|
127
|
+
*/
|
|
128
|
+
export function usageOf(words) {
|
|
129
|
+
return OWN_USAGE[words.slice(0, 2).join(" ")] ?? USAGE;
|
|
130
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ramon-sg/issue-flow",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.3",
|
|
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",
|