@wp-typia/project-tools 0.19.2 → 0.20.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 (77) hide show
  1. package/README.md +23 -0
  2. package/dist/runtime/ability-spec.d.ts +90 -0
  3. package/dist/runtime/ability-spec.js +51 -0
  4. package/dist/runtime/ai-artifacts.d.ts +39 -0
  5. package/dist/runtime/ai-artifacts.js +68 -0
  6. package/dist/runtime/ai-feature-artifacts.d.ts +85 -0
  7. package/dist/runtime/ai-feature-artifacts.js +139 -0
  8. package/dist/runtime/ai-feature-capability.d.ts +114 -0
  9. package/dist/runtime/ai-feature-capability.js +150 -0
  10. package/dist/runtime/block-generator-service-spec.js +6 -0
  11. package/dist/runtime/cli-add-shared.d.ts +32 -1
  12. package/dist/runtime/cli-add-shared.js +44 -0
  13. package/dist/runtime/cli-add-workspace-ability.d.ts +8 -0
  14. package/dist/runtime/cli-add-workspace-ability.js +810 -0
  15. package/dist/runtime/cli-add-workspace-ai-anchors.d.ts +22 -0
  16. package/dist/runtime/cli-add-workspace-ai-anchors.js +277 -0
  17. package/dist/runtime/cli-add-workspace-ai-source-emitters.d.ts +28 -0
  18. package/dist/runtime/cli-add-workspace-ai-source-emitters.js +346 -0
  19. package/dist/runtime/cli-add-workspace-ai.d.ts +14 -0
  20. package/dist/runtime/cli-add-workspace-ai.js +484 -0
  21. package/dist/runtime/cli-add-workspace.d.ts +10 -0
  22. package/dist/runtime/cli-add-workspace.js +10 -0
  23. package/dist/runtime/cli-add.d.ts +1 -1
  24. package/dist/runtime/cli-add.js +1 -1
  25. package/dist/runtime/cli-core.d.ts +3 -1
  26. package/dist/runtime/cli-core.js +3 -1
  27. package/dist/runtime/cli-diagnostics.d.ts +2 -0
  28. package/dist/runtime/cli-diagnostics.js +10 -1
  29. package/dist/runtime/cli-doctor-workspace.js +140 -1
  30. package/dist/runtime/cli-help.js +7 -0
  31. package/dist/runtime/cli-init.d.ts +49 -0
  32. package/dist/runtime/cli-init.js +354 -0
  33. package/dist/runtime/cli-scaffold.d.ts +1 -0
  34. package/dist/runtime/cli-scaffold.js +5 -1
  35. package/dist/runtime/cli-templates.js +36 -6
  36. package/dist/runtime/external-template-guards.d.ts +31 -0
  37. package/dist/runtime/external-template-guards.js +132 -0
  38. package/dist/runtime/index.d.ts +3 -1
  39. package/dist/runtime/index.js +2 -1
  40. package/dist/runtime/package-managers.d.ts +8 -0
  41. package/dist/runtime/package-managers.js +13 -0
  42. package/dist/runtime/package-versions.d.ts +8 -0
  43. package/dist/runtime/package-versions.js +84 -19
  44. package/dist/runtime/scaffold-compatibility.d.ts +65 -0
  45. package/dist/runtime/scaffold-compatibility.js +152 -0
  46. package/dist/runtime/scaffold-documents.js +19 -1
  47. package/dist/runtime/scaffold-onboarding.d.ts +4 -0
  48. package/dist/runtime/scaffold-onboarding.js +25 -1
  49. package/dist/runtime/scaffold-template-variable-groups.d.ts +2 -0
  50. package/dist/runtime/scaffold-template-variables.js +6 -0
  51. package/dist/runtime/scaffold.d.ts +3 -0
  52. package/dist/runtime/scaffold.js +2 -5
  53. package/dist/runtime/template-registry.d.ts +23 -1
  54. package/dist/runtime/template-registry.js +37 -3
  55. package/dist/runtime/template-source-external.js +9 -3
  56. package/dist/runtime/template-source-remote.js +5 -0
  57. package/dist/runtime/template-source-seeds.js +40 -6
  58. package/dist/runtime/typia-llm.d.ts +213 -0
  59. package/dist/runtime/typia-llm.js +348 -0
  60. package/dist/runtime/wordpress-ai.d.ts +122 -0
  61. package/dist/runtime/wordpress-ai.js +177 -0
  62. package/dist/runtime/workspace-inventory.d.ts +51 -4
  63. package/dist/runtime/workspace-inventory.js +157 -4
  64. package/package.json +17 -2
  65. package/templates/_shared/base/package.json.mustache +0 -1
  66. package/templates/_shared/base/{{slugKebabCase}}.php.mustache +3 -3
  67. package/templates/_shared/compound/core/package.json.mustache +0 -1
  68. package/templates/_shared/compound/core/{{slugKebabCase}}.php.mustache +3 -3
  69. package/templates/_shared/compound/persistence/package.json.mustache +0 -1
  70. package/templates/_shared/compound/persistence-auth/{{slugKebabCase}}.php.mustache +3 -3
  71. package/templates/_shared/compound/persistence-public/{{slugKebabCase}}.php.mustache +3 -3
  72. package/templates/_shared/persistence/auth/{{slugKebabCase}}.php.mustache +3 -3
  73. package/templates/_shared/persistence/core/package.json.mustache +0 -1
  74. package/templates/_shared/persistence/core/{{slugKebabCase}}.php.mustache +3 -3
  75. package/templates/_shared/persistence/public/{{slugKebabCase}}.php.mustache +3 -3
  76. package/templates/interactivity/package.json.mustache +0 -1
  77. package/templates/query-loop/{{slugKebabCase}}.php.mustache +3 -3
