@exellix/ai-skills 6.5.0 → 6.7.0

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.
Files changed (75) hide show
  1. package/README.md +15 -0
  2. package/dist/catalog-manager/createAiSkillsCatalogApi.d.ts +8 -0
  3. package/dist/catalog-manager/createAiSkillsCatalogApi.d.ts.map +1 -0
  4. package/dist/catalog-manager/createAiSkillsCatalogApi.js +144 -0
  5. package/dist/catalog-manager/createAiSkillsCatalogApi.js.map +1 -0
  6. package/dist/catalog-manager/index.d.ts +3 -0
  7. package/dist/catalog-manager/index.d.ts.map +1 -0
  8. package/dist/catalog-manager/index.js +2 -0
  9. package/dist/catalog-manager/index.js.map +1 -0
  10. package/dist/catalog-manager/types.d.ts +71 -0
  11. package/dist/catalog-manager/types.d.ts.map +1 -0
  12. package/dist/catalog-manager/types.js +2 -0
  13. package/dist/catalog-manager/types.js.map +1 -0
  14. package/dist/catalox/ai-skills-catalog.d.ts +12 -15
  15. package/dist/catalox/ai-skills-catalog.d.ts.map +1 -1
  16. package/dist/catalox/ai-skills-catalog.js +23 -143
  17. package/dist/catalox/ai-skills-catalog.js.map +1 -1
  18. package/dist/catalox/ai-sub-skills-catalog.d.ts +7 -0
  19. package/dist/catalox/ai-sub-skills-catalog.d.ts.map +1 -0
  20. package/dist/catalox/ai-sub-skills-catalog.js +72 -0
  21. package/dist/catalox/ai-sub-skills-catalog.js.map +1 -0
  22. package/dist/catalox/catalog-guards.d.ts +14 -0
  23. package/dist/catalox/catalog-guards.d.ts.map +1 -0
  24. package/dist/catalox/catalog-guards.js +86 -0
  25. package/dist/catalox/catalog-guards.js.map +1 -0
  26. package/dist/catalox/catalog-native-store.d.ts +21 -0
  27. package/dist/catalox/catalog-native-store.d.ts.map +1 -0
  28. package/dist/catalox/catalog-native-store.js +101 -0
  29. package/dist/catalox/catalog-native-store.js.map +1 -0
  30. package/dist/catalox/catalog-normalization.d.ts +70 -0
  31. package/dist/catalox/catalog-normalization.d.ts.map +1 -0
  32. package/dist/catalox/catalog-normalization.js +281 -0
  33. package/dist/catalox/catalog-normalization.js.map +1 -0
  34. package/dist/catalox/index.d.ts +5 -4
  35. package/dist/catalox/index.d.ts.map +1 -1
  36. package/dist/catalox/index.js +5 -4
  37. package/dist/catalox/index.js.map +1 -1
  38. package/dist/catalox/list-ai-skills-catalog.d.ts +12 -6
  39. package/dist/catalox/list-ai-skills-catalog.d.ts.map +1 -1
  40. package/dist/catalox/list-ai-skills-catalog.js +72 -19
  41. package/dist/catalox/list-ai-skills-catalog.js.map +1 -1
  42. package/dist/catalox/skill-optimixer-catalog.d.ts +16 -15
  43. package/dist/catalox/skill-optimixer-catalog.d.ts.map +1 -1
  44. package/dist/catalox/skill-optimixer-catalog.js +67 -70
  45. package/dist/catalox/skill-optimixer-catalog.js.map +1 -1
  46. package/dist/catalox/skill-template-catalog-mutations.d.ts +12 -52
  47. package/dist/catalox/skill-template-catalog-mutations.d.ts.map +1 -1
  48. package/dist/catalox/skill-template-catalog-mutations.js +288 -329
  49. package/dist/catalox/skill-template-catalog-mutations.js.map +1 -1
  50. package/dist/catalox/skill-templates.d.ts +32 -43
  51. package/dist/catalox/skill-templates.d.ts.map +1 -1
  52. package/dist/catalox/skill-templates.js +66 -50
  53. package/dist/catalox/skill-templates.js.map +1 -1
  54. package/dist/client/registry-manager.d.ts +2 -2
  55. package/dist/client/registry-manager.d.ts.map +1 -1
  56. package/dist/client/registry-manager.js +5 -5
  57. package/dist/client/registry-manager.js.map +1 -1
  58. package/dist/index.d.ts +1 -0
  59. package/dist/index.d.ts.map +1 -1
  60. package/dist/index.js +1 -0
  61. package/dist/index.js.map +1 -1
  62. package/dist/invocation/defaultAiProfilesResolveOptions.d.ts +1 -1
  63. package/dist/invocation/defaultAiProfilesResolveOptions.d.ts.map +1 -1
  64. package/dist/invocation/defaultAiProfilesResolveOptions.js +2 -2
  65. package/dist/invocation/defaultAiProfilesResolveOptions.js.map +1 -1
  66. package/dist/invocation/normalizeModelConfigForGatewayInvoke.js +2 -2
  67. package/dist/invocation/normalizeModelConfigForGatewayInvoke.js.map +1 -1
  68. package/dist/invocation/preferOpenRouterPolicy.d.ts +2 -3
  69. package/dist/invocation/preferOpenRouterPolicy.d.ts.map +1 -1
  70. package/dist/invocation/preferOpenRouterPolicy.js +6 -11
  71. package/dist/invocation/preferOpenRouterPolicy.js.map +1 -1
  72. package/dist/invocation/resolveSkillProfileToWireModel.js +3 -3
  73. package/dist/invocation/resolveSkillProfileToWireModel.js.map +1 -1
  74. package/dist/types.d.ts +1 -1
  75. package/package.json +8 -5
