diff --git a/packages/software-factory/.agents/skills/boxel-development/SKILL.md b/.agents/skills/boxel-development/SKILL.md similarity index 100% rename from packages/software-factory/.agents/skills/boxel-development/SKILL.md rename to .agents/skills/boxel-development/SKILL.md diff --git a/packages/software-factory/.agents/skills/boxel-development/references/dev-command-development.md b/.agents/skills/boxel-development/references/dev-command-development.md similarity index 100% rename from packages/software-factory/.agents/skills/boxel-development/references/dev-command-development.md rename to .agents/skills/boxel-development/references/dev-command-development.md diff --git a/packages/software-factory/.agents/skills/boxel-development/references/dev-core-concept.md b/.agents/skills/boxel-development/references/dev-core-concept.md similarity index 100% rename from packages/software-factory/.agents/skills/boxel-development/references/dev-core-concept.md rename to .agents/skills/boxel-development/references/dev-core-concept.md diff --git a/packages/software-factory/.agents/skills/boxel-development/references/dev-core-patterns.md b/.agents/skills/boxel-development/references/dev-core-patterns.md similarity index 100% rename from packages/software-factory/.agents/skills/boxel-development/references/dev-core-patterns.md rename to .agents/skills/boxel-development/references/dev-core-patterns.md diff --git a/packages/software-factory/.agents/skills/boxel-development/references/dev-data-management.md b/.agents/skills/boxel-development/references/dev-data-management.md similarity index 100% rename from packages/software-factory/.agents/skills/boxel-development/references/dev-data-management.md rename to .agents/skills/boxel-development/references/dev-data-management.md diff --git a/packages/software-factory/.agents/skills/boxel-development/references/dev-defensive-programming.md b/.agents/skills/boxel-development/references/dev-defensive-programming.md similarity index 100% rename from packages/software-factory/.agents/skills/boxel-development/references/dev-defensive-programming.md rename to .agents/skills/boxel-development/references/dev-defensive-programming.md diff --git a/packages/software-factory/.agents/skills/boxel-development/references/dev-delegated-rendering.md b/.agents/skills/boxel-development/references/dev-delegated-rendering.md similarity index 100% rename from packages/software-factory/.agents/skills/boxel-development/references/dev-delegated-rendering.md rename to .agents/skills/boxel-development/references/dev-delegated-rendering.md diff --git a/packages/software-factory/.agents/skills/boxel-development/references/dev-enumerations.md b/.agents/skills/boxel-development/references/dev-enumerations.md similarity index 100% rename from packages/software-factory/.agents/skills/boxel-development/references/dev-enumerations.md rename to .agents/skills/boxel-development/references/dev-enumerations.md diff --git a/packages/software-factory/.agents/skills/boxel-development/references/dev-external-libraries.md b/.agents/skills/boxel-development/references/dev-external-libraries.md similarity index 100% rename from packages/software-factory/.agents/skills/boxel-development/references/dev-external-libraries.md rename to .agents/skills/boxel-development/references/dev-external-libraries.md diff --git a/packages/software-factory/.agents/skills/boxel-development/references/dev-file-def.md b/.agents/skills/boxel-development/references/dev-file-def.md similarity index 100% rename from packages/software-factory/.agents/skills/boxel-development/references/dev-file-def.md rename to .agents/skills/boxel-development/references/dev-file-def.md diff --git a/packages/software-factory/.agents/skills/boxel-development/references/dev-file-editing.md b/.agents/skills/boxel-development/references/dev-file-editing.md similarity index 100% rename from packages/software-factory/.agents/skills/boxel-development/references/dev-file-editing.md rename to .agents/skills/boxel-development/references/dev-file-editing.md diff --git a/packages/software-factory/.agents/skills/boxel-development/references/dev-fitted-formats.md b/.agents/skills/boxel-development/references/dev-fitted-formats.md similarity index 100% rename from packages/software-factory/.agents/skills/boxel-development/references/dev-fitted-formats.md rename to .agents/skills/boxel-development/references/dev-fitted-formats.md diff --git a/packages/software-factory/.agents/skills/boxel-development/references/dev-query-systems.md b/.agents/skills/boxel-development/references/dev-query-systems.md similarity index 100% rename from packages/software-factory/.agents/skills/boxel-development/references/dev-query-systems.md rename to .agents/skills/boxel-development/references/dev-query-systems.md diff --git a/packages/software-factory/.agents/skills/boxel-development/references/dev-quick-reference.md b/.agents/skills/boxel-development/references/dev-quick-reference.md similarity index 100% rename from packages/software-factory/.agents/skills/boxel-development/references/dev-quick-reference.md rename to .agents/skills/boxel-development/references/dev-quick-reference.md diff --git a/packages/software-factory/.agents/skills/boxel-development/references/dev-qunit-testing.md b/.agents/skills/boxel-development/references/dev-qunit-testing.md similarity index 94% rename from packages/software-factory/.agents/skills/boxel-development/references/dev-qunit-testing.md rename to .agents/skills/boxel-development/references/dev-qunit-testing.md index 72671ed3fd9..8ca7abca0da 100644 --- a/packages/software-factory/.agents/skills/boxel-development/references/dev-qunit-testing.md +++ b/.agents/skills/boxel-development/references/dev-qunit-testing.md @@ -134,7 +134,7 @@ Add `data-test-*` attributes to card templates for stable test selectors: When tests fail, the orchestrator feeds test failure details back to the agent. For more detail: -- **TestRun cards** live in the target realm's `Validations/` folder with a `test_` prefix (e.g., `Validations/test_issue-slug-1.json`). To find all test runs, search by the TestRun card type in the target realm. Each TestRun has a `sequenceNumber` that increases with each iteration. Use `read_file` on a specific TestRun for full details. +- **TestRun cards** live in the target realm's `Validations/` folder with a `test_` prefix (e.g., `Validations/test_issue-slug-1.json`). To find all test runs, run `Glob` over `Validations/test_*.json` or shell out via `Bash` to `boxel search --realm ` filtered on the TestRun card type. Each TestRun has a `sequenceNumber` that increases with each iteration. Use native `Read` on a specific TestRun for full details — paths are workspace-relative. ## Rules diff --git a/packages/software-factory/.agents/skills/boxel-development/references/dev-replicate-ai.md b/.agents/skills/boxel-development/references/dev-replicate-ai.md similarity index 100% rename from packages/software-factory/.agents/skills/boxel-development/references/dev-replicate-ai.md rename to .agents/skills/boxel-development/references/dev-replicate-ai.md diff --git a/packages/software-factory/.agents/skills/boxel-development/references/dev-spec-usage.md b/.agents/skills/boxel-development/references/dev-spec-usage.md similarity index 73% rename from packages/software-factory/.agents/skills/boxel-development/references/dev-spec-usage.md rename to .agents/skills/boxel-development/references/dev-spec-usage.md index 4f19b8ac335..2921bd60cc7 100644 --- a/packages/software-factory/.agents/skills/boxel-development/references/dev-spec-usage.md +++ b/.agents/skills/boxel-development/references/dev-spec-usage.md @@ -1,24 +1,52 @@ # Catalog Spec Card Instances -For each top-level card definition, create a Catalog Spec card instance in the target realm's `Spec/` folder using the `create_catalog_spec` tool. This makes the card discoverable in the Boxel catalog. +For each top-level card definition, write a Catalog Spec card instance in the target realm's `Spec/` folder. This makes the card discoverable in the Boxel catalog. -The `create_catalog_spec` tool has the authoritative JSON schema for Spec card fields — use its parameter definitions to know which attributes and relationships are available. The tool auto-constructs the document with the correct `adoptsFrom` (`https://cardstack.com/base/spec#Spec`). +Specs adopt from the `Spec` class exported by `https://cardstack.com/base/spec` — that module lives in the base realm, not your target realm. Fetch the authoritative schema by calling the `get_card_schema` factory tool: -## Usage +``` +get_card_schema({ module: 'https://cardstack.com/base/spec', name: 'Spec' }) +``` -Use the `create_catalog_spec` tool to create a Spec card. The tool's parameters define the available fields dynamically from the card definition — consult the tool schema for the exact field names and types. +The result gives you the exact `attributes` and `relationships` shape. Write the JSON file with native `Write` (paths are workspace-relative, e.g. `Spec/sticky-note.json`); `boxel sync` pushes it to the realm between iterations. + +## Required Shape + +```json +{ + "data": { + "type": "card", + "attributes": { + "specType": "card", + "ref": { "module": "../sticky-note", "name": "StickyNote" }, + "readMe": "...", + "cardInfo": { "name": "Sticky Note", "summary": "..." } + }, + "relationships": { + "linkedExamples.0": { "links": { "self": "../StickyNote/welcome-note" } } + }, + "meta": { + "adoptsFrom": { + "module": "https://cardstack.com/base/spec", + "name": "Spec" + } + } + } +} +``` Key concepts: - `ref` — a CodeRef pointing to the card definition (module path + exported class name). The module path is relative from the Spec card to the `.gts` file (e.g., `../sticky-note` from `Spec/sticky-note.json`). - `specType` — `"card"` for CardDef, `"field"` for FieldDef, `"component"` for standalone components. -- `linkedExamples` — a relationship pointing to sample card instances. Create at least one sample instance and link it here. +- `linkedExamples` — a `linksToMany` relationship pointing to sample card instances. Use dotted keys (`linkedExamples.0`, `linkedExamples.1`, …) — the array form is rejected by the indexer. Create at least one sample instance and link it here. +- **Do NOT call `run_instantiate` on the Spec file itself.** Spec's module lives in the base realm; the prerender enforces same-origin module loads and the call always fails. To validate Specs, call `run_instantiate` WITHOUT a `path`; it discovers Specs in the target realm and exercises their `linkedExamples` against the card classes you wrote. ## Sample Card Instances Create at least one sample instance with realistic data for each top-level card. Sample instances serve as both catalog examples and test fixtures. -Place sample instances in a folder named after the card type (e.g., `StickyNote/welcome-note.json`). Use `write_file` to create them. The `linkedExamples` relationship in the Spec card points to these using a relative path (e.g., `../StickyNote/welcome-note`). +Place sample instances in a folder named after the card type (e.g., `StickyNote/welcome-note.json`) and write them with native `Write`. The `linkedExamples` relationship in the Spec card points to these using a relative path without the `.json` suffix (e.g., `../StickyNote/welcome-note`). --- diff --git a/packages/software-factory/.agents/skills/boxel-development/references/dev-styling-design.md b/.agents/skills/boxel-development/references/dev-styling-design.md similarity index 100% rename from packages/software-factory/.agents/skills/boxel-development/references/dev-styling-design.md rename to .agents/skills/boxel-development/references/dev-styling-design.md diff --git a/packages/software-factory/.agents/skills/boxel-development/references/dev-technical-rules.md b/.agents/skills/boxel-development/references/dev-technical-rules.md similarity index 100% rename from packages/software-factory/.agents/skills/boxel-development/references/dev-technical-rules.md rename to .agents/skills/boxel-development/references/dev-technical-rules.md diff --git a/packages/software-factory/.agents/skills/boxel-development/references/dev-template-patterns.md b/.agents/skills/boxel-development/references/dev-template-patterns.md similarity index 100% rename from packages/software-factory/.agents/skills/boxel-development/references/dev-template-patterns.md rename to .agents/skills/boxel-development/references/dev-template-patterns.md diff --git a/packages/software-factory/.agents/skills/boxel-development/references/dev-theme-design-system.md b/.agents/skills/boxel-development/references/dev-theme-design-system.md similarity index 100% rename from packages/software-factory/.agents/skills/boxel-development/references/dev-theme-design-system.md rename to .agents/skills/boxel-development/references/dev-theme-design-system.md diff --git a/packages/software-factory/.agents/skills/boxel-file-structure/SKILL.md b/.agents/skills/boxel-file-structure/SKILL.md similarity index 100% rename from packages/software-factory/.agents/skills/boxel-file-structure/SKILL.md rename to .agents/skills/boxel-file-structure/SKILL.md diff --git a/packages/boxel-cli/.agents/skills/boxel-api/SKILL.md b/packages/boxel-cli/.agents/skills/boxel-api/SKILL.md new file mode 100644 index 00000000000..ecdd16b8815 --- /dev/null +++ b/packages/boxel-cli/.agents/skills/boxel-api/SKILL.md @@ -0,0 +1,182 @@ +--- +name: boxel-api +description: Use when calling Boxel realm-server APIs from code — primarily federated search across realms. Documents the boxel-cli programmatic surface (`BoxelCLIClient`) and the matching CLI commands. Read this whenever you need to query a realm's index. +--- + +# Boxel API + +Canonical home for Boxel platform API knowledge. + +**Architectural principle:** boxel-cli owns the entire Boxel API surface. Any code that talks to the realm server or Matrix lives in boxel-cli; consumers (the software-factory, custom scripts, hand-written tools) import `BoxelCLIClient` from `@cardstack/boxel-cli/api` and call its methods. Auth (tokens, refresh, retries) is fully internal — if you're holding a JWT or calling `fetch` against a realm URL directly, you're in the wrong layer. + +```ts +import { BoxelCLIClient } from '@cardstack/boxel-cli/api'; + +let client = new BoxelCLIClient(); // reads the active Boxel profile +``` + +All examples below assume `client` is a `BoxelCLIClient` instance. + +## Federated search + +Search across one or more realms via `/_federated-search`. Query syntax matches the Boxel realm search format. + +### CLI + +``` +boxel search --realm [--realm ...] --query '' +``` + +`--realm` is repeatable. `--query` takes a JSON string. Append `--json` for raw output. + +### Programmatic + +```ts +let result = await client.search(realmUrl, query); // single realm +let result = await client.search([realmA, realmB], query); // federated +``` + +Returns `{ ok, status, data?, error? }`. `data` is an array of card resources. + +### Query syntax + +```json +{ + "filter": { ... }, + "sort": [ ... ], + "page": { "size": 10 } +} +``` + +All top-level fields are optional. An empty query `{}` returns all cards in the targeted realms. + +#### Filter by card type + +```json +{ + "filter": { + "type": { + "module": "http://localhost:4201/software-factory/darkfactory", + "name": "Project" + } + } +} +``` + +Returns all cards that adopt from (or extend) the specified type. Wildcards (`*`) in `module` or `name` are **not** supported — always use a specific CodeRef. + +#### Filter by field value (`eq`) + +`eq` requires an `on` to scope the field to a card type: + +```json +{ + "filter": { + "on": { "module": "...", "name": "Issue" }, + "eq": { "status": "in_progress" } + } +} +``` + +Multiple fields in `eq` are ANDed. Use dot paths for nested fields (e.g. `"author.firstName": "Carl"`). Use `null` to match empty/missing fields. + +#### Substring search (`contains`) + +Case-insensitive substring match: + +```json +{ "filter": { "contains": { "cardTitle": "sticky" } } } +``` + +Scoped form same as `eq` (`on` + `contains`). + +#### Range filters + +```json +{ + "filter": { + "on": { "module": "...", "name": "Post" }, + "range": { "views": { "lte": 10, "gt": 5 } } + } +} +``` + +Operators: `gt`, `gte`, `lt`, `lte`. Works on numeric, date, and string fields. + +#### Boolean combinators + +```json +// AND +{ "filter": { "on": {...}, "every": [ {...}, {...} ] } } + +// OR +{ "filter": { "any": [ {...}, {...} ] } } + +// NOT +{ "filter": { "on": {...}, "not": { "eq": { ... } } } } +``` + +#### Sort + +```json +{ + "sort": [ + { "by": "author.lastName", "on": { "module": "...", "name": "Article" } } + ], + "filter": { "type": { "module": "...", "name": "Article" } } +} +``` + +Add `"direction": "desc"` for descending. + +#### Pagination + +```json +{ "filter": {...}, "page": { "size": 10 } } +``` + +#### CodeRef field matching + +CodeRef fields (e.g. `ref` on a Spec card) are matched against the full `{ module, name }`: + +```json +{ + "filter": { + "on": { "module": "https://cardstack.com/base/spec", "name": "Spec" }, + "eq": { + "ref": { + "module": "http://localhost:4201/my-realm/sticky-note", + "name": "StickyNote" + } + } + } +} +``` + +### Common mistakes + +- **Field names without `on`.** Fields like `title`, `status`, etc. are type-specific. The exceptions are `cardTitle` and `cardDescription` — those exist on the base `CardDef`. +- **Relative or bare module URLs.** Always use full absolute module URLs in CodeRefs. +- **Slash separators in dotted paths.** Use `author.firstName`, not `author/firstName`. +- **Searching relationships that aren't rendered in an embedded/fitted template.** The query engine indexes a linked field only if it appears in an embedded format. Otherwise the filter silently misses. + +## When to use what + +| Goal | Use | +| -------------------------------------------------------- | ------------------------------------------------------------------ | +| Find cards in your local synced workspace | Native `grep` / `find` — files are already on disk | +| Find cards by type / field across one or more realms | `boxel search` / `client.search` | +| Read a single card's source from a realm | `client.read(realmUrl, path)` / `boxel file read` | +| Read the transpiled (browser) version of a `.gts` module | `client.readTranspiled(...)` / `boxel read-transpiled` | +| List files in a realm | `client.listFiles(realmUrl)` / `boxel file list` | +| Push local changes to a realm | `client.sync(realmUrl, dir, { preferLocal: true })` / `boxel sync` | +| Pull a realm's state to a local dir | `client.pull(realmUrl, dir)` / `boxel pull` | +| Run a host command (prerendered) | See the `boxel-command` skill | + +## What this skill is **not** for + +- **Card development patterns** (`.gts` field declarations, templates, `linksTo` vs `contains`) — that's `boxel-development`. +- **JSON:API document structure** for card instances — that's `boxel-file-structure`. +- **Sync / pull / track / watch CLI ergonomics** — those have their own per-command skills (`boxel-sync`, `boxel-track`, `boxel-watch`). +- **Host commands via the prerenderer** (`/_run-command`) — that's the `boxel-command` skill. +- **Realm provisioning** (`createRealm` / `boxel realm create`) and **readiness polling** (`client.waitForReady` / `/_readiness-check`) — orchestration concerns. The software-factory creates target realms in `factory-target-realm.ts` before the agent loop starts; consumers needing those APIs should read `boxel-cli/src/api.ts` directly or run `boxel realm create --help`. diff --git a/packages/boxel-cli/.agents/skills/boxel-command/SKILL.md b/packages/boxel-cli/.agents/skills/boxel-command/SKILL.md new file mode 100644 index 00000000000..171af2b3d73 --- /dev/null +++ b/packages/boxel-cli/.agents/skills/boxel-command/SKILL.md @@ -0,0 +1,71 @@ +--- +name: boxel-command +description: Use when running a Boxel host command via the realm server's prerenderer — invoking commands like `get-card-type-schema`, `evaluate-module`, `instantiate-card`, or any other module exposed at `@cardstack/boxel-host/commands/.../default`. Documents `boxel run-command` and the matching `client.runCommand()` method. +--- + +# Boxel Host Commands + +Some Boxel operations only exist inside the host app's prerendered runtime — there's no realm-server HTTP endpoint for them, and they can't be reimplemented in plain Node. The realm server's `/_run-command` endpoint forwards a job to the prerenderer (a headless Chrome instance that has the full host runtime loaded), executes the named command there, and returns the serialized result. Schema introspection, module evaluation, card instantiation, transpiled-module fetches — all of these go through `run-command`. + +This skill documents how to invoke that flow. + +## When to use it + +- **Card type schema lookup.** Get the live `{ attributes, relationships }` JSON Schema for a `CardDef` by introspecting its real class at runtime — not by reading the `.gts` source. +- **Module evaluation.** Load a `.gts` / `.ts` module in the prerender sandbox to surface broken imports, circular references, or top-level runtime errors before they hit a real consumer. +- **Card instantiation.** Construct a card instance from a JSON document inside the prerender — exercises the `CardDef` class against the document shape. +- **Anything else exposed at `@cardstack/boxel-host/commands//default`.** Each module is its own host command. + +## CLI + +``` +boxel run-command --realm [--input ''] [--json] +``` + +- `` — the module path of the command (e.g. `@cardstack/boxel-host/commands/get-card-type-schema/default`). +- `--realm` — the realm URL the command runs against. Required. +- `--input` — JSON string passed as the command's input. Optional; some commands take no input. +- `--json` — emit the raw response instead of the formatted summary. + +### Example + +``` +boxel run-command @cardstack/boxel-host/commands/get-card-type-schema/default \ + --realm http://localhost:4201/my-realm/ \ + --input '{"codeRef":{"module":"http://localhost:4201/my-realm/sticky-note","name":"StickyNote"}}' +``` + +## Programmatic + +```ts +import { BoxelCLIClient } from '@cardstack/boxel-cli/api'; + +let client = new BoxelCLIClient(); + +let result = await client.runCommand( + realmServerUrl, + realmUrl, + '@cardstack/boxel-host/commands/get-card-type-schema/default', + { codeRef: { module: '', name: 'StickyNote' } }, +); +``` + +Returns `{ status: 'ready' | 'error' | 'unusable', result?: string | null, error?: string | null }`. `result` is the command's serialized output (a JSON string — parse it yourself). `error` is set when `status !== 'ready'`. + +## How it works under the hood + +`/_run-command` enqueues a job for the realm worker. The worker hands it to the prerenderer (which has the host app, the realm's Loader, the CardAPI, and all field serializers loaded). The command module is imported, called with the input, and its result is serialized back through the queue to the HTTP response. + +Three failure modes you'll see: + +- `status: 'unusable'` — the prerender pool is broken (e.g. "No standby page available for prerender"). Not retryable from the caller's side; usually a sign the realm-server worker / prerender pool itself is unhealthy. +- `status: 'error'` with `error: "module URL not found"` — the realm's in-memory module map hasn't indexed the file yet. Common right after a `/_atomic` write; caller can retry briefly or use `client.sync(..., { waitForIndex: true })` upstream. +- `status: 'error'` with any other message — the command threw inside the prerender. The `error` is the thrown error's message; the original stack is usually in the worker logs. + +The realm server itself enforces auth (server JWT via `BoxelCLIClient`); the prerender executes inside the realm's sandbox with the realm's permissions. + +## What this skill is **not** for + +- **Realm-side HTTP endpoints** (search, file read/write, atomic batches) — those are direct `BoxelCLIClient` methods. See the `boxel-api` skill. +- **Programmatic in-memory validators** (`runLintInMemory`, `runEvaluateInMemory`, `runParseInMemory`, `runInstantiateInMemory`) — those wrap `runCommand` internally but expose a flatter result shape; consumers usually want those, not raw `runCommand`. They live in the software-factory package. +- **Defining new host commands.** That's host-app development (`packages/host/app/commands/`). diff --git a/packages/software-factory/.agents/skills/boxel-development/references/dev-realm-search.md b/packages/software-factory/.agents/skills/boxel-development/references/dev-realm-search.md deleted file mode 100644 index e4b1ec7563a..00000000000 --- a/packages/software-factory/.agents/skills/boxel-development/references/dev-realm-search.md +++ /dev/null @@ -1,288 +0,0 @@ -# Realm Search Query Reference - -How to use the `search_realm` tool to query cards in a realm. The query object follows the Boxel realm search API format. - -## Basic Structure - -```json -{ - "filter": { ... }, - "sort": [ ... ], - "page": { "size": 10 } -} -``` - -All top-level fields are optional. An empty query `{}` returns all cards. - -## Filter by Card Type - -Use `type` with a `{ module, name }` CodeRef to filter by card type. The `module` must be the full absolute URL of the module that defines the card. - -```json -{ - "filter": { - "type": { - "module": "http://localhost:4201/software-factory/darkfactory", - "name": "Project" - } - } -} -``` - -This returns all cards that adopt from (or extend) the specified type. Do NOT use wildcards (`*`) in module or name — they are not supported. - -## Filter by Field Value (eq) - -Use `eq` to match exact field values. You must specify `on` to scope the field to a card type: - -```json -{ - "filter": { - "on": { - "module": "http://localhost:4201/software-factory/darkfactory", - "name": "Issue" - }, - "eq": { "status": "in_progress" } - } -} -``` - -Multiple fields in `eq` are ANDed: - -```json -{ - "filter": { - "on": { "module": "...", "name": "Post" }, - "eq": { "cardTitle": "Card 1", "cardDescription": "Sample post" } - } -} -``` - -### Nested Fields - -Use dot paths for nested fields (e.g., fields inside a `contains` relationship): - -```json -{ - "filter": { - "on": { "module": "...", "name": "Post" }, - "eq": { "author.firstName": "Carl" } - } -} -``` - -### Null / Missing Values - -Use `null` to find cards where a field is empty or missing: - -```json -{ - "filter": { - "on": { "module": "...", "name": "TypeExamples" }, - "eq": { "stringField": null } - } -} -``` - -## Substring Search (contains) - -Use `contains` for case-insensitive substring matching: - -```json -{ - "filter": { - "contains": { "cardTitle": "sticky" } - } -} -``` - -Scoped to a type: - -```json -{ - "filter": { - "on": { "module": "...", "name": "Person" }, - "contains": { "cardTitle": "note" } - } -} -``` - -## Range Filters - -Use `range` with `gt`, `gte`, `lt`, `lte` for numeric, date, or string comparisons: - -```json -{ - "filter": { - "on": { "module": "...", "name": "Post" }, - "range": { - "views": { "lte": 10, "gt": 5 }, - "author.posts": { "gte": 1 } - } - } -} -``` - -## Combining Filters - -### AND (every) - -All conditions must match: - -```json -{ - "filter": { - "on": { "module": "...", "name": "Post" }, - "every": [ - { "eq": { "cardTitle": "Card 1" } }, - { "not": { "eq": { "author.firstName": "Cardy" } } } - ] - } -} -``` - -### OR (any) - -At least one condition must match. Can combine different types: - -```json -{ - "filter": { - "any": [ - { - "on": { "module": "...", "name": "Article" }, - "eq": { "author.firstName": "Cardy" } - }, - { - "on": { "module": "...", "name": "Book" }, - "eq": { "author.firstName": "Cardy" } - } - ] - } -} -``` - -### NOT (negation) - -```json -{ - "filter": { - "on": { "module": "...", "name": "Article" }, - "not": { "eq": { "author.firstName": "Carl" } } - } -} -``` - -## Sorting - -Sort results using the `sort` array. Each entry needs `by` (field path) and `on` (card type): - -```json -{ - "sort": [ - { - "by": "author.lastName", - "on": { "module": "...", "name": "Article" } - } - ], - "filter": { - "type": { "module": "...", "name": "Article" } - } -} -``` - -Descending order: - -```json -{ - "sort": [ - { - "by": "author.firstName", - "on": { "module": "...", "name": "Article" }, - "direction": "desc" - } - ] -} -``` - -## Pagination - -```json -{ - "filter": { "type": { "module": "...", "name": "Project" } }, - "page": { "size": 10 } -} -``` - -## Discovering Available Fields - -You can only filter/sort on fields that exist on the card type. To find which fields a card type has: - -1. Use `run_command` to fetch the JSON schema for a card type: - -```json -{ - "command": "@cardstack/boxel-host/commands/get-card-type-schema/default", - "commandInput": { - "codeRef": { - "module": "http://localhost:4201/software-factory/darkfactory", - "name": "Issue" - } - } -} -``` - -2. The result contains `attributes.properties` listing all searchable fields (e.g., `status`, `summary`, `priority`). - -3. Use those field names in your `eq`, `contains`, `range`, or `sort` with the matching `on` type. - -The card tools (`update_project`, `update_issue`, `create_knowledge`, `create_catalog_spec`) also have dynamic JSON schemas in their parameters that list available fields. - -### Inheritance - -Filtering on a base card type's fields matches all cards that inherit from it. For example, filtering on `CardDef` fields like `cardTitle` or `cardDescription` finds cards of any type. Filtering on an `Issue` field like `status` finds only Issue cards (and any subtypes of Issue). - -### Searching Through Relationship Fields - -You can filter on fields inside `linksTo` and `linksToMany` relationships, as long as those relationship fields are rendered in an embedded or fitted template. Rendering makes them indexable by the query engine. - -For example, if a `Friend` card has `@field friend = linksTo(Dog)` and Dog's `firstName` field is rendered in an embedded template: - -```json -{ - "filter": { - "on": { "module": "...", "name": "Friend" }, - "eq": { "friend.firstName": "Mango" } - } -} -``` - -### Searching by CodeRef Fields - -Some cards have CodeRef fields (e.g., `ref` on the Spec card). You can search by matching the full CodeRef: - -```json -{ - "filter": { - "on": { - "module": "https://cardstack.com/base/spec", - "name": "Spec" - }, - "eq": { - "ref": { - "module": "http://localhost:4201/my-realm/sticky-note", - "name": "StickyNote" - } - } - } -} -``` - -If a relationship field is NOT rendered in any embedded/fitted template, the query engine cannot index it and searches against it will fail. - -## Common Mistakes - -- **Do NOT use wildcards** (`*`) in `module` or `name` — the query engine does not support them. Use `type` with a specific CodeRef. -- **Do NOT use field names without `on`** — fields like `title`, `status`, etc. are specific to a card type. Without `on`, the query engine doesn't know which type's fields to search. The exception is `cardTitle` and `cardDescription` which exist on the base `CardDef`. -- **Use full absolute module URLs** — not relative paths, not bare package names. -- **Nested field paths use dots** — `author.firstName`, not `author/firstName` or `author[firstName]`. diff --git a/packages/software-factory/.agents/skills/boxel-repair/SKILL.md b/packages/software-factory/.agents/skills/boxel-repair/SKILL.md deleted file mode 100644 index a47dff30f2a..00000000000 --- a/packages/software-factory/.agents/skills/boxel-repair/SKILL.md +++ /dev/null @@ -1,46 +0,0 @@ ---- -name: boxel-repair -description: Use when a Boxel workspace has broken realm metadata, missing icons or backgrounds, bad `index.json` or `cards-grid.json` links, or stale Matrix realm metadata that needs `boxel repair-realm` or `boxel repair-realms`. ---- - -> **Factory agent note:** This skill is for human Claude Code sessions only. The factory agent's tool registry does not include boxel-cli tools — all realm I/O uses `write_file`, `read_file`, and `search_realm` tools via the realm HTTP API. - -# Boxel Repair - -Use this workflow when a workspace has any of these symptoms: - -- Missing icon/background in workspace tiles -- Display name is `Unknown Workspace` or mismatched -- Opening a workspace fails due to missing `cards-grid` relationship -- Matrix workspace list (`app.boxel.realms`) is stale/inconsistent - -## Commands - -```bash -# Inspect one realm without mutating -boxel repair-realm --dry-run - -# Repair one realm -boxel repair-realm - -# Repair all realms owned by active profile user -boxel repair-realms -``` - -## Behavior - -`repair-realm` and `repair-realms` perform these repairs: - -- `.realm.json`: normalize `name`, `iconURL`, `backgroundURL` -- `index.json`: ensure `relationships.cardsGrid.links.self` = `./cards-grid` -- `cards-grid.json`: restore default cards-grid card if missing/corrupt -- Before replacing `index.json`/`cards-grid.json`, preserve existing content as timestamped backup cards in the same realm -- `index.json`: write `data.meta._touched` timestamp to break cache -- Matrix `app.boxel.realms`: reconcile list to match repaired, accessible realms - -## Important Defaults - -- `personal` realm is excluded unless `--include-personal` is provided. -- Batch repair defaults to active profile owner. -- Use `--no-reconcile-matrix` when you want file/card repair only. -- Use `--no-fix-index`/`--no-touch-index` when debugging minimal metadata-only fixes. diff --git a/packages/software-factory/.agents/skills/boxel-restore/SKILL.md b/packages/software-factory/.agents/skills/boxel-restore/SKILL.md deleted file mode 100644 index 0e427fce6e7..00000000000 --- a/packages/software-factory/.agents/skills/boxel-restore/SKILL.md +++ /dev/null @@ -1,78 +0,0 @@ ---- -name: boxel-restore -description: Use when restoring a Boxel workspace to a previous checkpoint and syncing deletions back to the server safely, including stopping watch first and running `boxel sync . --prefer-local` after restore. ---- - -> **Factory agent note:** This skill is for human Claude Code sessions only. The factory agent's tool registry does not include boxel-cli tools — all realm I/O uses `write_file`, `read_file`, and `search_realm` tools via the realm HTTP API. - -# Boxel Restore - -Restore workspace to a previous checkpoint and sync deletions to server. - -## Workflow - -1. **Stop watch if running** - Prevents re-pulling deleted files -2. **Show history** - Display recent checkpoints with numbers -3. **Confirm target** - Ask user which checkpoint (or accept from command) -4. **Restore locally** - Run `boxel history . -r ` -5. **Sync to server** - Run `boxel sync . --prefer-local` to push deletions -6. **Restart watch** - Optionally restart watch if it was running - -## Usage - -``` -Use the `boxel-restore` skill interactively -Restore checkpoint `3` -Restore checkpoint `abc123` -``` - -## Commands Used - -```bash -# Stop any running watch first -# (check /tasks and stop if needed) - -# View history -boxel history . - -# Restore to checkpoint (auto-confirm) -echo "y" | boxel history . -r - -# ESSENTIAL: Push deletions to server -boxel sync . --prefer-local - -# Optionally restart watch -boxel watch . -i -d -``` - -## Response Format - -1. Show the checkpoint being restored to (hash, message, date, source) -2. List files that will be deleted (if any new files since checkpoint) -3. Execute restore -4. Execute sync with --prefer-local -5. Confirm completion - -## Critical Notes - -- **Always stop watch before restoring** - Otherwise it re-pulls deleted files -- **Always use --prefer-local after restore** - This syncs deletions to server -- After restore, workspace matches checkpoint exactly (files added later are gone) - -## Example Output - -``` -Restoring to checkpoint #3: abc1234 - Message: Pull: Update knicks-vip-ticket.gts - Source: SERVER (external change) - Date: 5 minutes ago - -Files that will be deleted: - - KnicksVipTicket/knicks-vs-magic.json - - KnicksVipTicket/knicks-vs-thunder.json - -Restoring... ✓ -Syncing deletions to server... ✓ - -Restore complete. Server now matches checkpoint #3. -``` diff --git a/packages/software-factory/.agents/skills/boxel-setup/SKILL.md b/packages/software-factory/.agents/skills/boxel-setup/SKILL.md deleted file mode 100644 index aeb72d9211d..00000000000 --- a/packages/software-factory/.agents/skills/boxel-setup/SKILL.md +++ /dev/null @@ -1,120 +0,0 @@ ---- -name: boxel-setup -description: Use for Boxel CLI onboarding, profile setup, verifying login, listing workspaces, switching profiles, or helping a new user perform their first sync. ---- - -> **Factory agent note:** This skill is for human Claude Code sessions only. The factory agent's tool registry does not include boxel-cli tools — all realm I/O uses `write_file`, `read_file`, and `search_realm` tools via the realm HTTP API. - -# Boxel Setup - -Guide new users through Boxel CLI setup. - -## Trigger - -Run this automatically when: - -- User first opens the repo -- No profile is configured (`npx boxel profile` shows nothing) -- User asks about setup or getting started - -## Flow - -### 1. Check Current State - -```bash -npx boxel profile -``` - -If no profile exists, proceed with setup. - -### 2. Add a Profile - -**Option A: Interactive (recommended)** - -```bash -npx boxel profile add -``` - -This wizard will: - -1. Ask for environment (Production or Staging) -2. Ask for username and password -3. Create the profile automatically - -**Option B: Non-interactive (CI/automation)** - -Ask the user for: - -- **Environment**: Production (app.boxel.ai) or Staging (realms-staging.stack.cards) -- **Username**: Their Boxel handle (e.g., `aallen90`, `ctse`). Found in Account panel as `@username:stack.cards` or in workspace URLs like `app.boxel.ai/username/workspace-name` -- **Password**: Same as Boxel web login - -Then run (using environment variable for security): - -**Production:** - -```bash -BOXEL_PASSWORD="password" npx boxel profile add -u @username:boxel.ai -n "Production" -``` - -**Staging:** - -```bash -BOXEL_PASSWORD="password" npx boxel profile add -u @username:stack.cards -n "Staging" -``` - -> **Security Note:** Avoid passing passwords via `-p` flag as they appear in shell history. - -### 3. Verify - -```bash -npx boxel list -``` - -### 4. First Sync - -Help them sync a workspace: - -```bash -npx boxel sync @username/workspace ./workspace-name -``` - -## Profile Management - -**List profiles:** - -```bash -npx boxel profile list -``` - -**Switch profile:** - -```bash -npx boxel profile switch -``` - -**Migrate from old .env:** - -```bash -npx boxel profile migrate -``` - -## Success Message - -``` -Setup complete! You can now: -- `npx boxel list` - See your workspaces -- `npx boxel sync @username/workspace` - Sync a workspace -- `npx boxel watch .` - Monitor for changes -- `npx boxel history .` - View/restore checkpoints - -Profile management: -- `npx boxel profile` - Show active profile -- `npx boxel profile list` - List all profiles -- `npx boxel profile switch ` - Switch profiles - -For AI-assisted development, try: -- `boxel-watch` - Smart watch with auto intervals -- `boxel-sync` - Context-aware sync -- `boxel-restore` - Undo changes -``` diff --git a/packages/software-factory/.agents/skills/boxel-sync/SKILL.md b/packages/software-factory/.agents/skills/boxel-sync/SKILL.md deleted file mode 100644 index c54845695e9..00000000000 --- a/packages/software-factory/.agents/skills/boxel-sync/SKILL.md +++ /dev/null @@ -1,90 +0,0 @@ ---- -name: boxel-sync -description: Use when deciding how to sync a Boxel workspace after local edits, server changes, or a restore, including choosing between interactive sync, `--prefer-local`, `--prefer-remote`, or `--prefer-newest`. ---- - -> **Factory agent note:** This skill is for human Claude Code sessions only. The factory agent's tool registry does not include boxel-cli tools — all realm I/O uses `write_file`, `read_file`, and `search_realm` tools via the realm HTTP API. - -# Boxel Sync - -Smart bidirectional sync with context-aware conflict resolution. - -## Context Detection - -Analyze the situation to choose the right sync strategy: - -### After Local Edits - -When Claude has been editing files locally: - -- Use `--prefer-local` to push changes -- Creates checkpoint for the push - -### After Server Activity - -When watch detected server changes or user mentions UI edits: - -- Use `--prefer-remote` or default (interactive) -- Pull changes first - -### After Restore - -When a restore was just performed: - -- Use `--prefer-local` to sync deletions to server -- Essential for completing the restore workflow - -### Conflict Detected - -When both sides have changes: - -- Show status first -- Ask user preference or use `--prefer-newest` - -## Commands - -```bash -# Check status first -boxel status . - -# Standard sync (interactive conflicts) -boxel sync . - -# Push local changes -boxel sync . --prefer-local - -# Pull remote changes -boxel sync . --prefer-remote - -# Auto-resolve by timestamp -boxel sync . --prefer-newest - -# Include deletions -boxel sync . --delete - -# Preview only -boxel sync . --dry-run -``` - -## Response Format - -1. Brief status check (what changed where) -2. Chosen strategy and why -3. Execute sync -4. Report results (files pushed/pulled/deleted) - -## Example Output - -``` -Checking status... - Local: 2 files modified - Remote: No changes - -Using --prefer-local since you have local edits. - -Syncing... - Pushed: card-definition.gts, instance.json - Checkpoint: abc1234 [MAJOR] Push: 2 files - -Sync complete! -``` diff --git a/packages/software-factory/.agents/skills/boxel-track/SKILL.md b/packages/software-factory/.agents/skills/boxel-track/SKILL.md deleted file mode 100644 index a47d8df6af7..00000000000 --- a/packages/software-factory/.agents/skills/boxel-track/SKILL.md +++ /dev/null @@ -1,129 +0,0 @@ ---- -name: boxel-track -description: Use when starting or explaining `boxel track` for local file watching, automatic checkpoints, or optional real-time push with `--push` during Boxel development. ---- - -> **Factory agent note:** This skill is for human Claude Code sessions only. The factory agent's tool registry does not include boxel-cli tools — all realm I/O uses `write_file`, `read_file`, and `search_realm` tools via the realm HTTP API. - -# Boxel Track - -Start `boxel track` to monitor local file changes and create checkpoints automatically. - -## When to Use Track - -Use **track** when you're editing files locally (in IDE, with AI agent, etc.) and want automatic backups: - -- Working in VS Code, Cursor, or other IDE -- AI agent is editing files -- You want checkpoint history of your work - -**Track vs Watch:** -| Command | Symbol | Direction | Purpose | -|---------|--------|-----------|---------| -| `track` | ⇆ | Local edits → Checkpoints | Backup your work as you edit | -| `watch` | ⇅ | Server → Local | Pull external changes from Boxel UI | - -## Commands - -```bash -# Start tracking (default: 3s debounce, 10s min interval) -boxel track . - -# Track AND auto-push to server (real-time sync) -boxel track . --push - -# Custom timing (5s debounce, 30s between checkpoints) -boxel track . -d 5 -i 30 - -# Quiet mode (only show checkpoints) -boxel track . -q - -# Verbose mode (debug output) -boxel track . -v - -# Stop all track/watch processes -boxel stop -``` - -## The Track → Sync Workflow - -### Option 1: Manual Sync (Default) - -Track creates local checkpoints only. Push to server when ready: - -```bash -# 1. Track creates checkpoints as you edit -boxel track . - -# 2. When ready to push to server, sync with --prefer-local -boxel sync . --prefer-local -``` - -This lets you: - -- Work offline with local backups -- Batch multiple edits before pushing -- Review changes before they go live - -### Option 2: Real-Time Sync (--push) - -Auto-push changes to server as you edit: - -```bash -# Track AND push changes automatically -boxel track . --push -``` - -Uses batch upload via `/_atomic` endpoint for efficient multi-file uploads. Definitions (.gts) are uploaded before instances (.json) to ensure proper indexing. - -## Context Detection - -When invoked, consider: - -### Standard Development (3s debounce, 10s interval) - -- Normal editing workflow -- Balanced between checkpoint frequency and overhead - -### Fast Iteration (2s debounce, 5s interval) - -- Rapid prototyping -- User says "track closely" or "capture everything" - -### Background Tracking (5s debounce, 30s interval) - -- Long editing sessions -- User says "just backup" or "light tracking" - -## Response Format - -When invoked: - -1. Confirm workspace directory -2. Start track with appropriate settings -3. **Remind user about sync options** - -Example (without --push): - -``` -Starting track in the current workspace (3s debounce, 10s interval). -Checkpoints will be created automatically as you save files. - -Remember: Track creates LOCAL checkpoints only. -When ready to push changes to Boxel server: - boxel sync . --prefer-local - -Or restart with --push for real-time sync: - boxel track . --push - -Use Ctrl+C to stop tracking, or `boxel stop` from another terminal. -``` - -Example (with --push): - -``` -Starting track with auto-push (3s debounce, 10s interval). -Changes will be checkpointed AND pushed to server automatically. - -Use Ctrl+C to stop, or `boxel stop` from another terminal. -``` diff --git a/packages/software-factory/.agents/skills/boxel-watch/SKILL.md b/packages/software-factory/.agents/skills/boxel-watch/SKILL.md deleted file mode 100644 index 5886bb8552a..00000000000 --- a/packages/software-factory/.agents/skills/boxel-watch/SKILL.md +++ /dev/null @@ -1,75 +0,0 @@ ---- -name: boxel-watch -description: Use when starting or choosing settings for `boxel watch` to monitor remote Boxel changes, including active-development, quick-feedback, and background-monitoring intervals. ---- - -> **Factory agent note:** This skill is for human Claude Code sessions only. The factory agent's tool registry does not include boxel-cli tools — all realm I/O uses `write_file`, `read_file`, and `search_realm` tools via the realm HTTP API. - -# Boxel Watch - -Start `boxel watch` with intelligent interval settings based on context. - -## Context Detection - -Analyze the conversation and recent activity to determine the appropriate watch settings: - -### Active Development Mode (5s interval, 3s debounce) - -Use when: - -- User is actively editing .gts or .json files -- User mentions "editing", "working on", "changing", "updating" -- Recent file writes or edits in the workspace -- User asks to "watch while I work" - -### Monitoring Mode (30s interval, 10s debounce) - -Use when: - -- User wants to "keep an eye on" changes -- User is doing research, reading, or planning -- No recent edits to workspace files -- User says "background", "monitor", or "check occasionally" - -### Quick Feedback Mode (10s interval, 5s debounce) - -Use when: - -- User is testing changes in Boxel UI -- User mentions "testing", "trying", "see if it works" -- Balanced between responsiveness and efficiency - -## Execution - -1. Determine the workspace directory (default: current synced workspace) -2. Determine the mode based on context -3. Explain the chosen settings briefly -4. Start watch in background with appropriate flags -5. Inform user how to stop (Ctrl+C or task stop) - -## Commands - -```bash -# Active development -boxel watch . -i 5 -d 3 - -# Monitoring -boxel watch . -i 30 -d 10 - -# Quick feedback -boxel watch . -i 10 -d 5 - -# Quiet mode (any interval) -boxel watch . -i -d -q -``` - -## Response Format - -When invoked, respond with: - -1. Detected mode and reasoning (1 sentence) -2. The watch command being run -3. How to stop or adjust - -Example: -"Starting watch in **active development mode** (5s interval) since you're editing card files. Run in background - use `/tasks` to check status or Ctrl+C to stop." diff --git a/packages/software-factory/.agents/skills/software-factory-operations/SKILL.md b/packages/software-factory/.agents/skills/software-factory-operations/SKILL.md index 5923655e9c5..d6f73380ce8 100644 --- a/packages/software-factory/.agents/skills/software-factory-operations/SKILL.md +++ b/packages/software-factory/.agents/skills/software-factory-operations/SKILL.md @@ -19,41 +19,44 @@ directly. - **Target realm** (user-specified, passed to `factory:go`) Receives all generated artifacts: Project, Issue, KnowledgeArticle, card definitions, card instances, Catalog Spec cards, and QUnit test files. -## Tool Surfaces +## Workspace files (local mirror of target realm) -Two surfaces are available, depending on which agent backend is running. -The system prompt makes the concrete mapping explicit; this skill describes -the operations. +The agent's working directory is the workspace — the local mirror of the +target realm that the orchestrator syncs back between iterations. Use the +**native** `Read`, `Write`, `Edit`, `Glob`, `Grep`, and `Bash` tools on +these files; the workspace `cwd` is set for you, so realm-relative paths +resolve directly. -### Workspace files (local mirror of target realm) +These files live in the workspace: -These files live in the workspace directory and are synced to the realm -by the orchestrator. Use the workspace fs surface for them — and only -for them: +- Card definitions: `*.gts` +- Card tests: `*.test.gts` +- Content card instances under `/.json` (the user data the + cards represent — e.g. `StickyNote/note-1.json`) +- Tracker-schema cards: `Projects/.json`, `Issues/.json`, + `Knowledge Articles/.json`, `Spec/.json` -- Card definitions: `*.gts` files -- Card tests: `*.test.gts` files -- Content card instances under `/.json` (the user data - the cards represent — e.g. `StickyNote/note-1.json`) +`Bash` is also available for `boxel` CLI commands: -Tooling: use the native `Read`, `Write`, `Edit`, `Glob`, `Grep`, and -`Bash` tools. Both agent backends (Claude Agent SDK and opencode) mount -the workspace as `cwd`, so realm-relative paths resolve directly. `Bash` -is available for safe shell helpers (`ls`, `find`, `cat`, read-only -`boxel` CLI commands like `boxel status` / `boxel history`). The -backend's path-scoping safety net rejects writes that resolve outside -the workspace. +- Read-only inspection: `boxel status`, `boxel history`, `boxel search`, + `boxel read-transpiled`. +- `boxel run-command` — dispatches to whatever host command you specify. + Most specifiers are read-only inspection commands (`get-card-type-schema`, + `evaluate-module`, `instantiate-card`), but the surface itself is generic; + treat it as "as safe as the named command." -Inspect before writing. Read or grep the file you plan to change, and +See the **Realm-side reads** section below for the full usage. + +**Inspect before writing.** Read or grep the file you plan to change, and glob for sibling files (e.g. existing card definitions in the same directory) before creating new ones. -### Tracker-schema cards — write JSON directly +## Tracker-schema cards — write JSON directly -Project, Issue, KnowledgeArticle, and Spec cards are plain `.json` files -in the workspace. Use `Write` to create them and `Read` + `Edit` (or -`Read` + `Write` of the merged document) to update them — same workspace -fs surface as `.gts` files. +Project, Issue, KnowledgeArticle, and Spec cards are plain `.json` files in +the workspace. Use `Write` to create them; to update one, `Read` it, then +either `Edit` the relevant attributes or `Write` the merged document +back — same as `.gts` files. | File path | adoptsFrom | | -------------------------------- | -------------------------------------------------------------- | @@ -65,26 +68,27 @@ fs surface as `.gts` files. `` is named in the system prompt — use that value verbatim. -**Always fetch the live schema before writing.** Field names, enum -values, and relationship keys for each card type are introspected at -runtime — never hard-coded in this skill. Call +**Always fetch the live schema before writing.** Field names, enum values, +and relationship keys for each card type are introspected at runtime — +never hard-coded in this skill. Call `get_card_schema({ module, name })` for the card you're about to write -and use the returned `{ attributes, relationships? }` JSON Schema to -shape the document. The bootstrap skill covers the bootstrap-specific -attribute population guidance; this skill covers the operational -patterns (read-before-write, comments, invariants) that layer on top. +and use the returned `{ attributes, relationships? }` JSON Schema to shape +the document. The bootstrap skill covers the bootstrap-specific attribute +population guidance; this skill covers the operational patterns +(read-before-write, comments, invariants) that layer on top. **Read before write.** When updating any tracker card, `Read` the file first, change only the attributes you intend to update, then write the -merged document back. Don't overwrite the whole file with only your -new fields — you'll silently drop the existing attributes. +merged document back. Don't overwrite the whole file with only your new +fields — you'll silently drop the existing attributes. -**Issue invariants you must enforce yourself:** +**Issue invariants you must enforce yourself** (these used to be enforced +by a wrapper tool; they aren't anymore): -- **`description` is immutable** after the issue is created. If you - need to add context — blocked reasons, progress notes, validation - failures, clarification requests — append to the `comments` array - instead. See "Adding a comment to an existing issue" below. +- **`description` is immutable** after the issue is created. If you need + to add context — blocked reasons, progress notes, validation failures, + clarification requests — append to the `comments` array instead. See + "Adding a comment to an existing issue" below. - **Status transitions are restricted.** You may set `status` to `"blocked"` (cannot proceed) or `"backlog"` (unblock). Never set `status` to `"done"` or `"in_progress"` — those are owned by the @@ -95,11 +99,20 @@ new fields — you'll silently drop the existing attributes. Issue cards carry a containsMany comments array on `attributes`. To append a comment: -1. Call `get_card_schema({ module: "", name: "Issue" })` if you don't already have the Issue schema cached. The comments array entry is itself an object with its own field shape — use the field names returned by the schema (the body / author / timestamp fields) verbatim. The timestamp field on a comment is **not** the same as the Issue's own top-level `createdAt` / `updatedAt` attributes; the schema disambiguates them. +1. Call `get_card_schema({ module: "", name: "Issue" })` + if you don't already have the Issue schema cached. The comments array + entry is itself an object with its own field shape — use the field + names returned by the schema (the body / author / timestamp fields) + verbatim. The timestamp field on a comment is **not** the same as the + Issue's own top-level `createdAt` / `updatedAt` attributes; the schema + disambiguates them. 2. `Read` the issue's `.json`. -3. Append a new entry to the comments array on `data.attributes`, populating the body (markdown comment text), the author (e.g. `"factory-agent"` or `"orchestrator"`), and the comment-timestamp field (ISO timestamp). -4. `Write` (or `Edit`) the document back. **Do not modify the - description or any other attribute** — comments are append-only. +3. Append a new entry to the comments array on `data.attributes`, + populating the body (markdown comment text), the author (e.g. + `"factory-agent"` or `"orchestrator"`), and the comment-timestamp + field (ISO timestamp). +4. `Write` (or `Edit`) the document back. **Do not modify the description + or any other attribute** — comments are append-only. ### Catalog Spec card shape @@ -111,9 +124,9 @@ module. Fetch the live schema before writing: get_card_schema({ module: "https://cardstack.com/base/spec", name: "Spec" }) ``` -Use the returned `{ attributes, relationships? }` to shape the -document. What the schema does **not** tell you and you must supply -yourself for entry-point cards: +Use the returned `{ attributes, relationships? }` to shape the document. +What the schema does **not** tell you and you must supply yourself for +entry-point cards: - A display title and short description suitable for the catalog. - The spec-type field set to the enum value the schema returns for @@ -139,59 +152,172 @@ The full document envelope is the same as for tracker cards (`data` / `type: "card"` / `attributes` / `relationships` / `meta.adoptsFrom`), just with the `https://cardstack.com/base/spec` adoptsFrom. -### Realm-side reads (Bash + boxel CLI) +## Realm-side reads (via `boxel` CLI) -For realm-runtime reads — fetching transpiled output, searching cards -by query — shell out via `Bash` to the boxel CLI. Both helpers run -read-only against the realm and don't modify workspace state. +For operations that need to reach the realm runtime — searching the +indexed cards, fetching transpiled JS, running host commands — shell out +via `Bash` to the `boxel` CLI. These never go through the workspace fs. -- Fetch the **transpiled** JavaScript for a `.gts` module — used only when an eval/instantiate error reports a line/column number, since those numbers reference the transpiled output, not your `.gts` source. - Run `boxel read-transpiled --realm ` via `Bash`. The `.gts` extension is optional. Pipe through `sed -n 'p'` (or wrap with `awk`) when you want to inspect a single line. -- Search the target realm for cards using a structured query object (filter, sort, page). Use this to check for existing cards, find duplicates, or inspect project state. - Run `boxel search --realm --query '' --json` via `Bash`. **Quoting:** single-quote the entire JSON object so the shell does not expand or split it; keep all keys and string values double-quoted inside. Example: `boxel search --realm https://realms.example/h/p/ --query '{"filter":{"type":{"module":"https://cardstack.com/base/spec","name":"Spec"}}}' --json`. Pipe through `jq` if you want a focused projection. +- **Search the target realm** for cards using a structured query + (filter, sort, page). Use this to check for existing cards, find + duplicates, or inspect project state. + ``` + boxel search --realm --query '' --json + ``` + Single-quote the entire JSON object so the shell does not expand or + split it; keep keys and string values double-quoted inside. Pipe + through `jq` to project. **For the full query syntax (filter / eq / + contains / range / every / any / not / sort / page, CodeRef matching, + common mistakes) see the `boxel-api` skill.** +- **Fetch the transpiled JavaScript** for a `.gts` module — used only + when an eval/instantiate error reports a line/column number, since + those numbers reference the transpiled output, not your `.gts` source. + ``` + boxel read-transpiled --realm + ``` + The `.gts` extension is optional. Pipe through `sed -n 'p'` (or + wrap with `awk`) to inspect a single line. See the **Debugging + Runtime Evaluation Errors** section below for when to reach for this. +- **Run any other host command** in the realm's prerendered runtime + (module evaluation, card instantiation, anything else exposed at + `@cardstack/boxel-host/commands//default`): + ``` + boxel run-command --realm --input '' --json + ``` + Most agent tasks won't need this — the validators below already wrap + the common host commands. See the `boxel-command` skill for the + programmatic surface and failure modes. ### Fetching live card-type schemas `get_card_schema({ module, name })` returns the live JSON Schema (`{ attributes, relationships? }`) for any `CardDef`, introspected from the actual class via the realm server's prerenderer (the same path the -AI Bot uses for its patch-tool schemas). Always call this before -writing a tracker card (Project / Issue / KnowledgeArticle), a Spec -card, or any other card whose shape you need to know. Schemas are -cached per-process, so repeated calls with the same code ref are free. - -### Running other Host Commands - -For host commands beyond the schema fetch, shell out via `Bash` to -`boxel run-command --realm --input '' --json` -(same single-quoting rules as the search example above). - -### Self-Validation (optional) - -All five tools are safe to call repeatedly mid-turn; none of them write a realm artifact. The orchestrator still runs the full validation pipeline (which persists the durable `TestRun` / `LintResult` / `ParseResult` / `EvalResult` / `InstantiateResult` cards) after `signal_done`, so calling any of these is optional. The realm-touching tools (`run_evaluate`, `run_instantiate`, `run_tests`) push your workspace to the realm before invoking the prerenderer, so they always see the writes you've just made — no manual sync needed. - -- `run_lint({ path? })` — Run ESLint + Prettier (with `@cardstack/boxel` rules) and return an in-memory `RunLintResult` with `status`, `filesChecked`, `filesWithErrors`, `errorCount`, `warningCount`, `durationMs`, `lintableFiles`, and per-violation `{ rule, file, line, column, message, severity }`. Without `path`, lints every `.gts` / `.gjs` / `.ts` / `.js` file in the target realm. With `path` (realm-relative file path), lints **only that one file** — prefer this right after writing or editing a single file. -- `run_tests()` — Run the realm's QUnit suite and receive an in-memory result object `{ status, passedCount, failedCount, skippedCount, durationMs, testFiles, failures, errorMessage? }`. Use it when you want feedback before signalling done. -- `run_parse({ path? })` — Parse and type-check files in the target realm and return an in-memory `RunParseResult` with `status`, `filesChecked`, `filesWithErrors`, `errorCount`, `durationMs`, `parseableFiles`, and per-error `{ file, line, column, message }`. Without `path`, runs glint (ember-tsc) over every `.gts` / `.gjs` / `.ts` file in the realm AND validates every `.json` file listed as a Spec `linkedExample` (same discovery as the parse validation step). With `path` (realm-relative file path), parses **only that one file** — `.gts` / `.gjs` / `.ts` runs through glint; `.json` is parsed and checked for card document structure. The extension is required; `parseableFiles` entries are always returned in the `.json` / `.gts` / `.gjs` / `.ts` form, so you can feed any of them straight back into `path`. Prefer the single-file form right after writing or editing one file. -- `run_evaluate({ path? })` — Evaluate ESM modules (`.gts` / `.gjs` / `.ts` / `.js`) in the target realm via the prerenderer sandbox and return a `RunEvaluateResult` (status, module counts, per-failure `{ path, error, stackTrace? }`). Without `path`, evaluates every non-test evaluable module. With `path`, evaluates only that single realm-relative file — handy for a quick self-check right after writing one module. Test files (`*.test.*`) are rejected — the test runner validates those. The tool bound-polls past the brief read-after-write window where the realm has the source on disk but indexing hasn't populated the module map yet, so a returned failure is a real failure — don't retry on the agent side. When a failure reports a line/column, those numbers refer to the transpiled module — pair with the transpiled-module fetch above (Bash + `boxel read-transpiled`) to locate the offending source construct, then fix the `.gts` source (never copy transpiled patterns back into source). -- `run_instantiate({ path? })` — Instantiate card example instances in the target realm via the prerenderer sandbox and return a `RunInstantiateResult` (status, instance counts, per-failure `{ path, cardName, error, stackTrace? }`). Without `path`, searches the realm for Spec cards and instantiates every `linkedExample` on every card/app Spec; specs with no `linkedExamples` still get a bare instantiation to exercise the card class. With `path`, instantiates only that single realm-relative `.json` example file — its `meta.adoptsFrom` supplies the module + card name, and spec discovery is skipped entirely so you can self-check one instance in isolation. The `path` argument must end in `.json`. `instanceFiles` only contains real `.json` example paths (bare-instantiation fallbacks are filtered out) so any entry can be fed straight back into `path`. If a bare instantiation fails, its failure entry has `path: ''` and a populated `cardName` — identify the spec by `cardName` and do NOT pass the empty path back into `path`. The tool bound-polls past the brief read-after-write window where the realm has the source on disk but indexing hasn't populated the module map yet, so a returned failure is a real failure — don't retry on the agent side. When a failure reports a line/column, those numbers refer to the transpiled module — pair with the transpiled-module fetch above (Bash + `boxel read-transpiled`) to locate the offending source construct, then fix the `.gts` source (never copy transpiled patterns back into source). - -### Control Flow - -- `signal_done()` — Signal that the current issue is complete. Call this only after all implementation and test files have been written. -- `request_clarification({ message })` — Signal that you cannot proceed and need human input. Describe what is blocking. +AI Bot uses for its patch-tool schemas). Always call this before writing +a tracker card (Project / Issue / KnowledgeArticle), a Spec card, or any +other card whose shape you need to know. Schemas are cached per-process, +so repeated calls with the same code ref are free. + +## Self-Validation (optional, in-memory results) + +All five validators are factory tools, safe to call repeatedly mid-turn. +They return in-memory result objects and **do not persist any durable +validation cards** — the orchestrator still runs the full validation +pipeline (which persists `TestRun` / `LintResult` / `ParseResult` / +`EvalResult` / `InstantiateResult` cards) after `signal_done`, so calling +any of these mid-turn is optional. + +**Side effect to know about:** the realm-touching validators +(`run_evaluate`, `run_instantiate`, `run_tests`) sync your workspace to +the realm before invoking the prerenderer, so they push whatever you've +just written. That's the same write the orchestrator's between-iteration +sync would have done — it's not destructive, but it does mean calling +these tools is the moment your local writes hit the realm. The lighter +validators (`run_lint`, `run_parse`) run entirely in-process and don't +touch the realm. + +- `run_lint({ path? })` — Run ESLint + Prettier (with `@cardstack/boxel` + rules) and return an in-memory `RunLintResult` with `status`, + `filesChecked`, `filesWithErrors`, `errorCount`, `warningCount`, + `durationMs`, `lintableFiles`, and per-violation `{ rule, file, line, +column, message, severity }`. Without `path`, lints every `.gts` / + `.gjs` / `.ts` / `.js` file in the target realm. With `path` + (realm-relative file path), lints **only that one file** — prefer this + right after writing or editing a single file. +- `run_tests()` — Run the realm's QUnit suite and receive an in-memory + result object `{ status, passedCount, failedCount, skippedCount, +durationMs, testFiles, failures, errorMessage? }`. Use it when you + want feedback before signalling done. +- `run_parse({ path? })` — Parse and type-check files in the target + realm and return an in-memory `RunParseResult` with `status`, + `filesChecked`, `filesWithErrors`, `errorCount`, `durationMs`, + `parseableFiles`, and per-error `{ file, line, column, message }`. + Without `path`, runs glint (ember-tsc) over every `.gts` / `.gjs` / + `.ts` file in the realm AND validates every `.json` file listed as a + Spec `linkedExample` (same discovery as the parse validation step). + With `path` (realm-relative file path), parses **only that one file** + — `.gts` / `.gjs` / `.ts` runs through glint; `.json` is parsed and + checked for card document structure. The extension is required; + `parseableFiles` entries are always returned in the `.json` / `.gts` + / `.gjs` / `.ts` form, so you can feed any of them straight back into + `path`. Prefer the single-file form right after writing or editing one + file. +- `run_evaluate({ path? })` — Evaluate ESM modules (`.gts` / `.gjs` / + `.ts` / `.js`) in the target realm via the prerenderer sandbox and + return a `RunEvaluateResult` (status, module counts, per-failure + `{ path, error, stackTrace? }`). Without `path`, evaluates every + non-test evaluable module. With `path`, evaluates only that single + realm-relative file — handy for a quick self-check right after writing + one module. Test files (`*.test.*`) are rejected — the test runner + validates those. The tool bound-polls past the brief read-after-write + window where the realm has the source on disk but indexing hasn't + populated the module map yet, so a returned failure is a real failure + — don't retry on the agent side. When a failure reports a line/column, + those numbers refer to the transpiled module — pair with + `boxel read-transpiled` (see Realm-side reads above) to locate the + offending source construct, then fix the `.gts` source (never copy + transpiled patterns back into source). +- `run_instantiate({ path? })` — Instantiate card example instances in + the target realm via the prerenderer sandbox and return a + `RunInstantiateResult` (status, instance counts, per-failure `{ path, +cardName, error, stackTrace? }`). Without `path`, searches the realm + for Spec cards and instantiates every `linkedExample` on every + card/app Spec; specs with no `linkedExamples` still get a bare + instantiation to exercise the card class. With `path`, instantiates + only that single realm-relative `.json` example file — its + `meta.adoptsFrom` supplies the module + card name, and spec discovery + is skipped entirely so you can self-check one instance in isolation. + The `path` argument must end in `.json`. `instanceFiles` only contains + real `.json` example paths (bare-instantiation fallbacks are filtered + out) so any entry can be fed straight back into `path`. If a bare + instantiation fails, its failure entry has `path: ''` and a populated + `cardName` — identify the spec by `cardName` and do NOT pass the empty + path back into `path`. The tool bound-polls past the brief + read-after-write window where the realm has the source on disk but + indexing hasn't populated the module map yet, so a returned failure + is a real failure — don't retry on the agent side. When a failure + reports a line/column, those numbers refer to the transpiled module — + pair with `boxel read-transpiled` (see Realm-side reads above) to + locate the offending source construct, then fix the `.gts` source + (never copy transpiled patterns back into source). + +## Control Flow + +- `signal_done()` — Signal that the current issue is complete. Call this + only after all implementation and test files have been written. +- `request_clarification({ message })` — Signal that you cannot proceed + and need human input. Describe what is blocking. ## Required Flow -1. **Inspect before writing.** Search the target realm for existing cards (Bash + `boxel search` — see the Realm-side reads section above). Read or grep the workspace files you plan to change (or sibling files in the same directory) before creating or modifying anything. +1. **Inspect before writing.** Search the target realm for existing + cards (`boxel search --realm --query ''` via `Bash` — + see Realm-side reads above, with full syntax in the `boxel-api` + skill). Read or grep the workspace files you plan to change (or + sibling files in the same directory) before creating or modifying + anything. 2. **Write card definitions** (`.gts`) into the workspace. -3. **Write `.test.gts` test files** co-located with card definitions. Every issue must have at least one test file. **Write tests immediately after the card definition, before any instances or catalog specs.** +3. **Write `.test.gts` test files** co-located with card definitions. + Every issue must have at least one test file. **Write tests + immediately after the card definition, before any instances or + catalog specs.** 4. **Write card instances** (`.json`) into the workspace. -5. **Write a Catalog Spec card** (`Spec/.json`) — adoptsFrom `https://cardstack.com/base/spec` / `Spec`. Link sample instances via `relationships.linkedExamples`. -6. **(Optional) Call `run_tests()`** to self-validate before signalling done. This returns test results in-memory without writing any realm artifacts. Iterating on your own work with `run_tests` is faster than round-tripping through the orchestrator pipeline. -7. **Call `signal_done()`** when all implementation and test files are written. The orchestrator runs the full validation pipeline (which persists a `TestRun` card, among other artifacts) automatically after this. -8. **If tests fail**, the orchestrator feeds failure details back. Re-read the affected workspace files, fix them, and call `signal_done()` again. -9. **Record progress** by appending to the issue's `comments` array (Read + Edit the issue JSON). Never modify the issue's `description`. +5. **Write a Catalog Spec card** (`Spec/.json`) — adoptsFrom + `https://cardstack.com/base/spec` / `Spec`. Link sample instances via + `relationships.linkedExamples`. +6. **(Optional) Call `run_tests()`** to self-validate before signalling + done. This returns test results in-memory without writing any realm + artifacts. Iterating on your own work with `run_tests` is faster than + round-tripping through the orchestrator pipeline. +7. **Call `signal_done()`** when all implementation and test files are + written. The orchestrator runs the full validation pipeline (which + persists a `TestRun` card, among other artifacts) automatically after + this. +8. **If tests fail**, the orchestrator feeds failure details back. + Re-read the affected workspace files, fix them, and call + `signal_done()` again. +9. **Record progress** by appending to the issue's `comments` array + (Read + Edit the issue JSON). Never modify the issue's `description`. ## Target Realm Artifact Structure @@ -228,30 +354,34 @@ and read the reported line to see what compiled construct raised the error — then reason back to the `.gts` source construct that produced it. -Run `boxel read-transpiled sticky-note.gts --realm ` -via `Bash`. Pipe through `sed -n '60,70p'` (or similar) to focus on a -window around the reported line. +``` +boxel read-transpiled sticky-note.gts --realm +``` + +Pipe through `sed -n '60,70p'` (or similar) to focus on a window around +the reported line. -For example, `" is not a valid character within attribute names: (error occurred in '/.../sticky-note.gts' @ line 66 : column 32)` -typically points inside a `precompileTemplate(...)` block in the -transpiled output. The actual fault in the source is often in a CSS -comment or a template expression — line 66 in your `.gts` source is -unrelated. Reading the transpiled line is what connects the error back -to the source. +For example, `" is not a valid character within attribute names: (error +occurred in '/.../sticky-note.gts' @ line 66 : column 32)` typically +points inside a `precompileTemplate(...)` block in the transpiled +output. The actual fault in the source is often in a CSS comment or a +template expression — line 66 in your `.gts` source is unrelated. +Reading the transpiled line is what connects the error back to the +source. ### The transpiled output is for DEBUGGING ONLY — never for implementation -**Scope:** the transpiled fetch (Bash + `boxel read-transpiled`) is -only for investigating **runtime errors in `.gts` modules you have -already written** — when an eval or instantiate validation failure points to -a line/column in the transpiled output and you need to map that +**Scope:** the transpiled fetch (`boxel read-transpiled`) is only for +investigating **runtime errors in `.gts` modules you have already +written** — when an eval or instantiate validation failure points to a +line/column in the transpiled output and you need to map that coordinate back to your source. It is not for learning how to write cards, not for understanding Boxel patterns, and not a general reference. -- **Do not copy patterns, imports, or shapes from the transpiled - output into your `.gts` source.** The transpiler emits artifacts - like `setComponentTemplate(...)`, `precompileTemplate(...)`, wire-format +- **Do not copy patterns, imports, or shapes from the transpiled output + into your `.gts` source.** The transpiler emits artifacts like + `setComponentTemplate(...)`, `precompileTemplate(...)`, wire-format template arrays, base64 CSS imports (`./file.gts.CiAg...`), and other compiler internals. None of those belong in source code. - **Do not write `.gts` that "looks like" the compiled JS.** Always @@ -267,13 +397,15 @@ reference. the right references — not what the compiler happens to emit. Use the transpiled fetch the way a developer uses a source map: to -translate a runtime line number back to a source construct in the -code **you wrote**, then close the transpiled view and fix the source +translate a runtime line number back to a source construct in the code +**you wrote**, then close the transpiled view and fix the source idiomatically. ## Writing QUnit Card Tests -Test files are `.test.gts` files co-located with card definitions in the target realm. Each test file exports a `runTests()` function that registers QUnit modules and tests. +Test files are `.test.gts` files co-located with card definitions in the +target realm. Each test file exports a `runTests()` function that +registers QUnit modules and tests. ### Example Test @@ -303,20 +435,43 @@ export function runTests() { ### Key Points -- Tests are `.test.gts` files co-located with the card definition (e.g., `sticky-note.gts` and `sticky-note.test.gts`) +- Tests are `.test.gts` files co-located with the card definition (e.g., + `sticky-note.gts` and `sticky-note.test.gts`) - Each test file must export a `runTests()` function -- Use `import.meta.url` to resolve card definitions relative to the test file — never hardcode realm URLs -- Use `setupCardTest(hooks)` for rendering context, then `renderCard(loader, card, format)` for DOM assertions -- No external realm writes during tests — all test data lives in browser memory -- Use `data-test-*` attributes for DOM selectors when testing rendered output -- Use QUnit assertions: `assert.dom()`, `assert.strictEqual()`, `assert.ok()` -- **Never use `QUnit.skip()` or `QUnit.todo()`.** All tests must actually execute. Skipped/todo tests are flagged as `skipped` in the TestRun card and treated as a failure when no tests actually ran. The orchestrator will reject a TestRun where every test is skipped. +- Use `import.meta.url` to resolve card definitions relative to the test + file — never hardcode realm URLs +- Use `setupCardTest(hooks)` for rendering context, then + `renderCard(loader, card, format)` for DOM assertions +- No external realm writes during tests — all test data lives in browser + memory +- Use `data-test-*` attributes for DOM selectors when testing rendered + output +- Use QUnit assertions: `assert.dom()`, `assert.strictEqual()`, + `assert.ok()` +- **Never use `QUnit.skip()` or `QUnit.todo()`.** All tests must + actually execute. Skipped/todo tests are flagged as `skipped` in the + TestRun card and treated as a failure when no tests actually ran. The + orchestrator will reject a TestRun where every test is skipped. ## Important Rules -- **Never write to the source realm.** All generated artifacts go to the target realm via the workspace mirror. -- **Stay inside the workspace.** Workspace fs operations are scoped to the local mirror of the target realm. Use realm-relative paths (`sticky-note.gts`, `StickyNote/note-1.json`) — never absolute paths outside the workspace, never the user's home directory, never the source realm. -- **Don't drive sync yourself.** The orchestrator owns `boxel sync` / `boxel push`. Read-only `boxel` commands (`boxel status`, `boxel history`) are fine for inspection, but never run sync, push, or any command that mutates the realm directly. -- **Write source code, not compiled output.** When writing `.gts` files, write clean idiomatic source — never compiled JSON blocks or base64-encoded content. -- **Use absolute `adoptsFrom.module` URLs** when referencing definitions that live in a different realm (e.g., the source realm's tracker schema). -- **Start small and iterate.** Write the smallest working implementation first, then add the test. If tests fail, read the failure output carefully before making targeted fixes. +- **Never write to the source realm.** All generated artifacts go to the + target realm via the workspace mirror. +- **Stay inside the workspace.** Workspace fs operations are scoped to + the local mirror of the target realm. Use realm-relative paths + (`sticky-note.gts`, `StickyNote/note-1.json`) — never absolute paths + outside the workspace, never the user's home directory, never the + source realm. +- **Don't drive sync yourself.** The orchestrator owns `boxel sync` / + `boxel push`. Read-only `boxel` commands (`boxel status`, + `boxel history`) are fine for inspection, but never run sync, push, + or any command that mutates the realm directly. +- **Write source code, not compiled output.** When writing `.gts` files, + write clean idiomatic source — never compiled JSON blocks or base64- + encoded content. +- **Use absolute `adoptsFrom.module` URLs** when referencing definitions + that live in a different realm (e.g., the source realm's tracker + schema). +- **Start small and iterate.** Write the smallest working implementation + first, then add the test. If tests fail, read the failure output + carefully before making targeted fixes. diff --git a/packages/software-factory/.claude/CLAUDE.md b/packages/software-factory/.claude/CLAUDE.md index f5aeded35d7..8ed19b34aa3 100644 --- a/packages/software-factory/.claude/CLAUDE.md +++ b/packages/software-factory/.claude/CLAUDE.md @@ -1,783 +1,55 @@ -# Boxel CLI - Claude Code Integration +# CLAUDE.md — software-factory -## GitHub Repository +This package implements the issue-driven factory loop. See +[README.md](../README.md) for architecture and +[AGENTS.md](../AGENTS.md) for the agent-facing summary. -**Official repo:** https://github.com/cardstack/boxel-cli +## Running the factory ---- - -## How to Run Boxel Commands - -After `npm install && npm run build`, use `npx boxel`: - -```bash -npx boxel sync . -npx boxel history ./workspace -npx boxel profile add -``` - -Or use `boxel` directly after `npm link`. - -**For development** (no rebuild needed after code changes): - -```bash -npm run dev -- -``` - -All documentation below shows `boxel ` for brevity. - ---- - -## Auto-Activate Boxel Development Skill - -**IMPORTANT:** When the user is doing ANY of the following, automatically read and follow `.claude/skills/boxel-development/SKILL.md`: - -- Creating or editing `.gts` files (card definitions) -- Creating or editing `.json` card instances -- Asking about Boxel patterns, cards, or components -- "Vibe coding" or prototyping Boxel cards -- Working in a synced Boxel workspace (has `.boxel-sync.json`) -- Asking to create, build, or design anything in Boxel - -**How to activate:** Read the skill file at the start of the task: - -``` -Read .claude/skills/boxel-development/SKILL.md -``` - -The skill contains comprehensive Boxel development guidance including CardDef/FieldDef patterns, templates, styling, and best practices. - ---- - -**When a user opens this repo, check if they need onboarding first!** - -## Onboarding Flow - -When you detect a new user (no profile configured), guide them through setup: - -### Step 1: Check Profile - -```bash -npx boxel profile -``` - -If no profile exists, run the interactive setup: - -### Step 2: Add a Profile - -```bash -npx boxel profile add -``` - -This launches an interactive wizard that: - -1. Asks for environment (Production or Staging) -2. Asks for username and password -3. Creates the profile in `~/.boxel-cli/profiles.json` - -**Non-interactive option (CI/automation only):** - -```bash -# Use environment variable to avoid exposing password in shell history -BOXEL_PASSWORD="password" npx boxel profile add -u @username:boxel.ai -n "My Prod Account" -``` - -> **Security Note:** Avoid passing passwords via `-p` flag as they appear in shell history and process listings. Use the interactive wizard or `BOXEL_PASSWORD` environment variable. - -### Step 3: Verify & List Workspaces - -```bash -npx boxel list -``` - -### Step 4: First Sync - -Help them sync their first workspace: - -```bash -npx boxel sync @username/workspace ./workspace-name -``` - -### Switching Between Profiles - -```bash -npx boxel profile list # See all profiles (★ = active) -npx boxel profile switch username # Switch by partial match +```sh +pnpm factory:go --brief-url --target-realm ``` ---- - -## Local Workspace Organization - -When syncing multiple workspaces locally, organize them by **domain/username/realm** to mirror the Matrix ID structure (`@username:domain`): - -``` -boxel-workspaces/ -├── boxel.ai/ # Production domain -│ └── acme-corp/ # Username -│ ├── personal/ # Realm -│ ├── project-atlas/ -│ └── inventory-tracker/ -└── stack.cards/ # Staging domain - └── acme-corp/ - └── sandbox/ -``` - -**Benefits:** - -- Clear separation between production and staging environments -- Matches the `@username:domain` profile ID format -- Easy to identify which profile/environment a workspace belongs to -- Supports multiple users on the same machine - -**First-time sync to this structure:** - -```bash -# Production workspace -boxel pull https://app.boxel.ai/acme-corp/project-atlas/ ./boxel-workspaces/boxel.ai/acme-corp/project-atlas - -# Staging workspace -boxel pull https://realms-staging.stack.cards/acme-corp/sandbox/ ./boxel-workspaces/stack.cards/acme-corp/sandbox -``` - ---- - -## Available Skills - -Shared repo-local skills live in `.agents/skills/`. -`.claude/skills/` should be a symlink to that directory so Claude and Codex read the same files. - -### `boxel-track` - Track Local Edits - -Use this skill when starting `boxel track` for local file watching and checkpoints: - -- Creates checkpoints as you save files in IDE -- Use `--push` flag to automatically push changes to server (batch upload) -- Without `--push`: Run `boxel sync . --prefer-local` to push to server - -### `boxel-watch` - Smart Watch - -Use this skill when starting `boxel watch` with context-aware timing: - -- **Active development** (5s interval, 3s debounce): When editing files -- **Monitoring** (30s interval, 10s debounce): Background observation -- **Quick feedback** (10s interval, 5s debounce): Testing changes - -### `boxel-restore` - Restore Checkpoint - -Use this skill for the full restore workflow: - -1. Shows history -2. Restores to checkpoint (properly deletes newer files) -3. Syncs deletions to server with `--prefer-local` -4. Optionally restarts watch - -### `boxel-sync` - Smart Sync - -Use this skill for context-aware bidirectional sync: - -- After local edits or track → `--prefer-local` -- After server changes → `--prefer-remote` -- After restore → `--prefer-local` (essential for syncing deletions) - -### `boxel-repair` - Realm Metadata/Card Repair - -Use when workspaces show missing icon/background, wrong display name, or fail to open due to broken `index.json`/`cards-grid.json` links. - -- Read `.claude/skills/boxel-repair/SKILL.md` for the step-by-step repair flow. -- `boxel repair-realm ` repairs one realm -- `boxel repair-realms` repairs all owned realms (excluding `personal` by default) -- Also reconciles Matrix account data (`app.boxel.realms`) unless disabled - -### `software-factory-operations` - End-to-End Delivery Loop - -Use this skill when the task is to break work into Boxel tickets, implement in an assigned realm, verify with Playwright, and keep knowledge plus progress checkpoints as durable factory memory. - ---- - -## Commands Reference - -### Status & Checking - -```bash -boxel status . # Check sync status -boxel status --all # Check all workspaces -boxel status . --pull # Auto-pull remote changes -boxel check ./file.json --sync # Check single file -``` - -### Pull, Push, Sync (Command Relationship) - -| Command | Direction | Purpose | Deletes Local | Deletes Remote | -| ------- | -------------- | -------------- | ---------------------- | --------------------- | -| `pull` | Remote → Local | Fresh download | with `--delete` | never | -| `push` | Local → Remote | Deploy changes | never | with `--delete` | -| `sync` | Both ways | Stay in sync | with `--prefer-remote` | with `--prefer-local` | - -```bash -boxel sync . # Interactive sync -boxel sync . --prefer-local # Keep local + sync deletions -boxel sync . --prefer-remote # Keep remote -boxel sync . --prefer-newest # Keep newest version -boxel sync . --delete # Sync deletions both ways -boxel sync . --dry-run # Preview only - -boxel push ./local # One-way push (local → remote) -boxel push ./local --delete # Push and remove orphaned remote files -boxel pull ./local # One-way pull (remote → local) -``` - -**Failed download cleanup:** When `sync` encounters files that return 500 errors (broken/corrupted on server), it will prompt you to delete them: - -``` -⚠️ 3 file(s) failed to download (server error): - - Staff/broken-card.json - - Student/corrupted.json - -These files may be broken on the server. Delete them from remote? [y/N] -``` - -> **Safety tip:** Before any destructive operation, create a checkpoint with a descriptive message: -> -> ```bash -> boxel history . -m "Before cleanup: removing broken server files" -> ``` - -### Track ⇆ (Local File Watching) - -```bash -boxel track . # Track local edits, auto-checkpoint as you save -boxel track . --push # Track AND push changes to server (batch upload) -boxel track . -d 5 -i 30 # 5s debounce, 30s min between checkpoints -boxel track . -q # Quiet mode -boxel track . -v # Verbose mode (debug output) -``` - -**Use track when:** Editing locally in IDE/VS Code. Creates checkpoints as you save files. -**Symbol:** ⇆ (horizontal arrows = local changes) -**With --push:** Real-time sync to server using batch upload via `/_atomic` endpoint. - -### Watch ⇅ (Remote Server Watching) - -```bash -boxel watch # Watch all configured realms (from .boxel-workspaces.json) -boxel watch . # Watch single workspace -boxel watch . ./other-realm # Watch multiple realms simultaneously -boxel watch . -i 5 -d 3 # Active: 5s interval, 3s debounce -boxel watch . -q # Quiet mode -``` - -**Use watch when:** Others are editing in Boxel web UI. Pulls their changes and creates checkpoints. -**Symbol:** ⇅ (vertical arrows = remote server changes) - -### Stop - -```bash -boxel stop # Stop all running watch (⇅) and track (⇆) processes -``` - -**Multi-realm watching:** Useful when code lives in one realm and data in another. Each realm gets its own checkpoint tracking and debouncing. - -### Realms (Multi-Realm Configuration) - -```bash -boxel realms # List configured realms -boxel realms --init # Create .boxel-workspaces.json -boxel realms --add ./path # Add a realm -boxel realms --add ./code --purpose "Card definitions" --patterns "*.gts" --default -boxel realms --add ./data --purpose "Data instances" --card-types "BlogPost,Product" -boxel realms --llm # Output LLM guidance for file placement -boxel realms --remove ./path # Remove a realm -``` - -**File placement guidance:** The `--llm` output tells Claude which realm to use for different file types and card types. - -### History & Restore - -```bash -boxel history . # View checkpoints -boxel history . -r # Interactive restore -boxel history . -r 3 # Quick restore to #3 -boxel history . -r abc123 # Restore by hash -boxel history . -m "Message" # Create checkpoint with custom message -``` - -### Skills - -```bash -boxel skills --refresh # Fetch skills from Boxel -boxel skills --list # List all available skills -boxel skills --enable "Name" # Enable a skill -boxel skills --disable "Name" # Disable a skill -boxel skills --export ./project # Export as Claude commands -``` - -### Profile (Authentication) - -```bash -boxel profile # Show current active profile -boxel profile list # List all saved profiles (★ = active) -boxel profile add # Interactive wizard to add profile (recommended) -# Non-interactive: use BOXEL_PASSWORD env var instead of -p flag for security -boxel profile switch # Switch profile (partial match OK) -boxel profile remove # Remove a profile -boxel profile migrate # Migrate from old .env file -``` - -**Profile IDs:** Use Matrix format `@username:domain` - -- Production: `@username:boxel.ai` -- Staging: `@username:stack.cards` - -**Storage:** Profiles stored in `~/.boxel-cli/profiles.json` (permissions: 0600) - -### Other - -```bash -boxel list # List workspaces -boxel create endpoint "Name" # Create workspace -boxel consolidate-workspaces . # Move legacy local dirs into domain/owner/realm -boxel repair-realm # Repair one realm metadata/starter cards -boxel repair-realms # Batch repair all owned realms -boxel pull ./local # One-way pull -boxel push ./local # One-way push -``` - -### Share & Gather (GitHub Workflow) - -```bash -boxel share . -t /path/to/repo -b branch-name --no-pr # Share to GitHub repo -boxel gather . -s /path/to/repo # Pull from GitHub repo -``` - -**Share** copies workspace state to a GitHub repo branch: - -- Preserves repo-level files (package.json, LICENSE, README, etc.) -- Skips realm-specific files (.realm.json, index.json, cards-grid.json) -- Creates branch and commits changes - -**Gather** pulls changes from GitHub back to workspace: - -- Symmetric to share -- Preserves workspace's realm-specific files - -**Pushing to GitHub:** Use GitHub Desktop to push branches (no CLI auth configured). -After share creates the branch locally, open GitHub Desktop and push. - -### `/boxel-development` - Default Vibe Coding Skill - -The **Boxel Development** skill is auto-enabled for vibe coding. It provides comprehensive guidance for: - -- Card definitions (.gts files) -- Card instances (.json files) -- Boxel patterns and best practices - -### `/boxel-file-structure` - File Organization Rules - -Reference for local file organization: - -- Directory naming: definitions (`kebab-case.gts`), instances (`PascalCase/`) -- Module paths: relative to JSON location (`../card` from subdirectory) -- JSON structure for card instances - -### `boxel skills` - Manage Additional Skills - -Fetch and manage AI instruction cards from Boxel: - -```bash -boxel skills --refresh # Fetch latest from Boxel -boxel skills --list # See available skills -boxel skills --enable "X" # Enable additional skills -boxel skills --export . # Re-export to .agents/skills/ (shared with .claude/skills/) -``` - ---- - -## Key Workflows - -### Local Development with Track (IDE/Agent Editing) - -```bash -boxel track . # Start tracking local edits (auto-checkpoints) -# ... edit files in IDE or with Claude ... -# Track creates LOCAL checkpoints as you save - -# IMPORTANT: When ready to push changes to Boxel server: -boxel sync . --prefer-local # Push your local changes to server -``` - -**Remember:** Track does NOT sync to server automatically - it only creates local checkpoints. Always run `sync --prefer-local` when you want your changes live on the server. - -### Real-Time Sync with Track --push - -```bash -boxel track . --push # Track AND auto-push to server -# ... edit files in IDE or with Claude ... -# Changes are checkpointed AND pushed to server automatically -``` - -**With --push:** Uses batch upload via `/_atomic` endpoint for efficient multi-file uploads. Definitions (.gts) are uploaded before instances (.json) to ensure proper indexing. - -### Active Development Session (Watching Server) - -```bash -boxel watch . -i 5 -d 3 # Active development settings -# ... edit in Boxel UI or locally ... -boxel sync . # Push/pull changes -``` - -### Undo Server Changes (Restore) - -```bash -boxel history . # Find checkpoint -boxel history . -r 3 # Restore to #3 -boxel sync . --prefer-local # ESSENTIAL: sync deletions to server -``` - -### Share Milestone to GitHub - -```bash -boxel share . -t /path/to/boxel-home -b boxel/feature-name --no-pr -# Then push via GitHub Desktop -``` - -**URL Portability:** Share automatically converts absolute realm URLs in `index.json` and `cards-grid.json` to relative URLs, making the content portable across different realms. - -### Gather Updates from GitHub - -```bash -boxel gather . -s /path/to/boxel-home -boxel sync . --prefer-local # Push gathered changes to Boxel server -``` - -**URL Portability:** Gather includes `index.json` and `cards-grid.json`, transforming any absolute URLs to relative paths for portability. - -Or simply: - -``` -consult boxel-restore and restore checkpoint 3 -``` - -### Monitor Server While Working - -```bash -boxel watch . -i 30 -d 10 # Monitoring settings -# Checkpoints created automatically -boxel history . # View what changed -``` - -### Multi-Realm Development - -When working with multiple realms (e.g., code + data separation): - -```bash -# Configure realms once -boxel realms --add ./code-realm --purpose "Card definitions" --patterns "*.gts" --default -boxel realms --add ./data-realm --purpose "Content instances" --card-types "BlogPost,Product" - -# Watch all configured realms -boxel watch - -# Check where to put a new file -boxel realms --llm -``` - -**File placement heuristics:** - -- `.gts` files → realm with `*.gts` pattern (usually code realm) -- Card instances → realm configured for that card type -- Ambiguous → use the default realm - ---- - -## Critical Patterns - -### ⚠️ SAFETY FIRST: Checkpoint Before Destructive Operations - -**Always create a checkpoint with a descriptive message before:** - -- Deleting files from server (`--prefer-local`, `push --delete`) -- Restoring to an earlier checkpoint -- Bulk cleanup operations -- Removing card definitions or instances - -```bash -boxel history . -m "Before cleanup: removing sample data and unused definitions" -# Now safe to proceed with destructive operation -boxel sync . --prefer-local -``` - -This ensures you can always recover if something goes wrong. The checkpoint message helps identify what state to restore to. - -### 0. ALWAYS Write Source Code, Never Compiled Output - -When editing `.gts` files, **always write clean idiomatic source code**: - -```gts -// CORRECT - Clean source -export class MyCard extends CardDef { - static fitted = class Fitted extends Component { - - }; -} -``` - -**NEVER** write or edit: - -- Compiled JSON blocks (`"block": "[[[10,0]..."`) -- Base64-encoded CSS imports (`./file.gts.CiAg...`) -- Wire format template arrays - -The server compiles source to these formats. If you see them, the file was pulled from server - rewrite it as clean source. - -### 0.5. Edit Lock Before Modifying Files - -When editing files locally while watch is running, use edit lock to prevent watch from overwriting your changes: - -```bash -boxel edit . grammy-gallery.gts # Lock file before editing -# ... make your edits ... -boxel sync . --prefer-local # Push your changes -boxel touch . Instance/file.json # Force re-index -boxel edit . --done grammy-gallery.gts # Release lock -``` - -**Quick commands:** - -```bash -boxel edit . --list # See what's locked -boxel edit . --clear # Clear all locks -boxel edit . --done # Release all locks -``` - -**Why:** Watch mode pulls remote changes which can overwrite local edits. Edit lock tells watch to skip those files. - -### 0.5. Touch Instance After Remote .gts Update - -When you update a `.gts` card definition file remotely (via sync/push), touch an instance file to force re-indexing: - -```bash -boxel touch . CardName/instance.json # Touch specific instance -boxel touch . # Or touch all files -``` - -**Why:** The realm server may not re-index the definition until an instance using it is touched. - -### 1. Stop Watch Before Restore - -Watch will re-pull deleted files if running during restore: - -```bash -# Stop watch first (Ctrl+C or kill process) -boxel history . -r 3 -boxel sync . --prefer-local -``` - -### 2. Always Use --prefer-local After Restore - -This syncs local deletions to the server: - -```bash -boxel history . -r 3 # Deletes files locally -boxel sync . --prefer-local # Deletes files on server -``` - -### 3. Debouncing Groups Rapid Changes - -Watch waits for changes to settle: - -- Change detected → timer starts -- More changes → timer resets -- Timer expires → single checkpoint with all changes - -### 4. Checkpoint Classification - -- `[MAJOR]` - New files, deleted files, .gts changes, >3 files -- `[minor]` - Small updates to existing .json files -- `LOCAL` ⇆ - Changes from local edits (track command) -- `SERVER` ⇅ - External changes from web UI (watch command) - ---- - -## File Structure - -``` -workspace/ -├── .boxel-sync.json # Sync manifest (hashes, mtimes) -├── .boxel-history/ # Git-based checkpoint history -├── .realm.json # Workspace config -├── index.json # Workspace index -├── *.gts # Card definitions -└── CardName/ - └── *.json # Card instances -``` - ---- - -## Workspace References - -Commands accept: - -- `.` - Current directory (needs `.boxel-sync.json`) -- `./path` - Local path -- `@user/workspace` - e.g., `@username/personal` -- `https://...` - Full URL - ---- - -## Understanding Boxel URLs (Card IDs) - -When a user shares a URL like: - -``` -https://app.boxel.ai/tribecaprep/employee-handbook/Document/d8341312-f3a0-442b-a2e5-49c5cdd84695 -``` - -**This is a Card ID, not a fetchable URL!** - -### How to Parse Boxel URLs - -| URL Part | Meaning | -| ----------------------- | --------------------------- | -| `app.boxel.ai` | Production server | -| `tribecaprep` | User/organization | -| `employee-handbook` | Realm/workspace name | -| `Document/d8341312-...` | Card type and instance path | - -### NEVER Use WebFetch on Boxel URLs - -- Boxel realms are **usually private** and require Matrix authentication -- WebFetch will fail with 401/403 errors -- The user is referencing content **they expect you to have locally** - -### Finding the Local Copy - -If the user references a Boxel URL, the file is likely already synced to the local workspace: - -1. **Parse the path**: `Document/d8341312-f3a0-442b-a2e5-49c5cdd84695` → local path is `Document/d8341312-f3a0-442b-a2e5-49c5cdd84695.json` - -2. **Search the workspace**: - -```bash -# Find by card ID -find . -name "d8341312-f3a0-442b-a2e5-49c5cdd84695*" - -# Or search for the card type folder -ls ./Document/ -``` - -3. **Read the local file** using the Read tool - -### Example Workflow - -User says: "Check the handbook at https://app.boxel.ai/tribecaprep/employee-handbook/Document/abc123" - -**Do this:** - -``` -# Look for local file -Read ./Document/abc123.json -``` - -**NOT this:** - -``` -# This will FAIL - private realm -WebFetch https://app.boxel.ai/tribecaprep/employee-handbook/Document/abc123 -``` - ---- - -## API Reference - -| Endpoint | Method | Purpose | -| ---------- | ------ | ----------------------- | -| `/_mtimes` | GET | File modification times | -| `/` | GET | Download file | -| `/` | POST | Upload file | -| `/` | DELETE | Delete file | -| `/_atomic` | POST | Batch atomic operations | - -Headers: - -- `Authorization`: JWT from Matrix auth -- `Accept`: `application/vnd.card+source` or `application/vnd.api+json` - -### Atomic Batch Operations - -The `/_atomic` endpoint supports batch file operations that succeed or fail atomically: - -```json -{ - "atomic:operations": [ - { "op": "add", "href": "./path/to/new.json", "data": { "data": {...} } }, - { "op": "update", "href": "./path/to/existing.gts", "data": { "data": { "type": "module", "attributes": { "content": "..." } } } }, - { "op": "remove", "href": "./path/to/delete.json" } - ] -} -``` - -| Operation | Behavior | -| --------- | ------------------------------------------- | -| `add` | Create new file (fails 409 if exists) | -| `update` | Update existing file (fails 404 if missing) | -| `remove` | Delete file | - -**Content-Type:** `application/vnd.api+json` - ---- - -## Conflict Resolution - -| Local | Remote | Action | -| --------- | --------- | ------------------------------- | -| Changed | Unchanged | Push | -| Unchanged | Changed | Pull | -| Changed | Changed | Conflict → use strategy | -| Deleted | Changed | `--prefer-local` deletes remote | -| Changed | Deleted | `--prefer-remote` deletes local | - ---- - -## Troubleshooting - -### "Authentication failed" - -- Check active profile: `boxel profile` -- Verify credentials: `boxel profile list` -- Verify you can log into Boxel web with same credentials -- For staging: ensure profile uses `@username:stack.cards` +- `--debug` — verbose logs. +- `--agent openrouter` — use the opencode-OpenRouter passthrough agent + (routes via the realm-server's `/_openrouter/chat/completions` proxy). -### "No workspace found" +## Running tests -- Run `boxel list` to see workspaces -- Use full URL for first sync -- Ensure correct profile is active for the environment +- `pnpm test:node` — QUnit node tests. +- `pnpm test:playwright` — Playwright e2e tests. +- `pnpm lint` — eslint + prettier + glint (`ember-tsc`). -### Files keep reverting after restore +## Skill loading -- Stop watch before restoring -- Use `boxel sync . --prefer-local` after +The agent's instructions live in `.agents/skills/`. The factory loader +(`src/factory-skill-loader.ts`) walks three directories: -### Watch not detecting changes +1. `packages/software-factory/.agents/skills/` — factory-specific skills + (`software-factory-bootstrap`, `software-factory-operations`). +2. `packages/boxel-cli/.agents/skills/` — Boxel API skills (`boxel-api`, + `boxel-command`). +3. monorepo root `.agents/skills/` — general domain skills + (`boxel-development`, `boxel-file-structure`, `ember-best-practices`). -- Check interval setting -- Verify server URL -- Check active profile: `boxel profile` +`packages/software-factory/.claude/skills` is a symlink to +`.agents/skills/` so Claude Code and the factory loader read the same +files. -### Switching environments (prod/staging) +## Architectural principle -- Add profiles for each environment -- Switch with: `boxel profile switch ` +`boxel-cli` owns the entire Boxel API surface. The factory imports +`BoxelCLIClient` from `@cardstack/boxel-cli/api`; it never calls +`fetch()` against a realm directly. Auth, token refresh, and retries +are internal to boxel-cli. -### "500 Internal Server Error" on specific files +## Key source files -- These files are broken/corrupted on the server -- Sync will prompt you to delete them after completion -- Or use `boxel push . --delete` to remove all orphaned remote files -- Check if card definitions have errors in Boxel web UI +- `src/factory-entrypoint.ts` — CLI entry; bootstraps target realm, + creates seed issue, runs the loop. +- `src/issue-loop.ts` — inner/outer issue scheduling. +- `src/workspace-fs.ts` — local mirror of the target realm. +- `src/factory-agent/opencode.ts` — agent backend. +- `src/factory-tool-builder.ts` — factory tool registry passed to the + agent (validators, `get_card_schema`, `signal_done`, + `request_clarification`). diff --git a/packages/software-factory/AGENTS.md b/packages/software-factory/AGENTS.md index 2474ca6cd28..dd2439c732a 100644 --- a/packages/software-factory/AGENTS.md +++ b/packages/software-factory/AGENTS.md @@ -1,224 +1,46 @@ -# AGENTS.md - Boxel CLI Codex Guidance - -## High-Priority Safety Rules - -1. Create a checkpoint before destructive operations: - - `boxel history . -m "Before destructive operation"` -2. After restore, always sync with local preference: - - `boxel history . -r ` - - `boxel sync . --prefer-local` -3. Stop watch before restore to avoid re-pulling deleted files. -4. When watch is running and you edit files locally, use edit locks: - - `boxel edit . ` before editing - - `boxel edit . --done ` after sync -5. Write clean source code, never compiled wire-format output for `.gts` files. - -## Mission - -This repo is part of a software factory where humans do not inspect or hand-edit code directly. - -The goal is to: - -- Accept a user request -- Break it down into persistent tasks -- Implement the work incrementally -- Test each step -- Commit or checkpoint progress continuously -- Store the plan, tickets, and knowledge base in Boxel so future runs become more reliable - -Code and project state should live in Boxel realms. Tickets and knowledge cards are the persistent memory for the factory. - -## Current Environment - -- Active Boxel CLI user: `factory` -- Do not put passwords directly into shell commands. If re-auth is needed, use `BOXEL_PASSWORD` or interactive login. -- Realm server: `http://localhost:4201/` -- Matrix server: `http://localhost:8008` -- `boxel list` currently shows access to: - - `http://localhost:4201/factory/guidance-tasks/` -- Check out Boxel workspaces into the local `realms/` subfolder under this repo -- Ignore `dark-factory`; it was an earlier iteration and should not be used as the current target -- Task tracker modules live in the `guidance-tasks` workspace under module `darkfactory` -- There are demo instances in that workspace that should be inspected before inventing new task structures -- Tickets may live in a dedicated task realm or in the realm created for a specific solution, but they should reuse the tracker module instead of duplicating it - -## Factory Execution Model - -When given a product request, the default operating loop is: - -1. Discover the relevant realms, modules, and existing task or knowledge cards -2. Choose or create the target implementation realm -3. Break the request into tickets, milestones, or task cards in Boxel -4. Add or update knowledge-base cards that capture decisions, constraints, and reusable procedures -5. Implement work one task at a time -6. Test after each meaningful change -7. Checkpoint with Boxel history and commit in git when a git repo exists -8. Sync changes back to Boxel and continue iterating until the request is demonstrably working - -Persistent Boxel artifacts are part of the deliverable, not just code. - -## Immediate Demo Priority - -There is one hour to produce an end-to-end demo of this workflow. - -Bias toward: - -- A small but complete project -- Visible task breakdown into tickets -- Clear knowledge-base entries -- Repeated task -> implement -> test -> checkpoint loops -- Fast feedback over architectural perfection - -## Auth and Testing Notes - -- Prefer Boxel CLI capabilities over rebuilding the same behavior from scratch -- Additional tooling is allowed when the CLI does not cover the task cleanly -- You will likely need auth helpers that can obtain JWTs for accessible realms -- The `_server-session` endpoint can provide JWTs for realms the current user can access -- Authenticated card URLs open directly into interact mode, which is the preferred surface for browser testing and Playwright-based verification -- Explore search and query options in the tracker and workspace data model before creating new structures -- Project tests should live in Boxel realms when they are part of the product's persistent memory -- Preferred convention: - - realm-local Playwright specs live under `tests/**/*.spec.ts` - - files copied into disposable verification realms live under `tests/fixtures/**` - - fixture contents are copied to the scratch realm root preserving paths, so `tests/fixtures/DeliveryBrief/example.json` becomes `DeliveryBrief/example.json` in the scratch realm -- Run realm-hosted tests with: - - `npm run test:realm -- --realm-path ./realms/` -- The default runner flow is: - 1. create a fresh scratch realm - 2. pull it locally under the canonical workspace path `realms////` - 3. copy fixture files from the source realm into the scratch realm - 4. sync the scratch realm - 5. run the source realm's Playwright specs against the scratch realm URL - 6. report failures and keep the scratch realm available for inspection -- When fixture instances depend on card definitions from the source realm, prefer absolute `meta.adoptsFrom.module` URLs so the scratch realm only needs the copied instances - -## Boxel Development Trigger - -When tasks involve Boxel card development, automatically consult: - -- `.agents/skills/boxel-development/SKILL.md` -- `.agents/skills/software-factory-operations/SKILL.md` when the task is about ticket-driven application delivery, realm coordination, or the end-to-end factory loop - -The shared repo-local skills live in `.agents/skills/`. -Claude should read them through `.claude/skills/`, which should point at the same directory to avoid duplicate instructions. - -Trigger examples: - -- Editing `.gts` card definitions -- Editing card instance `.json` -- Asking for Boxel card patterns/components -- Working in a synced workspace (`.boxel-sync.json` present) - -## Core Command Semantics - -- `pull`: remote -> local -- `push`: local -> remote -- `sync`: bidirectional conflict resolution -- `track`: local file watching with auto-checkpoints (use `--push` for real-time server sync) -- `watch`: remote change watching (pulls server changes) -- `repair-realm`: repair one realm metadata + starter cards + optional Matrix reconciliation -- `repair-realms`: batch repair all owned realms and reconcile Matrix realm list - -After local edits tracked with `track`, push to server with: - -- `boxel sync . --prefer-local` -- Or use `boxel track . --push` for automatic real-time sync - -## Onboarding Flow (When Needed) - -If user has no profile configured: - -1. `npx boxel profile` -2. `npx boxel profile add` (interactive preferred) -3. `npx boxel list` -4. First sync/pull into local workspace - -If `boxel list` already works, treat onboarding as complete and move on to workspace discovery. - -Security note: - -- Prefer interactive password entry or `BOXEL_PASSWORD` env var. -- Avoid plain `-p` password usage in shell history. - -## Multi-Realm Guidance - -- Configure realms with `boxel realms --add ...` -- Use `boxel realms --llm` for file-placement guidance. -- This repo already includes `.boxel-workspaces.json` mapping `guidance-tasks` as the shared tracker realm and `software-factory-demo` as the default implementation realm. -- Heuristic: - - `.gts` -> code realm (`*.gts` pattern) - - instances -> realm mapped for card type - - ambiguous -> default realm - -## Boxel URL Handling - -Boxel app URLs usually reference private, authenticated content. - -- Do not fetch them from the public web. -- Parse card path from URL and locate local synced file instead. - Example: -- URL segment `Document/` maps to local `Document/.json` - -## Useful Workflows - -### Local dev loop (manual sync) - -1. `boxel track .` -2. edit files -3. `boxel sync . --prefer-local` - -### Local dev loop (real-time sync) - -1. `boxel track . --push` -2. edit files (changes auto-pushed via batch upload) - -### Monitor server changes - -1. `boxel watch .` -2. inspect checkpoints with `boxel history .` - -### Restore workflow - -1. stop watch -2. `boxel history . -r ` -3. `boxel sync . --prefer-local` - -### Software Factory Loop - -1. `boxel list` -2. sync the relevant realm locally -3. inspect existing task and knowledge cards -4. create or update tickets for the requested outcome -5. implement in the target realm -6. store or update project tests inside the target realm when they are part of the deliverable -7. test using CLI plus authenticated browser flows where useful -8. prefer a disposable scratch realm for fixture-driven browser verification -9. report issues back into tickets or knowledge cards -10. sync to the realm -11. checkpoint and sync -12. update knowledge cards with what was learned - -## Related References - -- `.claude/CLAUDE.md` -- `.agents/skills/boxel-development/SKILL.md` -- `.agents/skills/boxel-file-structure/SKILL.md` -- `.agents/skills/boxel-repair/SKILL.md` -- `.agents/skills/boxel-sync/SKILL.md` -- `.agents/skills/boxel-watch/SKILL.md` -- `.agents/skills/boxel-track/SKILL.md` -- `.agents/skills/boxel-restore/SKILL.md` -- `.agents/skills/boxel-setup/SKILL.md` -- `.agents/skills/software-factory-operations/SKILL.md` - -## Share & Gather (GitHub Workflow) - -Share workspace to GitHub repo, gather changes back: - -```bash -boxel share . -t /path/to/repo -b branch-name --no-pr -boxel gather . -s /path/to/repo -``` - -**URL Portability:** Share/gather automatically convert absolute realm URLs in `index.json` and `cards-grid.json` to relative paths, making content portable across different realms. +# AGENTS.md — software-factory + +This package implements the issue-driven factory loop. The factory takes a +brief, creates a project + issues in a target realm, and the agent works +each issue using native fs tools (`Read` / `Write` / `Edit` / `Glob` / +`Grep` / `Bash`) plus a handful of factory tools (validators + control +flow). + +See [README.md](./README.md) for architecture. The agent's loaded +instructions live in `.agents/skills/` (root + this package + `boxel-cli`). + +## Commands + +- `pnpm factory:go --brief-url --target-realm ` — run the factory loop. + - `--debug` for verbose logs. + - `--agent openrouter` to use the opencode-OpenRouter passthrough agent. +- `pnpm test:node` — QUnit node tests. +- `pnpm test:playwright` — Playwright e2e tests. +- `pnpm lint` — eslint + prettier + glint (`ember-tsc`). + +## Key files + +- `src/factory-entrypoint.ts` — CLI entry; bootstraps the target realm, + creates the seed issue, runs the loop. +- `src/issue-loop.ts` — inner/outer issue scheduling loop. +- `src/factory-skill-loader.ts` — resolves and loads skills from + `packages/software-factory/.agents/skills/` (primary), + `packages/boxel-cli/.agents/skills/` (fallback), and monorepo root + `.agents/skills/` (fallback). +- `src/workspace-fs.ts` — local-filesystem mirror of the target realm; + the agent reads/writes here, the orchestrator syncs. +- `src/factory-agent/opencode.ts` — agent backend (opencode in passthrough + mode against the realm-server's `/_openrouter/chat/completions` proxy). + +## Architectural boundaries + +- **`boxel-cli` owns the entire Boxel API surface.** The factory imports + `BoxelCLIClient` from `@cardstack/boxel-cli/api`; it never calls + `fetch()` against a realm directly. Auth, token refresh, and retries + are internal to boxel-cli. +- **Target-realm I/O is local.** The agent operates on the workspace + mirror under `os.tmpdir()/boxel-factory-workspaces//`. The + orchestrator calls `client.sync()` between iterations. +- **Realm creation, pull, sync are orchestrator concerns.** The agent is + explicitly told not to drive sync — `factory-entrypoint.ts` and + `factory-issue-loop-wiring.ts` own those calls. diff --git a/packages/software-factory/scripts/smoke-tests/factory-skill-smoke.ts b/packages/software-factory/scripts/smoke-tests/factory-skill-smoke.ts index 1860de5153e..e6eb414956b 100644 --- a/packages/software-factory/scripts/smoke-tests/factory-skill-smoke.ts +++ b/packages/software-factory/scripts/smoke-tests/factory-skill-smoke.ts @@ -155,14 +155,10 @@ async function main(): Promise { let allSkillNames = [ 'boxel-development', 'boxel-file-structure', + 'boxel-api', + 'boxel-command', 'ember-best-practices', 'software-factory-operations', - 'boxel-sync', - 'boxel-track', - 'boxel-watch', - 'boxel-restore', - 'boxel-repair', - 'boxel-setup', ]; let allSkills = await loader.loadAll(allSkillNames); diff --git a/packages/software-factory/src/factory-skill-loader.ts b/packages/software-factory/src/factory-skill-loader.ts index aa989dc0883..d358f7e0f5b 100644 --- a/packages/software-factory/src/factory-skill-loader.ts +++ b/packages/software-factory/src/factory-skill-loader.ts @@ -13,10 +13,18 @@ const DEFAULT_SKILLS_DIR = join(PACKAGE_ROOT, '.agents', 'skills'); /** * Additional skill search directories, checked in order when a skill is not - * found in the primary directory. The monorepo root `.agents/skills/` hosts - * shared skills like `ember-best-practices` that live outside the package. + * found in the primary directory. + * + * - `packages/boxel-cli/.agents/skills/` hosts the CLI + API skills + * (`boxel-api`, `boxel-command`, `boxel-sync`, etc.) — boxel-cli owns the + * entire Boxel API surface, so its skills describe the platform. + * - The monorepo root `.agents/skills/` hosts shared domain skills + * (`boxel-development`, `boxel-file-structure`, `ember-best-practices`). */ -const DEFAULT_FALLBACK_DIRS = [join(MONOREPO_ROOT, '.agents', 'skills')]; +const DEFAULT_FALLBACK_DIRS = [ + join(MONOREPO_ROOT, 'packages', 'boxel-cli', '.agents', 'skills'), + join(MONOREPO_ROOT, '.agents', 'skills'), +]; /** Approximate characters per token for budget estimation. */ const CHARS_PER_TOKEN = 4; @@ -29,14 +37,10 @@ const SKILL_PRIORITY: readonly string[] = [ 'software-factory-bootstrap', 'boxel-development', 'boxel-file-structure', + 'boxel-api', + 'boxel-command', 'ember-best-practices', 'software-factory-operations', - 'boxel-sync', - 'boxel-track', - 'boxel-watch', - 'boxel-restore', - 'boxel-repair', - 'boxel-setup', ]; // --------------------------------------------------------------------------- @@ -63,20 +67,6 @@ const FACTORY_WORKFLOW_KEYWORDS = [ 'orchestrat', ]; -/** - * CLI skills that depend on boxel CLI commands. Excluded from the factory - * agent's tool registry — these skills reference commands the agent - * cannot invoke. They remain valid for human Claude Code sessions. - */ -const CLI_ONLY_SKILLS: readonly string[] = [ - 'boxel-sync', - 'boxel-track', - 'boxel-watch', - 'boxel-restore', - 'boxel-repair', - 'boxel-setup', -]; - /** * Reference files in `boxel-development/references/` and the keywords that * trigger their inclusion. When a ticket doesn't match any keyword, only the @@ -98,7 +88,6 @@ const REFERENCE_KEYWORD_MAP: Record = { 'dev-command-development.md': ['command', 'action', 'invoke'], 'dev-spec-usage.md': ['spec', 'catalog', 'specification'], 'dev-qunit-testing.md': ['test', 'qunit', 'test.gts', 'verify'], - 'dev-realm-search.md': ['search', 'query', 'filter', 'find', 'realm'], 'dev-replicate-ai.md': ['replicate', 'ai', 'model', 'ml'], }; @@ -107,7 +96,6 @@ const ALWAYS_LOAD_REFERENCES: readonly string[] = [ 'dev-core-concept.md', 'dev-technical-rules.md', 'dev-quick-reference.md', - 'dev-realm-search.md', 'dev-qunit-testing.md', 'dev-spec-usage.md', ]; @@ -143,9 +131,9 @@ export class DefaultSkillResolver implements SkillResolver { * 1. boxel-development + boxel-file-structure — always loaded * 2. ember-best-practices — when issue involves .gts component code * 3. software-factory-operations — for factory delivery workflow issues - * 4. KnowledgeArticle tags can specify additional skills - * - * CLI skills are excluded (see `CLI_ONLY_SKILLS`). + * 4. boxel-api + boxel-command — always loaded so the agent has the realm + * search query syntax and host-command failure modes inline. + * 5. KnowledgeArticle tags can specify additional skills. */ resolve(issue: IssueData, project: ProjectData): string[] { let issueText = extractIssueText(issue); @@ -156,7 +144,12 @@ export class DefaultSkillResolver implements SkillResolver { return ['software-factory-bootstrap', 'boxel-file-structure']; } - let skills: string[] = ['boxel-development', 'boxel-file-structure']; + let skills: string[] = [ + 'boxel-development', + 'boxel-file-structure', + 'boxel-api', + 'boxel-command', + ]; if (matchesAnyKeyword(issueText, GTS_KEYWORDS)) { skills.push('ember-best-practices'); @@ -175,9 +168,7 @@ export class DefaultSkillResolver implements SkillResolver { } } - // Filter out CLI-only skills that reference boxel CLI commands the - // factory agent cannot invoke (tool registry excludes boxel-cli tools). - return skills.filter((s) => !CLI_ONLY_SKILLS.includes(s)); + return skills; } } diff --git a/packages/software-factory/tests/factory-agent-claude-code.test.ts b/packages/software-factory/tests/factory-agent-claude-code.test.ts index c9ca3934ba6..c837b08373b 100644 --- a/packages/software-factory/tests/factory-agent-claude-code.test.ts +++ b/packages/software-factory/tests/factory-agent-claude-code.test.ts @@ -517,11 +517,15 @@ module('factory-agent-claude-code', function () { // into the Claude MCP catalog regardless of their plain name. makeTool({ name: 'realm-read', source: 'registered' }), makeTool({ name: 'search-realm', source: 'registered' }), - makeTool({ name: 'boxel-sync', source: 'registered' }), + makeTool({ name: 'sample-registered-tool', source: 'registered' }), ]); let allowed = capturedOptions!.allowedTools ?? []; - for (let registered of ['realm-read', 'search-realm', 'boxel-sync']) { + for (let registered of [ + 'realm-read', + 'search-realm', + 'sample-registered-tool', + ]) { assert.notOk( allowed.includes(`mcp__factory__${registered}`), `${registered} (registered) is not exposed on the Claude path`, diff --git a/packages/software-factory/tests/factory-skill-loader.test.ts b/packages/software-factory/tests/factory-skill-loader.test.ts index 15fe96dafa2..93351e8ce2a 100644 --- a/packages/software-factory/tests/factory-skill-loader.test.ts +++ b/packages/software-factory/tests/factory-skill-loader.test.ts @@ -106,6 +106,8 @@ module('factory-skill-loader > DefaultSkillResolver', function () { skills.includes('boxel-file-structure'), 'includes boxel-file-structure', ); + assert.true(skills.includes('boxel-api'), 'includes boxel-api'); + assert.true(skills.includes('boxel-command'), 'includes boxel-command'); }); test('includes ember-best-practices when issue mentions .gts', function (assert) { @@ -168,71 +170,30 @@ module('factory-skill-loader > DefaultSkillResolver', function () { ); }); - test('excludes CLI-only skills even when issue mentions sync', function (assert) { - let resolver = new DefaultSkillResolver(); - let issue = makeIssue({ - description: 'Sync the workspace after local edits', - }); - let project = makeProject(); - - let skills = resolver.resolve(issue, project); - - assert.false( - skills.includes('boxel-sync'), - 'boxel-sync excluded (CLI-only skill)', - ); - }); - - test('excludes CLI-only skills even when issue mentions restore', function (assert) { - let resolver = new DefaultSkillResolver(); - let issue = makeIssue({ - description: 'Restore workspace to a previous checkpoint', - }); - let project = makeProject(); - - let skills = resolver.resolve(issue, project); - - assert.false( - skills.includes('boxel-restore'), - 'boxel-restore excluded (CLI-only skill)', - ); - }); - - test('excludes all CLI-only skills even when issue mentions multiple CLI operations', function (assert) { - let resolver = new DefaultSkillResolver(); - let issue = makeIssue({ - description: 'Sync the workspace, track changes, and watch for updates', - }); - let project = makeProject(); - - let skills = resolver.resolve(issue, project); - - assert.false(skills.includes('boxel-sync'), 'boxel-sync excluded'); - assert.false(skills.includes('boxel-track'), 'boxel-track excluded'); - assert.false(skills.includes('boxel-watch'), 'boxel-watch excluded'); - }); - - test('excludes CLI-only skills even when added via knowledge article tags', function (assert) { + test('extra skills can be opted in via knowledge article tags', function (assert) { let resolver = new DefaultSkillResolver(); let issue = makeIssue(); let project = makeProject({ knowledge: [ { - id: 'Knowledge Articles/cli-ref', - tags: ['skill:boxel-sync', 'skill:boxel-repair'], + id: 'Knowledge Articles/extension-ref', + tags: ['skill:custom-extension', 'skill:another-domain-skill'], }, ], }); let skills = resolver.resolve(issue, project); - assert.false( - skills.includes('boxel-sync'), - 'boxel-sync excluded even from knowledge tags', + // Knowledge-tag opt-in is the deliberate way to include any non-default + // skill (e.g., a project-specific domain skill that lives in a knowledge + // article). The resolver returns the name; the loader resolves it on disk. + assert.true( + skills.includes('custom-extension'), + 'custom-extension included from knowledge tag', ); - assert.false( - skills.includes('boxel-repair'), - 'boxel-repair excluded even from knowledge tags', + assert.true( + skills.includes('another-domain-skill'), + 'another-domain-skill included from knowledge tag', ); }); @@ -855,7 +816,7 @@ module('factory-skill-loader > enforceSkillBudget', function () { let skills: ResolvedSkill[] = [ { name: 'boxel-development', content: 'a'.repeat(2000) }, // 500 tokens { name: 'boxel-file-structure', content: 'b'.repeat(2000) }, // 500 tokens - { name: 'boxel-sync', content: 'c'.repeat(2000) }, // 500 tokens + { name: 'low-priority-test-skill', content: 'c'.repeat(2000) }, // 500 tokens ]; // Budget of 1000 tokens — can fit first two but not third @@ -873,7 +834,7 @@ module('factory-skill-loader > enforceSkillBudget', function () { 'second priority kept', ); assert.true( - warnings.some((w) => w.includes('boxel-sync')), + warnings.some((w) => w.includes('low-priority-test-skill')), 'logged warning about dropped skill', ); } finally { @@ -888,9 +849,9 @@ module('factory-skill-loader > enforceSkillBudget', function () { try { let skills: ResolvedSkill[] = [ // Present in reverse priority order - { name: 'boxel-sync', content: 'c'.repeat(2000) }, // priority 4 - { name: 'boxel-file-structure', content: 'b'.repeat(2000) }, // priority 1 - { name: 'boxel-development', content: 'a'.repeat(2000) }, // priority 0 + { name: 'low-priority-test-skill', content: 'c'.repeat(2000) }, // not in SKILL_PRIORITY → lowest + { name: 'boxel-file-structure', content: 'b'.repeat(2000) }, // priority 2 + { name: 'boxel-development', content: 'a'.repeat(2000) }, // priority 1 ]; // Budget enough for only 2 skills