@devflow-one/cli 0.1.16 → 0.1.17

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
@@ -187,6 +187,12 @@ devflow mr create "Title" feat/x --base release/2026.4 --project ABC
187
187
  devflow mr create "Title" feat/x --type Bugfix --project ABC
188
188
  devflow mr types --project ABC # configured types: id, label, default flag
189
189
 
190
+ # ready for review: also applies the PR type's labels, assigns you when unassigned and
191
+ # requests the type's missing reviewers (additive, best-effort). --type switches the
192
+ # type by swapping its labels; to refresh this later, run `mr draft` then `mr ready` again
193
+ devflow mr ready 42 --project ABC
194
+ devflow mr ready 42 --type Bugfix --project ABC
195
+
190
196
  # issue key: parsed from the head branch by default; --issue sets it explicitly
191
197
  # ({parentWithChildIssueKeys}/{issueKey}/{issueTitle} template tokens) for a branch without one.
192
198
  # {issueTitle} needs the Jira API token set in Settings, else it renders empty.
@@ -200,8 +206,8 @@ devflow mr find ABC-123
200
206
  devflow pipeline status 42 deadbeef --issue ABC-9
201
207
 
202
208
  # commit a managed worktree with the project's commit-message template; it stages tracked
203
- # changes only (add --include-untracked for new files) and pushing needs --force
204
- devflow commit template /wt/ABC-123 --values '{"summary":"fix login"}' --project ABC --force
209
+ # changes only (add --include-untracked for new files); pushing is destructive
210
+ devflow commit template /wt/ABC-123 --values '{"summary":"fix login"}' --project ABC
205
211
  devflow commit template /wt/ABC-123 --no-push --project ABC # commit locally, skip push
206
212
 
207
213
  # read a PR/MR's title + description, then edit it: --title/--description replace,
@@ -225,12 +231,12 @@ devflow issue branches ABC-123 --project ABC
225
231
  devflow issue prs # every state, merged and closed included
226
232
 
227
233
  # review comments: counts, then "Reply as Fixed" or an AI "Fix Comments" run, which
228
- # commits and pushes (so it needs --force). It returns at once: `started`, `prNumber` and
234
+ # commits and pushes (so it is destructive). It returns at once: `started`, `prNumber` and
229
235
  # `queued` (true when an automation run is busy, so the fix starts on its next tick), or
230
236
  # `started: false` with a `reason` — e.g. while the panel or the timer is fixing that PR
231
237
  devflow mr comments 42 # unfixed / fixed / resolved / prComments
232
238
  devflow mr reply-fixed 42 # success / failed / resolved counts
233
- devflow mr fix-comments 42 --force # then re-check the counts
239
+ devflow mr fix-comments 42 # then re-check the counts
234
240
 
235
241
  # notify the project's configured Slack channel(s)
236
242
  devflow slack notify "Review fixes ready on {pr}" --values '{"pr":"#42"}' --project ABC
@@ -250,26 +256,27 @@ devflow mr ready 12 --profile prof-1
250
256
  ### Destructive actions
251
257
 
252
258
  `branch delete`, `mr close`, `mr merge`, `mr fix-comments` (its AI run commits and
253
- pushes) and `commit template` (unless `--no-push`) are destructive. They are **refused
254
- unless you confirm** with `--force` (or `--yes`); the gateway enforces the same
255
- gate server-side, so the confirmation can't be bypassed:
259
+ pushes) and `commit template` (unless `--no-push`) are destructive. By default they
260
+ run without confirmation. Turn on **Require confirmation for destructive CLI/MCP
261
+ actions** in a repo's DevFlow Git settings, and that repo then refuses them with
262
+ `DESTRUCTIVE_BLOCKED` unless you confirm with `--force` (or `--yes`). The extension
263
+ enforces the setting, so it can't be bypassed:
256
264
 
257
265
  ```sh
258
- devflow branch delete old --project ABC # refused
259
- devflow branch delete old --project ABC --force # executes
266
+ devflow branch delete old --project ABC # runs; refused if confirmation is on
267
+ devflow branch delete old --project ABC --force # always runs
260
268
 
261
269
  # merge a PR/MR, squashing and cleaning up the source branch
262
270
  devflow mr merge 42 --method squash --delete-source-branch --force
263
271
  ```
264
272
 
265
- Inference never softens this gate — a destructive command run with fully
266
- inferred routing still refuses without `--force`. Confirmation is always yours
267
- to give explicitly, precisely because inference makes mis-targeting easier.
273
+ Inference never confirms — even with fully inferred routing, only an explicit
274
+ `--force` counts, precisely because inference makes mis-targeting easier.
268
275
 
269
276
  ## Verify
270
277
 
271
278
  ```sh
272
- npm test # unit: arg parsing, command resolution, routing, destructive gate
279
+ npm test # unit: arg parsing, command resolution, routing, destructive confirmation
273
280
  ```
274
281
 
275
282
  ## Releasing
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@devflow-one/cli",
3
- "version": "0.1.16",
3
+ "version": "0.1.17",
4
4
  "description": "Terminal CLI wrapping the local DevFlow gateway so shell scripts can trigger repo actions.",
