@detergent-software/atk 8.9.0-dev.21 → 8.9.0-dev.23

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.
@@ -1,16 +1,16 @@
1
1
  import { writeFile } from 'node:fs/promises';
2
2
  import { join, resolve } from 'node:path';
3
3
  import { compare as semverCompare, valid as semverValid } from 'semver';
4
- import { loadAllAdapters } from './adapter.js';
4
+ import { loadAllAdapters, resolveInstalledPaths } from './adapter.js';
5
5
  import { isApiError } from './api-client.js';
6
6
  import { getCredential } from './auth.js';
7
7
  import { errorGuidance, errorMessage } from './errors.js';
8
- import { detectCompatibleTools, getSignedInUserName } from './init.js';
8
+ import { detectCompatibleTools, getEntrypointName, getSignedInUserName } from './init.js';
9
9
  import { findInstalledAsset, readLockfile } from './lockfile.js';
10
10
  import { diffManifests, fetchExistingManifest } from './manifest-diff.js';
11
11
  import { parseOrgFromName } from './org.js';
12
12
  import { findProjectRoot } from './paths.js';
13
- import { buildPublishPlan, buildStagingArea, bumpVersion, checkRegistryVersion, detectAssetFromPath, detectPublishType, executePublish, findSiblingDependencies, isOrgScopedTarget, loadPublishBatch, loadPublishEntries, mapPublishError, missingDependencies, resolvePlanDependencies, unknownToolsError, updateManifestField, updateManifestVersion, validatePublishTarget, } from './publisher.js';
13
+ import { buildPublishPlan, buildStagingArea, bumpVersion, checkRegistryVersion, detectAssetFromPath, detectPublishType, executePublish, filledManifestFields, findSiblingDependencies, isOrgScopedTarget, loadPublishBatch, loadPublishEntries, mapPublishError, missingDependencies, readManifestFile, resolvePlanDependencies, unknownToolsError, updateManifestField, updateManifestVersion, validatePublishTarget, } from './publisher.js';
14
14
  import { clearCache, fetchAssetFileBytes, fetchAssetReadme, fetchRegistry, findAsset } from './registry.js';
15
15
  import { parseReviewersInput, validateGithubUsername } from './reviewers.js';
16
16
  import { stageInstalledFiles } from './staging.js';
@@ -162,6 +162,64 @@ async function firstFailure(edits) {
162
162
  }
163
163
  return undefined;
164
164
  }
