@devflow-one/cli 0.1.9 → 0.1.11

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
@@ -166,9 +166,10 @@ devflow mr create "Title" feat/x --body "desc" --project ABC
166
166
  # pattern is expanded to the newest matching remote branch), or pick one
167
167
  devflow mr create "Title" feat/x --base release/2026.4 --project ABC
168
168
 
169
- # PR type: the project's first configured type by default — it supplies the
170
- # description/title templates and the labels the PR is stamped with. Pass its id
171
- # or its label to pick another; an unrecognized value falls back to that default.
169
+ # PR type: supplies the description/title templates and the labels the PR is
170
+ # stamped with. Omitted or unrecognized, it is auto-selected by matching configured
171
+ # type names against the title's conventional prefix, branch prefix, Jira issue type
172
+ # and title keywords, falling back to the first type. Pass its id or label to pick one.
172
173
  devflow mr create "Title" feat/x --type Bugfix --project ABC
173
174
  devflow mr types --project ABC # configured types: id, label, default flag
174
175
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@devflow-one/cli",
3
- "version": "0.1.9",
3
+ "version": "0.1.11",
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
@@ -4,7 +4,7 @@
4
4
 
5
5
  import { parseArgs } from "./args.js";
6
6
  import { resolveConfig, ConfigError } from "./config.js";
7
- import { gatewayRequest, GatewayError } from "./client.js";
7
+ import { gatewayRequest, GatewayError, retryHint } from "./client.js";
8
8
  import { resolveCommand, listCommands, UsageError } from "./commands.js";
9
9
  import {
10
10
  createContextQuery,
@@ -86,17 +86,21 @@ function printWarnings(result, stderr) {
86
86
  }
87
87
  }
88
88
 