5
5
  "keywords": [
6
6
  "devflow",
package/src/cli.js CHANGED
@@ -35,9 +35,9 @@ below is an override, applied ahead of anything inferred:
35
35
 
36
36
  Run \`devflow where\` to see what the current directory resolves to.
37
37
 
38
- Destructive actions (branch delete, mr close):
39
- --force, --yes Confirm a destructive action; required or it is refused.
40
- Never inferred — inference makes mis-targeting easier.
38
+ Destructive actions (branch delete, mr close, mr merge, pushing commits):
39
+ --force, --yes Confirm a destructive action. Needed only when confirmation
40
+ is enabled for the repo in DevFlow settings. Never inferred.
41
41
 
42
42
  Output:
43
43
  --json Print machine-readable JSON (envelope) for scripts.
@@ -199,16 +199,8 @@ export async function run(argv, io = {}) {
199
199
 
200
200
  assertRoutable(key, command, context, flags);
201
201
 
202
- // Destructive gate: refuse before touching the gateway unless confirmed.
203
- // Deliberately independent of inference — a confirmed target is never guessed.
204
- const confirmed = Boolean(flags.force || flags.yes);
205
- const destructive = isDestructiveCommand(command, flags);
206
-
207
- if (destructive && !confirmed) {
208
- throw new UsageError(
209
- `"${key}" is destructive — re-run with --force (or --yes) to confirm. Usage: ${command.usage}`,
210
- );
211
- }
202
+ // Only an explicit --force/--yes confirms; inference makes mis-targeting easier.
203
+ const confirmed = Boolean(flags.force || flags.yes) && isDestructiveCommand(command, flags);
212
204
 
213
205
  // The owning profile is known but closed: say so by name rather than letting
214
206
  // the gateway answer with a bare NO_PROFILE.
@@ -220,7 +212,7 @@ export async function run(argv, io = {}) {
220
212
  const args = command.buildArgs ? command.buildArgs(rest, flags, context) : [];
221
213
  const body = {
222
214
  ...buildRoutingBody(context),
223
- ...(destructive ? { allowDestructive: true } : {}),
215
+ ...(confirmed ? { allowDestructive: true } : {}),
224
216
  payload: {
225
217
  projectKey: projectKeyFrom(context),
226
218
  ...(context.repo ? { repo: context.repo } : {}),
package/src/client.js CHANGED
@@ -27,13 +27,18 @@ export class GatewayError extends Error {
27
27
  export const RETRY_HINT =
28
28
  "The action may have completed — verify (e.g. findPrByIssueKey) before retrying.";
29
29
 
30
+ export const DESTRUCTIVE_HINT =
31
+ "Confirmation is enabled in DevFlow settings for this repo — re-run with --force (or --yes).";
32
+
30
33
  /**
31
- * The "verify before retrying" hint for a TIMEOUT of a retry-unsafe action
32
- * that may already have run; undefined when a retry is safe.
34
+ * How to retry a failed call: confirm a DESTRUCTIVE_BLOCKED action, or verify a
35
+ * retry-unsafe TIMEOUT that may already have run; undefined when a retry is safe.
33
36
  * @param {unknown} err
34
37
  * @param {boolean} [retryUnsafe]
35
38
  */
36
39
  export function retryHint(err, retryUnsafe) {
40
+ if (err instanceof GatewayError && err.code === "DESTRUCTIVE_BLOCKED") return DESTRUCTIVE_HINT;
41
+
37
42
  const mayHaveRun =
38
43
  err instanceof GatewayError && err.code === "TIMEOUT" && err.delivered !== false;
39
44
 
package/src/commands.js CHANGED
@@ -123,8 +123,8 @@ export class UsageError extends Error {
123
123
  * @property {"GET" | "POST" | "LOCAL"} method `LOCAL` runs in-process, no action call.
124
124
  * @property {string} [path] For GET commands.
125
125
  * @property {string} [action] Gateway action name for POST commands.
126
- * @property {boolean | ((flags: Record<string, unknown>) => boolean)} [destructive] Requires
127
- * --force/--yes (gated by cli.js); a predicate when that depends on the flags.
126
+ * @property {boolean | ((flags: Record<string, unknown>) => boolean)} [destructive] Accepts
127
+ * --force/--yes as confirmation; a predicate when that depends on the flags.
128
128
  * @property {boolean} [retryUnsafe] A retry may repeat its side effect; a TIMEOUT warns to verify first.
129
129
  * @property {(pos: string[], flags: Record<string, unknown>, context?: object) => unknown[]} [buildArgs]
130
130
  * @property {string} usage One-line usage string.
@@ -182,7 +182,7 @@ const COMMANDS = {
182
182
  retryUnsafe: true,
183
183
  action: "deleteBranch",
184
184
  destructive: true,
185
- usage: "branch delete <branchName> [--issue KEY] [--project KEY] --force",
185
+ usage: "branch delete <branchName> [--issue KEY] [--project KEY] [--force]",
186
186
  buildArgs: ([branchName]) => {
187
187
  if (!branchName) throw new UsageError("branch delete requires <branchName>.");
188
188
 
@@ -198,7 +198,7 @@ const COMMANDS = {
198
198
  destructive: (flags) => !flags["no-push"],
199
199
  usage:
200
200
  'commit template <worktreePath> [--values \'{"key":"val"}\'] [--include-untracked]' +
201
- " [--issue KEY] [--project KEY] (--no-push | --force)" +
201
+ " [--issue KEY] [--project KEY] [--no-push | --force]" +
202
202
  " (stages every modified/deleted tracked file and pushes, refused from the base branch;" +
203
203
  " untracked files are refused unless --include-untracked;" +
204
204
  " --values keys fill the project's commit template, default" +
@@ -280,8 +280,18 @@ const COMMANDS = {
280
280
  "mr ready": {
281
281
  method: "POST",
282
282
  action: "markPrReadyForReview",
283
- usage: "mr ready <prId> [--issue KEY] [--project KEY]",
284
- buildArgs: ([prId]) => [toInt("prId", prId)],
283
+ usage:
284
+ "mr ready <prId> [--type <idOrLabel>] [--issue KEY] [--project KEY]" +
285
+ " (also applies the PR type's labels, assigns you when unassigned and requests the" +
286
+ " type's missing reviewers; --type switches the type by swapping its labels;" +
287
+ " run `mr draft` then `mr ready` again to refresh them)",
288
+ buildArgs: ([prId], flags) => {
289
+ const id = toInt("prId", prId);
290
+ const prType = toText("type", flags.type);
291
+
292
+ // An omitted prType is left out (not sent as null) so the gateway keeps the current type.
293
+ return prType === undefined ? [id] : [id, prType];
294
+ },
285
295
  },
286
296
  "mr draft": {
287
297
  method: "POST",
@@ -294,7 +304,7 @@ const COMMANDS = {
294
304
  retryUnsafe: true,
295
305
  action: "closePullRequest",
296
306
  destructive: true,
297
- usage: "mr close <prId> [--issue KEY] [--project KEY] --force",
307
+ usage: "mr close <prId> [--issue KEY] [--project KEY] [--force]",
298
308
  buildArgs: ([prId]) => [toInt("prId", prId)],
299
309
  },
300
310
  "mr merge": {
@@ -304,7 +314,7 @@ const COMMANDS = {
304
314
  destructive: true,
305
315
  usage:
306
316
  "mr merge <prId> [--method merge|squash|rebase] [--delete-source-branch] " +
307
- "[--issue KEY] [--project KEY] --force",
317
+ "[--issue KEY] [--project KEY] [--force]",
308
318
  buildArgs: ([prId], flags) => {
309
319
  const mergeMethod = flags.method === undefined ? undefined : toMergeMethod(flags.method);
310
320
  const deleteSourceBranch = flags["delete-source-branch"] ? true : undefined;
@@ -322,30 +332,6 @@ const COMMANDS = {
322
332
  return [toInt("prId", prId), assignee];
323
333
  },
324
334
  },
325
- "mr labels": {
326
- method: "POST",
327
- action: "addPrLabels",
328
- usage: "mr labels <prId> <label...> [--issue KEY] [--project KEY]",
329
- buildArgs: ([prId, ...labels]) => {
330
- if (labels.length === 0) {
331
- throw new UsageError("mr labels requires <prId> and at least one <label>.");
332
- }
333
-
334
- return [toInt("prId", prId), labels];
335
- },
336
- },
337
- "mr reviewers": {
338
- method: "POST",
339
- action: "requestPrReviewers",
340
- usage: "mr reviewers <prId> <reviewer...> [--issue KEY] [--project KEY]",
341
- buildArgs: ([prId, ...reviewers]) => {
342
- if (reviewers.length === 0) {
343
- throw new UsageError("mr reviewers requires <prId> and at least one <reviewer>.");
344
- }
345
-
346
- return [toInt("prId", prId), reviewers];
347
- },
348
- },
349
335
  "mr find": {
350
336
  method: "POST",
351
337
  action: "findPrByIssueKey",
@@ -431,7 +417,7 @@ const COMMANDS = {
431
417
  action: "fixPrComments",
432
418
  destructive: true,
433
419
  usage:
434
- "mr fix-comments <prId> [--issue KEY] [--project KEY] --force" +
420
+ "mr fix-comments <prId> [--issue KEY] [--project KEY] [--force]" +
435
421
  " (starts an AI fix of the PR's unresolved review comments, which commits and pushes;" +
436
422
  " returns at once: started / prNumber / queued, or a reason it was refused, e.g. that PR" +
437
423
  " is already being fixed; re-check with `mr comments`)",
@@ -517,7 +503,7 @@ const COMMANDS = {
517
503
  };
518
504
 
519
505
  /**
520
- * Does this invocation of `command` need --force/--yes?
506
+ * Is this invocation of `command` destructive?
521
507
  * @param {Command} command
522
508
  * @param {Record<string, unknown>} flags
523
509
  */