Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions skills/aem/forms/forms-orchestrator/assets/routing-table.md
Original file line number Diff line number Diff line change
Expand Up @@ -209,13 +209,21 @@ 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 <branch-name> --pr`.
3. **Wait for merge** — Ask the user to review and merge the PR.
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 <form_path>` 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_path> --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`.

---
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 <form_name>` |
| Scaffold an EDS form | `scaffold-form <form_name>` |
| Scaffold a Core Component form | `scaffold-form <form_name> --form-type core_component` |
| With custom title | `scaffold-form <form_name> --title "My Form Title"` |
| With submit button | `scaffold-form <form_name> --with-submit` |
| Custom output dir | `scaffold-form <form_name> --output-dir ./my-dir` |
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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"] = {
Expand Down Expand Up @@ -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)


Expand Down Expand Up @@ -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 ──────────────────────────────────────────────────
Expand All @@ -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),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 <form_path>` |
| Push form to AEM | `form-sync push <form_path>` |
| Push EDS form to AEM | `form-sync push <form_path>` |
| Push Core Component form to AEM | `form-sync push <form_path> --form-type core_component` |
| List forms in folder | `form-sync list <dam_path>` |
| List and pull all | `form-sync list <dam_path> --pull` |
| Clear local form | `form-sync clear <form_path>` |
Expand All @@ -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 <path>` to fetch form JSON to `repo/` or `refs/`
3. **Edit** — modify the local `.form.json` and `.rule.json` files
4. **Push** — `form-sync push <path>` 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_name> --form-type core_component --output-dir repo/content/forms/af/<team>/<path>`
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 <folder_path> <form_name>` once to register the form and create its AEM page shell
6. **Push** — `form-sync push <form_path> --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:
Expand All @@ -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.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)
Expand All @@ -317,6 +324,7 @@ def push(
suffix: str,
force_new: bool,
preview: bool,
form_type: str,
verbose: bool,
):
"""Push a local form to AEM.
Expand All @@ -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
Expand All @@ -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:
Expand Down Expand Up @@ -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
Expand All @@ -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",
Expand All @@ -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...")
Expand Down Expand Up @@ -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")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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"),
Expand All @@ -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,
)


Expand Down
Loading