@@ -0,0 +1,177 @@
1
+ import { normalizeEndpointAuthDefinition, projectJsonSchemaDocument, } from './schema-core.js';
2
+ import { resolveAbilityCategorySpec, } from './ability-spec.js';
3
+ function getEndpointInputContractName(endpoint) {
4
+ if (endpoint.method !== 'GET' &&
5
+ endpoint.bodyContract &&
6
+ endpoint.queryContract) {
7
+ throw new Error(`Endpoint "${endpoint.operationId}" defines both bodyContract and queryContract; WordPress AI input projection is ambiguous.`);
8
+ }
9
+ if (endpoint.method === 'GET') {
10
+ return endpoint.queryContract ?? null;
11
+ }
12
+ return endpoint.bodyContract ?? endpoint.queryContract ?? null;
13
+ }
14
+ function toAbilityId(categoryId, operationId) {
15
+ return `${categoryId}/${operationId
16
+ .replace(/([a-z0-9])([A-Z])/g, '$1-$2')
17
+ .toLowerCase()}`;
18
+ }
19
+ /**
20
+ * Projects a canonical schema into the AI structured-output profile used by
21
+ * WordPress AI artifact generation.
22
+ *
23
+ * @param schema Canonical JSON Schema document from the manifest-owned source.
24
+ * @returns The projected AI-friendly schema document.
25
+ */
26
+ export function projectWordPressAiSchema(schema) {
27
+ return projectJsonSchemaDocument(schema, {
28
+ profile: 'ai-structured-output',
29
+ });
30
+ }
31
+ function assertAbilityMetaKeys(abilitySpec, operationId) {
32
+ if (!abilitySpec.meta) {
33
+ return;
34
+ }
35
+ if ('annotations' in abilitySpec.meta) {
36
+ throw new Error(`Operation "${operationId}" cannot set AbilitySpec.meta.annotations directly; use AbilitySpec.annotations instead.`);
37
+ }
38
+ if ('show_in_rest' in abilitySpec.meta) {
39
+ throw new Error(`Operation "${operationId}" cannot set AbilitySpec.meta.show_in_rest directly; use AbilitySpec.showInRest instead.`);
40
+ }
41
+ }
42
+ /**
43
+ * Builds a WordPress abilities document from a manifest-first REST surface and
44
+ * a matching AbilitySpec catalog.
45
+ *
46
+ * Input schemas are loaded lazily per endpoint only when an input contract
47
+ * exists, and `transformInputSchema` runs after the schema has already been
48
+ * projected into the AI structured-output profile.
49
+ *
50
+ * @param options Manifest, ability catalog, and projected output schema inputs.
51
+ * @returns The generated WordPress abilities document.
52
+ * @throws When an endpoint is missing an AbilitySpec.
53
+ * @throws When categories are missing, inconsistent, or span multiple documents.
54
+ * @throws When an endpoint references a missing input contract or no loader was supplied.
55
+ */
56
+ export async function buildWordPressAbilitiesDocument({ abilityCatalog, buildAbilityId, generatedFrom, loadInputSchema, manifest, outputSchema, transformInputSchema, }) {
57
+ const resolvedEndpoints = manifest.endpoints.map((endpoint) => {
58
+ const abilitySpec = abilityCatalog.abilities[endpoint.operationId];
59
+ if (!abilitySpec) {
60
+ throw new Error(`Missing AbilitySpec for operationId "${endpoint.operationId}".`);
61
+ }
62
+ assertAbilityMetaKeys(abilitySpec, endpoint.operationId);
63
+ return {
64
+ abilitySpec,
65
+ category: resolveAbilityCategorySpec(abilitySpec, abilityCatalog.categories, endpoint.operationId),
66
+ endpoint,
67
+ };
68
+ });
69
+ const documentCategory = resolvedEndpoints[0]?.category ?? null;
70
+ if (!documentCategory) {
71
+ throw new Error('WordPress AI projection requires at least one endpoint before it can resolve an AbilitySpec category.');
72
+ }
73
+ for (const resolvedEndpoint of resolvedEndpoints) {
74
+ if (resolvedEndpoint.category.id !== documentCategory.id) {
75
+ throw new Error(`Operation "${resolvedEndpoint.endpoint.operationId}" uses AbilitySpec category "${resolvedEndpoint.category.id}" but WordPress AI projections currently support one shared category per document.`);
76
+ }
77
+ }
78
+ const abilities = await Promise.all(resolvedEndpoints.map(async ({ abilitySpec, category, endpoint }) => {
79
+ const inputContractName = getEndpointInputContractName(endpoint);
80
+ let inputSchema = null;
81
+ if (inputContractName) {
82
+ if (!manifest.contracts[inputContractName]) {
83
+ throw new Error(`Endpoint "${endpoint.operationId}" references missing input contract "${inputContractName}".`);
84
+ }
85
+ if (!loadInputSchema) {
86
+ throw new Error(`Missing input schema loader for operationId "${endpoint.operationId}".`);
87
+ }
88
+ const projectedInputSchema = projectWordPressAiSchema(await loadInputSchema(endpoint, inputContractName));
89
+ inputSchema = transformInputSchema
90
+ ? transformInputSchema({
91
+ contractName: inputContractName,
92
+ endpoint,
93
+ schema: projectedInputSchema,
94
+ })
95
+ : projectedInputSchema;
96
+ }
97
+ const normalizedAuth = normalizeEndpointAuthDefinition(endpoint);
98
+ return {
99
+ authIntent: normalizedAuth.auth,
100
+ ...(normalizedAuth.authMode
101
+ ? { authMode: normalizedAuth.authMode }
102
+ : {}),
103
+ category: category.id,
104
+ description: endpoint.summary ?? abilitySpec.label,
105
+ executeCallback: abilitySpec.executeCallback,
106
+ id: (buildAbilityId ??
107
+ ((operationId) => toAbilityId(category.id, operationId)))(endpoint.operationId),
108
+ inputSchema,
109
+ label: abilitySpec.label,
110
+ meta: {
111
+ ...(abilitySpec.meta ?? {}),
112
+ ...(abilitySpec.annotations
113
+ ? {
114
+ annotations: abilitySpec.annotations,
115
+ }
116
+ : {}),
117
+ show_in_rest: abilitySpec.showInRest ?? true,
118
+ },
119
+ method: endpoint.method,
120
+ operationId: endpoint.operationId,
121
+ outputSchema,
122
+ path: endpoint.path,
123
+ permissionCallback: abilitySpec.permissionCallback,
124
+ ...(normalizedAuth.wordpressAuth
125
+ ? { wordpressAuth: normalizedAuth.wordpressAuth }
126
+ : {}),
127
+ };
128
+ }));
129
+ return {
130
+ abilities,
131
+ category: documentCategory,
132
+ generatedFrom,
133
+ };
134
+ }
135
+ /**
136
+ * Builds the projected AI response schema together with its companion
137
+ * WordPress abilities document.
138
+ *
139
+ * All endpoints must share the same response contract and category so the
140
+ * generated artifacts stay aligned with one manifest-owned response surface.
141
+ *
142
+ * @param options Manifest, ability catalog, and response schema inputs.
143
+ * @returns The projected AI response schema and generated abilities document.
144
+ * @throws When the manifest has no endpoints.
145
+ * @throws When the shared response contract is missing or inconsistent.
146
+ */
147
+ export async function buildWordPressAiArtifacts({ abilityCatalog, buildAbilityId, generatedFrom, loadInputSchema, manifest, responseSchema, transformInputSchema, }) {
148
+ if (manifest.endpoints.length === 0) {
149
+ throw new Error('WordPress AI projection requires at least one endpoint in the manifest.');
150
+ }
151
+ const responseContractName = manifest.endpoints[0].responseContract;
152
+ if (!responseContractName) {
153
+ throw new Error('The manifest is missing its shared response contract.');
154
+ }
155
+ for (const endpoint of manifest.endpoints) {
156
+ if (endpoint.responseContract !== responseContractName) {
157
+ throw new Error(`Endpoint "${endpoint.operationId}" uses response contract "${endpoint.responseContract}" but expected shared response contract "${responseContractName}".`);
158
+ }
159
+ }
160
+ if (!manifest.contracts[responseContractName]) {
161
+ throw new Error(`The manifest references missing response contract "${responseContractName}".`);
162
+ }
163
+ const aiResponseSchema = projectWordPressAiSchema(responseSchema);
164
+ const abilitiesDocument = await buildWordPressAbilitiesDocument({
165
+ abilityCatalog,
166
+ buildAbilityId,
167
+ generatedFrom,
168
+ loadInputSchema,
169
+ manifest,
170
+ outputSchema: aiResponseSchema,
171
+ transformInputSchema,
172
+ });
173
+ return {
174
+ abilitiesDocument,
175
+ aiResponseSchema,
176
+ };
177
+ }
@@ -38,6 +38,44 @@ export interface WorkspaceRestResourceInventoryEntry {
38
38
  typesFile: string;
39
39
  validatorsFile: string;
40
40
  }