165
+ /**
166
+ * The metadata step of a raw publish, for a raw folder or an installed asset, whose `source`
167
+ * gives its org and any description of its own: the signed-in author, the description from
168
+ * --description or the source, the tags from --tags, and the tools from --tools or detection.
169
+ * An interactive run goes on to ask for what is missing; a non-interactive one stages the
170
+ * manifest now, or fails without a description or tools.
171
+ */
172
+ async function gatherRawMetadata(state, ctx, detection, source) {
173
+ const [signedIn, tools] = await Promise.all([
174
+ getSignedInUserName(),
175
+ state.flags.toolsFlag ? splitList(state.flags.toolsFlag) : detectCompatibleTools(detection.assetType),
176
+ ]);
177
+ if (ctx.cancelled())
178
+ return;
179
+ const author = signedIn ?? '';
180
+ const description = state.flags.descriptionFlag?.trim() || source.description;
181
+ if (!state.nonInteractive) {
182
+ ctx.dispatch({
183
+ authorValue: author,
184
+ ...(description ? { description } : {}),
185
+ orgValue: source.org ?? '',
186
+ rawDetection: detection,
187
+ toolsDetected: tools,
188
+ type: 'RAW_DETECT_DONE',
189
+ });
190
+ return;
191
+ }
192
+ if (!description) {
193
+ ctx.dispatch({
194
+ message: state.flags.fromInstalled
195
+ ? `description required: provide --description (the lockfile has none for '${detection.name}')`
196
+ : 'description required: provide --description or include in manifest.json',
197
+ type: 'ERROR',
198
+ });
199
+ return;
200
+ }
201
+ if (tools.length === 0) {
202
+ ctx.dispatch({
203
+ message: `--tools <comma-separated> required: no tools detected for type ${detection.assetType}`,
204
+ type: 'ERROR',
205
+ });
206
+ return;
207
+ }
208
+ const stagingDir = await buildStagingArea(detection, {
209
+ author,
210
+ description,
211
+ ...(source.org ? { org: source.org } : {}),
212
+ tags: splitList(state.flags.tagsFlag),
213
+ tools,
214
+ });
215
+ ctx.stage(stagingDir);
216
+ if (ctx.cancelled())
217
+ return;
218
+ const detected = await detectPublishType(stagingDir);
219
+ if (ctx.cancelled())
220
+ return;
221
+ ctx.dispatch({ rawDetection: detection, target: detected, type: 'RAW_DETECT_DONE_NON_INTERACTIVE' });
222
+ }
165
223
  function stepFor(state) {
166
224
  switch (state.phase) {
167
225
  case 'building-plan':
@@ -226,10 +284,10 @@ function toErrorAction(err, override) {
226
284
  }
227
285
  /**
228
286
  * Write what the run changed in memory to the manifests on disk: the root's bump and each
229
- * entry's, and the tags and tools the prompts asked for. Only once the publish has opened its
230
- * PR, so a cancelled or failed one leaves every manifest as it was. A file whose write fails
231
- * takes none of its later edits and comes back as one warning naming the fields it lacks; the
232
- * other files are still written.
287
+ * entry's, and the tags and tools the publish sent on top of the root's manifest (from flags,
288
+ * prompts or detection). Only once the publish has opened its PR, so a cancelled or failed one
289
+ * leaves every manifest as it was. A file whose write fails takes none of its later edits and
290
+ * comes back as one warning naming the fields it lacks; the other files are still written.
233
291
  */
234
292
  async function writeManifestEdits(state) {
235
293
  const target = state.target;
@@ -241,19 +299,14 @@ async function writeManifestEdits(state) {
241
299
  files.set(path, [...(files.get(path) ?? []), { field, value, write }]);
242
300
  };
243
301
  const version = (dir, file, value) => edit(dir, file, 'version', value, () => updateManifestVersion(dir, file, value));
244
- const field = (name, value) => edit(target.sourceDir, 'manifest.json', name, value, () => updateManifestField(target.sourceDir, 'manifest.json', name, value));
302
+ const manifestFile = target.type === 'asset' ? 'manifest.json' : 'bundle.json';
245
303
  if (state.versionCheck?.status === 'needs-bump' && state.resolvedVersion) {
246
- version(target.sourceDir, target.type === 'asset' ? 'manifest.json' : 'bundle.json', state.resolvedVersion);
304
+ version(target.sourceDir, manifestFile, state.resolvedVersion);
247
305
  }
248
- // Only an asset's manifest.json takes the prompted tags and tools, as the target (and so the plan) has them.
249
- if (target.type === 'asset') {
250
- const { tags, tools } = target.data;
251
- if (state.promptTagsValue && tags && tags.length > 0) {
252
- field('tags', tags);
253
- }
254
- if (state.selectedPromptTools.length > 0) {
255
- field('tools', tools);
256
- }
306
+ // A manifest that can't be read now takes no field edits; its version edit, if any, warns.
307
+ const written = await readManifestFile(target).catch(() => undefined);
308
+ for (const [name, value] of Object.entries(written ? filledManifestFields(written, target) : {})) {
309
+ edit(target.sourceDir, manifestFile, name, value, () => updateManifestField(target.sourceDir, manifestFile, name, value));
257
310
  }
258
311
  for (const entry of state.entries) {
259
312
  if (entry.versionCheck?.status === 'needs-bump' && entry.resolvedVersion) {
@@ -266,7 +319,7 @@ async function writeManifestEdits(state) {
266
319
  if (!failed)
267
320
  continue;
268
321
  const missing = edits.slice(failed.index);
269
- // Only the version matters to the next publish: it asks for missing tags and tools again.
322
+ // Only the version matters to the next publish: it fills missing tags and tools again.
270
323
  const again = missing.some((e) => e.field === 'version') ? ' before you publish it again' : '';
271
324
  unwritten.push(`The PR is open, but ${path} wasn't updated (${errorMessage(failed.error)}): set ${missing.map((e) => `"${e.field}" to ${JSON.stringify(e.value)}`).join(', ')}${again}.`);
272
325
  }
@@ -275,29 +328,45 @@ async function writeManifestEdits(state) {
275
328
  // ---------------------------------------------------------------------------
276
329
  // Steps, one per phase that waits on work
277
330
  // ---------------------------------------------------------------------------
331
+ /**
332
+ * The installed asset's files, at their source paths, through a raw publish's metadata step,
333
+ * with the version, entrypoint, dependencies, org and description its lockfile entry records.
334
+ */
278
335
  const resolvingInstalled = {
279
336
  async run(state, ctx) {
280
337
  const source = await findInstalledSource(state, ctx);
281
338
  if (!source)
282
339
  return;
283
340
  const { adapter, installed, projectRoot } = source;
284
- // Copy the installed files into a temp directory, preserving their source paths
285
- const tempDir = await stageInstalledFiles(installed, adapter, projectRoot);
286
- ctx.stage(tempDir);
287
- if (ctx.cancelled())
288
- return;
289
- // Generate manifest.json in the temp dir
290
- const manifest = {
291
- author: 'unknown',
292
- description: `Published from installed asset ${installed.name}`,
293
- entrypoint: installed.files[0]?.sourcePath ?? 'index.md',
341
+ const { files } = resolveInstalledPaths(installed, adapter, projectRoot);
342
+ // An older lockfile entry has no entrypoint: the type's usual one when it was installed, else its first file.
343
+ const usual = getEntrypointName(installed.type, installed.name);
344
+ const entrypoint = installed.entrypoint ?? (files.some((file) => file.sourcePath === usual) ? usual : files[0]?.sourcePath);
345
+ const dependencies = (installed.dependencies ?? []).map(({ name, type, version }) => ({
346
+ name,
347
+ type,
348
+ ...(version ? { version } : {}),
349
+ }));
350
+ const detection = {
351
+ assetType: installed.type,
352
+ ...(dependencies.length > 0 ? { dependencies } : {}),
353
+ ...(entrypoint ? { entrypoint } : {}),
354
+ files: files.map((file) => ({
355
+ absolutePath: join(projectRoot, file.installedPath),
356
+ relativePath: file.sourcePath,
357
+ })),
294
358
  name: installed.name,
295
- ...(installed.org ? { org: installed.org } : {}),
296
- type: installed.type,
359
+ sourceDir: projectRoot,
297
360
  version: installed.version,
298
361
  };
299
- await writeFile(join(tempDir, 'manifest.json'), JSON.stringify(manifest, null, 2) + '\n', 'utf-8');
300
- ctx.dispatch({ resolvedPath: tempDir, type: 'RESOLVED_INSTALLED' });
362
+ // A --with folder that is missing, a bundle or a repeat fails here, before any prompt, as in detecting.
363
+ if (state.flags.withPaths?.length) {
364
+ const entries = await loadPublishEntries(state.flags.withPaths, projectRoot);
365
+ if (ctx.cancelled())
366
+ return;
367
+ ctx.dispatch({ entries, type: 'ENTRIES_LOADED' });
368
+ }
369
+ await gatherRawMetadata(state, ctx, detection, { description: installed.description, org: installed.org });
301
370
  },
302
371
  };
303
372
  const resolvingUpdate = {
@@ -396,66 +465,11 @@ const detecting = {
396
465
  const detection = await detectAssetFromPath(state.resolvedPath, adapter, projectRoot);
397
466
  if (ctx.cancelled())
398
467
  return;
399
- // Pre-fill metadata
400
- const author = (await getSignedInUserName()) ?? '';
401
- if (ctx.cancelled())
402
- return;
468
+ // No manifest exists yet, so the project's org and the flags and prompts give the metadata.
403
469
  const lockfile = await readLockfile(projectRoot);
404
470
  if (ctx.cancelled())
405
471
  return;
406
- const tools = await detectCompatibleTools(detection.assetType);
407
- if (ctx.cancelled())
408
- return;
409
- // Non-interactive mode (raw publish — no manifest exists yet, so the only
410
- // sources for description are --description flag or exit-1; tags come from
411
- // --tags or default to empty). Tool detection runs for both modes above.
412
- if (!state.nonInteractive) {
413
- // Interactive mode — gather metadata from the user
414
- ctx.dispatch({
415
- authorValue: author,
416
- orgValue: lockfile.org ?? '',
417
- rawDetection: detection,
418
- toolsDetected: tools,
419
- type: 'RAW_DETECT_DONE',
420
- });
421
- return;
422
- }
423
- if (!state.flags.descriptionFlag) {
424
- ctx.dispatch({
425
- message: 'description required: provide --description or include in manifest.json',
426
- type: 'ERROR',
427
- });
428
- return;
429
- }
430
- // Tools resolution: --tools flag -> detected (already populated above)
431
- let toolsList;
432
- if (state.flags.toolsFlag) {
433
- toolsList = splitList(state.flags.toolsFlag);
434
- }
435
- else if (tools.length > 0) {
436
- toolsList = tools;
437
- }
438
- else {
439
- ctx.dispatch({
440
- message: `--tools <comma-separated> required: no tools detected for type ${detection.assetType}`,
441
- type: 'ERROR',
442
- });
443
- return;
444
- }
445
- const stagingDir = await buildStagingArea(detection, {
446
- author,
447
- description: state.flags.descriptionFlag,
448
- ...(lockfile.org ? { org: lockfile.org } : {}),
449
- tags: splitList(state.flags.tagsFlag),
450
- tools: toolsList,
451
- });
452
- ctx.stage(stagingDir);
453
- if (ctx.cancelled())
454
- return;
455
- const detected = await detectPublishType(stagingDir);
456
- if (ctx.cancelled())
457
- return;
458
- ctx.dispatch({ rawDetection: detection, target: detected, type: 'RAW_DETECT_DONE_NON_INTERACTIVE' });
472
+ await gatherRawMetadata(state, ctx, detection, { org: lockfile.org });
459
473
  },
460
474
  };
461
475
  /** The `building-staging` sub-phase of `gathering-metadata`: the manifest from the prompts' answers. */
@@ -536,14 +550,13 @@ const validating = {
536
550
  },
537
551
  };
538
552
  /**
539
- * In interactive mode, loads adapters for the multi-select prompt. In non-interactive
540
- * mode, resolves tools via manifest -> --tools flag -> detectCompatibleTools(assetType)
541
- * -> exit 1, then dispatches RESOLVE_TOOLS_NONINTERACTIVE to advance directly to
542
- * checking-registry.
553
+ * In interactive mode without --tools, loads adapters for the multi-select prompt. Otherwise
554
+ * resolves tools via manifest -> --tools flag -> detectCompatibleTools(assetType) -> exit 1,
555
+ * then dispatches RESOLVE_TOOLS_NONINTERACTIVE to advance past the prompt.
543
556
  */
544
557
  const loadingTools = {
545
558
  async run(state, ctx) {
546
- if (!state.nonInteractive) {
559
+ if (!state.nonInteractive && !state.flags.toolsFlag) {
547
560
  const adapters = await loadAllAdapters();
548
561
  if (ctx.cancelled())
549
562
  return;
@@ -825,19 +838,23 @@ export function publishReducer(state, action) {
825
838
  case 'PUBLISH_PROGRESS':
826
839
  return { ...state, progressMessage: action.message };
827
840
  case 'RAW_DETECT_DONE':
841
+ // Ask only for what neither a flag nor the installed asset gave.
828
842
  return {
829
843
  ...state,
830
844
  authorValue: action.authorValue,
831
- isRawPublish: true,
845
+ descriptionValue: action.description ?? '',
846
+ isRawPublish: !state.flags.fromInstalled,
847
+ metadataSubPhase: action.description === undefined ? 'description' : metadataAfterDescription(state),
832
848
  orgValue: action.orgValue,
833
849
  phase: 'gathering-metadata',
834
850
  rawDetection: action.rawDetection,
851
+ tagsValue: state.flags.tagsFlag ?? '',
835
852
  toolsDetected: action.toolsDetected,
836
853
  };
837
854
  case 'RAW_DETECT_DONE_NON_INTERACTIVE':
838
855
  return {
839
856
  ...state,
840
- isRawPublish: true,
857
+ isRawPublish: !state.flags.fromInstalled,
841
858
  phase: 'validating',
842
859
  rawDetection: action.rawDetection,
843
860
  target: action.target,
@@ -857,34 +874,32 @@ export function publishReducer(state, action) {
857
874
  }
858
875
  else {
859
876
  // needs-bump
860
- if (state.nonInteractive) {
861
- if (state.flags.bump && state.target) {
862
- // Auto-bump using the flag value; a successful publish writes it to the manifest
863
- try {
864
- bumpedVersion = bumpVersion(versionCheck.latestVersion, state.flags.bump);
865
- bumpedTarget = {
866
- ...state.target,
867
- data: { ...state.target.data, version: bumpedVersion },
868
- };
869
- nextPhase = 'building-plan';
870
- }
871
- catch {
872
- errorMessage = 'Failed to compute bumped version.';
873
- nextPhase = 'error';
874
- }
875
- }
876
- else if (isBatch(state.flags)) {
877
- // advanceToPlan lists every asset of the batch that needs --bump, once the entries are checked.
877
+ if (state.flags.bump && state.target) {
878
+ // --bump applies in any run; a successful publish writes it to the manifest
879
+ try {
880
+ bumpedVersion = bumpVersion(versionCheck.latestVersion, state.flags.bump);
881
+ bumpedTarget = {
882
+ ...state.target,
883
+ data: { ...state.target.data, version: bumpedVersion },
884
+ };
878
885
  nextPhase = 'building-plan';
879
886
  }
880
- else {
881
- errorMessage = `--bump <patch|minor|major> required: registry has v${versionCheck.latestVersion} but manifest is not newer`;
887
+ catch {
888
+ errorMessage = 'Failed to compute bumped version.';
882
889
  nextPhase = 'error';
883
890
  }
884
891
  }
885
- else {
892
+ else if (!state.nonInteractive) {
886
893
  nextPhase = 'bump-prompt';
887
894
  }
895
+ else if (isBatch(state.flags)) {
896
+ // advanceToPlan lists every asset of the batch that needs --bump, once the entries are checked.
897
+ nextPhase = 'building-plan';
898
+ }
899
+ else {
900
+ errorMessage = `--bump <patch|minor|major> required: registry has v${versionCheck.latestVersion} but manifest is not newer`;
901
+ nextPhase = 'error';
902
+ }
888
903
  }
889
904
  const checked = {
890
905
  ...state,
@@ -907,16 +922,11 @@ export function publishReducer(state, action) {
907
922
  : undefined;
908
923
  return {
909
924
  ...state,
910
- phase: 'checking-registry',
925
+ // An interactive run's --tools stood in for the tools prompt; the reviewer prompt may still follow.
926
+ phase: state.nonInteractive ? 'checking-registry' : phaseAfterMetadata(updatedTarget),
911
927
  target: updatedTarget,
912
928
  };
913
929
  }
914
- case 'RESOLVED_INSTALLED':
915
- return {
916
- ...state,
917
- phase: 'detecting',
918
- resolvedPath: action.resolvedPath,
919
- };
920
930
  case 'RESOLVED_UPDATE':
921
931
  return {
922
932
  ...state,
@@ -999,7 +1009,7 @@ export function publishReducer(state, action) {
999
1009
  return {
1000
1010
  ...state,
1001
1011
  descriptionValue: trimmed,
1002
- metadataSubPhase: 'tags',
1012
+ metadataSubPhase: metadataAfterDescription(state),
1003
1013
  };
1004
1014
  }
1005
1015
  case 'SUBMIT_PROMPT_TAGS': {
@@ -1107,19 +1117,21 @@ export function publishReducer(state, action) {
1107
1117
  validation: action.validation,
1108
1118
  };
1109
1119
  }
1110
- // Non-interactive path: resolve tags from --tags flag (or empty) right here, then
1111
- // either route to loading-tools (effect resolves tools async) or directly to
1112
- // checking-registry. Manifest tags always win when present. The reviewer prompt
1113
- // never applies here — non-interactive mode always skips it.
1120
+ // A manifest without tags takes them from --tags right here, in any run: a non-interactive
1121
+ // one without the flag publishes none, an interactive one asks. Manifest tags always win.
1122
+ const tagsFromFlag = !hasTags && (state.nonInteractive || state.flags.tagsFlag !== undefined);
1123
+ let nextTarget = state.target;
1124
+ if (tagsFromFlag && nextTarget) {
1125
+ const flagTags = splitList(state.flags.tagsFlag);
1126
+ nextTarget = {
1127
+ ...nextTarget,
1128
+ data: { ...nextTarget.data, tags: flagTags.length > 0 ? flagTags : undefined },
1129
+ };
1130
+ }
1131
+ // Non-interactive path: either route to loading-tools (effect resolves tools async) or
1132
+ // directly to checking-registry. The reviewer prompt never applies here — non-interactive
1133
+ // mode always skips it.
1114
1134
  if (state.nonInteractive) {
1115
- let nextTarget = state.target;
1116
- if (!hasTags && nextTarget) {
1117
- const flagTags = splitList(state.flags.tagsFlag);
1118
- nextTarget = {
1119
- ...nextTarget,
1120
- data: { ...nextTarget.data, tags: flagTags.length > 0 ? flagTags : undefined },
1121
- };
1122
- }
1123
1135
  let nextPhase;
1124
1136
  if (!hasTools) {
1125
1137
  // The loading-tools effect detects whether we're non-interactive and
@@ -1138,7 +1150,7 @@ export function publishReducer(state, action) {
1138
1150
  };
1139
1151
  }
1140
1152
  let nextPhase;
1141
- if (!hasTags) {
1153
+ if (!hasTags && !tagsFromFlag) {
1142
1154
  nextPhase = 'prompting-tags';
1143
1155
  }
1144
1156
  else if (!hasTools) {
@@ -1151,6 +1163,7 @@ export function publishReducer(state, action) {
1151
1163
  ...state,
1152
1164
  phase: nextPhase,
1153
1165
  reviewerValue,
1166
+ target: nextTarget,
1154
1167
  validation: action.validation,
1155
1168
  };
1156
1169
  }
@@ -1161,7 +1174,7 @@ export function publishReducer(state, action) {
1161
1174
  /**
1162
1175
  * The step before `building-plan`, once the root asset's own checks are done:
1163
1176
  * check the entries not yet checked, then settle each entry that needs a bump
1164
- * (the bump prompt; `--bump` or an error when non-interactive), then plan.
1177
+ * (`--bump`; without it, the bump prompt, or an error when non-interactive), then plan.
1165
1178
  */
1166
1179
  function advanceToPlan(state) {
1167
1180
  if (state.entries.some((entry) => !entry.versionCheck)) {
@@ -1191,18 +1204,17 @@ function advanceToPlan(state) {
1191
1204
  if (entry?.versionCheck?.status !== 'needs-bump') {
1192
1205
  return { ...state, bumpEntry: undefined, phase: 'building-plan' };
1193
1206
  }
1207
+ if (state.flags.bump) {
1208
+ return bumpEntryAt(state, index, state.flags.bump);
1209
+ }
1194
1210
  if (!state.nonInteractive) {
1195
1211
  return { ...state, bumpEntry: index, bumpSelection: '', phase: 'bump-prompt' };
1196
1212
  }
1197
- const { latestVersion } = entry.versionCheck;
1198
- if (!state.flags.bump) {
1199
- return {
1200
- ...state,
1201
- errorMessage: `--bump <patch|minor|major> required: registry has v${latestVersion} of ${entry.target.assetType}:${entry.target.data.name} (from ${entry.label}) but its manifest is not newer`,
1202
- phase: 'error',
1203
- };
1204
- }
1205
- return bumpEntryAt(state, index, state.flags.bump);
1213
+ return {
1214
+ ...state,
1215
+ errorMessage: `--bump <patch|minor|major> required: registry has v${entry.versionCheck.latestVersion} of ${entry.target.assetType}:${entry.target.data.name} (from ${entry.label}) but its manifest is not newer`,
1216
+ phase: 'error',
1217
+ };
1206
1218
  }
1207
1219
  /**
1208
1220
  * Bump the entry at `index` from its registry version, then go on toward the
@@ -1221,6 +1233,10 @@ function bumpEntryAt(state, index, bump) {
1221
1233
  }
1222
1234
  return advanceToPlan({ ...state, entries: replaceEntry(state.entries, index, withVersion(entry, version)) });
1223
1235
  }
1236
+ /** A raw publish's step after its description: the tags prompt, unless --tags answered it. */
1237
+ function metadataAfterDescription(state) {
1238
+ return state.flags.tagsFlag === undefined ? 'tags' : 'building-staging';
1239
+ }
1224
1240
  /**
1225
1241
  * Phase to enter once the tags/tools metadata prompts are resolved: org-scoped
1226
1242
  * targets get the reviewer prompt, everything else proceeds to the registry check.