@@ -1,19 +1,15 @@
1
- import { copySkillOptimixerCatalogDataFields, skillOptimixerCatalogSpecToDataFields, } from "./skill-optimixer-catalog.js";
2
1
  import { AI_SKILLS_CATALOG_ID, computeSkillRowStatus } from "./ai-skills-catalog.js";
2
+ import { AI_SUB_SKILLS_CATALOG_ID } from "./ai-sub-skills-catalog.js";
3
+ import { buildAiSkillsBaseWriteRecord, buildAiSubSkillsWriteRecord, computeSubSkillOverrides, resolveBaseSkillFromRecord, readIndexedParentSkillKey, } from "./catalog-normalization.js";
4
+ import { getNativeCatalogRecord, replaceNativeCatalogRecord, deleteNativeCatalogRecordByItemId } from "./catalog-native-store.js";
3
5
  import { CataloxSkillAlreadyExistsError, CataloxSkillNotFoundError } from "./catalox-skill-errors.js";
4
- import { catalogItemIdFromSkillKey } from "./skill-templates.js";
6
+ import { defaultSkillOptimixerCatalogSpecForTests } from "./skill-optimixer-catalog.js";
7
+ import { catalogItemIdFromSkillKey, resolveSkillRuntimeFromCatalogs, } from "./skill-templates.js";
5
8
  import { extractTemplateTokensFromTexts, extractTokenNamesFromStrings, normalizeForStorage, toPresentationMarkdown, } from "./skill-template-markdown.js";
6
- /** Stable role ids for template bodies (matches Catalox `*Text` fields). */
7
9
  export const SKILL_TEMPLATE_ROLES_ORDER = ["instructions", "prompt", "auditInstructions", "auditPrompt"];
8
10
  function isSkillTemplateRole(s) {
9
11
  return SKILL_TEMPLATE_ROLES_ORDER.includes(s);
10
12
  }
