@wp-typia/project-tools 0.19.3 → 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 (50) 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-doctor-workspace.js +140 -1
  28. package/dist/runtime/cli-help.js +4 -0
  29. package/dist/runtime/index.d.ts +3 -1
  30. package/dist/runtime/index.js +2 -1
  31. package/dist/runtime/scaffold-compatibility.d.ts +65 -0
  32. package/dist/runtime/scaffold-compatibility.js +152 -0
  33. package/dist/runtime/scaffold-template-variable-groups.d.ts +2 -0
  34. package/dist/runtime/scaffold-template-variables.js +6 -0
  35. package/dist/runtime/scaffold.d.ts +3 -0
  36. package/dist/runtime/typia-llm.d.ts +213 -0
  37. package/dist/runtime/typia-llm.js +348 -0
  38. package/dist/runtime/wordpress-ai.d.ts +122 -0
  39. package/dist/runtime/wordpress-ai.js +177 -0
  40. package/dist/runtime/workspace-inventory.d.ts +51 -4
  41. package/dist/runtime/workspace-inventory.js +157 -4
  42. package/package.json +12 -2
  43. package/templates/_shared/base/{{slugKebabCase}}.php.mustache +3 -3
  44. package/templates/_shared/compound/core/{{slugKebabCase}}.php.mustache +3 -3
  45. package/templates/_shared/compound/persistence-auth/{{slugKebabCase}}.php.mustache +3 -3
  46. package/templates/_shared/compound/persistence-public/{{slugKebabCase}}.php.mustache +3 -3
  47. package/templates/_shared/persistence/auth/{{slugKebabCase}}.php.mustache +3 -3
  48. package/templates/_shared/persistence/core/{{slugKebabCase}}.php.mustache +3 -3
  49. package/templates/_shared/persistence/public/{{slugKebabCase}}.php.mustache +3 -3
  50. package/templates/query-loop/{{slugKebabCase}}.php.mustache +3 -3
@@ -11,6 +11,10 @@ const WORKSPACE_BINDING_SERVER_GLOB = "/src/bindings/*/server.php";
11
11
  const WORKSPACE_BINDING_EDITOR_SCRIPT = "build/bindings/index.js";
12
12
  const WORKSPACE_BINDING_EDITOR_ASSET = "build/bindings/index.asset.php";
13
13
  const WORKSPACE_REST_RESOURCE_GLOB = "/inc/rest/*.php";
14
+ const WORKSPACE_ABILITY_GLOB = "/inc/abilities/*.php";
15
+ const WORKSPACE_ABILITY_EDITOR_SCRIPT = "build/abilities/index.js";
16
+ const WORKSPACE_ABILITY_EDITOR_ASSET = "build/abilities/index.asset.php";
17
+ const WORKSPACE_AI_FEATURE_GLOB = "/inc/ai-features/*.php";
14
18
  const WORKSPACE_EDITOR_PLUGIN_EDITOR_SCRIPT = "build/editor-plugins/index.js";
15
19
  const WORKSPACE_EDITOR_PLUGIN_EDITOR_ASSET = "build/editor-plugins/index.asset.php";
16
20
  const WORKSPACE_EDITOR_PLUGIN_EDITOR_STYLE = "build/editor-plugins/style-index.css";
@@ -249,6 +253,126 @@ function checkWorkspaceRestResourceBootstrap(projectDir, packageName, phpPrefix)
249
253
  ? "REST resource PHP loader hook is present"
250
254
  : "Missing REST resource PHP require glob or init hook");
251
255
  }