41
+ /**
42
+ * Ability entry parsed from `scripts/block-config.ts`.
43
+ *
44
+ * Each file path stays relative to the workspace root so doctor checks, schema
45
+ * sync scripts, and generated admin/editor helpers can resolve typed workflow
46
+ * artifacts without guessing their locations.
47
+ */
48
+ export interface WorkspaceAbilityInventoryEntry {
49
+ clientFile: string;
50
+ configFile: string;
51
+ dataFile: string;
52
+ inputSchemaFile: string;
53
+ inputTypeName: string;
54
+ outputSchemaFile: string;
55
+ outputTypeName: string;
56
+ phpFile: string;
57
+ slug: string;
58
+ typesFile: string;
59
+ }
60
+ /**
61
+ * AI-feature entry parsed from `scripts/block-config.ts`.
62
+ *
63
+ * Each file path stays relative to the workspace root so doctor checks, add
64
+ * workflows, and split sync scripts can reason about the REST and AI-safe
65
+ * artifacts without guessing their locations.
66
+ */
67
+ export interface WorkspaceAiFeatureInventoryEntry {
68
+ aiSchemaFile: string;
69
+ apiFile: string;
70
+ clientFile: string;
71
+ dataFile: string;
72
+ namespace: string;
73
+ openApiFile: string;
74
+ phpFile: string;
75
+ slug: string;
76
+ typesFile: string;
77
+ validatorsFile: string;
78
+ }
41
79
  /**
42
80
  * Editor-plugin entry parsed from `scripts/block-config.ts`.
43
81
  *
@@ -54,7 +92,11 @@ export interface WorkspaceInventory {
54
92
  bindingSources: WorkspaceBindingSourceInventoryEntry[];
55
93
  blockConfigPath: string;
56
94
  blocks: WorkspaceBlockInventoryEntry[];
95
+ abilities: WorkspaceAbilityInventoryEntry[];
96
+ aiFeatures: WorkspaceAiFeatureInventoryEntry[];
97
+ hasAbilitiesSection: boolean;
57
98
  hasBindingSourcesSection: boolean;
99
+ hasAiFeaturesSection: boolean;
58
100
  hasEditorPluginsSection: boolean;
59
101
  hasPatternsSection: boolean;
60
102
  hasRestResourcesSection: boolean;
@@ -70,6 +112,8 @@ export declare const VARIATION_CONFIG_ENTRY_MARKER = "\t// wp-typia add variatio
70
112
  export declare const PATTERN_CONFIG_ENTRY_MARKER = "\t// wp-typia add pattern entries";
71
113
  export declare const BINDING_SOURCE_CONFIG_ENTRY_MARKER = "\t// wp-typia add binding-source entries";
72
114
  export declare const REST_RESOURCE_CONFIG_ENTRY_MARKER = "\t// wp-typia add rest-resource entries";
115
+ export declare const ABILITY_CONFIG_ENTRY_MARKER = "\t// wp-typia add ability entries";
116
+ export declare const AI_FEATURE_CONFIG_ENTRY_MARKER = "\t// wp-typia add ai-feature entries";
73
117
  /**
74
118
  * Marker used to append generated editor-plugin entries into `EDITOR_PLUGINS`.
75
119
  */
