Skip to content
Merged
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
46 changes: 46 additions & 0 deletions .agents/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# .agents/ — vtex/address-form

This directory is the **source of truth** for all AI agent configuration in this repository.

## Directory layout

```
.agents/
├── README.md # This file
├── rules/ # Always-on rules (loaded automatically by supporting agents)
│ ├── 00-vtex-address-form.md # Baseline repo rules (stack, architecture, DO NOTs)
│ ├── 10-test-discipline.md # Test strategy and what requires a test
│ ├── 20-i18n-discipline.md # i18n key governance and Crowdin workflow
│ └── 30-public-api-discipline.md # npm + VTEX IO public API change protocol
├── skills/ # Vendored skills from vtex/vtex-agent-skills
│ ├── specification/SKILL.md # /specification slash command skill
│ │ └── references/template.md # SDD Lite spec template
│ ├── implementing/SKILL.md # /implementing slash command skill
│ └── README.md # Skill table + refresh instructions
└── commands/ # Repo-specific slash commands
├── sdd-lite-bootstrap.md # /sdd-lite-bootstrap — full SDD Lite flow
└── sdd-full-bootstrap.md # /sdd-full-bootstrap — full Spec Kit flow
```

## Routing table — which agent reads what

| Agent tool | Reads from |
|---|---|
| Claude Code | `.claude/` (symlinks → `.agents/`) |
| Cursor | `.agents/rules/*.md`, `.agents/skills/*/SKILL.md` |
| Copilot | `.github/copilot-instructions.md` (not present; falls back to `.agents/`) |

## Updating vendored skills