256
+ function getWorkspaceAbilityRequiredFiles(ability) {
257
+ return Array.from(new Set([
258
+ ability.clientFile,
259
+ ability.configFile,
260
+ ability.dataFile,
261
+ ability.inputSchemaFile,
262
+ ability.outputSchemaFile,
263
+ ability.phpFile,
264
+ ability.typesFile,
265
+ ]));
266
+ }
267
+ function checkWorkspaceAbilityConfig(projectDir, ability) {
268
+ const configPath = path.join(projectDir, ability.configFile);
269
+ if (!fs.existsSync(configPath)) {
270
+ return createDoctorCheck(`Ability config ${ability.slug}`, "fail", `Missing ${ability.configFile}`);
271
+ }
272
+ try {
273
+ const config = JSON.parse(fs.readFileSync(configPath, "utf8"));
274
+ const abilityId = typeof config.abilityId === "string" ? config.abilityId.trim() : "";
275
+ const categorySlug = typeof config.category?.slug === "string"
276
+ ? config.category.slug.trim()
277
+ : "";
278
+ const hasValidAbilityId = /^[a-z0-9-]+\/[a-z0-9-]+$/u.test(abilityId);
279
+ const hasValidCategorySlug = /^[a-z0-9-]+$/u.test(categorySlug);
280
+ return createDoctorCheck(`Ability config ${ability.slug}`, hasValidAbilityId && hasValidCategorySlug ? "pass" : "fail", hasValidAbilityId && hasValidCategorySlug
281
+ ? `Ability id ${abilityId} in category ${categorySlug} is valid`
282
+ : "Ability config must define a valid abilityId (`namespace/ability-name`) and category.slug.");
283
+ }
284
+ catch (error) {
285
+ return createDoctorCheck(`Ability config ${ability.slug}`, "fail", error instanceof Error ? error.message : String(error));
286
+ }
287
+ }
288
+ function checkWorkspaceAbilityBootstrap(projectDir, packageName, phpPrefix) {
289
+ const packageBaseName = packageName.split("/").pop() ?? packageName;
290
+ const bootstrapPath = path.join(projectDir, `${packageBaseName}.php`);
291
+ if (!fs.existsSync(bootstrapPath)) {
292
+ return createDoctorCheck("Ability bootstrap", "fail", `Missing ${path.basename(bootstrapPath)}`);
293
+ }
294
+ const source = fs.readFileSync(bootstrapPath, "utf8");
295
+ const loadFunctionName = `${phpPrefix}_load_workflow_abilities`;
296
+ const enqueueFunctionName = `${phpPrefix}_enqueue_workflow_abilities`;
297
+ const loadHook = `add_action( 'plugins_loaded', '${loadFunctionName}' );`;
298
+ const adminEnqueueHook = `add_action( 'admin_enqueue_scripts', '${enqueueFunctionName}' );`;
299
+ const editorEnqueueHook = `add_action( 'enqueue_block_editor_assets', '${enqueueFunctionName}' );`;
300
+ const hasLoaderHook = source.includes(loadHook);
301
+ const hasAdminEnqueueHook = source.includes(adminEnqueueHook);
302
+ const hasEditorEnqueueHook = source.includes(editorEnqueueHook);
303
+ const hasServerGlob = source.includes(WORKSPACE_ABILITY_GLOB);
304
+ const hasEditorScript = source.includes(WORKSPACE_ABILITY_EDITOR_SCRIPT);
305
+ const hasEditorAsset = source.includes(WORKSPACE_ABILITY_EDITOR_ASSET);
306
+ return createDoctorCheck("Ability bootstrap", hasLoaderHook &&
307
+ hasAdminEnqueueHook &&
308
+ hasEditorEnqueueHook &&
309
+ hasServerGlob &&
310
+ hasEditorScript &&
311
+ hasEditorAsset
312
+ ? "pass"
313
+ : "fail", hasLoaderHook &&
314
+ hasAdminEnqueueHook &&
315
+ hasEditorEnqueueHook &&
316
+ hasServerGlob &&
317
+ hasEditorScript &&
318
+ hasEditorAsset
319
+ ? "Ability loader and admin/editor client bootstrap hooks are present"
320
+ : "Missing ability loader hook or build/abilities script asset references");
321
+ }
322
+ function checkWorkspaceAbilityIndex(projectDir, abilities) {
323
+ const indexRelativePath = [
324
+ path.join("src", "abilities", "index.ts"),
325
+ path.join("src", "abilities", "index.js"),
326
+ ].find((relativePath) => fs.existsSync(path.join(projectDir, relativePath)));
327
+ if (!indexRelativePath) {
328
+ return createDoctorCheck("Abilities index", "fail", "Missing src/abilities/index.ts or src/abilities/index.js");
329
+ }
330
+ const indexPath = path.join(projectDir, indexRelativePath);
331
+ const source = fs.readFileSync(indexPath, "utf8");
332
+ const missingExports = abilities.filter((ability) => {
333
+ const exportPattern = new RegExp(`^\\s*export\\s+(?:\\*\\s+from|\\{[^}]+\\}\\s+from)\\s+['"\`]\\./${escapeRegex(ability.slug)}\\/client['"\`]`, "mu");
334
+ return !exportPattern.test(source);
335
+ });
336
+ return createDoctorCheck("Abilities index", missingExports.length === 0 ? "pass" : "fail", missingExports.length === 0
337
+ ? "Ability client helpers are aggregated"
338
+ : `Missing ability exports for: ${missingExports.map((entry) => entry.slug).join(", ")}`);
339
+ }
340
+ function getWorkspaceAiFeatureRequiredFiles(aiFeature) {
341
+ return Array.from(new Set([
342
+ aiFeature.aiSchemaFile,
343
+ aiFeature.apiFile,
344
+ path.join(path.dirname(aiFeature.typesFile), "api-schemas", "feature-request.schema.json"),
345
+ path.join(path.dirname(aiFeature.typesFile), "api-schemas", "feature-response.schema.json"),
346
+ path.join(path.dirname(aiFeature.typesFile), "api-schemas", "feature-result.schema.json"),
347
+ aiFeature.clientFile,
348
+ aiFeature.dataFile,
349
+ aiFeature.openApiFile,
350
+ aiFeature.phpFile,
351
+ aiFeature.typesFile,
352
+ aiFeature.validatorsFile,
353
+ ]));
354
+ }
355
+ function checkWorkspaceAiFeatureConfig(aiFeature) {
356
+ const hasNamespace = REST_RESOURCE_NAMESPACE_PATTERN.test(aiFeature.namespace);
357
+ return createDoctorCheck(`AI feature config ${aiFeature.slug}`, hasNamespace ? "pass" : "fail", hasNamespace
358
+ ? `AI feature namespace ${aiFeature.namespace} is valid`
359
+ : "AI feature namespace is invalid");
360
+ }
361
+ function checkWorkspaceAiFeatureBootstrap(projectDir, packageName, phpPrefix) {
362
+ const packageBaseName = packageName.split("/").pop() ?? packageName;
363
+ const bootstrapPath = path.join(projectDir, `${packageBaseName}.php`);
364
+ if (!fs.existsSync(bootstrapPath)) {
365
+ return createDoctorCheck("AI feature bootstrap", "fail", `Missing ${path.basename(bootstrapPath)}`);
366
+ }
367
+ const source = fs.readFileSync(bootstrapPath, "utf8");
368
+ const registerFunctionName = `${phpPrefix}_register_ai_features`;
369
+ const registerHook = `add_action( 'init', '${registerFunctionName}', 20 );`;
370
+ const hasServerGlob = source.includes(WORKSPACE_AI_FEATURE_GLOB);
371
+ const hasRegisterHook = source.includes(registerHook);
372
+ return createDoctorCheck("AI feature bootstrap", hasServerGlob && hasRegisterHook ? "pass" : "fail", hasServerGlob && hasRegisterHook
373
+ ? "AI feature PHP loader hook is present"
374
+ : "Missing AI feature PHP require glob or init hook");
375
+ }
252
376
  function getWorkspaceEditorPluginRequiredFiles(editorPlugin) {
253
377
  const editorPluginDir = path.join("src", "editor-plugins", editorPlugin.slug);
254
378
  return Array.from(new Set([
@@ -377,7 +501,7 @@ export function getWorkspaceDoctorChecks(cwd) {
377
501
  checks.push(checkWorkspacePackageMetadata(workspace, workspacePackageJson));
378
502
  try {
379
503
  const inventory = readWorkspaceInventory(workspace.projectDir);
380
- checks.push(createDoctorCheck("Workspace inventory", "pass", `${inventory.blocks.length} block(s), ${inventory.variations.length} variation(s), ${inventory.patterns.length} pattern(s), ${inventory.bindingSources.length} binding source(s), ${inventory.restResources.length} REST resource(s), ${inventory.editorPlugins.length} editor plugin(s)`));
504
+ checks.push(createDoctorCheck("Workspace inventory", "pass", `${inventory.blocks.length} block(s), ${inventory.variations.length} variation(s), ${inventory.patterns.length} pattern(s), ${inventory.bindingSources.length} binding source(s), ${inventory.restResources.length} REST resource(s), ${inventory.abilities.length} ability scaffold(s), ${inventory.aiFeatures.length} AI feature(s), ${inventory.editorPlugins.length} editor plugin(s)`));
381
505
  for (const block of inventory.blocks) {
382
506
  checks.push(checkExistingFiles(workspace.projectDir, `Block ${block.slug}`, getWorkspaceBlockRequiredFiles(block)));
383
507
  checks.push(checkWorkspaceBlockMetadata(workspace.projectDir, workspace, block));
@@ -422,6 +546,21 @@ export function getWorkspaceDoctorChecks(cwd) {
422
546
  checks.push(checkWorkspaceRestResourceConfig(restResource));
423
547
  checks.push(checkExistingFiles(workspace.projectDir, `REST resource ${restResource.slug}`, getWorkspaceRestResourceRequiredFiles(restResource)));
424
548
  }
549
+ if (inventory.abilities.length > 0) {
550
+ checks.push(checkWorkspaceAbilityBootstrap(workspace.projectDir, workspace.packageName, workspace.workspace.phpPrefix));
551
+ checks.push(checkWorkspaceAbilityIndex(workspace.projectDir, inventory.abilities));
552
+ }
553
+ for (const ability of inventory.abilities) {
554
+ checks.push(checkWorkspaceAbilityConfig(workspace.projectDir, ability));
555
+ checks.push(checkExistingFiles(workspace.projectDir, `Ability ${ability.slug}`, getWorkspaceAbilityRequiredFiles(ability)));
556
+ }
557
+ if (inventory.aiFeatures.length > 0) {
558
+ checks.push(checkWorkspaceAiFeatureBootstrap(workspace.projectDir, workspace.packageName, workspace.workspace.phpPrefix));
559
+ }
560
+ for (const aiFeature of inventory.aiFeatures) {
561
+ checks.push(checkWorkspaceAiFeatureConfig(aiFeature));
562
+ checks.push(checkExistingFiles(workspace.projectDir, `AI feature ${aiFeature.slug}`, getWorkspaceAiFeatureRequiredFiles(aiFeature)));
563
+ }
425
564
  if (inventory.editorPlugins.length > 0) {
426
565
  checks.push(checkWorkspaceEditorPluginBootstrap(workspace.projectDir, workspace.packageName, workspace.workspace.phpPrefix));
427
566
  checks.push(checkWorkspaceEditorPluginIndex(workspace.projectDir, inventory.editorPlugins));
@@ -23,6 +23,8 @@ export function formatHelpText() {
23
23
  wp-typia add pattern <name>
24
24
  wp-typia add binding-source <name>
25
25
  wp-typia add rest-resource <name> [--namespace <vendor/v1>] [--methods <method[,method...]>]
26
+ wp-typia add ability <name>
27
+ wp-typia add ai-feature <name> [--namespace <vendor/v1>]
26
28
  wp-typia add editor-plugin <name> [--slot <PluginSidebar>]
27
29
  wp-typia add hooked-block <block-slug> --anchor <anchor-block-name> --position <before|after|firstChild|lastChild>
28
30
  wp-typia migrate <init|snapshot|diff|scaffold|verify|doctor|fixtures|fuzz> [...]
@@ -45,6 +47,8 @@ Notes:
45
47
  \`add pattern\` scaffolds a namespaced PHP pattern shell under \`src/patterns/\`.
46
48
  \`add binding-source\` scaffolds shared PHP and editor registration under \`src/bindings/\`.
47
49
  \`add rest-resource\` scaffolds plugin-level TypeScript REST contracts under \`src/rest/\` and PHP route glue under \`inc/rest/\`.
50
+ \`add ability\` scaffolds typed workflow abilities under \`src/abilities/\` and server registration under \`inc/abilities/\`.
51
+ \`add ai-feature\` scaffolds server-owned AI feature endpoints under \`src/ai-features/\` and PHP route glue under \`inc/ai-features/\`.
48
52
  \`add editor-plugin\` scaffolds a document-level editor extension under \`src/editor-plugins/\`.
49
53
  \`add hooked-block\` patches an existing workspace block's \`block.json\` \`blockHooks\` metadata.
50
54
  \`wp-typia doctor\` always checks environment readiness and reports when it only ran environment-level diagnostics; official workspace roots also get inventory and source-tree drift checks.
@@ -13,6 +13,8 @@
13
13
  export { COMPOUND_INNER_BLOCKS_PRESET_IDS, DEFAULT_COMPOUND_INNER_BLOCKS_PRESET_ID, getCompoundInnerBlocksPresetDefinition, isCompoundInnerBlocksPresetId, parseCompoundInnerBlocksPreset, resolveCompoundInnerBlocksPreset, } from "./compound-inner-blocks.js";
14
14
  export type { CompoundInnerBlocksPresetDefinition, CompoundInnerBlocksPresetId, CompoundInnerBlocksTemplateLock, } from "./compound-inner-blocks.js";
15
15
  export { scaffoldProject, collectScaffoldAnswers, getScaffoldTemplateVariableGroups, getDefaultAnswers, getTemplateVariables, resolvePackageManagerId, resolveTemplateId, } from "./scaffold.js";
16
+ export { DEFAULT_SCAFFOLD_COMPATIBILITY, OPTIONAL_WORDPRESS_AI_CLIENT_COMPATIBILITY, REQUIRED_WORKSPACE_ABILITY_COMPATIBILITY, createScaffoldCompatibilityConfig, renderScaffoldCompatibilityConfig, resolveScaffoldCompatibilityPolicy, updatePluginHeaderCompatibility, } from "./scaffold-compatibility.js";
17
+ export type { ScaffoldCompatibilityConfig, ScaffoldCompatibilityPolicy, ScaffoldPluginHeaderCompatibility, } from "./scaffold-compatibility.js";
16
18
  export { BlockGeneratorService } from "./block-generator-service.js";
17
19
  export type { BasicScaffoldTemplateVariableGroups, CompoundScaffoldTemplateVariableGroups, ExternalScaffoldTemplateVariableGroups, FlatScaffoldTemplateVariables, InteractivityScaffoldTemplateVariableGroups, PersistenceScaffoldTemplateVariableGroups, QueryLoopScaffoldTemplateVariableGroups, ScaffoldTemplateFamily, ScaffoldTemplateVariableGroups, } from "./scaffold.js";
18
20
  export type { ApplyBlockInput, BlockGenerationTarget, BlockSpec, PlanBlockInput, PlanBlockResult, RenderBlockInput, RenderBlockResult, ValidateBlockInput, ValidateBlockResult, } from "./block-generator-service.js";
@@ -26,5 +28,5 @@ export type { EndpointAuthIntent, EndpointOpenApiAuthMode, EndpointOpenApiContra
26
28
  export { PACKAGE_MANAGER_IDS, PACKAGE_MANAGERS, formatPackageExecCommand, formatInstallCommand, formatRunScript, getPackageManager, getPackageManagerSelectOptions, transformPackageManagerText, } from "./package-managers.js";
27
29
  export { TEMPLATE_IDS, TEMPLATE_REGISTRY, getTemplateById, getTemplateSelectOptions, listTemplates, } from "./template-registry.js";
28
30
  export { STALE_TEMP_ROOT_MAX_AGE_MS, WP_TYPIA_TEMP_ROOT_PREFIX, cleanupManagedTempRoot, cleanupStaleTempRoots, createManagedTempRoot, getTrackedTempRoots, } from "./temp-roots.js";
29
- export { createReadlinePrompt, createCliCommandError, CliDiagnosticError, formatCliDiagnosticError, formatAddHelpText, formatDoctorCheckLine, formatDoctorSummaryLine, formatHelpText, formatTemplateDetails, formatTemplateFeatures, formatTemplateSummary, getDoctorChecks, getDoctorFailureDetailLines, getFailingDoctorChecks, getNextSteps, getOptionalOnboarding, getWorkspaceBlockSelectOptions, HOOKED_BLOCK_POSITION_IDS, EDITOR_PLUGIN_SLOT_IDS, isCliDiagnosticError, runAddBindingSourceCommand, runAddBlockCommand, runAddEditorPluginCommand, runAddHookedBlockCommand, runAddPatternCommand, runDoctor, runAddVariationCommand, runScaffoldFlow, } from "./cli-core.js";
31
+ export { createReadlinePrompt, createCliCommandError, CliDiagnosticError, formatCliDiagnosticError, formatAddHelpText, formatDoctorCheckLine, formatDoctorSummaryLine, formatHelpText, formatTemplateDetails, formatTemplateFeatures, formatTemplateSummary, getDoctorChecks, getDoctorFailureDetailLines, getFailingDoctorChecks, getNextSteps, getOptionalOnboarding, getWorkspaceBlockSelectOptions, HOOKED_BLOCK_POSITION_IDS, EDITOR_PLUGIN_SLOT_IDS, isCliDiagnosticError, runAddAbilityCommand, runAddAiFeatureCommand, runAddBindingSourceCommand, runAddBlockCommand, runAddEditorPluginCommand, runAddHookedBlockCommand, runAddPatternCommand, runDoctor, runAddVariationCommand, runScaffoldFlow, } from "./cli-core.js";
30
32
  export type { CliDiagnosticMessage, DoctorCheck, EditorPluginSlotId, HookedBlockPositionId, ReadlinePrompt, } from "./cli-core.js";
@@ -12,6 +12,7 @@
12
12
  */
13
13
  export { COMPOUND_INNER_BLOCKS_PRESET_IDS, DEFAULT_COMPOUND_INNER_BLOCKS_PRESET_ID, getCompoundInnerBlocksPresetDefinition, isCompoundInnerBlocksPresetId, parseCompoundInnerBlocksPreset, resolveCompoundInnerBlocksPreset, } from "./compound-inner-blocks.js";
14
14
  export { scaffoldProject, collectScaffoldAnswers, getScaffoldTemplateVariableGroups, getDefaultAnswers, getTemplateVariables, resolvePackageManagerId, resolveTemplateId, } from "./scaffold.js";
15
+ export { DEFAULT_SCAFFOLD_COMPATIBILITY, OPTIONAL_WORDPRESS_AI_CLIENT_COMPATIBILITY, REQUIRED_WORKSPACE_ABILITY_COMPATIBILITY, createScaffoldCompatibilityConfig, renderScaffoldCompatibilityConfig, resolveScaffoldCompatibilityPolicy, updatePluginHeaderCompatibility, } from "./scaffold-compatibility.js";
15
16
  export { BlockGeneratorService } from "./block-generator-service.js";
16
17
  export { BLOCK_GENERATION_TOOL_CONTRACT_VERSION, inspectBlockGeneration, } from "./block-generator-tool-contract.js";
17
18
  export { formatMigrationHelpText, parseMigrationArgs, runMigrationCommand, } from "./migrations.js";
@@ -21,4 +22,4 @@ export { buildCompoundChildStarterManifestDocument, getStarterManifestFiles, str
21
22
  export { PACKAGE_MANAGER_IDS, PACKAGE_MANAGERS, formatPackageExecCommand, formatInstallCommand, formatRunScript, getPackageManager, getPackageManagerSelectOptions, transformPackageManagerText, } from "./package-managers.js";
22
23
  export { TEMPLATE_IDS, TEMPLATE_REGISTRY, getTemplateById, getTemplateSelectOptions, listTemplates, } from "./template-registry.js";
23
24
  export { STALE_TEMP_ROOT_MAX_AGE_MS, WP_TYPIA_TEMP_ROOT_PREFIX, cleanupManagedTempRoot, cleanupStaleTempRoots, createManagedTempRoot, getTrackedTempRoots, } from "./temp-roots.js";
24
- export { createReadlinePrompt, createCliCommandError, CliDiagnosticError, formatCliDiagnosticError, formatAddHelpText, formatDoctorCheckLine, formatDoctorSummaryLine, formatHelpText, formatTemplateDetails, formatTemplateFeatures, formatTemplateSummary, getDoctorChecks, getDoctorFailureDetailLines, getFailingDoctorChecks, getNextSteps, getOptionalOnboarding, getWorkspaceBlockSelectOptions, HOOKED_BLOCK_POSITION_IDS, EDITOR_PLUGIN_SLOT_IDS, isCliDiagnosticError, runAddBindingSourceCommand, runAddBlockCommand, runAddEditorPluginCommand, runAddHookedBlockCommand, runAddPatternCommand, runDoctor, runAddVariationCommand, runScaffoldFlow, } from "./cli-core.js";
25
+ export { createReadlinePrompt, createCliCommandError, CliDiagnosticError, formatCliDiagnosticError, formatAddHelpText, formatDoctorCheckLine, formatDoctorSummaryLine, formatHelpText, formatTemplateDetails, formatTemplateFeatures, formatTemplateSummary, getDoctorChecks, getDoctorFailureDetailLines, getFailingDoctorChecks, getNextSteps, getOptionalOnboarding, getWorkspaceBlockSelectOptions, HOOKED_BLOCK_POSITION_IDS, EDITOR_PLUGIN_SLOT_IDS, isCliDiagnosticError, runAddAbilityCommand, runAddAiFeatureCommand, runAddBindingSourceCommand, runAddBlockCommand, runAddEditorPluginCommand, runAddHookedBlockCommand, runAddPatternCommand, runDoctor, runAddVariationCommand, runScaffoldFlow, } from "./cli-core.js";
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Compatibility policy helpers for generated scaffold outputs.
3
+ *
4
+ * The policy keeps plugin headers, runtime gates, and workspace inventory
5
+ * metadata aligned when optional or required AI-capable features are added.
6
+ */
7
+ import { type AiFeatureCapabilitySelection, type AiFeatureCompatibilityFloor, type ResolvedAiFeatureCapabilityPlan } from "./ai-feature-capability.js";
8
+ /**
9
+ * WordPress plugin header version floors emitted by scaffold templates.
10
+ */
11
+ export interface ScaffoldPluginHeaderCompatibility {
12
+ requiresAtLeast: string;
13
+ requiresPhp: string;
14
+ testedUpTo: string;
15
+ }
16
+ /**
17
+ * Resolved compatibility policy for a set of scaffold feature capabilities.
18
+ */
19
+ export interface ScaffoldCompatibilityPolicy {
20
+ capabilityPlan: ResolvedAiFeatureCapabilityPlan;
21
+ pluginHeader: ScaffoldPluginHeaderCompatibility;
22
+ }
23
+ /**
24
+ * Serializable compatibility metadata stored in generated workspace inventory.
25
+ */
26
+ export interface ScaffoldCompatibilityConfig {
27
+ hardMinimums: AiFeatureCompatibilityFloor;
28
+ mode: "baseline" | "optional" | "required";
29
+ optionalFeatures: string[];
30
+ requiredFeatures: string[];
31
+ runtimeGates: string[];
32
+ }
33
+ /**
34
+ * Baseline headers used by scaffold output before optional features are added.
35
+ */
36
+ export declare const DEFAULT_SCAFFOLD_COMPATIBILITY: ScaffoldPluginHeaderCompatibility;
37
+ /**
38
+ * Optional WordPress AI Client surface used by server-only AI feature scaffold.
39
+ */
40
+ export declare const OPTIONAL_WORDPRESS_AI_CLIENT_COMPATIBILITY: readonly AiFeatureCapabilitySelection[];
41
+ /**
42
+ * Required Abilities API surface used by typed workflow ability scaffold.
43
+ */
44
+ export declare const REQUIRED_WORKSPACE_ABILITY_COMPATIBILITY: readonly AiFeatureCapabilitySelection[];
45
+ /**
46
+ * Resolve plugin header floors and capability gates for scaffold selections.
47
+ */
48
+ export declare function resolveScaffoldCompatibilityPolicy(selections: readonly AiFeatureCapabilitySelection[], { baseline, }?: {
49
+ baseline?: ScaffoldPluginHeaderCompatibility;
50
+ }): ScaffoldCompatibilityPolicy;
51
+ /**
52
+ * Convert a resolved policy into workspace-inventory-safe JSON metadata.
53
+ */
54
+ export declare function createScaffoldCompatibilityConfig(policy: ScaffoldCompatibilityPolicy): ScaffoldCompatibilityConfig;
55
+ /**
56
+ * Render compatibility metadata as formatted TypeScript object literal JSON.
57
+ */
58
+ export declare function renderScaffoldCompatibilityConfig(policy: ScaffoldCompatibilityPolicy, indent?: string): string;
59
+ /**
60
+ * Patch a generated plugin bootstrap header without lowering custom floors.
61
+ *
62
+ * Preserves the original header line endings while replacing empty or invalid
63
+ * version strings with the policy values.
64
+ */
65
+ export declare function updatePluginHeaderCompatibility(source: string, policy: ScaffoldCompatibilityPolicy): string;
@@ -0,0 +1,152 @@
1
+ /**
2
+ * Compatibility policy helpers for generated scaffold outputs.
3
+ *
4
+ * The policy keeps plugin headers, runtime gates, and workspace inventory
5
+ * metadata aligned when optional or required AI-capable features are added.
6
+ */
7
+ import { AI_FEATURE_DEFINITIONS, resolveAiFeatureCapabilityPlan, } from "./ai-feature-capability.js";
8
+ /**
9
+ * Baseline headers used by scaffold output before optional features are added.
10
+ */
11
+ export const DEFAULT_SCAFFOLD_COMPATIBILITY = {
12
+ requiresAtLeast: "6.7",
13
+ requiresPhp: "8.0",
14
+ testedUpTo: "6.9",
15
+ };
16
+ /**
17
+ * Optional WordPress AI Client surface used by server-only AI feature scaffold.
18
+ */
19
+ export const OPTIONAL_WORDPRESS_AI_CLIENT_COMPATIBILITY = [
20
+ {
21
+ featureId: AI_FEATURE_DEFINITIONS.wordpressAiClient.id,
22
+ mode: "optional",
23
+ },
24
+ ];
25
+ /**
26
+ * Required Abilities API surface used by typed workflow ability scaffold.
27
+ */
28
+ export const REQUIRED_WORKSPACE_ABILITY_COMPATIBILITY = [
29
+ {
30
+ featureId: AI_FEATURE_DEFINITIONS.wordpressServerAbilities.id,
31
+ mode: "required",
32
+ },
33
+ {
34
+ featureId: AI_FEATURE_DEFINITIONS.wordpressCoreAbilities.id,
35
+ mode: "required",
36
+ },
37
+ ];
38
+ function parseVersionFloorParts(value) {
39
+ return value.split(".").map((part, index) => {
40
+ if (!/^\d+$/u.test(part)) {
41
+ throw new Error(`parseVersionFloorParts received an invalid version floor "${value}" at segment ${index + 1}.`);
42
+ }
43
+ return Number.parseInt(part, 10);
44
+ });
45
+ }
46
+ function compareVersionFloors(left, right) {
47
+ const leftParts = parseVersionFloorParts(left);
48
+ const rightParts = parseVersionFloorParts(right);
49
+ const length = Math.max(leftParts.length, rightParts.length);
50
+ for (let index = 0; index < length; index += 1) {
51
+ const leftValue = leftParts[index] ?? 0;
52
+ const rightValue = rightParts[index] ?? 0;
53
+ if (leftValue > rightValue) {
54
+ return 1;
55
+ }
56
+ if (leftValue < rightValue) {
57
+ return -1;
58
+ }
59
+ }
60
+ return 0;
61
+ }
62
+ function pickHigherVersionFloor(current, candidate) {
63
+ if (!candidate) {
64
+ return current;
65
+ }
66
+ return compareVersionFloors(current, candidate) >= 0 ? current : candidate;
67
+ }
68
+ function pickHigherHeaderVersionFloor(policyValue, currentValue) {
69
+ const normalizedCurrentValue = currentValue.trim();
70
+ if (!normalizedCurrentValue) {
71
+ return policyValue;
72
+ }
73
+ try {
74
+ return pickHigherVersionFloor(policyValue, normalizedCurrentValue);
75
+ }
76
+ catch {
77
+ return policyValue;
78
+ }
79
+ }
80
+ function formatRuntimeGate(feature) {
81
+ return (feature.runtimeGates ?? []).map((gate) => `${feature.label}: ${gate.kind} ${gate.value}`);
82
+ }
83
+ function getPolicyMode(capabilityPlan) {
84
+ if (capabilityPlan.requiredFeatures.length > 0) {
85
+ return "required";
86
+ }
87
+ if (capabilityPlan.optionalFeatures.length > 0) {
88
+ return "optional";
89
+ }
90
+ return "baseline";
91
+ }
92
+ /**
93
+ * Resolve plugin header floors and capability gates for scaffold selections.
94
+ */
95
+ export function resolveScaffoldCompatibilityPolicy(selections, { baseline = DEFAULT_SCAFFOLD_COMPATIBILITY, } = {}) {
96
+ const capabilityPlan = resolveAiFeatureCapabilityPlan(selections);
97
+ const requiresAtLeast = pickHigherVersionFloor(baseline.requiresAtLeast, capabilityPlan.hardMinimums.wordpress);
98
+ const requiresPhp = pickHigherVersionFloor(baseline.requiresPhp, capabilityPlan.hardMinimums.php);
99
+ const testedUpTo = pickHigherVersionFloor(baseline.testedUpTo, requiresAtLeast);
100
+ return {
101
+ capabilityPlan,
102
+ pluginHeader: {
103
+ requiresAtLeast,
104
+ requiresPhp,
105
+ testedUpTo,
106
+ },
107
+ };
108
+ }
109
+ /**
110
+ * Convert a resolved policy into workspace-inventory-safe JSON metadata.
111
+ */
112
+ export function createScaffoldCompatibilityConfig(policy) {
113
+ const { capabilityPlan } = policy;
114
+ return {
115
+ hardMinimums: capabilityPlan.hardMinimums,
116
+ mode: getPolicyMode(capabilityPlan),
117
+ optionalFeatures: capabilityPlan.optionalFeatures.map((feature) => feature.label),
118
+ requiredFeatures: capabilityPlan.requiredFeatures.map((feature) => feature.label),
119
+ runtimeGates: [
120
+ ...capabilityPlan.requiredFeatures.flatMap(formatRuntimeGate),
121
+ ...capabilityPlan.optionalFeatures.flatMap(formatRuntimeGate),
122
+ ],
123
+ };
124
+ }
125
+ /**
126
+ * Render compatibility metadata as formatted TypeScript object literal JSON.
127
+ */
128
+ export function renderScaffoldCompatibilityConfig(policy, indent = "\t\t") {
129
+ const config = createScaffoldCompatibilityConfig(policy);
130
+ return JSON.stringify(config, null, "\t")
131
+ .split("\n")
132
+ .map((line, index) => (index === 0 ? line : `${indent}${line}`))
133
+ .join("\n");
134
+ }
135
+ function replacePluginHeaderVersionFloor(source, pattern, policyValue) {
136
+ return source.replace(pattern, (_match, prefix, currentValue, lineEnding) => {
137
+ const versionPrefix = prefix.endsWith(":") ? `${prefix} ` : prefix;
138
+ return `${versionPrefix}${pickHigherHeaderVersionFloor(policyValue, currentValue)}${lineEnding}`;
139
+ });
140
+ }
141
+ /**
142
+ * Patch a generated plugin bootstrap header without lowering custom floors.
143
+ *
144
+ * Preserves the original header line endings while replacing empty or invalid
145
+ * version strings with the policy values.
146
+ */
147
+ export function updatePluginHeaderCompatibility(source, policy) {
148
+ const { pluginHeader } = policy;
149
+ const nextSource = replacePluginHeaderVersionFloor(source, /(\* Requires at least:[^\S\r\n]*)([^\r\n]*)(\r?)/u, pluginHeader.requiresAtLeast);
150
+ const nextSourceWithTestedUpTo = replacePluginHeaderVersionFloor(nextSource, /(\* Tested up to:[^\S\r\n]*)([^\r\n]*)(\r?)/u, pluginHeader.testedUpTo);
151
+ return replacePluginHeaderVersionFloor(nextSourceWithTestedUpTo, /(\* Requires PHP:[^\S\r\n]*)([^\r\n]*)(\r?)/u, pluginHeader.requiresPhp);
152
+ }
@@ -1,9 +1,11 @@
1
+ import type { ScaffoldPluginHeaderCompatibility } from "./scaffold-compatibility.js";
1
2
  export declare const SCAFFOLD_TEMPLATE_VARIABLE_GROUPS: unique symbol;
2
3
  export type ScaffoldTemplateFamily = "basic" | "interactivity" | "persistence" | "compound" | "query-loop" | "external";
3
4
  export interface ScaffoldSharedTemplateVariableGroup {
4
5
  author: string;
5
6
  blockMetadataVersion: string;
6
7
  category: string;
8
+ compatibility: ScaffoldPluginHeaderCompatibility;
7
9
  cssClassName: string;
8
10
  description: string;
9
11
  descriptionJson: string;
@@ -6,6 +6,7 @@ import { DEFAULT_COMPOUND_INNER_BLOCKS_PRESET_ID, getCompoundInnerBlocksPresetDe
6
6
  import { getTemplateById, isBuiltInTemplateId, } from './template-registry.js';
7
7
  import { toPascalCase, toSnakeCase, } from './string-case.js';
8
8
  import { attachScaffoldTemplateVariableGroups } from "./scaffold-template-variable-groups.js";
9
+ import { resolveScaffoldCompatibilityPolicy } from "./scaffold-compatibility.js";
9
10
  /**
10
11
  * Build the normalized template variables used by scaffold rendering.
11
12
  *
@@ -58,6 +59,7 @@ export function getTemplateVariables(templateId, answers) {
58
59
  const persistencePolicy = templateId === 'persistence' || compoundPersistenceEnabled
59
60
  ? answers.persistencePolicy ?? 'authenticated'
60
61
  : 'authenticated';
62
+ const compatibility = resolveScaffoldCompatibilityPolicy([]);
61
63
  const flatVariables = {
62
64
  alternateRenderTargetsCsv: '',
63
65
  alternateRenderTargetsJson: '[]',
@@ -92,6 +94,8 @@ export function getTemplateVariables(templateId, answers) {
92
94
  hasAlternatePlainTextRenderTarget: 'false',
93
95
  hasAlternateRenderTargets: 'false',
94
96
  projectToolsPackageVersion,
97
+ requiresAtLeast: compatibility.pluginHeader.requiresAtLeast,
98
+ requiresPhp: compatibility.pluginHeader.requiresPhp,
95
99
  cssClassName,
96
100
  dataStorageMode,
97
101
  dashCase: slug,
@@ -118,6 +122,7 @@ export function getTemplateVariables(templateId, answers) {
118
122
  phpPrefix,
119
123
  phpPrefixUpper,
120
124
  restPackageVersion,
125
+ testedUpTo: compatibility.pluginHeader.testedUpTo,
121
126
  publicWriteRequestIdDeclaration: persistencePolicy === 'public'
122
127
  ? "publicWriteRequestId: string & tags.MinLength< 1 > & tags.MaxLength< 128 >;"
123
128
  : "publicWriteRequestId?: string & tags.MinLength< 1 > & tags.MaxLength< 128 >;",
@@ -162,6 +167,7 @@ export function getTemplateVariables(templateId, answers) {
162
167
  author: answers.author.trim(),
163
168
  blockMetadataVersion: BUILTIN_BLOCK_METADATA_VERSION,
164
169
  category: metadataDefaults?.category ?? template?.defaultCategory ?? 'widgets',
170
+ compatibility: compatibility.pluginHeader,
165
171
  cssClassName,
166
172
  description,
167
173
  descriptionJson: JSON.stringify(description),
@@ -58,6 +58,8 @@ export interface FlatScaffoldTemplateVariables extends Record<string, string> {
58
58
  hasAlternatePlainTextRenderTarget: "false" | "true";
59
59
  hasAlternateRenderTargets: "false" | "true";
60
60
  projectToolsPackageVersion: string;
61
+ requiresAtLeast: string;
62
+ requiresPhp: string;
61
63
  cssClassName: string;
62
64
  dashCase: string;
63
65
  dataStorageMode: DataStorageMode;
@@ -90,6 +92,7 @@ export interface FlatScaffoldTemplateVariables extends Record<string, string> {
90
92
  slugSnakeCase: string;
91
93
  textDomain: string;
92
94
  textdomain: string;
95
+ testedUpTo: string;
93
96
  title: string;
94
97
  titleJson: string;
95
98
  titleCase: string;