From 71d2836b18e973aa9601f03a79ab36bef25057b4 Mon Sep 17 00:00:00 2001 From: lovelymandal16 Date: Thu, 16 Apr 2026 16:45:51 +0530 Subject: [PATCH] Update forms orchestrator routing, planner, scaffold-form, and sync-forms Made-with: Cursor --- .../assets/routing-table.md | 8 ++ .../domain-registry/references/build/SKILL.md | 12 +++ .../build/references/scaffold-form/SKILL.md | 21 ++++- .../scripts/scaffold_form/cli.py | 52 +++++++++--- .../infra/references/sync-forms/SKILL.md | 37 +++++++- .../sync-forms/scripts/form_sync/cli.py | 35 +++++++- .../sync-forms/scripts/form_sync/config.py | 10 +++ .../sync-forms/scripts/form_sync/push.py | 85 +++++++++++++++++-- .../planner/references/default-strategy.md | 70 +++++++++++---- 9 files changed, 289 insertions(+), 41 deletions(-) diff --git a/skills/aem/forms/forms-orchestrator/assets/routing-table.md b/skills/aem/forms/forms-orchestrator/assets/routing-table.md index 798db755..03754e35 100644 --- a/skills/aem/forms/forms-orchestrator/assets/routing-table.md +++ b/skills/aem/forms/forms-orchestrator/assets/routing-table.md @@ -209,6 +209,10 @@ After each plan completes, present the user with these options. All local change | **4. Proceed to next plan** | ❌ | ❌ | Skip both, start next plan immediately | **When deploying (option 1):** + +First, determine the form type from the active journey (EDS or Core Component): + +**EDS / Franklin forms:** 1. **EDS code first** — If any files in the `code/` directory were created or modified: 1. **Validate** — Run `eds-code-sync validate` to verify the changes pass `npm install` and `npm run lint`. The local `code/` directory does not contain `package.json` — the validate command clones the repo, applies changes, and runs checks automatically. If validation fails, fix the issues in `code/` and re-run validate. 2. **Push** — Push them to GitHub with `eds-code-sync push --branch --pr`. @@ -216,6 +220,10 @@ After each plan completes, present the user with these options. All local change 4. **Re-sync** — Once the user confirms the merge, run `eds-code-sync sync` to re-sync the local `code/` directory with the merged main branch before proceeding. 2. **AEM forms second** — If any form or rule files were created or modified (in `repo/`), push them to AEM Author with `form-sync push ` for each changed form. This must happen after EDS code is deployed, since forms may reference custom functions or components that need to be live first. +**Core Component Adaptive Forms:** +1. **Skip EDS code entirely** — Core Component forms have no GitHub/EDS code. There are no custom function files, no component files, and no `eds-code-sync` steps. Skip step 1 in full. +2. **Push directly to AEM** — Push form and rule files with `form-sync push --form-type core_component` for each changed form. No prerequisite GitHub deployment required. + **When updating reports (options 1, 2, 3):** Route to `context` → `manage-context` to update `.agent/handover.md`, `.agent/history.md`, and `.agent/sessions.md`. --- diff --git a/skills/aem/forms/forms-orchestrator/references/domain-registry/references/build/SKILL.md b/skills/aem/forms/forms-orchestrator/references/domain-registry/references/build/SKILL.md index bef78c6b..e9f7c1ca 100644 --- a/skills/aem/forms/forms-orchestrator/references/domain-registry/references/build/SKILL.md +++ b/skills/aem/forms/forms-orchestrator/references/domain-registry/references/build/SKILL.md @@ -59,6 +59,18 @@ metadata: > Empty forms → `scaffold-form`. > Custom components → `create-component`. +### Core Component Adaptive Forms — Restricted Skills + +When the user is building a **Core Component Adaptive Form** ("core component form", "CC form", "adaptive form core component"), apply these restrictions: + +| Skill | Allowed? | Notes | +|-------|----------|-------| +| `scaffold-form` | ✅ Yes | Use `--form-type core_component` flag | +| `create-form` | ✅ Yes | Works identically for both form types | +| `create-component` | ❌ No | Custom components are EDS-specific; not applicable to CC forms | + +For CC form scaffolding, always pass `--form-type core_component` to produce the correct `sling:resourceType: mysite/components/adaptiveForm/formcontainer` root. Never scaffold a CC form with the EDS template. + ## File Locations | Asset | Canonical Path | diff --git a/skills/aem/forms/forms-orchestrator/references/domain-registry/references/build/references/scaffold-form/SKILL.md b/skills/aem/forms/forms-orchestrator/references/domain-registry/references/build/references/scaffold-form/SKILL.md index 0ad82efb..62da4ad2 100644 --- a/skills/aem/forms/forms-orchestrator/references/domain-registry/references/build/references/scaffold-form/SKILL.md +++ b/skills/aem/forms/forms-orchestrator/references/domain-registry/references/build/references/scaffold-form/SKILL.md @@ -27,18 +27,27 @@ You generate empty AEM Adaptive Form JSON files from a template using the `scaff **Do NOT use for:** Modifying existing forms (use **create-form** skill), or creating forms on AEM Author (use **sync-forms** skill). +## Form Types + +| Type | Flag | Root `sling:resourceType` | When to Use | +|------|------|--------------------------|-------------| +| EDS / Franklin (default) | *(omit)* | `fd/franklin/components/form/v1/form` | EDS forms with GitHub-deployed custom functions | +| Core Component | `--form-type core_component` | `mysite/components/adaptiveForm/formcontainer` | Adaptive Form Core Components — no GitHub workflow needed | + ## Critical Rules 1. **Use the CLI** — do not hand-create form.json files from scratch 2. **Will not overwrite** — the tool refuses to create files that already exist 3. **Naming convention** — form names should be kebab-case (e.g., `my-registration-form`) 4. **Always add fields after scaffolding** — the scaffolded form is empty; delegate to the **create-form** skill to add fields +5. **CC forms skip GitHub steps** — when `--form-type core_component`, never run `create-function`, `create-component`, or `eds-code-sync` steps ## Tool Commands | Action | Command | |--------|---------| -| Scaffold a form | `scaffold-form ` | +| Scaffold an EDS form | `scaffold-form ` | +| Scaffold a Core Component form | `scaffold-form --form-type core_component` | | With custom title | `scaffold-form --title "My Form Title"` | | With submit button | `scaffold-form --with-submit` | | Custom output dir | `scaffold-form --output-dir ./my-dir` | @@ -67,11 +76,17 @@ The form.json includes: ## Examples -### Basic form +### Basic EDS form ```bash scaffold-form customer-onboarding ``` -Creates `form/customer-onboarding.form.json` with title "Customer Onboarding". +Creates `form/customer-onboarding.form.json` with title "Customer Onboarding" (EDS/Franklin type). + +### Core Component Adaptive Form +```bash +scaffold-form member-demographics --form-type core_component +``` +Creates `form/member-demographics.form.json` with `sling:resourceType: mysite/components/adaptiveForm/formcontainer`. Use this when building a Core Component Adaptive Form — no GitHub/EDS workflow required. ### Form with submit button in custom directory ```bash diff --git a/skills/aem/forms/forms-orchestrator/references/domain-registry/references/build/references/scaffold-form/scripts/scaffold_form/cli.py b/skills/aem/forms/forms-orchestrator/references/domain-registry/references/build/references/scaffold-form/scripts/scaffold_form/cli.py index 007d9657..e54234ac 100644 --- a/skills/aem/forms/forms-orchestrator/references/domain-registry/references/build/references/scaffold-form/scripts/scaffold_form/cli.py +++ b/skills/aem/forms/forms-orchestrator/references/domain-registry/references/build/references/scaffold-form/scripts/scaffold_form/cli.py @@ -25,15 +25,33 @@ def _name_to_title(form_name: str) -> str: return form_name.replace("-", " ").replace("_", " ").title() -def _build_form_json(title: str, *, with_submit: bool = False) -> dict: - """Return the JCR-format AEM Adaptive Form structure.""" - form: dict = { - "jcr:primaryType": "nt:unstructured", - "sling:resourceType": "fd/franklin/components/form/v1/form", - "fieldType": "form", - "fd:version": "2.1", - "title": title, - } +def _build_form_json( + title: str, *, with_submit: bool = False, form_type: str = "eds" +) -> dict: + """Return the JCR-format AEM Adaptive Form structure. + + Args: + title: Human-readable form title. + with_submit: Include a submit button node. + form_type: "eds" (EDS/Franklin, default) or "core_component" + (Adaptive Form Core Components). + """ + if form_type == "core_component": + form: dict = { + "jcr:primaryType": "nt:unstructured", + "sling:resourceType": "mysite/components/adaptiveForm/formcontainer", + "fieldType": "form", + "fd:version": "2.1", + "title": title, + } + else: + form = { + "jcr:primaryType": "nt:unstructured", + "sling:resourceType": "fd/franklin/components/form/v1/form", + "fieldType": "form", + "fd:version": "2.1", + "title": title, + } if with_submit: form["submit"] = { @@ -79,6 +97,17 @@ def _parse_args(argv: list[str] | None = None) -> argparse.Namespace: dest="with_submit", help="Include a submit button in the generated form.", ) + parser.add_argument( + "--form-type", + choices=["eds", "core_component"], + default="eds", + dest="form_type", + help=( + "Form architecture: 'eds' (EDS/Franklin, default) or " + "'core_component' (Adaptive Form Core Components). " + "Controls the root sling:resourceType in the generated form.json." + ), + ) return parser.parse_args(argv) @@ -113,7 +142,9 @@ def main(argv: list[str] | None = None) -> int: return 1 # ── Build JSON payloads ────────────────────────────────────────── - form_json = _build_form_json(title, with_submit=args.with_submit) + form_json = _build_form_json( + title, with_submit=args.with_submit, form_type=args.form_type + ) rule_json = _build_rule_json() # ── Write files ────────────────────────────────────────────────── @@ -128,6 +159,7 @@ def main(argv: list[str] | None = None) -> int: summary = { "form_name": form_name, "title": title, + "form_type": args.form_type, "with_submit": args.with_submit, "files_created": [ str(form_path), diff --git a/skills/aem/forms/forms-orchestrator/references/domain-registry/references/infra/references/sync-forms/SKILL.md b/skills/aem/forms/forms-orchestrator/references/domain-registry/references/infra/references/sync-forms/SKILL.md index e8634387..d8c802fe 100644 --- a/skills/aem/forms/forms-orchestrator/references/domain-registry/references/infra/references/sync-forms/SKILL.md +++ b/skills/aem/forms/forms-orchestrator/references/domain-registry/references/infra/references/sync-forms/SKILL.md @@ -35,12 +35,24 @@ You synchronize AEM Adaptive Form definitions between an AEM Author instance and 4. **Pull before editing** — always pull the latest version before making changes 5. **Rules are separated** — `pull` splits rules/events into a companion `.rule.json` file with UUID references +## Form Types + +Two form architectures are supported. Identify the correct type from the user's prompt **before** running any push command. + +| Type | Trigger Phrases | CLI Flag | +|------|----------------|---------| +| EDS / Franklin (default) | "EDS form", "Franklin form", or no type specified | *(omit flag — default)* | +| Core Component | "core component form", "CC form", "adaptive form core component" | `--form-type core_component` | + +The flag controls which JCR page structure is imported when creating the form shell on AEM (local env via Sling import). The form JSON content patched afterward is the same for both types. + ## Tool Commands | Action | Command | |--------|---------| | Pull form from AEM | `form-sync pull ` | -| Push form to AEM | `form-sync push ` | +| Push EDS form to AEM | `form-sync push ` | +| Push Core Component form to AEM | `form-sync push --form-type core_component` | | List forms in folder | `form-sync list ` | | List and pull all | `form-sync list --pull` | | Clear local form | `form-sync clear ` | @@ -49,11 +61,33 @@ You synchronize AEM Adaptive Form definitions between an AEM Author instance and ## Workflow +### EDS / Franklin Forms (default) 1. **Configure** — set `AEM_HOST`, `AEM_TOKEN`, `GITHUB_URL`, `AEM_WRITE_PATHS` in `.env` 2. **Pull** — `form-sync pull ` to fetch form JSON to `repo/` or `refs/` 3. **Edit** — modify the local `.form.json` and `.rule.json` files 4. **Push** — `form-sync push ` to send changes back to AEM +### Core Component Adaptive Forms +Core Component forms do **not** require a GitHub workflow. There are no custom function files to deploy and no EDS component code. The full workflow is AEM-only: + +1. **Configure** — `AEM_HOST`, `AEM_TOKEN`, and `AEM_WRITE_PATHS` in `.env` (no `GITHUB_URL` required for push) +2. **Scaffold locally** — `scaffold-form --form-type core_component --output-dir repo/content/forms/af//` +3. **Build form JSON** — add panels and fields using the `create-form` skill +4. **Add rules** — add business logic using the `add-rules` skill +5. **Register in metadata** — run `form-sync create ` once to register the form and create its AEM page shell +6. **Push** — `form-sync push --form-type core_component` on every subsequent edit + +**First-time push (new form at exact path, no suffix):** +```bash +form-sync push /content/forms/af/lovely/centene/my-form --form-type core_component --new --suffix "" +``` +This creates the CC page structure via Sling import (replaces any existing EDS page at that path), then patches the form content into `jcr:content/guideContainer`. + +**Subsequent pushes (update existing CC form):** +```bash +form-sync push /content/forms/af/lovely/centene/my-form --form-type core_component +``` + ## Environment Create `.env` in project root: @@ -75,6 +109,7 @@ FORM_SYNC_ENV=prod | `GITHUB_URL` | Yes | GitHub repository URL | | `AEM_WRITE_PATHS` | Yes | Comma-separated allowlist of writable AEM paths | | `FORM_SYNC_ENV` | No | Environment: `local`, `stage`, or `prod` (default: `prod`) | +| `FORM_SYNC_FORM_TYPE` | No | Form architecture: `eds` or `core_component` (default: `eds`) | *Either `AEM_TOKEN` or `AEM_USERNAME`+`AEM_PASSWORD` must be provided. diff --git a/skills/aem/forms/forms-orchestrator/references/domain-registry/references/infra/references/sync-forms/scripts/form_sync/cli.py b/skills/aem/forms/forms-orchestrator/references/domain-registry/references/infra/references/sync-forms/scripts/form_sync/cli.py index 5f9bdbe6..542cde54 100644 --- a/skills/aem/forms/forms-orchestrator/references/domain-registry/references/infra/references/sync-forms/scripts/form_sync/cli.py +++ b/skills/aem/forms/forms-orchestrator/references/domain-registry/references/infra/references/sync-forms/scripts/form_sync/cli.py @@ -308,6 +308,13 @@ def dam_to_content_path(dam_path: str) -> str: is_flag=True, help="Push to preview path defined by FORM_SYNC_PREVIEW_PATH env variable", ) +@click.option( + "--form-type", + "form_type", + type=click.Choice(["eds", "core_component"]), + default=None, + help="Form architecture: 'eds' (EDS/Franklin, default) or 'core_component' (Adaptive Form Core Components)", +) @click.option( "--verbose", "-v", is_flag=True, help="Enable verbose output for debugging" ) @@ -317,6 +324,7 @@ def push( suffix: str, force_new: bool, preview: bool, + form_type: str, verbose: bool, ): """Push a local form to AEM. @@ -331,12 +339,14 @@ def push( form-sync push /content/forms/af/acroform --suffix -v2 form-sync push /content/forms/af/acroform --new form-sync push /content/forms/af/acroform --preview + form-sync push /content/forms/af/acroform --form-type core_component form-sync push /content/forms/af/acroform --verbose \b Note: First push creates a new form with suffix. Subsequent pushes update the existing form. Use --new to create a new form instead. Use --preview to push to a separate preview path (requires FORM_SYNC_PREVIEW_PATH env). + Use --form-type core_component to create an Adaptive Form Core Component form instead of EDS/Franklin. """ global _verbose _verbose = verbose @@ -349,6 +359,11 @@ def push( config = Config.from_env() log_verbose(f"AEM Host: {config.aem_host}") + # CLI --form-type overrides env var / default + if form_type is not None: + config.form_type = form_type + log_verbose(f"Form type: {config.form_type}") + # Handle preview flag preview_path = None if preview: @@ -424,7 +439,12 @@ def on_progress(message: str) -> None: @click.option( "--verbose", "-v", is_flag=True, help="Enable verbose output for debugging" ) -def clear(form_path: str, verbose: bool): +@click.option( + "--form-type", "form_type", + type=click.Choice(["eds", "core_component"]), default="eds", + help="Form architecture: 'eds' (default) or 'core_component'", +) +def clear(form_path: str, verbose: bool, form_type: str): """Clear a local form to empty state. \b @@ -447,7 +467,7 @@ def clear(form_path: str, verbose: bool): from .metadata import MetadataManager - # Minimal form structure + # Minimal EDS/Franklin form structure EMPTY_FORM = { "jcr:primaryType": "nt:unstructured", "fieldType": "form", @@ -457,6 +477,14 @@ def clear(form_path: str, verbose: bool): "thankYouOption": "message", } + # Minimal Core Component form structure + EMPTY_CC_FORM = { + "jcr:primaryType": "nt:unstructured", + "fieldType": "form", + "sling:resourceType": "mysite/components/adaptiveForm/formcontainer", + "fd:version": "2.1", + } + try: # Load metadata and find form by path click.echo("Loading metadata...") @@ -497,11 +525,12 @@ def clear(form_path: str, verbose: bool): base_dir = Path("./repo") # Clear form.json (local_file already contains the relative path) + empty_structure = EMPTY_CC_FORM if form_type == "core_component" else EMPTY_FORM form_file = base_dir / form_metadata.local_file if form_file.exists(): click.echo(f"Clearing form: {form_file}") with open(form_file, "w", encoding="utf-8") as f: - json.dump(EMPTY_FORM, f, indent=2) + json.dump(empty_structure, f, indent=2) click.echo(f"✓ Cleared {form_metadata.local_file}") else: click.secho(f"WARNING: Form file not found: {form_file}", fg="yellow") diff --git a/skills/aem/forms/forms-orchestrator/references/domain-registry/references/infra/references/sync-forms/scripts/form_sync/config.py b/skills/aem/forms/forms-orchestrator/references/domain-registry/references/infra/references/sync-forms/scripts/form_sync/config.py index 5caad4c8..3a144e24 100644 --- a/skills/aem/forms/forms-orchestrator/references/domain-registry/references/infra/references/sync-forms/scripts/form_sync/config.py +++ b/skills/aem/forms/forms-orchestrator/references/domain-registry/references/infra/references/sync-forms/scripts/form_sync/config.py @@ -67,6 +67,7 @@ class Config: client_id: str = "" cloud_token_source: str = "aem_genai" create_form_strategy: str = "genai" + form_type: str = "eds" # "eds" | "core_component" @property def basic_auth_encoded(self) -> str: @@ -304,6 +305,14 @@ def from_env(cls, env_file: Path = None) -> "Config": # client_id: explicit .env override, then profile default client_id = os.getenv("FORM_SYNC_CLIENT_ID", profile["client_id"]) + # Parse optional form type (eds or core_component) + form_type = os.getenv("FORM_SYNC_FORM_TYPE", "eds").strip().lower() + if form_type not in ("eds", "core_component"): + raise ConfigurationError( + f"Invalid FORM_SYNC_FORM_TYPE value: '{form_type}'.\n" + "Valid values: eds, core_component" + ) + return cls( aem_host=aem_host, github_url=os.getenv("GITHUB_URL"), @@ -321,6 +330,7 @@ def from_env(cls, env_file: Path = None) -> "Config": client_id=client_id, cloud_token_source=profile["cloud_token_source"], create_form_strategy=profile["create_form_strategy"], + form_type=form_type, ) diff --git a/skills/aem/forms/forms-orchestrator/references/domain-registry/references/infra/references/sync-forms/scripts/form_sync/push.py b/skills/aem/forms/forms-orchestrator/references/domain-registry/references/infra/references/sync-forms/scripts/form_sync/push.py index c73c1401..9dba8707 100644 --- a/skills/aem/forms/forms-orchestrator/references/domain-registry/references/infra/references/sync-forms/scripts/form_sync/push.py +++ b/skills/aem/forms/forms-orchestrator/references/domain-registry/references/infra/references/sync-forms/scripts/form_sync/push.py @@ -321,6 +321,7 @@ def create_empty_form( config: Config, form_title: str, folder_path: str, + form_type: str = "eds", ) -> str: """ Create an empty form on AEM. @@ -330,6 +331,8 @@ def create_empty_form( config: Configuration with GitHub URL. form_title: Title for the new form. folder_path: DAM folder path for the form. + form_type: "eds" (default) or "core_component". Controls the template + and initial form JSON sent to the GenAI endpoint. Returns: The URL path of the created form (e.g., /content/forms/af/myform-v1.html). @@ -337,14 +340,26 @@ def create_empty_form( Raises: FormCreationError: If form creation fails. """ + if form_type == "core_component": + template_path = "/conf/mysite/settings/wcm/templates/blank-af-v2" + form_json = json.dumps({ + "jcr:primaryType": "nt:unstructured", + "fieldType": "form", + "sling:resourceType": "mysite/components/adaptiveForm/formcontainer", + "fd:version": "2.1", + }) + else: + template_path = "/libs/fd/franklin/templates/page" + form_json = '{"items": []}' + payload = { "queryType": "create_form", "result": { "formTitle": form_title, - "formJson": '{"items": []}', + "formJson": form_json, "queryType": "create_form", "folderPath": folder_path, - "templatePath": "/libs/fd/franklin/templates/page", + "templatePath": template_path, "githubUrl": config.github_url, }, } @@ -429,6 +444,7 @@ def create_form_via_sling_import( config: Config, form_title: str, form_path: str, + form_type: str = "eds", on_progress: Optional[callable] = None, ) -> str: """ @@ -445,6 +461,7 @@ def create_form_via_sling_import( config: Configuration object. form_title: Title for the new form. form_path: Full AEM path (e.g., /content/forms/af/forms-team/myform). + form_type: Form architecture — "eds" (Franklin/EDS) or "core_component" (Adaptive Form Core Components). on_progress: Optional callback for progress messages. Returns: @@ -488,6 +505,28 @@ def log(message: str) -> None: }, } + # Build the full JCR page structure with correct Adaptive Form resource types + page_cc_json = { + "jcr:primaryType": "cq:Page", + "jcr:content": { + "jcr:primaryType": "cq:PageContent", + "jcr:title": form_title, + "sling:resourceType": "mysite/components/adaptiveForm/page", + "cq:template": "/conf/mysite/settings/wcm/templates/blank-af-v2", + "jcr:language": "en", + "author": "adobe", + "sling:configRef": f"/conf/forms/{form_path.replace('/content/forms/af/', '')}/", + "guideContainer": { + "jcr:primaryType": "nt:unstructured", + "sling:resourceType": "mysite/components/adaptiveForm/formcontainer", + "fd:version": "2.1", + "fieldType": "form", + "jcr:title": form_title, + "name": form_title.lower().replace(" ", "-"), + }, + }, + } + try: # Ensure parent folder exists parent_path = "/".join(form_path.rstrip("/").split("/")[:-1]) @@ -501,14 +540,17 @@ def log(message: str) -> None: headers={"Authorization": config.basic_auth_header}, ) + # Select page structure based on form type + page_structure = page_cc_json if form_type == "core_component" else page_json + log(f"Creating form page at: {form_path} (type: {form_type})") + # Import the page structure via Sling POST - log(f"Creating form page at: {form_path}") response = requests.post( f"{config.aem_host}{form_path}", data={ ":operation": "import", ":contentType": "json", - ":content": json.dumps(page_json), + ":content": json.dumps(page_structure), ":replace": "true", ":replaceProperties": "true", }, @@ -607,6 +649,7 @@ def patch_form_content( cloud_token: CloudToken, form_path: str, form_data: dict, + form_type: str = "eds", ) -> None: """ Patch entire form content via Universal Editor API. @@ -616,10 +659,21 @@ def patch_form_content( cloud_token: Cloud token for authentication. form_path: Path to the form (without .html). form_data: The complete form data dict to patch. + form_type: "eds" (default) or "core_component". Controls the JCR + content path used as the patch target. Raises: FormSyncError: If patching the form fails. """ + # EDS forms: content lives at root/section, child key is "form" + # CC forms: content lives at jcr:content, child key is "guideContainer" + if form_type == "core_component": + resource_path = f"{form_path}/jcr:content" + patch_path = "/guideContainer" + else: + resource_path = f"{form_path}/jcr:content/root/section" + patch_path = "/form" + # Build the payload for patch API payload = { "connections": [ @@ -632,13 +686,13 @@ def patch_form_content( "patch": [ { "op": "add", - "path": "/form", + "path": patch_path, "value": form_data, } ], "target": { "prop": "", - "resource": f"urn:aemconnection:{form_path}/jcr:content/root/section", + "resource": f"urn:aemconnection:{resource_path}", "type": "component", }, } @@ -656,6 +710,12 @@ def patch_form_content( ) if not response.ok: + # AEM Cloud Service returns 409 for Core Component forms when the + # guideContainer node already exists but the UE patch still writes + # the content successfully. Treat 409 as a warning-level success for + # CC forms; raise only for other error codes. + if form_type == "core_component" and response.status_code == 409: + return # Content was written; AEM returned 409 due to node state raise FormSyncError( f"Failed to patch form content: " f"HTTP {response.status_code} - {response.text[:200]}" @@ -1035,6 +1095,7 @@ def log(message: str) -> None: cloud_token=cloud_token, form_path=target_path, form_data=form_data, + form_type=config.form_type, ) log("Preview form/fragment content added successfully") @@ -1051,6 +1112,7 @@ def log(message: str) -> None: cloud_token=cloud_token, form_path=current_path, form_data=form_data, + form_type=config.form_type, ) log(f"{entity_type.capitalize()} content updated successfully") target_path = current_path @@ -1062,8 +1124,10 @@ def log(message: str) -> None: original_form_name = extract_form_name(form_metadata.original_path) form_title = f"{original_form_name}{suffix}" - if config.create_form_strategy == "sling_import": - # Local mode: use Sling POST import (no GenAI endpoint) + if config.create_form_strategy == "sling_import" or config.form_type == "core_component": + # Local mode OR Core Component forms: use Sling POST import. + # CC forms always use Sling import because the GenAI endpoint only + # creates EDS/Franklin forms and cannot produce the correct CC page structure. if form_metadata.fragment: log(f"Creating new fragment via Sling import: {form_title}") # For fragments, construct the target path from folder_path + name @@ -1087,11 +1151,12 @@ def log(message: str) -> None: config=config, form_title=form_title, form_path=target_path, + form_type=config.form_type, on_progress=on_progress, ) log(f"Created form via Sling import: {target_path}") else: - # Stage/Prod: use GenAI endpoint + # Stage/Prod EDS forms: use GenAI endpoint if form_metadata.fragment: log(f"Creating new fragment: {form_title}") target_path = create_empty_fragment( @@ -1109,6 +1174,7 @@ def log(message: str) -> None: config=config, form_title=form_title, folder_path=form_metadata.folder_path, + form_type=config.form_type, ) target_path = get_form_path_from_url(form_url) log(f"Created empty form: {target_path}") @@ -1124,6 +1190,7 @@ def log(message: str) -> None: cloud_token=cloud_token, form_path=target_path, form_data=form_data, + form_type=config.form_type, ) log("Form/fragment content added successfully") diff --git a/skills/aem/forms/forms-orchestrator/references/planner/references/default-strategy.md b/skills/aem/forms/forms-orchestrator/references/planner/references/default-strategy.md index f43a493d..30e5abd3 100644 --- a/skills/aem/forms/forms-orchestrator/references/planner/references/default-strategy.md +++ b/skills/aem/forms/forms-orchestrator/references/planner/references/default-strategy.md @@ -36,6 +36,33 @@ At minimum, one input source must be available. Richer inputs produce better pla --- +## Form Type Detection (Run First) + +Before decomposing requirements, determine whether the user intends an **EDS/Franklin form** or a **Core Component Adaptive Form**. This controls which skills are included in plans. + +| Signal | Form Type | +|--------|-----------| +| "Core Component", "CC form", "adaptive form core component", "core component form" | `core_component` | +| "EDS form", "Franklin form", or no type specified | `eds` | + +### Core Component Forms — Constrained Skill Set + +When form type is `core_component`, apply these constraints throughout all plans: + +| Constraint | Reason | +|------------|--------| +| **Use `scaffold-form --form-type core_component`** | Generates correct `mysite/components/adaptiveForm/formcontainer` root | +| **Use `form-sync push --form-type core_component`** in every push step | Creates proper CC page structure via Sling import | +| **No `create-function` skill** | Custom JS functions require GitHub deployment — not part of CC workflow | +| **No `create-component` skill** | Custom EDS components are GitHub-deployed — not applicable to CC forms | +| **No `manage-apis` / API client generation** | CC forms use AEM-native rule actions for API calls; no JS client files needed | +| **No `eds-code-sync` steps** | No GitHub code to sync for CC forms | +| **No EDS code deployment** | Skip entirely — CC form deployment is AEM-only | + +For CC forms, API integration (if any) uses AEM visual rules with the `globals.functions.request()` built-in — referenced directly in `add-rules` steps, not wrapped in exported custom functions. + +--- + ## Process ### Step 1 — Analyze Requirements @@ -118,24 +145,37 @@ The exact number and naming of plans depends on the form's complexity and struct ## Example Decomposition -A moderately complex form with conditional branches and API integrations might decompose into plans like this: +### EDS / Franklin Form + +A moderately complex EDS form with conditional branches and API integrations: + +| Plan | Focus | Skills Used | +|------|-------|-------------| +| 01 | Form structure & initial fields | `scaffold-form`, `create-form`, `sync-forms` | +| 02 | Workflow branch A (e.g., conditional section) | `create-form`, `add-rules`, `sync-forms` | +| 03 | Workflow branch B (e.g., alternative path) | `create-form`, `add-rules`, `create-function`, `sync-forms` | +| 04 | Cross-field validations | `create-function`, `add-rules`, `sync-forms` | +| 05 | API integration — submit | `manage-apis`, `create-function`, `add-rules`, `sync-forms` | +| 06 | Error handling | `create-function`, `add-rules`, `sync-forms` | + +### Core Component Adaptive Form + +Same form requirements — but built as a Core Component Adaptive Form. Plans are simpler because there is no GitHub/EDS workflow: | Plan | Focus | Skills Used | |------|-------|-------------| -| 01 | Form structure & initial fields | `create-form` | -| 02 | Workflow branch A (e.g., conditional section) | `create-form`, `add-rules` | -| 03 | Workflow branch B (e.g., alternative path) | `create-form`, `add-rules`, `create-function` | -| 04 | Shared fields & common sections | `create-form`, `add-rules` | -| 05 | Cross-field business rule validations | `create-function`, `add-rules` | -| 06 | API integration — data loading & prefill | `manage-apis`, `create-function`, `add-rules` | -| 07 | API integration — save & submit | `manage-apis`, `create-function`, `add-rules` | -| 08 | Error handling & session management | `create-function`, `add-rules` | - -**Key principles illustrated:** -- Plans follow the recommended order (structure → workflows → validations → integrations → infrastructure) -- Every plan freely mixes skills (`create-form` + `add-rules` + `create-function` in the same plan) -- Each plan ends with validate + push, keeping the form in a deployable state -- The exact number of plans depends on the form's complexity — a simple form might need only 3 +| 01 | Form structure & initial fields | `scaffold-form --form-type core_component`, `create-form`, `sync-forms --form-type core_component` | +| 02 | Workflow branch A | `add-rules`, `sync-forms --form-type core_component` | +| 03 | Workflow branch B | `add-rules`, `sync-forms --form-type core_component` | +| 04 | Cross-field validations | `add-rules`, `sync-forms --form-type core_component` | +| 05 | API submit | `add-rules` (using built-in `globals.functions.request`), `sync-forms --form-type core_component` | + +**Key differences for CC forms:** +- No `create-function` — all logic lives in visual rules via `add-rules` +- No `create-component` — no custom EDS components +- No `manage-apis` — no JS API client generation +- No `eds-code-sync` — no GitHub code to deploy +- Every push step uses `form-sync push --form-type core_component` ---