@aexol/opencode-wizard 0.3.11 → 0.3.13

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.
@@ -10,7 +10,7 @@ import { fetchPublishedSkillDetail, fetchPublishedSkillsCatalog, fetchPublishedS
10
10
  import { normalizeAbsolutePath } from './path-utils.js';
11
11
  import { emitPluginActionEvent, emitPresenceEvent } from './presence.js';
12
12
  import { normalizeDirectoryArg, normalizeRepositoryPath, resolveWorkspace, toWorkspaceResolutionMetadata, toWorkspaceResolutionOutput } from './workspace.js';
13
- import { parseRequestedSkillArgs, selectPublishedSkills, toPublishedSkillDetail, toPublishedSkillSummary, toWizardArtifactCatalog, toWizardArtifactDetail, toWizardArtifactSummary } from '../published-skills-transform.js';
13
+ import { parseRequestedSkillArgs, selectPublishedSkills, toInstallableSkillSummary, toPublishedSkillDetail, toPublishedSkillSummary, toWizardArtifactCatalog, toWizardArtifactDetail, toWizardArtifactSummary } from '../published-skills-transform.js';
14
14
  import { CACHE_TTL_MS, LOGIN_TIMEOUT_MS, OIDC_CALLBACK_URL, PLUGIN_ID, PRESENCE_SHUTDOWN_SIGNALS, PRESENCE_SIGNAL_EXIT_CODES } from './constants.js';
15
15
  export { PLUGIN_ID, NATIVE_SKILLS_URL_COMPATIBILITY } from './constants.js';
16
16
  import { buildSystemNote, filterIgnoredPublishedSkills, resolvePluginStatusSnapshot, toAiFacingPluginStatusSnapshot, toFetchFailureOutput, toPluginStatusMetadata, toPublishedSkillCatalog } from './status.js';
@@ -20,7 +20,7 @@ import { getCatalogCacheKey, resolvePublishedSkillPreferenceCacheContext, setPub
20
20
  export { buildSystemNote, resolvePluginStatusSnapshot, toPluginAuthStateSummary, toPublishedSkillCatalog } from './status.js';
21
21
  const importOpencodePluginModule = new Function('specifier', 'return import(specifier)');
22
22
  export { resolvePluginStatusSnapshotWithAuthBootstrap } from './auth-bootstrap.js';
23
- export { setPublishedSkillIgnored, setPublishedSkillInstalled } from './preferences.js';
23
+ export { setPublishedSkillIgnored, setPublishedSkillInstalled, toPublishedSkillPreferenceScope } from './preferences.js';
24
24
  const getDetailCacheKey = (catalogCacheKey, skillVersionId, revision) => {
25
25
  return JSON.stringify([catalogCacheKey, skillVersionId, revision]);
26
26
  };
@@ -118,6 +118,105 @@ const selectWizardArtifacts = (items, identifiers) => {
118
118
  missingIdentifiers
119
119
  };
120
120
  };
121
+ const MAX_IDENTIFIER_SUGGESTIONS = 3;
122
+ const RECOMMENDATION_METADATA_NOTE = 'Recommendation fields are backend metadata for routing and discoverability only; fetch/detail tools are still required before using artifact bodies as guidance.';
123
+ const normalizeRecommendationContext = args => {
124
+ if (typeof args.recommendationContext !== 'string') return null;
125
+ const recommendationContext = args.recommendationContext.trim();
126
+ if (!recommendationContext) return null;
127
+ return recommendationContext;
128
+ };
129
+ const normalizeSuggestionIdentifier = value => value.trim().toLowerCase().normalize('NFKD').replace(/[^\p{Letter}\p{Number}]+/gu, '');
130
+ const getEditDistance = (left, right) => {
131
+ if (left === right) return 0;
132
+ if (!left) return right.length;
133
+ if (!right) return left.length;
134
+ const previous = Array.from({
135
+ length: right.length + 1
136
+ }, (_, index) => index);
137
+ for (let leftIndex = 1; leftIndex <= left.length; leftIndex += 1) {
138
+ const current = [leftIndex];
139
+ for (let rightIndex = 1; rightIndex <= right.length; rightIndex += 1) {
140
+ const substitutionCost = left[leftIndex - 1] === right[rightIndex - 1] ? 0 : 1;
141
+ current[rightIndex] = Math.min(current[rightIndex - 1] + 1, previous[rightIndex] + 1, previous[rightIndex - 1] + substitutionCost);
142
+ }
143
+ previous.splice(0, previous.length, ...current);
144
+ }
145
+ return previous[right.length];
146
+ };
147
+ const getSuggestionScore = (requested, candidate) => {
148
+ const normalizedRequested = normalizeSuggestionIdentifier(requested);
149
+ const normalizedCandidate = normalizeSuggestionIdentifier(candidate);
150
+ if (!normalizedRequested || !normalizedCandidate) return null;
151
+ if (normalizedRequested === normalizedCandidate) return 0;
152
+ if (normalizedCandidate.includes(normalizedRequested) || normalizedRequested.includes(normalizedCandidate)) return 1;
153
+ const distance = getEditDistance(normalizedRequested, normalizedCandidate);
154
+ const threshold = Math.max(2, Math.ceil(Math.max(normalizedRequested.length, normalizedCandidate.length) * 0.35));
155
+ if (distance > threshold) return null;
156
+ return distance + 2;
157
+ };
158
+ const dedupeSuggestionCandidates = candidates => {
159
+ const seen = new Set();
160
+ return candidates.filter(candidate => {
161
+ const key = normalizeSuggestionIdentifier(candidate.identifier);
162
+ if (seen.has(key)) return false;
163
+ seen.add(key);
164
+ return true;
165
+ });
166
+ };
167
+ const getIdentifierSuggestions = (requestedIdentifier, candidates) => dedupeSuggestionCandidates(candidates).map(candidate => ({
168
+ candidate,
169
+ score: getSuggestionScore(requestedIdentifier, candidate.identifier)
170
+ })).filter(result => result.score !== null).sort((left, right) => {
171
+ if (left.score !== right.score) return left.score - right.score;
172
+ if (left.candidate.source !== right.candidate.source) return left.candidate.source === 'active' ? -1 : 1;
173
+ return left.candidate.identifier.localeCompare(right.candidate.identifier);
174
+ }).slice(0, MAX_IDENTIFIER_SUGGESTIONS).map(({
175
+ candidate
176
+ }) => ({
177
+ identifier: candidate.canonicalIdentifier,
178
+ matchedIdentifier: candidate.identifier,
179
+ label: candidate.label,
180
+ source: candidate.source
181
+ }));
182
+ const getSkillSuggestionCandidates = payload => [...payload.skills.flatMap(item => {
183
+ const summary = toPublishedSkillSummary(item);
184
+ return summary.identifiers.map(identifier => ({
185
+ identifier,
186
+ canonicalIdentifier: summary.skillSlug,
187
+ label: summary.artifactName || summary.skillName || summary.skillSlug,
188
+ source: 'active'
189
+ }));
190
+ }), ...payload.catalogSkills.flatMap(item => {
191
+ const summary = toInstallableSkillSummary(item);
192
+ return summary.identifiers.map(identifier => ({
193
+ identifier,
194
+ canonicalIdentifier: summary.skillSlug,
195
+ label: summary.artifactName || summary.skillName || summary.skillSlug,
196
+ source: 'catalog'
197
+ }));
198
+ })];
199
+ const getWizardArtifactSuggestionCandidates = (items, catalogItems) => [...items.flatMap(item => {
200
+ const summary = toWizardArtifactSummary(item);
201
+ return summary.identifiers.map(identifier => ({
202
+ identifier,
203
+ canonicalIdentifier: summary.artifactSlug,
204
+ label: summary.artifactName || summary.artifactSlug,
205
+ source: 'active'
206
+ }));
207
+ }), ...toWizardArtifactCatalogCursorItems(catalogItems).flatMap(item => {
208
+ const summary = toWizardArtifactSummary(item);
209
+ return summary.identifiers.map(identifier => ({
210
+ identifier,
211
+ canonicalIdentifier: summary.artifactSlug,
212
+ label: summary.artifactName || summary.artifactSlug,
213
+ source: 'catalog'
214
+ }));
215
+ })];
216
+ const getMissingIdentifierSuggestions = (identifiers, candidates) => identifiers.map(identifier => ({
217
+ identifier,
218
+ suggestions: getIdentifierSuggestions(identifier, candidates)
219
+ }));
121
220
  export const OpencodeWizardSkillsPlugin = async input => {
122
221
  const {
123
222
  tool
@@ -339,7 +438,8 @@ export const OpencodeWizardSkillsPlugin = async input => {
339
438
  const loadPublishedSkillCatalog = async ({
340
439
  directory,
341
440
  useCache,
342
- signal
441
+ signal,
442
+ recommendationContext
343
443
  }) => {
344
444
  const workspaceResolution = await resolveWorkspace({
345
445
  config,
@@ -347,7 +447,7 @@ export const OpencodeWizardSkillsPlugin = async input => {
347
447
  });
348
448
  const directoryPath = workspaceResolution.directoryPath;
349
449
  const preferenceContext = await resolvePublishedSkillPreferenceCacheContext(config);
350
- const cacheKey = getCatalogCacheKey(workspaceResolution, preferenceContext);
450
+ const cacheKey = JSON.stringify([getCatalogCacheKey(workspaceResolution, preferenceContext), recommendationContext ?? '']);
351
451
  const cached = cache.get(cacheKey);
352
452
  if (useCache && cached && cached.expiresAt > Date.now()) {
353
453
  return {
@@ -364,7 +464,7 @@ export const OpencodeWizardSkillsPlugin = async input => {
364
464
  return inflight;
365
465
  }
366
466
  const requestPromise = (async () => {
367
- const fetchResult = await fetchPublishedSkillsCatalog(input.worktree, config, workspaceResolution, signal, clearPublishedSkillState);
467
+ const fetchResult = await fetchPublishedSkillsCatalog(input.worktree, config, workspaceResolution, signal, recommendationContext, clearPublishedSkillState);
368
468
  await maybePersistWorkspaceSlugFromCatalog({
369
469
  config,
370
470
  resolution: workspaceResolution,
@@ -421,6 +521,7 @@ export const OpencodeWizardSkillsPlugin = async input => {
421
521
  config,
422
522
  resolution: workspaceResolution,
423
523
  skillVersionId: item.skillVersion.id,
524
+ artifactVersionId: item.publishedArtifact.id,
424
525
  signal,
425
526
  onAuthStateChanged: clearPublishedSkillState,
426
527
  purpose
@@ -529,6 +630,7 @@ export const OpencodeWizardSkillsPlugin = async input => {
529
630
  }) => {
530
631
  const requestedDirectory = normalizeDirectoryArg(context.directory, args.directory);
531
632
  const requestedSkills = parseRequestedSkillArgs(args);
633
+ const recommendationContext = normalizeRecommendationContext(args);
532
634
  const fetchActionDirectoryPath = normalizeRepositoryPath(workspacePath, requestedDirectory);
533
635
  lastInteractiveDirectoryPath = fetchActionDirectoryPath;
534
636
  const emitFetchOutcome = async event => {
@@ -540,7 +642,8 @@ export const OpencodeWizardSkillsPlugin = async input => {
540
642
  let publishedSkillsResult = await loadPublishedSkillCatalog({
541
643
  directory: requestedDirectory,
542
644
  useCache: !args.refresh,
543
- signal: context.abort
645
+ signal: context.abort,
646
+ recommendationContext
544
647
  });
545
648
  if (publishedSkillsResult.fetchResult.ok) {
546
649
  await scheduleInteractivePresenceStart();
@@ -553,7 +656,8 @@ export const OpencodeWizardSkillsPlugin = async input => {
553
656
  publishedSkillsResult = await loadPublishedSkillCatalog({
554
657
  directory: requestedDirectory,
555
658
  useCache: false,
556
- signal: context.abort
659
+ signal: context.abort,
660
+ recommendationContext
557
661
  });
558
662
  if (publishedSkillsResult.fetchResult.ok) {
559
663
  await scheduleInteractivePresenceStart();
@@ -583,6 +687,7 @@ export const OpencodeWizardSkillsPlugin = async input => {
583
687
  }
584
688
  const selection = selectPublishedSkills(filteredPublishedSkillsResult.fetchResult.payload, requestedSkills);
585
689
  const isSingleRequest = requestedSkills.length === 1;
690
+ const skillSuggestionCandidates = getSkillSuggestionCandidates(filteredPublishedSkillsResult.fetchResult.payload);
586
691
  if (requestedSkills.length === 0) {
587
692
  const catalog = toPublishedSkillCatalog(filteredPublishedSkillsResult.fetchResult.payload, registeredTools);
588
693
  context.metadata({
@@ -610,9 +715,12 @@ export const OpencodeWizardSkillsPlugin = async input => {
610
715
  },
611
716
  fetchedAt: filteredPublishedSkillsResult.fetchResult.fetchedAt,
612
717
  source: filteredPublishedSkillsResult.fetchResult.source,
718
+ recommendationContext,
719
+ recommendationNote: RECOMMENDATION_METADATA_NOTE,
613
720
  cacheCursor: getCatalogCursor(filteredPublishedSkillsResult.fetchResult.payload.skills, filteredPublishedSkillsResult.fetchResult.payload.catalogSkills),
614
721
  cacheTtlMs: CACHE_TTL_MS,
615
- message: args.refresh ? 'Catalog discovery refreshed from the backend. Provide `skill` for one identifier or prefer `skills` for comma/newline-separated multiple identifiers to fetch markdown bodies/details.' : 'Catalog discovery only. Cached results include deterministic revision cursors; pass `refresh: true` to force a backend refresh immediately. Provide `skill` for one identifier or prefer `skills` for comma/newline-separated multiple identifiers to fetch markdown bodies/details.'
722
+ message: args.refresh ? 'Catalog discovery refreshed from the backend. Provide `skill` for one identifier or prefer `skills` for comma/newline-separated multiple identifiers to fetch markdown bodies/details.' : 'Catalog discovery only. Cached results include deterministic revision cursors; pass `refresh: true` to force a backend refresh immediately. Provide `skill` for one identifier or prefer `skills` for comma/newline-separated multiple identifiers to fetch markdown bodies/details.',
723
+ nextStep: 'Choose an active skill identifier from `skills` and call `opencode_wizard_published_skills_fetch` with `skill`; catalog-only skills must be installed before their bodies are fetchable.'
616
724
  }, null, 2),
617
725
  metadata: {
618
726
  status: 'ready',
@@ -627,7 +735,7 @@ export const OpencodeWizardSkillsPlugin = async input => {
627
735
  }
628
736
  };
629
737
  }
630
- if (selection.selectedItems.length === 0 && isSingleRequest) {
738
+ if (selection.selectedItems.length === 0) {
631
739
  await emitFetchOutcome('FETCH_FAILED');
632
740
  return {
633
741
  output: JSON.stringify({
@@ -636,12 +744,19 @@ export const OpencodeWizardSkillsPlugin = async input => {
636
744
  status: 'not_found',
637
745
  requestedDirectoryPath: filteredPublishedSkillsResult.directoryPath,
638
746
  workspaceResolution: toWorkspaceResolutionOutput(filteredPublishedSkillsResult.workspaceResolution),
639
- requestedSkill: requestedSkills[0],
747
+ requestedSkill: isSingleRequest ? requestedSkills[0] : undefined,
748
+ requestedSkills,
749
+ missingSkills: selection.missingIdentifiers,
750
+ recommendationContext,
751
+ recommendationNote: RECOMMENDATION_METADATA_NOTE,
752
+ suggestions: isSingleRequest ? getIdentifierSuggestions(requestedSkills[0] ?? '', skillSuggestionCandidates) : undefined,
753
+ missingSkillSuggestions: getMissingIdentifierSuggestions(selection.missingIdentifiers, skillSuggestionCandidates),
640
754
  availableSkills: filteredPublishedSkillsResult.fetchResult.payload.skills.map(toPublishedSkillSummary),
641
755
  ignoredPublishedSkills: {
642
756
  scopeKey: filteredPublishedSkillsResult.ignoreState.scopeKey,
643
757
  count: filteredPublishedSkillsResult.ignoreState.ignoredSkillSlugs.length
644
- }
758
+ },
759
+ nextStep: 'Retry with one suggested identifier, fetch the catalog with no args/refresh to inspect available skills, or install a catalog-only suggestion before fetching its body.'
645
760
  }, null, 2),
646
761
  metadata: {
647
762
  status: 'not_found',
@@ -703,6 +818,8 @@ export const OpencodeWizardSkillsPlugin = async input => {
703
818
  workspace: filteredPublishedSkillsResult.fetchResult.payload.workspace,
704
819
  fetchedAt: filteredPublishedSkillsResult.fetchResult.fetchedAt,
705
820
  source: filteredPublishedSkillsResult.fetchResult.source,
821
+ recommendationContext,
822
+ recommendationNote: RECOMMENDATION_METADATA_NOTE,
706
823
  skill: detail
707
824
  }, null, 2),
708
825
  metadata: {
@@ -733,6 +850,10 @@ export const OpencodeWizardSkillsPlugin = async input => {
733
850
  source: filteredPublishedSkillsResult.fetchResult.source,
734
851
  requestedSkills,
735
852
  missingSkills: selection.missingIdentifiers,
853
+ recommendationContext,
854
+ recommendationNote: RECOMMENDATION_METADATA_NOTE,
855
+ missingSkillSuggestions: getMissingIdentifierSuggestions(selection.missingIdentifiers, skillSuggestionCandidates),
856
+ nextStep: selection.missingIdentifiers.length > 0 ? 'Fetched matched skills. Retry missing identifiers with suggested active names, or inspect the no-arg catalog for catalog-only skills that need installation first.' : 'Use each fetched `skill.markdownDocument` as reference content for matched backend-published skills.',
736
857
  skills: skillDetails
737
858
  }, null, 2),
738
859
  metadata: {
@@ -757,11 +878,12 @@ export const OpencodeWizardSkillsPlugin = async input => {
757
878
  });
758
879
  }
759
880
  if (artifactKind === 'DESIGN_DOC') {
881
+ const recommendationContext = normalizeRecommendationContext(args);
760
882
  const workspaceResolution = await resolveWorkspace({
761
883
  config,
762
884
  directory: requestedDirectory
763
885
  });
764
- const fetchResult = await fetchWizardArtifactsCatalog(input.worktree, config, workspaceResolution, artifactKind, context.abort, clearPublishedSkillState);
886
+ const fetchResult = await fetchWizardArtifactsCatalog(input.worktree, config, workspaceResolution, artifactKind, context.abort, recommendationContext, clearPublishedSkillState);
765
887
  if (!fetchResult.ok) {
766
888
  return {
767
889
  output: JSON.stringify({
@@ -796,9 +918,12 @@ export const OpencodeWizardSkillsPlugin = async input => {
796
918
  workspaceResolution: toWorkspaceResolutionOutput(workspaceResolution),
797
919
  fetchedAt: fetchResult.fetchedAt,
798
920
  source: fetchResult.source,
921
+ recommendationContext,
922
+ recommendationNote: RECOMMENDATION_METADATA_NOTE,
799
923
  cacheCursor,
800
924
  cacheTtlMs: CACHE_TTL_MS,
801
- message: 'Generic artifact catalog discovery only. Full bodies/files require opencode_wizard_artifact_fetch with artifactKind and artifact identifiers.'
925
+ message: 'Generic artifact catalog discovery only. Full bodies/files require opencode_wizard_artifact_fetch with artifactKind and artifact identifiers.',
926
+ nextStep: 'Choose an active artifact identifier from `artifacts` and call `opencode_wizard_artifact_fetch`; catalog-only artifacts must be installed before their bodies are fetchable.'
802
927
  }, null, 2),
803
928
  metadata: {
804
929
  status: 'ready',
@@ -814,7 +939,8 @@ export const OpencodeWizardSkillsPlugin = async input => {
814
939
  const result = await executePublishedSkillsFetchTool({
815
940
  args: {
816
941
  directory: args.directory,
817
- refresh: args.refresh
942
+ refresh: args.refresh,
943
+ recommendationContext: args.recommendationContext
818
944
  },
819
945
  context
820
946
  });
@@ -838,11 +964,12 @@ export const OpencodeWizardSkillsPlugin = async input => {
838
964
  skill: args.artifact,
839
965
  skills: args.artifacts
840
966
  });
967
+ const recommendationContext = normalizeRecommendationContext(args);
841
968
  const workspaceResolution = await resolveWorkspace({
842
969
  config,
843
970
  directory: requestedDirectory
844
971
  });
845
- const fetchResult = await fetchWizardArtifactsCatalog(input.worktree, config, workspaceResolution, artifactKind, context.abort, clearPublishedSkillState);
972
+ const fetchResult = await fetchWizardArtifactsCatalog(input.worktree, config, workspaceResolution, artifactKind, context.abort, recommendationContext, clearPublishedSkillState);
846
973
  if (!fetchResult.ok) {
847
974
  return {
848
975
  output: JSON.stringify({
@@ -865,6 +992,7 @@ export const OpencodeWizardSkillsPlugin = async input => {
865
992
  };
866
993
  }
867
994
  const selection = selectWizardArtifacts(fetchResult.payload.artifacts, requestedArtifacts);
995
+ const artifactSuggestionCandidates = getWizardArtifactSuggestionCandidates(fetchResult.payload.artifacts, fetchResult.payload.catalogArtifacts);
868
996
  if (requestedArtifacts.length === 0) {
869
997
  const catalog = toWizardArtifactCatalog(fetchResult.payload, {
870
998
  pluginId: PLUGIN_ID,
@@ -879,9 +1007,12 @@ export const OpencodeWizardSkillsPlugin = async input => {
879
1007
  workspaceResolution: toWorkspaceResolutionOutput(workspaceResolution),
880
1008
  fetchedAt: fetchResult.fetchedAt,
881
1009
  source: fetchResult.source,
1010
+ recommendationContext,
1011
+ recommendationNote: RECOMMENDATION_METADATA_NOTE,
882
1012
  cacheCursor,
883
1013
  cacheTtlMs: CACHE_TTL_MS,
884
- message: 'Provide artifact or artifacts to fetch artifact body/files.'
1014
+ message: 'Provide artifact or artifacts to fetch artifact body/files.',
1015
+ nextStep: 'Choose an active artifact identifier from `artifacts` and call `opencode_wizard_artifact_fetch`; install catalog-only artifacts before fetching their bodies.'
885
1016
  }, null, 2),
886
1017
  metadata: {
887
1018
  status: 'ready',
@@ -892,17 +1023,24 @@ export const OpencodeWizardSkillsPlugin = async input => {
892
1023
  }
893
1024
  };
894
1025
  }
895
- if (selection.selectedItems.length === 0 && requestedArtifacts.length === 1) {
1026
+ if (selection.selectedItems.length === 0) {
896
1027
  return {
897
1028
  output: JSON.stringify({
898
1029
  pluginId: PLUGIN_ID,
899
1030
  runtimeMode: 'tool_fetch_only',
900
1031
  status: 'not_found',
901
1032
  artifactKind,
902
- requestedArtifact: requestedArtifacts[0],
1033
+ requestedArtifact: requestedArtifacts.length === 1 ? requestedArtifacts[0] : undefined,
1034
+ requestedArtifacts,
1035
+ missingArtifacts: selection.missingIdentifiers,
1036
+ recommendationContext,
1037
+ recommendationNote: RECOMMENDATION_METADATA_NOTE,
1038
+ suggestions: requestedArtifacts.length === 1 ? getIdentifierSuggestions(requestedArtifacts[0] ?? '', artifactSuggestionCandidates) : undefined,
1039
+ missingArtifactSuggestions: getMissingIdentifierSuggestions(selection.missingIdentifiers, artifactSuggestionCandidates),
903
1040
  availableArtifacts: fetchResult.payload.artifacts.map(toWizardArtifactSummary),
904
1041
  requestedDirectoryPath: directoryPath,
905
- workspaceResolution: toWorkspaceResolutionOutput(workspaceResolution)
1042
+ workspaceResolution: toWorkspaceResolutionOutput(workspaceResolution),
1043
+ nextStep: 'Retry with one suggested identifier, fetch the artifact catalog to inspect available artifacts, or install a catalog-only suggestion before fetching its body.'
906
1044
  }, null, 2),
907
1045
  metadata: {
908
1046
  status: 'not_found',
@@ -962,6 +1100,10 @@ export const OpencodeWizardSkillsPlugin = async input => {
962
1100
  cacheTtlMs: CACHE_TTL_MS,
963
1101
  requestedArtifacts,
964
1102
  missingArtifacts: selection.missingIdentifiers,
1103
+ recommendationContext,
1104
+ recommendationNote: RECOMMENDATION_METADATA_NOTE,
1105
+ missingArtifactSuggestions: getMissingIdentifierSuggestions(selection.missingIdentifiers, artifactSuggestionCandidates),
1106
+ nextStep: selection.missingIdentifiers.length > 0 ? 'Fetched matched artifacts. Retry missing identifiers with suggested active names, or inspect the catalog for catalog-only artifacts that need installation first.' : 'Use the fetched artifact markdown as reference content for matched backend-published artifacts.',
965
1107
  artifacts: details
966
1108
  }, null, 2),
967
1109
  metadata: {
@@ -978,7 +1120,8 @@ export const OpencodeWizardSkillsPlugin = async input => {
978
1120
  skill: args.artifact,
979
1121
  skills: args.artifacts,
980
1122
  directory: args.directory,
981
- refresh: args.refresh
1123
+ refresh: args.refresh,
1124
+ recommendationContext: args.recommendationContext
982
1125
  },
983
1126
  context
984
1127
  });
@@ -1048,7 +1191,8 @@ export const OpencodeWizardSkillsPlugin = async input => {
1048
1191
  const catalogResult = await loadPublishedSkillCatalog({
1049
1192
  directory: requestedDirectory,
1050
1193
  useCache: true,
1051
- signal: context.abort
1194
+ signal: context.abort,
1195
+ recommendationContext: null
1052
1196
  });
1053
1197
  if (!catalogResult.fetchResult.ok) {
1054
1198
  await emitPreferenceOutcome('PREFERENCE_FAILED');
@@ -1564,7 +1708,8 @@ export const OpencodeWizardSkillsPlugin = async input => {
1564
1708
  let publishedSkillsResult = await loadPublishedSkillCatalog({
1565
1709
  directory: input.directory,
1566
1710
  useCache: true,
1567
- signal: AbortSignal.timeout(5_000)
1711
+ signal: AbortSignal.timeout(5_000),
1712
+ recommendationContext: null
1568
1713
  });
1569
1714
  if (!publishedSkillsResult.fetchResult.ok && publishedSkillsResult.fetchResult.status === 'missing_auth') {
1570
1715
  try {
@@ -1574,7 +1719,8 @@ export const OpencodeWizardSkillsPlugin = async input => {
1574
1719
  publishedSkillsResult = await loadPublishedSkillCatalog({
1575
1720
  directory: input.directory,
1576
1721
  useCache: false,
1577
- signal: AbortSignal.timeout(5_000)
1722
+ signal: AbortSignal.timeout(5_000),
1723
+ recommendationContext: null
1578
1724
  });
1579
1725
  } catch {
1580
1726
  const loginMessage = loginBootstrap.snapshot.message ? ` Last login status: ${loginBootstrap.snapshot.message}` : '';