89
- /** Print an error, honoring --json, to stderr. */
90
- function printError(err, json, stderr) {
89
+ /** Print an error (plus an optional retry hint), honoring --json, to stderr. */
90
+ function printError(err, json, stderr, hint) {
91
91
  const code = err instanceof GatewayError ? err.code : err.name;
92
92
 
93
93
  if (json) {
94
- stderr(JSON.stringify({ ok: false, error: { code, message: err.message } }, null, 2));
94
+ const error = { code, message: err.message, delivered: err.delivered, hint };
95
+
96
+ stderr(JSON.stringify({ ok: false, error }, null, 2));
95
97
 
96
98
  return;
97
99
  }
98
100
 
99
101
  stderr(`error${code ? ` [${code}]` : ""}: ${err.message}`);
102
+
103
+ if (hint) stderr(`hint: ${hint}`);
100
104
  }
101
105
 
102
106
  /**
@@ -231,7 +235,7 @@ export async function run(argv, io = {}) {
231
235
  return 0;
232
236
  } catch (err) {
233
237
  if (err instanceof UsageError || err instanceof GatewayError) {
234
- printError(err, Boolean(flags.json), stderr);
238
+ printError(err, Boolean(flags.json), stderr, retryHint(err, command.retryUnsafe));
235
239
 
236
240
  return 1;
237
241
  }
package/src/client.js CHANGED
@@ -9,15 +9,34 @@ export class GatewayError extends Error {
9
9
  * @param {string} [code]
10
10
  * @param {unknown} [result] the envelope's `result`, which some failures still
11
11
  * carry — `/context` reports its ambiguity candidates there.
12
+ * @param {boolean} [delivered] on a TIMEOUT, whether the profile had already
13
+ * picked the action up (undefined from daemons that predate the flag).
12
14
  */
13
- constructor(message, code, result) {
15
+ constructor(message, code, result, delivered) {
14
16
  super(message);
15
17
  this.name = "GatewayError";
16
18
  this.code = code;
17
19
  this.result = result;
20
+ this.delivered = delivered;
18
21
  }
19
22
  }
20
23
 
24
+ export const RETRY_HINT =
25
+ "The action may have completed — verify (e.g. findPrByIssueKey) before retrying.";
26
+
27
+ /**
28
+ * The "verify before retrying" hint for a TIMEOUT of a retry-unsafe action
29
+ * that may already have run; undefined when a retry is safe.
30
+ * @param {unknown} err
31
+ * @param {boolean} [retryUnsafe]
32
+ */
33
+ export function retryHint(err, retryUnsafe) {
34
+ const mayHaveRun =
35
+ err instanceof GatewayError && err.code === "TIMEOUT" && err.delivered !== false;
36
+
37
+ return retryUnsafe && mayHaveRun ? RETRY_HINT : undefined;
38
+ }
39
+
21
40
  /**
22
41
  * Turn a network-level failure into an actionable message. ECONNREFUSED almost
23
42
  * always means the daemon isn't running.
@@ -83,5 +102,6 @@ export async function gatewayRequest(config, method, path, body) {
83
102
  error.message ?? `Gateway error (HTTP ${response.status}).`,
84
103
  error.code,
85
104
  envelope?.result,
105
+ error.delivered,
86
106
  );
87
107
  }
package/src/commands.js CHANGED
@@ -93,6 +93,7 @@ export class UsageError extends Error {
93
93
  * @property {string} [path] For GET commands.
94
94
  * @property {string} [action] Gateway action name for POST commands.
95
95
  * @property {boolean} [destructive] Requires --force/--yes (gated by cli.js).
96
+ * @property {boolean} [retryUnsafe] A retry may repeat its side effect; a TIMEOUT warns to verify first.
96
97
  * @property {(pos: string[], flags: Record<string, unknown>, context?: object) => unknown[]} [buildArgs]
97
98
  * @property {string} usage One-line usage string.
98
99
  */
@@ -118,6 +119,7 @@ const COMMANDS = {
118
119
 
119
120
  "branch create": {
120
121
  method: "POST",
122
+ retryUnsafe: true,
121
123
  action: "createBranch",
122
124
  usage: "branch create <branchName> [--issue KEY] [--project KEY]",
123
125
  buildArgs: ([branchName]) => {
@@ -128,6 +130,7 @@ const COMMANDS = {
128
130
  },
129
131
  "branch delete": {
130
132
  method: "POST",
133
+ retryUnsafe: true,
131
134
  action: "deleteBranch",
132
135
  destructive: true,
133
136
  usage: "branch delete <branchName> [--issue KEY] [--project KEY] --force",
@@ -140,6 +143,7 @@ const COMMANDS = {
140
143
 
141
144
  "commit template": {
142
145
  method: "POST",
146
+ retryUnsafe: true,
143
147
  action: "commitByTemplate",
144
148
  usage:
145
149
  'commit template <worktreePath> [--values \'{"key":"val"}\'] [--no-push] [--issue KEY] [--project KEY]' +
@@ -159,12 +163,14 @@ const COMMANDS = {
159
163
 
160
164
  "mr create": {
161
165
  method: "POST",
166
+ retryUnsafe: true,
162
167
  action: "createPullRequest",
163
168
  usage:
164
169
  "mr create <title> <headBranch> [--body <text>] [--base <branch>] [--type <idOrLabel>]" +
165
170
  " [--issue KEY] [--project KEY]" +
166
171
  " (--base defaults to the repo's configured base branch, expanding a {year}/{number} pattern;" +
167
- " --type defaults to the project's first configured PR type)",
172
+ " --type is auto-selected by matching PR type names against the title prefix, branch prefix," +
173
+ " Jira issue type and title keywords, falling back to the first configured type)",
168
174
  buildArgs: ([title, headBranch], flags) => {
169
175
  if (!title || !headBranch) {
170
176
  throw new UsageError("mr create requires <title> and <headBranch>.");
@@ -198,6 +204,7 @@ const COMMANDS = {
198
204
  },
199
205
  "mr close": {
200
206
  method: "POST",
207
+ retryUnsafe: true,
201
208
  action: "closePullRequest",
202
209
  destructive: true,
203
210
  usage: "mr close <prId> [--issue KEY] [--project KEY] --force",
@@ -205,6 +212,7 @@ const COMMANDS = {
205
212
  },
206
213
  "mr merge": {
207
214
  method: "POST",
215
+ retryUnsafe: true,
208
216
  action: "mergePullRequest",
209
217
  destructive: true,
210
218
  usage:
@@ -308,6 +316,7 @@ const COMMANDS = {
308
316
  },
309
317
  "pipeline retry": {
310
318
  method: "POST",
319
+ retryUnsafe: true,
311
320
  action: "restartFailedJobs",
312
321
  usage: "pipeline retry <pipelineOrRunId> [--all] [--job <jobId>] [--issue KEY] [--project KEY]",
313
322
  buildArgs: ([id], flags) => {
@@ -328,6 +337,7 @@ const COMMANDS = {
328
337
 
329
338
  "slack notify": {
330
339
  method: "POST",
340
+ retryUnsafe: true,
331
341
  action: "notifySlack",
332
342
  usage:
333
343
  'slack notify <message> [--channel ID] [--values \'{"key":"val"}\'] [--issue KEY] [--project KEY]',