@jay-framework/plugin-validator 0.22.2 → 0.23.1

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/dist/index.d.ts CHANGED
@@ -153,4 +153,28 @@ declare function lintAddMenuCatalog(items: AddMenuItem[], sourcePath: string): {
153
153
  /** Human-readable fix hints for validate-plugin output. */
154
154
  declare const ADD_MENU_VALIDATION_SUGGESTIONS: Record<string, string>;
155
155
 
156
- export { ADD_MENU_VALIDATION_SUGGESTIONS, type AddMenuBrowseSize, type AddMenuCatalogFile, type AddMenuCatalogLintWarning, type AddMenuInteraction, type AddMenuItem, type AddMenuPresentation, type AddMenuValidationError, type PluginContext, type ValidatePluginOptions, type ValidationError, type ValidationResult, type ValidationWarning, checkComponentPropsAndParams, lintAddMenuCatalog, validateAddMenuCatalogFile, validateAddMenuItem, validatePlugin };
156
+ /**
157
+ * AIditor Project settings contribution schema (materialized YAML).
158
+ * Source of truth for plugin-validator and AIditor runtime discovery.
159
+ */
160
+ type AiditorSettingsRequire = {
161
+ plugin: string;
162
+ status: 'configured';
163
+ };
164
+ type AiditorSettingsFile = {
165
+ label: string;
166
+ route: string;
167
+ pluginName?: string;
168
+ requires?: AiditorSettingsRequire[];
169
+ };
170
+ type AiditorSettingsValidationError = {
171
+ path: string;
172
+ message: string;
173
+ code?: string;
174
+ };
175
+ declare function validateAiditorSettingsFile(raw: unknown, sourcePath: string): {
176
+ file?: AiditorSettingsFile;
177
+ errors: AiditorSettingsValidationError[];
178
+ };
179
+
180
+ export { ADD_MENU_VALIDATION_SUGGESTIONS, type AddMenuBrowseSize, type AddMenuCatalogFile, type AddMenuCatalogLintWarning, type AddMenuInteraction, type AddMenuItem, type AddMenuPresentation, type AddMenuValidationError, type AiditorSettingsFile, type AiditorSettingsRequire, type AiditorSettingsValidationError, type PluginContext, type ValidatePluginOptions, type ValidationError, type ValidationResult, type ValidationWarning, checkComponentPropsAndParams, lintAddMenuCatalog, validateAddMenuCatalogFile, validateAddMenuItem, validateAiditorSettingsFile, validatePlugin };
package/dist/index.js CHANGED
@@ -93,17 +93,13 @@ function collectLocalInterfaces(sourceFile) {
93
93
  function collectContractImportedTypes(sourceFile) {
94
94
  const contractTypes = /* @__PURE__ */ new Set();
95
95
  for (const statement of sourceFile.statements) {
96
- if (!ts.isImportDeclaration(statement))
97
- continue;
96
+ if (!ts.isImportDeclaration(statement)) continue;
98
97
  const moduleSpecifier = statement.moduleSpecifier;
99
- if (!ts.isStringLiteral(moduleSpecifier))
100
- continue;
98
+ if (!ts.isStringLiteral(moduleSpecifier)) continue;
101
99
  const modulePath = moduleSpecifier.text;
102
- if (!modulePath.includes(".jay-contract"))
103
- continue;
100
+ if (!modulePath.includes(".jay-contract")) continue;
104
101
  const importClause = statement.importClause;
105
- if (!importClause)
106
- continue;
102
+ if (!importClause) continue;
107
103
  const namedBindings = importClause.namedBindings;
108
104
  if (namedBindings && ts.isNamedImports(namedBindings)) {
109
105
  for (const element of namedBindings.elements) {
@@ -139,8 +135,7 @@ function visitNode(node, result) {
139
135
  }
140
136
  if (node.arguments.length > 0) {
141
137
  const arg = node.arguments[0];
142
- if (ts.isIdentifier(arg))
143
- ;
138
+ if (ts.isIdentifier(arg)) ;
144
139
  }
145
140
  }
146
141
  }
@@ -162,10 +157,8 @@ function extractTypeNames(typeNode) {
162
157
  return [];
163
158
  }
164
159
  function checkPropsConsistency(propsTypeName, localInterfaces, contractImportedTypes, contract, contractName, contractPath, sourcePath, errors, warnings) {
165
- if (FRAMEWORK_PROP_TYPES.has(propsTypeName))
166
- return;
167
- if (contractImportedTypes.has(propsTypeName))
168
- return;
160
+ if (FRAMEWORK_PROP_TYPES.has(propsTypeName)) return;
161
+ if (contractImportedTypes.has(propsTypeName)) return;
169
162
  const prefix = `[${contractName}]`;
170
163
  const iface = localInterfaces.get(propsTypeName);
171
164
  if (!iface) {
@@ -180,8 +173,7 @@ function checkPropsConsistency(propsTypeName, localInterfaces, contractImportedT
180
173
  return;
181
174
  }
182
175
  const ownProperties = iface.properties;
183
- if (ownProperties.length === 0)
184
- return;
176
+ if (ownProperties.length === 0) return;
185
177
  if (!contract.props || contract.props.length === 0) {
186
178
  errors.push({
187
179
  type: "contract-invalid",
@@ -225,10 +217,8 @@ function checkParamsConsistency(paramsTypeNames, localInterfaces, contractImport
225
217
  suggestion: `Add a params section to the contract (e.g., params: { slug: string })`
226
218
  });
227
219
  for (const typeName of paramsTypeNames) {
228
- if (FRAMEWORK_PROP_TYPES.has(typeName))
229
- continue;
230
- if (contractImportedTypes.has(typeName))
231
- continue;
220
+ if (FRAMEWORK_PROP_TYPES.has(typeName)) continue;
221
+ if (contractImportedTypes.has(typeName)) continue;
232
222
  const iface = localInterfaces.get(typeName);
233
223
  if (iface) {
234
224
  const ownProps = iface.properties;
@@ -241,13 +231,10 @@ function checkParamsConsistency(paramsTypeNames, localInterfaces, contractImport
241
231
  return;
242
232
  }
243
233
  for (const typeName of paramsTypeNames) {
244
- if (FRAMEWORK_PROP_TYPES.has(typeName))
245
- continue;
246
- if (contractImportedTypes.has(typeName))
247
- continue;
234
+ if (FRAMEWORK_PROP_TYPES.has(typeName)) continue;
235
+ if (contractImportedTypes.has(typeName)) continue;
248
236
  const iface = localInterfaces.get(typeName);
249
- if (!iface)
250
- continue;
237
+ if (!iface) continue;
251
238
  const ownProperties = iface.properties;
252
239
  const contractParamNames = new Set(contract.params.map((p) => p.name));
253
240
  for (const prop of ownProperties) {
@@ -279,7 +266,7 @@ const FOLDER_PATH_MAX_SEGMENTS = 32;
279
266
  const BLOCKED_TAGS = /<\s*(script|iframe|object|embed)\b[^>]*>[\s\S]*?<\/\s*\1\s*>|<\s*(script|iframe|object|embed)\b[^>]*\/?>/gi;
280
267
  const EVENT_HANDLER_ATTR = /\s+on[a-z]+\s*=\s*("[^"]*"|'[^']*'|[^\s>]+)/gi;
281
268
  const JAVASCRIPT_URL = /\b(href|src|xlink:href)\s*=\s*("|')\s*javascript:/gi;
282
- function isRecord(value) {
269
+ function isRecord$1(value) {
283
270
  return typeof value === "object" && value !== null && !Array.isArray(value);
284
271
  }
285
272
  function byteLengthUtf8(value) {
@@ -339,17 +326,14 @@ function requiredString(obj, field, itemPath, errors, code) {
339
326
  }
340
327
  function optionalString(obj, field) {
341
328
  const value = obj[field];
342
- if (value === void 0)
343
- return void 0;
344
- if (typeof value !== "string")
345
- return void 0;
329
+ if (value === void 0) return void 0;
330
+ if (typeof value !== "string") return void 0;
346
331
  const trimmed = value.trim();
347
332
  return trimmed.length > 0 ? trimmed : void 0;
348
333
  }
349
334
  function validateInteraction(raw, itemPath, errors) {
350
- if (raw === void 0)
351
- return void 0;
352
- if (!isRecord(raw)) {
335
+ if (raw === void 0) return void 0;
336
+ if (!isRecord$1(raw)) {
353
337
  errors.push({
354
338
  path: itemPath,
355
339
  message: "interaction must be an object",
@@ -372,9 +356,8 @@ function validateInteraction(raw, itemPath, errors) {
372
356
  };
373
357
  }
374
358
  function validatePresentation(raw, itemPath, errors) {
375
- if (raw === void 0)
376
- return void 0;
377
- if (!isRecord(raw)) {
359
+ if (raw === void 0) return void 0;
360
+ if (!isRecord$1(raw)) {
378
361
  errors.push({
379
362
  path: itemPath,
380
363
  message: "presentation must be an object",
@@ -393,14 +376,12 @@ function validatePresentation(raw, itemPath, errors) {
393
376
  }
394
377
  if (type === "image") {
395
378
  const src = requiredString(raw, "src", itemPath, errors, "presentation-missing-src");
396
- if (!src)
397
- return void 0;
379
+ if (!src) return void 0;
398
380
  return { type: "image", src };
399
381
  }
400
382
  if (type === "gif") {
401
383
  const src = requiredString(raw, "src", itemPath, errors, "presentation-missing-src");
402
- if (!src)
403
- return void 0;
384
+ if (!src) return void 0;
404
385
  return {
405
386
  type: "gif",
406
387
  src,
@@ -445,9 +426,8 @@ function validatePresentation(raw, itemPath, errors) {
445
426
  }
446
427
  const BROWSE_SIZES = /* @__PURE__ */ new Set(["large", "medium", "small"]);
447
428
  function validateBrowse(raw, itemPath, errors) {
448
- if (raw === void 0)
449
- return void 0;
450
- if (!isRecord(raw)) {
429
+ if (raw === void 0) return void 0;
430
+ if (!isRecord$1(raw)) {
451
431
  errors.push({
452
432
  path: itemPath,
453
433
  message: "browse must be an object",
@@ -470,8 +450,7 @@ function validateBrowse(raw, itemPath, errors) {
470
450
  return { size: sizeRaw };
471
451
  }
472
452
  function validateFolderPath(raw, itemPath, errors) {
473
- if (raw === void 0)
474
- return void 0;
453
+ if (raw === void 0) return void 0;
475
454
  if (!Array.isArray(raw)) {
476
455
  errors.push({
477
456
  path: `${itemPath}.folderPath`,
@@ -530,7 +509,7 @@ function validateFolderPath(raw, itemPath, errors) {
530
509
  }
531
510
  function validateAddMenuItem(raw, itemPath) {
532
511
  const errors = [];
533
- if (!isRecord(raw)) {
512
+ if (!isRecord$1(raw)) {
534
513
  return {
535
514
  item: null,
536
515
  errors: [
@@ -581,7 +560,7 @@ function validateAddMenuItem(raw, itemPath) {
581
560
  }
582
561
  function validateAddMenuCatalogFile(raw, sourcePath) {
583
562
  const errors = [];
584
- if (!isRecord(raw)) {
563
+ if (!isRecord$1(raw)) {
585
564
  return {
586
565
  file: null,
587
566
  errors: [
@@ -609,8 +588,7 @@ function validateAddMenuCatalogFile(raw, sourcePath) {
609
588
  raw.items.forEach((entry, index) => {
610
589
  const result = validateAddMenuItem(entry, `${sourcePath}.items[${index}]`);
611
590
  errors.push(...result.errors);
612
- if (result.item)
613
- items.push(result.item);
591
+ if (result.item) items.push(result.item);
614
592
  });
615
593
  if (items.length === 0 && errors.length > 0) {
616
594
  return { file: null, errors };
@@ -618,11 +596,9 @@ function validateAddMenuCatalogFile(raw, sourcePath) {
618
596
  return { file: { items }, errors };
619
597
  }
620
598
  function normalizeAddMenuPresentation(item) {
621
- if (item.presentation)
622
- return item.presentation;
599
+ if (item.presentation) return item.presentation;
623
600
  const thumbnail = item.thumbnail?.trim();
624
- if (!thumbnail)
625
- return void 0;
601
+ if (!thumbnail) return void 0;
626
602
  if (/\.gif$/i.test(thumbnail)) {
627
603
  return { type: "gif", src: thumbnail };
628
604
  }
@@ -633,15 +609,12 @@ function normalizeAddMenuBrowseSize(item) {
633
609
  }
634
610
  function hasSingleRootDiv(html) {
635
611
  const trimmed = html.trim();
636
- if (!trimmed.startsWith("<div"))
637
- return false;
612
+ if (!trimmed.startsWith("<div")) return false;
638
613
  const openMatch = trimmed.match(/^<div\b[^>]*>/i);
639
- if (!openMatch)
640
- return false;
614
+ if (!openMatch) return false;
641
615
  const afterOpen = trimmed.slice(openMatch[0].length);
642
616
  const closeIdx = afterOpen.lastIndexOf("</div>");
643
- if (closeIdx < 0)
644
- return false;
617
+ if (closeIdx < 0) return false;
645
618
  const tail = afterOpen.slice(closeIdx + "</div>".length).trim();
646
619
  return tail.length === 0;
647
620
  }
@@ -717,8 +690,7 @@ function lintHtmlFragment(item, sourcePath) {
717
690
  }
718
691
  function lintGifPoster(item, sourcePath) {
719
692
  const presentation = normalizeAddMenuPresentation(item);
720
- if (presentation?.type !== "gif" || presentation.poster?.trim())
721
- return [];
693
+ if (presentation?.type !== "gif" || presentation.poster?.trim()) return [];
722
694
  return [
723
695
  catalogWarning(
724
696
  "gif-missing-poster",
@@ -729,10 +701,8 @@ function lintGifPoster(item, sourcePath) {
729
701
  ];
730
702
  }
731
703
  function lintBrowseLargeWithoutPresentation(item, sourcePath) {
732
- if (normalizeAddMenuBrowseSize(item) !== "large")
733
- return [];
734
- if (normalizeAddMenuPresentation(item))
735
- return [];
704
+ if (normalizeAddMenuBrowseSize(item) !== "large") return [];
705
+ if (normalizeAddMenuPresentation(item)) return [];
736
706
  return [
737
707
  catalogWarning(
738
708
  "browse-large-without-presentation",
@@ -805,8 +775,7 @@ function resolveModulePath$1(basePath) {
805
775
  return void 0;
806
776
  }
807
777
  function collectTypeScriptFiles(dir, depth = 0) {
808
- if (depth > 4)
809
- return [];
778
+ if (depth > 4) return [];
810
779
  const entries = fs.readdirSync(dir, { withFileTypes: true });
811
780
  const files = [];
812
781
  for (const entry of entries) {
@@ -825,8 +794,7 @@ function resolveHandlerSourceFile(pluginPath, handlerRef, isNpmPackage) {
825
794
  }
826
795
  const searchRoots = isNpmPackage ? [path.join(pluginPath, "lib"), path.join(pluginPath, "dist")] : [pluginPath];
827
796
  for (const root of searchRoots) {
828
- if (!fs.existsSync(root))
829
- continue;
797
+ if (!fs.existsSync(root)) continue;
830
798
  for (const file of collectTypeScriptFiles(root)) {
831
799
  const content = fs.readFileSync(file, "utf-8");
832
800
  const definesHandler = new RegExp(
@@ -846,8 +814,7 @@ function resolveHandlerSourceFile(pluginPath, handlerRef, isNpmPackage) {
846
814
  if (reExportMatch) {
847
815
  const importSpec = reExportMatch[1].replace(/\.js$/, "");
848
816
  const resolved = resolveModulePath$1(path.resolve(path.dirname(file), importSpec));
849
- if (resolved)
850
- return resolved;
817
+ if (resolved) return resolved;
851
818
  }
852
819
  }
853
820
  }
@@ -857,8 +824,7 @@ function extractBalancedBlock(source, openBraceIndex) {
857
824
  let depth = 0;
858
825
  for (let index = openBraceIndex; index < source.length; index++) {
859
826
  const char = source[index];
860
- if (char === "{")
861
- depth++;
827
+ if (char === "{") depth++;
862
828
  else if (char === "}") {
863
829
  depth--;
864
830
  if (depth === 0) {
@@ -878,8 +844,7 @@ function findFunctionBodyOpenBrace(source, searchFrom) {
878
844
  if (parenMatch?.index !== void 0) {
879
845
  candidates.push(searchFrom + parenMatch.index + parenMatch[0].length - 1);
880
846
  }
881
- if (candidates.length === 0)
882
- return -1;
847
+ if (candidates.length === 0) return -1;
883
848
  return Math.min(...candidates);
884
849
  }
885
850
  function extractFunctionBody(source, functionName) {
@@ -896,11 +861,9 @@ function extractFunctionBody(source, functionName) {
896
861
  ];
897
862
  for (const pattern of patterns) {
898
863
  const match = pattern.exec(source);
899
- if (!match)
900
- continue;
864
+ if (!match) continue;
901
865
  const braceIndex = findFunctionBodyOpenBrace(source, match.index);
902
- if (braceIndex === -1)
903
- continue;
866
+ if (braceIndex === -1) continue;
904
867
  return extractBalancedBlock(source, braceIndex);
905
868
  }
906
869
  return null;
@@ -913,11 +876,9 @@ function extractDefaultExportFunctionBody(source) {
913
876
  ];
914
877
  for (const pattern of patterns) {
915
878
  const match = pattern.exec(source);
916
- if (!match)
917
- continue;
879
+ if (!match) continue;
918
880
  const braceIndex = findFunctionBodyOpenBrace(source, match.index);
919
- if (braceIndex === -1)
920
- continue;
881
+ if (braceIndex === -1) continue;
921
882
  return extractBalancedBlock(source, braceIndex);
922
883
  }
923
884
  return null;
@@ -927,8 +888,7 @@ function handlerBodyWritesAddMenuCatalog(body) {
927
888
  }
928
889
  function resolveSetupHandlerFunctionBody(pluginPath, handlerRef, isNpmPackage) {
929
890
  const sourceFile = resolveHandlerSourceFile(pluginPath, handlerRef, isNpmPackage);
930
- if (!sourceFile)
931
- return null;
891
+ if (!sourceFile) return null;
932
892
  const source = fs.readFileSync(sourceFile, "utf-8");
933
893
  if (isRelativeHandlerRef(handlerRef)) {
934
894
  return extractDefaultExportFunctionBody(source) ?? extractFunctionBody(source, "setup") ?? extractFunctionBody(source, handlerRef);
@@ -936,11 +896,10 @@ function resolveSetupHandlerFunctionBody(pluginPath, handlerRef, isNpmPackage) {
936
896
  return extractFunctionBody(source, handlerRef);
937
897
  }
938
898
  function suggestionForCode(code) {
939
- if (!code)
940
- return `See ${CONTRIBUTOR_GUIDE} for schema and validation rules`;
899
+ if (!code) return `See ${CONTRIBUTOR_GUIDE} for schema and validation rules`;
941
900
  return ADD_MENU_VALIDATION_SUGGESTIONS[code] ?? `See ${CONTRIBUTOR_GUIDE} for schema and validation rules`;
942
901
  }
943
- function mapSchemaError(error, catalogPath) {
902
+ function mapSchemaError$1(error, catalogPath) {
944
903
  const code = error.code ?? "catalog-validation-error";
945
904
  return {
946
905
  type: "add-menu-catalog",
@@ -967,8 +926,7 @@ function pluginShipsAddMenuCatalog(context) {
967
926
  );
968
927
  }
969
928
  function validateAddMenuAgentKitHandler(context, result) {
970
- if (!pluginShipsAddMenuCatalog(context))
971
- return;
929
+ if (!pluginShipsAddMenuCatalog(context)) return;
972
930
  const agentKitHandler = context.manifest.agentkit;
973
931
  if (!agentKitHandler) {
974
932
  result.warnings.push({
@@ -980,15 +938,13 @@ function validateAddMenuAgentKitHandler(context, result) {
980
938
  });
981
939
  }
982
940
  const setupHandler = typeof context.manifest.setup === "string" ? context.manifest.setup : void 0;
983
- if (!setupHandler)
984
- return;
941
+ if (!setupHandler) return;
985
942
  const setupBody = resolveSetupHandlerFunctionBody(
986
943
  context.pluginPath,
987
944
  setupHandler,
988
945
  context.isNpmPackage
989
946
  );
990
- if (!setupBody || !handlerBodyWritesAddMenuCatalog(setupBody))
991
- return;
947
+ if (!setupBody || !handlerBodyWritesAddMenuCatalog(setupBody)) return;
992
948
  result.warnings.push({
993
949
  type: "add-menu-catalog",
994
950
  code: "add-menu-legacy-setup-handler",
@@ -1014,7 +970,7 @@ async function validateAddMenuCatalogFileAtPath(catalogPath, relPath, result) {
1014
970
  return;
1015
971
  }
1016
972
  const validated = validateAddMenuCatalogFile(parsed, relPath);
1017
- result.errors.push(...validated.errors.map((error) => mapSchemaError(error, relPath)));
973
+ result.errors.push(...validated.errors.map((error) => mapSchemaError$1(error, relPath)));
1018
974
  if (!validated.file?.items.length) {
1019
975
  return;
1020
976
  }
@@ -1030,11 +986,177 @@ async function validateAddMenuCatalog(context, result) {
1030
986
  validateAddMenuAgentKitHandler(context, result);
1031
987
  for (const relPath of ADD_MENU_CATALOG_REL_PATHS) {
1032
988
  const catalogPath = path.join(context.pluginPath, relPath);
1033
- if (!fs.existsSync(catalogPath))
1034
- continue;
989
+ if (!fs.existsSync(catalogPath)) continue;
1035
990
  await validateAddMenuCatalogFileAtPath(catalogPath, relPath, result);
1036
991
  }
1037
992
  }
993
+ function isRecord(value) {
994
+ return typeof value === "object" && value !== null && !Array.isArray(value);
995
+ }
996
+ function parseRequires(raw, filePath) {
997
+ const errors = [];
998
+ if (raw === void 0) {
999
+ return { requires: [], errors };
1000
+ }
1001
+ if (!Array.isArray(raw)) {
1002
+ errors.push({
1003
+ path: `${filePath}.requires`,
1004
+ message: "requires must be an array",
1005
+ code: "settings-requires-type"
1006
+ });
1007
+ return { requires: [], errors };
1008
+ }
1009
+ const requires = [];
1010
+ raw.forEach((entry, index) => {
1011
+ if (!isRecord(entry)) {
1012
+ errors.push({
1013
+ path: `${filePath}.requires[${index}]`,
1014
+ message: "require entry must be an object",
1015
+ code: "settings-require-shape"
1016
+ });
1017
+ return;
1018
+ }
1019
+ if (typeof entry.plugin !== "string" || !entry.plugin.trim()) {
1020
+ errors.push({
1021
+ path: `${filePath}.requires[${index}].plugin`,
1022
+ message: "plugin is required",
1023
+ code: "settings-require-plugin"
1024
+ });
1025
+ return;
1026
+ }
1027
+ if (entry.status !== "configured") {
1028
+ errors.push({
1029
+ path: `${filePath}.requires[${index}].status`,
1030
+ message: 'status must be "configured"',
1031
+ code: "settings-require-status"
1032
+ });
1033
+ return;
1034
+ }
1035
+ requires.push({ plugin: entry.plugin.trim(), status: "configured" });
1036
+ });
1037
+ return { requires, errors };
1038
+ }
1039
+ function validateAiditorSettingsFile(raw, sourcePath) {
1040
+ const errors = [];
1041
+ if (!isRecord(raw)) {
1042
+ return {
1043
+ errors: [
1044
+ {
1045
+ path: sourcePath,
1046
+ message: "settings file must be a YAML object",
1047
+ code: "settings-root-type"
1048
+ }
1049
+ ]
1050
+ };
1051
+ }
1052
+ if (typeof raw.label !== "string" || !raw.label.trim()) {
1053
+ errors.push({
1054
+ path: `${sourcePath}.label`,
1055
+ message: "label is required",
1056
+ code: "settings-label"
1057
+ });
1058
+ }
1059
+ if (typeof raw.route !== "string" || !raw.route.trim()) {
1060
+ errors.push({
1061
+ path: `${sourcePath}.route`,
1062
+ message: "route is required",
1063
+ code: "settings-route"
1064
+ });
1065
+ } else if (!raw.route.startsWith("/")) {
1066
+ errors.push({
1067
+ path: `${sourcePath}.route`,
1068
+ message: "route must start with /",
1069
+ code: "settings-route-format"
1070
+ });
1071
+ }
1072
+ if (raw.pluginName !== void 0 && typeof raw.pluginName !== "string") {
1073
+ errors.push({
1074
+ path: `${sourcePath}.pluginName`,
1075
+ message: "pluginName must be a string",
1076
+ code: "settings-plugin-name-type"
1077
+ });
1078
+ }
1079
+ const { requires, errors: requireErrors } = parseRequires(raw.requires, sourcePath);
1080
+ errors.push(...requireErrors);
1081
+ if (errors.length > 0) {
1082
+ return { errors };
1083
+ }
1084
+ return {
1085
+ file: {
1086
+ label: raw.label.trim(),
1087
+ route: raw.route.trim(),
1088
+ ...typeof raw.pluginName === "string" && raw.pluginName.trim() ? { pluginName: raw.pluginName.trim() } : {},
1089
+ ...requires.length > 0 ? { requires } : {}
1090
+ },
1091
+ errors: []
1092
+ };
1093
+ }
1094
+ const AIDITOR_SETTINGS_TEMPLATE_REL_PATH = "agent-kit/aiditor/settings.template.yaml";
1095
+ function mapSchemaError(error, relPath) {
1096
+ return {
1097
+ type: "schema",
1098
+ message: error.message,
1099
+ location: error.path || relPath,
1100
+ code: error.code
1101
+ };
1102
+ }
1103
+ function validateSettingsTemplateAtPath(catalogPath, relPath, result, manifest) {
1104
+ let parsed;
1105
+ try {
1106
+ parsed = YAML.parse(fs.readFileSync(catalogPath, "utf-8"));
1107
+ } catch (err) {
1108
+ result.errors.push({
1109
+ type: "schema",
1110
+ message: `Invalid YAML in ${relPath}: ${err instanceof Error ? err.message : String(err)}`,
1111
+ location: relPath
1112
+ });
1113
+ return;
1114
+ }
1115
+ const validated = validateAiditorSettingsFile(parsed, relPath);
1116
+ result.errors.push(...validated.errors.map((error) => mapSchemaError(error, relPath)));
1117
+ if (!validated.file) {
1118
+ return;
1119
+ }
1120
+ const routeEntry = manifest.routes?.find((route) => route.path === validated.file.route);
1121
+ if (!routeEntry) {
1122
+ result.warnings.push({
1123
+ type: "schema",
1124
+ message: `settings route "${validated.file.route}" is not declared in plugin.yaml routes[]`,
1125
+ location: relPath,
1126
+ code: "settings-route-missing",
1127
+ suggestion: "Add a matching routes[] entry or fix the route in settings.template.yaml"
1128
+ });
1129
+ } else if (routeEntry.devOnly !== true) {
1130
+ result.warnings.push({
1131
+ type: "schema",
1132
+ message: `settings route "${validated.file.route}" should declare devOnly: true on routes[]`,
1133
+ location: "plugin.yaml routes",
1134
+ code: "settings-route-dev-only",
1135
+ suggestion: "Add devOnly: true when the settings page is dev-server tooling (see Design Log #171)"
1136
+ });
1137
+ }
1138
+ }
1139
+ async function validateAiditorSettings(context, result) {
1140
+ const templatePath = path.join(context.pluginPath, AIDITOR_SETTINGS_TEMPLATE_REL_PATH);
1141
+ if (!fs.existsSync(templatePath)) {
1142
+ return;
1143
+ }
1144
+ validateSettingsTemplateAtPath(
1145
+ templatePath,
1146
+ AIDITOR_SETTINGS_TEMPLATE_REL_PATH,
1147
+ result,
1148
+ context.manifest
1149
+ );
1150
+ if (!context.manifest.agentkit) {
1151
+ result.warnings.push({
1152
+ type: "schema",
1153
+ message: "Plugin ships agent-kit/aiditor/settings.template.yaml but has no agentkit handler",
1154
+ location: "plugin.yaml",
1155
+ code: "settings-missing-agentkit-handler",
1156
+ suggestion: "Declare agentkit in plugin.yaml and materialize to agent-kit/aiditor/settings/<plugin>.yaml on jay-stack agent-kit — see agent-kit/plugin/aiditor-settings-guide.md"
1157
+ });
1158
+ }
1159
+ }
1038
1160
  async function validatePlugin(options = {}) {
1039
1161
  const pluginPath = options.pluginPath || process.cwd();
1040
1162
  if (options.local) {
@@ -1102,6 +1224,7 @@ async function validatePluginPackage(pluginPath, options) {
1102
1224
  await validateDynamicContracts(context, result);
1103
1225
  }
1104
1226
  await validateAddMenuCatalog(context, result);
1227
+ await validateAiditorSettings(context, result);
1105
1228
  result.valid = result.errors.length === 0;
1106
1229
  return result;
1107
1230
  }
@@ -1411,6 +1534,13 @@ async function validateSchema(context, result) {
1411
1534
  if (route.css) {
1412
1535
  validateDocFile(route.css, `route "${route.path}" css`, context, result);
1413
1536
  }
1537
+ if (route.devOnly !== void 0 && typeof route.devOnly !== "boolean") {
1538
+ result.errors.push({
1539
+ type: "schema",
1540
+ message: `Route "${route.path}" devOnly must be a boolean`,
1541
+ location: "plugin.yaml"
1542
+ });
1543
+ }
1414
1544
  });
1415
1545
  }
1416
1546
  }
@@ -1480,16 +1610,14 @@ async function validateSchema(context, result) {
1480
1610
  }
1481
1611
  function checkExportExists(exportName, context) {
1482
1612
  const packageJsonPath = path.join(context.pluginPath, "package.json");
1483
- if (!fs.existsSync(packageJsonPath))
1484
- return true;
1613
+ if (!fs.existsSync(packageJsonPath)) return true;
1485
1614
  let mainPath;
1486
1615
  try {
1487
1616
  const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
1488
1617
  if (packageJson.exports?.["."]) {
1489
1618
  const entry = packageJson.exports["."];
1490
1619
  const entryPath = typeof entry === "string" ? entry : entry.default || entry.import;
1491
- if (entryPath)
1492
- mainPath = path.join(context.pluginPath, entryPath);
1620
+ if (entryPath) mainPath = path.join(context.pluginPath, entryPath);
1493
1621
  }
1494
1622
  if (!mainPath && packageJson.main) {
1495
1623
  mainPath = path.join(context.pluginPath, packageJson.main);
@@ -1497,8 +1625,7 @@ function checkExportExists(exportName, context) {
1497
1625
  } catch {
1498
1626
  return true;
1499
1627
  }
1500
- if (!mainPath || !fs.existsSync(mainPath))
1501
- return true;
1628
+ if (!mainPath || !fs.existsSync(mainPath)) return true;
1502
1629
  try {
1503
1630
  const content = fs.readFileSync(mainPath, "utf-8");
1504
1631
  const patterns = [
@@ -1558,8 +1685,7 @@ function resolveContractFile(contractSpec, context) {
1558
1685
  const resolvedPath = typeof exportValue === "string" ? exportValue : exportValue.default || exportValue.import || exportValue.require;
1559
1686
  if (resolvedPath) {
1560
1687
  const fullPath = path.join(context.pluginPath, resolvedPath);
1561
- if (fs.existsSync(fullPath))
1562
- return fullPath;
1688
+ if (fs.existsSync(fullPath)) return fullPath;
1563
1689
  }
1564
1690
  }
1565
1691
  }
@@ -1568,8 +1694,7 @@ function resolveContractFile(contractSpec, context) {
1568
1694
  }
1569
1695
  for (const dir of ["dist", "lib", ""]) {
1570
1696
  const candidate = path.join(context.pluginPath, dir, contractSpec);
1571
- if (fs.existsSync(candidate))
1572
- return candidate;
1697
+ if (fs.existsSync(candidate)) return candidate;
1573
1698
  }
1574
1699
  return void 0;
1575
1700
  } else {
@@ -1657,8 +1782,7 @@ function hasExportModifier(node) {
1657
1782
  function resolveModulePath(basePath) {
1658
1783
  for (const ext of ["", ".ts", ".js", "/index.ts", "/index.js"]) {
1659
1784
  const candidate = basePath + ext;
1660
- if (fs.existsSync(candidate))
1661
- return candidate;
1785
+ if (fs.existsSync(candidate)) return candidate;
1662
1786
  }
1663
1787
  return void 0;
1664
1788
  }
@@ -1668,10 +1792,8 @@ function resolveComponentSourcePath(componentName, context) {
1668
1792
  const entryFile = resolveModulePath(entryBase);
1669
1793
  const libEntryFile = !entryFile ? resolveModulePath(path.join(context.pluginPath, "lib", modulePath)) : void 0;
1670
1794
  const sourceEntry = entryFile || libEntryFile;
1671
- if (!sourceEntry)
1672
- return void 0;
1673
- if (!sourceEntry.endsWith(".ts"))
1674
- return void 0;
1795
+ if (!sourceEntry) return void 0;
1796
+ if (!sourceEntry.endsWith(".ts")) return void 0;
1675
1797
  let sourceCode;
1676
1798
  try {
1677
1799
  sourceCode = fs.readFileSync(sourceEntry, "utf-8");
@@ -1687,12 +1809,9 @@ function resolveComponentSourcePath(componentName, context) {
1687
1809
  );
1688
1810
  const starReexportModules = [];
1689
1811
  for (const statement of sourceFile.statements) {
1690
- if (!ts.isExportDeclaration(statement))
1691
- continue;
1692
- if (!statement.moduleSpecifier)
1693
- continue;
1694
- if (!ts.isStringLiteral(statement.moduleSpecifier))
1695
- continue;
1812
+ if (!ts.isExportDeclaration(statement)) continue;
1813
+ if (!statement.moduleSpecifier) continue;
1814
+ if (!ts.isStringLiteral(statement.moduleSpecifier)) continue;
1696
1815
  const moduleSpec = statement.moduleSpecifier.text;
1697
1816
  const exportClause = statement.exportClause;
1698
1817
  if (!exportClause) {
@@ -1710,12 +1829,10 @@ function resolveComponentSourcePath(componentName, context) {
1710
1829
  }
1711
1830
  }
1712
1831
  for (const moduleSpec of starReexportModules) {
1713
- if (!moduleSpec.startsWith("."))
1714
- continue;
1832
+ if (!moduleSpec.startsWith(".")) continue;
1715
1833
  const resolvedBase = path.resolve(path.dirname(sourceEntry), moduleSpec);
1716
1834
  const resolvedPath = resolveModulePath(resolvedBase);
1717
- if (!resolvedPath || !resolvedPath.endsWith(".ts"))
1718
- continue;
1835
+ if (!resolvedPath || !resolvedPath.endsWith(".ts")) continue;
1719
1836
  try {
1720
1837
  const modSource = fs.readFileSync(resolvedPath, "utf-8");
1721
1838
  const modFile = ts.createSourceFile(
@@ -1748,16 +1865,12 @@ function resolveContractPath(contract, context) {
1748
1865
  }
1749
1866
  async function checkComponentContractConsistency(contract, context, result) {
1750
1867
  const componentName = contract.component;
1751
- if (!componentName)
1752
- return;
1868
+ if (!componentName) return;
1753
1869
  const sourcePath = resolveComponentSourcePath(componentName, context);
1754
- if (!sourcePath)
1755
- return;
1756
- if (!sourcePath.endsWith(".ts"))
1757
- return;
1870
+ if (!sourcePath) return;
1871
+ if (!sourcePath.endsWith(".ts")) return;
1758
1872
  const contractPath = resolveContractPath(contract, context);
1759
- if (!contractPath)
1760
- return;
1873
+ if (!contractPath) return;
1761
1874
  let contractContent;
1762
1875
  try {
1763
1876
  contractContent = await fs.promises.readFile(contractPath, "utf-8");
@@ -1765,8 +1878,7 @@ async function checkComponentContractConsistency(contract, context, result) {
1765
1878
  return;
1766
1879
  }
1767
1880
  const parsed = parseContract(contractContent, path.basename(contractPath));
1768
- if (parsed.validations.length > 0)
1769
- return;
1881
+ if (parsed.validations.length > 0) return;
1770
1882
  let sourceCode;
1771
1883
  try {
1772
1884
  sourceCode = await fs.promises.readFile(sourcePath, "utf-8");
@@ -1885,8 +1997,7 @@ async function validatePackageJson(context, result) {
1885
1997
  }
1886
1998
  async function validateDynamicContracts(context, result) {
1887
1999
  const { dynamic_contracts } = context.manifest;
1888
- if (!dynamic_contracts)
1889
- return;
2000
+ if (!dynamic_contracts) return;
1890
2001
  const dynamicConfigs = Array.isArray(dynamic_contracts) ? dynamic_contracts : [dynamic_contracts];
1891
2002
  for (const config of dynamicConfigs) {
1892
2003
  const prefix = config.prefix || "(unknown)";
@@ -1942,5 +2053,6 @@ export {
1942
2053
  lintAddMenuCatalog,
1943
2054
  validateAddMenuCatalogFile,
1944
2055
  validateAddMenuItem,
2056
+ validateAiditorSettingsFile,
1945
2057
  validatePlugin
1946
2058
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jay-framework/plugin-validator",
3
- "version": "0.22.2",
3
+ "version": "0.23.1",
4
4
  "type": "module",
5
5
  "license": "Apache-2.0",
6
6
  "description": "Validation tool for Jay Stack plugins",
@@ -25,15 +25,14 @@
25
25
  "test:watch": "vitest"
26
26
  },
27
27
  "dependencies": {
28
- "@jay-framework/compiler-jay-html": "^0.22.2",
29
- "@jay-framework/compiler-shared": "^0.22.2",
30
- "@jay-framework/editor-protocol": "^0.22.2",
31
- "@jay-framework/typescript-bridge": "^0.22.2",
28
+ "@jay-framework/compiler-jay-html": "^0.23.1",
29
+ "@jay-framework/compiler-shared": "^0.23.1",
30
+ "@jay-framework/typescript-bridge": "^0.23.1",
32
31
  "chalk": "^4.1.2",
33
32
  "yaml": "^2.3.4"
34
33
  },
35
34
  "devDependencies": {
36
- "@jay-framework/dev-environment": "^0.22.2",
35
+ "@jay-framework/dev-environment": "^0.23.1",
37
36
  "@types/node": "^22.15.21",
38
37
  "rimraf": "^5.0.5",
39
38
  "tsup": "^8.0.1",