refacil-sdd-ai 4.5.5 → 4.5.8

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
@@ -153,7 +153,7 @@ All invoked as `/refacil:<name>` in Claude Code, Cursor, or OpenCode.
153
153
  | `/refacil:test` | Generate unit tests from the artifacts |
154
154
  | `/refacil:verify` | Validate implementation vs specs (with optional autofix) |
155
155
  | `/refacil:review` | Quality checklist, emits `.review-passed` if approved |
156
- | `/refacil:archive` | Archive the completed change + sync specs (requests Jira links) |
156
+ | `/refacil:archive` | Archive the completed change + sync specs (requests task references) |
157
157
  | `/refacil:up-code` | Commit + push + PR (runs review if missing) |
158
158
  | `/refacil:bug` | Full bugfix flow with regression tests |
159
159
  | `/refacil:update` | Detect and apply pending methodology migrations to the current repo |
@@ -259,7 +259,7 @@ From there, the full cycle is:
259
259
  - For features/improvements: the CLI moves artifacts to `archive/` and extracts `.review-passed` fields to `review.yaml` inside each affected spec.
260
260
  - For bugs: manual archiving, creates `refacil-sdd/specs/fix-*/spec.md` in standard format + `review.yaml`.
261
261
  - A single branch can accumulate multiple bugs, each in its own independent `fix-*/` folder.
262
- - `/refacil:archive` always requests one or more **Jira links** associated with the change before proceeding. Links are stored in `review.yaml` under the `jiraTasks` field (YAML list). This field is mandatory — archiving does not proceed until the user provides at least one link.
262
+ - `/refacil:archive` always requests one or more **task references** associated with the change before proceeding. Accepted formats: URL, ticket/issue identifier, or task name. References are stored in `review.yaml` under the `taskReferences` field (YAML list). This field is mandatory — archiving does not proceed until the user provides at least one reference.
263
263
 
264
264
  ---
265
265
 
@@ -62,6 +62,47 @@ function validateChangeName(name) {
62
62
  return { valid: true };
63
63
  }
64
64
 