@@ -107,15 +151,18 @@ export declare function getWorkspaceBlockSelectOptions(projectDir: string): Arra
107
151
  /**
108
152
  * Update `scripts/block-config.ts` source text with additional inventory entries.
109
153
  *
110
- * Missing `VARIATIONS` and `PATTERNS` sections are created automatically before
111
- * new entries are appended at their marker comments. When provided,
112
- * `transformSource` runs before any entries are inserted.
154
+ * Missing inventory sections for variations, patterns, binding sources, REST
155
+ * resources, workflow abilities, AI features, and editor plugins are created
156
+ * automatically before new entries are appended at their marker comments.
157
+ * When provided, `transformSource` runs before any entries are inserted.
113
158
  *
114
159
  * @param source Existing `scripts/block-config.ts` source.
115
160
  * @param options Entry lists plus an optional source transformer.
116
161
  * @returns Updated source text with all requested inventory entries appended.
117
162
  */
118
- export declare function updateWorkspaceInventorySource(source: string, { blockEntries, bindingSourceEntries, editorPluginEntries, patternEntries, restResourceEntries, variationEntries, transformSource, }?: {
163
+ export declare function updateWorkspaceInventorySource(source: string, { blockEntries, bindingSourceEntries, abilityEntries, aiFeatureEntries, editorPluginEntries, patternEntries, restResourceEntries, variationEntries, transformSource, }?: {
164
+ abilityEntries?: string[];
165
+ aiFeatureEntries?: string[];
119
166
  blockEntries?: string[];
120
167
  bindingSourceEntries?: string[];
121
168
  editorPluginEntries?: string[];
@@ -8,6 +8,8 @@ export const VARIATION_CONFIG_ENTRY_MARKER = "\t// wp-typia add variation entrie
8
8
  export const PATTERN_CONFIG_ENTRY_MARKER = "\t// wp-typia add pattern entries";
9
9
  export const BINDING_SOURCE_CONFIG_ENTRY_MARKER = "\t// wp-typia add binding-source entries";
10
10
  export const REST_RESOURCE_CONFIG_ENTRY_MARKER = "\t// wp-typia add rest-resource entries";
11
+ export const ABILITY_CONFIG_ENTRY_MARKER = "\t// wp-typia add ability entries";
12
+ export const AI_FEATURE_CONFIG_ENTRY_MARKER = "\t// wp-typia add ai-feature entries";
11
13
  /**
12
14
  * Marker used to append generated editor-plugin entries into `EDITOR_PLUGINS`.
13
15
  */
@@ -77,6 +79,62 @@ export const REST_RESOURCES: WorkspaceRestResourceConfig[] = [
77
79
  \t// wp-typia add rest-resource entries
78
80
  ];
79
81
  `;
82
+ const WORKSPACE_COMPATIBILITY_CONFIG_FIELD = `\tcompatibility?: {
83
+ \t\thardMinimums: {
84
+ \t\t\tphp?: string;
85
+ \t\t\twordpress?: string;
86
+ \t\t};
87
+ \t\tmode: 'baseline' | 'optional' | 'required';
88
+ \t\toptionalFeatures: string[];
89
+ \t\trequiredFeatures: string[];
90
+ \t\truntimeGates: string[];
91
+ \t};
92
+ `;
93
+ const ABILITIES_INTERFACE_SECTION = `
94
+
95
+ export interface WorkspaceAbilityConfig {
96
+ \tclientFile: string;
97
+ ${WORKSPACE_COMPATIBILITY_CONFIG_FIELD}\tconfigFile: string;
98
+ \tdataFile: string;
99
+ \tinputSchemaFile: string;
100
+ \tinputTypeName: string;
101
+ \toutputSchemaFile: string;
102
+ \toutputTypeName: string;
103
+ \tphpFile: string;
104
+ \tslug: string;
105
+ \ttypesFile: string;
106
+ }
107
+ `;
108
+ const ABILITIES_CONST_SECTION = `
109
+
110
+ export const ABILITIES: WorkspaceAbilityConfig[] = [
111
+ \t// wp-typia add ability entries
112
+ ];
113
+ `;
114
+ const AI_FEATURES_INTERFACE_SECTION = `
115
+
116
+ export interface WorkspaceAiFeatureConfig {
117
+ \taiSchemaFile: string;
118
+ \tapiFile: string;
119
+ \tclientFile: string;
120
+ ${WORKSPACE_COMPATIBILITY_CONFIG_FIELD}\tdataFile: string;
121
+ \tnamespace: string;
122
+ \topenApiFile: string;
123
+ \tphpFile: string;
124
+ \trestManifest?: ReturnType<
125
+ \t\ttypeof import( '@wp-typia/block-runtime/metadata-core' ).defineEndpointManifest
126
+ \t>;
127
+ \tslug: string;
128
+ \ttypesFile: string;
129
+ \tvalidatorsFile: string;
130
+ }
131
+ `;
132
+ const AI_FEATURES_CONST_SECTION = `
133
+
134
+ export const AI_FEATURES: WorkspaceAiFeatureConfig[] = [
135
+ \t// wp-typia add ai-feature entries
136
+ ];
137
+ `;
80
138
  const EDITOR_PLUGINS_INTERFACE_SECTION = `
81
139
 
82
140
  export interface WorkspaceEditorPluginConfig {
@@ -243,6 +301,44 @@ function parseRestResourceEntries(arrayLiteral) {
243
301
  };
244
302
  });
245
303
  }
304
+ function parseAiFeatureEntries(arrayLiteral) {
305
+ return arrayLiteral.elements.map((element, elementIndex) => {
306
+ if (!ts.isObjectLiteralExpression(element)) {
307
+ throw new Error(`AI_FEATURES[${elementIndex}] must be an object literal in scripts/block-config.ts.`);
308
+ }
309
+ return {
310
+ aiSchemaFile: getRequiredStringProperty("AI_FEATURES", elementIndex, element, "aiSchemaFile"),
311
+ apiFile: getRequiredStringProperty("AI_FEATURES", elementIndex, element, "apiFile"),
312
+ clientFile: getRequiredStringProperty("AI_FEATURES", elementIndex, element, "clientFile"),
313
+ dataFile: getRequiredStringProperty("AI_FEATURES", elementIndex, element, "dataFile"),
314
+ namespace: getRequiredStringProperty("AI_FEATURES", elementIndex, element, "namespace"),
315
+ openApiFile: getRequiredStringProperty("AI_FEATURES", elementIndex, element, "openApiFile"),
316
+ phpFile: getRequiredStringProperty("AI_FEATURES", elementIndex, element, "phpFile"),
317
+ slug: getRequiredStringProperty("AI_FEATURES", elementIndex, element, "slug"),
318
+ typesFile: getRequiredStringProperty("AI_FEATURES", elementIndex, element, "typesFile"),
319
+ validatorsFile: getRequiredStringProperty("AI_FEATURES", elementIndex, element, "validatorsFile"),
320
+ };
321
+ });
322
+ }
323
+ function parseAbilityEntries(arrayLiteral) {
324
+ return arrayLiteral.elements.map((element, elementIndex) => {
325
+ if (!ts.isObjectLiteralExpression(element)) {
326
+ throw new Error(`ABILITIES[${elementIndex}] must be an object literal in scripts/block-config.ts.`);
327
+ }
328
+ return {
329
+ clientFile: getRequiredStringProperty("ABILITIES", elementIndex, element, "clientFile"),
330
+ configFile: getRequiredStringProperty("ABILITIES", elementIndex, element, "configFile"),
331
+ dataFile: getRequiredStringProperty("ABILITIES", elementIndex, element, "dataFile"),
332
+ inputSchemaFile: getRequiredStringProperty("ABILITIES", elementIndex, element, "inputSchemaFile"),
333
+ inputTypeName: getRequiredStringProperty("ABILITIES", elementIndex, element, "inputTypeName"),
334
+ outputSchemaFile: getRequiredStringProperty("ABILITIES", elementIndex, element, "outputSchemaFile"),
335
+ outputTypeName: getRequiredStringProperty("ABILITIES", elementIndex, element, "outputTypeName"),
336
+ phpFile: getRequiredStringProperty("ABILITIES", elementIndex, element, "phpFile"),
337
+ slug: getRequiredStringProperty("ABILITIES", elementIndex, element, "slug"),
338
+ typesFile: getRequiredStringProperty("ABILITIES", elementIndex, element, "typesFile"),
339
+ };
340
+ });
341
+ }
246
342
  function parseEditorPluginEntries(arrayLiteral) {
247
343
  return arrayLiteral.elements.map((element, elementIndex) => {
248
344
  if (!ts.isObjectLiteralExpression(element)) {
@@ -272,6 +368,8 @@ export function parseWorkspaceInventorySource(source) {
272
368
  const patternArray = findExportedArrayLiteral(sourceFile, "PATTERNS");
273
369
  const bindingSourceArray = findExportedArrayLiteral(sourceFile, "BINDING_SOURCES");
274
370
  const restResourceArray = findExportedArrayLiteral(sourceFile, "REST_RESOURCES");
371
+ const abilityArray = findExportedArrayLiteral(sourceFile, "ABILITIES");
372
+ const aiFeatureArray = findExportedArrayLiteral(sourceFile, "AI_FEATURES");
275
373
  const editorPluginArray = findExportedArrayLiteral(sourceFile, "EDITOR_PLUGINS");
276
374
  if (variationArray.found && !variationArray.array) {
277
375
  throw new Error("scripts/block-config.ts must export VARIATIONS as an array literal.");
@@ -285,14 +383,24 @@ export function parseWorkspaceInventorySource(source) {
285
383
  if (restResourceArray.found && !restResourceArray.array) {
286
384
  throw new Error("scripts/block-config.ts must export REST_RESOURCES as an array literal.");
287
385
  }
386
+ if (abilityArray.found && !abilityArray.array) {
387
+ throw new Error("scripts/block-config.ts must export ABILITIES as an array literal.");
388
+ }
389
+ if (aiFeatureArray.found && !aiFeatureArray.array) {
390
+ throw new Error("scripts/block-config.ts must export AI_FEATURES as an array literal.");
391
+ }
288
392
  if (editorPluginArray.found && !editorPluginArray.array) {
289
393
  throw new Error("scripts/block-config.ts must export EDITOR_PLUGINS as an array literal.");
290
394
  }
291
395
  return {
396
+ abilities: abilityArray.array ? parseAbilityEntries(abilityArray.array) : [],
397
+ aiFeatures: aiFeatureArray.array ? parseAiFeatureEntries(aiFeatureArray.array) : [],
292
398
  bindingSources: bindingSourceArray.array
293
399
  ? parseBindingSourceEntries(bindingSourceArray.array)
294
400
  : [],
295
401
  blocks: parseBlockEntries(blockArray.array),
402
+ hasAbilitiesSection: abilityArray.found,
403
+ hasAiFeaturesSection: aiFeatureArray.found,
296
404
  hasBindingSourcesSection: bindingSourceArray.found,
297
405
  hasEditorPluginsSection: editorPluginArray.found,
298
406
  hasPatternsSection: patternArray.found,
@@ -378,6 +486,18 @@ function ensureWorkspaceInventorySections(source) {
378
486
  if (!/export\s+const\s+REST_RESOURCES\b/u.test(nextSource)) {
379
487
  nextSource += REST_RESOURCES_CONST_SECTION;
380
488
  }
489
+ if (!/export\s+interface\s+WorkspaceAbilityConfig\b/u.test(nextSource)) {
490
+ nextSource += ABILITIES_INTERFACE_SECTION;
491
+ }
492
+ if (!/export\s+const\s+ABILITIES\b/u.test(nextSource)) {
493
+ nextSource += ABILITIES_CONST_SECTION;
494
+ }
495
+ if (!/export\s+interface\s+WorkspaceAiFeatureConfig\b/u.test(nextSource)) {
496
+ nextSource += AI_FEATURES_INTERFACE_SECTION;
497
+ }
498
+ if (!/export\s+const\s+AI_FEATURES\b/u.test(nextSource)) {
499
+ nextSource += AI_FEATURES_CONST_SECTION;
500
+ }
381
501
  if (!/export\s+interface\s+WorkspaceEditorPluginConfig\b/u.test(nextSource)) {
382
502
  nextSource += EDITOR_PLUGINS_INTERFACE_SECTION;
383
503
  }
@@ -395,18 +515,47 @@ function appendEntriesAtMarker(source, marker, entries) {
395
515
  }
396
516
  return source.replace(marker, `${entries.join("\n")}\n${marker}`);
397
517
  }
518
+ function escapeRegex(value) {
519
+ return value.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
520
+ }
521
+ function ensureInterfaceField(source, interfaceName, fieldName, fieldSource) {
522
+ const interfacePattern = new RegExp(`(export\\s+interface\\s+${escapeRegex(interfaceName)}\\s*\\{\\r?\\n)([\\s\\S]*?)(\\r?\\n\\})`, "u");
523
+ return source.replace(interfacePattern, (match, start, body, end) => {
524
+ if (new RegExp(`^[ \t]*${escapeRegex(fieldName)}\\??:`, "mu").test(body)) {
525
+ return match;
526
+ }
527
+ const lineEnding = start.endsWith("\r\n") ? "\r\n" : "\n";
528
+ const formattedFieldSource = `${fieldSource
529
+ .replace(/\r?\n$/u, "")
530
+ .split("\n")
531
+ .join(lineEnding)}${lineEnding}`;
532
+ const memberPattern = /^[ \t]*([A-Za-z_$][\w$]*)\??:/gmu;
533
+ for (const member of body.matchAll(memberPattern)) {
534
+ const memberIndex = member.index;
535
+ const memberName = member[1];
536
+ if (memberIndex === undefined || !memberName) {
537
+ continue;
538
+ }
539
+ if (memberName.localeCompare(fieldName) > 0) {
540
+ return `${start}${body.slice(0, memberIndex)}${formattedFieldSource}${body.slice(memberIndex)}${end}`;
541
+ }
542
+ }
543
+ return `${start}${body}${body.length > 0 && !body.endsWith(lineEnding) ? lineEnding : ""}${formattedFieldSource}${end}`;
544
+ });
545
+ }
398
546
  /**
399
547
  * Update `scripts/block-config.ts` source text with additional inventory entries.
400
548
  *
401
- * Missing `VARIATIONS` and `PATTERNS` sections are created automatically before
402
- * new entries are appended at their marker comments. When provided,
403
- * `transformSource` runs before any entries are inserted.
549
+ * Missing inventory sections for variations, patterns, binding sources, REST
550
+ * resources, workflow abilities, AI features, and editor plugins are created
551
+ * automatically before new entries are appended at their marker comments.
552
+ * When provided, `transformSource` runs before any entries are inserted.
404
553
  *
405
554
  * @param source Existing `scripts/block-config.ts` source.
406
555
  * @param options Entry lists plus an optional source transformer.
407
556
  * @returns Updated source text with all requested inventory entries appended.
408
557
  */
409
- export function updateWorkspaceInventorySource(source, { blockEntries = [], bindingSourceEntries = [], editorPluginEntries = [], patternEntries = [], restResourceEntries = [], variationEntries = [], transformSource, } = {}) {
558
+ export function updateWorkspaceInventorySource(source, { blockEntries = [], bindingSourceEntries = [], abilityEntries = [], aiFeatureEntries = [], editorPluginEntries = [], patternEntries = [], restResourceEntries = [], variationEntries = [], transformSource, } = {}) {
410
559
  let nextSource = ensureWorkspaceInventorySections(source);
411
560
  if (transformSource) {
412
561
  nextSource = transformSource(nextSource);
@@ -416,6 +565,10 @@ export function updateWorkspaceInventorySource(source, { blockEntries = [], bind
416
565
  nextSource = appendEntriesAtMarker(nextSource, PATTERN_CONFIG_ENTRY_MARKER, patternEntries);
417
566
  nextSource = appendEntriesAtMarker(nextSource, BINDING_SOURCE_CONFIG_ENTRY_MARKER, bindingSourceEntries);
418
567
  nextSource = appendEntriesAtMarker(nextSource, REST_RESOURCE_CONFIG_ENTRY_MARKER, restResourceEntries);
568
+ nextSource = appendEntriesAtMarker(nextSource, ABILITY_CONFIG_ENTRY_MARKER, abilityEntries);
569
+ nextSource = appendEntriesAtMarker(nextSource, AI_FEATURE_CONFIG_ENTRY_MARKER, aiFeatureEntries);
570
+ nextSource = ensureInterfaceField(nextSource, "WorkspaceAbilityConfig", "compatibility", WORKSPACE_COMPATIBILITY_CONFIG_FIELD);
571
+ nextSource = ensureInterfaceField(nextSource, "WorkspaceAiFeatureConfig", "compatibility", WORKSPACE_COMPATIBILITY_CONFIG_FIELD);
419
572
  nextSource = appendEntriesAtMarker(nextSource, EDITOR_PLUGIN_CONFIG_ENTRY_MARKER, editorPluginEntries);
420
573
  return nextSource;
421
574
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wp-typia/project-tools",
3
- "version": "0.19.2",
3
+ "version": "0.20.0",
4
4
  "description": "Project orchestration and programmatic tooling for wp-typia",
5
5
  "packageManager": "bun@1.3.11",
6
6
  "type": "module",
@@ -27,6 +27,11 @@
27
27
  "import": "./dist/runtime/cli-doctor.js",
28
28
  "default": "./dist/runtime/cli-doctor.js"
29
29
  },
30
+ "./cli-init": {
31
+ "types": "./dist/runtime/cli-init.d.ts",
32
+ "import": "./dist/runtime/cli-init.js",
33
+ "default": "./dist/runtime/cli-init.js"
34
+ },
30
35
  "./cli-prompt": {
31
36
  "types": "./dist/runtime/cli-prompt.d.ts",
32
37
  "import": "./dist/runtime/cli-prompt.js",
@@ -37,6 +42,16 @@
37
42
  "import": "./dist/runtime/cli-scaffold.js",
38
43
  "default": "./dist/runtime/cli-scaffold.js"
39
44
  },
45
+ "./ai-artifacts": {
46
+ "types": "./dist/runtime/ai-artifacts.d.ts",
47
+ "import": "./dist/runtime/ai-artifacts.js",
48
+ "default": "./dist/runtime/ai-artifacts.js"
49
+ },
50
+ "./typia-llm": {
51
+ "types": "./dist/runtime/typia-llm.d.ts",
52
+ "import": "./dist/runtime/typia-llm.js",
53
+ "default": "./dist/runtime/typia-llm.js"
54
+ },
40
55
  "./compound-inner-blocks": {
41
56
  "types": "./dist/runtime/compound-inner-blocks.d.ts",
42
57
  "import": "./dist/runtime/compound-inner-blocks.js",
@@ -90,7 +105,7 @@
90
105
  "prepack": "bun run build && node ./scripts/publish-manifest.mjs prepare",
91
106
  "postpack": "node ./scripts/publish-manifest.mjs restore",
92
107
  "test": "bun run build && bun test tests/*.test.ts",
93
- "test:scaffold-core": "bun run build && bun test tests/block-generator-service.test.ts tests/built-in-block-artifacts.test.ts tests/scaffold-basic.test.ts tests/scaffold-persistence.test.ts tests/template-source.test.ts tests/cli-entry.test.ts tests/cli-prompt.test.ts tests/import-policy.test.ts",
108
+ "test:scaffold-core": "bun run build && bun test tests/block-generator-service.test.ts tests/built-in-block-artifacts.test.ts tests/scaffold-basic.test.ts tests/scaffold-persistence.test.ts tests/template-source.test.ts tests/init-command.test.ts tests/cli-entry.test.ts tests/cli-prompt.test.ts tests/import-policy.test.ts tests/wordpress-ai-spec.test.ts tests/typia-llm.test.ts",
94
109
  "test:workspace": "bun run build && bun test tests/workspace-add.test.ts tests/workspace-doctor.test.ts",
95
110
  "test:compound": "bun run build && bun test tests/scaffold-compound.test.ts",
96
111
  "test:migration-planning": "bun run build && bun test tests/migration-init.test.ts tests/migration-config.test.ts tests/migration-plan-wizard.test.ts",
@@ -21,7 +21,6 @@
21
21
  "devDependencies": {
22
22
  "@wp-typia/block-runtime": "{{blockRuntimePackageVersion}}",
23
23
  "@wp-typia/block-types": "{{blockTypesPackageVersion}}",
24
- "ajv": "^8.18.0",
25
24
  "@types/wordpress__block-editor": "^11.5.17",
26
25
  "@types/wordpress__blocks": "^12.5.18",
27
26
  "@wordpress/browserslist-config": "^6.42.0",
@@ -3,9 +3,9 @@
3
3
  * Plugin Name: {{title}}
4
4
  * Description: {{description}}
5
5
  * Version: 0.1.0
6
- * Requires at least: 6.7
7
- * Tested up to: 6.9
8
- * Requires PHP: 8.0
6
+ * Requires at least: {{requiresAtLeast}}
7
+ * Tested up to: {{testedUpTo}}
8
+ * Requires PHP: {{requiresPhp}}
9
9
  * Author: {{author}}
10
10
  * License: GPL-2.0-or-later
11
11
  * License URI: https://www.gnu.org/licenses/gpl-2.0.html
@@ -29,7 +29,6 @@
29
29
  "@types/react-dom": "^19.2.2",
30
30
  "@types/wordpress__block-editor": "^11.5.17",
31
31
  "@types/wordpress__blocks": "^12.5.18",
32
- "ajv": "^8.18.0",
33
32
  "eslint-plugin-jsx-a11y": "^6.10.2",
34
33
  "prettier": "3.8.2",
35
34
  "tsx": "^4.20.5",
@@ -3,9 +3,9 @@
3
3
  * Plugin Name: {{title}}
4
4
  * Description: {{description}}
5
5
  * Version: 0.1.0
6
- * Requires at least: 6.7
7
- * Tested up to: 6.9
8
- * Requires PHP: 8.0
6
+ * Requires at least: {{requiresAtLeast}}
7
+ * Tested up to: {{testedUpTo}}
8
+ * Requires PHP: {{requiresPhp}}
9
9
  * Author: {{author}}
10
10
  * License: GPL-2.0-or-later
11
11
  * License URI: https://www.gnu.org/licenses/gpl-2.0.html
@@ -32,7 +32,6 @@
32
32
  "@types/react-dom": "^19.2.2",
33
33
  "@types/wordpress__block-editor": "^11.5.17",
34
34
  "@types/wordpress__blocks": "^12.5.18",
35
- "ajv": "^8.18.0",
36
35
  "eslint-plugin-jsx-a11y": "^6.10.2",
37
36
  "prettier": "3.8.2",
38
37
  "tsx": "^4.20.5",
@@ -3,9 +3,9 @@
3
3
  * Plugin Name: {{title}}
4
4
  * Description: {{description}}
5
5
  * Version: 0.1.0
6
- * Requires at least: 6.7
7
- * Tested up to: 6.9
8
- * Requires PHP: 8.0
6
+ * Requires at least: {{requiresAtLeast}}
7
+ * Tested up to: {{testedUpTo}}
8
+ * Requires PHP: {{requiresPhp}}
9
9
  * Author: {{author}}
10
10
  * License: GPL-2.0-or-later
11
11
  * License URI: https://www.gnu.org/licenses/gpl-2.0.html
@@ -3,9 +3,9 @@
3
3
  * Plugin Name: {{title}}
4
4
  * Description: {{description}}
5
5
  * Version: 0.1.0
6
- * Requires at least: 6.7
7
- * Tested up to: 6.9
8
- * Requires PHP: 8.0
6
+ * Requires at least: {{requiresAtLeast}}
7
+ * Tested up to: {{testedUpTo}}
8
+ * Requires PHP: {{requiresPhp}}
9
9
  * Author: {{author}}
10
10
  * License: GPL-2.0-or-later
11
11
  * License URI: https://www.gnu.org/licenses/gpl-2.0.html
@@ -3,9 +3,9 @@
3
3
  * Plugin Name: {{title}}
4
4
  * Description: {{description}}
5
5
  * Version: 0.1.0
6
- * Requires at least: 6.7
7
- * Tested up to: 6.9
8
- * Requires PHP: 8.0
6
+ * Requires at least: {{requiresAtLeast}}
7
+ * Tested up to: {{testedUpTo}}
8
+ * Requires PHP: {{requiresPhp}}
9
9
  * Author: {{author}}
10
10
  * License: GPL-2.0-or-later
11
11
  * License URI: https://www.gnu.org/licenses/gpl-2.0.html
@@ -24,7 +24,6 @@
24
24
  "@wp-typia/api-client": "{{apiClientPackageVersion}}",
25
25
  "@wp-typia/block-types": "{{blockTypesPackageVersion}}",
26
26
  "@wp-typia/rest": "{{restPackageVersion}}",
27
- "ajv": "^8.18.0",
28
27
  "@types/wordpress__block-editor": "^11.5.17",
29
28
  "@types/wordpress__blocks": "^12.5.18",
30
29
  "@wordpress/browserslist-config": "^6.42.0",
@@ -3,9 +3,9 @@
3
3
  * Plugin Name: {{title}}
4
4
  * Description: {{description}}
5
5
  * Version: 0.1.0
6
- * Requires at least: 6.7
7
- * Tested up to: 6.9
8
- * Requires PHP: 8.0
6
+ * Requires at least: {{requiresAtLeast}}
7
+ * Tested up to: {{testedUpTo}}
8
+ * Requires PHP: {{requiresPhp}}
9
9
  * Author: {{author}}
10
10
  * License: GPL-2.0-or-later
11
11
  * License URI: https://www.gnu.org/licenses/gpl-2.0.html