The `skills/` directory is vendored verbatim from [`vtex/vtex-agent-skills`](https://github.com/vtex/vtex-agent-skills). To update:

```bash
npx skills add vtex/vtex-agent-skills
# or
git clone https://github.com/vtex/vtex-agent-skills /tmp/vtas
cp /tmp/vtas/skills/specification/SKILL.md .agents/skills/specification/SKILL.md
cp /tmp/vtas/skills/specification/references/template.md .agents/skills/specification/references/template.md
cp /tmp/vtas/skills/implementing/SKILL.md .agents/skills/implementing/SKILL.md
```

Do **not** hand-edit `SKILL.md` files. Repo-specific guidance goes in `.agents/rules/`.
1 change: 1 addition & 0 deletions .agents/commands/commands
54 changes: 54 additions & 0 deletions .agents/commands/sdd-full-bootstrap.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# /sdd-full-bootstrap — SDD Full (Spec Kit) workflow for vtex/address-form

Use this command to understand and run the SDD Full (Spec Kit) development cycle for this repository.

## When to use SDD Full

Use SDD Full (not SDD Lite) when:
- The task is > 5 days
- Cross-team or cross-repo coordination is required
- Significant architectural impact (e.g., changing the `PostalCodeRules` type shape)
- Breaking change to `react/index.ts` or `react/components.ts` (requires consumer coordination with vtex.omnishipping, vtex.shipping-preview, vtex.checkout)
- Changes to `react/types/rules.ts` or `react/types/address.ts` that affect the public type surface
- High ambiguity or unresolved product decisions

## One-time machine setup

```bash
# Install uv
curl -LsSf https://astral.sh/uv/install.sh | sh
# or: brew install uv

source "$HOME/.local/bin/env"
uv tool install specify-cli --from git+https://github.com/github/spec-kit.git@v0.6.0
specify version
```

## Per-task workflow

```
1. Drop the relevant PRD/RFC sections into docs/scope_of_work/<feature-name>.md
2. /speckit.specify → specs/<feature>/spec.md (commit — this is the only committed artifact)
3. /speckit.clarify → resolve ambiguity, update spec
4. /speckit.plan → specs/<feature>/plan.md (DO NOT commit — gitignored)
5. /speckit.tasks → specs/<feature>/tasks.md (DO NOT commit — gitignored)
6. /speckit.analyze → specs/<feature>/analysis.md (DO NOT commit — gitignored)
7. /speckit.implement phase 1 only → code + branch (commit code only)
```

The **Constitution** (`.specify/memory/constitution.md`) is the architectural contract. Spec Kit reads it before every plan/analyze/implement step.

## Multi-repo coordination

For breaking changes that affect `vtex.omnishipping` and `vtex.shipping-preview`:

1. Start with this repo's spec (`specs/<feature>/spec.md`)
2. Use the [vtex/speckit-multi-repo](https://github.com/vtex/speckit-multi-repo) extension to propagate the spec context to consumer repos
3. Consumer repos have their own `specs/` directories and constitutions — respect their architecture rules

## address-form-specific notes for SDD Full

- The constitution at `.specify/memory/constitution.md` encodes the dual-nature (IO app + npm package) as a non-negotiable principle
- Breaking API changes must list ALL consumers in the spec's Risks & Mitigations table
- The spec must call out version bump implications (both `manifest.json` and `react/package.json`)
- `PostalCodeRules` shape changes are the highest-risk category — they require coordinated updates in both omnishipping and shipping-preview before merging
66 changes: 66 additions & 0 deletions .agents/commands/sdd-lite-bootstrap.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# /sdd-lite-bootstrap — SDD Lite workflow for vtex/address-form

Use this command to understand and run the SDD Lite development cycle for this repository.

## When to use SDD Lite

Use SDD Lite (not Spec Kit) when:
- The task fits in < 5 days
- Single-repo, contained scope
- Bug fixes, small features, focused refactors
- Low ambiguity
- No cross-consumer coordination needed
- Adding a new country rule file without changing `PostalCodeRules` shape
- No breaking changes to `react/index.ts` or `react/components.ts`

Use **SDD Full** instead when the task involves changing the `PostalCodeRules` type, adding/removing exports from `react/index.ts` or `react/components.ts`, or any cross-repo coordination.

## The two-skill pipeline

```
/specification "<task description, definition of done, repos affected>"
Writes specs/<feature-name>.md
Opens PR on branch spec/<feature-name>
Status: Draft
[Engineer reviews, asks PM if needed, edits spec, manually flips Status: Approved, merges]
/implementing "specs/<feature-name>.md"
Non-interactive sandbox run
Creates branch feat/<feature-name>
Writes failing tests first, then minimal implementation
Opens PR with: Summary / Tests / Assumptions / Deviations / Follow-ups / Spec
On success: Status: Done in the spec file
On block: opens GitHub issue "implementing blocked: <feature-name>" and ends
```

## address-form-specific notes

### Running tests
```bash
# Unit tests — run from react/ subdirectory
yarn --cwd react test

# Watch mode during development
yarn --cwd react test:watch

# i18n parity (runs automatically on pre-push)
yarn lint:locales
```

### What always needs a test
- New country rule file: assert fields array, postalCodeFrom type, required field presence
- New selector/transform function: unit test every logic branch
- New validation path: test valid/invalid/edge cases
- Any bug fix: failing regression test before the fix

### Country rule files
New files go in `react/country/<ISO3>.ts`. Export a `PostalCodeRules` object. Copy an existing similar country as a template. Test with the demo app (`cd demo && yarn start`).

### Public API caution
If the spec touches `react/index.ts` or `react/components.ts`, escalate to SDD Full — those changes require consumer coordination with `vtex.omnishipping` and `vtex.shipping-preview`.

### i18n
New user-visible strings go in `messages/en.json`. Run `yarn lint:locales` before pushing.
87 changes: 87 additions & 0 deletions .agents/rules/00-vtex-address-form.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
---
applyTo: "**/*"
---

# Baseline rules — vtex/address-form

These rules apply to **every** agent conversation in this repo. They override generic best practices and codify the address-form team's standards.

## Repository purpose

`vtex/address-form` is the **reusable address input UI library** for VTEX. It provides country-aware address field rendering, postal code autocomplete, geolocation-based address resolution, and field validation.

It has a **dual nature**:
- **VTEX IO app** (`vtex.address-form`) — consumed as a peer dependency inside VTEX IO apps
- **npm package** (`@vtex/address-form`) — published from `react/lib/` via Rollup

It is consumed by `vtex.omnishipping`, `vtex.shipping-preview`, `vtex.checkout`, and any storefront app that needs address input. A breaking change here can affect checkout for all VTEX merchants.

## Stack (do not invent alternatives)

- VTEX IO `react@2.x` + `messages@0.x` builders (no `node` builder, no `graphql` builder)
- React 16.x (peerDep: `15.x || 16.x`) — do not upgrade without an RFC
- **TypeScript 3.9** for new code; JavaScript (JSX/JS) for legacy files. Do not migrate past TS 3.9 without an RFC.
- **No Redux, no MobX** — state is managed entirely via React Context (`RulesContext`, `AddressContext`)
- Jest 26 + `@testing-library/react` 12 + Enzyme 3 (legacy)
- ESLint with `eslint-config-vtex` + `eslint-config-vtex-react`
- Prettier with `@vtex/prettier-config`
- `@vtex/intl-equalizer` for i18n parity; Crowdin for translations; `%two_letters_code%` in `crowdin.yml`
- `window.logSplunk` for Splunk telemetry (NOT `@vtex/evidence-client-js`)
- Package manager: `yarn` (classic v1). No npm, no pnpm.
- Node 16 in the dev container.
- **Test runner**: `yarn --cwd react test` (NOT at root — Jest config lives in `react/package.json`)

## Code style — non-negotiable

- 2 spaces, LF, UTF-8 (see `.editorconfig`).
- Run lint **only on files you touched**, never globally.
- Never disable ESLint rules without an inline comment explaining why and a TODO.
- Never reformat unrelated files. PRs must show a small, reviewable diff.

## Architecture rules

- **Two React Contexts are the backbone**:
- `RulesContext` (from `addressRulesContext.tsx`) — provides loaded `PostalCodeRules` to the subtree. Set by `<AddressRules country="...">`.
- `AddressContext` (from `addressContainerContext.tsx`) — provides `{ address, handleAddressChange, Input }`. Set by `<AddressContainer>`.
- **`<AddressRules>`** dynamically imports `react/country/<COUNTRY>.ts` rules via `import()`. Falls back to `react/country/default.ts` on 404.
- **`<AddressContainer>`** runs `validateChangedFields` on every address change and triggers postal code autocomplete when a valid postal code is entered.
- **Country rules live in `react/country/*.ts`**. Each file exports a `PostalCodeRules` object. New country files must include a unit test.
- **Selectors live in `react/selectors/`**. They are pure functions — no side effects, no component imports.
- **Transforms live in `react/transforms/`**. They produce new values from inputs — pure, no side effects.
- **`react/index.ts`** is the npm package public API (named exports). **`react/components.ts`** is the VTEX IO public component map (default export). Both are load-bearing for consumers — see rule 30.
- **No new dependencies** without a spec justification and explicit CHANGELOG entry.
- **`react/metrics.ts`** `window.logSplunk` calls must not be removed without a follow-up telemetry task.

## Versioning & releases

- **`manifest.json` and `react/package.json` must move in lockstep.** Use `publish-release.sh` — never bump manually.
- Release notes go in `CHANGELOG.md` (Keep a Changelog format).
- Conventional Commits required. Breaking changes use `feat!:` / `fix!:` and a `BREAKING CHANGE:` footer.

## Testing

- Test command (CI): `yarn --cwd react test`.
- Pre-push hook runs: `yarn lint:locales && yarn --cwd react test`.
- New utility functions and logic paths **must** have unit tests.
- New components **should** have tests asserting key rendering behaviors.
- Bug fixes ship with a regression test before the fix.
- Prefer `@testing-library/react` for new tests; Enzyme is legacy and should not be extended.

## Branching & PRs

- Spec PRs: branch `spec/<feature-name>`, only the spec file in the diff.
- Implementation PRs: branch `feat/<feature-name>` — even for fixes. Use `feat!:` when breaking.
- Never push to `main` directly.
- PR title = Conventional Commit. PR body must link the spec.

## Things to NOT do

- Do not add a `node/` builder.
- Do not introduce Redux or any other state library.
- Do not switch from yarn to npm or pnpm.
- Do not migrate TypeScript past 3.9 without an RFC.
- Do not call VTEX private APIs from this app — it's a frontend library.
- Do not commit `.env`, `.env.local`, or anything containing real account/workspace credentials.
- Do not manually edit locale files other than `en.json` — Crowdin owns the translations.
- Do not edit `manifest.json` or `react/package.json` version fields manually.
- Do not remove `window.logSplunk` calls from `react/metrics.ts` without a follow-up task.
68 changes: 68 additions & 0 deletions .agents/rules/10-test-discipline.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
---
applyTo: "react/**/*.{js,jsx,ts,tsx}"
---

# Test discipline — vtex/address-form

## Order of operations

1. Read the spec. Identify User Stories and their Given/When/Then acceptance criteria.
2. Translate each acceptance criterion and each row of the Key Scenarios table into one or more tests (red first).
3. Implement the minimum to flip them green.
4. Run `yarn --cwd react test` (unit) and `yarn lint:locales` (i18n parity).

## Unit tests (Jest 26)

### Framework preference

- **New tests use `@testing-library/react`**. It is the current standard.
- Enzyme is present for legacy tests only. Do not write new Enzyme tests; when modifying an Enzyme test, consider migrating it to Testing Library if the scope is reasonable.

### What requires a unit test

- Every new selector function under `react/selectors/`.
- Every new transform function under `react/transforms/`.
- Every new validation path in `react/validateAddress.ts`.
- Every new country rule file under `react/country/` — assert required fields are present, postal code regex is correct, and the postalCodeFrom type is valid.
- Every new or modified component that contains conditional logic.
- Every bug fix — add a failing regression test before the fix.

### What does NOT require a unit test

- New country rule files that are pure data changes with no logic (new field ordering only) — but a manual demo app check is required.
- Type-only changes (TypeScript interface additions with no runtime behavior change).
- Adding a key to `messages/en.json` without logic changes.
- Pure CSS/style changes.
- Pure dependency upgrades (Renovate-style).

### Test file location

- Tests for `react/Foo.js` or `react/Foo.tsx` live at `react/Foo.test.js` or `react/__tests__/Foo.test.tsx` (both patterns exist — match the existing co-location convention for the file's directory).
- Tests for `react/selectors/bar.ts` live at `react/selectors/bar.test.ts`.
- Tests for `react/transforms/baz.ts` live at `react/transforms/baz.test.ts`.
- Tests for `react/country/XYZ.ts` live alongside the rule file or in `react/country/__tests__/` if that pattern is established.
- Shared test utilities live in `react/test-modules/test-utils.tsx`.

### Fixtures and mocks

- Reusable address fixtures live in `react/__mocks__/`. Use them before inlining test data.
- Country rule mocks live in `react/country/__mocks__/`. Use `usePostalCode`, `useOneLevel`, `useTwoLevels`, `useThreeLevels`, `displayBrazil`, `displayUSA` as the canonical fixtures.
- Google Maps mocks live in `react/geolocation/__mocks__/`.
- `postalCodeService` mock lives at `react/__mocks__/postalCodeService.js`.
- When adding a new external dependency, add a corresponding mock under `react/__mocks__/<package>.js`.

### Snapshot tests

- Existing snapshots cover `AddressForm`, `AddressSummary`, `CountrySelector`, `DefaultInput`, `GeolocationNumberInput`, `InputText`. Do not add new snapshots unless the rendered output is genuinely a stable contract.
- Never update snapshots with `--updateSnapshot` to clear noise — update them intentionally.

## i18n parity

- After any `messages/en.json` change, run `yarn lint:locales` to confirm parity (reference locale: `pt`).
- A failing `lint:locales` is a blocker — do not merge.
- This check also runs on `pre-push`.

## Coverage

- No enforced threshold today. Do not let coverage drop on changed files.
- `yarn test:coverage` runs through `react/` Jest config.
49 changes: 49 additions & 0 deletions .agents/rules/20-i18n-discipline.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
---
applyTo: "{messages/**,react/**/*.{js,jsx,ts,tsx}}"
---

# i18n discipline — vtex/address-form

## Source of truth

`messages/en.json` is the **only file agents and engineers edit directly**. The other locale files are owned by Crowdin and must not be manually edited.

**Reference locale for parity checks:** `pt` (Brazilian Portuguese). The `intl-equalizer` configuration (`"intl-equalizer": { "referenceLocale": "pt" }` in root `package.json`) uses `pt` to determine key parity.

**Crowdin locale format:** `crowdin.yml` uses `%two_letters_code%` (e.g., `pt`, `es`, `de`) — **not** the full locale code (`pt-BR`). This differs from omnishipping, which uses `%locale%`.

## Adding a new string

1. Add the key and its English default to `messages/en.json`.
2. Use the key in the component via `react-intl`'s `<FormattedMessage>` or `intl.formatMessage(...)` — never hard-code the string.
3. Run `yarn lint:locales` to confirm parity. Missing keys in other locales are expected — Crowdin will fill them.
4. Include the new key in the CHANGELOG entry.

## Key naming convention

- Use `<componentName>.<descriptor>` or `<featureDomain>.<descriptor>`.
- Keep keys stable — once a key is published and Crowdin has produced translations, renaming it is a breaking change.
- Examples: `addressForm.street`, `postalCode.label`, `geolocation.searchPlaceholder`, `countrySelector.label`.

## Removing or renaming a key

Removing or renaming a key in `en.json`:

1. Constitutes a **breaking change** (active translations become orphaned in Crowdin).
2. Requires a `BREAKING CHANGE:` footer in the Conventional Commit.
3. Must be called out in the Arch Decisions section of the spec (Key Decision — backward compatibility).
4. Requires a CHANGELOG entry under `### Breaking Changes`.

## Component usage

- Always use `react-intl`'s `<FormattedMessage>` or `intl.formatMessage()` — never fall back to a string literal.
- The intl utilities in `react/intl/utils.jsx` provide `injectIntl` and `intlShape` for class component compatibility.
- Parametrized messages use ICU syntax in `en.json` (e.g., `"{count, plural, one {# item} other {# items}}"`).
- Do not put HTML markup inside message values — use `FormattedMessage`'s `values` prop.

## Crowdin workflow

- `crowdin.yml` maps `messages/en.json` as the source and `messages/%two_letters_code%.json` as the translation output.
- Do not modify `crowdin.yml` without coordinating with the localization team.
- Translation PRs from Crowdin are auto-merged by CI; do not interfere with them.
- `messages/context.json` provides Crowdin with string context — keep it in sync when adding keys.
Loading
Loading