11
- function appendParentSkillKeyIfPresent(data, payload) {
12
- const p = asString(data.parentSkillKey);
13
- if (p.length > 0) {
14
- payload.parentSkillKey = p;
15
- }
16
- }
17
13
  function asSkillCatalogStatus(v) {
18
14
  if (v === "draft" || v === "published" || v === "planned")
19
15
  return v;
@@ -22,36 +18,27 @@ function asSkillCatalogStatus(v) {
22
18
  function asString(v) {
23
19
  return typeof v === "string" ? v : "";
24
20
  }
25
- /**
26
- * Loads skill template bodies from Catalox and returns markdown formatted for editors / presentation.
27
- * Requires Catalox read access for `appId` + catalog binding.
28
- */
29
21
  export async function getSkillTemplatesForPresentation(catalox, context, skillKey, options = {}) {
22
+ void catalox;
23
+ void context;
30
24
  const catalogItemId = catalogItemIdFromSkillKey(skillKey);
31
- const res = await catalox.getCatalogItem(context, AI_SKILLS_CATALOG_ID, catalogItemId);
32
- if (res.outcome === "not_found") {
33
- throw new CataloxSkillNotFoundError(skillKey, catalogItemId);
34
- }
35
- if (res.outcome === "mapping_blocked") {
36
- throw new Error(`[AI-SKILLS] Catalox mapping blocked for skill ${catalogItemId}`);
37
- }
38
- const data = (res.item.data ?? {});
39
- const shortKey = asString(data.skillKey) || catalogItemId;
40
- const ins = asString(data.instructionsText);
41
- const pr = asString(data.promptText);
25
+ const resolved = await resolveSkillRuntimeFromCatalogs(skillKey);
26
+ const ins = resolved.instructionsText ?? "";
27
+ const pr = resolved.promptText ?? "";
42
28
  const out = {
43
- skillKey: shortKey,
29
+ skillKey: resolved.subSkillKey ?? resolved.skillKey,
44
30
  catalogItemId,
45
- status: asSkillCatalogStatus(data.status),
46
- title: asString(data.title) || shortKey,
47
- description: asString(data.description),
48
- isLocal: data.isLocal === true,
31
+ status: resolved.status,
32
+ title: resolved.title || catalogItemId,
33
+ description: resolved.description,
34
+ isLocal: false,
49
35
  instructionsMarkdown: ins ? toPresentationMarkdown(ins) : "",
50
36
  promptMarkdown: pr ? toPresentationMarkdown(pr) : "",
37
+ ...(resolved.parentSkillKey ? { parentSkillKey: resolved.parentSkillKey } : {}),
51
38
  };
52
39
  if (options.includeAudit === true) {
53
- const ains = asString(data.auditInstructionsText);
54
- const apr = asString(data.auditPromptText);
40
+ const ains = resolved.auditInstructionsText ?? "";
41
+ const apr = resolved.auditPromptText ?? "";
55
42
  if (ains)
56
43
  out.auditInstructionsMarkdown = toPresentationMarkdown(ains);
57
44
  if (apr)
@@ -59,174 +46,173 @@ export async function getSkillTemplatesForPresentation(catalox, context, skillKe
59
46
  }
60
47
  return out;
61
48
  }
62
- async function loadAiSkillsItemDataOrThrow(catalox, context, skillKey) {
63
- const catalogItemId = catalogItemIdFromSkillKey(skillKey);
64
- const res = await catalox.getCatalogItem(context, AI_SKILLS_CATALOG_ID, catalogItemId);
65
- if (res.outcome === "not_found") {
66
- throw new CataloxSkillNotFoundError(skillKey, catalogItemId);
67
- }
68
- if (res.outcome === "mapping_blocked") {
69
- throw new Error(`[AI-SKILLS] Catalox mapping blocked for skill ${catalogItemId}`);
70
- }
71
- return (res.item.data ?? {});
49
+ async function isSubSkillItem(itemId) {
50
+ const sub = await getNativeCatalogRecord(AI_SUB_SKILLS_CATALOG_ID, itemId);
51
+ return sub != null;
52
+ }
53
+ /** Whether a catalog item id resolves to an ai-sub-skills row. */
54
+ export async function isSubSkillCatalogItem(itemId) {
55
+ return isSubSkillItem(itemId);
56
+ }
57
+ async function writeBaseSkillRecord(input) {
58
+ const existing = await getNativeCatalogRecord(AI_SKILLS_CATALOG_ID, input.itemId);
59
+ const record = buildAiSkillsBaseWriteRecord({
60
+ itemId: input.itemId,
61
+ catalogId: AI_SKILLS_CATALOG_ID,
62
+ status: input.status,
63
+ title: input.title,
64
+ description: input.description,
65
+ instructionsText: input.instructionsText,
66
+ promptText: input.promptText,
67
+ auditInstructionsText: input.auditInstructionsText,
68
+ auditPromptText: input.auditPromptText,
69
+ optimixer: input.optimixer,
70
+ existing,
71
+ });
72
+ await replaceNativeCatalogRecord(AI_SKILLS_CATALOG_ID, record);
73
+ }
74
+ async function writeSubSkillRecord(input) {
75
+ const existing = await getNativeCatalogRecord(AI_SUB_SKILLS_CATALOG_ID, input.itemId);
76
+ const record = buildAiSubSkillsWriteRecord({
77
+ itemId: input.itemId,
78
+ catalogId: AI_SUB_SKILLS_CATALOG_ID,
79
+ parentSkillKey: input.parentSkillKey,
80
+ status: input.status,
81
+ overrides: input.overrides,
82
+ existing,
83
+ });
84
+ await replaceNativeCatalogRecord(AI_SUB_SKILLS_CATALOG_ID, record);
72
85
  }
73
- /**
74
- * Template bodies as `{ role, content }[]` (presentation markdown). Always four entries in stable order.
75
- */
76
86
  export async function getSkillContent(catalox, context, skillKey) {
77
- const data = await loadAiSkillsItemDataOrThrow(catalox, context, skillKey);
87
+ void catalox;
88
+ void context;
89
+ const resolved = await resolveSkillRuntimeFromCatalogs(skillKey);
78
90
  return SKILL_TEMPLATE_ROLES_ORDER.map((role) => {
79
91
  const raw = role === "instructions"
80
- ? asString(data.instructionsText)
92
+ ? resolved.instructionsText ?? ""
81
93
  : role === "prompt"
82
- ? asString(data.promptText)
94
+ ? resolved.promptText ?? ""
83
95
  : role === "auditInstructions"
84
- ? asString(data.auditInstructionsText)
85
- : asString(data.auditPromptText);
96
+ ? resolved.auditInstructionsText ?? ""
97
+ : resolved.auditPromptText ?? "";
86
98
  return { role, content: raw ? toPresentationMarkdown(raw) : "" };
87
99
  });
88
100
  }
89
- /**
90
- * Unique `{{token}}` names across all template bodies (same presentation strings as {@link getSkillContent}).
91
- */
92
101
  export async function getSkillTokens(catalox, context, skillKey) {
93
- const data = await loadAiSkillsItemDataOrThrow(catalox, context, skillKey);
94
- const contents = SKILL_TEMPLATE_ROLES_ORDER.map((role) => {
95
- const raw = role === "instructions"
96
- ? asString(data.instructionsText)
97
- : role === "prompt"
98
- ? asString(data.promptText)
99
- : role === "auditInstructions"
100
- ? asString(data.auditInstructionsText)
101
- : asString(data.auditPromptText);
102
- return raw ? toPresentationMarkdown(raw) : "";
103
- });
102
+ void catalox;
103
+ void context;
104
+ const resolved = await resolveSkillRuntimeFromCatalogs(skillKey);
105
+ const contents = [
106
+ resolved.instructionsText ?? "",
107
+ resolved.promptText ?? "",
108
+ resolved.auditInstructionsText ?? "",
109
+ resolved.auditPromptText ?? "",
110
+ ].map((raw) => (raw ? toPresentationMarkdown(raw) : ""));
104
111
  return extractTokenNamesFromStrings(contents);
105
112
  }
106
- /**
107
- * Partial template update by role. Returns structured success/failure (including Catalox/not-found errors).
108
- */
109
113
  export async function modifySkillContent(catalox, context, skillKey, parts, options) {
110
114
  try {
111
115
  if (parts.length === 0) {
112
- return {
113
- success: false,
114
- error: "[AI-SKILLS] modifySkillContent: parts must include at least one role.",
115
- };
116
+ return { success: false, error: "[AI-SKILLS] modifySkillContent: parts must include at least one role." };
116
117
  }
117
118
  const seen = new Set();
118
119
  const patch = {};
119
120
  for (const p of parts) {
120
121
  if (!isSkillTemplateRole(p.role)) {
121
- return {
122
- success: false,
123
- error: `[AI-SKILLS] modifySkillContent: unknown role "${String(p.role)}".`,
124
- };
122
+ return { success: false, error: `[AI-SKILLS] modifySkillContent: unknown role "${String(p.role)}".` };
125
123
  }
126
124
  if (seen.has(p.role)) {
127
- return {
128
- success: false,
129
- error: `[AI-SKILLS] modifySkillContent: duplicate role "${p.role}".`,
130
- };
125
+ return { success: false, error: `[AI-SKILLS] modifySkillContent: duplicate role "${p.role}".` };
131
126
  }
132
127
  seen.add(p.role);
133
- if (p.role === "instructions") {
128
+ if (p.role === "instructions")
134
129
  patch.instructionsMarkdown = p.content;
135
- }
136
- else if (p.role === "prompt") {
130
+ else if (p.role === "prompt")
137
131
  patch.promptMarkdown = p.content;
138
- }
139
- else if (p.role === "auditInstructions") {
132
+ else if (p.role === "auditInstructions")
140
133
  patch.auditInstructionsMarkdown = p.content;
141
- }
142
- else {
134
+ else
143
135
  patch.auditPromptMarkdown = p.content;
144
- }
145
136
  }
146
137
  await updateSkillTemplatesFromPresentation(catalox, context, skillKey, patch, options);
147
138
  return { success: true };
148
139
  }
149
140
  catch (e) {
150
- const msg = e instanceof Error ? e.message : String(e);
151
- return { success: false, error: msg };
141
+ return { success: false, error: e instanceof Error ? e.message : String(e) };
152
142
  }
153
143
  }
154
- /**
155
- * Merges edited markdown into the native catalog item and upserts the full row.
156
- * Requires Catalox **write** access (e.g. god-mode or app binding with write).
157
- *
158
- * @throws CataloxSkillNotFoundError when the item does not exist.
159
- */
160
144
  export async function updateSkillTemplatesFromPresentation(catalox, context, skillKey, patch, options) {
145
+ void catalox;
146
+ void context;
161
147
  const keys = Object.keys(patch).filter((k) => patch[k] !== undefined);
162
148
  if (keys.length === 0) {
163
149
  throw new Error("[AI-SKILLS] updateSkillTemplatesFromPresentation: patch must include at least one field.");
164
150
  }
165
151
  const catalogItemId = catalogItemIdFromSkillKey(skillKey);
166
- const res = await catalox.getCatalogItem(context, AI_SKILLS_CATALOG_ID, catalogItemId);
167
- if (res.outcome === "not_found") {
168
- throw new CataloxSkillNotFoundError(skillKey, catalogItemId);
169
- }
170
- if (res.outcome === "mapping_blocked") {
171
- throw new Error(`[AI-SKILLS] Catalox mapping blocked for skill ${catalogItemId}`);
172
- }
173
- const data = (res.item.data ?? {});
174
- let instructionsText = asString(data.instructionsText);
175
- let promptText = asString(data.promptText);
176
- let auditInstructionsText = asString(data.auditInstructionsText);
177
- let auditPromptText = asString(data.auditPromptText);
178
- if (patch.instructionsMarkdown !== undefined) {
152
+ const resolved = await resolveSkillRuntimeFromCatalogs(skillKey);
153
+ let instructionsText = resolved.instructionsText ?? "";
154
+ let promptText = resolved.promptText ?? "";
155
+ let auditInstructionsText = resolved.auditInstructionsText ?? "";
156
+ let auditPromptText = resolved.auditPromptText ?? "";
157
+ if (patch.instructionsMarkdown !== undefined)
179
158
  instructionsText = normalizeForStorage(patch.instructionsMarkdown);
180
- }
181
- if (patch.promptMarkdown !== undefined) {
159
+ if (patch.promptMarkdown !== undefined)
182
160
  promptText = normalizeForStorage(patch.promptMarkdown);
183
- }
184
161
  if (patch.auditInstructionsMarkdown !== undefined) {
185
162
  auditInstructionsText = normalizeForStorage(patch.auditInstructionsMarkdown);
186
163
  }
187
- if (patch.auditPromptMarkdown !== undefined) {
164
+ if (patch.auditPromptMarkdown !== undefined)
188
165
  auditPromptText = normalizeForStorage(patch.auditPromptMarkdown);
189
- }
190
- const prevStatus = asSkillCatalogStatus(data.status);
191
166
  const catalogReleaseStatus = options?.catalogReleaseStatus !== undefined
192
167
  ? options.catalogReleaseStatus
193
- : prevStatus === "published"
168
+ : resolved.status === "published"
194
169
  ? "published"
195
170
  : undefined;
196
171
  const status = computeSkillRowStatus(instructionsText || undefined, promptText || undefined, catalogReleaseStatus);
197
- const shortKey = asString(data.skillKey) || catalogItemId;
198
- const payload = {
199
- skillKey: shortKey,
200
- title: asString(data.title) || shortKey,
201
- description: asString(data.description),
202
- isLocal: data.isLocal === true,
172
+ if (resolved.parentSkillKey) {
173
+ const parentRecord = await getNativeCatalogRecord(AI_SKILLS_CATALOG_ID, resolved.parentSkillKey);
174
+ if (!parentRecord) {
175
+ throw new CataloxSkillNotFoundError(skillKey, resolved.parentSkillKey);
176
+ }
177
+ const parent = resolveBaseSkillFromRecord(parentRecord, `skills/${resolved.parentSkillKey}`);
178
+ const nextRuntime = {
179
+ ...parent,
180
+ skillKey: catalogItemId,
181
+ subSkillKey: catalogItemId,
182
+ parentSkillKey: resolved.parentSkillKey,
183
+ status,
184
+ instructionsText,
185
+ promptText,
186
+ auditInstructionsText,
187
+ auditPromptText,
188
+ };
189
+ const overrides = computeSubSkillOverrides(parent, nextRuntime);
190
+ await writeSubSkillRecord({
191
+ itemId: catalogItemId,
192
+ parentSkillKey: resolved.parentSkillKey,
193
+ status,
194
+ overrides,
195
+ });
196
+ return;
197
+ }
198
+ await writeBaseSkillRecord({
199
+ itemId: catalogItemId,
203
200
  status,
201
+ title: resolved.title || catalogItemId,
202
+ description: resolved.description,
204
203
  instructionsText,
205
204
  promptText,
206
- };
207
- if (auditInstructionsText.length > 0 || patch.auditInstructionsMarkdown !== undefined) {
208
- payload.auditInstructionsText = auditInstructionsText;
209
- }
210
- if (auditPromptText.length > 0 || patch.auditPromptMarkdown !== undefined) {
211
- payload.auditPromptText = auditPromptText;
212
- }
213
- appendParentSkillKeyIfPresent(data, payload);
214
- await catalox.batchUpsertNativeCatalogItems(context, AI_SKILLS_CATALOG_ID, [payload]);
205
+ auditInstructionsText,
206
+ auditPromptText,
207
+ optimixer: resolved.optimixer,
208
+ });
215
209
  }
216
- /**
217
- * Creates or updates a full skill catalog row (metadata + optional template markdown).
218
- * Uses read–merge–write when the item exists; otherwise writes a new row.
219
- * Requires Catalox **write** access.
220
- *
221
- * @throws CataloxSkillAlreadyExistsError when `options.ifNotExists` is true and the item exists.
222
- */
223
210
  export async function upsertSkillCatalogItem(catalox, context, input, options) {
211
+ void catalox;
212
+ void context;
224
213
  const catalogItemId = catalogItemIdFromSkillKey(input.skillKey);
225
- const res = await catalox.getCatalogItem(context, AI_SKILLS_CATALOG_ID, catalogItemId);
226
- if (res.outcome === "mapping_blocked") {
227
- throw new Error(`[AI-SKILLS] Catalox mapping blocked for skill ${catalogItemId}`);
228
- }
229
- if (options?.ifNotExists === true && res.outcome === "found") {
214
+ const existing = await getNativeCatalogRecord(AI_SKILLS_CATALOG_ID, catalogItemId);
215
+ if (options?.ifNotExists === true && existing) {
230
216
  throw new CataloxSkillAlreadyExistsError(input.skillKey, catalogItemId);
231
217
  }
232
218
  let instructionsText = "";
@@ -234,248 +220,221 @@ export async function upsertSkillCatalogItem(catalox, context, input, options) {
234
220
  let auditInstructionsText = "";
235
221
  let auditPromptText = "";
236
222
  let prevStatus = "planned";
237
- if (res.outcome === "found") {
238
- const data = (res.item.data ?? {});
239
- prevStatus = asSkillCatalogStatus(data.status);
240
- instructionsText = asString(data.instructionsText);
241
- promptText = asString(data.promptText);
242
- auditInstructionsText = asString(data.auditInstructionsText);
243
- auditPromptText = asString(data.auditPromptText);
244
- }
245
- if (input.instructionsMarkdown !== undefined) {
223
+ let optimixer = input.optimixer ?? defaultSkillOptimixerCatalogSpecForTests();
224
+ if (existing) {
225
+ const resolved = resolveBaseSkillFromRecord(existing, input.skillKey);
226
+ prevStatus = resolved.status;
227
+ instructionsText = resolved.instructionsText ?? "";
228
+ promptText = resolved.promptText ?? "";
229
+ auditInstructionsText = resolved.auditInstructionsText ?? "";
230
+ auditPromptText = resolved.auditPromptText ?? "";
231
+ if (input.optimixer === undefined)
232
+ optimixer = resolved.optimixer;
233
+ }
234
+ if (input.instructionsMarkdown !== undefined)
246
235
  instructionsText = normalizeForStorage(input.instructionsMarkdown);
247
- }
248
- if (input.promptMarkdown !== undefined) {
236
+ if (input.promptMarkdown !== undefined)
249
237
  promptText = normalizeForStorage(input.promptMarkdown);
250
- }
251
238
  if (input.auditInstructionsMarkdown !== undefined) {
252
239
  auditInstructionsText = normalizeForStorage(input.auditInstructionsMarkdown);
253
240
  }
254
- if (input.auditPromptMarkdown !== undefined) {
241
+ if (input.auditPromptMarkdown !== undefined)
255
242
  auditPromptText = normalizeForStorage(input.auditPromptMarkdown);
256
- }
257
- const shortKey = res.outcome === "found"
258
- ? asString((res.item.data ?? {}).skillKey) || catalogItemId
259
- : catalogItemId;
260
243
  const catalogReleaseStatus = input.catalogReleaseStatus !== undefined
261
244
  ? input.catalogReleaseStatus
262
245
  : prevStatus === "published"
263
246
  ? "published"
264
247
  : undefined;
265
248
  const status = computeSkillRowStatus(instructionsText || undefined, promptText || undefined, catalogReleaseStatus);
266
- const payload = {
267
- skillKey: shortKey,
268
- title: input.title || shortKey,
269
- description: input.description,
270
- isLocal: input.isLocal,
249
+ await writeBaseSkillRecord({
250
+ itemId: catalogItemId,
271
251
  status,
252
+ title: input.title || catalogItemId,
253
+ description: input.description,
272
254
  instructionsText,
273
255
  promptText,
274
- };
275
- if (auditInstructionsText.length > 0 || input.auditInstructionsMarkdown !== undefined) {
276
- payload.auditInstructionsText = auditInstructionsText;
277
- }
278
- if (auditPromptText.length > 0 || input.auditPromptMarkdown !== undefined) {
279
- payload.auditPromptText = auditPromptText;
280
- }
281
- if (input.optimixer !== undefined) {
282
- Object.assign(payload, skillOptimixerCatalogSpecToDataFields(input.optimixer));
283
- }
284
- else if (res.outcome === "found") {
285
- copySkillOptimixerCatalogDataFields((res.item.data ?? {}), payload);
286
- }
287
- else {
288
- throw new Error(`[AI-SKILLS] upsertSkillCatalogItem("${input.skillKey}") requires optimixer on new catalog rows (Optimixer v3 mandatory fields).`);
256
+ auditInstructionsText,
257
+ auditPromptText,
258
+ optimixer,
259
+ });
260
+ }
261
+ export async function updateSkillMetadata(catalox, context, input) {
262
+ void catalox;
263
+ void context;
264
+ const keys = ["title", "description", "optimixer", "catalogReleaseStatus"];
265
+ const hasPatch = keys.some((k) => input[k] !== undefined);
266
+ if (!hasPatch) {
267
+ throw new Error("[AI-SKILLS] updateSkillMetadata: patch must include at least one field.");
289
268
  }
290
- if (res.outcome === "found") {
291
- appendParentSkillKeyIfPresent((res.item.data ?? {}), payload);
269
+ const catalogItemId = catalogItemIdFromSkillKey(input.skillKey);
270
+ const resolved = await resolveSkillRuntimeFromCatalogs(input.skillKey);
271
+ const title = input.title !== undefined ? input.title : resolved.title || catalogItemId;
272
+ const description = input.description !== undefined ? input.description : resolved.description;
273
+ const optimixer = input.optimixer !== undefined ? input.optimixer : resolved.optimixer;
274
+ const catalogReleaseStatus = input.catalogReleaseStatus !== undefined
275
+ ? input.catalogReleaseStatus
276
+ : resolved.status === "published"
277
+ ? "published"
278
+ : undefined;
279
+ const status = computeSkillRowStatus(resolved.instructionsText || undefined, resolved.promptText || undefined, catalogReleaseStatus);
280
+ if (resolved.parentSkillKey) {
281
+ const parentRecord = await getNativeCatalogRecord(AI_SKILLS_CATALOG_ID, resolved.parentSkillKey);
282
+ if (!parentRecord) {
283
+ throw new CataloxSkillNotFoundError(input.skillKey, resolved.parentSkillKey);
284
+ }
285
+ const parent = resolveBaseSkillFromRecord(parentRecord, `skills/${resolved.parentSkillKey}`);
286
+ const nextRuntime = {
287
+ ...parent,
288
+ skillKey: catalogItemId,
289
+ subSkillKey: catalogItemId,
290
+ parentSkillKey: resolved.parentSkillKey,
291
+ status,
292
+ title,
293
+ description,
294
+ instructionsText: resolved.instructionsText,
295
+ promptText: resolved.promptText,
296
+ auditInstructionsText: resolved.auditInstructionsText,
297
+ auditPromptText: resolved.auditPromptText,
298
+ optimixer,
299
+ };
300
+ const overrides = computeSubSkillOverrides(parent, nextRuntime);
301
+ await writeSubSkillRecord({
302
+ itemId: catalogItemId,
303
+ parentSkillKey: resolved.parentSkillKey,
304
+ status,
305
+ overrides,
306
+ });
307
+ return;
292
308
  }
293
- await catalox.batchUpsertNativeCatalogItems(context, AI_SKILLS_CATALOG_ID, [payload]);
309
+ await writeBaseSkillRecord({
310
+ itemId: catalogItemId,
311
+ status,
312
+ title,
313
+ description,
314
+ instructionsText: resolved.instructionsText ?? "",
315
+ promptText: resolved.promptText ?? "",
316
+ auditInstructionsText: resolved.auditInstructionsText ?? "",
317
+ auditPromptText: resolved.auditPromptText ?? "",
318
+ optimixer,
319
+ });
294
320
  }
295
- /**
296
- * Soft-deletes a skill: clears template bodies and audit fields and sets status to `planned`.
297
- * Requires Catalox **write** access.
298
- *
299
- * @throws CataloxSkillNotFoundError when the item does not exist.
300
- */
301
321
  export async function softDeleteSkillCatalogItem(catalox, context, skillKey) {
322
+ void catalox;
323
+ void context;
302
324
  const catalogItemId = catalogItemIdFromSkillKey(skillKey);
303
- const res = await catalox.getCatalogItem(context, AI_SKILLS_CATALOG_ID, catalogItemId);
304
- if (res.outcome === "not_found") {
325
+ if (await isSubSkillItem(catalogItemId)) {
326
+ const sub = await getNativeCatalogRecord(AI_SUB_SKILLS_CATALOG_ID, catalogItemId);
327
+ if (!sub)
328
+ throw new CataloxSkillNotFoundError(skillKey, catalogItemId);
329
+ const parentKey = readIndexedParentSkillKey(sub);
330
+ await writeSubSkillRecord({
331
+ itemId: catalogItemId,
332
+ parentSkillKey: parentKey,
333
+ status: "planned",
334
+ overrides: {},
335
+ });
336
+ return;
337
+ }
338
+ const existing = await getNativeCatalogRecord(AI_SKILLS_CATALOG_ID, catalogItemId);
339
+ if (!existing)
305
340
  throw new CataloxSkillNotFoundError(skillKey, catalogItemId);
306
- }
307
- if (res.outcome === "mapping_blocked") {
308
- throw new Error(`[AI-SKILLS] Catalox mapping blocked for skill ${catalogItemId}`);
309
- }
310
- const data = (res.item.data ?? {});
311
- const shortKey = asString(data.skillKey) || catalogItemId;
312
- const title = asString(data.title) || shortKey;
313
- const description = asString(data.description);
314
- const isLocal = data.isLocal === true;
315
- const instructionsText = "";
316
- const promptText = "";
317
- const status = computeSkillRowStatus(undefined, undefined, undefined);
318
- const payload = {
319
- skillKey: shortKey,
320
- title,
321
- description,
322
- isLocal,
323
- status,
324
- instructionsText,
325
- promptText,
341
+ const resolved = resolveBaseSkillFromRecord(existing, skillKey);
342
+ await writeBaseSkillRecord({
343
+ itemId: catalogItemId,
344
+ status: "planned",
345
+ title: resolved.title,
346
+ description: resolved.description,
347
+ instructionsText: "",
348
+ promptText: "",
326
349
  auditInstructionsText: "",
327
350
  auditPromptText: "",
328
- };
329
- appendParentSkillKeyIfPresent(data, payload);
330
- await catalox.batchUpsertNativeCatalogItems(context, AI_SKILLS_CATALOG_ID, [payload]);
351
+ optimixer: resolved.optimixer,
352
+ });
331
353
  }
332
- /**
333
- * Duplicates a catalog skill into a new key, marking it with `parentSkillKey`. Structured success/failure.
334
- */
335
354
  export async function createSubSkill(catalox, context, input) {
355
+ void catalox;
356
+ void context;
336
357
  try {
337
358
  const sourceItemId = catalogItemIdFromSkillKey(input.sourceSkillKey);
338
359
  const newItemId = catalogItemIdFromSkillKey(input.newSkillKey);
339
360
  if (sourceItemId === newItemId) {
340
- return {
341
- success: false,
342
- error: "[AI-SKILLS] createSubSkill: sourceSkillKey and newSkillKey must differ.",
343
- };
344
- }
345
- const srcRes = await catalox.getCatalogItem(context, AI_SKILLS_CATALOG_ID, sourceItemId);
346
- if (srcRes.outcome === "not_found") {
347
- return {
348
- success: false,
349
- error: `[AI-SKILLS] createSubSkill: source skill not found (${input.sourceSkillKey}).`,
350
- };
361
+ return { success: false, error: "[AI-SKILLS] createSubSkill: sourceSkillKey and newSkillKey must differ." };
351
362
  }
352
- if (srcRes.outcome === "mapping_blocked") {
353
- return {
354
- success: false,
355
- error: `[AI-SKILLS] Catalox mapping blocked for skill ${sourceItemId}`,
356
- };
363
+ const parentRecord = await getNativeCatalogRecord(AI_SKILLS_CATALOG_ID, sourceItemId);
364
+ if (!parentRecord) {
365
+ return { success: false, error: `[AI-SKILLS] createSubSkill: source skill not found (${input.sourceSkillKey}).` };
357
366
  }
358
- const tgtRes = await catalox.getCatalogItem(context, AI_SKILLS_CATALOG_ID, newItemId);
359
- if (tgtRes.outcome === "mapping_blocked") {
360
- return {
361
- success: false,
362
- error: `[AI-SKILLS] Catalox mapping blocked for skill ${newItemId}`,
363
- };
367
+ const parent = resolveBaseSkillFromRecord(parentRecord, input.sourceSkillKey);
368
+ const existingSub = await getNativeCatalogRecord(AI_SUB_SKILLS_CATALOG_ID, newItemId);
369
+ if (existingSub) {
370
+ return { success: false, error: `[AI-SKILLS] createSubSkill: target skill already exists (${input.newSkillKey}).` };
364
371
  }
365
- if (tgtRes.outcome === "found") {
366
- return {
367
- success: false,
368
- error: `[AI-SKILLS] createSubSkill: target skill already exists (${input.newSkillKey}).`,
369
- };
370
- }
371
- const sourceData = (srcRes.item.data ?? {});
372
- const parentShortKey = asString(sourceData.skillKey) || sourceItemId;
373
372
  const ov = input.overrides ?? {};
374
- let instructionsText = asString(sourceData.instructionsText);
375
- let promptText = asString(sourceData.promptText);
376
- let auditInstructionsText = asString(sourceData.auditInstructionsText);
377
- let auditPromptText = asString(sourceData.auditPromptText);
378
- if (ov.instructionsMarkdown !== undefined) {
379
- instructionsText = normalizeForStorage(ov.instructionsMarkdown);
380
- }
381
- if (ov.promptMarkdown !== undefined) {
382
- promptText = normalizeForStorage(ov.promptMarkdown);
383
- }
384
- if (ov.auditInstructionsMarkdown !== undefined) {
385
- auditInstructionsText = normalizeForStorage(ov.auditInstructionsMarkdown);
386
- }
387
- if (ov.auditPromptMarkdown !== undefined) {
388
- auditPromptText = normalizeForStorage(ov.auditPromptMarkdown);
389
- }
390
- const newShortKey = newItemId;
391
- const title = ov.title !== undefined ? ov.title : asString(sourceData.title) || newShortKey;
392
- const description = ov.description !== undefined ? ov.description : asString(sourceData.description);
393
- const isLocal = ov.isLocal !== undefined ? ov.isLocal : sourceData.isLocal === true;
394
- const prevStatus = asSkillCatalogStatus(sourceData.status);
373
+ const childRuntime = {
374
+ ...parent,
375
+ skillKey: newItemId,
376
+ subSkillKey: newItemId,
377
+ parentSkillKey: sourceItemId,
378
+ status: parent.status,
379
+ title: ov.title !== undefined ? ov.title : parent.title,
380
+ description: ov.description !== undefined ? ov.description : parent.description,
381
+ instructionsText: ov.instructionsMarkdown !== undefined
382
+ ? normalizeForStorage(ov.instructionsMarkdown)
383
+ : parent.instructionsText,
384
+ promptText: ov.promptMarkdown !== undefined ? normalizeForStorage(ov.promptMarkdown) : parent.promptText,
385
+ auditInstructionsText: ov.auditInstructionsMarkdown !== undefined
386
+ ? normalizeForStorage(ov.auditInstructionsMarkdown)
387
+ : parent.auditInstructionsText,
388
+ auditPromptText: ov.auditPromptMarkdown !== undefined
389
+ ? normalizeForStorage(ov.auditPromptMarkdown)
390
+ : parent.auditPromptText,
391
+ optimixer: ov.optimixer ?? parent.optimixer,
392
+ };
395
393
  const catalogReleaseStatus = ov.catalogReleaseStatus !== undefined
396
394
  ? ov.catalogReleaseStatus
397
- : prevStatus === "published"
395
+ : parent.status === "published"
398
396
  ? "published"
399
397
  : undefined;
400
- const status = computeSkillRowStatus(instructionsText || undefined, promptText || undefined, catalogReleaseStatus);
401
- const payload = {
402
- skillKey: newShortKey,
403
- parentSkillKey: parentShortKey,
404
- title: title || newShortKey,
405
- description,
406
- isLocal,
398
+ const status = computeSkillRowStatus(childRuntime.instructionsText, childRuntime.promptText, catalogReleaseStatus);
399
+ childRuntime.status = status;
400
+ const overrides = computeSubSkillOverrides(parent, childRuntime);
401
+ await writeSubSkillRecord({
402
+ itemId: newItemId,
403
+ parentSkillKey: sourceItemId,
407
404
  status,
408
- instructionsText,
409
- promptText,
410
- };
411
- if (auditInstructionsText.length > 0 || ov.auditInstructionsMarkdown !== undefined) {
412
- payload.auditInstructionsText = auditInstructionsText;
413
- }
414
- if (auditPromptText.length > 0 || ov.auditPromptMarkdown !== undefined) {
415
- payload.auditPromptText = auditPromptText;
416
- }
417
- copySkillOptimixerCatalogDataFields(sourceData, payload);
418
- await catalox.batchUpsertNativeCatalogItems(context, AI_SKILLS_CATALOG_ID, [payload]);
405
+ overrides,
406
+ });
419
407
  return { success: true };
420
408
  }
421
409
  catch (e) {
422
- const msg = e instanceof Error ? e.message : String(e);
423
- return { success: false, error: msg };
410
+ return { success: false, error: e instanceof Error ? e.message : String(e) };
424
411
  }
425
412
  }
426
- /**
427
- * Deletes a native catalog row only when it is a sub-skill (`parentSkillKey` set).
428
- */
429
413
  export async function deleteSubSkill(catalox, context, subSkillKey) {
414
+ void catalox;
415
+ void context;
430
416
  try {
431
417
  const catalogItemId = catalogItemIdFromSkillKey(subSkillKey);
432
- const res = await catalox.getCatalogItem(context, AI_SKILLS_CATALOG_ID, catalogItemId);
433
- if (res.outcome === "not_found") {
434
- return {
435
- success: false,
436
- error: `[AI-SKILLS] deleteSubSkill: skill not found (${subSkillKey}).`,
437
- };
418
+ const sub = await getNativeCatalogRecord(AI_SUB_SKILLS_CATALOG_ID, catalogItemId);
419
+ if (!sub) {
420
+ return { success: false, error: `[AI-SKILLS] deleteSubSkill: skill not found (${subSkillKey}).` };
438
421
  }
439
- if (res.outcome === "mapping_blocked") {
440
- return {
441
- success: false,
442
- error: `[AI-SKILLS] Catalox mapping blocked for skill ${catalogItemId}`,
443
- };
444
- }
445
- const data = (res.item.data ?? {});
446
- const parent = asString(data.parentSkillKey);
447
- if (parent.length === 0) {
448
- return {
449
- success: false,
450
- error: "[AI-SKILLS] deleteSubSkill: only sub-skills (rows with parentSkillKey) can be deleted.",
451
- };
452
- }
453
- await catalox.deleteNativeCatalogItem(context, AI_SKILLS_CATALOG_ID, catalogItemId);
422
+ await deleteNativeCatalogRecordByItemId(AI_SUB_SKILLS_CATALOG_ID, catalogItemId, sub);
454
423
  return { success: true };
455
424
  }
456
425
  catch (e) {
457
- const msg = e instanceof Error ? e.message : String(e);
458
- return { success: false, error: msg };
426
+ return { success: false, error: e instanceof Error ? e.message : String(e) };
459
427
  }
460
428
  }
461
- /**
462
- * Returns unique `{{token}}` names from raw Catalox template strings (before presentation transforms).
463
- */
464
429
  export async function getSkillTemplateInputs(catalox, context, skillKey) {
465
- const catalogItemId = catalogItemIdFromSkillKey(skillKey);
466
- const res = await catalox.getCatalogItem(context, AI_SKILLS_CATALOG_ID, catalogItemId);
467
- if (res.outcome === "not_found") {
468
- throw new CataloxSkillNotFoundError(skillKey, catalogItemId);
469
- }
470
- if (res.outcome === "mapping_blocked") {
471
- throw new Error(`[AI-SKILLS] Catalox mapping blocked for skill ${catalogItemId}`);
472
- }
473
- const data = (res.item.data ?? {});
430
+ void catalox;
431
+ void context;
432
+ const resolved = await resolveSkillRuntimeFromCatalogs(skillKey);
474
433
  return extractTemplateTokensFromTexts({
475
- instructionsText: asString(data.instructionsText) || undefined,
476
- promptText: asString(data.promptText) || undefined,
477
- auditInstructionsText: asString(data.auditInstructionsText) || undefined,
478
- auditPromptText: asString(data.auditPromptText) || undefined,
434
+ instructionsText: resolved.instructionsText,
435
+ promptText: resolved.promptText,
436
+ auditInstructionsText: resolved.auditInstructionsText,
437
+ auditPromptText: resolved.auditPromptText,
479
438
  });
480
439
  }
481
440
  //# sourceMappingURL=skill-template-catalog-mutations.js.map