65
+ function resolveExistingChangeName(projectRoot, inputName) {
66
+ if (!inputName || typeof inputName !== 'string') {
67
+ return { ok: false, reason: 'El nombre del cambio no puede estar vacío.' };
68
+ }
69
+
70
+ const normalizedInput = inputName.trim();
71
+ const lowerInput = normalizedInput.toLowerCase();
72
+ const changesDir = path.join(projectRoot, 'refacil-sdd', 'changes');
73
+
74
+ // Keep backward-compatible behavior when directory doesn't exist yet.
75
+ if (!fs.existsSync(changesDir)) {
76
+ return { ok: true, name: lowerInput };
77
+ }
78
+
79
+ const entries = fs.readdirSync(changesDir, { withFileTypes: true })
80
+ .filter((e) => e.isDirectory() && e.name !== 'archive')
81
+ .map((e) => e.name);
82
+
83
+ if (entries.includes(normalizedInput)) {
84
+ return { ok: true, name: normalizedInput };
85
+ }
86
+
87
+ if (entries.includes(lowerInput)) {
88
+ return { ok: true, name: lowerInput };
89
+ }
90
+
91
+ const ciMatches = entries.filter((n) => n.toLowerCase() === lowerInput);
92
+ if (ciMatches.length === 1) {
93
+ return { ok: true, name: ciMatches[0] };
94
+ }
95
+
96
+ if (ciMatches.length > 1) {
97
+ return {
98
+ ok: false,
99
+ reason: `Nombre de cambio ambiguo: '${inputName}'. Coincidencias: ${ciMatches.join(', ')}`,
100
+ };
101
+ }
102
+
103
+ return { ok: true, name: lowerInput };
104
+ }
105
+
65
106
  function autoMigrateOpenspec(root) {
66
107
  const oldDir = path.join(root, 'openspec');
67
108
  const newDir = path.join(root, 'refacil-sdd');
@@ -180,7 +221,15 @@ function cmdNewChange(argv, projectRoot) {
180
221
 
181
222
  function cmdArchive(argv, projectRoot) {
182
223
  const args = parseArgs(argv);
183
- const name = args._positional[0];
224
+ const rawName = args._positional[0];
225
+
226
+ autoMigrateOpenspec(projectRoot);
227
+ const resolved = resolveExistingChangeName(projectRoot, rawName);
228
+ if (!resolved.ok) {
229
+ console.error(resolved.reason);
230
+ process.exit(1);
231
+ }
232
+ const name = resolved.name;
184
233
 
185
234
  const validation = validateChangeName(name);
186
235
  if (!validation.valid) {
@@ -188,8 +237,6 @@ function cmdArchive(argv, projectRoot) {
188
237
  process.exit(1);
189
238
  }
190
239
 
191
- autoMigrateOpenspec(projectRoot);
192
-
193
240
  const sourceDir = path.join(projectRoot, 'refacil-sdd', 'changes', name);
194
241
  if (!fs.existsSync(sourceDir)) {
195
242
  console.error(`No existe el cambio '${name}' en refacil-sdd/changes/${name}/`);
@@ -222,14 +269,21 @@ function cmdArchive(argv, projectRoot) {
222
269
 
223
270
  function cmdSetMemory(argv, projectRoot) {
224
271
  const args = parseArgs(argv);
225
- const name = args._positional[0];
272
+ const rawName = args._positional[0];
226
273
 
227
- if (!name) {
274
+ if (!rawName) {
228
275
  console.error('Uso: refacil-sdd-ai sdd set-memory <nombre-cambio> [--last-step <value>] [--stack-detected <value>] [--touched-files <csv>] [--commands-run <value>] [--criteria-run <csv>]');
229
276
  process.exit(1);
230
277
  }
231
278
 
232
279
  const root = projectRoot;
280
+ autoMigrateOpenspec(root);
281
+ const resolved = resolveExistingChangeName(root, rawName);
282
+ if (!resolved.ok) {
283
+ console.error(resolved.reason);
284
+ process.exit(1);
285
+ }
286
+ const name = resolved.name;
233
287
 
234
288
  // Guard: ensure the change directory exists before any file operation
235
289
  const changeDir = path.join(root, 'refacil-sdd', 'changes', name);
@@ -274,15 +328,22 @@ function cmdSetMemory(argv, projectRoot) {
274
328
 
275
329
  function cmdGetMemory(argv, projectRoot) {
276
330
  const args = parseArgs(argv);
277
- const name = args._positional[0];
331
+ const rawName = args._positional[0];
278
332
  const wantJson = args.json === true;
279
333
 
280
- if (!name) {
334
+ if (!rawName) {
281
335
  console.error('Uso: refacil-sdd-ai sdd get-memory <nombre-cambio> [--json]');
282
336
  process.exit(1);
283
337
  }
284
338
 
285
339
  const root = projectRoot;
340
+ autoMigrateOpenspec(root);
341
+ const resolved = resolveExistingChangeName(root, rawName);
342
+ if (!resolved.ok) {
343
+ console.error(resolved.reason);
344
+ process.exit(1);
345
+ }
346
+ const name = resolved.name;
286
347
  const memoryPath = path.join(root, 'refacil-sdd', 'changes', name, 'memory.yaml');
287
348
 
288
349
  if (!fs.existsSync(memoryPath)) {
@@ -309,14 +370,21 @@ function cmdGetMemory(argv, projectRoot) {
309
370
 
310
371
  function cmdSetReviewFails(argv, projectRoot) {
311
372
  const args = parseArgs(argv);
312
- const name = args._positional[0];
373
+ const rawName = args._positional[0];
313
374
 
314
- if (!name) {
375
+ if (!rawName) {
315
376
  console.error('Uso: refacil-sdd-ai sdd set-review-fails <nombre-cambio> --files <csv>');
316
377
  process.exit(1);
317
378
  }
318
379
 
319
380
  const root = projectRoot;
381
+ autoMigrateOpenspec(root);
382
+ const resolved = resolveExistingChangeName(root, rawName);
383
+ if (!resolved.ok) {
384
+ console.error(resolved.reason);
385
+ process.exit(1);
386
+ }
387
+ const name = resolved.name;
320
388
  const changeDir = path.join(root, 'refacil-sdd', 'changes', name);
321
389
  if (!fs.existsSync(changeDir)) {
322
390
  console.error(`No existe el cambio '${name}' en refacil-sdd/changes/${name}/`);
@@ -334,14 +402,21 @@ function cmdSetReviewFails(argv, projectRoot) {
334
402
 
335
403
  function cmdClearReviewFails(argv, projectRoot) {
336
404
  const args = parseArgs(argv);
337
- const name = args._positional[0];
405
+ const rawName = args._positional[0];
338
406
 
339
- if (!name) {
407
+ if (!rawName) {
340
408
  console.error('Uso: refacil-sdd-ai sdd clear-review-fails <nombre-cambio>');
341
409
  process.exit(1);
342
410
  }
343
411
 
344
412
  const root = projectRoot;
413
+ autoMigrateOpenspec(root);
414
+ const resolved = resolveExistingChangeName(root, rawName);
415
+ if (!resolved.ok) {
416
+ console.error(resolved.reason);
417
+ process.exit(1);
418
+ }
419
+ const name = resolved.name;
345
420
  const reviewFailsPath = path.join(root, 'refacil-sdd', 'changes', name, '.review-last-fails.json');
346
421
 
347
422
  if (fs.existsSync(reviewFailsPath)) {
@@ -392,15 +467,21 @@ function cmdList(argv, projectRoot) {
392
467
 
393
468
  function cmdStatus(argv, projectRoot) {
394
469
  const args = parseArgs(argv);
395
- const name = args._positional[0];
470
+ const rawName = args._positional[0];
396
471
  const wantJson = args.json === true;
397
472
 
398
- if (!name) {
473
+ if (!rawName) {
399
474
  console.error('Uso: refacil-sdd-ai sdd status <nombre-cambio> [--json]');
400
475
  process.exit(1);
401
476
  }
402
477
 
403
478
  autoMigrateOpenspec(projectRoot);
479
+ const resolved = resolveExistingChangeName(projectRoot, rawName);
480
+ if (!resolved.ok) {
481
+ console.error(resolved.reason);
482
+ process.exit(1);
483
+ }
484
+ const name = resolved.name;
404
485
 
405
486
  const changeDir = path.join(projectRoot, 'refacil-sdd', 'changes', name);
406
487
  if (!fs.existsSync(changeDir)) {
@@ -481,9 +562,9 @@ function cmdStatus(argv, projectRoot) {
481
562
 
482
563
  function cmdMarkReviewed(argv, projectRoot) {
483
564
  const args = parseArgs(argv);
484
- const name = args._positional[0];
565
+ const rawName = args._positional[0];
485
566
 
486
- if (!name) {
567
+ if (!rawName) {
487
568
  console.error('Uso: refacil-sdd-ai sdd mark-reviewed <nombre-cambio> --verdict <verdict> --summary "<resumen>" [--fail-count N] [--preexisting-count N] [--blockers]');
488
569
  process.exit(1);
489
570
  }
@@ -499,6 +580,12 @@ function cmdMarkReviewed(argv, projectRoot) {
499
580
  }
500
581
 
501
582
  autoMigrateOpenspec(projectRoot);
583
+ const resolved = resolveExistingChangeName(projectRoot, rawName);
584
+ if (!resolved.ok) {
585
+ console.error(resolved.reason);
586
+ process.exit(1);
587
+ }
588
+ const name = resolved.name;
502
589
 
503
590
  const changeDir = path.join(projectRoot, 'refacil-sdd', 'changes', name);
504
591
  if (!fs.existsSync(changeDir)) {
@@ -522,9 +609,9 @@ function cmdMarkReviewed(argv, projectRoot) {
522
609
 
523
610
  function cmdTasksUpdate(argv, projectRoot) {
524
611
  const args = parseArgs(argv);
525
- const name = args._positional[0];
612
+ const rawName = args._positional[0];
526
613
 
527
- if (!name) {
614
+ if (!rawName) {
528
615
  console.error('Uso: refacil-sdd-ai sdd tasks-update <nombre-cambio> --task N --done');
529
616
  process.exit(1);
530
617
  }
@@ -541,6 +628,12 @@ function cmdTasksUpdate(argv, projectRoot) {
541
628
  }
542
629
 
543
630
  autoMigrateOpenspec(projectRoot);
631
+ const resolved = resolveExistingChangeName(projectRoot, rawName);
632
+ if (!resolved.ok) {
633
+ console.error(resolved.reason);
634
+ process.exit(1);
635
+ }
636
+ const name = resolved.name;
544
637
 
545
638
  const tasksFile = path.join(projectRoot, 'refacil-sdd', 'changes', name, 'tasks.md');
546
639
  if (!fs.existsSync(tasksFile)) {
@@ -661,4 +754,4 @@ function handleSdd(sub, argv, projectRoot) {
661
754
  }
662
755
  }
663
756
 
664
- module.exports = { handleSdd, parseArgs, autoMigrateOpenspec, validateChangeName, findProjectRoot };
757
+ module.exports = { handleSdd, parseArgs, autoMigrateOpenspec, validateChangeName, resolveExistingChangeName, findProjectRoot };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "refacil-sdd-ai",
3
- "version": "4.5.5",
3
+ "version": "4.5.8",
4
4
  "description": "SDD-AI: Specification-Driven Development with AI — development methodology using AI with Claude Code, Cursor and OpenCode",
5
5
  "bin": {
6
6
  "refacil-sdd-ai": "./bin/cli.js"
@@ -22,7 +22,7 @@ Verify the change is truly complete:
22
22
 
23
23
  2. **Tests pass**: Resolve and run the test command according to `refacil-prereqs/METHODOLOGY-CONTRACT.md`. If there are failing tests, inform and ask if they want to continue.
24
24
 
25
- 3. **No pending files**: Run `git status` and verify if there are uncommitted changes related to the feature. If there are, suggest committing before archiving.
25
+ 3. **Working tree scope hygiene**: Run `git status` and check whether there are files unrelated to the current change scope. It is expected to have uncommitted changes in this step. If unrelated files are detected, warn the user and ask whether to continue archiving anyway. Do not suggest commit in this step; commit/push decisions are handled in `refacil:up-code`.
26
26
 
27
27
  4. **Review approved (blocking)**: Verify that the `.review-passed` file exists in the change folder (`refacil-sdd/changes/[change-name]/.review-passed`) following **`METHODOLOGY-CONTRACT.md` §8** (dotfile; do not conclude by listings without dotfiles). If it does NOT exist, **stop the archiving** and inform the user:
28
28
  ```
@@ -34,23 +34,33 @@ Verify the change is truly complete:
34
34
  If any of checks 1-3 fail, inform the user but allow them to decide whether to continue.
35
35
  If check 4 fails, archiving cannot continue.
36
36
 
37
- ### Step 1.5: Request Jira links (traceability — mandatory)
37
+ ### Step 1.5: Request task reference(s) (traceability — mandatory)
38
38
 
39
- Before proceeding to archiving, ask the user for the Jira links associated with the change:
39
+ Before proceeding to archiving, ask the user for the task reference(s) associated with the change (URL, issue/ticket number, or short task name):
40
40
 
41
41
  ```
42
- Jira link(s) associated with this change (if multiple, separate with commas):
42
+ Task reference(s) associated with this change (URL, ticket number, or task name; if multiple, separate with commas):
43
43
  ```
44
44
 
45
45
  **Rules:**
46
- - The user may enter one or multiple links separated by commas in a single message.
47
- - If the user provides no link (answers empty, "n", "no", "none", blank Enter), **block the archiving** and ask again:
46
+ - The user may enter one or multiple references separated by commas in a single message.
47
+ - Accepted formats: URL (`https://tracker.company.com/TASK-123`), identifier (`TASK-123`, `INC-9001`), or short descriptive name (`ajuste checkout`).
48
+ - Minimum validation rule for each reference (operational and mandatory):
49
+ - `URL`: starts with `http://` or `https://` and has at least one non-space character after the protocol.
50
+ - `identifier`: matches `^[A-Za-z][A-Za-z0-9_-]*-\d+$` (examples: `BP-4610`, `INC-9001`).
51
+ - `short name`: 3-80 characters, includes at least one letter, and is not only symbols/spaces.
52
+ - If the user provides no reference (answers empty, "n", "no", "none", blank Enter), **block the archiving** and ask again:
48
53
  ```
49
- Cannot archive without at least one Jira link.
50
- Provide the link to the task that originated this change to continue.
54
+ Cannot archive without at least one task reference.
55
+ Provide the task URL, identifier, or name that originated this change to continue.
51
56
  ```
52
- - Repeat until at least one valid URL is received.
53
- - Save the links in `jiraTasks` to use when writing `review.yaml` in the following steps.
57
+ - If the user provides a non-empty but invalid value (for example: `---`, `???`, `123`, `_`), **reject it** and ask again:
58
+ ```
59
+ Invalid task reference format.
60
+ Use one of: URL (https://...), identifier (ABC-123), or short task name (3-80 chars, includes letters).
61
+ ```
62
+ - Repeat until at least one valid reference is received.
63
+ - Save the references in `taskReferences` to use when writing `review.yaml` in the following steps.
54
64
 
55
65
  ### Step 2: Determine change type
56
66
 
@@ -75,7 +85,7 @@ Bug fixes only contain `summary.md` (and optionally `.review-passed`). The CLI `
75
85
 
76
86
  2. **Document in specs**: using the content read in step 0, create an individual spec at `$(git rev-parse --show-toplevel)/refacil-sdd/specs/[descriptive-name]/spec.md`.
77
87
 
78
- **Spec folder name**: Use a short, clear kebab-case description of the bug (e.g. `fix-session-timeout-redis`, `fix-null-pointer-payment-callback`). **Do NOT use ticket IDs** (REF-123, JIRA-456, etc.) — the name must be descriptive so `/refacil:explore` can find and understand the fix without external context.
88
+ **Spec folder name**: Use a short, clear kebab-case description of the bug (e.g. `fix-session-timeout-redis`, `fix-null-pointer-payment-callback`). **Do NOT use ticket IDs** (REF-123, TASK-456, etc.) — the name must be descriptive so `/refacil:explore` can find and understand the fix without external context.
79
89
 
80
90
  Content of `spec.md` (Refacil SDD spec layout):
81
91
  ```markdown
@@ -97,7 +107,7 @@ Bug fixes only contain `summary.md` (and optionally `.review-passed`). The CLI `
97
107
  - **THEN** the system SHALL [normal behavior without regression]
98
108
  ```
99
109
 
100
- 3. **Persist review metadata separately**: create `$(git rev-parse --show-toplevel)/refacil-sdd/specs/[descriptive-name]/review.yaml` with the fields from `.review-passed` plus the Jira links from Step 1.5:
110
+ 3. **Persist review metadata separately**: create `$(git rev-parse --show-toplevel)/refacil-sdd/specs/[descriptive-name]/review.yaml` with the fields from `.review-passed` plus the task references from Step 1.5:
101
111
  ```yaml
102
112
  verdict: APROBADO|APROBADO CON OBSERVACIONES
103
113
  date: 2026-04-10T00:00:00.000Z
@@ -105,8 +115,9 @@ Bug fixes only contain `summary.md` (and optionally `.review-passed`). The CLI `
105
115
  summary: "..."
106
116
  failCount: 0
107
117
  blockers: false
108
- jiraTasks:
109
- - https://your-company.atlassian.net/browse/REF-123
118
+ taskReferences:
119
+ - https://tracker.company.com/TASK-123
120
+ - TASK-123
110
121
  ```
111
122
 
112
123
  4. Continue to **Step 3**.
@@ -115,6 +126,8 @@ Bug fixes only contain `summary.md` (and optionally `.review-passed`). The CLI `
115
126
 
116
127
  The spec and review evidence are written **before** running the CLI archive command, while the artifacts are still at their original paths. The CLI only moves the folder — it never syncs specs.
117
128
 
129
+ `refacil-sdd-ai sdd archive` normalizes the provided `changeName` to lowercase before validation/path resolution. Prefer lowercase names for consistency across commands and records.
130
+
118
131
  1. **Sync spec to `refacil-sdd/specs/` (before archiving)**:
119
132
  - Read `refacil-sdd/changes/<changeName>/specs.md` (and all `.md` under `specs/` if that subfolder exists).
120
133
  - Determine the spec folder name: use `<changeName>` unless a more descriptive name is clearly better.
@@ -123,7 +136,7 @@ The spec and review evidence are written **before** running the CLI archive comm
123
136
 
124
137
  2. **Persist review evidence (before archiving)**:
125
138
  - Read `refacil-sdd/changes/<changeName>/.review-passed` (dotfile — use `ls -la` or read by explicit path, not directory listing).
126
- - Create/update `refacil-sdd/specs/<specName>/review.yaml` with its fields plus `jiraTasks` from Step 1.5:
139
+ - Create/update `refacil-sdd/specs/<specName>/review.yaml` with its fields plus `taskReferences` from Step 1.5:
127
140
  ```yaml
128
141
  verdict: APROBADO|APROBADO CON OBSERVACIONES
129
142
  date: 2026-04-10T00:00:00.000Z
@@ -131,8 +144,9 @@ The spec and review evidence are written **before** running the CLI archive comm
131
144
  summary: "..."
132
145
  failCount: 0
133
146
  blockers: false
134
- jiraTasks:
135
- - https://your-company.atlassian.net/browse/REF-123
147
+ taskReferences:
148
+ - https://tracker.company.com/TASK-123
149
+ - TASK-123
136
150
  ```
137
151
  - If `review.yaml` already exists, update only the fields that changed without removing others.
138
152
 
@@ -127,6 +127,8 @@ If the user does not request detail, use concise mode.
127
127
 
128
128
  - `archive` requires `.review-passed` as a blocking precondition (verify existence according to **§8**).
129
129
  - When archiving regular changes (proposal-driven flow), the `.review-passed` metadata must be persisted in `refacil-sdd/specs/`.
130
+ - `archive` must request and persist at least one task reference for traceability. Accepted formats: URL, ticket/issue identifier, or short task name.
131
+ - The recommended field in `review.yaml` is `taskReferences` (YAML list). Do not enforce provider-specific fields such as `jiraTasks`.
130
132
  - The recommended format is `review.yaml` inside each affected spec folder.
131
133
  - If it cannot be reliably mapped to specific specs, record the evidence in `refacil-sdd/specs/review-metadata.yaml`.
132
134
 
@@ -83,15 +83,17 @@ Run `git push -u origin [current-branch]` to push the changes.
83
83
  Which branch do you want to create the PR to? (recommended: testing)
84
84
  ```
85
85
 
86
- If the user indicates a different branch than `testing`, verify it exists on the remote with `git branch -r | grep <indicated-branch>` before generating the link. If it does not exist, inform the user and ask them to confirm or correct the name.
86
+ If the user indicates a different branch than `testing`, verify it exists on the remote by inspecting `git branch -r` output before generating the link. If it does not exist, inform the user and ask them to confirm or correct the name.
87
87
 
88
- 3. Get the remote repository URL with `git remote get-url origin` and determine the hosting to generate the correct link:
88
+ 3. Get the remote repository URL with `git remote get-url origin` and detect the VCS hosting used by this repository to generate the correct PR/MR link:
89
89
  - **GitHub** (url contains `github.com`): `https://github.com/[owner]/[repo]/compare/[target-branch]...[current-branch]?expand=1`
90
- - **Bitbucket** (url contains `bitbucket.org`): `https://bitbucket.org/[workspace]/[repo]/pull-requests/new?source=[current-branch]&dest=[target-branch]`
91
- - If hosting cannot be determined, show both formats for the user to choose.
92
- - Note: for SSH URLs (`git@github.com:owner/repo.git` or `git@bitbucket.org:workspace/repo.git`), extract owner/workspace and repo from the path after `:`.
90
+ - **Bitbucket Cloud** (url contains `bitbucket.org`): `https://bitbucket.org/[workspace]/[repo]/pull-requests/new?source=[current-branch]&dest=[target-branch]`
91
+ - **GitLab** (url contains `gitlab.` or `gitlab.com`): `https://[gitlab-host]/[group]/[repo]/-/merge_requests/new?merge_request[source_branch]=[current-branch]&merge_request[target_branch]=[target-branch]`
92
+ - **Azure DevOps** (url contains `dev.azure.com` or `visualstudio.com`): build the "create PR" URL for the detected project/repo using source and target branches.
93
+ - For SSH remotes (`git@host:group/repo.git`), extract host/namespace/repo from the segment after `:`.
94
+ - If hosting cannot be determined, do not assume a provider: show the detected remote URL and ask the user which platform is used before generating the final PR/MR link.
93
95
 
94
- 4. Show the link to the user and recommend PR to `testing`:
96
+ 4. Show the generated link (provider-specific) to the user and recommend PR to `testing`:
95
97
  ```
96
98
  Create your PR here: [link]
97
99