|
| 1 | +--- |
| 2 | +name: build-strata-application-form |
| 3 | +description: Adds a Strata SDK application form to a scaffolded Strata Rails app. Use when extending a Strata Rails project with a government intake form (unemployment, SNAP, Medicaid, etc.) via the strata_sdk_rails gem. |
| 4 | +--- |
| 5 | + |
| 6 | +# Build Strata Application Form |
| 7 | + |
| 8 | +## Overview |
| 9 | + |
| 10 | +Extends an existing Strata Rails app (typically scaffolded by the `build-strata-rails-app` skill) with a Strata SDK application form. Installs the `strata_sdk_rails` gem, generates agent rules, generates the form model + migration + views, and wires up an entry point chosen by the user. |
| 11 | + |
| 12 | +**Scope (currently):** application forms only. Other Strata features (cases, business processes, tasks) are out of scope for this skill. |
| 13 | + |
| 14 | +**TDD is mandatory.** Every step that produces or modifies Ruby code (model, migration, controller, views, routes) must follow the `test-driven-development` skill: write a failing RSpec spec, run `make test` to watch it fail, write minimal Ruby, run `make test` to watch it pass, then `make lint`. Generator output (model stubs, migration stubs, scaffold view specs) is NOT a substitute — replace generator-stub specs with real failing specs before adding behavior. See `skills/test-driven-development/SKILL.md` and `skills/test-driven-development/testing-anti-patterns.md`. |
| 15 | + |
| 16 | +## Step 1: Confirm intent |
| 17 | + |
| 18 | +Ask the user exactly this: |
| 19 | + |
| 20 | +> **Are you building a government application/intake form (e.g. unemployment, SNAP, Medicaid)? (reply "skip" to exit, otherwise say yes)** |
| 21 | +
|
| 22 | +- Reply declines / "skip" / "no" → stop. Do not run any commands. |
| 23 | +- Reply confirms → tell the user the Strata SDK is a good fit for this and proceed. |
| 24 | + |
| 25 | +## Step 2: Locate the Rails app directory |
| 26 | + |
| 27 | +The project may be a monorepo — the Rails app likely lives in a subdirectory (e.g. `apps/<app_name>/`, `<app_name>/`), not necessarily the current working directory. Find it before doing anything else. |
| 28 | + |
| 29 | +**2a. Check if cwd is already the Rails app:** |
| 30 | + |
| 31 | +```sh |
| 32 | +test -f Gemfile && test -f bin/rails && grep -q "rails" Gemfile |
| 33 | +``` |
| 34 | + |
| 35 | +- All checks pass → cwd is the Rails app. Save `<RAILS_DIR>=.` and proceed to Step 3. |
| 36 | +- Any check fails → continue to 2b. |
| 37 | + |
| 38 | +**2b. Search for Rails app directories under cwd (depth ≤ 3):** |
| 39 | + |
| 40 | +```sh |
| 41 | +find . -maxdepth 3 -type f -name "Gemfile" -not -path "*/node_modules/*" -not -path "*/.git/*" -exec sh -c 'test -f "$(dirname "$1")/bin/rails" && grep -q "rails" "$1" && dirname "$1"' _ {} \; |
| 42 | +``` |
| 43 | + |
| 44 | +Interpret the output: |
| 45 | + |
| 46 | +- **Zero matches** → not a Rails project anywhere reachable. Tell the user this skill must run from a scaffolded Strata Rails app (see the `build-strata-rails-app` skill). Stop. |
| 47 | +- **Exactly one match** → confirm with the user: |
| 48 | + > Found Rails app at `<path>`. Use this one? (yes / pick another) |
| 49 | +
|
| 50 | + Save the path as `<RAILS_DIR>` on yes. |
| 51 | +- **Multiple matches** → list them and ask: |
| 52 | + > Found multiple Rails apps: `<path1>`, `<path2>`, ... Which one is the Strata app? (number or path) |
| 53 | +
|
| 54 | + Save the chosen path as `<RAILS_DIR>`. |
| 55 | + |
| 56 | +**2c. All subsequent commands** in this skill (Step 5 onward) **must run from inside `<RAILS_DIR>`**. Either `cd <RAILS_DIR>` once at the start of Step 5, or prefix each command (e.g. `cd <RAILS_DIR> && bundle install`). Edits to `Gemfile`, `config/routes.rb`, models, views, etc. all target paths under `<RAILS_DIR>`. |
| 57 | + |
| 58 | +## Step 3: Pick the feature |
| 59 | + |
| 60 | +Tell the user the Strata SDK supports several features (application forms, cases, business processes, tasks, determinations) but this skill currently only helps with **application forms**. Confirm: |
| 61 | + |
| 62 | +> **Build an application form? (yes / no)** |
| 63 | +
|
| 64 | +Only proceed on yes. |
| 65 | + |
| 66 | +## Step 4: Ask which AI coding agent |
| 67 | + |
| 68 | +Ask: |
| 69 | + |
| 70 | +> **Which AI coding agent are you using? (claude / cursor / copilot / other)** |
| 71 | +
|
| 72 | +Map answer to a `--agent` flag: |
| 73 | + |
| 74 | +| Answer | Flag | Rules dir | |
| 75 | +|--------|------|-----------| |
| 76 | +| claude | `--agent claude` | `.claude/rules/strata-sdk/` | |
| 77 | +| cursor | `--agent cursor` | `.cursor/rules/strata-sdk/` | |
| 78 | +| copilot | `--agent copilot` | `.copilot/rules/strata-sdk/` | |
| 79 | +| other / unsure | *(omit flag)* | `.agents/rules/strata-sdk/` | |
| 80 | + |
| 81 | +Save the chosen flag as `<AGENT_FLAG>` (may be empty). |
| 82 | + |
| 83 | +## Step 5: Verify Ruby version matches the project |
| 84 | + |
| 85 | +Before installing any gems, confirm the active Ruby matches what the project requires. Mismatched Ruby is a common cause of confusing `bundle install` failures. |
| 86 | + |
| 87 | +**Follow the shared reference: [`references/ruby-version-check.md`](../../references/ruby-version-check.md)** (relative to the repo root of this skills repository). |
| 88 | + |
| 89 | +It walks through: |
| 90 | + |
| 91 | +- **A.** read `.ruby-version` / `Gemfile` / `.tool-versions` → `<REQUIRED_RUBY>` |
| 92 | +- **B.** compare against `ruby -v` |
| 93 | +- **C.** ask the user which version manager they use (rbenv / asdf / rvm / chruby / other) |
| 94 | +- **D.** install the version if missing, then activate it (per-manager commands in the reference's table) |
| 95 | +- **E.** verify `bundle -v` |
| 96 | + |
| 97 | +Run all of `<RAILS_DIR>` paths in the reference relative to the directory chosen in Step 2. Do not proceed to Step 6 until `ruby -v` matches `<REQUIRED_RUBY>` and `bundle -v` succeeds. |
| 98 | + |
| 99 | +## Step 6: Install the strata_sdk_rails gem |
| 100 | + |
| 101 | +Add the gem to the `Gemfile` (idempotent — skip if already present): |
| 102 | + |
| 103 | +```ruby |
| 104 | +# Strata Government Digital Services SDK Rails engine |
| 105 | +gem "strata", git: "https://github.com/navapbc/strata-sdk-rails.git" |
| 106 | +``` |
| 107 | + |
| 108 | +**6a. Install locally:** |
| 109 | + |
| 110 | +```sh |
| 111 | +bundle install |
| 112 | +``` |
| 113 | + |
| 114 | +If `bundle install` fails, stop and report the exact error. |
| 115 | + |
| 116 | +**6b. Rebuild the Docker image so the container has the new gem.** `bundle install` only updates the host's `vendor/bundle` (or system gem path); the running container still has the old gem set. Without this step, the next `bin/rails generate strata:...` invocation inside the container will fail with "command not found" or the gem will be missing. |
| 117 | + |
| 118 | +```sh |
| 119 | +make build |
| 120 | +``` |
| 121 | + |
| 122 | +Stop and report on failure. |
| 123 | + |
| 124 | +**6c. Verify the existing test suite still passes** with the new gem installed (no behavior should have changed yet — only the dependency set): |
| 125 | + |
| 126 | +```sh |
| 127 | +make lint |
| 128 | +make test |
| 129 | +``` |
| 130 | + |
| 131 | +If `make test` regresses, the gem may conflict with an existing dependency. Stop and report. Do not move on with a broken baseline — every later step assumes a green starting point. |
| 132 | + |
| 133 | +## Step 7: Generate SDK rules |
| 134 | + |
| 135 | +Run the rules generator so the agent picks up Strata-specific guidance: |
| 136 | + |
| 137 | +```sh |
| 138 | +bin/rails generate strata:rules all <AGENT_FLAG> |
| 139 | +``` |
| 140 | + |
| 141 | +(Omit `<AGENT_FLAG>` if the user picked "other / unsure".) |
| 142 | + |
| 143 | +After it succeeds, **read the generated `application_form` rule file** (e.g. `.claude/rules/strata-sdk/application_form.md`) before proceeding. The rule file is the recipe for everything that follows; the steps below are a high-level guide and must defer to the rule file when they conflict. |
| 144 | + |
| 145 | +## Step 8: Identify the application type |
| 146 | + |
| 147 | +Ask: |
| 148 | + |
| 149 | +> **What kind of application is this? (e.g. unemployment benefits, SNAP, Medicaid, housing assistance, passport, business license, appeal, other)** |
| 150 | +
|
| 151 | +Use the answer to drive the model name and attribute suggestions. Examples: |
| 152 | + |
| 153 | +| Type | Suggested form name | |
| 154 | +|------|---------------------| |
| 155 | +| Unemployment | `UnemploymentApplicationForm` | |
| 156 | +| SNAP | `SnapApplicationForm` | |
| 157 | +| Medicaid | `MedicaidApplicationForm` | |
| 158 | +| Housing | `HousingApplicationForm` | |
| 159 | +| Other | `<DomainName>ApplicationForm` | |
| 160 | + |
| 161 | +## Step 9: Propose attributes, iterate, confirm |
| 162 | + |
| 163 | +Suggest a starting set of attributes appropriate for the application type, using **Strata attribute types** (see the rule file). Common starter set: |
| 164 | + |
| 165 | +| Attribute | Strata type | Notes | |
| 166 | +|-----------|-------------|-------| |
| 167 | +| `name` | `name` | Applicant full name | |
| 168 | +| `birth_date` | `memorable_date` | DOB | |
| 169 | +| `ssn` | `tax_id` | If program requires SSN | |
| 170 | +| `residential_address` | `address` | Mailing/residential | |
| 171 | +| `email` | `email` | Contact | |
| 172 | +| `phone` | `phone` | Contact | |
| 173 | + |
| 174 | +Tailor by program (examples — confirm with the user, do not invent silently): |
| 175 | + |
| 176 | +- **Unemployment:** last employer, last day worked, reason for separation, weekly earnings |
| 177 | +- **SNAP:** household size, monthly income, household members, expenses |
| 178 | +- **Medicaid:** household size, income, citizenship status, disability status |
| 179 | + |
| 180 | +Go back and forth with the user until the attribute list is final, then ask: |
| 181 | + |
| 182 | +> **Confirm this attribute list? (yes / edit)** |
| 183 | +
|
| 184 | +Only proceed on yes. Save the final list as `<ATTRS>` formatted as `name:strata_type` pairs separated by spaces. |
| 185 | + |
| 186 | +## Step 10: Validate against generated rules, then generate the model |
| 187 | + |
| 188 | +**10a. Re-read the rule files generated in Step 7** before invoking any generator. List and read every file under the rules dir chosen in Step 4: |
| 189 | + |
| 190 | +```sh |
| 191 | +ls <RULES_DIR> |
| 192 | +``` |
| 193 | + |
| 194 | +At minimum, read: |
| 195 | + |
| 196 | +- `<RULES_DIR>/application_form.md` — recipe for the form model |
| 197 | +- Any rule file covering attributes / data modeling (commonly `strata_attributes.md`, `data_modeler.md`, or similar — names depend on the installed gem version) |
| 198 | +- Any rule file covering migrations (commonly `migration.md` or `data_modeler.md`) |
| 199 | + |
| 200 | +`<RULES_DIR>` is the agent dir from Step 4 (`.claude/rules/strata-sdk/`, `.cursor/rules/strata-sdk/`, `.copilot/rules/strata-sdk/`, or `.agents/rules/strata-sdk/`). |
| 201 | + |
| 202 | +**10b. Validate the proposed model + attributes against the rules.** Check at least: |
| 203 | + |
| 204 | +| Check | What to verify | |
| 205 | +|-------|----------------| |
| 206 | +| Form name | Matches the naming convention the rule file requires (typically `<Domain>ApplicationForm`) | |
| 207 | +| Parent class | Rule file confirms model must extend `Strata::ApplicationForm` | |
| 208 | +| Attribute types | Every attribute in `<ATTRS>` uses a Strata type listed in the rules (e.g. `name`, `memorable_date`, `tax_id`, `address`, `email`, `phone`, `integer`, etc.) — flag any that aren't supported | |
| 209 | +| Required base columns | Rules confirm `status`, `user_id`, `submitted_at` are needed in the migration (used in Step 11) | |
| 210 | +| Attribute naming | Matches conventions in the rule file (snake_case, no reserved names, etc.) | |
| 211 | +| Anything else the rule file calls out | Read carefully — rules may require specific validations, callbacks, or associations | |
| 212 | + |
| 213 | +**10c. If any check fails**, do not run the generator. Report the conflict to the user, propose a fix (rename attribute, swap type, drop unsupported attribute, etc.), and re-confirm: |
| 214 | + |
| 215 | +> Rule `<rule_file>` requires `<X>` but the plan has `<Y>`. Change to `<proposal>`? (yes / edit) |
| 216 | +
|
| 217 | +Loop back to Step 9 if attribute changes are needed. Only proceed once every check passes. |
| 218 | + |
| 219 | +**10d. Write failing model specs FIRST (TDD).** Before running the generator, write RSpec specs for the form model under `spec/models/strata/<form_name>_spec.rb` covering at least: |
| 220 | + |
| 221 | +- Extends `Strata::ApplicationForm` |
| 222 | +- Each `<ATTRS>` entry is declared as the right Strata type |
| 223 | +- Required-attribute validations the rule file specifies |
| 224 | +- Status transitions (`in_progress` → `submitted`) and the immutability rule (no edits after `submitted`) |
| 225 | + |
| 226 | +Run `make test` and confirm these specs **fail** (model doesn't exist yet). See the `test-driven-development` skill — do not skip the watch-it-fail step. |
| 227 | + |
| 228 | +**10e. Generate the model:** |
| 229 | + |
| 230 | +```sh |
| 231 | +bin/rails generate strata:application_form <FormName> <ATTRS> |
| 232 | +``` |
| 233 | + |
| 234 | +Example: |
| 235 | + |
| 236 | +```sh |
| 237 | +bin/rails generate strata:application_form SnapApplicationForm name:name birth_date:memorable_date residential_address:address household_size:integer |
| 238 | +``` |
| 239 | + |
| 240 | +**10f. Verify and finish the model.** Open `app/models/strata/<form_name>.rb`: |
| 241 | + |
| 242 | +- Extends `Strata::ApplicationForm` (not `ApplicationRecord`) — fix if not. |
| 243 | +- Each attribute uses `strata_attribute :<name>, :<type>` per the rule file. |
| 244 | +- Add any rule-mandated validations, scopes, associations the generator didn't. |
| 245 | + |
| 246 | +If the generator created stub specs, replace or fold them into the specs from 10d. Run `make test` until the specs from 10d pass, then `make lint`. Do not move on while either fails. |
| 247 | + |
| 248 | +## Step 11: Generate and run the migration |
| 249 | + |
| 250 | +The migration **must** include the `ApplicationForm` base columns plus the form's attributes: |
| 251 | + |
| 252 | +```sh |
| 253 | +bin/rails generate strata:migration status:integer user_id:uuid submitted_at:datetime <ATTRS> |
| 254 | +``` |
| 255 | + |
| 256 | +Then: |
| 257 | + |
| 258 | +```sh |
| 259 | +bin/rails db:migrate |
| 260 | +``` |
| 261 | + |
| 262 | +If the migration fails, stop and report. |
| 263 | + |
| 264 | +**Re-run TDD checkpoint:** the model specs from Step 10d still need to pass against the now-migrated schema. Run: |
| 265 | + |
| 266 | +```sh |
| 267 | +make test |
| 268 | +make lint |
| 269 | +``` |
| 270 | + |
| 271 | +If specs that previously passed now fail because of the migration (missing column, wrong type, etc.), fix the migration — do not weaken the specs. |
| 272 | + |
| 273 | +## Step 12: Ask how users reach the form (entry point) |
| 274 | + |
| 275 | +Before generating views, ask: |
| 276 | + |
| 277 | +> **How will users reach this form?** |
| 278 | +> |
| 279 | +> 1. Landing page after login (root for authenticated users) |
| 280 | +> 2. Link or card on an existing dashboard |
| 281 | +> 3. Button on a specific page (which one?) |
| 282 | +> 4. Other (describe) |
| 283 | +
|
| 284 | +Save the answer as `<ENTRY_POINT>`. Do not invent — if the user is unclear, ask follow-ups. |
| 285 | + |
| 286 | +## Step 13: Generate views and wire up the entry point (TDD) |
| 287 | + |
| 288 | +Follow the **generated `application_form` rule file's recipe** for views (controllers, views, routes). Apply TDD throughout — see `test-driven-development` skill. |
| 289 | + |
| 290 | +**13a. Write failing request and system specs FIRST.** Cover at least: |
| 291 | + |
| 292 | +- `GET <form>/new` returns 200 for an authenticated user, redirects/denies otherwise (per rule file's auth expectations) |
| 293 | +- `POST <form>` with valid `<ATTRS>` creates a record with `status: 'in_progress'` |
| 294 | +- `POST <form>` with invalid params re-renders the form and shows the error |
| 295 | +- A system spec for `<ENTRY_POINT>`: |
| 296 | + - **Option 1 (post-login landing):** after sign-in, user lands on the form |
| 297 | + - **Option 2 (dashboard link/card):** dashboard shows the link/card and clicking it reaches the form |
| 298 | + - **Option 3 (button on a page):** the named page shows the button and clicking it reaches the form |
| 299 | + - **Option 4 (other):** spec mirrors what the user described |
| 300 | + |
| 301 | +Run `make test` — confirm all of the above **fail** for the right reasons (route missing, controller missing, link absent). |
| 302 | + |
| 303 | +**13b. Implement minimum to pass each spec, one at a time:** |
| 304 | + |
| 305 | +1. Generate or hand-write the controller + views per the rule file (the SDK may provide a scaffold-style generator — prefer it if so). |
| 306 | +2. Add the route in `config/routes.rb`. |
| 307 | +3. Wire the entry point per `<ENTRY_POINT>`: |
| 308 | + - **Option 1 (post-login landing):** point the authenticated root route at the new form's `new` action. |
| 309 | + - **Option 2 (dashboard link/card):** add a link or card on the existing dashboard view. |
| 310 | + - **Option 3 (button on a page):** add a button to the page the user named, linking to the form's `new` action. |
| 311 | + - **Option 4 (other):** implement what the user described, ask if unclear. |
| 312 | + |
| 313 | +Show the route changes and view edits to the user before saving. |
| 314 | + |
| 315 | +**13c. After each pass, run:** |
| 316 | + |
| 317 | +```sh |
| 318 | +make test |
| 319 | +make lint |
| 320 | +``` |
| 321 | + |
| 322 | +Both must be green before moving to the next spec. If a spec passes immediately without an implementation change, the spec is wrong — rewrite it. |
| 323 | + |
| 324 | +## Step 14: Verify |
| 325 | + |
| 326 | +Run the project's standard checks: |
| 327 | + |
| 328 | +```sh |
| 329 | +make lint |
| 330 | +make test |
| 331 | +``` |
| 332 | + |
| 333 | +If either fails, stop and report. The user can then decide next steps (fix tests, adjust the form, etc.). |
| 334 | + |
| 335 | +## Step 15: Report |
| 336 | + |
| 337 | +Tell the user: |
| 338 | + |
| 339 | +> **Application form ready.** Run `make start-container` (or the project's normal start command) and visit the entry point you chose. The form lives at `app/models/strata/<form_name>.rb` and its rule file at `<rules_dir>/application_form.md` — re-run the rules generator after upgrading the gem. |
| 340 | +
|
| 341 | +## Common pitfalls |
| 342 | + |
| 343 | +| Problem | Fix | |
| 344 | +|---------|-----| |
| 345 | +| `bin/rails generate strata:...` says command not found | Gem not installed — re-run `bundle install` | |
| 346 | +| Model extends `ApplicationRecord` instead of `Strata::ApplicationForm` | Edit the model file, re-run tests | |
| 347 | +| Migration missing `status` / `user_id` / `submitted_at` | Re-generate the migration with the base columns included | |
| 348 | +| User picked an agent not in the table | Use `--agent <answer>` if the gem supports it; otherwise omit the flag and use `.agents/rules/strata-sdk/` | |
| 349 | +| Rule file recipe conflicts with this skill | Rule file wins — it reflects the installed gem version | |
| 350 | + |
| 351 | +## Reference |
| 352 | + |
| 353 | +- Strata SDK Rails (gem): https://github.com/navapbc/strata-sdk-rails |
| 354 | +- Intake application forms guide: `docs/intake-application-forms.md` in the gem repo |
| 355 | +- Strata attribute types: `docs/strata-attributes.md` in the gem repo |
| 356 | +- Rules generator: `bin/rails generate strata:rules --help` |
0 commit comments