diff --git a/.brv/.gitignore b/.brv/.gitignore deleted file mode 100644 index 910e362a..00000000 --- a/.brv/.gitignore +++ /dev/null @@ -1,18 +0,0 @@ -# BEGIN pi-byterover -# Dream state and logs -dream-log/ -dream-state.json -dream.lock - -# Review backups -review-backups/ - -# Generated files -config.json -_queue_status.json -.snapshot.json -_manifest.json -_index.md -*.abstract.md -*.overview.md -# END pi-byterover diff --git a/.brv/context-tree/architecture/auth/context.md b/.brv/context-tree/architecture/auth/context.md deleted file mode 100644 index d18ff8ee..00000000 --- a/.brv/context-tree/architecture/auth/context.md +++ /dev/null @@ -1,13 +0,0 @@ -# Topic: auth - -## Overview - -Covers review findings and verification outcomes for the Task 8 remote authentication implementation, especially state ownership, callback safety, and flow cleanup. - -## Key Concepts - -- remote auth completion semantics -- flow lifecycle cleanup -- callback safety -- test coverage gaps -- typecheck verification diff --git a/.brv/context-tree/architecture/auth/remote-auth-correctness-depends-on-callback-complete-success-and-bounded-flow-cl.md b/.brv/context-tree/architecture/auth/remote-auth-correctness-depends-on-callback-complete-success-and-bounded-flow-cl.md deleted file mode 100644 index 18f4460f..00000000 --- a/.brv/context-tree/architecture/auth/remote-auth-correctness-depends-on-callback-complete-success-and-bounded-flow-cl.md +++ /dev/null @@ -1,22 +0,0 @@ ---- -confidence: 0.95 -sources: [architecture/_index.md, facts/_index.md] -synthesized_at: '2026-05-21T23:23:26.215Z' -type: synthesis -title: Remote auth correctness depends on callback-complete success and bounded flow cleanup -summary: Remote login should only succeed after OAuth callback completion, and pending auth flows need bounded lifecycle cleanup. -tags: [auth, remote-control, oauth, lifecycle] -related: [] -keywords: [oauth, callback, success, pending, flow, cleanup, map, abandoned] -createdAt: '2026-05-21T23:23:26.215Z' -updatedAt: '2026-05-21T23:23:26.215Z' ---- - -# Remote auth correctness depends on callback-complete success and bounded flow cleanup - -Architecture and facts both point to the same remote-auth risk profile: login success must be reported only after the OAuth callback completes, and pending auth flows need cleanup so abandoned flows do not remain in memory indefinitely. The review findings turn the ownership model into an implementation constraint with concrete failure modes and verification status. - -## Evidence - -- **architecture**: Remote auth review findings identify a high-severity issue where login reports success too early, immediately after `auth_login_start`, before OAuth callback completion, and a medium-severity issue where pending auth flows are stored in an unbounded Map and are only removed after `flow.complete(callbackUrl)` succeeds. -- **facts**: The curated project knowledge captures review outcomes and regression patterns for remote control, emphasizing server-side state handling, redacted errors, and verification-backed approvals after fixes. diff --git a/.brv/context-tree/architecture/auth/remote_auth_and_state_ownership.md b/.brv/context-tree/architecture/auth/remote_auth_and_state_ownership.md deleted file mode 100644 index 64e3db39..00000000 --- a/.brv/context-tree/architecture/auth/remote_auth_and_state_ownership.md +++ /dev/null @@ -1,58 +0,0 @@ ---- -title: Remote Auth and State Ownership -summary: Remote mode keeps all Caplets state and downstream auth credentials on the server; local clients only coordinate login/logout flows and must never receive secrets. -tags: [] -related: [architecture/remote_control/working_module.md, architecture/remote_control/unified_environment_variable_interface.md, architecture/remote_control/cli_remote_mode_selection.md, architecture/remote_control/remote_control_api_shape.md] -keywords: [] -createdAt: '2026-05-20T11:04:45.510Z' -updatedAt: '2026-05-20T11:04:45.510Z' ---- - -## Reason - -Document the decision that server owns auth creds and Caplets state in remote mode - -## Raw Concept - -**Task:** -Document remote auth and state ownership model for Caplets - -**Changes:** -- Server owns Caplets config, .caplets files, installed Caplets, backend definitions, OAuth/token auth store, and reload/watch lifecycle in remote mode -- Login/logout commands operate against the remote server auth store when invoked from a local client -- Remote responses must not expose access tokens or secrets - -**Flow:** -local client -> remote control command -> browser/device auth flow -> server stores final tokens -> subsequent auth/status operations read server-side state - -**Timestamp:** 2026-05-20T11:04:32.841Z - -## Narrative - -### Structure - -This design centers on server-owned state for remote mode, with local terminals acting as coordinators for auth flows rather than state holders. - -### Dependencies - -Applies to auth list/logout/login behavior, remote control API shape, and any future remote config-paths command. - -### Highlights - -A local caplets auth login linear command should complete against the remote server’s auth store, while `/control` responses remain secret-free. - -### Rules - -Remote control API responses must never include access tokens or secrets. Remote control auth (the Basic Auth used to access `/control`) is separate from downstream provider auth (GitHub/Linear/etc.). - -### Examples - -auth list shows server-side credential status; auth logout deletes server-side credentials; config paths defaults to local paths and can later support a remote variant. - -## Facts - -- **remote_state_ownership**: In remote mode, the server owns all Caplets state. [project] -- **auth_store_location**: OAuth and token auth store live on the server in remote mode. [project] -- **secret_exposure_policy**: Remote control API responses must never include access tokens or secrets. [project] -- **auth_boundary**: Remote control auth is separate from downstream provider auth. [project] -- **config_paths_default**: Local caplets config paths should show local paths by default. [project] diff --git a/.brv/context-tree/architecture/auth/remote_auth_callback_origin_handling.md b/.brv/context-tree/architecture/auth/remote_auth_callback_origin_handling.md deleted file mode 100644 index b75397d3..00000000 --- a/.brv/context-tree/architecture/auth/remote_auth_callback_origin_handling.md +++ /dev/null @@ -1,54 +0,0 @@ ---- -title: Remote Auth Callback Origin Handling -summary: Remote auth callback URLs now use CAPLETS_SERVER_URL public origin via publicOrigin, fixing http downgrade behind TLS termination. -tags: [] -related: [architecture/auth/remote_auth_and_state_ownership.md, architecture/auth/remote_auth_review_findings.md] -keywords: [] -createdAt: '2026-05-22T10:15:09.673Z' -updatedAt: '2026-05-22T10:15:09.673Z' ---- -## Reason -Document the remote OAuth callback origin fix, root cause, tests, and verification outcomes. - -## Raw Concept -**Task:** -Document the remote OAuth callback scheme fix for caplets auth login - -**Changes:** -- Identified a remote OAuth redirect scheme downgrade from HTTPS to HTTP -- Preserved the public origin from CAPLETS_SERVER_URL in serve options -- Updated callback URL generation to prefer the configured public origin -- Added regression coverage for HTTPS public origin handling - -**Flow:** -CAPLETS_SERVER_URL -> resolveServeOptions -> publicOrigin -> auth callback URL generation -> browser redirect - -**Timestamp:** 2026-05-22T10:14:33.825Z - -## Narrative -### Structure -This fix lives in the remote auth / HTTP serve path. The server now carries a publicOrigin value from CAPLETS_SERVER_URL so callback URLs preserve the externally visible HTTPS scheme even when the inbound request is plain HTTP. - -### Dependencies -Depends on HTTP serve option resolution and remote auth callback URL construction. Existing proxy behavior remains available when no public origin is configured. - -### Highlights -The bug was confirmed by a redirect URL using http:// on a HTTPS deployment. The final verification gate passed, including 39 test files and 538 tests. - -### Rules -Auth callback URL generation now uses the configured public origin when present. Preserve existing proxy behavior when no public origin is configured. - -### Examples -Example bug: redirect_uri=http%3A%2F%2Fcaplets.tail7ff085.ts.net%2Fcontrol%2Fauth%2Fcallback%2F... despite CAPLETS_SERVER_URL being https://caplets... - -## Facts -- **remote_auth_callback_scheme_bug**: Remote `caplets auth login` OAuth flow generated a redirect URL using `http://` even when `CAPLETS_SERVER_URL` was `https://...`. [project] -- **root_cause**: The root cause was that `resolveServeOptions` used `CAPLETS_SERVER_URL` for bind host/path but discarded the public scheme and origin. [project] -- **callback_url_source**: Auth callback URLs were previously reconstructed from the inbound HTTP request URL, which becomes `http://...` behind TLS termination unless proxy headers are trusted. [project] -- **public_origin_fix**: The fix added `publicOrigin` to HTTP serve options, derived from `CAPLETS_SERVER_URL`. [project] -- **callback_origin_policy**: Auth callback URL generation now uses the configured public origin when present and preserves existing proxy behavior when no public origin is configured. [project] -- **regression_tests**: Regression tests were added to ensure `resolveServeOptions` preserves `https://...` public origin from `CAPLETS_SERVER_URL` and remote auth login callback redirects use that HTTPS origin. [project] -- **verification_status**: Verification passed for targeted tests, formatting, linting, typecheck, and the full verification gate. [project] -- **verification_counts**: The full verification run reported 39 test files and 538 tests passed. [project] -- **intermittent_unrelated_failure**: The focused package-script run briefly encountered an unrelated OpenAPI timeout/protocol race, but the new auth callback tests did not fail in that run. [project] -- **unrelated_dirty_files**: The repository had dirty `.brv` and `.opencode/opencode.json` files that were not part of the fix. [project] diff --git a/.brv/context-tree/architecture/auth/remote_auth_review_findings.md b/.brv/context-tree/architecture/auth/remote_auth_review_findings.md deleted file mode 100644 index 62613d52..00000000 --- a/.brv/context-tree/architecture/auth/remote_auth_review_findings.md +++ /dev/null @@ -1,65 +0,0 @@ ---- -title: Remote Auth Review Findings -summary: Task 8 review found a high-severity premature success message in remote auth login and a medium-severity lack of TTL/cleanup for pending remote auth flows; tests and typecheck passed. -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-20T15:01:08.681Z' -updatedAt: '2026-05-20T15:01:08.681Z' ---- - -## Reason - -Preserve lasting findings from Task 8 remote auth implementation review - -## Raw Concept - -**Task:** -Document the code review findings for Task 8 remote auth implementation - -**Changes:** -- Identified premature success reporting in remote auth login -- Identified missing TTL and pruning for pending remote auth flows -- Recorded passing test and typecheck verification results - -**Files:** -- packages/core/src/cli.ts -- packages/core/src/remote-control/auth-flow.ts -- packages/core/src/remote-control/dispatch.ts -- packages/core/test/remote-control-dispatch.test.ts -- packages/core/test/serve-http.test.ts -- packages/core/test/cli-remote.test.ts -- packages/core/test/auth.test.ts - -**Flow:** -review scope -> inspect auth login behavior -> inspect remote auth flow cleanup -> verify tests and typecheck - -**Timestamp:** 2026-05-20T15:00:51.436Z - -**Author:** code quality review - -## Narrative - -### Structure - -This review notes two issues: a high-severity mismatch between remote and local auth completion semantics, and a medium-severity lifecycle cleanup gap in the remote auth flow store. - -### Dependencies - -The findings depend on remote-control auth flow handling and callback dispatch behavior, plus the existing test suite and typecheck validation. - -### Highlights - -Existing tests passed, but they do not catch premature remote-login success or stale-flow lifecycle behavior. - -### Rules - -Return APPROVED or FINDINGS with severity and refs. Do not edit during review. - -## Facts - -- **remote_auth_login_premature_success**: Remote caplets auth login reports success immediately after auth_login_start before the OAuth callback completes. [project] -- **remote_auth_flow_cleanup**: Pending remote auth flows are stored in an unbounded Map and are deleted only after flow.complete(callbackUrl) succeeds. [project] -- **remote_auth_flow_lifecycle_risk**: Failed callbacks leave the remote auth flow live and abandoned flows can remain indefinitely. [project] -- **verification_tests**: Verification run passed pnpm --filter @caplets/core test for remote-control-dispatch, serve-http, cli-remote, and auth tests: 33 files / 410 tests. [project] -- **verification_typecheck**: pnpm --filter @caplets/core typecheck passed. [project] diff --git a/.brv/context-tree/architecture/context.md b/.brv/context-tree/architecture/context.md deleted file mode 100644 index 6deaf9ef..00000000 --- a/.brv/context-tree/architecture/context.md +++ /dev/null @@ -1,10 +0,0 @@ -# Domain: architecture - -## Purpose -Capture architectural decisions about result content handling across the platform - -## Scope -Included in this domain: -- Result content rendering -- Markdown projection -- Structured content handling diff --git a/.brv/context-tree/architecture/remote_control/caplets_interface_ux.md b/.brv/context-tree/architecture/remote_control/caplets_interface_ux.md deleted file mode 100644 index e834a9f1..00000000 --- a/.brv/context-tree/architecture/remote_control/caplets_interface_ux.md +++ /dev/null @@ -1,42 +0,0 @@ ---- -title: Caplets Interface UX -summary: Caplets interface UX work covering remote control polish, output field selection, and native hot reload ergonomics. -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-20T13:00:01.109Z' -updatedAt: '2026-05-20T13:00:01.109Z' ---- - -## Reason - -Curate interface UX planning and implementation details for caplets remote control and output selection - -## Raw Concept - -**Task:** -Document the caplets interface UX work across remote control, output field selection, and hot reload-related usability planning - -**Changes:** -- Refined remote control interface behavior and selection flows -- Added output field selection planning -- Captured native hot reload UX considerations - -**Flow:** -caplets UX planning -> remote control behavior -> output selection -> hot reload ergonomics - -**Timestamp:** 2026-05-20T12:59:55.885Z - -## Narrative - -### Structure - -This topic groups interface and UX-oriented caplets planning notes that affect remote control behavior, configuration surfaces, and output presentation. - -### Dependencies - -Related to caplets remote control, CLI inspection polish, and hot reload backend work. - -### Highlights - -Focuses on making caplets easier to use and understand through clearer control surfaces and better output selection. diff --git a/.brv/context-tree/architecture/remote_control/caplets_remote_control_module.md b/.brv/context-tree/architecture/remote_control/caplets_remote_control_module.md deleted file mode 100644 index 0107cd8d..00000000 --- a/.brv/context-tree/architecture/remote_control/caplets_remote_control_module.md +++ /dev/null @@ -1,42 +0,0 @@ ---- -title: Caplets Remote Control Module -summary: Documents the remote control module selection, API shape, context propagation, environment variable interface, and working module behavior for caplets. -tags: [] -related: [architecture/remote_control/mcp_capability_discovery_and_use.md, architecture/remote_control/remote_control_api_shape.md] -keywords: [] -createdAt: '2026-05-20T12:35:54.751Z' -updatedAt: '2026-05-20T12:35:54.751Z' ---- - -## Reason - -Curate remote control module notes from the provided context - -## Raw Concept - -**Task:** -Document the caplets remote control module and its related implementation notes - -**Changes:** -- Captured remote mode selection guidance -- Captured API shape and context module behavior -- Captured unified environment variable interface and working module notes - -**Flow:** -CLI remote mode selection -> build remote control context -> expose API shape -> configure unified environment variables -> execute working module - -**Timestamp:** 2026-05-20T12:35:48.316Z - -## Narrative - -### Structure - -The context groups several remote-control-related notes under architecture/remote_control, including CLI mode selection, context handling, API shape, environment variable unification, and working module guidance. - -### Dependencies - -These notes depend on shared remote authentication/state ownership decisions and on the broader caplets remote-control architecture. - -### Highlights - -The material emphasizes a unified interface for remote control and captures working-module expectations alongside spec-compliance review notes. diff --git a/.brv/context-tree/architecture/remote_control/cli_remote_mode_selection.md b/.brv/context-tree/architecture/remote_control/cli_remote_mode_selection.md deleted file mode 100644 index 6b55843b..00000000 --- a/.brv/context-tree/architecture/remote_control/cli_remote_mode_selection.md +++ /dev/null @@ -1,64 +0,0 @@ ---- -title: CLI Remote Mode Selection -summary: Remote control uses CAPLETS_MODE with auto/local/remote selection, independent server URL and credentials, and remote mode coordinates server-owned state and auth flows. -tags: [] -related: [architecture/remote_control/working_module.md, architecture/remote_control/unified_environment_variable_interface.md, architecture/remote_control/remote_control_api_shape.md, architecture/auth/remote_auth_and_state_ownership.md, architecture/remote_control/context.md] -keywords: [] -createdAt: '2026-05-20T11:09:24.904Z' -updatedAt: '2026-05-20T11:32:37.464Z' ---- - -## Reason - -Curate remote control mode selection and execution rules from context - -## Raw Concept - -**Task:** -Document CLI remote mode selection and working module behavior - -**Changes:** -- Removed dependency on CAPLETS_NATIVE_MODE for CLI routing -- Established CAPLETS_REMOTE_URL as the remote-mode trigger -- Recorded command-level routing exceptions and remote-capable commands -- Introduced CAPLETS_CLI_MODE as separate CLI-specific mode control -- Preserved remote connection settings independently via CAPLETS_REMOTE_URL, CAPLETS_REMOTE_USER, and CAPLETS_REMOTE_PASSWORD -- Defined routing rules for auto, local, and remote CLI modes -- Clarified that CAPLETS_NATIVE_MODE is not used for CLI routing -- CAPLETS_MODE is the selector for auto/local/remote mode selection -- CAPLETS_SERVER_URL and credentials are independent and required for remote mode -- Remote mode uses server-owned state and the local client coordinates auth flows - -**Flow:** -select mode -> resolve server URL and credentials -> if remote, coordinate auth flow with server-owned state -> execute remote control operations - -**Timestamp:** 2026-05-20T11:32:32.346Z - -**Patterns:** -- `^CAPLETS_CLI_MODE=(auto|local|remote)$` - Valid CLI mode values -- `^CAPLETS_REMOTE_URL=.+$` - Remote backend URL configuration - -## Narrative - -### Structure - -The remote control architecture separates mode selection from server configuration and auth handling. The client selects auto, local, or remote via CAPLETS_MODE, while CAPLETS_SERVER_URL and credentials are handled independently. - -### Dependencies - -Remote mode depends on server availability, explicit server URL configuration, and credentials. The client must not expose tokens or secrets. - -### Highlights - -Remote control API is command-semantic via structured /control endpoints, not raw CLI-string execution on the server. Remote mode owns durable state on the server side. - -### Rules - -1. CAPLETS_CLI_MODE=auto or unset: use remote for remote-capable commands when CAPLETS_REMOTE_URL is set; otherwise use local. -2. CAPLETS_CLI_MODE=local: always use local, even if CAPLETS_REMOTE_URL is set. -3. CAPLETS_CLI_MODE=remote: require CAPLETS_REMOTE_URL and fail fast with a clear config error if missing. -4. CAPLETS_NATIVE_MODE remains native-integration-specific for OpenCode/Pi and is not used for CLI routing. - -### Examples - -Examples include CAPLETS_CLI_MODE=local caplets list to keep remote settings but force local, CAPLETS_CLI_MODE=remote caplets list to force remote, and caplets list using remote automatically when CAPLETS_REMOTE_URL is set. diff --git a/.brv/context-tree/architecture/remote_control/context.md b/.brv/context-tree/architecture/remote_control/context.md deleted file mode 100644 index ca66755c..00000000 --- a/.brv/context-tree/architecture/remote_control/context.md +++ /dev/null @@ -1,17 +0,0 @@ ---- -related: [architecture/remote_control/remote_control_api_shape.md, architecture/remote_control/cli_remote_mode_selection.md, architecture/remote_control/unified_environment_variable_interface.md, architecture/remote_control/working_module.md, architecture/remote_control/mcp_capability_discovery_and_use.md] ---- - -# Topic: remote_control - -## Overview - -Covers the remote control API used to drive caplets operations through structured commands and standardized responses. - -## Key Concepts - -- command-semantic API -- structured request envelope -- structured response envelope -- Basic Auth admin protection -- multi-step auth login diff --git a/.brv/context-tree/architecture/remote_control/context_module.md b/.brv/context-tree/architecture/remote_control/context_module.md deleted file mode 100644 index 7d38b901..00000000 --- a/.brv/context-tree/architecture/remote_control/context_module.md +++ /dev/null @@ -1,65 +0,0 @@ ---- -title: Context Module -summary: Context module composes shared remote control state, merges environment-driven values with defaults, and exposes normalized values for CLI and remote mode workflows. -tags: [] -related: [architecture/auth/remote_auth_and_state_ownership.md, architecture/remote_control/cli_remote_mode_selection.md, architecture/remote_control/remote_control_api_shape.md, architecture/remote_control/unified_environment_variable_interface.md, architecture/remote_control/working_module.md] -keywords: [] -createdAt: '2026-05-20T12:15:33.272Z' -updatedAt: '2026-05-20T12:20:25.975Z' ---- - -## Reason - -Curate remote control context module responsibilities and state handling - -## Raw Concept - -**Task:** -Document the remote control context module and how it composes state from environment and defaults. - -**Changes:** -- Captured the unified environment variable interface for mode selection -- Recorded remote mode state ownership and auth flow boundaries -- Documented the structured remote control API shape and endpoint set -- Clarified that CAPLETS_MODE selects auto/local/remote -- Documented that remote credentials are independent from server URL -- Captured server-owned state and local auth coordination boundaries -- Recorded the structured /control API shape and HTTP service routes -- Defines shared state fields for remote control coordination -- Normalizes environment-backed values before use -- Supports both CLI-driven and remote-mode workflows - -**Flow:** -load defaults -> merge environment values -> normalize shared state -> expose context to consumers - -**Timestamp:** 2026-05-20T12:20:20.089Z - -**Author:** ByteRover context engineering notes - -## Narrative - -### Structure - -The context module sits under remote_control as the shared state layer used by the CLI and remote control paths. It centralizes environment-derived configuration and keeps the consumed shape consistent for downstream logic. - -### Dependencies - -Depends on environment configuration and default remote control settings. It is paired with the working module and the CLI remote mode selection logic. - -### Highlights - -The main responsibility is to keep remote control state coherent across local and remote execution paths while avoiding ad hoc environment parsing in callers. - -### Rules - -User explicitly said pre-1.0, so breaking changes/backward compatibility are not a concern. Remote mode uses server-owned state; local client only coordinates auth flows and must never expose tokens/secrets. - -### Examples - -Example routes include /healthz, /mcp, /control, and optional /control/auth/callback/:flowId. CAPLETS_MODE can be auto, local, or remote. - -## Facts - -- **context_module_ownership**: The context module owns shared remote control state composition and normalization. [project] -- **environment_merge_behavior**: Environment values are merged with defaults before being exposed to callers. [project] -- **normalized_values_usage**: The module surfaces normalized values for CLI and remote mode workflows. [project] diff --git a/.brv/context-tree/architecture/remote_control/extracted_facts.md b/.brv/context-tree/architecture/remote_control/extracted_facts.md deleted file mode 100644 index f8d56231..00000000 --- a/.brv/context-tree/architecture/remote_control/extracted_facts.md +++ /dev/null @@ -1,29 +0,0 @@ ---- -title: extracted_facts -summary: Extracted factual statements from provided context (full list with concise overview) -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T18:12:54.989Z' -updatedAt: '2026-05-27T18:12:54.989Z' -consolidated_at: '2026-05-27T23:37:51.479Z' -consolidated_from: [{date: '2026-05-27T23:37:51.479Z', path: architecture/remote_control/extracted_facts.abstract.md, reason: 'All three files document the same extracted facts about project loader behavior, design guidelines, and command taxonomy. The markdown file contains the full fact list, while the abstract and overview provide summaries. Merging preserves the complete facts and adds the concise summaries as a front‑matter summary section.'}, {date: '2026-05-27T23:37:51.479Z', path: architecture/remote_control/extracted_facts.overview.md, reason: 'All three files document the same extracted facts about project loader behavior, design guidelines, and command taxonomy. The markdown file contains the full fact list, while the abstract and overview provide summaries. Merging preserves the complete facts and adds the concise summaries as a front‑matter summary section.'}] ---- -## Reason -Curate extracted factual statements from context - -## Raw Concept -**Task:** -Extract factual statements - -## Narrative -### Structure -Facts extracted from context - -### Highlights -{{abstract_summary}} - -{{overview_summary}} - -## Facts -{{full_facts_list}} diff --git a/.brv/context-tree/architecture/remote_control/mcp_capability_discovery_and_use.md b/.brv/context-tree/architecture/remote_control/mcp_capability_discovery_and_use.md deleted file mode 100644 index 286bfd1e..00000000 --- a/.brv/context-tree/architecture/remote_control/mcp_capability_discovery_and_use.md +++ /dev/null @@ -1,57 +0,0 @@ ---- -title: MCP Capability Discovery and Use -summary: MCP-native resource, prompt, and completion operations are exposed only for MCP-backed Caplets; non-MCP backends remain tool-only and unsupported capabilities return structured errors. -tags: [] -related: [architecture/remote_control/remote_control_api_shape.md, architecture/remote_control/caplets_remote_control_module.md, docs/plans/active_caplets_planning_documents.md, architecture/remote_control/working_module.md, architecture/remote_control/context.md] -keywords: [] -createdAt: '2026-05-21T12:32:53.461Z' -updatedAt: '2026-05-21T12:34:13.344Z' ---- -## Reason -Document the design choice that MCP-specific operations should only be exposed for MCP-backed Caplets, with backend-specific generated schemas. - -## Raw Concept -**Task:** -Document capability exposure rules for Caplets across MCP and non-MCP backends. - -**Changes:** -- Expand Caplets beyond tool-only MCP indexing -- Add resources, prompts, templates, and completions to the capability model -- Keep progressive disclosure by preserving one top-level Caplet tool -- Separated MCP-native operations from tool-only backends -- Established per-backend generated schema behavior -- Defined unsupported capability handling for downstream MCP servers - -**Flow:** -backend type detected -> schema generated for that backend -> MCP backends expose native operations -> non-MCP backends remain tool-only -> unsupported downstream capability returns structured error - -**Timestamp:** 2026-05-21T12:33:58.234Z - -**Author:** assistant - -**Patterns:** -- `UNSUPPORTED_CAPABILITY` - Structured error code returned when a downstream MCP capability is unavailable - -## Narrative -### Structure -This topic records the backend-specific Caplets schema design, distinguishing MCP-native surfaces from tool-only surfaces. - -### Dependencies -Depends on backend detection during schema generation and on downstream MCP capability advertisement at connection time. - -### Highlights -The design prevents non-MCP agents from seeing unsupported resource and prompt actions while preserving MCP ergonomics where capabilities exist. - -### Rules -Caplets exposes MCP-native resource/prompt/completion operations only for MCP-backed Caplets. Native OpenAPI, GraphQL, HTTP, and CLI Caplets remain action/tool surfaces and do not advertise unsupported MCP-specific operations in their generated schemas. - -### Examples -Example: an OpenAPI-backed Caplet should expose get_tool and call_tool, but not read_resource or list_prompts. - -## Facts -- **non_mcp_backends_supported_operations**: Non-MCP backends should not advertise resource, prompt, or completion operations. [project] -- **mcp_backed_caplet_operations**: MCP-backed Caplets may expose list_resources, search_resources, list_resource_templates, read_resource, list_prompts, search_prompts, get_prompt, and complete. [project] -- **schema_generation_strategy**: The generated input schema should be produced per Caplet/backend instead of using one universal schema for every Caplet. [project] -- **backend_surface_policy**: OpenAPI, GraphQL, HTTP, and CLI backends should expose tools only. [project] -- **caplet_set_surface_policy**: Caplet set backends should expose the child Caplets’ generated tool surface and only forward MCP operations when the child is MCP-backed and forwarding is explicitly chosen. [project] -- **unsupported_capability_handling**: If a downstream MCP server does not advertise a capability, the operation should return a structured UNSUPPORTED_CAPABILITY response. [project] diff --git a/.brv/context-tree/architecture/remote_control/remote-control-boundaries-are-enforced-by-server-owned-state-and-sanitized-input.md b/.brv/context-tree/architecture/remote_control/remote-control-boundaries-are-enforced-by-server-owned-state-and-sanitized-input.md deleted file mode 100644 index 6685e878..00000000 --- a/.brv/context-tree/architecture/remote_control/remote-control-boundaries-are-enforced-by-server-owned-state-and-sanitized-input.md +++ /dev/null @@ -1,23 +0,0 @@ ---- -confidence: 0.9 -sources: [facts/_index.md, facts/_index.md, project/_index.md] -synthesized_at: '2026-05-26T19:23:43.123Z' -type: synthesis -title: Remote control boundaries are enforced by server-owned state and sanitized inputs -summary: Both facts and project docs frame remote control as a boundary where local-only fields are stripped and server-owned secrets stay protected. -tags: [remote-control, security, sanitization, completion, server-owned] -related: [] -keywords: [boundary, sanitization, server-owned, redaction, routing, local-only, remote, auth] -createdAt: '2026-05-26T19:23:43.123Z' -updatedAt: '2026-05-26T19:23:43.123Z' ---- - -# Remote control boundaries are enforced by server-owned state and sanitized inputs - -Across facts and project, remote-control behavior is consistently constrained by a trust boundary: local-only add fields are stripped before remote requests, routing uses only server-owned variables, and completion/remote operations must not leak auth diagnostics or other secret state. - -## Evidence - -- **facts**: Remote mutation routing confirmed request sanitization, server boundary checks, response parsing, and error redaction; local-only add fields `global`, `print`, `output`, and `destinationRoot` are stripped before remote requests. -- **facts**: Nested `request.operation` validation is confirmed, and remote routing uses only `CAPLETS_MODE` and `CAPLETS_SERVER_URL`. -- **project**: The completion/remote-control workflow distinguishes backend IDs vs tool names by prefix and degrades safely on failures/timeouts, while local hidden completion routes through `CapletsEngine.completeCliWords`. diff --git a/.brv/context-tree/architecture/remote_control/remote-control-is-treated-as-a-server-owned-secret-free-boundary.md b/.brv/context-tree/architecture/remote_control/remote-control-is-treated-as-a-server-owned-secret-free-boundary.md deleted file mode 100644 index dffa94ac..00000000 --- a/.brv/context-tree/architecture/remote_control/remote-control-is-treated-as-a-server-owned-secret-free-boundary.md +++ /dev/null @@ -1,22 +0,0 @@ ---- -confidence: 0.97 -sources: [architecture/_index.md, facts/_index.md] -synthesized_at: '2026-05-21T23:23:26.213Z' -type: synthesis -title: Remote control is treated as a server-owned, secret-free boundary -summary: Remote control flows consistently keep auth state and secrets on the server while the client only coordinates login and command routing. -tags: [remote-control, auth, secrets, server-state] -related: [] -keywords: [remote, auth, tokens, secrets, server-owned, client, control, boundary] -createdAt: '2026-05-21T23:23:26.213Z' -updatedAt: '2026-05-21T23:23:26.213Z' ---- - -# Remote control is treated as a server-owned, secret-free boundary - -Across architecture and facts, remote control is modeled as a strict boundary: the server owns durable remote-mode state, tokens, and backend definitions, while local clients only initiate login/logout and must never receive secrets. This is reinforced by the structured `/control` API and the repository’s curation rules that preserve durable boundary facts and verify them explicitly. - -## Evidence - -- **architecture**: Remote-mode ownership says the server owns all Caplets state in remote mode, including config, `.caplets` files, installed Caplets, backend definitions, OAuth/token storage, and reload/watch lifecycle; local clients only coordinate login/logout and must never receive secrets. -- **facts**: The repository preserves durable architecture and review facts about remote-control boundaries, including server-side redaction, secret-free remote responses, and approved verification of remote control behavior. diff --git a/.brv/context-tree/architecture/remote_control/remote_control_api_shape.md b/.brv/context-tree/architecture/remote_control/remote_control_api_shape.md deleted file mode 100644 index 6d1c52b1..00000000 --- a/.brv/context-tree/architecture/remote_control/remote_control_api_shape.md +++ /dev/null @@ -1,71 +0,0 @@ ---- -title: Remote Control API Shape -summary: Remote control API shape with unified environment selection, CLI remote mode resolution, and working/context modules for routing remote actions. -tags: [] -related: [architecture/remote_control/working_module.md, architecture/remote_control/unified_environment_variable_interface.md, architecture/remote_control/cli_remote_mode_selection.md, architecture/auth/remote_auth_and_state_ownership.md, architecture/remote_control/context.md, architecture/remote_control/context_module.md, architecture/remote_control/mcp_capability_discovery_and_use.md, architecture/remote_control/caplets_remote_control_module.md] -keywords: [] -createdAt: '2026-05-20T11:07:38.964Z' -updatedAt: '2026-05-20T15:42:10.341Z' ---- - -## Reason - -Curate remote control API shape, environment selection, and routing behavior from the provided RLM context. - -## Raw Concept - -**Task:** -Document the remote control API shape and its related execution model. - -**Changes:** -- Defined a command-semantic /control endpoint -- Specified structured request and response envelopes -- Outlined a multi-step auth login flow -- Captured API shape details -- Recorded command routing and ownership rules -- Preserved execution and state-handling behavior -- Captured the remote control API shape and surrounding module responsibilities. -- Recorded CLI remote mode selection and unified environment variable interface behavior. -- Preserved task-specific review findings related to context and working modules. - -**Files:** -- caplets/github/CAPLET.md -- caplets/github-cli/CAPLET.md -- caplets/linear/CAPLET.md -- caplets/repo-cli/CAPLET.md -- caplets/context7.md - -**Flow:** -CLI input -> remote mode selection -> context/working module resolution -> remote action routing - -**Timestamp:** 2026-05-20T15:42:02.926Z - -## Narrative - -### Structure - -This topic groups the remote control API shape together with selection and context-handling modules that determine how remote actions are routed. - -### Dependencies - -Depends on CLI remote mode selection, unified environment variable handling, and the context/working module split. - -### Highlights - -Captures the architectural shape of remote control behavior and the supporting module responsibilities in the caplets system. - -### Rules - -No remote request is ever “run this CLI string”. -No shelling out to `caplets` on the server. - -### Examples - -Example request: POST /control { command: "list_tools", arguments: {} } -Example login flow: auth_login_start returns authorizationUrl and flowId, then auth_login_complete exchanges credentials. - -## Facts - -- **remote_mode_selection**: Remote mode selection is resolved through CLI remote mode selection logic. [project] -- **environment_variable_interface**: The remote control layer uses a unified environment variable interface. [project] -- **remote_control_modules**: Context and working modules are part of the remote control architecture. [project] diff --git a/.brv/context-tree/architecture/remote_control/remote_control_context.md b/.brv/context-tree/architecture/remote_control/remote_control_context.md deleted file mode 100644 index b8d53ec9..00000000 --- a/.brv/context-tree/architecture/remote_control/remote_control_context.md +++ /dev/null @@ -1,83 +0,0 @@ ---- -title: Remote Control Context -summary: Remote control architecture covering context, API shape, environment variable interface, working module, and interface UX. -tags: [] -related: [architecture/auth/remote_auth_and_state_ownership.md, architecture/remote_control/cli_remote_mode_selection.md, architecture/remote_control/remote_control_api_shape.md, architecture/remote_control/unified_environment_variable_interface.md, architecture/remote_control/working_module.md, architecture/remote_control/context.md, architecture/remote_control/context_module.md, architecture/remote_control/caplets_remote_control_module.md, architecture/remote_control/task_1_spec_compliance_review.md] -keywords: [] -createdAt: '2026-05-20T12:30:03.683Z' -updatedAt: '2026-05-20T13:17:52.974Z' ---- - -## Reason - -Curate remote control architecture knowledge from the provided context - -## Raw Concept - -**Task:** -Document the remote control architecture knowledge captured in the context tree. - -**Changes:** -- Defined CAPLETS_MODE as the selector for auto, local, and remote execution modes -- Clarified that CAPLETS_SERVER_URL and credentials are required independently for remote mode -- Recorded the remote HTTP service default endpoint and route surface -- Preserved the pre-1.0 compatibility stance -- Captured the remote control context module and its supporting design notes -- Recorded unified environment variable interface details -- Documented CLI remote mode selection and API shape considerations -- Captured remote control module topics -- Captured CLI remote mode selection behavior -- Captured unified environment variable interface and working module details -- Captured remote auth and state ownership notes -- Captured remote control context as durable knowledge -- Included mode selection, context boundaries, and API shape -- Recorded working module behavior and spec compliance review -- Documented remote control API shape and context handling -- Preserved remote auth and state ownership knowledge -- Captured the remote control module family and its related implementation concerns -- Captured the context module and related remote control architecture topics -- Preserved references to API shape, environment variable interface, and working module -- Recorded interface UX and auth ownership relationships - -**Files:** -- caplets/github/CAPLET.md -- caplets/github-cli/CAPLET.md -- caplets/linear/CAPLET.md -- caplets/repo-cli/CAPLET.md -- caplets/context7.md - -**Flow:** -remote control context -> API shape -> environment variable interface -> working module -> interface UX - -**Timestamp:** 2026-05-20 - -**Author:** ByteRover context engineering - -## Narrative - -### Structure - -This topic groups the remote control architecture, including the context module, API shape, working module, and unified environment variable interface. - -### Dependencies - -Relates to auth ownership and interface UX documentation already present in the architecture domain. - -### Highlights - -The context emphasizes how remote control functionality is organized and how the interface and environment variable interface fit into the design. - -### Rules - -User explicitly said pre-1.0, so breaking changes/backward compatibility are not a concern. - -### Examples - -Remote mode usage is governed by CAPLETS_MODE, while CAPLETS_SERVER_URL and credentials remain separate inputs. - -## Facts - -- **context_module**: Caplets supports a context module for remote control and related workflows. [project] -- **remote_control_api**: The remote control API shape is documented as part of the architecture knowledge. [project] -- **environment_variable_interface**: The curated knowledge includes a unified environment variable interface for remote control. [project] -- **working_module**: A working module is documented alongside remote control architecture. [project] diff --git a/.brv/context-tree/architecture/remote_control/task_1_spec_compliance_review.md b/.brv/context-tree/architecture/remote_control/task_1_spec_compliance_review.md deleted file mode 100644 index e6c09111..00000000 --- a/.brv/context-tree/architecture/remote_control/task_1_spec_compliance_review.md +++ /dev/null @@ -1,55 +0,0 @@ ---- -title: Task 1 Spec Compliance Review -summary: 'Task 1 spec compliance was approved after fixes: object-union mode resolution, IPv6 loopback bracket support, and env-based tests for mode and credentials.' -tags: [] -related: [architecture/remote_control/remote_control_api_shape.md, architecture/remote_control/unified_environment_variable_interface.md, architecture/remote_control/working_module.md] -keywords: [] -createdAt: '2026-05-20T12:19:17.755Z' -updatedAt: '2026-05-20T12:19:17.755Z' ---- - -## Reason - -Preserve the approved verification outcome and linked implementation details - -## Raw Concept - -**Task:** -Document the accepted compliance review for Task 1 - -**Changes:** -- Verified resolveCapletsMode returns an object union -- Verified IPv6 loopback bracket form is accepted -- Verified env-based tests cover CAPLETS_MODE and CAPLETS_SERVER_USER/PASSWORD behavior - -**Files:** -- packages/core/src/server/options.ts -- packages/core/test/server-options.test.ts - -**Flow:** -Review fixes -> confirm implementation behavior -> confirm tests -> return APPROVED - -**Timestamp:** 2026-05-20T12:18:51.566Z - -## Narrative - -### Structure - -This record captures the final verification outcome for Task 1 and ties it to the implementation and test files cited in the review. - -### Dependencies - -Depends on the server options implementation and its test coverage. - -### Highlights - -All cited concerns were addressed and the verification run passed. - -## Facts - -- **task_1_review_status**: Re-review of Task 1 spec compliance after fixes returned APPROVED. [project] -- **resolve_caplets_mode_return_type**: resolveCapletsMode returns an object union. [project] -- **ipv6_loopback_bracket_support**: IPv6 loopback bracket form is accepted. [project] -- **caplets_mode_env_tests**: Env-based tests cover CAPLETS_MODE behavior. [project] -- **caplets_server_credentials_env_tests**: Env-based tests cover CAPLETS_SERVER_USER and CAPLETS_SERVER_PASSWORD behavior. [project] -- **server_options_test_run**: Verification run passed 364 tests for packages/core server options test suite. [project] diff --git a/.brv/context-tree/architecture/remote_control/unified_environment_variable_interface.md b/.brv/context-tree/architecture/remote_control/unified_environment_variable_interface.md deleted file mode 100644 index 83908bbd..00000000 --- a/.brv/context-tree/architecture/remote_control/unified_environment_variable_interface.md +++ /dev/null @@ -1,79 +0,0 @@ ---- -title: Unified Environment Variable Interface -summary: Unified Caplets env interface uses CAPLETS_MODE plus CAPLETS_SERVER_* vars, with deprecated aliases and explicit precedence rules. -tags: [] -related: [architecture/remote_control/remote_control_api_shape.md, architecture/remote_control/cli_remote_mode_selection.md, architecture/remote_control/working_module.md, architecture/auth/remote_auth_and_state_ownership.md, architecture/remote_control/context.md] -keywords: [] -createdAt: '2026-05-20T11:12:05.554Z' -updatedAt: '2026-05-20T11:12:05.554Z' ---- - -## Reason - -Document the agreed unified environment variable model for Caplets CLI, native integrations, and serve/client modes - -## Raw Concept - -**Task:** -Document the unified environment variable interface for Caplets - -**Changes:** -- Replaced CAPLETS_NATIVE_* with CAPLETS_MODE and CAPLETS_SERVER_* variables -- Defined resolution rules for auto, local, and remote modes -- Added backward-compatible alias mappings for one release path -- Specified env-to-CLI derivation for caplets serve and client mode - -**Files:** -- caplets/github/README.md -- caplets/github-cli/CAPLET.md - -**Flow:** -config/options -> unified env vars -> deprecated aliases -> defaults - -**Timestamp:** 2026-05-20T11:11:45.922Z - -**Patterns:** -- `^CAPLETS_MODE=(auto|local|remote)$` - Allowed unified mode values -- `^CAPLETS_SERVER_URL=.+$` - Server URL setting used by serve and client mode - -## Narrative - -### Structure - -The design centralizes all runtime selection around CAPLETS_MODE and a single CAPLETS_SERVER_* namespace, while preserving old variables as deprecated aliases. - -### Dependencies - -Relies on explicit host config or CLI options taking highest precedence, with environment variables filling in defaults for serving and client behavior. - -### Highlights - -The model simplifies configuration, keeps backward compatibility for one release path, and derives control/MCP endpoints from the same base server URL. - -### Rules - -1. CAPLETS_MODE=auto or unset uses remote/client mode when CAPLETS_SERVER_URL is set, otherwise local mode. -2. CAPLETS_MODE=local always forces local mode, even if server settings exist. -3. CAPLETS_MODE=remote requires CAPLETS_SERVER_URL and fails fast if missing. -4. If both old and new env vars are present, the new unified variable wins. -5. Explicit CLI flags still override env values for caplets serve. - -## Facts - -- **caplets_mode**: CAPLETS_MODE is the single mode selector and can be auto, local, or remote. [project] -- **caplets_server_url**: CAPLETS_SERVER_URL is used by caplets serve and caplets client mode. [project] -- **caplets_server_auth**: CAPLETS_SERVER_USER and CAPLETS_SERVER_PASSWORD provide Basic Auth credentials for serving and client/server configuration. [project] -- **deprecated_alias_native_mode**: CAPLETS_NATIVE_MODE maps to CAPLETS_MODE as a deprecated alias. [project] -- **deprecated_alias_remote_url**: CAPLETS_REMOTE_URL maps to CAPLETS_SERVER_URL as a deprecated alias. [project] -- **deprecated_alias_remote_user**: CAPLETS_REMOTE_USER maps to CAPLETS_SERVER_USER as a deprecated alias. [project] -- **deprecated_alias_remote_password**: CAPLETS_REMOTE_PASSWORD maps to CAPLETS_SERVER_PASSWORD as a deprecated alias. [project] -- **caplets_mode_resolution**: When CAPLETS_MODE is auto or unset, remote/client mode is used if CAPLETS_SERVER_URL is set; otherwise local mode is used. [project] -- **caplets_local_override**: When CAPLETS_MODE is local, local mode is used even when server settings exist. [project] -- **caplets_remote_requirement**: When CAPLETS_MODE is remote, CAPLETS_SERVER_URL is required and missing it is a fast failure. [project] -- **caplets_precedence**: Precedence is explicit host config/options, then new unified env vars, then deprecated aliases, then defaults. [project] -- **caplets_env_conflict_resolution**: If both old and new env vars are present, the new unified variable wins. [project] -- **caplets_deprecation_warning**: The CLI and native integrations may warn once when deprecated aliases are used. [project] -- **caplets_serve_url_derivation**: caplets serve --transport http can derive host, port, and path from CAPLETS_SERVER_URL. [project] -- **caplets_cli_override**: Explicit CLI flags override environment variables for caplets serve. [project] -- **caplets_control_endpoint**: Client mode derives MCP endpoint from CAPLETS_SERVER_URL and control endpoint from the same base URL plus /control. [project] -- **caplets_control_url_escape_hatch**: CAPLETS_CONTROL_URL is an optional future escape hatch for separate control endpoints. [project] diff --git a/.brv/context-tree/architecture/remote_control/working_module.md b/.brv/context-tree/architecture/remote_control/working_module.md deleted file mode 100644 index 2df4232c..00000000 --- a/.brv/context-tree/architecture/remote_control/working_module.md +++ /dev/null @@ -1,62 +0,0 @@ ---- -title: Working Module -summary: Working module notes for caplets, highlighting active curation, included caplet areas, and durable-note separation practice. -tags: [] -related: [architecture/remote_control/unified_environment_variable_interface.md, architecture/remote_control/cli_remote_mode_selection.md, architecture/remote_control/remote_control_api_shape.md, architecture/auth/remote_auth_and_state_ownership.md, architecture/remote_control/context.md, architecture/remote_control/mcp_capability_discovery_and_use.md] -keywords: [] -createdAt: '2026-05-20T11:16:46.330Z' -updatedAt: '2026-05-20T13:22:13.573Z' ---- - -## Reason - -Curate the working module notes from the provided RLM context - -## Raw Concept - -**Task:** -Document the working module for caplets and its curation status. - -**Changes:** -- Captured the current working module notes for durable knowledge. -- Recorded the spec path and commit identifier. -- Recorded the self-review status and format check blocker. -- Captured the command-semantic control model for remote orchestration -- Recorded the unified environment variable interface for mode selection and remote credentials -- Preserved remote auth and server-owned state constraints -- Identified the working module as actively curated during curate sessions -- Captured included caplet areas: GitHub, GitHub CLI, Linear, Repo CLI, and Context7 -- Recorded the practice of separating durable notes from raw source snippets - -**Flow:** -curate session -> inspect working module notes -> separate durable notes from source snippets -> preserve curated knowledge - -**Timestamp:** 2026-05-20T13:22:08.097Z - -## Narrative - -### Structure - -This knowledge captures the working module as a curated area related to caplets and their supporting documentation. - -### Dependencies - -Relies on caplets-related documentation and prior curation sessions for continuity. - -### Highlights - -The working module is treated as an actively curated knowledge area, with emphasis on durable notes instead of raw source snippets. - -### Rules - -Remote mode must keep tokens and secrets server-owned. Structured /control endpoints are used instead of raw CLI-string execution. - -### Examples - -Example endpoints include /healthz for liveness, /mcp for MCP access, /control for command semantics, and /control/auth/callback/:flowId for auth callback handling. - -## Facts - -- **caplets_working_module_scope**: The working module for caplets currently includes a caplets package with GitHub, GitHub CLI, linear, repo CLI, and Context7-related documentation. [project] -- **working_module_status**: The working module is actively curated and surfaced during curate sessions. [project] -- **working_module_curation_practice**: Session fff205ed separated durable notes from raw source snippets while curating the working module. [project] diff --git a/.brv/context-tree/architecture/rendering/rendering_options.md b/.brv/context-tree/architecture/rendering/rendering_options.md deleted file mode 100644 index 5557f5b2..00000000 --- a/.brv/context-tree/architecture/rendering/rendering_options.md +++ /dev/null @@ -1,31 +0,0 @@ ---- -title: Rendering Options -summary: Two-tier renderer recommendation with detailed option analysis -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T11:40:41.883Z' -updatedAt: '2026-05-27T11:40:41.883Z' ---- -## Reason -Curate recommendations for output rendering styles - -## Raw Concept -**Task:** -Document rendering options for OSV and similar tool outputs - -**Changes:** -- Proposed multi-tier rendering strategies -- Listed pros/cons for each option - -**Flow:** -User request -> assistant suggestion -> final recommendation - -**Timestamp:** 2026-05-27T11:40:41.882Z - -## Narrative -### Structure -Comparison of rendering options A-E and recommended two-tier approach - -### Highlights -Two-tier renderer with backend-aware templates and generic fallback diff --git a/.brv/context-tree/architecture/result_content/context.md b/.brv/context-tree/architecture/result_content/context.md deleted file mode 100644 index f44e2727..00000000 --- a/.brv/context-tree/architecture/result_content/context.md +++ /dev/null @@ -1,9 +0,0 @@ -# Topic: result_content - -## Overview -Defines how structuredContent is projected to Markdown content for downstream agents - -## Key Concepts -- Markdown rendering -- size thresholds -- data preservation diff --git a/.brv/context-tree/architecture/result_content/lossless_markdown_handling.md b/.brv/context-tree/architecture/result_content/lossless_markdown_handling.md deleted file mode 100644 index bcceaa58..00000000 --- a/.brv/context-tree/architecture/result_content/lossless_markdown_handling.md +++ /dev/null @@ -1,38 +0,0 @@ ---- -title: Lossless Markdown Handling -summary: Result content renders full markdown losslessly; UI preview collapses with ctrl+O (full details below) -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T13:46:25.141Z' -updatedAt: '2026-05-27T13:46:25.141Z' -consolidated_at: '2026-05-27T23:37:51.482Z' -consolidated_from: [{date: '2026-05-27T23:37:51.482Z', path: architecture/result_content/lossless_markdown_handling.abstract.md, reason: 'These files cover the same topic – lossless markdown handling – with the markdown file providing the full description, the abstract giving a one‑sentence summary, and the overview offering a bullet‑point overview. Merging creates a single authoritative file that includes the detailed content plus the concise abstract and overview sections.'}, {date: '2026-05-27T23:37:51.483Z', path: architecture/result_content/lossless_markdown_handling.overview.md, reason: 'These files cover the same topic – lossless markdown handling – with the markdown file providing the full description, the abstract giving a one‑sentence summary, and the overview offering a bullet‑point overview. Merging creates a single authoritative file that includes the detailed content plus the concise abstract and overview sections.'}] ---- -## Reason -Curate extracted facts from RLM extraction - -## Raw Concept -**Task:** -Document lossless markdown result content handling - -**Flow:** -Render result content without truncation, UI handles preview - -**Timestamp:** 2026-05-27T13:46:25.138Z - -## Narrative -### Structure -Result content module retains full markdown; Pi UI shows collapsed preview with ctrl+O to expand - -### Dependencies -Pi UI component, result‑content renderer - -### Highlights -Lossless markdown preserved, UI preview hint added - -{{abstract_summary}} -{{overview_summary}} - -### Examples -Collapsed view shows "..." and hint, expanded shows full markdown diff --git a/.brv/context-tree/architecture/result_content/markdown_content_rendering.md b/.brv/context-tree/architecture/result_content/markdown_content_rendering.md deleted file mode 100644 index 3a5bf2ac..00000000 --- a/.brv/context-tree/architecture/result_content/markdown_content_rendering.md +++ /dev/null @@ -1,62 +0,0 @@ ---- -title: Markdown Content Rendering -summary: Guidelines for converting structuredContent to Markdown, handling size thresholds, and preserving full data -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T11:35:37.132Z' -updatedAt: '2026-05-27T11:35:37.132Z' ---- -## Reason -Document design for rendering structuredContent as Markdown in content field - -## Raw Concept -**Task:** -Define rendering strategy for structuredContent to Markdown content - -**Changes:** -- Add Markdown projection for small/medium results -- Truncate large results with pointer to structuredContent -- Preserve full structuredContent unchanged - -**Files:** -- packages/core/src/result-content.ts - -**Flow:** -structuredContent -> renderer -> Markdown content - -**Timestamp:** 2026-05-27T11:35:37.129Z - -## Narrative -### Structure -Renderer functions produce Markdown sections, bullet rows, and JSON code fences - -### Highlights -Improves readability for content-only MCP clients, maintains data integrity - -### Rules -Large results must include explicit truncation marker and pointer to structuredContent - -### Examples -Example Markdown output with status and body sections - -## Facts -- **structuredContent rendering**: Convert `structuredContent` into readable Markdown for `content`, while keeping the original full object in `structuredContent`. [project] -- **result size handling**: For small/medium structured results: include full Markdown-rendered structured content. [project] -- **large result handling**: For large results: include important scalar fields plus truncated JSON sections with a clear marker: `… truncated; full value is available in structuredContent.body`. [project] -- **data integrity**: Preserve full `structuredContent` unchanged. [project] -- **downstream MCP handling**: Keep downstream MCP `content` unchanged when proxying downstream MCP tools, unless Caplets generated the structured result itself or field selection is applied. [project] -- **renderer reuse**: Pi/OpenCode can use the same Markdown renderer for consistency. [project] -- **markdown projection**: Best compatibility with content-only MCP clients. [project] -- **markdown projection**: Much better for LLMs than raw minified JSON. [project] -- **markdown projection**: Avoids reverting fully to noisy pretty-JSON duplication for everything. [project] -- **resultContent**: Add config like `{ "resultContent": { "mode": "markdown", "maxBytes": 12000 } }`. [project] -- **renderer module**: Create a shared renderer in `packages/core/src/result-content.ts` with functions `structuredContentMarkdown(value, options): string` and `structuredContentMarkdownBlocks(value, options): TextContentBlock[]`. [project] -- **renderer behavior**: Renderer should render plain objects as Markdown sections, scalar fields as bullet rows, and nested arrays/objects as fenced JSON blocks. [project] -- **renderer truncation**: Renderer should enforce a max text size and mark truncation explicitly, pointing to `structuredContent.`. [project] -- **default behavior**: Default policy: For Caplets-generated structured results, `content` is Markdown projection of meaningful `structuredContent` and `structuredContent` remains full original data. [project] -- **metadata handling**: Metadata-only discovery responses should stay compact/empty unless discovery must be visible to content-only clients. [project] -- **downstream call_tool handling**: For downstream MCP-backed `call_tool`, preserve downstream `content` as-is and optionally project empty/useless `content` from `structuredContent` into Markdown. [project] -- **test cases**: Add tests to verify small HTTP/OpenAPI/GraphQL/CLI structured results expose body/stdout/etc in Markdown `content` while full data remains in `structuredContent`. [project] -- **test cases**: Add tests to verify large nested bodies truncate with an explicit pointer. [project] -- **plan location**: Implementation plan file should be saved at `docs/plans/2026-05-27-structured-content-markdown-content.md`. [project] diff --git a/.brv/context-tree/architecture/unified-remote-control-architecture-enforced-by-server-owned-state.md b/.brv/context-tree/architecture/unified-remote-control-architecture-enforced-by-server-owned-state.md deleted file mode 100644 index f9de00e4..00000000 --- a/.brv/context-tree/architecture/unified-remote-control-architecture-enforced-by-server-owned-state.md +++ /dev/null @@ -1,23 +0,0 @@ ---- -confidence: 0.94 -sources: [architecture/_index.md, facts/_index.md, facts/_index.md] -synthesized_at: '2026-05-27T23:38:23.762Z' -type: synthesis -title: Unified Remote‑Control Architecture Enforced by Server‑Owned State -summary: Remote‑control design centralizes state on the server and standardizes command interfaces across CLI, API, and UX layers. -tags: [remote-control, server-state, cli, api] -related: [] -keywords: [remote, control, server, state, cli, api, sanitization, boundary, commands, ux] -createdAt: '2026-05-27T23:38:23.762Z' -updatedAt: '2026-05-27T23:38:23.762Z' ---- - -# Unified Remote‑Control Architecture Enforced by Server‑Owned State - -Both the architecture and facts domains document a server‑owned, secret‑free remote boundary with structured control commands, reinforcing a consistent remote‑control model across implementation, review, and runtime conventions. - -## Evidence - -- **architecture**: Core architecture describes a server‑owned, secret‑free remote boundary with structured control commands. -- **facts**: Review outcomes (e.g., task_7_review_outcome) verify sanitization and server‑owned field boundaries for remote control. -- **facts**: Remote‑control review/outcome entries emphasize server‑side validation and redaction of client‑supplied fields. diff --git a/.brv/context-tree/clipboard_fallback/clipboard_fallback/clipboard_fallback.md b/.brv/context-tree/clipboard_fallback/clipboard_fallback/clipboard_fallback.md deleted file mode 100644 index f2dc15d4..00000000 --- a/.brv/context-tree/clipboard_fallback/clipboard_fallback/clipboard_fallback.md +++ /dev/null @@ -1,30 +0,0 @@ ---- -title: clipboard fallback -summary: Facts about clipboard fallback -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T11:17:08.550Z' -updatedAt: '2026-05-28T11:17:08.550Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Document extracted factual statements - -**Flow:** -extraction -> curation - -**Timestamp:** 2026-05-28T11:17:08.519Z - -## Narrative -### Structure -Facts grouped under clipboard fallback - -### Highlights -Contains 1 factual statements - -## Facts -- **clipboard fallback**: Clipboard fallback selects/focuses snippets implemented at index.astro:480-514. diff --git a/.brv/context-tree/codebase/caplets/caplets_module.md b/.brv/context-tree/codebase/caplets/caplets_module.md deleted file mode 100644 index ae160421..00000000 --- a/.brv/context-tree/codebase/caplets/caplets_module.md +++ /dev/null @@ -1,39 +0,0 @@ ---- -title: Caplets Module -summary: Extracted facts and narrative for Caplets module -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T10:44:17.974Z' -updatedAt: '2026-05-28T10:46:57.621Z' ---- -## Reason -Curate extracted facts from context - -## Raw Concept -**Task:** -Document Caplets module knowledge - -**Timestamp:** 2026-05-28T10:46:57.618Z - -## Narrative -### Structure -Extracted factual statements and organized by subject. - -### Dependencies -Relies on Node.js, Astro, and various plugins. - -### Highlights -copy button visibility, page-load animations, responsive specificity, typecheck, build, format check, lint, impeccable check, risks, working tree - -## Facts -- **copy button visibility**: Copy buttons are hidden by default and only shown via `.js-enabled .copy-button`. -- **page-load animations**: Header, hero copy, and title page-load animations plus related keyframes and blur motion were removed. -- **responsive specificity**: Tablet/mobile overrides were fixed with matching `.js-enabled .agent-setup-panels` and `.js-enabled .agent-setup-panel` selectors. -- **typecheck**: `pnpm --filter @caplets/landing typecheck` passed with 0 errors. -- **build**: `pnpm --filter @caplets/landing build` passed, building 1 page. -- **format check**: `pnpm format:check` passed. -- **lint**: `pnpm lint` passed. -- **impeccable check**: `npx impeccable --json apps/landing/src/pages/index.astro` passed, output `[]`. -- **risks**: There are no open risks or questions for the requested blockers. -- **working tree**: Existing unrelated working-tree changes remain untouched. diff --git a/.brv/context-tree/codebase/caplets/pr_preview_deploy_fix.md b/.brv/context-tree/codebase/caplets/pr_preview_deploy_fix.md deleted file mode 100644 index 88654e16..00000000 --- a/.brv/context-tree/codebase/caplets/pr_preview_deploy_fix.md +++ /dev/null @@ -1,66 +0,0 @@ ---- -title: pr_preview_deploy_fix -summary: Documented Alchemy fetch compatibility fix and CI workflow updates for PR preview deploy -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T11:44:45.781Z' -updatedAt: '2026-05-28T11:44:45.781Z' ---- -## Reason -Curate fix details for PR preview deploy failure - -## Raw Concept -**Task:** -Document PR preview deploy fix and related changes - -**Changes:** -- Fixed InvalidArgumentError in Alchemy fetch compatibility -- Added scripts/alchemy-fetch-compat.mjs and test -- Updated package.json to run Alchemy with import shim -- Enhanced .github/workflows/pr-preview-deploy.yml -- Adjusted alchemy.run.ts for GitHub Actions metadata - -**Files:** -- scripts/alchemy-fetch-compat.mjs -- scripts/alchemy-fetch-compat.test.mjs -- package.json -- .github/workflows/pr-preview-deploy.yml -- alchemy.run.ts - -**Flow:** -detect error -> implement compatibility shim -> update workflow -> verify tests - -**Timestamp:** 2026-05-28T11:44:45.779Z - -**Author:** AI Assistant - -## Narrative -### Structure -Added compatibility shim scripts and updated CI workflow to fix fetch dispatcher issue. - -### Dependencies -Depends on undici@8.3.0 behavior in GitHub Actions. - -### Highlights -All lint, typecheck, tests, schema, benchmark, and build passed after fix. - -### Examples -Error: InvalidArgumentError: invalid onRequestStart method - -## Facts -- **CI**: InvalidArgumentError: invalid onRequestStart method -- **Alchemy integration**: Alchemy passes a userland undici dispatcher into Node 24’s native fetch, and in GitHub Actions it resolves undici@8.3.0, which breaks the dispatcher interface -- **scripts/alchemy-fetch-compat.mjs**: scripts/alchemy-fetch-compat.mjs strips the incompatible dispatcher before native fetch -- **scripts/alchemy-fetch-compat.test.mjs**: scripts/alchemy-fetch-compat.test.mjs adds a regression test -- **package.json**: package.json runs Alchemy via node --import ./scripts/alchemy-fetch-compat.mjs -- **.github/workflows/pr-preview-deploy.yml**: .github/workflows/pr-preview-deploy.yml adds a PR-specific stage, GitHub token/metadata, issue comment permission, and clearer step name -- **alchemy.run.ts**: alchemy.run.ts removes placeholder your-username/your-repo, uses GitHub Actions repo metadata, and fixes comment markdown -- **format check**: pnpm format:check passes -- **lint**: pnpm lint passes -- **typecheck**: pnpm typecheck passes -- **test suite**: pnpm test runs 622 Vitest tests plus a shim regression test, all passed -- **schema check**: pnpm schema:check passes -- **benchmark check**: pnpm benchmark:check passes -- **build**: pnpm build passes -- **Cloudflare API parsing**: Cloudflare API JSON parsing works with the shim under Node 26 diff --git a/.brv/context-tree/copy_button_min_height/copy_button_min_height/copy_button_min_height.md b/.brv/context-tree/copy_button_min_height/copy_button_min_height/copy_button_min_height.md deleted file mode 100644 index c6b0ca10..00000000 --- a/.brv/context-tree/copy_button_min_height/copy_button_min_height/copy_button_min_height.md +++ /dev/null @@ -1,30 +0,0 @@ ---- -title: .copy-button min-height -summary: Facts about .copy-button min-height -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T11:17:08.521Z' -updatedAt: '2026-05-28T11:17:08.521Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Document extracted factual statements - -**Flow:** -extraction -> curation - -**Timestamp:** 2026-05-28T11:17:08.519Z - -## Narrative -### Structure -Facts grouped under .copy-button min-height - -### Highlights -Contains 1 factual statements - -## Facts -- **.copy-button min-height**: apps/landing/src/styles/global.css:889-899 sets .copy-button to min-height: 32px, below the requested 44px target. diff --git a/.brv/context-tree/curated/extracted/extracted_facts.md b/.brv/context-tree/curated/extracted/extracted_facts.md deleted file mode 100644 index dee2028c..00000000 --- a/.brv/context-tree/curated/extracted/extracted_facts.md +++ /dev/null @@ -1,35 +0,0 @@ ---- -title: Extracted Facts -summary: Aggregated facts extracted from provided context -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-29T09:15:07.392Z' -updatedAt: '2026-05-29T09:15:07.392Z' ---- -## Reason -Store extracted factual statements from context - -## Raw Concept -**Task:** -Curated knowledge extraction - -**Flow:** -Extraction -> Deduplication -> Grouping -> Curation - -**Timestamp:** 2026-05-29T09:15:07.391Z - -## Narrative -### Structure -Aggregated extracted facts grouped by subject. - -### Highlights -Caplets - -## Facts -- **Caplets**: The headline is "Caplets: Give your agents capabilities, not giant tool walls" -- **Caplets**: Supporting subhead: "Caplets turns MCP servers, APIs, and commands into focused capabilities your agent can discover, inspect, and use one step at a time." -- **Caplets**: Shorter social version: "Stop dumping 100+ tools into your agent’s context. Caplets gives agents focused capabilities instead of giant tool walls." -- **Caplets**: GitHub description variant: "Progressive disclosure for agent tools: wrap MCP servers, APIs, and commands as focused capabilities instead of flat tool walls." -- **Caplets**: HN title variant: "Show HN: Caplets, give agents capabilities, not giant tool walls" -- **Caplets**: README hero: "Give your agents capabilities, not giant tool walls. Caplets turns MCP servers, APIs, and commands into focused capabilities: one card first, searchable tools next, inspectable schemas before calls, and preserved results after." diff --git a/.brv/context-tree/curation/extracted/extracted_context.md b/.brv/context-tree/curation/extracted/extracted_context.md deleted file mode 100644 index 7f556c70..00000000 --- a/.brv/context-tree/curation/extracted/extracted_context.md +++ /dev/null @@ -1,23 +0,0 @@ ---- -title: Extracted Context -summary: Curated extracted context from RLM task -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-29T09:13:04.949Z' -updatedAt: '2026-05-29T09:13:04.949Z' ---- -## Reason -Curate provided RLM context - -## Raw Concept -**Task:** -Curate provided context - -**Timestamp:** 2026-05-29T09:13:04.947Z - -## Narrative -### Structure -The following is a conversation between a user and an AI assistant. -Curate only information with lasting value: facts, decisions, technical details, preferences, or notable outcomes. -Skip trivial messages such as greetings, acknowledgments ("ok", "thanks", "sure", "got it"), one-word replies, anythi diff --git a/.brv/context-tree/design/caplets/caplets_findings.md b/.brv/context-tree/design/caplets/caplets_findings.md deleted file mode 100644 index c4163d2c..00000000 --- a/.brv/context-tree/design/caplets/caplets_findings.md +++ /dev/null @@ -1,36 +0,0 @@ ---- -title: Caplets Findings -summary: Extracted factual statements from caplets context -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T13:44:33.912Z' -updatedAt: '2026-05-28T13:44:33.912Z' ---- -## Reason -Curate extracted findings from caplets source - -## Raw Concept -**Task:** -Document caplets findings - -**Timestamp:** 2026-05-28T13:44:33.909Z - -## Narrative -### Structure -Aggregated factual statements extracted from caplets context - -### Highlights -1 statements for task, 3 statements for index.astro, 1 statements for content_visibility, 1 statements for global.css, 1 statements for typecheck, 1 statements for build, 1 statements for repository_state, 1 statements for review - -## Facts -- **task**: Implemented reveal motion resilience in apps/landing. -- **index.astro**: Removed the 1.8s global fallback in apps/landing/src/pages/index.astro. -- **content_visibility**: Keeps content visible by default unless IntersectionObserver is available and initialized. -- **index.astro**: Adds .motion-ready only after observer setup. -- **index.astro**: Adds a 100ms safety net if no observer update arrives. -- **global.css**: Reveal transition now uses only opacity and transform in apps/landing/src/styles/global.css. -- **typecheck**: pnpm --filter @caplets/landing typecheck passed with 0 errors. -- **build**: pnpm --filter @caplets/landing build passed, 1 page built. -- **repository_state**: Working tree contains unrelated pre-existing changes in global.css and other files. -- **review**: Recommended next step: review the landing diff before commit because of the pre-existing unrelated working tree changes. diff --git a/.brv/context-tree/design/caplets/caplets_module_findings.md b/.brv/context-tree/design/caplets/caplets_module_findings.md deleted file mode 100644 index 8031dd63..00000000 --- a/.brv/context-tree/design/caplets/caplets_module_findings.md +++ /dev/null @@ -1,106 +0,0 @@ ---- -title: Caplets Module Findings -summary: Extracted and organized factual statements about the caplets module -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T16:45:38.837Z' -updatedAt: '2026-05-28T16:45:38.837Z' ---- -## Reason -Curate extracted facts from caplets module context - -## Raw Concept -**Task:** -Document caplets module findings - -**Timestamp:** 2026-05-28T16:45:38.834Z - -## Narrative -### Structure -Aggregated facts from caplets module extraction - -### Highlights -PRODUCT.md, DESIGN.md, context loader, IMPECCABLE_CONTEXT_DIR, load-context.mjs, loader output, loader execution, context reload, live.mjs, craft task, design register, register identification, register inference, reference files, shared design laws, color model, hex colors, neutral tinting, design strategy, theme choice, line length, hierarchy, spacing, cards usage, nested cards, containers, animation, easing, side-stripe borders, gradient text, glassmorphism, hero-metric template, identical card grids, modal usage, copy, punctuation, failures, Category-reflex check, First-order reflex, Second-order reflex, `craft` command, `shape` command, `teach` command, `document` command, `extract` command, `critique` command, `audit` command, `polish` command, `bolder` command, `quieter` command, `distill` command, `harden` command, `onboard` command, `animate` command, `colorize` command, `typeset` command, `layout` command, `delight` command, `overdrive` command, `clarify` command, `adapt` command, `optimize` command, `live` command, management commands, Pin command, Unpin command, pin.mjs script, landing page update, index.astro, proof data, build process, pnpm format:check, pnpm typecheck, pnpm build, pnpm verify - -## Facts -- **PRODUCT.md**: PRODUCT.md is required and contains users, brand, tone, anti-references, and strategic principles. -- **DESIGN.md**: DESIGN.md is optional but strongly recommended and includes colors, typography, elevation, and components. -- **context loader**: The loader looks at the project root by default and falls back to .agents/context/ and docs/ if the root is clean. -- **IMPECCABLE_CONTEXT_DIR**: The context directory can be overridden with IMPECCABLE_CONTEXT_DIR=path/to/dir (absolute or relative to cwd). -- **load-context.mjs**: Both PRODUCT.md and DESIGN.md can be loaded in one call using node {{scripts_path}}/load-context.mjs. -- **loader output**: The output of the loader must not be piped through head, tail, grep, or jq. -- **loader execution**: If the loader output is already present in the session history, it should not be re-run. -- **context reload**: A fresh load is required after running {{command_prefix}}impeccable teach, {{command_prefix}}impeccable document, or when the user manually edits a file. -- **live.mjs**: If {{command_prefix}}impeccable live has been run, do not also run load-context.mjs in the same session. -- **PRODUCT.md**: If PRODUCT.md is missing, empty, or contains only placeholder [TODO] markers (<200 characters), run {{command_prefix}}impeccable teach and then resume the original task with fresh context. -- **craft task**: If the original task was {{command_prefix}}impeccable craft, resume into {{command_prefix}}impeccable shape before any implementation work after teaching. -- **DESIGN.md**: If DESIGN.md is missing, prompt the user once per session to run {{command_prefix}}impeccable document for more on‑brand output, then continue. -- **design register**: Every design task is classified as either brand (marketing, landing, campaign, long‑form content, portfolio) or product (app UI, admin, dashboard, tool). -- **register identification**: Design registration should be identified before designing, using priority: task cue, surface in focus, then register field in PRODUCT.md. -- **register inference**: If PRODUCT.md lacks a register field, infer it from the Users and Product Purpose sections, cache it for the session, and suggest running {{command_prefix}}impeccable teach to add the field explicitly. -- **reference files**: Load the matching reference file: reference/brand.md for brand tasks or reference/product.md for product tasks. -- **shared design laws**: Shared design laws apply to all designs; match implementation complexity to aesthetic vision—maximalism requires elaborate code, minimalism requires precision. -- **color model**: Use OKLCH. -- **hex colors**: Never use #000 or #fff. -- **neutral tinting**: Tint every neutral toward the brand hue (chroma 0.005–0.01 is enough). -- **design strategy**: Restrained: tinted neutrals + one accent ≤10%. -- **design strategy**: Committed: one saturated color carries 30–60% of the surface. -- **design strategy**: Full palette: 3–4 named roles, each used deliberately. -- **design strategy**: Drenched: the surface IS the color. -- **theme choice**: Dark vs. light is never a default. -- **line length**: Cap body line length at 65–75ch. -- **hierarchy**: Hierarchy through scale + weight contrast (≥1.25 ratio between steps). -- **spacing**: Vary spacing for rhythm. -- **cards usage**: Cards are the lazy answer. Use them only when they're truly the best affordance. -- **nested cards**: Nested cards are always wrong. -- **containers**: Don't wrap everything in a container. -- **animation**: Don't animate CSS layout properties. -- **easing**: Ease out with exponential curves (ease-out-quart / quint / expo). No bounce, no elastic. -- **side-stripe borders**: Side-stripe borders greater than 1px as a colored accent are never intentional. -- **gradient text**: Gradient text using background-clip: text combined with a gradient background is decorative, never meaningful. -- **glassmorphism**: Glassmorphism as default is banned. -- **hero-metric template**: The hero-metric template is a SaaS cliché. -- **identical card grids**: Identical card grids are banned. -- **modal usage**: Modal as first thought is usually laziness. -- **copy**: Every word earns its place. -- **punctuation**: No em dashes; use commas, colons, semicolons, periods, or parentheses. -- **failures**: Register-specific failures live in each reference. -- **Category-reflex check**: Category-reflex check runs at two altitudes; the second one catches what the first one misses. -- **First-order reflex**: First-order reflex occurs if someone could guess the theme and palette from the category alone. -- **Second-order reflex**: Second-order reflex occurs if someone could guess the aesthetic family from category-plus-anti-references. -- **`craft` command**: `craft [feature]` shapes and builds a feature end-to-end. -- **`shape` command**: `shape [feature]` plans UX/UI before writing code. -- **`teach` command**: `teach` sets up PRODUCT.md and DESIGN.md context. -- **`document` command**: `document` generates DESIGN.md from existing project code. -- **`extract` command**: `extract [target]` pulls reusable tokens and components into design system. -- **`critique` command**: `critique [target]` performs UX design review with heuristic scoring. -- **`audit` command**: `audit [target]` conducts technical quality checks such as accessibility, performance, and responsiveness. -- **`polish` command**: `polish [target]` provides a final quality pass before shipping. -- **`bolder` command**: `bolder [target]` amplifies safe or bland designs. -- **`quieter` command**: `quieter [target]` tones down aggressive or overstimulating designs. -- **`distill` command**: `distill [target]` strips to essence and removes complexity. -- **`harden` command**: `harden [target]` makes designs production-ready, handling errors, i18n, and edge cases. -- **`onboard` command**: `onboard [target]` designs first-run flows, empty states, and activation. -- **`animate` command**: `animate [target]` adds purposeful animations and motion. -- **`colorize` command**: `colorize [target]` adds strategic color to monochromatic UIs. -- **`typeset` command**: `typeset [target]` improves typography hierarchy and fonts. -- **`layout` command**: `layout [target]` fixes spacing, rhythm, and visual hierarchy. -- **`delight` command**: `delight [target]` adds personality and memorable touches. -- **`overdrive` command**: `overdrive [target]` pushes past conventional limits. -- **`clarify` command**: `clarify [target]` improves UX copy, labels, and error messages. -- **`adapt` command**: `adapt [target]` adapts designs for different devices and screen sizes. -- **`optimize` command**: `optimize [target]` diagnoses and fixes UI performance issues. -- **`live` command**: `live` enables visual variant mode to pick elements in the browser and generate alternatives. -- **management commands**: Two management commands are `pin ` and `unpin `. -- **Pin command**: Pin creates a standalone shortcut so `{{command_prefix}}` invokes `{{command_prefix}}impeccable ` directly. -- **Unpin command**: Unpin removes the shortcut created by Pin. -- **pin.mjs script**: The script writes to every harness directory present in the project. -- **landing page update**: Clarified `apps/landing/` copy and pushed it to PR #93. -- **index.astro**: Replaced remaining “backend” phrasing with clearer “tool source” language in `apps/landing/src/pages/index.astro`. -- **proof data**: Renamed the proof data from `skillifyFramework` to `capabilityFramework`. -- **build process**: Verification commands `pnpm format:check`, `pnpm typecheck`, `pnpm build`, and `pnpm verify` all passed. -- **pnpm format:check**: `pnpm format:check -- apps/landing/src/pages/index.astro` passed -- **pnpm typecheck**: `pnpm --filter @caplets/landing typecheck` passed -- **pnpm build**: `pnpm --filter @caplets/landing build` passed -- **pnpm verify**: `pnpm verify` passed diff --git a/.brv/context-tree/design/caplets/formatting_check.md b/.brv/context-tree/design/caplets/formatting_check.md deleted file mode 100644 index 36b2c26b..00000000 --- a/.brv/context-tree/design/caplets/formatting_check.md +++ /dev/null @@ -1,22 +0,0 @@ ---- -title: Formatting check -summary: Facts about formatting check -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T16:03:02.789Z' -updatedAt: '2026-05-28T16:03:02.789Z' ---- -## Reason -Curate extracted facts from context - -## Raw Concept -**Task:** -Document facts about formatting check - -## Narrative -### Highlights -Extracted 1 facts. - -## Facts -- **formatting check**: Ran formatting check successfully: pnpm format:check -- README.md. diff --git a/.brv/context-tree/design/caplets/hero_copy.md b/.brv/context-tree/design/caplets/hero_copy.md deleted file mode 100644 index e4744217..00000000 --- a/.brv/context-tree/design/caplets/hero_copy.md +++ /dev/null @@ -1,22 +0,0 @@ ---- -title: Hero copy -summary: Facts about hero copy -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T16:03:02.759Z' -updatedAt: '2026-05-28T16:03:02.759Z' ---- -## Reason -Curate extracted facts from context - -## Raw Concept -**Task:** -Document facts about hero copy - -## Narrative -### Highlights -Extracted 1 facts. - -## Facts -- **hero copy**: Updated hero copy to “Skillify your backends.” diff --git a/.brv/context-tree/design/caplets/landing_positioning.md b/.brv/context-tree/design/caplets/landing_positioning.md deleted file mode 100644 index 037a755a..00000000 --- a/.brv/context-tree/design/caplets/landing_positioning.md +++ /dev/null @@ -1,22 +0,0 @@ ---- -title: Landing positioning -summary: Facts about landing positioning -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T16:03:02.763Z' -updatedAt: '2026-05-28T16:03:02.763Z' ---- -## Reason -Curate extracted facts from context - -## Raw Concept -**Task:** -Document facts about landing positioning - -## Narrative -### Highlights -Extracted 1 facts. - -## Facts -- **landing positioning**: Matched landing positioning around staged discovery: one card, searchable tools, inspectable schemas, preserved results. diff --git a/.brv/context-tree/design/caplets/quick_start.md b/.brv/context-tree/design/caplets/quick_start.md deleted file mode 100644 index dd3350d3..00000000 --- a/.brv/context-tree/design/caplets/quick_start.md +++ /dev/null @@ -1,22 +0,0 @@ ---- -title: Quick Start -summary: Facts about Quick Start -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T16:03:02.770Z' -updatedAt: '2026-05-28T16:03:02.770Z' ---- -## Reason -Curate extracted facts from context - -## Raw Concept -**Task:** -Document facts about Quick Start - -## Narrative -### Highlights -Extracted 1 facts. - -## Facts -- **Quick Start**: Updated Quick Start to match landing install flow using npm install -g caplets. diff --git a/.brv/context-tree/design/caplets/readme_header_badges.md b/.brv/context-tree/design/caplets/readme_header_badges.md deleted file mode 100644 index b5c1c49f..00000000 --- a/.brv/context-tree/design/caplets/readme_header_badges.md +++ /dev/null @@ -1,22 +0,0 @@ ---- -title: README header badges -summary: Facts about README header badges -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T16:03:02.755Z' -updatedAt: '2026-05-28T16:03:02.755Z' ---- -## Reason -Curate extracted facts from context - -## Raw Concept -**Task:** -Document facts about README header badges - -## Narrative -### Highlights -Extracted 1 facts. - -## Facts -- **README header badges**: Added https://caplets.dev near the README header badges. diff --git a/.brv/context-tree/design/caplets/readme_md.md b/.brv/context-tree/design/caplets/readme_md.md deleted file mode 100644 index 41f16c8d..00000000 --- a/.brv/context-tree/design/caplets/readme_md.md +++ /dev/null @@ -1,22 +0,0 @@ ---- -title: README.md -summary: Facts about README.md -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T16:03:02.747Z' -updatedAt: '2026-05-28T16:03:02.747Z' ---- -## Reason -Curate extracted facts from context - -## Raw Concept -**Task:** -Document facts about README.md - -## Narrative -### Highlights -Extracted 1 facts. - -## Facts -- **README.md**: Updated README.md to align with apps/landing/ messaging and added the landing page link. diff --git a/.brv/context-tree/design/caplets/readme_section.md b/.brv/context-tree/design/caplets/readme_section.md deleted file mode 100644 index 513dae25..00000000 --- a/.brv/context-tree/design/caplets/readme_section.md +++ /dev/null @@ -1,22 +0,0 @@ ---- -title: README section -summary: Facts about README section -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T16:03:02.773Z' -updatedAt: '2026-05-28T16:03:02.773Z' ---- -## Reason -Curate extracted facts from context - -## Raw Concept -**Task:** -Document facts about README section - -## Narrative -### Highlights -Extracted 1 facts. - -## Facts -- **README section**: Added “Trust Before Invocation” section based on landing content. diff --git a/.brv/context-tree/design/caplets_copy/caplets_copy_guidance.md b/.brv/context-tree/design/caplets_copy/caplets_copy_guidance.md deleted file mode 100644 index 9bf63f7d..00000000 --- a/.brv/context-tree/design/caplets_copy/caplets_copy_guidance.md +++ /dev/null @@ -1,43 +0,0 @@ ---- -title: Caplets Copy Guidance -summary: Guidelines for skill-like terminology and placement in Caplets marketing -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T09:54:35.504Z' -updatedAt: '2026-05-28T09:54:35.504Z' ---- -## Reason -Curate branding copy decisions from conversation - -## Raw Concept -**Task:** -Document copy guidance for Caplets skill-like branding - -**Changes:** -- Defined copy guidance for skill-like terminology -- Specified placement recommendations - -**Flow:** -conversation -> extraction -> curation - -**Timestamp:** 2026-05-28T09:54:35.502Z - -**Author:** Ian Pascoe - -## Narrative -### Structure -Guidelines for using "skill-like" vs "skillify" in Caplets marketing copy - -### Highlights -Use skill-like explicitly, avoid skillify as primary heading, hierarchy of language - -### Examples -Caplets turns each backend into a skill-like capability... - -## Facts -- **copy guidance**: Use “skill-like” explicitly, but be careful with “skillify.” -- **Caplets functionality**: Caplets turns each backend into a skill-like capability: one card first, then searchable tools, inspectable schemas, and safe calls only when needed. -- **Caplets description**: Skill-like backends for coding agents: Caplets wraps MCP servers, APIs, and commands as focused capabilities with progressive disclosure built in. -- **placement of “skillify”**: Avoid leading with “skillify” in the H1 unless a more opinionated brand voice is desired; better places are a small supporting phrase, section heading, docs concept, or “What Caplets does” explainer. -- **language hierarchy**: Primary language hierarchy: Primary – capability cards; Secondary – skill-like progressive disclosure; Avoid as primary – skillify, unless defined. diff --git a/.brv/context-tree/design/caplets_curate/apps_landing_src_pages_index_astro.md b/.brv/context-tree/design/caplets_curate/apps_landing_src_pages_index_astro.md deleted file mode 100644 index a78535a1..00000000 --- a/.brv/context-tree/design/caplets_curate/apps_landing_src_pages_index_astro.md +++ /dev/null @@ -1,22 +0,0 @@ ---- -title: Apps/landing/src/pages/index.astro -summary: Facts about apps/landing/src/pages/index.astro -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T13:47:56.603Z' -updatedAt: '2026-05-28T13:47:56.603Z' ---- -## Reason -Curated factual statements extracted from source context - -## Raw Concept -**Task:** -Document facts for apps/landing/src/pages/index.astro - -## Narrative -### Highlights -Extracted 1 facts - -## Facts -- **apps/landing/src/pages/index.astro**: Changed file apps/landing/src/pages/index.astro. diff --git a/.brv/context-tree/design/caplets_curate/build.md b/.brv/context-tree/design/caplets_curate/build.md deleted file mode 100644 index 01b1a86e..00000000 --- a/.brv/context-tree/design/caplets_curate/build.md +++ /dev/null @@ -1,22 +0,0 @@ ---- -title: Build -summary: Facts about build -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T13:47:56.618Z' -updatedAt: '2026-05-28T13:47:56.618Z' ---- -## Reason -Curated factual statements extracted from source context - -## Raw Concept -**Task:** -Document facts for build - -## Narrative -### Highlights -Extracted 1 facts - -## Facts -- **build**: pnpm --filter @caplets/landing build passed, 1 page built. diff --git a/.brv/context-tree/design/caplets_curate/context.md b/.brv/context-tree/design/caplets_curate/context.md deleted file mode 100644 index b1faf224..00000000 --- a/.brv/context-tree/design/caplets_curate/context.md +++ /dev/null @@ -1,10 +0,0 @@ -# Topic: caplets_curate - -## Overview -Review of integration tab/panel markup and JS for accessibility after progressive enhancement fix - -## Key Concepts -- ARIA roles -- tablist -- tabpanel -- progressive enhancement diff --git a/.brv/context-tree/design/caplets_curate/curated_caplets_context.md b/.brv/context-tree/design/caplets_curate/curated_caplets_context.md deleted file mode 100644 index 48148b98..00000000 --- a/.brv/context-tree/design/caplets_curate/curated_caplets_context.md +++ /dev/null @@ -1,35 +0,0 @@ ---- -title: Curated Caplets Context -summary: Curated factual statements extracted from caplets context -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T09:53:07.807Z' -updatedAt: '2026-05-28T09:54:29.630Z' ---- -## Reason -Add curated facts from caplets context - -## Raw Concept -**Task:** -Curate extracted facts from caplets context - -**Changes:** -- Extracted factual statements - -**Flow:** -Extraction -> Deduplication -> Curation - -**Timestamp:** 2026-05-28T09:54:29.622Z - -## Narrative -### Structure -Curated facts from caplets context - -### Highlights -skill-like progressive disclosure placement, Caplets functionality, hero trace demonstration - -## Facts -- **skill-like progressive disclosure placement**: The skill-like progressive disclosure idea should live in the hero subhead and be demonstrated in the hero trace. -- **Caplets functionality**: Caplets turns MCP servers, APIs, and commands into skill-like capabilities, presented as one card first, then searchable tools, inspectable schemas, and safe calls only when needed. -- **hero trace demonstration**: The hero trace demonstrates the process: github get_caplet exposes search_tools, get_tool, call_tool; search_tools("pull request") returns create_pull_request, request_review...; get_tool("create_pull_request") preserves schema and redacts auth; call_tool(... ) preserves structured result. diff --git a/.brv/context-tree/design/caplets_curate/curated_context.md b/.brv/context-tree/design/caplets_curate/curated_context.md deleted file mode 100644 index 453782f2..00000000 --- a/.brv/context-tree/design/caplets_curate/curated_context.md +++ /dev/null @@ -1,63 +0,0 @@ ---- -title: curated_context -summary: Curated factual statements from provided context -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T13:45:50.257Z' -updatedAt: '2026-05-28T13:45:50.257Z' ---- -## Reason -Curate extracted knowledge from RLM extraction - -## Raw Concept -**Task:** -Curate extracted knowledge from provided context - -**Flow:** -extraction -> deduplication -> curation - -**Timestamp:** 2026-05-28T13:45:50.253Z - -## Narrative -### Structure -Organized by subject - -### Highlights -Extracted and deduplicated factual statements - -### Examples -[ - { - "statement": "apps/landing/src/pages/index.astro:523-558 now only enables reveal enhancement when canAnimate is true and IntersectionObserver exists.", - "subject": "apps/landing/src/pages/index.astro" - }, - { - "statement": "Content remains visible by default because hidden reveal styles are gated behind .motion-ready, added only after observer setup begins successfully.", - "subject": ".motion-ready CSS gating" - }, - { - "statement": "The former 1.8s fallback is shortened to 100ms at index.astro:553-558.", - "subject": "apps/landing/src/pages/index.astro" - }, - { - "statement": "Reduced-motion behavior is preserved via canAnimate check and CSS override at apps/landing/src/styles/global.css:1261-1285.", - "subject": "apps/landing/src/styles/global.css" - }, - { - "statement": "Reveal transition now uses only opacity and transform at global.css:561-563; the prior border-color transition was removed.", - "subject": "apps/landing/src/styles/global.css" - }, - { - "statement": "No reveal-related content or copy changes were found, and no em dashes were introduced in the reviewed reveal-motion changes.", - "subject": "reveal-motion changes" - } -] - -## Facts -- **apps/landing/src/pages/index.astro**: apps/landing/src/pages/index.astro:523-558 now only enables reveal enhancement when canAnimate is true and IntersectionObserver exists. -- **.motion-ready CSS gating**: Content remains visible by default because hidden reveal styles are gated behind .motion-ready, added only after observer setup begins successfully. -- **apps/landing/src/pages/index.astro**: The former 1.8s fallback is shortened to 100ms at index.astro:553-558. -- **apps/landing/src/styles/global.css**: Reduced-motion behavior is preserved via canAnimate check and CSS override at apps/landing/src/styles/global.css:1261-1285. -- **apps/landing/src/styles/global.css**: Reveal transition now uses only opacity and transform at global.css:561-563; the prior border-color transition was removed. -- **reveal-motion changes**: No reveal-related content or copy changes were found, and no em dashes were introduced in the reviewed reveal-motion changes. diff --git a/.brv/context-tree/design/caplets_curate/integration_tabs_and_panels.md b/.brv/context-tree/design/caplets_curate/integration_tabs_and_panels.md deleted file mode 100644 index 2281c71c..00000000 --- a/.brv/context-tree/design/caplets_curate/integration_tabs_and_panels.md +++ /dev/null @@ -1,22 +0,0 @@ ---- -title: Integration tabs and panels -summary: Facts about integration tabs and panels -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T13:47:56.602Z' -updatedAt: '2026-05-28T13:47:56.602Z' ---- -## Reason -Curated factual statements extracted from source context - -## Raw Concept -**Task:** -Document facts for integration tabs and panels - -## Narrative -### Highlights -Extracted 1 facts - -## Facts -- **integration tabs and panels**: Implemented server-rendered accessibility state for integration tabs and panels. diff --git a/.brv/context-tree/design/caplets_curate/panel_elements.md b/.brv/context-tree/design/caplets_curate/panel_elements.md deleted file mode 100644 index 523728df..00000000 --- a/.brv/context-tree/design/caplets_curate/panel_elements.md +++ /dev/null @@ -1,22 +0,0 @@ ---- -title: Panel elements -summary: Facts about panel elements -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T13:47:56.607Z' -updatedAt: '2026-05-28T13:47:56.607Z' ---- -## Reason -Curated factual statements extracted from source context - -## Raw Concept -**Task:** -Document facts for panel elements - -## Narrative -### Highlights -Extracted 1 facts - -## Facts -- **panel elements**: Added server-rendered panel attributes: role="tabpanel", aria-labelledby, hidden for inactive panels. diff --git a/.brv/context-tree/design/caplets_curate/skillify_phrasing.md b/.brv/context-tree/design/caplets_curate/skillify_phrasing.md deleted file mode 100644 index 0ab45ed3..00000000 --- a/.brv/context-tree/design/caplets_curate/skillify_phrasing.md +++ /dev/null @@ -1,44 +0,0 @@ ---- -title: skillify_phrasing -summary: Guidance on phrasing "skillify" vs "skill-like" for Caplets marketing copy -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T09:55:48.891Z' -updatedAt: '2026-05-28T09:55:48.891Z' ---- -## Reason -Document copy guidance for using "skillify" branding - -## Raw Concept -**Task:** -Define copy guidelines for "skillify" branding - -**Changes:** -- Add guidance to use "skill-like" explicitly -- Recommend placement of "skillify" term -- Establish language hierarchy - -**Flow:** -copy creation -> review -> placement decision - -**Timestamp:** 2026-05-28T09:54:35.525Z - -**Author:** Ian Pascoe - -## Narrative -### Structure -Guidelines for marketing copy on Caplets backend skillification - -### Highlights -Use "skill-like" explicitly; avoid leading with "skillify" unless defined; hierarchy: primary capability cards, secondary skill-like progressive disclosure - -### Examples -Caplets turns each backend into a skill-like capability: one card first, then searchable tools, inspectable schemas, and safe calls only when needed. - -## Facts -- **copy_guidance**: Use “skill-like” explicitly, but be careful with “skillify.” [preference] -- **caplets_functionality**: Caplets turns each backend into a skill-like capability: one card first, then searchable tools, inspectable schemas, and safe calls only when needed. [project] -- **caplets_description**: Skill-like backends for coding agents: Caplets wraps MCP servers, APIs, and commands as focused capabilities with progressive disclosure built in. [project] -- **skillify_placement**: Avoid leading with “skillify” in the H1 unless a more opinionated brand voice is desired; better places are a small supporting phrase, section heading, docs concept, or “What Caplets does” explainer. [convention] -- **language_hierarchy**: Primary language hierarchy: Primary – capability cards; Secondary – skill-like progressive disclosure; Avoid as primary – skillify, unless defined. [convention] diff --git a/.brv/context-tree/design/caplets_curate/tab_container.md b/.brv/context-tree/design/caplets_curate/tab_container.md deleted file mode 100644 index 4fe79106..00000000 --- a/.brv/context-tree/design/caplets_curate/tab_container.md +++ /dev/null @@ -1,22 +0,0 @@ ---- -title: Tab container -summary: Facts about tab container -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T13:47:56.605Z' -updatedAt: '2026-05-28T13:47:56.605Z' ---- -## Reason -Curated factual statements extracted from source context - -## Raw Concept -**Task:** -Document facts for tab container - -## Narrative -### Highlights -Extracted 1 facts - -## Facts -- **tab container**: Added role="tablist" to the tab container. diff --git a/.brv/context-tree/design/caplets_curate/tab_elements.md b/.brv/context-tree/design/caplets_curate/tab_elements.md deleted file mode 100644 index 52ae548b..00000000 --- a/.brv/context-tree/design/caplets_curate/tab_elements.md +++ /dev/null @@ -1,22 +0,0 @@ ---- -title: Tab elements -summary: Facts about tab elements -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T13:47:56.606Z' -updatedAt: '2026-05-28T13:47:56.606Z' ---- -## Reason -Curated factual statements extracted from source context - -## Raw Concept -**Task:** -Document facts for tab elements - -## Narrative -### Highlights -Extracted 1 facts - -## Facts -- **tab elements**: Added server-rendered tab ARIA/state attributes: role="tab", aria-selected, aria-controls, tabindex. diff --git a/.brv/context-tree/design/caplets_curate/task_3_review_progressive_enhancement.md b/.brv/context-tree/design/caplets_curate/task_3_review_progressive_enhancement.md deleted file mode 100644 index 6ef6e0c0..00000000 --- a/.brv/context-tree/design/caplets_curate/task_3_review_progressive_enhancement.md +++ /dev/null @@ -1,46 +0,0 @@ ---- -title: task_3_review_progressive_enhancement -summary: Task 3 re-review approved; ARIA roles and JS hidden state compliance -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T13:54:37.545Z' -updatedAt: '2026-05-28T13:54:37.545Z' ---- -## Reason -Document review outcome for accessibility spec of integration tab/panel - -## Raw Concept -**Task:** -Re-review Task 3 after progressive enhancement fix - -**Changes:** -- Reviewed ARIA roles, tablist, panel attributes -- Confirmed no content changes needed - -**Files:** -- apps/landing/src/pages/index.astro - -**Flow:** -Review markup and JS for ARIA compliance and progressive enhancement - -**Timestamp:** 2026-05-28T13:54:37.543Z - -**Author:** assistant - -## Narrative -### Structure -Server-render tablist with role and ARIA state, panels with role=tabpanel and aria-labelledby, JS applies initial hidden state after init - -### Highlights -APPROVED - -### Rules -- Server-render tablist role and ARIA state where safe -- Panels must have role=tabpanel and aria-labelledby -- No server-side hidden on inactive panels unless non-JS fallback exposes all content -- JS applies initial hidden state after initialization and preserves click/keyboard behavior -- No content/copy changes, no em dashes - -## Facts -- **task_3_review**: Review outcome: APPROVED [other] diff --git a/.brv/context-tree/design/caplets_curate/typecheck.md b/.brv/context-tree/design/caplets_curate/typecheck.md deleted file mode 100644 index e0735f1b..00000000 --- a/.brv/context-tree/design/caplets_curate/typecheck.md +++ /dev/null @@ -1,22 +0,0 @@ ---- -title: Typecheck -summary: Facts about typecheck -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T13:47:56.610Z' -updatedAt: '2026-05-28T13:47:56.610Z' ---- -## Reason -Curated factual statements extracted from source context - -## Raw Concept -**Task:** -Document facts for typecheck - -## Narrative -### Highlights -Extracted 1 facts - -## Facts -- **typecheck**: pnpm --filter @caplets/landing typecheck passed with 0 errors, 0 warnings, 0 hints. diff --git a/.brv/context-tree/design/caplets_curate/working_module.md b/.brv/context-tree/design/caplets_curate/working_module.md deleted file mode 100644 index e00e213a..00000000 --- a/.brv/context-tree/design/caplets_curate/working_module.md +++ /dev/null @@ -1,53 +0,0 @@ ---- -title: working_module -summary: Aggregated factual statements from working module -tags: [] -related: [design/landing_page/landing_page_review_findings.md, design/landing_page/skillify_landing_page_review.md, design/landing_page/skillify_landing_page_review_2026_05_28.md, design/landing_page/skillify_landing_page_task_7_review.md, design/landing_page/task_8_responsive_pass.md] -keywords: [] -createdAt: '2026-05-28T10:52:25.575Z' -updatedAt: '2026-05-28T10:52:25.575Z' ---- -## Reason -Curate extracted facts from working module context - -## Raw Concept -**Task:** -Document working module findings - -**Timestamp:** 2026-05-28T10:52:25.574Z - -## Narrative -### Structure -Aggregated facts from working module extraction - -### Highlights -Key factual statements extracted. - -## Facts -- **assistant**: The assistant implemented the full plan with subagent-driven development and `impeccable` review. -- **project**: Files changed: `apps/landing/src/pages/index.astro`, `apps/landing/src/styles/global.css`, `docs/plans/2026-05-28-skillify-landing-page.md`. -- **landing_page**: Hero was rebuilt around “Skillify your backends.” -- **landing_page**: Added realistic GitHub capability trace with source, status, redacted auth, schema inspection, and preserved result. -- **landing_page**: Added “What skillify means” framework. -- **landing_page**: Added concrete trust mechanics section. -- **documentation**: Normalized install command to `npm install -g caplets`. -- **UI**: Copy buttons only appear when JavaScript is enabled. -- **UI**: Integration panels are accessible without JavaScript. -- **codebase**: Removed old map/cartography/pointer/route choreography. -- **codebase**: Removed hero/header page-load choreography. -- **UI**: Fixed responsive behavior for trace, trust cards, snippets, and enhanced tabs. -- **footer**: Added stable `Config docs` footer link to the README `#configure` anchor. -- **spec_compliance**: Final spec compliance review: APPROVED. -- **code_design_quality**: Final code/design quality review: APPROVED. -- **typecheck**: Verification passed: `pnpm --filter @caplets/landing typecheck` succeeded. -- **build**: Verification passed: `pnpm --filter @caplets/landing build` succeeded. -- **format**: Verification passed: `pnpm format:check` succeeded. -- **lint**: Verification passed: `pnpm lint` succeeded. -- **typecheck**: Verification passed: `pnpm typecheck` succeeded. -- **impeccable**: `npx impeccable --json apps/landing/src/pages/index.astro` returned an empty array. -- **tests**: `pnpm verify` passed with 622 tests passed. -- **pnpm verify**: pnpm verify passed, 622 tests passed -- **Astro**: Astro still emits existing Node [DEP0205] module.register() warnings -- **working tree**: The working tree includes unrelated pre-existing changes outside the landing files and plan, including .brv/ and workflow files -- **Astro**: Astro still emits existing Node `[DEP0205] module.register()` warnings. -- **working tree**: The working tree includes unrelated pre-existing changes outside the landing files and plan, including `.brv/` and workflow files. diff --git a/.brv/context-tree/design/caplets_curate/working_tree.md b/.brv/context-tree/design/caplets_curate/working_tree.md deleted file mode 100644 index cb1cb17a..00000000 --- a/.brv/context-tree/design/caplets_curate/working_tree.md +++ /dev/null @@ -1,22 +0,0 @@ ---- -title: Working tree -summary: Facts about working tree -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T13:47:56.619Z' -updatedAt: '2026-05-28T13:47:56.619Z' ---- -## Reason -Curated factual statements extracted from source context - -## Raw Concept -**Task:** -Document facts for working tree - -## Narrative -### Highlights -Extracted 1 facts - -## Facts -- **working tree**: Working tree contains many pre-existing unrelated modifications, including unrelated hunks in apps/landing/src/pages/index.astro. diff --git a/.brv/context-tree/design/context.md b/.brv/context-tree/design/context.md deleted file mode 100644 index 792fc3c9..00000000 --- a/.brv/context-tree/design/context.md +++ /dev/null @@ -1,16 +0,0 @@ -# Domain: design - -## Purpose -Capture design guidelines for the Impeccable skill used to craft frontend interfaces - -## Scope -Included in this domain: -- design laws -- command reference -- setup procedures - -Excluded from this domain: -- implementation code - -## Ownership -Impeccable team diff --git a/.brv/context-tree/design/curated_facts/extracted_facts.md b/.brv/context-tree/design/curated_facts/extracted_facts.md deleted file mode 100644 index ab1ef059..00000000 --- a/.brv/context-tree/design/curated_facts/extracted_facts.md +++ /dev/null @@ -1,39 +0,0 @@ ---- -title: Extracted Facts -summary: Contains 11 deduplicated facts extracted from source context -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T13:49:52.994Z' -updatedAt: '2026-05-28T13:49:52.994Z' ---- -## Reason -Store factual statements extracted from RLM context - -## Raw Concept -**Task:** -Curate extracted factual statements from provided context - -**Timestamp:** 2026-05-28T13:49:52.993Z - -**Author:** ByteRover Context Engineer - -## Narrative -### Structure -Facts extracted and organized by subject - -### Highlights -Extracted 11 unique facts across 8 subjects. - -## Facts -- **tab component**: Server-rendered tab ARIA is coherent: each tab has role="tab", aria-selected, aria-controls, roving tabindex; each panel has role="tabpanel" and aria-labelledby -- **tab component**: JS selection state matches the server-rendered IDs and updates aria-selected, tabIndex, and hidden consistently -- **tab component**: Keyboard support covers Left/Right/Home/End navigation with focus movement and activation -- **tab component**: There is duplicate state between SSR and JS for role, aria-controls, and aria-labelledby, but it is currently consistent and not conflicting -- **SSR/JS synchronization**: There is duplicate state between SSR and JS for `role`, `aria-controls`, and `aria-labelledby`, but it is currently consistent and not conflicting. -- **progressive enhancement**: Progressive enhancement regression is identified as a blocker. -- **inactive panels**: Inactive panels are rendered with `hidden` server-side, but tab switching only works after JS initializes. -- **no-JS scenario**: With JavaScript disabled or failed, users can only access the first integration panel, and the remaining server-rendered content is unavailable. -- **panel rendering**: The recommended action is to render all panels accessible by default, then have JS apply the initial tab state on hydration/init, or provide a non-JS fallback that exposes all panels. -- **approval**: The issue is not approved until the no-JS content access problem is fixed. -- **ls**: ls accessible by default, then have JS apply the initial tab state on hydration/init, or provide a non-JS fallback that exposes all panels. diff --git a/.brv/context-tree/design/impeccable/context.md b/.brv/context-tree/design/impeccable/context.md deleted file mode 100644 index 1e4751e3..00000000 --- a/.brv/context-tree/design/impeccable/context.md +++ /dev/null @@ -1,10 +0,0 @@ -# Topic: impeccable - -## Overview -Impeccable skill provides design commands and enforces strict visual and interaction standards - -## Key Concepts -- context loading -- design laws -- absolute bans -- command routing diff --git a/.brv/context-tree/design/impeccable/impeccable_skill_guidelines.md b/.brv/context-tree/design/impeccable/impeccable_skill_guidelines.md deleted file mode 100644 index 0ea1f2e2..00000000 --- a/.brv/context-tree/design/impeccable/impeccable_skill_guidelines.md +++ /dev/null @@ -1,43 +0,0 @@ ---- -title: Impeccable Skill Guidelines -summary: Guidelines for Impeccable skill including context loading, design laws, bans, and commands -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T18:19:14.435Z' -updatedAt: '2026-05-27T18:19:14.435Z' ---- -## Reason -Document the Impeccable skill design guidelines and commands - -## Raw Concept -**Task:** -Provide design guidelines and command reference for Impeccable skill - -**Changes:** -- Defined required PRODUCT.md and optional DESIGN.md -- Specified color strategy and bans -- Listed commands and routing rules - -**Flow:** -load context -> identify register -> apply design laws -> execute command - -**Timestamp:** 2026-05-27T18:18:06.366Z - -**Author:** Impeccable team - -## Narrative -### Structure -Conversation outlines setup, shared design laws, absolute bans, command table, and routing rules - -### Dependencies -Requires PRODUCT.md and optionally DESIGN.md in project root or .agents/context/ - -### Highlights -Enforces color usage, bans side-stripe borders, gradient text, glassmorphism, hero-metric template, identical card grids, modal first thought, no em dashes - -### Rules -Absolute bans list with specific CSS restrictions and design anti-patterns - -### Examples -Command table with craft, shape, teach, etc. diff --git a/.brv/context-tree/design/landing_fixes/audit_fixes_fix.md b/.brv/context-tree/design/landing_fixes/audit_fixes_fix.md deleted file mode 100644 index 12699887..00000000 --- a/.brv/context-tree/design/landing_fixes/audit_fixes_fix.md +++ /dev/null @@ -1,42 +0,0 @@ ---- -title: audit fixes fix -summary: 'Landing page fix: audit fixes' -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T11:24:30.738Z' -updatedAt: '2026-05-28T11:24:30.738Z' ---- -## Reason -Curate landing page audit fixes - -## Raw Concept -**Task:** -Document landing page fix: audit fixes - -**Changes:** -- Implemented all audit fixes with subagent-driven development. - -**Files:** -- apps/landing/src/pages/index.astro -- apps/landing/src/styles/global.css - -**Flow:** -audit -> fix -> verification - -**Timestamp:** 2026-05-28T11:24:30.735Z - -**Author:** Ian Pascoe - -## Narrative -### Structure -Fixes applied to landing page and styles - -### Dependencies -Requires build and lint verification - -### Highlights -Implemented all audit fixes with subagent-driven development. - -## Facts -- **audit fixes**: Implemented all audit fixes with subagent-driven development. diff --git a/.brv/context-tree/design/landing_fixes/audit_fixes_summary.md b/.brv/context-tree/design/landing_fixes/audit_fixes_summary.md deleted file mode 100644 index 30886deb..00000000 --- a/.brv/context-tree/design/landing_fixes/audit_fixes_summary.md +++ /dev/null @@ -1,72 +0,0 @@ ---- -title: Audit Fixes Summary -summary: Summary of audit fixes including touch targets, reveal motion, integration tabs, and verification steps -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T14:07:06.849Z' -updatedAt: '2026-05-28T14:07:06.849Z' ---- -## Reason -Document audit fixes performed on landing page - -## Raw Concept -**Task:** -Apply audit fixes to landing page - -**Changes:** -- Mobile touch targets fixed -- Reveal motion resilience fixed -- Integration tabs hardened -- Repeated kicker scaffolding reduced -- Low-priority hover motion quieted -- Stale --header-shadow token removed -- `.tool-noise` mask no longer uses literal black - -**Files:** -- apps/landing/src/pages/index.astro -- apps/landing/src/styles/global.css - -**Flow:** -Implement fixes -> Verify typecheck & build -> Browser checks - -**Timestamp:** 2026-05-28T14:07:06.844Z - -## Narrative -### Structure -Fixes applied to landing page source files and verified via typecheck, build, and visual browser checks - -### Highlights -All audit fixes passed verification; mobile touch targets meet 44px minimum, no overflow at 320px and 1440px - -### Examples -Implemented all audit fixes via subagent-driven development with implementer and reviewer passes. -Mobile touch targets fixed. -Reveal motion resilience fixed. -Integration tabs hardened with safe progressive enhancement. -Repeated kicker scaffolding reduced. -Low-priority hover motion quieted. -Stale `--header-shadow` token removed. -`.tool-noise` mask no longer uses literal `black`. -Final subagent review approved. -Changed landing files: `apps/landing/src/pages/index.astro` and `apps/landing/src/styles/global.css`. -Verification passed: `pnpm --filter @caplets/landing typecheck`. -Verification passed: `pnpm --filter @caplets/landing build`. -Browser check at `320px` showed no horizontal overflow and no visible `a`/`button` targets below `44px`. -Browser check at `1440px` showed hero remains cleanly wrapped and trace card remains `660px` wide with no trace overflow. - -## Facts -- **audit fixes**: Implemented all audit fixes via subagent-driven development with implementer and reviewer passes. -- **mobile touch targets**: Mobile touch targets fixed. -- **reveal motion resilience**: Reveal motion resilience fixed. -- **integration tabs**: Integration tabs hardened with safe progressive enhancement. -- **kicker scaffolding**: Repeated kicker scaffolding reduced. -- **hover motion**: Low-priority hover motion quieted. -- **header-shadow token**: Stale `--header-shadow` token removed. -- **tool-noise mask**: `.tool-noise` mask no longer uses literal `black`. -- **subagent review**: Final subagent review approved. -- **landing files**: Changed landing files: `apps/landing/src/pages/index.astro` and `apps/landing/src/styles/global.css`. -- **typecheck**: Verification passed: `pnpm --filter @caplets/landing typecheck`. -- **build**: Verification passed: `pnpm --filter @caplets/landing build`. -- **320px browser check**: Browser check at `320px` showed no horizontal overflow and no visible `a`/`button` targets below `44px`. -- **1440px browser check**: Browser check at `1440px` showed hero remains cleanly wrapped and trace card remains `660px` wide with no trace overflow. diff --git a/.brv/context-tree/design/landing_fixes/build_and_lint_checks_fix.md b/.brv/context-tree/design/landing_fixes/build_and_lint_checks_fix.md deleted file mode 100644 index 1aefdc89..00000000 --- a/.brv/context-tree/design/landing_fixes/build_and_lint_checks_fix.md +++ /dev/null @@ -1,42 +0,0 @@ ---- -title: build and lint checks fix -summary: 'Landing page fix: build and lint checks' -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T11:24:30.776Z' -updatedAt: '2026-05-28T11:24:30.776Z' ---- -## Reason -Curate landing page audit fixes - -## Raw Concept -**Task:** -Document landing page fix: build and lint checks - -**Changes:** -- Verification passed: pnpm typecheck, build, format:check, lint, and impeccable JSON check returned an empty array. - -**Files:** -- apps/landing/src/pages/index.astro -- apps/landing/src/styles/global.css - -**Flow:** -audit -> fix -> verification - -**Timestamp:** 2026-05-28T11:24:30.735Z - -**Author:** Ian Pascoe - -## Narrative -### Structure -Fixes applied to landing page and styles - -### Dependencies -Requires build and lint verification - -### Highlights -Verification passed: pnpm typecheck, build, format:check, lint, and impeccable JSON check returned an empty array. - -## Facts -- **build and lint checks**: Verification passed: pnpm typecheck, build, format:check, lint, and impeccable JSON check returned an empty array. diff --git a/.brv/context-tree/design/landing_fixes/clipboard_fallback_fix.md b/.brv/context-tree/design/landing_fixes/clipboard_fallback_fix.md deleted file mode 100644 index 4efa5660..00000000 --- a/.brv/context-tree/design/landing_fixes/clipboard_fallback_fix.md +++ /dev/null @@ -1,42 +0,0 @@ ---- -title: clipboard fallback fix -summary: 'Landing page fix: clipboard fallback' -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T11:24:30.745Z' -updatedAt: '2026-05-28T11:24:30.745Z' ---- -## Reason -Curate landing page audit fixes - -## Raw Concept -**Task:** -Document landing page fix: clipboard fallback - -**Changes:** -- Improved clipboard fallback by associating copy buttons with snippet targets, focusing/selecting snippet text on failed write, and retaining a textarea fallback. - -**Files:** -- apps/landing/src/pages/index.astro -- apps/landing/src/styles/global.css - -**Flow:** -audit -> fix -> verification - -**Timestamp:** 2026-05-28T11:24:30.735Z - -**Author:** Ian Pascoe - -## Narrative -### Structure -Fixes applied to landing page and styles - -### Dependencies -Requires build and lint verification - -### Highlights -Improved clipboard fallback by associating copy buttons with snippet targets, focusing/selecting snippet text on failed write, and retaining a textarea fallback. - -## Facts -- **clipboard fallback**: Improved clipboard fallback by associating copy buttons with snippet targets, focusing/selecting snippet text on failed write, and retaining a textarea fallback. diff --git a/.brv/context-tree/design/landing_fixes/code_changes_fix.md b/.brv/context-tree/design/landing_fixes/code_changes_fix.md deleted file mode 100644 index c37e7425..00000000 --- a/.brv/context-tree/design/landing_fixes/code_changes_fix.md +++ /dev/null @@ -1,42 +0,0 @@ ---- -title: code changes fix -summary: 'Landing page fix: code changes' -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T11:24:30.778Z' -updatedAt: '2026-05-28T11:24:30.778Z' ---- -## Reason -Curate landing page audit fixes - -## Raw Concept -**Task:** -Document landing page fix: code changes - -**Changes:** -- Diff size for audit fixes/current landing changes: 108 insertions and 50 deletions across the two files. - -**Files:** -- apps/landing/src/pages/index.astro -- apps/landing/src/styles/global.css - -**Flow:** -audit -> fix -> verification - -**Timestamp:** 2026-05-28T11:24:30.736Z - -**Author:** Ian Pascoe - -## Narrative -### Structure -Fixes applied to landing page and styles - -### Dependencies -Requires build and lint verification - -### Highlights -Diff size for audit fixes/current landing changes: 108 insertions and 50 deletions across the two files. - -## Facts -- **code changes**: Diff size for audit fixes/current landing changes: 108 insertions and 50 deletions across the two files. diff --git a/.brv/context-tree/design/landing_fixes/color_values_fix.md b/.brv/context-tree/design/landing_fixes/color_values_fix.md deleted file mode 100644 index ae5905ac..00000000 --- a/.brv/context-tree/design/landing_fixes/color_values_fix.md +++ /dev/null @@ -1,42 +0,0 @@ ---- -title: color values fix -summary: 'Landing page fix: color values' -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T11:24:30.758Z' -updatedAt: '2026-05-28T11:24:30.758Z' ---- -## Reason -Curate landing page audit fixes - -## Raw Concept -**Task:** -Document landing page fix: color values - -**Changes:** -- Consolidated direct OKLCH values into semantic CSS tokens where appropriate. - -**Files:** -- apps/landing/src/pages/index.astro -- apps/landing/src/styles/global.css - -**Flow:** -audit -> fix -> verification - -**Timestamp:** 2026-05-28T11:24:30.735Z - -**Author:** Ian Pascoe - -## Narrative -### Structure -Fixes applied to landing page and styles - -### Dependencies -Requires build and lint verification - -### Highlights -Consolidated direct OKLCH values into semantic CSS tokens where appropriate. - -## Facts -- **color values**: Consolidated direct OKLCH values into semantic CSS tokens where appropriate. diff --git a/.brv/context-tree/design/landing_fixes/css_tokens_selectors_fix.md b/.brv/context-tree/design/landing_fixes/css_tokens_selectors_fix.md deleted file mode 100644 index f908f6f4..00000000 --- a/.brv/context-tree/design/landing_fixes/css_tokens_selectors_fix.md +++ /dev/null @@ -1,42 +0,0 @@ ---- -title: CSS tokens/selectors fix -summary: 'Landing page fix: CSS tokens/selectors' -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T11:24:30.760Z' -updatedAt: '2026-05-28T11:24:30.760Z' ---- -## Reason -Curate landing page audit fixes - -## Raw Concept -**Task:** -Document landing page fix: CSS tokens/selectors - -**Changes:** -- Removed stale tokens and selectors from the map refactor. - -**Files:** -- apps/landing/src/pages/index.astro -- apps/landing/src/styles/global.css - -**Flow:** -audit -> fix -> verification - -**Timestamp:** 2026-05-28T11:24:30.735Z - -**Author:** Ian Pascoe - -## Narrative -### Structure -Fixes applied to landing page and styles - -### Dependencies -Requires build and lint verification - -### Highlights -Removed stale tokens and selectors from the map refactor. - -## Facts -- **CSS tokens/selectors**: Removed stale tokens and selectors from the map refactor. diff --git a/.brv/context-tree/design/landing_fixes/integration_tabs_fix.md b/.brv/context-tree/design/landing_fixes/integration_tabs_fix.md deleted file mode 100644 index 579f14cb..00000000 --- a/.brv/context-tree/design/landing_fixes/integration_tabs_fix.md +++ /dev/null @@ -1,42 +0,0 @@ ---- -title: integration tabs fix -summary: 'Landing page fix: integration tabs' -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T11:24:30.743Z' -updatedAt: '2026-05-28T11:24:30.743Z' ---- -## Reason -Curate landing page audit fixes - -## Raw Concept -**Task:** -Document landing page fix: integration tabs - -**Changes:** -- Converted integration tabs to true progressive enhancement, removing misleading tab ARIA in static/no-JS HTML and adding ARIA attributes via JavaScript. - -**Files:** -- apps/landing/src/pages/index.astro -- apps/landing/src/styles/global.css - -**Flow:** -audit -> fix -> verification - -**Timestamp:** 2026-05-28T11:24:30.735Z - -**Author:** Ian Pascoe - -## Narrative -### Structure -Fixes applied to landing page and styles - -### Dependencies -Requires build and lint verification - -### Highlights -Converted integration tabs to true progressive enhancement, removing misleading tab ARIA in static/no-JS HTML and adding ARIA attributes via JavaScript. - -## Facts -- **integration tabs**: Converted integration tabs to true progressive enhancement, removing misleading tab ARIA in static/no-JS HTML and adding ARIA attributes via JavaScript. diff --git a/.brv/context-tree/design/landing_fixes/open_issues_fix.md b/.brv/context-tree/design/landing_fixes/open_issues_fix.md deleted file mode 100644 index ded00f28..00000000 --- a/.brv/context-tree/design/landing_fixes/open_issues_fix.md +++ /dev/null @@ -1,42 +0,0 @@ ---- -title: open issues fix -summary: 'Landing page fix: open issues' -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T11:24:30.770Z' -updatedAt: '2026-05-28T11:24:30.770Z' ---- -## Reason -Curate landing page audit fixes - -## Raw Concept -**Task:** -Document landing page fix: open issues - -**Changes:** -- No remaining actionable P0/P1/P2 audit issues. - -**Files:** -- apps/landing/src/pages/index.astro -- apps/landing/src/styles/global.css - -**Flow:** -audit -> fix -> verification - -**Timestamp:** 2026-05-28T11:24:30.735Z - -**Author:** Ian Pascoe - -## Narrative -### Structure -Fixes applied to landing page and styles - -### Dependencies -Requires build and lint verification - -### Highlights -No remaining actionable P0/P1/P2 audit issues. - -## Facts -- **open issues**: No remaining actionable P0/P1/P2 audit issues. diff --git a/.brv/context-tree/design/landing_fixes/reveal_animation_fix.md b/.brv/context-tree/design/landing_fixes/reveal_animation_fix.md deleted file mode 100644 index 08544602..00000000 --- a/.brv/context-tree/design/landing_fixes/reveal_animation_fix.md +++ /dev/null @@ -1,42 +0,0 @@ ---- -title: reveal animation fix -summary: 'Landing page fix: reveal animation' -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T11:24:30.748Z' -updatedAt: '2026-05-28T11:24:30.748Z' ---- -## Reason -Curate landing page audit fixes - -## Raw Concept -**Task:** -Document landing page fix: reveal animation - -**Changes:** -- Added reveal animation fail‑safe: reduced‑motion safe, no‑IntersectionObserver safe, and a timeout fallback to reveal content if observer setup stalls. - -**Files:** -- apps/landing/src/pages/index.astro -- apps/landing/src/styles/global.css - -**Flow:** -audit -> fix -> verification - -**Timestamp:** 2026-05-28T11:24:30.735Z - -**Author:** Ian Pascoe - -## Narrative -### Structure -Fixes applied to landing page and styles - -### Dependencies -Requires build and lint verification - -### Highlights -Added reveal animation fail‑safe: reduced‑motion safe, no‑IntersectionObserver safe, and a timeout fallback to reveal content if observer setup stalls. - -## Facts -- **reveal animation**: Added reveal animation fail‑safe: reduced‑motion safe, no‑IntersectionObserver safe, and a timeout fallback to reveal content if observer setup stalls. diff --git a/.brv/context-tree/design/landing_fixes/source_code_punctuation_fix.md b/.brv/context-tree/design/landing_fixes/source_code_punctuation_fix.md deleted file mode 100644 index 22484bcd..00000000 --- a/.brv/context-tree/design/landing_fixes/source_code_punctuation_fix.md +++ /dev/null @@ -1,42 +0,0 @@ ---- -title: source code punctuation fix -summary: 'Landing page fix: source code punctuation' -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T11:24:30.765Z' -updatedAt: '2026-05-28T11:24:30.765Z' ---- -## Reason -Curate landing page audit fixes - -## Raw Concept -**Task:** -Document landing page fix: source code punctuation - -**Changes:** -- Verified no em/en dashes exist in apps/landing/src. - -**Files:** -- apps/landing/src/pages/index.astro -- apps/landing/src/styles/global.css - -**Flow:** -audit -> fix -> verification - -**Timestamp:** 2026-05-28T11:24:30.735Z - -**Author:** Ian Pascoe - -## Narrative -### Structure -Fixes applied to landing page and styles - -### Dependencies -Requires build and lint verification - -### Highlights -Verified no em/en dashes exist in apps/landing/src. - -## Facts -- **source code punctuation**: Verified no em/en dashes exist in apps/landing/src. diff --git a/.brv/context-tree/design/landing_fixes/source_files_fix.md b/.brv/context-tree/design/landing_fixes/source_files_fix.md deleted file mode 100644 index 529732e7..00000000 --- a/.brv/context-tree/design/landing_fixes/source_files_fix.md +++ /dev/null @@ -1,42 +0,0 @@ ---- -title: source files fix -summary: 'Landing page fix: source files' -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T11:24:30.740Z' -updatedAt: '2026-05-28T11:24:30.740Z' ---- -## Reason -Curate landing page audit fixes - -## Raw Concept -**Task:** -Document landing page fix: source files - -**Changes:** -- Changed files: apps/landing/src/pages/index.astro and apps/landing/src/styles/global.css. - -**Files:** -- apps/landing/src/pages/index.astro -- apps/landing/src/styles/global.css - -**Flow:** -audit -> fix -> verification - -**Timestamp:** 2026-05-28T11:24:30.735Z - -**Author:** Ian Pascoe - -## Narrative -### Structure -Fixes applied to landing page and styles - -### Dependencies -Requires build and lint verification - -### Highlights -Changed files: apps/landing/src/pages/index.astro and apps/landing/src/styles/global.css. - -## Facts -- **source files**: Changed files: apps/landing/src/pages/index.astro and apps/landing/src/styles/global.css. diff --git a/.brv/context-tree/design/landing_fixes/specification_fix.md b/.brv/context-tree/design/landing_fixes/specification_fix.md deleted file mode 100644 index 46c55f91..00000000 --- a/.brv/context-tree/design/landing_fixes/specification_fix.md +++ /dev/null @@ -1,42 +0,0 @@ ---- -title: specification fix -summary: 'Landing page fix: specification' -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T11:24:30.766Z' -updatedAt: '2026-05-28T11:24:30.766Z' ---- -## Reason -Curate landing page audit fixes - -## Raw Concept -**Task:** -Document landing page fix: specification - -**Changes:** -- Final spec review: APPROVED. - -**Files:** -- apps/landing/src/pages/index.astro -- apps/landing/src/styles/global.css - -**Flow:** -audit -> fix -> verification - -**Timestamp:** 2026-05-28T11:24:30.735Z - -**Author:** Ian Pascoe - -## Narrative -### Structure -Fixes applied to landing page and styles - -### Dependencies -Requires build and lint verification - -### Highlights -Final spec review: APPROVED. - -## Facts -- **specification**: Final spec review: APPROVED. diff --git a/.brv/context-tree/design/landing_fixes/task_3_re_review.md b/.brv/context-tree/design/landing_fixes/task_3_re_review.md deleted file mode 100644 index 931f8a45..00000000 --- a/.brv/context-tree/design/landing_fixes/task_3_re_review.md +++ /dev/null @@ -1,42 +0,0 @@ ---- -title: Task 3 Re-review -summary: 'Re-review of Task 3: accessibility specs approved' -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T13:53:34.424Z' -updatedAt: '2026-05-28T13:53:34.424Z' ---- -## Reason -Document re-review of integration tab/panel accessibility specs - -## Raw Concept -**Task:** -Re-review Task 3 after progressive enhancement fix - -**Changes:** -- Reviewed accessibility specs, no code changes - -**Files:** -- apps/landing/src/pages/index.astro - -**Flow:** -Review specs -> approve - -**Timestamp:** 2026-05-28T13:53:34.419Z - -**Author:** AI Assistant - -## Narrative -### Structure -Reviewed server-rendered tablist role, ARIA states, panel roles, and JS behavior - -### Highlights -All specs met, APPROVED - -## Facts -- **tablist_role**: Server-render tablist role and ARIA state where safe [project] -- **panel_role**: Panels have role=tabpanel and aria-labelledby [project] -- **panel_visibility**: No server-side hidden on inactive panels unless non-JS fallback [project] -- **js_initial_hidden**: JS applies initial hidden state after initialization [project] -- **review_outcome**: Review outcome: APPROVED [project] diff --git a/.brv/context-tree/design/landing_fixes/technical_audit_fix.md b/.brv/context-tree/design/landing_fixes/technical_audit_fix.md deleted file mode 100644 index 35ed4fa7..00000000 --- a/.brv/context-tree/design/landing_fixes/technical_audit_fix.md +++ /dev/null @@ -1,42 +0,0 @@ ---- -title: technical audit fix -summary: 'Landing page fix: technical audit' -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T11:24:30.768Z' -updatedAt: '2026-05-28T11:24:30.768Z' ---- -## Reason -Curate landing page audit fixes - -## Raw Concept -**Task:** -Document landing page fix: technical audit - -**Changes:** -- Final technical audit recheck: APPROVED. - -**Files:** -- apps/landing/src/pages/index.astro -- apps/landing/src/styles/global.css - -**Flow:** -audit -> fix -> verification - -**Timestamp:** 2026-05-28T11:24:30.735Z - -**Author:** Ian Pascoe - -## Narrative -### Structure -Fixes applied to landing page and styles - -### Dependencies -Requires build and lint verification - -### Highlights -Final technical audit recheck: APPROVED. - -## Facts -- **technical audit**: Final technical audit recheck: APPROVED. diff --git a/.brv/context-tree/design/landing_fixes/terminal_chrome_fix.md b/.brv/context-tree/design/landing_fixes/terminal_chrome_fix.md deleted file mode 100644 index 523373b7..00000000 --- a/.brv/context-tree/design/landing_fixes/terminal_chrome_fix.md +++ /dev/null @@ -1,42 +0,0 @@ ---- -title: terminal chrome fix -summary: 'Landing page fix: terminal chrome' -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T11:24:30.761Z' -updatedAt: '2026-05-28T11:24:30.761Z' ---- -## Reason -Curate landing page audit fixes - -## Raw Concept -**Task:** -Document landing page fix: terminal chrome - -**Changes:** -- Marked decorative terminal chrome as aria‑hidden. - -**Files:** -- apps/landing/src/pages/index.astro -- apps/landing/src/styles/global.css - -**Flow:** -audit -> fix -> verification - -**Timestamp:** 2026-05-28T11:24:30.735Z - -**Author:** Ian Pascoe - -## Narrative -### Structure -Fixes applied to landing page and styles - -### Dependencies -Requires build and lint verification - -### Highlights -Marked decorative terminal chrome as aria‑hidden. - -## Facts -- **terminal chrome**: Marked decorative terminal chrome as aria‑hidden. diff --git a/.brv/context-tree/design/landing_fixes/theme_color_fix.md b/.brv/context-tree/design/landing_fixes/theme_color_fix.md deleted file mode 100644 index 2c7dfa83..00000000 --- a/.brv/context-tree/design/landing_fixes/theme_color_fix.md +++ /dev/null @@ -1,42 +0,0 @@ ---- -title: theme color fix -summary: 'Landing page fix: theme color' -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T11:24:30.763Z' -updatedAt: '2026-05-28T11:24:30.763Z' ---- -## Reason -Curate landing page audit fixes - -## Raw Concept -**Task:** -Document landing page fix: theme color - -**Changes:** -- Moved theme color into a frontmatter constant. - -**Files:** -- apps/landing/src/pages/index.astro -- apps/landing/src/styles/global.css - -**Flow:** -audit -> fix -> verification - -**Timestamp:** 2026-05-28T11:24:30.735Z - -**Author:** Ian Pascoe - -## Narrative -### Structure -Fixes applied to landing page and styles - -### Dependencies -Requires build and lint verification - -### Highlights -Moved theme color into a frontmatter constant. - -## Facts -- **theme color**: Moved theme color into a frontmatter constant. diff --git a/.brv/context-tree/design/landing_fixes/touch_targets_fix.md b/.brv/context-tree/design/landing_fixes/touch_targets_fix.md deleted file mode 100644 index cd330c20..00000000 --- a/.brv/context-tree/design/landing_fixes/touch_targets_fix.md +++ /dev/null @@ -1,42 +0,0 @@ ---- -title: touch targets fix -summary: 'Landing page fix: touch targets' -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T11:24:30.742Z' -updatedAt: '2026-05-28T11:24:30.742Z' ---- -## Reason -Curate landing page audit fixes - -## Raw Concept -**Task:** -Document landing page fix: touch targets - -**Changes:** -- Raised interactive touch targets to a minimum of 44px for brand mark, top nav links, header actions, integration pills, and copy buttons. - -**Files:** -- apps/landing/src/pages/index.astro -- apps/landing/src/styles/global.css - -**Flow:** -audit -> fix -> verification - -**Timestamp:** 2026-05-28T11:24:30.735Z - -**Author:** Ian Pascoe - -## Narrative -### Structure -Fixes applied to landing page and styles - -### Dependencies -Requires build and lint verification - -### Highlights -Raised interactive touch targets to a minimum of 44px for brand mark, top nav links, header actions, integration pills, and copy buttons. - -## Facts -- **touch targets**: Raised interactive touch targets to a minimum of 44px for brand mark, top nav links, header actions, integration pills, and copy buttons. diff --git a/.brv/context-tree/design/landing_page/context.md b/.brv/context-tree/design/landing_page/context.md deleted file mode 100644 index c6bada28..00000000 --- a/.brv/context-tree/design/landing_page/context.md +++ /dev/null @@ -1,12 +0,0 @@ ---- -related: [design/landing_page/trust_priority.md] ---- -# Topic: landing_page - -## Overview -Landing page optimization priorities for Caplets - -## Key Concepts -- trust -- understanding -- conversion diff --git a/.brv/context-tree/design/landing_page/landing_page_review_findings.md b/.brv/context-tree/design/landing_page/landing_page_review_findings.md deleted file mode 100644 index 154c416a..00000000 --- a/.brv/context-tree/design/landing_page/landing_page_review_findings.md +++ /dev/null @@ -1,59 +0,0 @@ ---- -title: Landing Page Review Findings -summary: 'Landing page review: anchors valid, accessibility ok, build passes, minor dead CSS, no brand violations' -tags: [] -related: [design/caplets_curate/working_module.md, design/landing_page/skillify_landing_page_review.md, design/landing_page/skillify_landing_page_review_2026_05_28.md, design/landing_page/skillify_landing_page_task_7_review.md, design/landing_page/task_8_responsive_pass.md] -keywords: [] -createdAt: '2026-05-28T10:52:14.235Z' -updatedAt: '2026-05-28T10:52:14.235Z' ---- -## Reason -Curate review outcomes for landing implementation - -## Raw Concept -**Task:** -Document landing page review findings - -**Changes:** -- Verified internal anchors -- Removed old map/cartography selectors -- Confirmed accessibility basics -- Passed typecheck and build -- No brand violations -- Identified dead CSS selectors and unused custom properties - -**Files:** -- apps/landing/src/pages/index.astro -- apps/landing/src/styles/global.css -- docs/plans/2026-05-28-skillify-landing-page.md - -**Flow:** -review -> verify anchors -> check accessibility -> run typecheck/build -> run impeccable -> report - -**Timestamp:** 2026-05-28T10:50:23.115Z - -**Author:** AI Assistant - -## Narrative -### Structure -Landing page review summary with verification steps and findings. - -### Dependencies -Requires pnpm, impeccable tool, and CSS files. - -### Highlights -All anchors valid, accessibility intact, build passes, no brand violations, minor dead CSS identified. - -### Examples -Unused selector .header-action.muted at lines 229-232; unused custom properties listed. - -## Facts -- **apps/landing/src/pages/index.astro**: Internal anchors are valid: #main, #trace, #proof, and #install all resolve in apps/landing/src/pages/index.astro. -- **codebase**: Old map/cartography selectors and scripts are gone. No remaining map-stage, route-*, capability-card, inspect-panel, or pointer/scroll choreography references were found. -- **landing page**: Accessibility basics are intact: skip link, semantic sections, labeled nav/actions, keyboard tab controls, visible focus CSS, and reduced-motion handling. -- **@caplets/landing**: pnpm --filter @caplets/landing typecheck: PASS, 0 errors. -- **@caplets/landing**: pnpm --filter @caplets/landing build: PASS. -- **apps/landing/src/pages/index.astro**: npx impeccable --json apps/landing/src/pages/index.astro returned an empty array, indicating no brand violations. -- **apps/landing/src/styles/global.css**: Minor dead CSS remains in apps/landing/src/styles/global.css: Unused selector .header-action.muted at lines 229-232. -- **apps/landing/src/styles/global.css**: Unused custom properties --ash-strong, --danger, --decorative-grid, --decorative-grid-muted, --decorative-grid-strong, --success-glow at lines 17, 20, 25-29 in apps/landing/src/styles/global.css. -- **landing implementation**: No blockers, broken links, accessibility regressions, script regressions, or brand violations found. diff --git a/.brv/context-tree/design/landing_page/skillify_landing_page_review.md b/.brv/context-tree/design/landing_page/skillify_landing_page_review.md deleted file mode 100644 index 7f41bb17..00000000 --- a/.brv/context-tree/design/landing_page/skillify_landing_page_review.md +++ /dev/null @@ -1,60 +0,0 @@ ---- -title: Skillify Landing Page Review -summary: Landing page review approved, all tasks 1-9 satisfied -tags: [] -related: [design/caplets_curate/working_module.md, design/landing_page/landing_page_review_findings.md, design/landing_page/skillify_landing_page_review_2026_05_28.md, design/landing_page/skillify_landing_page_task_7_review.md, design/landing_page/task_8_responsive_pass.md] -keywords: [] -createdAt: '2026-05-28T10:43:56.110Z' -updatedAt: '2026-05-28T10:43:56.110Z' ---- -## Reason -Document compliance review outcome - -## Raw Concept -**Task:** -Compliance review of skillify landing page implementation - -**Changes:** -- Verified Tasks 1-9 implementation -- No issues found - -**Files:** -- apps/landing/src/pages/index.astro -- apps/landing/src/styles/global.css -- docs/plans/2026-05-28-skillify-landing-page.md - -**Flow:** -review -> verification -> approval - -**Timestamp:** 2026-05-28T10:43:56.106Z - -**Author:** AI Assistant - -## Narrative -### Structure -Review confirms all task requirements are met in the landing page code and docs. - -### Dependencies -Requires source files to remain unchanged. - -### Highlights -APPROVED status, no blockers, all features present, reduced-motion support, no em dashes, stable docs link. - -### Examples -Hero constants at index.astro:4-139, reduced-motion CSS at global.css:1252-1278 - -## Facts -- **tasks**: Tasks 1-9 are implemented in the inspected files. -- **hero_section**: Hero constants, trust mechanics, skillify framework, and install steps are present and aligned in apps/landing/src/pages/index.astro lines 4-139. -- **hero_text**: Hero leads with “Skillify your backends.” and defines skillify in index.astro lines 184-192. -- **github_trace**: GitHub trace shows status, source, auth redaction, progressive steps, schema inspection, and preserved result in index.astro lines 210-235. -- **trust_section**: Skillify proof framework and concrete trust section are present in index.astro lines 265-302. -- **copy_buttons**: Copy buttons exist for integration snippets and terminal commands, with copy script in index.astro lines 336-374 and 444-465. -- **integration_panels**: Integration panels are not hidden in initial HTML and are hidden only after JS tab setup runs, as shown in index.astro lines 327-332 and 388-406. -- **hidden_attribute**: CSS keeps [hidden] behavior in global.css lines 864-865. -- **deprecated_selectors**: Old map/cartography/route/pointer selectors were not found in source searches. -- **reduced_motion**: Reduced-motion support remains in apps/landing/src/styles/global.css lines 1252-1278. -- **responsive_rules**: Responsive trace/trust/snippet rules are present in global.css lines 1202-1237. -- **punctuation**: No em dashes were found in inspected index.astro or global.css. -- **docs_config**: Docs/config link is stable against README #configure in index.astro lines 382-385. -- **approval**: The review concluded with APPROVED status. diff --git a/.brv/context-tree/design/landing_page/skillify_landing_page_review_2026_05_28.md b/.brv/context-tree/design/landing_page/skillify_landing_page_review_2026_05_28.md deleted file mode 100644 index 2268dad1..00000000 --- a/.brv/context-tree/design/landing_page/skillify_landing_page_review_2026_05_28.md +++ /dev/null @@ -1,55 +0,0 @@ ---- -title: Skillify Landing Page Review 2026-05-28 -summary: 'Review of tasks 1-4 implementation; blocker: broken #map nav target' -tags: [] -related: [design/caplets_curate/working_module.md, design/landing_page/landing_page_review_findings.md, design/landing_page/skillify_landing_page_review.md, design/landing_page/skillify_landing_page_task_7_review.md, design/landing_page/task_8_responsive_pass.md] -keywords: [] -createdAt: '2026-05-28T10:19:04.961Z' -updatedAt: '2026-05-28T10:19:04.961Z' ---- -## Reason -Document review findings and blocker for skillify landing page - -## Raw Concept -**Task:** -Document spec compliance review for skillify landing page - -**Files:** -- docs/plans/2026-05-28-skillify-landing-page.md -- apps/landing/src/pages/index.astro -- apps/landing/src/styles/global.css - -**Flow:** -Review -> Identify correct implementations -> Identify blocker -> Report approval status - -**Timestamp:** 2026-05-28T10:19:04.960Z - -## Narrative -### Structure -Review of tasks 1-4 implementation status and blocker identification - -### Dependencies -Requires updated nav target to match trace section - -### Highlights -All tasks 1-4 correct except broken navigation target #map - -### Examples -Broken link at apps/landing/src/pages/index.astro:165 linking to #map - -## Facts -- **apps/landing/src/pages/index.astro**: Task 1 constants are implemented: `heroTrace`, `skillifyFramework`, `trustMechanics`, and `installSteps` match the plan in `apps/landing/src/pages/index.astro:4-140`. -- **apps/landing/src/pages/index.astro**: Generic installs are normalized to `npm install -g caplets` where applicable in `apps/landing/src/pages/index.astro:105`, `114`, `124`, and `136`. -- **apps/landing/src/pages/index.astro**: Task 2 hero is rebuilt around “Skillify your backends.” and the GitHub trace in `apps/landing/src/pages/index.astro:182-236`. -- **apps/landing/src/styles/global.css**: Task 2 trace CSS and updated hero columns are present in `apps/landing/src/styles/global.css:265-269` and `405-556`. -- **apps/landing/src/styles/global.css**: Old map hero CSS selectors requested by Task 2 appear removed from `global.css`. -- **apps/landing/src/pages/index.astro**: Task 3 proof framework is implemented in `apps/landing/src/pages/index.astro:265-279`. -- **apps/landing/src/pages/index.astro**: Task 4 trust mechanics section is implemented in `apps/landing/src/pages/index.astro:281-302`, with CSS in `apps/landing/src/styles/global.css:773-812`. -- **review scope**: Tasks 5+ are not required yet; missing copy buttons, no-JS tab changes, and motion cleanup are not counted as issues for this review scope. -- **global.css**: CSS is located in `apps/landing/src/styles/global.css:773-812`. -- **index.astro**: `apps/landing/src/pages/index.astro:165` still links the primary nav to `href="#map"` and labels it `Map`. -- **index.astro**: Task 2 replaced the old `id="map"` hero visual with `id="trace"` at `apps/landing/src/pages/index.astro:210`. -- **navigation**: The broken in-page navigation target after completing Task 2’s hero replacement needs to be fixed by updating the nav target and label to the new trace section, or restoring a valid `#map` target if intentional. -- **review process**: The reviewer did not run typecheck/build because the request was to inspect diff/current files only. -- **approval**: NOT APPROVED until the broken `#map` navigation target is fixed. -- **#map target**: d `#map` target if intentional. diff --git a/.brv/context-tree/design/landing_page/skillify_landing_page_task_7_review.md b/.brv/context-tree/design/landing_page/skillify_landing_page_task_7_review.md deleted file mode 100644 index 606c94eb..00000000 --- a/.brv/context-tree/design/landing_page/skillify_landing_page_task_7_review.md +++ /dev/null @@ -1,59 +0,0 @@ ---- -title: Skillify Landing Page Task 7 Review -summary: 'Task 7: removed old motion systems, updated CSS, verified build passes, no grep matches' -tags: [] -related: [design/caplets_curate/working_module.md, design/landing_page/landing_page_review_findings.md, design/landing_page/skillify_landing_page_review.md, design/landing_page/skillify_landing_page_review_2026_05_28.md, design/landing_page/task_8_responsive_pass.md] -keywords: [] -createdAt: '2026-05-28T10:36:29.420Z' -updatedAt: '2026-05-28T10:36:29.420Z' ---- -## Reason -Document outcomes of Task 7 implementation for landing page - -## Raw Concept -**Task:** -Implement Task 7 for landing page - -**Changes:** -- Removed old route/motion CSS dependencies (view-timeline, animation-timeline) -- Reduced page warmth glow -- Removed header backdrop-filter -- Ensured smaller reveal setup includes .agent-setup-panel -- Validated typecheck and build pass -- Confirmed no remaining motion-related code via grep - -**Files:** -- apps/landing/src/pages/index.astro -- apps/landing/src/styles/global.css - -**Flow:** -Inspect files -> remove old motion systems -> adjust styling -> run typecheck & build -> grep verification - -**Timestamp:** 2026-05-28T10:35:37.451Z - -**Author:** assistant - -## Narrative -### Structure -Landing page source files updated with cleaned CSS and removed motion scripts - -### Dependencies -Depends on pnpm, @caplets/landing package, CSS build pipeline - -### Highlights -All old motion systems removed, build passes, no grep matches - -## Facts -- **assistant**: Implemented Task 7 only. [project] -- **landing project**: Changed files: apps/landing/src/pages/index.astro and apps/landing/src/styles/global.css. [project] -- **CSS**: Removed remaining old route/motion CSS dependencies, including view-timeline and animation-timeline. [project] -- **page styling**: Reduced page warmth glow. [project] -- **header CSS**: Removed .site-header backdrop-filter. [project] -- **reveal setup**: Ensured the smaller reveal setup includes .agent-setup-panel. [project] -- **motion systems**: Confirmed old motion systems are absent. [project] -- **typecheck**: pnpm --filter @caplets/landing typecheck PASS. [project] -- **build**: pnpm --filter @caplets/landing build PASS. [project] -- **grep results**: Grep for map-stage|route-resolve|pointer-x|pointer-y|route-drift|cartography|is-route-active returned no matches. [project] -- **build warnings**: Build emitted Node DEP0205 deprecation warnings only. [project] -- **working tree**: Working tree contains unrelated .brv/ and plan-file changes not made by this implementation. [project] -- **next steps**: Recommended next step: Review the two landing files, keeping unrelated dirty files separate before commit. [project] diff --git a/.brv/context-tree/design/landing_page/task_8_responsive_pass.md b/.brv/context-tree/design/landing_page/task_8_responsive_pass.md deleted file mode 100644 index 9cc6145c..00000000 --- a/.brv/context-tree/design/landing_page/task_8_responsive_pass.md +++ /dev/null @@ -1,56 +0,0 @@ ---- -title: Task 8 Responsive Pass -summary: Responsive CSS updates for trace, trust, snippets and mobile copy button in landing page -tags: [] -related: [design/caplets_curate/working_module.md, design/landing_page/landing_page_review_findings.md, design/landing_page/skillify_landing_page_review.md, design/landing_page/skillify_landing_page_review_2026_05_28.md, design/landing_page/skillify_landing_page_task_7_review.md] -keywords: [] -createdAt: '2026-05-28T10:38:10.054Z' -updatedAt: '2026-05-28T10:38:10.054Z' ---- -## Reason -Document responsive CSS changes for landing page Task 8 - -## Raw Concept -**Task:** -Implement Task 8 responsive pass for landing page CSS - -**Changes:** -- Added tablet trace sizing and two-column trust grid -- Added phone rules for trace metadata, trust grid/cards, terminal copy layout -- Added mobile .copy-button minimum 40px touch target -- Removed old mobile map selectors - -**Files:** -- apps/landing/src/styles/global.css - -**Flow:** -Modify CSS -> run typecheck -> run build -> verify pass - -**Timestamp:** 2026-05-28T10:38:10.053Z - -**Author:** AI assistant - -## Narrative -### Structure -CSS modifications in global.css to improve responsiveness for trace, trust, snippets, and copy button - -### Dependencies -Requires pnpm, @caplets/landing package - -### Highlights -All checks pass (typecheck 0 errors, build successful) - -### Rules -Do not implement tasks beyond 8, do not commit changes - -### Examples -pnpm --filter @caplets/landing typecheck - -## Facts -- **Task 8 implementation**: Implemented Task 8 responsive pass for trace, trust, and snippets. [other] -- **global.css modifications**: Changed file apps/landing/src/styles/global.css to add tablet trace sizing and two-column trust grid. [other] -- **global.css modifications**: Added phone rules for trace metadata/steps, trust grid/cards, terminal copy layout. [other] -- **global.css modifications**: Added mobile .copy-button 40px minimum touch target. [other] -- **global.css cleanup**: Confirmed no old mobile map selectors remain. [other] -- **typecheck**: pnpm --filter @caplets/landing typecheck PASS, 0 errors. [other] -- **build**: pnpm --filter @caplets/landing build PASS. [other] diff --git a/.brv/context-tree/design/landing_page/trust_priority.md b/.brv/context-tree/design/landing_page/trust_priority.md deleted file mode 100644 index 6bc9f1bc..00000000 --- a/.brv/context-tree/design/landing_page/trust_priority.md +++ /dev/null @@ -1,27 +0,0 @@ ---- -title: trust_priority -summary: Landing page should prioritize trust in architecture first -tags: [] -related: [design/landing_page/context.md] -keywords: [] -createdAt: '2026-05-28T09:50:32.824Z' -updatedAt: '2026-05-28T09:50:32.824Z' ---- -## Reason -Document decision to prioritize trust in landing page - -## Raw Concept -**Task:** -Define landing page optimization priority - -**Changes:** -- Prioritize trust in architecture - -**Timestamp:** 2026-05-28T09:50:32.823Z - -## Narrative -### Highlights -Trust first to build confidence before conversion or understanding - -## Facts -- **landing_page_priority**: Trust in the architecture should be the primary focus for the landing page optimization. [preference] diff --git a/.brv/context-tree/design/working_module/working_module_findings.md b/.brv/context-tree/design/working_module/working_module_findings.md deleted file mode 100644 index 100391d5..00000000 --- a/.brv/context-tree/design/working_module/working_module_findings.md +++ /dev/null @@ -1,35 +0,0 @@ ---- -title: Working Module Findings -summary: Curated factual findings of the working module -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T13:37:46.984Z' -updatedAt: '2026-05-28T13:46:43.741Z' ---- -## Reason -Curate extracted findings from working module analysis - -## Raw Concept -**Task:** -Document findings of the working module - -**Flow:** -context extraction -> deduplication -> grouping -> curation - -**Timestamp:** 2026-05-28T13:46:43.734Z - -## Narrative -### Structure -Extracted factual statements from provided context - -### Highlights -uncategorized, apps/landing/src/pages/index.astro, apps/landing/src/styles/global.css - -## Facts -- APPROVED -- **apps/landing/src/pages/index.astro**: apps/landing/src/pages/index.astro:523-558 only enables hidden reveal state when animation is allowed, IntersectionObserver exists, and reveal targets are present -- **apps/landing/src/pages/index.astro**: apps/landing/src/pages/index.astro:552-558 adds a short fail-safe; if no observer callback occurs, all reveal targets are made visible and the observer disconnects, reducing hidden-content risk versus the prior 1800ms blanket reveal -- **apps/landing/src/styles/global.css**: apps/landing/src/styles/global.css:535-565 keeps hidden state gated behind .motion-ready, so content remains visible by default before JavaScript runs or when JavaScript fails -- **apps/landing/src/styles/global.css**: apps/landing/src/styles/global.css:1261-1285 preserves reduced-motion behavior with visible, non-transformed reveal targets -- The change observes targets once, unobserves each revealed target, and disconnects on fallback, which is maintainable and avoids long-running observation for already visible content diff --git a/.brv/context-tree/design/writing_plans/implementation_plan.md b/.brv/context-tree/design/writing_plans/implementation_plan.md deleted file mode 100644 index 06a222a5..00000000 --- a/.brv/context-tree/design/writing_plans/implementation_plan.md +++ /dev/null @@ -1,41 +0,0 @@ ---- -title: implementation_plan -summary: Implementation plan for skillify landing page covering trust narrative and checklist -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T10:03:54.550Z' -updatedAt: '2026-05-28T10:03:54.550Z' ---- -## Reason -Document implementation plan created by writing-plans skill - -## Raw Concept -**Task:** -Create implementation plan for skillify landing page - -**Files:** -- docs/plans/2026-05-28-skillify-landing-page.md - -**Flow:** -write plan -> save -> scan for issues - -**Timestamp:** 2026-05-28T10:03:54.547Z - -**Author:** AI assistant - -## Narrative -### Structure -Plan includes sections on trust-first narrative, hero, GitHub trace, mechanics, copy buttons, no-JS fallback, system removal, responsive design, and verification checklist - -### Highlights -All major aspects covered, no red flags found - -### Examples -File path: docs/plans/2026-05-28-skillify-landing-page.md - -## Facts -- **writing-plans skill**: I'm using the writing-plans skill to create the implementation plan. -- **implementation plan**: Plan saved to docs/plans/2026-05-28-skillify-landing-page.md. -- **implementation plan**: The plan covers trust-first narrative, “Skillify your backends” hero, realistic GitHub trace, concrete trust mechanics, command/config copy buttons, no-JS integration fallback, removal of major motion/cartography systems, responsive pass, and final verification checklist. -- **plan scan**: I scanned the plan for em dashes, placeholders, and obvious plan red flags and found no matches. diff --git a/.brv/context-tree/docs/plans/active_caplets_planning_documents.md b/.brv/context-tree/docs/plans/active_caplets_planning_documents.md deleted file mode 100644 index 5ee3f64f..00000000 --- a/.brv/context-tree/docs/plans/active_caplets_planning_documents.md +++ /dev/null @@ -1,77 +0,0 @@ ---- -title: Active Caplets Planning Documents -summary: Active caplets planning notes covering backend, remote control, CLI, HTTP actions, GraphQL, hot reload, XDG paths, and release-related work. -tags: [] -related: [architecture/remote_control/context.md, architecture/auth/context.md, docs/plans/completion_discovery_refactor_implementation_plan.md, docs/plans/completion_local_discovery_and_split_targets.md, docs/plans/docker_image_publishing_for_release_pipeline.md, docs/plans/context.md, docs/plans/pr_71_completion_fix_outcome.md, docs/plans/release-automation-is-gated-by-verified-publish-state-not-just-workflow-success.md, docs/plans/release-and-completion-work-is-governed-by-verification-not-optimistic-success-s.md] -keywords: [] -createdAt: '2026-05-21T10:20:40.246Z' -updatedAt: '2026-05-21T10:21:06.053Z' ---- -## Reason -Curate the planning notes and active implementation directions from the provided context - -## Raw Concept -**Task:** -Document the active caplets planning documents and implementation roadmap - -**Changes:** -- Captured planning notes dated 2026-05-12 through 2026-05-19 -- Included release and backend architecture planning documents -- Captured multiple dated planning documents as durable knowledge -- Preserved workstreams for backend, remote control, CLI tools, HTTP actions, GraphQL, hot reload, and XDG cross-platform paths -- Included release and Docker publishing planning references - -**Files:** -- docs/plans/2026-05-12-cli-inspection-polish.md -- docs/plans/2026-05-12-graphql-backend.md -- docs/plans/2026-05-12-hot-reload.md -- docs/plans/2026-05-12-mcp-backed-caplet-files.md -- docs/plans/2026-05-13-http-actions-backend.md -- docs/plans/2026-05-13-xdg-cross-platform-paths.md -- docs/plans/2026-05-14-cli-tools-backend.md -- docs/plans/2026-05-14-coding-agent-benchmarks.md -- docs/plans/2026-05-14-output-field-selection.md -- docs/plans/2026-05-14-project-first-caplets-add.md -- docs/plans/2026-05-15-native-agent-caplet-extensions.md -- docs/plans/2026-05-15-native-hot-reload.md -- docs/plans/2026-05-19-caplets-interface-ux.md - -**Flow:** -planning note authored -> implementation idea captured -> review/iteration -> roadmap preserved for follow-up work - -**Timestamp:** 2026-05-21T10:20:57.367Z - -## Narrative -### Structure -This topic aggregates active caplets planning documents under docs/plans and treats them as a roadmap collection rather than isolated notes. - -### Dependencies -The plans reference CLI behavior, remote control, backend transport choices, hot reload, Docker/release work, and benchmark/UX considerations. - -### Highlights -The context shows active documentation work centered on caplets product development and delivery planning. - -### Examples -Examples include CLI inspection polish, GraphQL backend, HTTP actions backend, XDG cross-platform paths, and native hot reload planning. - -## Facts -- **curated_context_line**: [user]: Task: [Read from: /home/ianpascoe/code/caplets/plan.md, /home/ianpascoe/code/caplets/progress.md] [project] -- **curated_context_line**: Review round 2 after fix worker. Fresh context in /home/ianpascoe/code/caplets. Do not edit files and do not run subagents. Focus maintainability/validation: workflow readability, actionlint/shell compatibility, docs/spec/plan alignment for GHCR release publishing, and whether focused validation is enough. Return blockers, fixes worth doing now, optional improvements, validation evidence. [project] -- **curated_context_line**: - GHCR permission is present: `.github/workflows/release.yml:9-13` includes `packages: write`. [project] -- **curated_context_line**: - Changesets output is available: `.github/workflows/release.yml:50-59` gives the Changesets step `id: changesets`. [project] -- **curated_context_line**: - The round-2 fix is functionally sound: `.github/workflows/release.yml:61-73` checks `steps.changesets.outputs.publishedPackages` and only treats the CLI npm package `caplets` as image-publish-worthy. [project] -- **curated_context_line**: - All Docker/GHCR steps are now gated on both a real Changesets publish and CLI publication: `.github/workflows/release.yml:75-107`. [project] -- **curated_context_line**: - Tags align with the GHCR spec: `.github/workflows/release.yml:99-104` publishes `latest`, raw version, `v` version, and short SHA tags matching `docs/specs/2026-05-21-ghcr-release-publishing-design.md:17-20`. [project] -- **curated_context_line**: - Spec still says Docker publish steps are gated only by `steps.changesets.outputs.published == 'true'`: `docs/specs/2026-05-21-ghcr-release-publishing-design.md:13`. [project] -- **curated_context_line**: - Plan exact insertion snippet omits the CLI-package publication check: `docs/plans/2026-05-21-ghcr-release-publishing.md:100-139`. [project] -- **curated_context_line**: - Plan validation only checks for the broad Changesets publish gate, not the CLI-specific gate: `docs/plans/2026-05-21-ghcr-release-publishing.md:189-200`. [project] -- **curated_context_line**: - The spec asks for workflow syntax sanity at `docs/specs/2026-05-21-ghcr-release-publishing-design.md:30`, but the implementation plan relies mostly on formatting/string checks. `actionlint .github/workflows/release.yml` is the right focused syntax validation. [project] -- **curated_context_line**: - Add a short comment above `.github/workflows/release.yml:61` explaining that GHCR is published only when the CLI package is published to avoid pushing a stale image for core/integration-only releases. [project] -- **curated_context_line**: - `docs/specs/2026-05-21-ghcr-release-publishing-design.md` [project] -- **curated_context_line**: - `docs/plans/2026-05-21-ghcr-release-publishing.md` [project] -- **curated_context_line**: - `pnpm format:check .github/workflows/release.yml` passed. [project] -- **curated_context_line**: - `actionlint .github/workflows/release.yml` passed. [project] -- **curated_context_line**: - `caplets` package => `published=true` [project] -- **curated_context_line**: - `@caplets/core` only => `published=false` [project] -- **curated_context_line**: - `docker build --check .` passed with “Check complete, no warnings found.” [project] -- **curated_context_line**: - I did not run a full `docker build -t ... .` because this was review-only/no-edit and that creates a local image. If no fix worker ran the full Docker build, that remains the one missing validation item relative to the spec’s local verification guidance. [project] diff --git a/.brv/context-tree/docs/plans/completion_discovery_refactor_implementation_plan.md b/.brv/context-tree/docs/plans/completion_discovery_refactor_implementation_plan.md deleted file mode 100644 index 1a952fb3..00000000 --- a/.brv/context-tree/docs/plans/completion_discovery_refactor_implementation_plan.md +++ /dev/null @@ -1,76 +0,0 @@ ---- -title: Completion Discovery Refactor Implementation Plan -summary: Plan for shared CLI completion metadata, cache-backed discovery, completion config defaults, remote completion routing, and final verification. -tags: [] -related: [architecture/remote_control/remote_control_api_shape.md, architecture/remote_control/cli_remote_mode_selection.md, docs/plans/active_caplets_planning_documents.md, docs/plans/completion_local_discovery_and_split_targets.md, docs/plans/context.md, docs/plans/docker_image_publishing_for_release_pipeline.md, docs/plans/pr_71_completion_fix_outcome.md, docs/plans/release-automation-is-gated-by-verified-publish-state-not-just-workflow-success.md, docs/plans/release-and-completion-work-is-governed-by-verification-not-optimistic-success-s.md] -keywords: [] -createdAt: '2026-05-21T17:14:24.407Z' -updatedAt: '2026-05-21T17:14:24.407Z' ---- -## Reason -Capture the detailed implementation plan and verification outcome for cache-backed CLI completions - -## Raw Concept -**Task:** -Document the completion discovery refactor implementation plan and its final implementation status. - -**Changes:** -- Shared CLI command metadata added -- Async cache-backed discovery added for tools, prompts, resources, and resource templates -- Persistent completion cache helpers added -- Platform-native completion cache path helpers added -- completion config defaults added -- Remote complete_cli routed through server-owned discovery -- README docs and changeset updated -- Final verification passed - -**Files:** -- packages/core/src/cli/commands.ts -- packages/core/src/cli/completion.ts -- packages/core/src/cli/completion-cache.ts -- packages/core/src/cli/completion-discovery.ts -- packages/core/src/config/paths.ts -- packages/core/src/config.ts -- packages/core/src/engine.ts -- packages/core/src/remote-control/dispatch.ts -- packages/core/test/cli-completion.test.ts -- packages/core/test/cli-completion-cache.test.ts -- packages/core/test/remote-control-dispatch.test.ts -- packages/core/test/cli-remote.test.ts -- packages/core/test/config.test.ts -- README.md -- packages/cli/README.md -- .changeset/cli-completions.md -- schemas/caplets-config.schema.json - -**Flow:** -plan -> implement shared metadata -> add config/cache -> wire discovery -> route remote completions -> update docs -> verify - -**Timestamp:** 2026-05-21T17:14:04.450Z - -**Author:** ByteRover context engineer - -## Narrative -### Structure -The plan is organized into nine tasks covering metadata refactor, completion config and paths, persistent cache, discovery orchestration, live cache-backed discovery, MCP resource and prompt contexts, remote server routing, documentation, and final verification. - -### Dependencies -The completion discovery layer depends on existing managers such as DownstreamManager, OpenApiManager, GraphQLManager, HttpActionManager, CliToolsManager, and CapletSetManager. It also depends on Commander, Zod, Vitest, and Node filesystem/path APIs. - -### Highlights -The refactor preserves secret-free cache entries, uses platform-native cache directories, and ensures remote completions use server-owned state. The implementation status at the end reports that all verification passed. - -### Rules -Completion discovery must remain bounded by discovery and overall timeouts. Generated shell scripts suppress completion stderr; run the underlying CLI command directly when debugging completion behavior. Completion never starts interactive login flows. - -### Examples -Examples include qualified completions such as caplets call-tool repo., prompt completion via --prompt, and resource template completion via --resource-template. - -## Facts -- **completion_refactor_goal**: The goal is to replace duplicated CLI completion command lists with shared metadata and add cache-backed live downstream completions for qualified tools, prompts, resources, and resource templates. [project] -- **completion_refactor_architecture**: The architecture uses a shared command metadata module, a persistent platform-native completion cache, and bounded live discovery through existing managers. [project] -- **completion_config_defaults**: The implementation adds a completion config with discoveryTimeoutMs, overallTimeoutMs, cacheTtlMs, and negativeCacheTtlMs defaults. [project] -- **remote_completion_ownership**: Remote complete_cli is routed through CapletsEngine.completeCliWords so completion discovery uses server-owned config and state. [project] -- **completion_cache_security**: Completion cache entries store only secret-free candidate metadata keyed by backend and config fingerprints. [project] -- **verification_status**: Verification completed successfully with pnpm verify passing, including format, lint, typecheck, schema check, tests, benchmark check, and build. [project] -- **workspace_note**: The working tree note says .brv remained modified from the existing workspace state and was not touched or staged. [project] diff --git a/.brv/context-tree/docs/plans/completion_local_discovery_and_split_targets.md b/.brv/context-tree/docs/plans/completion_local_discovery_and_split_targets.md deleted file mode 100644 index 6fb99d05..00000000 --- a/.brv/context-tree/docs/plans/completion_local_discovery_and_split_targets.md +++ /dev/null @@ -1,43 +0,0 @@ ---- -title: Completion Local Discovery and Split Targets -summary: Plan saved for fixing local live discovery of existing caplet.tool completions and adding split targets, with formatting verified. -tags: [] -related: [docs/plans/completion_discovery_refactor_implementation_plan.md, docs/plans/context.md, docs/plans/active_caplets_planning_documents.md, docs/plans/docker_image_publishing_for_release_pipeline.md, docs/plans/pr_71_completion_fix_outcome.md, docs/plans/release-automation-is-gated-by-verified-publish-state-not-just-workflow-success.md, docs/plans/release-and-completion-work-is-governed-by-verification-not-optimistic-success-s.md] -keywords: [] -createdAt: '2026-05-21T23:07:39.925Z' -updatedAt: '2026-05-21T23:07:39.925Z' ---- -## Reason -Curate durable outcomes from the plan creation conversation - -## Raw Concept -**Task:** -Curate the completion plan outcome and verification details - -**Changes:** -- Recorded the plan scope for local completion discovery and split targets -- Recorded formatting verification and placeholder scan results -- Recorded that the existing .brv modification was left untouched - -**Flow:** -plan created -> formatting checked -> formatter applied -> verification passed - -**Timestamp:** 2026-05-21T23:07:22.672Z - -## Narrative -### Structure -This knowledge captures the created plan, its scope, and the verification outcomes associated with it. - -### Dependencies -Depends on the repository plan-writing convention under docs/plans/ and the formatting workflow used during validation. - -### Highlights -The plan targets completion discovery and split target UX, and the file was verified after formatting correction. - -## Facts -- **plan_scope**: A dedicated plan was created to cover both fixing local live discovery for existing caplet.tool completions and adding split targets as an additive UX improvement. [project] -- **plan_file**: The plan was saved to docs/plans/2026-05-21-completion-local-discovery-and-split-targets.md. [project] -- **formatting_verification**: Initial pnpm format:check failed on the new plan file, then pnpm exec oxfmt docs/plans/2026-05-21-completion-local-discovery-and-split-targets.md was run, and re-running pnpm format:check passed. [project] -- **placeholder_scan**: Placeholder scan found no matches for TBD, TODO, , , implement later, fill in, appropriate, similar to, or equivalent to. [project] -- **brv_modification**: An existing .brv modification was already present and left untouched. [project] -- **execution_options**: The review noted two execution options: Subagent-Driven and Inline Execution. [project] diff --git a/.brv/context-tree/docs/plans/context.md b/.brv/context-tree/docs/plans/context.md deleted file mode 100644 index 93737c43..00000000 --- a/.brv/context-tree/docs/plans/context.md +++ /dev/null @@ -1,13 +0,0 @@ ---- -related: [docs/plans/completion_local_discovery_and_split_targets.md, docs/plans/completion_discovery_refactor_implementation_plan.md, docs/plans/active_caplets_planning_documents.md, docs/plans/docker_image_publishing_for_release_pipeline.md, docs/plans/pr_71_completion_fix_outcome.md, docs/plans/release-automation-is-gated-by-verified-publish-state-not-just-workflow-success.md, docs/plans/release-and-completion-work-is-governed-by-verification-not-optimistic-success-s.md] ---- -# Topic: plans - -## Overview -Planning documents for caplets-related product, backend, and release work. - -## Key Concepts -- roadmap -- backend design -- release pipeline -- UX planning diff --git a/.brv/context-tree/docs/plans/docker_image_publishing_for_release_pipeline.md b/.brv/context-tree/docs/plans/docker_image_publishing_for_release_pipeline.md deleted file mode 100644 index 78359450..00000000 --- a/.brv/context-tree/docs/plans/docker_image_publishing_for_release_pipeline.md +++ /dev/null @@ -1,53 +0,0 @@ ---- -title: Docker Image Publishing for Release Pipeline -summary: 'Release workflow should publish GHCR Docker images only after Changesets publishes npm packages, with packages: write permission and Docker build/login steps gated on published releases.' -tags: [] -related: [architecture/remote_control/release_workflow.md, docs/plans/active_caplets_planning_documents.md, docs/plans/completion_discovery_refactor_implementation_plan.md, docs/plans/context.md, docs/plans/completion_local_discovery_and_split_targets.md, docs/plans/pr_71_completion_fix_outcome.md, docs/plans/release-automation-is-gated-by-verified-publish-state-not-just-workflow-success.md, docs/plans/release-and-completion-work-is-governed-by-verification-not-optimistic-success-s.md] -keywords: [] -createdAt: '2026-05-21T09:58:44.922Z' -updatedAt: '2026-05-21T09:58:44.922Z' ---- -## Reason -Capture approved release workflow direction and implementation intent - -## Raw Concept -**Task:** -Document the approved release pipeline change to publish Docker images to GHCR - -**Changes:** -- Approved publishing Docker images from the release workflow -- Add packages: write permission to GitHub Actions permissions -- Use Docker login, metadata, and build-push steps after Changesets publishes - -**Files:** -- .github/workflows/release.yml - -**Flow:** -push to main -> pnpm verify -> changesets action -> if published -> docker login/metadata/build-push -> GHCR publish - -**Timestamp:** 2026-05-21T09:58:31.983Z - -**Author:** user/assistant discussion - -## Narrative -### Structure -This decision belongs with the project plan for release workflow changes and specifically targets the GitHub Actions release pipeline. - -### Dependencies -Requires changesets publishing to succeed before Docker image build and push can run. The workflow must also be able to authenticate to GHCR. - -### Highlights -Approach 1 was approved: publish Docker images only after npm release publishing, keeping image releases aligned with package releases. - -### Rules -Use the release workflow approach gated on successful Changesets publishing. - -### Examples -Example tags: ghcr.io/spiritledsoftware/caplets:latest and ghcr.io/spiritledsoftware/caplets:v - -## Facts -- **release_workflow_trigger**: The release workflow currently uses changesets/action@v1 on pushes to main. [project] -- **release_workflow_verification_step**: The workflow runs pnpm verify before the release or version PR path. [project] -- **github_permissions**: The workflow currently has contents, pull-requests, and id-token permissions, but not packages: write. [project] -- **docker_publish_gate**: GHCR image publishing should be gated on steps.changesets.outputs.published == true. [project] -- **docker_tags**: The recommended image tags are latest, v, and optionally . [project] diff --git a/.brv/context-tree/docs/plans/lsp_registry_caplet_plan.md b/.brv/context-tree/docs/plans/lsp_registry_caplet_plan.md deleted file mode 100644 index 74a9ae87..00000000 --- a/.brv/context-tree/docs/plans/lsp_registry_caplet_plan.md +++ /dev/null @@ -1,51 +0,0 @@ ---- -title: LSP Registry Caplet Plan -summary: A plan was created and saved for the LSP registry caplet, with execution options presented for either subagent-driven or inline implementation. -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T09:12:01.444Z' -updatedAt: '2026-05-27T09:12:01.444Z' -consolidated_at: '2026-05-27T10:03:41.871Z' -consolidated_from: [{date: '2026-05-27T10:03:41.871Z', path: docs/plans/lsp_registry_caplet_plan.abstract.md, reason: 'These three files describe the same LSP registry caplet planning outcome with near-duplicate content in three formats (full entry, abstract, and overview). They are redundant and should be consolidated into the richest file while preserving the unique phrasing and metadata details from the other two.'}, {date: '2026-05-27T10:03:41.871Z', path: docs/plans/lsp_registry_caplet_plan.overview.md, reason: 'These three files describe the same LSP registry caplet planning outcome with near-duplicate content in three formats (full entry, abstract, and overview). They are redundant and should be consolidated into the richest file while preserving the unique phrasing and metadata details from the other two.'}] ---- -## Reason -Persist the plan completion outcome and user-visible decision point from the conversation - -## Raw Concept -**Task:** -Record the completion of the LSP registry caplet planning step - -**Changes:** -- Created an implementation plan -- Saved the plan to the plans directory -- Presented two execution approaches - -**Files:** -- docs/plans/2026-05-26-lsp-registry-caplet.md - -**Flow:** -plan creation -> plan saved -> execution options presented - -**Timestamp:** 2026-05-27T09:11:49.118Z - -## Narrative -### Structure -This captures the outcome of a planning interaction for the LSP registry caplet. - -### Highlights -The plan was completed and saved, then the user was offered a choice between subagent-driven execution and inline execution. - -## Facts -- **plan_creation**: An implementation plan was created using the writing-plans skill. [project] -- **plan_file**: The plan was saved to docs/plans/2026-05-26-lsp-registry-caplet.md. [project] -- **execution_options**: Two execution options were presented: subagent-driven or inline execution. [project] - -## Overview -This document records the outcome of a planning step for the LSP registry caplet. -The plan was created using the writing-plans skill and saved to the project plans directory. -A user-visible decision point was presented after planning: choose between subagent-driven execution or inline execution. -The stated flow is: plan creation → plan saved → execution options presented. -The plan file is identified as docs/plans/2026-05-26-lsp-registry-caplet.md. -The document emphasizes persistence of the plan completion outcome and the interaction narrative, rather than implementation details. -Notable entities/patterns: "LSP registry caplet," "subagent-driven" vs "inline" execution, and the structured metadata fields for reason, raw concept, narrative, and facts. \ No newline at end of file diff --git a/.brv/context-tree/docs/plans/pr_71_completion_fix_outcome.md b/.brv/context-tree/docs/plans/pr_71_completion_fix_outcome.md deleted file mode 100644 index 56ff32ef..00000000 --- a/.brv/context-tree/docs/plans/pr_71_completion_fix_outcome.md +++ /dev/null @@ -1,57 +0,0 @@ ---- -title: PR 71 Completion Fix Outcome -summary: Completion-related PR review fixes were implemented and verified with focused tests and pnpm verify, with no unresolved outside-diff review threads found. -tags: [] -related: [docs/plans/context.md, docs/plans/completion_local_discovery_and_split_targets.md, docs/plans/completion_discovery_refactor_implementation_plan.md, docs/plans/active_caplets_planning_documents.md, docs/plans/docker_image_publishing_for_release_pipeline.md, docs/plans/release-automation-is-gated-by-verified-publish-state-not-just-workflow-success.md, docs/plans/release-and-completion-work-is-governed-by-verification-not-optimistic-success-s.md] -keywords: [] -createdAt: '2026-05-22T10:56:26.474Z' -updatedAt: '2026-05-22T10:56:26.474Z' ---- -## Reason -Document the reviewed completion-related fixes, verification, and release note update. - -## Raw Concept -**Task:** -Record the outcome of completing PR review fixes for the completion flow. - -**Changes:** -- Added OAuth callback fix to the release note -- Restored static local completion fallback on engine failure -- Guarded split-target completion for option flags -- Added regression coverage for option-flag discovery behavior - -**Files:** -- .changeset/local-completion-split-tools.md -- packages/core/src/cli.ts -- packages/core/src/cli/completion.ts -- packages/core/test/cli-completion.test.ts - -**Flow:** -implement fixes -> run focused tests -> run pnpm verify -> check review threads -> stop before commit/push - -**Timestamp:** 2026-05-22T10:56:10.364Z - -## Narrative -### Structure -The outcome spans release-note maintenance, CLI fallback behavior, completion token handling, and regression testing. - -### Dependencies -Validation depended on focused completion/CLI tests, the full pnpm verify gate, and PR review-thread inspection. - -### Highlights -Focused tests and pnpm verify passed; no unresolved outside-diff review threads remained. - -### Rules -Git commit/push requires explicit approval and was intentionally not performed. - -## Facts -- **pr_review_status**: All unresolved PR review comments found via GraphQL review threads were fixed. [project] -- **changeset_update**: .changeset/local-completion-split-tools.md was updated to add an OAuth public-origin callback fix to the release note. [project] -- **cli_fallback_behavior**: packages/core/src/cli.ts restored static local completion fallback when engine-backed hidden completion fails. [project] -- **completion_short_circuit**: packages/core/src/cli/completion.ts short-circuits split-target completion when the current token is an option flag. [project] -- **regression_test**: packages/core/test/cli-completion.test.ts gained regression coverage ensuring option-flag completion does not trigger discovery. [project] -- **verification_test**: Verification passed for pnpm --filter @caplets/core test -- test/cli-completion.test.ts. [project] -- **verification_test**: Verification passed for pnpm --filter @caplets/core test -- test/cli.test.ts. [project] -- **verification_gate**: Verification passed for pnpm verify. [project] -- **review_threads**: No unresolved outside-diff review threads were returned by the PR review-thread query. [project] -- **git_commit_push_status**: Git commit and push were not performed because explicit approval is required. [project] diff --git a/.brv/context-tree/docs/plans/release-and-completion-work-is-governed-by-verification-not-optimistic-success-s.md b/.brv/context-tree/docs/plans/release-and-completion-work-is-governed-by-verification-not-optimistic-success-s.md deleted file mode 100644 index 87d2af47..00000000 --- a/.brv/context-tree/docs/plans/release-and-completion-work-is-governed-by-verification-not-optimistic-success-s.md +++ /dev/null @@ -1,23 +0,0 @@ ---- -confidence: 0.94 -sources: [docs/_index.md, facts/_index.md, project/_index.md] -synthesized_at: '2026-05-26T19:23:43.121Z' -type: synthesis -title: Release and completion work is governed by verification, not optimistic success signals -summary: Planning and outcomes both emphasize gating behavior on explicit verification rather than workflow success alone. -tags: [release, completion, verification, fallback, automation] -related: [docs/plans/context.md, docs/plans/active_caplets_planning_documents.md, docs/plans/completion_discovery_refactor_implementation_plan.md, docs/plans/completion_local_discovery_and_split_targets.md, docs/plans/docker_image_publishing_for_release_pipeline.md, docs/plans/pr_71_completion_fix_outcome.md, docs/plans/release-automation-is-gated-by-verified-publish-state-not-just-workflow-success.md] -keywords: [gated, publish, fallback, auth, discovery, candidate, workflow, verification] -createdAt: '2026-05-26T19:23:43.121Z' -updatedAt: '2026-05-26T19:23:43.121Z' ---- - -# Release and completion work is governed by verification, not optimistic success signals - -Across docs and facts/project, the repository repeatedly treats completion and release behavior as verification-gated: completion falls back to safe candidates when discovery/auth is blocked, and release publishing waits for a verified publish state rather than assuming workflow success means it is safe to publish. - -## Evidence - -- **docs**: The release pipeline publishes GHCR Docker images only after Changesets publishes npm packages, and Docker publication is gated on verified publish state rather than workflow success alone. -- **facts**: Completion must not trigger interactive auth; if discovery is blocked, return stale cached results first, otherwise use static/config fallback only, and completion output remains candidate-only. -- **project**: The completion contract says failures and timeouts must degrade to safe fallbacks, and hidden `__complete` uses `CapletsEngine.completeCliWords`. diff --git a/.brv/context-tree/docs/plans/release-automation-is-gated-by-verified-publish-state-not-just-workflow-success.md b/.brv/context-tree/docs/plans/release-automation-is-gated-by-verified-publish-state-not-just-workflow-success.md deleted file mode 100644 index 16756ea8..00000000 --- a/.brv/context-tree/docs/plans/release-automation-is-gated-by-verified-publish-state-not-just-workflow-success.md +++ /dev/null @@ -1,22 +0,0 @@ ---- -confidence: 0.93 -sources: [docs/_index.md, facts/_index.md] -synthesized_at: '2026-05-21T23:23:26.220Z' -type: synthesis -title: Release automation is gated by verified publish state, not just workflow success -summary: The release pipeline only publishes Docker images after Changesets confirms a real npm publish, with validation checkpoints documented alongside it. -tags: [release, docker, changesets, verification] -related: [docs/plans/context.md, docs/plans/completion_local_discovery_and_split_targets.md, docs/plans/completion_discovery_refactor_implementation_plan.md, docs/plans/active_caplets_planning_documents.md, docs/plans/docker_image_publishing_for_release_pipeline.md, docs/plans/pr_71_completion_fix_outcome.md, docs/plans/release-and-completion-work-is-governed-by-verification-not-optimistic-success-s.md] -keywords: [publish, changesets, ghcr, docker, gating, workflow, validation, release] -createdAt: '2026-05-21T23:23:26.220Z' -updatedAt: '2026-05-21T23:23:26.220Z' ---- - -# Release automation is gated by verified publish state, not just workflow success - -The docs and facts together show a release policy that ties Docker image publication to an authenticated upstream publish signal, not merely a successful workflow run. The same durable-knowledge practice records which checks passed and which validation remains missing, making the release pipeline explicitly stateful and auditable. - -## Evidence - -- **docs**: The approved GHCR release flow publishes Docker images only after Changesets publishes npm packages, requires `packages: write`, and gates Docker/GHCR steps on a real publish signal plus CLI package publication. -- **facts**: The project knowledge stores review outcomes and verification patterns, including passing `pnpm format:check`, `actionlint`, and `docker build --check`, while noting that a full `docker build -t ... .` remains the missing validation item in review-only context. diff --git a/.brv/context-tree/em_dash_usage/em_dash_usage/em_dash_usage.md b/.brv/context-tree/em_dash_usage/em_dash_usage/em_dash_usage.md deleted file mode 100644 index d02f00be..00000000 --- a/.brv/context-tree/em_dash_usage/em_dash_usage/em_dash_usage.md +++ /dev/null @@ -1,30 +0,0 @@ ---- -title: em dash usage -summary: Facts about em dash usage -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T11:17:08.565Z' -updatedAt: '2026-05-28T11:17:08.565Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Document extracted factual statements - -**Flow:** -extraction -> curation - -**Timestamp:** 2026-05-28T11:17:08.519Z - -## Narrative -### Structure -Facts grouped under em dash usage - -### Highlights -Contains 1 factual statements - -## Facts -- **em dash usage**: No em dashes found in inspected landing files. diff --git a/.brv/context-tree/extracted/curated/accessibility.md b/.brv/context-tree/extracted/curated/accessibility.md deleted file mode 100644 index ad9e5461..00000000 --- a/.brv/context-tree/extracted/curated/accessibility.md +++ /dev/null @@ -1,27 +0,0 @@ ---- -title: Accessibility -summary: Facts about Accessibility -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T11:22:45.568Z' -updatedAt: '2026-05-28T11:22:45.568Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Document facts for Accessibility - -**Flow:** -extraction -> curation - -**Timestamp:** 2026-05-28T11:22:45.563Z - -## Narrative -### Highlights -Extracted 1 facts for Accessibility - -## Facts -- **Accessibility**: Accessibility score is Excellent. diff --git a/.brv/context-tree/extracted/curated/ai_slop_detection.md b/.brv/context-tree/extracted/curated/ai_slop_detection.md deleted file mode 100644 index d490646e..00000000 --- a/.brv/context-tree/extracted/curated/ai_slop_detection.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -title: AI Slop Detection -summary: Facts about AI Slop Detection -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T09:43:19.232Z' -updatedAt: '2026-05-28T09:43:19.232Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-28T09:43:19.220Z - -## Facts -- **AI Slop Detection**: AI Slop Detection is a critical evaluation criterion for the interface design. diff --git a/.brv/context-tree/extracted/curated/anti_patterns.md b/.brv/context-tree/extracted/curated/anti_patterns.md deleted file mode 100644 index f8ce4f2f..00000000 --- a/.brv/context-tree/extracted/curated/anti_patterns.md +++ /dev/null @@ -1,27 +0,0 @@ ---- -title: anti-patterns -summary: Facts about anti-patterns -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T11:22:45.574Z' -updatedAt: '2026-05-28T11:22:45.588Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Document facts for anti-patterns - -**Flow:** -extraction -> curation - -**Timestamp:** 2026-05-28T11:22:45.563Z - -## Narrative -### Highlights -Extracted 1 facts for anti-patterns - -## Facts -- **anti-patterns**: No actionable P0, P1, or P2 anti-pattern issues were found. diff --git a/.brv/context-tree/extracted/curated/audit.md b/.brv/context-tree/extracted/curated/audit.md deleted file mode 100644 index b03ef940..00000000 --- a/.brv/context-tree/extracted/curated/audit.md +++ /dev/null @@ -1,27 +0,0 @@ ---- -title: audit -summary: Facts about audit -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T11:22:45.599Z' -updatedAt: '2026-05-28T11:22:45.599Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Document facts for audit - -**Flow:** -extraction -> curation - -**Timestamp:** 2026-05-28T11:22:45.563Z - -## Narrative -### Highlights -Extracted 1 facts for audit - -## Facts -- **audit**: The audit result is APPROVED. diff --git a/.brv/context-tree/extracted/curated/browser_automation.md b/.brv/context-tree/extracted/curated/browser_automation.md deleted file mode 100644 index e9381af1..00000000 --- a/.brv/context-tree/extracted/curated/browser_automation.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -title: Browser automation -summary: Facts about browser automation -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T09:43:19.225Z' -updatedAt: '2026-05-28T09:43:19.225Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-28T09:43:19.220Z - -## Facts -- **browser automation**: Each assessment MUST create its own new tab when browser automation is available. diff --git a/.brv/context-tree/extracted/curated/browser_detection.md b/.brv/context-tree/extracted/curated/browser_detection.md deleted file mode 100644 index 3b78fd58..00000000 --- a/.brv/context-tree/extracted/curated/browser_detection.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -title: Browser detection -summary: Facts about Browser detection -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T09:43:19.258Z' -updatedAt: '2026-05-28T09:43:19.258Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-28T09:43:19.220Z - -## Facts -- **Browser detection**: The detector logs all findings with the `[impeccable]` prefix diff --git a/.brv/context-tree/extracted/curated/browser_visualization.md b/.brv/context-tree/extracted/curated/browser_visualization.md deleted file mode 100644 index 75f72a72..00000000 --- a/.brv/context-tree/extracted/curated/browser_visualization.md +++ /dev/null @@ -1,21 +0,0 @@ ---- -title: Browser visualization -summary: Facts about Browser visualization -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T09:43:19.254Z' -updatedAt: '2026-05-28T09:43:19.254Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-28T09:43:19.220Z - -## Facts -- **Browser visualization**: The overlay is a visual aid for the user -- **Browser visualization**: Do NOT scroll through the page to screenshot overlays diff --git a/.brv/context-tree/extracted/curated/build_verification.md b/.brv/context-tree/extracted/curated/build_verification.md deleted file mode 100644 index c8926173..00000000 --- a/.brv/context-tree/extracted/curated/build_verification.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: build verification -summary: Facts about build verification -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T09:09:29.211Z' -updatedAt: '2026-05-28T09:09:29.211Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-28T09:09:29.206Z - -## Narrative -### Highlights -Extracted 1 facts for build verification - -## Facts -- **build verification**: Verified commands: `pnpm format:check`, `pnpm --filter @caplets/landing typecheck`, and `pnpm --filter @caplets/landing build` diff --git a/.brv/context-tree/extracted/curated/claude_code_install_command.md b/.brv/context-tree/extracted/curated/claude_code_install_command.md deleted file mode 100644 index 27d9ea18..00000000 --- a/.brv/context-tree/extracted/curated/claude_code_install_command.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Claude Code install command -summary: Facts about Claude Code install command -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T09:09:29.207Z' -updatedAt: '2026-05-28T09:09:29.207Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-28T09:09:29.206Z - -## Narrative -### Highlights -Extracted 1 facts for Claude Code install command - -## Facts -- **Claude Code install command**: Claude Code install command is `npm install -g caplets && claude plugin marketplace add spiritledsoftware/caplets && claude plugin install caplets@caplets` diff --git a/.brv/context-tree/extracted/curated/cli_scan.md b/.brv/context-tree/extracted/curated/cli_scan.md deleted file mode 100644 index 1e12d99d..00000000 --- a/.brv/context-tree/extracted/curated/cli_scan.md +++ /dev/null @@ -1,22 +0,0 @@ ---- -title: CLI scan -summary: Facts about CLI scan -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T09:43:19.253Z' -updatedAt: '2026-05-28T09:43:19.253Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-28T09:43:19.220Z - -## Facts -- **CLI scan**: For large directories (200+ scannable files), use `--fast` (regex-only, skips jsdom) -- **CLI scan**: For 500+ files, narrow scope or ask the user -- **CLI scan**: Pass HTML/JSX/TSX/Vue/Svelte files or directories as `[target]` (anything with markup). Do not pass CSS-only files. diff --git a/.brv/context-tree/extracted/curated/codex_install_command.md b/.brv/context-tree/extracted/curated/codex_install_command.md deleted file mode 100644 index 6a2f3595..00000000 --- a/.brv/context-tree/extracted/curated/codex_install_command.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Codex install command -summary: Facts about Codex install command -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T09:09:29.210Z' -updatedAt: '2026-05-28T09:09:29.210Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-28T09:09:29.206Z - -## Narrative -### Highlights -Extracted 1 facts for Codex install command - -## Facts -- **Codex install command**: Codex install command is `npm install -g caplets && codex plugin marketplace add spiritledsoftware/caplets` diff --git a/.brv/context-tree/extracted/curated/cognitive_load.md b/.brv/context-tree/extracted/curated/cognitive_load.md deleted file mode 100644 index e60a1a2b..00000000 --- a/.brv/context-tree/extracted/curated/cognitive_load.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -title: Cognitive load -summary: Facts about cognitive load -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T09:43:19.235Z' -updatedAt: '2026-05-28T09:43:19.235Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-28T09:43:19.220Z - -## Facts -- **cognitive load**: Cognitive load checklist failure count of 0-1 is considered low, 2-3 moderate, and 4+ critical. diff --git a/.brv/context-tree/extracted/curated/curated_content.md b/.brv/context-tree/extracted/curated/curated_content.md deleted file mode 100644 index 9e4c78aa..00000000 --- a/.brv/context-tree/extracted/curated/curated_content.md +++ /dev/null @@ -1,38 +0,0 @@ ---- -title: Curated Content -summary: Curated factual statements extracted from provided context -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T09:56:03.656Z' -updatedAt: '2026-05-28T13:24:55.559Z' ---- -## Reason -Store extracted factual statements from curating task - -## Raw Concept -**Task:** -Curate extracted factual statements - -**Flow:** -extraction -> deduplication -> curation - -**Timestamp:** 2026-05-28T13:24:55.556Z - -## Narrative -### Structure -Curated factual statements from provided context - -### Highlights -hero text, desktop hero wrap, global.css, hero grid, desktop h1, line height and letter spacing, headline, browser size, checks - -## Facts -- **hero text**: The hero text "Skillify your backends." was wrapped weird on desktop -- **desktop hero wrap**: The desktop hero wrap was fixed -- **global.css**: The file apps/landing/src/styles/global.css was changed -- **hero grid**: The hero grid was rebalanced so the text column has enough room -- **desktop h1**: The oversized desktop h1 scale was reduced -- **line height and letter spacing**: The line height and letter spacing were relaxed -- **headline**: The result is now a clean two-line headline: "Skillify your" and "backends." -- **browser size**: Verified in browser at 1440 × 900 -- **checks**: Build checks passed: pnpm --filter @caplets/landing typecheck and pnpm --filter @caplets/landing build diff --git a/.brv/context-tree/extracted/curated/curated_context.md b/.brv/context-tree/extracted/curated/curated_context.md deleted file mode 100644 index 7d033b40..00000000 --- a/.brv/context-tree/extracted/curated/curated_context.md +++ /dev/null @@ -1,39 +0,0 @@ ---- -title: curated_context -summary: Extracted factual statements from context -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T10:33:03.171Z' -updatedAt: '2026-05-28T13:39:45.007Z' ---- -## Reason -Curate extracted facts from context variable - -## Raw Concept -**Task:** -Curated knowledge from provided context - -**Flow:** -extraction - -**Timestamp:** 2026-05-28T13:39:44.994Z - -## Narrative -### Structure -Aggregated factual statements extracted from context - -### Highlights -Task 6, codebase, typecheck, build, node deprecation, risks, next steps - -## Facts -- **changed files**: Unexpected changed files are present. -- **expected changed file**: Spec expects only the file apps/landing/src/styles/global.css to be changed. -- **global.css**: apps/landing/src/styles/global.css appears compliant with the touch‑target requirements. -- **mobile brand target**: Mobile brand target has min‑width: 44px and min‑height: 44px (lines 1118‑1122). -- **mobile nav/header actions**: Mobile nav/header actions have min‑height: 44px (lines 1140‑1144). -- **npm/GitHub icon links**: npm/GitHub icon links have width: 44px via .icon-link (lines 1155‑1157) combined with .header-action min‑height. -- **footer links**: Footer links have min‑height: 44px, display: flex, align‑items: center, padding‑block: 8px (lines 1254‑1258). -- **integration pill**: Integration pill has min‑width: 44px and min‑height: 44px (lines 791‑793). -- **header layout**: Header remains compact/full‑width, not a tall card; .site-header width: 100%, sticky at top: 0, with small mobile padding (lines 121‑133 and 1111‑1115). -- **design constraints**: No CSS evidence found for em dashes, side‑stripe borders, gradient text, or glassmorphism. diff --git a/.brv/context-tree/extracted/curated/curated_extraction.md b/.brv/context-tree/extracted/curated/curated_extraction.md deleted file mode 100644 index 88f24874..00000000 --- a/.brv/context-tree/extracted/curated/curated_extraction.md +++ /dev/null @@ -1,35 +0,0 @@ ---- -title: Curated Extraction -summary: Extracted factual statements organized by subject -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T09:24:42.638Z' -updatedAt: '2026-05-28T09:56:12.966Z' ---- -## Reason -Curate extracted facts from provided context - -## Raw Concept -**Task:** -Extract and organize factual statements - -**Changes:** -- Extracted facts from context -- Extracted facts -- Deduplicated -- Grouped by subject - -**Timestamp:** 2026-05-28T09:56:12.962Z - -## Narrative -### Structure -Facts extracted and deduplicated - -### Highlights -implementation plan, motion systems, fix-all plan - -## Facts -- **implementation plan**: The implementation plan should remove substantial animation code, not just hide it behind toned-down styling. -- **motion systems**: The page contains multiple motion systems: scroll-progress route resolution, pointer-reactive perspective tilt, route drift animation, entrance choreography, reveal choreography, glow and drop-shadow route effects, and a large animated hero headline treatment. -- **fix-all plan**: The fix-all plan includes: keep reduced-motion support; keep small hover/focus transitions; keep maybe one restrained reveal pattern if it clarifies scanning; remove pointer tilt and scroll-driven route resolution; replace animated cartography with a mostly static, inspectable trace panel. diff --git a/.brv/context-tree/extracted/curated/curated_facts.md b/.brv/context-tree/extracted/curated/curated_facts.md deleted file mode 100644 index 2f12b583..00000000 --- a/.brv/context-tree/extracted/curated/curated_facts.md +++ /dev/null @@ -1,120 +0,0 @@ ---- -title: curated_facts -summary: Curated factual statements extracted from raw context -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T09:46:02.466Z' -updatedAt: '2026-05-28T09:46:02.466Z' ---- -## Reason -Store extracted factual statements from context - -## Raw Concept -**Task:** -Curate extracted factual statements from provided context - -**Changes:** -- Extracted factual statements - -**Timestamp:** 2026-05-28T09:46:02.465Z - -## Narrative -### Highlights -Extracted factual statements organized by subject - -### Examples -[ - { - "statement": "The CLI detector `npx impeccable --json apps/landing/src/pages/index.astro` returned an empty JSON array `[]`.", - "subject": "CLI detector output" - }, - { - "statement": "The detector reported no findings.", - "subject": "detector summary" - }, - { - "statement": "The detector likely misses CSS-driven brand and motion violations because most visual issues live in `global.css`.", - "subject": "detector blind spot" - }, - { - "statement": "The detector likely does not compare implementation against `PRODUCT.md` and `DESIGN.md`.", - "subject": "detector blind spot" - }, - { - "statement": "The detector likely does not reason about responsive layout quality, especially the mobile hero and map height.", - "subject": "detector blind spot" - }, - { - "statement": "The detector likely does not inspect interaction fallback behavior, such as tab content when JavaScript is unavailable.", - "subject": "detector blind spot" - }, - { - "statement": "The detector likely does not flag content credibility issues, such as inconsistent install commands.", - "subject": "detector blind spot" - }, - { - "statement": "`DESIGN.md` specifies a quiet confidence brand with no decorative gradients, orchestrated page-load choreography, or glassmorphism.", - "subject": "DESIGN.md" - }, - { - "statement": "The CSS uses radial gradients, perspective tilt, route glow/drop-shadow, sticky blurred header, large entrance animations, pointer-reactive map transforms, and animated route drift.", - "subject": "global.css" - }, - { - "statement": "The hero `

` has a font size of 9.5rem, line-height of 0.82, and letter-spacing of -0.085em.", - "subject": "hero typography" - }, - { - "statement": "Under a viewport width of 720px, `.map-stage` has a minimum height of 770px.", - "subject": "mobile layout" - }, - { - "statement": "Reduced-motion handling exists in the page.", - "subject": "motion policy" - }, - { - "statement": "`PRODUCT.md` advises avoiding neon/devtool dark and terminal theatrics.", - "subject": "PRODUCT.md" - }, - { - "statement": "The page includes a dark integrations block and a dark terminal install section.", - "subject": "page content" - }, - { - "statement": "The hero/install flow uses the command `pnpm add -g caplets`.", - "subject": "install command" - }, - { - "statement": "Agent setup cards use the command `npm install -g caplets` for OpenCode, Pi, and generic MCP.", - "subject": "install command" - }, - { - "statement": "Tabs after the first are rendered with `hidden={index !== 0}` causing them to be inaccessible without JavaScript.", - "subject": "tab markup" - }, - { - "statement": "The `.status-dot` element is green and has `aria-hidden` attribute, providing only a color cue for status.", - "subject": "status indicator" - } -] - -## Facts -- **CLI detector output**: The CLI detector `npx impeccable --json apps/landing/src/pages/index.astro` returned an empty JSON array `[]`. -- **detector summary**: The detector reported no findings. -- **detector blind spot**: The detector likely misses CSS-driven brand and motion violations because most visual issues live in `global.css`. -- **detector blind spot**: The detector likely does not compare implementation against `PRODUCT.md` and `DESIGN.md`. -- **detector blind spot**: The detector likely does not reason about responsive layout quality, especially the mobile hero and map height. -- **detector blind spot**: The detector likely does not inspect interaction fallback behavior, such as tab content when JavaScript is unavailable. -- **detector blind spot**: The detector likely does not flag content credibility issues, such as inconsistent install commands. -- **DESIGN.md**: `DESIGN.md` specifies a quiet confidence brand with no decorative gradients, orchestrated page-load choreography, or glassmorphism. -- **global.css**: The CSS uses radial gradients, perspective tilt, route glow/drop-shadow, sticky blurred header, large entrance animations, pointer-reactive map transforms, and animated route drift. -- **hero typography**: The hero `

` has a font size of 9.5rem, line-height of 0.82, and letter-spacing of -0.085em. -- **mobile layout**: Under a viewport width of 720px, `.map-stage` has a minimum height of 770px. -- **motion policy**: Reduced-motion handling exists in the page. -- **PRODUCT.md**: `PRODUCT.md` advises avoiding neon/devtool dark and terminal theatrics. -- **page content**: The page includes a dark integrations block and a dark terminal install section. -- **install command**: The hero/install flow uses the command `pnpm add -g caplets`. -- **install command**: Agent setup cards use the command `npm install -g caplets` for OpenCode, Pi, and generic MCP. -- **tab markup**: Tabs after the first are rendered with `hidden={index !== 0}` causing them to be inaccessible without JavaScript. -- **status indicator**: The `.status-dot` element is green and has `aria-hidden` attribute, providing only a color cue for status. diff --git a/.brv/context-tree/extracted/curated/curated_knowledge.md b/.brv/context-tree/extracted/curated/curated_knowledge.md deleted file mode 100644 index 8c097a8c..00000000 --- a/.brv/context-tree/extracted/curated/curated_knowledge.md +++ /dev/null @@ -1,34 +0,0 @@ ---- -title: Curated Knowledge -summary: Aggregated factual statements extracted and deduplicated -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T09:30:44.778Z' -updatedAt: '2026-05-28T09:30:44.778Z' ---- -## Reason -Curate extracted facts from context - -## Raw Concept -**Task:** -Curate extracted factual statements from provided context - -**Flow:** -extraction -> deduplication -> curation - -**Timestamp:** 2026-05-28T09:30:44.777Z - -## Narrative -### Structure -Aggregated factual statements grouped by subject - -### Highlights -apps/landing/src/styles/global.css, d2b942a, pnpm format:check, pnpm typecheck, .github/workflows/pr-preview.yml - -## Facts -- **apps/landing/src/styles/global.css**: Changed files: apps/landing/src/styles/global.css -- **d2b942a**: Commit created: d2b942a fix(landing): remove unused npm icon styles -- **pnpm format:check**: pnpm format:check passed after formatting the touched CSS file -- **pnpm typecheck**: pnpm --filter @caplets/landing typecheck passed with 0 errors, 0 warnings, 0 hints -- **.github/workflows/pr-preview.yml**: Commit was created with --no-verify because the pre-commit hook failed on pre-existing unrelated formatting in untracked .github/workflows/pr-preview.yml diff --git a/.brv/context-tree/extracted/curated/extracted_context.md b/.brv/context-tree/extracted/curated/extracted_context.md deleted file mode 100644 index 1f48dfec..00000000 --- a/.brv/context-tree/extracted/curated/extracted_context.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: extracted_context -summary: Contains extracted factual statements from provided short context -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T12:52:11.616Z' -updatedAt: '2026-05-28T12:52:11.616Z' ---- -## Reason -Curate extracted facts from short context via RLM single-pass - -## Raw Concept -**Task:** -Curate extracted knowledge from provided context - -**Flow:** -extraction -> deduplication -> curation - -**Timestamp:** 2026-05-28T12:52:11.615Z - -## Narrative -### Structure -Aggregated facts extracted from short context diff --git a/.brv/context-tree/extracted/curated/extracted_facts.md b/.brv/context-tree/extracted/curated/extracted_facts.md deleted file mode 100644 index c25b2445..00000000 --- a/.brv/context-tree/extracted/curated/extracted_facts.md +++ /dev/null @@ -1,41 +0,0 @@ ---- -title: extracted_facts -summary: Extracted factual statements from provided context -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T11:15:49.857Z' -updatedAt: '2026-05-28T13:27:23.975Z' ---- -## Reason -Curate extracted factual statements from context - -## Raw Concept -**Task:** -Curate extracted facts - -**Changes:** -- Extracted facts from provided context - -**Flow:** -extract -> dedup -> group -> curate - -**Timestamp:** 2026-05-28T13:27:23.971Z - -## Narrative -### Structure -Aggregated factual statements - -### Highlights -hero layout, trace column, hero text column, headline sizing, trace card width - -## Facts -- **hero layout**: Rebalanced the hero layout so the trace card is wider again while keeping the headline to a clean two-line wrap. -- **trace column**: Increased trace column from 576px to about 660px at desktop width in apps/landing/src/styles/global.css. -- **hero text column**: Reduced the hero text column slightly. -- **headline sizing**: Adjusted headline sizing so it still wraps as: “Skillify your” and “backends.” -- **trace card width**: Browser verified at 1440 × 900 that trace card width is 660px. -- **trace card overflow**: No trace card horizontal overflow observed. -- **hero headline**: Hero headline remains two lines. -- **typecheck**: Build check passed: pnpm --filter @caplets/landing typecheck. -- **build**: Build check passed: pnpm --filter @caplets/landing build. diff --git a/.brv/context-tree/extracted/curated/github_job_url.md b/.brv/context-tree/extracted/curated/github_job_url.md deleted file mode 100644 index ffbaacd1..00000000 --- a/.brv/context-tree/extracted/curated/github_job_url.md +++ /dev/null @@ -1,14 +0,0 @@ ---- -title: GitHub job URL -summary: Extracted facts about GitHub job URL -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T11:51:29.535Z' -updatedAt: '2026-05-28T11:51:29.535Z' ---- -## Reason -Curate extracted facts from RLM context - -## Facts -- **GitHub job URL**: https://github.com/spiritledsoftware/caplets/actions/runs/26572806394/job/78284056512 diff --git a/.brv/context-tree/extracted/curated/heuristics_scoring.md b/.brv/context-tree/extracted/curated/heuristics_scoring.md deleted file mode 100644 index 10e61951..00000000 --- a/.brv/context-tree/extracted/curated/heuristics_scoring.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -title: Heuristics scoring -summary: Facts about heuristics scoring -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T09:43:19.248Z' -updatedAt: '2026-05-28T09:43:19.248Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-28T09:43:19.220Z - -## Facts -- **heuristics scoring**: Nielsen's Heuristics should be scored 0-4 for each of the 10 heuristics. diff --git a/.brv/context-tree/extracted/curated/impeccable_tool.md b/.brv/context-tree/extracted/curated/impeccable_tool.md deleted file mode 100644 index b65a55c9..00000000 --- a/.brv/context-tree/extracted/curated/impeccable_tool.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -title: Impeccable tool -summary: Facts about impeccable tool -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T09:43:19.251Z' -updatedAt: '2026-05-28T09:43:19.251Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-28T09:43:19.220Z - -## Facts -- **impeccable tool**: Exit code 0 = clean, 2 = findings diff --git a/.brv/context-tree/extracted/curated/interface.md b/.brv/context-tree/extracted/curated/interface.md deleted file mode 100644 index 2f98e55b..00000000 --- a/.brv/context-tree/extracted/curated/interface.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -title: Interface -summary: Facts about interface -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T09:43:19.271Z' -updatedAt: '2026-05-28T09:43:19.271Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-28T09:43:19.220Z - -## Facts -- **interface**: The interface feels clinical and corporate. diff --git a/.brv/context-tree/extracted/curated/interface_scores.md b/.brv/context-tree/extracted/curated/interface_scores.md deleted file mode 100644 index ace8b75b..00000000 --- a/.brv/context-tree/extracted/curated/interface_scores.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -title: Interface scores -summary: Facts about interface_scores -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T09:43:19.263Z' -updatedAt: '2026-05-28T09:43:19.263Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-28T09:43:19.220Z - -## Facts -- **interface_scores**: Most real interfaces score 20-32. diff --git a/.brv/context-tree/extracted/curated/issues.md b/.brv/context-tree/extracted/curated/issues.md deleted file mode 100644 index 4c2a14e9..00000000 --- a/.brv/context-tree/extracted/curated/issues.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -title: Issues -summary: Facts about issues -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T09:43:19.272Z' -updatedAt: '2026-05-28T09:43:19.272Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-28T09:43:19.220Z - -## Facts -- **issues**: I found N issues. diff --git a/.brv/context-tree/extracted/curated/option_count.md b/.brv/context-tree/extracted/curated/option_count.md deleted file mode 100644 index 82fc77ee..00000000 --- a/.brv/context-tree/extracted/curated/option_count.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -title: Option count -summary: Facts about option count -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T09:43:19.245Z' -updatedAt: '2026-05-28T09:43:19.245Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-28T09:43:19.220Z - -## Facts -- **option count**: If visible options at a decision point exceed 4, it should be flagged. diff --git a/.brv/context-tree/extracted/curated/performance.md b/.brv/context-tree/extracted/curated/performance.md deleted file mode 100644 index d3bf50b6..00000000 --- a/.brv/context-tree/extracted/curated/performance.md +++ /dev/null @@ -1,27 +0,0 @@ ---- -title: Performance -summary: Facts about Performance -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T11:22:45.569Z' -updatedAt: '2026-05-28T11:22:45.569Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Document facts for Performance - -**Flow:** -extraction -> curation - -**Timestamp:** 2026-05-28T11:22:45.563Z - -## Narrative -### Highlights -Extracted 1 facts for Performance - -## Facts -- **Performance**: Performance score is Good. diff --git a/.brv/context-tree/extracted/curated/persona_generation.md b/.brv/context-tree/extracted/curated/persona_generation.md deleted file mode 100644 index 3ecea568..00000000 --- a/.brv/context-tree/extracted/curated/persona_generation.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -title: Persona generation -summary: Facts about persona_generation -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T09:43:19.269Z' -updatedAt: '2026-05-28T09:43:19.269Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-28T09:43:19.220Z - -## Facts -- **persona_generation**: If .github/copilot-instructions.md contains a ## Design Context section from impeccable teach, also generate 1-2 project-specific personas. diff --git a/.brv/context-tree/extracted/curated/responsive.md b/.brv/context-tree/extracted/curated/responsive.md deleted file mode 100644 index 07615667..00000000 --- a/.brv/context-tree/extracted/curated/responsive.md +++ /dev/null @@ -1,27 +0,0 @@ ---- -title: Responsive -summary: Facts about Responsive -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T11:22:45.571Z' -updatedAt: '2026-05-28T11:22:45.571Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Document facts for Responsive - -**Flow:** -extraction -> curation - -**Timestamp:** 2026-05-28T11:22:45.563Z - -## Narrative -### Highlights -Extracted 1 facts for Responsive - -## Facts -- **Responsive**: Responsive score is Excellent. diff --git a/.brv/context-tree/extracted/curated/score_meaning.md b/.brv/context-tree/extracted/curated/score_meaning.md deleted file mode 100644 index 1a6bd589..00000000 --- a/.brv/context-tree/extracted/curated/score_meaning.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -title: Score meaning -summary: Facts about score_meaning -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T09:43:19.259Z' -updatedAt: '2026-05-28T09:43:19.259Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-28T09:43:19.220Z - -## Facts -- **score_meaning**: A 4 means genuinely excellent. diff --git a/.brv/context-tree/extracted/curated/source_inspection.md b/.brv/context-tree/extracted/curated/source_inspection.md deleted file mode 100644 index e981e4fd..00000000 --- a/.brv/context-tree/extracted/curated/source_inspection.md +++ /dev/null @@ -1,27 +0,0 @@ ---- -title: source inspection -summary: Facts about source inspection -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T11:22:45.566Z' -updatedAt: '2026-05-28T11:22:45.566Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Document facts for source inspection - -**Flow:** -extraction -> curation - -**Timestamp:** 2026-05-28T11:22:45.563Z - -## Narrative -### Highlights -Extracted 1 facts for source inspection - -## Facts -- **source inspection**: Source inspection was completed for apps/landing/src/pages/index.astro, apps/landing/src/styles/global.css, PRODUCT.md, and DESIGN.md. diff --git a/.brv/context-tree/extracted/curated/sub_agents.md b/.brv/context-tree/extracted/curated/sub_agents.md deleted file mode 100644 index 2ed1bd8d..00000000 --- a/.brv/context-tree/extracted/curated/sub_agents.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -title: Sub-agents -summary: Facts about sub-agents -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T09:43:19.222Z' -updatedAt: '2026-05-28T09:43:19.222Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-28T09:43:19.219Z - -## Facts -- **sub-agents**: If sub-agents are not available in the current environment, complete each assessment sequentially, writing findings to internal notes before proceeding. diff --git a/.brv/context-tree/extracted/curated/tab_labeling.md b/.brv/context-tree/extracted/curated/tab_labeling.md deleted file mode 100644 index df8716a6..00000000 --- a/.brv/context-tree/extracted/curated/tab_labeling.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -title: Tab labeling -summary: Facts about tab labeling -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T09:43:19.230Z' -updatedAt: '2026-05-28T09:43:19.230Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-28T09:43:19.220Z - -## Facts -- **tab labeling**: Assessment A should label its tab by setting the document title to '[LLM] ' + document.title. diff --git a/.brv/context-tree/extracted/curated/tab_usage.md b/.brv/context-tree/extracted/curated/tab_usage.md deleted file mode 100644 index 76ce0ca8..00000000 --- a/.brv/context-tree/extracted/curated/tab_usage.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -title: Tab usage -summary: Facts about tab usage -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T09:43:19.228Z' -updatedAt: '2026-05-28T09:43:19.228Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-28T09:43:19.220Z - -## Facts -- **tab usage**: Never reuse an existing tab, even if one is already open at the correct URL. diff --git a/.brv/context-tree/extracted/curated/theming.md b/.brv/context-tree/extracted/curated/theming.md deleted file mode 100644 index e8d1a9df..00000000 --- a/.brv/context-tree/extracted/curated/theming.md +++ /dev/null @@ -1,27 +0,0 @@ ---- -title: Theming -summary: Facts about Theming -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T11:22:45.572Z' -updatedAt: '2026-05-28T11:22:45.572Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Document facts for Theming - -**Flow:** -extraction -> curation - -**Timestamp:** 2026-05-28T11:22:45.563Z - -## Narrative -### Highlights -Extracted 1 facts for Theming - -## Facts -- **Theming**: Theming score is Good. diff --git a/.brv/context-tree/extracted/curated/user.md b/.brv/context-tree/extracted/curated/user.md deleted file mode 100644 index 456ce8b9..00000000 --- a/.brv/context-tree/extracted/curated/user.md +++ /dev/null @@ -1,21 +0,0 @@ ---- -title: User -summary: Facts about user -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T09:43:19.274Z' -updatedAt: '2026-05-28T09:43:19.274Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-28T09:43:19.220Z - -## Facts -- **user**: You can ask me to run these one at a time, all at once, or in any order you prefer. -- **user**: Re-run `/critique` after fixes to see your score improve. diff --git a/.brv/context-tree/extracted/curated/visual_hierarchy_color_usage_information_overload.md b/.brv/context-tree/extracted/curated/visual_hierarchy_color_usage_information_overload.md deleted file mode 100644 index 55cccad5..00000000 --- a/.brv/context-tree/extracted/curated/visual_hierarchy_color_usage_information_overload.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -title: Visual hierarchy, color usage, information overload -summary: Facts about visual hierarchy, color usage, information overload -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T09:43:19.270Z' -updatedAt: '2026-05-28T09:43:19.270Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-28T09:43:19.220Z - -## Facts -- **visual hierarchy, color usage, information overload**: I found problems with visual hierarchy, color usage, and information overload. diff --git a/.brv/context-tree/extracted/curated/visual_overlays.md b/.brv/context-tree/extracted/curated/visual_overlays.md deleted file mode 100644 index 9840165e..00000000 --- a/.brv/context-tree/extracted/curated/visual_overlays.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -title: Visual overlays -summary: Facts about visual_overlays -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T09:43:19.265Z' -updatedAt: '2026-05-28T09:43:19.265Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-28T09:43:19.220Z - -## Facts -- **visual_overlays**: Visual overlays are now visible in the [Human] tab in the browser, highlighting the detected issues. diff --git a/.brv/context-tree/extracted/curated_context/codex_and_claude_code_install_commands.md b/.brv/context-tree/extracted/curated_context/codex_and_claude_code_install_commands.md deleted file mode 100644 index 9b591e7a..00000000 --- a/.brv/context-tree/extracted/curated_context/codex_and_claude_code_install_commands.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: codex_and_claude_code_install_commands -summary: Facts about codex and claude code install commands -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T09:07:52.574Z' -updatedAt: '2026-05-28T09:07:52.574Z' ---- -## Reason -Curated extracted facts from RLM context - -## Raw Concept -**Task:** -Document extracted facts for codex and claude code install commands - -**Timestamp:** 2026-05-28T09:07:52.568Z - -## Narrative -### Highlights -Extracted 1 facts for codex and claude code install commands - -## Facts -- **codex and claude code install commands**: The codex and claude code install commands are wrong diff --git a/.brv/context-tree/extracted/curated_context/curated_context_extraction.md b/.brv/context-tree/extracted/curated_context/curated_context_extraction.md deleted file mode 100644 index 62fd8a1a..00000000 --- a/.brv/context-tree/extracted/curated_context/curated_context_extraction.md +++ /dev/null @@ -1,38 +0,0 @@ ---- -title: Curated Context Extraction -summary: Curated factual statements from provided context -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T23:53:36.112Z' -updatedAt: '2026-05-27T23:53:36.112Z' ---- -## Reason -Store extracted factual statements from RLM curating session - -## Raw Concept -**Task:** -Curate extracted knowledge from provided context - -**Flow:** -extraction -> deduplication -> curation - -**Timestamp:** 2026-05-27T23:53:36.110Z - -## Narrative -### Structure -Aggregated factual statements extracted from context - -### Highlights -landing setup, apps/landing/src/pages/index.astro, 352c0d5, README files, pnpm format, typecheck, commit hooks, risks, unstaged changes - -## Facts -- **landing setup**: Implemented the landing setup snippet fixes. -- **apps/landing/src/pages/index.astro**: Changed files: - apps/landing/src/pages/index.astro -- **352c0d5**: Commit: 352c0d5 fix(landing): correct native agent setup snippets -- **README files**: Read and verified relevant sections in: packages/opencode/README.md, packages/pi/README.md -- **pnpm format**: pnpm format:check passed -- **typecheck**: pnpm --filter @caplets/landing typecheck passed -- **commit hooks**: Commit hook checks also passed -- **risks**: Open risks/questions: None for this task. -- **unstaged changes**: Existing unrelated unstaged changes remain untouched. diff --git a/.brv/context-tree/extracted/curated_extraction/curated_extraction.md b/.brv/context-tree/extracted/curated_extraction/curated_extraction.md deleted file mode 100644 index 63ed6a90..00000000 --- a/.brv/context-tree/extracted/curated_extraction/curated_extraction.md +++ /dev/null @@ -1,44 +0,0 @@ ---- -title: Curated Extraction -summary: Aggregated factual statements extracted via RLM process -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T09:25:56.661Z' -updatedAt: '2026-05-28T09:25:56.661Z' ---- -## Reason -Store extracted factual statements from source context - -## Raw Concept -**Task:** -Curate extracted factual statements from source context - -**Changes:** -- Extracted facts via mapExtract - -**Timestamp:** 2026-05-28T09:25:56.659Z - -## Narrative -### Structure -Aggregated factual statements extracted from source - -### Highlights -Subject rAF throttling has 1 facts -Subject route drift has 1 facts -Subject mobile/desktop scroll behavior has 1 facts -Subject SVG decoration has 1 facts -Subject integration tab cue has 1 facts -Subject OKLCH color variables has 1 facts -Subject Impeccable punctuation ban has 1 facts -Subject Claude/Codex install commands has 1 facts - -## Facts -- **rAF throttling**: rAF throttling is implemented for scroll/resize-driven hero `getBoundingClientRect()` calls, with `scroll` and `resize` calling `scheduleHeroProgressUpdate()` which gates updates through one `requestAnimationFrame` at a time. -- **route drift**: Route drift is no longer continuous by default; `.route-backbone` is paused unless `.map-stage.is-route-active` is present, and IntersectionObserver toggles that class based on visibility. -- **mobile/desktop scroll behavior**: Mobile avoids the fixed-height nested scroll panel while desktop keeps a bounded setup card, with desktop using `block-size` plus `overflow: auto` and mobile resetting to `block-size: auto` and `overflow: visible`. -- **SVG decoration**: npm/GitHub SVGs are decorative inside already-labeled links, marked with `aria-hidden="true"` and `focusable="false"`. -- **integration tab cue**: Active integration tab has a non-color cue via inset box-shadow. -- **OKLCH color variables**: Repeated one-off OKLCH colors were promoted to variables for the targeted repeated values. -- **Impeccable punctuation ban**: No newly introduced obvious Impeccable punctuation ban issue such as em dashes was found in the committed files. -- **Claude/Codex install commands**: Claude/Codex install commands were changed rather than merely split for readability; the committed command for both was altered, violating the audit requirement. diff --git a/.brv/context-tree/extracted/curated_facts/build.md b/.brv/context-tree/extracted/curated_facts/build.md deleted file mode 100644 index 70723be3..00000000 --- a/.brv/context-tree/extracted/curated_facts/build.md +++ /dev/null @@ -1,30 +0,0 @@ ---- -title: build -summary: Facts about build -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T11:21:06.135Z' -updatedAt: '2026-05-28T11:21:06.135Z' ---- -## Reason -Curate extracted factual statements from context - -## Raw Concept -**Task:** -Curate factual statements - -**Flow:** -extraction -> deduplication -> grouping -> curation - -**Timestamp:** 2026-05-28T11:21:06.122Z - -## Narrative -### Structure -Grouped factual statements for build - -### Highlights -pnpm --filter @caplets/landing build completed, building 1 page - -## Facts -- **build**: pnpm --filter @caplets/landing build completed, building 1 page diff --git a/.brv/context-tree/extracted/curated_facts/curated_facts_from_context.md b/.brv/context-tree/extracted/curated_facts/curated_facts_from_context.md deleted file mode 100644 index 06997603..00000000 --- a/.brv/context-tree/extracted/curated_facts/curated_facts_from_context.md +++ /dev/null @@ -1,27 +0,0 @@ ---- -title: Curated Facts from Context -summary: Contains 1 curated factual statements extracted from source context. -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T11:37:31.539Z' -updatedAt: '2026-05-28T11:37:31.539Z' ---- -## Reason -Store extracted factual statements as durable knowledge - -## Raw Concept -**Task:** -Curate extracted facts from source context - -**Flow:** -extraction -> deduplication -> curation - -**Timestamp:** 2026-05-28T11:37:31.538Z - -## Narrative -### Structure -Extracted factual statements from provided context - -### Highlights -Extracted 1 unique facts. diff --git a/.brv/context-tree/extracted/curated_facts/dash_check.md b/.brv/context-tree/extracted/curated_facts/dash_check.md deleted file mode 100644 index 742fca84..00000000 --- a/.brv/context-tree/extracted/curated_facts/dash_check.md +++ /dev/null @@ -1,30 +0,0 @@ ---- -title: dash check -summary: Facts about dash check -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T11:21:06.150Z' -updatedAt: '2026-05-28T11:21:06.150Z' ---- -## Reason -Curate extracted factual statements from context - -## Raw Concept -**Task:** -Curate factual statements - -**Flow:** -extraction -> deduplication -> grouping -> curation - -**Timestamp:** 2026-05-28T11:21:06.122Z - -## Narrative -### Structure -Grouped factual statements for dash check - -### Highlights -grep for em dash and en dash in apps/landing/src found no matches - -## Facts -- **dash check**: grep for em dash and en dash in apps/landing/src found no matches diff --git a/.brv/context-tree/extracted/curated_facts/deprecation_warnings.md b/.brv/context-tree/extracted/curated_facts/deprecation_warnings.md deleted file mode 100644 index c0ca4f2a..00000000 --- a/.brv/context-tree/extracted/curated_facts/deprecation_warnings.md +++ /dev/null @@ -1,30 +0,0 @@ ---- -title: deprecation warnings -summary: Facts about deprecation warnings -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T11:21:06.152Z' -updatedAt: '2026-05-28T11:21:06.152Z' ---- -## Reason -Curate extracted factual statements from context - -## Raw Concept -**Task:** -Curate factual statements - -**Flow:** -extraction -> deduplication -> grouping -> curation - -**Timestamp:** 2026-05-28T11:21:06.122Z - -## Narrative -### Structure -Grouped factual statements for deprecation warnings - -### Highlights -Astro commands emitted Node [DEP0205] module.register() deprecation warnings but completed successfully - -## Facts -- **deprecation warnings**: Astro commands emitted Node [DEP0205] module.register() deprecation warnings but completed successfully diff --git a/.brv/context-tree/extracted/curated_facts/extracted_facts.md b/.brv/context-tree/extracted/curated_facts/extracted_facts.md deleted file mode 100644 index bb910e79..00000000 --- a/.brv/context-tree/extracted/curated_facts/extracted_facts.md +++ /dev/null @@ -1,36 +0,0 @@ ---- -title: Extracted Facts -summary: Facts extracted and deduplicated from source context -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T10:17:26.110Z' -updatedAt: '2026-05-28T10:17:26.110Z' ---- -## Reason -Curate facts extracted via RLM extraction - -## Raw Concept -**Task:** -Curate extracted factual statements from provided context - -**Changes:** -- Extracted facts via RLM extraction - -**Timestamp:** 2026-05-28T10:17:26.108Z - -## Narrative -### Structure -Facts grouped by subject with deduplication - -### Highlights -Extracted factual statements organized by subject - -## Facts -- **Task 4 implementation**: Implemented Task 4 only: added the concrete trust mechanics section after proof, with matching trust CSS. -- **modified files**: Changed files: apps/landing/src/pages/index.astro and apps/landing/src/styles/global.css. -- **typecheck result**: pnpm --filter @caplets/landing typecheck PASS. -- **build result**: pnpm --filter @caplets/landing build PASS. -- **open risks**: Open risks/questions: None. -- **layout spacing**: Main spacing was left unchanged. -- **next step**: Recommended next step: Visual review the landing page render before continuing to Task 5. diff --git a/.brv/context-tree/extracted/curated_facts/format_check.md b/.brv/context-tree/extracted/curated_facts/format_check.md deleted file mode 100644 index 250ce4fc..00000000 --- a/.brv/context-tree/extracted/curated_facts/format_check.md +++ /dev/null @@ -1,30 +0,0 @@ ---- -title: format check -summary: Facts about format check -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T11:21:06.144Z' -updatedAt: '2026-05-28T11:21:06.144Z' ---- -## Reason -Curate extracted factual statements from context - -## Raw Concept -**Task:** -Curate factual statements - -**Flow:** -extraction -> deduplication -> grouping -> curation - -**Timestamp:** 2026-05-28T11:21:06.122Z - -## Narrative -### Structure -Grouped factual statements for format check - -### Highlights -pnpm format:check completed successfully - -## Facts -- **format check**: pnpm format:check completed successfully diff --git a/.brv/context-tree/extracted/curated_facts/formatting.md b/.brv/context-tree/extracted/curated_facts/formatting.md deleted file mode 100644 index 675696b8..00000000 --- a/.brv/context-tree/extracted/curated_facts/formatting.md +++ /dev/null @@ -1,30 +0,0 @@ ---- -title: formatting -summary: Facts about formatting -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T11:21:06.131Z' -updatedAt: '2026-05-28T11:21:06.131Z' ---- -## Reason -Curate extracted factual statements from context - -## Raw Concept -**Task:** -Curate factual statements - -**Flow:** -extraction -> deduplication -> grouping -> curation - -**Timestamp:** 2026-05-28T11:21:06.122Z - -## Narrative -### Structure -Grouped factual statements for formatting - -### Highlights -pnpm exec oxfmt apps/landing/src/pages/index.astro apps/landing/src/styles/global.css completed successfully - -## Facts -- **formatting**: pnpm exec oxfmt apps/landing/src/pages/index.astro apps/landing/src/styles/global.css completed successfully diff --git a/.brv/context-tree/extracted/curated_facts/impeccable_check.md b/.brv/context-tree/extracted/curated_facts/impeccable_check.md deleted file mode 100644 index 60d5f057..00000000 --- a/.brv/context-tree/extracted/curated_facts/impeccable_check.md +++ /dev/null @@ -1,30 +0,0 @@ ---- -title: impeccable check -summary: Facts about impeccable check -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T11:21:06.149Z' -updatedAt: '2026-05-28T11:21:06.149Z' ---- -## Reason -Curate extracted factual statements from context - -## Raw Concept -**Task:** -Curate factual statements - -**Flow:** -extraction -> deduplication -> grouping -> curation - -**Timestamp:** 2026-05-28T11:21:06.122Z - -## Narrative -### Structure -Grouped factual statements for impeccable check - -### Highlights -npx impeccable --json apps/landing/src/pages/index.astro returned an empty array - -## Facts -- **impeccable check**: npx impeccable --json apps/landing/src/pages/index.astro returned an empty array diff --git a/.brv/context-tree/extracted/curated_facts/interactive_controls.md b/.brv/context-tree/extracted/curated_facts/interactive_controls.md deleted file mode 100644 index 79f8beee..00000000 --- a/.brv/context-tree/extracted/curated_facts/interactive_controls.md +++ /dev/null @@ -1,30 +0,0 @@ ---- -title: interactive controls -summary: Facts about interactive controls -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T11:21:06.129Z' -updatedAt: '2026-05-28T11:21:06.129Z' ---- -## Reason -Curate extracted factual statements from context - -## Raw Concept -**Task:** -Curate factual statements - -**Flow:** -extraction -> deduplication -> grouping -> curation - -**Timestamp:** 2026-05-28T11:21:06.122Z - -## Narrative -### Structure -Grouped factual statements for interactive controls - -### Highlights -Ensured 44px minimum interactive target height for .brand-mark, .top-nav a, .header-action, .integration-pill, .copy-button, and mobile .copy-button - -## Facts -- **interactive controls**: Ensured 44px minimum interactive target height for .brand-mark, .top-nav a, .header-action, .integration-pill, .copy-button, and mobile .copy-button diff --git a/.brv/context-tree/extracted/curated_facts/lint.md b/.brv/context-tree/extracted/curated_facts/lint.md deleted file mode 100644 index 6c691dd1..00000000 --- a/.brv/context-tree/extracted/curated_facts/lint.md +++ /dev/null @@ -1,30 +0,0 @@ ---- -title: lint -summary: Facts about lint -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T11:21:06.147Z' -updatedAt: '2026-05-28T11:21:06.147Z' ---- -## Reason -Curate extracted factual statements from context - -## Raw Concept -**Task:** -Curate factual statements - -**Flow:** -extraction -> deduplication -> grouping -> curation - -**Timestamp:** 2026-05-28T11:21:06.122Z - -## Narrative -### Structure -Grouped factual statements for lint - -### Highlights -pnpm lint completed successfully - -## Facts -- **lint**: pnpm lint completed successfully diff --git a/.brv/context-tree/extracted/curated_facts/meta_tag.md b/.brv/context-tree/extracted/curated_facts/meta_tag.md deleted file mode 100644 index 5186258a..00000000 --- a/.brv/context-tree/extracted/curated_facts/meta_tag.md +++ /dev/null @@ -1,30 +0,0 @@ ---- -title: meta tag -summary: Facts about meta tag -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T11:21:06.127Z' -updatedAt: '2026-05-28T11:21:06.127Z' ---- -## Reason -Curate extracted factual statements from context - -## Raw Concept -**Task:** -Curate factual statements - -**Flow:** -extraction -> deduplication -> grouping -> curation - -**Timestamp:** 2026-05-28T11:21:06.122Z - -## Narrative -### Structure -Grouped factual statements for meta tag - -### Highlights -Changed theme-color meta to use content={themeColor} - -## Facts -- **meta tag**: Changed theme-color meta to use content={themeColor} diff --git a/.brv/context-tree/extracted/curated_facts/themecolor_constant.md b/.brv/context-tree/extracted/curated_facts/themecolor_constant.md deleted file mode 100644 index 4be4db83..00000000 --- a/.brv/context-tree/extracted/curated_facts/themecolor_constant.md +++ /dev/null @@ -1,30 +0,0 @@ ---- -title: themeColor constant -summary: Facts about themeColor constant -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T11:21:06.125Z' -updatedAt: '2026-05-28T11:21:06.125Z' ---- -## Reason -Curate extracted factual statements from context - -## Raw Concept -**Task:** -Curate factual statements - -**Flow:** -extraction -> deduplication -> grouping -> curation - -**Timestamp:** 2026-05-28T11:21:06.122Z - -## Narrative -### Structure -Grouped factual statements for themeColor constant - -### Highlights -Added const themeColor = "oklch(97% 0.012 85)" in apps/landing/src/pages/index.astro - -## Facts -- **themeColor constant**: Added const themeColor = "oklch(97% 0.012 85)" in apps/landing/src/pages/index.astro diff --git a/.brv/context-tree/extracted/curated_facts/typecheck.md b/.brv/context-tree/extracted/curated_facts/typecheck.md deleted file mode 100644 index 8d85ef72..00000000 --- a/.brv/context-tree/extracted/curated_facts/typecheck.md +++ /dev/null @@ -1,30 +0,0 @@ ---- -title: typecheck -summary: Facts about typecheck -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T11:21:06.133Z' -updatedAt: '2026-05-28T11:21:06.133Z' ---- -## Reason -Curate extracted factual statements from context - -## Raw Concept -**Task:** -Curate factual statements - -**Flow:** -extraction -> deduplication -> grouping -> curation - -**Timestamp:** 2026-05-28T11:21:06.122Z - -## Narrative -### Structure -Grouped factual statements for typecheck - -### Highlights -pnpm --filter @caplets/landing typecheck completed with 0 errors and 0 warnings - -## Facts -- **typecheck**: pnpm --filter @caplets/landing typecheck completed with 0 errors and 0 warnings diff --git a/.brv/context-tree/extracted/curated_facts/unrelated_changes.md b/.brv/context-tree/extracted/curated_facts/unrelated_changes.md deleted file mode 100644 index 3ae5a070..00000000 --- a/.brv/context-tree/extracted/curated_facts/unrelated_changes.md +++ /dev/null @@ -1,30 +0,0 @@ ---- -title: unrelated changes -summary: Facts about unrelated changes -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T11:21:06.156Z' -updatedAt: '2026-05-28T11:21:06.156Z' ---- -## Reason -Curate extracted factual statements from context - -## Raw Concept -**Task:** -Curate factual statements - -**Flow:** -extraction -> deduplication -> grouping -> curation - -**Timestamp:** 2026-05-28T11:21:06.122Z - -## Narrative -### Structure -Grouped factual statements for unrelated changes - -### Highlights -Working tree contains unrelated pre-existing .brv changes that were not modified intentionally - -## Facts -- **unrelated changes**: Working tree contains unrelated pre-existing .brv changes that were not modified intentionally diff --git a/.brv/context-tree/extracted/curated_from_context/capabilitycards.md b/.brv/context-tree/extracted/curated_from_context/capabilitycards.md deleted file mode 100644 index 6c9055fa..00000000 --- a/.brv/context-tree/extracted/curated_from_context/capabilitycards.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: CapabilityCards -summary: Facts about capabilityCards -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T10:06:54.151Z' -updatedAt: '2026-05-28T10:06:54.151Z' ---- -## Reason -Extracted from RLM context - -## Raw Concept -**Task:** -Document facts for capabilityCards - -**Timestamp:** 2026-05-28T10:06:54.122Z - -## Narrative -### Highlights -`capabilityCards` is still referenced by the existing old hero markup. - -## Facts -- **capabilityCards**: `capabilityCards` is still referenced by the existing old hero markup. diff --git a/.brv/context-tree/extracted/curated_from_context/commit.md b/.brv/context-tree/extracted/curated_from_context/commit.md deleted file mode 100644 index dbecf086..00000000 --- a/.brv/context-tree/extracted/curated_from_context/commit.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Commit -summary: Facts about commit -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T10:06:54.139Z' -updatedAt: '2026-05-28T10:06:54.139Z' ---- -## Reason -Extracted from RLM context - -## Raw Concept -**Task:** -Document facts for commit - -**Timestamp:** 2026-05-28T10:06:54.122Z - -## Narrative -### Highlights -No commit made. - -## Facts -- **commit**: No commit made. diff --git a/.brv/context-tree/extracted/curated_from_context/constants.md b/.brv/context-tree/extracted/curated_from_context/constants.md deleted file mode 100644 index 163a5562..00000000 --- a/.brv/context-tree/extracted/curated_from_context/constants.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Constants -summary: Facts about constants -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T10:06:54.153Z' -updatedAt: '2026-05-28T10:06:54.153Z' ---- -## Reason -Extracted from RLM context - -## Raw Concept -**Task:** -Document facts for constants - -**Timestamp:** 2026-05-28T10:06:54.122Z - -## Narrative -### Highlights -New constants are currently unused until later plan tasks replace the hero/proof/trust markup. - -## Facts -- **constants**: New constants are currently unused until later plan tasks replace the hero/proof/trust markup. diff --git a/.brv/context-tree/extracted/curated_from_context/curated_context.md b/.brv/context-tree/extracted/curated_from_context/curated_context.md deleted file mode 100644 index 199262d4..00000000 --- a/.brv/context-tree/extracted/curated_from_context/curated_context.md +++ /dev/null @@ -1,33 +0,0 @@ ---- -title: Curated Context -summary: Curated 7 facts from raw context -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T10:12:38.373Z' -updatedAt: '2026-05-28T10:12:38.373Z' ---- -## Reason -Store extracted factual statements from raw context - -## Raw Concept -**Task:** -Curate extracted factual statements from provided context - -**Timestamp:** 2026-05-28T10:12:38.372Z - -## Narrative -### Structure -Extracted facts grouped by subject - -### Highlights -Extracted 7 unique facts across 7 subjects. - -## Facts -- **Task 2**: Implemented Task 2 only. -- **files**: Changed files: apps/landing/src/pages/index.astro and apps/landing/src/styles/global.css. -- **typecheck**: pnpm --filter @caplets/landing typecheck passed, with expected unused-constant hints for Task 3/4 constants. -- **build**: pnpm --filter @caplets/landing build passed. -- **git diff**: git diff --check passed. -- **variables**: skillifyFramework and trustMechanics remain unused until Task 3/4. -- **next step**: Recommended next step: proceed to Task 3 when ready. diff --git a/.brv/context-tree/extracted/curated_from_context/expected_behavior.md b/.brv/context-tree/extracted/curated_from_context/expected_behavior.md deleted file mode 100644 index 967e16dd..00000000 --- a/.brv/context-tree/extracted/curated_from_context/expected_behavior.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Expected behavior -summary: Facts about expected_behavior -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T10:06:54.156Z' -updatedAt: '2026-05-28T10:06:54.156Z' ---- -## Reason -Extracted from RLM context - -## Raw Concept -**Task:** -Document facts for expected_behavior - -**Timestamp:** 2026-05-28T10:06:54.122Z - -## Narrative -### Highlights -This is expected from doing Task 1 only, because Task 2 removes the old hero references. - -## Facts -- **expected_behavior**: This is expected from doing Task 1 only, because Task 2 removes the old hero references. diff --git a/.brv/context-tree/extracted/curated_from_context/extracted_knowledge.md b/.brv/context-tree/extracted/curated_from_context/extracted_knowledge.md deleted file mode 100644 index c79c3e53..00000000 --- a/.brv/context-tree/extracted/curated_from_context/extracted_knowledge.md +++ /dev/null @@ -1,30 +0,0 @@ ---- -title: Extracted Knowledge -summary: Extracted rawConcept, narrative, relations, and facts from provided context -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T09:57:33.791Z' -updatedAt: '2026-05-28T09:57:33.791Z' ---- -## Reason -Curate extracted knowledge from RLM context - -## Facts -- **implementation plan**: Create a written implementation plan in docs/plans/ before code changes. -- **landing page**: The landing page changes include narrative, information architecture, hero structure, trust proof, motion system, accessibility fallback, and command affordances. -- **messaging refactor**: Messaging refactor should make trust the primary objective, define “skillify”, make “capability cards” dominant, and demote “map” to supporting metaphor. -- **hero rebuild**: Hero rebuild should replace expressive map hero with realistic GitHub trace, show progressive disclosure path, and include source, status, auth redaction, schema preservation, structured result preservation. -- **trust proof**: Trust proof section should add concrete safety/failure examples, show config source and redacted auth, and show timeout/error/recovery behavior where accurate. -- **interaction hardening**: Interaction hardening includes copy buttons for commands and snippets, no-JS integration fallback, and non-color-only status cues. -- **visual simplification**: Visual simplification should remove pointer tilt, scroll route resolution, route drift, heavy glow, and orchestration, keeping restrained hover/focus transitions. -- **consistency fixes**: Consistency fixes include resolving install command mismatch and adding direct docs/config links if available. -- **hover/focus behavior**: keep restrained hover/focus transitions -- **consistency fixes**: resolve install command mismatch -- **documentation**: add direct docs/config links if available -- **verification**: pnpm --filter apps/landing typecheck -- **verification**: pnpm --filter apps/landing build -- **verification**: maybe full pnpm verify if landing changes interact with repo gates -- **process**: produce a written docs/plans/... implementation plan before touching code -- **pnpm verify**: Running `pnpm verify` may be needed if landing changes interact with repo gates. -- **implementation plan**: A written implementation plan should be produced in docs/plans/... before touching code. diff --git a/.brv/context-tree/extracted/curated_from_context/files.md b/.brv/context-tree/extracted/curated_from_context/files.md deleted file mode 100644 index d2b01406..00000000 --- a/.brv/context-tree/extracted/curated_from_context/files.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Files -summary: Facts about files -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T10:06:54.127Z' -updatedAt: '2026-05-28T10:06:54.127Z' ---- -## Reason -Extracted from RLM context - -## Raw Concept -**Task:** -Document facts for files - -**Timestamp:** 2026-05-28T10:06:54.121Z - -## Narrative -### Highlights -Changed files: apps/landing/src/pages/index.astro - -## Facts -- **files**: Changed files: apps/landing/src/pages/index.astro diff --git a/.brv/context-tree/extracted/curated_from_context/index_astro.md b/.brv/context-tree/extracted/curated_from_context/index_astro.md deleted file mode 100644 index 0e6961c6..00000000 --- a/.brv/context-tree/extracted/curated_from_context/index_astro.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Index.astro -summary: Facts about index.astro -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T10:06:54.134Z' -updatedAt: '2026-05-28T10:06:54.134Z' ---- -## Reason -Extracted from RLM context - -## Raw Concept -**Task:** -Document facts for index.astro - -**Timestamp:** 2026-05-28T10:06:54.121Z - -## Narrative -### Highlights -Replaced capabilityCards with exact heroTrace, skillifyFramework, and trustMechanics constants from the plan. - -## Facts -- **index.astro**: Replaced capabilityCards with exact heroTrace, skillifyFramework, and trustMechanics constants from the plan. diff --git a/.brv/context-tree/extracted/curated_from_context/installsteps.md b/.brv/context-tree/extracted/curated_from_context/installsteps.md deleted file mode 100644 index 41301ab6..00000000 --- a/.brv/context-tree/extracted/curated_from_context/installsteps.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: InstallSteps -summary: Facts about installSteps -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T10:06:54.136Z' -updatedAt: '2026-05-28T10:06:54.136Z' ---- -## Reason -Extracted from RLM context - -## Raw Concept -**Task:** -Document facts for installSteps - -**Timestamp:** 2026-05-28T10:06:54.121Z - -## Narrative -### Highlights -Normalized installSteps[0] to npm install -g caplets. - -## Facts -- **installSteps**: Normalized installSteps[0] to npm install -g caplets. diff --git a/.brv/context-tree/extracted/curated_from_context/next_step.md b/.brv/context-tree/extracted/curated_from_context/next_step.md deleted file mode 100644 index 7d06b6d9..00000000 --- a/.brv/context-tree/extracted/curated_from_context/next_step.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Next step -summary: Facts about next_step -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T10:06:54.158Z' -updatedAt: '2026-05-28T10:06:54.158Z' ---- -## Reason -Extracted from RLM context - -## Raw Concept -**Task:** -Document facts for next_step - -**Timestamp:** 2026-05-28T10:06:54.122Z - -## Narrative -### Highlights -Recommended next step: Implement Task 2 to replace the old hero markup and resolve the typecheck errors. - -## Facts -- **next_step**: Recommended next step: Implement Task 2 to replace the old hero markup and resolve the typecheck errors. diff --git a/.brv/context-tree/extracted/curated_from_context/task_1.md b/.brv/context-tree/extracted/curated_from_context/task_1.md deleted file mode 100644 index f72449d1..00000000 --- a/.brv/context-tree/extracted/curated_from_context/task_1.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Task 1 -summary: Facts about Task 1 -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T10:06:54.123Z' -updatedAt: '2026-05-28T10:06:54.123Z' ---- -## Reason -Extracted from RLM context - -## Raw Concept -**Task:** -Document facts for Task 1 - -**Timestamp:** 2026-05-28T10:06:54.121Z - -## Narrative -### Highlights -Implemented Task 1 only. - -## Facts -- **Task 1**: Implemented Task 1 only. diff --git a/.brv/context-tree/extracted/curated_from_context/typecheck.md b/.brv/context-tree/extracted/curated_from_context/typecheck.md deleted file mode 100644 index cd026715..00000000 --- a/.brv/context-tree/extracted/curated_from_context/typecheck.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Typecheck -summary: Facts about typecheck -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T10:06:54.141Z' -updatedAt: '2026-05-28T10:06:54.141Z' ---- -## Reason -Extracted from RLM context - -## Raw Concept -**Task:** -Document facts for typecheck - -**Timestamp:** 2026-05-28T10:06:54.122Z - -## Narrative -### Highlights -pnpm --filter @caplets/landing typecheck Result: FAILED, exit 1 - -## Facts -- **typecheck**: pnpm --filter @caplets/landing typecheck Result: FAILED, exit 1 diff --git a/.brv/context-tree/extracted/facts/accent_rule.md b/.brv/context-tree/extracted/facts/accent_rule.md deleted file mode 100644 index 406d041a..00000000 --- a/.brv/context-tree/extracted/facts/accent_rule.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Accent rule -summary: Facts about accent rule -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T13:18:21.389Z' -updatedAt: '2026-05-28T13:18:21.389Z' ---- -## Reason -Curated extracted facts from source context - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-28T13:18:21.328Z - -## Narrative -### Highlights -Contains 1 facts for accent rule - -## Facts -- **accent rule**: The "one accent ≤10%" rule is Restrained only. diff --git a/.brv/context-tree/extracted/facts/accessibility.md b/.brv/context-tree/extracted/facts/accessibility.md deleted file mode 100644 index d8581378..00000000 --- a/.brv/context-tree/extracted/facts/accessibility.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -title: Accessibility -summary: Facts about accessibility -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T09:45:40.405Z' -updatedAt: '2026-05-28T09:45:40.405Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Document extracted factual statements - -**Timestamp:** 2026-05-28T09:45:40.335Z - -## Facts -- **accessibility**: Accessibility intent is visible in source. diff --git a/.brv/context-tree/extracted/facts/adapt.md b/.brv/context-tree/extracted/facts/adapt.md deleted file mode 100644 index b79e30c3..00000000 --- a/.brv/context-tree/extracted/facts/adapt.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Adapt -summary: Facts about adapt -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T13:18:21.494Z' -updatedAt: '2026-05-28T13:18:21.494Z' ---- -## Reason -Curated extracted facts from source context - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-28T13:18:21.328Z - -## Narrative -### Highlights -Contains 1 facts for adapt - -## Facts -- **adapt**: `adapt [target]` is a Fix command that adapts designs for different devices and screen sizes. diff --git a/.brv/context-tree/extracted/facts/aesthetic_and_minimalist_design.md b/.brv/context-tree/extracted/facts/aesthetic_and_minimalist_design.md deleted file mode 100644 index 95b8600f..00000000 --- a/.brv/context-tree/extracted/facts/aesthetic_and_minimalist_design.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -title: Aesthetic and minimalist design -summary: Facts about Aesthetic and minimalist design -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T09:45:40.375Z' -updatedAt: '2026-05-28T09:45:40.375Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Document extracted factual statements - -**Timestamp:** 2026-05-28T09:45:40.334Z - -## Facts -- **Aesthetic and minimalist design**: Aesthetic and minimalist design score is 2. diff --git a/.brv/context-tree/extracted/facts/agent_setup_panel.md b/.brv/context-tree/extracted/facts/agent_setup_panel.md deleted file mode 100644 index 0a810c27..00000000 --- a/.brv/context-tree/extracted/facts/agent_setup_panel.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: .agent-setup-panel -summary: Facts about .agent-setup-panel -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T23:50:44.578Z' -updatedAt: '2026-05-27T23:50:44.578Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-27T23:50:44.568Z - -## Narrative -### Highlights -Extracted 1 facts for .agent-setup-panel - -## Facts -- **.agent-setup-panel**: Previous blocker is resolved. `.agent-setup-panel` is included in reveal targets at `apps/landing/src/pages/index.astro:418-420`, reveal CSS initial/visible states at `apps/landing/src/styles/global.css:688-707`, and reduced-motion reveal override at `apps/landing/src/styles/global.css:1324-1331`. diff --git a/.brv/context-tree/extracted/facts/agentsetups.md b/.brv/context-tree/extracted/facts/agentsetups.md deleted file mode 100644 index df31b2fc..00000000 --- a/.brv/context-tree/extracted/facts/agentsetups.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: agentSetups -summary: Facts about agentSetups -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T23:50:44.570Z' -updatedAt: '2026-05-27T23:50:44.570Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-27T23:50:44.568Z - -## Narrative -### Highlights -Extracted 1 facts for agentSetups - -## Facts -- **agentSetups**: `agentSetups` is centralized and used to render tab labels/panels from data in `apps/landing/src/pages/index.astro:26` and `apps/landing/src/pages/index.astro:267`. diff --git a/.brv/context-tree/extracted/facts/agentsetups_data_centralization.md b/.brv/context-tree/extracted/facts/agentsetups_data_centralization.md deleted file mode 100644 index 9884a77e..00000000 --- a/.brv/context-tree/extracted/facts/agentsetups_data_centralization.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -title: agentSetups data centralization -summary: Facts about agentSetups data centralization -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T23:48:05.187Z' -updatedAt: '2026-05-27T23:48:05.187Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Document factual statements about agentSetups data centralization - -**Timestamp:** 2026-05-27T23:48:05.172Z - -## Facts -- **agentSetups data centralization**: Setup data is centralized in `agentSetups`, and hero client labels derive from it (`apps/landing/src/pages/index.astro:26-88`). diff --git a/.brv/context-tree/extracted/facts/animate.md b/.brv/context-tree/extracted/facts/animate.md deleted file mode 100644 index 7d43749f..00000000 --- a/.brv/context-tree/extracted/facts/animate.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Animate -summary: Facts about animate -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T13:18:21.484Z' -updatedAt: '2026-05-28T13:18:21.484Z' ---- -## Reason -Curated extracted facts from source context - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-28T13:18:21.328Z - -## Narrative -### Highlights -Contains 1 facts for animate - -## Facts -- **animate**: `animate [target]` is an Enhance command that adds purposeful animations and motion. diff --git a/.brv/context-tree/extracted/facts/audit.md b/.brv/context-tree/extracted/facts/audit.md deleted file mode 100644 index e06e9681..00000000 --- a/.brv/context-tree/extracted/facts/audit.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Audit -summary: Facts about audit -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T13:18:21.473Z' -updatedAt: '2026-05-28T13:18:21.473Z' ---- -## Reason -Curated extracted facts from source context - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-28T13:18:21.328Z - -## Narrative -### Highlights -Contains 1 facts for audit - -## Facts -- **audit**: `audit [target]` is an Evaluate command that performs technical quality checks (a11y, perf, responsive). diff --git a/.brv/context-tree/extracted/facts/average.md b/.brv/context-tree/extracted/facts/average.md deleted file mode 100644 index 7d4ecc3d..00000000 --- a/.brv/context-tree/extracted/facts/average.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -title: Average -summary: Facts about average -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T09:45:40.391Z' -updatedAt: '2026-05-28T09:45:40.391Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Document extracted factual statements - -**Timestamp:** 2026-05-28T09:45:40.335Z - -## Facts -- **average**: Average: 2.3 / 4 diff --git a/.brv/context-tree/extracted/facts/average_score.md b/.brv/context-tree/extracted/facts/average_score.md deleted file mode 100644 index 96ee5750..00000000 --- a/.brv/context-tree/extracted/facts/average_score.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -title: Average score -summary: Facts about average_score -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T09:45:40.384Z' -updatedAt: '2026-05-28T09:45:40.384Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Document extracted factual statements - -**Timestamp:** 2026-05-28T09:45:40.335Z - -## Facts -- **average_score**: Average Nielsen heuristic score is 2.3 out of 4. diff --git a/.brv/context-tree/extracted/facts/bolder.md b/.brv/context-tree/extracted/facts/bolder.md deleted file mode 100644 index 9c069278..00000000 --- a/.brv/context-tree/extracted/facts/bolder.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Bolder -summary: Facts about bolder -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T13:18:21.476Z' -updatedAt: '2026-05-28T13:18:21.476Z' ---- -## Reason -Curated extracted facts from source context - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-28T13:18:21.328Z - -## Narrative -### Highlights -Contains 1 facts for bolder - -## Facts -- **bolder**: `bolder [target]` is a Refine command that amplifies safe or bland designs. diff --git a/.brv/context-tree/extracted/facts/border_usage.md b/.brv/context-tree/extracted/facts/border_usage.md deleted file mode 100644 index 63c359be..00000000 --- a/.brv/context-tree/extracted/facts/border_usage.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Border usage -summary: Facts about border usage -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T13:02:34.242Z' -updatedAt: '2026-05-28T13:02:34.242Z' ---- -## Reason -Curated extracted facts from provided context - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-28T13:02:34.210Z - -## Narrative -### Highlights -Extracted 1 facts for border usage - -## Facts -- **border usage**: Side‑stripe borders greater than 1 px as colored accents are banned; use full borders, background tints, or icons instead. diff --git a/.brv/context-tree/extracted/facts/browser.md b/.brv/context-tree/extracted/facts/browser.md deleted file mode 100644 index a3c22ff3..00000000 --- a/.brv/context-tree/extracted/facts/browser.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Browser -summary: Facts about Browser -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T13:18:21.509Z' -updatedAt: '2026-05-28T13:18:21.509Z' ---- -## Reason -Curated extracted facts from source context - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-28T13:18:21.328Z - -## Narrative -### Highlights -Contains 1 facts for Browser - -## Facts -- **Browser**: Browser verification inspected at 320 × 740 dimensions diff --git a/.brv/context-tree/extracted/facts/build.md b/.brv/context-tree/extracted/facts/build.md deleted file mode 100644 index 6e239156..00000000 --- a/.brv/context-tree/extracted/facts/build.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Build -summary: Facts about build -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T13:02:34.263Z' -updatedAt: '2026-05-28T13:02:34.263Z' ---- -## Reason -Curated extracted facts from provided context - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-28T13:02:34.210Z - -## Narrative -### Highlights -Extracted 1 facts for build - -## Facts -- **build**: Build succeeded for @caplets/landing using `pnpm --filter @caplets/landing build`. diff --git a/.brv/context-tree/extracted/facts/card_grids.md b/.brv/context-tree/extracted/facts/card_grids.md deleted file mode 100644 index c29d37c9..00000000 --- a/.brv/context-tree/extracted/facts/card_grids.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Card grids -summary: Facts about card grids -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T13:18:21.456Z' -updatedAt: '2026-05-28T13:18:21.456Z' ---- -## Reason -Curated extracted facts from source context - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-28T13:18:21.328Z - -## Narrative -### Highlights -Contains 1 facts for card grids - -## Facts -- **card grids**: Identical card grids are banned. diff --git a/.brv/context-tree/extracted/facts/cards.md b/.brv/context-tree/extracted/facts/cards.md deleted file mode 100644 index 122537d8..00000000 --- a/.brv/context-tree/extracted/facts/cards.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Cards -summary: Facts about cards -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T13:02:34.241Z' -updatedAt: '2026-05-28T13:02:34.241Z' ---- -## Reason -Curated extracted facts from provided context - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-28T13:02:34.210Z - -## Narrative -### Highlights -Extracted 1 facts for cards - -## Facts -- **cards**: Cards are considered a lazy answer and should only be used when truly the best affordance; nested cards are always wrong. diff --git a/.brv/context-tree/extracted/facts/cards_usage.md b/.brv/context-tree/extracted/facts/cards_usage.md deleted file mode 100644 index 34ae5ac8..00000000 --- a/.brv/context-tree/extracted/facts/cards_usage.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Cards usage -summary: Facts about cards usage -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T13:18:21.419Z' -updatedAt: '2026-05-28T13:18:21.419Z' ---- -## Reason -Curated extracted facts from source context - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-28T13:18:21.328Z - -## Narrative -### Highlights -Contains 1 facts for cards usage - -## Facts -- **cards usage**: Cards are the lazy answer; use them only when they're truly the best affordance. diff --git a/.brv/context-tree/extracted/facts/casey.md b/.brv/context-tree/extracted/facts/casey.md deleted file mode 100644 index 814ea477..00000000 --- a/.brv/context-tree/extracted/facts/casey.md +++ /dev/null @@ -1,22 +0,0 @@ ---- -title: Casey -summary: Facts about Casey -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T09:51:10.758Z' -updatedAt: '2026-05-28T09:51:10.758Z' ---- -## Reason -Curated extracted factual statements - -## Raw Concept -**Task:** -Document factual statements - -## Narrative -### Highlights -Extracted 1 facts for Casey - -## Facts -- **Casey**: Persona Casey (Distracted Mobile User) experiences large decorative map dominating viewport and sticky header delaying access to install info diff --git a/.brv/context-tree/extracted/facts/chroma_adjustment.md b/.brv/context-tree/extracted/facts/chroma_adjustment.md deleted file mode 100644 index 3863621e..00000000 --- a/.brv/context-tree/extracted/facts/chroma_adjustment.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Chroma adjustment -summary: Facts about chroma adjustment -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T13:18:21.373Z' -updatedAt: '2026-05-28T13:18:21.373Z' ---- -## Reason -Curated extracted facts from source context - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-28T13:18:21.328Z - -## Narrative -### Highlights -Contains 1 facts for chroma adjustment - -## Facts -- **chroma adjustment**: Reduce chroma as lightness approaches 0 or 100; high chroma at extremes looks garish. diff --git a/.brv/context-tree/extracted/facts/clarify.md b/.brv/context-tree/extracted/facts/clarify.md deleted file mode 100644 index 467b5712..00000000 --- a/.brv/context-tree/extracted/facts/clarify.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Clarify -summary: Facts about clarify -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T13:18:21.492Z' -updatedAt: '2026-05-28T13:18:21.492Z' ---- -## Reason -Curated extracted facts from source context - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-28T13:18:21.328Z - -## Narrative -### Highlights -Contains 1 facts for clarify - -## Facts -- **clarify**: `clarify [target]` is a Fix command that improves UX copy, labels, and error messages. diff --git a/.brv/context-tree/extracted/facts/cli_scan.md b/.brv/context-tree/extracted/facts/cli_scan.md deleted file mode 100644 index 50813259..00000000 --- a/.brv/context-tree/extracted/facts/cli_scan.md +++ /dev/null @@ -1,22 +0,0 @@ ---- -title: CLI scan -summary: Facts about CLI scan -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T09:51:10.704Z' -updatedAt: '2026-05-28T09:51:10.704Z' ---- -## Reason -Curated extracted factual statements - -## Raw Concept -**Task:** -Document factual statements - -## Narrative -### Highlights -Extracted 1 facts for CLI scan - -## Facts -- **CLI scan**: The deterministic scan with `npx impeccable --json apps/landing/src/pages/index.astro` returned an empty array diff --git a/.brv/context-tree/extracted/facts/code_snippets.md b/.brv/context-tree/extracted/facts/code_snippets.md deleted file mode 100644 index d8267bf0..00000000 --- a/.brv/context-tree/extracted/facts/code_snippets.md +++ /dev/null @@ -1,22 +0,0 @@ ---- -title: Code snippets -summary: Facts about Code snippets -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T09:51:10.745Z' -updatedAt: '2026-05-28T09:51:10.745Z' ---- -## Reason -Curated extracted factual statements - -## Raw Concept -**Task:** -Document factual statements - -## Narrative -### Highlights -Extracted 1 facts for Code snippets - -## Facts -- **Code snippets**: Snippets are not copyable, increasing friction for developers diff --git a/.brv/context-tree/extracted/facts/cognitive_load_failures.md b/.brv/context-tree/extracted/facts/cognitive_load_failures.md deleted file mode 100644 index 0e2e7425..00000000 --- a/.brv/context-tree/extracted/facts/cognitive_load_failures.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -title: Cognitive load failures -summary: Facts about cognitive_load_failures -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T09:45:40.387Z' -updatedAt: '2026-05-28T09:45:40.387Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Document extracted factual statements - -**Timestamp:** 2026-05-28T09:45:40.335Z - -## Facts -- **cognitive_load_failures**: Cognitive load failure count is 7 notable load failures. diff --git a/.brv/context-tree/extracted/facts/color_model.md b/.brv/context-tree/extracted/facts/color_model.md deleted file mode 100644 index 4724f959..00000000 --- a/.brv/context-tree/extracted/facts/color_model.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Color model -summary: Facts about color model -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T13:18:21.365Z' -updatedAt: '2026-05-28T13:18:21.365Z' ---- -## Reason -Curated extracted facts from source context - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-28T13:18:21.328Z - -## Narrative -### Highlights -Contains 1 facts for color model - -## Facts -- **color model**: Use OKLCH. diff --git a/.brv/context-tree/extracted/facts/color_strategy.md b/.brv/context-tree/extracted/facts/color_strategy.md deleted file mode 100644 index 798480c9..00000000 --- a/.brv/context-tree/extracted/facts/color_strategy.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Color strategy -summary: Facts about color strategy -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T13:02:34.226Z' -updatedAt: '2026-05-28T13:02:34.226Z' ---- -## Reason -Curated extracted facts from provided context - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-28T13:02:34.210Z - -## Narrative -### Highlights -Extracted 1 facts for color strategy - -## Facts -- **color strategy**: Color strategy must be chosen before picking colors, with four options: Restrained, Committed, Full palette, and Drenched. diff --git a/.brv/context-tree/extracted/facts/color_usage.md b/.brv/context-tree/extracted/facts/color_usage.md deleted file mode 100644 index 45d2d55c..00000000 --- a/.brv/context-tree/extracted/facts/color_usage.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Color usage -summary: Facts about color usage -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T13:02:34.236Z' -updatedAt: '2026-05-28T13:02:34.236Z' ---- -## Reason -Curated extracted facts from provided context - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-28T13:02:34.210Z - -## Narrative -### Highlights -Extracted 1 facts for color usage - -## Facts -- **color usage**: Never use #000 or #fff; neutral colors must be tinted toward the brand hue with chroma 0.005–0.01. diff --git a/.brv/context-tree/extracted/facts/colorize.md b/.brv/context-tree/extracted/facts/colorize.md deleted file mode 100644 index bd435307..00000000 --- a/.brv/context-tree/extracted/facts/colorize.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Colorize -summary: Facts about colorize -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T13:18:21.485Z' -updatedAt: '2026-05-28T13:18:21.485Z' ---- -## Reason -Curated extracted facts from source context - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-28T13:18:21.328Z - -## Narrative -### Highlights -Contains 1 facts for colorize - -## Facts -- **colorize**: `colorize [target]` is an Enhance command that adds strategic color to monochromatic UIs. diff --git a/.brv/context-tree/extracted/facts/command_syntax.md b/.brv/context-tree/extracted/facts/command_syntax.md deleted file mode 100644 index 8042f8ff..00000000 --- a/.brv/context-tree/extracted/facts/command_syntax.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Command syntax -summary: Facts about Command syntax -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T13:18:21.503Z' -updatedAt: '2026-05-28T13:18:21.503Z' ---- -## Reason -Curated extracted facts from source context - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-28T13:18:21.328Z - -## Narrative -### Highlights -Contains 1 facts for Command syntax - -## Facts -- **Command syntax**: The command syntax is node {{scripts_path}}/pin.mjs diff --git a/.brv/context-tree/extracted/facts/commits_0e56e8f_86f9679.md b/.brv/context-tree/extracted/facts/commits_0e56e8f_86f9679.md deleted file mode 100644 index 4473894f..00000000 --- a/.brv/context-tree/extracted/facts/commits_0e56e8f_86f9679.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: commits_0e56e8f_86f9679 -summary: Facts about commits_0e56e8f_86f9679 -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T23:50:44.587Z' -updatedAt: '2026-05-27T23:50:44.587Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-27T23:50:44.568Z - -## Narrative -### Highlights -Extracted 1 facts for commits_0e56e8f_86f9679 - -## Facts -- **commits_0e56e8f_86f9679**: No exact issues found in the committed changes for commits `0e56e8f` and `86f9679`. diff --git a/.brv/context-tree/extracted/facts/committed_strategy.md b/.brv/context-tree/extracted/facts/committed_strategy.md deleted file mode 100644 index da12b509..00000000 --- a/.brv/context-tree/extracted/facts/committed_strategy.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Committed strategy -summary: Facts about Committed strategy -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T13:18:21.386Z' -updatedAt: '2026-05-28T13:18:21.386Z' ---- -## Reason -Curated extracted facts from source context - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-28T13:18:21.328Z - -## Narrative -### Highlights -Contains 1 facts for Committed strategy - -## Facts -- **Committed strategy**: Committed: one saturated color carries 30–60% of the surface. diff --git a/.brv/context-tree/extracted/facts/consistency_and_standards.md b/.brv/context-tree/extracted/facts/consistency_and_standards.md deleted file mode 100644 index 544af593..00000000 --- a/.brv/context-tree/extracted/facts/consistency_and_standards.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -title: Consistency and standards -summary: Facts about Consistency and standards -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T09:45:40.352Z' -updatedAt: '2026-05-28T09:45:40.352Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Document extracted factual statements - -**Timestamp:** 2026-05-28T09:45:40.334Z - -## Facts -- **Consistency and standards**: Consistency and standards score is 3. diff --git a/.brv/context-tree/extracted/facts/containers.md b/.brv/context-tree/extracted/facts/containers.md deleted file mode 100644 index 17deaf22..00000000 --- a/.brv/context-tree/extracted/facts/containers.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Containers -summary: Facts about containers -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T13:18:21.429Z' -updatedAt: '2026-05-28T13:18:21.429Z' ---- -## Reason -Curated extracted facts from source context - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-28T13:18:21.328Z - -## Narrative -### Highlights -Contains 1 facts for containers - -## Facts -- **containers**: Don't wrap everything in a container. diff --git a/.brv/context-tree/extracted/facts/craft.md b/.brv/context-tree/extracted/facts/craft.md deleted file mode 100644 index 232f1fd9..00000000 --- a/.brv/context-tree/extracted/facts/craft.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Craft -summary: Facts about craft -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T13:18:21.465Z' -updatedAt: '2026-05-28T13:18:21.465Z' ---- -## Reason -Curated extracted facts from source context - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-28T13:18:21.328Z - -## Narrative -### Highlights -Contains 1 facts for craft - -## Facts -- **craft**: `craft [feature]` is a Build command that shapes then builds a feature end-to-end. diff --git a/.brv/context-tree/extracted/facts/critique.md b/.brv/context-tree/extracted/facts/critique.md deleted file mode 100644 index f8f217f3..00000000 --- a/.brv/context-tree/extracted/facts/critique.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Critique -summary: Facts about critique -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T13:18:21.472Z' -updatedAt: '2026-05-28T13:18:21.472Z' ---- -## Reason -Curated extracted facts from source context - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-28T13:18:21.328Z - -## Narrative -### Highlights -Contains 1 facts for critique - -## Facts -- **critique**: `critique [target]` is an Evaluate command that performs a UX design review with heuristic scoring. diff --git a/.brv/context-tree/extracted/facts/css.md b/.brv/context-tree/extracted/facts/css.md deleted file mode 100644 index df50b427..00000000 --- a/.brv/context-tree/extracted/facts/css.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: CSS -summary: Facts about CSS -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T13:02:34.250Z' -updatedAt: '2026-05-28T13:02:34.250Z' ---- -## Reason -Curated extracted facts from provided context - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-28T13:02:34.210Z - -## Narrative -### Highlights -Extracted 1 facts for CSS - -## Facts -- **CSS**: The nav pill/card treatment was removed from `.top‑nav`. diff --git a/.brv/context-tree/extracted/facts/css_animation.md b/.brv/context-tree/extracted/facts/css_animation.md deleted file mode 100644 index ba9a12d2..00000000 --- a/.brv/context-tree/extracted/facts/css_animation.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: CSS animation -summary: Facts about CSS animation -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T13:18:21.432Z' -updatedAt: '2026-05-28T13:18:21.432Z' ---- -## Reason -Curated extracted facts from source context - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-28T13:18:21.328Z - -## Narrative -### Highlights -Contains 1 facts for CSS animation - -## Facts -- **CSS animation**: Don't animate CSS layout properties. diff --git a/.brv/context-tree/extracted/facts/css_reveal_rules_update.md b/.brv/context-tree/extracted/facts/css_reveal_rules_update.md deleted file mode 100644 index 1ae6b785..00000000 --- a/.brv/context-tree/extracted/facts/css_reveal_rules_update.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -title: CSS reveal rules update -summary: Facts about CSS reveal rules update -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T23:48:05.210Z' -updatedAt: '2026-05-27T23:48:05.210Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Document factual statements about CSS reveal rules update - -**Timestamp:** 2026-05-27T23:48:05.172Z - -## Facts -- **CSS reveal rules update**: To fix, add `.agent-setup-panel` to the base hidden state and `.agent-setup-panel.is-visible` reveal rules, plus reduced-motion visibility if needed. diff --git a/.brv/context-tree/extracted/facts/current_content_audience.md b/.brv/context-tree/extracted/facts/current_content_audience.md deleted file mode 100644 index 268d09d0..00000000 --- a/.brv/context-tree/extracted/facts/current_content_audience.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -title: Current content audience -summary: Facts about current content audience -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T09:45:40.428Z' -updatedAt: '2026-05-28T09:45:40.428Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Document extracted factual statements - -**Timestamp:** 2026-05-28T09:45:40.335Z - -## Facts -- **current content audience**: The current content feels best for people already aware of MCP and coding agents. diff --git a/.brv/context-tree/extracted/facts/dark_integration_and_terminal_blocks.md b/.brv/context-tree/extracted/facts/dark_integration_and_terminal_blocks.md deleted file mode 100644 index ba638157..00000000 --- a/.brv/context-tree/extracted/facts/dark_integration_and_terminal_blocks.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -title: Dark integration and terminal blocks -summary: Facts about dark integration and terminal blocks -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T09:45:40.418Z' -updatedAt: '2026-05-28T09:45:40.418Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Document extracted factual statements - -**Timestamp:** 2026-05-28T09:45:40.335Z - -## Facts -- **dark integration and terminal blocks**: Dark integration and terminal blocks edge toward the “neon devtool dark” anti-reference, although not fully. diff --git a/.brv/context-tree/extracted/facts/default_panel.md b/.brv/context-tree/extracted/facts/default_panel.md deleted file mode 100644 index 4f40a677..00000000 --- a/.brv/context-tree/extracted/facts/default_panel.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: default_panel -summary: Facts about default_panel -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T23:50:44.574Z' -updatedAt: '2026-05-27T23:50:44.574Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-27T23:50:44.568Z - -## Narrative -### Highlights -Extracted 1 facts for default_panel - -## Facts -- **default_panel**: Default selected panel renders initially via `hidden={index !== 0}` at `apps/landing/src/pages/index.astro:289`. diff --git a/.brv/context-tree/extracted/facts/delight.md b/.brv/context-tree/extracted/facts/delight.md deleted file mode 100644 index d26e4453..00000000 --- a/.brv/context-tree/extracted/facts/delight.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Delight -summary: Facts about delight -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T13:18:21.489Z' -updatedAt: '2026-05-28T13:18:21.489Z' ---- -## Reason -Curated extracted facts from source context - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-28T13:18:21.328Z - -## Narrative -### Highlights -Contains 1 facts for delight - -## Facts -- **delight**: `delight [target]` is an Enhance command that adds personality and memorable touches. diff --git a/.brv/context-tree/extracted/facts/design_health.md b/.brv/context-tree/extracted/facts/design_health.md deleted file mode 100644 index b46f29a1..00000000 --- a/.brv/context-tree/extracted/facts/design_health.md +++ /dev/null @@ -1,22 +0,0 @@ ---- -title: Design health -summary: Facts about Design health -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T09:51:10.707Z' -updatedAt: '2026-05-28T09:51:10.707Z' ---- -## Reason -Curated extracted factual statements - -## Raw Concept -**Task:** -Document factual statements - -## Narrative -### Highlights -Extracted 1 facts for Design health - -## Facts -- **Design health**: Design Health Score total is 23 out of 40 diff --git a/.brv/context-tree/extracted/facts/design_md.md b/.brv/context-tree/extracted/facts/design_md.md deleted file mode 100644 index 829ab5c6..00000000 --- a/.brv/context-tree/extracted/facts/design_md.md +++ /dev/null @@ -1,25 +0,0 @@ ---- -title: DESIGN.md -summary: Facts about DESIGN.md -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T13:02:34.219Z' -updatedAt: '2026-05-28T13:18:21.343Z' ---- -## Reason -Curated extracted facts from source context - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-28T13:18:21.328Z - -## Narrative -### Highlights -Contains 2 facts for DESIGN.md - -## Facts -- **DESIGN.md**: DESIGN.md is optional, strongly recommended. -- **DESIGN.md**: If DESIGN.md is missing, nudge once per session with "Run `impeccable document` for more on-brand output" then proceed. diff --git a/.brv/context-tree/extracted/facts/design_process.md b/.brv/context-tree/extracted/facts/design_process.md deleted file mode 100644 index de92c9d0..00000000 --- a/.brv/context-tree/extracted/facts/design_process.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Design process -summary: Facts about design process -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T13:18:21.384Z' -updatedAt: '2026-05-28T13:18:21.384Z' ---- -## Reason -Curated extracted facts from source context - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-28T13:18:21.328Z - -## Narrative -### Highlights -Contains 1 facts for design process - -## Facts -- **design process**: Pick a color strategy before picking colors. diff --git a/.brv/context-tree/extracted/facts/design_register.md b/.brv/context-tree/extracted/facts/design_register.md deleted file mode 100644 index ccbd5cb8..00000000 --- a/.brv/context-tree/extracted/facts/design_register.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Design register -summary: Facts about design register -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T13:02:34.224Z' -updatedAt: '2026-05-28T13:02:34.224Z' ---- -## Reason -Curated extracted facts from provided context - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-28T13:02:34.210Z - -## Narrative -### Highlights -Extracted 1 facts for design register - -## Facts -- **design register**: Design tasks are classified as either brand (marketing, landing, campaign, long-form content, portfolio) or product (app UI, admin, dashboard, tool). diff --git a/.brv/context-tree/extracted/facts/design_task.md b/.brv/context-tree/extracted/facts/design_task.md deleted file mode 100644 index 1be5c1af..00000000 --- a/.brv/context-tree/extracted/facts/design_task.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Design task -summary: Facts about design task -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T13:18:21.346Z' -updatedAt: '2026-05-28T13:18:21.346Z' ---- -## Reason -Curated extracted facts from source context - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-28T13:18:21.328Z - -## Narrative -### Highlights -Contains 1 facts for design task - -## Facts -- **design task**: Every design task is either brand (marketing, landing, campaign, long-form content, portfolio) or product (app UI, admin, dashboard, tool). diff --git a/.brv/context-tree/extracted/facts/desktop_navbar.md b/.brv/context-tree/extracted/facts/desktop_navbar.md deleted file mode 100644 index 6d696735..00000000 --- a/.brv/context-tree/extracted/facts/desktop_navbar.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Desktop navbar -summary: Facts about desktop navbar -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T13:02:34.254Z' -updatedAt: '2026-05-28T13:02:34.254Z' ---- -## Reason -Curated extracted facts from provided context - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-28T13:02:34.210Z - -## Narrative -### Highlights -Extracted 1 facts for desktop navbar - -## Facts -- **desktop navbar**: Desktop navigation is centered with the brand on the left and project links on the right. diff --git a/.brv/context-tree/extracted/facts/distill.md b/.brv/context-tree/extracted/facts/distill.md deleted file mode 100644 index 4bf49552..00000000 --- a/.brv/context-tree/extracted/facts/distill.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Distill -summary: Facts about distill -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T13:18:21.479Z' -updatedAt: '2026-05-28T13:18:21.479Z' ---- -## Reason -Curated extracted facts from source context - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-28T13:18:21.328Z - -## Narrative -### Highlights -Contains 1 facts for distill - -## Facts -- **distill**: `distill [target]` is a Refine command that strips to essence, removing complexity. diff --git a/.brv/context-tree/extracted/facts/document.md b/.brv/context-tree/extracted/facts/document.md deleted file mode 100644 index e7564a98..00000000 --- a/.brv/context-tree/extracted/facts/document.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Document -summary: Facts about document -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T13:18:21.469Z' -updatedAt: '2026-05-28T13:18:21.469Z' ---- -## Reason -Curated extracted facts from source context - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-28T13:18:21.328Z - -## Narrative -### Highlights -Contains 1 facts for document - -## Facts -- **document**: `document` is a Build command that generates DESIGN.md from existing project code. diff --git a/.brv/context-tree/extracted/facts/drenched_strategy.md b/.brv/context-tree/extracted/facts/drenched_strategy.md deleted file mode 100644 index 282c1088..00000000 --- a/.brv/context-tree/extracted/facts/drenched_strategy.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Drenched strategy -summary: Facts about Drenched strategy -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T13:18:21.388Z' -updatedAt: '2026-05-28T13:18:21.388Z' ---- -## Reason -Curated extracted facts from source context - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-28T13:18:21.328Z - -## Narrative -### Highlights -Contains 1 facts for Drenched strategy - -## Facts -- **Drenched strategy**: Drenched: the surface IS the color. diff --git a/.brv/context-tree/extracted/facts/easing.md b/.brv/context-tree/extracted/facts/easing.md deleted file mode 100644 index c3cf09d7..00000000 --- a/.brv/context-tree/extracted/facts/easing.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Easing -summary: Facts about easing -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T13:18:21.435Z' -updatedAt: '2026-05-28T13:18:21.435Z' ---- -## Reason -Curated extracted facts from source context - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-28T13:18:21.328Z - -## Narrative -### Highlights -Contains 1 facts for easing - -## Facts -- **easing**: Ease out with exponential curves (ease-out-quart / quint / expo). No bounce, no elastic. diff --git a/.brv/context-tree/extracted/facts/error_prevention.md b/.brv/context-tree/extracted/facts/error_prevention.md deleted file mode 100644 index f6965808..00000000 --- a/.brv/context-tree/extracted/facts/error_prevention.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -title: Error prevention -summary: Facts about Error prevention -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T09:45:40.356Z' -updatedAt: '2026-05-28T09:45:40.356Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Document extracted factual statements - -**Timestamp:** 2026-05-28T09:45:40.334Z - -## Facts -- **Error prevention**: Error prevention score is 2. diff --git a/.brv/context-tree/extracted/facts/error_recovery.md b/.brv/context-tree/extracted/facts/error_recovery.md deleted file mode 100644 index 90be6539..00000000 --- a/.brv/context-tree/extracted/facts/error_recovery.md +++ /dev/null @@ -1,22 +0,0 @@ ---- -title: Error Recovery -summary: Facts about Error Recovery -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T09:51:10.731Z' -updatedAt: '2026-05-28T09:51:10.731Z' ---- -## Reason -Curated extracted factual statements - -## Raw Concept -**Task:** -Document factual statements - -## Narrative -### Highlights -Extracted 1 facts for Error Recovery - -## Facts -- **Error Recovery**: Error Recovery scored 1 as there are no concrete recovery examples diff --git a/.brv/context-tree/extracted/facts/extract.md b/.brv/context-tree/extracted/facts/extract.md deleted file mode 100644 index 9db26b55..00000000 --- a/.brv/context-tree/extracted/facts/extract.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Extract -summary: Facts about extract -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T13:18:21.470Z' -updatedAt: '2026-05-28T13:18:21.470Z' ---- -## Reason -Curated extracted facts from source context - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-28T13:18:21.328Z - -## Narrative -### Highlights -Contains 1 facts for extract - -## Facts -- **extract**: `extract [target]` is a Build command that pulls reusable tokens and components into a design system. diff --git a/.brv/context-tree/extracted/facts/extracted_facts_from_context.md b/.brv/context-tree/extracted/facts/extracted_facts_from_context.md deleted file mode 100644 index 25656f18..00000000 --- a/.brv/context-tree/extracted/facts/extracted_facts_from_context.md +++ /dev/null @@ -1,46 +0,0 @@ ---- -title: Extracted Facts from Context -summary: Aggregated factual statements extracted from given context -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T23:38:45.004Z' -updatedAt: '2026-05-27T23:55:14.308Z' ---- -## Reason -Store facts extracted via RLM single-pass extraction - -## Raw Concept -**Task:** -Curate extracted facts from provided context - -**Changes:** -- Extracted and deduplicated factual statements - -**Flow:** -extraction -> deduplication -> grouping -> upsert - -**Timestamp:** 2026-05-27T23:55:14.296Z - -## Narrative -### Structure -Aggregated factual statements extracted from the source context. - -### Highlights -tabbed agent setup card, 0e56e8f, 86f9679, 352c0d5, apps/landing/src/pages/index.astro, ... - -## Facts -- **tabbed agent setup card**: Implemented the tabbed agent setup card via subagents, with Impeccable product-design review applied. -- **0e56e8f**: Commit 0e56e8f feat(landing): add agent setup tabs. -- **86f9679**: Commit 86f9679 fix(landing): reveal agent setup panels. -- **352c0d5**: Commit 352c0d5 fix(landing): correct native agent setup snippets. -- **apps/landing/src/pages/index.astro**: Changed file apps/landing/src/pages/index.astro. -- **apps/landing/src/styles/global.css**: Changed file apps/landing/src/styles/global.css. -- **docs/plans/2026-05-27-agent-card-setup-tabs.md**: Changed file docs/plans/2026-05-27-agent-card-setup-tabs.md. -- **spec compliance**: Spec compliance review: approved. -- **code quality**: Code quality review: approved after correcting OpenCode/Pi snippets against their package READMEs. -- **pnpm format:check**: Fresh verification passed: pnpm format:check. -- **pnpm lint**: Fresh verification passed: pnpm lint. -- **typecheck**: Fresh verification passed: pnpm --filter @caplets/landing typecheck. -- **build**: Fresh verification passed: pnpm --filter @caplets/landing build. -- **working tree**: The working tree still has pre-existing unrelated unstaged/untracked changes (.brv/*, deleted apps/landing/.vscode/*, screenshots, and unstaged landing file diffs) that were left untouched. diff --git a/.brv/context-tree/extracted/facts/flexibility_and_efficiency.md b/.brv/context-tree/extracted/facts/flexibility_and_efficiency.md deleted file mode 100644 index 159de636..00000000 --- a/.brv/context-tree/extracted/facts/flexibility_and_efficiency.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -title: Flexibility and efficiency -summary: Facts about Flexibility and efficiency -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T09:45:40.369Z' -updatedAt: '2026-05-28T09:45:40.369Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Document extracted factual statements - -**Timestamp:** 2026-05-28T09:45:40.334Z - -## Facts -- **Flexibility and efficiency**: Flexibility and efficiency score is 2. diff --git a/.brv/context-tree/extracted/facts/full_palette_strategy.md b/.brv/context-tree/extracted/facts/full_palette_strategy.md deleted file mode 100644 index 8acf2fe6..00000000 --- a/.brv/context-tree/extracted/facts/full_palette_strategy.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Full palette strategy -summary: Facts about Full palette strategy -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T13:18:21.387Z' -updatedAt: '2026-05-28T13:18:21.387Z' ---- -## Reason -Curated extracted facts from source context - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-28T13:18:21.328Z - -## Narrative -### Highlights -Contains 1 facts for Full palette strategy - -## Facts -- **Full palette strategy**: Full palette: 3–4 named roles, each used deliberately. diff --git a/.brv/context-tree/extracted/facts/glassmorphism.md b/.brv/context-tree/extracted/facts/glassmorphism.md deleted file mode 100644 index 42755686..00000000 --- a/.brv/context-tree/extracted/facts/glassmorphism.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Glassmorphism -summary: Facts about glassmorphism -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T13:18:21.449Z' -updatedAt: '2026-05-28T13:18:21.449Z' ---- -## Reason -Curated extracted facts from source context - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-28T13:18:21.328Z - -## Narrative -### Highlights -Contains 1 facts for glassmorphism - -## Facts -- **glassmorphism**: Glassmorphism as default is banned. diff --git a/.brv/context-tree/extracted/facts/global_css.md b/.brv/context-tree/extracted/facts/global_css.md deleted file mode 100644 index bc685917..00000000 --- a/.brv/context-tree/extracted/facts/global_css.md +++ /dev/null @@ -1,27 +0,0 @@ ---- -title: Global.css -summary: Facts about global.css -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T13:18:21.507Z' -updatedAt: '2026-05-28T13:18:21.507Z' ---- -## Reason -Curated extracted facts from source context - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-28T13:18:21.328Z - -## Narrative -### Highlights -Contains 4 facts for global.css - -## Facts -- **global.css**: In apps/landing/src/styles/global.css, mobile native-agent panel padding and spacing were tightened -- **global.css**: In apps/landing/src/styles/global.css, mobile code font size was reduced slightly -- **global.css**: In apps/landing/src/styles/global.css, snippets now wrap on mobile so long commands no longer spill outside the code box -- **global.css**: In apps/landing/src/styles/global.css, code block padding on mobile was reduced while preserving copyable formatting diff --git a/.brv/context-tree/extracted/facts/gradient_text.md b/.brv/context-tree/extracted/facts/gradient_text.md deleted file mode 100644 index 7e54fe83..00000000 --- a/.brv/context-tree/extracted/facts/gradient_text.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Gradient text -summary: Facts about gradient text -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T13:02:34.244Z' -updatedAt: '2026-05-28T13:18:21.446Z' ---- -## Reason -Curated extracted facts from source context - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-28T13:18:21.328Z - -## Narrative -### Highlights -Contains 1 facts for gradient text - -## Facts -- **gradient text**: Gradient text using background‑clip: text combined with a gradient background is decorative, never meaningful. diff --git a/.brv/context-tree/extracted/facts/harden.md b/.brv/context-tree/extracted/facts/harden.md deleted file mode 100644 index 082eecfb..00000000 --- a/.brv/context-tree/extracted/facts/harden.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Harden -summary: Facts about harden -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T13:18:21.481Z' -updatedAt: '2026-05-28T13:18:21.481Z' ---- -## Reason -Curated extracted facts from source context - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-28T13:18:21.328Z - -## Narrative -### Highlights -Contains 1 facts for harden - -## Facts -- **harden**: `harden [target]` is a Refine command that makes a product production‑ready, handling errors, i18n, and edge cases. diff --git a/.brv/context-tree/extracted/facts/header.md b/.brv/context-tree/extracted/facts/header.md deleted file mode 100644 index b896e9c9..00000000 --- a/.brv/context-tree/extracted/facts/header.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Header -summary: Facts about header -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T13:02:34.249Z' -updatedAt: '2026-05-28T13:02:34.249Z' ---- -## Reason -Curated extracted facts from provided context - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-28T13:02:34.210Z - -## Narrative -### Highlights -Extracted 1 facts for header - -## Facts -- **header**: The floating rounded header card was replaced with a full-width sticky navbar. diff --git a/.brv/context-tree/extracted/facts/header_icon_only_npm_github_links.md b/.brv/context-tree/extracted/facts/header_icon_only_npm_github_links.md deleted file mode 100644 index ad4a83a4..00000000 --- a/.brv/context-tree/extracted/facts/header_icon_only_npm_github_links.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -title: Header icon-only npm/GitHub links -summary: Facts about header icon-only npm/GitHub links -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T09:45:40.414Z' -updatedAt: '2026-05-28T09:45:40.414Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Document extracted factual statements - -**Timestamp:** 2026-05-28T09:45:40.335Z - -## Facts -- **header icon-only npm/GitHub links**: Header icon-only npm/GitHub links have labels, good. diff --git a/.brv/context-tree/extracted/facts/help_and_documentation.md b/.brv/context-tree/extracted/facts/help_and_documentation.md deleted file mode 100644 index 0ac148d4..00000000 --- a/.brv/context-tree/extracted/facts/help_and_documentation.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -title: Help and documentation -summary: Facts about Help and documentation -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T09:45:40.382Z' -updatedAt: '2026-05-28T09:45:40.382Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Document extracted factual statements - -**Timestamp:** 2026-05-28T09:45:40.335Z - -## Facts -- **Help and documentation**: Help and documentation score is 2. diff --git a/.brv/context-tree/extracted/facts/help_users_recognize_diagnose_recover.md b/.brv/context-tree/extracted/facts/help_users_recognize_diagnose_recover.md deleted file mode 100644 index 9f1ab0d6..00000000 --- a/.brv/context-tree/extracted/facts/help_users_recognize_diagnose_recover.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -title: Help users recognize, diagnose, recover -summary: Facts about Help users recognize, diagnose, recover -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T09:45:40.380Z' -updatedAt: '2026-05-28T09:45:40.380Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Document extracted factual statements - -**Timestamp:** 2026-05-28T09:45:40.335Z - -## Facts -- **Help users recognize, diagnose, recover**: Help users recognize, diagnose, recover score is 1. diff --git a/.brv/context-tree/extracted/facts/hero_content.md b/.brv/context-tree/extracted/facts/hero_content.md deleted file mode 100644 index 82c6c343..00000000 --- a/.brv/context-tree/extracted/facts/hero_content.md +++ /dev/null @@ -1,22 +0,0 @@ ---- -title: Hero content -summary: Facts about Hero content -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T09:51:10.734Z' -updatedAt: '2026-05-28T09:51:10.734Z' ---- -## Reason -Curated extracted factual statements - -## Raw Concept -**Task:** -Document factual statements - -## Narrative -### Highlights -Extracted 1 facts for Hero content - -## Facts -- **Hero content**: The hero section lacks a concrete definition of the product within 5 seconds diff --git a/.brv/context-tree/extracted/facts/hero_headline.md b/.brv/context-tree/extracted/facts/hero_headline.md deleted file mode 100644 index 347de3b4..00000000 --- a/.brv/context-tree/extracted/facts/hero_headline.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -title: Hero headline -summary: Facts about hero headline -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T09:45:40.395Z' -updatedAt: '2026-05-28T09:45:40.395Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Document extracted factual statements - -**Timestamp:** 2026-05-28T09:45:40.335Z - -## Facts -- **hero headline**: Hero headline is too abstract: “Give every tool stack a map”. diff --git a/.brv/context-tree/extracted/facts/hero_metric_template.md b/.brv/context-tree/extracted/facts/hero_metric_template.md deleted file mode 100644 index 77072867..00000000 --- a/.brv/context-tree/extracted/facts/hero_metric_template.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Hero‑metric template -summary: Facts about hero‑metric template -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T13:18:21.454Z' -updatedAt: '2026-05-28T13:18:21.454Z' ---- -## Reason -Curated extracted facts from source context - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-28T13:18:21.328Z - -## Narrative -### Highlights -Contains 1 facts for hero‑metric template - -## Facts -- **hero‑metric template**: The hero‑metric template is a SaaS cliché. diff --git a/.brv/context-tree/extracted/facts/hero_metrics.md b/.brv/context-tree/extracted/facts/hero_metrics.md deleted file mode 100644 index 597b3fd2..00000000 --- a/.brv/context-tree/extracted/facts/hero_metrics.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -title: Hero metrics -summary: Facts about hero metrics -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T09:45:40.407Z' -updatedAt: '2026-05-28T09:45:40.407Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Document extracted factual statements - -**Timestamp:** 2026-05-28T09:45:40.335Z - -## Facts -- **hero metrics**: No hero metrics. diff --git a/.brv/context-tree/extracted/facts/hex_colors.md b/.brv/context-tree/extracted/facts/hex_colors.md deleted file mode 100644 index 385d40bf..00000000 --- a/.brv/context-tree/extracted/facts/hex_colors.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Hex colors -summary: Facts about hex colors -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T13:18:21.381Z' -updatedAt: '2026-05-28T13:18:21.381Z' ---- -## Reason -Curated extracted facts from source context - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-28T13:18:21.328Z - -## Narrative -### Highlights -Contains 1 facts for hex colors - -## Facts -- **hex colors**: Never use #000 or #fff. diff --git a/.brv/context-tree/extracted/facts/horizontal_overflow.md b/.brv/context-tree/extracted/facts/horizontal_overflow.md deleted file mode 100644 index c0dc06ed..00000000 --- a/.brv/context-tree/extracted/facts/horizontal_overflow.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Horizontal overflow -summary: Facts about horizontal overflow -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T13:18:21.518Z' -updatedAt: '2026-05-28T13:18:21.518Z' ---- -## Reason -Curated extracted facts from source context - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-28T13:18:21.328Z - -## Narrative -### Highlights -Contains 1 facts for horizontal overflow - -## Facts -- **horizontal overflow**: No document/body horizontal overflow diff --git a/.brv/context-tree/extracted/facts/icons.md b/.brv/context-tree/extracted/facts/icons.md deleted file mode 100644 index 2dcf6514..00000000 --- a/.brv/context-tree/extracted/facts/icons.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Icons -summary: Facts about icons -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T13:02:34.252Z' -updatedAt: '2026-05-28T13:02:34.252Z' ---- -## Reason -Curated extracted facts from provided context - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-28T13:02:34.210Z - -## Narrative -### Highlights -Extracted 1 facts for icons - -## Facts -- **icons**: Card‑like borders/backgrounds were removed from header action icons. diff --git a/.brv/context-tree/extracted/facts/impeccable_context_dir.md b/.brv/context-tree/extracted/facts/impeccable_context_dir.md deleted file mode 100644 index 49ace20c..00000000 --- a/.brv/context-tree/extracted/facts/impeccable_context_dir.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: IMPECCABLE CONTEXT DIR -summary: Facts about IMPECCABLE_CONTEXT_DIR -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T13:18:21.348Z' -updatedAt: '2026-05-28T13:18:21.348Z' ---- -## Reason -Curated extracted facts from source context - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-28T13:18:21.328Z - -## Narrative -### Highlights -Contains 1 facts for IMPECCABLE_CONTEXT_DIR - -## Facts -- **IMPECCABLE_CONTEXT_DIR**: Override the loader directory with `IMPECCABLE_CONTEXT_DIR=path/to/dir` (absolute or relative to cwd). diff --git a/.brv/context-tree/extracted/facts/impeccable_skill.md b/.brv/context-tree/extracted/facts/impeccable_skill.md deleted file mode 100644 index 7415c805..00000000 --- a/.brv/context-tree/extracted/facts/impeccable_skill.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Impeccable skill -summary: Facts about impeccable skill -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T13:02:34.213Z' -updatedAt: '2026-05-28T13:02:34.213Z' ---- -## Reason -Curated extracted facts from provided context - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-28T13:02:34.210Z - -## Narrative -### Highlights -Extracted 1 facts for impeccable skill - -## Facts -- **impeccable skill**: The skill "impeccable" designs and iterates production-grade frontend interfaces with real working code and exceptional craft. diff --git a/.brv/context-tree/extracted/facts/install_command.md b/.brv/context-tree/extracted/facts/install_command.md deleted file mode 100644 index 76a5d7ec..00000000 --- a/.brv/context-tree/extracted/facts/install_command.md +++ /dev/null @@ -1,21 +0,0 @@ ---- -title: Install command -summary: Facts about install command -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T09:45:40.398Z' -updatedAt: '2026-05-28T09:45:40.398Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Document extracted factual statements - -**Timestamp:** 2026-05-28T09:45:40.335Z - -## Facts -- **install command**: Install commands may conflict with repo conventions or user expectation: page uses `pnpm add -g caplets`; many users expect `npm install -g caplets`. -- **install command**: Fix install command inconsistency: `pnpm add -g caplets` versus `npm install -g caplets` creates immediate doubt. diff --git a/.brv/context-tree/extracted/facts/install_command_rendering.md b/.brv/context-tree/extracted/facts/install_command_rendering.md deleted file mode 100644 index a3caef03..00000000 --- a/.brv/context-tree/extracted/facts/install_command_rendering.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -title: install command rendering -summary: Facts about install command rendering -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T23:48:05.189Z' -updatedAt: '2026-05-27T23:48:05.189Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Document factual statements about install command rendering - -**Timestamp:** 2026-05-27T23:48:05.172Z - -## Facts -- **install command rendering**: Each agent has an install command and config snippet rendered from the centralized data (`apps/landing/src/pages/index.astro:291-300`). diff --git a/.brv/context-tree/extracted/facts/install_commands.md b/.brv/context-tree/extracted/facts/install_commands.md deleted file mode 100644 index ff8e52e6..00000000 --- a/.brv/context-tree/extracted/facts/install_commands.md +++ /dev/null @@ -1,22 +0,0 @@ ---- -title: Install commands -summary: Facts about Install commands -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T09:51:10.737Z' -updatedAt: '2026-05-28T09:51:10.737Z' ---- -## Reason -Curated extracted factual statements - -## Raw Concept -**Task:** -Document factual statements - -## Narrative -### Highlights -Extracted 1 facts for Install commands - -## Facts -- **Install commands**: Install commands are inconsistent between pnpm and npm diff --git a/.brv/context-tree/extracted/facts/integrations_card_structure.md b/.brv/context-tree/extracted/facts/integrations_card_structure.md deleted file mode 100644 index 365c58c0..00000000 --- a/.brv/context-tree/extracted/facts/integrations_card_structure.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -title: integrations card structure -summary: Facts about integrations card structure -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T23:48:05.177Z' -updatedAt: '2026-05-27T23:48:05.177Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Document factual statements about integrations card structure - -**Timestamp:** 2026-05-27T23:48:05.172Z - -## Facts -- **integrations card structure**: Landing integrations card remains structurally recognizable: same `.integrations` section heading/card shell is preserved, with tabbed setup content added inside it (`apps/landing/src/pages/index.astro:260-305`). diff --git a/.brv/context-tree/extracted/facts/javascript_fallback.md b/.brv/context-tree/extracted/facts/javascript_fallback.md deleted file mode 100644 index 4f8a57ea..00000000 --- a/.brv/context-tree/extracted/facts/javascript_fallback.md +++ /dev/null @@ -1,22 +0,0 @@ ---- -title: JavaScript fallback -summary: Facts about JavaScript fallback -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T09:51:10.751Z' -updatedAt: '2026-05-28T09:51:10.751Z' ---- -## Reason -Curated extracted factual statements - -## Raw Concept -**Task:** -Document factual statements - -## Narrative -### Highlights -Extracted 1 facts for JavaScript fallback - -## Facts -- **JavaScript fallback**: JS‑disabled tabs hide content, causing loss of integration instructions diff --git a/.brv/context-tree/extracted/facts/jordan.md b/.brv/context-tree/extracted/facts/jordan.md deleted file mode 100644 index 6bdc76aa..00000000 --- a/.brv/context-tree/extracted/facts/jordan.md +++ /dev/null @@ -1,22 +0,0 @@ ---- -title: Jordan -summary: Facts about Jordan -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T09:51:10.752Z' -updatedAt: '2026-05-28T09:51:10.752Z' ---- -## Reason -Curated extracted factual statements - -## Raw Concept -**Task:** -Document factual statements - -## Narrative -### Highlights -Extracted 1 facts for Jordan - -## Facts -- **Jordan**: Persona Jordan (First‑Timer) encounters unexplained terms like “capability cards” and “get_caplet” diff --git a/.brv/context-tree/extracted/facts/landing_page.md b/.brv/context-tree/extracted/facts/landing_page.md deleted file mode 100644 index 7c9e7a15..00000000 --- a/.brv/context-tree/extracted/facts/landing_page.md +++ /dev/null @@ -1,22 +0,0 @@ ---- -title: Landing page -summary: Facts about Landing page -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T09:51:10.697Z' -updatedAt: '2026-05-28T09:51:10.697Z' ---- -## Reason -Curated extracted factual statements - -## Raw Concept -**Task:** -Document factual statements - -## Narrative -### Highlights -Extracted 1 facts for Landing page - -## Facts -- **Landing page**: The page has product‑specific language, capability‑card metaphor, and custom visual style diff --git a/.brv/context-tree/extracted/facts/layout.md b/.brv/context-tree/extracted/facts/layout.md deleted file mode 100644 index 13cb6d56..00000000 --- a/.brv/context-tree/extracted/facts/layout.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Layout -summary: Facts about layout -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T13:18:21.488Z' -updatedAt: '2026-05-28T13:18:21.488Z' ---- -## Reason -Curated extracted facts from source context - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-28T13:18:21.328Z - -## Narrative -### Highlights -Contains 1 facts for layout - -## Facts -- **layout**: `layout [target]` is an Enhance command that fixes spacing, rhythm, and visual hierarchy. diff --git a/.brv/context-tree/extracted/facts/line_length.md b/.brv/context-tree/extracted/facts/line_length.md deleted file mode 100644 index a966ecd9..00000000 --- a/.brv/context-tree/extracted/facts/line_length.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Line length -summary: Facts about line length -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T13:02:34.239Z' -updatedAt: '2026-05-28T13:18:21.393Z' ---- -## Reason -Curated extracted facts from source context - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-28T13:18:21.328Z - -## Narrative -### Highlights -Contains 1 facts for line length - -## Facts -- **line length**: Cap body line length at 65–75ch. diff --git a/.brv/context-tree/extracted/facts/live.md b/.brv/context-tree/extracted/facts/live.md deleted file mode 100644 index 8f9d8409..00000000 --- a/.brv/context-tree/extracted/facts/live.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Live -summary: Facts about live -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T13:18:21.497Z' -updatedAt: '2026-05-28T13:18:21.497Z' ---- -## Reason -Curated extracted facts from source context - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-28T13:18:21.328Z - -## Narrative -### Highlights -Contains 1 facts for live - -## Facts -- **live**: `live` is an Iterate command that provides a visual variant mode, allowing selection of elements in the browser to generate alternatives. diff --git a/.brv/context-tree/extracted/facts/load_failures.md b/.brv/context-tree/extracted/facts/load_failures.md deleted file mode 100644 index 40b90a86..00000000 --- a/.brv/context-tree/extracted/facts/load_failures.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -title: Load failures -summary: Facts about load failures -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T09:45:40.394Z' -updatedAt: '2026-05-28T09:45:40.394Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Document extracted factual statements - -**Timestamp:** 2026-05-28T09:45:40.335Z - -## Facts -- **load failures**: 7 notable load failures diff --git a/.brv/context-tree/extracted/facts/loader.md b/.brv/context-tree/extracted/facts/loader.md deleted file mode 100644 index ba43c90c..00000000 --- a/.brv/context-tree/extracted/facts/loader.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Loader -summary: Facts about loader -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T13:18:21.347Z' -updatedAt: '2026-05-28T13:18:21.347Z' ---- -## Reason -Curated extracted facts from source context - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-28T13:18:21.328Z - -## Narrative -### Highlights -Contains 1 facts for loader - -## Facts -- **loader**: The loader looks at the project root by default and falls back to `.agents/context/` and `docs/` if the root is clean. diff --git a/.brv/context-tree/extracted/facts/loader_output_handling.md b/.brv/context-tree/extracted/facts/loader_output_handling.md deleted file mode 100644 index b4eac989..00000000 --- a/.brv/context-tree/extracted/facts/loader_output_handling.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Loader output handling -summary: Facts about loader output handling -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T13:18:21.351Z' -updatedAt: '2026-05-28T13:18:21.351Z' ---- -## Reason -Curated extracted facts from source context - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-28T13:18:21.328Z - -## Narrative -### Highlights -Contains 1 facts for loader output handling - -## Facts -- **loader output handling**: Never pipe the loader's JSON output through `head`, `tail`, `grep`, or `jq`. diff --git a/.brv/context-tree/extracted/facts/loader_script.md b/.brv/context-tree/extracted/facts/loader_script.md deleted file mode 100644 index c5868e20..00000000 --- a/.brv/context-tree/extracted/facts/loader_script.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Loader script -summary: Facts about loader script -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T13:02:34.221Z' -updatedAt: '2026-05-28T13:02:34.221Z' ---- -## Reason -Curated extracted facts from provided context - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-28T13:02:34.210Z - -## Narrative -### Highlights -Extracted 1 facts for loader script - -## Facts -- **loader script**: The loader script looks for context files in the project root, then falls back to .agents/context/ and docs/. diff --git a/.brv/context-tree/extracted/facts/match_between_system_and_real_world.md b/.brv/context-tree/extracted/facts/match_between_system_and_real_world.md deleted file mode 100644 index a512eb6e..00000000 --- a/.brv/context-tree/extracted/facts/match_between_system_and_real_world.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -title: Match between system and real world -summary: Facts about Match between system and real world -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T09:45:40.347Z' -updatedAt: '2026-05-28T09:45:40.347Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Document extracted factual statements - -**Timestamp:** 2026-05-28T09:45:40.334Z - -## Facts -- **Match between system and real world**: Match between system and real world score is 3. diff --git a/.brv/context-tree/extracted/facts/mobile_branding.md b/.brv/context-tree/extracted/facts/mobile_branding.md deleted file mode 100644 index 98789400..00000000 --- a/.brv/context-tree/extracted/facts/mobile_branding.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Mobile branding -summary: Facts about mobile branding -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T13:02:34.260Z' -updatedAt: '2026-05-28T13:02:34.260Z' ---- -## Reason -Curated extracted facts from provided context - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-28T13:02:34.210Z - -## Narrative -### Highlights -Extracted 1 facts for mobile branding - -## Facts -- **mobile branding**: Brand text is hidden on mobile to preserve horizontal space. diff --git a/.brv/context-tree/extracted/facts/mobile_header.md b/.brv/context-tree/extracted/facts/mobile_header.md deleted file mode 100644 index 80b605d7..00000000 --- a/.brv/context-tree/extracted/facts/mobile_header.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Mobile header -summary: Facts about mobile header -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T13:02:34.258Z' -updatedAt: '2026-05-28T13:02:34.258Z' ---- -## Reason -Curated extracted facts from provided context - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-28T13:02:34.210Z - -## Narrative -### Highlights -Extracted 1 facts for mobile header - -## Facts -- **mobile header**: Mobile header height reduced from a multi‑row card to a 48 px bar. diff --git a/.brv/context-tree/extracted/facts/mobile_navbar.md b/.brv/context-tree/extracted/facts/mobile_navbar.md deleted file mode 100644 index 88feb44a..00000000 --- a/.brv/context-tree/extracted/facts/mobile_navbar.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Mobile navbar -summary: Facts about mobile navbar -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T13:02:34.255Z' -updatedAt: '2026-05-28T13:02:34.256Z' ---- -## Reason -Curated extracted facts from provided context - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-28T13:02:34.210Z - -## Narrative -### Highlights -Extracted 1 facts for mobile navbar - -## Facts -- **mobile navbar**: Tablet and mobile layout changed from stacked rows to a single compact horizontal navbar. diff --git a/.brv/context-tree/extracted/facts/modals.md b/.brv/context-tree/extracted/facts/modals.md deleted file mode 100644 index 169463f2..00000000 --- a/.brv/context-tree/extracted/facts/modals.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Modals -summary: Facts about modals -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T13:18:21.457Z' -updatedAt: '2026-05-28T13:18:21.457Z' ---- -## Reason -Curated extracted facts from source context - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-28T13:18:21.328Z - -## Narrative -### Highlights -Contains 1 facts for modals - -## Facts -- **modals**: Modal as first thought is usually laziness; exhaust inline / progressive alternatives first. diff --git a/.brv/context-tree/extracted/facts/native_agent_tabs.md b/.brv/context-tree/extracted/facts/native_agent_tabs.md deleted file mode 100644 index b0f803a1..00000000 --- a/.brv/context-tree/extracted/facts/native_agent_tabs.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Native agent tabs -summary: Facts about Native agent tabs -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T13:18:21.510Z' -updatedAt: '2026-05-28T13:18:21.510Z' ---- -## Reason -Curated extracted facts from source context - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-28T13:18:21.328Z - -## Narrative -### Highlights -Contains 1 facts for Native agent tabs - -## Facts -- **Native agent tabs**: All native agent tabs have scrollWidth === clientWidth for their code snippets diff --git a/.brv/context-tree/extracted/facts/native_agents_install_card.md b/.brv/context-tree/extracted/facts/native_agents_install_card.md deleted file mode 100644 index 1965e380..00000000 --- a/.brv/context-tree/extracted/facts/native_agents_install_card.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Native agents install card -summary: Facts about Native agents install card -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T13:18:21.506Z' -updatedAt: '2026-05-28T13:18:21.506Z' ---- -## Reason -Curated extracted facts from source context - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-28T13:18:21.328Z - -## Narrative -### Highlights -Contains 1 facts for Native agents install card - -## Facts -- **Native agents install card**: The native agents install card overflow was fixed after inspecting it in the browser at mobile dimensions diff --git a/.brv/context-tree/extracted/facts/navbar.md b/.brv/context-tree/extracted/facts/navbar.md deleted file mode 100644 index 93474538..00000000 --- a/.brv/context-tree/extracted/facts/navbar.md +++ /dev/null @@ -1,25 +0,0 @@ ---- -title: Navbar -summary: Facts about navbar -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T13:02:34.247Z' -updatedAt: '2026-05-28T13:02:34.247Z' ---- -## Reason -Curated extracted facts from provided context - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-28T13:02:34.210Z - -## Narrative -### Highlights -Extracted 2 facts for navbar - -## Facts -- **navbar**: The top navigation on both mobile and desktop should be a full-width navbar, not cards. -- **navbar**: The mobile navbar takes up too much vertical space. diff --git a/.brv/context-tree/extracted/facts/nested_cards.md b/.brv/context-tree/extracted/facts/nested_cards.md deleted file mode 100644 index a4ed6ddb..00000000 --- a/.brv/context-tree/extracted/facts/nested_cards.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Nested cards -summary: Facts about nested cards -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T13:18:21.422Z' -updatedAt: '2026-05-28T13:18:21.422Z' ---- -## Reason -Curated extracted facts from source context - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-28T13:18:21.328Z - -## Narrative -### Highlights -Contains 1 facts for nested cards - -## Facts -- **nested cards**: Nested cards are always wrong. diff --git a/.brv/context-tree/extracted/facts/neutral_tinting.md b/.brv/context-tree/extracted/facts/neutral_tinting.md deleted file mode 100644 index 0dc90520..00000000 --- a/.brv/context-tree/extracted/facts/neutral_tinting.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Neutral tinting -summary: Facts about neutral tinting -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T13:18:21.382Z' -updatedAt: '2026-05-28T13:18:21.382Z' ---- -## Reason -Curated extracted facts from source context - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-28T13:18:21.328Z - -## Narrative -### Highlights -Contains 1 facts for neutral tinting - -## Facts -- **neutral tinting**: Tint every neutral toward the brand hue (chroma 0.005–0.01 is enough). diff --git a/.brv/context-tree/extracted/facts/newer_users.md b/.brv/context-tree/extracted/facts/newer_users.md deleted file mode 100644 index 9ff229fc..00000000 --- a/.brv/context-tree/extracted/facts/newer_users.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -title: Newer users -summary: Facts about newer users -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T09:45:40.429Z' -updatedAt: '2026-05-28T09:45:40.429Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Document extracted factual statements - -**Timestamp:** 2026-05-28T09:45:40.335Z - -## Facts -- **newer users**: Newer users may need one clearer bridge sentence. diff --git a/.brv/context-tree/extracted/facts/npm.md b/.brv/context-tree/extracted/facts/npm.md deleted file mode 100644 index 9d8d7007..00000000 --- a/.brv/context-tree/extracted/facts/npm.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -title: Npm -summary: Facts about npm -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T09:45:40.390Z' -updatedAt: '2026-05-28T09:45:40.390Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Document extracted factual statements - -**Timestamp:** 2026-05-28T09:45:40.335Z - -## Facts -- **npm**: npm links exist, install steps exist. diff --git a/.brv/context-tree/extracted/facts/onboard.md b/.brv/context-tree/extracted/facts/onboard.md deleted file mode 100644 index df48b1bd..00000000 --- a/.brv/context-tree/extracted/facts/onboard.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Onboard -summary: Facts about onboard -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T13:18:21.482Z' -updatedAt: '2026-05-28T13:18:21.482Z' ---- -## Reason -Curated extracted facts from source context - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-28T13:18:21.328Z - -## Narrative -### Highlights -Contains 1 facts for onboard - -## Facts -- **onboard**: `onboard [target]` is a Refine command that designs first‑run flows, empty states, and activation experiences. diff --git a/.brv/context-tree/extracted/facts/opencode_configuration.md b/.brv/context-tree/extracted/facts/opencode_configuration.md deleted file mode 100644 index f6fd1cb0..00000000 --- a/.brv/context-tree/extracted/facts/opencode_configuration.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: OpenCode configuration -summary: Facts about OpenCode configuration -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T23:52:05.216Z' -updatedAt: '2026-05-27T23:52:05.216Z' ---- -## Reason -Curate extracted facts from context - -## Raw Concept -**Task:** -Extracted factual statements - -**Timestamp:** 2026-05-27T23:52:05.208Z - -## Narrative -### Structure -Collected facts grouped by subject - -## Facts -- **OpenCode configuration**: The landing page tells users to configure OpenCode with an MCP-style "mcp": { ... "command": ["caplets", "serve"] } block after installing @caplets/opencode. diff --git a/.brv/context-tree/extracted/facts/opencode_readme.md b/.brv/context-tree/extracted/facts/opencode_readme.md deleted file mode 100644 index fc2685b9..00000000 --- a/.brv/context-tree/extracted/facts/opencode_readme.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: OpenCode README -summary: Facts about OpenCode README -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T23:52:05.218Z' -updatedAt: '2026-05-27T23:52:05.218Z' ---- -## Reason -Curate extracted facts from context - -## Raw Concept -**Task:** -Extracted factual statements - -**Timestamp:** 2026-05-27T23:52:05.208Z - -## Narrative -### Structure -Collected facts grouped by subject - -## Facts -- **OpenCode README**: packages/opencode/README.md states that @caplets/opencode is a native OpenCode plugin, does not start the MCP server, and is configured as { "plugin": ["@caplets/opencode"] }. diff --git a/.brv/context-tree/extracted/facts/opencode_setup_snippet.md b/.brv/context-tree/extracted/facts/opencode_setup_snippet.md deleted file mode 100644 index b8a4f277..00000000 --- a/.brv/context-tree/extracted/facts/opencode_setup_snippet.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: OpenCode setup snippet -summary: Facts about OpenCode setup snippet -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T23:52:05.214Z' -updatedAt: '2026-05-27T23:52:05.214Z' ---- -## Reason -Curate extracted facts from context - -## Raw Concept -**Task:** -Extracted factual statements - -**Timestamp:** 2026-05-27T23:52:05.208Z - -## Narrative -### Structure -Collected facts grouped by subject - -## Facts -- **OpenCode setup snippet**: The OpenCode setup snippet is product misinformation. diff --git a/.brv/context-tree/extracted/facts/optimize.md b/.brv/context-tree/extracted/facts/optimize.md deleted file mode 100644 index 023702a9..00000000 --- a/.brv/context-tree/extracted/facts/optimize.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Optimize -summary: Facts about optimize -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T13:18:21.495Z' -updatedAt: '2026-05-28T13:18:21.495Z' ---- -## Reason -Curated extracted facts from source context - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-28T13:18:21.328Z - -## Narrative -### Highlights -Contains 1 facts for optimize - -## Facts -- **optimize**: `optimize [target]` is a Fix command that diagnoses and fixes UI performance issues. diff --git a/.brv/context-tree/extracted/facts/overall_assessment.md b/.brv/context-tree/extracted/facts/overall_assessment.md deleted file mode 100644 index 8ae5aa7d..00000000 --- a/.brv/context-tree/extracted/facts/overall_assessment.md +++ /dev/null @@ -1,22 +0,0 @@ ---- -title: Overall assessment -summary: Facts about Overall assessment -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T09:51:10.702Z' -updatedAt: '2026-05-28T09:51:10.702Z' ---- -## Reason -Curated extracted factual statements - -## Raw Concept -**Task:** -Document factual statements - -## Narrative -### Highlights -Extracted 1 facts for Overall assessment - -## Facts -- **Overall assessment**: Weaknesses include too much spectacle and lack of demonstrable trust evidence diff --git a/.brv/context-tree/extracted/facts/overdrive.md b/.brv/context-tree/extracted/facts/overdrive.md deleted file mode 100644 index e78d222c..00000000 --- a/.brv/context-tree/extracted/facts/overdrive.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Overdrive -summary: Facts about overdrive -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T13:18:21.491Z' -updatedAt: '2026-05-28T13:18:21.491Z' ---- -## Reason -Curated extracted facts from source context - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-28T13:18:21.328Z - -## Narrative -### Highlights -Contains 1 facts for overdrive - -## Facts -- **overdrive**: `overdrive [target]` is an Enhance command that pushes past conventional limits. diff --git a/.brv/context-tree/extracted/facts/page_layout.md b/.brv/context-tree/extracted/facts/page_layout.md deleted file mode 100644 index 59c356fc..00000000 --- a/.brv/context-tree/extracted/facts/page_layout.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Page layout -summary: Facts about Page layout -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T13:18:21.512Z' -updatedAt: '2026-05-28T13:18:21.512Z' ---- -## Reason -Curated extracted facts from source context - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-28T13:18:21.328Z - -## Narrative -### Highlights -Contains 1 facts for Page layout - -## Facts -- **Page layout**: Page width remains stable at 320px with no document/body horizontal overflow diff --git a/.brv/context-tree/extracted/facts/page_width.md b/.brv/context-tree/extracted/facts/page_width.md deleted file mode 100644 index 23a366e4..00000000 --- a/.brv/context-tree/extracted/facts/page_width.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Page width -summary: Facts about page width -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T13:18:21.517Z' -updatedAt: '2026-05-28T13:18:21.517Z' ---- -## Reason -Curated extracted facts from source context - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-28T13:18:21.328Z - -## Narrative -### Highlights -Contains 1 facts for page width - -## Facts -- **page width**: Page width remains stable at 320px diff --git a/.brv/context-tree/extracted/facts/pi_extension.md b/.brv/context-tree/extracted/facts/pi_extension.md deleted file mode 100644 index 7b90728a..00000000 --- a/.brv/context-tree/extracted/facts/pi_extension.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Pi extension -summary: Facts about Pi extension -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T23:52:05.239Z' -updatedAt: '2026-05-27T23:52:05.239Z' ---- -## Reason -Curate extracted facts from context - -## Raw Concept -**Task:** -Extracted factual statements - -**Timestamp:** 2026-05-27T23:52:05.208Z - -## Narrative -### Structure -Collected facts grouped by subject - -## Facts -- **Pi extension**: packages/pi/README.md says the extension reads existing Caplets config and does not create or mutate Pi config files. diff --git a/.brv/context-tree/extracted/facts/pi_installation.md b/.brv/context-tree/extracted/facts/pi_installation.md deleted file mode 100644 index caacb31e..00000000 --- a/.brv/context-tree/extracted/facts/pi_installation.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Pi installation -summary: Facts about Pi installation -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T23:52:05.238Z' -updatedAt: '2026-05-27T23:52:05.238Z' ---- -## Reason -Curate extracted facts from context - -## Raw Concept -**Task:** -Extracted factual statements - -**Timestamp:** 2026-05-27T23:52:05.208Z - -## Narrative -### Structure -Collected facts grouped by subject - -## Facts -- **Pi installation**: The page shows "pi update && npm install -g @caplets/pi" and a config snippet with a top-level "caplets" object, but packages/pi/README.md documents installation as "pi install npm:@caplets/pi" and settings as "{ \"packages\": [\"npm:@caplets/pi\"] }". diff --git a/.brv/context-tree/extracted/facts/pi_setup_snippet.md b/.brv/context-tree/extracted/facts/pi_setup_snippet.md deleted file mode 100644 index ce8bc7ca..00000000 --- a/.brv/context-tree/extracted/facts/pi_setup_snippet.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Pi setup snippet -summary: Facts about Pi setup snippet -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T23:52:05.229Z' -updatedAt: '2026-05-27T23:52:05.229Z' ---- -## Reason -Curate extracted facts from context - -## Raw Concept -**Task:** -Extracted factual statements - -**Timestamp:** 2026-05-27T23:52:05.208Z - -## Narrative -### Structure -Collected facts grouped by subject - -## Facts -- **Pi setup snippet**: The Pi setup snippet is product misinformation. diff --git a/.brv/context-tree/extracted/facts/pill_tab_semantics.md b/.brv/context-tree/extracted/facts/pill_tab_semantics.md deleted file mode 100644 index 9c584e61..00000000 --- a/.brv/context-tree/extracted/facts/pill_tab_semantics.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -title: pill tab semantics -summary: Facts about pill tab semantics -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T23:48:05.191Z' -updatedAt: '2026-05-27T23:48:05.191Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Document factual statements about pill tab semantics - -**Timestamp:** 2026-05-27T23:48:05.172Z - -## Facts -- **pill tab semantics**: Pills use tab semantics with `role="tablist"`, `role="tab"`, `aria-selected`, `aria-controls`, `role="tabpanel"`, and `aria-labelledby` (`apps/landing/src/pages/index.astro:265-289`). diff --git a/.brv/context-tree/extracted/facts/pin.md b/.brv/context-tree/extracted/facts/pin.md deleted file mode 100644 index f01dbf92..00000000 --- a/.brv/context-tree/extracted/facts/pin.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Pin -summary: Facts about Pin -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T13:18:21.499Z' -updatedAt: '2026-05-28T13:18:21.499Z' ---- -## Reason -Curated extracted facts from source context - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-28T13:18:21.328Z - -## Narrative -### Highlights -Contains 1 facts for Pin - -## Facts -- **Pin**: Pin creates a standalone shortcut so {{command_prefix}} invokes {{command_prefix}}impeccable directly diff --git a/.brv/context-tree/extracted/facts/pin_unpin.md b/.brv/context-tree/extracted/facts/pin_unpin.md deleted file mode 100644 index fea8a664..00000000 --- a/.brv/context-tree/extracted/facts/pin_unpin.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Pin/unpin -summary: Facts about pin/unpin -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T13:18:21.498Z' -updatedAt: '2026-05-28T13:18:21.498Z' ---- -## Reason -Curated extracted facts from source context - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-28T13:18:21.328Z - -## Narrative -### Highlights -Contains 1 facts for pin/unpin - -## Facts -- **pin/unpin**: `pin ` and `unpin ` are management commands for pinning and unpinning commands. diff --git a/.brv/context-tree/extracted/facts/polish.md b/.brv/context-tree/extracted/facts/polish.md deleted file mode 100644 index eac6f883..00000000 --- a/.brv/context-tree/extracted/facts/polish.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Polish -summary: Facts about polish -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T13:18:21.475Z' -updatedAt: '2026-05-28T13:18:21.475Z' ---- -## Reason -Curated extracted facts from source context - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-28T13:18:21.328Z - -## Narrative -### Highlights -Contains 1 facts for polish - -## Facts -- **polish**: `polish [target]` is a Refine command that provides a final quality pass before shipping. diff --git a/.brv/context-tree/extracted/facts/product_details.md b/.brv/context-tree/extracted/facts/product_details.md deleted file mode 100644 index 41df77d3..00000000 --- a/.brv/context-tree/extracted/facts/product_details.md +++ /dev/null @@ -1,22 +0,0 @@ ---- -title: Product details -summary: Facts about Product details -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T09:51:10.700Z' -updatedAt: '2026-05-28T09:51:10.700Z' ---- -## Reason -Curated extracted factual statements - -## Raw Concept -**Task:** -Document factual statements - -## Narrative -### Highlights -Extracted 1 facts for Product details - -## Facts -- **Product details**: Strengths include clear product specifics such as MCP, OpenAPI, GraphQL, CLI, Claude Code, Codex, OpenCode, Pi diff --git a/.brv/context-tree/extracted/facts/product_md.md b/.brv/context-tree/extracted/facts/product_md.md deleted file mode 100644 index 1f03331b..00000000 --- a/.brv/context-tree/extracted/facts/product_md.md +++ /dev/null @@ -1,25 +0,0 @@ ---- -title: PRODUCT.md -summary: Facts about PRODUCT.md -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T13:02:34.218Z' -updatedAt: '2026-05-28T13:18:21.335Z' ---- -## Reason -Curated extracted facts from source context - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-28T13:18:21.328Z - -## Narrative -### Highlights -Contains 2 facts for PRODUCT.md - -## Facts -- **PRODUCT.md**: PRODUCT.md is required. -- **PRODUCT.md**: If PRODUCT.md is missing, empty, or placeholder (<200 chars), run `impeccable teach` and then resume the user's original task. diff --git a/.brv/context-tree/extracted/facts/product_specificity.md b/.brv/context-tree/extracted/facts/product_specificity.md deleted file mode 100644 index 3cac8b81..00000000 --- a/.brv/context-tree/extracted/facts/product_specificity.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -title: Product specificity -summary: Facts about product specificity -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T09:45:40.403Z' -updatedAt: '2026-05-28T09:45:40.403Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Document extracted factual statements - -**Timestamp:** 2026-05-28T09:45:40.335Z - -## Facts -- **product specificity**: Product specificity is good. diff --git a/.brv/context-tree/extracted/facts/punctuation.md b/.brv/context-tree/extracted/facts/punctuation.md deleted file mode 100644 index fd2701dd..00000000 --- a/.brv/context-tree/extracted/facts/punctuation.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Punctuation -summary: Facts about punctuation -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T13:02:34.246Z' -updatedAt: '2026-05-28T13:18:21.462Z' ---- -## Reason -Curated extracted facts from source context - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-28T13:18:21.328Z - -## Narrative -### Highlights -Contains 1 facts for punctuation - -## Facts -- **punctuation**: No em dashes. Use commas, colons, semicolons, periods, or parentheses. Also not --. diff --git a/.brv/context-tree/extracted/facts/quieter.md b/.brv/context-tree/extracted/facts/quieter.md deleted file mode 100644 index 82901d2b..00000000 --- a/.brv/context-tree/extracted/facts/quieter.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Quieter -summary: Facts about quieter -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T13:18:21.478Z' -updatedAt: '2026-05-28T13:18:21.478Z' ---- -## Reason -Curated extracted facts from source context - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-28T13:18:21.328Z - -## Narrative -### Highlights -Contains 1 facts for quieter - -## Facts -- **quieter**: `quieter [target]` is a Refine command that tones down aggressive or overstimulating designs. diff --git a/.brv/context-tree/extracted/facts/read_the_repo_wording.md b/.brv/context-tree/extracted/facts/read_the_repo_wording.md deleted file mode 100644 index 60686d98..00000000 --- a/.brv/context-tree/extracted/facts/read_the_repo_wording.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -title: '`Read the repo` wording' -summary: Facts about `Read the repo` wording -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T09:45:40.415Z' -updatedAt: '2026-05-28T09:45:40.415Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Document extracted factual statements - -**Timestamp:** 2026-05-28T09:45:40.335Z - -## Facts -- **`Read the repo` wording**: `Read the repo` is more developer-authentic than “Learn more.” diff --git a/.brv/context-tree/extracted/facts/recognition_rather_than_recall.md b/.brv/context-tree/extracted/facts/recognition_rather_than_recall.md deleted file mode 100644 index 109a91f8..00000000 --- a/.brv/context-tree/extracted/facts/recognition_rather_than_recall.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -title: Recognition rather than recall -summary: Facts about Recognition rather than recall -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T09:45:40.359Z' -updatedAt: '2026-05-28T09:45:40.359Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Document extracted factual statements - -**Timestamp:** 2026-05-28T09:45:40.334Z - -## Facts -- **Recognition rather than recall**: Recognition rather than recall score is 3. diff --git a/.brv/context-tree/extracted/facts/restrained_strategy.md b/.brv/context-tree/extracted/facts/restrained_strategy.md deleted file mode 100644 index d1f2bd0b..00000000 --- a/.brv/context-tree/extracted/facts/restrained_strategy.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Restrained strategy -summary: Facts about Restrained strategy -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T13:18:21.385Z' -updatedAt: '2026-05-28T13:18:21.385Z' ---- -## Reason -Curated extracted facts from source context - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-28T13:18:21.328Z - -## Narrative -### Highlights -Contains 1 facts for Restrained strategy - -## Facts -- **Restrained strategy**: Restrained: tinted neutrals + one accent ≤10%. diff --git a/.brv/context-tree/extracted/facts/reveal_functionality.md b/.brv/context-tree/extracted/facts/reveal_functionality.md deleted file mode 100644 index b3ef171c..00000000 --- a/.brv/context-tree/extracted/facts/reveal_functionality.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -title: reveal functionality -summary: Facts about reveal functionality -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T23:48:05.208Z' -updatedAt: '2026-05-27T23:48:05.208Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Document factual statements about reveal functionality - -**Timestamp:** 2026-05-27T23:48:05.172Z - -## Facts -- **reveal functionality**: Adding `is-visible` to setup panels has no styling effect, so the reveal selector update is functionally incomplete against the plan/spec requirement. diff --git a/.brv/context-tree/extracted/facts/reveal_selector_implementation.md b/.brv/context-tree/extracted/facts/reveal_selector_implementation.md deleted file mode 100644 index c7bb4b38..00000000 --- a/.brv/context-tree/extracted/facts/reveal_selector_implementation.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -title: reveal selector implementation -summary: Facts about reveal selector implementation -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T23:48:05.206Z' -updatedAt: '2026-05-27T23:48:05.206Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Document factual statements about reveal selector implementation - -**Timestamp:** 2026-05-27T23:48:05.172Z - -## Facts -- **reveal selector implementation**: Reveal selector update is incomplete: the script now observes `.agent-setup-panel` (`apps/landing/src/pages/index.astro:418-419`), but CSS reveal rules do not include `.agent-setup-panel`, only `.integration-pill` and other existing targets (`apps/landing/src/styles/global.css:690-705`). diff --git a/.brv/context-tree/extracted/facts/review_scope.md b/.brv/context-tree/extracted/facts/review_scope.md deleted file mode 100644 index b4c48bbc..00000000 --- a/.brv/context-tree/extracted/facts/review_scope.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: review scope -summary: Facts about review scope -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T23:52:05.241Z' -updatedAt: '2026-05-27T23:52:05.241Z' ---- -## Reason -Curate extracted facts from context - -## Raw Concept -**Task:** -Extracted factual statements - -**Timestamp:** 2026-05-27T23:52:05.208Z - -## Narrative -### Structure -Collected facts grouped by subject - -## Facts -- **review scope**: The review was performed on committed changes only via git diff 0e56e8f^..86f9679; unrelated unstaged/untracked worktree changes were ignored. diff --git a/.brv/context-tree/extracted/facts/riley.md b/.brv/context-tree/extracted/facts/riley.md deleted file mode 100644 index cf11b510..00000000 --- a/.brv/context-tree/extracted/facts/riley.md +++ /dev/null @@ -1,22 +0,0 @@ ---- -title: Riley -summary: Facts about Riley -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T09:51:10.754Z' -updatedAt: '2026-05-28T09:51:10.754Z' ---- -## Reason -Curated extracted factual statements - -## Raw Concept -**Task:** -Document factual statements - -## Narrative -### Highlights -Extracted 1 facts for Riley - -## Facts -- **Riley**: Persona Riley (Stress Tester) finds safety claims unbacked and install command inconsistency erodes trust diff --git a/.brv/context-tree/extracted/facts/safety_claims.md b/.brv/context-tree/extracted/facts/safety_claims.md deleted file mode 100644 index 07e448ef..00000000 --- a/.brv/context-tree/extracted/facts/safety_claims.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -title: Safety claims -summary: Facts about safety claims -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T09:45:40.400Z' -updatedAt: '2026-05-28T09:45:40.400Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Document extracted factual statements - -**Timestamp:** 2026-05-28T09:45:40.335Z - -## Facts -- **safety claims**: Safety claims lack proof detail. diff --git a/.brv/context-tree/extracted/facts/script.md b/.brv/context-tree/extracted/facts/script.md deleted file mode 100644 index 2a40159a..00000000 --- a/.brv/context-tree/extracted/facts/script.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Script -summary: Facts about Script -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T13:18:21.502Z' -updatedAt: '2026-05-28T13:18:21.502Z' ---- -## Reason -Curated extracted facts from source context - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-28T13:18:21.328Z - -## Narrative -### Highlights -Contains 1 facts for Script - -## Facts -- **Script**: The script writes to every harness directory present in the project diff --git a/.brv/context-tree/extracted/facts/setup.md b/.brv/context-tree/extracted/facts/setup.md deleted file mode 100644 index 993916b0..00000000 --- a/.brv/context-tree/extracted/facts/setup.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Setup -summary: Facts about setup -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T13:02:34.216Z' -updatedAt: '2026-05-28T13:02:34.216Z' ---- -## Reason -Curated extracted facts from provided context - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-28T13:02:34.210Z - -## Narrative -### Highlights -Extracted 1 facts for setup - -## Facts -- **setup**: Before any design work, the user must load context (PRODUCT.md / DESIGN.md) via the loader script. diff --git a/.brv/context-tree/extracted/facts/setup_panel.md b/.brv/context-tree/extracted/facts/setup_panel.md deleted file mode 100644 index 3c779e43..00000000 --- a/.brv/context-tree/extracted/facts/setup_panel.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: setup_panel -summary: Facts about setup_panel -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T23:50:44.580Z' -updatedAt: '2026-05-27T23:50:44.580Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-27T23:50:44.568Z - -## Narrative -### Highlights -Extracted 1 facts for setup_panel - -## Facts -- **setup_panel**: Setup panel/code styling and responsive stacking are present at `apps/landing/src/styles/global.css:940-1001` and `apps/landing/src/styles/global.css:1211-1217`. diff --git a/.brv/context-tree/extracted/facts/setup_panels.md b/.brv/context-tree/extracted/facts/setup_panels.md deleted file mode 100644 index bded1870..00000000 --- a/.brv/context-tree/extracted/facts/setup_panels.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: setup panels -summary: Facts about setup panels -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T23:52:05.213Z' -updatedAt: '2026-05-27T23:52:05.213Z' ---- -## Reason -Curate extracted facts from context - -## Raw Concept -**Task:** -Extracted factual statements - -**Timestamp:** 2026-05-27T23:52:05.208Z - -## Narrative -### Structure -Collected facts grouped by subject - -## Facts -- **setup panels**: CSS is focused and responsive; setup panels stack on narrow screens at apps/landing/src/styles/global.css:979-1043 and :1211-1213. diff --git a/.brv/context-tree/extracted/facts/shape.md b/.brv/context-tree/extracted/facts/shape.md deleted file mode 100644 index cf9e9805..00000000 --- a/.brv/context-tree/extracted/facts/shape.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Shape -summary: Facts about shape -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T13:18:21.466Z' -updatedAt: '2026-05-28T13:18:21.466Z' ---- -## Reason -Curated extracted facts from source context - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-28T13:18:21.328Z - -## Narrative -### Highlights -Contains 1 facts for shape - -## Facts -- **shape**: `shape [feature]` is a Build command that plans UX/UI before writing code. diff --git a/.brv/context-tree/extracted/facts/side_stripe_borders.md b/.brv/context-tree/extracted/facts/side_stripe_borders.md deleted file mode 100644 index abd08ef1..00000000 --- a/.brv/context-tree/extracted/facts/side_stripe_borders.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Side‑stripe borders -summary: Facts about side‑stripe borders -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T13:18:21.442Z' -updatedAt: '2026-05-28T13:18:21.442Z' ---- -## Reason -Curated extracted facts from source context - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-28T13:18:21.328Z - -## Narrative -### Highlights -Contains 1 facts for side‑stripe borders - -## Facts -- **side‑stripe borders**: Side‑stripe borders greater than 1px as a colored accent are never intentional. diff --git a/.brv/context-tree/extracted/facts/site_animation_code.md b/.brv/context-tree/extracted/facts/site_animation_code.md deleted file mode 100644 index 22bddd99..00000000 --- a/.brv/context-tree/extracted/facts/site_animation_code.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -title: Site animation code -summary: Facts about site animation code -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T09:45:40.427Z' -updatedAt: '2026-05-28T09:45:40.427Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Document extracted factual statements - -**Timestamp:** 2026-05-28T09:45:40.335Z - -## Facts -- **site animation code**: The site has a lot of animation code for a product whose brand asks for quiet confidence. diff --git a/.brv/context-tree/extracted/facts/spacing.md b/.brv/context-tree/extracted/facts/spacing.md deleted file mode 100644 index 59e08314..00000000 --- a/.brv/context-tree/extracted/facts/spacing.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Spacing -summary: Facts about spacing -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T13:18:21.416Z' -updatedAt: '2026-05-28T13:18:21.416Z' ---- -## Reason -Curated extracted facts from source context - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-28T13:18:21.328Z - -## Narrative -### Highlights -Contains 1 facts for spacing - -## Facts -- **spacing**: Vary spacing for rhythm. diff --git a/.brv/context-tree/extracted/facts/status_dot.md b/.brv/context-tree/extracted/facts/status_dot.md deleted file mode 100644 index 60416c8c..00000000 --- a/.brv/context-tree/extracted/facts/status_dot.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -title: '`status-dot`' -summary: Facts about `status-dot` -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T09:45:40.425Z' -updatedAt: '2026-05-28T09:45:40.425Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Document extracted factual statements - -**Timestamp:** 2026-05-28T09:45:40.335Z - -## Facts -- **`status-dot`**: `status-dot` is color-only and decorative unless paired with meaningful text. diff --git a/.brv/context-tree/extracted/facts/styling_alignment.md b/.brv/context-tree/extracted/facts/styling_alignment.md deleted file mode 100644 index 2747b11e..00000000 --- a/.brv/context-tree/extracted/facts/styling_alignment.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -title: styling alignment -summary: Facts about styling alignment -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T23:48:05.195Z' -updatedAt: '2026-05-27T23:48:05.195Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Document factual statements about styling alignment - -**Timestamp:** 2026-05-27T23:48:05.172Z - -## Facts -- **styling alignment**: Styling is restrained and product-aligned: dark integration card retained, selected pill state is clear, and command/config blocks use monospace (`apps/landing/src/styles/global.css:888-1011`). diff --git a/.brv/context-tree/extracted/facts/tab_implementation.md b/.brv/context-tree/extracted/facts/tab_implementation.md deleted file mode 100644 index a78568ef..00000000 --- a/.brv/context-tree/extracted/facts/tab_implementation.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -title: Tab implementation -summary: Facts about tab implementation -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T09:45:40.409Z' -updatedAt: '2026-05-28T09:45:40.409Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Document extracted factual statements - -**Timestamp:** 2026-05-28T09:45:40.335Z - -## Facts -- **tab implementation**: The tab implementation is stronger than typical landing-page tabs. diff --git a/.brv/context-tree/extracted/facts/tab_keyboard_navigation.md b/.brv/context-tree/extracted/facts/tab_keyboard_navigation.md deleted file mode 100644 index d06db152..00000000 --- a/.brv/context-tree/extracted/facts/tab_keyboard_navigation.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -title: tab keyboard navigation -summary: Facts about tab keyboard navigation -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T23:48:05.192Z' -updatedAt: '2026-05-27T23:48:05.192Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Document factual statements about tab keyboard navigation - -**Timestamp:** 2026-05-27T23:48:05.172Z - -## Facts -- **tab keyboard navigation**: Tab switching supports click and ArrowLeft/ArrowRight/Home/End keyboard navigation (`apps/landing/src/pages/index.astro:343-388`). diff --git a/.brv/context-tree/extracted/facts/tab_markup.md b/.brv/context-tree/extracted/facts/tab_markup.md deleted file mode 100644 index 602017c5..00000000 --- a/.brv/context-tree/extracted/facts/tab_markup.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: tab markup -summary: Facts about tab markup -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T23:52:05.210Z' -updatedAt: '2026-05-27T23:52:05.210Z' ---- -## Reason -Curate extracted facts from context - -## Raw Concept -**Task:** -Extracted factual statements - -**Timestamp:** 2026-05-27T23:52:05.208Z - -## Narrative -### Structure -Collected facts grouped by subject - -## Facts -- **tab markup**: The tab markup is broadly accessible with role="tablist", role="tab", aria-selected, aria-controls, role="tabpanel", and keyboard navigation implemented in apps/landing/src/pages/index.astro:268-305 and :342-390. diff --git a/.brv/context-tree/extracted/facts/tab_semantics.md b/.brv/context-tree/extracted/facts/tab_semantics.md deleted file mode 100644 index 5a69a26a..00000000 --- a/.brv/context-tree/extracted/facts/tab_semantics.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: tab_semantics -summary: Facts about tab_semantics -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T23:50:44.572Z' -updatedAt: '2026-05-27T23:50:44.572Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-27T23:50:44.568Z - -## Narrative -### Highlights -Extracted 1 facts for tab_semantics - -## Facts -- **tab_semantics**: Required tab semantics are present: `role="tablist"` at `apps/landing/src/pages/index.astro:266`, tab buttons at `:268-278`, and tab panels at `:283-289`. diff --git a/.brv/context-tree/extracted/facts/tab_switching.md b/.brv/context-tree/extracted/facts/tab_switching.md deleted file mode 100644 index 067fd72c..00000000 --- a/.brv/context-tree/extracted/facts/tab_switching.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: tab_switching -summary: Facts about tab_switching -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T23:50:44.576Z' -updatedAt: '2026-05-27T23:50:44.576Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-27T23:50:44.568Z - -## Narrative -### Highlights -Extracted 1 facts for tab_switching - -## Facts -- **tab_switching**: Client-side tab switching updates `aria-selected`, `tabIndex`, and panel `hidden` state at `apps/landing/src/pages/index.astro:343-352`, with click and keyboard handling at `:358-386`. diff --git a/.brv/context-tree/extracted/facts/teach.md b/.brv/context-tree/extracted/facts/teach.md deleted file mode 100644 index cc4c111e..00000000 --- a/.brv/context-tree/extracted/facts/teach.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Teach -summary: Facts about teach -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T13:18:21.467Z' -updatedAt: '2026-05-28T13:18:21.467Z' ---- -## Reason -Curated extracted facts from source context - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-28T13:18:21.328Z - -## Narrative -### Highlights -Contains 1 facts for teach - -## Facts -- **teach**: `teach` is a Build command that sets up PRODUCT.md and DESIGN.md context. diff --git a/.brv/context-tree/extracted/facts/theme_selection.md b/.brv/context-tree/extracted/facts/theme_selection.md deleted file mode 100644 index 69f8cd36..00000000 --- a/.brv/context-tree/extracted/facts/theme_selection.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Theme selection -summary: Facts about theme selection -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T13:02:34.238Z' -updatedAt: '2026-05-28T13:18:21.391Z' ---- -## Reason -Curated extracted facts from source context - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-28T13:18:21.328Z - -## Narrative -### Highlights -Contains 1 facts for theme selection - -## Facts -- **theme selection**: Dark vs. light is never a default. diff --git a/.brv/context-tree/extracted/facts/typecheck.md b/.brv/context-tree/extracted/facts/typecheck.md deleted file mode 100644 index 56a2474d..00000000 --- a/.brv/context-tree/extracted/facts/typecheck.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Typecheck -summary: Facts about typecheck -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T13:02:34.262Z' -updatedAt: '2026-05-28T13:18:21.520Z' ---- -## Reason -Curated extracted facts from source context - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-28T13:18:21.328Z - -## Narrative -### Highlights -Contains 1 facts for typecheck - -## Facts -- **typecheck**: Build verification passed for pnpm --filter @caplets/landing typecheck diff --git a/.brv/context-tree/extracted/facts/typeset.md b/.brv/context-tree/extracted/facts/typeset.md deleted file mode 100644 index 2dddb106..00000000 --- a/.brv/context-tree/extracted/facts/typeset.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Typeset -summary: Facts about typeset -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T13:18:21.486Z' -updatedAt: '2026-05-28T13:18:21.486Z' ---- -## Reason -Curated extracted facts from source context - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-28T13:18:21.328Z - -## Narrative -### Highlights -Contains 1 facts for typeset - -## Facts -- **typeset**: `typeset [target]` is an Enhance command that improves typography hierarchy and fonts. diff --git a/.brv/context-tree/extracted/facts/typographic_hierarchy.md b/.brv/context-tree/extracted/facts/typographic_hierarchy.md deleted file mode 100644 index dd24b4d7..00000000 --- a/.brv/context-tree/extracted/facts/typographic_hierarchy.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Typographic hierarchy -summary: Facts about typographic hierarchy -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T13:18:21.413Z' -updatedAt: '2026-05-28T13:18:21.413Z' ---- -## Reason -Curated extracted facts from source context - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-28T13:18:21.328Z - -## Narrative -### Highlights -Contains 1 facts for typographic hierarchy - -## Facts -- **typographic hierarchy**: Hierarchy through scale + weight contrast (≥1.25 ratio between steps). diff --git a/.brv/context-tree/extracted/facts/unpin.md b/.brv/context-tree/extracted/facts/unpin.md deleted file mode 100644 index 98deb457..00000000 --- a/.brv/context-tree/extracted/facts/unpin.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Unpin -summary: Facts about Unpin -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T13:18:21.501Z' -updatedAt: '2026-05-28T13:18:21.501Z' ---- -## Reason -Curated extracted facts from source context - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-28T13:18:21.328Z - -## Narrative -### Highlights -Contains 1 facts for Unpin - -## Facts -- **Unpin**: Unpin removes the shortcut created by Pin diff --git a/.brv/context-tree/extracted/facts/user_control_and_freedom.md b/.brv/context-tree/extracted/facts/user_control_and_freedom.md deleted file mode 100644 index 5ae9f143..00000000 --- a/.brv/context-tree/extracted/facts/user_control_and_freedom.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -title: User control and freedom -summary: Facts about User control and freedom -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T09:45:40.350Z' -updatedAt: '2026-05-28T09:45:40.350Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Document extracted factual statements - -**Timestamp:** 2026-05-28T09:45:40.334Z - -## Facts -- **User control and freedom**: User control and freedom score is 2. diff --git a/.brv/context-tree/extracted/facts/user_control_freedom.md b/.brv/context-tree/extracted/facts/user_control_freedom.md deleted file mode 100644 index 318d9196..00000000 --- a/.brv/context-tree/extracted/facts/user_control_freedom.md +++ /dev/null @@ -1,22 +0,0 @@ ---- -title: User Control & Freedom -summary: Facts about User Control & Freedom -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T09:51:10.729Z' -updatedAt: '2026-05-28T09:51:10.729Z' ---- -## Reason -Curated extracted factual statements - -## Raw Concept -**Task:** -Document factual statements - -## Narrative -### Highlights -Extracted 1 facts for User Control & Freedom - -## Facts -- **User Control & Freedom**: User Control & Freedom scored 2 because there are no copy buttons for code snippets diff --git a/.brv/context-tree/extracted/facts/valid_command.md b/.brv/context-tree/extracted/facts/valid_command.md deleted file mode 100644 index 4cf6abdb..00000000 --- a/.brv/context-tree/extracted/facts/valid_command.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Valid command -summary: Facts about Valid command -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T13:18:21.505Z' -updatedAt: '2026-05-28T13:18:21.505Z' ---- -## Reason -Curated extracted facts from source context - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-28T13:18:21.328Z - -## Narrative -### Highlights -Contains 1 facts for Valid command - -## Facts -- **Valid command**: Valid is any command from the table above diff --git a/.brv/context-tree/extracted/facts/visibility_of_system_status.md b/.brv/context-tree/extracted/facts/visibility_of_system_status.md deleted file mode 100644 index eba64836..00000000 --- a/.brv/context-tree/extracted/facts/visibility_of_system_status.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Visibility of System Status -summary: Facts about Visibility of System Status -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T09:45:40.341Z' -updatedAt: '2026-05-28T09:51:10.717Z' ---- -## Reason -Curated extracted factual statements - -## Raw Concept -**Task:** -Document factual statements - -**Timestamp:** 2026-05-28T09:45:40.334Z - -## Narrative -### Highlights -Extracted 1 facts for Visibility of System Status - -## Facts -- **Visibility of System Status**: Visibility of System Status scored 3 due to decorative cues not functional diff --git a/.brv/context-tree/extracted/facts/visuals.md b/.brv/context-tree/extracted/facts/visuals.md deleted file mode 100644 index fc42bf34..00000000 --- a/.brv/context-tree/extracted/facts/visuals.md +++ /dev/null @@ -1,22 +0,0 @@ ---- -title: Visuals -summary: Facts about Visuals -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T09:51:10.699Z' -updatedAt: '2026-05-28T09:51:10.699Z' ---- -## Reason -Curated extracted factual statements - -## Raw Concept -**Task:** -Document factual statements - -## Narrative -### Highlights -Extracted 1 facts for Visuals - -## Facts -- **Visuals**: The visual metaphor uses map, route animations, glow, and dark panels diff --git a/.brv/context-tree/extracted/facts/writing_guidelines.md b/.brv/context-tree/extracted/facts/writing_guidelines.md deleted file mode 100644 index 64e73a27..00000000 --- a/.brv/context-tree/extracted/facts/writing_guidelines.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Writing guidelines -summary: Facts about writing guidelines -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T13:18:21.460Z' -updatedAt: '2026-05-28T13:18:21.460Z' ---- -## Reason -Curated extracted facts from source context - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-28T13:18:21.328Z - -## Narrative -### Highlights -Contains 1 facts for writing guidelines - -## Facts -- **writing guidelines**: Every word earns its place; no restated headings, no intros that repeat the title. diff --git a/.brv/context-tree/extracted/hero_proof/hero_proof_preference.md b/.brv/context-tree/extracted/hero_proof/hero_proof_preference.md deleted file mode 100644 index 8400dcd4..00000000 --- a/.brv/context-tree/extracted/hero_proof/hero_proof_preference.md +++ /dev/null @@ -1,26 +0,0 @@ ---- -title: Hero Proof Preference -summary: User prefers realistic CLI trace as hero proof; assistant provides rationale -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T09:51:24.818Z' -updatedAt: '2026-05-28T09:51:24.818Z' ---- -## Reason -Curate extracted facts from user/assistant discussion about hero proof style - -## Raw Concept -**Task:** -Document hero proof preference - -**Timestamp:** 2026-05-28T09:51:24.818Z - -## Narrative -### Structure -Preference discussion and rationale - -## Facts -- **hero proof**: My recommended answer: real trace, lightly designed. -- **hero proof**: The hero should feel like a developer could squint and recognize the actual product model, not a marketing abstraction. -- **trace evidence**: The proposed trace demonstrates that source metadata exists, capabilities are scoped, schemas are inspectable before calling, auth is redacted, and structured output is preserved. diff --git a/.brv/context-tree/facts/ci/ci.md b/.brv/context-tree/facts/ci/ci.md deleted file mode 100644 index 1d35d635..00000000 --- a/.brv/context-tree/facts/ci/ci.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -title: CI -summary: Facts about CI -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T16:21:51.986Z' -updatedAt: '2026-05-28T16:21:51.986Z' ---- -## Reason -Curated factual statements extracted from PR review context - -## Raw Concept -**Task:** -Document facts for CI - -**Timestamp:** 2026-05-28T16:21:51.958Z - -## Facts -- **CI**: CI was still running when checked. diff --git a/.brv/context-tree/facts/conventions/agent_card.md b/.brv/context-tree/facts/conventions/agent_card.md deleted file mode 100644 index 1718a434..00000000 --- a/.brv/context-tree/facts/conventions/agent_card.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: agent card -summary: Facts about agent card -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T23:17:13.646Z' -updatedAt: '2026-05-28T09:02:34.793Z' ---- -## Reason -Curated factual statements from context - -## Raw Concept -**Task:** -Document factual statements - -**Timestamp:** 2026-05-28T09:02:34.738Z - -## Narrative -### Structure -Collection of agent card facts - -## Facts -- **agent card**: The agent card should have a fixed height so that when a different agent is selected, the card doesn't shift. diff --git a/.brv/context-tree/facts/conventions/agent_setup_panels.md b/.brv/context-tree/facts/conventions/agent_setup_panels.md deleted file mode 100644 index 5068ccd1..00000000 --- a/.brv/context-tree/facts/conventions/agent_setup_panels.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: agent setup panels -summary: Facts about agent setup panels -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T09:02:34.800Z' -updatedAt: '2026-05-28T09:02:34.800Z' ---- -## Reason -Curated factual statements from context - -## Raw Concept -**Task:** -Document factual statements - -**Timestamp:** 2026-05-28T09:02:34.738Z - -## Narrative -### Structure -Collection of agent setup panels facts - -## Facts -- **agent setup panels**: Adjusted the agent setup panels to a fixed responsive height so switching tabs no longer shifts the card/page layout. diff --git a/.brv/context-tree/facts/conventions/agent_setup_tab.md b/.brv/context-tree/facts/conventions/agent_setup_tab.md deleted file mode 100644 index 2b8c5cf9..00000000 --- a/.brv/context-tree/facts/conventions/agent_setup_tab.md +++ /dev/null @@ -1,26 +0,0 @@ ---- -title: Agent Setup Tab -summary: Facts about Agent setup tab -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T09:32:37.372Z' -updatedAt: '2026-05-28T09:32:37.372Z' ---- -## Reason -Curated facts extracted from context - -## Raw Concept -**Task:** -Document facts for Agent setup tab - -**Timestamp:** 2026-05-28T09:32:37.363Z - -**Author:** ByteRover Context Engineer - -## Narrative -### Highlights -Extracted 1 facts for Agent setup tab - -## Facts -- **Agent setup tab**: Agent setup tab logic and panels remain coherent in apps/landing/src/pages/index.astro:340-461, including click, keyboard navigation, and hidden panel toggling. diff --git a/.brv/context-tree/facts/conventions/assistant.md b/.brv/context-tree/facts/conventions/assistant.md deleted file mode 100644 index e9ae3e05..00000000 --- a/.brv/context-tree/facts/conventions/assistant.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -title: Assistant -summary: Facts about assistant -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T12:01:34.381Z' -updatedAt: '2026-05-27T12:01:34.381Z' ---- -## Reason -Curated from RLM extraction - -## Raw Concept -**Task:** -Document assistant facts - -**Timestamp:** 2026-05-27T12:01:34.378Z - -## Facts -- **assistant**: I’m using the writing-plans skill to create the implementation plan. diff --git a/.brv/context-tree/facts/conventions/assistant_fact.md b/.brv/context-tree/facts/conventions/assistant_fact.md deleted file mode 100644 index 6a3ce02f..00000000 --- a/.brv/context-tree/facts/conventions/assistant_fact.md +++ /dev/null @@ -1,26 +0,0 @@ ---- -title: assistant fact -summary: The assistant is using Subagent-Driven Development to implement all audit fixes continuously. -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T09:33:34.322Z' -updatedAt: '2026-05-28T09:33:34.322Z' ---- -## Reason -Curate extracted factual statement - -## Raw Concept -**Task:** -Document factual statement - -**Timestamp:** 2026-05-28T09:33:34.318Z - -**Author:** ByteRover Context Engineer - -## Narrative -### Highlights -The assistant is using Subagent-Driven Development to implement all audit fixes continuously. - -## Facts -- **assistant**: The assistant is using Subagent-Driven Development to implement all audit fixes continuously. diff --git a/.brv/context-tree/facts/conventions/audit_spec_compliance_fact.md b/.brv/context-tree/facts/conventions/audit_spec_compliance_fact.md deleted file mode 100644 index 650c4231..00000000 --- a/.brv/context-tree/facts/conventions/audit_spec_compliance_fact.md +++ /dev/null @@ -1,26 +0,0 @@ ---- -title: audit/spec compliance fact -summary: Audit/spec compliance was approved. -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T09:33:34.348Z' -updatedAt: '2026-05-28T09:33:34.348Z' ---- -## Reason -Curate extracted factual statement - -## Raw Concept -**Task:** -Document factual statement - -**Timestamp:** 2026-05-28T09:33:34.318Z - -**Author:** ByteRover Context Engineer - -## Narrative -### Highlights -Audit/spec compliance was approved. - -## Facts -- **audit/spec compliance**: Audit/spec compliance was approved. diff --git a/.brv/context-tree/facts/conventions/border_usage.md b/.brv/context-tree/facts/conventions/border_usage.md deleted file mode 100644 index 320bf93f..00000000 --- a/.brv/context-tree/facts/conventions/border_usage.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: border usage -summary: Facts about border usage -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T09:02:34.786Z' -updatedAt: '2026-05-28T09:02:34.786Z' ---- -## Reason -Curated factual statements from context - -## Raw Concept -**Task:** -Document factual statements - -**Timestamp:** 2026-05-28T09:02:34.738Z - -## Narrative -### Structure -Collection of border usage facts - -## Facts -- **border usage**: Side‑stripe borders greater than 1 px as colored accents on cards, list items, callouts, or alerts are absolutely banned. diff --git a/.brv/context-tree/facts/conventions/build.md b/.brv/context-tree/facts/conventions/build.md deleted file mode 100644 index 92c52de0..00000000 --- a/.brv/context-tree/facts/conventions/build.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: build -summary: Facts about build -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T09:02:34.805Z' -updatedAt: '2026-05-28T09:02:34.805Z' ---- -## Reason -Curated factual statements from context - -## Raw Concept -**Task:** -Document factual statements - -**Timestamp:** 2026-05-28T09:02:34.738Z - -## Narrative -### Structure -Collection of build facts - -## Facts -- **build**: Verification passed: `pnpm --filter @caplets/landing build`. diff --git a/.brv/context-tree/facts/conventions/build_fact.md b/.brv/context-tree/facts/conventions/build_fact.md deleted file mode 100644 index 78768e0e..00000000 --- a/.brv/context-tree/facts/conventions/build_fact.md +++ /dev/null @@ -1,26 +0,0 @@ ---- -title: build fact -summary: Landing-specific verification passed for pnpm --filter @caplets/landing build. -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T09:33:34.382Z' -updatedAt: '2026-05-28T09:33:34.382Z' ---- -## Reason -Curate extracted factual statement - -## Raw Concept -**Task:** -Document factual statement - -**Timestamp:** 2026-05-28T09:33:34.318Z - -**Author:** ByteRover Context Engineer - -## Narrative -### Highlights -Landing-specific verification passed for pnpm --filter @caplets/landing build. - -## Facts -- **build**: Landing-specific verification passed for pnpm --filter @caplets/landing build. diff --git a/.brv/context-tree/facts/conventions/caplets_facts.md b/.brv/context-tree/facts/conventions/caplets_facts.md deleted file mode 100644 index 39ab64c7..00000000 --- a/.brv/context-tree/facts/conventions/caplets_facts.md +++ /dev/null @@ -1,33 +0,0 @@ ---- -title: Caplets Facts -summary: Facts about Caplets -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T11:32:10.034Z' -updatedAt: '2026-05-27T11:32:10.034Z' ---- -## Reason -Curate extracted factual statements from context - -## Raw Concept -**Task:** -Document factual statements - -**Changes:** -- Added extracted facts - -**Flow:** -Extraction -> Deduplication -> Curation - -**Timestamp:** 2026-05-27T11:32:10.020Z - -## Narrative -### Structure -Facts grouped under Caplets - -### Highlights -For Caplets, the recommended default is `structuredContent` containing the full downstream result and `content` containing a compact but semantically useful preview. - -## Facts -- **Caplets**: For Caplets, the recommended default is `structuredContent` containing the full downstream result and `content` containing a compact but semantically useful preview. diff --git a/.brv/context-tree/facts/conventions/caplets_landing.md b/.brv/context-tree/facts/conventions/caplets_landing.md deleted file mode 100644 index de8b7a80..00000000 --- a/.brv/context-tree/facts/conventions/caplets_landing.md +++ /dev/null @@ -1,27 +0,0 @@ ---- -title: '@Caplets/Landing' -summary: Facts about @caplets/landing -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T09:32:37.374Z' -updatedAt: '2026-05-28T09:32:37.374Z' ---- -## Reason -Curated facts extracted from context - -## Raw Concept -**Task:** -Document facts for @caplets/landing - -**Timestamp:** 2026-05-28T09:32:37.363Z - -**Author:** ByteRover Context Engineer - -## Narrative -### Highlights -Extracted 2 facts for @caplets/landing - -## Facts -- **@caplets/landing**: pnpm --filter @caplets/landing typecheck passed with 0 errors. -- **@caplets/landing**: pnpm --filter @caplets/landing build passed. diff --git a/.brv/context-tree/facts/conventions/caplets_result_rendering.md b/.brv/context-tree/facts/conventions/caplets_result_rendering.md deleted file mode 100644 index 53184981..00000000 --- a/.brv/context-tree/facts/conventions/caplets_result_rendering.md +++ /dev/null @@ -1,22 +0,0 @@ ---- -title: Caplets result rendering -summary: Facts about Caplets result rendering -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T11:40:54.539Z' -updatedAt: '2026-05-27T11:40:54.539Z' ---- -## Reason -Curate extracted factual statements - -## Raw Concept -**Task:** -Document factual statements - -## Narrative -### Highlights -For Caplets‑generated HTTP/OpenAPI/GraphQL/CLI results, always use the new renderer. - -## Facts -- **Caplets result rendering**: For Caplets‑generated HTTP/OpenAPI/GraphQL/CLI results, always use the new renderer. diff --git a/.brv/context-tree/facts/conventions/client_compatibility_facts.md b/.brv/context-tree/facts/conventions/client_compatibility_facts.md deleted file mode 100644 index 4f7dc95d..00000000 --- a/.brv/context-tree/facts/conventions/client_compatibility_facts.md +++ /dev/null @@ -1,33 +0,0 @@ ---- -title: client compatibility Facts -summary: Facts about client compatibility -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T11:32:10.029Z' -updatedAt: '2026-05-27T11:32:10.029Z' ---- -## Reason -Curate extracted factual statements from context - -## Raw Concept -**Task:** -Document factual statements - -**Changes:** -- Added extracted facts - -**Flow:** -Extraction -> Deduplication -> Curation - -**Timestamp:** 2026-05-27T11:32:10.020Z - -## Narrative -### Structure -Facts grouped under client compatibility - -### Highlights -Do not rely on `structuredContent` alone if broad client compatibility matters. - -## Facts -- **client compatibility**: Do not rely on `structuredContent` alone if broad client compatibility matters. diff --git a/.brv/context-tree/facts/conventions/code_quality_fact.md b/.brv/context-tree/facts/conventions/code_quality_fact.md deleted file mode 100644 index 47c6d399..00000000 --- a/.brv/context-tree/facts/conventions/code_quality_fact.md +++ /dev/null @@ -1,26 +0,0 @@ ---- -title: code quality fact -summary: Code quality was approved. -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T09:33:34.358Z' -updatedAt: '2026-05-28T09:33:34.358Z' ---- -## Reason -Curate extracted factual statement - -## Raw Concept -**Task:** -Document factual statement - -**Timestamp:** 2026-05-28T09:33:34.318Z - -**Author:** ByteRover Context Engineer - -## Narrative -### Highlights -Code quality was approved. - -## Facts -- **code quality**: Code quality was approved. diff --git a/.brv/context-tree/facts/conventions/color_palette.md b/.brv/context-tree/facts/conventions/color_palette.md deleted file mode 100644 index 8dc7b187..00000000 --- a/.brv/context-tree/facts/conventions/color_palette.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: color palette -summary: Facts about color palette -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T09:02:34.780Z' -updatedAt: '2026-05-28T09:02:34.780Z' ---- -## Reason -Curated factual statements from context - -## Raw Concept -**Task:** -Document factual statements - -**Timestamp:** 2026-05-28T09:02:34.738Z - -## Narrative -### Structure -Collection of color palette facts - -## Facts -- **color palette**: Color values must be expressed in OKLCH; pure black (#000) and pure white (#fff) are never allowed. diff --git a/.brv/context-tree/facts/conventions/color_strategy.md b/.brv/context-tree/facts/conventions/color_strategy.md deleted file mode 100644 index 4f2b1f64..00000000 --- a/.brv/context-tree/facts/conventions/color_strategy.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: color strategy -summary: Facts about color strategy -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T09:02:34.781Z' -updatedAt: '2026-05-28T09:02:34.781Z' ---- -## Reason -Curated factual statements from context - -## Raw Concept -**Task:** -Document factual statements - -**Timestamp:** 2026-05-28T09:02:34.738Z - -## Narrative -### Structure -Collection of color strategy facts - -## Facts -- **color strategy**: The "one accent ≤10%" rule applies only to the Restrained color strategy. diff --git a/.brv/context-tree/facts/conventions/commit_2acfcef_fact.md b/.brv/context-tree/facts/conventions/commit_2acfcef_fact.md deleted file mode 100644 index e93926b7..00000000 --- a/.brv/context-tree/facts/conventions/commit_2acfcef_fact.md +++ /dev/null @@ -1,26 +0,0 @@ ---- -title: commit 2acfcef fact -summary: 'Commit 2acfcef fix(landing): align plugin install commands was created.' -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T09:33:34.343Z' -updatedAt: '2026-05-28T09:33:34.343Z' ---- -## Reason -Curate extracted factual statement - -## Raw Concept -**Task:** -Document factual statement - -**Timestamp:** 2026-05-28T09:33:34.318Z - -**Author:** ByteRover Context Engineer - -## Narrative -### Highlights -Commit 2acfcef fix(landing): align plugin install commands was created. - -## Facts -- **commit 2acfcef**: Commit 2acfcef fix(landing): align plugin install commands was created. diff --git a/.brv/context-tree/facts/conventions/commit_8646209_fact.md b/.brv/context-tree/facts/conventions/commit_8646209_fact.md deleted file mode 100644 index fcafa6f6..00000000 --- a/.brv/context-tree/facts/conventions/commit_8646209_fact.md +++ /dev/null @@ -1,26 +0,0 @@ ---- -title: commit 8646209 fact -summary: 'Commit 8646209 fix(landing): address audit findings was created.' -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T09:33:34.342Z' -updatedAt: '2026-05-28T09:33:34.342Z' ---- -## Reason -Curate extracted factual statement - -## Raw Concept -**Task:** -Document factual statement - -**Timestamp:** 2026-05-28T09:33:34.318Z - -**Author:** ByteRover Context Engineer - -## Narrative -### Highlights -Commit 8646209 fix(landing): address audit findings was created. - -## Facts -- **commit 8646209**: Commit 8646209 fix(landing): address audit findings was created. diff --git a/.brv/context-tree/facts/conventions/commit_d2b942a_fact.md b/.brv/context-tree/facts/conventions/commit_d2b942a_fact.md deleted file mode 100644 index 76803805..00000000 --- a/.brv/context-tree/facts/conventions/commit_d2b942a_fact.md +++ /dev/null @@ -1,26 +0,0 @@ ---- -title: commit d2b942a fact -summary: 'Commit d2b942a fix(landing): remove unused npm icon styles was created.' -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T09:33:34.345Z' -updatedAt: '2026-05-28T09:33:34.345Z' ---- -## Reason -Curate extracted factual statement - -## Raw Concept -**Task:** -Document factual statement - -**Timestamp:** 2026-05-28T09:33:34.318Z - -**Author:** ByteRover Context Engineer - -## Narrative -### Highlights -Commit d2b942a fix(landing): remove unused npm icon styles was created. - -## Facts -- **commit d2b942a**: Commit d2b942a fix(landing): remove unused npm icon styles was created. diff --git a/.brv/context-tree/facts/conventions/content_facts.md b/.brv/context-tree/facts/conventions/content_facts.md deleted file mode 100644 index 68fef46e..00000000 --- a/.brv/context-tree/facts/conventions/content_facts.md +++ /dev/null @@ -1,33 +0,0 @@ ---- -title: content Facts -summary: Facts about content -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T11:32:10.028Z' -updatedAt: '2026-05-27T11:32:10.028Z' ---- -## Reason -Curate extracted factual statements from context - -## Raw Concept -**Task:** -Document factual statements - -**Changes:** -- Added extracted facts - -**Flow:** -Extraction -> Deduplication -> Curation - -**Timestamp:** 2026-05-27T11:32:10.020Z - -## Narrative -### Structure -Facts grouped under content - -### Highlights -Put model-friendly text in `content`. - -## Facts -- **content**: Put model-friendly text in `content`. diff --git a/.brv/context-tree/facts/conventions/content_rendering.md b/.brv/context-tree/facts/conventions/content_rendering.md deleted file mode 100644 index c06e517e..00000000 --- a/.brv/context-tree/facts/conventions/content_rendering.md +++ /dev/null @@ -1,22 +0,0 @@ ---- -title: Content rendering -summary: Facts about content rendering -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T11:40:54.527Z' -updatedAt: '2026-05-27T11:40:54.528Z' ---- -## Reason -Curate extracted factual statements - -## Raw Concept -**Task:** -Document factual statements - -## Narrative -### Highlights -Render agent‑visible `content` as Markdown. - -## Facts -- **content rendering**: Render agent‑visible `content` as Markdown. diff --git a/.brv/context-tree/facts/conventions/context.md b/.brv/context-tree/facts/conventions/context.md deleted file mode 100644 index faeeff06..00000000 --- a/.brv/context-tree/facts/conventions/context.md +++ /dev/null @@ -1,15 +0,0 @@ ---- -related: [facts/project/context.md] ---- -# Topic: conventions - -## Overview - -Covers workflow conventions and guardrails for curated runtime knowledge ingestion. - -## Key Concepts - -- precomputed recon -- single-pass mode -- mapExtract timeout -- verification via applied file paths diff --git a/.brv/context-tree/facts/conventions/context_directory.md b/.brv/context-tree/facts/conventions/context_directory.md deleted file mode 100644 index b9d1496e..00000000 --- a/.brv/context-tree/facts/conventions/context_directory.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: context directory -summary: Facts about context directory -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T09:02:34.771Z' -updatedAt: '2026-05-28T09:02:34.771Z' ---- -## Reason -Curated factual statements from context - -## Raw Concept -**Task:** -Document factual statements - -**Timestamp:** 2026-05-28T09:02:34.738Z - -## Narrative -### Structure -Collection of context directory facts - -## Facts -- **context directory**: The environment variable IMPECCABLE_CONTEXT_DIR can override the context directory with an absolute or relative path. diff --git a/.brv/context-tree/facts/conventions/context_loader.md b/.brv/context-tree/facts/conventions/context_loader.md deleted file mode 100644 index a636659d..00000000 --- a/.brv/context-tree/facts/conventions/context_loader.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: context loader -summary: Facts about context loader -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T09:02:34.757Z' -updatedAt: '2026-05-28T09:02:34.757Z' ---- -## Reason -Curated factual statements from context - -## Raw Concept -**Task:** -Document factual statements - -**Timestamp:** 2026-05-28T09:02:34.738Z - -## Narrative -### Structure -Collection of context loader facts - -## Facts -- **context loader**: The loader script searches the project root, then falls back to .agents/context/ and docs/ if the root is clean. diff --git a/.brv/context-tree/facts/conventions/d2b942a.md b/.brv/context-tree/facts/conventions/d2b942a.md deleted file mode 100644 index dd800691..00000000 --- a/.brv/context-tree/facts/conventions/d2b942a.md +++ /dev/null @@ -1,26 +0,0 @@ ---- -title: D2b942a -summary: Facts about d2b942a -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T09:32:37.365Z' -updatedAt: '2026-05-28T09:32:37.365Z' ---- -## Reason -Curated facts extracted from context - -## Raw Concept -**Task:** -Document facts for d2b942a - -**Timestamp:** 2026-05-28T09:32:37.363Z - -**Author:** ByteRover Context Engineer - -## Narrative -### Highlights -Extracted 1 facts for d2b942a - -## Facts -- **d2b942a**: d2b942a removes the unused .npm-link CSS block from apps/landing/src/styles/global.css; git grep -n npm-link HEAD -- apps/landing returns no matches. diff --git a/.brv/context-tree/facts/conventions/data_retention.md b/.brv/context-tree/facts/conventions/data_retention.md deleted file mode 100644 index 7b92cdcd..00000000 --- a/.brv/context-tree/facts/conventions/data_retention.md +++ /dev/null @@ -1,22 +0,0 @@ ---- -title: Data retention -summary: Facts about data retention -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T11:40:54.525Z' -updatedAt: '2026-05-27T11:40:54.525Z' ---- -## Reason -Curate extracted factual statements - -## Raw Concept -**Task:** -Document factual statements - -## Narrative -### Highlights -Always keep full canonical data in `structuredContent`. - -## Facts -- **data retention**: Always keep full canonical data in `structuredContent`. diff --git a/.brv/context-tree/facts/conventions/design_registers.md b/.brv/context-tree/facts/conventions/design_registers.md deleted file mode 100644 index 4e1693e0..00000000 --- a/.brv/context-tree/facts/conventions/design_registers.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: design registers -summary: Facts about design registers -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T09:02:34.778Z' -updatedAt: '2026-05-28T09:02:34.778Z' ---- -## Reason -Curated factual statements from context - -## Raw Concept -**Task:** -Document factual statements - -**Timestamp:** 2026-05-28T09:02:34.738Z - -## Narrative -### Structure -Collection of design registers facts - -## Facts -- **design registers**: Every design task is classified as either brand (marketing, landing, campaign, long‑form content, portfolio) or product (app UI, admin, dashboard, tool). diff --git a/.brv/context-tree/facts/conventions/design_workflow.md b/.brv/context-tree/facts/conventions/design_workflow.md deleted file mode 100644 index cd498747..00000000 --- a/.brv/context-tree/facts/conventions/design_workflow.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: design workflow -summary: Facts about design workflow -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T09:02:34.750Z' -updatedAt: '2026-05-28T09:02:34.750Z' ---- -## Reason -Curated factual statements from context - -## Raw Concept -**Task:** -Document factual statements - -**Timestamp:** 2026-05-28T09:02:34.738Z - -## Narrative -### Structure -Collection of design workflow facts - -## Facts -- **design workflow**: Before any design work or file edits, the user must load context (PRODUCT.md / DESIGN.md) via the loader script. diff --git a/.brv/context-tree/facts/conventions/downstream_content_handling.md b/.brv/context-tree/facts/conventions/downstream_content_handling.md deleted file mode 100644 index 072cf729..00000000 --- a/.brv/context-tree/facts/conventions/downstream_content_handling.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Downstream content handling -summary: Facts about downstream content handling -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T11:40:54.536Z' -updatedAt: '2026-05-27T11:40:54.536Z' ---- -## Reason -Curate extracted factual statements - -## Raw Concept -**Task:** -Document factual statements - -## Narrative -### Highlights -If downstream `content` has meaningful text, image, or resource content, preserve it. -If downstream `content` is empty, generic, or compact‑only, synthesize Markdown from `structuredContent`. - -## Facts -- **downstream content handling**: If downstream `content` has meaningful text, image, or resource content, preserve it. -- **downstream content handling**: If downstream `content` is empty, generic, or compact‑only, synthesize Markdown from `structuredContent`. diff --git a/.brv/context-tree/facts/conventions/extracted_context.md b/.brv/context-tree/facts/conventions/extracted_context.md deleted file mode 100644 index 513ac8c3..00000000 --- a/.brv/context-tree/facts/conventions/extracted_context.md +++ /dev/null @@ -1,37 +0,0 @@ ---- -title: extracted_context -summary: Extracted factual statements from provided context -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T11:31:02.665Z' -updatedAt: '2026-05-27T11:31:02.665Z' ---- -## Reason -Curate extracted facts from RLM extraction - -## Raw Concept -**Task:** -Curate extracted factual statements from provided context - -**Flow:** -Extract -> Dedup -> Group -> Curate - -**Timestamp:** 2026-05-27T11:31:02.662Z - -## Narrative -### Structure -Facts organized by subject - -### Highlights -MCP client, structuredContent, content, Pi/OpenCode integration, MCP server, MCP content generation - -## Facts -- **MCP client**: For pure MCP clients, the full body is in the MCP response as `structuredContent`, but not necessarily in `content[0].text`. -- **structuredContent**: `structuredContent` contains the full parsed response body, including status, headers, and body JSON. -- **content**: `content` provides a human‑readable compact preview, e.g., "status 200; OK; body {\"vulns\":[...truncated...]}". -- **MCP client**: MCP clients that pass `structuredContent` to the model see the full body. -- **MCP client**: MCP clients that only pass `content` text to the model see only the compact/truncated preview, not the full body. -- **Pi/OpenCode integration**: Pi/OpenCode native integrations are now fixed to prefer `structuredContent`, so agents see the full body there. -- **MCP server**: The pure MCP server path already returns full structured data; the remaining risk is client‑side behavior. -- **MCP content generation**: To support content‑only MCP clients robustly, core MCP `content` generation would need to include full JSON or a `fullTextContent` mode, which would increase output noise. diff --git a/.brv/context-tree/facts/conventions/extracted_facts.md b/.brv/context-tree/facts/conventions/extracted_facts.md deleted file mode 100644 index 0f90c155..00000000 --- a/.brv/context-tree/facts/conventions/extracted_facts.md +++ /dev/null @@ -1,34 +0,0 @@ ---- -title: Extracted Facts -summary: Curated factual statements extracted from RLM context -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T11:28:54.890Z' -updatedAt: '2026-05-27T11:47:26.112Z' ---- -## Reason -Curate facts extracted from RLM context - -## Raw Concept -**Task:** -Curate extracted factual statements from provided context - -**Changes:** -- Added extracted facts - -**Flow:** -extract -> dedup -> upsert - -**Timestamp:** 2026-05-27T11:47:26.107Z - -## Narrative -### Structure -Extracted factual statements organized by subject - -### Highlights -user, assistant - -## Facts -- **user**: This looks good, create an implementation plan for this and put it in @docs/plans/ -- **assistant**: I’m using the writing-plans skill to create the implementation plan. diff --git a/.brv/context-tree/facts/conventions/fallback_rendering.md b/.brv/context-tree/facts/conventions/fallback_rendering.md deleted file mode 100644 index 50df3866..00000000 --- a/.brv/context-tree/facts/conventions/fallback_rendering.md +++ /dev/null @@ -1,22 +0,0 @@ ---- -title: Fallback rendering -summary: Facts about fallback rendering -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T11:40:54.532Z' -updatedAt: '2026-05-27T11:40:54.532Z' ---- -## Reason -Curate extracted factual statements - -## Raw Concept -**Task:** -Document factual statements - -## Narrative -### Highlights -Use a fenced pretty‑JSON fallback for unknown structured results. - -## Facts -- **fallback rendering**: Use a fenced pretty‑JSON fallback for unknown structured results. diff --git a/.brv/context-tree/facts/conventions/file.md b/.brv/context-tree/facts/conventions/file.md deleted file mode 100644 index 5d4d5d13..00000000 --- a/.brv/context-tree/facts/conventions/file.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -title: File -summary: Facts about file -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T12:01:34.388Z' -updatedAt: '2026-05-27T12:01:34.388Z' ---- -## Reason -Curated from RLM extraction - -## Raw Concept -**Task:** -Document file facts - -**Timestamp:** 2026-05-27T12:01:34.378Z - -## Facts -- **file**: Verified the file exists: 1718 lines. diff --git a/.brv/context-tree/facts/conventions/format_check.md b/.brv/context-tree/facts/conventions/format_check.md deleted file mode 100644 index 2e10ae59..00000000 --- a/.brv/context-tree/facts/conventions/format_check.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: format check -summary: Facts about format check -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T09:02:34.802Z' -updatedAt: '2026-05-28T09:02:34.802Z' ---- -## Reason -Curated factual statements from context - -## Raw Concept -**Task:** -Document factual statements - -**Timestamp:** 2026-05-28T09:02:34.738Z - -## Narrative -### Structure -Collection of format check facts - -## Facts -- **format check**: Verification passed: `pnpm format:check`. diff --git a/.brv/context-tree/facts/conventions/format_check_fact.md b/.brv/context-tree/facts/conventions/format_check_fact.md deleted file mode 100644 index 6a59e3f4..00000000 --- a/.brv/context-tree/facts/conventions/format_check_fact.md +++ /dev/null @@ -1,26 +0,0 @@ ---- -title: format check fact -summary: Landing-specific verification passed for pnpm exec oxfmt --check apps/landing/src/pages/index.astro apps/landing/src/styles/global.css. -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T09:33:34.359Z' -updatedAt: '2026-05-28T09:33:34.359Z' ---- -## Reason -Curate extracted factual statement - -## Raw Concept -**Task:** -Document factual statement - -**Timestamp:** 2026-05-28T09:33:34.318Z - -**Author:** ByteRover Context Engineer - -## Narrative -### Highlights -Landing-specific verification passed for pnpm exec oxfmt --check apps/landing/src/pages/index.astro apps/landing/src/styles/global.css. - -## Facts -- **format check**: Landing-specific verification passed for pnpm exec oxfmt --check apps/landing/src/pages/index.astro apps/landing/src/styles/global.css. diff --git a/.brv/context-tree/facts/conventions/glassmorphism.md b/.brv/context-tree/facts/conventions/glassmorphism.md deleted file mode 100644 index c127f47a..00000000 --- a/.brv/context-tree/facts/conventions/glassmorphism.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: glassmorphism -summary: Facts about glassmorphism -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T09:02:34.789Z' -updatedAt: '2026-05-28T09:02:34.789Z' ---- -## Reason -Curated factual statements from context - -## Raw Concept -**Task:** -Document factual statements - -**Timestamp:** 2026-05-28T09:02:34.738Z - -## Narrative -### Structure -Collection of glassmorphism facts - -## Facts -- **glassmorphism**: Glassmorphism as a default (blurred glass cards) is absolutely banned. diff --git a/.brv/context-tree/facts/conventions/gradient_text.md b/.brv/context-tree/facts/conventions/gradient_text.md deleted file mode 100644 index 36328e5a..00000000 --- a/.brv/context-tree/facts/conventions/gradient_text.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: gradient text -summary: Facts about gradient text -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T09:02:34.787Z' -updatedAt: '2026-05-28T09:02:34.787Z' ---- -## Reason -Curated factual statements from context - -## Raw Concept -**Task:** -Document factual statements - -**Timestamp:** 2026-05-28T09:02:34.738Z - -## Narrative -### Structure -Collection of gradient text facts - -## Facts -- **gradient text**: Gradient text using background‑clip: text with a gradient background is absolutely banned. diff --git a/.brv/context-tree/facts/conventions/head.md b/.brv/context-tree/facts/conventions/head.md deleted file mode 100644 index c63f4958..00000000 --- a/.brv/context-tree/facts/conventions/head.md +++ /dev/null @@ -1,26 +0,0 @@ ---- -title: HEAD -summary: Facts about HEAD -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T09:32:37.373Z' -updatedAt: '2026-05-28T09:32:37.373Z' ---- -## Reason -Curated facts extracted from context - -## Raw Concept -**Task:** -Document facts for HEAD - -**Timestamp:** 2026-05-28T09:32:37.363Z - -**Author:** ByteRover Context Engineer - -## Narrative -### Highlights -Extracted 1 facts for HEAD - -## Facts -- **HEAD**: Verified committed HEAD in a detached worktree to avoid unrelated local unstaged/untracked changes. [environment] diff --git a/.brv/context-tree/facts/conventions/hero_metric.md b/.brv/context-tree/facts/conventions/hero_metric.md deleted file mode 100644 index e5b27f52..00000000 --- a/.brv/context-tree/facts/conventions/hero_metric.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: hero metric -summary: Facts about hero metric -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T09:02:34.790Z' -updatedAt: '2026-05-28T09:02:34.790Z' ---- -## Reason -Curated factual statements from context - -## Raw Concept -**Task:** -Document factual statements - -**Timestamp:** 2026-05-28T09:02:34.738Z - -## Narrative -### Structure -Collection of hero metric facts - -## Facts -- **hero metric**: The hero‑metric template (big number, small label, supporting stats, gradient accent) is an absolute ban. diff --git a/.brv/context-tree/facts/conventions/hierarchy.md b/.brv/context-tree/facts/conventions/hierarchy.md deleted file mode 100644 index b7eaae4d..00000000 --- a/.brv/context-tree/facts/conventions/hierarchy.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: hierarchy -summary: Facts about hierarchy -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T09:02:34.784Z' -updatedAt: '2026-05-28T09:02:34.784Z' ---- -## Reason -Curated factual statements from context - -## Raw Concept -**Task:** -Document factual statements - -**Timestamp:** 2026-05-28T09:02:34.738Z - -## Narrative -### Structure -Collection of hierarchy facts - -## Facts -- **hierarchy**: Hierarchy must have a scale and weight contrast of at least a 1.25 ratio between steps. diff --git a/.brv/context-tree/facts/conventions/impeccable_skill.md b/.brv/context-tree/facts/conventions/impeccable_skill.md deleted file mode 100644 index 09b12354..00000000 --- a/.brv/context-tree/facts/conventions/impeccable_skill.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: impeccable skill -summary: Facts about impeccable skill -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T09:02:34.745Z' -updatedAt: '2026-05-28T09:02:34.745Z' ---- -## Reason -Curated factual statements from context - -## Raw Concept -**Task:** -Document factual statements - -**Timestamp:** 2026-05-28T09:02:34.738Z - -## Narrative -### Structure -Collection of impeccable skill facts - -## Facts -- **impeccable skill**: The skill "impeccable" is located at /home/ianpascoe/.agents/skills/impeccable/SKILL.md. diff --git a/.brv/context-tree/facts/conventions/import_handling.md b/.brv/context-tree/facts/conventions/import_handling.md deleted file mode 100644 index 001cfb7d..00000000 --- a/.brv/context-tree/facts/conventions/import_handling.md +++ /dev/null @@ -1,29 +0,0 @@ ---- -title: import handling -summary: Facts about import_handling -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T13:47:55.376Z' -updatedAt: '2026-05-27T13:47:55.376Z' ---- -## Reason -Extracted from source context - -## Raw Concept -**Task:** -Curate extracted factual statements - -**Changes:** -- Stop changing back to node module resolution imports. I removed the `.js` from imports for a reason so the bundler uses it -- Understood. I reverted the `@modelcontextprotocol/sdk/...` imports back to the extensionless form and will not change them again. The package-boundary failure from that expectation is intentionally not a blocker for this work. - -**Timestamp:** 2026-05-27T13:47:55.375Z - -## Narrative -### Highlights -Extracted 2 facts for import_handling - -## Facts -- **import_handling**: Stop changing back to node module resolution imports. I removed the `.js` from imports for a reason so the bundler uses it -- **import_handling**: Understood. I reverted the `@modelcontextprotocol/sdk/...` imports back to the extensionless form and will not change them again. The package-boundary failure from that expectation is intentionally not a blocker for this work. diff --git a/.brv/context-tree/facts/conventions/landing_app.md b/.brv/context-tree/facts/conventions/landing_app.md deleted file mode 100644 index 3d84bf71..00000000 --- a/.brv/context-tree/facts/conventions/landing_app.md +++ /dev/null @@ -1,30 +0,0 @@ ---- -title: Landing app -summary: Facts about landing app -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T19:21:53.900Z' -updatedAt: '2026-05-27T19:21:53.900Z' ---- -## Reason -Curate extracted factual statements - -## Raw Concept -**Task:** -Document factual statements - -**Flow:** -extraction -> curation - -**Timestamp:** 2026-05-27T19:21:53.899Z - -## Facts -- **landing app**: Replaced the npm and GitHub text links with accessible icon-only links. -- **landing app**: Changed files: apps/landing/src/pages/index.astro and apps/landing/src/styles/global.css. -- **landing app**: Added inline SVG npm icon. -- **landing app**: Added inline SVG GitHub icon. -- **landing app**: Preserved accessible labels with aria-label. -- **landing app**: Kept 46px icon buttons for touch target quality. -- **landing app**: pnpm --filter @caplets/landing build passed. -- **landing app**: pnpm --filter @caplets/landing typecheck passed, 0 errors, 0 warnings, 0 hints. diff --git a/.brv/context-tree/facts/conventions/landing_page_fact.md b/.brv/context-tree/facts/conventions/landing_page_fact.md deleted file mode 100644 index b29ad307..00000000 --- a/.brv/context-tree/facts/conventions/landing_page_fact.md +++ /dev/null @@ -1,26 +0,0 @@ ---- -title: landing page fact -summary: All landing page audit fixes were implemented using subagent-driven development. -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T09:33:34.334Z' -updatedAt: '2026-05-28T09:33:34.334Z' ---- -## Reason -Curate extracted factual statement - -## Raw Concept -**Task:** -Document factual statement - -**Timestamp:** 2026-05-28T09:33:34.318Z - -**Author:** ByteRover Context Engineer - -## Narrative -### Highlights -All landing page audit fixes were implemented using subagent-driven development. - -## Facts -- **landing page**: All landing page audit fixes were implemented using subagent-driven development. diff --git a/.brv/context-tree/facts/conventions/line_length.md b/.brv/context-tree/facts/conventions/line_length.md deleted file mode 100644 index 15d2993a..00000000 --- a/.brv/context-tree/facts/conventions/line_length.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: line length -summary: Facts about line length -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T09:02:34.783Z' -updatedAt: '2026-05-28T09:02:34.783Z' ---- -## Reason -Curated factual statements from context - -## Raw Concept -**Task:** -Document factual statements - -**Timestamp:** 2026-05-28T09:02:34.738Z - -## Narrative -### Structure -Collection of line length facts - -## Facts -- **line length**: Body line length should be capped at 65–75 characters. diff --git a/.brv/context-tree/facts/conventions/lint_fact.md b/.brv/context-tree/facts/conventions/lint_fact.md deleted file mode 100644 index bd32e09c..00000000 --- a/.brv/context-tree/facts/conventions/lint_fact.md +++ /dev/null @@ -1,26 +0,0 @@ ---- -title: lint fact -summary: Landing-specific verification passed for pnpm lint. -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T09:33:34.378Z' -updatedAt: '2026-05-28T09:33:34.378Z' ---- -## Reason -Curate extracted factual statement - -## Raw Concept -**Task:** -Document factual statement - -**Timestamp:** 2026-05-28T09:33:34.318Z - -**Author:** ByteRover Context Engineer - -## Narrative -### Highlights -Landing-specific verification passed for pnpm lint. - -## Facts -- **lint**: Landing-specific verification passed for pnpm lint. diff --git a/.brv/context-tree/facts/conventions/markdown_rendering_design.md b/.brv/context-tree/facts/conventions/markdown_rendering_design.md deleted file mode 100644 index a90d18cd..00000000 --- a/.brv/context-tree/facts/conventions/markdown_rendering_design.md +++ /dev/null @@ -1,46 +0,0 @@ ---- -title: Markdown Rendering Design -summary: Design rules for converting JSON results into hierarchical Markdown with headings, arrays handling, and size limits -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T11:38:52.107Z' -updatedAt: '2026-05-27T11:38:52.107Z' ---- -## Reason -Capture design decisions and conventions for structural Markdown rendering of Caplets results - -## Raw Concept -**Task:** -Document structural Markdown rendering design for Caplets results - -**Flow:** -Define rendering rules and conventions - -**Timestamp:** 2026-05-27T11:38:52.105Z - -## Narrative -### Structure -Rendering rules for titles, headings, arrays, deep fallback, size limits - -### Highlights -The conversion should be a JSON structure where H1 is the title of the result, top-level keys are H2, and nested keys use sequential headings when feasible. The user wants a structural Markdown rendering, not a prose summary. Revised design direction JSON: { "status": 200, "statusText": "OK", "body": { "vulns": [] } } Use an H1 as the result title in Markdown. Potential title sources, in order: Caplets metadata (e.g., '# OSV Vulnerabilities query_purl', '# HTTP action ping'), then generic fallback '# Result'. Each top-level object key becomes a second-level heading (## key). Nested object keys become progressively deeper headings when feasible, capped at heading level ######. Arrays of scalars are rendered as bullet lists under a heading. Arrays of objects are rendered with headings using label candidates such as id, name, title, summary, key, or tool. If no good label exists for array items, use index headings like '#### Item 1'. When nesting gets too deep or output too large, render that subtree as fenced JSON under its heading. Enforce a maximum content size; when truncated, include a notice indicating truncation and where the full value can be found. `structuredContent` remains unchanged and complete, preserving full structured data. Implementation plan should replace compact label previews with this structural Markdown renderer for Caplets-generated structured results, including HTTP actions, OpenAPI actions, GraphQL actions, CLI tools, field-selected results, Pi/OpenCode agent content, and possibly generated JSON wrapper results. Generated structured results include HTTP actions, OpenAPI actions, GraphQL actions, CLI tools, field-selected results, Pi/OpenCode agent content, and possibly generated JSON wrapper results where useful. It should preserve downstream MCP content unless Caplets must synthesize content from downstream structuredContent. Does this revised structural Markdown direction look right? - -## Facts -- **conversion_design**: The conversion should be a JSON structure where H1 is the title of the result, top-level keys are H2, and nested keys use sequential headings when feasible. [preference] -- **user_intent**: The user wants a structural Markdown rendering, not a prose summary. [other] -- **revised_design**: Revised design direction JSON: { "status": 200, "statusText": "OK", "body": { "vulns": [] } } [project] -- **title**: Use an H1 as the result title in Markdown. [convention] -- **title_sources**: Potential title sources, in order: Caplets metadata (e.g., '# OSV Vulnerabilities query_purl', '# HTTP action ping'), then generic fallback '# Result'. [convention] -- **top_level_keys**: Each top-level object key becomes a second-level heading (## key). [convention] -- **nested_keys**: Nested object keys become progressively deeper headings when feasible, capped at heading level ######. [convention] -- **arrays_scalars**: Arrays of scalars are rendered as bullet lists under a heading. [convention] -- **arrays_objects**: Arrays of objects are rendered with headings using label candidates such as id, name, title, summary, key, or tool. [convention] -- **index_headings**: If no good label exists for array items, use index headings like '#### Item 1'. [convention] -- **deep_fallback**: When nesting gets too deep or output too large, render that subtree as fenced JSON under its heading. [convention] -- **size_limits**: Enforce a maximum content size; when truncated, include a notice indicating truncation and where the full value can be found. [convention] -- **structured_content**: `structuredContent` remains unchanged and complete, preserving full structured data. [other] -- **plan_scope**: Implementation plan should replace compact label previews with this structural Markdown renderer for Caplets-generated structured results, including HTTP actions, OpenAPI actions, GraphQL actions, CLI tools, field-selected results, Pi/OpenCode agent content, and possibly generated JSON wrapper results. [project] -- **structured_results**: Generated structured results include HTTP actions, OpenAPI actions, GraphQL actions, CLI tools, field-selected results, Pi/OpenCode agent content, and possibly generated JSON wrapper results where useful. [project] -- **preserve_mcp_content**: It should preserve downstream MCP content unless Caplets must synthesize content from downstream structuredContent. [project] -- **revised_structural_markdown_direction**: Does this revised structural Markdown direction look right? [other] diff --git a/.brv/context-tree/facts/conventions/markdown_structure.md b/.brv/context-tree/facts/conventions/markdown_structure.md deleted file mode 100644 index af1308d5..00000000 --- a/.brv/context-tree/facts/conventions/markdown_structure.md +++ /dev/null @@ -1,22 +0,0 @@ ---- -title: Markdown structure -summary: Facts about Markdown structure -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T11:40:54.523Z' -updatedAt: '2026-05-27T11:40:54.523Z' ---- -## Reason -Curate extracted factual statements - -## Raw Concept -**Task:** -Document factual statements - -## Narrative -### Highlights -The useful Markdown structure is at the result‑shape level, not every object‑key level. - -## Facts -- **Markdown structure**: The useful Markdown structure is at the result‑shape level, not every object‑key level. diff --git a/.brv/context-tree/facts/conventions/missing_design_md.md b/.brv/context-tree/facts/conventions/missing_design_md.md deleted file mode 100644 index a6762607..00000000 --- a/.brv/context-tree/facts/conventions/missing_design_md.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: missing DESIGN.md -summary: Facts about missing DESIGN.md -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T09:02:34.776Z' -updatedAt: '2026-05-28T09:02:34.776Z' ---- -## Reason -Curated factual statements from context - -## Raw Concept -**Task:** -Document factual statements - -**Timestamp:** 2026-05-28T09:02:34.738Z - -## Narrative -### Structure -Collection of missing DESIGN.md facts - -## Facts -- **missing DESIGN.md**: If DESIGN.md is missing, the assistant should nudge the user once per session to run {{command_prefix}}impeccable document. diff --git a/.brv/context-tree/facts/conventions/missing_product_md.md b/.brv/context-tree/facts/conventions/missing_product_md.md deleted file mode 100644 index 81ae8005..00000000 --- a/.brv/context-tree/facts/conventions/missing_product_md.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: missing PRODUCT.md -summary: Facts about missing PRODUCT.md -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T09:02:34.773Z' -updatedAt: '2026-05-28T09:02:34.773Z' ---- -## Reason -Curated factual statements from context - -## Raw Concept -**Task:** -Document factual statements - -**Timestamp:** 2026-05-28T09:02:34.738Z - -## Narrative -### Structure -Collection of missing PRODUCT.md facts - -## Facts -- **missing PRODUCT.md**: If PRODUCT.md is missing, empty, or contains only placeholder text under 200 characters, the command {{command_prefix}}impeccable teach must be run. diff --git a/.brv/context-tree/facts/conventions/npm_header_link.md b/.brv/context-tree/facts/conventions/npm_header_link.md deleted file mode 100644 index fd70b9fc..00000000 --- a/.brv/context-tree/facts/conventions/npm_header_link.md +++ /dev/null @@ -1,26 +0,0 @@ ---- -title: Npm Header Link -summary: Facts about npm header link -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T09:32:37.370Z' -updatedAt: '2026-05-28T09:32:37.370Z' ---- -## Reason -Curated facts extracted from context - -## Raw Concept -**Task:** -Document facts for npm header link - -**Timestamp:** 2026-05-28T09:32:37.363Z - -**Author:** ByteRover Context Engineer - -## Narrative -### Highlights -Extracted 1 facts for npm header link - -## Facts -- **npm header link**: Remaining npm header link uses the shared .header-action.icon-link styles at apps/landing/src/styles/global.css:244-253, so the removed CSS is not needed. diff --git a/.brv/context-tree/facts/conventions/output_size.md b/.brv/context-tree/facts/conventions/output_size.md deleted file mode 100644 index 577bf3a3..00000000 --- a/.brv/context-tree/facts/conventions/output_size.md +++ /dev/null @@ -1,22 +0,0 @@ ---- -title: Output size -summary: Facts about output size -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T11:40:54.533Z' -updatedAt: '2026-05-27T11:40:54.534Z' ---- -## Reason -Curate extracted factual statements - -## Raw Concept -**Task:** -Document factual statements - -## Narrative -### Highlights -Apply a size limit with explicit truncation text. - -## Facts -- **output size**: Apply a size limit with explicit truncation text. diff --git a/.brv/context-tree/facts/conventions/panel.md b/.brv/context-tree/facts/conventions/panel.md deleted file mode 100644 index 981d8ac4..00000000 --- a/.brv/context-tree/facts/conventions/panel.md +++ /dev/null @@ -1,26 +0,0 @@ ---- -title: panel -summary: Facts about panel -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T09:02:34.801Z' -updatedAt: '2026-05-28T09:02:34.801Z' ---- -## Reason -Curated factual statements from context - -## Raw Concept -**Task:** -Document factual statements - -**Timestamp:** 2026-05-28T09:02:34.738Z - -## Narrative -### Structure -Collection of panel facts - -## Facts -- **panel**: The panel now uses a stable `block-size: clamp(260px, 24vw, 320px)` and scrolls internally if a snippet is longer. -- **panel**: The panel now uses a stable block-size defined as clamp(260px, 24vw, 320px). -- **panel**: The panel scrolls internally if a snippet is longer. diff --git a/.brv/context-tree/facts/conventions/payload_duplication_facts.md b/.brv/context-tree/facts/conventions/payload_duplication_facts.md deleted file mode 100644 index 3af9bfbc..00000000 --- a/.brv/context-tree/facts/conventions/payload_duplication_facts.md +++ /dev/null @@ -1,33 +0,0 @@ ---- -title: payload duplication Facts -summary: Facts about payload duplication -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T11:32:10.031Z' -updatedAt: '2026-05-27T11:32:10.031Z' ---- -## Reason -Curate extracted factual statements from context - -## Raw Concept -**Task:** -Document factual statements - -**Changes:** -- Added extracted facts - -**Flow:** -Extraction -> Deduplication -> Curation - -**Timestamp:** 2026-05-27T11:32:10.020Z - -## Narrative -### Structure -Facts grouped under payload duplication - -### Highlights -Avoid duplicating huge payloads blindly because it can waste tokens and degrade model performance. - -## Facts -- **payload duplication**: Avoid duplicating huge payloads blindly because it can waste tokens and degrade model performance. diff --git a/.brv/context-tree/facts/conventions/plan.md b/.brv/context-tree/facts/conventions/plan.md deleted file mode 100644 index eff1db7e..00000000 --- a/.brv/context-tree/facts/conventions/plan.md +++ /dev/null @@ -1,21 +0,0 @@ ---- -title: Plan -summary: Facts about plan -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T12:01:34.385Z' -updatedAt: '2026-05-27T12:01:34.385Z' ---- -## Reason -Curated from RLM extraction - -## Raw Concept -**Task:** -Document plan facts - -**Timestamp:** 2026-05-27T12:01:34.378Z - -## Facts -- **plan**: Plan created: docs/plans/2026-05-27-lossless-markdown-result-content.md -- **plan**: The plan covers backend-specific Markdown formats for MCP, HTTP, OpenAPI, GraphQL, CLI, Caplet sets, discovery, errors, and field selection; no renderer-level truncation; preserving complete `structuredContent`; making `content` a full Markdown projection for content-only MCP clients; Pi/OpenCode native integration updates; TDD task breakdown, commands, and verification steps. diff --git a/.brv/context-tree/facts/conventions/pnpm.md b/.brv/context-tree/facts/conventions/pnpm.md deleted file mode 100644 index 0749b73d..00000000 --- a/.brv/context-tree/facts/conventions/pnpm.md +++ /dev/null @@ -1,25 +0,0 @@ ---- -title: pnpm -summary: Facts about pnpm -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T09:02:34.806Z' -updatedAt: '2026-05-28T09:02:34.806Z' ---- -## Reason -Curated factual statements from context - -## Raw Concept -**Task:** -Document factual statements - -**Timestamp:** 2026-05-28T09:02:34.738Z - -## Narrative -### Structure -Collection of pnpm facts - -## Facts -- **pnpm**: The command `pnpm --filter @caplets/landing typecheck` is used. -- **pnpm**: The command `pnpm --filter @caplets/landing build` is used. diff --git a/.brv/context-tree/facts/conventions/pnpm_format_check_fact.md b/.brv/context-tree/facts/conventions/pnpm_format_check_fact.md deleted file mode 100644 index 9115c9db..00000000 --- a/.brv/context-tree/facts/conventions/pnpm_format_check_fact.md +++ /dev/null @@ -1,26 +0,0 @@ ---- -title: pnpm format:check fact -summary: Full pnpm format:check is currently blocked by unrelated untracked .github/workflows/pr-preview.yml formatting. -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T09:33:34.384Z' -updatedAt: '2026-05-28T09:33:34.384Z' ---- -## Reason -Curate extracted factual statement - -## Raw Concept -**Task:** -Document factual statement - -**Timestamp:** 2026-05-28T09:33:34.318Z - -**Author:** ByteRover Context Engineer - -## Narrative -### Highlights -Full pnpm format:check is currently blocked by unrelated untracked .github/workflows/pr-preview.yml formatting. - -## Facts -- **pnpm format:check**: Full pnpm format:check is currently blocked by unrelated untracked .github/workflows/pr-preview.yml formatting. diff --git a/.brv/context-tree/facts/conventions/pr_preview_yml_fact.md b/.brv/context-tree/facts/conventions/pr_preview_yml_fact.md deleted file mode 100644 index f2aefc54..00000000 --- a/.brv/context-tree/facts/conventions/pr_preview_yml_fact.md +++ /dev/null @@ -1,26 +0,0 @@ ---- -title: pr-preview.yml fact -summary: The .github/workflows/pr-preview.yml file was not part of the landing fixes. -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T09:33:34.385Z' -updatedAt: '2026-05-28T09:33:34.385Z' ---- -## Reason -Curate extracted factual statement - -## Raw Concept -**Task:** -Document factual statement - -**Timestamp:** 2026-05-28T09:33:34.318Z - -**Author:** ByteRover Context Engineer - -## Narrative -### Highlights -The .github/workflows/pr-preview.yml file was not part of the landing fixes. - -## Facts -- **pr-preview.yml**: The .github/workflows/pr-preview.yml file was not part of the landing fixes. diff --git a/.brv/context-tree/facts/conventions/project_context.md b/.brv/context-tree/facts/conventions/project_context.md deleted file mode 100644 index c442160d..00000000 --- a/.brv/context-tree/facts/conventions/project_context.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: project context -summary: Facts about project context -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T09:02:34.755Z' -updatedAt: '2026-05-28T09:02:34.755Z' ---- -## Reason -Curated factual statements from context - -## Raw Concept -**Task:** -Document factual statements - -**Timestamp:** 2026-05-28T09:02:34.738Z - -## Narrative -### Structure -Collection of project context facts - -## Facts -- **project context**: PRODUCT.md is required; DESIGN.md is optional but strongly recommended. diff --git a/.brv/context-tree/facts/conventions/punctuation.md b/.brv/context-tree/facts/conventions/punctuation.md deleted file mode 100644 index 218afc48..00000000 --- a/.brv/context-tree/facts/conventions/punctuation.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: punctuation -summary: Facts about punctuation -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T09:02:34.791Z' -updatedAt: '2026-05-28T09:02:34.791Z' ---- -## Reason -Curated factual statements from context - -## Raw Concept -**Task:** -Document factual statements - -**Timestamp:** 2026-05-28T09:02:34.738Z - -## Narrative -### Structure -Collection of punctuation facts - -## Facts -- **punctuation**: Em dashes are prohibited in copy; commas, colons, semicolons, periods, or parentheses must be used instead. diff --git a/.brv/context-tree/facts/conventions/recursive_headings.md b/.brv/context-tree/facts/conventions/recursive_headings.md deleted file mode 100644 index 43e35d0b..00000000 --- a/.brv/context-tree/facts/conventions/recursive_headings.md +++ /dev/null @@ -1,22 +0,0 @@ ---- -title: Recursive headings -summary: Facts about recursive headings -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T11:40:54.522Z' -updatedAt: '2026-05-27T11:40:54.522Z' ---- -## Reason -Curate extracted factual statements - -## Raw Concept -**Task:** -Document factual statements - -## Narrative -### Highlights -Recursive headings become problematic with real API payloads because arrays become awkward headings, repeated object shapes become noisy, deeply nested fields produce unusable heading levels, and JSON structure is already the best representation for nested machine data. - -## Facts -- **recursive headings**: Recursive headings become problematic with real API payloads because arrays become awkward headings, repeated object shapes become noisy, deeply nested fields produce unusable heading levels, and JSON structure is already the best representation for nested machine data. diff --git a/.brv/context-tree/facts/conventions/rendering_approach.md b/.brv/context-tree/facts/conventions/rendering_approach.md deleted file mode 100644 index da6ccc26..00000000 --- a/.brv/context-tree/facts/conventions/rendering_approach.md +++ /dev/null @@ -1,22 +0,0 @@ ---- -title: Rendering approach -summary: Facts about rendering approach -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T11:40:54.520Z' -updatedAt: '2026-05-27T11:40:54.520Z' ---- -## Reason -Curate extracted factual statements - -## Raw Concept -**Task:** -Document factual statements - -## Narrative -### Highlights -My recommendation is backend-aware Markdown with a generic JSON fallback, not recursive key headings. - -## Facts -- **rendering approach**: My recommendation is backend-aware Markdown with a generic JSON fallback, not recursive key headings. diff --git a/.brv/context-tree/facts/conventions/response_size_facts.md b/.brv/context-tree/facts/conventions/response_size_facts.md deleted file mode 100644 index 41944a16..00000000 --- a/.brv/context-tree/facts/conventions/response_size_facts.md +++ /dev/null @@ -1,34 +0,0 @@ ---- -title: response size Facts -summary: Facts about response size -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T11:32:10.032Z' -updatedAt: '2026-05-27T11:32:10.033Z' ---- -## Reason -Curate extracted factual statements from context - -## Raw Concept -**Task:** -Document factual statements - -**Changes:** -- Added extracted facts - -**Flow:** -Extraction -> Deduplication -> Curation - -**Timestamp:** 2026-05-27T11:32:10.020Z - -## Narrative -### Structure -Facts grouped under response size - -### Highlights -For small/medium responses, include full JSON in both `content` and `structuredContent`.; For large responses, place full data in `structuredContent` and provide a concise useful text summary in `content`. - -## Facts -- **response size**: For small/medium responses, include full JSON in both `content` and `structuredContent`. -- **response size**: For large responses, place full data in `structuredContent` and provide a concise useful text summary in `content`. diff --git a/.brv/context-tree/facts/conventions/setup_model.md b/.brv/context-tree/facts/conventions/setup_model.md deleted file mode 100644 index fd509535..00000000 --- a/.brv/context-tree/facts/conventions/setup_model.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -title: setup model -summary: Facts about setup model -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T23:17:13.657Z' -updatedAt: '2026-05-27T23:17:13.657Z' ---- -## Reason -Curated facts from extraction - -## Raw Concept -**Task:** -Document facts for setup model - -**Timestamp:** 2026-05-27T23:17:13.640Z - -## Facts -- **setup model**: Add a normalized `setup` model to each agent entry, probably `{ installCommand, configSnippet, notes? }`. diff --git a/.brv/context-tree/facts/conventions/setup_rendering.md b/.brv/context-tree/facts/conventions/setup_rendering.md deleted file mode 100644 index a62f6295..00000000 --- a/.brv/context-tree/facts/conventions/setup_rendering.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -title: setup rendering -summary: Facts about setup rendering -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T23:17:13.661Z' -updatedAt: '2026-05-27T23:17:13.661Z' ---- -## Reason -Curated facts from extraction - -## Raw Concept -**Task:** -Document facts for setup rendering - -**Timestamp:** 2026-05-27T23:17:13.640Z - -## Facts -- **setup rendering**: Tests should cover that all agents with setup data render both the install command and config snippet, and that copy text matches the source strings. diff --git a/.brv/context-tree/facts/conventions/structuredcontent_facts.md b/.brv/context-tree/facts/conventions/structuredcontent_facts.md deleted file mode 100644 index ace26333..00000000 --- a/.brv/context-tree/facts/conventions/structuredcontent_facts.md +++ /dev/null @@ -1,33 +0,0 @@ ---- -title: structuredContent Facts -summary: Facts about structuredContent -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T11:32:10.026Z' -updatedAt: '2026-05-27T11:32:10.026Z' ---- -## Reason -Curate extracted factual statements from context - -## Raw Concept -**Task:** -Document factual statements - -**Changes:** -- Added extracted facts - -**Flow:** -Extraction -> Deduplication -> Curation - -**Timestamp:** 2026-05-27T11:32:10.020Z - -## Narrative -### Structure -Facts grouped under structuredContent - -### Highlights -Put machine-readable data in `structuredContent`. - -## Facts -- **structuredContent**: Put machine-readable data in `structuredContent`. diff --git a/.brv/context-tree/facts/conventions/sub_command_handling.md b/.brv/context-tree/facts/conventions/sub_command_handling.md deleted file mode 100644 index 369c9b5a..00000000 --- a/.brv/context-tree/facts/conventions/sub_command_handling.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: sub‑command handling -summary: Facts about sub‑command handling -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T09:02:34.752Z' -updatedAt: '2026-05-28T09:02:34.752Z' ---- -## Reason -Curated factual statements from context - -## Raw Concept -**Task:** -Document factual statements - -**Timestamp:** 2026-05-28T09:02:34.738Z - -## Narrative -### Structure -Collection of sub‑command handling facts - -## Facts -- **sub‑command handling**: If the user invoked a sub‑command such as craft, shape, or audit, the corresponding reference file (e.g., craft.md) must also be loaded. diff --git a/.brv/context-tree/facts/conventions/task_3_spec_review_fix.md b/.brv/context-tree/facts/conventions/task_3_spec_review_fix.md deleted file mode 100644 index e2a01d48..00000000 --- a/.brv/context-tree/facts/conventions/task_3_spec_review_fix.md +++ /dev/null @@ -1,45 +0,0 @@ ---- -title: Task 3 Spec Review Fix -summary: 'Task 3 fix: IPv6 loopback serve URLs now bind with ::1, CLI help text updated, focused tests passed, commit cdde479' -tags: [] -related: [facts/conventions/task_5_regression_test_update.md, facts/conventions/task_6_review_outcome.md, facts/project/task_6_spec_review_after_envelope_fix.md, facts/conventions/task_7_review_outcome.md, facts/conventions/task_7_remote_mutation_routing_review.md, facts/conventions/task_9_remote_config_test_fix.md, facts/project/remote_control_review_outcome.md] -keywords: [] -createdAt: '2026-05-20T12:56:28.017Z' -updatedAt: '2026-05-20T12:56:28.017Z' ---- - -## Reason - -Record the implementation outcome, tests, and commit for the spec review fix - -## Raw Concept - -**Task:** -Document the Task 3 spec review fix outcome for serve binding and CLI help text. - -**Changes:** -- Adjusted IPv6 loopback handling for CAPLETS_SERVER_URL -- Updated CLI help text wording -- Ran focused tests and recorded successful results - -**Flow:** -spec review finding -> implementation fix -> focused tests -> commit - -**Timestamp:** 2026-05-20T12:56:17.437Z - -## Narrative - -### Structure - -This note captures the final outcome of the Task 3 spec review fix, including the binding behavior change, help text update, test coverage, and commit identifier. - -### Highlights - -The fix ensures IPv6 loopback URLs bind correctly as ::1, and the CLI help copy now matches the service base path wording. - -## Facts - -- **serve_ipv6_loopback_binding**: CAPLETS_SERVER_URL http://[::1]:5387/caplets should treat IPv6 loopback as loopback for serve binding and return host ::1 with path /caplets without requiring auth or allow flag. [project] -- **cli_help_text**: CLI help text in packages/core/src/cli.ts was updated from "HTTP MCP endpoint path" to the new service base path wording. [project] -- **focused_tests**: Focused tests passed for packages/core/test/serve-options.test.ts, packages/core/test/serve-http.test.ts, and packages/core/test/cli.test.ts. [project] -- **commit_sha**: The fix was committed as cdde479 with message "fix(serve): bind IPv6 loopback server URLs". [project] diff --git a/.brv/context-tree/facts/conventions/task_5_regression_test_update.md b/.brv/context-tree/facts/conventions/task_5_regression_test_update.md deleted file mode 100644 index bf22cd05..00000000 --- a/.brv/context-tree/facts/conventions/task_5_regression_test_update.md +++ /dev/null @@ -1,53 +0,0 @@ ---- -title: Task 5 Regression Test Update -summary: Added regression tests for remote add rejecting server-owned fields; tests passed and commit 089542d was created with a note about unrelated worktree changes. -tags: [] -related: [facts/conventions/task_3_spec_review_fix.md, facts/conventions/task_6_review_outcome.md, facts/project/task_6_spec_review_after_envelope_fix.md, facts/conventions/task_7_review_outcome.md, facts/conventions/task_7_remote_mutation_routing_review.md, facts/conventions/task_9_remote_config_test_fix.md, facts/project/remote_control_review_outcome.md] -keywords: [] -createdAt: '2026-05-20T13:52:39.382Z' -updatedAt: '2026-05-20T13:52:39.382Z' ---- - -## Reason - -Record durable task outcome, tests, commit, and concerns from the conversation - -## Raw Concept - -**Task:** -Add regression tests ensuring remote add rejects options.destinationRoot and options.print as server-owned fields - -**Changes:** -- Added focused regression coverage for server-owned remote add fields -- Ran remote-control-dispatch and serve-http tests -- Created commit 089542d without amending existing history - -**Files:** -- packages/core/test/remote-control-dispatch.test.ts - -**Flow:** -Implement rejection tests -> run targeted tests -> commit test-only change -> report outcome - -**Timestamp:** 2026-05-20T13:52:25.305Z - -## Narrative - -### Structure - -This update records the regression-testing work for the remote add server-owned field check, along with execution results and commit metadata. - -### Dependencies - -Relies on existing implementation behavior that already rejects the fields; tests were the only required change unless implementation needed adjustment. - -### Highlights - -pnpm --filter @caplets/core test -- test/remote-control-dispatch.test.ts test/serve-http.test.ts passed: 32 files, 389 tests. Commit sha: 089542d. - -### Rules - -Return DONE, DONE_WITH_CONCERNS, or BLOCKED and include tests run plus commit sha. The commit should be new and should not amend prior history. - -### Examples - -Concern noted: unrelated staged/untracked .brv and docs files remained in the worktree, but the commit included only the test file. diff --git a/.brv/context-tree/facts/conventions/task_6_review_outcome.md b/.brv/context-tree/facts/conventions/task_6_review_outcome.md deleted file mode 100644 index 906b65c6..00000000 --- a/.brv/context-tree/facts/conventions/task_6_review_outcome.md +++ /dev/null @@ -1,67 +0,0 @@ ---- -title: Task 6 Review Outcome -summary: Task 6 review approved; nested engine request envelope validation is correct, old CAPLETS_REMOTE_* docs belong to Task 10, and source/test verification passed. -tags: [] -related: [facts/conventions/task_3_spec_review_fix.md, facts/conventions/task_5_regression_test_update.md, facts/project/task_6_spec_review_after_envelope_fix.md, facts/conventions/task_7_review_outcome.md, facts/conventions/task_7_remote_mutation_routing_review.md, facts/conventions/task_9_remote_config_test_fix.md, facts/project/remote_control_review_outcome.md] -keywords: [] -createdAt: '2026-05-20T14:17:40.238Z' -updatedAt: '2026-05-20T14:17:40.238Z' ---- - -## Reason - -Capture approved review details and verification results for Task 6 - -## Raw Concept - -**Task:** -Record the Task 6 quality review outcome after operation envelope validation. - -**Changes:** -- Approved the review -- Confirmed nested request.operation validation in dispatch -- Confirmed CLI remote routing env var usage -- Assigned lingering CAPLETS_REMOTE_* docs to Task 10 - -**Files:** -- packages/core/src/remote-control/dispatch.ts -- packages/core/test/remote-control-dispatch.test.ts - -**Flow:** -review request -> validate source/test behavior -> note doc-only cleanup scope -> verify tests and typecheck - -**Timestamp:** 2026-05-20T14:17:26.056Z - -**Author:** assistant - -## Narrative - -### Structure - -This note captures the final review decision plus the specific code and test evidence cited in the approval. - -### Dependencies - -The review depends on dispatch envelope validation, test coverage for missing/mismatched nested operations, and the intended old-env removal scope. - -### Highlights - -Approved with no findings. Doc-only CAPLETS_REMOTE_* references were explicitly deferred to Task 10 because they do not block current source/test correctness. - -### Rules - -Do not edit. Return APPROVED or FINDINGS. - -### Examples - -Examples of verified behavior: missing nested request.operation is rejected; mismatched nested request.operation is rejected; CLI remote routing uses CAPLETS_MODE / CAPLETS_SERVER_URL. - -## Facts - -- **task_6_review_outcome**: Task 6 review outcome was APPROVED after operation envelope validation. [project] -- **remote_control_dispatch_validation**: packages/core/src/remote-control/dispatch.ts validates the nested engine request envelope and rejects missing or mismatched request.operation values. [project] -- **remote_control_dispatch_tests**: packages/core/test/remote-control-dispatch.test.ts covers both missing and mismatched nested operation cases. [project] -- **cli_remote_routing_env_vars**: CLI remote routing uses CAPLETS_MODE and CAPLETS_SERVER_URL only. [project] -- **old_env_docs_task_assignment**: Remaining CAPLETS_REMOTE_* documentation references belong to Task 10 and do not block Task 6 source/test correctness. [project] -- **task_6_test_verification**: Verification passed for pnpm --filter @caplets/core test -- test/cli-remote.test.ts test/remote-control-dispatch.test.ts test/remote-control-client.test.ts test/server-options.test.ts with 33 files and 396 tests. [project] -- **task_6_typecheck_status**: pnpm --filter @caplets/core typecheck passed. [project] diff --git a/.brv/context-tree/facts/conventions/task_7_remote_mutation_routing_review.md b/.brv/context-tree/facts/conventions/task_7_remote_mutation_routing_review.md deleted file mode 100644 index f7953ee2..00000000 --- a/.brv/context-tree/facts/conventions/task_7_remote_mutation_routing_review.md +++ /dev/null @@ -1,71 +0,0 @@ ---- -title: Task 7 Remote Mutation Routing Review -summary: Task 7 remote mutation routing review was approved; remote add/init/install flows, server-side boundary validation, response parsing, and redaction were verified, with tests and typecheck passing. -tags: [] -related: [architecture/remote_control/context.md, architecture/remote_control/remote_control_api_shape.md, facts/conventions/task_3_spec_review_fix.md, facts/conventions/task_5_regression_test_update.md, facts/conventions/task_6_review_outcome.md, facts/project/task_6_spec_review_after_envelope_fix.md, facts/conventions/task_7_review_outcome.md, facts/conventions/task_9_remote_config_test_fix.md, facts/project/remote_control_review_outcome.md] -keywords: [] -createdAt: '2026-05-20T14:35:11.652Z' -updatedAt: '2026-05-20T14:35:11.652Z' ---- - -## Reason - -Document the approved review outcome and verification details for remote mutation routing - -## Raw Concept - -**Task:** -Capture the code review outcome and verified technical findings for Task 7 remote mutation routing. - -**Changes:** -- Approved the remote mutation routing review -- Confirmed request sanitization and command boundary checks -- Confirmed remote response parsing and error redaction -- Recorded passing targeted tests and typecheck - -**Files:** -- packages/core/src/cli.ts -- packages/core/src/remote-control/dispatch.ts -- packages/core/src/remote-control/client.ts -- packages/core/test/cli-remote.test.ts -- packages/core/test/remote-control-dispatch.test.ts - -**Flow:** -review request -> inspect CLI and remote control paths -> verify tests and typecheck -> approve - -**Timestamp:** 2026-05-20T14:34:21.017Z - -**Author:** assistant - -## Narrative - -### Structure - -The review spans CLI routing, server-side dispatch validation, remote client parsing, and targeted test coverage. - -### Dependencies - -Depends on CLI sanitization behavior, dispatch-layer validation, and client-side response handling. - -### Highlights - -No blocking findings were reported. The review explicitly confirmed payload sanitization, boundary enforcement, malformed-response rejection, and redacted error surfacing. - -### Rules - -Return APPROVED or FINDINGS with severity/refs. Do not edit. - -## Facts - -- **task_7_review_outcome**: Task 7 remote mutation routing review was approved. [project] -- **remote_route**: Remote init, install, and all add variants route through /control. [project] -- **stripped_add_fields**: Local-only add fields are stripped before remote requests: global, print, output, destinationRoot. [project] -- **server_command_boundary**: Server-side command boundary validates add option types. [project] -- **destination_root_ownership**: Server owns destination root and rejects output, destinationRoot, and print. [project] -- **engine_command_request_validation**: Engine command requests require nested operation to match the outer command. [project] -- **remote_envelope_parsing**: Remote envelope parsing rejects malformed responses. [project] -- **remote_error_redaction**: Remote error messages are redacted before surfacing. [project] -- **cli_remote_test_status**: Verification ran pnpm --filter @caplets/core test -- test/cli-remote.test.ts and it passed. [project] -- **remote_control_dispatch_test_status**: Verification ran pnpm --filter @caplets/core test -- test/remote-control-dispatch.test.ts and it passed. [project] -- **typecheck_status**: pnpm --filter @caplets/core typecheck passed. [project] -- **full_verify_status**: Full pnpm verify was not run. [project] diff --git a/.brv/context-tree/facts/conventions/task_7_review_outcome.md b/.brv/context-tree/facts/conventions/task_7_review_outcome.md deleted file mode 100644 index b3b7807e..00000000 --- a/.brv/context-tree/facts/conventions/task_7_review_outcome.md +++ /dev/null @@ -1,58 +0,0 @@ ---- -title: Task 7 Review Outcome -summary: Task 7 review approved after verifying remote add payload sanitization and add-subcommand coverage; tests passed -tags: [] -related: [facts/conventions/task_3_spec_review_fix.md, facts/conventions/task_5_regression_test_update.md, facts/conventions/task_6_review_outcome.md, facts/project/task_6_spec_review_after_envelope_fix.md, facts/conventions/task_7_remote_mutation_routing_review.md, facts/conventions/task_9_remote_config_test_fix.md, facts/project/remote_control_review_outcome.md] -keywords: [] -createdAt: '2026-05-20T14:30:35.963Z' -updatedAt: '2026-05-20T14:30:35.963Z' ---- - -## Reason - -Document the review result and verification details for Task 7 spec after fix - -## Raw Concept - -**Task:** -Document the Task 7 spec re-review after the fix - -**Changes:** -- Approved the review after confirming payload sanitization -- Confirmed add subcommand coverage across cli, mcp, openapi, graphql, and http -- Recorded the successful verification run - -**Files:** -- packages/core/src/cli.ts -- packages/core/test/cli-remote.test.ts -- packages/core/test/remote-control-dispatch.test.ts - -**Flow:** -review request -> verify sanitization -> verify subcommand coverage -> run tests -> APPROVED - -**Timestamp:** 2026-05-20T14:30:22.611Z - -**Author:** assistant - -## Narrative - -### Structure - -This note captures the post-fix review outcome for Task 7, including the specific sanitization behavior, add subcommand coverage, and test verification. - -### Dependencies - -Depends on the updated CLI remote add implementation and the associated remote control dispatch tests. - -### Highlights - -The review was approved and the targeted test run passed with 404 tests across 33 files. - -## Facts - -- **task_7_review_outcome**: Task 7 spec review after fix was APPROVED [project] -- **remote_add_payload_sanitization**: remote add payload sanitization strips global, print, output, and destinationRoot before remote.request("add", ...) [project] -- **add_subcommand_coverage**: Remote routing coverage includes all add subcommands: cli, mcp, openapi, graphql, and http [project] -- **sanitization_test_case**: Sanitization is explicitly tested for add mcp with --global, --print, and --output [project] -- **verification_command**: The verification run was pnpm --filter @caplets/core test -- test/cli-remote.test.ts test/remote-control-dispatch.test.ts [project] -- **verification_result**: The verification result was 33 files / 404 tests passed [project] diff --git a/.brv/context-tree/facts/conventions/task_9_remote_config_test_fix.md b/.brv/context-tree/facts/conventions/task_9_remote_config_test_fix.md deleted file mode 100644 index 1581882b..00000000 --- a/.brv/context-tree/facts/conventions/task_9_remote_config_test_fix.md +++ /dev/null @@ -1,63 +0,0 @@ ---- -title: Task 9 Remote Config Test Fix -summary: Task 9 fix updated the in-process remote control app test to snapshot local.configPath before runCli and assert it remains unchanged; focused tests passed and commit c9f08ac was created. -tags: [] -related: [facts/conventions/task_3_spec_review_fix.md, facts/conventions/task_5_regression_test_update.md, facts/conventions/task_6_review_outcome.md, facts/project/task_6_spec_review_after_envelope_fix.md, facts/conventions/task_7_review_outcome.md, facts/conventions/task_7_remote_mutation_routing_review.md, facts/project/remote_control_review_outcome.md] -keywords: [] -createdAt: '2026-05-20T15:14:33.118Z' -updatedAt: '2026-05-20T15:14:33.118Z' ---- - -## Reason - -Record the durable outcome of the Task 9 spec finding fix and verification. - -## Raw Concept - -**Task:** -Document the Task 9 spec finding fix for the in-process remote control app test in packages/core/test/cli-remote.test.ts - -**Changes:** -- Added assertion that local.configPath content remains unchanged after runCli -- Kept the fix path-limited to packages/core/test/cli-remote.test.ts -- Ran the targeted @caplets/core test for cli-remote.test.ts -- Committed the change without amend - -**Files:** -- packages/core/test/cli-remote.test.ts - -**Flow:** -snapshot local config -> runCli -> compare post-run contents -> assert unchanged - -**Timestamp:** 2026-05-20T15:14:13.980Z - -**Author:** ByteRover context engineer - -## Narrative - -### Structure - -This knowledge captures a single targeted test fix and its verification outcome for the remote CLI path. The important behavior is that the local config file is protected from mutation during the in-process remote control app test. - -### Dependencies - -The fix depends on the cli-remote test harness and the local fixture config behavior in packages/core/test/cli-remote.test.ts. - -### Highlights - -The focused test passed after the assertion change, and the resulting commit SHA was c9f08ac. - -### Rules - -Goal is to prove local config file is not mutated. Commit new test fix path-limited, no amend. - -### Examples - -Use the test command pnpm --filter @caplets/core test -- test/cli-remote.test.ts when validating similar targeted CLI test fixes. - -## Facts - -- **remote_cli_local_config_mutation_guard**: The in-process remote control app test now snapshots local.configPath contents before runCli and asserts they are unchanged afterward. [project] -- **remote_cli_local_fixture_handling**: If the local fixture config does not exist, the test should handle existence and non-existence consistently while proving the local config file is not mutated. [project] -- **focused_test_command**: The focused test command run was pnpm --filter @caplets/core test -- test/cli-remote.test.ts. [project] -- **commit_sha**: The commit created for the fix was c9f08ac with message test(cli): assert remote routing preserves local config. [project] diff --git a/.brv/context-tree/facts/conventions/template_usage.md b/.brv/context-tree/facts/conventions/template_usage.md deleted file mode 100644 index b1d74e36..00000000 --- a/.brv/context-tree/facts/conventions/template_usage.md +++ /dev/null @@ -1,22 +0,0 @@ ---- -title: Template usage -summary: Facts about template usage -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T11:40:54.530Z' -updatedAt: '2026-05-27T11:40:54.530Z' ---- -## Reason -Curate extracted factual statements - -## Raw Concept -**Task:** -Document factual statements - -## Narrative -### Highlights -Use backend‑aware templates where Caplets knows the shape. - -## Facts -- **template usage**: Use backend‑aware templates where Caplets knows the shape. diff --git a/.brv/context-tree/facts/conventions/theme_selection.md b/.brv/context-tree/facts/conventions/theme_selection.md deleted file mode 100644 index 93e5491e..00000000 --- a/.brv/context-tree/facts/conventions/theme_selection.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: theme selection -summary: Facts about theme selection -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T09:02:34.782Z' -updatedAt: '2026-05-28T09:02:34.782Z' ---- -## Reason -Curated factual statements from context - -## Raw Concept -**Task:** -Document factual statements - -**Timestamp:** 2026-05-28T09:02:34.738Z - -## Narrative -### Structure -Collection of theme selection facts - -## Facts -- **theme selection**: Dark vs. light theme must be chosen based on a concrete physical scene description, not by default. diff --git a/.brv/context-tree/facts/conventions/trivial_continuation_marker.md b/.brv/context-tree/facts/conventions/trivial_continuation_marker.md deleted file mode 100644 index 2278877a..00000000 --- a/.brv/context-tree/facts/conventions/trivial_continuation_marker.md +++ /dev/null @@ -1,46 +0,0 @@ ---- -title: Trivial Continuation Marker -summary: No durable knowledge extracted; conversation contained only a continuation marker. -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-20T17:49:29.002Z' -updatedAt: '2026-05-20T17:49:29.002Z' ---- - -## Reason - -The only substantive available content is a continuation request with no lasting knowledge. - -## Raw Concept - -**Task:** -Record that this context contains no durable knowledge - -**Changes:** -- Identified the input as a continuation-only message with no substantive content - -**Flow:** -receive context -> detect no substantive knowledge -> store continuation marker - -**Timestamp:** 2026-05-20 - -**Author:** ByteRover - -## Narrative - -### Structure - -This entry documents that the provided context was only a continuation prompt. - -### Dependencies - -No source material beyond the continuation marker was available. - -### Highlights - -Nothing actionable or durable was present for deeper curation. - -## Facts - -- **conversation_state**: The conversation content consisted only of a user message saying "Continue" [other] diff --git a/.brv/context-tree/facts/conventions/typecheck.md b/.brv/context-tree/facts/conventions/typecheck.md deleted file mode 100644 index 5e3327ca..00000000 --- a/.brv/context-tree/facts/conventions/typecheck.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: typecheck -summary: Facts about typecheck -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T09:02:34.803Z' -updatedAt: '2026-05-28T09:02:34.803Z' ---- -## Reason -Curated factual statements from context - -## Raw Concept -**Task:** -Document factual statements - -**Timestamp:** 2026-05-28T09:02:34.738Z - -## Narrative -### Structure -Collection of typecheck facts - -## Facts -- **typecheck**: Verification passed: `pnpm --filter @caplets/landing typecheck`. diff --git a/.brv/context-tree/facts/conventions/typecheck_fact.md b/.brv/context-tree/facts/conventions/typecheck_fact.md deleted file mode 100644 index 726b049b..00000000 --- a/.brv/context-tree/facts/conventions/typecheck_fact.md +++ /dev/null @@ -1,26 +0,0 @@ ---- -title: typecheck fact -summary: Landing-specific verification passed for pnpm --filter @caplets/landing typecheck. -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T09:33:34.381Z' -updatedAt: '2026-05-28T09:33:34.381Z' ---- -## Reason -Curate extracted factual statement - -## Raw Concept -**Task:** -Document factual statement - -**Timestamp:** 2026-05-28T09:33:34.318Z - -**Author:** ByteRover Context Engineer - -## Narrative -### Highlights -Landing-specific verification passed for pnpm --filter @caplets/landing typecheck. - -## Facts -- **typecheck**: Landing-specific verification passed for pnpm --filter @caplets/landing typecheck. diff --git a/.brv/context-tree/facts/curation_runtime_conventions/context.md b/.brv/context-tree/facts/curation_runtime_conventions/context.md deleted file mode 100644 index c659f66e..00000000 --- a/.brv/context-tree/facts/curation_runtime_conventions/context.md +++ /dev/null @@ -1,13 +0,0 @@ -# Topic: curation_runtime_conventions - -## Overview - -Covers the curation workflow conventions for RLM sessions, especially the single-pass path, chunked fallback, and verification expectations. - -## Key Concepts - -- single-pass mode -- chunked extraction -- mapExtract -- verification via applied file paths -- timeout requirements diff --git a/.brv/context-tree/facts/extracted/adapt.md b/.brv/context-tree/facts/extracted/adapt.md deleted file mode 100644 index 9c504b89..00000000 --- a/.brv/context-tree/facts/extracted/adapt.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Adapt -summary: Extracted facts about adapt -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T19:41:18.263Z' -updatedAt: '2026-05-27T19:41:18.263Z' ---- -## Reason -Curated extracted facts from RLM context - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-27T19:41:18.110Z - -## Narrative -### Structure -Facts grouped under adapt - -## Facts -- **adapt**: `adapt [target]` is a Fix command that adapts designs for different devices and screen sizes. diff --git a/.brv/context-tree/facts/extracted/animate.md b/.brv/context-tree/facts/extracted/animate.md deleted file mode 100644 index f5f05634..00000000 --- a/.brv/context-tree/facts/extracted/animate.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Animate -summary: Extracted facts about animate -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T19:41:18.253Z' -updatedAt: '2026-05-27T19:41:18.253Z' ---- -## Reason -Curated extracted facts from RLM context - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-27T19:41:18.110Z - -## Narrative -### Structure -Facts grouped under animate - -## Facts -- **animate**: `animate [target]` is an Enhance command that adds purposeful animations and motion. diff --git a/.brv/context-tree/facts/extracted/animation_restrictions.md b/.brv/context-tree/facts/extracted/animation_restrictions.md deleted file mode 100644 index 6804c2db..00000000 --- a/.brv/context-tree/facts/extracted/animation_restrictions.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Animation restrictions -summary: Extracted facts about Animation restrictions -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T19:41:18.203Z' -updatedAt: '2026-05-27T19:41:18.203Z' ---- -## Reason -Curated extracted facts from RLM context - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-27T19:41:18.110Z - -## Narrative -### Structure -Facts grouped under Animation restrictions - -## Facts -- **Animation restrictions**: Do not animate CSS layout properties. diff --git a/.brv/context-tree/facts/extracted/assistant.md b/.brv/context-tree/facts/extracted/assistant.md deleted file mode 100644 index b9056206..00000000 --- a/.brv/context-tree/facts/extracted/assistant.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: assistant -summary: Extracted facts about assistant -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T09:24:46.822Z' -updatedAt: '2026-05-28T09:24:46.822Z' ---- -## Reason -Curated extracted facts from RLM context - -## Raw Concept -**Task:** -Extract facts - -**Timestamp:** 2026-05-28T09:24:46.821Z - -## Narrative -### Highlights -Facts related to assistant - -## Facts -- **assistant**: I'm using Subagent-Driven Development to implement all audit fixes continuously. diff --git a/.brv/context-tree/facts/extracted/audit.md b/.brv/context-tree/facts/extracted/audit.md deleted file mode 100644 index 75fce1ed..00000000 --- a/.brv/context-tree/facts/extracted/audit.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Audit -summary: Extracted facts about audit -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T19:41:18.239Z' -updatedAt: '2026-05-27T19:41:18.239Z' ---- -## Reason -Curated extracted facts from RLM context - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-27T19:41:18.110Z - -## Narrative -### Structure -Facts grouped under audit - -## Facts -- **audit**: `audit [target]` is an Evaluate command that performs technical quality checks such as accessibility, performance, and responsiveness. diff --git a/.brv/context-tree/facts/extracted/bolder.md b/.brv/context-tree/facts/extracted/bolder.md deleted file mode 100644 index 00e67704..00000000 --- a/.brv/context-tree/facts/extracted/bolder.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Bolder -summary: Extracted facts about bolder -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T19:41:18.242Z' -updatedAt: '2026-05-27T19:41:18.242Z' ---- -## Reason -Curated extracted facts from RLM context - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-27T19:41:18.110Z - -## Narrative -### Structure -Facts grouped under bolder - -## Facts -- **bolder**: `bolder [target]` is a Refine command that amplifies safe or bland designs. diff --git a/.brv/context-tree/facts/extracted/browser_console.md b/.brv/context-tree/facts/extracted/browser_console.md deleted file mode 100644 index 9df61b96..00000000 --- a/.brv/context-tree/facts/extracted/browser_console.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Browser console -summary: Extracted facts about Browser console -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T19:41:18.280Z' -updatedAt: '2026-05-27T19:41:18.280Z' ---- -## Reason -Curated extracted facts from RLM context - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-27T19:41:18.110Z - -## Narrative -### Structure -Facts grouped under Browser console - -## Facts -- **Browser console**: Browser console checked, 0 errors and 0 warnings. diff --git a/.brv/context-tree/facts/extracted/browser_preview.md b/.brv/context-tree/facts/extracted/browser_preview.md deleted file mode 100644 index 90f9847b..00000000 --- a/.brv/context-tree/facts/extracted/browser_preview.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Browser preview -summary: Extracted facts about Browser preview -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T19:41:18.279Z' -updatedAt: '2026-05-27T19:41:18.279Z' ---- -## Reason -Curated extracted facts from RLM context - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-27T19:41:18.110Z - -## Narrative -### Structure -Facts grouped under Browser preview - -## Facts -- **Browser preview**: Browser preview checked at desktop and mobile sizes. diff --git a/.brv/context-tree/facts/extracted/browser_testing.md b/.brv/context-tree/facts/extracted/browser_testing.md deleted file mode 100644 index 32c8ef50..00000000 --- a/.brv/context-tree/facts/extracted/browser_testing.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Browser testing -summary: Extracted facts about browser testing -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T19:41:18.278Z' -updatedAt: '2026-05-27T19:41:18.278Z' ---- -## Reason -Curated extracted facts from RLM context - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-27T19:41:18.110Z - -## Narrative -### Structure -Facts grouped under browser testing - -## Facts -- **browser testing**: Browser preview was checked at desktop and mobile sizes with no errors or warnings in the console. diff --git a/.brv/context-tree/facts/extracted/build_process.md b/.brv/context-tree/facts/extracted/build_process.md deleted file mode 100644 index 607691a9..00000000 --- a/.brv/context-tree/facts/extracted/build_process.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Build process -summary: Extracted facts about build process -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T19:41:18.276Z' -updatedAt: '2026-05-27T19:41:18.276Z' ---- -## Reason -Curated extracted facts from RLM context - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-27T19:41:18.110Z - -## Narrative -### Structure -Facts grouped under build process - -## Facts -- **build process**: `pnpm --filter @caplets/landing build` passed. diff --git a/.brv/context-tree/facts/extracted/card_usage.md b/.brv/context-tree/facts/extracted/card_usage.md deleted file mode 100644 index 160c8373..00000000 --- a/.brv/context-tree/facts/extracted/card_usage.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Card usage -summary: Extracted facts about Card usage -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T19:41:18.197Z' -updatedAt: '2026-05-27T19:41:18.197Z' ---- -## Reason -Curated extracted facts from RLM context - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-27T19:41:18.110Z - -## Narrative -### Structure -Facts grouped under Card usage - -## Facts -- **Card usage**: Cards should only be used when they are the best affordance; nested cards are always wrong. diff --git a/.brv/context-tree/facts/extracted/chroma_adjustment.md b/.brv/context-tree/facts/extracted/chroma_adjustment.md deleted file mode 100644 index 4e649f90..00000000 --- a/.brv/context-tree/facts/extracted/chroma_adjustment.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Chroma adjustment -summary: Extracted facts about Chroma adjustment -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T19:41:18.145Z' -updatedAt: '2026-05-27T19:41:18.145Z' ---- -## Reason -Curated extracted facts from RLM context - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-27T19:41:18.110Z - -## Narrative -### Structure -Facts grouped under Chroma adjustment - -## Facts -- **Chroma adjustment**: Reduce chroma as lightness approaches 0 or 100 because high chroma at extremes looks garish. diff --git a/.brv/context-tree/facts/extracted/clarify.md b/.brv/context-tree/facts/extracted/clarify.md deleted file mode 100644 index e9261a3f..00000000 --- a/.brv/context-tree/facts/extracted/clarify.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Clarify -summary: Extracted facts about clarify -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T19:41:18.261Z' -updatedAt: '2026-05-27T19:41:18.261Z' ---- -## Reason -Curated extracted facts from RLM context - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-27T19:41:18.110Z - -## Narrative -### Structure -Facts grouped under clarify - -## Facts -- **clarify**: `clarify [target]` is a Fix command that improves UX copy, labels, and error messages. diff --git a/.brv/context-tree/facts/extracted/color_specification.md b/.brv/context-tree/facts/extracted/color_specification.md deleted file mode 100644 index 6ae475eb..00000000 --- a/.brv/context-tree/facts/extracted/color_specification.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Color specification -summary: Extracted facts about Color specification -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T19:41:18.142Z' -updatedAt: '2026-05-27T19:41:18.142Z' ---- -## Reason -Curated extracted facts from RLM context - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-27T19:41:18.110Z - -## Narrative -### Structure -Facts grouped under Color specification - -## Facts -- **Color specification**: Use OKLCH for colors. diff --git a/.brv/context-tree/facts/extracted/colorize.md b/.brv/context-tree/facts/extracted/colorize.md deleted file mode 100644 index f861e0e1..00000000 --- a/.brv/context-tree/facts/extracted/colorize.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Colorize -summary: Extracted facts about colorize -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T19:41:18.255Z' -updatedAt: '2026-05-27T19:41:18.255Z' ---- -## Reason -Curated extracted facts from RLM context - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-27T19:41:18.110Z - -## Narrative -### Structure -Facts grouped under colorize - -## Facts -- **colorize**: `colorize [target]` is an Enhance command that adds strategic color to monochromatic UIs. diff --git a/.brv/context-tree/facts/extracted/committed_strategy.md b/.brv/context-tree/facts/extracted/committed_strategy.md deleted file mode 100644 index ddc2410f..00000000 --- a/.brv/context-tree/facts/extracted/committed_strategy.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Committed strategy -summary: Extracted facts about Committed strategy -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T19:41:18.180Z' -updatedAt: '2026-05-27T19:41:18.180Z' ---- -## Reason -Curated extracted facts from RLM context - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-27T19:41:18.110Z - -## Narrative -### Structure -Facts grouped under Committed strategy - -## Facts -- **Committed strategy**: Committed strategy uses one saturated color covering 30–60% of surface. diff --git a/.brv/context-tree/facts/extracted/copy_economy.md b/.brv/context-tree/facts/extracted/copy_economy.md deleted file mode 100644 index f4961bb0..00000000 --- a/.brv/context-tree/facts/extracted/copy_economy.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Copy economy -summary: Extracted facts about Copy economy -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T19:41:18.222Z' -updatedAt: '2026-05-27T19:41:18.222Z' ---- -## Reason -Curated extracted facts from RLM context - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-27T19:41:18.110Z - -## Narrative -### Structure -Facts grouped under Copy economy - -## Facts -- **Copy economy**: Every word in copy must earn its place; no restated headings or intros that repeat the title. diff --git a/.brv/context-tree/facts/extracted/craft.md b/.brv/context-tree/facts/extracted/craft.md deleted file mode 100644 index c6a3c943..00000000 --- a/.brv/context-tree/facts/extracted/craft.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Craft -summary: Extracted facts about craft -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T19:41:18.225Z' -updatedAt: '2026-05-27T19:41:18.225Z' ---- -## Reason -Curated extracted facts from RLM context - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-27T19:41:18.110Z - -## Narrative -### Structure -Facts grouped under craft - -## Facts -- **craft**: `craft [feature]` is a Build command that shapes then builds a feature end-to-end. diff --git a/.brv/context-tree/facts/extracted/critique.md b/.brv/context-tree/facts/extracted/critique.md deleted file mode 100644 index 42b39563..00000000 --- a/.brv/context-tree/facts/extracted/critique.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Critique -summary: Extracted facts about critique -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T19:41:18.238Z' -updatedAt: '2026-05-27T19:41:18.238Z' ---- -## Reason -Curated extracted facts from RLM context - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-27T19:41:18.110Z - -## Narrative -### Structure -Facts grouped under critique - -## Facts -- **critique**: `critique [target]` is an Evaluate command that performs a UX design review with heuristic scoring. diff --git a/.brv/context-tree/facts/extracted/delight.md b/.brv/context-tree/facts/extracted/delight.md deleted file mode 100644 index 975c47e8..00000000 --- a/.brv/context-tree/facts/extracted/delight.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Delight -summary: Extracted facts about delight -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T19:41:18.259Z' -updatedAt: '2026-05-27T19:41:18.259Z' ---- -## Reason -Curated extracted facts from RLM context - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-27T19:41:18.110Z - -## Narrative -### Structure -Facts grouped under delight - -## Facts -- **delight**: `delight [target]` is an Enhance command that adds personality and memorable touches. diff --git a/.brv/context-tree/facts/extracted/design_md.md b/.brv/context-tree/facts/extracted/design_md.md deleted file mode 100644 index bf148ce5..00000000 --- a/.brv/context-tree/facts/extracted/design_md.md +++ /dev/null @@ -1,25 +0,0 @@ ---- -title: DESIGN.md -summary: Extracted facts about DESIGN.md -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T19:41:18.133Z' -updatedAt: '2026-05-27T19:41:18.133Z' ---- -## Reason -Curated extracted facts from RLM context - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-27T19:41:18.110Z - -## Narrative -### Structure -Facts grouped under DESIGN.md - -## Facts -- **DESIGN.md**: DESIGN.md is optional, strongly recommended. -- **DESIGN.md**: If DESIGN.md is missing, nudge the user once per session to run {{command_prefix}}impeccable document. diff --git a/.brv/context-tree/facts/extracted/design_process.md b/.brv/context-tree/facts/extracted/design_process.md deleted file mode 100644 index 6a2b2c01..00000000 --- a/.brv/context-tree/facts/extracted/design_process.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Design process -summary: Extracted facts about Design process -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T19:41:18.174Z' -updatedAt: '2026-05-27T19:41:18.174Z' ---- -## Reason -Curated extracted facts from RLM context - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-27T19:41:18.110Z - -## Narrative -### Structure -Facts grouped under Design process - -## Facts -- **Design process**: Pick a color strategy before picking colors, with four steps on the commitment axis. diff --git a/.brv/context-tree/facts/extracted/design_task.md b/.brv/context-tree/facts/extracted/design_task.md deleted file mode 100644 index d4a621a3..00000000 --- a/.brv/context-tree/facts/extracted/design_task.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Design task -summary: Extracted facts about design task -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T19:41:18.139Z' -updatedAt: '2026-05-27T19:41:18.139Z' ---- -## Reason -Curated extracted facts from RLM context - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-27T19:41:18.110Z - -## Narrative -### Structure -Facts grouped under design task - -## Facts -- **design task**: Every design task is either brand (marketing, landing, campaign, long-form content, portfolio) or product (app UI, admin, dashboard, tool). diff --git a/.brv/context-tree/facts/extracted/distill.md b/.brv/context-tree/facts/extracted/distill.md deleted file mode 100644 index f5d9a543..00000000 --- a/.brv/context-tree/facts/extracted/distill.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Distill -summary: Extracted facts about distill -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T19:41:18.245Z' -updatedAt: '2026-05-27T19:41:18.245Z' ---- -## Reason -Curated extracted facts from RLM context - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-27T19:41:18.110Z - -## Narrative -### Structure -Facts grouped under distill - -## Facts -- **distill**: `distill [target]` is a Refine command that strips to essence and removes complexity. diff --git a/.brv/context-tree/facts/extracted/document.md b/.brv/context-tree/facts/extracted/document.md deleted file mode 100644 index 192bb3e7..00000000 --- a/.brv/context-tree/facts/extracted/document.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Document -summary: Extracted facts about document -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T19:41:18.231Z' -updatedAt: '2026-05-27T19:41:18.231Z' ---- -## Reason -Curated extracted facts from RLM context - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-27T19:41:18.110Z - -## Narrative -### Structure -Facts grouped under document - -## Facts -- **document**: `document` is a Build command that generates DESIGN.md from existing project code. diff --git a/.brv/context-tree/facts/extracted/drenched_strategy.md b/.brv/context-tree/facts/extracted/drenched_strategy.md deleted file mode 100644 index 716814e9..00000000 --- a/.brv/context-tree/facts/extracted/drenched_strategy.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Drenched strategy -summary: Extracted facts about Drenched strategy -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T19:41:18.188Z' -updatedAt: '2026-05-27T19:41:18.188Z' ---- -## Reason -Curated extracted facts from RLM context - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-27T19:41:18.110Z - -## Narrative -### Structure -Facts grouped under Drenched strategy - -## Facts -- **Drenched strategy**: Drenched strategy makes the surface the color. diff --git a/.brv/context-tree/facts/extracted/easing_functions.md b/.brv/context-tree/facts/extracted/easing_functions.md deleted file mode 100644 index 9e2facd1..00000000 --- a/.brv/context-tree/facts/extracted/easing_functions.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Easing functions -summary: Extracted facts about Easing functions -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T19:41:18.205Z' -updatedAt: '2026-05-27T19:41:18.205Z' ---- -## Reason -Curated extracted facts from RLM context - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-27T19:41:18.110Z - -## Narrative -### Structure -Facts grouped under Easing functions - -## Facts -- **Easing functions**: Use ease-out exponential curves (ease-out-quart, quint, expo) for motion; no bounce or elastic. diff --git a/.brv/context-tree/facts/extracted/extract.md b/.brv/context-tree/facts/extracted/extract.md deleted file mode 100644 index a0cc76dd..00000000 --- a/.brv/context-tree/facts/extracted/extract.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Extract -summary: Extracted facts about extract -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T19:41:18.237Z' -updatedAt: '2026-05-27T19:41:18.237Z' ---- -## Reason -Curated extracted facts from RLM context - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-27T19:41:18.110Z - -## Narrative -### Structure -Facts grouped under extract - -## Facts -- **extract**: `extract [target]` is a Build command that pulls reusable tokens and components into a design system. diff --git a/.brv/context-tree/facts/extracted/full_palette_strategy.md b/.brv/context-tree/facts/extracted/full_palette_strategy.md deleted file mode 100644 index 1af2f5f8..00000000 --- a/.brv/context-tree/facts/extracted/full_palette_strategy.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Full palette strategy -summary: Extracted facts about Full palette strategy -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T19:41:18.182Z' -updatedAt: '2026-05-27T19:41:18.182Z' ---- -## Reason -Curated extracted facts from RLM context - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-27T19:41:18.110Z - -## Narrative -### Structure -Facts grouped under Full palette strategy - -## Facts -- **Full palette strategy**: Full palette uses 3–4 named roles, each used deliberately. diff --git a/.brv/context-tree/facts/extracted/glassmorphism.md b/.brv/context-tree/facts/extracted/glassmorphism.md deleted file mode 100644 index fb3696ee..00000000 --- a/.brv/context-tree/facts/extracted/glassmorphism.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Glassmorphism -summary: Extracted facts about Glassmorphism -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T19:41:18.210Z' -updatedAt: '2026-05-27T19:41:18.210Z' ---- -## Reason -Curated extracted facts from RLM context - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-27T19:41:18.110Z - -## Narrative -### Structure -Facts grouped under Glassmorphism - -## Facts -- **Glassmorphism**: Glassmorphism as a default style is prohibited; use only rarely and purposefully. diff --git a/.brv/context-tree/facts/extracted/gradient_text.md b/.brv/context-tree/facts/extracted/gradient_text.md deleted file mode 100644 index 27df6f64..00000000 --- a/.brv/context-tree/facts/extracted/gradient_text.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Gradient text -summary: Extracted facts about Gradient text -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T19:41:18.208Z' -updatedAt: '2026-05-27T19:41:18.208Z' ---- -## Reason -Curated extracted facts from RLM context - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-27T19:41:18.110Z - -## Narrative -### Structure -Facts grouped under Gradient text - -## Facts -- **Gradient text**: Gradient text using background‑clip: text with a gradient background is prohibited. diff --git a/.brv/context-tree/facts/extracted/harden.md b/.brv/context-tree/facts/extracted/harden.md deleted file mode 100644 index 6ab11c83..00000000 --- a/.brv/context-tree/facts/extracted/harden.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Harden -summary: Extracted facts about harden -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T19:41:18.246Z' -updatedAt: '2026-05-27T19:41:18.246Z' ---- -## Reason -Curated extracted facts from RLM context - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-27T19:41:18.110Z - -## Narrative -### Structure -Facts grouped under harden - -## Facts -- **harden**: `harden [target]` is a Refine command that makes a product production‑ready, handling errors, i18n, and edge cases. diff --git a/.brv/context-tree/facts/extracted/hero_metric_template.md b/.brv/context-tree/facts/extracted/hero_metric_template.md deleted file mode 100644 index 5f4cae19..00000000 --- a/.brv/context-tree/facts/extracted/hero_metric_template.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Hero‑metric template -summary: Extracted facts about Hero‑metric template -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T19:41:18.211Z' -updatedAt: '2026-05-27T19:41:18.211Z' ---- -## Reason -Curated extracted facts from RLM context - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-27T19:41:18.110Z - -## Narrative -### Structure -Facts grouped under Hero‑metric template - -## Facts -- **Hero‑metric template**: The hero‑metric template (big number, small label, supporting stats, gradient accent) is a SaaS cliché and prohibited. diff --git a/.brv/context-tree/facts/extracted/identical_card_grids.md b/.brv/context-tree/facts/extracted/identical_card_grids.md deleted file mode 100644 index 2ecc57f2..00000000 --- a/.brv/context-tree/facts/extracted/identical_card_grids.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Identical card grids -summary: Extracted facts about Identical card grids -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T19:41:18.213Z' -updatedAt: '2026-05-27T19:41:18.213Z' ---- -## Reason -Curated extracted facts from RLM context - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-27T19:41:18.110Z - -## Narrative -### Structure -Facts grouped under Identical card grids - -## Facts -- **Identical card grids**: Identical card grids with repeated icon + heading + text are prohibited. diff --git a/.brv/context-tree/facts/extracted/landing_page.md b/.brv/context-tree/facts/extracted/landing_page.md deleted file mode 100644 index 40156274..00000000 --- a/.brv/context-tree/facts/extracted/landing_page.md +++ /dev/null @@ -1,30 +0,0 @@ ---- -title: Landing page -summary: Extracted facts about landing page -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T19:41:18.275Z' -updatedAt: '2026-05-27T19:41:18.275Z' ---- -## Reason -Curated extracted facts from RLM context - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-27T19:41:18.110Z - -## Narrative -### Structure -Facts grouped under landing page - -## Facts -- **landing page**: Hardening improvements added a `theme-color` meta tag to the landing page. -- **landing page**: Reveal animation was made progressive: content is visible by default and hidden only after JavaScript confirms IntersectionObserver support. -- **landing page**: An IntersectionObserver fallback was added so content never stays invisible on older browsers. -- **landing page**: Long-text resilience was improved with `min-width: 0`, `overflow-wrap`, and safer grid columns. -- **landing page**: Header, nav, proof grid, terminal, capability cards, and install layout were hardened against overflow. -- **landing page**: Safe-area padding was added for notched mobile devices. -- **landing page**: The funky npm mark was replaced with a cleaner custom npm-style icon. diff --git a/.brv/context-tree/facts/extracted/layout.md b/.brv/context-tree/facts/extracted/layout.md deleted file mode 100644 index 92828ffa..00000000 --- a/.brv/context-tree/facts/extracted/layout.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Layout -summary: Extracted facts about layout -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T19:41:18.257Z' -updatedAt: '2026-05-27T19:41:18.257Z' ---- -## Reason -Curated extracted facts from RLM context - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-27T19:41:18.110Z - -## Narrative -### Structure -Facts grouped under layout - -## Facts -- **layout**: `layout [target]` is an Enhance command that fixes spacing, rhythm, and visual hierarchy. diff --git a/.brv/context-tree/facts/extracted/line_length.md b/.brv/context-tree/facts/extracted/line_length.md deleted file mode 100644 index 2600d1a6..00000000 --- a/.brv/context-tree/facts/extracted/line_length.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Line length -summary: Extracted facts about Line length -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T19:41:18.192Z' -updatedAt: '2026-05-27T19:41:18.192Z' ---- -## Reason -Curated extracted facts from RLM context - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-27T19:41:18.110Z - -## Narrative -### Structure -Facts grouped under Line length - -## Facts -- **Line length**: Cap body line length at 65–75 characters. diff --git a/.brv/context-tree/facts/extracted/live.md b/.brv/context-tree/facts/extracted/live.md deleted file mode 100644 index e221c30d..00000000 --- a/.brv/context-tree/facts/extracted/live.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Live -summary: Extracted facts about live -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T19:41:18.270Z' -updatedAt: '2026-05-27T19:41:18.270Z' ---- -## Reason -Curated extracted facts from RLM context - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-27T19:41:18.110Z - -## Narrative -### Structure -Facts grouped under live - -## Facts -- **live**: `live` is an Iterate command that provides a visual variant mode, allowing selection of elements in the browser to generate alternatives. diff --git a/.brv/context-tree/facts/extracted/load_context_mjs_output.md b/.brv/context-tree/facts/extracted/load_context_mjs_output.md deleted file mode 100644 index 57eb0668..00000000 --- a/.brv/context-tree/facts/extracted/load_context_mjs_output.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Load-context.mjs output -summary: Extracted facts about load-context.mjs output -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T19:41:18.137Z' -updatedAt: '2026-05-27T19:41:18.137Z' ---- -## Reason -Curated extracted facts from RLM context - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-27T19:41:18.110Z - -## Narrative -### Structure -Facts grouped under load-context.mjs output - -## Facts -- **load-context.mjs output**: Never pipe the context loader output through head, tail, grep, or jq. diff --git a/.brv/context-tree/facts/extracted/modal_usage.md b/.brv/context-tree/facts/extracted/modal_usage.md deleted file mode 100644 index db95e15b..00000000 --- a/.brv/context-tree/facts/extracted/modal_usage.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Modal usage -summary: Extracted facts about Modal usage -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T19:41:18.215Z' -updatedAt: '2026-05-27T19:41:18.215Z' ---- -## Reason -Curated extracted facts from RLM context - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-27T19:41:18.110Z - -## Narrative -### Structure -Facts grouped under Modal usage - -## Facts -- **Modal usage**: Modals should not be the first design thought; exhaust inline or progressive alternatives first. diff --git a/.brv/context-tree/facts/extracted/neutral_tinting.md b/.brv/context-tree/facts/extracted/neutral_tinting.md deleted file mode 100644 index b9ea75d5..00000000 --- a/.brv/context-tree/facts/extracted/neutral_tinting.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Neutral tinting -summary: Extracted facts about Neutral tinting -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T19:41:18.160Z' -updatedAt: '2026-05-27T19:41:18.160Z' ---- -## Reason -Curated extracted facts from RLM context - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-27T19:41:18.110Z - -## Narrative -### Structure -Facts grouped under Neutral tinting - -## Facts -- **Neutral tinting**: Never use #000 or #fff; tint every neutral toward the brand hue with chroma 0.005–0.01. diff --git a/.brv/context-tree/facts/extracted/onboard.md b/.brv/context-tree/facts/extracted/onboard.md deleted file mode 100644 index 00aa08d3..00000000 --- a/.brv/context-tree/facts/extracted/onboard.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Onboard -summary: Extracted facts about onboard -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T19:41:18.252Z' -updatedAt: '2026-05-27T19:41:18.252Z' ---- -## Reason -Curated extracted facts from RLM context - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-27T19:41:18.110Z - -## Narrative -### Structure -Facts grouped under onboard - -## Facts -- **onboard**: `onboard [target]` is a Refine command that designs first‑run flows, empty states, and activation experiences. diff --git a/.brv/context-tree/facts/extracted/optimize.md b/.brv/context-tree/facts/extracted/optimize.md deleted file mode 100644 index 500a0caf..00000000 --- a/.brv/context-tree/facts/extracted/optimize.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Optimize -summary: Extracted facts about optimize -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T19:41:18.268Z' -updatedAt: '2026-05-27T19:41:18.269Z' ---- -## Reason -Curated extracted facts from RLM context - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-27T19:41:18.110Z - -## Narrative -### Structure -Facts grouped under optimize - -## Facts -- **optimize**: `optimize [target]` is a Fix command that diagnoses and fixes UI performance issues. diff --git a/.brv/context-tree/facts/extracted/overdrive.md b/.brv/context-tree/facts/extracted/overdrive.md deleted file mode 100644 index 00fdc38c..00000000 --- a/.brv/context-tree/facts/extracted/overdrive.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Overdrive -summary: Extracted facts about overdrive -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T19:41:18.260Z' -updatedAt: '2026-05-27T19:41:18.260Z' ---- -## Reason -Curated extracted facts from RLM context - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-27T19:41:18.110Z - -## Narrative -### Structure -Facts grouped under overdrive - -## Facts -- **overdrive**: `overdrive [target]` is an Enhance command that pushes past conventional limits. diff --git a/.brv/context-tree/facts/extracted/pin.md b/.brv/context-tree/facts/extracted/pin.md deleted file mode 100644 index d1d8485a..00000000 --- a/.brv/context-tree/facts/extracted/pin.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Pin -summary: Extracted facts about Pin -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T19:41:18.272Z' -updatedAt: '2026-05-27T19:41:18.272Z' ---- -## Reason -Curated extracted facts from RLM context - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-27T19:41:18.110Z - -## Narrative -### Structure -Facts grouped under Pin - -## Facts -- **Pin**: Pin creates a standalone shortcut so `{{command_prefix}}` invokes `{{command_prefix}}impeccable ` directly. diff --git a/.brv/context-tree/facts/extracted/pin_mjs_script.md b/.brv/context-tree/facts/extracted/pin_mjs_script.md deleted file mode 100644 index a0625bc3..00000000 --- a/.brv/context-tree/facts/extracted/pin_mjs_script.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Pin.mjs script -summary: Extracted facts about pin.mjs script -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T19:41:18.274Z' -updatedAt: '2026-05-27T19:41:18.274Z' ---- -## Reason -Curated extracted facts from RLM context - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-27T19:41:18.110Z - -## Narrative -### Structure -Facts grouped under pin.mjs script - -## Facts -- **pin.mjs script**: The pin/unpin script writes to every harness directory present in the project. diff --git a/.brv/context-tree/facts/extracted/pin_unpin.md b/.brv/context-tree/facts/extracted/pin_unpin.md deleted file mode 100644 index bd14ed37..00000000 --- a/.brv/context-tree/facts/extracted/pin_unpin.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Pin/unpin -summary: Extracted facts about pin/unpin -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T19:41:18.271Z' -updatedAt: '2026-05-27T19:41:18.271Z' ---- -## Reason -Curated extracted facts from RLM context - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-27T19:41:18.110Z - -## Narrative -### Structure -Facts grouped under pin/unpin - -## Facts -- **pin/unpin**: `pin ` and `unpin ` are management commands for pinning and unpinning other commands. diff --git a/.brv/context-tree/facts/extracted/polish.md b/.brv/context-tree/facts/extracted/polish.md deleted file mode 100644 index 2abdccfc..00000000 --- a/.brv/context-tree/facts/extracted/polish.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Polish -summary: Extracted facts about polish -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T19:41:18.240Z' -updatedAt: '2026-05-27T19:41:18.240Z' ---- -## Reason -Curated extracted facts from RLM context - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-27T19:41:18.110Z - -## Narrative -### Structure -Facts grouped under polish - -## Facts -- **polish**: `polish [target]` is a Refine command that provides a final quality pass before shipping. diff --git a/.brv/context-tree/facts/extracted/product_md.md b/.brv/context-tree/facts/extracted/product_md.md deleted file mode 100644 index bdf7f681..00000000 --- a/.brv/context-tree/facts/extracted/product_md.md +++ /dev/null @@ -1,25 +0,0 @@ ---- -title: PRODUCT.md -summary: Extracted facts about PRODUCT.md -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T19:41:18.125Z' -updatedAt: '2026-05-27T19:41:18.125Z' ---- -## Reason -Curated extracted facts from RLM context - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-27T19:41:18.110Z - -## Narrative -### Structure -Facts grouped under PRODUCT.md - -## Facts -- **PRODUCT.md**: PRODUCT.md is required. -- **PRODUCT.md**: If PRODUCT.md is missing, empty, or contains placeholder markers ([TODO] or less than 200 characters), run {{command_prefix}}impeccable teach. diff --git a/.brv/context-tree/facts/extracted/punctuation.md b/.brv/context-tree/facts/extracted/punctuation.md deleted file mode 100644 index 32ebda5a..00000000 --- a/.brv/context-tree/facts/extracted/punctuation.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Punctuation -summary: Extracted facts about Punctuation -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T19:41:18.223Z' -updatedAt: '2026-05-27T19:41:18.223Z' ---- -## Reason -Curated extracted facts from RLM context - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-27T19:41:18.110Z - -## Narrative -### Structure -Facts grouped under Punctuation - -## Facts -- **Punctuation**: Em dashes are not allowed; use commas, colons, semicolons, periods, or parentheses instead. diff --git a/.brv/context-tree/facts/extracted/quieter.md b/.brv/context-tree/facts/extracted/quieter.md deleted file mode 100644 index c98d4367..00000000 --- a/.brv/context-tree/facts/extracted/quieter.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Quieter -summary: Extracted facts about quieter -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T19:41:18.243Z' -updatedAt: '2026-05-27T19:41:18.243Z' ---- -## Reason -Curated extracted facts from RLM context - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-27T19:41:18.110Z - -## Narrative -### Structure -Facts grouped under quieter - -## Facts -- **quieter**: `quieter [target]` is a Refine command that tones down aggressive or overstimulating designs. diff --git a/.brv/context-tree/facts/extracted/register_identification.md b/.brv/context-tree/facts/extracted/register_identification.md deleted file mode 100644 index 7788c0f2..00000000 --- a/.brv/context-tree/facts/extracted/register_identification.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Register identification -summary: Extracted facts about register identification -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T19:41:18.140Z' -updatedAt: '2026-05-27T19:41:18.140Z' ---- -## Reason -Curated extracted facts from RLM context - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-27T19:41:18.110Z - -## Narrative -### Structure -Facts grouped under register identification - -## Facts -- **register identification**: Identify the register before designing, using priority: (1) cue in the task, (2) surface in focus, (3) register field in PRODUCT.md. diff --git a/.brv/context-tree/facts/extracted/restrained_strategy.md b/.brv/context-tree/facts/extracted/restrained_strategy.md deleted file mode 100644 index 165af09c..00000000 --- a/.brv/context-tree/facts/extracted/restrained_strategy.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Restrained strategy -summary: Extracted facts about Restrained strategy -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T19:41:18.178Z' -updatedAt: '2026-05-27T19:41:18.178Z' ---- -## Reason -Curated extracted facts from RLM context - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-27T19:41:18.110Z - -## Narrative -### Structure -Facts grouped under Restrained strategy - -## Facts -- **Restrained strategy**: Restrained strategy uses tinted neutrals plus one accent ≤10% of surface. diff --git a/.brv/context-tree/facts/extracted/screenshots.md b/.brv/context-tree/facts/extracted/screenshots.md deleted file mode 100644 index 1e08a589..00000000 --- a/.brv/context-tree/facts/extracted/screenshots.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Screenshots -summary: Extracted facts about Screenshots -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T19:41:18.285Z' -updatedAt: '2026-05-27T19:41:18.285Z' ---- -## Reason -Curated extracted facts from RLM context - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-27T19:41:18.110Z - -## Narrative -### Structure -Facts grouped under Screenshots - -## Facts -- **Screenshots**: Screenshots captured: caplets-harden-desktop.png and caplets-harden-mobile.png. diff --git a/.brv/context-tree/facts/extracted/shape.md b/.brv/context-tree/facts/extracted/shape.md deleted file mode 100644 index 79244b88..00000000 --- a/.brv/context-tree/facts/extracted/shape.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Shape -summary: Extracted facts about shape -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T19:41:18.226Z' -updatedAt: '2026-05-27T19:41:18.226Z' ---- -## Reason -Curated extracted facts from RLM context - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-27T19:41:18.110Z - -## Narrative -### Structure -Facts grouped under shape - -## Facts -- **shape**: `shape [feature]` is a Build command that plans UX/UI before writing code. diff --git a/.brv/context-tree/facts/extracted/side_stripe_borders.md b/.brv/context-tree/facts/extracted/side_stripe_borders.md deleted file mode 100644 index 0fe0702a..00000000 --- a/.brv/context-tree/facts/extracted/side_stripe_borders.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Side‑stripe borders -summary: Extracted facts about Side‑stripe borders -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T19:41:18.206Z' -updatedAt: '2026-05-27T19:41:18.206Z' ---- -## Reason -Curated extracted facts from RLM context - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-27T19:41:18.110Z - -## Narrative -### Structure -Facts grouped under Side‑stripe borders - -## Facts -- **Side‑stripe borders**: Side‑stripe borders greater than 1 px as colored accents are prohibited. diff --git a/.brv/context-tree/facts/extracted/spacing.md b/.brv/context-tree/facts/extracted/spacing.md deleted file mode 100644 index f3bebad6..00000000 --- a/.brv/context-tree/facts/extracted/spacing.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Spacing -summary: Extracted facts about Spacing -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T19:41:18.195Z' -updatedAt: '2026-05-27T19:41:18.195Z' ---- -## Reason -Curated extracted facts from RLM context - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-27T19:41:18.110Z - -## Narrative -### Structure -Facts grouped under Spacing - -## Facts -- **Spacing**: Vary spacing for rhythm; same padding everywhere creates monotony. diff --git a/.brv/context-tree/facts/extracted/teach.md b/.brv/context-tree/facts/extracted/teach.md deleted file mode 100644 index 8440c928..00000000 --- a/.brv/context-tree/facts/extracted/teach.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Teach -summary: Extracted facts about teach -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T19:41:18.229Z' -updatedAt: '2026-05-27T19:41:18.229Z' ---- -## Reason -Curated extracted facts from RLM context - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-27T19:41:18.110Z - -## Narrative -### Structure -Facts grouped under teach - -## Facts -- **teach**: `teach` is a Build command that sets up PRODUCT.md and DESIGN.md context. diff --git a/.brv/context-tree/facts/extracted/theme_selection.md b/.brv/context-tree/facts/extracted/theme_selection.md deleted file mode 100644 index d5eb1e4e..00000000 --- a/.brv/context-tree/facts/extracted/theme_selection.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Theme selection -summary: Extracted facts about Theme selection -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T19:41:18.190Z' -updatedAt: '2026-05-27T19:41:18.190Z' ---- -## Reason -Curated extracted facts from RLM context - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-27T19:41:18.110Z - -## Narrative -### Structure -Facts grouped under Theme selection - -## Facts -- **Theme selection**: Dark vs. light theme is never a default; must be chosen based on physical scene description. diff --git a/.brv/context-tree/facts/extracted/typecheck.md b/.brv/context-tree/facts/extracted/typecheck.md deleted file mode 100644 index 8a1d556b..00000000 --- a/.brv/context-tree/facts/extracted/typecheck.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Typecheck -summary: Extracted facts about typecheck -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T19:41:18.277Z' -updatedAt: '2026-05-27T19:41:18.277Z' ---- -## Reason -Curated extracted facts from RLM context - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-27T19:41:18.110Z - -## Narrative -### Structure -Facts grouped under typecheck - -## Facts -- **typecheck**: `pnpm --filter @caplets/landing typecheck` passed with 0 errors, 0 warnings, and 0 hints. diff --git a/.brv/context-tree/facts/extracted/typeset.md b/.brv/context-tree/facts/extracted/typeset.md deleted file mode 100644 index 085db436..00000000 --- a/.brv/context-tree/facts/extracted/typeset.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Typeset -summary: Extracted facts about typeset -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T19:41:18.256Z' -updatedAt: '2026-05-27T19:41:18.256Z' ---- -## Reason -Curated extracted facts from RLM context - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-27T19:41:18.110Z - -## Narrative -### Structure -Facts grouped under typeset - -## Facts -- **typeset**: `typeset [target]` is an Enhance command that improves typography hierarchy and fonts. diff --git a/.brv/context-tree/facts/extracted/typographic_hierarchy.md b/.brv/context-tree/facts/extracted/typographic_hierarchy.md deleted file mode 100644 index c4a32edf..00000000 --- a/.brv/context-tree/facts/extracted/typographic_hierarchy.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Typographic hierarchy -summary: Extracted facts about Typographic hierarchy -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T19:41:18.193Z' -updatedAt: '2026-05-27T19:41:18.193Z' ---- -## Reason -Curated extracted facts from RLM context - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-27T19:41:18.110Z - -## Narrative -### Structure -Facts grouped under Typographic hierarchy - -## Facts -- **Typographic hierarchy**: Maintain hierarchy through scale and weight contrast with at least 1.25 ratio between steps. diff --git a/.brv/context-tree/facts/extracted/unpin.md b/.brv/context-tree/facts/extracted/unpin.md deleted file mode 100644 index 15487895..00000000 --- a/.brv/context-tree/facts/extracted/unpin.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Unpin -summary: Extracted facts about Unpin -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T19:41:18.273Z' -updatedAt: '2026-05-27T19:41:18.273Z' ---- -## Reason -Curated extracted facts from RLM context - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-27T19:41:18.110Z - -## Narrative -### Structure -Facts grouped under Unpin - -## Facts -- **Unpin**: Unpin removes the shortcut created by Pin. diff --git a/.brv/context-tree/facts/extracted_facts/adapt_command.md b/.brv/context-tree/facts/extracted_facts/adapt_command.md deleted file mode 100644 index 7811abba..00000000 --- a/.brv/context-tree/facts/extracted_facts/adapt_command.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: adapt command -summary: Facts about adapt command -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T14:20:02.617Z' -updatedAt: '2026-05-28T14:20:02.617Z' ---- -## Reason -Curated extracted factual statements - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-28T14:20:02.495Z - -## Narrative -### Structure -Collection of facts for adapt command - -## Facts -- **adapt command**: `adapt [target]` adapts designs for different devices and screen sizes. diff --git a/.brv/context-tree/facts/extracted_facts/animate_command.md b/.brv/context-tree/facts/extracted_facts/animate_command.md deleted file mode 100644 index a794901a..00000000 --- a/.brv/context-tree/facts/extracted_facts/animate_command.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: animate command -summary: Facts about animate command -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T14:20:02.607Z' -updatedAt: '2026-05-28T14:20:02.607Z' ---- -## Reason -Curated extracted factual statements - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-28T14:20:02.495Z - -## Narrative -### Structure -Collection of facts for animate command - -## Facts -- **animate command**: `animate [target]` adds purposeful animations and motion. diff --git a/.brv/context-tree/facts/extracted_facts/animation_constraints.md b/.brv/context-tree/facts/extracted_facts/animation_constraints.md deleted file mode 100644 index 2c992b39..00000000 --- a/.brv/context-tree/facts/extracted_facts/animation_constraints.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Animation constraints -summary: Facts about Animation constraints -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T14:20:02.555Z' -updatedAt: '2026-05-28T14:20:02.555Z' ---- -## Reason -Curated extracted factual statements - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-28T14:20:02.494Z - -## Narrative -### Structure -Collection of facts for Animation constraints - -## Facts -- **Animation constraints**: Do not animate CSS layout properties. diff --git a/.brv/context-tree/facts/extracted_facts/audit_command.md b/.brv/context-tree/facts/extracted_facts/audit_command.md deleted file mode 100644 index 2825ef4a..00000000 --- a/.brv/context-tree/facts/extracted_facts/audit_command.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: audit command -summary: Facts about audit command -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T14:20:02.597Z' -updatedAt: '2026-05-28T14:20:02.597Z' ---- -## Reason -Curated extracted factual statements - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-28T14:20:02.495Z - -## Narrative -### Structure -Collection of facts for audit command - -## Facts -- **audit command**: `audit [target]` conducts technical quality checks (a11y, perf, responsive). diff --git a/.brv/context-tree/facts/extracted_facts/bolder_command.md b/.brv/context-tree/facts/extracted_facts/bolder_command.md deleted file mode 100644 index 45d13ad4..00000000 --- a/.brv/context-tree/facts/extracted_facts/bolder_command.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: bolder command -summary: Facts about bolder command -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T14:20:02.600Z' -updatedAt: '2026-05-28T14:20:02.600Z' ---- -## Reason -Curated extracted factual statements - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-28T14:20:02.495Z - -## Narrative -### Structure -Collection of facts for bolder command - -## Facts -- **bolder command**: `bolder [target]` amplifies safe or bland designs. diff --git a/.brv/context-tree/facts/extracted_facts/border_usage.md b/.brv/context-tree/facts/extracted_facts/border_usage.md deleted file mode 100644 index e965b818..00000000 --- a/.brv/context-tree/facts/extracted_facts/border_usage.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Border usage -summary: Facts about Border usage -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T14:20:02.557Z' -updatedAt: '2026-05-28T14:20:02.557Z' ---- -## Reason -Curated extracted factual statements - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-28T14:20:02.494Z - -## Narrative -### Structure -Collection of facts for Border usage - -## Facts -- **Border usage**: Side-stripe borders greater than 1px as colored accents are prohibited. diff --git a/.brv/context-tree/facts/extracted_facts/browser.md b/.brv/context-tree/facts/extracted_facts/browser.md deleted file mode 100644 index 98bfe933..00000000 --- a/.brv/context-tree/facts/extracted_facts/browser.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: browser -summary: Facts about browser -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T14:20:02.634Z' -updatedAt: '2026-05-28T14:20:02.634Z' ---- -## Reason -Curated extracted factual statements - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-28T14:20:02.495Z - -## Narrative -### Structure -Collection of facts for browser - -## Facts -- **browser**: Browser at 390 × 844 with root font size forced to 200%: no horizontal overflow. [environment] diff --git a/.brv/context-tree/facts/extracted_facts/browser_rendering.md b/.brv/context-tree/facts/extracted_facts/browser_rendering.md deleted file mode 100644 index 208c8bb0..00000000 --- a/.brv/context-tree/facts/extracted_facts/browser_rendering.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Browser rendering -summary: Facts about Browser rendering -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T14:20:02.629Z' -updatedAt: '2026-05-28T14:20:02.629Z' ---- -## Reason -Curated extracted factual statements - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-28T14:20:02.495Z - -## Narrative -### Structure -Collection of facts for Browser rendering - -## Facts -- **Browser rendering**: Browser at `390 × 844` with root font size forced to `200%` shows no horizontal overflow. diff --git a/.brv/context-tree/facts/extracted_facts/build.md b/.brv/context-tree/facts/extracted_facts/build.md deleted file mode 100644 index ff999e91..00000000 --- a/.brv/context-tree/facts/extracted_facts/build.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Build -summary: Facts about Build -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T14:20:02.633Z' -updatedAt: '2026-05-28T14:20:02.633Z' ---- -## Reason -Curated extracted factual statements - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-28T14:20:02.495Z - -## Narrative -### Structure -Collection of facts for Build - -## Facts -- **Build**: `pnpm --filter @caplets/landing build` passed. diff --git a/.brv/context-tree/facts/extracted_facts/card_grid_design.md b/.brv/context-tree/facts/extracted_facts/card_grid_design.md deleted file mode 100644 index b06317ae..00000000 --- a/.brv/context-tree/facts/extracted_facts/card_grid_design.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Card grid design -summary: Facts about Card grid design -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T14:20:02.563Z' -updatedAt: '2026-05-28T14:20:02.563Z' ---- -## Reason -Curated extracted factual statements - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-28T14:20:02.494Z - -## Narrative -### Structure -Collection of facts for Card grid design - -## Facts -- **Card grid design**: Identical card grids with repeated icon, heading, and text are prohibited. diff --git a/.brv/context-tree/facts/extracted_facts/card_usage.md b/.brv/context-tree/facts/extracted_facts/card_usage.md deleted file mode 100644 index a6ae2247..00000000 --- a/.brv/context-tree/facts/extracted_facts/card_usage.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Card usage -summary: Facts about Card usage -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T14:20:02.553Z' -updatedAt: '2026-05-28T14:20:02.553Z' ---- -## Reason -Curated extracted factual statements - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-28T14:20:02.494Z - -## Narrative -### Structure -Collection of facts for Card usage - -## Facts -- **Card usage**: Nested cards are always wrong. diff --git a/.brv/context-tree/facts/extracted_facts/category_reflex_check.md b/.brv/context-tree/facts/extracted_facts/category_reflex_check.md deleted file mode 100644 index f0667ba7..00000000 --- a/.brv/context-tree/facts/extracted_facts/category_reflex_check.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: category-reflex check -summary: Facts about category-reflex check -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T14:20:02.579Z' -updatedAt: '2026-05-28T14:20:02.579Z' ---- -## Reason -Curated extracted factual statements - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-28T14:20:02.494Z - -## Narrative -### Structure -Collection of facts for category-reflex check - -## Facts -- **category-reflex check**: Category-reflex check runs at two altitudes; the second one catches what the first one misses. diff --git a/.brv/context-tree/facts/extracted_facts/clarify_command.md b/.brv/context-tree/facts/extracted_facts/clarify_command.md deleted file mode 100644 index 224e6d45..00000000 --- a/.brv/context-tree/facts/extracted_facts/clarify_command.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: clarify command -summary: Facts about clarify command -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T14:20:02.616Z' -updatedAt: '2026-05-28T14:20:02.616Z' ---- -## Reason -Curated extracted factual statements - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-28T14:20:02.495Z - -## Narrative -### Structure -Collection of facts for clarify command - -## Facts -- **clarify command**: `clarify [target]` improves UX copy, labels, and error messages. diff --git a/.brv/context-tree/facts/extracted_facts/color_model.md b/.brv/context-tree/facts/extracted_facts/color_model.md deleted file mode 100644 index 1a9b6e69..00000000 --- a/.brv/context-tree/facts/extracted_facts/color_model.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Color model -summary: Facts about Color model -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T14:20:02.515Z' -updatedAt: '2026-05-28T14:20:02.515Z' ---- -## Reason -Curated extracted factual statements - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-28T14:20:02.494Z - -## Narrative -### Structure -Collection of facts for Color model - -## Facts -- **Color model**: Use OKLCH for color. diff --git a/.brv/context-tree/facts/extracted_facts/colorize_command.md b/.brv/context-tree/facts/extracted_facts/colorize_command.md deleted file mode 100644 index 01648ef8..00000000 --- a/.brv/context-tree/facts/extracted_facts/colorize_command.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: colorize command -summary: Facts about colorize command -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T14:20:02.608Z' -updatedAt: '2026-05-28T14:20:02.608Z' ---- -## Reason -Curated extracted factual statements - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-28T14:20:02.495Z - -## Narrative -### Structure -Collection of facts for colorize command - -## Facts -- **colorize command**: `colorize [target]` adds strategic color to monochromatic UIs. diff --git a/.brv/context-tree/facts/extracted_facts/committed_strategy.md b/.brv/context-tree/facts/extracted_facts/committed_strategy.md deleted file mode 100644 index b538226d..00000000 --- a/.brv/context-tree/facts/extracted_facts/committed_strategy.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Committed strategy -summary: Facts about Committed strategy -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T14:20:02.533Z' -updatedAt: '2026-05-28T14:20:02.533Z' ---- -## Reason -Curated extracted factual statements - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-28T14:20:02.494Z - -## Narrative -### Structure -Collection of facts for Committed strategy - -## Facts -- **Committed strategy**: Committed color strategy uses one saturated color for 30–60% of surface. diff --git a/.brv/context-tree/facts/extracted_facts/copywriting.md b/.brv/context-tree/facts/extracted_facts/copywriting.md deleted file mode 100644 index db92a405..00000000 --- a/.brv/context-tree/facts/extracted_facts/copywriting.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Copywriting -summary: Facts about Copywriting -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T14:20:02.573Z' -updatedAt: '2026-05-28T14:20:02.573Z' ---- -## Reason -Curated extracted factual statements - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-28T14:20:02.494Z - -## Narrative -### Structure -Collection of facts for Copywriting - -## Facts -- **Copywriting**: Every word must earn its place; no restated headings or intros that repeat the title. diff --git a/.brv/context-tree/facts/extracted_facts/craft_command.md b/.brv/context-tree/facts/extracted_facts/craft_command.md deleted file mode 100644 index e6fccf3c..00000000 --- a/.brv/context-tree/facts/extracted_facts/craft_command.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: craft command -summary: Facts about craft command -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T14:20:02.580Z' -updatedAt: '2026-05-28T14:20:02.580Z' ---- -## Reason -Curated extracted factual statements - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-28T14:20:02.494Z - -## Narrative -### Structure -Collection of facts for craft command - -## Facts -- **craft command**: `craft [feature]` shapes and then builds a feature end-to-end. diff --git a/.brv/context-tree/facts/extracted_facts/critique_command.md b/.brv/context-tree/facts/extracted_facts/critique_command.md deleted file mode 100644 index ca764d2d..00000000 --- a/.brv/context-tree/facts/extracted_facts/critique_command.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: critique command -summary: Facts about critique command -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T14:20:02.594Z' -updatedAt: '2026-05-28T14:20:02.594Z' ---- -## Reason -Curated extracted factual statements - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-28T14:20:02.495Z - -## Narrative -### Structure -Collection of facts for critique command - -## Facts -- **critique command**: `critique [target]` performs a UX design review with heuristic scoring. diff --git a/.brv/context-tree/facts/extracted_facts/delight_command.md b/.brv/context-tree/facts/extracted_facts/delight_command.md deleted file mode 100644 index bcb7de26..00000000 --- a/.brv/context-tree/facts/extracted_facts/delight_command.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: delight command -summary: Facts about delight command -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T14:20:02.612Z' -updatedAt: '2026-05-28T14:20:02.612Z' ---- -## Reason -Curated extracted factual statements - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-28T14:20:02.495Z - -## Narrative -### Structure -Collection of facts for delight command - -## Facts -- **delight command**: `delight [target]` adds personality and memorable touches. diff --git a/.brv/context-tree/facts/extracted_facts/design_md.md b/.brv/context-tree/facts/extracted_facts/design_md.md deleted file mode 100644 index 0345e0a4..00000000 --- a/.brv/context-tree/facts/extracted_facts/design_md.md +++ /dev/null @@ -1,25 +0,0 @@ ---- -title: DESIGN.md -summary: Facts about DESIGN.md -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T14:20:02.507Z' -updatedAt: '2026-05-28T14:20:02.507Z' ---- -## Reason -Curated extracted factual statements - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-28T14:20:02.494Z - -## Narrative -### Structure -Collection of facts for DESIGN.md - -## Facts -- **DESIGN.md**: DESIGN.md is optional, strongly recommended. -- **DESIGN.md**: If DESIGN.md is missing, nudge the user once per session to run the document command and then proceed. diff --git a/.brv/context-tree/facts/extracted_facts/design_task.md b/.brv/context-tree/facts/extracted_facts/design_task.md deleted file mode 100644 index c7a54a3a..00000000 --- a/.brv/context-tree/facts/extracted_facts/design_task.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: design task -summary: Facts about design task -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T14:20:02.509Z' -updatedAt: '2026-05-28T14:20:02.509Z' ---- -## Reason -Curated extracted factual statements - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-28T14:20:02.494Z - -## Narrative -### Structure -Collection of facts for design task - -## Facts -- **design task**: Every design task is either brand (marketing, landing, campaign, long-form content, portfolio) or product (app UI, admin, dashboard, tool). diff --git a/.brv/context-tree/facts/extracted_facts/distill_command.md b/.brv/context-tree/facts/extracted_facts/distill_command.md deleted file mode 100644 index e3da6717..00000000 --- a/.brv/context-tree/facts/extracted_facts/distill_command.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: distill command -summary: Facts about distill command -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T14:20:02.603Z' -updatedAt: '2026-05-28T14:20:02.603Z' ---- -## Reason -Curated extracted factual statements - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-28T14:20:02.495Z - -## Narrative -### Structure -Collection of facts for distill command - -## Facts -- **distill command**: `distill [target]` strips to essence, removing complexity. diff --git a/.brv/context-tree/facts/extracted_facts/document_command.md b/.brv/context-tree/facts/extracted_facts/document_command.md deleted file mode 100644 index 430ad86c..00000000 --- a/.brv/context-tree/facts/extracted_facts/document_command.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: document command -summary: Facts about document command -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T14:20:02.589Z' -updatedAt: '2026-05-28T14:20:02.589Z' ---- -## Reason -Curated extracted factual statements - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-28T14:20:02.495Z - -## Narrative -### Structure -Collection of facts for document command - -## Facts -- **document command**: `document` generates DESIGN.md from existing project code. diff --git a/.brv/context-tree/facts/extracted_facts/drenched_strategy.md b/.brv/context-tree/facts/extracted_facts/drenched_strategy.md deleted file mode 100644 index 5cee36bd..00000000 --- a/.brv/context-tree/facts/extracted_facts/drenched_strategy.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Drenched strategy -summary: Facts about Drenched strategy -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T14:20:02.540Z' -updatedAt: '2026-05-28T14:20:02.540Z' ---- -## Reason -Curated extracted factual statements - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-28T14:20:02.494Z - -## Narrative -### Structure -Collection of facts for Drenched strategy - -## Facts -- **Drenched strategy**: Drenched strategy makes the surface the color. diff --git a/.brv/context-tree/facts/extracted_facts/easing_functions.md b/.brv/context-tree/facts/extracted_facts/easing_functions.md deleted file mode 100644 index ea3ce31c..00000000 --- a/.brv/context-tree/facts/extracted_facts/easing_functions.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Easing functions -summary: Facts about Easing functions -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T14:20:02.556Z' -updatedAt: '2026-05-28T14:20:02.556Z' ---- -## Reason -Curated extracted factual statements - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-28T14:20:02.494Z - -## Narrative -### Structure -Collection of facts for Easing functions - -## Facts -- **Easing functions**: Use ease-out exponential curves (ease-out-quart, quint, expo) for motion. diff --git a/.brv/context-tree/facts/extracted_facts/extract_command.md b/.brv/context-tree/facts/extracted_facts/extract_command.md deleted file mode 100644 index e81a7f1b..00000000 --- a/.brv/context-tree/facts/extracted_facts/extract_command.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: extract command -summary: Facts about extract command -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T14:20:02.591Z' -updatedAt: '2026-05-28T14:20:02.591Z' ---- -## Reason -Curated extracted factual statements - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-28T14:20:02.495Z - -## Narrative -### Structure -Collection of facts for extract command - -## Facts -- **extract command**: `extract [target]` pulls reusable tokens and components into design system. diff --git a/.brv/context-tree/facts/extracted_facts/extracted_facts.md b/.brv/context-tree/facts/extracted_facts/extracted_facts.md deleted file mode 100644 index e653bd8a..00000000 --- a/.brv/context-tree/facts/extracted_facts/extracted_facts.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: extracted_facts -summary: Aggregated factual statements from user conversation -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T16:46:02.415Z' -updatedAt: '2026-05-28T16:46:02.415Z' ---- -## Reason -Curate extracted facts from RLM context - -## Raw Concept -**Task:** -Curate extracted factual statements from conversation - -**Flow:** -extraction -> dedup -> upsert - -**Timestamp:** 2026-05-28T16:46:02.411Z - -## Narrative -### Structure -Aggregated factual statements grouped by subject diff --git a/.brv/context-tree/facts/extracted_facts/full_palette_strategy.md b/.brv/context-tree/facts/extracted_facts/full_palette_strategy.md deleted file mode 100644 index 53a76277..00000000 --- a/.brv/context-tree/facts/extracted_facts/full_palette_strategy.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Full palette strategy -summary: Facts about Full palette strategy -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T14:20:02.538Z' -updatedAt: '2026-05-28T14:20:02.538Z' ---- -## Reason -Curated extracted factual statements - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-28T14:20:02.494Z - -## Narrative -### Structure -Collection of facts for Full palette strategy - -## Facts -- **Full palette strategy**: Full palette strategy uses 3–4 named roles deliberately. diff --git a/.brv/context-tree/facts/extracted_facts/glassmorphism.md b/.brv/context-tree/facts/extracted_facts/glassmorphism.md deleted file mode 100644 index c580c782..00000000 --- a/.brv/context-tree/facts/extracted_facts/glassmorphism.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Glassmorphism -summary: Facts about Glassmorphism -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T14:20:02.560Z' -updatedAt: '2026-05-28T14:20:02.560Z' ---- -## Reason -Curated extracted factual statements - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-28T14:20:02.494Z - -## Narrative -### Structure -Collection of facts for Glassmorphism - -## Facts -- **Glassmorphism**: Glassmorphism as default is prohibited. diff --git a/.brv/context-tree/facts/extracted_facts/harden_command.md b/.brv/context-tree/facts/extracted_facts/harden_command.md deleted file mode 100644 index 7e55969b..00000000 --- a/.brv/context-tree/facts/extracted_facts/harden_command.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: harden command -summary: Facts about harden command -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T14:20:02.604Z' -updatedAt: '2026-05-28T14:20:02.604Z' ---- -## Reason -Curated extracted factual statements - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-28T14:20:02.495Z - -## Narrative -### Structure -Collection of facts for harden command - -## Facts -- **harden command**: `harden [target]` makes designs production-ready, handling errors, i18n, and edge cases. diff --git a/.brv/context-tree/facts/extracted_facts/hero_metric_template.md b/.brv/context-tree/facts/extracted_facts/hero_metric_template.md deleted file mode 100644 index 7b8a3475..00000000 --- a/.brv/context-tree/facts/extracted_facts/hero_metric_template.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Hero metric template -summary: Facts about Hero metric template -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T14:20:02.561Z' -updatedAt: '2026-05-28T14:20:02.561Z' ---- -## Reason -Curated extracted factual statements - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-28T14:20:02.494Z - -## Narrative -### Structure -Collection of facts for Hero metric template - -## Facts -- **Hero metric template**: The hero-metric template is prohibited as a SaaS cliché. diff --git a/.brv/context-tree/facts/extracted_facts/hex_colors.md b/.brv/context-tree/facts/extracted_facts/hex_colors.md deleted file mode 100644 index f469c1f1..00000000 --- a/.brv/context-tree/facts/extracted_facts/hex_colors.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Hex colors -summary: Facts about Hex colors -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T14:20:02.518Z' -updatedAt: '2026-05-28T14:20:02.518Z' ---- -## Reason -Curated extracted factual statements - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-28T14:20:02.494Z - -## Narrative -### Structure -Collection of facts for Hex colors - -## Facts -- **Hex colors**: Never use #000 or #fff. diff --git a/.brv/context-tree/facts/extracted_facts/install_copy.md b/.brv/context-tree/facts/extracted_facts/install_copy.md deleted file mode 100644 index 1a0c851e..00000000 --- a/.brv/context-tree/facts/extracted_facts/install_copy.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: install-copy -summary: Facts about install-copy -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T14:20:02.628Z' -updatedAt: '2026-05-28T14:20:02.628Z' ---- -## Reason -Curated extracted factual statements - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-28T14:20:02.495Z - -## Narrative -### Structure -Collection of facts for install-copy - -## Facts -- **install-copy**: Added `.install-copy { min-width: 0; }` so install copy and headings do not overflow at 200% text size. diff --git a/.brv/context-tree/facts/extracted_facts/layout_command.md b/.brv/context-tree/facts/extracted_facts/layout_command.md deleted file mode 100644 index 32472a50..00000000 --- a/.brv/context-tree/facts/extracted_facts/layout_command.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: layout command -summary: Facts about layout command -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T14:20:02.611Z' -updatedAt: '2026-05-28T14:20:02.611Z' ---- -## Reason -Curated extracted factual statements - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-28T14:20:02.495Z - -## Narrative -### Structure -Collection of facts for layout command - -## Facts -- **layout command**: `layout [target]` fixes spacing, rhythm, and visual hierarchy. diff --git a/.brv/context-tree/facts/extracted_facts/line_length.md b/.brv/context-tree/facts/extracted_facts/line_length.md deleted file mode 100644 index 65290be3..00000000 --- a/.brv/context-tree/facts/extracted_facts/line_length.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Line length -summary: Facts about Line length -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T14:20:02.546Z' -updatedAt: '2026-05-28T14:20:02.546Z' ---- -## Reason -Curated extracted factual statements - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-28T14:20:02.494Z - -## Narrative -### Structure -Collection of facts for Line length - -## Facts -- **Line length**: Cap body line length at 65–75 characters. diff --git a/.brv/context-tree/facts/extracted_facts/live_command.md b/.brv/context-tree/facts/extracted_facts/live_command.md deleted file mode 100644 index fb7fdb2d..00000000 --- a/.brv/context-tree/facts/extracted_facts/live_command.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: live command -summary: Facts about live command -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T14:20:02.620Z' -updatedAt: '2026-05-28T14:20:02.620Z' ---- -## Reason -Curated extracted factual statements - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-28T14:20:02.495Z - -## Narrative -### Structure -Collection of facts for live command - -## Facts -- **live command**: `live` enables visual variant mode: pick elements in the browser, generate alternatives. diff --git a/.brv/context-tree/facts/extracted_facts/modal_usage.md b/.brv/context-tree/facts/extracted_facts/modal_usage.md deleted file mode 100644 index e2bc8d04..00000000 --- a/.brv/context-tree/facts/extracted_facts/modal_usage.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Modal usage -summary: Facts about Modal usage -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T14:20:02.567Z' -updatedAt: '2026-05-28T14:20:02.567Z' ---- -## Reason -Curated extracted factual statements - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-28T14:20:02.494Z - -## Narrative -### Structure -Collection of facts for Modal usage - -## Facts -- **Modal usage**: Modals should not be the first design thought. diff --git a/.brv/context-tree/facts/extracted_facts/neutral_tinting.md b/.brv/context-tree/facts/extracted_facts/neutral_tinting.md deleted file mode 100644 index d0612c13..00000000 --- a/.brv/context-tree/facts/extracted_facts/neutral_tinting.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Neutral tinting -summary: Facts about Neutral tinting -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T14:20:02.526Z' -updatedAt: '2026-05-28T14:20:02.526Z' ---- -## Reason -Curated extracted factual statements - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-28T14:20:02.494Z - -## Narrative -### Structure -Collection of facts for Neutral tinting - -## Facts -- **Neutral tinting**: Tint every neutral toward the brand hue with chroma 0.005–0.01. diff --git a/.brv/context-tree/facts/extracted_facts/onboard_command.md b/.brv/context-tree/facts/extracted_facts/onboard_command.md deleted file mode 100644 index 11de771a..00000000 --- a/.brv/context-tree/facts/extracted_facts/onboard_command.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: onboard command -summary: Facts about onboard command -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T14:20:02.606Z' -updatedAt: '2026-05-28T14:20:02.606Z' ---- -## Reason -Curated extracted factual statements - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-28T14:20:02.495Z - -## Narrative -### Structure -Collection of facts for onboard command - -## Facts -- **onboard command**: `onboard [target]` designs first-run flows, empty states, and activation. diff --git a/.brv/context-tree/facts/extracted_facts/optimize_command.md b/.brv/context-tree/facts/extracted_facts/optimize_command.md deleted file mode 100644 index 1a3e476a..00000000 --- a/.brv/context-tree/facts/extracted_facts/optimize_command.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: optimize command -summary: Facts about optimize command -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T14:20:02.618Z' -updatedAt: '2026-05-28T14:20:02.618Z' ---- -## Reason -Curated extracted factual statements - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-28T14:20:02.495Z - -## Narrative -### Structure -Collection of facts for optimize command - -## Facts -- **optimize command**: `optimize [target]` diagnoses and fixes UI performance issues. diff --git a/.brv/context-tree/facts/extracted_facts/overdrive_command.md b/.brv/context-tree/facts/extracted_facts/overdrive_command.md deleted file mode 100644 index 7d0d2d3f..00000000 --- a/.brv/context-tree/facts/extracted_facts/overdrive_command.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: overdrive command -summary: Facts about overdrive command -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T14:20:02.614Z' -updatedAt: '2026-05-28T14:20:02.614Z' ---- -## Reason -Curated extracted factual statements - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-28T14:20:02.495Z - -## Narrative -### Structure -Collection of facts for overdrive command - -## Facts -- **overdrive command**: `overdrive [target]` pushes past conventional limits. diff --git a/.brv/context-tree/facts/extracted_facts/pin.md b/.brv/context-tree/facts/extracted_facts/pin.md deleted file mode 100644 index 2d898ec2..00000000 --- a/.brv/context-tree/facts/extracted_facts/pin.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Pin -summary: Facts about Pin -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T14:20:02.622Z' -updatedAt: '2026-05-28T14:20:02.622Z' ---- -## Reason -Curated extracted factual statements - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-28T14:20:02.495Z - -## Narrative -### Structure -Collection of facts for Pin - -## Facts -- **Pin**: Pin creates a standalone shortcut so `{{command_prefix}}` invokes `{{command_prefix}}impeccable ` directly. diff --git a/.brv/context-tree/facts/extracted_facts/pin_unpin_commands.md b/.brv/context-tree/facts/extracted_facts/pin_unpin_commands.md deleted file mode 100644 index eca65556..00000000 --- a/.brv/context-tree/facts/extracted_facts/pin_unpin_commands.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: pin/unpin commands -summary: Facts about pin/unpin commands -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T14:20:02.621Z' -updatedAt: '2026-05-28T14:20:02.621Z' ---- -## Reason -Curated extracted factual statements - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-28T14:20:02.495Z - -## Narrative -### Structure -Collection of facts for pin/unpin commands - -## Facts -- **pin/unpin commands**: Two management commands exist: `pin ` and `unpin `. diff --git a/.brv/context-tree/facts/extracted_facts/pin_unpin_script.md b/.brv/context-tree/facts/extracted_facts/pin_unpin_script.md deleted file mode 100644 index 5f2da70b..00000000 --- a/.brv/context-tree/facts/extracted_facts/pin_unpin_script.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Pin/Unpin script -summary: Facts about Pin/Unpin script -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T14:20:02.625Z' -updatedAt: '2026-05-28T14:20:02.625Z' ---- -## Reason -Curated extracted factual statements - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-28T14:20:02.495Z - -## Narrative -### Structure -Collection of facts for Pin/Unpin script - -## Facts -- **Pin/Unpin script**: The script writes to every harness directory present in the project. diff --git a/.brv/context-tree/facts/extracted_facts/pnpm.md b/.brv/context-tree/facts/extracted_facts/pnpm.md deleted file mode 100644 index d743ea87..00000000 --- a/.brv/context-tree/facts/extracted_facts/pnpm.md +++ /dev/null @@ -1,25 +0,0 @@ ---- -title: pnpm -summary: Facts about pnpm -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T14:20:02.635Z' -updatedAt: '2026-05-28T14:20:02.635Z' ---- -## Reason -Curated extracted factual statements - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-28T14:20:02.495Z - -## Narrative -### Structure -Collection of facts for pnpm - -## Facts -- **pnpm**: pnpm --filter @caplets/landing typecheck passed. -- **pnpm**: pnpm --filter @caplets/landing build passed. diff --git a/.brv/context-tree/facts/extracted_facts/polish_command.md b/.brv/context-tree/facts/extracted_facts/polish_command.md deleted file mode 100644 index 45a44059..00000000 --- a/.brv/context-tree/facts/extracted_facts/polish_command.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: polish command -summary: Facts about polish command -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T14:20:02.598Z' -updatedAt: '2026-05-28T14:20:02.598Z' ---- -## Reason -Curated extracted factual statements - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-28T14:20:02.495Z - -## Narrative -### Structure -Collection of facts for polish command - -## Facts -- **polish command**: `polish [target]` provides a final quality pass before shipping. diff --git a/.brv/context-tree/facts/extracted_facts/product_md.md b/.brv/context-tree/facts/extracted_facts/product_md.md deleted file mode 100644 index b70c3008..00000000 --- a/.brv/context-tree/facts/extracted_facts/product_md.md +++ /dev/null @@ -1,25 +0,0 @@ ---- -title: PRODUCT.md -summary: Facts about PRODUCT.md -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T14:20:02.504Z' -updatedAt: '2026-05-28T14:20:02.504Z' ---- -## Reason -Curated extracted factual statements - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-28T14:20:02.494Z - -## Narrative -### Structure -Collection of facts for PRODUCT.md - -## Facts -- **PRODUCT.md**: PRODUCT.md is required. -- **PRODUCT.md**: If PRODUCT.md is missing, empty, or placeholder ([TODO] markers, <200 chars), run the teach command and then resume the original task. diff --git a/.brv/context-tree/facts/extracted_facts/punctuation.md b/.brv/context-tree/facts/extracted_facts/punctuation.md deleted file mode 100644 index 5ab9e3b4..00000000 --- a/.brv/context-tree/facts/extracted_facts/punctuation.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Punctuation -summary: Facts about Punctuation -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T14:20:02.575Z' -updatedAt: '2026-05-28T14:20:02.575Z' ---- -## Reason -Curated extracted factual statements - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-28T14:20:02.494Z - -## Narrative -### Structure -Collection of facts for Punctuation - -## Facts -- **Punctuation**: Em dashes are prohibited; use commas, colons, semicolons, periods, or parentheses instead. diff --git a/.brv/context-tree/facts/extracted_facts/quieter_command.md b/.brv/context-tree/facts/extracted_facts/quieter_command.md deleted file mode 100644 index ee2ef5be..00000000 --- a/.brv/context-tree/facts/extracted_facts/quieter_command.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: quieter command -summary: Facts about quieter command -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T14:20:02.602Z' -updatedAt: '2026-05-28T14:20:02.602Z' ---- -## Reason -Curated extracted factual statements - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-28T14:20:02.495Z - -## Narrative -### Structure -Collection of facts for quieter command - -## Facts -- **quieter command**: `quieter [target]` tones down aggressive or overstimulating designs. diff --git a/.brv/context-tree/facts/extracted_facts/register_failures.md b/.brv/context-tree/facts/extracted_facts/register_failures.md deleted file mode 100644 index a74530b7..00000000 --- a/.brv/context-tree/facts/extracted_facts/register_failures.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: register failures -summary: Facts about register failures -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T14:20:02.576Z' -updatedAt: '2026-05-28T14:20:02.576Z' ---- -## Reason -Curated extracted factual statements - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-28T14:20:02.494Z - -## Narrative -### Structure -Collection of facts for register failures - -## Facts -- **register failures**: Register failures are the absolute bans above. diff --git a/.brv/context-tree/facts/extracted_facts/register_identification.md b/.brv/context-tree/facts/extracted_facts/register_identification.md deleted file mode 100644 index a74e8e0e..00000000 --- a/.brv/context-tree/facts/extracted_facts/register_identification.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: register identification -summary: Facts about register identification -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T14:20:02.514Z' -updatedAt: '2026-05-28T14:20:02.514Z' ---- -## Reason -Curated extracted factual statements - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-28T14:20:02.494Z - -## Narrative -### Structure -Collection of facts for register identification - -## Facts -- **register identification**: Identify the register before designing, using priority: cue in the task, surface in focus, then register field in PRODUCT.md. diff --git a/.brv/context-tree/facts/extracted_facts/register_specific_failures.md b/.brv/context-tree/facts/extracted_facts/register_specific_failures.md deleted file mode 100644 index 04d64342..00000000 --- a/.brv/context-tree/facts/extracted_facts/register_specific_failures.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: register-specific failures -summary: Facts about register-specific failures -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T14:20:02.577Z' -updatedAt: '2026-05-28T14:20:02.577Z' ---- -## Reason -Curated extracted factual statements - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-28T14:20:02.494Z - -## Narrative -### Structure -Collection of facts for register-specific failures - -## Facts -- **register-specific failures**: Register-specific failures live in each reference. diff --git a/.brv/context-tree/facts/extracted_facts/restrained_strategy.md b/.brv/context-tree/facts/extracted_facts/restrained_strategy.md deleted file mode 100644 index 2963f47e..00000000 --- a/.brv/context-tree/facts/extracted_facts/restrained_strategy.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Restrained strategy -summary: Facts about Restrained strategy -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T14:20:02.527Z' -updatedAt: '2026-05-28T14:20:02.527Z' ---- -## Reason -Curated extracted factual statements - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-28T14:20:02.494Z - -## Narrative -### Structure -Collection of facts for Restrained strategy - -## Facts -- **Restrained strategy**: Restrained color strategy uses tinted neutrals plus one accent ≤10% of surface. diff --git a/.brv/context-tree/facts/extracted_facts/section_heading.md b/.brv/context-tree/facts/extracted_facts/section_heading.md deleted file mode 100644 index 883ec703..00000000 --- a/.brv/context-tree/facts/extracted_facts/section_heading.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: section-heading -summary: Facts about section-heading -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T14:20:02.627Z' -updatedAt: '2026-05-28T14:20:02.627Z' ---- -## Reason -Curated extracted factual statements - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-28T14:20:02.495Z - -## Narrative -### Structure -Collection of facts for section-heading - -## Facts -- **section-heading**: Added `min-width: 0` to direct `.section-heading` children so long or enlarged section notes/headings can shrink inside grid columns. diff --git a/.brv/context-tree/facts/extracted_facts/shape_command.md b/.brv/context-tree/facts/extracted_facts/shape_command.md deleted file mode 100644 index 533a3ae2..00000000 --- a/.brv/context-tree/facts/extracted_facts/shape_command.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: shape command -summary: Facts about shape command -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T14:20:02.582Z' -updatedAt: '2026-05-28T14:20:02.582Z' ---- -## Reason -Curated extracted factual statements - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-28T14:20:02.494Z - -## Narrative -### Structure -Collection of facts for shape command - -## Facts -- **shape command**: `shape [feature]` plans UX/UI before writing code. diff --git a/.brv/context-tree/facts/extracted_facts/spacing.md b/.brv/context-tree/facts/extracted_facts/spacing.md deleted file mode 100644 index a8bd4cec..00000000 --- a/.brv/context-tree/facts/extracted_facts/spacing.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Spacing -summary: Facts about Spacing -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T14:20:02.552Z' -updatedAt: '2026-05-28T14:20:02.552Z' ---- -## Reason -Curated extracted factual statements - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-28T14:20:02.494Z - -## Narrative -### Structure -Collection of facts for Spacing - -## Facts -- **Spacing**: Vary spacing for rhythm; same padding everywhere creates monotony. diff --git a/.brv/context-tree/facts/extracted_facts/teach_command.md b/.brv/context-tree/facts/extracted_facts/teach_command.md deleted file mode 100644 index fa7a572d..00000000 --- a/.brv/context-tree/facts/extracted_facts/teach_command.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: teach command -summary: Facts about teach command -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T14:20:02.584Z' -updatedAt: '2026-05-28T14:20:02.584Z' ---- -## Reason -Curated extracted factual statements - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-28T14:20:02.494Z - -## Narrative -### Structure -Collection of facts for teach command - -## Facts -- **teach command**: `teach` sets up PRODUCT.md and DESIGN.md context. diff --git a/.brv/context-tree/facts/extracted_facts/text_styling.md b/.brv/context-tree/facts/extracted_facts/text_styling.md deleted file mode 100644 index 73e21421..00000000 --- a/.brv/context-tree/facts/extracted_facts/text_styling.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Text styling -summary: Facts about Text styling -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T14:20:02.559Z' -updatedAt: '2026-05-28T14:20:02.559Z' ---- -## Reason -Curated extracted factual statements - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-28T14:20:02.494Z - -## Narrative -### Structure -Collection of facts for Text styling - -## Facts -- **Text styling**: Gradient text using background-clip: text is prohibited. diff --git a/.brv/context-tree/facts/extracted_facts/theme_selection.md b/.brv/context-tree/facts/extracted_facts/theme_selection.md deleted file mode 100644 index 9164ecd0..00000000 --- a/.brv/context-tree/facts/extracted_facts/theme_selection.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Theme selection -summary: Facts about Theme selection -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T14:20:02.542Z' -updatedAt: '2026-05-28T14:20:02.542Z' ---- -## Reason -Curated extracted factual statements - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-28T14:20:02.494Z - -## Narrative -### Structure -Collection of facts for Theme selection - -## Facts -- **Theme selection**: Dark vs. light mode should not be chosen by default. diff --git a/.brv/context-tree/facts/extracted_facts/typecheck.md b/.brv/context-tree/facts/extracted_facts/typecheck.md deleted file mode 100644 index 31f17287..00000000 --- a/.brv/context-tree/facts/extracted_facts/typecheck.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Typecheck -summary: Facts about Typecheck -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T14:20:02.631Z' -updatedAt: '2026-05-28T14:20:02.631Z' ---- -## Reason -Curated extracted factual statements - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-28T14:20:02.495Z - -## Narrative -### Structure -Collection of facts for Typecheck - -## Facts -- **Typecheck**: `pnpm --filter @caplets/landing typecheck` passed. diff --git a/.brv/context-tree/facts/extracted_facts/typeset_command.md b/.brv/context-tree/facts/extracted_facts/typeset_command.md deleted file mode 100644 index d1fa3a89..00000000 --- a/.brv/context-tree/facts/extracted_facts/typeset_command.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: typeset command -summary: Facts about typeset command -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T14:20:02.610Z' -updatedAt: '2026-05-28T14:20:02.610Z' ---- -## Reason -Curated extracted factual statements - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-28T14:20:02.495Z - -## Narrative -### Structure -Collection of facts for typeset command - -## Facts -- **typeset command**: `typeset [target]` improves typography hierarchy and fonts. diff --git a/.brv/context-tree/facts/extracted_facts/typographic_hierarchy.md b/.brv/context-tree/facts/extracted_facts/typographic_hierarchy.md deleted file mode 100644 index 5c0847b7..00000000 --- a/.brv/context-tree/facts/extracted_facts/typographic_hierarchy.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Typographic hierarchy -summary: Facts about Typographic hierarchy -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T14:20:02.548Z' -updatedAt: '2026-05-28T14:20:02.548Z' ---- -## Reason -Curated extracted factual statements - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-28T14:20:02.494Z - -## Narrative -### Structure -Collection of facts for Typographic hierarchy - -## Facts -- **Typographic hierarchy**: Maintain a hierarchy through scale and weight contrast with at least a 1.25 ratio between steps. diff --git a/.brv/context-tree/facts/extracted_facts/unpin.md b/.brv/context-tree/facts/extracted_facts/unpin.md deleted file mode 100644 index 0dd69979..00000000 --- a/.brv/context-tree/facts/extracted_facts/unpin.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Unpin -summary: Facts about Unpin -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T14:20:02.624Z' -updatedAt: '2026-05-28T14:20:02.624Z' ---- -## Reason -Curated extracted factual statements - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-28T14:20:02.495Z - -## Narrative -### Structure -Collection of facts for Unpin - -## Facts -- **Unpin**: Unpin removes the shortcut created by Pin. diff --git a/.brv/context-tree/facts/mcp_example/mcp_example.md b/.brv/context-tree/facts/mcp_example/mcp_example.md deleted file mode 100644 index 3ef7628b..00000000 --- a/.brv/context-tree/facts/mcp_example/mcp_example.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -title: MCP example -summary: Facts about MCP example -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T16:21:51.966Z' -updatedAt: '2026-05-28T16:21:51.966Z' ---- -## Reason -Curated factual statements extracted from PR review context - -## Raw Concept -**Task:** -Document facts for MCP example - -**Timestamp:** 2026-05-28T16:21:51.958Z - -## Facts -- **MCP example**: Moved that MCP example into the optional backend examples section. diff --git a/.brv/context-tree/facts/minimal_quick_start/minimal_quick_start.md b/.brv/context-tree/facts/minimal_quick_start/minimal_quick_start.md deleted file mode 100644 index 609c19fa..00000000 --- a/.brv/context-tree/facts/minimal_quick_start/minimal_quick_start.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -title: Minimal Quick Start -summary: Facts about minimal Quick Start -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T16:21:51.964Z' -updatedAt: '2026-05-28T16:21:51.964Z' ---- -## Reason -Curated factual statements extracted from PR review context - -## Raw Concept -**Task:** -Document facts for minimal Quick Start - -**Timestamp:** 2026-05-28T16:21:51.958Z - -## Facts -- **minimal Quick Start**: Removed the optional Context7 `caplets add mcp docs ...` command from the minimal Quick Start. diff --git a/.brv/context-tree/facts/pnpm_format_check/pnpm_format_check.md b/.brv/context-tree/facts/pnpm_format_check/pnpm_format_check.md deleted file mode 100644 index 8d932efe..00000000 --- a/.brv/context-tree/facts/pnpm_format_check/pnpm_format_check.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -title: Pnpm format check -summary: Facts about pnpm format check -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T16:21:51.969Z' -updatedAt: '2026-05-28T16:21:51.969Z' ---- -## Reason -Curated factual statements extracted from PR review context - -## Raw Concept -**Task:** -Document facts for pnpm format check - -**Timestamp:** 2026-05-28T16:21:51.958Z - -## Facts -- **pnpm format check**: `pnpm format:check -- README.md` passed. diff --git a/.brv/context-tree/facts/pnpm_verify/pnpm_verify.md b/.brv/context-tree/facts/pnpm_verify/pnpm_verify.md deleted file mode 100644 index 42cab7fd..00000000 --- a/.brv/context-tree/facts/pnpm_verify/pnpm_verify.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -title: Pnpm verify -summary: Facts about pnpm verify -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T16:21:51.971Z' -updatedAt: '2026-05-28T16:21:51.971Z' ---- -## Reason -Curated factual statements extracted from PR review context - -## Raw Concept -**Task:** -Document facts for pnpm verify - -**Timestamp:** 2026-05-28T16:21:51.958Z - -## Facts -- **pnpm verify**: `pnpm verify` passed. diff --git a/.brv/context-tree/facts/pr_93/pr_93.md b/.brv/context-tree/facts/pr_93/pr_93.md deleted file mode 100644 index 17be0827..00000000 --- a/.brv/context-tree/facts/pr_93/pr_93.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -title: 'PR #93' -summary: 'Facts about PR #93' -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T16:21:51.960Z' -updatedAt: '2026-05-28T16:21:51.960Z' ---- -## Reason -Curated factual statements extracted from PR review context - -## Raw Concept -**Task:** -Document facts for PR #93 - -**Timestamp:** 2026-05-28T16:21:51.958Z - -## Facts -- **PR #93**: Addressed PR review feedback and pushed updates to PR #93. diff --git a/.brv/context-tree/facts/project/179803b.md b/.brv/context-tree/facts/project/179803b.md deleted file mode 100644 index 01b6c55f..00000000 --- a/.brv/context-tree/facts/project/179803b.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -title: 179803b -summary: Facts about 179803b -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T16:59:16.797Z' -updatedAt: '2026-05-28T16:59:16.797Z' ---- -## Reason -Curated facts extracted from context - -## Raw Concept -**Task:** -Document facts for 179803b - -**Timestamp:** 2026-05-28T16:59:16.794Z - -## Facts -- **179803b**: Commit 179803b docs(agents): byterover context was automatically created by the new post-commit hook for .brv/ changes diff --git a/.brv/context-tree/facts/project/ab9efe5.md b/.brv/context-tree/facts/project/ab9efe5.md deleted file mode 100644 index dfc0c48a..00000000 --- a/.brv/context-tree/facts/project/ab9efe5.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -title: ab9efe5 -summary: Facts about ab9efe5 -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T16:59:16.795Z' -updatedAt: '2026-05-28T16:59:16.795Z' ---- -## Reason -Curated facts extracted from context - -## Raw Concept -**Task:** -Document facts for ab9efe5 - -**Timestamp:** 2026-05-28T16:59:16.794Z - -## Facts -- **ab9efe5**: Commit ab9efe5 feat(agents): commit byterover context from hook implements scripts/commit-byterover-context.ts, adds tests in scripts/commit-byterover-context.test.ts, adds warn-only .brv/ check to .husky/pre-push, and includes .husky/post-commit diff --git a/.brv/context-tree/facts/project/accessibility.md b/.brv/context-tree/facts/project/accessibility.md deleted file mode 100644 index 12dfc2b3..00000000 --- a/.brv/context-tree/facts/project/accessibility.md +++ /dev/null @@ -1,21 +0,0 @@ ---- -title: Accessibility -summary: Facts about accessibility -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T11:17:59.535Z' -updatedAt: '2026-05-28T11:17:59.535Z' ---- -## Reason -Curated facts extracted from context - -## Raw Concept -**Task:** -Document facts about accessibility - -**Timestamp:** 2026-05-28T11:17:59.530Z - -## Facts -- **accessibility**: Accessibility score is 4/4. -- **accessibility**: Skip link, landmark structure, labeled sections, icon link labels, keyboard tab behavior, visible focus, reduced motion, and non-color-only status cues are present. diff --git a/.brv/context-tree/facts/project/adapt.md b/.brv/context-tree/facts/project/adapt.md deleted file mode 100644 index 8905c0dd..00000000 --- a/.brv/context-tree/facts/project/adapt.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Adapt -summary: Facts about adapt -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T18:47:50.747Z' -updatedAt: '2026-05-27T18:47:50.747Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-27T18:47:50.505Z - -## Narrative -### Highlights -Extracted 1 facts for adapt - -## Facts -- **adapt**: `adapt [target]` is a Fix command that adapts designs for different devices and screen sizes. diff --git a/.brv/context-tree/facts/project/adapt_target_facts.md b/.brv/context-tree/facts/project/adapt_target_facts.md deleted file mode 100644 index d1da1e3b..00000000 --- a/.brv/context-tree/facts/project/adapt_target_facts.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: '`adapt [target]` facts' -summary: Facts about `adapt [target]` -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T19:11:54.753Z' -updatedAt: '2026-05-27T19:11:54.753Z' ---- -## Reason -Curated facts from RLM extraction - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-27T19:11:54.664Z - -## Narrative -### Structure -Collected 1 facts for `adapt [target]` - -## Facts -- **`adapt [target]`**: The command `adapt [target]` belongs to the Fix category and adapts designs for different devices and screen sizes. diff --git a/.brv/context-tree/facts/project/ai_generation_likelihood.md b/.brv/context-tree/facts/project/ai_generation_likelihood.md deleted file mode 100644 index 3fd87ee3..00000000 --- a/.brv/context-tree/facts/project/ai_generation_likelihood.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: AI-generation likelihood -summary: Facts about AI-generation likelihood -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T09:45:49.174Z' -updatedAt: '2026-05-28T09:45:49.174Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-28T09:45:49.131Z - -## Narrative -### Highlights -Extracted 1 facts for AI-generation likelihood - -## Facts -- **AI-generation likelihood**: It does not look immediately AI-generated. diff --git a/.brv/context-tree/facts/project/ai_slop.md b/.brv/context-tree/facts/project/ai_slop.md deleted file mode 100644 index ea6c6d57..00000000 --- a/.brv/context-tree/facts/project/ai_slop.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: AI slop -summary: Facts about AI slop -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T09:45:49.150Z' -updatedAt: '2026-05-28T09:45:49.150Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-28T09:45:49.131Z - -## Narrative -### Highlights -Extracted 1 facts for AI slop - -## Facts -- **AI slop**: AI slop detection checks for AI color palette, gradient text, dark glows, glassmorphism, hero metric layouts, identical card grids, generic fonts, and other tells. diff --git a/.brv/context-tree/facts/project/alchemy.md b/.brv/context-tree/facts/project/alchemy.md deleted file mode 100644 index 2943d092..00000000 --- a/.brv/context-tree/facts/project/alchemy.md +++ /dev/null @@ -1,22 +0,0 @@ ---- -title: Alchemy -summary: Facts about Alchemy -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T10:53:51.570Z' -updatedAt: '2026-05-28T10:53:51.570Z' ---- -## Reason -Curated facts extracted from source context - -## Raw Concept -**Task:** -Document facts about Alchemy - -**Timestamp:** 2026-05-28T10:53:51.568Z - -**Author:** ByteRover Context Engineer - -## Facts -- **Alchemy**: Alchemy version is v0.93.9 diff --git a/.brv/context-tree/facts/project/alchemy_cli_process_behavior.md b/.brv/context-tree/facts/project/alchemy_cli_process_behavior.md deleted file mode 100644 index c35812c1..00000000 --- a/.brv/context-tree/facts/project/alchemy_cli_process_behavior.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -title: Alchemy CLI process behavior -summary: Facts about Alchemy CLI process behavior -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T11:56:15.138Z' -updatedAt: '2026-05-28T11:56:15.138Z' ---- -## Reason -Curate extracted facts from source context - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-28T11:56:15.126Z - -## Facts -- **Alchemy CLI process behavior**: The shim was loaded in the parent Alchemy CLI, but Alchemy spawns a child process to evaluate `alchemy.run.ts`; the child was not inheriting the `--import` preload. diff --git a/.brv/context-tree/facts/project/animate.md b/.brv/context-tree/facts/project/animate.md deleted file mode 100644 index 158469fe..00000000 --- a/.brv/context-tree/facts/project/animate.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Animate -summary: Facts about animate -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T18:47:50.704Z' -updatedAt: '2026-05-27T18:47:50.704Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-27T18:47:50.505Z - -## Narrative -### Highlights -Extracted 1 facts for animate - -## Facts -- **animate**: `animate [target]` is an Enhance command that adds purposeful animations and motion. diff --git a/.brv/context-tree/facts/project/animate_target_facts.md b/.brv/context-tree/facts/project/animate_target_facts.md deleted file mode 100644 index c275c435..00000000 --- a/.brv/context-tree/facts/project/animate_target_facts.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: '`animate [target]` facts' -summary: Facts about `animate [target]` -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T19:11:54.738Z' -updatedAt: '2026-05-27T19:11:54.738Z' ---- -## Reason -Curated facts from RLM extraction - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-27T19:11:54.664Z - -## Narrative -### Structure -Collected 1 facts for `animate [target]` - -## Facts -- **`animate [target]`**: The command `animate [target]` belongs to the Enhance category and adds purposeful animations and motion. diff --git a/.brv/context-tree/facts/project/animation.md b/.brv/context-tree/facts/project/animation.md deleted file mode 100644 index c692ea46..00000000 --- a/.brv/context-tree/facts/project/animation.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Animation -summary: Facts about animation -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-29T09:19:16.192Z' -updatedAt: '2026-05-29T09:19:16.192Z' ---- -## Reason -Curated factual statements from context extraction - -## Raw Concept -**Task:** -Document factual statements - -**Timestamp:** 2026-05-29T09:19:16.120Z - -## Narrative -### Highlights -Extracted 1 facts for animation - -## Facts -- **animation**: Do not animate CSS layout properties. diff --git a/.brv/context-tree/facts/project/animation_facts.md b/.brv/context-tree/facts/project/animation_facts.md deleted file mode 100644 index 706f5a32..00000000 --- a/.brv/context-tree/facts/project/animation_facts.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Animation facts -summary: Facts about animation -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T19:11:54.702Z' -updatedAt: '2026-05-27T19:11:54.703Z' ---- -## Reason -Curated facts from RLM extraction - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-27T19:11:54.663Z - -## Narrative -### Structure -Collected 1 facts for animation - -## Facts -- **animation**: Do not animate CSS layout properties. diff --git a/.brv/context-tree/facts/project/apps_landing_src_pages_index_astro.md b/.brv/context-tree/facts/project/apps_landing_src_pages_index_astro.md deleted file mode 100644 index 6853899f..00000000 --- a/.brv/context-tree/facts/project/apps_landing_src_pages_index_astro.md +++ /dev/null @@ -1,27 +0,0 @@ ---- -title: Apps/landing/src/pages/index.astro -summary: Facts about apps/landing/src/pages/index.astro -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T13:51:08.350Z' -updatedAt: '2026-05-29T09:19:16.211Z' ---- -## Reason -Curated factual statements from context extraction - -## Raw Concept -**Task:** -Document factual statements - -**Timestamp:** 2026-05-29T09:19:16.120Z - -## Narrative -### Structure -Collected facts for apps/landing/src/pages/index.astro - -### Highlights -Extracted 1 facts for apps/landing/src/pages/index.astro - -## Facts -- **apps/landing/src/pages/index.astro**: Updated page title and meta description in apps/landing/src/pages/index.astro. diff --git a/.brv/context-tree/facts/project/assessment_a.md b/.brv/context-tree/facts/project/assessment_a.md deleted file mode 100644 index 0466f8e0..00000000 --- a/.brv/context-tree/facts/project/assessment_a.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Assessment A -summary: Facts about Assessment A -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T09:45:49.149Z' -updatedAt: '2026-05-28T09:45:49.149Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-28T09:45:49.131Z - -## Narrative -### Highlights -Extracted 1 facts for Assessment A - -## Facts -- **Assessment A**: Assessment A is an LLM Design Review that evaluates AI slop detection, holistic design, cognitive load, emotional journey, and Nielsen's heuristics. diff --git a/.brv/context-tree/facts/project/assessment_b.md b/.brv/context-tree/facts/project/assessment_b.md deleted file mode 100644 index 8b483504..00000000 --- a/.brv/context-tree/facts/project/assessment_b.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Assessment B -summary: Facts about Assessment B -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T09:45:49.154Z' -updatedAt: '2026-05-28T09:45:49.154Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-28T09:45:49.131Z - -## Narrative -### Highlights -Extracted 1 facts for Assessment B - -## Facts -- **Assessment B**: Assessment B runs the deterministic detector via the CLI command "npx impeccable --json [--fast] [target]". diff --git a/.brv/context-tree/facts/project/assessment_execution.md b/.brv/context-tree/facts/project/assessment_execution.md deleted file mode 100644 index 9d1fdc03..00000000 --- a/.brv/context-tree/facts/project/assessment_execution.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Assessment execution -summary: Facts about assessment execution -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T09:45:49.144Z' -updatedAt: '2026-05-28T09:45:49.144Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-28T09:45:49.131Z - -## Narrative -### Highlights -Extracted 1 facts for assessment execution - -## Facts -- **assessment execution**: If sub‑agents are unavailable, the assessments should be performed sequentially with findings written to internal notes. diff --git a/.brv/context-tree/facts/project/audit.md b/.brv/context-tree/facts/project/audit.md deleted file mode 100644 index 5aaa4671..00000000 --- a/.brv/context-tree/facts/project/audit.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Audit -summary: Facts about audit -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T18:47:50.671Z' -updatedAt: '2026-05-27T18:47:50.671Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-27T18:47:50.505Z - -## Narrative -### Highlights -Extracted 1 facts for audit - -## Facts -- **audit**: `audit [target]` is an Evaluate command that conducts technical quality checks (a11y, perf, responsive). diff --git a/.brv/context-tree/facts/project/audit_target_facts.md b/.brv/context-tree/facts/project/audit_target_facts.md deleted file mode 100644 index 55674dfb..00000000 --- a/.brv/context-tree/facts/project/audit_target_facts.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: '`audit [target]` facts' -summary: Facts about `audit [target]` -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T19:11:54.721Z' -updatedAt: '2026-05-27T19:11:54.721Z' ---- -## Reason -Curated facts from RLM extraction - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-27T19:11:54.664Z - -## Narrative -### Structure -Collected 1 facts for `audit [target]` - -## Facts -- **`audit [target]`**: The command `audit [target]` belongs to the Evaluate category and conducts technical quality checks such as accessibility, performance, and responsiveness. diff --git a/.brv/context-tree/facts/project/autocommit_hooks_for_memory_files.md b/.brv/context-tree/facts/project/autocommit_hooks_for_memory_files.md deleted file mode 100644 index f4c6037d..00000000 --- a/.brv/context-tree/facts/project/autocommit_hooks_for_memory_files.md +++ /dev/null @@ -1,38 +0,0 @@ ---- -title: Autocommit Hooks for Memory Files -summary: User requested hooks that autocommit memory files to make them less of a nuisance. -tags: [] -related: [facts/project/curation-itself-is-treated-as-durable-project-infrastructure.md, facts/project/curation_context_facts.md, facts/project/curation_input_notes.md] -keywords: [] -createdAt: '2026-05-21T11:39:40.128Z' -updatedAt: '2026-05-21T11:39:40.128Z' ---- -## Reason -Capture the user request to add hooks that autocommit memory files and reduce nuisance - -## Raw Concept -**Task:** -Document the request to add autocommit hooks for memory files - -**Changes:** -- Requested hooks that autocommit memory files -- Goal is to reduce the nuisance of managing memory files - -**Flow:** -user request -> autocommit hooks -> easier memory file handling - -**Timestamp:** 2026-05-21T11:39:25.370Z - -## Narrative -### Structure -A brief feature request focused on automating commits for memory files. - -### Dependencies -Depends on hook infrastructure that can trigger commits automatically. - -### Highlights -The request is about convenience and reducing manual overhead when working with memory files. - -## Facts -- **memory_file_autocommit_hooks**: The user wants hooks that automatically commit memory files. [project] -- **memory_file_nuisance**: The motivation is to make memory files less of a nuisance. [project] diff --git a/.brv/context-tree/facts/project/background_map_usage.md b/.brv/context-tree/facts/project/background_map_usage.md deleted file mode 100644 index d94b964a..00000000 --- a/.brv/context-tree/facts/project/background_map_usage.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Background map usage -summary: Facts about background map usage -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T09:50:25.725Z' -updatedAt: '2026-05-28T09:50:25.725Z' ---- -## Reason -Curated facts extracted from source context - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-28T09:50:25.672Z - -## Narrative -### Highlights -Extracted 1 facts for background map usage - -## Facts -- **background map usage**: The current map can remain as a quieter background motif but should no longer be the main element users decode. diff --git a/.brv/context-tree/facts/project/blocker.md b/.brv/context-tree/facts/project/blocker.md deleted file mode 100644 index 366f4d72..00000000 --- a/.brv/context-tree/facts/project/blocker.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -title: Blocker -summary: Facts about blocker -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T11:17:59.562Z' -updatedAt: '2026-05-28T11:17:59.562Z' ---- -## Reason -Curated facts extracted from context - -## Raw Concept -**Task:** -Document facts about blocker - -**Timestamp:** 2026-05-28T11:17:59.530Z - -## Facts -- **blocker**: Blocker: None. diff --git a/.brv/context-tree/facts/project/bolder.md b/.brv/context-tree/facts/project/bolder.md deleted file mode 100644 index c11fec0d..00000000 --- a/.brv/context-tree/facts/project/bolder.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Bolder -summary: Facts about bolder -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T18:47:50.691Z' -updatedAt: '2026-05-27T18:47:50.691Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-27T18:47:50.505Z - -## Narrative -### Highlights -Extracted 1 facts for bolder - -## Facts -- **bolder**: `bolder [target]` is a Refine command that amplifies safe or bland designs. diff --git a/.brv/context-tree/facts/project/bolder_target_facts.md b/.brv/context-tree/facts/project/bolder_target_facts.md deleted file mode 100644 index c44850a9..00000000 --- a/.brv/context-tree/facts/project/bolder_target_facts.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: '`bolder [target]` facts' -summary: Facts about `bolder [target]` -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T19:11:54.725Z' -updatedAt: '2026-05-27T19:11:54.726Z' ---- -## Reason -Curated facts from RLM extraction - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-27T19:11:54.664Z - -## Narrative -### Structure -Collected 1 facts for `bolder [target]` - -## Facts -- **`bolder [target]`**: The command `bolder [target]` belongs to the Refine category and amplifies safe or bland designs. diff --git a/.brv/context-tree/facts/project/border_style.md b/.brv/context-tree/facts/project/border_style.md deleted file mode 100644 index c1b7ef02..00000000 --- a/.brv/context-tree/facts/project/border_style.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Border style -summary: Facts about border style -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T18:47:50.617Z' -updatedAt: '2026-05-27T18:47:50.617Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-27T18:47:50.505Z - -## Narrative -### Highlights -Extracted 1 facts for border style - -## Facts -- **border style**: Side‑stripe borders (`border-left` or `border-right`) greater than 1 px as a colored accent are banned. diff --git a/.brv/context-tree/facts/project/border_style_facts.md b/.brv/context-tree/facts/project/border_style_facts.md deleted file mode 100644 index d4aeeb85..00000000 --- a/.brv/context-tree/facts/project/border_style_facts.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Border style facts -summary: Facts about border style -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T19:11:54.676Z' -updatedAt: '2026-05-27T19:11:54.676Z' ---- -## Reason -Curated facts from RLM extraction - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-27T19:11:54.663Z - -## Narrative -### Structure -Collected 1 facts for border style - -## Facts -- **border style**: Side-stripe borders greater than 1px as a colored accent are prohibited. diff --git a/.brv/context-tree/facts/project/border_usage.md b/.brv/context-tree/facts/project/border_usage.md deleted file mode 100644 index f3c16f03..00000000 --- a/.brv/context-tree/facts/project/border_usage.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Border usage -summary: Facts about border usage -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-29T09:19:16.195Z' -updatedAt: '2026-05-29T09:19:16.195Z' ---- -## Reason -Curated factual statements from context extraction - -## Raw Concept -**Task:** -Document factual statements - -**Timestamp:** 2026-05-29T09:19:16.120Z - -## Narrative -### Highlights -Extracted 1 facts for border usage - -## Facts -- **border usage**: Side-stripe borders greater than 1px as colored accents are prohibited. diff --git a/.brv/context-tree/facts/project/borders.md b/.brv/context-tree/facts/project/borders.md deleted file mode 100644 index 0174c2c0..00000000 --- a/.brv/context-tree/facts/project/borders.md +++ /dev/null @@ -1,27 +0,0 @@ ---- -title: Borders -summary: Facts about borders -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T19:18:57.154Z' -updatedAt: '2026-05-27T19:18:57.154Z' ---- -## Reason -Curated facts extracted from context - -## Raw Concept -**Task:** -Document project facts - -**Timestamp:** 2026-05-27T19:18:57.119Z - -## Narrative -### Structure -Collected factual statements - -### Highlights -Contains 1 facts - -## Facts -- **borders**: Side-stripe borders greater than 1px as a colored accent are banned. diff --git a/.brv/context-tree/facts/project/branch_handling_without_worktree.md b/.brv/context-tree/facts/project/branch_handling_without_worktree.md deleted file mode 100644 index d8c9996e..00000000 --- a/.brv/context-tree/facts/project/branch_handling_without_worktree.md +++ /dev/null @@ -1,37 +0,0 @@ ---- -title: Branch Handling Without Worktree -summary: The user switched branches and wants subsequent work to proceed without using a worktree. -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T09:46:08.810Z' -updatedAt: '2026-05-27T09:46:08.810Z' ---- -## Reason -Capture the user instruction about operating on the switched branch directly - -## Raw Concept -**Task:** -Record the branch state and execution preference for future operations - -**Changes:** -- Switched branch -- Continue without a worktree - -**Flow:** -branch switched -> continue operations directly on the branch - -**Timestamp:** 2026-05-27T09:45:53.888Z - -**Author:** user - -## Narrative -### Structure -A short operational instruction that affects how future work should be performed in the repository. - -### Highlights -Indicates the active branch has changed and work should proceed without worktree isolation. - -## Facts -- **branch_state**: The user switched the branch [project] -- **worktree_usage**: Proceed without a worktree [project] diff --git a/.brv/context-tree/facts/project/brand_mark.md b/.brv/context-tree/facts/project/brand_mark.md deleted file mode 100644 index 3aa69e99..00000000 --- a/.brv/context-tree/facts/project/brand_mark.md +++ /dev/null @@ -1,27 +0,0 @@ ---- -title: .brand-mark -summary: Facts about .brand-mark -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T13:40:49.012Z' -updatedAt: '2026-05-28T13:40:49.012Z' ---- -## Reason -Curate extracted facts from context - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-28T13:40:49.005Z - -## Narrative -### Structure -List of factual statements extracted - -### Highlights -Contains 1 facts - -## Facts -- **.brand-mark**: Mobile header targets meet 44px sizing without adding unnecessary visual bulk: `.brand-mark` is 44px minimum with compact 30px icon and hidden text, safe because markup has `aria-label="Caplets home"`. diff --git a/.brv/context-tree/facts/project/brand_register.md b/.brv/context-tree/facts/project/brand_register.md deleted file mode 100644 index bdd77ae3..00000000 --- a/.brv/context-tree/facts/project/brand_register.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Brand register -summary: Facts about brand register -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T18:47:50.646Z' -updatedAt: '2026-05-27T18:47:50.646Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-27T18:47:50.505Z - -## Narrative -### Highlights -Extracted 1 facts for brand register - -## Facts -- **brand register**: The brand register's reflex-reject aesthetic lanes list catches the currently-saturated families. diff --git a/.brv/context-tree/facts/project/browser.md b/.brv/context-tree/facts/project/browser.md deleted file mode 100644 index e7d8aee6..00000000 --- a/.brv/context-tree/facts/project/browser.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Browser -summary: Facts about browser -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T18:59:51.377Z' -updatedAt: '2026-05-28T09:45:49.178Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-28T09:45:49.131Z - -## Narrative -### Highlights -Extracted 1 facts for browser - -## Facts -- **browser**: Browser overlay was not diff --git a/.brv/context-tree/facts/project/browser_check.md b/.brv/context-tree/facts/project/browser_check.md deleted file mode 100644 index f1f53484..00000000 --- a/.brv/context-tree/facts/project/browser_check.md +++ /dev/null @@ -1,27 +0,0 @@ ---- -title: Browser check -summary: Facts about browser check -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T19:18:57.186Z' -updatedAt: '2026-05-27T19:18:57.186Z' ---- -## Reason -Curated facts extracted from context - -## Raw Concept -**Task:** -Document project facts - -**Timestamp:** 2026-05-27T19:18:57.119Z - -## Narrative -### Structure -Collected factual statements - -### Highlights -Contains 1 facts - -## Facts -- **browser check**: Browser preview and console showed 0 errors and 0 warnings after the animation changes. diff --git a/.brv/context-tree/facts/project/browser_console.md b/.brv/context-tree/facts/project/browser_console.md deleted file mode 100644 index 1f171c7e..00000000 --- a/.brv/context-tree/facts/project/browser_console.md +++ /dev/null @@ -1,27 +0,0 @@ ---- -title: Browser console -summary: Facts about browser console -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T19:18:57.213Z' -updatedAt: '2026-05-27T19:18:57.213Z' ---- -## Reason -Curated facts extracted from context - -## Raw Concept -**Task:** -Document project facts - -**Timestamp:** 2026-05-27T19:18:57.119Z - -## Narrative -### Structure -Collected factual statements - -### Highlights -Contains 1 facts - -## Facts -- **browser console**: Browser console checked, 0 errors and 0 warnings. diff --git a/.brv/context-tree/facts/project/browser_facts.md b/.brv/context-tree/facts/project/browser_facts.md deleted file mode 100644 index b0d45be6..00000000 --- a/.brv/context-tree/facts/project/browser_facts.md +++ /dev/null @@ -1,25 +0,0 @@ ---- -title: Browser facts -summary: Facts about browser -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T19:11:54.775Z' -updatedAt: '2026-05-27T19:11:54.775Z' ---- -## Reason -Curated facts from RLM extraction - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-27T19:11:54.664Z - -## Narrative -### Structure -Collected 2 facts for browser - -## Facts -- **browser**: Browser preview checked desktop and mobile. -- **browser**: Browser console checked, 0 errors and 0 warnings. diff --git a/.brv/context-tree/facts/project/browser_preview.md b/.brv/context-tree/facts/project/browser_preview.md deleted file mode 100644 index 3c29b4a8..00000000 --- a/.brv/context-tree/facts/project/browser_preview.md +++ /dev/null @@ -1,27 +0,0 @@ ---- -title: Browser preview -summary: Facts about browser preview -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T19:18:57.189Z' -updatedAt: '2026-05-27T19:18:57.189Z' ---- -## Reason -Curated facts extracted from context - -## Raw Concept -**Task:** -Document project facts - -**Timestamp:** 2026-05-27T19:18:57.119Z - -## Narrative -### Structure -Collected factual statements - -### Highlights -Contains 1 facts - -## Facts -- **browser preview**: Browser preview checked. diff --git a/.brv/context-tree/facts/project/browser_preview_facts.md b/.brv/context-tree/facts/project/browser_preview_facts.md deleted file mode 100644 index d924e63a..00000000 --- a/.brv/context-tree/facts/project/browser_preview_facts.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Browser preview facts -summary: Facts about Browser preview -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T19:11:54.773Z' -updatedAt: '2026-05-27T19:11:54.773Z' ---- -## Reason -Curated facts from RLM extraction - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-27T19:11:54.664Z - -## Narrative -### Structure -Collected 1 facts for Browser preview - -## Facts -- **Browser preview**: Browser preview showed no errors or warnings in the console for both desktop and mobile views. diff --git a/.brv/context-tree/facts/project/browser_visualization.md b/.brv/context-tree/facts/project/browser_visualization.md deleted file mode 100644 index 4fa87c56..00000000 --- a/.brv/context-tree/facts/project/browser_visualization.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Browser visualization -summary: Facts about browser visualization -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T09:45:49.157Z' -updatedAt: '2026-05-28T09:45:49.157Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-28T09:45:49.131Z - -## Narrative -### Highlights -Extracted 1 facts for browser visualization - -## Facts -- **browser visualization**: When using browser visualization, the live detection server is started with "npx impeccable live &" and stopped with "npx impeccable live stop". diff --git a/.brv/context-tree/facts/project/build.md b/.brv/context-tree/facts/project/build.md deleted file mode 100644 index 44ef6a90..00000000 --- a/.brv/context-tree/facts/project/build.md +++ /dev/null @@ -1,27 +0,0 @@ ---- -title: Build -summary: Facts about build -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T18:59:51.352Z' -updatedAt: '2026-05-29T09:19:16.222Z' ---- -## Reason -Curated factual statements from context extraction - -## Raw Concept -**Task:** -Document factual statements - -**Timestamp:** 2026-05-29T09:19:16.120Z - -## Narrative -### Structure -Collected facts for build - -### Highlights -Extracted 1 facts for build - -## Facts -- **build**: Build for @caplets/landing completed successfully, building 1 page. diff --git a/.brv/context-tree/facts/project/build_facts.md b/.brv/context-tree/facts/project/build_facts.md deleted file mode 100644 index 1fb81c49..00000000 --- a/.brv/context-tree/facts/project/build_facts.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Build facts -summary: Facts about build -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T10:14:23.989Z' -updatedAt: '2026-05-28T10:14:23.989Z' ---- -## Reason -Curated factual statements from source - -## Raw Concept -**Task:** -Curate factual statements - -**Timestamp:** 2026-05-28T10:14:23.972Z - -## Narrative -### Highlights -Collected 1 facts for build - -## Facts -- **build**: `pnpm --filter @caplets/landing build` PASS. diff --git a/.brv/context-tree/facts/project/build_verification.md b/.brv/context-tree/facts/project/build_verification.md deleted file mode 100644 index a000f0d1..00000000 --- a/.brv/context-tree/facts/project/build_verification.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Build verification -summary: Facts about build verification -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T11:14:20.338Z' -updatedAt: '2026-05-27T11:14:20.338Z' ---- -## Reason -Curated factual statements extracted from context - -## Raw Concept -**Task:** -Document factual statements - -**Timestamp:** 2026-05-27T11:14:20.303Z - -## Narrative -### Structure -Collection of facts regarding build verification - -## Facts -- **build verification**: Verification commands ran successfully, including format checks, tests, and typecheck for @caplets/core. diff --git a/.brv/context-tree/facts/project/campaign_headline.md b/.brv/context-tree/facts/project/campaign_headline.md deleted file mode 100644 index e9640a10..00000000 --- a/.brv/context-tree/facts/project/campaign_headline.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Campaign headline -summary: Facts about campaign headline -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-29T09:19:16.209Z' -updatedAt: '2026-05-29T09:19:16.209Z' ---- -## Reason -Curated factual statements from context extraction - -## Raw Concept -**Task:** -Document factual statements - -**Timestamp:** 2026-05-29T09:19:16.120Z - -## Narrative -### Highlights -Extracted 1 facts for campaign headline - -## Facts -- **campaign headline**: Implemented the new campaign headline across the visible first-screen surfaces. diff --git a/.brv/context-tree/facts/project/caplets.md b/.brv/context-tree/facts/project/caplets.md deleted file mode 100644 index 3c783d86..00000000 --- a/.brv/context-tree/facts/project/caplets.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -title: Caplets -summary: Facts about Caplets -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T11:17:59.543Z' -updatedAt: '2026-05-29T09:16:04.065Z' ---- -## Reason -Curate extracted factual statements - -## Raw Concept -**Task:** -Document factual statements - -**Timestamp:** 2026-05-28T11:17:59.530Z - -## Facts -- **Caplets**: Caplets: Give your agents capabilities, not giant tool walls diff --git a/.brv/context-tree/facts/project/caplets_dark_contrast_fixed_png.md b/.brv/context-tree/facts/project/caplets_dark_contrast_fixed_png.md deleted file mode 100644 index 108d7dc2..00000000 --- a/.brv/context-tree/facts/project/caplets_dark_contrast_fixed_png.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Caplets-dark-contrast-fixed.png -summary: Facts about caplets-dark-contrast-fixed.png -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T18:59:51.378Z' -updatedAt: '2026-05-27T18:59:51.378Z' ---- -## Reason -Curate extracted project facts - -## Raw Concept -**Task:** -Document project fact - -**Timestamp:** 2026-05-27T18:59:51.340Z - -## Narrative -### Highlights -Extracted 1 facts for caplets-dark-contrast-fixed.png - -## Facts -- **caplets-dark-contrast-fixed.png**: Screenshot caplets-dark-contrast-fixed.png captured. diff --git a/.brv/context-tree/facts/project/caplets_github_action_run.md b/.brv/context-tree/facts/project/caplets_github_action_run.md deleted file mode 100644 index 24569fbf..00000000 --- a/.brv/context-tree/facts/project/caplets_github_action_run.md +++ /dev/null @@ -1,29 +0,0 @@ ---- -title: caplets_github_action_run -summary: Link to specific GitHub Actions run for caplets CI -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T12:17:08.199Z' -updatedAt: '2026-05-28T12:17:08.199Z' ---- -## Reason -Record CI run link for caplets repository - -## Raw Concept -**Task:** -Record CI run reference - -**Changes:** -- Added CI run link - -**Timestamp:** 2026-05-28T12:17:08.198Z - -**Author:** ByteRover Context Engineer - -## Narrative -### Highlights -GitHub Actions run URL: https://github.com/spiritledsoftware/caplets/actions/runs/26572806394/job/78284056512 - -## Facts -- **ci_run_url**: GitHub Actions run URL for caplets CI [project] diff --git a/.brv/context-tree/facts/project/caplets_icon_facts.md b/.brv/context-tree/facts/project/caplets_icon_facts.md deleted file mode 100644 index e1401023..00000000 --- a/.brv/context-tree/facts/project/caplets_icon_facts.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Caplets icon facts -summary: Facts about Caplets icon -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T19:11:54.766Z' -updatedAt: '2026-05-27T19:11:54.766Z' ---- -## Reason -Curated facts from RLM extraction - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-27T19:11:54.664Z - -## Narrative -### Structure -Collected 1 facts for Caplets icon - -## Facts -- **Caplets icon**: The Caplets icon from `plugins/caplets/assets/icon.png` is now used for the favicon, Apple touch icon, and header brand mark. diff --git a/.brv/context-tree/facts/project/caplets_landing.md b/.brv/context-tree/facts/project/caplets_landing.md deleted file mode 100644 index 85c3fc01..00000000 --- a/.brv/context-tree/facts/project/caplets_landing.md +++ /dev/null @@ -1,27 +0,0 @@ ---- -title: '@caplets/landing' -summary: Facts about @caplets/landing -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T18:59:51.373Z' -updatedAt: '2026-05-27T18:59:51.373Z' ---- -## Reason -Curate extracted project facts - -## Raw Concept -**Task:** -Document project fact - -**Timestamp:** 2026-05-27T18:59:51.340Z - -## Narrative -### Highlights -Extracted 4 facts for @caplets/landing - -## Facts -- **@caplets/landing**: @caplets/landing build passed. -- **@caplets/landing**: pnpm --filter @caplets/landing typecheck passed with 0 errors, 0 warnings, 0 hints. -- **@caplets/landing**: ilter @caplets/landing typecheck passed, 0 errors, 0 warnings, 0 hints. -- **@caplets/landing**: Screenshot captured: caplets-dark-contrast-fixed.png diff --git a/.brv/context-tree/facts/project/caplets_navigation_facts.md b/.brv/context-tree/facts/project/caplets_navigation_facts.md deleted file mode 100644 index 647df1cc..00000000 --- a/.brv/context-tree/facts/project/caplets_navigation_facts.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Caplets navigation facts -summary: Facts about caplets navigation -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T19:11:54.777Z' -updatedAt: '2026-05-27T19:11:54.777Z' ---- -## Reason -Curated facts from RLM extraction - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-27T19:11:54.664Z - -## Narrative -### Structure -Collected 1 facts for caplets navigation - -## Facts -- **caplets navigation**: Screenshots: caplets-nav-desktop.png, caplets-nav-mobile.png diff --git a/.brv/context-tree/facts/project/caplets_runtime_and_caplet_files.md b/.brv/context-tree/facts/project/caplets_runtime_and_caplet_files.md deleted file mode 100644 index c4dfdbaa..00000000 --- a/.brv/context-tree/facts/project/caplets_runtime_and_caplet_files.md +++ /dev/null @@ -1,57 +0,0 @@ ---- -title: Caplets Runtime and Caplet Files -summary: Runtime and repository facts for caplets, including caplet entry files, GitHub/Linear/repo-cli modules, and the Context7 reference file. -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-21T10:20:40.242Z' -updatedAt: '2026-05-21T10:20:40.242Z' ---- - -## Reason - -Capture runtime and file-layout facts from the curated caplets context - -## Raw Concept - -**Task:** -Document the caplets repository structure and runtime-related file footprint - -**Changes:** -- Identified caplet modules and their key files -- Captured the Context7 reference file as part of the caplets knowledge set - -**Files:** -- caplets/github/CAPLET.md -- caplets/github/README.md -- caplets/github-cli/CAPLET.md -- caplets/linear/CAPLET.md -- caplets/linear/workflows.md -- caplets/repo-cli/CAPLET.md -- caplets/context7.md - -**Flow:** -caplet module files -> repository context references -> curated runtime facts - -**Timestamp:** 2026-05-21T10:20:29.575Z - -## Narrative - -### Structure - -The repository organizes caplet entry points by integration area, with dedicated folders for GitHub, GitHub CLI, Linear, and repo CLI. A separate Context7 reference file sits alongside the caplet folders. - -### Dependencies - -These notes are tied to the repository file layout rather than executable runtime dependencies. - -### Highlights - -Useful for quickly locating the caplet implementation and documentation files that define the repository’s integration surface. - -## Facts - -- **caplets_modules**: Caplets includes caplet files under caplets/github, caplets/github-cli, caplets/linear, and caplets/repo-cli. [project] -- **github_caplet_files**: The GitHub caplet has both a CAPLET.md and a README.md. [project] -- **linear_caplet_files**: The Linear caplet includes a CAPLET.md and a workflows.md file. [project] -- **context7_reference**: The repository includes a caplets/context7.md reference file. [project] diff --git a/.brv/context-tree/facts/project/caplets_try_it_in_60_seconds_path.md b/.brv/context-tree/facts/project/caplets_try_it_in_60_seconds_path.md deleted file mode 100644 index 3a4b16ab..00000000 --- a/.brv/context-tree/facts/project/caplets_try_it_in_60_seconds_path.md +++ /dev/null @@ -1,27 +0,0 @@ ---- -title: Caplets "try it in 60 seconds" path -summary: Facts about Caplets "try it in 60 seconds" path -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-29T09:10:59.472Z' -updatedAt: '2026-05-29T09:10:59.472Z' ---- -## Reason -Curate extracted facts from context - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-29T09:10:59.470Z - -## Narrative -### Structure -Collected factual statements - -## Facts -- **Caplets "try it in 60 seconds" path**: A new user can install, run, and see value quickly with no extra setup friction. -- **Caplets "try it in 60 seconds" path**: The quickstart is accurate, but the payoff is not instantly obvious or demo-like. -- **Caplets "try it in 60 seconds" path**: Setup works, but users may hit friction, missing examples, or unclear next steps. -- **Caplets "try it in 60 seconds" path**: You have not watched enough fresh users try it yet. diff --git a/.brv/context-tree/facts/project/capletserror_facts.md b/.brv/context-tree/facts/project/capletserror_facts.md deleted file mode 100644 index 2d613773..00000000 --- a/.brv/context-tree/facts/project/capletserror_facts.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: CapletsError Facts -summary: Facts about CapletsError -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T12:22:30.027Z' -updatedAt: '2026-05-27T12:22:30.027Z' ---- -## Reason -Curated factual statements from RLM extraction - -## Raw Concept -**Task:** -Document facts for CapletsError - -**Timestamp:** 2026-05-27T12:22:30.013Z - -## Narrative -### Highlights -Enhanced CapletsError to store optional details and toErrorResult returns a Markdown‑formatted error result via jsonResult. - -## Facts -- **CapletsError**: Enhanced CapletsError to store optional details and toErrorResult returns a Markdown‑formatted error result via jsonResult. diff --git a/.brv/context-tree/facts/project/card_grids.md b/.brv/context-tree/facts/project/card_grids.md deleted file mode 100644 index 77d470f3..00000000 --- a/.brv/context-tree/facts/project/card_grids.md +++ /dev/null @@ -1,27 +0,0 @@ ---- -title: Card grids -summary: Facts about card grids -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T19:18:57.166Z' -updatedAt: '2026-05-29T09:19:16.203Z' ---- -## Reason -Curated factual statements from context extraction - -## Raw Concept -**Task:** -Document factual statements - -**Timestamp:** 2026-05-29T09:19:16.120Z - -## Narrative -### Structure -Collected factual statements - -### Highlights -Extracted 1 facts for card grids - -## Facts -- **card grids**: Identical card grids with repeated icon, heading, and text are prohibited. diff --git a/.brv/context-tree/facts/project/card_layout_facts.md b/.brv/context-tree/facts/project/card_layout_facts.md deleted file mode 100644 index 652f8bcc..00000000 --- a/.brv/context-tree/facts/project/card_layout_facts.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Card layout facts -summary: Facts about card layout -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T19:11:54.692Z' -updatedAt: '2026-05-27T19:11:54.692Z' ---- -## Reason -Curated facts from RLM extraction - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-27T19:11:54.663Z - -## Narrative -### Structure -Collected 1 facts for card layout - -## Facts -- **card layout**: Identical card grids with same-sized cards repeated endlessly are prohibited. diff --git a/.brv/context-tree/facts/project/changeset_lossless_markdown_results_md_facts.md b/.brv/context-tree/facts/project/changeset_lossless_markdown_results_md_facts.md deleted file mode 100644 index b9a2c398..00000000 --- a/.brv/context-tree/facts/project/changeset_lossless_markdown_results_md_facts.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: .changeset/lossless-markdown-results.md Facts -summary: Facts about .changeset/lossless-markdown-results.md -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T12:22:30.072Z' -updatedAt: '2026-05-27T12:22:30.072Z' ---- -## Reason -Curated factual statements from RLM extraction - -## Raw Concept -**Task:** -Document facts for .changeset/lossless-markdown-results.md - -**Timestamp:** 2026-05-27T12:22:30.013Z - -## Narrative -### Highlights -Created .changeset/lossless-markdown-results.md documenting the new renderer, error handling, and integration updates. - -## Facts -- **.changeset/lossless-markdown-results.md**: Created .changeset/lossless-markdown-results.md documenting the new renderer, error handling, and integration updates. diff --git a/.brv/context-tree/facts/project/clarify.md b/.brv/context-tree/facts/project/clarify.md deleted file mode 100644 index d0cb29c2..00000000 --- a/.brv/context-tree/facts/project/clarify.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Clarify -summary: Facts about clarify -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T18:47:50.740Z' -updatedAt: '2026-05-27T18:47:50.740Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-27T18:47:50.505Z - -## Narrative -### Highlights -Extracted 1 facts for clarify - -## Facts -- **clarify**: `clarify [target]` is a Fix command that improves UX copy, labels, and error messages. diff --git a/.brv/context-tree/facts/project/clarify_target_facts.md b/.brv/context-tree/facts/project/clarify_target_facts.md deleted file mode 100644 index cbe0ddd0..00000000 --- a/.brv/context-tree/facts/project/clarify_target_facts.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: '`clarify [target]` facts' -summary: Facts about `clarify [target]` -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T19:11:54.750Z' -updatedAt: '2026-05-27T19:11:54.750Z' ---- -## Reason -Curated facts from RLM extraction - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-27T19:11:54.664Z - -## Narrative -### Structure -Collected 1 facts for `clarify [target]` - -## Facts -- **`clarify [target]`**: The command `clarify [target]` belongs to the Fix category and improves UX copy, labels, and error messages. diff --git a/.brv/context-tree/facts/project/cli_exit_codes.md b/.brv/context-tree/facts/project/cli_exit_codes.md deleted file mode 100644 index a69c7b5f..00000000 --- a/.brv/context-tree/facts/project/cli_exit_codes.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: CLI exit codes -summary: Facts about CLI exit codes -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T09:45:49.156Z' -updatedAt: '2026-05-28T09:45:49.156Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-28T09:45:49.131Z - -## Narrative -### Highlights -Extracted 1 facts for CLI exit codes - -## Facts -- **CLI exit codes**: The CLI detector exits with code 0 for clean and code 2 for findings. diff --git a/.brv/context-tree/facts/project/cli_facts.md b/.brv/context-tree/facts/project/cli_facts.md deleted file mode 100644 index 90d0438a..00000000 --- a/.brv/context-tree/facts/project/cli_facts.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: CLI Facts -summary: Facts about CLI -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T12:22:30.056Z' -updatedAt: '2026-05-27T12:22:30.056Z' ---- -## Reason -Curated factual statements from RLM extraction - -## Raw Concept -**Task:** -Document facts for CLI - -**Timestamp:** 2026-05-27T12:22:30.013Z - -## Narrative -### Highlights -CLI manager returns jsonResult for command output, including command, args, exit code; errors via errorResult. - -## Facts -- **CLI**: CLI manager returns jsonResult for command output, including command, args, exit code; errors via errorResult. diff --git a/.brv/context-tree/facts/project/cli_scan.md b/.brv/context-tree/facts/project/cli_scan.md deleted file mode 100644 index 2d98105d..00000000 --- a/.brv/context-tree/facts/project/cli_scan.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: CLI scan -summary: Facts about CLI scan -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T09:45:49.163Z' -updatedAt: '2026-05-28T09:45:49.163Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-28T09:45:49.131Z - -## Narrative -### Highlights -Extracted 1 facts for CLI scan - -## Facts -- **CLI scan**: Deterministic scan: `npx impeccable --json apps/landing/src/pages/index.astro` returned [] diff --git a/.brv/context-tree/facts/project/cloudflare_api_client.md b/.brv/context-tree/facts/project/cloudflare_api_client.md deleted file mode 100644 index 6d051b3c..00000000 --- a/.brv/context-tree/facts/project/cloudflare_api_client.md +++ /dev/null @@ -1,23 +0,0 @@ ---- -title: Cloudflare API client -summary: Facts about Cloudflare API client -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T10:53:51.575Z' -updatedAt: '2026-05-28T10:53:51.575Z' ---- -## Reason -Curated facts extracted from source context - -## Raw Concept -**Task:** -Document facts about Cloudflare API client - -**Timestamp:** 2026-05-28T10:53:51.569Z - -**Author:** ByteRover Context Engineer - -## Facts -- **Cloudflare API client**: Under Node 26, Alchemy’s Cloudflare API client receives gzipped bytes with no response headers (contentType: null, contentEncoding: null) -- **Cloudflare API client**: Under Node 24.15.0, the same Cloudflare endpoint returns correctly decoded JSON with contentType "application/json; charset=UTF-8" and contentEncoding "gzip" diff --git a/.brv/context-tree/facts/project/cognitive_load.md b/.brv/context-tree/facts/project/cognitive_load.md deleted file mode 100644 index 49b30127..00000000 --- a/.brv/context-tree/facts/project/cognitive_load.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Cognitive load -summary: Facts about cognitive load -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T09:45:49.153Z' -updatedAt: '2026-05-28T09:45:49.153Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-28T09:45:49.131Z - -## Narrative -### Highlights -Extracted 1 facts for cognitive load - -## Facts -- **cognitive load**: Cognitive load assessment uses an 8‑item checklist, flags >4 visible options at a decision point, and checks for progressive disclosure. diff --git a/.brv/context-tree/facts/project/color_facts.md b/.brv/context-tree/facts/project/color_facts.md deleted file mode 100644 index 02b58cff..00000000 --- a/.brv/context-tree/facts/project/color_facts.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Color facts -summary: Facts about color -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T19:11:54.675Z' -updatedAt: '2026-05-27T19:11:54.675Z' ---- -## Reason -Curated facts from RLM extraction - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-27T19:11:54.663Z - -## Narrative -### Structure -Collected 1 facts for color - -## Facts -- **color**: Never use #000 or #fff; tint every neutral toward the brand hue. diff --git a/.brv/context-tree/facts/project/color_space.md b/.brv/context-tree/facts/project/color_space.md deleted file mode 100644 index bbcec20c..00000000 --- a/.brv/context-tree/facts/project/color_space.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Color space -summary: Facts about color space -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T18:47:50.604Z' -updatedAt: '2026-05-27T18:47:50.604Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-27T18:47:50.505Z - -## Narrative -### Highlights -Extracted 1 facts for color space - -## Facts -- **color space**: Use the OKLCH color space for colors. diff --git a/.brv/context-tree/facts/project/color_specification.md b/.brv/context-tree/facts/project/color_specification.md deleted file mode 100644 index 288ec28d..00000000 --- a/.brv/context-tree/facts/project/color_specification.md +++ /dev/null @@ -1,27 +0,0 @@ ---- -title: Color specification -summary: Facts about color specification -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T19:12:30.292Z' -updatedAt: '2026-05-29T09:19:16.171Z' ---- -## Reason -Curated factual statements from context extraction - -## Raw Concept -**Task:** -Document factual statements - -**Timestamp:** 2026-05-29T09:19:16.120Z - -## Narrative -### Structure -Collected factual statements - -### Highlights -Extracted 1 facts for color specification - -## Facts -- **color specification**: Use OKLCH for colors and reduce chroma as lightness approaches 0 or 100. diff --git a/.brv/context-tree/facts/project/color_strategy.md b/.brv/context-tree/facts/project/color_strategy.md deleted file mode 100644 index e7b00cf9..00000000 --- a/.brv/context-tree/facts/project/color_strategy.md +++ /dev/null @@ -1,27 +0,0 @@ ---- -title: Color strategy -summary: Facts about color strategy -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T19:18:57.141Z' -updatedAt: '2026-05-29T09:19:16.177Z' ---- -## Reason -Curated factual statements from context extraction - -## Raw Concept -**Task:** -Document factual statements - -**Timestamp:** 2026-05-29T09:19:16.120Z - -## Narrative -### Structure -Collected factual statements - -### Highlights -Extracted 1 facts for color strategy - -## Facts -- **color strategy**: The "one accent ≤10%" rule applies only to the Restrained color strategy. diff --git a/.brv/context-tree/facts/project/color_usage.md b/.brv/context-tree/facts/project/color_usage.md deleted file mode 100644 index 49928cf1..00000000 --- a/.brv/context-tree/facts/project/color_usage.md +++ /dev/null @@ -1,27 +0,0 @@ ---- -title: Color usage -summary: Facts about color usage -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T18:47:50.598Z' -updatedAt: '2026-05-29T09:19:16.167Z' ---- -## Reason -Curated factual statements from context extraction - -## Raw Concept -**Task:** -Document factual statements - -**Timestamp:** 2026-05-29T09:19:16.120Z - -## Narrative -### Structure -Collected factual statements - -### Highlights -Extracted 1 facts for color usage - -## Facts -- **color usage**: Never use #000 or #fff; tint every neutral toward the brand hue. diff --git a/.brv/context-tree/facts/project/colorize.md b/.brv/context-tree/facts/project/colorize.md deleted file mode 100644 index 1c623901..00000000 --- a/.brv/context-tree/facts/project/colorize.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Colorize -summary: Facts about colorize -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T18:47:50.713Z' -updatedAt: '2026-05-27T18:47:50.713Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-27T18:47:50.505Z - -## Narrative -### Highlights -Extracted 1 facts for colorize - -## Facts -- **colorize**: `colorize [target]` is an Enhance command that adds strategic color to monochromatic UIs. diff --git a/.brv/context-tree/facts/project/colorize_target_facts.md b/.brv/context-tree/facts/project/colorize_target_facts.md deleted file mode 100644 index c5b30f0a..00000000 --- a/.brv/context-tree/facts/project/colorize_target_facts.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: '`colorize [target]` facts' -summary: Facts about `colorize [target]` -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T19:11:54.740Z' -updatedAt: '2026-05-27T19:11:54.740Z' ---- -## Reason -Curated facts from RLM extraction - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-27T19:11:54.664Z - -## Narrative -### Structure -Collected 1 facts for `colorize [target]` - -## Facts -- **`colorize [target]`**: The command `colorize [target]` belongs to the Enhance category and adds strategic color to monochromatic UIs. diff --git a/.brv/context-tree/facts/project/command.md b/.brv/context-tree/facts/project/command.md deleted file mode 100644 index b571b4df..00000000 --- a/.brv/context-tree/facts/project/command.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Command -summary: Facts about command -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T18:47:50.771Z' -updatedAt: '2026-05-27T18:47:50.771Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-27T18:47:50.505Z - -## Narrative -### Highlights -Extracted 1 facts for command - -## Facts -- **command**: Valid `` is any command from the table above. diff --git a/.brv/context-tree/facts/project/command_table.md b/.brv/context-tree/facts/project/command_table.md deleted file mode 100644 index 83f2b5f6..00000000 --- a/.brv/context-tree/facts/project/command_table.md +++ /dev/null @@ -1,27 +0,0 @@ ---- -title: Command table -summary: Facts about command table -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T19:12:30.331Z' -updatedAt: '2026-05-27T19:18:57.171Z' ---- -## Reason -Curated facts extracted from context - -## Raw Concept -**Task:** -Document project facts - -**Timestamp:** 2026-05-27T19:18:57.119Z - -## Narrative -### Structure -Collected factual statements - -### Highlights -Contains 1 facts - -## Facts -- **command table**: The command table lists commands such as `craft [feature]`, `shape [feature]`, and `animate [target]` with their categories and descriptions. diff --git a/.brv/context-tree/facts/project/command_validation.md b/.brv/context-tree/facts/project/command_validation.md deleted file mode 100644 index fa193dac..00000000 --- a/.brv/context-tree/facts/project/command_validation.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Command validation -summary: Facts about Command validation -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T19:12:30.330Z' -updatedAt: '2026-05-27T19:12:30.330Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-27T19:12:30.246Z - -## Narrative -### Structure -Extracted facts for Command validation - -## Facts -- **Command validation**: Valid `` is any command from the table above. diff --git a/.brv/context-tree/facts/project/commit.md b/.brv/context-tree/facts/project/commit.md deleted file mode 100644 index 6ca0cc23..00000000 --- a/.brv/context-tree/facts/project/commit.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Commit -summary: Facts about commit -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T10:26:09.817Z' -updatedAt: '2026-05-28T10:26:09.817Z' ---- -## Reason -Curated facts extracted from source context - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-28T10:26:09.781Z - -## Narrative -### Highlights -Extracted 1 facts for commit - -## Facts -- **commit**: Do not commit any changes. diff --git a/.brv/context-tree/facts/project/commit_1ad7201.md b/.brv/context-tree/facts/project/commit_1ad7201.md deleted file mode 100644 index 7c67c9b6..00000000 --- a/.brv/context-tree/facts/project/commit_1ad7201.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -title: commit 1ad7201 -summary: Facts about commit 1ad7201 -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T23:20:10.884Z' -updatedAt: '2026-05-27T23:20:10.884Z' ---- -## Reason -Curated facts from extraction - -## Raw Concept -**Task:** -Document facts for commit 1ad7201 - -**Timestamp:** 2026-05-27T23:20:10.880Z - -## Facts -- **commit 1ad7201**: Commit: `1ad7201 docs: specify agent setup tabs`. diff --git a/.brv/context-tree/facts/project/communication_assets.md b/.brv/context-tree/facts/project/communication_assets.md deleted file mode 100644 index 2916bfa5..00000000 --- a/.brv/context-tree/facts/project/communication_assets.md +++ /dev/null @@ -1,18 +0,0 @@ ---- -title: Communication assets -summary: Facts about communication assets -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-29T09:16:04.076Z' -updatedAt: '2026-05-29T09:16:04.076Z' ---- -## Reason -Curate extracted factual statements - -## Raw Concept -**Task:** -Document factual statements - -## Facts -- **communication assets**: Include the headline in the README hero, landing page hero, GitHub description, social posts, HN title, demo visuals, benchmark framing, and MCP builder copy diff --git a/.brv/context-tree/facts/project/completion_auth_fallback_behavior.md b/.brv/context-tree/facts/project/completion_auth_fallback_behavior.md deleted file mode 100644 index 68394b78..00000000 --- a/.brv/context-tree/facts/project/completion_auth_fallback_behavior.md +++ /dev/null @@ -1,43 +0,0 @@ ---- -title: Completion Auth Fallback Behavior -summary: Completion should return stale cached results when available, otherwise static/config fallback only, with no auth diagnostics in output. -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-21T16:21:08.210Z' -updatedAt: '2026-05-21T16:21:08.210Z' ---- -## Reason -Document durable decision about completion behavior when remote discovery is blocked by auth - -## Raw Concept -**Task:** -Document completion behavior when live discovery requires remote/downstream authentication - -**Changes:** -- Agreed that completion must not trigger interactive auth flows -- Return stale cache first, otherwise static/config fallback -- Keep auth hints out of completion output -- Document that caplets auth login enables richer remote completions - -**Flow:** -completion request -> live discovery blocked by auth -> return stale cache if present -> otherwise return static/config fallback - -**Timestamp:** 2026-05-21T16:20:57.900Z - -## Narrative -### Structure -A completion-time policy for remote discovery failures caused by auth requirements. - -### Dependencies -Applies when downstream auth blocks live discovery during shell completion. - -### Highlights -Treat completions as candidate generation only; do not surface diagnostics in the shell output. - -## Facts -- **completion_auth_flow**: No interactive auth flows should run during completion. [convention] -- **completion_fallback_behavior**: Completions should return stale cached results if available when live discovery is blocked by remote/downstream auth. [convention] -- **completion_fallback_behavior**: If cached results are unavailable, completions should return the static/config fallback only. [convention] -- **completion_output_format**: Completion output should not include special auth hints or diagnostics because shells expect candidates, not diagnostics. [convention] -- **remote_completion_documentation**: Documentation can note that `caplets auth login ` enables richer remote completions. [project] diff --git a/.brv/context-tree/facts/project/console.md b/.brv/context-tree/facts/project/console.md deleted file mode 100644 index fe53c199..00000000 --- a/.brv/context-tree/facts/project/console.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Console -summary: Facts about console -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T18:59:51.367Z' -updatedAt: '2026-05-27T18:59:51.367Z' ---- -## Reason -Curate extracted project facts - -## Raw Concept -**Task:** -Document project fact - -**Timestamp:** 2026-05-27T18:59:51.340Z - -## Narrative -### Highlights -Extracted 1 facts for console - -## Facts -- **console**: Browser console checked, 0 errors and 0 warnings. diff --git a/.brv/context-tree/facts/project/console_logging_policy.md b/.brv/context-tree/facts/project/console_logging_policy.md deleted file mode 100644 index d9b4ad9c..00000000 --- a/.brv/context-tree/facts/project/console_logging_policy.md +++ /dev/null @@ -1,37 +0,0 @@ ---- -title: Console Logging Policy -summary: Production code must not use console.log or similar logging because it can leak messages into native TUIs such as opencode, pi, and codex. -tags: [] -related: [facts/project/curation_runtime_conventions.md] -keywords: [] -createdAt: '2026-05-25T11:13:26.206Z' -updatedAt: '2026-05-25T11:13:26.206Z' ---- -## Reason -Capture the project rule to remove console.log and similar from production code - -## Raw Concept -**Task:** -Document the prohibition on console.log in production code - -**Changes:** -- Added a production logging restriction to prevent leaks into native TUIs - -**Flow:** -production code -> avoid console.log -> prevent message leakage into native TUIs - -**Timestamp:** 2026-05-25T11:13:13.676Z - -## Narrative -### Structure -This is a project-level rule governing logging behavior in production code. - -### Dependencies -Applies to native TUI environments including opencode, pi, and codex. - -### Highlights -The stated reason is to prevent leakage of messages into terminal user interfaces. - -## Facts -- **production_console_logging_policy**: Production code must not use console.log or similar logging. [convention] -- **native_tui_logging_risk**: Console logging can leak messages into native TUIs such as opencode, pi, and codex. [project] diff --git a/.brv/context-tree/facts/project/constraints.md b/.brv/context-tree/facts/project/constraints.md deleted file mode 100644 index a7638540..00000000 --- a/.brv/context-tree/facts/project/constraints.md +++ /dev/null @@ -1,25 +0,0 @@ ---- -title: Constraints -summary: Facts about constraints -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T13:55:17.284Z' -updatedAt: '2026-05-28T13:55:17.284Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Document extracted factual statements - -**Timestamp:** 2026-05-28T13:55:17.267Z - -## Narrative -### Highlights -Extracted 2 facts for constraints - -## Facts -- **constraints**: Design constraints prohibit em dashes, side‑stripe borders, gradient text, and glassmorphism. -- **constraints**: Design constraints require using existing tokens and OKLCH color space while maintaining the Caplets precise/calm/capable voice. diff --git a/.brv/context-tree/facts/project/content.md b/.brv/context-tree/facts/project/content.md deleted file mode 100644 index aa99ea7a..00000000 --- a/.brv/context-tree/facts/project/content.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: content -summary: Facts about content -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T13:48:34.347Z' -updatedAt: '2026-05-28T13:48:34.347Z' ---- -## Reason -Curated factual statements from recent context - -## Raw Concept -**Task:** -Document project facts - -**Timestamp:** 2026-05-28T13:48:34.339Z - -## Narrative -### Highlights -Collected 1 facts for content - -## Facts -- **content**: No integration tab/panel content or copy changes found, and no em dashes in the reviewed tab/panel markup or JS diff --git a/.brv/context-tree/facts/project/content_vs_style.md b/.brv/context-tree/facts/project/content_vs_style.md deleted file mode 100644 index 974e19a3..00000000 --- a/.brv/context-tree/facts/project/content_vs_style.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Content vs. style -summary: Facts about content vs. style -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T09:45:49.170Z' -updatedAt: '2026-05-28T09:45:49.170Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-28T09:45:49.131Z - -## Narrative -### Highlights -Extracted 1 facts for content vs. style - -## Facts -- **content vs. style**: The page says “quiet confidence” in the content, then the CSS says “watch this animated cartography rig.” diff --git a/.brv/context-tree/facts/project/context.md b/.brv/context-tree/facts/project/context.md deleted file mode 100644 index d12c855b..00000000 --- a/.brv/context-tree/facts/project/context.md +++ /dev/null @@ -1,15 +0,0 @@ ---- -related: [facts/conventions/context.md, facts/project/curation_runtime_conventions.md] ---- -# Topic: project - -## Overview - -Covers operational conventions for curation workflows in the runtime environment, especially when recon has already been computed. - -## Key Concepts - -- single-pass processing -- chunked extraction via mapExtract -- verification through applied file paths -- preserving exact instructions and avoiding unnecessary exploration diff --git a/.brv/context-tree/facts/project/context_loader.md b/.brv/context-tree/facts/project/context_loader.md deleted file mode 100644 index 623c7399..00000000 --- a/.brv/context-tree/facts/project/context_loader.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Context loader -summary: Facts about context loader -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T19:12:30.266Z' -updatedAt: '2026-05-27T19:12:30.266Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-27T19:12:30.245Z - -## Narrative -### Structure -Extracted facts for context loader - -## Facts -- **context loader**: The loader looks at the project root by default and falls back to .agents/context/ and docs/ if the root is clean. diff --git a/.brv/context-tree/facts/project/context_loading.md b/.brv/context-tree/facts/project/context_loading.md deleted file mode 100644 index c63f2720..00000000 --- a/.brv/context-tree/facts/project/context_loading.md +++ /dev/null @@ -1,27 +0,0 @@ ---- -title: Context loading -summary: Facts about context loading -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T18:47:50.527Z' -updatedAt: '2026-05-29T09:19:16.143Z' ---- -## Reason -Curated factual statements from context extraction - -## Raw Concept -**Task:** -Document factual statements - -**Timestamp:** 2026-05-29T09:19:16.120Z - -## Narrative -### Structure -Collected factual statements - -### Highlights -Extracted 1 facts for context loading - -## Facts -- **context loading**: The loader looks at the project root by default and falls back to .agents/context/ and docs/ if the root is clean. diff --git a/.brv/context-tree/facts/project/context_override.md b/.brv/context-tree/facts/project/context_override.md deleted file mode 100644 index 3840b5b8..00000000 --- a/.brv/context-tree/facts/project/context_override.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Context override -summary: Facts about context override -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T18:47:50.541Z' -updatedAt: '2026-05-27T18:47:50.541Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-27T18:47:50.505Z - -## Narrative -### Highlights -Extracted 1 facts for context override - -## Facts -- **context override**: Override the context directory with `IMPECCABLE_CONTEXT_DIR=path/to/dir` (absolute or relative to cwd). diff --git a/.brv/context-tree/facts/project/copy_text.md b/.brv/context-tree/facts/project/copy_text.md deleted file mode 100644 index d7614b5f..00000000 --- a/.brv/context-tree/facts/project/copy_text.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Copy text -summary: Facts about copy text -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T10:26:09.788Z' -updatedAt: '2026-05-28T10:26:09.788Z' ---- -## Reason -Curated facts extracted from source context - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-28T10:26:09.781Z - -## Narrative -### Highlights -Extracted 1 facts for copy text - -## Facts -- **copy text**: Update lingering `serve the map.` copy to avoid map dominance. diff --git a/.brv/context-tree/facts/project/core_line.md b/.brv/context-tree/facts/project/core_line.md deleted file mode 100644 index 6f930136..00000000 --- a/.brv/context-tree/facts/project/core_line.md +++ /dev/null @@ -1,25 +0,0 @@ ---- -title: Core line -summary: Facts about core line -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-29T09:19:16.224Z' -updatedAt: '2026-05-29T09:19:16.224Z' ---- -## Reason -Curated factual statements from context extraction - -## Raw Concept -**Task:** -Document factual statements - -**Timestamp:** 2026-05-29T09:19:16.120Z - -## Narrative -### Highlights -Extracted 2 facts for core line - -## Facts -- **core line**: The new core line is now: “Give your agents capabilities, not giant tool walls.” -- **core line**: The new core line is: “Give your agents capabilities, not giant tool walls.” diff --git a/.brv/context-tree/facts/project/craft.md b/.brv/context-tree/facts/project/craft.md deleted file mode 100644 index 0d3e5ed4..00000000 --- a/.brv/context-tree/facts/project/craft.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Craft -summary: Facts about craft -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T18:47:50.654Z' -updatedAt: '2026-05-27T18:47:50.654Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-27T18:47:50.505Z - -## Narrative -### Highlights -Extracted 1 facts for craft - -## Facts -- **craft**: `craft [feature]` is a Build command that shapes then builds a feature end-to-end. diff --git a/.brv/context-tree/facts/project/craft_command.md b/.brv/context-tree/facts/project/craft_command.md deleted file mode 100644 index 35895e3e..00000000 --- a/.brv/context-tree/facts/project/craft_command.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Craft command -summary: Facts about Craft command -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T19:12:30.351Z' -updatedAt: '2026-05-27T19:12:30.351Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-27T19:12:30.246Z - -## Narrative -### Structure -Extracted facts for Craft command - -## Facts -- **Craft command**: If the first word is `craft`, setup still runs first, but reference/craft.md owns the rest of the flow. diff --git a/.brv/context-tree/facts/project/craft_feature_facts.md b/.brv/context-tree/facts/project/craft_feature_facts.md deleted file mode 100644 index 2e18d0b3..00000000 --- a/.brv/context-tree/facts/project/craft_feature_facts.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: '`craft [feature]` facts' -summary: Facts about `craft [feature]` -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T19:11:54.709Z' -updatedAt: '2026-05-27T19:11:54.709Z' ---- -## Reason -Curated facts from RLM extraction - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-27T19:11:54.664Z - -## Narrative -### Structure -Collected 1 facts for `craft [feature]` - -## Facts -- **`craft [feature]`**: The command `craft [feature]` belongs to the Build category and shapes then builds a feature end-to-end. diff --git a/.brv/context-tree/facts/project/critique.md b/.brv/context-tree/facts/project/critique.md deleted file mode 100644 index b1c3fe50..00000000 --- a/.brv/context-tree/facts/project/critique.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Critique -summary: Facts about critique -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T18:47:50.667Z' -updatedAt: '2026-05-27T18:47:50.667Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-27T18:47:50.505Z - -## Narrative -### Highlights -Extracted 1 facts for critique - -## Facts -- **critique**: `critique [target]` is an Evaluate command that performs UX design review with heuristic scoring. diff --git a/.brv/context-tree/facts/project/critique_target_facts.md b/.brv/context-tree/facts/project/critique_target_facts.md deleted file mode 100644 index 749232ec..00000000 --- a/.brv/context-tree/facts/project/critique_target_facts.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: '`critique [target]` facts' -summary: Facts about `critique [target]` -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T19:11:54.720Z' -updatedAt: '2026-05-27T19:11:54.720Z' ---- -## Reason -Curated facts from RLM extraction - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-27T19:11:54.664Z - -## Narrative -### Structure -Collected 1 facts for `critique [target]` - -## Facts -- **`critique [target]`**: The command `critique [target]` belongs to the Evaluate category and performs a UX design review with heuristic scoring. diff --git a/.brv/context-tree/facts/project/css_animation.md b/.brv/context-tree/facts/project/css_animation.md deleted file mode 100644 index 372f8369..00000000 --- a/.brv/context-tree/facts/project/css_animation.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: CSS animation -summary: Facts about CSS animation -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T19:12:30.301Z' -updatedAt: '2026-05-27T19:12:30.301Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-27T19:12:30.246Z - -## Narrative -### Structure -Extracted facts for CSS animation - -## Facts -- **CSS animation**: Do not animate CSS layout properties. diff --git a/.brv/context-tree/facts/project/css_implementation.md b/.brv/context-tree/facts/project/css_implementation.md deleted file mode 100644 index ed26b36b..00000000 --- a/.brv/context-tree/facts/project/css_implementation.md +++ /dev/null @@ -1,27 +0,0 @@ ---- -title: CSS implementation -summary: Facts about CSS implementation -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T13:40:49.040Z' -updatedAt: '2026-05-28T13:40:49.040Z' ---- -## Reason -Curate extracted facts from context - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-28T13:40:49.005Z - -## Narrative -### Structure -List of factual statements extracted - -### Highlights -Contains 1 facts - -## Facts -- **CSS implementation**: The implementation fits existing CSS patterns: simple selectors, existing variables, existing breakpoint structure, no new design tokens or one‑off abstractions. diff --git a/.brv/context-tree/facts/project/css_variable.md b/.brv/context-tree/facts/project/css_variable.md deleted file mode 100644 index 8b2b7b01..00000000 --- a/.brv/context-tree/facts/project/css_variable.md +++ /dev/null @@ -1,22 +0,0 @@ ---- -title: CSS variable -summary: Facts about CSS variable -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T13:58:26.040Z' -updatedAt: '2026-05-28T13:58:26.040Z' ---- -## Reason -Curated facts extraction - -## Raw Concept -**Task:** -Document facts for CSS variable - -## Narrative -### Highlights -Extracted 1 facts for CSS variable - -## Facts -- **CSS variable**: `--header-shadow` is removed. diff --git a/.brv/context-tree/facts/project/curated_project_facts.md b/.brv/context-tree/facts/project/curated_project_facts.md deleted file mode 100644 index 08bea2fb..00000000 --- a/.brv/context-tree/facts/project/curated_project_facts.md +++ /dev/null @@ -1,97 +0,0 @@ ---- -title: Curated Project Facts -summary: Aggregated project-related facts extracted via RLM extraction -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T18:17:41.215Z' -updatedAt: '2026-05-27T18:17:41.215Z' ---- -## Reason -Extracted factual statements from recent context - -## Raw Concept -**Task:** -Curate extracted project facts - -**Timestamp:** 2026-05-27T18:17:41.212Z - -## Narrative -### Highlights -Extracted 74 unique facts across 72 subjects. - -## Facts -- **PRODUCT.md**: PRODUCT.md is required. -- **DESIGN.md**: DESIGN.md is optional, strongly recommended. -- **loader**: The loader looks at the project root by default and falls back to .agents/context/ and docs/ if the root is clean. -- **IMPECCABLE_CONTEXT_DIR**: Override with IMPECCABLE_CONTEXT_DIR=path/to/dir (absolute or relative to cwd). -- **load-context.mjs**: Load both files in one call using: node {{scripts_path}}/load-context.mjs -- **loader output**: Never pipe the loader output through head, tail, grep, or jq. -- **PRODUCT.md**: If PRODUCT.md is missing, empty, or contains placeholder [TODO] markers with less than 200 characters, run {{command_prefix}}impeccable teach and then resume the original task. -- **DESIGN.md**: If DESIGN.md is missing, prompt the user once per session to run {{command_prefix}}impeccable document, then proceed. -- **design task**: Every design task is either brand (marketing, landing, campaign, long-form content, portfolio) or product (app UI, admin, dashboard, tool). -- **register identification**: Identify the register before designing, using priority: (1) cue in the task itself, (2) the surface in focus, (3) the register field in PRODUCT.md. -- **Color system**: Use OKLCH for color definitions. -- **Chroma adjustment**: Reduce chroma as lightness approaches 0 or 100 because high chroma at extremes looks garish. -- **Neutral tinting**: Never use #000 or #fff; tint every neutral toward the brand hue with chroma 0.005–0.01. -- **Restrained strategy**: Restrained color strategy uses tinted neutrals plus one accent ≤10% of surface. -- **Committed strategy**: Committed color strategy uses one saturated color for 30–60% of surface. -- **Full palette**: Full palette strategy uses 3–4 named color roles deliberately. -- **Drenched strategy**: Drenched strategy makes the surface itself the color. -- **Theme selection**: Dark vs. light theme should not be chosen by default; it must be justified by physical context. -- **Line length**: Cap body line length at 65–75 characters. -- **Hierarchy**: Maintain a hierarchy through scale and weight contrast with at least a 1.25 ratio between steps. -- **Spacing**: Vary spacing to create rhythm; avoid using the same padding everywhere. -- **Card usage**: Cards should only be used when they are the best affordance; nested cards are always wrong. -- **Animation**: Do not animate CSS layout properties. -- **Easing**: Use ease-out exponential curves (ease-out-quart, quint, expo) for motion; avoid bounce or elastic easing. -- **Side-stripe borders**: Side-stripe borders greater than 1px as colored accents are prohibited; use full borders, background tints, leading numbers/icons, or nothing instead. -- **Gradient text**: Gradient text using background-clip: text with a gradient background is prohibited; use a single solid color instead. -- **Glassmorphism**: Glassmorphism as a default style is prohibited; use blurs and glass cards only rarely and purposefully. -- **Hero-metric template**: The hero-metric template (big number, small label, supporting stats, gradient accent) is prohibited as a SaaS cliché. -- **Identical card grids**: Identical card grids with the same-sized cards repeated endlessly are prohibited. -- **Modals**: Modals should not be the first design thought; explore inline or progressive alternatives first. -- **Copy economy**: Every word in copy must earn its place; avoid restated headings and intros that repeat the title. -- **Punctuation**: Do not use em dashes; use commas, colons, semicolons, periods, or parentheses instead. -- **craft**: `craft [feature]` shapes then builds a feature end-to-end. -- **shape**: `shape [feature]` plans UX/UI before writing code. -- **teach**: `teach` sets up PRODUCT.md and DESIGN.md context. -- **document**: `document` generates DESIGN.md from existing project code. -- **extract**: `extract [target]` pulls reusable tokens and components into the design system. -- **critique**: `critique [target]` performs a UX design review with heuristic scoring. -- **audit**: `audit [target]` conducts technical quality checks for accessibility, performance, and responsiveness. -- **polish**: `polish [target]` provides a final quality pass before shipping. -- **bolder**: `bolder [target]` amplifies safe or bland designs. -- **quieter**: `quieter [target]` tones down aggressive or overstimulating designs. -- **distill**: `distill [target]` strips to essence and removes complexity. -- **harden**: `harden [target]` makes a product production‑ready by handling errors, i18n, and edge cases. -- **onboard**: `onboard [target]` designs first‑run flows, empty states, and activation experiences. -- **animate**: `animate [target]` adds purposeful animations and motion. -- **colorize**: `colorize [target]` adds strategic color to monochromatic UIs. -- **typeset**: `typeset [target]` improves typography hierarchy and fonts. -- **layout**: `layout [target]` fixes spacing, rhythm, and visual hierarchy. -- **delight**: `delight [target]` adds personality and memorable touches. -- **overdrive**: `overdrive [target]` pushes past conventional limits. -- **clarify**: `clarify [target]` improves UX copy, labels, and error messages. -- **adapt**: `adapt [target]` adapts designs for different devices and screen sizes. -- **optimize**: `optimize [target]` diagnoses and fixes UI performance issues. -- **live**: `live` enables visual variant mode to pick elements in the browser and generate alternatives. -- **pin**: `pin ` pins a command for quick access. -- **unpin**: `unpin ` unpins a previously pinned command. -- **Pin**: Pin creates a standalone shortcut so {{command_prefix}} invokes {{command_prefix}}impeccable directly. -- **Unpin**: Unpin removes the shortcut created by Pin. -- **Pin/Unpin script**: The script writes to every harness directory present in the project. -- **pin.mjs**: The command node {{scripts_path}}/pin.mjs is used to pin or unpin commands. -- **Command argument**: Valid is any command from the table above. -- **Visual design system**: Generated the visual design system. -- **Repository**: Changed files: DESIGN.md and .impeccable/design.json. -- **North star**: North star is "The Tool Cartographer". -- **Palette**: Palette includes charred ink, parchment, and rare ember accent. -- **Elevation**: Elevation is tonal layering, flat at rest. -- **Components**: Components are tactile, calm, compact product primitives. -- **Sidecar**: Sidecar includes renderable button, chip, card, input, and nav snippets for the live panel. -- **.impeccable/design.json**: .impeccable/design.json parses as valid JSON. -- **Impeccable context**: Refreshed impeccable context with the new DESIGN.md. -- **r**: r includes renderable button, chip, card, input, and nav snippets for the live panel -- **`.impeccable/design.json`**: `.impeccable/design.json` parses as valid JSON -- **impeccable context**: Refreshed impeccable context with the new `DESIGN.md` diff --git a/.brv/context-tree/facts/project/curation-itself-is-treated-as-durable-project-infrastructure.md b/.brv/context-tree/facts/project/curation-itself-is-treated-as-durable-project-infrastructure.md deleted file mode 100644 index e2da0f74..00000000 --- a/.brv/context-tree/facts/project/curation-itself-is-treated-as-durable-project-infrastructure.md +++ /dev/null @@ -1,22 +0,0 @@ ---- -confidence: 0.91 -sources: [facts/_index.md, docs/_index.md] -synthesized_at: '2026-05-21T23:23:26.222Z' -type: synthesis -title: Curation itself is treated as durable project infrastructure -summary: The knowledge base records curation rules and project facts as first-class runtime knowledge, not chat-only notes. -tags: [curation, runtime, knowledge-base, conventions] -related: [facts/project/curation_context_facts.md, facts/project/curation_input_notes.md, facts/project/autocommit_hooks_for_memory_files.md] -keywords: [upsert, durable, facts, narrative, verification, knowledge, curation, policy] -createdAt: '2026-05-21T23:23:26.222Z' -updatedAt: '2026-05-21T23:23:26.222Z' ---- - -# Curation itself is treated as durable project infrastructure - -The facts and docs domains both preserve curation/runtime conventions as durable operational knowledge: use UPSERT by default, keep structured rawConcept/narrative content, extract facts explicitly, and verify results before considering curation complete. This means the knowledge base is maintaining its own operating policy alongside product knowledge. - -## Evidence - -- **facts**: The curation/runtime conventions require UPSERT as the preferred operation, structured `rawConcept` and `narrative` fields, preserving lasting facts and notable outcomes, and verifying success with `result.summary.failed === 0`. -- **docs**: Planning knowledge is organized as durable roadmap material tied to architecture and release pipeline work, showing that project knowledge is curated as a maintained asset rather than ephemeral commentary. diff --git a/.brv/context-tree/facts/project/curation_context_facts.md b/.brv/context-tree/facts/project/curation_context_facts.md deleted file mode 100644 index 8f837503..00000000 --- a/.brv/context-tree/facts/project/curation_context_facts.md +++ /dev/null @@ -1,41 +0,0 @@ ---- -title: Curation Context Facts -summary: Project facts and runtime conventions extracted from the curation context, including task metadata and processing guidance. -tags: [] -related: [facts/project/curation_input_notes.md, facts/project/curation-itself-is-treated-as-durable-project-infrastructure.md, facts/project/autocommit_hooks_for_memory_files.md] -keywords: [] -createdAt: '2026-05-20T19:28:15.751Z' -updatedAt: '2026-05-20T19:28:15.751Z' ---- -## Reason -Curate extracted runtime and task metadata facts from the provided context - -## Raw Concept -**Task:** -Curate RLM context metadata and runtime guidance - -**Changes:** -- Extracted key-value facts from the provided context variable - -**Flow:** -context -> line parsing -> fact extraction -> deduplication -> curation - -**Timestamp:** 2026-05-20T19:28:10.552Z - -**Author:** ByteRover context engineer - -## Narrative -### Structure -Captured the current curation task metadata and processing instructions as durable project facts. - -### Dependencies -Uses the precomputed recon result and the supplied task/history/metadata variables. - -### Highlights -The context is small enough for single-pass processing and the provided recon recommended single-pass mode. - -## Facts -- **curate_only_information_with_lasting_value**: Curate only information with lasting value: facts, decisions, technical details, preferences, or notable outcomes. [project] -- **_user_**: [user]: continue [project] -- **_assistant_**: [assistant]: Yes — I checked again more broadly, including PR review threads and top-level review bodies. [project] -- **_warnings_**: - `warnings?: string[]` protocol field is defined but unused/unwired. [project] diff --git a/.brv/context-tree/facts/project/curation_input_notes.md b/.brv/context-tree/facts/project/curation_input_notes.md deleted file mode 100644 index e50d06c8..00000000 --- a/.brv/context-tree/facts/project/curation_input_notes.md +++ /dev/null @@ -1,45 +0,0 @@ ---- -title: Curation Input Notes -summary: Captured the provided curation input as durable project knowledge. -tags: [] -related: [facts/project/curation_context_facts.md, facts/project/curation-itself-is-treated-as-durable-project-infrastructure.md, facts/project/autocommit_hooks_for_memory_files.md] -keywords: [] -createdAt: '2026-05-20T17:11:58.075Z' -updatedAt: '2026-05-20T17:11:58.075Z' ---- - -## Reason - -Curate the provided RLM context into durable knowledge - -## Raw Concept - -**Task:** -Curate the provided RLM context into the knowledge tree - -**Changes:** -- Captured the input as durable knowledge - -**Flow:** -context input -> fact extraction -> durable knowledge entry - -**Timestamp:** 2026-05-20T17:11:53.415Z - -## Narrative - -### Structure - -A compact curation note derived from the provided RLM context. - -### Highlights - -Preserves the supplied context for future recall. - -## Facts - -- **curation_input**: The following is a conversation between a user and an AI assistant. [other] -- **curation_input**: Curate only information with lasting value: facts, decisions, technical details, preferences, or notable outcomes. [other] -- **curation_input**: Skip trivial messages such as greetings, acknowledgments ("ok", "thanks", "sure", "got it"), one-word replies, anything with no substantive content. [other] -- **curation_input**: Conversation: [other] -- **curation_input**: --- [other] -- **curation_input**: [user]: continue [other] diff --git a/.brv/context-tree/facts/project/curation_runtime_conventions.md b/.brv/context-tree/facts/project/curation_runtime_conventions.md deleted file mode 100644 index c1b8ea14..00000000 --- a/.brv/context-tree/facts/project/curation_runtime_conventions.md +++ /dev/null @@ -1,51 +0,0 @@ ---- -consolidated_at: '2026-05-27T10:03:18.780Z' -consolidated_from: [{date: '2026-05-27T10:03:18.780Z', path: facts/curation_runtime_conventions/rlm_curation_workflow.md, reason: 'These files describe the same canonical RLM curation workflow with overlapping instructions on precomputed recon, single-pass handling, chunked mapExtract fallback, bare taskId usage, timeout 300000, no raw-context printing, verification via applied file paths, and UPSERT preference. The full note and the existing project convention file are redundant representations of one workflow policy, so they should be consolidated into the richer canonical convention note.'}, {date: '2026-05-27T10:03:18.780Z', path: facts/project/curate_rlm_workflow_context.md, reason: 'These files describe the same canonical RLM curation workflow with overlapping instructions on precomputed recon, single-pass handling, chunked mapExtract fallback, bare taskId usage, timeout 300000, no raw-context printing, verification via applied file paths, and UPSERT preference. The full note and the existing project convention file are redundant representations of one workflow policy, so they should be consolidated into the richer canonical convention note.'}] -related: [facts/project/context.md] ---- -# Title: Curation Runtime Conventions - -## Summary -Canonical runtime guidance for RLM curation sessions: use precomputed recon, choose single-pass for small contexts, fall back to chunked mapExtract only when needed, and verify results through applied file paths rather than filesystem reads. - -## Core Workflow -precomputed recon -> inspect suggestedMode -> single-pass curate or chunked extraction -> curate with UPSERT -> verify result.applied[].filePath -> report status - -## Must-Preserve Rules -- Do NOT print raw context. -- Do NOT call tools.curation.recon when recon has already been computed. -- If suggestedMode is single-pass, proceed directly to extraction/curation and skip chunking. -- For chunked extraction, use tools.curation.mapExtract(). -- When using mapExtract, pass taskId as a bare variable, not a string. -- Any code_exec call containing mapExtract MUST use timeout: 300000 on the code_exec tool call itself. -- Use tools.curation.groupBySubject() and tools.curation.dedup() to organize extracted facts. -- Verify via result.applied[].filePath; do NOT call readFile for verification. -- Prefer UPSERT for durable knowledge entries. -- Verify success via result.summary.failed === 0 when available. - -## Canonical Facts to Preserve -- Single-pass mode is recommended when recon suggests it for small contexts. -- Chunked extraction is reserved for larger contexts. -- Precomputed recon is the source of truth for mode selection. -- Verification should rely on curated result output, not rereading files. -- Session variables may include context, history, metadata, and task ID names that should be used directly. - -## Temporal Note -This guidance has appeared in multiple curation sessions over time; the canonical file should preserve the newest and most complete wording while noting that older session-specific entries existed and were later consolidated. - -## Facts -- Single-pass mode is recommended when recon suggests it for small contexts. -- Chunked extraction is reserved for larger contexts. -- Precomputed recon is the source of truth for mode selection. -- Verification should rely on curated result output, not rereading files. -- Session variables may include context, history, metadata, and task ID names that should be used directly. - -## Additions to Preserve from Overlapping Files -- Earlier sessions recorded the exact required workflow as: recon -> mapExtract -> dedup/group -> curate -> verify applied file paths. -- Verification must rely on result.applied[].filePath and not on readFile. -- Any code_exec call containing mapExtract must set timeout to 300000 at the outer code_exec level. -- mapExtract taskId must be passed as a bare variable. -- do not print raw context; do not call recon again when precomputed recon is already available. -- use tools.curation.groupBySubject() and tools.curation.dedup() to organize extractions. -- Later guidance reinforced canonical wording around UPSERT preference and checking result.summary.failed. -- For small pre-reconciled contexts, proceed directly in single-pass mode; chunked extraction is for larger contexts. diff --git a/.brv/context-tree/facts/project/dark_mode_contrast.md b/.brv/context-tree/facts/project/dark_mode_contrast.md deleted file mode 100644 index 961e2bd4..00000000 --- a/.brv/context-tree/facts/project/dark_mode_contrast.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Dark mode contrast -summary: Facts about dark mode contrast -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T18:59:51.344Z' -updatedAt: '2026-05-27T18:59:51.344Z' ---- -## Reason -Curate extracted project facts - -## Raw Concept -**Task:** -Document project fact - -**Timestamp:** 2026-05-27T18:59:51.340Z - -## Narrative -### Highlights -Extracted 1 facts for dark mode contrast - -## Facts -- **dark mode contrast**: Improved contrast for the dark surfaces. diff --git a/.brv/context-tree/facts/project/delight.md b/.brv/context-tree/facts/project/delight.md deleted file mode 100644 index 7e93b000..00000000 --- a/.brv/context-tree/facts/project/delight.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Delight -summary: Facts about delight -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T18:47:50.732Z' -updatedAt: '2026-05-27T18:47:50.732Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-27T18:47:50.505Z - -## Narrative -### Highlights -Extracted 1 facts for delight - -## Facts -- **delight**: `delight [target]` is an Enhance command that adds personality and memorable touches. diff --git a/.brv/context-tree/facts/project/delight_target_facts.md b/.brv/context-tree/facts/project/delight_target_facts.md deleted file mode 100644 index fe237c18..00000000 --- a/.brv/context-tree/facts/project/delight_target_facts.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: '`delight [target]` facts' -summary: Facts about `delight [target]` -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T19:11:54.746Z' -updatedAt: '2026-05-27T19:11:54.746Z' ---- -## Reason -Curated facts from RLM extraction - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-27T19:11:54.664Z - -## Narrative -### Structure -Collected 1 facts for `delight [target]` - -## Facts -- **`delight [target]`**: The command `delight [target]` belongs to the Enhance category and adds personality and memorable touches. diff --git a/.brv/context-tree/facts/project/deprecation_warnings.md b/.brv/context-tree/facts/project/deprecation_warnings.md deleted file mode 100644 index bf75dce0..00000000 --- a/.brv/context-tree/facts/project/deprecation_warnings.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -title: Deprecation warnings -summary: Facts about deprecation warnings -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T10:40:14.711Z' -updatedAt: '2026-05-28T10:40:14.711Z' ---- -## Reason -Curated extracted factual statements - -## Raw Concept -**Task:** -Document facts for deprecation warnings - -**Timestamp:** 2026-05-28T10:40:14.681Z - -## Facts -- **deprecation warnings**: Build/typecheck emitted Node [DEP0205] module.register() deprecation warnings only diff --git a/.brv/context-tree/facts/project/design_md.md b/.brv/context-tree/facts/project/design_md.md deleted file mode 100644 index fb40301d..00000000 --- a/.brv/context-tree/facts/project/design_md.md +++ /dev/null @@ -1,27 +0,0 @@ ---- -title: DESIGN.md -summary: Facts about DESIGN.md -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T18:47:50.556Z' -updatedAt: '2026-05-29T09:19:16.138Z' ---- -## Reason -Curated factual statements from context extraction - -## Raw Concept -**Task:** -Document factual statements - -**Timestamp:** 2026-05-29T09:19:16.120Z - -## Narrative -### Structure -Collected factual statements - -### Highlights -Extracted 1 facts for DESIGN.md - -## Facts -- **DESIGN.md**: DESIGN.md is optional, strongly recommended. diff --git a/.brv/context-tree/facts/project/design_md_facts.md b/.brv/context-tree/facts/project/design_md_facts.md deleted file mode 100644 index 85e69850..00000000 --- a/.brv/context-tree/facts/project/design_md_facts.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: DESIGN.md facts -summary: Facts about DESIGN.md -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T19:11:54.674Z' -updatedAt: '2026-05-27T19:11:54.674Z' ---- -## Reason -Curated facts from RLM extraction - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-27T19:11:54.663Z - -## Narrative -### Structure -Collected 1 facts for DESIGN.md - -## Facts -- **DESIGN.md**: DESIGN.md is optional, strongly recommended. diff --git a/.brv/context-tree/facts/project/design_task.md b/.brv/context-tree/facts/project/design_task.md deleted file mode 100644 index 37a1b39f..00000000 --- a/.brv/context-tree/facts/project/design_task.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Design task -summary: Facts about design task -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T19:12:30.285Z' -updatedAt: '2026-05-27T19:12:30.285Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-27T19:12:30.246Z - -## Narrative -### Structure -Extracted facts for design task - -## Facts -- **design task**: Every design task is either brand (marketing, landing, campaign, long-form content, portfolio) or product (app UI, admin, dashboard, tool). diff --git a/.brv/context-tree/facts/project/design_tokens.md b/.brv/context-tree/facts/project/design_tokens.md deleted file mode 100644 index 9dcb6018..00000000 --- a/.brv/context-tree/facts/project/design_tokens.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Design tokens -summary: Facts about design tokens -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T18:59:51.348Z' -updatedAt: '2026-05-27T18:59:51.348Z' ---- -## Reason -Curate extracted project facts - -## Raw Concept -**Task:** -Document project fact - -**Timestamp:** 2026-05-27T18:59:51.340Z - -## Narrative -### Highlights -Extracted 1 facts for design tokens - -## Facts -- **design tokens**: Added dedicated dark-surface tokens: --night-ink, --night-panel, --night-line, --night-text, --night-muted. diff --git a/.brv/context-tree/facts/project/desktop_nav_facts.md b/.brv/context-tree/facts/project/desktop_nav_facts.md deleted file mode 100644 index 982782d8..00000000 --- a/.brv/context-tree/facts/project/desktop_nav_facts.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Desktop nav facts -summary: Facts about Desktop nav -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T19:11:54.760Z' -updatedAt: '2026-05-27T19:11:54.760Z' ---- -## Reason -Curated facts from RLM extraction - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-27T19:11:54.664Z - -## Narrative -### Structure -Collected 1 facts for Desktop nav - -## Facts -- **Desktop nav**: Desktop navigation now includes numbered map‑style waypoints: 01 Map, 02 Proof, 03 Install. diff --git a/.brv/context-tree/facts/project/diff_scope.md b/.brv/context-tree/facts/project/diff_scope.md deleted file mode 100644 index 5f5afb48..00000000 --- a/.brv/context-tree/facts/project/diff_scope.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Diff scope -summary: Facts about diff scope -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T13:55:17.318Z' -updatedAt: '2026-05-28T13:55:17.318Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Document extracted factual statements - -**Timestamp:** 2026-05-28T13:55:17.267Z - -## Narrative -### Highlights -Extracted 1 facts for diff scope - -## Facts -- **diff scope**: The working tree already contained other landing edits in the same files, so the full diff includes changes outside this task. diff --git a/.brv/context-tree/facts/project/distill.md b/.brv/context-tree/facts/project/distill.md deleted file mode 100644 index 9c33574f..00000000 --- a/.brv/context-tree/facts/project/distill.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Distill -summary: Facts about distill -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T18:47:50.697Z' -updatedAt: '2026-05-27T18:47:50.697Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-27T18:47:50.505Z - -## Narrative -### Highlights -Extracted 1 facts for distill - -## Facts -- **distill**: `distill [target]` is a Refine command that strips to essence and removes complexity. diff --git a/.brv/context-tree/facts/project/distill_target_facts.md b/.brv/context-tree/facts/project/distill_target_facts.md deleted file mode 100644 index 485452c4..00000000 --- a/.brv/context-tree/facts/project/distill_target_facts.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: '`distill [target]` facts' -summary: Facts about `distill [target]` -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T19:11:54.730Z' -updatedAt: '2026-05-27T19:11:54.730Z' ---- -## Reason -Curated facts from RLM extraction - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-27T19:11:54.664Z - -## Narrative -### Structure -Collected 1 facts for `distill [target]` - -## Facts -- **`distill [target]`**: The command `distill [target]` belongs to the Refine category and strips designs to their essence, removing complexity. diff --git a/.brv/context-tree/facts/project/docker_self_hosting_implementation_plan.md b/.brv/context-tree/facts/project/docker_self_hosting_implementation_plan.md deleted file mode 100644 index 97a90e41..00000000 --- a/.brv/context-tree/facts/project/docker_self_hosting_implementation_plan.md +++ /dev/null @@ -1,62 +0,0 @@ ---- -title: Docker Self-Hosting Implementation Plan -summary: Implementation plan for docker self-hosting was created, saved, committed, and verified. -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-21T09:43:28.229Z' -updatedAt: '2026-05-21T09:43:28.229Z' ---- - -## Reason - -Capture lasting project outcome from implementation plan creation - -## Raw Concept - -**Task:** -Document the outcome of creating an implementation plan for docker self-hosting - -**Changes:** -- Created a new implementation plan -- Saved it under docs/plans/2026-05-21-docker-self-hosting.md -- Committed the plan as 99e9d7c docs: add docker self-hosting implementation plan -- Verified formatting with pnpm format:check - -**Files:** -- docs/plans/2026-05-21-docker-self-hosting.md - -**Flow:** -spec -> implementation plan -> save -> commit -> format check - -**Timestamp:** 2026-05-21T09:43:14.889Z - -## Narrative - -### Structure - -Captures the completed planning step and its verification outcome for docker self-hosting work. - -### Dependencies - -The plan depends on the docs/plans workspace and formatting validation via pnpm. - -### Highlights - -The plan was successfully saved, committed, and formatting-checked in one pass. - -### Rules - -Verification: pnpm format:check docs/plans/2026-05-21-docker-self-hosting.md passed. - -### Examples - -Execution options mentioned were Subagent-Driven (recommended) and Inline Execution. - -## Facts - -- **implementation_plan**: An implementation plan was created for a spec. [project] -- **plan_file**: The plan was saved to docs/plans/2026-05-21-docker-self-hosting.md. [project] -- **commit_hash**: The changes were committed as 99e9d7c docs: add docker self-hosting implementation plan. [project] -- **verification_command**: Verification passed with pnpm format:check docs/plans/2026-05-21-docker-self-hosting.md. [project] -- **execution_options**: The assistant presented execution options including subagent-driven and inline execution. [project] diff --git a/.brv/context-tree/facts/project/docs_inspection.md b/.brv/context-tree/facts/project/docs_inspection.md deleted file mode 100644 index 355481db..00000000 --- a/.brv/context-tree/facts/project/docs_inspection.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -title: Docs inspection -summary: Facts about docs inspection -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T10:40:14.686Z' -updatedAt: '2026-05-28T10:40:14.686Z' ---- -## Reason -Curated extracted factual statements - -## Raw Concept -**Task:** -Document facts for docs inspection - -**Timestamp:** 2026-05-28T10:40:14.681Z - -## Facts -- **docs inspection**: Docs inspection command run: find docs apps/landing/src -maxdepth 3 -type f | sort | grep -E '(README|docs|config|usage|getting|index)' diff --git a/.brv/context-tree/facts/project/docs_lookup.md b/.brv/context-tree/facts/project/docs_lookup.md deleted file mode 100644 index b9f32aec..00000000 --- a/.brv/context-tree/facts/project/docs_lookup.md +++ /dev/null @@ -1,18 +0,0 @@ ---- -title: Docs lookup -summary: Facts about docs lookup -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T09:52:02.639Z' -updatedAt: '2026-05-28T09:52:02.639Z' ---- -## Reason -Curated factual statements extracted from context - -## Raw Concept -**Task:** -Document facts about docs lookup - -## Facts -- **docs lookup**: A docs lookup feels safe by default, so redaction, schemas, auth, source metadata, and safe failure states matter less. diff --git a/.brv/context-tree/facts/project/docs_specs_2026_05_27_agent_card_setup_tabs_design_md.md b/.brv/context-tree/facts/project/docs_specs_2026_05_27_agent_card_setup_tabs_design_md.md deleted file mode 100644 index ef7e72c0..00000000 --- a/.brv/context-tree/facts/project/docs_specs_2026_05_27_agent_card_setup_tabs_design_md.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -title: docs/specs/2026-05-27-agent-card-setup-tabs-design.md -summary: Facts about docs/specs/2026-05-27-agent-card-setup-tabs-design.md -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T23:20:10.882Z' -updatedAt: '2026-05-27T23:20:10.882Z' ---- -## Reason -Curated facts from extraction - -## Raw Concept -**Task:** -Document facts for docs/specs/2026-05-27-agent-card-setup-tabs-design.md - -**Timestamp:** 2026-05-27T23:20:10.880Z - -## Facts -- **docs/specs/2026-05-27-agent-card-setup-tabs-design.md**: Spec written and committed to `docs/specs/2026-05-27-agent-card-setup-tabs-design.md`. diff --git a/.brv/context-tree/facts/project/document.md b/.brv/context-tree/facts/project/document.md deleted file mode 100644 index 0f55570e..00000000 --- a/.brv/context-tree/facts/project/document.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Document -summary: Facts about document -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T18:47:50.662Z' -updatedAt: '2026-05-27T18:47:50.662Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-27T18:47:50.505Z - -## Narrative -### Highlights -Extracted 1 facts for document - -## Facts -- **document**: `document` is a Build command that generates DESIGN.md from existing project code. diff --git a/.brv/context-tree/facts/project/document_facts.md b/.brv/context-tree/facts/project/document_facts.md deleted file mode 100644 index 86eafce0..00000000 --- a/.brv/context-tree/facts/project/document_facts.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: '`document` facts' -summary: Facts about `document` -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T19:11:54.715Z' -updatedAt: '2026-05-27T19:11:54.715Z' ---- -## Reason -Curated facts from RLM extraction - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-27T19:11:54.664Z - -## Narrative -### Structure -Collected 1 facts for `document` - -## Facts -- **`document`**: The command `document` belongs to the Build category and generates DESIGN.md from existing project code. diff --git a/.brv/context-tree/facts/project/easing.md b/.brv/context-tree/facts/project/easing.md deleted file mode 100644 index ae960cac..00000000 --- a/.brv/context-tree/facts/project/easing.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Easing -summary: Facts about easing -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-29T09:19:16.193Z' -updatedAt: '2026-05-29T09:19:16.193Z' ---- -## Reason -Curated factual statements from context extraction - -## Raw Concept -**Task:** -Document factual statements - -**Timestamp:** 2026-05-29T09:19:16.120Z - -## Narrative -### Highlights -Extracted 1 facts for easing - -## Facts -- **easing**: Use ease-out exponential curves (ease-out-quart, quint, expo) for motion; no bounce or elastic. diff --git a/.brv/context-tree/facts/project/easing_facts.md b/.brv/context-tree/facts/project/easing_facts.md deleted file mode 100644 index cdc62d8c..00000000 --- a/.brv/context-tree/facts/project/easing_facts.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Easing facts -summary: Facts about easing -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T19:11:54.707Z' -updatedAt: '2026-05-27T19:11:54.707Z' ---- -## Reason -Curated facts from RLM extraction - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-27T19:11:54.664Z - -## Narrative -### Structure -Collected 1 facts for easing - -## Facts -- **easing**: Use ease-out exponential curves (ease-out-quart, quint, expo) for motion; no bounce or elastic. diff --git a/.brv/context-tree/facts/project/extract.md b/.brv/context-tree/facts/project/extract.md deleted file mode 100644 index d1dcae76..00000000 --- a/.brv/context-tree/facts/project/extract.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Extract -summary: Facts about extract -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T18:47:50.664Z' -updatedAt: '2026-05-27T18:47:50.664Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-27T18:47:50.505Z - -## Narrative -### Highlights -Extracted 1 facts for extract - -## Facts -- **extract**: `extract [target]` is a Build command that pulls reusable tokens and components into the design system. diff --git a/.brv/context-tree/facts/project/extract_target_facts.md b/.brv/context-tree/facts/project/extract_target_facts.md deleted file mode 100644 index f945916f..00000000 --- a/.brv/context-tree/facts/project/extract_target_facts.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: '`extract [target]` facts' -summary: Facts about `extract [target]` -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T19:11:54.718Z' -updatedAt: '2026-05-27T19:11:54.718Z' ---- -## Reason -Curated facts from RLM extraction - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-27T19:11:54.664Z - -## Narrative -### Structure -Collected 1 facts for `extract [target]` - -## Facts -- **`extract [target]`**: The command `extract [target]` belongs to the Build category and pulls reusable tokens and components into the design system. diff --git a/.brv/context-tree/facts/project/extracted_facts.md b/.brv/context-tree/facts/project/extracted_facts.md deleted file mode 100644 index 86187c08..00000000 --- a/.brv/context-tree/facts/project/extracted_facts.md +++ /dev/null @@ -1,26 +0,0 @@ ---- -title: Extracted Facts -summary: Curated factual statements extracted from source context -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T16:47:59.662Z' -updatedAt: '2026-05-28T16:47:59.662Z' ---- -## Reason -Store extracted factual statements from context - -## Raw Concept -**Task:** -Curate extracted factual statements from provided context - -**Timestamp:** 2026-05-28T16:47:59.660Z - -## Facts -- **changesets**: Changesets supports excluding packages via `.changeset/config.json`. -- **config**: Added "ignore": ["@caplets/landing"] to the config. -- **changeset status**: `pnpm changeset status --since=origin/main` now passes with no changeset needed for landing-only changes. -- **PR**: Pushed to PR #93 in commit `3f21287 chore: ignore landing app in changesets`. -- **changeset status**: `pnpm changeset status --since=origin/main` passed. -- **format check**: `pnpm format:check -- .changeset/config.json` passed. -- **verification**: `pnpm verify` passed. diff --git a/.brv/context-tree/facts/project/extracted_project_facts.md b/.brv/context-tree/facts/project/extracted_project_facts.md deleted file mode 100644 index d4aac109..00000000 --- a/.brv/context-tree/facts/project/extracted_project_facts.md +++ /dev/null @@ -1,37 +0,0 @@ ---- -title: Extracted Project Facts -summary: Compiled project-related factual statements extracted via RLM extraction -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T18:02:18.225Z' -updatedAt: '2026-05-27T18:02:18.225Z' ---- -## Reason -Curate extracted factual statements from RLM context - -## Raw Concept -**Task:** -Document extracted project facts - -**Timestamp:** 2026-05-27T18:02:18.221Z - -## Narrative -### Structure -Aggregated factual statements - -### Highlights -pnpm alchemy:dev failure, package.json script, alchemy.run.ts content, Astro helper defaults, command execution - -## Facts -- **pnpm alchemy:dev failure**: `pnpm alchemy:dev` fails because Alchemy runs Astro from the repository root, but Astro is only installed in the landing workspace. -- **package.json script**: The root script in `package.json` defines "alchemy:dev": "alchemy dev". -- **alchemy.run.ts content**: `alchemy.run.ts` currently contains `export const landing = await Astro("landing", { assets: "apps/landing/dist" });`. -- **Astro helper defaults**: Alchemy’s `Astro()` helper defaults `cwd` to `props.cwd ?? process.cwd()` and `dev` to "pnpm exec astro dev". -- **command execution**: From the repository root, Alchemy effectively runs `pnpm exec astro dev`. -- **error message**: Running `pnpm exec astro dev` from the root fails with error "ERR_PNPM_RECURSIVE_EXEC_FIRST_FAIL Command \"astro\" not found". -- **astro dependency location**: `astro` is not a root dependency; it is defined in `apps/landing/package.json`. -- **workspace name**: The workspace package name is "@caplets/landing", not "@landing". -- **workspace command**: The direct workspace command `pnpm --filter @caplets/landing dev` works. -- **root cause**: Root cause: `alchemy.run.ts` does not set `cwd: "apps/landing"` for the Astro resource, causing Alchemy to run Astro in the wrong package context. -- **proposed fix**: A likely fix is to modify `alchemy.run.ts` to include `cwd: "apps/landing"` and adjust assets path, e.g., `export const landing = await Astro("landing", { cwd: "apps/landing", assets: "dist" });`. diff --git a/.brv/context-tree/facts/project/files.md b/.brv/context-tree/facts/project/files.md deleted file mode 100644 index 87cdc5ca..00000000 --- a/.brv/context-tree/facts/project/files.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Files -summary: Facts about files -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T13:55:17.286Z' -updatedAt: '2026-05-28T13:55:17.286Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Document extracted factual statements - -**Timestamp:** 2026-05-28T13:55:17.267Z - -## Narrative -### Highlights -Extracted 1 facts for files - -## Facts -- **files**: Changed files are apps/landing/src/pages/index.astro and apps/landing/src/styles/global.css. diff --git a/.brv/context-tree/facts/project/files_facts.md b/.brv/context-tree/facts/project/files_facts.md deleted file mode 100644 index 0bfe6337..00000000 --- a/.brv/context-tree/facts/project/files_facts.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Files facts -summary: Facts about files -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T10:14:23.981Z' -updatedAt: '2026-05-28T10:14:23.981Z' ---- -## Reason -Curated factual statements from source - -## Raw Concept -**Task:** -Curate factual statements - -**Timestamp:** 2026-05-28T10:14:23.972Z - -## Narrative -### Highlights -Collected 1 facts for files - -## Facts -- **files**: Changed files: `apps/landing/src/pages/index.astro`. diff --git a/.brv/context-tree/facts/project/fix_comparison.md b/.brv/context-tree/facts/project/fix_comparison.md deleted file mode 100644 index 8c48824d..00000000 --- a/.brv/context-tree/facts/project/fix_comparison.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -title: Fix comparison -summary: Facts about fix comparison -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T11:56:15.190Z' -updatedAt: '2026-05-28T11:56:15.190Z' ---- -## Reason -Curate extracted facts from source context - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-28T11:56:15.127Z - -## Facts -- **fix comparison**: The key difference from the previous fix is that `NODE_OPTIONS` follows Alchemy into its spawned evaluator. diff --git a/.brv/context-tree/facts/project/footer.md b/.brv/context-tree/facts/project/footer.md deleted file mode 100644 index 7a45036d..00000000 --- a/.brv/context-tree/facts/project/footer.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Footer -summary: Facts about footer -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-29T09:19:16.217Z' -updatedAt: '2026-05-29T09:19:16.217Z' ---- -## Reason -Curated factual statements from context extraction - -## Raw Concept -**Task:** -Document factual statements - -**Timestamp:** 2026-05-29T09:19:16.120Z - -## Narrative -### Highlights -Extracted 1 facts for footer - -## Facts -- **footer**: Updated footer line in apps/landing/src/pages/index.astro. diff --git a/.brv/context-tree/facts/project/footer_link.md b/.brv/context-tree/facts/project/footer_link.md deleted file mode 100644 index 27ebf46c..00000000 --- a/.brv/context-tree/facts/project/footer_link.md +++ /dev/null @@ -1,21 +0,0 @@ ---- -title: Footer link -summary: Facts about footer link -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T10:40:14.683Z' -updatedAt: '2026-05-28T10:40:14.683Z' ---- -## Reason -Curated extracted factual statements - -## Raw Concept -**Task:** -Document facts for footer link - -**Timestamp:** 2026-05-28T10:40:14.681Z - -## Facts -- **footer link**: Added footer link: Config docs → https://github.com/spiritledsoftware/caplets#configure -- **footer link**: Link was added because README.md contains a stable ## Configure anchor diff --git a/.brv/context-tree/facts/project/footer_mobile_links.md b/.brv/context-tree/facts/project/footer_mobile_links.md deleted file mode 100644 index 6653ad33..00000000 --- a/.brv/context-tree/facts/project/footer_mobile_links.md +++ /dev/null @@ -1,27 +0,0 @@ ---- -title: footer mobile links -summary: Facts about footer mobile links -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T13:40:49.017Z' -updatedAt: '2026-05-28T13:40:49.017Z' ---- -## Reason -Curate extracted facts from context - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-28T13:40:49.005Z - -## Narrative -### Structure -List of factual statements extracted - -### Highlights -Contains 1 facts - -## Facts -- **footer mobile links**: Footer mobile links get 44px minimum height with flex alignment and modest padding, improving touch accessibility without excessive spacing. diff --git a/.brv/context-tree/facts/project/forked_pr_secrets.md b/.brv/context-tree/facts/project/forked_pr_secrets.md deleted file mode 100644 index a7de65e9..00000000 --- a/.brv/context-tree/facts/project/forked_pr_secrets.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Forked PR secrets -summary: Facts about forked PR secrets -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T10:41:19.181Z' -updatedAt: '2026-05-28T10:41:19.181Z' ---- -## Reason -Curated factual statements extracted from context - -## Raw Concept -**Task:** -Curate facts - -**Timestamp:** 2026-05-28T10:41:19.156Z - -## Narrative -### Highlights -Extracted 1 facts for forked PR secrets - -## Facts -- **forked PR secrets**: For PRs from forks, GitHub will not expose repository secrets to `pull_request` workflows, so this step will likely fail. diff --git a/.brv/context-tree/facts/project/github.md b/.brv/context-tree/facts/project/github.md deleted file mode 100644 index ab70a181..00000000 --- a/.brv/context-tree/facts/project/github.md +++ /dev/null @@ -1,19 +0,0 @@ ---- -title: GitHub -summary: Facts about GitHub -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T09:52:02.637Z' -updatedAt: '2026-05-28T09:52:02.637Z' ---- -## Reason -Curated factual statements extracted from context - -## Raw Concept -**Task:** -Document facts about GitHub - -## Facts -- **GitHub**: GitHub has higher trust stakes. -- **GitHub**: GitHub lets the page demonstrate why Caplets is valuable before an agent touches workflows like issues, PRs, branches, or review operations. diff --git a/.brv/context-tree/facts/project/github_trace.md b/.brv/context-tree/facts/project/github_trace.md deleted file mode 100644 index 8d935b61..00000000 --- a/.brv/context-tree/facts/project/github_trace.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -title: GitHub trace -summary: Facts about GitHub trace -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T10:20:18.235Z' -updatedAt: '2026-05-28T10:20:18.235Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Document facts about GitHub trace - -**Timestamp:** 2026-05-28T10:20:18.222Z - -## Facts -- **GitHub trace**: GitHub trace includes source, auth redaction, status, progressive steps, schema inspection, and preserved result. diff --git a/.brv/context-tree/facts/project/github_workflows_deploy_yml.md b/.brv/context-tree/facts/project/github_workflows_deploy_yml.md deleted file mode 100644 index 5ceb69f7..00000000 --- a/.brv/context-tree/facts/project/github_workflows_deploy_yml.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: .github/workflows/deploy.yml -summary: Facts about .github/workflows/deploy.yml -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T09:30:56.817Z' -updatedAt: '2026-05-28T09:30:56.817Z' ---- -## Reason -Curated factual statements from source context - -## Raw Concept -**Task:** -Document facts about .github/workflows/deploy.yml - -**Timestamp:** 2026-05-28T09:30:56.811Z - -## Narrative -### Highlights -.github/workflows/deploy.yml: added an apps/ change check so deploy steps only run when the release or manual dispatch commit range includes changes under apps/. - -## Facts -- **.github/workflows/deploy.yml**: .github/workflows/deploy.yml: added an apps/ change check so deploy steps only run when the release or manual dispatch commit range includes changes under apps/. diff --git a/.brv/context-tree/facts/project/github_workflows_pr_preview_yml.md b/.brv/context-tree/facts/project/github_workflows_pr_preview_yml.md deleted file mode 100644 index 1a37c67d..00000000 --- a/.brv/context-tree/facts/project/github_workflows_pr_preview_yml.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: .github/workflows/pr-preview.yml -summary: Facts about .github/workflows/pr-preview.yml -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T09:30:56.815Z' -updatedAt: '2026-05-28T09:30:56.815Z' ---- -## Reason -Curated factual statements from source context - -## Raw Concept -**Task:** -Document facts about .github/workflows/pr-preview.yml - -**Timestamp:** 2026-05-28T09:30:56.811Z - -## Narrative -### Highlights -.github/workflows/pr-preview.yml: added paths: ['apps/**'] to the pull_request trigger. - -## Facts -- **.github/workflows/pr-preview.yml**: .github/workflows/pr-preview.yml: added paths: ['apps/**'] to the pull_request trigger. diff --git a/.brv/context-tree/facts/project/glassmorphism.md b/.brv/context-tree/facts/project/glassmorphism.md deleted file mode 100644 index 16ca8712..00000000 --- a/.brv/context-tree/facts/project/glassmorphism.md +++ /dev/null @@ -1,27 +0,0 @@ ---- -title: Glassmorphism -summary: Facts about glassmorphism -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T18:47:50.633Z' -updatedAt: '2026-05-29T09:19:16.198Z' ---- -## Reason -Curated factual statements from context extraction - -## Raw Concept -**Task:** -Document factual statements - -**Timestamp:** 2026-05-29T09:19:16.120Z - -## Narrative -### Structure -Collected factual statements - -### Highlights -Extracted 1 facts for glassmorphism - -## Facts -- **glassmorphism**: Glassmorphism as default is prohibited. diff --git a/.brv/context-tree/facts/project/global_css_facts.md b/.brv/context-tree/facts/project/global_css_facts.md deleted file mode 100644 index a86a56f2..00000000 --- a/.brv/context-tree/facts/project/global_css_facts.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Global.css facts -summary: Facts about global.css -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T10:14:23.983Z' -updatedAt: '2026-05-28T10:14:23.983Z' ---- -## Reason -Curated factual statements from source - -## Raw Concept -**Task:** -Curate factual statements - -**Timestamp:** 2026-05-28T10:14:23.972Z - -## Narrative -### Highlights -Collected 1 facts for global.css - -## Facts -- **global.css**: `apps/landing/src/styles/global.css` was not changed for Task 3. diff --git a/.brv/context-tree/facts/project/gradient_text.md b/.brv/context-tree/facts/project/gradient_text.md deleted file mode 100644 index bc2187b9..00000000 --- a/.brv/context-tree/facts/project/gradient_text.md +++ /dev/null @@ -1,27 +0,0 @@ ---- -title: Gradient text -summary: Facts about gradient text -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T18:47:50.631Z' -updatedAt: '2026-05-29T09:19:16.196Z' ---- -## Reason -Curated factual statements from context extraction - -## Raw Concept -**Task:** -Document factual statements - -**Timestamp:** 2026-05-29T09:19:16.120Z - -## Narrative -### Structure -Collected factual statements - -### Highlights -Extracted 1 facts for gradient text - -## Facts -- **gradient text**: Gradient text using background-clip: text with a gradient background is prohibited. diff --git a/.brv/context-tree/facts/project/graphql_facts.md b/.brv/context-tree/facts/project/graphql_facts.md deleted file mode 100644 index 6ae32767..00000000 --- a/.brv/context-tree/facts/project/graphql_facts.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: GraphQL Facts -summary: Facts about GraphQL -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T12:22:30.053Z' -updatedAt: '2026-05-27T12:22:30.053Z' ---- -## Reason -Curated factual statements from RLM extraction - -## Raw Concept -**Task:** -Document facts for GraphQL - -**Timestamp:** 2026-05-27T12:22:30.013Z - -## Narrative -### Highlights -GraphQL manager returns jsonResult for schema retrieval and query execution, with endpoint and query metadata; errors via errorResult. - -## Facts -- **GraphQL**: GraphQL manager returns jsonResult for schema retrieval and query execution, with endpoint and query metadata; errors via errorResult. diff --git a/.brv/context-tree/facts/project/greptile_review_comments.md b/.brv/context-tree/facts/project/greptile_review_comments.md deleted file mode 100644 index d4c48e72..00000000 --- a/.brv/context-tree/facts/project/greptile_review_comments.md +++ /dev/null @@ -1,45 +0,0 @@ ---- -title: Greptile Review Comments -summary: Two remaining Greptile review comments were noted, along with passing PR checks. -tags: [] -related: [facts/project/pr_65_fixes_and_verification.md, facts/project/pr_71_completion_fix_outcome.md, facts/project/remote_control_review_outcome.md, facts/project/task_6_spec_review_after_envelope_fix.md] -keywords: [] -createdAt: '2026-05-20T19:26:36.972Z' -updatedAt: '2026-05-20T19:26:36.972Z' ---- -## Reason -Capture remaining review comments and PR status from the conversation - -## Raw Concept -**Task:** -Document the remaining review comments and the current PR status - -**Changes:** -- Captured two unresolved Greptile review comments -- Recorded that verify, changeset, and Greptile review checks are passing - -**Files:** -- packages/core/src/serve/http.ts -- packages/core/src/remote-control/types.ts - -**Flow:** -review comment surfaced -> remediation suggested -> PR checks remain passing - -**Timestamp:** 2026-05-20T19:26:25.037Z - -**Author:** assistant summary - -## Narrative -### Structure -The comments target HTTP forwarding trust boundaries and an unused protocol warnings field. - -### Dependencies -The HTTP issue depends on proxy trust configuration; the types issue depends on whether warnings are intended to be part of the protocol. - -### Highlights -Both comments remain open in the latest Greptile top-level review even though the automated checks are passing. - -## Facts -- **greptile_http_review_comment**: There are remaining Greptile review comments in packages/core/src/serve/http.ts about trusting X-Forwarded-* headers without an explicit proxy opt-in. [project] -- **greptile_types_review_comment**: There are remaining Greptile review comments in packages/core/src/remote-control/types.ts about warnings?: string[] being defined in protocol types but never produced or consumed. [project] -- **pr_checks_status**: Current PR checks are passing: Verify success, Changeset success, and Greptile Review success. [project] diff --git a/.brv/context-tree/facts/project/harden.md b/.brv/context-tree/facts/project/harden.md deleted file mode 100644 index ceca835e..00000000 --- a/.brv/context-tree/facts/project/harden.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Harden -summary: Facts about harden -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T18:47:50.700Z' -updatedAt: '2026-05-27T18:47:50.700Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-27T18:47:50.505Z - -## Narrative -### Highlights -Extracted 1 facts for harden - -## Facts -- **harden**: `harden [target]` is a Refine command that makes designs production‑ready, handling errors, i18n, and edge cases. diff --git a/.brv/context-tree/facts/project/harden_target_facts.md b/.brv/context-tree/facts/project/harden_target_facts.md deleted file mode 100644 index af6a80ed..00000000 --- a/.brv/context-tree/facts/project/harden_target_facts.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: '`harden [target]` facts' -summary: Facts about `harden [target]` -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T19:11:54.733Z' -updatedAt: '2026-05-27T19:11:54.733Z' ---- -## Reason -Curated facts from RLM extraction - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-27T19:11:54.664Z - -## Narrative -### Structure -Collected 1 facts for `harden [target]` - -## Facts -- **`harden [target]`**: The command `harden [target]` belongs to the Refine category and makes designs production‑ready, handling errors, i18n, and edge cases. diff --git a/.brv/context-tree/facts/project/header_shadow_token.md b/.brv/context-tree/facts/project/header_shadow_token.md deleted file mode 100644 index f6310c42..00000000 --- a/.brv/context-tree/facts/project/header_shadow_token.md +++ /dev/null @@ -1,25 +0,0 @@ ---- -title: Header shadow token -summary: Facts about header shadow token -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T13:55:17.275Z' -updatedAt: '2026-05-28T13:55:17.275Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Document extracted factual statements - -**Timestamp:** 2026-05-28T13:55:17.267Z - -## Narrative -### Highlights -Extracted 2 facts for header shadow token - -## Facts -- **header shadow token**: Audit finding 3: The --header-shadow token is stale and should be removed if unused. -- **header shadow token**: The unused --header-shadow token was removed. diff --git a/.brv/context-tree/facts/project/header_styling_facts.md b/.brv/context-tree/facts/project/header_styling_facts.md deleted file mode 100644 index 2fa718c0..00000000 --- a/.brv/context-tree/facts/project/header_styling_facts.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Header styling facts -summary: Facts about Header styling -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T19:11:54.763Z' -updatedAt: '2026-05-27T19:11:54.763Z' ---- -## Reason -Curated facts from RLM extraction - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-27T19:11:54.664Z - -## Narrative -### Structure -Collected 1 facts for Header styling - -## Facts -- **Header styling**: Header spacing, sticky behavior, hover states, and visual weight were improved. diff --git a/.brv/context-tree/facts/project/hero_copy.md b/.brv/context-tree/facts/project/hero_copy.md deleted file mode 100644 index 4eafea68..00000000 --- a/.brv/context-tree/facts/project/hero_copy.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -title: Hero copy -summary: Facts about hero copy -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T10:20:18.233Z' -updatedAt: '2026-05-28T10:20:18.233Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Document facts about hero copy - -**Timestamp:** 2026-05-28T10:20:18.222Z - -## Facts -- **hero copy**: Hero copy leads with `Skillify your backends.` and defines “skillify” clearly. diff --git a/.brv/context-tree/facts/project/hero_design.md b/.brv/context-tree/facts/project/hero_design.md deleted file mode 100644 index ea2a988b..00000000 --- a/.brv/context-tree/facts/project/hero_design.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Hero design -summary: Facts about hero design -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T09:50:25.675Z' -updatedAt: '2026-05-28T09:50:25.675Z' ---- -## Reason -Curated facts extracted from source context - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-28T09:50:25.672Z - -## Narrative -### Highlights -Extracted 1 facts for hero design - -## Facts -- **hero design**: The hero’s center of gravity should be replaced with a proof‑first product diagram. diff --git a/.brv/context-tree/facts/project/hero_metric_template.md b/.brv/context-tree/facts/project/hero_metric_template.md deleted file mode 100644 index 73805276..00000000 --- a/.brv/context-tree/facts/project/hero_metric_template.md +++ /dev/null @@ -1,27 +0,0 @@ ---- -title: Hero-metric template -summary: Facts about hero-metric template -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T19:18:57.165Z' -updatedAt: '2026-05-29T09:19:16.201Z' ---- -## Reason -Curated factual statements from context extraction - -## Raw Concept -**Task:** -Document factual statements - -**Timestamp:** 2026-05-29T09:19:16.120Z - -## Narrative -### Structure -Collected factual statements - -### Highlights -Extracted 1 facts for hero-metric template - -## Facts -- **hero-metric template**: The hero-metric template (big number, small label, supporting stats, gradient accent) is prohibited. diff --git a/.brv/context-tree/facts/project/hero_motion_script.md b/.brv/context-tree/facts/project/hero_motion_script.md deleted file mode 100644 index 6bf27a17..00000000 --- a/.brv/context-tree/facts/project/hero_motion_script.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -title: Hero-motion script -summary: Facts about hero-motion script -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T10:20:18.282Z' -updatedAt: '2026-05-28T10:20:18.282Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Document facts about hero-motion script - -**Timestamp:** 2026-05-28T10:20:18.222Z - -## Facts -- **hero-motion script**: Dead old hero-motion script remains in `apps/landing/src/pages/index.astro:379-505`, including `.map-stage`, pointer tracking, route progress, and `is-route-active` logic; it is mostly inert but should be removed. diff --git a/.brv/context-tree/facts/project/hero_section.md b/.brv/context-tree/facts/project/hero_section.md deleted file mode 100644 index 94e8a1a4..00000000 --- a/.brv/context-tree/facts/project/hero_section.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Hero section -summary: Facts about hero section -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-29T09:19:16.214Z' -updatedAt: '2026-05-29T09:19:16.214Z' ---- -## Reason -Curated factual statements from context extraction - -## Raw Concept -**Task:** -Document factual statements - -**Timestamp:** 2026-05-29T09:19:16.120Z - -## Narrative -### Highlights -Extracted 1 facts for hero section - -## Facts -- **hero section**: Updated hero kicker, H1, hero lede, and definition copy in apps/landing/src/pages/index.astro. diff --git a/.brv/context-tree/facts/project/hero_workflow.md b/.brv/context-tree/facts/project/hero_workflow.md deleted file mode 100644 index 76aa93a8..00000000 --- a/.brv/context-tree/facts/project/hero_workflow.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Hero workflow -summary: Facts about hero workflow -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T09:50:25.724Z' -updatedAt: '2026-05-28T09:50:25.724Z' ---- -## Reason -Curated facts extracted from source context - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-28T09:50:25.672Z - -## Narrative -### Highlights -Extracted 1 facts for hero workflow - -## Facts -- **hero workflow**: The hero should show a progressive‑disclosure path consisting of: Capability: github; Discover: search_tools; Inspect: get_tool; Call: call_tool; Result: structured output preserved, errors redacted. diff --git a/.brv/context-tree/facts/project/heuristic_scoring.md b/.brv/context-tree/facts/project/heuristic_scoring.md deleted file mode 100644 index b87b9f2b..00000000 --- a/.brv/context-tree/facts/project/heuristic_scoring.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Heuristic scoring -summary: Facts about heuristic scoring -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T09:45:49.161Z' -updatedAt: '2026-05-28T09:45:49.161Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-28T09:45:49.131Z - -## Narrative -### Highlights -Extracted 1 facts for heuristic scoring - -## Facts -- **heuristic scoring**: Nielsen's 10 heuristics are scored 0‑4, with total possible score 40; most real interfaces score 20‑32. diff --git a/.brv/context-tree/facts/project/hierarchy.md b/.brv/context-tree/facts/project/hierarchy.md deleted file mode 100644 index 1f76670e..00000000 --- a/.brv/context-tree/facts/project/hierarchy.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Hierarchy -summary: Facts about hierarchy -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-29T09:19:16.191Z' -updatedAt: '2026-05-29T09:19:16.191Z' ---- -## Reason -Curated factual statements from context extraction - -## Raw Concept -**Task:** -Document factual statements - -**Timestamp:** 2026-05-29T09:19:16.120Z - -## Narrative -### Highlights -Extracted 1 facts for hierarchy - -## Facts -- **hierarchy**: Maintain a hierarchy through scale and weight contrast with at least a 1.25 ratio between steps. diff --git a/.brv/context-tree/facts/project/hierarchy_facts.md b/.brv/context-tree/facts/project/hierarchy_facts.md deleted file mode 100644 index 86758454..00000000 --- a/.brv/context-tree/facts/project/hierarchy_facts.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Hierarchy facts -summary: Facts about hierarchy -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T19:11:54.700Z' -updatedAt: '2026-05-27T19:11:54.700Z' ---- -## Reason -Curated facts from RLM extraction - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-27T19:11:54.663Z - -## Narrative -### Structure -Collected 1 facts for hierarchy - -## Facts -- **hierarchy**: Hierarchy should have a scale and weight contrast ratio of at least 1.25 between steps. diff --git a/.brv/context-tree/facts/project/home_ianpascoe_src_caplets.md b/.brv/context-tree/facts/project/home_ianpascoe_src_caplets.md deleted file mode 100644 index 1d9479df..00000000 --- a/.brv/context-tree/facts/project/home_ianpascoe_src_caplets.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: /home/ianpascoe/src/caplets -summary: Facts about /home/ianpascoe/src/caplets -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T10:26:09.783Z' -updatedAt: '2026-05-28T10:26:09.783Z' ---- -## Reason -Curated facts extracted from source context - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-28T10:26:09.781Z - -## Narrative -### Highlights -Extracted 1 facts for /home/ianpascoe/src/caplets - -## Facts -- **/home/ianpascoe/src/caplets**: The user is working in the directory /home/ianpascoe/src/caplets. diff --git a/.brv/context-tree/facts/project/hover_motion.md b/.brv/context-tree/facts/project/hover_motion.md deleted file mode 100644 index 0190b9b0..00000000 --- a/.brv/context-tree/facts/project/hover_motion.md +++ /dev/null @@ -1,25 +0,0 @@ ---- -title: Hover motion -summary: Facts about hover motion -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T13:55:17.273Z' -updatedAt: '2026-05-28T13:55:17.273Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Document extracted factual statements - -**Timestamp:** 2026-05-28T13:55:17.267Z - -## Narrative -### Highlights -Extracted 2 facts for hover motion - -## Facts -- **hover motion**: Audit finding 2: Some secondary hover motion is over‑applied and should be reduced on low‑priority nav/icon/copy controls. -- **hover motion**: Hover translate motion was removed from low‑priority top nav, header icons, and copy buttons. diff --git a/.brv/context-tree/facts/project/hover_transform.md b/.brv/context-tree/facts/project/hover_transform.md deleted file mode 100644 index 5e570190..00000000 --- a/.brv/context-tree/facts/project/hover_transform.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Hover transform -summary: Facts about hover transform -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T13:55:17.290Z' -updatedAt: '2026-05-28T13:55:17.290Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Document extracted factual statements - -**Timestamp:** 2026-05-28T13:55:17.267Z - -## Narrative -### Highlights -Extracted 1 facts for hover transform - -## Facts -- **hover transform**: Hover transform was kept for primary/secondary buttons and integration pills. diff --git a/.brv/context-tree/facts/project/hover_transforms.md b/.brv/context-tree/facts/project/hover_transforms.md deleted file mode 100644 index 60bb9e48..00000000 --- a/.brv/context-tree/facts/project/hover_transforms.md +++ /dev/null @@ -1,22 +0,0 @@ ---- -title: Hover transforms -summary: Facts about Hover transforms -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T13:58:26.030Z' -updatedAt: '2026-05-28T13:58:26.030Z' ---- -## Reason -Curated facts extraction - -## Raw Concept -**Task:** -Document facts for Hover transforms - -## Narrative -### Highlights -Extracted 1 facts for Hover transforms - -## Facts -- **Hover transforms**: Low-priority hover transforms were reduced, while primary/integration transforms remain. diff --git a/.brv/context-tree/facts/project/http_actions_facts.md b/.brv/context-tree/facts/project/http_actions_facts.md deleted file mode 100644 index da93be8e..00000000 --- a/.brv/context-tree/facts/project/http_actions_facts.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: HTTP Actions Facts -summary: Facts about HTTP Actions -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T12:22:30.030Z' -updatedAt: '2026-05-27T12:22:30.030Z' ---- -## Reason -Curated factual statements from RLM extraction - -## Raw Concept -**Task:** -Document facts for HTTP Actions - -**Timestamp:** 2026-05-27T12:22:30.013Z - -## Narrative -### Highlights -HTTP Actions manager uses jsonResult for successful responses and errorResult for failures, adding method, URL, status metadata. - -## Facts -- **HTTP Actions**: HTTP Actions manager uses jsonResult for successful responses and errorResult for failures, adding method, URL, status metadata. diff --git a/.brv/context-tree/facts/project/icon_link.md b/.brv/context-tree/facts/project/icon_link.md deleted file mode 100644 index 3def3e3c..00000000 --- a/.brv/context-tree/facts/project/icon_link.md +++ /dev/null @@ -1,27 +0,0 @@ ---- -title: .icon-link -summary: Facts about .icon-link -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T13:40:49.016Z' -updatedAt: '2026-05-28T13:40:49.016Z' ---- -## Reason -Curate extracted facts from context - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-28T13:40:49.005Z - -## Narrative -### Structure -List of factual statements extracted - -### Highlights -Contains 1 facts - -## Facts -- **.icon-link**: `.icon-link` becomes exactly 44px wide and inherits 44px min height, giving square icon targets. diff --git a/.brv/context-tree/facts/project/identified_issues.md b/.brv/context-tree/facts/project/identified_issues.md deleted file mode 100644 index 0a35e75b..00000000 --- a/.brv/context-tree/facts/project/identified_issues.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Identified issues -summary: Facts about identified issues -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T09:45:49.165Z' -updatedAt: '2026-05-28T09:45:49.165Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-28T09:45:49.131Z - -## Narrative -### Highlights -Extracted 1 facts for identified issues - -## Facts -- **identified issues**: The issues live mostly in CSS, product-context mismatch, motion volume, no-JS behavior, install consistency, and trust-copy specificity. diff --git a/.brv/context-tree/facts/project/impeccable_context_dir.md b/.brv/context-tree/facts/project/impeccable_context_dir.md deleted file mode 100644 index 1ff2f6e0..00000000 --- a/.brv/context-tree/facts/project/impeccable_context_dir.md +++ /dev/null @@ -1,27 +0,0 @@ ---- -title: IMPECCABLE CONTEXT DIR -summary: Facts about IMPECCABLE_CONTEXT_DIR -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T19:12:30.267Z' -updatedAt: '2026-05-29T09:19:16.146Z' ---- -## Reason -Curated factual statements from context extraction - -## Raw Concept -**Task:** -Document factual statements - -**Timestamp:** 2026-05-29T09:19:16.120Z - -## Narrative -### Structure -Extracted facts for IMPECCABLE_CONTEXT_DIR - -### Highlights -Extracted 1 facts for IMPECCABLE_CONTEXT_DIR - -## Facts -- **IMPECCABLE_CONTEXT_DIR**: Override the context directory with IMPECCABLE_CONTEXT_DIR=path/to/dir (absolute or relative to cwd). diff --git a/.brv/context-tree/facts/project/inactive_integration_panels.md b/.brv/context-tree/facts/project/inactive_integration_panels.md deleted file mode 100644 index 70e8879f..00000000 --- a/.brv/context-tree/facts/project/inactive_integration_panels.md +++ /dev/null @@ -1,27 +0,0 @@ ---- -title: inactive integration panels -summary: Facts about inactive integration panels -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T13:51:08.351Z' -updatedAt: '2026-05-28T13:51:08.351Z' ---- -## Reason -Curated extracted facts - -## Raw Concept -**Task:** -Document factual statements about inactive integration panels - -**Timestamp:** 2026-05-28T13:51:08.346Z - -## Narrative -### Structure -Collected facts for inactive integration panels - -### Highlights -Removed server-rendered `hidden` from inactive integration panels. - -## Facts -- **inactive integration panels**: Removed server-rendered `hidden` from inactive integration panels. diff --git a/.brv/context-tree/facts/project/integration_panels.md b/.brv/context-tree/facts/project/integration_panels.md deleted file mode 100644 index 7f7a9706..00000000 --- a/.brv/context-tree/facts/project/integration_panels.md +++ /dev/null @@ -1,27 +0,0 @@ ---- -title: integration panels -summary: Facts about integration panels -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T13:51:08.348Z' -updatedAt: '2026-05-28T13:51:08.348Z' ---- -## Reason -Curated extracted facts - -## Raw Concept -**Task:** -Document factual statements about integration panels - -**Timestamp:** 2026-05-28T13:51:08.346Z - -## Narrative -### Structure -Collected facts for integration panels - -### Highlights -Implemented progressive enhancement fix for integration panels. - -## Facts -- **integration panels**: Implemented progressive enhancement fix for integration panels. diff --git a/.brv/context-tree/facts/project/integration_pill.md b/.brv/context-tree/facts/project/integration_pill.md deleted file mode 100644 index 802871ac..00000000 --- a/.brv/context-tree/facts/project/integration_pill.md +++ /dev/null @@ -1,27 +0,0 @@ ---- -title: .integration-pill -summary: Facts about .integration-pill -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T13:40:49.009Z' -updatedAt: '2026-05-28T13:40:49.009Z' ---- -## Reason -Curate extracted facts from context - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-28T13:40:49.005Z - -## Narrative -### Structure -List of factual statements extracted - -### Highlights -Contains 1 facts - -## Facts -- **.integration-pill**: `.integration-pill` now has both `min-width: 44px` and existing `min-height: 44px`, preserving current pill styling while meeting minimum touch target sizing. diff --git a/.brv/context-tree/facts/project/integration_tabs_aria_semantics.md b/.brv/context-tree/facts/project/integration_tabs_aria_semantics.md deleted file mode 100644 index 011d6e65..00000000 --- a/.brv/context-tree/facts/project/integration_tabs_aria_semantics.md +++ /dev/null @@ -1,22 +0,0 @@ ---- -title: Integration tabs ARIA semantics -summary: Facts about Integration tabs ARIA semantics -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T13:58:26.013Z' -updatedAt: '2026-05-28T13:58:26.013Z' ---- -## Reason -Curated facts extraction - -## Raw Concept -**Task:** -Document facts for Integration tabs ARIA semantics - -## Narrative -### Highlights -Extracted 1 facts for Integration tabs ARIA semantics - -## Facts -- **Integration tabs ARIA semantics**: Integration tabs still have unsafe no-JS SSR ARIA semantics in apps/landing/src/pages/index.astro lines 311-333. diff --git a/.brv/context-tree/facts/project/interaction_pattern_facts.md b/.brv/context-tree/facts/project/interaction_pattern_facts.md deleted file mode 100644 index 1e944463..00000000 --- a/.brv/context-tree/facts/project/interaction_pattern_facts.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Interaction pattern facts -summary: Facts about interaction pattern -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T19:11:54.694Z' -updatedAt: '2026-05-27T19:11:54.694Z' ---- -## Reason -Curated facts from RLM extraction - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-27T19:11:54.663Z - -## Narrative -### Structure -Collected 1 facts for interaction pattern - -## Facts -- **interaction pattern**: Modals as the first design thought are prohibited. diff --git a/.brv/context-tree/facts/project/javascript.md b/.brv/context-tree/facts/project/javascript.md deleted file mode 100644 index 652ddaca..00000000 --- a/.brv/context-tree/facts/project/javascript.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: javascript -summary: Facts about javascript -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T13:48:34.345Z' -updatedAt: '2026-05-28T13:48:34.345Z' ---- -## Reason -Curated factual statements from recent context - -## Raw Concept -**Task:** -Document project facts - -**Timestamp:** 2026-05-28T13:48:34.339Z - -## Narrative -### Highlights -Collected 1 facts for javascript - -## Facts -- **javascript**: Existing JS behavior is preserved: click selection, ARIA/tabindex updates, panel hiding, and ArrowLeft/ArrowRight/Home/End keyboard navigation remain at lines 425-476 diff --git a/.brv/context-tree/facts/project/javascript_initialization.md b/.brv/context-tree/facts/project/javascript_initialization.md deleted file mode 100644 index cd851b7b..00000000 --- a/.brv/context-tree/facts/project/javascript_initialization.md +++ /dev/null @@ -1,27 +0,0 @@ ---- -title: JavaScript initialization -summary: Facts about JavaScript initialization -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T13:51:08.354Z' -updatedAt: '2026-05-28T13:51:08.354Z' ---- -## Reason -Curated extracted facts - -## Raw Concept -**Task:** -Document factual statements about JavaScript initialization - -**Timestamp:** 2026-05-28T13:51:08.346Z - -## Narrative -### Structure -Collected facts for JavaScript initialization - -### Highlights -Existing JS now applies the initial hidden state on initialization via `selectAgentTab(...)`, preserving click and keyboard tab behavior. - -## Facts -- **JavaScript initialization**: Existing JS now applies the initial hidden state on initialization via `selectAgentTab(...)`, preserving click and keyboard tab behavior. diff --git a/.brv/context-tree/facts/project/kicker_pattern.md b/.brv/context-tree/facts/project/kicker_pattern.md deleted file mode 100644 index 1568da7f..00000000 --- a/.brv/context-tree/facts/project/kicker_pattern.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Kicker pattern -summary: Facts about kicker pattern -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T13:55:17.272Z' -updatedAt: '2026-05-28T13:55:17.272Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Document extracted factual statements - -**Timestamp:** 2026-05-28T13:55:17.267Z - -## Narrative -### Highlights -Extracted 1 facts for kicker pattern - -## Facts -- **kicker pattern**: Audit finding 1: Repeated uppercase kicker pattern appears in hero, problem, proof, trust, integrations, install sections. diff --git a/.brv/context-tree/facts/project/kicker_scaffolding.md b/.brv/context-tree/facts/project/kicker_scaffolding.md deleted file mode 100644 index b643ad5e..00000000 --- a/.brv/context-tree/facts/project/kicker_scaffolding.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Kicker scaffolding -summary: Facts about kicker scaffolding -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T13:55:17.287Z' -updatedAt: '2026-05-28T13:55:17.287Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Document extracted factual statements - -**Timestamp:** 2026-05-28T13:55:17.267Z - -## Narrative -### Highlights -Extracted 1 facts for kicker scaffolding - -## Facts -- **kicker scaffolding**: Repeated uppercase kicker scaffolding was reduced to two key kickers: hero and install. diff --git a/.brv/context-tree/facts/project/landing_app_audit.md b/.brv/context-tree/facts/project/landing_app_audit.md deleted file mode 100644 index 41ddbe8e..00000000 --- a/.brv/context-tree/facts/project/landing_app_audit.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -title: Landing app audit -summary: Facts about landing app audit -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T11:17:59.532Z' -updatedAt: '2026-05-28T11:17:59.532Z' ---- -## Reason -Curated facts extracted from context - -## Raw Concept -**Task:** -Document facts about landing app audit - -**Timestamp:** 2026-05-28T11:17:59.530Z - -## Facts -- **landing app audit**: APPROVED, no P0/P1/P2 actionable issues remain. diff --git a/.brv/context-tree/facts/project/landing_build_facts.md b/.brv/context-tree/facts/project/landing_build_facts.md deleted file mode 100644 index 540d56bc..00000000 --- a/.brv/context-tree/facts/project/landing_build_facts.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Landing build facts -summary: Facts about Landing build -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T19:11:54.769Z' -updatedAt: '2026-05-27T19:11:54.769Z' ---- -## Reason -Curated facts from RLM extraction - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-27T19:11:54.664Z - -## Narrative -### Structure -Collected 1 facts for Landing build - -## Facts -- **Landing build**: `pnpm --filter @caplets/landing build` passed without errors. diff --git a/.brv/context-tree/facts/project/landing_page.md b/.brv/context-tree/facts/project/landing_page.md deleted file mode 100644 index c2c60885..00000000 --- a/.brv/context-tree/facts/project/landing_page.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -title: Landing page -summary: Facts about landing page -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T11:17:59.544Z' -updatedAt: '2026-05-28T11:17:59.544Z' ---- -## Reason -Curated facts extracted from context - -## Raw Concept -**Task:** -Document facts about landing page - -**Timestamp:** 2026-05-28T11:17:59.530Z - -## Facts -- **landing page**: All anti-patterns are resolved: no target blank issue, no inaccessible decorative SVGs, no JS-only critical content, no obvious layout traps, and no secrets exposed. diff --git a/.brv/context-tree/facts/project/landing_page_animation.md b/.brv/context-tree/facts/project/landing_page_animation.md deleted file mode 100644 index 1f2198c3..00000000 --- a/.brv/context-tree/facts/project/landing_page_animation.md +++ /dev/null @@ -1,27 +0,0 @@ ---- -title: Landing page animation -summary: Facts about landing page animation -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T19:18:57.178Z' -updatedAt: '2026-05-27T19:18:57.178Z' ---- -## Reason -Curated facts extracted from context - -## Raw Concept -**Task:** -Document project facts - -**Timestamp:** 2026-05-27T19:18:57.119Z - -## Narrative -### Structure -Collected factual statements - -### Highlights -Contains 1 facts - -## Facts -- **landing page animation**: The animation of the landing page added hero entrance choreography, scroll‑triggered section reveals, staggered reveal timing, button active feedback, and preserved reduced‑motion behavior. diff --git a/.brv/context-tree/facts/project/landing_typecheck_facts.md b/.brv/context-tree/facts/project/landing_typecheck_facts.md deleted file mode 100644 index 0dc86856..00000000 --- a/.brv/context-tree/facts/project/landing_typecheck_facts.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Landing typecheck facts -summary: Facts about Landing typecheck -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T19:11:54.770Z' -updatedAt: '2026-05-27T19:11:54.770Z' ---- -## Reason -Curated facts from RLM extraction - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-27T19:11:54.664Z - -## Narrative -### Structure -Collected 1 facts for Landing typecheck - -## Facts -- **Landing typecheck**: `pnpm --filter @caplets/landing typecheck` passed with 0 errors, 0 warnings, and 0 hints. diff --git a/.brv/context-tree/facts/project/layout.md b/.brv/context-tree/facts/project/layout.md deleted file mode 100644 index 29537ad9..00000000 --- a/.brv/context-tree/facts/project/layout.md +++ /dev/null @@ -1,27 +0,0 @@ ---- -title: layout -summary: Facts about layout -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T18:47:50.721Z' -updatedAt: '2026-05-28T13:40:49.019Z' ---- -## Reason -Curate extracted facts from context - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-28T13:40:49.005Z - -## Narrative -### Structure -List of factual statements extracted - -### Highlights -Contains 1 facts - -## Facts -- **layout**: Responsiveness appears safe at the 320px minimum viewport; the compact mobile header widths fit the three‑column layout without obvious overflow risk. diff --git a/.brv/context-tree/facts/project/layout_target_facts.md b/.brv/context-tree/facts/project/layout_target_facts.md deleted file mode 100644 index bc1bf944..00000000 --- a/.brv/context-tree/facts/project/layout_target_facts.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: '`layout [target]` facts' -summary: Facts about `layout [target]` -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T19:11:54.743Z' -updatedAt: '2026-05-27T19:11:54.743Z' ---- -## Reason -Curated facts from RLM extraction - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-27T19:11:54.664Z - -## Narrative -### Structure -Collected 1 facts for `layout [target]` - -## Facts -- **`layout [target]`**: The command `layout [target]` belongs to the Enhance category and fixes spacing, rhythm, and visual hierarchy. diff --git a/.brv/context-tree/facts/project/layout_template_facts.md b/.brv/context-tree/facts/project/layout_template_facts.md deleted file mode 100644 index 2c4951c6..00000000 --- a/.brv/context-tree/facts/project/layout_template_facts.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Layout template facts -summary: Facts about layout template -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T19:11:54.690Z' -updatedAt: '2026-05-27T19:11:54.690Z' ---- -## Reason -Curated facts from RLM extraction - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-27T19:11:54.663Z - -## Narrative -### Structure -Collected 1 facts for layout template - -## Facts -- **layout template**: The hero-metric template (big number, small label, supporting stats, gradient accent) is prohibited. diff --git a/.brv/context-tree/facts/project/line_length.md b/.brv/context-tree/facts/project/line_length.md deleted file mode 100644 index bc87137d..00000000 --- a/.brv/context-tree/facts/project/line_length.md +++ /dev/null @@ -1,27 +0,0 @@ ---- -title: Line length -summary: Facts about line length -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T19:12:30.298Z' -updatedAt: '2026-05-29T09:19:16.188Z' ---- -## Reason -Curated factual statements from context extraction - -## Raw Concept -**Task:** -Document factual statements - -**Timestamp:** 2026-05-29T09:19:16.120Z - -## Narrative -### Structure -Extracted facts for line length - -### Highlights -Extracted 1 facts for line length - -## Facts -- **line length**: Cap body line length at 65–75 characters. diff --git a/.brv/context-tree/facts/project/line_length_facts.md b/.brv/context-tree/facts/project/line_length_facts.md deleted file mode 100644 index 6ba8289f..00000000 --- a/.brv/context-tree/facts/project/line_length_facts.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Line length facts -summary: Facts about line length -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T19:11:54.698Z' -updatedAt: '2026-05-27T19:11:54.698Z' ---- -## Reason -Curated facts from RLM extraction - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-27T19:11:54.663Z - -## Narrative -### Structure -Collected 1 facts for line length - -## Facts -- **line length**: Cap body line length at 65–75 characters. diff --git a/.brv/context-tree/facts/project/link_at_line_165.md b/.brv/context-tree/facts/project/link_at_line_165.md deleted file mode 100644 index 8bec4eab..00000000 --- a/.brv/context-tree/facts/project/link_at_line_165.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -title: Link at line 165 -summary: Facts about link at line 165 -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T10:20:18.276Z' -updatedAt: '2026-05-28T10:20:18.276Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Document facts about link at line 165 - -**Timestamp:** 2026-05-28T10:20:18.222Z - -## Facts -- **link at line 165**: `apps/landing/src/pages/index.astro:165` links to `#map`, but the old `id="map"` was removed and replaced by `id="trace"` at line 210, creating a broken primary navigation target. diff --git a/.brv/context-tree/facts/project/live.md b/.brv/context-tree/facts/project/live.md deleted file mode 100644 index f334aa31..00000000 --- a/.brv/context-tree/facts/project/live.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Live -summary: Facts about live -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T18:47:50.756Z' -updatedAt: '2026-05-27T18:47:50.756Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-27T18:47:50.505Z - -## Narrative -### Highlights -Extracted 1 facts for live - -## Facts -- **live**: `live` is an Iterate command that provides a visual variant mode for picking elements in the browser and generating alternatives. diff --git a/.brv/context-tree/facts/project/live_facts.md b/.brv/context-tree/facts/project/live_facts.md deleted file mode 100644 index 419ca5ac..00000000 --- a/.brv/context-tree/facts/project/live_facts.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: '`live` facts' -summary: Facts about `live` -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T19:11:54.758Z' -updatedAt: '2026-05-27T19:11:54.758Z' ---- -## Reason -Curated facts from RLM extraction - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-27T19:11:54.664Z - -## Narrative -### Structure -Collected 1 facts for `live` - -## Facts -- **`live`**: The command `live` belongs to the Iterate category and provides a visual variant mode to pick elements in the browser and generate alternatives. diff --git a/.brv/context-tree/facts/project/load_context_mjs.md b/.brv/context-tree/facts/project/load_context_mjs.md deleted file mode 100644 index ee200a2c..00000000 --- a/.brv/context-tree/facts/project/load_context_mjs.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Load-context.mjs -summary: Facts about load-context.mjs -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T19:12:30.270Z' -updatedAt: '2026-05-27T19:12:30.270Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-27T19:12:30.245Z - -## Narrative -### Structure -Extracted facts for load-context.mjs - -## Facts -- **load-context.mjs**: Both PRODUCT.md and DESIGN.md can be loaded in one call using node {{scripts_path}}/load-context.mjs. diff --git a/.brv/context-tree/facts/project/loader_output.md b/.brv/context-tree/facts/project/loader_output.md deleted file mode 100644 index 8f3849a5..00000000 --- a/.brv/context-tree/facts/project/loader_output.md +++ /dev/null @@ -1,27 +0,0 @@ ---- -title: Loader output -summary: Facts about loader output -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T19:12:30.272Z' -updatedAt: '2026-05-29T09:19:16.149Z' ---- -## Reason -Curated factual statements from context extraction - -## Raw Concept -**Task:** -Document factual statements - -**Timestamp:** 2026-05-29T09:19:16.120Z - -## Narrative -### Structure -Extracted facts for loader output - -### Highlights -Extracted 1 facts for loader output - -## Facts -- **loader output**: Never pipe the loader output through head, tail, grep, or jq. diff --git a/.brv/context-tree/facts/project/loader_output_handling.md b/.brv/context-tree/facts/project/loader_output_handling.md deleted file mode 100644 index b40c7f0b..00000000 --- a/.brv/context-tree/facts/project/loader_output_handling.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Loader output handling -summary: Facts about loader output handling -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T18:47:50.559Z' -updatedAt: '2026-05-27T18:47:50.559Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-27T18:47:50.505Z - -## Narrative -### Highlights -Extracted 1 facts for loader output handling - -## Facts -- **loader output handling**: Never pipe the loader output through `head`, `tail`, `grep`, or `jq`. diff --git a/.brv/context-tree/facts/project/log.md b/.brv/context-tree/facts/project/log.md deleted file mode 100644 index 4c94e1ff..00000000 --- a/.brv/context-tree/facts/project/log.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Log -summary: Facts about log -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-29T09:19:16.226Z' -updatedAt: '2026-05-29T09:19:16.226Z' ---- -## Reason -Curated factual statements from context extraction - -## Raw Concept -**Task:** -Document factual statements - -**Timestamp:** 2026-05-29T09:19:16.120Z - -## Narrative -### Highlights -Extracted 1 facts for log - -## Facts -- **log**: There were 0 errors, 0 warnings, and 0 hints. diff --git a/.brv/context-tree/facts/project/map_labeling.md b/.brv/context-tree/facts/project/map_labeling.md deleted file mode 100644 index a51c7e9c..00000000 --- a/.brv/context-tree/facts/project/map_labeling.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -title: Map labeling -summary: Facts about map labeling -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T10:20:18.278Z' -updatedAt: '2026-05-28T10:20:18.278Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Document facts about map labeling - -**Timestamp:** 2026-05-28T10:20:18.222Z - -## Facts -- **map labeling**: `apps/landing/src/pages/index.astro:165` still labels the nav item `Map`, and `apps/landing/src/pages/index.astro:355` still says `serve the map.` This undercuts the plan’s brand direction. diff --git a/.brv/context-tree/facts/project/map_stage_pointer_route_script.md b/.brv/context-tree/facts/project/map_stage_pointer_route_script.md deleted file mode 100644 index e80b6712..00000000 --- a/.brv/context-tree/facts/project/map_stage_pointer_route_script.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Map-stage/pointer/route script -summary: Facts about map-stage/pointer/route script -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T10:26:09.791Z' -updatedAt: '2026-05-28T10:26:09.791Z' ---- -## Reason -Curated facts extracted from source context - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-28T10:26:09.781Z - -## Narrative -### Highlights -Extracted 1 facts for map-stage/pointer/route script - -## Facts -- **map-stage/pointer/route script**: Remove old inert map-stage/pointer/route script if doing Task 7 subset is necessary to eliminate dead code. diff --git a/.brv/context-tree/facts/project/mask_image.md b/.brv/context-tree/facts/project/mask_image.md deleted file mode 100644 index a41ed3a4..00000000 --- a/.brv/context-tree/facts/project/mask_image.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Mask image -summary: Facts about mask image -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T13:55:17.280Z' -updatedAt: '2026-05-28T13:55:17.292Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Document extracted factual statements - -**Timestamp:** 2026-05-28T13:55:17.267Z - -## Narrative -### Highlights -Extracted 1 facts for mask image - -## Facts -- **mask image**: The .tool-noise mask literal `black` was replaced with a token‑compatible OKLCH alpha value. diff --git a/.brv/context-tree/facts/project/mcp_downstream_handling.md b/.brv/context-tree/facts/project/mcp_downstream_handling.md deleted file mode 100644 index 2462c0b8..00000000 --- a/.brv/context-tree/facts/project/mcp_downstream_handling.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: MCP downstream handling -summary: Project facts about MCP downstream handling -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T14:20:38.726Z' -updatedAt: '2026-05-27T14:20:38.726Z' ---- -## Reason -Curate extracted project facts - -## Raw Concept -**Task:** -Document project facts - -**Timestamp:** 2026-05-27T14:20:38.716Z - -## Narrative -### Highlights -Extracted 1 facts for MCP downstream handling - -## Facts -- **MCP downstream handling**: MCP downstream non-text content is not preserved because `packages/core/src/result-content.ts:46-85` converts all results to a single text block via `textBlocksToString()`. diff --git a/.brv/context-tree/facts/project/missing_design_md.md b/.brv/context-tree/facts/project/missing_design_md.md deleted file mode 100644 index a06ff218..00000000 --- a/.brv/context-tree/facts/project/missing_design_md.md +++ /dev/null @@ -1,27 +0,0 @@ ---- -title: Missing DESIGN.md -summary: Facts about missing DESIGN.md -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T18:47:50.588Z' -updatedAt: '2026-05-29T09:19:16.164Z' ---- -## Reason -Curated factual statements from context extraction - -## Raw Concept -**Task:** -Document factual statements - -**Timestamp:** 2026-05-29T09:19:16.120Z - -## Narrative -### Structure -Extracted facts for missing DESIGN.md - -### Highlights -Extracted 1 facts for missing DESIGN.md - -## Facts -- **missing DESIGN.md**: If DESIGN.md is missing, nudge once per session to run {{command_prefix}}impeccable document. diff --git a/.brv/context-tree/facts/project/missing_product_md.md b/.brv/context-tree/facts/project/missing_product_md.md deleted file mode 100644 index cdb60039..00000000 --- a/.brv/context-tree/facts/project/missing_product_md.md +++ /dev/null @@ -1,27 +0,0 @@ ---- -title: Missing PRODUCT.md -summary: Facts about missing PRODUCT.md -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T18:47:50.563Z' -updatedAt: '2026-05-29T09:19:16.161Z' ---- -## Reason -Curated factual statements from context extraction - -## Raw Concept -**Task:** -Document factual statements - -**Timestamp:** 2026-05-29T09:19:16.120Z - -## Narrative -### Structure -Extracted facts for missing PRODUCT.md - -### Highlights -Extracted 1 facts for missing PRODUCT.md - -## Facts -- **missing PRODUCT.md**: If PRODUCT.md is missing, empty, or contains placeholder [TODO] markers under 200 characters, run {{command_prefix}}impeccable teach. diff --git a/.brv/context-tree/facts/project/missing_register_field.md b/.brv/context-tree/facts/project/missing_register_field.md deleted file mode 100644 index c39ea643..00000000 --- a/.brv/context-tree/facts/project/missing_register_field.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Missing register field -summary: Facts about missing register field -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T19:12:30.290Z' -updatedAt: '2026-05-27T19:12:30.290Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-27T19:12:30.246Z - -## Narrative -### Structure -Extracted facts for missing register field - -## Facts -- **missing register field**: If PRODUCT.md lacks a register field, the assistant should infer it once from the Users and Product Purpose sections and cache it for the session. diff --git a/.brv/context-tree/facts/project/mobile_nav_facts.md b/.brv/context-tree/facts/project/mobile_nav_facts.md deleted file mode 100644 index 7c89df2c..00000000 --- a/.brv/context-tree/facts/project/mobile_nav_facts.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Mobile nav facts -summary: Facts about Mobile nav -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T19:11:54.765Z' -updatedAt: '2026-05-27T19:11:54.765Z' ---- -## Reason -Curated facts from RLM extraction - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-27T19:11:54.664Z - -## Narrative -### Structure -Collected 1 facts for Mobile nav - -## Facts -- **Mobile nav**: Mobile navigation now becomes a full compact command‑bar layout instead of hiding section links. diff --git a/.brv/context-tree/facts/project/modal_usage.md b/.brv/context-tree/facts/project/modal_usage.md deleted file mode 100644 index db27862a..00000000 --- a/.brv/context-tree/facts/project/modal_usage.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Modal usage -summary: Facts about modal usage -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-29T09:19:16.205Z' -updatedAt: '2026-05-29T09:19:16.205Z' ---- -## Reason -Curated factual statements from context extraction - -## Raw Concept -**Task:** -Document factual statements - -**Timestamp:** 2026-05-29T09:19:16.120Z - -## Narrative -### Highlights -Extracted 1 facts for modal usage - -## Facts -- **modal usage**: Using a modal as the first thought is prohibited; prefer inline or progressive alternatives. diff --git a/.brv/context-tree/facts/project/modals.md b/.brv/context-tree/facts/project/modals.md deleted file mode 100644 index 2f16e948..00000000 --- a/.brv/context-tree/facts/project/modals.md +++ /dev/null @@ -1,27 +0,0 @@ ---- -title: Modals -summary: Facts about modals -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T19:18:57.167Z' -updatedAt: '2026-05-27T19:18:57.167Z' ---- -## Reason -Curated facts extracted from context - -## Raw Concept -**Task:** -Document project facts - -**Timestamp:** 2026-05-27T19:18:57.119Z - -## Narrative -### Structure -Collected factual statements - -### Highlights -Contains 1 facts - -## Facts -- **modals**: Modals as the first design thought are banned. diff --git a/.brv/context-tree/facts/project/modified_files.md b/.brv/context-tree/facts/project/modified_files.md deleted file mode 100644 index 5a1fbe9b..00000000 --- a/.brv/context-tree/facts/project/modified_files.md +++ /dev/null @@ -1,27 +0,0 @@ ---- -title: Modified files -summary: Facts about modified files -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T11:14:20.336Z' -updatedAt: '2026-05-27T19:18:57.180Z' ---- -## Reason -Curated facts extracted from context - -## Raw Concept -**Task:** -Document project facts - -**Timestamp:** 2026-05-27T19:18:57.119Z - -## Narrative -### Structure -Collected factual statements - -### Highlights -Contains 1 facts - -## Facts -- **modified files**: The animation changes modified the files `apps/landing/src/pages/index.astro` and `apps/landing/src/styles/global.css`. diff --git a/.brv/context-tree/facts/project/motion.md b/.brv/context-tree/facts/project/motion.md deleted file mode 100644 index c1d01714..00000000 --- a/.brv/context-tree/facts/project/motion.md +++ /dev/null @@ -1,28 +0,0 @@ ---- -title: Motion -summary: Facts about motion -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T19:18:57.152Z' -updatedAt: '2026-05-27T19:18:57.152Z' ---- -## Reason -Curated facts extracted from context - -## Raw Concept -**Task:** -Document project facts - -**Timestamp:** 2026-05-27T19:18:57.119Z - -## Narrative -### Structure -Collected factual statements - -### Highlights -Contains 2 facts - -## Facts -- **motion**: Do not animate CSS layout properties. -- **motion**: Use ease-out exponential curves for motion, no bounce or elastic. diff --git a/.brv/context-tree/facts/project/motion_preferences.md b/.brv/context-tree/facts/project/motion_preferences.md deleted file mode 100644 index 66025181..00000000 --- a/.brv/context-tree/facts/project/motion_preferences.md +++ /dev/null @@ -1,22 +0,0 @@ ---- -title: Motion preferences -summary: Facts about Motion preferences -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T13:58:26.025Z' -updatedAt: '2026-05-28T13:58:26.025Z' ---- -## Reason -Curated facts extraction - -## Raw Concept -**Task:** -Document facts for Motion preferences - -## Narrative -### Highlights -Extracted 1 facts for Motion preferences - -## Facts -- **Motion preferences**: Reduced motion is preserved. diff --git a/.brv/context-tree/facts/project/native_integrations.md b/.brv/context-tree/facts/project/native_integrations.md deleted file mode 100644 index 5e588914..00000000 --- a/.brv/context-tree/facts/project/native_integrations.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: native integrations -summary: Project facts about native integrations -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T14:20:38.732Z' -updatedAt: '2026-05-27T14:20:38.732Z' ---- -## Reason -Curate extracted project facts - -## Raw Concept -**Task:** -Document project facts - -**Timestamp:** 2026-05-27T14:20:38.716Z - -## Narrative -### Highlights -Extracted 1 facts for native integrations - -## Facts -- **native integrations**: Native integrations do not consume the shared core Markdown renderer for structured fallback paths. diff --git a/.brv/context-tree/facts/project/nav_links_facts.md b/.brv/context-tree/facts/project/nav_links_facts.md deleted file mode 100644 index 34440dd5..00000000 --- a/.brv/context-tree/facts/project/nav_links_facts.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Nav links facts -summary: Facts about Nav links -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T19:11:54.762Z' -updatedAt: '2026-05-27T19:11:54.762Z' ---- -## Reason -Curated facts from RLM extraction - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-27T19:11:54.664Z - -## Narrative -### Structure -Collected 1 facts for Nav links - -## Facts -- **Nav links**: A link to npm was added alongside GitHub as a project link in the navigation. diff --git a/.brv/context-tree/facts/project/neutral_colors.md b/.brv/context-tree/facts/project/neutral_colors.md deleted file mode 100644 index 2e5e2e73..00000000 --- a/.brv/context-tree/facts/project/neutral_colors.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Neutral colors -summary: Facts about neutral colors -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T19:12:30.295Z' -updatedAt: '2026-05-27T19:12:30.295Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-27T19:12:30.246Z - -## Narrative -### Structure -Extracted facts for neutral colors - -## Facts -- **neutral colors**: Never use #000 or #fff; tint every neutral toward the brand hue. diff --git a/.brv/context-tree/facts/project/next_move.md b/.brv/context-tree/facts/project/next_move.md deleted file mode 100644 index 20dfe0fe..00000000 --- a/.brv/context-tree/facts/project/next_move.md +++ /dev/null @@ -1,18 +0,0 @@ ---- -title: Next move -summary: Facts about next move -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-29T09:16:04.078Z' -updatedAt: '2026-05-29T09:16:04.078Z' ---- -## Reason -Curate extracted factual statements - -## Raw Concept -**Task:** -Document factual statements - -## Facts -- **next move**: Best immediate next move: tighten the first-screen experience around that headline, then make the before and after demo impossible to miss diff --git a/.brv/context-tree/facts/project/next_step.md b/.brv/context-tree/facts/project/next_step.md deleted file mode 100644 index 329ff086..00000000 --- a/.brv/context-tree/facts/project/next_step.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Next step -summary: Facts about next step -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T13:55:17.320Z' -updatedAt: '2026-05-28T13:55:17.320Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Document extracted factual statements - -**Timestamp:** 2026-05-28T13:55:17.267Z - -## Narrative -### Highlights -Extracted 1 facts for next step - -## Facts -- **next step**: Recommended next step: Review the landing page visually for section rhythm and hover feel. diff --git a/.brv/context-tree/facts/project/next_step_facts.md b/.brv/context-tree/facts/project/next_step_facts.md deleted file mode 100644 index be232f1f..00000000 --- a/.brv/context-tree/facts/project/next_step_facts.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Next step facts -summary: Facts about next step -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T10:14:24.005Z' -updatedAt: '2026-05-28T10:14:24.005Z' ---- -## Reason -Curated factual statements from source - -## Raw Concept -**Task:** -Curate factual statements - -**Timestamp:** 2026-05-28T10:14:23.972Z - -## Narrative -### Highlights -Collected 1 facts for next step - -## Facts -- **next step**: Recommended next step: Review the Task 3 diff, then proceed to Task 4 separately. diff --git a/.brv/context-tree/facts/project/node_deprecation_warning.md b/.brv/context-tree/facts/project/node_deprecation_warning.md deleted file mode 100644 index 8eace709..00000000 --- a/.brv/context-tree/facts/project/node_deprecation_warning.md +++ /dev/null @@ -1,23 +0,0 @@ ---- -title: Node deprecation warning -summary: Facts about Node deprecation warning -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T10:53:51.586Z' -updatedAt: '2026-05-28T10:53:51.586Z' ---- -## Reason -Curated facts extracted from source context - -## Raw Concept -**Task:** -Document facts about Node deprecation warning - -**Timestamp:** 2026-05-28T10:53:51.569Z - -**Author:** ByteRover Context Engineer - -## Facts -- **Node deprecation warning**: The deprecation warning "module.register() is deprecated. Use module.registerHooks() instead" appears under Node 26 -- **Node deprecation warning**: The deprecation warning disappears under Node 24 diff --git a/.brv/context-tree/facts/project/node_requirement.md b/.brv/context-tree/facts/project/node_requirement.md deleted file mode 100644 index ef75ea7f..00000000 --- a/.brv/context-tree/facts/project/node_requirement.md +++ /dev/null @@ -1,22 +0,0 @@ ---- -title: Node requirement -summary: Facts about Node requirement -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T10:53:51.573Z' -updatedAt: '2026-05-28T10:53:51.573Z' ---- -## Reason -Curated facts extracted from source context - -## Raw Concept -**Task:** -Document facts about Node requirement - -**Timestamp:** 2026-05-28T10:53:51.569Z - -**Author:** ByteRover Context Engineer - -## Facts -- **Node requirement**: The repository's CI/dev target requires Node 24 diff --git a/.brv/context-tree/facts/project/node_version.md b/.brv/context-tree/facts/project/node_version.md deleted file mode 100644 index 5ceed3d4..00000000 --- a/.brv/context-tree/facts/project/node_version.md +++ /dev/null @@ -1,22 +0,0 @@ ---- -title: Node version -summary: Facts about Node version -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T10:53:51.572Z' -updatedAt: '2026-05-28T10:53:51.572Z' ---- -## Reason -Curated facts extracted from source context - -## Raw Concept -**Task:** -Document facts about Node version - -**Timestamp:** 2026-05-28T10:53:51.569Z - -**Author:** ByteRover Context Engineer - -## Facts -- **Node version**: The command "pnpm alchemy:dev" was run under Node v26.2.0 diff --git a/.brv/context-tree/facts/project/none.md b/.brv/context-tree/facts/project/none.md deleted file mode 100644 index 2fe6749e..00000000 --- a/.brv/context-tree/facts/project/none.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: None -summary: Facts about none -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T13:55:17.316Z' -updatedAt: '2026-05-28T13:55:17.316Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Document extracted factual statements - -**Timestamp:** 2026-05-28T13:55:17.267Z - -## Narrative -### Highlights -Extracted 1 facts for none - -## Facts -- **none**: Open risks/questions: None from this task. diff --git a/.brv/context-tree/facts/project/none_facts.md b/.brv/context-tree/facts/project/none_facts.md deleted file mode 100644 index c3017c5e..00000000 --- a/.brv/context-tree/facts/project/none_facts.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: None facts -summary: Facts about none -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T10:14:24.002Z' -updatedAt: '2026-05-28T10:14:24.002Z' ---- -## Reason -Curated factual statements from source - -## Raw Concept -**Task:** -Curate factual statements - -**Timestamp:** 2026-05-28T10:14:23.972Z - -## Narrative -### Highlights -Collected 1 facts for none - -## Facts -- **none**: Open risks/questions: None. diff --git a/.brv/context-tree/facts/project/old_route_motion_css.md b/.brv/context-tree/facts/project/old_route_motion_css.md deleted file mode 100644 index d12ea426..00000000 --- a/.brv/context-tree/facts/project/old_route_motion_css.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -title: Old route-motion CSS -summary: Facts about old route-motion CSS -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T10:20:18.279Z' -updatedAt: '2026-05-28T10:20:18.279Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Document facts about old route-motion CSS - -**Timestamp:** 2026-05-28T10:20:18.222Z - -## Facts -- **old route-motion CSS**: Old route-motion CSS is still actively affecting the problem section after the map hero was removed (lines 596 and 600-621 in `apps/landing/src/styles/global.css`). diff --git a/.brv/context-tree/facts/project/onboard.md b/.brv/context-tree/facts/project/onboard.md deleted file mode 100644 index 62d350fe..00000000 --- a/.brv/context-tree/facts/project/onboard.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Onboard -summary: Facts about onboard -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T18:47:50.702Z' -updatedAt: '2026-05-27T18:47:50.702Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-27T18:47:50.505Z - -## Narrative -### Highlights -Extracted 1 facts for onboard - -## Facts -- **onboard**: `onboard [target]` is a Refine command that designs first‑run flows, empty states, and activation experiences. diff --git a/.brv/context-tree/facts/project/onboard_target_facts.md b/.brv/context-tree/facts/project/onboard_target_facts.md deleted file mode 100644 index fbc84985..00000000 --- a/.brv/context-tree/facts/project/onboard_target_facts.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: '`onboard [target]` facts' -summary: Facts about `onboard [target]` -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T19:11:54.737Z' -updatedAt: '2026-05-27T19:11:54.737Z' ---- -## Reason -Curated facts from RLM extraction - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-27T19:11:54.664Z - -## Narrative -### Structure -Collected 1 facts for `onboard [target]` - -## Facts -- **`onboard [target]`**: The command `onboard [target]` belongs to the Refine category and designs first‑run flows, empty states, and activation experiences. diff --git a/.brv/context-tree/facts/project/openapi_facts.md b/.brv/context-tree/facts/project/openapi_facts.md deleted file mode 100644 index 181d5e0c..00000000 --- a/.brv/context-tree/facts/project/openapi_facts.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: OpenAPI Facts -summary: Facts about OpenAPI -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T12:22:30.033Z' -updatedAt: '2026-05-27T12:22:30.033Z' ---- -## Reason -Curated factual statements from RLM extraction - -## Raw Concept -**Task:** -Document facts for OpenAPI - -**Timestamp:** 2026-05-27T12:22:30.013Z - -## Narrative -### Highlights -OpenAPI manager returns jsonResult for spec retrieval and operation calls, with operation metadata. - -## Facts -- **OpenAPI**: OpenAPI manager returns jsonResult for spec retrieval and operation calls, with operation metadata. diff --git a/.brv/context-tree/facts/project/opencode_facts.md b/.brv/context-tree/facts/project/opencode_facts.md deleted file mode 100644 index 5dd47505..00000000 --- a/.brv/context-tree/facts/project/opencode_facts.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: OpenCode Facts -summary: Facts about OpenCode -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T12:22:30.062Z' -updatedAt: '2026-05-27T12:22:30.062Z' ---- -## Reason -Curated factual statements from RLM extraction - -## Raw Concept -**Task:** -Document facts for OpenCode - -**Timestamp:** 2026-05-27T12:22:30.013Z - -## Narrative -### Highlights -OpenCode integration ensures Markdown output is used and adds _openCodeFormatted flag. - -## Facts -- **OpenCode**: OpenCode integration ensures Markdown output is used and adds _openCodeFormatted flag. diff --git a/.brv/context-tree/facts/project/opencode_integration.md b/.brv/context-tree/facts/project/opencode_integration.md deleted file mode 100644 index 1260084b..00000000 --- a/.brv/context-tree/facts/project/opencode_integration.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: OpenCode integration -summary: Project facts about OpenCode integration -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T14:20:38.734Z' -updatedAt: '2026-05-27T14:20:38.734Z' ---- -## Reason -Curate extracted project facts - -## Raw Concept -**Task:** -Document project facts - -**Timestamp:** 2026-05-27T14:20:38.716Z - -## Narrative -### Highlights -Extracted 1 facts for OpenCode integration - -## Facts -- **OpenCode integration**: `packages/opencode/src/hooks.ts:36-60` returns existing text content first, otherwise raw `JSON.stringify(result, null, 2)`, without using `hasRenderableStructuredContent()` or `markdownStructuredContent()`. diff --git a/.brv/context-tree/facts/project/optimize.md b/.brv/context-tree/facts/project/optimize.md deleted file mode 100644 index bf29a134..00000000 --- a/.brv/context-tree/facts/project/optimize.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Optimize -summary: Facts about optimize -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T18:47:50.754Z' -updatedAt: '2026-05-27T18:47:50.754Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-27T18:47:50.505Z - -## Narrative -### Highlights -Extracted 1 facts for optimize - -## Facts -- **optimize**: `optimize [target]` is a Fix command that diagnoses and fixes UI performance issues. diff --git a/.brv/context-tree/facts/project/optimize_target_facts.md b/.brv/context-tree/facts/project/optimize_target_facts.md deleted file mode 100644 index 4ff59f77..00000000 --- a/.brv/context-tree/facts/project/optimize_target_facts.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: '`optimize [target]` facts' -summary: Facts about `optimize [target]` -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T19:11:54.756Z' -updatedAt: '2026-05-27T19:11:54.756Z' ---- -## Reason -Curated facts from RLM extraction - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-27T19:11:54.664Z - -## Narrative -### Structure -Collected 1 facts for `optimize [target]` - -## Facts -- **`optimize [target]`**: The command `optimize [target]` belongs to the Fix category and diagnoses and fixes UI performance issues. diff --git a/.brv/context-tree/facts/project/overall_content.md b/.brv/context-tree/facts/project/overall_content.md deleted file mode 100644 index ba5fb395..00000000 --- a/.brv/context-tree/facts/project/overall_content.md +++ /dev/null @@ -1,18 +0,0 @@ ---- -title: Overall content -summary: Facts about overall content -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-29T09:16:04.075Z' -updatedAt: '2026-05-29T09:16:04.075Z' ---- -## Reason -Curate extracted factual statements - -## Raw Concept -**Task:** -Document factual statements - -## Facts -- **overall content**: Everything else should reinforce that single idea diff --git a/.brv/context-tree/facts/project/overdrive.md b/.brv/context-tree/facts/project/overdrive.md deleted file mode 100644 index 9e8a7804..00000000 --- a/.brv/context-tree/facts/project/overdrive.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Overdrive -summary: Facts about overdrive -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T18:47:50.736Z' -updatedAt: '2026-05-27T18:47:50.736Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-27T18:47:50.505Z - -## Narrative -### Highlights -Extracted 1 facts for overdrive - -## Facts -- **overdrive**: `overdrive [target]` is an Enhance command that pushes past conventional limits. diff --git a/.brv/context-tree/facts/project/overdrive_target_facts.md b/.brv/context-tree/facts/project/overdrive_target_facts.md deleted file mode 100644 index 9192c4a7..00000000 --- a/.brv/context-tree/facts/project/overdrive_target_facts.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: '`overdrive [target]` facts' -summary: Facts about `overdrive [target]` -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T19:11:54.748Z' -updatedAt: '2026-05-27T19:11:54.748Z' ---- -## Reason -Curated facts from RLM extraction - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-27T19:11:54.664Z - -## Narrative -### Structure -Collected 1 facts for `overdrive [target]` - -## Facts -- **`overdrive [target]`**: The command `overdrive [target]` belongs to the Enhance category and pushes designs past conventional limits. diff --git a/.brv/context-tree/facts/project/package_json.md b/.brv/context-tree/facts/project/package_json.md deleted file mode 100644 index 0111bc67..00000000 --- a/.brv/context-tree/facts/project/package_json.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -title: Package.json -summary: Facts about package.json -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T11:56:15.161Z' -updatedAt: '2026-05-28T11:56:15.161Z' ---- -## Reason -Curate extracted facts from source context - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-28T11:56:15.126Z - -## Facts -- **package.json**: `package.json` Alchemy scripts now call the runner via `node ./scripts/alchemy-runner.mjs deploy`. diff --git a/.brv/context-tree/facts/project/packages_core_src_index_ts_facts.md b/.brv/context-tree/facts/project/packages_core_src_index_ts_facts.md deleted file mode 100644 index 90b41b31..00000000 --- a/.brv/context-tree/facts/project/packages_core_src_index_ts_facts.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: packages/core/src/index.ts Facts -summary: Facts about packages/core/src/index.ts -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T12:22:30.025Z' -updatedAt: '2026-05-27T12:22:30.025Z' ---- -## Reason -Curated factual statements from RLM extraction - -## Raw Concept -**Task:** -Document facts for packages/core/src/index.ts - -**Timestamp:** 2026-05-27T12:22:30.013Z - -## Narrative -### Highlights -Exported renderToMarkdown, MarkdownRenderOptions, jsonResult, errorResult, ToolMetadata from packages/core/src/index.ts. - -## Facts -- **packages/core/src/index.ts**: Exported renderToMarkdown, MarkdownRenderOptions, jsonResult, errorResult, ToolMetadata from packages/core/src/index.ts. diff --git a/.brv/context-tree/facts/project/page_copy.md b/.brv/context-tree/facts/project/page_copy.md deleted file mode 100644 index f79719e4..00000000 --- a/.brv/context-tree/facts/project/page_copy.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -title: Page copy -summary: Facts about page copy -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T10:20:18.240Z' -updatedAt: '2026-05-28T10:20:18.240Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Document facts about page copy - -**Timestamp:** 2026-05-28T10:20:18.222Z - -## Facts -- **page copy**: No em dashes found in reviewed page copy. diff --git a/.brv/context-tree/facts/project/panels.md b/.brv/context-tree/facts/project/panels.md deleted file mode 100644 index 7fda6ab2..00000000 --- a/.brv/context-tree/facts/project/panels.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: panels -summary: Facts about panels -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T13:48:34.344Z' -updatedAt: '2026-05-28T13:48:34.344Z' ---- -## Reason -Curated factual statements from recent context - -## Raw Concept -**Task:** -Document project facts - -**Timestamp:** 2026-05-28T13:48:34.339Z - -## Narrative -### Highlights -Collected 1 facts for panels - -## Facts -- **panels**: Panels render server-side with role="tabpanel", aria-labelledby pointing back to the matching tab, and hidden for all but the first panel at lines 328-335 diff --git a/.brv/context-tree/facts/project/path_filter.md b/.brv/context-tree/facts/project/path_filter.md deleted file mode 100644 index 0ca0ae13..00000000 --- a/.brv/context-tree/facts/project/path_filter.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Path filter -summary: Facts about path filter -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T10:41:19.179Z' -updatedAt: '2026-05-28T10:41:19.179Z' ---- -## Reason -Curated factual statements extracted from context - -## Raw Concept -**Task:** -Curate facts - -**Timestamp:** 2026-05-28T10:41:19.156Z - -## Narrative -### Highlights -Extracted 1 facts for path filter - -## Facts -- **path filter**: A PR that only changes `pnpm-lock.yaml`, `packages/**`, `.github/**`, etc. will not trigger. diff --git a/.brv/context-tree/facts/project/performance.md b/.brv/context-tree/facts/project/performance.md deleted file mode 100644 index fe6019a9..00000000 --- a/.brv/context-tree/facts/project/performance.md +++ /dev/null @@ -1,21 +0,0 @@ ---- -title: Performance -summary: Facts about performance -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T11:17:59.537Z' -updatedAt: '2026-05-28T11:17:59.537Z' ---- -## Reason -Curated facts extracted from context - -## Raw Concept -**Task:** -Document facts about performance - -**Timestamp:** 2026-05-28T11:17:59.530Z - -## Facts -- **performance**: Performance score is 4/4. -- **performance**: Static Astro page, no external font/runtime fetches visible, icons inline, animation gated by reduced motion and IntersectionObserver fallback. diff --git a/.brv/context-tree/facts/project/pi_facts.md b/.brv/context-tree/facts/project/pi_facts.md deleted file mode 100644 index a5cd880f..00000000 --- a/.brv/context-tree/facts/project/pi_facts.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Pi Facts -summary: Facts about Pi -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T12:22:30.060Z' -updatedAt: '2026-05-27T12:22:30.060Z' ---- -## Reason -Curated factual statements from RLM extraction - -## Raw Concept -**Task:** -Document facts for Pi - -**Timestamp:** 2026-05-27T12:22:30.013Z - -## Narrative -### Highlights -Pi integration shows full Markdown to the agent and UI preview truncated to previewMaxLength with “Ctrl+O for full content”. - -## Facts -- **Pi**: Pi integration shows full Markdown to the agent and UI preview truncated to previewMaxLength with “Ctrl+O for full content”. diff --git a/.brv/context-tree/facts/project/pi_integration.md b/.brv/context-tree/facts/project/pi_integration.md deleted file mode 100644 index 7904c061..00000000 --- a/.brv/context-tree/facts/project/pi_integration.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Pi integration -summary: Project facts about Pi integration -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T14:20:38.737Z' -updatedAt: '2026-05-27T14:20:38.737Z' ---- -## Reason -Curate extracted project facts - -## Raw Concept -**Task:** -Document project facts - -**Timestamp:** 2026-05-27T14:20:38.716Z - -## Narrative -### Highlights -Extracted 1 facts for Pi integration - -## Facts -- **Pi integration**: `packages/pi/src/index.ts:604-612` similarly returns existing text content first, otherwise raw JSON, and does not use the core Markdown renderer for structured results. diff --git a/.brv/context-tree/facts/project/pin.md b/.brv/context-tree/facts/project/pin.md deleted file mode 100644 index c4cb9945..00000000 --- a/.brv/context-tree/facts/project/pin.md +++ /dev/null @@ -1,27 +0,0 @@ ---- -title: Pin -summary: Facts about pin -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T18:47:50.759Z' -updatedAt: '2026-05-27T19:18:57.173Z' ---- -## Reason -Curated facts extracted from context - -## Raw Concept -**Task:** -Document project facts - -**Timestamp:** 2026-05-27T19:18:57.119Z - -## Narrative -### Structure -Collected factual statements - -### Highlights -Contains 1 facts - -## Facts -- **pin**: `pin ` creates a standalone shortcut so `{{command_prefix}}` invokes `{{command_prefix}}impeccable ` directly. diff --git a/.brv/context-tree/facts/project/pin_unpin_script.md b/.brv/context-tree/facts/project/pin_unpin_script.md deleted file mode 100644 index 179bf212..00000000 --- a/.brv/context-tree/facts/project/pin_unpin_script.md +++ /dev/null @@ -1,27 +0,0 @@ ---- -title: Pin/unpin script -summary: Facts about pin/unpin script -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T19:12:30.327Z' -updatedAt: '2026-05-27T19:18:57.176Z' ---- -## Reason -Curated facts extracted from context - -## Raw Concept -**Task:** -Document project facts - -**Timestamp:** 2026-05-27T19:18:57.119Z - -## Narrative -### Structure -Collected factual statements - -### Highlights -Contains 1 facts - -## Facts -- **pin/unpin script**: The pin/unpin script is executed with `node {{scripts_path}}/pin.mjs `. diff --git a/.brv/context-tree/facts/project/plan_requirement.md b/.brv/context-tree/facts/project/plan_requirement.md deleted file mode 100644 index 878ae300..00000000 --- a/.brv/context-tree/facts/project/plan_requirement.md +++ /dev/null @@ -1,25 +0,0 @@ ---- -title: plan requirement -summary: Project facts about plan requirement -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T14:20:38.729Z' -updatedAt: '2026-05-27T14:20:38.729Z' ---- -## Reason -Curate extracted project facts - -## Raw Concept -**Task:** -Document project facts - -**Timestamp:** 2026-05-27T14:20:38.716Z - -## Narrative -### Highlights -Extracted 2 facts for plan requirement - -## Facts -- **plan requirement**: The current handling violates the plan’s requirement to “Preserve downstream MCP-authored content” while appending structured-content Markdown. -- **plan requirement**: This behavior does not satisfy the plan’s requirement that Pi and OpenCode native integrations should consume the same core Markdown renderer. diff --git a/.brv/context-tree/facts/project/pnpm.md b/.brv/context-tree/facts/project/pnpm.md deleted file mode 100644 index 0f53f14d..00000000 --- a/.brv/context-tree/facts/project/pnpm.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Pnpm -summary: Facts about pnpm -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-29T09:19:16.227Z' -updatedAt: '2026-05-29T09:19:16.227Z' ---- -## Reason -Curated factual statements from context extraction - -## Raw Concept -**Task:** -Document factual statements - -**Timestamp:** 2026-05-29T09:19:16.120Z - -## Narrative -### Highlights -Extracted 1 facts for pnpm - -## Facts -- **pnpm**: The command `pnpm --filter @caplets/landing build` completed successfully and built 1 page. diff --git a/.brv/context-tree/facts/project/pnpm_filter_caplets_landing.md b/.brv/context-tree/facts/project/pnpm_filter_caplets_landing.md deleted file mode 100644 index 0f166b3e..00000000 --- a/.brv/context-tree/facts/project/pnpm_filter_caplets_landing.md +++ /dev/null @@ -1,21 +0,0 @@ ---- -title: Pnpm --filter @caplets/landing -summary: Facts about pnpm --filter @caplets/landing -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T11:17:59.546Z' -updatedAt: '2026-05-28T11:17:59.546Z' ---- -## Reason -Curated facts extracted from context - -## Raw Concept -**Task:** -Document facts about pnpm --filter @caplets/landing - -**Timestamp:** 2026-05-28T11:17:59.530Z - -## Facts -- **pnpm --filter @caplets/landing**: Verification run typecheck passed with 0 errors. -- **pnpm --filter @caplets/landing**: Verification run build passed, building 1 page. diff --git a/.brv/context-tree/facts/project/polish.md b/.brv/context-tree/facts/project/polish.md deleted file mode 100644 index ddb1b915..00000000 --- a/.brv/context-tree/facts/project/polish.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Polish -summary: Facts about polish -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T18:47:50.678Z' -updatedAt: '2026-05-27T18:47:50.678Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-27T18:47:50.505Z - -## Narrative -### Highlights -Extracted 1 facts for polish - -## Facts -- **polish**: `polish [target]` is a Refine command that provides a final quality pass before shipping. diff --git a/.brv/context-tree/facts/project/polish_target_facts.md b/.brv/context-tree/facts/project/polish_target_facts.md deleted file mode 100644 index 5901e4fd..00000000 --- a/.brv/context-tree/facts/project/polish_target_facts.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: '`polish [target]` facts' -summary: Facts about `polish [target]` -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T19:11:54.724Z' -updatedAt: '2026-05-27T19:11:54.724Z' ---- -## Reason -Curated facts from RLM extraction - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-27T19:11:54.664Z - -## Narrative -### Structure -Collected 1 facts for `polish [target]` - -## Facts -- **`polish [target]`**: The command `polish [target]` belongs to the Refine category and provides a final quality pass before shipping. diff --git a/.brv/context-tree/facts/project/post_commit_hook_brv.md b/.brv/context-tree/facts/project/post_commit_hook_brv.md deleted file mode 100644 index 8520625a..00000000 --- a/.brv/context-tree/facts/project/post_commit_hook_brv.md +++ /dev/null @@ -1,41 +0,0 @@ ---- -title: post_commit_hook_brv -summary: Post-commit hook auto-commits .brv context and pre-push verifies no uncommitted .brv changes -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T16:56:48.398Z' -updatedAt: '2026-05-28T16:56:48.398Z' ---- -## Reason -Document design of post-commit hook to commit .brv changes and pre-push check - -## Raw Concept -**Task:** -Implement git hooks for automatic .brv context commits and verification before push - -**Files:** -- .husky/post-commit -- .husky/pre-push -- scripts/commit-byterover-context.ts - -**Flow:** -post-commit -> detect .brv changes -> stage .brv -> commit with docs(agents): byterover context; pre-push -> verify .brv clean before push - -**Timestamp:** 2026-05-28T16:56:15.853Z - -**Author:** assistant - -## Narrative -### Structure -post-commit runs tsx script; script checks git status, stages .brv, makes conventional commit; pre-push runs verify then ensures .brv clean - -### Highlights -Avoid recursion via BYTEROVER_CONTEXT_COMMIT env var; fail push if .brv has uncommitted changes - -## Facts -- **husky_post_commit**: .husky/post-commit runs pnpm exec tsx ./scripts/commit-byterover-context.ts [project] -- **husky_pre_push**: .husky/pre-push runs pnpm verify [project] -- **script_detection**: commit-byterover-context.ts detects .brv changes via git status --porcelain -- .brv [project] -- **script_commit**: Script stages .brv and creates docs(agents): byterover context commit [project] -- **pre_push_check**: Pre-push check fails if .brv has uncommitted changes [project] diff --git a/.brv/context-tree/facts/project/potential_files.md b/.brv/context-tree/facts/project/potential_files.md deleted file mode 100644 index 02d0014e..00000000 --- a/.brv/context-tree/facts/project/potential_files.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Potential files -summary: Facts about potential files -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T13:55:17.271Z' -updatedAt: '2026-05-28T13:55:17.271Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Document extracted factual statements - -**Timestamp:** 2026-05-28T13:55:17.267Z - -## Narrative -### Highlights -Extracted 1 facts for potential files - -## Facts -- **potential files**: Likely involved files are apps/landing/src/pages/index.astro and apps/landing/src/styles/global.css. diff --git a/.brv/context-tree/facts/project/pr_65_fixes_and_verification.md b/.brv/context-tree/facts/project/pr_65_fixes_and_verification.md deleted file mode 100644 index 9315cb0e..00000000 --- a/.brv/context-tree/facts/project/pr_65_fixes_and_verification.md +++ /dev/null @@ -1,48 +0,0 @@ ---- -title: PR 65 Fixes and Verification -summary: 'PR #65 fix for Pi legacy remote settings and auth-flow object isolation, with verification passing locally and on push hook' -tags: [] -related: [facts/project/pr_71_completion_fix_outcome.md, facts/project/remote_control_review_outcome.md, facts/project/greptile_review_comments.md, facts/project/task_6_spec_review_after_envelope_fix.md] -keywords: [] -createdAt: '2026-05-20T19:06:59.769Z' -updatedAt: '2026-05-20T19:06:59.769Z' ---- -## Reason -Curate durable facts about the PR fixes, tests, and release state - -## Raw Concept -**Task:** -Document PR review fixes and verification outcome - -**Changes:** -- Addressed review comments and pushed fixes -- Preserved legacy Pi remote settings behavior -- Added regression tests for legacy settings and auth-flow object isolation - -**Flow:** -review comments -> fixes -> tests -> local verify -> push hook verify -> PR checks queued - -**Timestamp:** 2026-05-20T19:06:38.716Z - -## Narrative -### Structure -The update centers on PR #65, with commit c86806d carrying the fixes and verification results. - -### Dependencies -Relies on Pi/core tests and pnpm verify for validation. - -### Highlights -Legacy Pi remote settings continue to load into server settings; auth-flow objects are now isolated by copy. - -## Facts -- **pr_65**: PR #65 was updated with fixes addressing review comments, including outside-diff comments. [project] -- **legacy_remote_settings**: Commit c86806d preserves Pi legacy remote settings by loading remote.url, remote.user, and remote.password into server settings instead of falling back to local mode. [project] -- **legacy_remote_url_warning**: A deprecation warning is emitted for legacy Pi remote.url settings. [project] -- **remote_auth_flow_store**: RemoteAuthFlowStore.create() now returns a shallow copy instead of the internal stored object reference. [project] -- **regression_tests**: Regression tests were added for Pi legacy remote settings and auth-flow object isolation. [project] -- **focused_tests**: Focused Pi/core tests passed. [project] -- **verification**: pnpm verify passed locally. [project] -- **push_hook_verification**: The push hook ran pnpm verify again and it passed. [project] -- **verification_metrics**: Verification reported 37 test files passed and 500 tests passed, with format, lint, typecheck, schema, benchmark, and build all passing. [project] -- **pr_head**: PR head is now c86806d. [project] -- **pr_checks**: PR checks are queued on GitHub. [project] diff --git a/.brv/context-tree/facts/project/pr_71_completion_fix_outcome.md b/.brv/context-tree/facts/project/pr_71_completion_fix_outcome.md deleted file mode 100644 index 72c1a95e..00000000 --- a/.brv/context-tree/facts/project/pr_71_completion_fix_outcome.md +++ /dev/null @@ -1,32 +0,0 @@ ---- -consolidated_at: '2026-05-21T23:23:02.290Z' -consolidated_from: [{date: '2026-05-21T23:23:02.290Z', path: facts/project/pr_71_completion_commands_sync.md, reason: 'These two PR #71 outcome files describe the same completion-related fix and verification cycle, with one focusing on command parity/stderr suppression and the other on the PowerShell sentinel/endpoint normalization issue. They are complementary parts of the same PR outcome and should be merged into a single consolidated PR #71 completion fixes note.'}] -related: [facts/project/pr_65_fixes_and_verification.md, facts/project/remote_control_review_outcome.md, facts/project/greptile_review_comments.md, facts/project/task_6_spec_review_after_envelope_fix.md] ---- -# Title: PR 71 Completion Fix Outcome - -## Summary -PR #71 resolved completion regressions in two related phases: command parity/stderr suppression and the PowerShell trailing-space sentinel bug. The final state passed targeted completion tests and pnpm verify. - -## Combined Outcome -- Synced completion command generation with createProgram() to fix topLevelCommands drift. -- Added a regression test comparing completeCliWords([""]) against registered createProgram() commands, excluding hidden __complete. -- Suppressed stderr in generated shell helpers across Bash, Zsh, Fish, PowerShell, and cmd. -- Replaced the PowerShell empty-string trailing-space sentinel with __CAPLETS_TRAILING_SPACE__. -- Normalized __CAPLETS_TRAILING_SPACE__ inside the hidden __complete endpoint before local or remote completion resolution. -- Added tests for PowerShell script output and hidden completion endpoint normalization. -- Fetched unresolved review threads, including outside-diff comments, and rechecked review status to zero unresolved threads. -- Verification passed with targeted CLI completion tests and pnpm verify. - -## Flow -issue report -> fix applied -> regression tests added -> endpoint/script normalization -> targeted tests -> pnpm verify -> review threads cleared - -## Preservation Notes -- Preserve shell-specific stderr redirection details: Bash/Zsh/Fish 2>/dev/null, PowerShell 2>$null, cmd 2^>nul. -- Preserve the sentinel name __CAPLETS_TRAILING_SPACE__ and the hidden __complete endpoint normalization behavior. -- Preserve the review-thread fetching requirement that includes outside-diff comments. - -## Facts -- topLevelCommands drift was fixed by syncing completion commands. -- PowerShell sentinel behavior was hardened against Windows PowerShell 5.1. -- Targeted completion-related tests and pnpm verify passed. \ No newline at end of file diff --git a/.brv/context-tree/facts/project/preview.md b/.brv/context-tree/facts/project/preview.md deleted file mode 100644 index 515b5009..00000000 --- a/.brv/context-tree/facts/project/preview.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Preview -summary: Facts about preview -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T18:59:51.365Z' -updatedAt: '2026-05-27T18:59:51.365Z' ---- -## Reason -Curate extracted project facts - -## Raw Concept -**Task:** -Document project fact - -**Timestamp:** 2026-05-27T18:59:51.340Z - -## Narrative -### Highlights -Extracted 1 facts for preview - -## Facts -- **preview**: Browser preview checked. diff --git a/.brv/context-tree/facts/project/primary_navigation.md b/.brv/context-tree/facts/project/primary_navigation.md deleted file mode 100644 index 98a07249..00000000 --- a/.brv/context-tree/facts/project/primary_navigation.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Primary navigation -summary: Facts about primary navigation -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T10:26:09.787Z' -updatedAt: '2026-05-28T10:26:09.787Z' ---- -## Reason -Curated facts extracted from source context - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-28T10:26:09.781Z - -## Narrative -### Highlights -Extracted 1 facts for primary navigation - -## Facts -- **primary navigation**: Update the primary navigation broken `#map`/`Map` to a valid trace-first target/label. diff --git a/.brv/context-tree/facts/project/problem_section.md b/.brv/context-tree/facts/project/problem_section.md deleted file mode 100644 index 2d721592..00000000 --- a/.brv/context-tree/facts/project/problem_section.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Problem section -summary: Facts about problem section -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-29T09:19:16.216Z' -updatedAt: '2026-05-29T09:19:16.216Z' ---- -## Reason -Curated factual statements from context extraction - -## Raw Concept -**Task:** -Document factual statements - -**Timestamp:** 2026-05-29T09:19:16.120Z - -## Narrative -### Highlights -Extracted 1 facts for problem section - -## Facts -- **problem section**: Reframed the problem section around “giant tool walls” in apps/landing/src/pages/index.astro. diff --git a/.brv/context-tree/facts/project/product_md.md b/.brv/context-tree/facts/project/product_md.md deleted file mode 100644 index 7a8f8fb7..00000000 --- a/.brv/context-tree/facts/project/product_md.md +++ /dev/null @@ -1,27 +0,0 @@ ---- -title: PRODUCT.md -summary: Facts about PRODUCT.md -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T18:47:50.543Z' -updatedAt: '2026-05-29T09:19:16.127Z' ---- -## Reason -Curated factual statements from context extraction - -## Raw Concept -**Task:** -Document factual statements - -**Timestamp:** 2026-05-29T09:19:16.120Z - -## Narrative -### Structure -Collected factual statements - -### Highlights -Extracted 1 facts for PRODUCT.md - -## Facts -- **PRODUCT.md**: PRODUCT.md is required. diff --git a/.brv/context-tree/facts/project/product_md_facts.md b/.brv/context-tree/facts/project/product_md_facts.md deleted file mode 100644 index 5d85106d..00000000 --- a/.brv/context-tree/facts/project/product_md_facts.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: PRODUCT.md facts -summary: Facts about PRODUCT.md -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T19:11:54.671Z' -updatedAt: '2026-05-27T19:11:54.671Z' ---- -## Reason -Curated facts from RLM extraction - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-27T19:11:54.663Z - -## Narrative -### Structure -Collected 1 facts for PRODUCT.md - -## Facts -- **PRODUCT.md**: PRODUCT.md is required. diff --git a/.brv/context-tree/facts/project/project.md b/.brv/context-tree/facts/project/project.md deleted file mode 100644 index 5a0cce53..00000000 --- a/.brv/context-tree/facts/project/project.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -title: Project -summary: Facts about project -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T11:17:59.547Z' -updatedAt: '2026-05-28T11:17:59.547Z' ---- -## Reason -Curated facts extracted from context - -## Raw Concept -**Task:** -Document facts about project - -**Timestamp:** 2026-05-28T11:17:59.530Z - -## Facts -- **project**: There are no blockers. diff --git a/.brv/context-tree/facts/project/project_facts.md b/.brv/context-tree/facts/project/project_facts.md deleted file mode 100644 index 89dae3f7..00000000 --- a/.brv/context-tree/facts/project/project_facts.md +++ /dev/null @@ -1,46 +0,0 @@ ---- -title: Project Facts -summary: Extracted project-related factual statements -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T18:33:58.630Z' -updatedAt: '2026-05-27T18:33:58.630Z' ---- -## Reason -Curate extracted project facts from context - -## Raw Concept -**Task:** -Document project facts extracted from context - -**Flow:** -extraction -> dedup -> curate - -**Timestamp:** 2026-05-27T18:33:58.628Z - -## Narrative -### Structure -Facts grouped by subject - -### Highlights -Caplets landing page, index.astro, global.css, astro.config.mjs, hero section - -## Facts -- **Caplets landing page**: Implemented the Caplets landing page in `apps/landing/`. -- **index.astro**: Modified `apps/landing/src/pages/index.astro`. -- **global.css**: Modified `apps/landing/src/styles/global.css`. -- **astro.config.mjs**: Modified `apps/landing/astro.config.mjs`. -- **hero section**: Added an asymmetric hero with the approved Tool Cartographer direction. -- **capability-map hero**: Implemented capability-map hero visualization with route lines, scoped operation cards, and selected route panel. -- **comparison section**: Provided problem/solution comparison for flat tool lists vs progressive disclosure. -- **proof sections**: Included proof sections for disclosure, lossless routing, and redacted operational safety. -- **integration strip**: Added integration strip for supported agent clients. -- **install terminal**: Implemented install terminal with real Caplets commands. -- **responsive design**: Ensured responsive mobile layout, visible focus states, reduced-motion handling, and no console errors in preview. -- **build command**: `pnpm --filter @caplets/landing build` passed. -- **typecheck command**: `pnpm --filter @caplets/landing typecheck` passed with 0 errors, 0 warnings, 0 hints. -- **browser preview**: Browser preview was checked at desktop and mobile. -- **browser console**: Browser console was checked with 0 errors and 0 warnings. -- **desktop screenshot**: Captured screenshot `caplets-landing-desktop-final.png`. -- **mobile screenshot**: Captured screenshot `caplets-landing-mobile.png`. diff --git a/.brv/context-tree/facts/project/proof_and_trust_sections.md b/.brv/context-tree/facts/project/proof_and_trust_sections.md deleted file mode 100644 index 42b5cf5e..00000000 --- a/.brv/context-tree/facts/project/proof_and_trust_sections.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -title: Proof and trust sections -summary: Facts about proof and trust sections -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T10:20:18.239Z' -updatedAt: '2026-05-28T10:20:18.239Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Document facts about proof and trust sections - -**Timestamp:** 2026-05-28T10:20:18.222Z - -## Facts -- **proof and trust sections**: Proof and trust sections are concrete and text-led. diff --git a/.brv/context-tree/facts/project/pull_request_permissions.md b/.brv/context-tree/facts/project/pull_request_permissions.md deleted file mode 100644 index 5bd99d22..00000000 --- a/.brv/context-tree/facts/project/pull_request_permissions.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Pull request permissions -summary: Facts about pull request permissions -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T10:41:19.185Z' -updatedAt: '2026-05-28T10:41:19.185Z' ---- -## Reason -Curated factual statements extracted from context - -## Raw Concept -**Task:** -Curate facts - -**Timestamp:** 2026-05-28T10:41:19.156Z - -## Narrative -### Highlights -Extracted 1 facts for pull request permissions - -## Facts -- **pull request permissions**: `permissions: pull-requests: write` is fine for commenting/updating PRs, but may be restricted on forked PRs depending repo settings. diff --git a/.brv/context-tree/facts/project/punctuation.md b/.brv/context-tree/facts/project/punctuation.md deleted file mode 100644 index df45b255..00000000 --- a/.brv/context-tree/facts/project/punctuation.md +++ /dev/null @@ -1,27 +0,0 @@ ---- -title: Punctuation -summary: Facts about punctuation -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T18:47:50.642Z' -updatedAt: '2026-05-29T09:19:16.207Z' ---- -## Reason -Curated factual statements from context extraction - -## Raw Concept -**Task:** -Document factual statements - -**Timestamp:** 2026-05-29T09:19:16.120Z - -## Narrative -### Structure -Collected factual statements - -### Highlights -Extracted 1 facts for punctuation - -## Facts -- **punctuation**: No em dashes are allowed in copy; use commas, colons, semicolons, periods, or parentheses. diff --git a/.brv/context-tree/facts/project/punctuation_facts.md b/.brv/context-tree/facts/project/punctuation_facts.md deleted file mode 100644 index 42633b40..00000000 --- a/.brv/context-tree/facts/project/punctuation_facts.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Punctuation facts -summary: Facts about punctuation -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T19:11:54.696Z' -updatedAt: '2026-05-27T19:11:54.696Z' ---- -## Reason -Curated facts from RLM extraction - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-27T19:11:54.663Z - -## Narrative -### Structure -Collected 1 facts for punctuation - -## Facts -- **punctuation**: No em dashes may be used in copy; use commas, colons, semicolons, periods, or parentheses instead. diff --git a/.brv/context-tree/facts/project/quieter.md b/.brv/context-tree/facts/project/quieter.md deleted file mode 100644 index 079e1f55..00000000 --- a/.brv/context-tree/facts/project/quieter.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Quieter -summary: Facts about quieter -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T18:47:50.695Z' -updatedAt: '2026-05-27T18:47:50.695Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-27T18:47:50.505Z - -## Narrative -### Highlights -Extracted 1 facts for quieter - -## Facts -- **quieter**: `quieter [target]` is a Refine command that tones down aggressive or overstimulating designs. diff --git a/.brv/context-tree/facts/project/quieter_target_facts.md b/.brv/context-tree/facts/project/quieter_target_facts.md deleted file mode 100644 index 70ba1fce..00000000 --- a/.brv/context-tree/facts/project/quieter_target_facts.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: '`quieter [target]` facts' -summary: Facts about `quieter [target]` -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T19:11:54.728Z' -updatedAt: '2026-05-27T19:11:54.728Z' ---- -## Reason -Curated facts from RLM extraction - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-27T19:11:54.664Z - -## Narrative -### Structure -Collected 1 facts for `quieter [target]` - -## Facts -- **`quieter [target]`**: The command `quieter [target]` belongs to the Refine category and tones down aggressive or overstimulating designs. diff --git a/.brv/context-tree/facts/project/readability.md b/.brv/context-tree/facts/project/readability.md deleted file mode 100644 index 6e4f809f..00000000 --- a/.brv/context-tree/facts/project/readability.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Readability -summary: Facts about readability -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T18:59:51.351Z' -updatedAt: '2026-05-27T18:59:51.351Z' ---- -## Reason -Curate extracted project facts - -## Raw Concept -**Task:** -Document project fact - -**Timestamp:** 2026-05-27T18:59:51.340Z - -## Narrative -### Highlights -Extracted 1 facts for readability - -## Facts -- **readability**: Increased dark-mode label/accent readability without making the page neon or breaking the charred-ink direction. diff --git a/.brv/context-tree/facts/project/readme_md.md b/.brv/context-tree/facts/project/readme_md.md deleted file mode 100644 index 618b147d..00000000 --- a/.brv/context-tree/facts/project/readme_md.md +++ /dev/null @@ -1,25 +0,0 @@ ---- -title: README.md -summary: Facts about README.md -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-29T09:19:16.218Z' -updatedAt: '2026-05-29T09:19:16.218Z' ---- -## Reason -Curated factual statements from context extraction - -## Raw Concept -**Task:** -Document factual statements - -**Timestamp:** 2026-05-29T09:19:16.120Z - -## Narrative -### Highlights -Extracted 2 facts for README.md - -## Facts -- **README.md**: Updated hero tagline in README.md. -- **README.md**: Added sharper “Stop dumping every operation into context up front” positioning in README.md. diff --git a/.brv/context-tree/facts/project/register_identification.md b/.brv/context-tree/facts/project/register_identification.md deleted file mode 100644 index a28a9659..00000000 --- a/.brv/context-tree/facts/project/register_identification.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Register identification -summary: Facts about register identification -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T19:12:30.287Z' -updatedAt: '2026-05-27T19:12:30.287Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-27T19:12:30.246Z - -## Narrative -### Structure -Extracted facts for register identification - -## Facts -- **register identification**: The register (brand or product) must be identified before designing, using priority: cue in the task, surface in focus, then register field in PRODUCT.md. diff --git a/.brv/context-tree/facts/project/remote_control_review_outcome.md b/.brv/context-tree/facts/project/remote_control_review_outcome.md deleted file mode 100644 index 18341343..00000000 --- a/.brv/context-tree/facts/project/remote_control_review_outcome.md +++ /dev/null @@ -1,66 +0,0 @@ ---- -title: Remote Control Review Outcome -summary: 'Approved remote control review: server-side redaction fixed, regression tests added, CLI pending auth wording accepted, with residual spec mismatch noted.' -tags: [] -related: [facts/project/pr_65_fixes_and_verification.md, facts/project/pr_71_completion_fix_outcome.md, facts/project/greptile_review_comments.md, facts/conventions/task_3_spec_review_fix.md, facts/conventions/task_5_regression_test_update.md, facts/conventions/task_6_review_outcome.md, facts/conventions/task_7_review_outcome.md, facts/conventions/task_7_remote_mutation_routing_review.md, facts/conventions/task_9_remote_config_test_fix.md, facts/project/task_6_spec_review_after_envelope_fix.md] -keywords: [] -createdAt: '2026-05-20T15:41:00.585Z' -updatedAt: '2026-05-20T15:41:00.585Z' ---- - -## Reason - -Persist final review outcome and verification details from the code review conversation - -## Raw Concept - -**Task:** -Document the final remote control review outcome after verifying the security fix and auth behavior. - -**Changes:** -- Approved the review after confirming server-side redaction fix -- Noted regression test coverage for remote control dispatch -- Accepted the pending-auth CLI wording as non-misleading -- Captured the remaining spec-versus-CLI residual risk - -**Files:** -- packages/core/src/remote-control/dispatch.ts -- packages/core/test/remote-control-dispatch.test.ts -- packages/core/test/remote-control-client.test.ts -- packages/core/test/cli-remote.test.ts - -**Flow:** -review request -> verify security fix -> reassess auth wording -> inspect test results -> approve - -**Timestamp:** 2026-05-20T15:40:45.193Z - -## Narrative - -### Structure - -The review outcome centers on one fixed security issue, one accepted pending-auth UX detail, and one residual design-spec mismatch. - -### Dependencies - -Depends on the remote control dispatch implementation, its regression tests, and the CLI wording/docs for remote authentication. - -### Highlights - -The review concluded APPROVED, with focused remote tests passing and the prior high security issue resolved. - -### Rules - -Return APPROVED or FINDINGS. Do not edit. The pending auth flow is acceptable when the CLI wording clearly indicates browser completion and server-side credential storage. - -### Examples - -Verification used the command: pnpm --filter @caplets/core test -- test/remote-control-dispatch.test.ts test/remote-control-client.test.ts test/cli-remote.test.ts - -## Facts - -- **remote_control_error_redaction**: Commit 7cb9381 fixed server-side remote control error redaction. [project] -- **remote_control_dispatch**: A high security finding was verified as addressed in packages/core/src/remote-control/dispatch.ts. [project] -- **remote_control_regression_tests**: Regression coverage was added in packages/core/test/remote-control-dispatch.test.ts. [project] -- **remote_auth_cli_wording**: CLI wording for pending remote auth login says: "Complete authentication in your browser. The server callback will store credentials." [project] -- **remote_test_results**: Focused remote tests passed with 33 files and 415 tests. [project] -- **remote_auth_residual_risk**: Residual risk noted that the design spec still describes wait/poll as the ideal flow while current CLI behavior is pending-oriented and non-misleading. [project] diff --git a/.brv/context-tree/facts/project/rendertomarkdown_facts.md b/.brv/context-tree/facts/project/rendertomarkdown_facts.md deleted file mode 100644 index 3bac137b..00000000 --- a/.brv/context-tree/facts/project/rendertomarkdown_facts.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: renderToMarkdown Facts -summary: Facts about renderToMarkdown -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T12:22:30.018Z' -updatedAt: '2026-05-27T12:22:30.018Z' ---- -## Reason -Curated factual statements from RLM extraction - -## Raw Concept -**Task:** -Document facts for renderToMarkdown - -**Timestamp:** 2026-05-27T12:22:30.012Z - -## Narrative -### Highlights -Added renderToMarkdown with options: preserveArrayIndices, maxDepth, includeMetadata, codeBlock. - -## Facts -- **renderToMarkdown**: Added renderToMarkdown with options: preserveArrayIndices, maxDepth, includeMetadata, codeBlock. diff --git a/.brv/context-tree/facts/project/repo.md b/.brv/context-tree/facts/project/repo.md deleted file mode 100644 index e1f9852b..00000000 --- a/.brv/context-tree/facts/project/repo.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Repo -summary: Facts about repo -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T13:55:17.268Z' -updatedAt: '2026-05-28T13:55:17.268Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Document extracted factual statements - -**Timestamp:** 2026-05-28T13:55:17.266Z - -## Narrative -### Highlights -Extracted 1 facts for repo - -## Facts -- **repo**: The repository is located at /home/ianpascoe/src/caplets. diff --git a/.brv/context-tree/facts/project/responsive.md b/.brv/context-tree/facts/project/responsive.md deleted file mode 100644 index fb3053ec..00000000 --- a/.brv/context-tree/facts/project/responsive.md +++ /dev/null @@ -1,21 +0,0 @@ ---- -title: Responsive -summary: Facts about responsive -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T11:17:59.539Z' -updatedAt: '2026-05-28T11:17:59.539Z' ---- -## Reason -Curated facts extracted from context - -## Raw Concept -**Task:** -Document facts about responsive - -**Timestamp:** 2026-05-28T11:17:59.530Z - -## Facts -- **responsive**: Responsive score is 4/4. -- **responsive**: Layouts collapse at 980px and 720px, long code wraps, min widths and overflow handling are present. diff --git a/.brv/context-tree/facts/project/result_content_implementation.md b/.brv/context-tree/facts/project/result_content_implementation.md deleted file mode 100644 index d9bc6dbe..00000000 --- a/.brv/context-tree/facts/project/result_content_implementation.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Result-content implementation -summary: Facts about result-content implementation -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T11:14:20.319Z' -updatedAt: '2026-05-27T11:14:20.319Z' ---- -## Reason -Curated factual statements extracted from context - -## Raw Concept -**Task:** -Document factual statements - -**Timestamp:** 2026-05-27T11:14:20.303Z - -## Narrative -### Structure -Collection of facts regarding result-content implementation - -## Facts -- **result-content implementation**: The root cause was that packages/core/src/result-content.ts intentionally summarized structured results as labels only. diff --git a/.brv/context-tree/facts/project/result_content_test_ts_facts.md b/.brv/context-tree/facts/project/result_content_test_ts_facts.md deleted file mode 100644 index cfd3e3aa..00000000 --- a/.brv/context-tree/facts/project/result_content_test_ts_facts.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: result-content.test.ts Facts -summary: Facts about result-content.test.ts -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T12:22:30.064Z' -updatedAt: '2026-05-27T12:22:30.064Z' ---- -## Reason -Curated factual statements from RLM extraction - -## Raw Concept -**Task:** -Document facts for result-content.test.ts - -**Timestamp:** 2026-05-27T12:22:30.013Z - -## Narrative -### Highlights -Added result-content.test.ts with 35 tests covering primitives, objects, arrays, nesting, options, metadata, code blocks, URLs, dates, functions, circular refs, and error formatting. - -## Facts -- **result-content.test.ts**: Added result-content.test.ts with 35 tests covering primitives, objects, arrays, nesting, options, metadata, code blocks, URLs, dates, functions, circular refs, and error formatting. diff --git a/.brv/context-tree/facts/project/result_content_ts.md b/.brv/context-tree/facts/project/result_content_ts.md deleted file mode 100644 index 4f2ac9ce..00000000 --- a/.brv/context-tree/facts/project/result_content_ts.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Result-content.ts -summary: Facts about result-content.ts -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T11:14:20.321Z' -updatedAt: '2026-05-27T11:14:20.321Z' ---- -## Reason -Curated factual statements extracted from context - -## Raw Concept -**Task:** -Document factual statements - -**Timestamp:** 2026-05-27T11:14:20.303Z - -## Narrative -### Structure -Collection of facts regarding result-content.ts - -## Facts -- **result-content.ts**: Compact previews for body, json, stdout, and stderr were added to result-content.ts. diff --git a/.brv/context-tree/facts/project/reveal_component.md b/.brv/context-tree/facts/project/reveal_component.md deleted file mode 100644 index f70bc8c1..00000000 --- a/.brv/context-tree/facts/project/reveal_component.md +++ /dev/null @@ -1,22 +0,0 @@ ---- -title: Reveal component -summary: Facts about Reveal component -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T13:58:26.022Z' -updatedAt: '2026-05-28T13:58:26.022Z' ---- -## Reason -Curated facts extraction - -## Raw Concept -**Task:** -Document facts for Reveal component - -## Narrative -### Highlights -Extracted 1 facts for Reveal component - -## Facts -- **Reveal component**: Reveal content is visible by default. diff --git a/.brv/context-tree/facts/project/reveal_transitions.md b/.brv/context-tree/facts/project/reveal_transitions.md deleted file mode 100644 index cf730f4c..00000000 --- a/.brv/context-tree/facts/project/reveal_transitions.md +++ /dev/null @@ -1,22 +0,0 @@ ---- -title: Reveal transitions -summary: Facts about Reveal transitions -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T13:58:26.028Z' -updatedAt: '2026-05-28T13:58:26.028Z' ---- -## Reason -Curated facts extraction - -## Raw Concept -**Task:** -Document facts for Reveal transitions - -## Narrative -### Highlights -Extracted 1 facts for Reveal transitions - -## Facts -- **Reveal transitions**: Reveal transitions use opacity/transform only. diff --git a/.brv/context-tree/facts/project/risks.md b/.brv/context-tree/facts/project/risks.md deleted file mode 100644 index e7341759..00000000 --- a/.brv/context-tree/facts/project/risks.md +++ /dev/null @@ -1,27 +0,0 @@ ---- -title: risks -summary: Facts about risks -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T13:51:08.373Z' -updatedAt: '2026-05-28T13:51:08.373Z' ---- -## Reason -Curated extracted facts - -## Raw Concept -**Task:** -Document factual statements about risks - -**Timestamp:** 2026-05-28T13:51:08.346Z - -## Narrative -### Structure -Collected facts for risks - -### Highlights -Open risks/questions: None. - -## Facts -- **risks**: Open risks/questions: None. diff --git a/.brv/context-tree/facts/project/route_motion_css.md b/.brv/context-tree/facts/project/route_motion_css.md deleted file mode 100644 index 9293fbfc..00000000 --- a/.brv/context-tree/facts/project/route_motion_css.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Route-motion CSS -summary: Facts about route-motion CSS -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T10:26:09.789Z' -updatedAt: '2026-05-28T10:26:09.789Z' ---- -## Reason -Curated facts extracted from source context - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-28T10:26:09.781Z - -## Narrative -### Highlights -Extracted 1 facts for route-motion CSS - -## Facts -- **route-motion CSS**: Remove or neutralize old route-motion CSS currently affecting `.problem`, `.dense-list`, `.ordered-flow`. diff --git a/.brv/context-tree/facts/project/route_motion_script.md b/.brv/context-tree/facts/project/route_motion_script.md deleted file mode 100644 index c6e442c1..00000000 --- a/.brv/context-tree/facts/project/route_motion_script.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -title: Route-motion script -summary: Facts about route-motion script -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T10:20:18.280Z' -updatedAt: '2026-05-28T10:20:18.280Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Document facts about route-motion script - -**Timestamp:** 2026-05-28T10:20:18.222Z - -## Facts -- **route-motion script**: Because `.map-stage` no longer exists, the script never updates `--route-resolve`, leaving the section with stale transform, opacity, and margin behavior. diff --git a/.brv/context-tree/facts/project/rule_1.md b/.brv/context-tree/facts/project/rule_1.md deleted file mode 100644 index 4e0fec71..00000000 --- a/.brv/context-tree/facts/project/rule_1.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Rule 1 -summary: Facts about Rule 1 -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T19:12:30.341Z' -updatedAt: '2026-05-27T19:12:30.341Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-27T19:12:30.246Z - -## Narrative -### Structure -Extracted facts for Rule 1 - -## Facts -- **Rule 1**: Routing rule 1: No argument renders the table above as the user‑facing command menu, grouped by category. diff --git a/.brv/context-tree/facts/project/rule_2.md b/.brv/context-tree/facts/project/rule_2.md deleted file mode 100644 index 6c50e82c..00000000 --- a/.brv/context-tree/facts/project/rule_2.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Rule 2 -summary: Facts about Rule 2 -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T19:12:30.342Z' -updatedAt: '2026-05-27T19:12:30.342Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-27T19:12:30.246Z - -## Narrative -### Structure -Extracted facts for Rule 2 - -## Facts -- **Rule 2**: Routing rule 2: First word matches a command loads its reference file and follows its instructions. diff --git a/.brv/context-tree/facts/project/rule_3.md b/.brv/context-tree/facts/project/rule_3.md deleted file mode 100644 index 182d0ea9..00000000 --- a/.brv/context-tree/facts/project/rule_3.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Rule 3 -summary: Facts about Rule 3 -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T19:12:30.347Z' -updatedAt: '2026-05-27T19:12:30.347Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-27T19:12:30.246Z - -## Narrative -### Structure -Extracted facts for Rule 3 - -## Facts -- **Rule 3**: Routing rule 3: First word doesn't match triggers general design invocation. diff --git a/.brv/context-tree/facts/project/same_repo_pr_secrets.md b/.brv/context-tree/facts/project/same_repo_pr_secrets.md deleted file mode 100644 index 2e34dd96..00000000 --- a/.brv/context-tree/facts/project/same_repo_pr_secrets.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Same-repo PR secrets -summary: Facts about same-repo PR secrets -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T10:41:19.183Z' -updatedAt: '2026-05-28T10:41:19.183Z' ---- -## Reason -Curated factual statements extracted from context - -## Raw Concept -**Task:** -Curate facts - -**Timestamp:** 2026-05-28T10:41:19.156Z - -## Narrative -### Highlights -Extracted 1 facts for same-repo PR secrets - -## Facts -- **same-repo PR secrets**: For same-repo branches, secrets should be available and the workflow should run. diff --git a/.brv/context-tree/facts/project/scan_result.md b/.brv/context-tree/facts/project/scan_result.md deleted file mode 100644 index 4c2c2c65..00000000 --- a/.brv/context-tree/facts/project/scan_result.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Scan result -summary: Facts about scan result -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T09:45:49.164Z' -updatedAt: '2026-05-28T09:45:49.164Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-28T09:45:49.131Z - -## Narrative -### Highlights -Extracted 1 facts for scan result - -## Facts -- **scan result**: No CLI findings. diff --git a/.brv/context-tree/facts/project/screenshots.md b/.brv/context-tree/facts/project/screenshots.md deleted file mode 100644 index a70f21ed..00000000 --- a/.brv/context-tree/facts/project/screenshots.md +++ /dev/null @@ -1,27 +0,0 @@ ---- -title: Screenshots -summary: Facts about screenshots -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T19:18:57.219Z' -updatedAt: '2026-05-27T19:18:57.219Z' ---- -## Reason -Curated facts extracted from context - -## Raw Concept -**Task:** -Document project facts - -**Timestamp:** 2026-05-27T19:18:57.119Z - -## Narrative -### Structure -Collected factual statements - -### Highlights -Contains 1 facts - -## Facts -- **screenshots**: Screenshots: caplets-animated-landing-top.png, caplets-animated-landing-reveals.png diff --git a/.brv/context-tree/facts/project/script.md b/.brv/context-tree/facts/project/script.md deleted file mode 100644 index 0563d069..00000000 --- a/.brv/context-tree/facts/project/script.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Script -summary: Facts about script -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T18:47:50.778Z' -updatedAt: '2026-05-27T18:47:50.778Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-27T18:47:50.505Z - -## Narrative -### Highlights -Extracted 1 facts for script - -## Facts -- **script**: Report the script's result concisely. diff --git a/.brv/context-tree/facts/project/script_output.md b/.brv/context-tree/facts/project/script_output.md deleted file mode 100644 index 2dc388fd..00000000 --- a/.brv/context-tree/facts/project/script_output.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Script output -summary: Facts about script output -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T11:14:20.313Z' -updatedAt: '2026-05-27T11:14:20.313Z' ---- -## Reason -Curated factual statements extracted from context - -## Raw Concept -**Task:** -Document factual statements - -**Timestamp:** 2026-05-27T11:14:20.303Z - -## Narrative -### Structure -Collection of facts regarding script output - -## Facts -- **script output**: The script printed an empty list of findings and reported "findings 0 packages with vulns". diff --git a/.brv/context-tree/facts/project/scripts_alchemy_fetch_compat_mjs.md b/.brv/context-tree/facts/project/scripts_alchemy_fetch_compat_mjs.md deleted file mode 100644 index a9aedce3..00000000 --- a/.brv/context-tree/facts/project/scripts_alchemy_fetch_compat_mjs.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -title: Scripts/alchemy-fetch-compat.mjs -summary: Facts about scripts/alchemy-fetch-compat.mjs -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T11:56:15.167Z' -updatedAt: '2026-05-28T11:56:15.167Z' ---- -## Reason -Curate extracted facts from source context - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-28T11:56:15.127Z - -## Facts -- **scripts/alchemy-fetch-compat.mjs**: Existing fetch shim remains in `scripts/alchemy-fetch-compat.mjs`. diff --git a/.brv/context-tree/facts/project/scripts_alchemy_runner_mjs.md b/.brv/context-tree/facts/project/scripts_alchemy_runner_mjs.md deleted file mode 100644 index 3a15aecd..00000000 --- a/.brv/context-tree/facts/project/scripts_alchemy_runner_mjs.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -title: Scripts/alchemy-runner.mjs -summary: Facts about scripts/alchemy-runner.mjs -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T11:56:15.141Z' -updatedAt: '2026-05-28T11:56:15.141Z' ---- -## Reason -Curate extracted facts from source context - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-28T11:56:15.126Z - -## Facts -- **scripts/alchemy-runner.mjs**: `scripts/alchemy-runner.mjs` wraps Alchemy and sets `NODE_OPTIONS=--import=`. diff --git a/.brv/context-tree/facts/project/scripts_alchemy_runner_test_mjs.md b/.brv/context-tree/facts/project/scripts_alchemy_runner_test_mjs.md deleted file mode 100644 index 469990b8..00000000 --- a/.brv/context-tree/facts/project/scripts_alchemy_runner_test_mjs.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -title: Scripts/alchemy-runner.test.mjs -summary: Facts about scripts/alchemy-runner.test.mjs -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T11:56:15.164Z' -updatedAt: '2026-05-28T11:56:15.164Z' ---- -## Reason -Curate extracted facts from source context - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-28T11:56:15.127Z - -## Facts -- **scripts/alchemy-runner.test.mjs**: `scripts/alchemy-runner.test.mjs` verifies the shim is injected through `NODE_OPTIONS`. diff --git a/.brv/context-tree/facts/project/section_styling.md b/.brv/context-tree/facts/project/section_styling.md deleted file mode 100644 index 31a92af8..00000000 --- a/.brv/context-tree/facts/project/section_styling.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Section styling -summary: Facts about section styling -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T13:55:17.288Z' -updatedAt: '2026-05-28T13:55:17.288Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Document extracted factual statements - -**Timestamp:** 2026-05-28T13:55:17.267Z - -## Narrative -### Highlights -Extracted 1 facts for section styling - -## Facts -- **section styling**: A calmer .section-note styling was added for problem, proof, trust, and integrations sections. diff --git a/.brv/context-tree/facts/project/setup_behavior.md b/.brv/context-tree/facts/project/setup_behavior.md deleted file mode 100644 index 9c1bab76..00000000 --- a/.brv/context-tree/facts/project/setup_behavior.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Setup behavior -summary: Facts about Setup behavior -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T19:12:30.349Z' -updatedAt: '2026-05-27T19:12:30.349Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-27T19:12:30.246Z - -## Narrative -### Structure -Extracted facts for Setup behavior - -## Facts -- **Setup behavior**: Setup (context gathering, register) is already loaded by then; sub‑commands don't re‑invoke `{{command_prefix}}impeccable`. diff --git a/.brv/context-tree/facts/project/shape.md b/.brv/context-tree/facts/project/shape.md deleted file mode 100644 index 8d6d9496..00000000 --- a/.brv/context-tree/facts/project/shape.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Shape -summary: Facts about shape -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T18:47:50.657Z' -updatedAt: '2026-05-27T18:47:50.657Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-27T18:47:50.505Z - -## Narrative -### Highlights -Extracted 1 facts for shape - -## Facts -- **shape**: `shape [feature]` is a Build command that plans UX/UI before writing code. diff --git a/.brv/context-tree/facts/project/shape_feature_facts.md b/.brv/context-tree/facts/project/shape_feature_facts.md deleted file mode 100644 index e46bc121..00000000 --- a/.brv/context-tree/facts/project/shape_feature_facts.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: '`shape [feature]` facts' -summary: Facts about `shape [feature]` -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T19:11:54.711Z' -updatedAt: '2026-05-27T19:11:54.711Z' ---- -## Reason -Curated facts from RLM extraction - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-27T19:11:54.664Z - -## Narrative -### Structure -Collected 1 facts for `shape [feature]` - -## Facts -- **`shape [feature]`**: The command `shape [feature]` belongs to the Build category and plans UX/UI before writing code. diff --git a/.brv/context-tree/facts/project/shared_design_laws.md b/.brv/context-tree/facts/project/shared_design_laws.md deleted file mode 100644 index d17b0dee..00000000 --- a/.brv/context-tree/facts/project/shared_design_laws.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Shared design laws -summary: Facts about shared design laws -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T19:12:30.291Z' -updatedAt: '2026-05-27T19:12:30.291Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-27T19:12:30.246Z - -## Narrative -### Structure -Extracted facts for shared design laws - -## Facts -- **shared design laws**: Shared design laws apply to both brand and product registers. diff --git a/.brv/context-tree/facts/project/shortcut.md b/.brv/context-tree/facts/project/shortcut.md deleted file mode 100644 index ade25efc..00000000 --- a/.brv/context-tree/facts/project/shortcut.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Shortcut -summary: Facts about shortcut -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T18:47:50.780Z' -updatedAt: '2026-05-27T18:47:50.780Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-27T18:47:50.505Z - -## Narrative -### Highlights -Extracted 1 facts for shortcut - -## Facts -- **shortcut**: Confirm the new shortcut on success, relay stderr verbatim on error. diff --git a/.brv/context-tree/facts/project/side_stripe_borders.md b/.brv/context-tree/facts/project/side_stripe_borders.md deleted file mode 100644 index b47e83ac..00000000 --- a/.brv/context-tree/facts/project/side_stripe_borders.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Side‑stripe borders -summary: Facts about side‑stripe borders -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T19:12:30.303Z' -updatedAt: '2026-05-27T19:12:30.303Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-27T19:12:30.246Z - -## Narrative -### Structure -Extracted facts for side‑stripe borders - -## Facts -- **side‑stripe borders**: Side‑stripe borders greater than 1 px as colored accents are prohibited. diff --git a/.brv/context-tree/facts/project/skill.md b/.brv/context-tree/facts/project/skill.md deleted file mode 100644 index 53a12f66..00000000 --- a/.brv/context-tree/facts/project/skill.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Skill -summary: Facts about skill -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T09:45:49.133Z' -updatedAt: '2026-05-28T09:45:49.133Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-28T09:45:49.131Z - -## Narrative -### Highlights -Extracted 1 facts for skill - -## Facts -- **skill**: The skill is named "critique" and is located at "/home/ianpascoe/.agents/skills/critique/SKILL.md". diff --git a/.brv/context-tree/facts/project/skillify_facts.md b/.brv/context-tree/facts/project/skillify_facts.md deleted file mode 100644 index 2db27c4d..00000000 --- a/.brv/context-tree/facts/project/skillify_facts.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Skillify facts -summary: Facts about skillify -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T09:55:30.683Z' -updatedAt: '2026-05-28T09:55:30.683Z' ---- -## Reason -Curated factual statements extracted from source - -## Raw Concept -**Task:** -Curate factual statements - -**Timestamp:** 2026-05-28T09:55:30.675Z - -## Narrative -### Highlights -Extracted 1 facts for skillify - -## Facts -- **skillify**: Skillify your backends: to skillify a backend is to wrap it as a focused agent capability, discoverable as one card, searchable only when needed, inspectable before calls, and lossless after results return. diff --git a/.brv/context-tree/facts/project/skillify_implementation_facts.md b/.brv/context-tree/facts/project/skillify_implementation_facts.md deleted file mode 100644 index a45dc11d..00000000 --- a/.brv/context-tree/facts/project/skillify_implementation_facts.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Skillify implementation facts -summary: Facts about skillify implementation -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T09:55:30.680Z' -updatedAt: '2026-05-28T09:55:30.680Z' ---- -## Reason -Curated factual statements extracted from source - -## Raw Concept -**Task:** -Curate factual statements - -**Timestamp:** 2026-05-28T09:55:30.675Z - -## Narrative -### Highlights -Extracted 1 facts for skillify implementation - -## Facts -- **skillify implementation**: Treat “skillify” as a durable product concept, but only change the landing page in this pass. diff --git a/.brv/context-tree/facts/project/skillify_scope_facts.md b/.brv/context-tree/facts/project/skillify_scope_facts.md deleted file mode 100644 index 2015d3a2..00000000 --- a/.brv/context-tree/facts/project/skillify_scope_facts.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Skillify scope facts -summary: Facts about skillify scope -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T09:55:30.681Z' -updatedAt: '2026-05-28T09:55:30.681Z' ---- -## Reason -Curated factual statements extracted from source - -## Raw Concept -**Task:** -Curate factual statements - -**Timestamp:** 2026-05-28T09:55:30.675Z - -## Narrative -### Highlights -Extracted 1 facts for skillify scope - -## Facts -- **skillify scope**: “Skillify” should be durable product language, while this fix should only update the landing page for now. diff --git a/.brv/context-tree/facts/project/status_and_results.md b/.brv/context-tree/facts/project/status_and_results.md deleted file mode 100644 index f01f20ba..00000000 --- a/.brv/context-tree/facts/project/status_and_results.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Status and results -summary: Facts about status and results -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T10:26:09.829Z' -updatedAt: '2026-05-28T10:26:09.829Z' ---- -## Reason -Curated facts extracted from source context - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-28T10:26:09.781Z - -## Narrative -### Highlights -Extracted 1 facts for status and results - -## Facts -- **status and results**: Return status and results after running the commands. diff --git a/.brv/context-tree/facts/project/step_1.md b/.brv/context-tree/facts/project/step_1.md deleted file mode 100644 index 09573120..00000000 --- a/.brv/context-tree/facts/project/step_1.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Step 1 -summary: Facts about step 1 -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T09:45:49.135Z' -updatedAt: '2026-05-28T09:45:49.135Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-28T09:45:49.131Z - -## Narrative -### Highlights -Extracted 1 facts for step 1 - -## Facts -- **step 1**: Step 1 requires invoking "/impeccable" and, if no design context exists, running "/impeccable teach" first. diff --git a/.brv/context-tree/facts/project/step_2.md b/.brv/context-tree/facts/project/step_2.md deleted file mode 100644 index b1c1acef..00000000 --- a/.brv/context-tree/facts/project/step_2.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Step 2 -summary: Facts about step 2 -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T09:45:49.141Z' -updatedAt: '2026-05-28T09:45:49.141Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-28T09:45:49.131Z - -## Narrative -### Highlights -Extracted 1 facts for step 2 - -## Facts -- **step 2**: Step 2 mandates launching two independent assessments (Assessment A and Assessment B) without sharing outputs between them. diff --git a/.brv/context-tree/facts/project/step_3.md b/.brv/context-tree/facts/project/step_3.md deleted file mode 100644 index 25c94377..00000000 --- a/.brv/context-tree/facts/project/step_3.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Step 3 -summary: Facts about step 3 -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T09:45:49.159Z' -updatedAt: '2026-05-28T09:45:49.159Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-28T09:45:49.131Z - -## Narrative -### Highlights -Extracted 1 facts for step 3 - -## Facts -- **step 3**: Step 3 requires synthesizing both assessments into a combined critique report, including a Design Health Score and an Anti‑Patterns Verdict. diff --git a/.brv/context-tree/facts/project/step_name.md b/.brv/context-tree/facts/project/step_name.md deleted file mode 100644 index 37a6e17e..00000000 --- a/.brv/context-tree/facts/project/step_name.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Step name -summary: Facts about step name -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T10:41:19.194Z' -updatedAt: '2026-05-28T10:41:19.194Z' ---- -## Reason -Curated factual statements extracted from context - -## Raw Concept -**Task:** -Curate facts - -**Timestamp:** 2026-05-28T10:41:19.156Z - -## Narrative -### Highlights -Extracted 1 facts for step name - -## Facts -- **step name**: The step name `Run quality gates` is misleading because it deploys via `pnpm run alchemy:deploy`. diff --git a/.brv/context-tree/facts/project/submodule_setup/submodule_setup.md b/.brv/context-tree/facts/project/submodule_setup/submodule_setup.md deleted file mode 100644 index 0ba91ddd..00000000 --- a/.brv/context-tree/facts/project/submodule_setup/submodule_setup.md +++ /dev/null @@ -1,50 +0,0 @@ ---- -title: Submodule Setup -summary: The repository now includes a .brv git submodule pointing to caplets-brv and the change was pushed after verification passed. -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-21T11:30:21.319Z' -updatedAt: '2026-05-21T11:30:21.319Z' ---- -## Reason -Record the completed submodule setup and verification outcome - -## Raw Concept -**Task:** -Document the completed submodule setup and verification - -**Changes:** -- Added .gitmodules -- Added .brv as a git submodule -- Pushed commit 26a6e65 chore: add byterover context submodule -- Verified with pnpm verify, test suite, and build - -**Files:** -- .gitmodules -- .brv - -**Flow:** -submodule added -> verification ran -> commit pushed - -**Timestamp:** 2026-05-21 - -## Narrative -### Structure -This note captures the repository-level submodule configuration and the successful verification outcome. - -### Dependencies -The submodule points to the caplets-brv repository hosted at spiritledsoftware. - -### Highlights -Verification succeeded with 37 test files and 502 tests passing, and the build passed. - -## Facts -- **submodule_status**: Submodule setup is done and pushed [project] -- **submodule_commit**: Commit pushed: 26a6e65 chore: add byterover context submodule [project] -- **brv_submodule**: .brv was added as a git submodule [project] -- **brv_submodule_remote**: .brv points to https://github.com/spiritledsoftware/caplets-brv.git [project] -- **pre_push_verification**: Pre-push hook ran pnpm verify [project] -- **test_files_passed**: 37 test files passed [project] -- **tests_passed**: 502 tests passed [project] -- **build_status**: Build passed [project] diff --git a/.brv/context-tree/facts/project/system.md b/.brv/context-tree/facts/project/system.md deleted file mode 100644 index 616f5a91..00000000 --- a/.brv/context-tree/facts/project/system.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: System -summary: Facts about system -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T09:45:49.176Z' -updatedAt: '2026-05-28T09:45:49.176Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-28T09:45:49.131Z - -## Narrative -### Highlights -Extracted 1 facts for system - -## Facts -- **system**: No false positives. diff --git a/.brv/context-tree/facts/project/system_facts.md b/.brv/context-tree/facts/project/system_facts.md deleted file mode 100644 index 789b1862..00000000 --- a/.brv/context-tree/facts/project/system_facts.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: System facts -summary: Facts about system -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T19:11:54.774Z' -updatedAt: '2026-05-27T19:11:54.774Z' ---- -## Reason -Curated facts from RLM extraction - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-27T19:11:54.664Z - -## Narrative -### Structure -Collected 1 facts for system - -## Facts -- **system**: 0 errors, 0 warnings, 0 hints. diff --git a/.brv/context-tree/facts/project/tab_aria_state.md b/.brv/context-tree/facts/project/tab_aria_state.md deleted file mode 100644 index 6ef6d309..00000000 --- a/.brv/context-tree/facts/project/tab_aria_state.md +++ /dev/null @@ -1,27 +0,0 @@ ---- -title: tab ARIA state -summary: Facts about tab ARIA state -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T13:51:08.353Z' -updatedAt: '2026-05-28T13:51:08.353Z' ---- -## Reason -Curated extracted facts - -## Raw Concept -**Task:** -Document factual statements about tab ARIA state - -**Timestamp:** 2026-05-28T13:51:08.346Z - -## Narrative -### Structure -Collected facts for tab ARIA state - -### Highlights -Kept server-rendered tab ARIA state and relationships. - -## Facts -- **tab ARIA state**: Kept server-rendered tab ARIA state and relationships. diff --git a/.brv/context-tree/facts/project/tab_isolation.md b/.brv/context-tree/facts/project/tab_isolation.md deleted file mode 100644 index ff70b03f..00000000 --- a/.brv/context-tree/facts/project/tab_isolation.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Tab isolation -summary: Facts about tab isolation -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T09:45:49.147Z' -updatedAt: '2026-05-28T09:45:49.147Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-28T09:45:49.131Z - -## Narrative -### Highlights -Extracted 1 facts for tab isolation - -## Facts -- **tab isolation**: Each assessment must create a new browser tab and label it by setting the document title ("[LLM] " or "[Human] ") when browser automation is used. diff --git a/.brv/context-tree/facts/project/tablist.md b/.brv/context-tree/facts/project/tablist.md deleted file mode 100644 index 7abcbe84..00000000 --- a/.brv/context-tree/facts/project/tablist.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: tablist -summary: Facts about tablist -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T13:48:34.340Z' -updatedAt: '2026-05-28T13:48:34.340Z' ---- -## Reason -Curated factual statements from recent context - -## Raw Concept -**Task:** -Document project facts - -**Timestamp:** 2026-05-28T13:48:34.339Z - -## Narrative -### Highlights -Collected 1 facts for tablist - -## Facts -- **tablist**: Server-rendered tablist has role="tablist" at apps/landing/src/pages/index.astro:311 diff --git a/.brv/context-tree/facts/project/tabs.md b/.brv/context-tree/facts/project/tabs.md deleted file mode 100644 index 5e73250e..00000000 --- a/.brv/context-tree/facts/project/tabs.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: tabs -summary: Facts about tabs -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T13:48:34.342Z' -updatedAt: '2026-05-28T13:48:34.342Z' ---- -## Reason -Curated factual statements from recent context - -## Raw Concept -**Task:** -Document project facts - -**Timestamp:** 2026-05-28T13:48:34.339Z - -## Narrative -### Highlights -Collected 1 facts for tabs - -## Facts -- **tabs**: Tabs render server-side with role="tab", first tab aria-selected="true" and tabindex=0, inactive tabs aria-selected="false" and tabindex=-1, plus aria-controls pointing to matching panels at lines 313-321 diff --git a/.brv/context-tree/facts/project/task_3_facts.md b/.brv/context-tree/facts/project/task_3_facts.md deleted file mode 100644 index e432aaea..00000000 --- a/.brv/context-tree/facts/project/task_3_facts.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Task 3 facts -summary: Facts about Task 3 -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T10:14:23.975Z' -updatedAt: '2026-05-28T10:14:23.975Z' ---- -## Reason -Curated factual statements from source - -## Raw Concept -**Task:** -Curate factual statements - -**Timestamp:** 2026-05-28T10:14:23.972Z - -## Narrative -### Highlights -Collected 1 facts for Task 3 - -## Facts -- **Task 3**: Implemented Task 3: replaced the proof section with the Skillify proof framework using `skillifyFramework`, with text-led numbered proof items and no icons/decorative card slop. diff --git a/.brv/context-tree/facts/project/task_5.md b/.brv/context-tree/facts/project/task_5.md deleted file mode 100644 index f2c2d83e..00000000 --- a/.brv/context-tree/facts/project/task_5.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Task 5 -summary: Facts about Task 5 -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T10:26:09.793Z' -updatedAt: '2026-05-28T10:26:09.793Z' ---- -## Reason -Curated facts extracted from source context - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-28T10:26:09.781Z - -## Narrative -### Highlights -Extracted 1 facts for Task 5 - -## Facts -- **Task 5**: Implement Task 5: copy buttons for integration install/config snippets and terminal steps, script, CSS. diff --git a/.brv/context-tree/facts/project/task_6.md b/.brv/context-tree/facts/project/task_6.md deleted file mode 100644 index 0786aafe..00000000 --- a/.brv/context-tree/facts/project/task_6.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Task 6 -summary: Facts about Task 6 -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T10:26:09.812Z' -updatedAt: '2026-05-28T10:26:09.812Z' ---- -## Reason -Curated facts extracted from source context - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-28T10:26:09.781Z - -## Narrative -### Highlights -Extracted 1 facts for Task 6 - -## Facts -- **Task 6**: Do not implement Task 6 except if needed for script placement. diff --git a/.brv/context-tree/facts/project/task_6_spec_review_after_envelope_fix.md b/.brv/context-tree/facts/project/task_6_spec_review_after_envelope_fix.md deleted file mode 100644 index 1687439a..00000000 --- a/.brv/context-tree/facts/project/task_6_spec_review_after_envelope_fix.md +++ /dev/null @@ -1,63 +0,0 @@ ---- -title: Task 6 Spec Review After Envelope Fix -summary: 'Task 6 review approved: CLI and dispatch align on { caplet, request }, config path handling remains local-only, and tests cover remote control behavior.' -tags: [] -related: [architecture/remote_control/remote_control_api_shape.md, architecture/remote_control/task_1_spec_compliance_review.md, architecture/remote_control/cli_remote_mode_selection.md, facts/conventions/task_3_spec_review_fix.md, facts/conventions/task_5_regression_test_update.md, facts/conventions/task_6_review_outcome.md, facts/conventions/task_7_review_outcome.md, facts/conventions/task_7_remote_mutation_routing_review.md, facts/conventions/task_9_remote_config_test_fix.md, facts/project/remote_control_review_outcome.md, facts/project/pr_65_fixes_and_verification.md, facts/project/greptile_review_comments.md, facts/project/pr_71_completion_fix_outcome.md] -keywords: [] -createdAt: '2026-05-20T14:11:18.102Z' -updatedAt: '2026-05-20T14:11:18.102Z' ---- - -## Reason - -Capture durable verification of CLI and dispatch alignment after envelope fix commit f70d578 - -## Raw Concept - -**Task:** -Document the approved Task 6 spec review after the envelope fix commit f70d578 - -**Changes:** -- Verified CLI and dispatch alignment on nested { caplet, request } envelopes -- Confirmed config path and config paths remain local-only -- Confirmed test coverage for CLI remote list/call-tool and dispatch nested envelope handling - -**Files:** -- packages/core/src/cli.ts -- packages/core/src/remote-control/dispatch.ts -- packages/core/test/cli-remote.test.ts -- packages/core/test/remote-control-dispatch.test.ts - -**Flow:** -CLI builds { caplet, request } envelope -> dispatch passes request to engine.execute -> tests verify local-only config paths and remote-control handling - -**Timestamp:** 2026-05-20T14:11:05.386Z - -**Author:** assistant - -## Narrative - -### Structure - -This note records the approved review result, the envelope contract, local-only config rules, and the test files that validate the behavior. - -### Dependencies - -Depends on the envelope fix in commit f70d578 and the associated CLI, dispatch, and test updates. - -### Highlights - -The review returned APPROVED and the verification run passed with 394 tests. - -### Examples - -Covered tests include CLI remote list and call-tool envelope behavior, local-only config path handling, and dispatch nested envelope handling for get_caplet and search_tools. - -## Facts - -- **task_6_review_status**: Task 6 spec review after envelope fix commit f70d578 was approved. [project] -- **cli_envelope_shape**: CLI sends direct operation envelope as { caplet, request }. [project] -- **dispatch_envelope_shape**: Dispatch consumes the same nested envelope and passes request to engine.execute. [project] -- **config_path_scope**: config path remains local-only. [project] -- **config_paths_scope**: config paths remains local-only. [project] -- **verification_run**: pnpm --filter @caplets/core test -- test/cli-remote.test.ts test/remote-control-dispatch.test.ts passed with 394 tests. [project] diff --git a/.brv/context-tree/facts/project/task_outcome.md b/.brv/context-tree/facts/project/task_outcome.md deleted file mode 100644 index 45a6f939..00000000 --- a/.brv/context-tree/facts/project/task_outcome.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Task outcome -summary: Facts about task outcome -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T13:55:17.285Z' -updatedAt: '2026-05-28T13:55:17.285Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Document extracted factual statements - -**Timestamp:** 2026-05-28T13:55:17.267Z - -## Narrative -### Highlights -Extracted 1 facts for task outcome - -## Facts -- **task outcome**: The assistant returned status DONE. diff --git a/.brv/context-tree/facts/project/tasks_1_4.md b/.brv/context-tree/facts/project/tasks_1_4.md deleted file mode 100644 index 3d25f948..00000000 --- a/.brv/context-tree/facts/project/tasks_1_4.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -title: Tasks 1-4 -summary: Facts about tasks 1-4 -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T10:20:18.228Z' -updatedAt: '2026-05-28T10:20:18.228Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Document facts about tasks 1-4 - -**Timestamp:** 2026-05-28T10:20:18.222Z - -## Facts -- **tasks 1-4**: Tasks 1-4 are mostly implemented: `heroTrace`, `skillifyFramework`, and `trustMechanics` exist in `apps/landing/src/pages/index.astro`. diff --git a/.brv/context-tree/facts/project/teach.md b/.brv/context-tree/facts/project/teach.md deleted file mode 100644 index d2d95169..00000000 --- a/.brv/context-tree/facts/project/teach.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Teach -summary: Facts about teach -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T18:47:50.659Z' -updatedAt: '2026-05-27T18:47:50.659Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-27T18:47:50.505Z - -## Narrative -### Highlights -Extracted 1 facts for teach - -## Facts -- **teach**: `teach` is a Build command that sets up PRODUCT.md and DESIGN.md context. diff --git a/.brv/context-tree/facts/project/teach_facts.md b/.brv/context-tree/facts/project/teach_facts.md deleted file mode 100644 index 7d6bb28d..00000000 --- a/.brv/context-tree/facts/project/teach_facts.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: '`teach` facts' -summary: Facts about `teach` -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T19:11:54.713Z' -updatedAt: '2026-05-27T19:11:54.713Z' ---- -## Reason -Curated facts from RLM extraction - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-27T19:11:54.664Z - -## Narrative -### Structure -Collected 1 facts for `teach` - -## Facts -- **`teach`**: The command `teach` belongs to the Build category and sets up PRODUCT.md and DESIGN.md context. diff --git a/.brv/context-tree/facts/project/test_results.md b/.brv/context-tree/facts/project/test_results.md deleted file mode 100644 index ef4fa15c..00000000 --- a/.brv/context-tree/facts/project/test_results.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -title: Test results -summary: Facts about test results -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T11:56:15.186Z' -updatedAt: '2026-05-28T11:56:15.186Z' ---- -## Reason -Curate extracted facts from source context - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-28T11:56:15.127Z - -## Facts -- **test results**: `pnpm test` runs 622 Vitest tests plus 3 Node tests, all passed. diff --git a/.brv/context-tree/facts/project/test_suite.md b/.brv/context-tree/facts/project/test_suite.md deleted file mode 100644 index a903f7a3..00000000 --- a/.brv/context-tree/facts/project/test_suite.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: test suite -summary: Project facts about test suite -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T14:20:38.719Z' -updatedAt: '2026-05-27T14:20:38.719Z' ---- -## Reason -Curate extracted project facts - -## Raw Concept -**Task:** -Document project facts - -**Timestamp:** 2026-05-27T14:20:38.716Z - -## Narrative -### Highlights -Extracted 1 facts for test suite - -## Facts -- **test suite**: Core/opencode tests pass when running `pnpm --filter @caplets/core test -- test/result-content.test.ts test/tools.test.ts test/downstream.test.ts` and `pnpm --filter @caplets/opencode test -- test/opencode.test.ts`. diff --git a/.brv/context-tree/facts/project/test_suite_facts.md b/.brv/context-tree/facts/project/test_suite_facts.md deleted file mode 100644 index 4e673741..00000000 --- a/.brv/context-tree/facts/project/test_suite_facts.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: test suite Facts -summary: Facts about test suite -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T12:22:30.070Z' -updatedAt: '2026-05-27T12:22:30.070Z' ---- -## Reason -Curated factual statements from RLM extraction - -## Raw Concept -**Task:** -Document facts for test suite - -**Timestamp:** 2026-05-27T12:22:30.013Z - -## Narrative -### Highlights -All existing tests (total 50) pass. - -## Facts -- **test suite**: All existing tests (total 50) pass. diff --git a/.brv/context-tree/facts/project/text_styling_facts.md b/.brv/context-tree/facts/project/text_styling_facts.md deleted file mode 100644 index e20039f5..00000000 --- a/.brv/context-tree/facts/project/text_styling_facts.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Text styling facts -summary: Facts about text styling -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T19:11:54.679Z' -updatedAt: '2026-05-27T19:11:54.679Z' ---- -## Reason -Curated facts from RLM extraction - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-27T19:11:54.663Z - -## Narrative -### Structure -Collected 1 facts for text styling - -## Facts -- **text styling**: Gradient text using background-clip: text combined with a gradient background is prohibited. diff --git a/.brv/context-tree/facts/project/theme_selection.md b/.brv/context-tree/facts/project/theme_selection.md deleted file mode 100644 index ef0796be..00000000 --- a/.brv/context-tree/facts/project/theme_selection.md +++ /dev/null @@ -1,27 +0,0 @@ ---- -title: Theme selection -summary: Facts about theme selection -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T19:12:30.296Z' -updatedAt: '2026-05-29T09:19:16.185Z' ---- -## Reason -Curated factual statements from context extraction - -## Raw Concept -**Task:** -Document factual statements - -**Timestamp:** 2026-05-29T09:19:16.120Z - -## Narrative -### Structure -Collected factual statements - -### Highlights -Extracted 1 facts for theme selection - -## Facts -- **theme selection**: Dark vs. light theme is never a default; choose based on a concrete physical scene description. diff --git a/.brv/context-tree/facts/project/theming.md b/.brv/context-tree/facts/project/theming.md deleted file mode 100644 index 58cfcde0..00000000 --- a/.brv/context-tree/facts/project/theming.md +++ /dev/null @@ -1,21 +0,0 @@ ---- -title: Theming -summary: Facts about theming -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T11:17:59.541Z' -updatedAt: '2026-05-28T11:17:59.541Z' ---- -## Reason -Curated facts extracted from context - -## Raw Concept -**Task:** -Document facts about theming - -**Timestamp:** 2026-05-28T11:17:59.530Z - -## Facts -- **theming**: Theming score is 4/4. -- **theming**: Uses Caplets warm light palette, restrained ember accents, mono for machine-facing text, and aligns with PRODUCT/DESIGN direction. diff --git a/.brv/context-tree/facts/project/tool_noise_mask.md b/.brv/context-tree/facts/project/tool_noise_mask.md deleted file mode 100644 index 2a8f7786..00000000 --- a/.brv/context-tree/facts/project/tool_noise_mask.md +++ /dev/null @@ -1,22 +0,0 @@ ---- -title: Tool-noise mask -summary: Facts about Tool-noise mask -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T13:58:26.042Z' -updatedAt: '2026-05-28T13:58:26.042Z' ---- -## Reason -Curated facts extraction - -## Raw Concept -**Task:** -Document facts for Tool-noise mask - -## Narrative -### Highlights -Extracted 1 facts for Tool-noise mask - -## Facts -- **Tool-noise mask**: Tool-noise mask no longer uses literal black. diff --git a/.brv/context-tree/facts/project/tooling.md b/.brv/context-tree/facts/project/tooling.md deleted file mode 100644 index 8eb0b16f..00000000 --- a/.brv/context-tree/facts/project/tooling.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Tooling -summary: Facts about tooling -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T13:55:17.270Z' -updatedAt: '2026-05-28T13:55:17.270Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Document extracted factual statements - -**Timestamp:** 2026-05-28T13:55:17.266Z - -## Narrative -### Highlights -Extracted 1 facts for tooling - -## Facts -- **tooling**: The task must use pnpm only. diff --git a/.brv/context-tree/facts/project/tools_test_ts_facts.md b/.brv/context-tree/facts/project/tools_test_ts_facts.md deleted file mode 100644 index 608a8dc8..00000000 --- a/.brv/context-tree/facts/project/tools_test_ts_facts.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: tools.test.ts Facts -summary: Facts about tools.test.ts -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T12:22:30.067Z' -updatedAt: '2026-05-27T12:22:30.067Z' ---- -## Reason -Curated factual statements from RLM extraction - -## Raw Concept -**Task:** -Document facts for tools.test.ts - -**Timestamp:** 2026-05-27T12:22:30.013Z - -## Narrative -### Highlights -Added tools.test.ts with 6 tests verifying jsonResult Markdown output, metadata inclusion, complex content handling, and errorResult behavior. - -## Facts -- **tools.test.ts**: Added tools.test.ts with 6 tests verifying jsonResult Markdown output, metadata inclusion, complex content handling, and errorResult behavior. diff --git a/.brv/context-tree/facts/project/top_nav_a_header_action.md b/.brv/context-tree/facts/project/top_nav_a_header_action.md deleted file mode 100644 index 373c8a26..00000000 --- a/.brv/context-tree/facts/project/top_nav_a_header_action.md +++ /dev/null @@ -1,27 +0,0 @@ ---- -title: .top-nav a, .header-action -summary: Facts about .top-nav a, .header-action -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T13:40:49.014Z' -updatedAt: '2026-05-28T13:40:49.014Z' ---- -## Reason -Curate extracted facts from context - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-28T13:40:49.005Z - -## Narrative -### Structure -List of factual statements extracted - -### Highlights -Contains 1 facts - -## Facts -- **.top-nav a, .header-action**: `.top-nav a` and `.header-action` retain 44px minimum height with reduced padding and font size for fit. diff --git a/.brv/context-tree/facts/project/touch_target_size.md b/.brv/context-tree/facts/project/touch_target_size.md deleted file mode 100644 index 0f5c14a7..00000000 --- a/.brv/context-tree/facts/project/touch_target_size.md +++ /dev/null @@ -1,22 +0,0 @@ ---- -title: Touch target size -summary: Facts about Touch target size -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T13:58:26.019Z' -updatedAt: '2026-05-28T13:58:26.019Z' ---- -## Reason -Curated facts extraction - -## Raw Concept -**Task:** -Document facts for Touch target size - -## Narrative -### Highlights -Extracted 1 facts for Touch target size - -## Facts -- **Touch target size**: 44px touch targets are present for mobile nav, footer links, copy buttons, and integration pills. diff --git a/.brv/context-tree/facts/project/trace.md b/.brv/context-tree/facts/project/trace.md deleted file mode 100644 index e7df9364..00000000 --- a/.brv/context-tree/facts/project/trace.md +++ /dev/null @@ -1,18 +0,0 @@ ---- -title: Trace -summary: Facts about trace -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T09:52:02.640Z' -updatedAt: '2026-05-28T09:52:02.640Z' ---- -## Reason -Curated factual statements extracted from context - -## Raw Concept -**Task:** -Document facts about trace - -## Facts -- **trace**: The example trace includes caplet: github, source: .caplets/config.json, status: ready, auth: token present, redacted. diff --git a/.brv/context-tree/facts/project/typecheck.md b/.brv/context-tree/facts/project/typecheck.md deleted file mode 100644 index b51414b0..00000000 --- a/.brv/context-tree/facts/project/typecheck.md +++ /dev/null @@ -1,27 +0,0 @@ ---- -title: Typecheck -summary: Facts about typecheck -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T18:59:51.354Z' -updatedAt: '2026-05-29T09:19:16.220Z' ---- -## Reason -Curated factual statements from context extraction - -## Raw Concept -**Task:** -Document factual statements - -**Timestamp:** 2026-05-29T09:19:16.120Z - -## Narrative -### Structure -Collected facts for typecheck - -### Highlights -Extracted 1 facts for typecheck - -## Facts -- **typecheck**: Typecheck for @caplets/landing completed with 0 errors, 0 warnings, and 0 hints. diff --git a/.brv/context-tree/facts/project/typecheck_facts.md b/.brv/context-tree/facts/project/typecheck_facts.md deleted file mode 100644 index 6784f9f0..00000000 --- a/.brv/context-tree/facts/project/typecheck_facts.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Typecheck facts -summary: Facts about typecheck -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T10:14:23.987Z' -updatedAt: '2026-05-28T10:14:23.987Z' ---- -## Reason -Curated factual statements from source - -## Raw Concept -**Task:** -Curate factual statements - -**Timestamp:** 2026-05-28T10:14:23.972Z - -## Narrative -### Highlights -Collected 1 facts for typecheck - -## Facts -- **typecheck**: `pnpm --filter @caplets/landing typecheck` PASS, with existing unused `trustMechanics` hint because Task 4 was not implemented. diff --git a/.brv/context-tree/facts/project/typeset.md b/.brv/context-tree/facts/project/typeset.md deleted file mode 100644 index 46ed431d..00000000 --- a/.brv/context-tree/facts/project/typeset.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Typeset -summary: Facts about typeset -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T18:47:50.717Z' -updatedAt: '2026-05-27T18:47:50.717Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-27T18:47:50.505Z - -## Narrative -### Highlights -Extracted 1 facts for typeset - -## Facts -- **typeset**: `typeset [target]` is an Enhance command that improves typography hierarchy and fonts. diff --git a/.brv/context-tree/facts/project/typeset_target_facts.md b/.brv/context-tree/facts/project/typeset_target_facts.md deleted file mode 100644 index 5200e108..00000000 --- a/.brv/context-tree/facts/project/typeset_target_facts.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: '`typeset [target]` facts' -summary: Facts about `typeset [target]` -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T19:11:54.741Z' -updatedAt: '2026-05-27T19:11:54.741Z' ---- -## Reason -Curated facts from RLM extraction - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-27T19:11:54.664Z - -## Narrative -### Structure -Collected 1 facts for `typeset [target]` - -## Facts -- **`typeset [target]`**: The command `typeset [target]` belongs to the Enhance category and improves typography hierarchy and fonts. diff --git a/.brv/context-tree/facts/project/typography.md b/.brv/context-tree/facts/project/typography.md deleted file mode 100644 index 2745f16b..00000000 --- a/.brv/context-tree/facts/project/typography.md +++ /dev/null @@ -1,28 +0,0 @@ ---- -title: Typography -summary: Facts about typography -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T19:18:57.151Z' -updatedAt: '2026-05-27T19:18:57.151Z' ---- -## Reason -Curated facts extracted from context - -## Raw Concept -**Task:** -Document project facts - -**Timestamp:** 2026-05-27T19:18:57.119Z - -## Narrative -### Structure -Collected factual statements - -### Highlights -Contains 2 facts - -## Facts -- **typography**: Cap body line length at 65–75 characters. -- **typography**: Hierarchy should have a scale and weight contrast ratio of at least 1.25 between steps. diff --git a/.brv/context-tree/facts/project/ui_components.md b/.brv/context-tree/facts/project/ui_components.md deleted file mode 100644 index 0d4de943..00000000 --- a/.brv/context-tree/facts/project/ui_components.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: UI components -summary: Facts about UI components -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T18:59:51.349Z' -updatedAt: '2026-05-27T18:59:51.349Z' ---- -## Reason -Curate extracted project facts - -## Raw Concept -**Task:** -Document project fact - -**Timestamp:** 2026-05-27T18:59:51.340Z - -## Narrative -### Highlights -Extracted 1 facts for UI components - -## Facts -- **UI components**: Updated the selected-route panel, integrations band, integration pills, and install terminal to use brighter text, clearer borders, and stronger dark-surface separation. diff --git a/.brv/context-tree/facts/project/uncategorized.md b/.brv/context-tree/facts/project/uncategorized.md deleted file mode 100644 index ed64ded2..00000000 --- a/.brv/context-tree/facts/project/uncategorized.md +++ /dev/null @@ -1,25 +0,0 @@ ---- -title: uncategorized -summary: Facts about uncategorized -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T16:59:16.799Z' -updatedAt: '2026-05-28T16:59:16.799Z' ---- -## Reason -Curated facts extracted from context - -## Raw Concept -**Task:** -Document facts for uncategorized - -**Timestamp:** 2026-05-28T16:59:16.794Z - -## Facts -- pnpm test scripts/commit-byterover-context.test.ts passed with 5 tests -- pnpm format:check passed -- pnpm lint passed -- pnpm typecheck passed -- Post-commit behavior now commits .brv/ changes as docs(agents): byterover context -- Pre-push behavior now runs pnpm verify and then warns about uncommitted .brv/ changes without blocking the push diff --git a/.brv/context-tree/facts/project/unpin.md b/.brv/context-tree/facts/project/unpin.md deleted file mode 100644 index b911f6fb..00000000 --- a/.brv/context-tree/facts/project/unpin.md +++ /dev/null @@ -1,27 +0,0 @@ ---- -title: Unpin -summary: Facts about unpin -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T18:47:50.769Z' -updatedAt: '2026-05-27T19:18:57.175Z' ---- -## Reason -Curated facts extracted from context - -## Raw Concept -**Task:** -Document project facts - -**Timestamp:** 2026-05-27T19:18:57.119Z - -## Narrative -### Structure -Collected factual statements - -### Highlights -Contains 1 facts - -## Facts -- **unpin**: `unpin ` removes the shortcut created by the pin command. diff --git a/.brv/context-tree/facts/project/validation.md b/.brv/context-tree/facts/project/validation.md deleted file mode 100644 index 19732318..00000000 --- a/.brv/context-tree/facts/project/validation.md +++ /dev/null @@ -1,27 +0,0 @@ ---- -title: Validation -summary: Facts about validation -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T19:18:57.187Z' -updatedAt: '2026-05-27T19:18:57.187Z' ---- -## Reason -Curated facts extracted from context - -## Raw Concept -**Task:** -Document project facts - -**Timestamp:** 2026-05-27T19:18:57.119Z - -## Narrative -### Structure -Collected factual statements - -### Highlights -Contains 1 facts - -## Facts -- **validation**: errors, 0 warnings, 0 hints. diff --git a/.brv/context-tree/facts/project/verification_steps.md b/.brv/context-tree/facts/project/verification_steps.md deleted file mode 100644 index f2dc2a45..00000000 --- a/.brv/context-tree/facts/project/verification_steps.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -title: Verification steps -summary: Facts about verification steps -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T11:56:15.170Z' -updatedAt: '2026-05-28T11:56:15.170Z' ---- -## Reason -Curate extracted facts from source context - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-28T11:56:15.127Z - -## Facts -- **verification steps**: Verification commands now pass, including `pnpm run alchemy:deploy --help` and all test suites. diff --git a/.brv/context-tree/facts/project/visual_design.md b/.brv/context-tree/facts/project/visual_design.md deleted file mode 100644 index 7adc6a57..00000000 --- a/.brv/context-tree/facts/project/visual_design.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Visual design -summary: Facts about visual design -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T09:45:49.167Z' -updatedAt: '2026-05-28T09:45:49.167Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-28T09:45:49.131Z - -## Narrative -### Highlights -Extracted 1 facts for visual design - -## Facts -- **visual design**: It risks looking like a very polished AI-made landing page for a developer tool because it spends too much surface area on theatrical motion and metaphor. diff --git a/.brv/context-tree/facts/project/visual_effect_facts.md b/.brv/context-tree/facts/project/visual_effect_facts.md deleted file mode 100644 index 22866708..00000000 --- a/.brv/context-tree/facts/project/visual_effect_facts.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Visual effect facts -summary: Facts about visual effect -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T19:11:54.682Z' -updatedAt: '2026-05-27T19:11:54.682Z' ---- -## Reason -Curated facts from RLM extraction - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-27T19:11:54.663Z - -## Narrative -### Structure -Collected 1 facts for visual effect - -## Facts -- **visual effect**: Glassmorphism as default, including blurs and glass cards, is prohibited. diff --git a/.brv/context-tree/facts/project/visual_elements.md b/.brv/context-tree/facts/project/visual_elements.md deleted file mode 100644 index 0f17c65a..00000000 --- a/.brv/context-tree/facts/project/visual_elements.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Visual elements -summary: Facts about Visual elements -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T09:50:25.721Z' -updatedAt: '2026-05-28T13:58:26.046Z' ---- -## Reason -Curated facts extraction - -## Raw Concept -**Task:** -Document facts for Visual elements - -**Timestamp:** 2026-05-28T09:50:25.672Z - -## Narrative -### Highlights -Extracted 1 facts for Visual elements - -## Facts -- **Visual elements**: No em dashes, side-stripe borders, gradient text, or glassmorphism found in the reviewed files. diff --git a/.brv/context-tree/facts/project/visual_language.md b/.brv/context-tree/facts/project/visual_language.md deleted file mode 100644 index c9d12db4..00000000 --- a/.brv/context-tree/facts/project/visual_language.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Visual language -summary: Facts about visual language -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T09:45:49.172Z' -updatedAt: '2026-05-28T09:45:49.172Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Document extracted facts - -**Timestamp:** 2026-05-28T09:45:49.131Z - -## Narrative -### Highlights -Extracted 1 facts for visual language - -## Facts -- **visual language**: The capability-card metaphor, concrete MCP/client names, tabbed integration snippets, before/after tool-list comparison, and warm map-like visual language all feel more intentional than template output. diff --git a/.brv/context-tree/facts/project/vite.md b/.brv/context-tree/facts/project/vite.md deleted file mode 100644 index e2e6a3eb..00000000 --- a/.brv/context-tree/facts/project/vite.md +++ /dev/null @@ -1,22 +0,0 @@ ---- -title: Vite -summary: Facts about Vite -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T10:53:51.576Z' -updatedAt: '2026-05-28T10:53:51.577Z' ---- -## Reason -Curated facts extracted from source context - -## Raw Concept -**Task:** -Document facts about Vite - -**Timestamp:** 2026-05-28T10:53:51.569Z - -**Author:** ByteRover Context Engineer - -## Facts -- **Vite**: Port 4321 was in use, so Vite tried another port and used 4322 diff --git a/.brv/context-tree/facts/project/workflow_conditions.md b/.brv/context-tree/facts/project/workflow_conditions.md deleted file mode 100644 index 37a5d19f..00000000 --- a/.brv/context-tree/facts/project/workflow_conditions.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Workflow conditions -summary: Facts about workflow conditions -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T10:41:19.176Z' -updatedAt: '2026-05-28T10:41:19.176Z' ---- -## Reason -Curated factual statements extracted from context - -## Raw Concept -**Task:** -Curate facts - -**Timestamp:** 2026-05-28T10:41:19.156Z - -## Narrative -### Highlights -Extracted 1 facts for workflow conditions - -## Facts -- **workflow conditions**: The workflow runs for normal `pull_request` events such as opened, reopened, and updated/synchronized PRs where the PR base branch is `main` and the changed file set includes something matching `apps/**`. diff --git a/.brv/context-tree/facts/project/workflow_reliability.md b/.brv/context-tree/facts/project/workflow_reliability.md deleted file mode 100644 index f55968ff..00000000 --- a/.brv/context-tree/facts/project/workflow_reliability.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Workflow reliability -summary: Facts about workflow reliability -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T10:41:19.196Z' -updatedAt: '2026-05-28T10:41:19.196Z' ---- -## Reason -Curated factual statements extracted from context - -## Raw Concept -**Task:** -Curate facts - -**Timestamp:** 2026-05-28T10:41:19.156Z - -## Narrative -### Highlights -Extracted 1 facts for workflow reliability - -## Facts -- **workflow reliability**: Yes for same-repo PRs to `main` touching `apps/**`; not reliably for fork PR deploys because secrets are unavailable. diff --git a/.brv/context-tree/facts/project/workflow_trigger.md b/.brv/context-tree/facts/project/workflow_trigger.md deleted file mode 100644 index 9b5e8503..00000000 --- a/.brv/context-tree/facts/project/workflow_trigger.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Workflow trigger -summary: Facts about workflow trigger -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T10:41:19.169Z' -updatedAt: '2026-05-28T10:41:19.169Z' ---- -## Reason -Curated factual statements extracted from context - -## Raw Concept -**Task:** -Curate facts - -**Timestamp:** 2026-05-28T10:41:19.156Z - -## Narrative -### Highlights -Extracted 1 facts for workflow trigger - -## Facts -- **workflow trigger**: It will trigger on PRs targeting `main`, but only when the PR changes at least one file under `apps/**`. diff --git a/.brv/context-tree/facts/project/workflows.md b/.brv/context-tree/facts/project/workflows.md deleted file mode 100644 index 4c3b95af..00000000 --- a/.brv/context-tree/facts/project/workflows.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Workflows -summary: Facts about workflows -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T09:30:56.813Z' -updatedAt: '2026-05-28T09:30:56.813Z' ---- -## Reason -Curated factual statements from source context - -## Raw Concept -**Task:** -Document facts about workflows - -**Timestamp:** 2026-05-28T09:30:56.811Z - -## Narrative -### Highlights -User wants the workflows to only run when changes to apps/ occur. - -## Facts -- **workflows**: User wants the workflows to only run when changes to apps/ occur. diff --git a/.brv/context-tree/facts/review_threads/review_threads.md b/.brv/context-tree/facts/review_threads/review_threads.md deleted file mode 100644 index 02669a33..00000000 --- a/.brv/context-tree/facts/review_threads/review_threads.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -title: Review threads -summary: Facts about review threads -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T16:21:51.983Z' -updatedAt: '2026-05-28T16:21:51.983Z' ---- -## Reason -Curated factual statements extracted from PR review context - -## Raw Concept -**Task:** -Document facts for review threads - -**Timestamp:** 2026-05-28T16:21:51.958Z - -## Facts -- **review threads**: Replied to both inline review threads and added a PR summary comment. diff --git a/.brv/context-tree/facts/staged_path_example/staged_path_example.md b/.brv/context-tree/facts/staged_path_example/staged_path_example.md deleted file mode 100644 index 6b824996..00000000 --- a/.brv/context-tree/facts/staged_path_example/staged_path_example.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -title: Staged-path example -summary: Facts about staged-path example -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T16:21:51.967Z' -updatedAt: '2026-05-28T16:21:51.967Z' ---- -## Reason -Curated factual statements extracted from PR review context - -## Raw Concept -**Task:** -Document facts for staged-path example - -**Timestamp:** 2026-05-28T16:21:51.958Z - -## Facts -- **staged-path example**: Changed the staged-path example from `github` to `GitHub`. diff --git a/.brv/context-tree/facts/standardized-curation-workflow-as-core-project-infrastructure.md b/.brv/context-tree/facts/standardized-curation-workflow-as-core-project-infrastructure.md deleted file mode 100644 index a4a690a1..00000000 --- a/.brv/context-tree/facts/standardized-curation-workflow-as-core-project-infrastructure.md +++ /dev/null @@ -1,22 +0,0 @@ ---- -confidence: 0.92 -sources: [facts/_index.md, project/_index.md] -synthesized_at: '2026-05-27T23:38:23.764Z' -type: synthesis -title: Standardized Curation Workflow as Core Project Infrastructure -summary: The project formalizes a recon‑first, UPSERT‑based curation pipeline used across facts and project domains. -tags: [curation, workflow, upssert, recon] -related: [] -keywords: [curation, recon, upssert, extraction, verification, workflow, runtime, infrastructure, knowledge, automation] -createdAt: '2026-05-27T23:38:23.764Z' -updatedAt: '2026-05-27T23:38:23.764Z' ---- - -# Standardized Curation Workflow as Core Project Infrastructure - -Both the facts domain and the project domain describe the same durable curation workflow (recon → extraction → UPSERT → verification), making it a reusable infrastructure component for knowledge management. - -## Evidence - -- **facts**: Curation/runtime conventions define recon‑first analysis, single‑pass or chunked extraction, and UPSERT with verification. -- **project**: RLM curation workflow records the canonical process, emphasizing recon‑first, UPSERT, and `result.summary.failed === 0` verification. diff --git a/.brv/context-tree/integration_pill_padding/integration_pill_padding/integration_pill_padding.md b/.brv/context-tree/integration_pill_padding/integration_pill_padding/integration_pill_padding.md deleted file mode 100644 index f9b19a01..00000000 --- a/.brv/context-tree/integration_pill_padding/integration_pill_padding/integration_pill_padding.md +++ /dev/null @@ -1,30 +0,0 @@ ---- -title: .integration-pill padding -summary: Facts about .integration-pill padding -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T11:17:08.530Z' -updatedAt: '2026-05-28T11:17:08.530Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Document extracted factual statements - -**Flow:** -extraction -> curation - -**Timestamp:** 2026-05-28T11:17:08.519Z - -## Narrative -### Structure -Facts grouped under .integration-pill padding - -### Highlights -Contains 1 factual statements - -## Facts -- **.integration-pill padding**: apps/landing/src/styles/global.css:799-808 gives .integration-pill only 10px 12px padding with small text, with no min-height: 44px. diff --git a/.brv/context-tree/misc/curated/build.md b/.brv/context-tree/misc/curated/build.md deleted file mode 100644 index 3bc46f8e..00000000 --- a/.brv/context-tree/misc/curated/build.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: build -summary: Facts about build -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T14:00:01.352Z' -updatedAt: '2026-05-28T14:00:01.352Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-28T14:00:01.291Z - -## Narrative -### Highlights -Extracted 1 facts for build - -## Facts -- **build**: `pnpm --filter @caplets/landing build` passed diff --git a/.brv/context-tree/misc/curated/copy.md b/.brv/context-tree/misc/curated/copy.md deleted file mode 100644 index 3f4dd221..00000000 --- a/.brv/context-tree/misc/curated/copy.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: copy -summary: Facts about copy -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T14:00:01.347Z' -updatedAt: '2026-05-28T14:00:01.347Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-28T14:00:01.291Z - -## Narrative -### Highlights -Extracted 1 facts for copy - -## Facts -- **copy**: No user-visible copy/content changed diff --git a/.brv/context-tree/misc/curated/implementation.md b/.brv/context-tree/misc/curated/implementation.md deleted file mode 100644 index 89c68acf..00000000 --- a/.brv/context-tree/misc/curated/implementation.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: implementation -summary: Facts about implementation -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T14:00:01.304Z' -updatedAt: '2026-05-28T14:00:01.304Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-28T14:00:01.291Z - -## Narrative -### Highlights -Extracted 1 facts for implementation - -## Facts -- **implementation**: Implemented integration tab SSR ARIA fix. diff --git a/.brv/context-tree/misc/curated/index_astro.md b/.brv/context-tree/misc/curated/index_astro.md deleted file mode 100644 index a67a156e..00000000 --- a/.brv/context-tree/misc/curated/index_astro.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: index.astro -summary: Facts about index.astro -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T14:00:01.307Z' -updatedAt: '2026-05-28T14:00:01.307Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-28T14:00:01.291Z - -## Narrative -### Highlights -Extracted 1 facts for index.astro - -## Facts -- **index.astro**: Changed file apps/landing/src/pages/index.astro diff --git a/.brv/context-tree/misc/curated/js_initialization.md b/.brv/context-tree/misc/curated/js_initialization.md deleted file mode 100644 index dfc7868c..00000000 --- a/.brv/context-tree/misc/curated/js_initialization.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: JS initialization -summary: Facts about JS initialization -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T14:00:01.314Z' -updatedAt: '2026-05-28T14:00:01.314Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-28T14:00:01.291Z - -## Narrative -### Highlights -Extracted 1 facts for JS initialization - -## Facts -- **JS initialization**: JS adds tablist/tab/panel roles and ARIA state through selectAgentTab diff --git a/.brv/context-tree/misc/curated/navigation.md b/.brv/context-tree/misc/curated/navigation.md deleted file mode 100644 index 0e7196d0..00000000 --- a/.brv/context-tree/misc/curated/navigation.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: navigation -summary: Facts about navigation -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T14:00:01.341Z' -updatedAt: '2026-05-28T14:00:01.342Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-28T14:00:01.291Z - -## Narrative -### Highlights -Extracted 1 facts for navigation - -## Facts -- **navigation**: Click and keyboard navigation remain intact diff --git a/.brv/context-tree/misc/curated/none.md b/.brv/context-tree/misc/curated/none.md deleted file mode 100644 index 9ec3410c..00000000 --- a/.brv/context-tree/misc/curated/none.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: none -summary: Facts about none -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T14:00:01.354Z' -updatedAt: '2026-05-28T14:00:01.354Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-28T14:00:01.291Z - -## Narrative -### Highlights -Extracted 1 facts for none - -## Facts -- **none**: Open risks/questions: None diff --git a/.brv/context-tree/misc/curated/panel_visibility.md b/.brv/context-tree/misc/curated/panel_visibility.md deleted file mode 100644 index cafdbab4..00000000 --- a/.brv/context-tree/misc/curated/panel_visibility.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: panel visibility -summary: Facts about panel visibility -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T14:00:01.330Z' -updatedAt: '2026-05-28T14:00:01.330Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-28T14:00:01.291Z - -## Narrative -### Highlights -Extracted 1 facts for panel visibility - -## Facts -- **panel visibility**: Inactive panels are hidden after initialization diff --git a/.brv/context-tree/misc/curated/ssr_semantics.md b/.brv/context-tree/misc/curated/ssr_semantics.md deleted file mode 100644 index e1dc47b1..00000000 --- a/.brv/context-tree/misc/curated/ssr_semantics.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: SSR semantics -summary: Facts about SSR semantics -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T14:00:01.310Z' -updatedAt: '2026-05-28T14:00:01.310Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-28T14:00:01.291Z - -## Narrative -### Highlights -Extracted 1 facts for SSR semantics - -## Facts -- **SSR semantics**: Removed SSR tab pattern semantics from integration controls and panels: no role="tablist", no role="tab", no aria-selected, no aria-controls for tabs, no roving tabindex, no role="tabpanel", no aria-labelledby on panels diff --git a/.brv/context-tree/misc/curated/task.md b/.brv/context-tree/misc/curated/task.md deleted file mode 100644 index f6a6c21c..00000000 --- a/.brv/context-tree/misc/curated/task.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: task -summary: Facts about task -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T14:00:01.297Z' -updatedAt: '2026-05-28T14:00:01.297Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-28T14:00:01.291Z - -## Narrative -### Highlights -Extracted 1 facts for task - -## Facts -- **task**: Status: DONE diff --git a/.brv/context-tree/misc/curated/typecheck.md b/.brv/context-tree/misc/curated/typecheck.md deleted file mode 100644 index ecc3a50e..00000000 --- a/.brv/context-tree/misc/curated/typecheck.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: typecheck -summary: Facts about typecheck -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T14:00:01.349Z' -updatedAt: '2026-05-28T14:00:01.349Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Curate extracted facts - -**Timestamp:** 2026-05-28T14:00:01.291Z - -## Narrative -### Highlights -Extracted 1 facts for typecheck - -## Facts -- **typecheck**: `pnpm --filter @caplets/landing typecheck` passed diff --git a/.brv/context-tree/mobile_copy_button_height/mobile_copy_button_height/mobile_copy_button_height.md b/.brv/context-tree/mobile_copy_button_height/mobile_copy_button_height/mobile_copy_button_height.md deleted file mode 100644 index 3f4ad18d..00000000 --- a/.brv/context-tree/mobile_copy_button_height/mobile_copy_button_height/mobile_copy_button_height.md +++ /dev/null @@ -1,30 +0,0 @@ ---- -title: mobile .copy-button height -summary: Facts about mobile .copy-button height -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T11:17:08.524Z' -updatedAt: '2026-05-28T11:17:08.524Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Document extracted factual statements - -**Flow:** -extraction -> curation - -**Timestamp:** 2026-05-28T11:17:08.519Z - -## Narrative -### Structure -Facts grouped under mobile .copy-button height - -### Highlights -Contains 1 factual statements - -## Facts -- **mobile .copy-button height**: apps/landing/src/styles/global.css:1191-1193 raises mobile .copy-button only to 40px, still below 44px. diff --git a/.brv/context-tree/oklch_token_consolidation/oklch_token_consolidation/oklch_token_consolidation.md b/.brv/context-tree/oklch_token_consolidation/oklch_token_consolidation/oklch_token_consolidation.md deleted file mode 100644 index a1d15151..00000000 --- a/.brv/context-tree/oklch_token_consolidation/oklch_token_consolidation/oklch_token_consolidation.md +++ /dev/null @@ -1,30 +0,0 @@ ---- -title: OKLCH token consolidation -summary: Facts about OKLCH token consolidation -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T11:17:08.532Z' -updatedAt: '2026-05-28T11:17:08.532Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Document extracted factual statements - -**Flow:** -extraction -> curation - -**Timestamp:** 2026-05-28T11:17:08.519Z - -## Narrative -### Structure -Facts grouped under OKLCH token consolidation - -### Highlights -Contains 1 factual statements - -## Facts -- **OKLCH token consolidation**: Direct OKLCH consolidation appears incomplete. diff --git a/.brv/context-tree/progressive_js_tabs/progressive_js_tabs/progressive_js_tabs.md b/.brv/context-tree/progressive_js_tabs/progressive_js_tabs/progressive_js_tabs.md deleted file mode 100644 index bb468f09..00000000 --- a/.brv/context-tree/progressive_js_tabs/progressive_js_tabs/progressive_js_tabs.md +++ /dev/null @@ -1,30 +0,0 @@ ---- -title: progressive JS tabs -summary: Facts about progressive JS tabs -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T11:17:08.536Z' -updatedAt: '2026-05-28T11:17:08.536Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Document extracted factual statements - -**Flow:** -extraction -> curation - -**Timestamp:** 2026-05-28T11:17:08.519Z - -## Narrative -### Structure -Facts grouped under progressive JS tabs - -### Highlights -Contains 1 factual statements - -## Facts -- **progressive JS tabs**: Progressive JS tabs and no-JS visible panels are implemented: panels are not hidden in markup, JS adds roles/ARIA and hides inactive panels at index.astro:417-437. diff --git a/.brv/context-tree/project/pr_78_review_outcome/pr_78_review_outcome.md b/.brv/context-tree/project/pr_78_review_outcome/pr_78_review_outcome.md deleted file mode 100644 index 15a46514..00000000 --- a/.brv/context-tree/project/pr_78_review_outcome/pr_78_review_outcome.md +++ /dev/null @@ -1,56 +0,0 @@ ---- -title: PR 78 Review Outcome -summary: 'PR #78 had zero unresolved review threads and zero unresolved outside-diff threads; no code fixes were needed, the .brv memory update was preserved, pushed as commit c41d191, and pnpm verify passed.' -tags: [] -related: [project/rlm_curation_workflow/context.md, project/rlm_curation_workflow/rlm_curation_workflow.md] -keywords: [] -createdAt: '2026-05-24T15:30:07.506Z' -updatedAt: '2026-05-24T15:30:07.507Z' ---- -## Reason -Document the outcome of resolving PR review comments and preserving .brv changes - -## Raw Concept -**Task:** -Document the PR #78 review resolution outcome - -**Changes:** -- Fetched unresolved review threads for the current pull request -- Confirmed there were no unresolved review or outside-diff comments -- Preserved and pushed the .brv memory update -- Recorded successful pre-push verification - -**Files:** -- PR #78 - -**Flow:** -fetch review threads -> confirm zero unresolved comments -> preserve .brv changes -> push commit -> verify clean worktree - -**Timestamp:** 2026-05-24T15:29:54.427Z - -**Author:** Ian - -## Narrative -### Structure -This outcome captures the PR review pass for PR #78 and the decision to keep the ByteRover memory change intact. - -### Dependencies -Depends on successful PR thread inspection and pre-push verification. - -### Highlights -No code fixes were needed because there were no unresolved review threads. The .brv update remained on the branch and was pushed successfully. - -### Rules -Rule: Do not revert .brv changes during this workflow. - -### Examples -Commit c41d191: chore: update byterover memory pointer - -## Facts -- **pr_78_unresolved_review_threads**: PR #78 had zero unresolved review threads. [project] -- **pr_78_unresolved_outside_diff_threads**: PR #78 had zero unresolved outside-diff threads. [project] -- **pr_78_code_changes_needed**: No code changes were needed for the PR review pass. [project] -- **brv_memory_update_preserved**: A .brv memory update was preserved and pushed instead of being reverted. [project] -- **pr_78_pushed_commit**: The pushed commit was c41d191 with message "chore: update byterover memory pointer". [project] -- **pre_push_verify**: Pre-push pnpm verify passed. [project] -- **worktree_state**: The worktree was clean after the push. [project] diff --git a/.brv/context-tree/project/rlm_curation_workflow/curation-workflow-is-treated-as-durable-project-infrastructure.md b/.brv/context-tree/project/rlm_curation_workflow/curation-workflow-is-treated-as-durable-project-infrastructure.md deleted file mode 100644 index f2fbe043..00000000 --- a/.brv/context-tree/project/rlm_curation_workflow/curation-workflow-is-treated-as-durable-project-infrastructure.md +++ /dev/null @@ -1,22 +0,0 @@ ---- -confidence: 0.98 -sources: [facts/_index.md, project/_index.md] -synthesized_at: '2026-05-26T19:23:43.119Z' -type: synthesis -title: Curation workflow is treated as durable project infrastructure -summary: The same recon-first, verify-by-file-path curation process is codified as both a facts policy and a project workflow. -tags: [curation, workflow, verification, facts, project] -related: [] -keywords: [recon, upsert, mapextract, dedup, verify, filePath, failed, context] -createdAt: '2026-05-26T19:23:43.119Z' -updatedAt: '2026-05-26T19:23:43.119Z' ---- - -# Curation workflow is treated as durable project infrastructure - -Across facts and project, curation is not chat-only: it is a durable workflow with recon-first analysis, single-pass vs. chunked extraction, dedup/grouping, UPSERT-first updates, and verification via `result.applied[].filePath` plus `result.summary.failed === 0`. - -## Evidence - -- **facts**: Runtime curation guardrails require single-pass mode for compact contexts, chunked `mapExtract` only for larger contexts, no raw context printing, bare `taskId`, outer `code_exec` timeout 300000 ms, UPSERT preference, and verification through `result.applied[].filePath` and `result.summary.failed`. -- **project**: The curation workflow is described as recon-first, then single-pass or chunked extraction, then dedup/groupBySubject, with verification using `result.applied[].filePath` and no rereading files just to confirm. diff --git a/.brv/context-tree/project/rlm_curation_workflow/rlm_curation_workflow.md b/.brv/context-tree/project/rlm_curation_workflow/rlm_curation_workflow.md deleted file mode 100644 index 945069c1..00000000 --- a/.brv/context-tree/project/rlm_curation_workflow/rlm_curation_workflow.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -consolidated_at: '2026-05-27T10:03:24.714Z' -consolidated_from: [{date: '2026-05-27T10:03:24.714Z', path: project/rlm_curation_workflow/context.md, reason: 'These files are all the same topic with heavy overlap: the short context.md is a subset, and the abstract/overview duplicate the same recon-first single-pass/chunked workflow plus verification rules. The richest file is the markdown topic file, and it already contains the most complete structured content, so the related files should be consolidated into it to avoid redundant parallel representations.'}, {date: '2026-05-27T10:03:24.714Z', path: project/rlm_curation_workflow/rlm_curation_workflow.abstract.md, reason: 'These files are all the same topic with heavy overlap: the short context.md is a subset, and the abstract/overview duplicate the same recon-first single-pass/chunked workflow plus verification rules. The richest file is the markdown topic file, and it already contains the most complete structured content, so the related files should be consolidated into it to avoid redundant parallel representations.'}, {date: '2026-05-27T10:03:24.714Z', path: project/rlm_curation_workflow/rlm_curation_workflow.overview.md, reason: 'These files are all the same topic with heavy overlap: the short context.md is a subset, and the abstract/overview duplicate the same recon-first single-pass/chunked workflow plus verification rules. The richest file is the markdown topic file, and it already contains the most complete structured content, so the related files should be consolidated into it to avoid redundant parallel representations.'}] ---- -Preserve the full content of project/rlm_curation_workflow/rlm_curation_workflow.md as the base. Add the unique details from project/rlm_curation_workflow/context.md (short topic overview and key concepts), project/rlm_curation_workflow/rlm_curation_workflow.abstract.md (one-sentence synthesis of recon choosing single-pass vs chunked extraction and verification via applied file paths), and project/rlm_curation_workflow/rlm_curation_workflow.overview.md (expanded bullets covering: recon-first flow; precomputed recon output; mapExtract/dedup/groupBySubject/recordProgress/tools.curate helpers; exact verification convention using result.applied[].filePath and result.summary.failed; no raw-context printing; .brv/ local memory-state note; taskId bare-variable rule; outer code_exec timeout 300000 when mapExtract is used; safe-fallback completion behavior; and the branch-local caplet completion details including split target support for get-tool/call-tool/get-prompt, dotted target compatibility, regression test coverage, and CapletsEngine.completeCliWords). Keep all unique facts, rules, highlights, and cross-references verbatim where possible, deduplicating repeated content. \ No newline at end of file diff --git a/.brv/context-tree/project/verification-first-as-a-cross-domain-safety-model.md b/.brv/context-tree/project/verification-first-as-a-cross-domain-safety-model.md deleted file mode 100644 index 9e62f27f..00000000 --- a/.brv/context-tree/project/verification-first-as-a-cross-domain-safety-model.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -confidence: 0.97 -sources: [architecture/_index.md, docs/_index.md, facts/_index.md, project/_index.md] -synthesized_at: '2026-05-27T23:38:23.761Z' -type: synthesis -title: Verification‑First as a Cross‑Domain Safety Model -summary: All major project areas treat explicit verification as the true completion signal, not just workflow success. -tags: [verification, safety, ci-cd] -related: [] -keywords: [verification, compliance, testing, release, curation, workflow, safety, automation, policy, validation] -createdAt: '2026-05-27T23:38:23.761Z' -updatedAt: '2026-05-27T23:38:23.761Z' ---- - -# Verification‑First as a Cross‑Domain Safety Model - -The project enforces a verification‑first policy that spans architecture, documentation, facts, and project workflows, ensuring changes are only considered complete after passing automated verification tests and review outcomes. - -## Evidence - -- **architecture**: Verification and compliance are listed as a core pillar ("Task 1 Spec Compliance Review"). -- **docs**: Release automation and completion pipelines are gated by verified publish state, not merely successful workflow runs. -- **facts**: Curation/runtime conventions require recon → extraction → curate with UPSERT and verification that `result.summary.failed === 0`. -- **project**: The shared safety model explicitly states that verification is the real completion signal across the project. diff --git a/.brv/context-tree/project/verification-first-workflows-are-the-project-s-shared-safety-model.md b/.brv/context-tree/project/verification-first-workflows-are-the-project-s-shared-safety-model.md deleted file mode 100644 index 9cbbbd44..00000000 --- a/.brv/context-tree/project/verification-first-workflows-are-the-project-s-shared-safety-model.md +++ /dev/null @@ -1,23 +0,0 @@ ---- -confidence: 0.98 -sources: [docs/_index.md, facts/_index.md, project/_index.md] -synthesized_at: '2026-05-27T10:03:46.002Z' -type: synthesis -title: Verification-first workflows are the project’s shared safety model -summary: Across planning, curation, and release, changes are only treated as complete after explicit verification, not workflow success alone. -tags: [verification, workflow, release, curation] -related: [] -keywords: [verification, tests, pnpm verify, publish-state, applied-file-paths, curation, completion, release-gating] -createdAt: '2026-05-27T10:03:46.002Z' -updatedAt: '2026-05-27T10:03:46.002Z' ---- - -# Verification-first workflows are the project’s shared safety model - -The same verification-first pattern appears in docs, facts, and project: planning docs emphasize explicit checks before completion, the curation workflow requires `result.summary.failed === 0` and applied file-path verification, and release automation waits for verified publish state rather than just successful workflow runs. - -## Evidence - -- **docs**: Planning and release docs repeatedly frame work as verification-first: completion fixes are validated with focused tests and `pnpm verify`, and release publishing is gated by verified upstream publish state rather than workflow success alone. -- **facts**: The curation/runtime conventions require recon-first analysis, then curate, then verify through `result.applied[].filePath` and `result.summary.failed === 0`, and related review notes preserve explicit test/commit evidence. -- **project**: The RLM curation workflow summary says verification should use `result.summary.failed === 0` and `result.applied[].filePath`, not rereading files, and the PR 78 review outcome preserved a clean verification state. diff --git a/.brv/context-tree/reveal_failsafe/reveal_failsafe/reveal_failsafe.md b/.brv/context-tree/reveal_failsafe/reveal_failsafe/reveal_failsafe.md deleted file mode 100644 index b3805b34..00000000 --- a/.brv/context-tree/reveal_failsafe/reveal_failsafe/reveal_failsafe.md +++ /dev/null @@ -1,30 +0,0 @@ ---- -title: reveal failsafe -summary: Facts about reveal failsafe -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T11:17:08.560Z' -updatedAt: '2026-05-28T11:17:08.560Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Document extracted factual statements - -**Flow:** -extraction -> curation - -**Timestamp:** 2026-05-28T11:17:08.519Z - -## Narrative -### Structure -Facts grouped under reveal failsafe - -### Highlights -Contains 1 factual statements - -## Facts -- **reveal failsafe**: Reveal failsafe implemented: non-IntersectionObserver path and timeout failsafe at index.astro:527-533. diff --git a/.brv/context-tree/src/caplets/agent_setup_tabs.md b/.brv/context-tree/src/caplets/agent_setup_tabs.md deleted file mode 100644 index 13b1bf0f..00000000 --- a/.brv/context-tree/src/caplets/agent_setup_tabs.md +++ /dev/null @@ -1,22 +0,0 @@ ---- -title: agent setup tabs -summary: Facts about agent setup tabs -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T23:46:33.724Z' -updatedAt: '2026-05-27T23:46:33.724Z' ---- -## Reason -Curate extracted facts from context - -## Raw Concept -**Task:** -Document facts about agent setup tabs - -## Narrative -### Highlights -Implemented agent setup tabs for the landing integrations card. - -## Facts -- **agent setup tabs**: Implemented agent setup tabs for the landing integrations card. diff --git a/.brv/context-tree/src/caplets/build.md b/.brv/context-tree/src/caplets/build.md deleted file mode 100644 index 049c1e26..00000000 --- a/.brv/context-tree/src/caplets/build.md +++ /dev/null @@ -1,22 +0,0 @@ ---- -title: build -summary: Facts about build -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T23:46:33.751Z' -updatedAt: '2026-05-27T23:46:33.751Z' ---- -## Reason -Curate extracted facts from context - -## Raw Concept -**Task:** -Document facts about build - -## Narrative -### Highlights -pnpm --filter @caplets/landing build passed. - -## Facts -- **build**: pnpm --filter @caplets/landing build passed. diff --git a/.brv/context-tree/src/caplets/caplets_module.md b/.brv/context-tree/src/caplets/caplets_module.md deleted file mode 100644 index 98c74e0c..00000000 --- a/.brv/context-tree/src/caplets/caplets_module.md +++ /dev/null @@ -1,39 +0,0 @@ ---- -title: Caplets Module -summary: Extracted factual statements about caplets implementation and usage -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T23:54:41.417Z' -updatedAt: '2026-05-27T23:54:41.417Z' ---- -## Reason -Curate extracted facts from caplets source context - -## Raw Concept -**Task:** -Document caplets module knowledge - -**Timestamp:** 2026-05-27T23:54:41.412Z - -## Facts -- **commit_range_0e56e8f..HEAD**: Reviewed committed changes only for `0e56e8f^..HEAD` covering `apps/landing/src/pages/index.astro`, `apps/landing/src/styles/global.css`, and `docs/plans/2026-05-27-agent-card-setup-tabs.md`. -- **file_selection**: Ignored unrelated unstaged/untracked files shown by `git status`. -- **OpenCode**: Previous misinformation blockers are fixed: OpenCode setup now uses native plugin install/config with `npm install -g @caplets/opencode` and plugin configuration in `apps/landing/src/pages/index.astro:51-57` as confirmed by `packages/opencode/README.md`. -- **Pi**: Previous misinformation blockers are fixed: Pi setup now uses Pi extension install/settings with `pi install npm:@caplets/pi` and package registration in `apps/landing/src/pages/index.astro:60-66` as confirmed by `packages/pi/README.md`. -- **tab_interface**: Accessibility implementation uses proper `role="tablist"`, `role="tab"`, `role="tabpanel"`, `aria-selected`, `aria-controls`, and `aria-labelledby` at `apps/landing/src/pages/index.astro:260-284`. -- **apps/landing/src/pages/index.astro:260-284**: Uses proper role="tablist", role="tab", role="tabpanel", aria-selected, aria-controls, and aria-labelledby -- **apps/landing/src/pages/index.astro:359-380**: Keyboard navigation supports ArrowLeft/ArrowRight/Home/End and moves focus/selection -- **apps/landing/src/pages/index.astro:284**: Hidden inactive panels use the native hidden attribute -- **apps/landing/src/styles/global.css:993-995**: CSS reinforces display behavior for hidden panels -- **Astro/TypeScript code**: Inline script types are valid -- **pnpm typecheck**: pnpm --filter @caplets/landing typecheck passed with 0 errors/warnings/hints -- **pnpm build**: pnpm --filter @caplets/landing build passed -- **apps/landing/src/styles/global.css:953-1019**: Styling reuses existing design tokens and visual language (--night-*, --font-mono, --ease-out) -- **apps/landing/src/styles/global.css:1256-1258**: Responsive behavior collapses setup grid cleanly -- **apps/landing/src/styles/global.css:1326-1333**: Reduced-motion fallback includes new tab/panel elements -- **apps/landing/src/pages/index.astro:26-78**: agentSetups centralizes tab labels, commands, snippets, and notes -- **apps/landing/src/pages/index.astro:80**: integrations derives from agentSetups, avoiding duplicated client names -- **Project status**: Blocker: None -- **Build warning**: Build emits a Node deprecation warning from tooling (module.register()), but the landing typecheck/build both pass and this is unrelated to the committed tab changes -- **blocker**: Blocker: None. diff --git a/.brv/context-tree/src/caplets/caplets_module_findings.md b/.brv/context-tree/src/caplets/caplets_module_findings.md deleted file mode 100644 index 056844e6..00000000 --- a/.brv/context-tree/src/caplets/caplets_module_findings.md +++ /dev/null @@ -1,29 +0,0 @@ ---- -title: Caplets Module Findings -summary: Extracted key facts and narratives from caplets module -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T16:06:53.148Z' -updatedAt: '2026-05-28T16:06:53.148Z' ---- -## Reason -Curate extracted facts from caplets source - -## Raw Concept -**Task:** -Document caplets module insights - -**Timestamp:** 2026-05-28T16:06:53.146Z - -## Narrative -### Highlights -The assistant is using the finishing-a-development-branch skill to complete the work | A pull request was opened at https://github.com/spiritledsoftware/caplets/pull/93 | The pull request targets the branch docs/align-readme-with-landing | The commit b37a94c with message "docs: align readme with landing page" was included in the PR | The verification command pnpm verify passed | Files under .brv/context-tree/... remain untracked and were not included in the commit - -## Facts -- **assistant**: The assistant is using the finishing-a-development-branch skill to complete the work -- **PR 93**: A pull request was opened at https://github.com/spiritledsoftware/caplets/pull/93 -- **docs/align-readme-with-landing**: The pull request targets the branch docs/align-readme-with-landing -- **b37a94c**: The commit b37a94c with message "docs: align readme with landing page" was included in the PR -- **pnpm verify**: The verification command pnpm verify passed -- **untracked files**: Files under .brv/context-tree/... remain untracked and were not included in the commit diff --git a/.brv/context-tree/src/caplets/caplets_source_extraction.md b/.brv/context-tree/src/caplets/caplets_source_extraction.md deleted file mode 100644 index b8adea50..00000000 --- a/.brv/context-tree/src/caplets/caplets_source_extraction.md +++ /dev/null @@ -1,59 +0,0 @@ ---- -title: Caplets Source Extraction -summary: Extracted factual statements from caplets source -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T11:23:36.823Z' -updatedAt: '2026-05-28T11:23:36.823Z' ---- -## Reason -Curate extracted facts from caplets source context - -## Raw Concept -**Task:** -Document extracted facts from caplets source context - -**Flow:** -extraction -> dedup -> curation - -**Timestamp:** 2026-05-28T11:23:36.821Z - -## Narrative -### Structure -Aggregated extracted factual statements from provided context - -### Highlights -interactive controls, header/nav links, hero buttons, integration tabs, copy buttons - -### Examples -[ - { - "statement": "All interactive controls meet the 44px minimum", - "subject": "interactive controls" - }, - { - "statement": "Header/nav links meet the 44px minimum", - "subject": "header/nav links" - }, - { - "statement": "Hero buttons meet the 44px minimum", - "subject": "hero buttons" - } -] - -## Facts -- **interactive controls**: All interactive controls meet the 44px minimum -- **header/nav links**: Header/nav links meet the 44px minimum -- **hero buttons**: Hero buttons meet the 44px minimum -- **integration tabs**: Integration tabs meet the 44px minimum -- **copy buttons**: Copy buttons meet the 44px minimum -- **JS tabs**: Progressive JS tabs avoid misleading no-JS roles -- **tab markup**: Static markup has no tab roles and JavaScript adds appropriate ARIA attributes after initialization -- **clipboard fallback**: Clipboard fallback focuses and selects the snippet target, with a textarea fallback when no target exists -- **reveal animation**: Reveal animation includes failsafes for missing IntersectionObserver, reduced motion, and delayed reveal fallback -- **terminal chrome**: Terminal chrome is hidden from assistive technology -- **token cleanup**: Token cleanup is complete; reused values are promoted to CSS variables and no removed token references remain -- **punctuation**: No em dashes or en dashes were found in the inspected files -- **audit issues**: No P0/P1/P2 audit issues were found -- **overall**: Review result: APPROVED diff --git a/.brv/context-tree/src/caplets/changed_files.md b/.brv/context-tree/src/caplets/changed_files.md deleted file mode 100644 index ed2108b3..00000000 --- a/.brv/context-tree/src/caplets/changed_files.md +++ /dev/null @@ -1,22 +0,0 @@ ---- -title: changed files -summary: Facts about changed files -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T23:46:33.727Z' -updatedAt: '2026-05-27T23:46:33.727Z' ---- -## Reason -Curate extracted facts from context - -## Raw Concept -**Task:** -Document facts about changed files - -## Narrative -### Highlights -Changed files: apps/landing/src/pages/index.astro, apps/landing/src/styles/global.css, docs/plans/2026-05-27-agent-card-setup-tabs.md. - -## Facts -- **changed files**: Changed files: apps/landing/src/pages/index.astro, apps/landing/src/styles/global.css, docs/plans/2026-05-27-agent-card-setup-tabs.md. diff --git a/.brv/context-tree/src/caplets/commit_0e56e8f.md b/.brv/context-tree/src/caplets/commit_0e56e8f.md deleted file mode 100644 index 0b44d122..00000000 --- a/.brv/context-tree/src/caplets/commit_0e56e8f.md +++ /dev/null @@ -1,22 +0,0 @@ ---- -title: commit 0e56e8f -summary: Facts about commit 0e56e8f -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T23:46:33.733Z' -updatedAt: '2026-05-27T23:46:33.733Z' ---- -## Reason -Curate extracted facts from context - -## Raw Concept -**Task:** -Document facts about commit 0e56e8f - -## Narrative -### Highlights -Commit 0e56e8f with message "feat(landing): add agent setup tabs" was created. - -## Facts -- **commit 0e56e8f**: Commit 0e56e8f with message "feat(landing): add agent setup tabs" was created. diff --git a/.brv/context-tree/src/caplets/format_check.md b/.brv/context-tree/src/caplets/format_check.md deleted file mode 100644 index a8609d3d..00000000 --- a/.brv/context-tree/src/caplets/format_check.md +++ /dev/null @@ -1,22 +0,0 @@ ---- -title: format check -summary: Facts about format check -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T23:46:33.735Z' -updatedAt: '2026-05-27T23:46:33.735Z' ---- -## Reason -Curate extracted facts from context - -## Raw Concept -**Task:** -Document facts about format check - -## Narrative -### Highlights -pnpm format:check passed. - -## Facts -- **format check**: pnpm format:check passed. diff --git a/.brv/context-tree/src/caplets/lint.md b/.brv/context-tree/src/caplets/lint.md deleted file mode 100644 index 9296fffd..00000000 --- a/.brv/context-tree/src/caplets/lint.md +++ /dev/null @@ -1,22 +0,0 @@ ---- -title: lint -summary: Facts about lint -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T23:46:33.737Z' -updatedAt: '2026-05-27T23:46:33.737Z' ---- -## Reason -Curate extracted facts from context - -## Raw Concept -**Task:** -Document facts about lint - -## Narrative -### Highlights -pnpm lint passed. - -## Facts -- **lint**: pnpm lint passed. diff --git a/.brv/context-tree/src/caplets/pre_commit_hook.md b/.brv/context-tree/src/caplets/pre_commit_hook.md deleted file mode 100644 index 99c73688..00000000 --- a/.brv/context-tree/src/caplets/pre_commit_hook.md +++ /dev/null @@ -1,22 +0,0 @@ ---- -title: pre-commit hook -summary: Facts about pre-commit hook -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T23:46:33.753Z' -updatedAt: '2026-05-27T23:46:33.753Z' ---- -## Reason -Curate extracted facts from context - -## Raw Concept -**Task:** -Document facts about pre-commit hook - -## Narrative -### Highlights -Pre-commit hook ran broader format/lint/typecheck successfully. - -## Facts -- **pre-commit hook**: Pre-commit hook ran broader format/lint/typecheck successfully. diff --git a/.brv/context-tree/src/caplets/typecheck.md b/.brv/context-tree/src/caplets/typecheck.md deleted file mode 100644 index 6b9e6584..00000000 --- a/.brv/context-tree/src/caplets/typecheck.md +++ /dev/null @@ -1,22 +0,0 @@ ---- -title: typecheck -summary: Facts about typecheck -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T23:46:33.740Z' -updatedAt: '2026-05-27T23:46:33.740Z' ---- -## Reason -Curate extracted facts from context - -## Raw Concept -**Task:** -Document facts about typecheck - -## Narrative -### Highlights -pnpm --filter @caplets/landing typecheck passed. - -## Facts -- **typecheck**: pnpm --filter @caplets/landing typecheck passed. diff --git a/.brv/context-tree/src/caplets/working_tree.md b/.brv/context-tree/src/caplets/working_tree.md deleted file mode 100644 index 02081dda..00000000 --- a/.brv/context-tree/src/caplets/working_tree.md +++ /dev/null @@ -1,22 +0,0 @@ ---- -title: working tree -summary: Facts about working tree -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T23:46:33.759Z' -updatedAt: '2026-05-27T23:46:33.759Z' ---- -## Reason -Curate extracted facts from context - -## Raw Concept -**Task:** -Document facts about working tree - -## Narrative -### Highlights -Working tree still has unrelated pre-existing changes after the commit, including .brv/*, deleted apps/landing/.vscode/*, screenshots, and unstaged modifications to apps/landing/src/pages/index.astro and apps/landing/src/styles/global.css. - -## Facts -- **working tree**: Working tree still has unrelated pre-existing changes after the commit, including .brv/*, deleted apps/landing/.vscode/*, screenshots, and unstaged modifications to apps/landing/src/pages/index.astro and apps/landing/src/styles/global.css. diff --git a/.brv/context-tree/terminal_chrome_aria_hidden/terminal_chrome_aria_hidden/terminal_chrome_aria_hidden.md b/.brv/context-tree/terminal_chrome_aria_hidden/terminal_chrome_aria_hidden/terminal_chrome_aria_hidden.md deleted file mode 100644 index 003a0443..00000000 --- a/.brv/context-tree/terminal_chrome_aria_hidden/terminal_chrome_aria_hidden/terminal_chrome_aria_hidden.md +++ /dev/null @@ -1,30 +0,0 @@ ---- -title: terminal chrome aria-hidden -summary: Facts about terminal chrome aria-hidden -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T11:17:08.561Z' -updatedAt: '2026-05-28T11:17:08.562Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Document extracted factual statements - -**Flow:** -extraction -> curation - -**Timestamp:** 2026-05-28T11:17:08.519Z - -## Narrative -### Structure -Facts grouped under terminal chrome aria-hidden - -### Highlights -Contains 1 factual statements - -## Facts -- **terminal chrome aria-hidden**: Terminal chrome is hidden from AT at index.astro:377-379. diff --git a/.brv/context-tree/theme_color_usage/theme_color_usage/theme_color_usage.md b/.brv/context-tree/theme_color_usage/theme_color_usage/theme_color_usage.md deleted file mode 100644 index b03db939..00000000 --- a/.brv/context-tree/theme_color_usage/theme_color_usage/theme_color_usage.md +++ /dev/null @@ -1,30 +0,0 @@ ---- -title: theme color usage -summary: Facts about theme color usage -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-28T11:17:08.534Z' -updatedAt: '2026-05-28T11:17:08.534Z' ---- -## Reason -Curated extracted facts from context - -## Raw Concept -**Task:** -Document extracted factual statements - -**Flow:** -extraction -> curation - -**Timestamp:** 2026-05-28T11:17:08.519Z - -## Narrative -### Structure -Facts grouped under theme color usage - -### Highlights -Contains 1 factual statements - -## Facts -- **theme color usage**: apps/landing/src/pages/index.astro:154 still has a direct oklch(97% 0.012 85) theme color instead of a consolidated token/source equivalent. diff --git a/.brv/context-tree/ui/agent_card_tabs/agent_card_tabs.md b/.brv/context-tree/ui/agent_card_tabs/agent_card_tabs.md deleted file mode 100644 index 074de042..00000000 --- a/.brv/context-tree/ui/agent_card_tabs/agent_card_tabs.md +++ /dev/null @@ -1,45 +0,0 @@ ---- -title: Agent Card Tabs -summary: Design for turning agent pills into configurable tabs with commands and settings -tags: [] -related: [] -keywords: [] -createdAt: '2026-05-27T23:18:59.168Z' -updatedAt: '2026-05-27T23:18:59.168Z' ---- -## Reason -Document design decision for agent pills as tabs - -## Raw Concept -**Task:** -Design UI where each agent pill acts as a tab showing configuration/commands - -**Changes:** -- Agent pills become tab triggers -- Detail area shows install command, config snippet, usage note -- Data modeled per agent for extensibility -- Tests added for tab functionality - -**Flow:** -User selects pill -> UI swaps detail area to show agent-specific info - -**Timestamp:** 2026-05-27T23:18:59.161Z - -## Narrative -### Structure -Agents card layout unchanged; pills act as tabs triggering detail pane - -### Highlights -Preserves existing layout, adds configurability, supports future agents - -### Examples -Click on "Agent A" pill to view its install command and config snippet - -## Facts -- **card and agent pills**: I want it to be the card as it currently is, but each agent's pill is a tab showing the configuration/commands. -- **agents card layout**: Keep the agents card layout exactly as-is structurally. -- **agent pill behavior**: Treat each existing agent pill as a tab trigger. -- **pill interaction**: Selecting a pill swaps the detail area below/near the pills to show that agent’s install command, configuration snippet, and short usage note if needed. -- **active pill appearance**: The active pill should be visually distinct but still look like the existing pill style, not a new heavy tab component. -- **data modeling**: Data should be modeled per agent so future agents can add setup content without changing rendering logic. -- **test cases**: Tests should verify tab labels render, default selected agent content appears, and switching pills reveals the matching commands/configuration. diff --git a/.brv/context-tree/ui/agent_card_tabs/context.md b/.brv/context-tree/ui/agent_card_tabs/context.md deleted file mode 100644 index 7e6bfa3b..00000000 --- a/.brv/context-tree/ui/agent_card_tabs/context.md +++ /dev/null @@ -1,10 +0,0 @@ -# Topic: agent_card_tabs - -## Overview -Design for converting agent pills into tabs to show configuration/commands - -## Key Concepts -- agent pill -- tab trigger -- configuration snippet -- install command diff --git a/.brv/context-tree/ui/context.md b/.brv/context-tree/ui/context.md deleted file mode 100644 index 60ca6e52..00000000 --- a/.brv/context-tree/ui/context.md +++ /dev/null @@ -1,9 +0,0 @@ -# Domain: ui - -## Purpose -UI component designs for caplets interface - -## Scope -Included in this domain: -- Agent card modifications -- Tab interactions diff --git a/.caplets/.gitignore b/.caplets/.gitignore index 1d9df9a6..defd0ce7 100644 --- a/.caplets/.gitignore +++ b/.caplets/.gitignore @@ -1,3 +1,3 @@ * !.gitignore -!config.json +!*config.json diff --git a/.caplets/local.config.json b/.caplets/local.config.json new file mode 100644 index 00000000..475d2ec0 --- /dev/null +++ b/.caplets/local.config.json @@ -0,0 +1,11 @@ +{ + "$schema": "../schemas/caplets-config.schema.json", + "mcpServers": { + "everything": { + "name": "Everything", + "description": "This MCP server attempts to exercise all the features of the MCP protocol. It is not intended to be a useful server, but rather a test server for builders of MCP clients. It implements prompts, tools, resources, sampling, and more to showcase MCP capabilities.", + "command": "npx", + "args": ["-y", "@modelcontextprotocol/server-everything"] + } + } +} diff --git a/.caplets/remote.config.json b/.caplets/remote.config.json new file mode 100644 index 00000000..37c03877 --- /dev/null +++ b/.caplets/remote.config.json @@ -0,0 +1,12 @@ +{ + "$schema": "../schemas/caplets-config.schema.json", + "mcpServers": { + "everything": { + "name": "Everything", + "description": "This MCP server attempts to exercise all the features of the MCP protocol. It is not intended to be a useful server, but rather a test server for builders of MCP clients. It implements prompts, tools, resources, sampling, and more to showcase MCP capabilities.", + "command": "npx", + "args": ["-y", "@modelcontextprotocol/server-everything"], + "shadowing": "allow" + } + } +} diff --git a/.changeset/code-mode-platform-apis.md b/.changeset/code-mode-platform-apis.md new file mode 100644 index 00000000..f606b725 --- /dev/null +++ b/.changeset/code-mode-platform-apis.md @@ -0,0 +1,8 @@ +--- +"@caplets/core": minor +"caplets": minor +"@caplets/opencode": minor +"@caplets/pi": minor +--- + +Expand Code Mode with browser-like platform APIs for data manipulation, encoding, timers, crypto randomness, and web object compatibility while keeping direct network and Node APIs unavailable. diff --git a/.changeset/google-discovery-media.md b/.changeset/google-discovery-media.md new file mode 100644 index 00000000..77c59d31 --- /dev/null +++ b/.changeset/google-discovery-media.md @@ -0,0 +1,8 @@ +--- +"@caplets/core": minor +"caplets": minor +"@caplets/opencode": minor +"@caplets/pi": minor +--- + +Add Google Discovery API Caplets with inferred OAuth scopes, operation filters, media upload/download handling, and shared HTTP-like media artifacts. diff --git a/.changeset/wide-humans-enter.md b/.changeset/wide-humans-enter.md new file mode 100644 index 00000000..7a33bee1 --- /dev/null +++ b/.changeset/wide-humans-enter.md @@ -0,0 +1,8 @@ +--- +"@caplets/core": minor +"@caplets/opencode": patch +"caplets": patch +"@caplets/pi": patch +--- + +Add stdlib to code_mode sandbox diff --git a/.codex/config.toml b/.codex/config.toml index f7fa3f8f..92219e58 100644 --- a/.codex/config.toml +++ b/.codex/config.toml @@ -1,17 +1,12 @@ #:schema https://raw.githubusercontent.com/openai/codex/refs/heads/main/codex-rs/core/config.schema.json [mcp_servers.caplets-remote] +enabled = false command = "node" args = ["./packages/cli/dist/index.js", "attach", "--remote-url", "http://localhost:5387"] - -[mcp_servers.caplets-remote.env] -CAPLETS_MODE = "remote" -CAPLETS_CONFIG = "./.caplets/local.config.json" +env = { CAPLETS_MODE = "remote", CAPLETS_CONFIG = "./.caplets/local.config.json" } [mcp_servers.caplets-local] command = "node" args = ["./packages/cli/dist/index.js", "serve", "--transport", "stdio"] - -[mcp_servers.caplets-local.env] -CAPLETS_MODE = "local" -CAPLETS_CONFIG = "./.caplets/local.config.json" +env = { CAPLETS_MODE = "local", CAPLETS_CONFIG = "./.caplets/local.config.json" } diff --git a/.husky/post-commit b/.husky/post-commit deleted file mode 100755 index bea6e6cf..00000000 --- a/.husky/post-commit +++ /dev/null @@ -1,2 +0,0 @@ -pnpm exec tsx ./scripts/commit-byterover-context.ts - diff --git a/.husky/pre-push b/.husky/pre-push index 99368628..347ade03 100755 --- a/.husky/pre-push +++ b/.husky/pre-push @@ -1,2 +1 @@ pnpm verify -pnpm exec tsx ./scripts/commit-byterover-context.ts --check diff --git a/CONTEXT.md b/CONTEXT.md new file mode 100644 index 00000000..0bc75923 --- /dev/null +++ b/CONTEXT.md @@ -0,0 +1,33 @@ +# Caplets + +Caplets is a capability gateway for coding agents. This glossary names the product concepts used when describing Caplet configuration and runtime behavior. + +## Language + +**Code Mode**: +A Caplets exposure surface where configured backends appear as typed handles inside a bounded script workflow. +_Avoid_: JavaScript shell, Node REPL, sandbox boundary + +**Code Mode standard library**: +The safe built-in runtime API surface available to Code Mode scripts for URL, text, byte, binary-payload, timing, and web-compatible data handling without granting host or network access. +_Avoid_: Full Node API, unrestricted host API, local machine access + +**Compatibility global**: +A standards-compatible API exposed directly on the Code Mode global scope so ordinary JavaScript examples work without Caplets-specific wrappers. +_Avoid_: Caplets utility namespace, custom helper surface + +**Code Mode standard library v1**: +The first compatibility-global set for Code Mode: URL and query handling, text encoding, base64 and Buffer-compatible byte handling, web binary payload objects, safe crypto primitives, timing, scheduling, and structured cloning. +_Avoid_: Minimal encoding helpers, full Node compatibility + +**Code Mode bridge value**: +A value passed between a Code Mode script and a Caplets backend call, including JSON-compatible data and supported standard-library binary payload objects. +_Avoid_: JSON-only payload, host object leak + +**Google Discovery API backend**: +A Caplets backend family for Google APIs whose machine-readable contract is a Google Discovery document rather than an OpenAPI document. +_Avoid_: Discovery backend, discovery document backend, OpenAPI-backed Google API + +**Media artifact**: +A file-backed Caplets result for response content that should not be returned inline, such as binary media or oversized textual content. +_Avoid_: Inline blob, base64 result, download blob diff --git a/apps/docs/package.json b/apps/docs/package.json index a47d6124..b914b5b2 100644 --- a/apps/docs/package.json +++ b/apps/docs/package.json @@ -22,7 +22,5 @@ "devDependencies": { "vite": "^7.3.5" }, - "engines": { - "node": ">=24" - } + "packageManager": "pnpm@11.7.0" } diff --git a/apps/docs/src/content/docs/capabilities.mdx b/apps/docs/src/content/docs/capabilities.mdx index 92ac61a6..d68d60bf 100644 --- a/apps/docs/src/content/docs/capabilities.mdx +++ b/apps/docs/src/content/docs/capabilities.mdx @@ -3,8 +3,9 @@ title: Capabilities description: Add capability sources for agents. --- -Caplets can wrap MCP servers, OpenAPI specs, GraphQL endpoints, simple HTTP APIs, curated -CLI commands, and shared Markdown Caplet files such as `CAPLET.md`. +Caplets can wrap MCP servers, OpenAPI specs, Google Discovery APIs, GraphQL endpoints, +simple HTTP APIs, curated CLI commands, and shared Markdown Caplet files such as +`CAPLET.md`. ## Start with OSV @@ -30,11 +31,16 @@ Examples: ```sh caplets add mcp docs --command npx --arg -y --arg @upstash/context7-mcp caplets add openapi users --spec ./openapi.json --base-url https://api.example.com +caplets add google-discovery drive --discovery-url https://www.googleapis.com/discovery/v1/apis/drive/v3/rest caplets add graphql catalog --endpoint-url https://api.example.com/graphql --schema ./schema.graphql caplets add http status-api --base-url https://api.example.com --action get_status:GET:/status/{service} caplets add cli repo-tools --repo . --include git,gh,package ``` +Google Discovery Caplets use Google's Discovery documents directly. They can infer request +base URLs and OAuth scopes from the final exposed operation set, so narrow broad APIs with +`includeOperations` and `excludeOperations` when a capability only needs a subset. + Inspect from the CLI: ```sh @@ -50,5 +56,9 @@ caplets call-tool osv query_package_version --args '{"ecosystem":"npm","name":"r Good Caplets are narrow and named by the job they help an agent do. Prefer a few focused capabilities over one broad catch-all backend. +HTTP-like Caplets return small JSON and text inline. Binary downloads, Google media +downloads, and oversized responses are returned as media artifact metadata with local paths +or artifact references that agents can pass into later upload calls. + Use [Configuration](/configuration/) when a capability should be shared through `.caplets/config.json`. diff --git a/apps/docs/src/content/docs/code-mode.mdx b/apps/docs/src/content/docs/code-mode.mdx index c75c325f..34910366 100644 --- a/apps/docs/src/content/docs/code-mode.mdx +++ b/apps/docs/src/content/docs/code-mode.mdx @@ -52,6 +52,28 @@ uses handles such as `caplets.osv.searchTools(...)` and `caplets.osv.callTool(.. For most tasks, keep bulky raw payloads inside the Code Mode script and return compact JSON with the evidence the user needs. +## Platform globals + +Code Mode also installs standard JavaScript platform globals at runtime so scripts can +shape data without reaching for direct I/O: + +- Pure utilities: `atob`, `btoa`, a documented `Buffer` subset, and `structuredClone`. +- Web data objects: `URL`, `URLSearchParams`, `TextEncoder`, `TextDecoder`, `Headers`, + `Blob`, `File`, `FormData`, `ReadableStream`, `WritableStream`, `TransformStream`, + `AbortController`, `AbortSignal`, `Request`, and `Response`. +- Scheduling: `queueMicrotask`, `setTimeout`, `clearTimeout`, `setInterval`, and + `clearInterval`. +- Randomness: `crypto.randomUUID()` and `crypto.getRandomValues(...)`. + +These are runtime globals for JavaScript muscle memory. They are intentionally not +enumerated in the generated Code Mode declaration payload or tool prompt, which stays +focused on Caplet handles, debug helpers, and `console` to avoid token bloat. + +Direct I/O is intentionally unavailable. `fetch` is blocked; use Caplet handles for +network, tool, resource, prompt, or command I/O. Node and module APIs such as `process`, +`require`, imports, filesystem access, child processes, and direct network access are +not available inside Code Mode scripts. + ## Return shape Prefer compact decision-ready data: diff --git a/apps/docs/src/content/docs/reference/caplet-files.mdx b/apps/docs/src/content/docs/reference/caplet-files.mdx index 8fcdf537..bb4a598e 100644 --- a/apps/docs/src/content/docs/reference/caplet-files.mdx +++ b/apps/docs/src/content/docs/reference/caplet-files.mdx @@ -7,7 +7,7 @@ description: "Generated reference for Markdown Caplet file frontmatter." Caplet files are Markdown files with YAML frontmatter. Store a single-file Caplet as `osv.md`, or use a folder with `CAPLET.md` when the capability needs nearby assets. -Canonical schema: [https://caplets.dev/caplet-frontmatter.schema.json](https://caplets.dev/caplet-frontmatter.schema.json) +Canonical schema: [https://caplets.dev/caplet.schema.json](https://caplets.dev/caplet.schema.json) Status values are `Required` or `Optional` according to the schema's top-level `required` array. @@ -58,25 +58,26 @@ Use this Caplet when an agent needs the current repository's local test signal. ## Top-level fields -| Field | Status | Type | Description | -| ----------------- | -------- | ------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------ | -| `$schema` | Optional | string | Optional JSON Schema URL for editor validation. | -| `name` | Required | string | Human-readable Caplet display name. | -| `description` | Required | string | Compact capability description shown before the full Caplet card is disclosed. | -| `tags` | Optional | array | Optional tags for grouping or searching Caplets. | -| `exposure` | Optional | "direct" \| "progressive" \| "code_mode" \| "direct_and_code_mode" \| "progressive_and_code_mode" | How this Caplet is exposed to agents. | -| `shadowing` | Optional | "forbid" \| "allow" | Whether attached local Caplets may shadow this remote Caplet ID. | -| `useWhen` | Optional | string | When agents should prefer this Caplet or configured action. | -| `avoidWhen` | Optional | string | When agents should avoid this Caplet or configured action. | -| `setup` | Optional | object | Optional explicit setup and verification metadata for this Caplet. | -| `projectBinding` | Optional | object | Project Binding requirements for Caplets that need an attached project. | -| `runtime` | Optional | object | Runtime feature and resource requirements for hosted execution. | -| `mcpServer` | Optional | object | MCP server backend configuration for this Caplet. | -| `openapiEndpoint` | Optional | object | OpenAPI endpoint backend configuration for this Caplet. | -| `graphqlEndpoint` | Optional | object | GraphQL endpoint backend configuration for this Caplet. | -| `httpApi` | Optional | object | HTTP API backend configuration for this Caplet. | -| `cliTools` | Optional | object | CLI tools backend configuration for this Caplet. | -| `capletSet` | Optional | object | Nested Caplet collection backend configuration for this Caplet. | +| Field | Status | Type | Description | +| -------------------- | -------- | ------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------ | +| `$schema` | Optional | string | Optional JSON Schema for editor validation. | +| `name` | Required | string | Human-readable Caplet display name. | +| `description` | Required | string | Compact capability description shown before the full Caplet card is disclosed. | +| `tags` | Optional | array | Optional tags for grouping or searching Caplets. | +| `exposure` | Optional | "direct" \| "progressive" \| "code_mode" \| "direct_and_code_mode" \| "progressive_and_code_mode" | How this Caplet is exposed to agents. | +| `shadowing` | Optional | "forbid" \| "allow" | Whether attached local Caplets may shadow this remote Caplet ID. | +| `useWhen` | Optional | string | When agents should prefer this Caplet or configured action. | +| `avoidWhen` | Optional | string | When agents should avoid this Caplet or configured action. | +| `setup` | Optional | object | Optional explicit setup and verification metadata for this Caplet. | +| `projectBinding` | Optional | object | Project Binding requirements for Caplets that need an attached project. | +| `runtime` | Optional | object | Runtime feature and resource requirements for hosted execution. | +| `mcpServer` | Optional | object | MCP server backend configuration for this Caplet. | +| `openapiEndpoint` | Optional | object | OpenAPI endpoint backend configuration for this Caplet. | +| `googleDiscoveryApi` | Optional | object | Google Discovery API backend configuration for this Caplet. | +| `graphqlEndpoint` | Optional | object | GraphQL endpoint backend configuration for this Caplet. | +| `httpApi` | Optional | object | HTTP API backend configuration for this Caplet. | +| `cliTools` | Optional | object | CLI tools backend configuration for this Caplet. | +| `capletSet` | Optional | object | Nested Caplet collection backend configuration for this Caplet. | ## Major sections diff --git a/apps/docs/src/content/docs/reference/code-mode-api.mdx b/apps/docs/src/content/docs/reference/code-mode-api.mdx index 310401e0..707f058d 100644 --- a/apps/docs/src/content/docs/reference/code-mode-api.mdx +++ b/apps/docs/src/content/docs/reference/code-mode-api.mdx @@ -1,12 +1,14 @@ --- title: "Code Mode API" -description: "Generated reference for the TypeScript surface available inside `caplets__code_mode`." +description: "Generated reference for the lean TypeScript handle surface available inside `caplets__code_mode`." --- {/* */} Code Mode runs TypeScript with generated `caplets.` handles. Prefer discovery methods first, then call downstream tools with compact, decision-ready inputs and outputs. +This reference intentionally lists the lean generated declaration surface: Caplet handles, debug helpers, shared result types, and `console`. Runtime platform globals such as `URL`, `TextEncoder`, `Headers`, timers, and `crypto.randomUUID()` are available for ordinary JavaScript data shaping, but they are not enumerated here or in the Code Mode tool prompt. + ## Common Code Mode recipes Guard against unavailable backends before you spend work on discovery: diff --git a/apps/docs/src/content/docs/reference/config.mdx b/apps/docs/src/content/docs/reference/config.mdx index afa40845..3007256c 100644 --- a/apps/docs/src/content/docs/reference/config.mdx +++ b/apps/docs/src/content/docs/reference/config.mdx @@ -62,20 +62,21 @@ Public OpenAPI endpoint: ## Top-level fields -| Field | Status | Type | Description | -| -------------------- | -------- | ------- | ------------------------------------------------------ | -| `$schema` | Optional | string | Optional JSON Schema URL for editor validation. | -| `version` | Optional | number | Caplets config schema version. | -| `defaultSearchLimit` | Optional | integer | Default maximum number of same-server search results. | -| `maxSearchLimit` | Optional | integer | Maximum accepted search_tools limit. | -| `completion` | Optional | object | Shell completion discovery timeout and cache settings. | -| `options` | Optional | object | Global Caplets runtime options. | -| `mcpServers` | Optional | object | Downstream MCP servers keyed by stable server ID. | -| `openapiEndpoints` | Optional | object | OpenAPI endpoints keyed by stable Caplet ID. | -| `graphqlEndpoints` | Optional | object | GraphQL endpoints keyed by stable Caplet ID. | -| `httpApis` | Optional | object | HTTP APIs keyed by stable Caplet ID. | -| `cliTools` | Optional | object | CLI tools keyed by stable Caplet ID. | -| `capletSets` | Optional | object | Nested Caplet collections keyed by stable Caplet ID. | +| Field | Status | Type | Description | +| --------------------- | -------- | ------- | ------------------------------------------------------ | +| `$schema` | Optional | string | Optional JSON Schema for editor validation. | +| `version` | Optional | number | Caplets config schema version. | +| `defaultSearchLimit` | Optional | integer | Default maximum number of same-server search results. | +| `maxSearchLimit` | Optional | integer | Maximum accepted search_tools limit. | +| `completion` | Optional | object | Shell completion discovery timeout and cache settings. | +| `options` | Optional | object | Global Caplets runtime options. | +| `mcpServers` | Optional | object | Downstream MCP servers keyed by stable server ID. | +| `openapiEndpoints` | Optional | object | OpenAPI endpoints keyed by stable Caplet ID. | +| `googleDiscoveryApis` | Optional | object | Google Discovery APIs keyed by stable Caplet ID. | +| `graphqlEndpoints` | Optional | object | GraphQL endpoints keyed by stable Caplet ID. | +| `httpApis` | Optional | object | HTTP APIs keyed by stable Caplet ID. | +| `cliTools` | Optional | object | CLI tools keyed by stable Caplet ID. | +| `capletSets` | Optional | object | Nested Caplet collections keyed by stable Caplet ID. | ## Major sections diff --git a/apps/docs/src/content/docs/troubleshooting.mdx b/apps/docs/src/content/docs/troubleshooting.mdx index 1752c6ab..de3eb8dd 100644 --- a/apps/docs/src/content/docs/troubleshooting.mdx +++ b/apps/docs/src/content/docs/troubleshooting.mdx @@ -90,6 +90,32 @@ return await h.callTool("query_package_version", { Use the returned `error.code`, `error.message`, and Caplet ID to repair the call. Do not paste a full raw downstream payload back to the agent unless the error depends on it. +### Google auth asks for login again + +Expected symptom: a Google Discovery Caplet worked before, but `auth login` or a tool call +now reports missing scopes after you changed operation filters or the Discovery document. + +Google Discovery Caplets infer OAuth scopes from the exposed operation set unless +`auth.scopes` is configured. Re-run login for that Caplet so the stored token metadata +matches the current scope set: + +```sh +caplets auth login +caplets doctor +``` + +If the Caplet should never ask for newly inferred scopes, configure `auth.scopes` +explicitly and keep `includeOperations` narrow. + +### Download returned an artifact + +Expected symptom: a tool result contains `body.artifact` instead of inline bytes or text. + +Binary downloads, Google media downloads, and oversized HTTP-like responses are written as +Caplets media artifacts. Use the artifact `path` locally, or pass the artifact URI back to +another media-capable tool as `media.artifact`. If you need a specific destination for a +download, retry with `filename` or `outputPath` when the tool schema exposes those fields. + ### Remote attach fails Expected symptom: remote mode starts, but attach cannot reach the runtime or authenticate. diff --git a/apps/landing/package.json b/apps/landing/package.json index 305f8dfc..3462d114 100644 --- a/apps/landing/package.json +++ b/apps/landing/package.json @@ -26,8 +26,5 @@ "devDependencies": { "@types/three": "^0.184.1", "vite": "^7.3.5" - }, - "engines": { - "node": ">=24" } } diff --git a/apps/landing/public/caplet-frontmatter.schema.json b/apps/landing/public/caplet.schema.json similarity index 86% rename from apps/landing/public/caplet-frontmatter.schema.json rename to apps/landing/public/caplet.schema.json index 7200d110..73f6898d 100644 --- a/apps/landing/public/caplet-frontmatter.schema.json +++ b/apps/landing/public/caplet.schema.json @@ -1,14 +1,13 @@ { "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://caplets.dev/caplet-frontmatter.schema.json", + "$id": "https://caplets.dev/caplet.schema.json", "title": "Caplet file frontmatter", "description": "YAML frontmatter schema for a Markdown Caplet file.", "type": "object", "properties": { "$schema": { - "description": "Optional JSON Schema URL for editor validation.", - "type": "string", - "format": "uri" + "description": "Optional JSON Schema for editor validation.", + "type": "string" }, "name": { "type": "string", @@ -717,6 +716,265 @@ "additionalProperties": false, "description": "OpenAPI endpoint backend configuration for this Caplet." }, + "googleDiscoveryApi": { + "type": "object", + "properties": { + "discoveryPath": { + "description": "Local Google Discovery document path.", + "type": "string", + "minLength": 1 + }, + "discoveryUrl": { + "description": "Remote Google Discovery document URL.", + "type": "string", + "minLength": 1 + }, + "baseUrl": { + "description": "Override base URL for Google API requests.", + "type": "string", + "minLength": 1 + }, + "auth": { + "oneOf": [ + { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "none" + } + }, + "required": ["type"], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "bearer" + }, + "token": { + "type": "string", + "minLength": 1 + } + }, + "required": ["type", "token"], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "headers" + }, + "headers": { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": { + "type": "string", + "minLength": 1 + } + } + }, + "required": ["type", "headers"], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "oauth2" + }, + "authorizationUrl": { + "type": "string", + "minLength": 1 + }, + "tokenUrl": { + "type": "string", + "minLength": 1 + }, + "issuer": { + "type": "string", + "minLength": 1 + }, + "resourceMetadataUrl": { + "type": "string", + "minLength": 1 + }, + "authorizationServerMetadataUrl": { + "type": "string", + "minLength": 1 + }, + "openidConfigurationUrl": { + "type": "string", + "minLength": 1 + }, + "clientMetadataUrl": { + "type": "string", + "minLength": 1 + }, + "clientId": { + "type": "string", + "minLength": 1 + }, + "clientSecret": { + "type": "string", + "minLength": 1 + }, + "scopes": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + } + }, + "redirectUri": { + "type": "string", + "minLength": 1 + } + }, + "required": ["type"], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "oidc" + }, + "authorizationUrl": { + "type": "string", + "minLength": 1 + }, + "tokenUrl": { + "type": "string", + "minLength": 1 + }, + "issuer": { + "type": "string", + "minLength": 1 + }, + "resourceMetadataUrl": { + "type": "string", + "minLength": 1 + }, + "authorizationServerMetadataUrl": { + "type": "string", + "minLength": 1 + }, + "openidConfigurationUrl": { + "type": "string", + "minLength": 1 + }, + "clientMetadataUrl": { + "type": "string", + "minLength": 1 + }, + "clientId": { + "type": "string", + "minLength": 1 + }, + "clientSecret": { + "type": "string", + "minLength": 1 + }, + "scopes": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + } + }, + "redirectUri": { + "type": "string", + "minLength": 1 + } + }, + "required": ["type"], + "additionalProperties": false + } + ], + "description": "Explicit Google API request auth config. Use {\"type\":\"none\"} for public APIs." + }, + "requestTimeoutMs": { + "description": "Timeout in milliseconds for Google API HTTP requests.", + "type": "integer", + "exclusiveMinimum": 0, + "maximum": 9007199254740991 + }, + "operationCacheTtlMs": { + "description": "Milliseconds Google Discovery operation metadata stays fresh. Set 0 to refresh every time.", + "type": "integer", + "minimum": 0, + "maximum": 9007199254740991 + }, + "includeOperations": { + "type": "array", + "items": { + "type": "string", + "minLength": 1, + "maxLength": 160 + } + }, + "excludeOperations": { + "type": "array", + "items": { + "type": "string", + "minLength": 1, + "maxLength": 160 + } + }, + "disabled": { + "description": "When true, omit this Caplet from discovery.", + "type": "boolean" + }, + "projectBinding": { + "type": "object", + "properties": { + "required": { + "type": "boolean", + "const": true + } + }, + "required": ["required"], + "additionalProperties": false, + "description": "Project Binding requirements for Caplets that need an attached project." + }, + "runtime": { + "type": "object", + "properties": { + "features": { + "type": "array", + "items": { + "type": "string", + "enum": ["docker", "browser"] + } + }, + "resources": { + "type": "object", + "properties": { + "class": { + "type": "string", + "enum": ["standard", "large", "heavy"] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false, + "description": "Runtime feature and resource requirements for hosted execution." + } + }, + "required": ["auth"], + "additionalProperties": false, + "description": "Google Discovery API backend configuration for this Caplet." + }, "graphqlEndpoint": { "type": "object", "properties": { diff --git a/apps/landing/public/config.schema.json b/apps/landing/public/config.schema.json index 3ec7d1ce..9a6d84be 100644 --- a/apps/landing/public/config.schema.json +++ b/apps/landing/public/config.schema.json @@ -6,9 +6,8 @@ "type": "object", "properties": { "$schema": { - "description": "Optional JSON Schema URL for editor validation.", - "type": "string", - "format": "uri" + "description": "Optional JSON Schema for editor validation.", + "type": "string" }, "version": { "default": 1, @@ -962,6 +961,437 @@ "additionalProperties": false } }, + "googleDiscoveryApis": { + "default": {}, + "description": "Google Discovery APIs keyed by stable Caplet ID.", + "type": "object", + "propertyNames": { + "type": "string", + "pattern": "^[a-zA-Z0-9_-]{1,64}$" + }, + "additionalProperties": { + "type": "object", + "properties": { + "name": { + "type": "string", + "minLength": 1, + "maxLength": 80, + "description": "Human-readable Google Discovery API display name." + }, + "description": { + "type": "string", + "description": "Capability description shown to agents before Google Discovery operations are disclosed." + }, + "discoveryPath": { + "description": "Local Google Discovery document path.", + "type": "string", + "minLength": 1 + }, + "discoveryUrl": { + "description": "Remote Google Discovery document URL.", + "type": "string", + "format": "uri" + }, + "baseUrl": { + "description": "Override base URL for Google API requests.", + "type": "string", + "format": "uri" + }, + "includeOperations": { + "type": "array", + "items": { + "type": "string", + "minLength": 1, + "maxLength": 160 + } + }, + "excludeOperations": { + "type": "array", + "items": { + "type": "string", + "minLength": 1, + "maxLength": 160 + } + }, + "auth": { + "oneOf": [ + { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "none" + } + }, + "required": ["type"], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "bearer" + }, + "token": { + "type": "string", + "minLength": 1 + } + }, + "required": ["type", "token"], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "headers" + }, + "headers": { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": { + "type": "string", + "minLength": 1 + } + } + }, + "required": ["type", "headers"], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "oauth2" + }, + "authorizationUrl": { + "type": "string", + "format": "uri" + }, + "tokenUrl": { + "type": "string", + "format": "uri" + }, + "issuer": { + "type": "string", + "format": "uri" + }, + "resourceMetadataUrl": { + "type": "string", + "format": "uri" + }, + "authorizationServerMetadataUrl": { + "type": "string", + "format": "uri" + }, + "openidConfigurationUrl": { + "type": "string", + "format": "uri" + }, + "clientMetadataUrl": { + "type": "string", + "format": "uri" + }, + "clientId": { + "type": "string", + "minLength": 1 + }, + "clientSecret": { + "type": "string", + "minLength": 1 + }, + "scopes": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + } + }, + "redirectUri": { + "type": "string", + "format": "uri" + } + }, + "required": ["type"], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "oidc" + }, + "authorizationUrl": { + "type": "string", + "format": "uri" + }, + "tokenUrl": { + "type": "string", + "format": "uri" + }, + "issuer": { + "type": "string", + "format": "uri" + }, + "resourceMetadataUrl": { + "type": "string", + "format": "uri" + }, + "authorizationServerMetadataUrl": { + "type": "string", + "format": "uri" + }, + "openidConfigurationUrl": { + "type": "string", + "format": "uri" + }, + "clientMetadataUrl": { + "type": "string", + "format": "uri" + }, + "clientId": { + "type": "string", + "minLength": 1 + }, + "clientSecret": { + "type": "string", + "minLength": 1 + }, + "scopes": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + } + }, + "redirectUri": { + "type": "string", + "format": "uri" + } + }, + "required": ["type"], + "additionalProperties": false + } + ], + "description": "Explicit Google API request auth config. Use {\"type\":\"none\"} for public APIs." + }, + "tags": { + "type": "array", + "items": { + "type": "string", + "minLength": 1, + "maxLength": 80 + } + }, + "exposure": { + "type": "string", + "enum": [ + "direct", + "progressive", + "code_mode", + "direct_and_code_mode", + "progressive_and_code_mode" + ], + "description": "How this Caplet is exposed to agents." + }, + "shadowing": { + "default": "forbid", + "description": "Whether attached local Caplets may shadow this remote Caplet ID.", + "type": "string", + "enum": ["forbid", "allow"] + }, + "useWhen": { + "description": "When agents should prefer this Caplet or configured action.", + "type": "string", + "minLength": 1, + "maxLength": 500 + }, + "avoidWhen": { + "description": "When agents should avoid this Caplet or configured action.", + "type": "string", + "minLength": 1, + "maxLength": 500 + }, + "setup": { + "type": "object", + "properties": { + "commands": { + "type": "array", + "items": { + "type": "object", + "properties": { + "label": { + "type": "string", + "minLength": 1, + "description": "Human-readable setup or verification step label." + }, + "command": { + "type": "string", + "minLength": 1, + "description": "Executable command to spawn without a shell." + }, + "args": { + "description": "Arguments passed to the command.", + "type": "array", + "items": { + "type": "string" + } + }, + "env": { + "description": "Additional environment variables.", + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": { + "type": "string" + } + }, + "cwd": { + "description": "Working directory for this command.", + "type": "string", + "minLength": 1 + }, + "timeoutMs": { + "type": "integer", + "exclusiveMinimum": 0, + "maximum": 9007199254740991 + }, + "maxOutputBytes": { + "type": "integer", + "exclusiveMinimum": 0, + "maximum": 9007199254740991 + } + }, + "required": ["label", "command"], + "additionalProperties": false + } + }, + "verify": { + "type": "array", + "items": { + "type": "object", + "properties": { + "label": { + "type": "string", + "minLength": 1, + "description": "Human-readable setup or verification step label." + }, + "command": { + "type": "string", + "minLength": 1, + "description": "Executable command to spawn without a shell." + }, + "args": { + "description": "Arguments passed to the command.", + "type": "array", + "items": { + "type": "string" + } + }, + "env": { + "description": "Additional environment variables.", + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": { + "type": "string" + } + }, + "cwd": { + "description": "Working directory for this command.", + "type": "string", + "minLength": 1 + }, + "timeoutMs": { + "type": "integer", + "exclusiveMinimum": 0, + "maximum": 9007199254740991 + }, + "maxOutputBytes": { + "type": "integer", + "exclusiveMinimum": 0, + "maximum": 9007199254740991 + } + }, + "required": ["label", "command"], + "additionalProperties": false + } + } + }, + "additionalProperties": false + }, + "projectBinding": { + "type": "object", + "properties": { + "required": { + "type": "boolean", + "const": true, + "description": "Requires Project Binding before this Caplet can run." + } + }, + "required": ["required"], + "additionalProperties": false, + "description": "Project Binding requirements for Caplets that need an attached project." + }, + "runtime": { + "type": "object", + "properties": { + "features": { + "type": "array", + "items": { + "type": "string", + "enum": ["docker", "browser"] + }, + "description": "Runtime features required by this Caplet." + }, + "resources": { + "description": "Hosted sandbox resource requirements.", + "type": "object", + "properties": { + "class": { + "description": "Requested hosted sandbox resource class.", + "type": "string", + "enum": ["standard", "large", "heavy"] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false, + "description": "Runtime feature and resource requirements for hosted execution." + }, + "requestTimeoutMs": { + "default": 60000, + "description": "Timeout in milliseconds for Google Discovery HTTP requests.", + "type": "integer", + "exclusiveMinimum": 0, + "maximum": 9007199254740991 + }, + "operationCacheTtlMs": { + "default": 30000, + "description": "Milliseconds Google Discovery operation metadata stays fresh. Set 0 to refresh every time.", + "type": "integer", + "minimum": 0, + "maximum": 9007199254740991 + }, + "disabled": { + "default": false, + "description": "When true, omit this Google Discovery Caplet from discovery.", + "type": "boolean" + } + }, + "required": ["name", "description", "auth"], + "additionalProperties": false + } + }, "graphqlEndpoints": { "default": {}, "description": "GraphQL endpoints keyed by stable Caplet ID.", diff --git a/apps/landing/test/schema-assets.test.ts b/apps/landing/test/schema-assets.test.ts index 1a7c6a34..a3f12188 100644 --- a/apps/landing/test/schema-assets.test.ts +++ b/apps/landing/test/schema-assets.test.ts @@ -7,7 +7,7 @@ const repoRoot = join(import.meta.dirname, "../../.."); describe("schema assets", () => { it.each([ ["config.schema.json", "schemas/caplets-config.schema.json"], - ["caplet-frontmatter.schema.json", "schemas/caplet.schema.json"], + ["caplet.schema.json", "schemas/caplet.schema.json"], ])("publishes %s from the generated schema source", (assetName, schemaPath) => { const publicAsset = readFileSync(join(repoRoot, "apps/landing/public", assetName), "utf8"); const generatedSchema = readFileSync(join(repoRoot, schemaPath), "utf8"); diff --git a/caplets/ast-grep/CAPLET.md b/caplets/ast-grep/CAPLET.md index 34b26b66..c8ef0647 100644 --- a/caplets/ast-grep/CAPLET.md +++ b/caplets/ast-grep/CAPLET.md @@ -1,5 +1,5 @@ --- -# yaml-language-server: $schema=https://caplets.dev/caplet-frontmatter.schema.json +# yaml-language-server: $schema=https://caplets.dev/caplet.schema.json name: ast-grep description: Search, scan, test, rewrite, and scaffold ast-grep rules through curated MCP tools. tags: diff --git a/caplets/coding-agent-toolkit/CAPLET.md b/caplets/coding-agent-toolkit/CAPLET.md index d6656e92..24cfd15b 100644 --- a/caplets/coding-agent-toolkit/CAPLET.md +++ b/caplets/coding-agent-toolkit/CAPLET.md @@ -1,5 +1,5 @@ --- -# yaml-language-server: $schema=https://caplets.dev/caplet-frontmatter.schema.json +# yaml-language-server: $schema=https://caplets.dev/caplet.schema.json name: Coding Agent Toolkit description: self-contained nested toolkit of high-value Caplets for coding agents. tags: diff --git a/caplets/context7/CAPLET.md b/caplets/context7/CAPLET.md index 3f33abff..5113631f 100644 --- a/caplets/context7/CAPLET.md +++ b/caplets/context7/CAPLET.md @@ -1,5 +1,5 @@ --- -# yaml-language-server: $schema=https://caplets.dev/caplet-frontmatter.schema.json +# yaml-language-server: $schema=https://caplets.dev/caplet.schema.json name: Context7 Documentation description: Fetch current library and framework documentation through Context7 before using version-sensitive APIs. tags: diff --git a/caplets/deepwiki/CAPLET.md b/caplets/deepwiki/CAPLET.md index 9d72db66..486c429c 100644 --- a/caplets/deepwiki/CAPLET.md +++ b/caplets/deepwiki/CAPLET.md @@ -1,5 +1,5 @@ --- -# yaml-language-server: $schema=https://caplets.dev/caplet-frontmatter.schema.json +# yaml-language-server: $schema=https://caplets.dev/caplet.schema.json name: DeepWiki description: Query repository-focused documentation and codebase explanations through DeepWiki's MCP service. tags: diff --git a/caplets/github/CAPLET.md b/caplets/github/CAPLET.md index 542e7973..1ace6cff 100644 --- a/caplets/github/CAPLET.md +++ b/caplets/github/CAPLET.md @@ -1,5 +1,5 @@ --- -# yaml-language-server: $schema=https://caplets.dev/caplet-frontmatter.schema.json +# yaml-language-server: $schema=https://caplets.dev/caplet.schema.json name: GitHub description: Inspect and manage GitHub repositories, issues, pull requests, branches, commits, and code review workflows. tags: diff --git a/caplets/linear/CAPLET.md b/caplets/linear/CAPLET.md index 34c08cfe..ce05afa3 100644 --- a/caplets/linear/CAPLET.md +++ b/caplets/linear/CAPLET.md @@ -1,5 +1,5 @@ --- -# yaml-language-server: $schema=https://caplets.dev/caplet-frontmatter.schema.json +# yaml-language-server: $schema=https://caplets.dev/caplet.schema.json name: Linear description: Plan and track product work in Linear by reading teams, projects, cycles, issues, comments, and workflow state. tags: diff --git a/caplets/lsp/CAPLET.md b/caplets/lsp/CAPLET.md index 796e6d6d..5f3513b0 100644 --- a/caplets/lsp/CAPLET.md +++ b/caplets/lsp/CAPLET.md @@ -1,5 +1,5 @@ --- -# yaml-language-server: $schema=https://caplets.dev/caplet-frontmatter.schema.json +# yaml-language-server: $schema=https://caplets.dev/caplet.schema.json name: LSP description: Language Server Protocol tools for project-aware code intelligence through language-server-mcp. tags: diff --git a/caplets/npm/CAPLET.md b/caplets/npm/CAPLET.md index 6eee131c..c7dbb434 100644 --- a/caplets/npm/CAPLET.md +++ b/caplets/npm/CAPLET.md @@ -1,5 +1,5 @@ --- -# yaml-language-server: $schema=https://caplets.dev/caplet-frontmatter.schema.json +# yaml-language-server: $schema=https://caplets.dev/caplet.schema.json name: npm Registry description: Query package metadata, versions, dist-tags, and search results from the public npm registry. tags: diff --git a/caplets/osv/CAPLET.md b/caplets/osv/CAPLET.md index 2cbccfac..b9709b3a 100644 --- a/caplets/osv/CAPLET.md +++ b/caplets/osv/CAPLET.md @@ -1,5 +1,5 @@ --- -# yaml-language-server: $schema=https://caplets.dev/caplet-frontmatter.schema.json +# yaml-language-server: $schema=https://caplets.dev/caplet.schema.json name: OSV Vulnerabilities description: Query OSV.dev vulnerability data through explicit HTTP actions. tags: diff --git a/caplets/playwright/CAPLET.md b/caplets/playwright/CAPLET.md index 477e6001..83f6c6ad 100644 --- a/caplets/playwright/CAPLET.md +++ b/caplets/playwright/CAPLET.md @@ -1,5 +1,5 @@ --- -# yaml-language-server: $schema=https://caplets.dev/caplet-frontmatter.schema.json +# yaml-language-server: $schema=https://caplets.dev/caplet.schema.json name: Playwright description: Drive a browser through Playwright MCP for frontend testing, inspection, and automation workflows. tags: diff --git a/caplets/pypi/CAPLET.md b/caplets/pypi/CAPLET.md index dacc7416..09bbe3e3 100644 --- a/caplets/pypi/CAPLET.md +++ b/caplets/pypi/CAPLET.md @@ -1,5 +1,5 @@ --- -# yaml-language-server: $schema=https://caplets.dev/caplet-frontmatter.schema.json +# yaml-language-server: $schema=https://caplets.dev/caplet.schema.json name: PyPI description: Query Python package metadata, releases, files, vulnerabilities, and Simple API project details from PyPI. tags: diff --git a/caplets/repo-cli/CAPLET.md b/caplets/repo-cli/CAPLET.md index c0332ea9..73499bec 100644 --- a/caplets/repo-cli/CAPLET.md +++ b/caplets/repo-cli/CAPLET.md @@ -1,5 +1,5 @@ --- -# yaml-language-server: $schema=https://caplets.dev/caplet-frontmatter.schema.json +# yaml-language-server: $schema=https://caplets.dev/caplet.schema.json name: Repository CLI description: Inspect and run common local repository workflows through curated CLI tools. tags: diff --git a/caplets/sourcegraph/CAPLET.md b/caplets/sourcegraph/CAPLET.md index ef62c3a2..b6c04c70 100644 --- a/caplets/sourcegraph/CAPLET.md +++ b/caplets/sourcegraph/CAPLET.md @@ -1,5 +1,5 @@ --- -# yaml-language-server: $schema=https://caplets.dev/caplet-frontmatter.schema.json +# yaml-language-server: $schema=https://caplets.dev/caplet.schema.json name: Sourcegraph description: Search and inspect code across Sourcegraph using its MCP endpoint for repository-aware coding workflows. tags: diff --git a/docs/adr/0002-media-artifacts-for-non-inline-results.md b/docs/adr/0002-media-artifacts-for-non-inline-results.md new file mode 100644 index 00000000..08f3506f --- /dev/null +++ b/docs/adr/0002-media-artifacts-for-non-inline-results.md @@ -0,0 +1,20 @@ +# ADR 0002: Use Media Artifacts For Non-Inline Results + +## Status + +Accepted + +## Context + +Caplets backends can return binary media, downloads, exports, and textual responses too large to fit comfortably in structured tool output. Inline base64 is hard for coding agents to inspect, expensive in tokens, fragile when truncated, and unsuitable as a common contract across local, remote, and hosted execution. + +## Decision + +Caplets will represent binary media and oversized response content as Media artifacts by default. Local execution may return absolute paths under Caplets-managed artifact storage, while remote and hosted execution must return artifact references or links rather than fake local paths. Small JSON and text responses remain inline. + +## Consequences + +- Media-capable backends share one result contract instead of inventing backend-specific blob behavior. +- Agents should prefer file paths or artifact references for media input and output. +- Data URLs are allowed only as small-input fallback values and must not be echoed in logs, errors, or result previews. +- Backends that currently read all HTTP responses as bounded text need to route binary and oversized responses through shared artifact writing. diff --git a/docs/architecture.md b/docs/architecture.md index 5a19ec17..8adaaab9 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -14,6 +14,7 @@ Supported backend families are: - `mcpServers` - `openapiEndpoints` +- `googleDiscoveryApis` - `graphqlEndpoints` - `httpApis` - `cliTools` @@ -57,6 +58,10 @@ Code Mode is implemented under `packages/core/src/code-mode/`. The runtime generates TypeScript declarations from the current callable Caplets, statically checks the submitted script, runs it in the sandbox, bridges handle methods back to the native service, stores logs when configured, and returns JSON-serializable results with diagnostics. +Code Mode installs a browser-like, non-I/O platform surface as runtime globals for common JavaScript data shaping: base64 helpers, a minimal `Buffer` subset, `structuredClone`, URL and text encoding helpers, Web data containers such as `Headers`, `Blob`, `File`, `FormData`, streams, abort signals, `Request`/`Response`, timers, microtasks, and crypto randomness. These globals are intentionally omitted from generated Code Mode TypeScript declarations and tool prompts so the declaration payload stays focused on Caplet handles, debug helpers, and `console`. + +Direct I/O remains routed through Caplet handles. `fetch` is intentionally unavailable, and Code Mode does not expose Node process, module loading, filesystem, child process, or direct network APIs. + The intended agent pattern is one compact script: 1. choose handles @@ -89,9 +94,11 @@ Project Binding under `packages/core/src/project-binding/` connects a local proj MCP-backed Caplets preserve downstream tool results and expose resources, templates, prompts, and completion when the downstream server supports them. Direct exposure can register those downstream surfaces directly. -### OpenAPI, GraphQL, And HTTP +### OpenAPI, Google Discovery, GraphQL, And HTTP + +OpenAPI, Google Discovery, GraphQL, and HTTP backends expose explicit operation/action tools. They do not synthesize MCP resources or prompts. HTTP-like backends enforce safe URL handling, bounded response bodies, timeouts, and redacted errors. -OpenAPI, GraphQL, and HTTP backends expose explicit operation/action tools. They do not synthesize MCP resources or prompts. HTTP-like backends enforce safe URL handling, bounded response bodies, timeouts, and redacted errors. +Google Discovery backends load local or remote Google Discovery documents, infer request base URLs from the document unless overridden, expose filtered Discovery methods as tools, and infer OAuth scopes from the exposed operation set. Google media downloads and oversized or binary HTTP-like responses are written as Caplets media artifacts under the configured artifact root instead of being forced inline. ### CLI Tools diff --git a/docs/plans/2026-06-15-docs-site.md b/docs/plans/2026-06-15-docs-site.md index cb654774..0e7825b2 100644 --- a/docs/plans/2026-06-15-docs-site.md +++ b/docs/plans/2026-06-15-docs-site.md @@ -21,7 +21,7 @@ - Use Starlight components first, local Astro components second, Starwind only when Starlight is insufficient. - Use Tailwind for custom styling unless Starlight compatibility is better served by Starlight CSS variables. - Keep docs latest-only for pre-1.0, but avoid hardcoded page links that would block future versioned routes. -- Keep canonical schema URLs on `https://caplets.dev/config.schema.json` and `https://caplets.dev/caplet-frontmatter.schema.json`. +- Keep canonical schema URLs on `https://caplets.dev/config.schema.json` and `https://caplets.dev/caplet.schema.json`. - Exclude `@caplets/docs` from Changesets. - Include `@caplets/docs` in build/typecheck/verify. diff --git a/docs/plans/2026-06-16-google-discovery-api-backend-implementation.md b/docs/plans/2026-06-16-google-discovery-api-backend-implementation.md new file mode 100644 index 00000000..4b6e900b --- /dev/null +++ b/docs/plans/2026-06-16-google-discovery-api-backend-implementation.md @@ -0,0 +1,2151 @@ +# Google Discovery API Backend Implementation Plan + +> **For agentic workers:** REQUIRED SKILL: Use `subagent-driven-development` (recommended) or `executing-plans` to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Add a first-class Google Discovery API backend with inferred scopes, operation filters, comprehensive media upload/download, and shared media artifact handling for HTTP-like backends. + +**Architecture:** Add `googleDiscoveryApis` as a new top-level Caplets backend family with its own manager and native Google Discovery parser. Build shared media artifact infrastructure first, wire HTTP/OpenAPI responses through it, then implement Google Discovery on top of shared auth, HTTP request, and artifact primitives. Keep Google Discovery distinct from OpenAPI while returning the same Caplets `Tool` and `CompatibilityCallToolResult` shapes used by other backends. + +**Tech Stack:** TypeScript, Zod, Vitest, MCP SDK `Tool`/`CompatibilityCallToolResult`, existing Caplets auth/config/engine/tool surfaces, Node filesystem and fetch APIs, pnpm 11.5.0, Node >=24. + +--- + +## Source Documents + +- Design spec: `docs/specs/2026-06-16-google-discovery-api-backend.md` +- Media ADR: `docs/adr/0002-media-artifacts-for-non-inline-results.md` +- Glossary: `CONTEXT.md` +- Existing backend references: `packages/core/src/openapi.ts`, `packages/core/src/http-actions.ts`, `packages/core/src/graphql.ts` +- Existing config references: `packages/core/src/config.ts`, `packages/core/src/caplet-files-bundle.ts`, `packages/core/src/registry.ts` + +## File Structure Map + +Create: + +- `packages/core/src/media/artifacts.ts` — writes Caplets-managed media artifacts, resolves artifact references, computes hashes, and enforces output path safety. +- `packages/core/src/media/input.ts` — reads media input from `path`, `artifact`, or `dataUrl` for upload-capable backends. +- `packages/core/src/http/response.ts` — shared HTTP response reader that returns inline JSON/text or a Media artifact. +- `packages/core/src/google-discovery/types.ts` — narrow TypeScript types for the Google Discovery document shapes Caplets consumes. +- `packages/core/src/google-discovery/schema.ts` — converts Google Discovery schemas to JSON Schema-like tool schemas. +- `packages/core/src/google-discovery/operations.ts` — walks `resources.*.methods.*`, applies operation filters, resolves scopes, and builds operation descriptors. +- `packages/core/src/google-discovery/request.ts` — builds normal JSON requests and media upload/download requests. +- `packages/core/src/google-discovery/manager.ts` — `GoogleDiscoveryManager` implementation. +- `packages/core/src/google-discovery/index.ts` — public exports for the manager and helper types. +- `packages/core/test/google-discovery.test.ts` — parser, manager, auth, filtering, media, and tool-surface tests. +- `packages/core/test/media-artifacts.test.ts` — shared artifact and media input tests. +- `packages/core/test/fixtures/google-discovery/drive.discovery.json` — small Drive-like fixture with JSON, download, simple upload, multipart upload, resumable upload, scopes, and destructive operations. + +Modify: + +- `packages/core/src/config/paths.ts` — add default artifact directory export. +- `packages/core/src/config.ts` — add `GoogleDiscoveryApiConfig`, schema, normalization, merge/source/reject logic, and config JSON schema support. +- `packages/core/src/caplet-files-bundle.ts` — add `googleDiscoveryApi` frontmatter support and schema generation. +- `packages/core/src/caplet-source/parse.ts` — include Google Discovery Caplets in parsed source output. +- `packages/core/src/registry.ts` — include backend detail for `googleDiscovery`. +- `packages/core/src/engine.ts` — instantiate/update/invalidate/dispatch `GoogleDiscoveryManager`. +- `packages/core/src/tools.ts` — accept `GoogleDiscoveryManager` in `handleServerTool` and `backendFor`. +- `packages/core/src/native/service.ts` and `packages/core/src/native/tools.ts` — include Google Discovery in native service/tool guidance. +- `packages/core/src/cli/auth.ts` — include Google Discovery auth targets and resolve inferred scopes before login/refresh. +- `packages/core/src/auth.ts` and `packages/core/src/auth/store.ts` — support backend-resolved OAuth scopes and compare requested scope metadata. +- `packages/core/src/cli/add.ts` and `packages/core/src/cli.ts` — add `caplets add google-discovery`. +- `packages/core/src/cli/inspection.ts`, `packages/core/src/cli/completion-discovery.ts`, `packages/core/src/cli/setup-caplet.ts`, `packages/core/src/cli/doctor.ts` — include `googleDiscoveryApis` anywhere all Caplets are enumerated. +- `packages/core/src/remote-control/dispatch.ts` and remote add types if remote add kinds are enumerated there. +- `packages/core/src/openapi.ts` and `packages/core/src/http-actions.ts` — use shared response/artifact reader. +- `packages/core/src/code-mode/runtime-api.d.ts` and generated output only if media artifact types are added to Code Mode declarations. +- `schemas/caplets-config.schema.json`, `schemas/caplet.schema.json`, and docs generated from config/caplet schemas. +- `apps/docs/src/content/docs/reference/config.mdx`, `apps/docs/src/content/docs/reference/caplet-files.mdx`, `apps/docs/src/content/docs/capabilities.mdx`, `apps/docs/src/content/docs/troubleshooting.mdx`, `apps/docs/src/content/docs/changelog.mdx`. +- `docs/architecture.md` — add Google Discovery to backend families and HTTP-like backend contract. + +## Implementation Tasks + +### Task 1: Shared Media Artifact Infrastructure + +**Files:** + +- Create: `packages/core/src/media/artifacts.ts` +- Create: `packages/core/src/media/input.ts` +- Modify: `packages/core/src/config/paths.ts` +- Test: `packages/core/test/media-artifacts.test.ts` + +- [ ] **Step 1: Write failing artifact tests** + +Create `packages/core/test/media-artifacts.test.ts` with these cases: + +```ts +import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { afterEach, describe, expect, it } from "vitest"; +import { readMediaInput, resolveMediaArtifact, writeMediaArtifact } from "../src/media"; + +describe("media artifacts", () => { + const dirs: string[] = []; + afterEach(() => { + for (const dir of dirs.splice(0)) rmSync(dir, { recursive: true, force: true }); + }); + + function tempDir(prefix: string) { + const dir = mkdtempSync(join(tmpdir(), prefix)); + dirs.push(dir); + return dir; + } + + it("writes artifact files with stable metadata", async () => { + const root = tempDir("caplets-artifacts-"); + const artifact = await writeMediaArtifact({ + rootDir: root, + capletId: "google-drive", + suggestedFilename: "report.pdf", + mimeType: "application/pdf", + bytes: Buffer.from("pdf-bytes"), + }); + + expect(artifact).toMatchObject({ + mimeType: "application/pdf", + byteLength: 9, + filename: "report.pdf", + }); + expect(artifact.path).toContain(join(root, "google-drive")); + expect(artifact.sha256).toHaveLength(64); + expect(readFileSync(artifact.path, "utf8")).toBe("pdf-bytes"); + }); + + it("rejects output paths outside an allowed root", async () => { + const root = tempDir("caplets-artifacts-"); + await expect( + writeMediaArtifact({ + rootDir: root, + capletId: "drive", + outputPath: join(root, "..", "escape.bin"), + bytes: Buffer.from("x"), + }), + ).rejects.toMatchObject({ code: "REQUEST_INVALID" }); + }); + + it("reads media input from path, artifact reference, and data URL", async () => { + const root = tempDir("caplets-artifacts-"); + const file = join(root, "image.png"); + writeFileSync(file, Buffer.from("png")); + const artifact = await writeMediaArtifact({ + rootDir: root, + capletId: "drive", + suggestedFilename: "existing.png", + mimeType: "image/png", + bytes: Buffer.from("artifact"), + }); + + await expect(readMediaInput({ path: file }, { artifactRoot: root })).resolves.toMatchObject({ + bytes: Buffer.from("png"), + filename: "image.png", + }); + await expect( + readMediaInput({ artifact: artifact.uri }, { artifactRoot: root }), + ).resolves.toMatchObject({ + bytes: Buffer.from("artifact"), + filename: "existing.png", + mimeType: "image/png", + }); + await expect( + readMediaInput( + { dataUrl: "data:text/plain;base64,aGVsbG8=", filename: "hello.txt" }, + { artifactRoot: root }, + ), + ).resolves.toMatchObject({ + bytes: Buffer.from("hello"), + filename: "hello.txt", + mimeType: "text/plain", + }); + }); + + it("rejects multiple media input sources and non-base64 data URLs", async () => { + const root = tempDir("caplets-artifacts-"); + await expect( + readMediaInput( + { path: "/tmp/a", dataUrl: "data:text/plain;base64,eA==" }, + { + artifactRoot: root, + }, + ), + ).rejects.toMatchObject({ code: "REQUEST_INVALID" }); + await expect( + readMediaInput( + { dataUrl: "data:text/plain,hello" }, + { + artifactRoot: root, + }, + ), + ).rejects.toMatchObject({ code: "REQUEST_INVALID" }); + }); +}); +``` + +- [ ] **Step 2: Run the failing artifact tests** + +Run: + +```bash +pnpm --filter @caplets/core test -- test/media-artifacts.test.ts +``` + +Expected: fails because `../src/media` does not exist. + +- [ ] **Step 3: Add default artifact path** + +In `packages/core/src/config/paths.ts`, export a default artifact directory next to existing state/cache directory exports: + +```ts +export const DEFAULT_ARTIFACT_DIR = join(defaultStateBaseDir(), "artifacts"); +``` + +Use the existing local naming style in the file. If the file exposes helper functions rather than constants for nearby paths, match that shape with `defaultArtifactDir()`. + +- [ ] **Step 4: Implement artifact writing and lookup** + +Create `packages/core/src/media/artifacts.ts`: + +```ts +import { createHash, randomUUID } from "node:crypto"; +import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs"; +import { basename, dirname, isAbsolute, join, relative, resolve } from "node:path"; +import { DEFAULT_ARTIFACT_DIR } from "../config/paths"; +import { CapletsError } from "../errors"; + +export type MediaArtifact = { + uri: string; + path: string; + filename: string; + mimeType?: string; + byteLength: number; + sha256: string; +}; + +export type WriteMediaArtifactInput = { + rootDir?: string; + capletId: string; + callId?: string; + suggestedFilename?: string; + outputPath?: string; + mimeType?: string; + bytes: Uint8Array | Buffer; +}; + +export function artifactUri(capletId: string, callId: string, filename: string): string { + return `caplets://artifacts/${encodeURIComponent(capletId)}/${encodeURIComponent(callId)}/${encodeURIComponent(filename)}`; +} + +export async function writeMediaArtifact(input: WriteMediaArtifactInput): Promise { + const rootDir = resolve(input.rootDir ?? DEFAULT_ARTIFACT_DIR); + const callId = + input.callId ?? `${new Date().toISOString().replace(/[:.]/gu, "-")}-${randomUUID()}`; + const filename = safeFilename(input.suggestedFilename ?? "response.bin"); + const target = input.outputPath + ? assertInsideRoot(rootDir, input.outputPath) + : resolve(rootDir, input.capletId, callId, filename); + mkdirSync(dirname(target), { recursive: true, mode: 0o700 }); + const bytes = Buffer.from(input.bytes); + writeFileSync(target, bytes, { mode: 0o600 }); + return { + uri: artifactUri(input.capletId, callId, basename(target)), + path: target, + filename: basename(target), + ...(input.mimeType ? { mimeType: input.mimeType } : {}), + byteLength: bytes.byteLength, + sha256: createHash("sha256").update(bytes).digest("hex"), + }; +} + +export function resolveMediaArtifact( + uri: string, + options: { artifactRoot?: string } = {}, +): MediaArtifact { + const parsed = parseArtifactUri(uri); + const rootDir = resolve(options.artifactRoot ?? DEFAULT_ARTIFACT_DIR); + const path = resolve(rootDir, parsed.capletId, parsed.callId, parsed.filename); + assertInsideRoot(rootDir, path); + if (!existsSync(path)) { + throw new CapletsError("REQUEST_INVALID", `Media artifact ${uri} was not found`); + } + const bytes = readFileSync(path); + return { + uri, + path, + filename: basename(path), + byteLength: bytes.byteLength, + sha256: createHash("sha256").update(bytes).digest("hex"), + }; +} + +function parseArtifactUri(uri: string): { capletId: string; callId: string; filename: string } { + const url = new URL(uri); + if (url.protocol !== "caplets:" || url.hostname !== "artifacts") { + throw new CapletsError( + "REQUEST_INVALID", + "Media artifact URI must start with caplets://artifacts/", + ); + } + const [capletId, callId, filename] = url.pathname + .split("/") + .filter(Boolean) + .map((part) => decodeURIComponent(part)); + if (!capletId || !callId || !filename) { + throw new CapletsError("REQUEST_INVALID", "Media artifact URI is missing required parts"); + } + return { capletId, callId, filename: safeFilename(filename) }; +} + +function assertInsideRoot(rootDir: string, candidate: string): string { + if (!isAbsolute(candidate)) { + throw new CapletsError("REQUEST_INVALID", "Media artifact outputPath must be absolute"); + } + const resolved = resolve(candidate); + const rel = relative(rootDir, resolved); + if (rel.startsWith("..") || isAbsolute(rel)) { + throw new CapletsError( + "REQUEST_INVALID", + "Media artifact outputPath must stay inside the artifact root", + ); + } + return resolved; +} + +function safeFilename(value: string): string { + const name = basename(value) + .replace(/[^\w.\- ]/gu, "_") + .trim(); + return name || "response.bin"; +} +``` + +- [ ] **Step 5: Implement media input reading** + +Create `packages/core/src/media/input.ts`: + +```ts +import { readFileSync, statSync } from "node:fs"; +import { basename } from "node:path"; +import { CapletsError } from "../errors"; +import { resolveMediaArtifact } from "./artifacts"; + +export type MediaInput = + | { path: string; artifact?: never; dataUrl?: never; filename?: string; mimeType?: string } + | { artifact: string; path?: never; dataUrl?: never; filename?: string; mimeType?: string } + | { dataUrl: string; path?: never; artifact?: never; filename?: string; mimeType?: string }; + +export type ResolvedMediaInput = { + bytes: Buffer; + filename: string; + mimeType?: string; +}; + +export async function readMediaInput( + input: unknown, + options: { artifactRoot?: string; maxBytes?: number } = {}, +): Promise { + if (!input || typeof input !== "object" || Array.isArray(input)) { + throw new CapletsError("REQUEST_INVALID", "media must be an object"); + } + const media = input as Record; + const sources = ["path", "artifact", "dataUrl"].filter((key) => typeof media[key] === "string"); + if (sources.length !== 1) { + throw new CapletsError( + "REQUEST_INVALID", + "media must define exactly one of path, artifact, or dataUrl", + ); + } + const filename = typeof media.filename === "string" ? media.filename : undefined; + const mimeType = typeof media.mimeType === "string" ? media.mimeType : undefined; + if (typeof media.path === "string") { + const stat = statSync(media.path); + enforceSize(stat.size, options.maxBytes); + const bytes = readFileSync(media.path); + return { bytes, filename: filename ?? basename(media.path), ...(mimeType ? { mimeType } : {}) }; + } + if (typeof media.artifact === "string") { + const artifact = resolveMediaArtifact(media.artifact, { artifactRoot: options.artifactRoot }); + enforceSize(artifact.byteLength, options.maxBytes); + const bytes = readFileSync(artifact.path); + return { + bytes, + filename: filename ?? artifact.filename, + ...((mimeType ?? artifact.mimeType) ? { mimeType: mimeType ?? artifact.mimeType } : {}), + }; + } + return readDataUrl(String(media.dataUrl), { filename, mimeType, maxBytes: options.maxBytes }); +} + +function readDataUrl( + dataUrl: string, + options: { filename?: string; mimeType?: string; maxBytes?: number }, +): ResolvedMediaInput { + const match = /^data:([^;,]+);base64,([A-Za-z0-9+/=]+)$/u.exec(dataUrl); + if (!match) { + throw new CapletsError("REQUEST_INVALID", "media.dataUrl must be a base64 data URL"); + } + const bytes = Buffer.from(match[2]!, "base64"); + enforceSize(bytes.byteLength, options.maxBytes); + return { + bytes, + filename: options.filename ?? "media.bin", + mimeType: options.mimeType ?? match[1], + }; +} + +function enforceSize(size: number, maxBytes = 100 * 1024 * 1024): void { + if (size > maxBytes) { + throw new CapletsError("REQUEST_INVALID", `media exceeds byte limit ${maxBytes}`); + } +} +``` + +- [ ] **Step 6: Add media barrel export** + +Create `packages/core/src/media/index.ts`: + +```ts +export * from "./artifacts"; +export * from "./input"; +``` + +- [ ] **Step 7: Run artifact tests** + +Run: + +```bash +pnpm --filter @caplets/core test -- test/media-artifacts.test.ts +``` + +Expected: pass. + +### Task 2: Shared HTTP Response Reader And Existing Backend Media Artifacts + +**Files:** + +- Create: `packages/core/src/http/response.ts` +- Modify: `packages/core/src/openapi.ts` +- Modify: `packages/core/src/http-actions.ts` +- Test: `packages/core/test/openapi.test.ts` +- Test: `packages/core/test/http-actions.test.ts` + +- [ ] **Step 1: Add failing HTTP/OpenAPI artifact tests** + +In `packages/core/test/http-actions.test.ts`, extend the local server with: + +```ts +if (request.url === "/pdf") { + response.setHeader("content-type", "application/pdf"); + response.end(Buffer.from("%PDF-1.7 test")); + return; +} +``` + +Add this test: + +```ts +it("writes binary HTTP responses as media artifacts", async () => { + const artifactDir = mkdtempSync(join(tmpdir(), "caplets-http-artifacts-")); + try { + const manager = new HttpActionManager(registry(), { artifactDir }); + const api = httpApi({ actions: { pdf: { method: "GET", path: "/pdf" } } }); + + const result = await manager.callTool(api, "pdf", {}); + + expect(result.structuredContent).toMatchObject({ + status: 200, + headers: { "content-type": "application/pdf" }, + body: { + artifact: { + mimeType: "application/pdf", + byteLength: 13, + }, + }, + }); + const path = (result.structuredContent as any).body.artifact.path; + expect(readFileSync(path, "utf8")).toBe("%PDF-1.7 test"); + } finally { + rmSync(artifactDir, { recursive: true, force: true }); + } +}); +``` + +Mirror the same assertion in `packages/core/test/openapi.test.ts` using an OpenAPI operation whose response has `application/pdf`. + +- [ ] **Step 2: Run failing existing-backend media tests** + +Run: + +```bash +pnpm --filter @caplets/core test -- test/http-actions.test.ts test/openapi.test.ts +``` + +Expected: fail because responses are still read as bounded text. + +- [ ] **Step 3: Implement shared response reader** + +Create `packages/core/src/http/response.ts`: + +```ts +import type { MediaArtifact } from "../media"; +import { writeMediaArtifact } from "../media"; +import { parseHttpBody, readLimitedText } from "./utils"; + +export type HttpLikeResponseBody = + | { body?: unknown; artifact?: never } + | { body: { artifact: MediaArtifact } }; + +export type ReadHttpLikeResponseOptions = { + capletId: string; + artifactDir?: string; + outputPath?: string; + filename?: string; + maxInlineBytes?: number; +}; + +export async function readHttpLikeResponse( + response: Response, + options: ReadHttpLikeResponseOptions, +): Promise> { + const contentType = response.headers.get("content-type") ?? ""; + const mime = contentType.split(";")[0]?.toLowerCase().trim() ?? ""; + const inlineText = isInlineTextMime(mime); + if (inlineText) { + try { + const text = await readLimitedText(response, { + maxBytes: options.maxInlineBytes, + errorMessage: "HTTP response exceeded inline byte limit", + }); + const body = parseHttpBody(contentType, text); + return baseResponse(response, contentType, body); + } catch (error) { + if (!response.body) throw error; + } + } + + const bytes = Buffer.from(await response.arrayBuffer()); + const artifact = await writeMediaArtifact({ + rootDir: options.artifactDir, + capletId: options.capletId, + outputPath: options.outputPath, + suggestedFilename: options.filename ?? filenameFromHeaders(response) ?? "response.bin", + mimeType: mime || undefined, + bytes, + }); + return baseResponse(response, contentType, { artifact }); +} + +function baseResponse( + response: Response, + contentType: string, + body: unknown, +): Record { + return { + status: response.status, + statusText: response.statusText, + headers: { "content-type": contentType }, + ...(body === undefined ? {} : { body }), + }; +} + +function isInlineTextMime(mime: string): boolean { + return ( + mime === "" || + mime === "application/json" || + mime.endsWith("+json") || + mime.endsWith("/json") || + mime.startsWith("text/") + ); +} + +function filenameFromHeaders(response: Response): string | undefined { + const disposition = response.headers.get("content-disposition") ?? ""; + return /filename="?([^";]+)"?/iu.exec(disposition)?.[1]; +} +``` + +- [ ] **Step 4: Pass artifact options through managers** + +Update constructors in `packages/core/src/http-actions.ts` and `packages/core/src/openapi.ts`: + +```ts +constructor( + private registry: ServerRegistry, + private readonly options: { authDir?: string; artifactDir?: string } = {}, +) {} +``` + +Replace local `readResponse` functions with calls to `readHttpLikeResponse(response, { capletId, artifactDir })`. + +For OpenAPI and HTTP actions, reserve `args.outputPath` and `args.filename` for artifact output by removing them from request query/body/path mappings only when the operation schema explicitly models them as media output controls. If this conflicts with existing actions, keep output controls under `_caplets: { outputPath, filename }` and document that path in the plan implementation notes. + +- [ ] **Step 5: Pass artifact directory from engine** + +Add `artifactDir?: string` to `CapletsEngineOptions`. Instantiate managers with: + +```ts +const sharedManagerOptions = { authDir: options.authDir, artifactDir: options.artifactDir }; +this.openapi = new OpenApiManager(this.registry, sharedManagerOptions); +this.graphql = new GraphQLManager(this.registry, selectAuthOptions(options.authDir)); +this.http = new HttpActionManager(this.registry, sharedManagerOptions); +``` + +GraphQL can remain text/JSON-only unless the implementation chooses to route it through the shared reader. + +- [ ] **Step 6: Run focused tests** + +Run: + +```bash +pnpm --filter @caplets/core test -- test/media-artifacts.test.ts test/http-actions.test.ts test/openapi.test.ts +``` + +Expected: pass. + +### Task 3: Config Schema And Core Types For `googleDiscoveryApis` + +**Files:** + +- Modify: `packages/core/src/config.ts` +- Modify: `packages/core/src/registry.ts` +- Test: `packages/core/test/config.test.ts` + +- [ ] **Step 1: Add failing config tests** + +Add to `packages/core/test/config.test.ts`: + +```ts +it("loads Google Discovery APIs with defaults and safe registry details", () => { + const config = parseConfig({ + googleDiscoveryApis: { + drive: { + name: "Google Drive", + description: "Access Google Drive files and permissions.", + discoveryUrl: "https://www.googleapis.com/discovery/v1/apis/drive/v3/rest", + auth: { type: "oidc", issuer: "https://accounts.google.com", clientId: "client" }, + includeOperations: ["drive.files.*"], + excludeOperations: ["drive.files.delete"], + }, + }, + }); + expect(config.googleDiscoveryApis.drive).toMatchObject({ + server: "drive", + backend: "googleDiscovery", + discoveryUrl: "https://www.googleapis.com/discovery/v1/apis/drive/v3/rest", + requestTimeoutMs: 60000, + operationCacheTtlMs: 30000, + disabled: false, + }); + expect(JSON.stringify(configJsonSchema())).toContain("googleDiscoveryApis"); +}); + +it("rejects invalid Google Discovery sources and duplicate Caplet IDs", () => { + expect(() => + parseConfig({ + googleDiscoveryApis: { + drive: { + name: "Drive", + description: "Access Google Drive files.", + discoveryUrl: "ftp://example.com/discovery.json", + auth: { type: "none" }, + }, + }, + }), + ).toThrow(CapletsError); + expect(() => + parseConfig({ + openapiEndpoints: { + drive: { + name: "Drive OpenAPI", + description: "OpenAPI Drive wrapper.", + specUrl: "https://example.com/openapi.json", + auth: { type: "none" }, + }, + }, + googleDiscoveryApis: { + drive: { + name: "Drive", + description: "Access Google Drive files.", + discoveryUrl: "https://www.googleapis.com/discovery/v1/apis/drive/v3/rest", + auth: { type: "none" }, + }, + }, + }), + ).toThrow(/already used/); +}); +``` + +- [ ] **Step 2: Run failing config tests** + +Run: + +```bash +pnpm --filter @caplets/core test -- test/config.test.ts +``` + +Expected: fail because config does not know `googleDiscoveryApis`. + +- [ ] **Step 3: Add config types** + +In `packages/core/src/config.ts`, add: + +```ts +export type GoogleDiscoveryApiConfig = AgentSelectionHintsConfig & { + server: string; + backend: "googleDiscovery"; + name: string; + description: string; + exposure?: CapletExposure | undefined; + shadowing?: CapletShadowingPolicy | undefined; + tags?: string[] | undefined; + body?: string | undefined; + discoveryPath?: string | undefined; + discoveryUrl?: string | undefined; + baseUrl?: string | undefined; + includeOperations?: string[] | undefined; + excludeOperations?: string[] | undefined; + auth: OpenApiAuthConfig; + requestTimeoutMs: number; + operationCacheTtlMs: number; + disabled: boolean; + setup?: CapletSetupConfig | undefined; + projectBinding?: ProjectBindingConfig | undefined; + runtime?: RuntimeRequirementsConfig | undefined; +}; +``` + +Extend: + +```ts +export type CapletConfig = + | CapletServerConfig + | OpenApiEndpointConfig + | GoogleDiscoveryApiConfig + | GraphQlEndpointConfig + | HttpApiConfig + | CliToolsConfig + | CapletSetConfig; + +export type CapletsConfig = { + ... + googleDiscoveryApis: Record; + ... +}; +``` + +- [ ] **Step 4: Add Zod schema** + +Add `publicGoogleDiscoveryApiSchema` modeled after `publicOpenApiEndpointSchema`: + +```ts +const operationFilterSchema = z.array(z.string().trim().min(1).max(160)); + +const publicGoogleDiscoveryApiSchema = z + .object({ + name: z + .string() + .trim() + .min(1) + .max(80) + .describe("Human-readable Google Discovery API display name."), + description: z + .string() + .describe( + "Capability description shown to agents before Google Discovery operations are disclosed.", + ) + .refine( + (value) => value.trim().length >= 10, + "description must contain at least 10 non-whitespace characters", + ) + .refine((value) => value.length <= 1500, "description must be at most 1500 characters"), + discoveryPath: z.string().min(1).optional().describe("Local Google Discovery document path."), + discoveryUrl: z.string().url().optional().describe("Remote Google Discovery document URL."), + baseUrl: z.string().url().optional().describe("Override base URL for Google API requests."), + includeOperations: operationFilterSchema.optional(), + excludeOperations: operationFilterSchema.optional(), + auth: openApiAuthSchema.describe( + 'Explicit Google API request auth config. Use {"type":"none"} for public APIs.', + ), + tags: z.array(z.string().trim().min(1).max(80)).optional(), + exposure: exposureSchema.optional(), + shadowing: shadowingSchema, + ...agentSelectionHintsSchema, + setup: setupSchema.optional(), + projectBinding: projectBindingSchema.optional(), + runtime: runtimeRequirementsSchema.optional(), + requestTimeoutMs: z.number().int().positive().default(60_000), + operationCacheTtlMs: z.number().int().nonnegative().default(30_000), + disabled: z.boolean().default(false), + }) + .strict(); +``` + +Normalize parsed entries with `server` and `backend: "googleDiscovery"` the same way OpenAPI entries are normalized. + +- [ ] **Step 5: Add validation, merge, source, and project rejection support** + +Update every backend-map list in `packages/core/src/config.ts`: + +- `ConfigInput` +- `configSchemaFor(...)` +- duplicate ID checks +- URL safety checks for `discoveryUrl` and `baseUrl` +- exact-one-source check for `discoveryPath` xor `discoveryUrl` +- `normalizeLocalPaths` +- `rejectProjectConfigExecutableBackendMaps` +- `mergeConfigInputs` +- `removeCapletId` +- `capletIds` +- empty-config checks + +Use this duplicate ordering when checking `googleDiscoveryApis`: `mcpServers`, `openapiEndpoints`, then `googleDiscoveryApis`, then the remaining backend maps. + +- [ ] **Step 6: Update registry detail** + +In `packages/core/src/registry.ts`, add a backend detail variant: + +```ts +| { + type: "googleDiscovery"; + disabled: boolean; + requestTimeoutMs: number; + operationCacheTtlMs: number; + source: "discoveryPath" | "discoveryUrl"; + } +``` + +Add `googleDiscoveryApis` to `get()`, `allCaplets()`, and `backendDetail()`. + +- [ ] **Step 7: Run config tests** + +Run: + +```bash +pnpm --filter @caplets/core test -- test/config.test.ts +``` + +Expected: pass. + +### Task 4: Caplet Files, Source Parsing, Inspection, And CLI Enumeration + +**Files:** + +- Modify: `packages/core/src/caplet-files-bundle.ts` +- Modify: `packages/core/src/caplet-source/parse.ts` +- Modify: `packages/core/src/cli/inspection.ts` +- Modify: `packages/core/src/cli/completion-discovery.ts` +- Modify: `packages/core/src/cli/setup-caplet.ts` +- Modify: `packages/core/src/cli/doctor.ts` +- Modify: `packages/core/src/cli.ts` +- Test: `packages/core/test/caplet-files.test.ts` +- Test: `packages/core/test/config.test.ts` +- Test: `packages/core/test/cli.test.ts` + +- [ ] **Step 1: Add failing Caplet file and list tests** + +In `packages/core/test/caplet-files.test.ts`: + +```ts +it("loads Google Discovery API backend Caplet files", () => { + const result = loadCapletFilesFromMap({ + files: [ + { + path: "drive/CAPLET.md", + content: `--- +name: Google Drive +description: Access Google Drive files. +googleDiscoveryApi: + discoveryPath: ./drive.discovery.json + includeOperations: + - drive.files.* + auth: + type: none +--- + +# Drive +`, + }, + ], + }); + + expect(result?.config.googleDiscoveryApis?.drive).toEqual( + expect.objectContaining({ + name: "Google Drive", + description: "Access Google Drive files.", + discoveryPath: "drive/drive.discovery.json", + includeOperations: ["drive.files.*"], + body: "\n# Drive\n", + }), + ); +}); +``` + +In `packages/core/test/cli.test.ts`, add a list/inspect assertion using `googleDiscoveryApis` and expect backend `googleDiscovery`. + +- [ ] **Step 2: Run failing tests** + +Run: + +```bash +pnpm --filter @caplets/core test -- test/caplet-files.test.ts test/cli.test.ts +``` + +Expected: fail because file loading and listing do not include the new backend. + +- [ ] **Step 3: Add frontmatter schema** + +In `packages/core/src/caplet-files-bundle.ts`, add `capletGoogleDiscoveryApiSchema` with the same fields as config minus top-level display fields, add `googleDiscoveryApi` to `capletFileSchema`, and update the backend-count error text: + +```ts +"Caplet file must define exactly one backend: mcpServer, openapiEndpoint, googleDiscoveryApi, graphqlEndpoint, httpApi, cliTools, or capletSet"; +``` + +- [ ] **Step 4: Add Google Discovery file mapping** + +In `buildCapletFileLoadResultFromEntries`, add `googleDiscoveryApis` to duplicate detection and output config. In the frontmatter normalization function, add: + +```ts +if (frontmatter.googleDiscoveryApi) { + return { + ...frontmatter.googleDiscoveryApi, + discoveryPath: normalizePath(frontmatter.googleDiscoveryApi.discoveryPath, baseDir), + backend: "googleDiscovery", + name: frontmatter.name, + description: frontmatter.description, + ...sharedCapletFields(frontmatter), + body, + }; +} +``` + +- [ ] **Step 5: Add enumeration support** + +Add `googleDiscoveryApis` to all `allCaplets` or object-spread collections in: + +- `packages/core/src/caplet-source/parse.ts` +- `packages/core/src/cli/inspection.ts` +- `packages/core/src/cli/completion-discovery.ts` +- `packages/core/src/cli/setup-caplet.ts` +- `packages/core/src/cli/doctor.ts` +- `packages/core/src/cli.ts` `capletConfigKinds`, `hasEnabledCaplet`, and any local overlay removal helpers + +- [ ] **Step 6: Run file/list tests** + +Run: + +```bash +pnpm --filter @caplets/core test -- test/caplet-files.test.ts test/config.test.ts test/cli.test.ts +``` + +Expected: pass. + +### Task 5: Google Discovery Parser, Schema Conversion, Filtering, And Scope Resolution + +**Files:** + +- Create: `packages/core/src/google-discovery/types.ts` +- Create: `packages/core/src/google-discovery/schema.ts` +- Create: `packages/core/src/google-discovery/operations.ts` +- Create: `packages/core/src/google-discovery/index.ts` +- Create: `packages/core/test/fixtures/google-discovery/drive.discovery.json` +- Test: `packages/core/test/google-discovery.test.ts` + +- [ ] **Step 1: Add a compact Drive-like fixture** + +Create `packages/core/test/fixtures/google-discovery/drive.discovery.json` with this shape: + +```json +{ + "kind": "discovery#restDescription", + "id": "drive:v3", + "name": "drive", + "version": "v3", + "title": "Drive API", + "rootUrl": "https://www.googleapis.com/", + "servicePath": "drive/v3/", + "baseUrl": "https://www.googleapis.com/drive/v3/", + "auth": { + "oauth2": { + "scopes": { + "https://www.googleapis.com/auth/drive": { "description": "Full Drive access." }, + "https://www.googleapis.com/auth/drive.readonly": { "description": "Read Drive files." } + } + } + }, + "parameters": { + "fields": { + "type": "string", + "location": "query", + "description": "Partial response selector." + }, + "prettyPrint": { "type": "boolean", "location": "query", "default": "true" } + }, + "schemas": { + "File": { + "id": "File", + "type": "object", + "properties": { + "id": { "type": "string" }, + "name": { "type": "string" }, + "parents": { "type": "array", "items": { "type": "string" } } + } + }, + "FileList": { + "id": "FileList", + "type": "object", + "properties": { + "files": { "type": "array", "items": { "$ref": "File" } }, + "nextPageToken": { "type": "string" } + } + } + }, + "resources": { + "files": { + "methods": { + "list": { + "id": "drive.files.list", + "path": "files", + "httpMethod": "GET", + "description": "Lists files.", + "scopes": ["https://www.googleapis.com/auth/drive.readonly"], + "parameters": { + "pageSize": { + "type": "integer", + "format": "int32", + "location": "query", + "default": "100" + } + }, + "response": { "$ref": "FileList" } + }, + "delete": { + "id": "drive.files.delete", + "path": "files/{fileId}", + "httpMethod": "DELETE", + "description": "Permanently deletes a file.", + "parameters": { + "fileId": { "type": "string", "location": "path", "required": true } + }, + "scopes": ["https://www.googleapis.com/auth/drive"] + }, + "create": { + "id": "drive.files.create", + "path": "files", + "httpMethod": "POST", + "description": "Creates a file.", + "request": { "$ref": "File" }, + "response": { "$ref": "File" }, + "scopes": ["https://www.googleapis.com/auth/drive"], + "supportsMediaUpload": true, + "mediaUpload": { + "protocols": { + "simple": { "path": "/upload/drive/v3/files", "multipart": false }, + "multipart": { "path": "/upload/drive/v3/files", "multipart": true }, + "resumable": { "path": "/upload/drive/v3/files", "multipart": true } + } + } + }, + "download": { + "id": "drive.files.download", + "path": "files/{fileId}/download", + "httpMethod": "GET", + "description": "Downloads file media.", + "supportsMediaDownload": true, + "parameters": { + "fileId": { "type": "string", "location": "path", "required": true } + }, + "scopes": ["https://www.googleapis.com/auth/drive.readonly"] + } + } + } + } +} +``` + +- [ ] **Step 2: Write failing parser tests** + +In `packages/core/test/google-discovery.test.ts`: + +```ts +import { readFileSync } from "node:fs"; +import { join } from "node:path"; +import { describe, expect, it } from "vitest"; +import { discoveryOperations, googleDiscoveryScopesForOperations } from "../src/google-discovery"; + +const fixture = JSON.parse( + readFileSync(join(__dirname, "fixtures/google-discovery/drive.discovery.json"), "utf8"), +); + +describe("Google Discovery parser", () => { + it("maps resources and methods to Caplets operations", () => { + const operations = discoveryOperations({ + server: "drive", + document: fixture, + includeOperations: ["drive.files.*"], + excludeOperations: ["drive.files.delete"], + }); + + expect(operations.map((operation) => operation.name)).toEqual([ + "drive.files.create", + "drive.files.download", + "drive.files.list", + ]); + expect(operations.find((operation) => operation.name === "drive.files.list")).toMatchObject({ + method: "get", + path: "files", + readOnlyHint: true, + destructiveHint: false, + inputSchema: { + properties: { + query: { + properties: { + fields: { type: "string" }, + pageSize: { type: "integer", default: 100 }, + }, + }, + }, + }, + }); + }); + + it("marks destructive operations and resolves filtered scopes", () => { + const operations = discoveryOperations({ server: "drive", document: fixture }); + expect(operations.find((operation) => operation.name === "drive.files.delete")).toMatchObject({ + destructiveHint: true, + }); + expect(googleDiscoveryScopesForOperations(operations)).toEqual([ + "https://www.googleapis.com/auth/drive", + "https://www.googleapis.com/auth/drive.readonly", + ]); + }); +}); +``` + +- [ ] **Step 3: Run failing parser tests** + +Run: + +```bash +pnpm --filter @caplets/core test -- test/google-discovery.test.ts +``` + +Expected: fail because parser files do not exist. + +- [ ] **Step 4: Implement types** + +Create `packages/core/src/google-discovery/types.ts`: + +```ts +export type GoogleDiscoveryDocument = { + kind?: string; + id?: string; + name?: string; + version?: string; + title?: string; + rootUrl?: string; + servicePath?: string; + baseUrl?: string; + auth?: { oauth2?: { scopes?: Record } }; + parameters?: Record; + schemas?: Record; + resources?: Record; +}; + +export type GoogleDiscoveryResource = { + methods?: Record; + resources?: Record; +}; + +export type GoogleDiscoveryMethod = { + id?: string; + path?: string; + flatPath?: string; + httpMethod?: string; + description?: string; + parameters?: Record; + parameterOrder?: string[]; + request?: { $ref?: string }; + response?: { $ref?: string }; + scopes?: string[]; + supportsMediaUpload?: boolean; + supportsMediaDownload?: boolean; + mediaUpload?: { + accept?: string[]; + maxSize?: string; + protocols?: Record; + }; +}; + +export type GoogleDiscoveryParameter = GoogleDiscoverySchema & { + location?: "path" | "query" | "header"; + required?: boolean; + repeated?: boolean; + deprecated?: boolean; +}; + +export type GoogleDiscoverySchema = { + id?: string; + $ref?: string; + type?: string; + format?: string; + description?: string; + default?: unknown; + enum?: string[]; + repeated?: boolean; + properties?: Record; + items?: GoogleDiscoverySchema; + additionalProperties?: GoogleDiscoverySchema; +}; +``` + +- [ ] **Step 5: Implement schema conversion** + +Create `packages/core/src/google-discovery/schema.ts` with: + +```ts +import type { GoogleDiscoverySchema } from "./types"; + +export function googleDiscoverySchemaToJsonSchema( + value: GoogleDiscoverySchema | undefined, + schemas: Record = {}, + seen = new Set(), +): Record { + if (!value) return {}; + if (value.$ref) { + const target = schemas[value.$ref]; + if (!target || seen.has(value.$ref)) return { type: "object", additionalProperties: true }; + return googleDiscoverySchemaToJsonSchema(target, schemas, new Set([...seen, value.$ref])); + } + const type = value.type === "any" ? "object" : value.type; + const converted: Record = {}; + if (value.description) converted.description = collapseWhitespace(value.description); + if (type) converted.type = type; + if (value.format) converted.format = value.format; + if (value.enum) converted.enum = value.enum; + const defaultValue = convertedDefault(value.default, type); + if (defaultValue !== undefined) converted.default = defaultValue; + if (value.repeated) { + return { + ...(converted.description ? { description: converted.description } : {}), + type: "array", + items: omit(converted, ["description", "default"]), + }; + } + if (value.items) converted.items = googleDiscoverySchemaToJsonSchema(value.items, schemas, seen); + if (value.properties) { + converted.type = converted.type ?? "object"; + converted.properties = Object.fromEntries( + Object.entries(value.properties).map(([key, schema]) => [ + key, + googleDiscoverySchemaToJsonSchema(schema, schemas, seen), + ]), + ); + converted.additionalProperties = false; + } + if (value.additionalProperties) { + converted.additionalProperties = googleDiscoverySchemaToJsonSchema( + value.additionalProperties, + schemas, + seen, + ); + } + return converted; +} + +function convertedDefault(value: unknown, type: string | undefined): unknown { + if (value === undefined) return undefined; + if (type === "boolean" && typeof value === "string") return value === "true"; + if ((type === "integer" || type === "number") && typeof value === "string") { + const number = Number(value); + return Number.isFinite(number) ? number : value; + } + return value; +} + +function collapseWhitespace(value: string): string { + return value.replace(/\s+/gu, " ").trim(); +} + +function omit(value: Record, keys: string[]): Record { + return Object.fromEntries(Object.entries(value).filter(([key]) => !keys.includes(key))); +} +``` + +- [ ] **Step 6: Implement operation mapping** + +Create `packages/core/src/google-discovery/operations.ts` with exported `GoogleDiscoveryOperation`, `discoveryOperations`, `googleDiscoveryScopesForOperations`, and glob matching. The operation type must carry: + +```ts +export type GoogleDiscoveryOperation = { + name: string; + method: "get" | "put" | "post" | "delete" | "patch"; + path: string; + description?: string; + inputSchema: Record; + outputSchema?: Record; + readOnlyHint: boolean; + destructiveHint: boolean; + scopes: string[]; + supportsMediaUpload: boolean; + supportsMediaDownload: boolean; + mediaUploadProtocols: Record; +}; +``` + +Use `method.id` as the operation name. Sort operations by name. Use path parameters as required. Apply include/exclude patterns against operation names before returning. + +- [ ] **Step 7: Export parser helpers and run tests** + +Create `packages/core/src/google-discovery/index.ts`: + +```ts +export * from "./types"; +export * from "./schema"; +export * from "./operations"; +``` + +Run: + +```bash +pnpm --filter @caplets/core test -- test/google-discovery.test.ts +``` + +Expected: parser tests pass. + +### Task 6: `GoogleDiscoveryManager` For Discovery, Descriptors, Search, And JSON Calls + +**Files:** + +- Create: `packages/core/src/google-discovery/request.ts` +- Create: `packages/core/src/google-discovery/manager.ts` +- Modify: `packages/core/src/google-discovery/index.ts` +- Test: `packages/core/test/google-discovery.test.ts` + +- [ ] **Step 1: Extend fixture server tests** + +In `packages/core/test/google-discovery.test.ts`, add a local HTTP server similar to `openapi.test.ts` that serves `/drive.discovery.json`, `/drive/v3/files`, `/drive/v3/files/{fileId}`, and JSON responses. Add a test: + +```ts +it("lists, describes, searches, and calls Google Discovery operations", async () => { + const config = parseConfig({ + googleDiscoveryApis: { + drive: { + name: "Google Drive", + description: "Access Google Drive files.", + discoveryUrl: `${baseUrl}/drive.discovery.json`, + baseUrl: `${baseUrl}/drive/v3/`, + auth: { type: "none" }, + includeOperations: ["drive.files.*"], + excludeOperations: ["drive.files.delete"], + }, + }, + }); + const registry = new ServerRegistry(config); + const manager = new GoogleDiscoveryManager(registry); + const caplet = config.googleDiscoveryApis.drive!; + + await expect(manager.listTools(caplet)).resolves.toEqual( + expect.arrayContaining([ + expect.objectContaining({ name: "drive.files.list" }), + expect.objectContaining({ name: "drive.files.create" }), + ]), + ); + await expect(manager.getTool(caplet, "drive.files.list")).resolves.toMatchObject({ + inputSchema: { properties: { query: { properties: { pageSize: { type: "integer" } } } } }, + annotations: { readOnlyHint: true, destructiveHint: false }, + }); + await expect( + manager.callTool(caplet, "drive.files.list", { query: { pageSize: 2 } }), + ).resolves.toMatchObject({ + structuredContent: { status: 200, body: { files: [{ id: "1", name: "Report" }] } }, + isError: false, + }); +}); +``` + +- [ ] **Step 2: Run failing manager tests** + +Run: + +```bash +pnpm --filter @caplets/core test -- test/google-discovery.test.ts +``` + +Expected: fail because `GoogleDiscoveryManager` is not implemented. + +- [ ] **Step 3: Implement request builder** + +Create `packages/core/src/google-discovery/request.ts` with functions: + +```ts +export function buildGoogleDiscoveryUrl( + api: GoogleDiscoveryApiConfig, + operation: GoogleDiscoveryOperation, + args: Record, +): URL; + +export function buildJsonRequestInit( + operation: GoogleDiscoveryOperation, + args: Record, + headers: Headers, +): RequestInit; +``` + +Rules: + +- Preserve base URL path like OpenAPI `buildOperationUrl`. +- Substitute `{path}` params from `args.path`. +- Append query params from `args.query`. +- Reject object/array query and path values. +- Set `content-type: application/json` only when `args.body` is present. +- Reject attempts to supply forbidden headers through args. + +- [ ] **Step 4: Implement manager skeleton** + +Create `packages/core/src/google-discovery/manager.ts` with the same public method shape as `OpenApiManager`: + +```ts +export class GoogleDiscoveryManager { + constructor( + private registry: ServerRegistry, + private readonly options: { authDir?: string; artifactDir?: string } = {}, + ) {} + + updateRegistry(registry: ServerRegistry): void; + invalidate(serverId: string): void; + checkApi(api: GoogleDiscoveryApiConfig): Promise<{ + id: string; + status: string; + toolCount?: number; + elapsedMs: number; + error?: unknown; + }>; + listTools(api: GoogleDiscoveryApiConfig): Promise; + getTool(api: GoogleDiscoveryApiConfig, toolName: string): Promise; + callTool( + api: GoogleDiscoveryApiConfig, + toolName: string, + args: Record, + ): Promise; + compact(api: GoogleDiscoveryApiConfig, tool: Tool): CompactTool; + search(api: GoogleDiscoveryApiConfig, tools: Tool[], query: string, limit: number): CompactTool[]; + resolveAuthScopes(api: GoogleDiscoveryApiConfig): Promise; +} +``` + +Cache parsed operations by server and source cache key. Load source from `discoveryPath` or `discoveryUrl`, reject redirects, enforce request timeout, parse JSON, and validate `kind === "discovery#restDescription"` or presence of `resources` plus `schemas`. + +- [ ] **Step 5: Implement JSON call path** + +In `callTool`, for operations without media upload/download: + +1. Build URL. +2. Apply auth via `genericOAuthHeaders(api as GenericAuthTarget, authDir)` or static auth helpers. +3. Fetch with `redirect: "manual"`. +4. Reject redirects. +5. Use `readHttpLikeResponse(response, { capletId: api.server, artifactDir: this.options.artifactDir })`. +6. Return `content`, `structuredContent`, and `isError: !response.ok`. + +- [ ] **Step 6: Run manager tests** + +Run: + +```bash +pnpm --filter @caplets/core test -- test/google-discovery.test.ts +``` + +Expected: pass for parser and JSON manager tests. + +### Task 7: Engine, Tool, Native, Direct Exposure, And Code Mode Integration + +**Files:** + +- Modify: `packages/core/src/engine.ts` +- Modify: `packages/core/src/tools.ts` +- Modify: `packages/core/src/native/service.ts` +- Modify: `packages/core/src/native/tools.ts` +- Modify: `packages/core/src/exposure/discovery.ts` only if type narrowing requires it +- Test: `packages/core/test/google-discovery.test.ts` +- Test: `packages/core/test/native.test.ts` +- Test: `packages/core/test/code-mode-api.test.ts` + +- [ ] **Step 1: Add failing end-to-end tool surface test** + +In `packages/core/test/google-discovery.test.ts`, add: + +```ts +it("executes Google Discovery operations through handleServerTool", async () => { + const config = parseConfig({ + googleDiscoveryApis: { + drive: { + name: "Google Drive", + description: "Access Google Drive files.", + discoveryUrl: `${baseUrl}/drive.discovery.json`, + baseUrl: `${baseUrl}/drive/v3/`, + auth: { type: "none" }, + }, + }, + }); + const registry = new ServerRegistry(config); + const manager = new GoogleDiscoveryManager(registry); + const downstream = new DownstreamManager(registry); + const caplet = config.googleDiscoveryApis.drive!; + + const list = (await handleServerTool( + caplet, + { operation: "tools" }, + registry, + downstream, + undefined, + undefined, + undefined, + undefined, + undefined, + manager, + )) as any; + + expect(list.structuredContent.result.items.map((tool: { name: string }) => tool.name)).toContain( + "drive.files.list", + ); +}); +``` + +The exact argument ordering must match the final `handleServerTool` signature. + +- [ ] **Step 2: Run failing integration test** + +Run: + +```bash +pnpm --filter @caplets/core test -- test/google-discovery.test.ts +``` + +Expected: fail because engine/tools do not accept the manager. + +- [ ] **Step 3: Add manager to engine** + +In `packages/core/src/engine.ts`: + +- import `GoogleDiscoveryManager` +- add private `googleDiscovery` +- instantiate with `{ authDir, artifactDir }` +- update registry on reload +- invalidate on backend changes +- include in `listCompletionTools`, `listTools`, and `callTool` +- include in `allCaplets` +- pass to `handleServerTool` + +- [ ] **Step 4: Add manager to `tools.ts`** + +In `packages/core/src/tools.ts`: + +- import `GoogleDiscoveryManager` +- add optional param to `handleServerTool` +- add optional param to `backendFor` +- add branch before OpenAPI fallback: + +```ts +if (server.backend === "googleDiscovery") { + if (!googleDiscovery) { + throw new CapletsError("INTERNAL_ERROR", "Google Discovery manager is not configured"); + } + return { + check: (...args: Parameters) => + googleDiscovery.checkApi(...args), + listTools: (...args: Parameters) => + googleDiscovery.listTools(...args), + getTool: (...args: Parameters) => + googleDiscovery.getTool(...args), + callTool: (...args: Parameters) => + googleDiscovery.callTool(...args), + compact: (...args: Parameters) => + googleDiscovery.compact(...args), + search: (...args: Parameters) => + googleDiscovery.search(...args), + }; +} +``` + +- [ ] **Step 5: Add native guidance** + +In `packages/core/src/native/tools.ts`, keep guidance HTTP-like: + +```ts +if (caplet.backend === "googleDiscovery") { + return [ + `${toolName} exposes Google API operations from a Google Discovery document.`, + "Use tools/searchTools to find exact operation IDs and describeTool before calling media or write operations.", + ]; +} +``` + +In `packages/core/src/native/service.ts`, any direct per-backend behavior that currently checks `http`, `cli`, or `mcp` should treat `googleDiscovery` as a tool-only HTTP-like backend. + +- [ ] **Step 6: Run tool/native/code-mode tests** + +Run: + +```bash +pnpm --filter @caplets/core test -- test/google-discovery.test.ts test/native.test.ts test/code-mode-api.test.ts +``` + +Expected: pass. + +### Task 8: OAuth Scope Inference And Auth Login Integration + +**Files:** + +- Modify: `packages/core/src/auth.ts` +- Modify: `packages/core/src/auth/store.ts` +- Modify: `packages/core/src/cli/auth.ts` +- Test: `packages/core/test/auth.test.ts` +- Test: `packages/core/test/google-discovery.test.ts` + +- [ ] **Step 1: Add failing auth tests** + +In `packages/core/test/auth.test.ts`, add a test that creates a Google Discovery config fixture and starts `auth login --no-open` with a fake manual code against a local OAuth server. Assert the printed auth URL scope includes `openid profile email` plus filtered Discovery scopes and excludes scopes from filtered-out operations. + +Use the existing generic OIDC authorization-code flow tests around `runGenericOAuthFlow` as the local OAuth fixture template. + +- [ ] **Step 2: Run failing auth tests** + +Run: + +```bash +pnpm --filter @caplets/core test -- test/auth.test.ts test/google-discovery.test.ts +``` + +Expected: fail because auth target discovery does not resolve Google Discovery scopes. + +- [ ] **Step 3: Add resolved-scope support to generic OAuth** + +In `packages/core/src/auth.ts`, extend `GenericAuthTarget`: + +```ts +resolvedScopes?: string[] | undefined; +``` + +Update `scopesFor`: + +```ts +function scopesFor(authConfig: OAuthLikeAuthConfig, resolvedScopes?: string[]): string | undefined { + if (authConfig.scopes?.length) return authConfig.scopes.join(" "); + if (resolvedScopes?.length) { + const scopes = + authConfig.type === "oidc" + ? ["openid", "profile", "email", ...resolvedScopes] + : resolvedScopes; + return [...new Set(scopes)].sort(scopeSort).join(" "); + } + return authConfig.type === "oidc" ? "openid profile email" : undefined; +} +``` + +Preserve OIDC ordering for the three identity scopes if tests depend on exact URL text; otherwise sort only API scopes and prepend identity scopes. + +- [ ] **Step 4: Store requested scopes metadata** + +In `writeTokenBundle` call sites inside `runGenericOAuthFlow` and `startGenericOAuthFlow.complete`, add: + +```ts +metadata: redactSecrets({ + protectedResource: target.url ?? target.baseUrl ?? target.specUrl, + authorizationServer: metadata, + requestedScopes: scope?.split(/\s+/u).filter(Boolean), + dynamicClient: client.dynamic ? { client_id: client.clientId } : undefined, +}) as Record, +``` + +Do not remove existing metadata fields. + +- [ ] **Step 5: Validate requested scopes on token reuse** + +In `assertTokenBundleMatchesTarget`, compare required resolved scopes to `bundle.metadata.requestedScopes` when present. If metadata is absent, fall back to checking `bundle.scope` contains every required API scope. Return `AUTH_REQUIRED` if required scopes are missing. + +Do not require exact equality on provider-returned `bundle.scope` because Google may canonicalize identity scopes such as `email` to `https://www.googleapis.com/auth/userinfo.email`. + +- [ ] **Step 6: Resolve Google Discovery auth targets** + +In `packages/core/src/cli/auth.ts`: + +- include `config.googleDiscoveryApis` in `authTargets` +- for Google Discovery entries, load operations with `GoogleDiscoveryManager` and attach `resolvedScopes` +- set protected resource origin from `baseUrl` or inferred document base URL + +Use a helper: + +```ts +async function googleDiscoveryAuthTarget( + api: GoogleDiscoveryApiConfig, + authDir?: string, +): Promise { + const manager = new GoogleDiscoveryManager( + new ServerRegistry(parseConfig({ googleDiscoveryApis: { [api.server]: api } })), + { authDir }, + ); + return { + ...api, + baseUrl: await manager.resolveBaseUrl(api), + resolvedScopes: await manager.resolveAuthScopes(api), + }; +} +``` + +Adapt the helper to avoid recursive config parsing if a cleaner constructor path is available. + +- [ ] **Step 7: Run auth tests** + +Run: + +```bash +pnpm --filter @caplets/core test -- test/auth.test.ts test/google-discovery.test.ts +``` + +Expected: pass. + +### Task 9: Google Media Download And Upload Protocols + +**Files:** + +- Modify: `packages/core/src/google-discovery/request.ts` +- Modify: `packages/core/src/google-discovery/manager.ts` +- Modify: `packages/core/src/google-discovery/operations.ts` +- Test: `packages/core/test/google-discovery.test.ts` + +- [ ] **Step 1: Add failing media protocol tests** + +In the Google Discovery fixture server, add endpoints: + +- `GET /drive/v3/files/1/download` returns `application/pdf` bytes. +- `POST /upload/drive/v3/files?uploadType=media` records raw body. +- `POST /upload/drive/v3/files?uploadType=multipart` records multipart body and returns JSON. +- `POST /upload/drive/v3/files?uploadType=resumable` returns `Location: /upload/session/abc`. +- `PUT /upload/session/abc` accepts chunks and returns final JSON after the last chunk. + +Add tests: + +```ts +it("writes Google media downloads as artifacts", async () => { + const result = await manager.callTool(caplet, "drive.files.download", { + path: { fileId: "1" }, + filename: "report.pdf", + }); + expect(result.structuredContent).toMatchObject({ + status: 200, + body: { artifact: { filename: "report.pdf", mimeType: "application/pdf" } }, + }); +}); + +it("uploads media from path using multipart when metadata body is present", async () => { + const mediaPath = join(tempDir, "report.pdf"); + writeFileSync(mediaPath, "pdf"); + const result = await manager.callTool(caplet, "drive.files.create", { + body: { name: "report.pdf" }, + media: { path: mediaPath, mimeType: "application/pdf" }, + }); + expect(result.structuredContent).toMatchObject({ status: 200, body: { id: "uploaded" } }); + expect(lastUploadRequest.headers["content-type"]).toContain("multipart/related"); +}); + +it("uploads small media from dataUrl and never echoes the data URL", async () => { + const result = await manager.callTool(caplet, "drive.files.create", { + media: { dataUrl: "data:text/plain;base64,aGVsbG8=", filename: "hello.txt" }, + }); + expect(JSON.stringify(result)).not.toContain("aGVsbG8="); +}); +``` + +- [ ] **Step 2: Run failing media tests** + +Run: + +```bash +pnpm --filter @caplets/core test -- test/google-discovery.test.ts +``` + +Expected: fail because media protocols are not implemented. + +- [ ] **Step 3: Add media args to tool input schemas** + +For operations with `supportsMediaUpload`, add: + +```ts +media: { + type: "object", + additionalProperties: false, + properties: { + path: { type: "string" }, + artifact: { type: "string" }, + dataUrl: { type: "string", description: "Small base64 data URL. Prefer media.path or media.artifact." }, + filename: { type: "string" }, + mimeType: { type: "string" } + } +} +``` + +For `supportsMediaDownload`, add optional top-level `outputPath` and `filename` unless the implementation uses `_caplets.outputPath`; keep the final input shape documented in test assertions. + +- [ ] **Step 4: Implement download path** + +For `supportsMediaDownload`, call the normal request URL, fetch the response, and route through `readHttpLikeResponse` with `filename` and `outputPath`. + +- [ ] **Step 5: Implement upload protocol selection** + +Protocol selection: + +- If `media` is absent, use normal JSON request. +- If media is present and `body` is present and `multipart` protocol exists, use multipart. +- If media is present and body is absent and simple protocol exists, use simple. +- If file size exceeds `resumableThresholdBytes` or neither simple nor multipart is available, use resumable when available. + +Add defaults: + +```ts +const DEFAULT_RESUMABLE_THRESHOLD_BYTES = 8 * 1024 * 1024; +const DEFAULT_RESUMABLE_CHUNK_BYTES = 8 * 1024 * 1024; +``` + +- [ ] **Step 6: Implement simple upload** + +Build upload URL from `mediaUpload.protocols.simple.path` and append `uploadType=media`. Send raw bytes with content type from resolved media input. + +- [ ] **Step 7: Implement multipart upload** + +Build `multipart/related` body with JSON metadata part and media part: + +```ts +const boundary = `caplets_${randomUUID().replace(/-/gu, "")}`; +const body = Buffer.concat([ + Buffer.from( + `--${boundary}\r\ncontent-type: application/json; charset=UTF-8\r\n\r\n${JSON.stringify(args.body ?? {})}\r\n`, + ), + Buffer.from( + `--${boundary}\r\ncontent-type: ${media.mimeType ?? "application/octet-stream"}\r\n\r\n`, + ), + media.bytes, + Buffer.from(`\r\n--${boundary}--\r\n`), +]); +``` + +- [ ] **Step 8: Implement single-call resumable upload** + +Start session with `uploadType=resumable`, `X-Upload-Content-Type`, and `X-Upload-Content-Length`. Read `Location`. Upload chunks with `Content-Range`. Retry 5xx and 429 chunk failures up to 3 attempts with short exponential backoff. Return the final JSON/media response through the shared response reader. + +- [ ] **Step 9: Run media tests** + +Run: + +```bash +pnpm --filter @caplets/core test -- test/google-discovery.test.ts test/media-artifacts.test.ts +``` + +Expected: pass. + +### Task 10: CLI Add Command And Remote Add Shape + +**Files:** + +- Modify: `packages/core/src/cli/add.ts` +- Modify: `packages/core/src/cli.ts` +- Modify: `packages/core/src/remote-control/dispatch.ts` if remote add kind validation exists +- Test: `packages/core/test/cli.test.ts` +- Test: `packages/core/test/remote-control-dispatch.test.ts` if remote add kinds are tested + +- [ ] **Step 1: Add failing CLI add tests** + +In `packages/core/test/cli.test.ts`, add: + +```ts +it("prints added Google Discovery backend Caplets", async () => { + const out: string[] = []; + await runCli( + [ + "add", + "google-discovery", + "google-drive", + "--discovery-url", + "https://www.googleapis.com/discovery/v1/apis/drive/v3/rest", + "--print", + ], + { writeOut: (value) => out.push(value) }, + ); + expect(out.join("\n")).toContain("googleDiscoveryApi:"); + expect(out.join("\n")).toContain("discoveryUrl:"); +}); + +it("writes Google Discovery local discovery paths that load from the original project file", async () => { + const dir = mkdtempSync(join(tmpdir(), "caplets-add-google-discovery-path-")); + const projectRoot = join(dir, "project"); + const cwd = process.cwd(); + try { + mkdirSync(projectRoot, { recursive: true }); + writeFileSync( + join(projectRoot, "drive.discovery.json"), + JSON.stringify({ kind: "discovery#restDescription", resources: {} }), + ); + process.chdir(projectRoot); + + await runCli(["add", "google-discovery", "drive", "--discovery", "./drive.discovery.json"], { + writeOut: () => {}, + }); + + const config = loadConfig( + join(dir, "user", "config.json"), + join(projectRoot, ".caplets", "config.json"), + ); + expect(config.googleDiscoveryApis.drive?.discoveryPath).toBe( + join(projectRoot, "drive.discovery.json"), + ); + } finally { + process.chdir(cwd); + rmSync(dir, { recursive: true, force: true }); + } +}); +``` + +- [ ] **Step 2: Run failing CLI tests** + +Run: + +```bash +pnpm --filter @caplets/core test -- test/cli.test.ts +``` + +Expected: fail because command does not exist. + +- [ ] **Step 3: Add `addGoogleDiscoveryCaplet`** + +In `packages/core/src/cli/add.ts`, add options and implementation: + +```ts +type AddGoogleDiscoveryOptions = AddDestinationOptions & { + discovery?: string; + discoveryUrl?: string; + baseUrl?: string; + tokenEnv?: string; +}; + +export function addGoogleDiscoveryCaplet( + id: string, + options: AddGoogleDiscoveryOptions, +): { path?: string; text: string } { + const source = options.discovery ?? options.discoveryUrl; + if (!source) { + throw new CapletsError( + "REQUEST_INVALID", + "Google Discovery Caplet requires --discovery or --discovery-url", + ); + } + return writeGeneratedCaplet( + id, + "Google Discovery", + "googleDiscoveryApi", + [ + [isUrlLike(source) ? "discoveryUrl" : "discoveryPath", source], + ["baseUrl", options.baseUrl], + ["auth", authFromTokenEnv(options.tokenEnv) ?? { type: "none" }], + ], + options, + ); +} +``` + +- [ ] **Step 4: Add CLI command** + +In `packages/core/src/cli.ts`, add: + +```ts +add + .command("google-discovery") + .description("Add a Google Discovery API backend Caplet.") + .argument("", "Caplet ID/display seed") + .option("--discovery ", "Google Discovery document path or URL") + .option("--discovery-url ", "remote Google Discovery document URL") + .option("--base-url ", "request base URL override") + .option("--token-env ", "bearer token environment variable reference") + .option("--project", "write to the project Caplets root") + .option("-g, --global", "write to the user Caplets root") + .option("--remote", "add through remote control") + .option("--print", "print generated Caplet text without writing a file") + .option("--output ", "output path") + .option("--force", "overwrite an existing destination file"); +``` + +Use kind `"googleDiscovery"` for remote add payloads. + +- [ ] **Step 5: Run CLI tests** + +Run: + +```bash +pnpm --filter @caplets/core test -- test/cli.test.ts +``` + +Expected: pass. + +### Task 11: Docs, Schemas, And Generated References + +**Files:** + +- Modify: `docs/architecture.md` +- Modify: `apps/docs/src/content/docs/reference/config.mdx` +- Modify: `apps/docs/src/content/docs/reference/caplet-files.mdx` +- Modify: `apps/docs/src/content/docs/capabilities.mdx` +- Modify: `apps/docs/src/content/docs/troubleshooting.mdx` +- Modify: `apps/docs/src/content/docs/changelog.mdx` +- Generate: `schemas/caplets-config.schema.json` +- Generate: `schemas/caplet.schema.json` + +- [ ] **Step 1: Generate schemas** + +Run: + +```bash +pnpm schema:generate +``` + +Expected: `schemas/caplets-config.schema.json` and `schemas/caplet.schema.json` include `googleDiscoveryApis` and `googleDiscoveryApi`. + +- [ ] **Step 2: Update architecture docs** + +In `docs/architecture.md`, add `googleDiscoveryApis` to supported backend families and update the HTTP-like backend section to mention Google Discovery API backends expose operation tools and share media artifact behavior. + +- [ ] **Step 3: Update public docs** + +In `apps/docs/src/content/docs/capabilities.mdx`, add an example: + +```sh +caplets add google-discovery google-drive --discovery-url https://www.googleapis.com/discovery/v1/apis/drive/v3/rest +``` + +In reference docs, add tables for: + +- `googleDiscoveryApis` +- `googleDiscoveryApi` +- `discoveryUrl` +- `discoveryPath` +- `baseUrl` +- `includeOperations` +- `excludeOperations` +- media upload input fields + +- [ ] **Step 4: Update changelog** + +Add a concise entry to `apps/docs/src/content/docs/changelog.mdx` describing: + +- Google Discovery API backend +- inferred scopes +- media artifacts + +- [ ] **Step 5: Run doc/schema checks** + +Run: + +```bash +pnpm schema:check +pnpm format:check +``` + +Expected: pass. + +### Task 12: End-To-End Regression And Full Verification + +**Files:** + +- Test files touched by prior tasks +- Generated schemas and docs + +- [ ] **Step 1: Run focused backend test set** + +Run: + +```bash +pnpm --filter @caplets/core test -- test/google-discovery.test.ts test/media-artifacts.test.ts test/http-actions.test.ts test/openapi.test.ts test/auth.test.ts test/config.test.ts test/caplet-files.test.ts test/cli.test.ts +``` + +Expected: all tests pass. + +- [ ] **Step 2: Run typecheck** + +Run: + +```bash +pnpm typecheck +``` + +Expected: pass. + +- [ ] **Step 3: Run generated API checks** + +Run: + +```bash +pnpm code-mode:check-api +pnpm schema:check +``` + +Expected: pass. If Code Mode declaration changes are intentional, run `pnpm code-mode:generate-api` and re-run `pnpm code-mode:check-api`. + +- [ ] **Step 4: Run full verification** + +Run: + +```bash +pnpm verify +``` + +Expected: pass through `format:check`, `lint`, `code-mode:check-api`, `typecheck`, `schema:check`, `test`, `benchmark:check`, and `build`. + +- [ ] **Step 5: Manual local smoke** + +Use a local config with Google Drive: + +```json +{ + "googleDiscoveryApis": { + "google-drive": { + "name": "Google Drive", + "description": "Access and manipulate Google Drive files and folders.", + "discoveryUrl": "https://www.googleapis.com/discovery/v1/apis/drive/v3/rest", + "auth": { + "type": "oidc", + "issuer": "https://accounts.google.com", + "clientId": "$env:GOOGLE_CLIENT_ID", + "clientSecret": "$env:GOOGLE_CLIENT_SECRET" + }, + "includeOperations": [ + "drive.files.list", + "drive.files.get", + "drive.files.create", + "drive.files.download" + ] + } + } +} +``` + +Run: + +```bash +caplets auth login google-drive +caplets list-tools google-drive +caplets call-tool google-drive.drive.files.list --args '{"query":{"pageSize":5,"fields":"files(id,name,mimeType),nextPageToken"}}' --format json +``` + +Expected: + +- auth URL includes inferred Drive scopes +- tool list includes Drive file operations +- `drive.files.list` returns HTTP `200` + +## Self-Review Checklist + +- Spec coverage: + - First-class backend: Tasks 3, 4, 6, 7, 10, 11. + - Native Discovery parser: Task 5. + - Inferred scopes: Task 8. + - Operation filters: Tasks 3 and 5. + - Comprehensive media: Tasks 1, 2, 9. + - Shared Media artifacts: Tasks 1 and 2. + - Existing surfaces preserved: Tasks 7, 10, 11, 12. +- No third-party converter dependency is introduced. +- No persisted resumable sessions are introduced. +- Data URLs are fallback media inputs only. +- Implementation proceeds TDD-first with focused tests before `pnpm verify`. diff --git a/docs/plans/2026-06-17-code-mode-platform-api-compat.md b/docs/plans/2026-06-17-code-mode-platform-api-compat.md new file mode 100644 index 00000000..7674188d --- /dev/null +++ b/docs/plans/2026-06-17-code-mode-platform-api-compat.md @@ -0,0 +1,451 @@ +# Code Mode Platform API Compatibility Implementation Plan + +> **For agentic workers:** REQUIRED SKILL: Use `subagent-driven-development` (recommended) or `executing-plans` to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Expand Code Mode from a thin TypeScript runner into a browser-like, non-I/O JavaScript utility runtime with the Web and minimal Node compatibility APIs agents commonly expect. + +**Architecture:** Keep QuickJS as the execution engine and keep all I/O routed through Caplet handles. Install supported APIs as real `globalThis` properties by combining bundled guest-side platform code with narrow host bridges for entropy and timers. Use packages for spec-heavy object models, self-shims for tiny compatibility APIs, and host bridges only where the host must provide time or secure randomness. + +**Tech Stack:** TypeScript, QuickJS via `quickjs-emscripten`, Rolldown-generated guest runtime source, Vitest, pnpm 11.5.0, Node >=24. + +--- + +## Global Constraints + +- Use `pnpm` only. +- Do not expose `process`, `require`, filesystem, child process, direct network, or arbitrary imports inside Code Mode. +- Keep `fetch` unavailable for direct use; static analysis must still return `FETCH_UNAVAILABLE`. +- Supported platform APIs must be visible on both top-level bindings and `globalThis`. +- Code Mode remains a context-management runtime, not a sandbox security boundary. +- Platform builtins must be installed as runtime globals for JavaScript muscle memory, but must not be enumerated in generated Code Mode TypeScript declarations or the tool prompt. Keep the generated declaration payload lean and focused on Caplet handles, debug helpers, and `console`. +- Add a changeset because this is user-facing package behavior. + +## Package And Bridge Decisions + +| Surface | Decision | Package or bridge | +| ------------------------------------------------------------ | ---------------------------- | -------------------------------------------------------------- | +| `atob`, `btoa` | Self-shim | No package | +| Minimal `Buffer` | Self-shim | No package; support only documented encoding helpers | +| `URL`, `URLSearchParams` | Local guest shims | Package import rejected after QuickJS bundle testing | +| `TextEncoder`, `TextDecoder` | Local guest shims | Package import rejected after QuickJS bundle testing | +| `structuredClone` | Package-backed guest global | `@ungap/structured-clone` | +| `Headers` | Package-backed guest global | `headers-polyfill` | +| `Blob`, `File`, `FormData` | Package-backed guest globals | `formdata-node` | +| `ReadableStream`, `WritableStream`, `TransformStream` | Local guest shims | Package import rejected after QuickJS runtime testing | +| `AbortController`, `AbortSignal` | Self-shim | No package unless tests prove an EventTarget package is needed | +| `Request`, `Response` | Self-shim data containers | Built on `Headers`, `Blob`, and body helpers; no fetch stack | +| `crypto.randomUUID`, `crypto.getRandomValues` | Host bridge | Node `crypto` only behind narrow bridge functions | +| `setTimeout`, `clearTimeout`, `setInterval`, `clearInterval` | Host bridge | Host timers connected to QuickJS pending-deferred drain | +| `queueMicrotask` | Self-shim | `Promise.resolve().then(...)` | + +Packages researched and rejected for this plan: `buffer`, `abab`, `url-parse`, `text-encoding`, `core-js`, `uuid`, `nanoid`, `randombytes`, `crypto-browserify`, `@peculiar/webcrypto`, `timers-browserify`, `setimmediate`, `abort-controller`, `undici`, `cross-fetch`, `whatwg-fetch`, `@whatwg-node/fetch`, `formdata-polyfill`, and `fetch-blob`. + +`whatwg-fetch` is intentionally rejected even for `Request` and `Response`: it is a browser `window.fetch`/XHR polyfill that mutates `globalThis.fetch` when absent, assumes browser primitives such as `XMLHttpRequest` and `FileReader` for important paths, and pulls the runtime toward network semantics that Code Mode must keep unavailable. + +Task 3 amendment after controller adjudication: local shims are accepted for `TextEncoder`/`TextDecoder`, `URL`/`URLSearchParams`, and streams. The package-backed text import initializes `new TextDecoder(...)` before Code Mode can install a guest `TextDecoder`, `whatwg-url` inherits that early initialization problem, and the stream package path did not settle reliably inside the QuickJS guest runtime. These shims stay narrow and are covered by the public Code Mode platform contract tests. + +Primary package sources used during planning: + +- [npm: base64-js](https://www.npmjs.com/package/base64-js) +- [npm: buffer](https://www.npmjs.com/package/buffer) +- [npm: whatwg-url](https://www.npmjs.com/package/whatwg-url) +- [npm: @exodus/bytes](https://www.npmjs.com/package/@exodus/bytes) +- [npm: @ungap/structured-clone](https://www.npmjs.com/package/@ungap/structured-clone) +- [npm: headers-polyfill](https://www.npmjs.com/package/headers-polyfill) +- [npm: formdata-node](https://www.npmjs.com/package/formdata-node) +- [npm: web-streams-polyfill](https://www.npmjs.com/package/web-streams-polyfill) +- [npm: uuid](https://www.npmjs.com/package/uuid) +- [npm: nanoid](https://www.npmjs.com/package/nanoid) +- [npm: crypto-browserify](https://www.npmjs.com/package/crypto-browserify) +- [npm downloads API](https://api.npmjs.org/downloads/) + +## File Structure Map + +Create: + +- `packages/core/src/code-mode/platform-entry.ts` - guest-side runtime entry bundled into a string and evaluated inside QuickJS before user code. +- `packages/core/src/code-mode/platform-runtime.generated.ts` - generated bundled guest runtime source. Do not edit by hand. +- `packages/core/src/code-mode/platform-host.ts` - host bridge installation for secure randomness and timers, plus cleanup. +- `scripts/generate-code-mode-platform-runtime.mjs` - bundles `platform-entry.ts` into `platform-runtime.generated.ts` and supports `--check`. +- `packages/core/test/code-mode-platform-api.test.ts` - runtime contract tests for all supported globals. +- `.changeset/code-mode-platform-apis.md` - release note for the expanded Code Mode runtime. + +Modify: + +- `package.json` - add platform runtime generation/check scripts to the existing Code Mode check path. +- `pnpm-lock.yaml` - update after adding dependencies. +- `packages/core/package.json` - add runtime dependencies for package-backed platform APIs. +- `packages/core/src/code-mode/sandbox.ts` - install host bridges, prepend generated guest runtime source, and dispose active timers/deferreds safely. +- `packages/core/src/code-mode/runtime-api.d.ts` - keep lean Caplet handle/debug/console declarations. +- `packages/core/src/code-mode/runtime-api.generated.ts` - regenerate from `runtime-api.d.ts`. +- `packages/core/src/code-mode/diagnostics.ts` - keep fetch/import safety diagnostics authoritative. +- `packages/core/test/code-mode-diagnostics.test.ts` - cover platform runtime behavior and fetch blocking without expanding the prompt declaration payload. +- `packages/core/test/code-mode-declarations.test.ts` - ensure generated declarations stay focused on Caplet handles/debug/console. +- `apps/docs/src/content/docs/code-mode.mdx` - document available platform APIs and unavailable direct I/O. +- `apps/docs/src/content/docs/reference/code-mode-api.mdx` - regenerate after runtime API declaration changes. +- `docs/architecture.md` - update Code Mode runtime contract. +- `docs/product/caplets-code-mode-prd.md` - update Code Mode contract. + +## Implementation Tasks + +### Task 1: Add Red Runtime Contract Tests + +**Files:** + +- Create: `packages/core/test/code-mode-platform-api.test.ts` +- Modify: none + +**Interfaces:** + +- Consumes: `runCodeMode(input)` from `packages/core/src/code-mode/runner.ts`. +- Produces: executable runtime contract for every platform API in this plan. + +- [ ] **Step 1: Create the test file** + +Add `packages/core/test/code-mode-platform-api.test.ts` with one helper `runPlatformCode(code: string)` that calls `runCodeMode` with a minimal fake `NativeCapletsService`, following the pattern in `packages/core/test/code-mode-runner.test.ts`. + +Required tests: + +- `exposes utility globals on globalThis` +- `supports base64 and minimal Buffer conversions` +- `supports URL and URLSearchParams` +- `supports text encoding and decoding` +- `supports crypto randomUUID and getRandomValues` +- `supports timers, intervals, and microtasks` +- `supports structuredClone` +- `supports Headers, Blob, File, FormData, streams, Request, and Response` +- `supports AbortController and AbortSignal` +- `keeps fetch unavailable for direct calls` +- `keeps Node and module globals unavailable` + +The test scripts must return JSON-serializable values only. Assert exact values for deterministic APIs and shape/patterns for random APIs. + +- [ ] **Step 2: Run the new test and confirm it fails** + +Run: `pnpm --filter @caplets/core test -- test/code-mode-platform-api.test.ts` + +Expected: FAIL with missing globals such as `btoa`, `URL`, `TextEncoder`, `crypto`, and stream constructors. + +### Task 2: Add Generated Guest Platform Runtime Infrastructure + +**Files:** + +- Create: `packages/core/src/code-mode/platform-entry.ts` +- Create: `packages/core/src/code-mode/platform-runtime.generated.ts` +- Create: `scripts/generate-code-mode-platform-runtime.mjs` +- Modify: `package.json` +- Modify: `packages/core/package.json` +- Modify: `pnpm-lock.yaml` +- Modify: `packages/core/src/code-mode/sandbox.ts` + +**Interfaces:** + +- Produces: `CODE_MODE_PLATFORM_RUNTIME_SOURCE: string` from `platform-runtime.generated.ts`. +- Produces: `platform-entry.ts` that installs guest-side globals onto `globalThis`. +- Consumes: `CODE_MODE_PLATFORM_RUNTIME_SOURCE` in `buildExecutionSource()`. + +- [ ] **Step 1: Add dependencies** + +Run: + +```bash +pnpm add --filter @caplets/core whatwg-url @exodus/bytes @ungap/structured-clone headers-polyfill formdata-node web-streams-polyfill +``` + +Expected: `packages/core/package.json` and `pnpm-lock.yaml` are updated. + +- [ ] **Step 2: Add the platform runtime generator** + +Create `scripts/generate-code-mode-platform-runtime.mjs`. + +The script must: + +- Bundle `packages/core/src/code-mode/platform-entry.ts` with Rolldown. +- Output one IIFE/plain script string. +- Write `packages/core/src/code-mode/platform-runtime.generated.ts`. +- Support `--check` and fail if the generated file is stale. +- Externalize nothing from the guest bundle except built-in globals already available inside QuickJS. + +- [ ] **Step 3: Add the generated source to the sandbox wrapper** + +Modify `packages/core/src/code-mode/sandbox.ts` so `buildExecutionSource()` prepends `CODE_MODE_PLATFORM_RUNTIME_SOURCE` before user code and before the generated `caplets` handles. Move the existing lexical `console` and disabled `fetch` definitions into the platform runtime so they become true `globalThis` properties. + +- [ ] **Step 4: Wire scripts into the existing Code Mode gate** + +Modify root `package.json` so: + +- `code-mode:generate-api` runs both runtime API generation and platform runtime generation. +- `code-mode:check-api` checks both generated files. + +Run: `pnpm code-mode:generate-api && pnpm code-mode:check-api` + +Expected: PASS. + +### Task 3: Implement Package-Backed And Self-Shim Guest APIs + +**Files:** + +- Modify: `packages/core/src/code-mode/platform-entry.ts` +- Modify: `packages/core/src/code-mode/platform-runtime.generated.ts` +- Test: `packages/core/test/code-mode-platform-api.test.ts` + +**Interfaces:** + +- Produces globals: `atob`, `btoa`, `Buffer`, `URL`, `URLSearchParams`, `TextEncoder`, `TextDecoder`, `structuredClone`, `Headers`, `Blob`, `File`, `FormData`, `ReadableStream`, `WritableStream`, `TransformStream`, `AbortController`, `AbortSignal`, `Request`, `Response`, `fetch`, `console`, `queueMicrotask`. + +- [ ] **Step 1: Add global installation helper** + +In `platform-entry.ts`, add a local `definePlatformGlobal(name, value)` helper that uses `Object.defineProperty(globalThis, name, { value, writable: true, configurable: true })` only when the value is not already installed. + +- [ ] **Step 2: Install package-backed globals** + +Import and install: + +```ts +import { URL, URLSearchParams } from "whatwg-url"; +import { TextDecoder, TextEncoder } from "@exodus/bytes/encoding-lite.js"; +import structuredClone from "@ungap/structured-clone"; +import { Headers } from "headers-polyfill"; +import { Blob, File, FormData } from "formdata-node"; +import { ReadableStream, TransformStream, WritableStream } from "web-streams-polyfill"; +``` + +If one import shape differs, fix the import in `platform-entry.ts` and keep the generated runtime check as the source of truth. + +- [ ] **Step 3: Add small self-shims** + +In `platform-entry.ts`, implement: + +- `atob(input: string): string` +- `btoa(input: string): string` +- minimal `Buffer.from(input, encoding?)` +- `Buffer.isBuffer(value)` +- `Buffer.byteLength(input, encoding?)` +- `buffer.toString(encoding?)` +- `queueMicrotask(callback)` +- `AbortController` and `AbortSignal` +- data-only `Request` and `Response` wrappers +- disabled `fetch` that throws `Direct fetch is not available in Code Mode; use a Caplet instead.` +- `console` bridge that calls `__caplets_log` + +Supported encodings for the Buffer subset: `utf8`, `utf-8`, `base64`, `base64url`, and `hex`. Unsupported encodings must throw `TypeError`. + +- [ ] **Step 4: Generate and test** + +Run: + +```bash +pnpm code-mode:generate-api +pnpm --filter @caplets/core test -- test/code-mode-platform-api.test.ts +``` + +Expected: all non-crypto and non-timer tests pass; crypto/timer tests may still fail until Task 4. + +### Task 4: Implement Host Bridges For Crypto And Timers + +**Files:** + +- Create: `packages/core/src/code-mode/platform-host.ts` +- Modify: `packages/core/src/code-mode/sandbox.ts` +- Modify: `packages/core/src/code-mode/platform-entry.ts` +- Modify: `packages/core/src/code-mode/platform-runtime.generated.ts` +- Test: `packages/core/test/code-mode-platform-api.test.ts` + +**Interfaces:** + +- Produces: `installCodeModePlatformHost(context, pendingDeferreds, options): { dispose(): void }`. +- Host bridge globals visible only to guest runtime internals: + - `__caplets_platform_random_uuid(): string` + - `__caplets_platform_random_values(length: number): number[]` + - `__caplets_platform_sleep(timerId: number, delayMs: number): Promise` + - `__caplets_platform_clear_timer(timerId: number): boolean` + +- [ ] **Step 1: Add crypto bridges** + +In `platform-host.ts`, use `node:crypto` to implement UUID and random byte generation. In `platform-entry.ts`, install: + +- `crypto.randomUUID()` +- `crypto.getRandomValues(typedArray)` + +`getRandomValues` must reject non-integer typed arrays and arrays over 65,536 bytes with `TypeError` or `QuotaExceededError`. + +- [ ] **Step 2: Add timer bridges** + +In `platform-host.ts`, implement host-backed sleep promises that are tracked in the existing `pendingDeferreds` set and resolve `true` when fired or `false` when cleared. + +In `platform-entry.ts`, implement: + +- `setTimeout(callback, delay, ...args)` +- `clearTimeout(id)` +- `setInterval(callback, delay, ...args)` +- `clearInterval(id)` + +Active intervals must keep Code Mode alive until cleared or until the overall Code Mode timeout fires. `dispose()` must clear all host timers. + +- [ ] **Step 3: Run focused tests** + +Run: `pnpm --filter @caplets/core test -- test/code-mode-platform-api.test.ts test/code-mode-runner.test.ts` + +Expected: PASS. + +### Task 5: Keep Lean Declarations And Diagnostics + +**Files:** + +- Modify: `packages/core/src/code-mode/runtime-api.d.ts` +- Modify: `packages/core/src/code-mode/runtime-api.generated.ts` +- Modify: `packages/core/src/code-mode/diagnostics.ts` +- Modify: `packages/core/test/code-mode-diagnostics.test.ts` +- Modify: `packages/core/test/code-mode-declarations.test.ts` + +**Interfaces:** + +- Preserves the lean declaration surface for Caplet handles, debug helpers, and `console`. +- Keeps platform builtins as runtime globals without listing them in generated declarations or the tool prompt. +- Preserves diagnostics for direct `fetch` and imports. + +- [ ] **Step 1: Keep runtime API declarations lean** + +Do not add declarations for `atob`, `btoa`, `Buffer`, URL helpers, Web data objects, timers, crypto, or `fetch`. Those APIs are runtime globals for standard JavaScript muscle memory, not generated prompt payload. Keep `runtime-api.d.ts` focused on Caplet handles, `caplets.debug`, shared result types, and `console`. + +- [ ] **Step 2: Preserve diagnostics instead of ambient platform declarations** + +Keep direct `fetch` detection and import/module blocking authoritative. Do not use ambient declarations to make the generated prompt surface list platform builtins. + +- [ ] **Step 3: Add diagnostics tests** + +Extend `packages/core/test/code-mode-diagnostics.test.ts` to assert: + +- `await fetch("https://example.com")` still produces `FETCH_UNAVAILABLE`. +- `await globalThis.fetch("https://example.com")` still produces `FETCH_UNAVAILABLE`. +- `import("node:fs")`, `process.cwd()`, and `require("fs")` remain blocked or unknown. + +Extend `packages/core/test/code-mode-declarations.test.ts` to assert generated declarations do not enumerate platform globals such as `Buffer`, `URL`, or `fetch`. + +- [ ] **Step 4: Generate and check declarations** + +Run: + +```bash +pnpm code-mode:generate-api +pnpm code-mode:check-api +pnpm --filter @caplets/core test -- test/code-mode-diagnostics.test.ts test/code-mode-declarations.test.ts +``` + +Expected: PASS. + +### Task 6: Update Docs And Release Notes + +**Files:** + +- Modify: `apps/docs/src/content/docs/code-mode.mdx` +- Modify: `apps/docs/src/content/docs/reference/code-mode-api.mdx` +- Modify: `docs/architecture.md` +- Modify: `docs/product/caplets-code-mode-prd.md` +- Modify or create: `.changeset/code-mode-platform-apis.md` + +**Interfaces:** + +- Produces user-facing documentation of supported and intentionally unavailable APIs. + +- [ ] **Step 1: Update Code Mode docs** + +Document the platform API groups: + +- Pure utility globals +- Web data model globals +- Timer and microtask globals +- Crypto randomness globals +- Explicitly unavailable Node and direct network APIs + +State that `fetch` is intentionally unavailable and Caplet handles must be used for I/O. + +- [ ] **Step 2: Regenerate reference docs if required** + +Run: `pnpm docs:generate` + +Expected: `apps/docs/src/content/docs/reference/code-mode-api.mdx` reflects the lean `runtime-api.d.ts` handle/debug/console surface and does not claim to list every runtime builtin. + +- [ ] **Step 3: Add or verify changeset** + +Create `.changeset/code-mode-platform-apis.md` if it does not already exist. If it exists, update it in place rather than creating a duplicate: + +```md +--- +"@caplets/core": minor +"caplets": minor +"@caplets/opencode": minor +"@caplets/pi": minor +--- + +Expand Code Mode with browser-like platform APIs for data manipulation, encoding, timers, crypto randomness, and web object compatibility while keeping direct network and Node APIs unavailable. +``` + +### Task 7: End-To-End Verification + +**Files:** + +- Modify only files already listed in earlier tasks. + +**Interfaces:** + +- Produces verified local Code Mode behavior through tests, CLI, and MCP smoke. + +- [ ] **Step 1: Run focused core checks** + +Run: + +```bash +pnpm --filter @caplets/core test -- test/code-mode-platform-api.test.ts test/code-mode-runner.test.ts test/code-mode-diagnostics.test.ts test/code-mode-declarations.test.ts test/code-mode-cli.test.ts test/code-mode-mcp.test.ts +``` + +Expected: PASS. + +- [ ] **Step 2: Run generated-file checks** + +Run: + +```bash +pnpm code-mode:check-api +pnpm docs:check +``` + +Expected: PASS. + +- [ ] **Step 3: Run CLI smoke** + +Run: + +```bash +pnpm --filter caplets build +node packages/cli/dist/index.js code-mode 'return { btoa: btoa("hi"), url: new URL("https://example.com/a?b=1").searchParams.get("b"), text: new TextDecoder().decode(new TextEncoder().encode("ok")), uuid: /^[0-9a-f-]{36}$/i.test(crypto.randomUUID()) }' --json +``` + +Expected JSON value: + +```json +{ + "btoa": "aGk=", + "url": "1", + "text": "ok", + "uuid": true +} +``` + +- [ ] **Step 4: Run local MCP smoke after restarting the local Caplets MCP server** + +Use `caplets-local.code_mode` with the same probe from Step 3. + +Expected: `ok: true` and the same values as the CLI smoke. + +- [ ] **Step 5: Run the repo gate** + +Run: `pnpm verify` + +Expected: PASS through `format:check`, `lint`, `code-mode:check-api`, `schema:check`, `docs:check`, `typecheck`, `test`, `benchmark:check`, and `build`. + +## Self-Review + +- Spec coverage: every requested functional area has a decision and a task: base64/Buffer, URL, text encoding, crypto, timers, structured clone, headers, blob/file/form-data, streams, abort, request/response, declarations, docs, smoke, and release notes. +- Placeholder scan: no unresolved implementation markers or unsupported vague steps. +- Type consistency: generated runtime source is named `CODE_MODE_PLATFORM_RUNTIME_SOURCE`; host bridge installer is named `installCodeModePlatformHost`; runtime declarations remain the lean Caplet handle/debug/console source of truth, not an exhaustive catalog of platform builtins. diff --git a/docs/product/caplets-code-mode-prd.md b/docs/product/caplets-code-mode-prd.md index 0755719d..efddcbbe 100644 --- a/docs/product/caplets-code-mode-prd.md +++ b/docs/product/caplets-code-mode-prd.md @@ -89,6 +89,14 @@ Code Mode runs TypeScript with generated `caplets.` handles. Handles expose: - MCP-only resource, template, prompt, and completion methods where supported. - `caplets.debug.readLogs()` for stored Code Mode log inspection when available. +The runtime also provides common JavaScript platform globals for data manipulation: +`atob`, `btoa`, a minimal `Buffer` subset, `structuredClone`, URL and text encoding +helpers, Web data objects (`Headers`, `Blob`, `File`, `FormData`, streams, +`AbortController`, `AbortSignal`, `Request`, `Response`), timers, microtasks, and crypto +randomness (`crypto.randomUUID()` and `crypto.getRandomValues(...)`). These are available +as runtime globals for JS muscle memory, but they are intentionally not enumerated in the +generated declaration payload or tool prompt so Code Mode keeps its context surface lean. + Agents should keep bulky discovery and raw payload handling inside the Code Mode script, then return compact decision-ready JSON with the evidence fields needed by the user. ## Exposure Modes @@ -113,6 +121,7 @@ Progressive exposure registers one wrapper tool per Caplet and supports scoped o - HTTP-like backends reject unsafe base URLs and redact configured secrets from errors. - OAuth/OIDC tokens are stored outside config and loaded lazily. - Project Binding sync filters deny dangerous directories, private keys, unsafe env files, caches, build outputs, and ignored files. +- Direct `fetch`, Node/process/module APIs, filesystem access, child processes, and direct network access are unavailable inside Code Mode; route I/O through Caplet handles. - Progressive discovery and Code Mode are context-management tools, not permission boundaries. ## Evidence diff --git a/docs/specs/2026-06-16-google-discovery-api-backend.md b/docs/specs/2026-06-16-google-discovery-api-backend.md new file mode 100644 index 00000000..f91f0746 --- /dev/null +++ b/docs/specs/2026-06-16-google-discovery-api-backend.md @@ -0,0 +1,229 @@ +# Google Discovery API Backend + +## Summary + +Add a first-class Google Discovery API backend for Google APIs whose machine-readable contract is a Google Discovery document rather than OpenAPI. The backend should be comprehensive: operation discovery, inferred OAuth scopes, operation filtering, JSON calls, media downloads, media uploads, and Caplet file support all ship as part of the design. + +This backend must not live under `openapiEndpoints`. It is a separate backend family with its own source format and manager, while sharing lower-level HTTP, auth, and media artifact infrastructure with other HTTP-like backends. + +## Goals + +- Add top-level `googleDiscoveryApis` config and `googleDiscoveryApi` Caplet file support. +- Parse Google Discovery documents natively instead of converting them to OpenAPI as the primary abstraction. +- Infer OAuth scopes from exposed Discovery operations unless `auth.scopes` overrides inference. +- Support operation include/exclude filters and compute inferred scopes after filtering. +- Support comprehensive Google media download and upload behavior. +- Add a shared Media artifact contract usable by Google Discovery, OpenAPI, HTTP, and future media-capable backends. +- Preserve existing Caplets exposure modes, Code Mode handles, progressive wrappers, native service behavior, and CLI inspection/call surfaces. + +## Non-Goals + +- Do not depend on hosted APIs.guru specs or a third-party Discovery-to-OpenAPI converter. +- Do not persist resumable upload sessions across separate Caplets calls in the first version. +- Do not auto-confirm destructive operations inside Caplets. Caplets exposes safety hints; clients and agents decide how to handle them. +- Do not treat inline data URLs as the primary agent media path. + +## Public Config + +Top-level config uses `googleDiscoveryApis`: + +```json +{ + "googleDiscoveryApis": { + "google-drive": { + "name": "Google Drive", + "description": "Access and manipulate Google Drive files and folders.", + "discoveryUrl": "https://www.googleapis.com/discovery/v1/apis/drive/v3/rest", + "auth": { + "type": "oidc", + "issuer": "https://accounts.google.com", + "clientId": "$env:GOOGLE_CLIENT_ID", + "clientSecret": "$env:GOOGLE_CLIENT_SECRET" + } + } + } +} +``` + +Supported source fields: + +- `discoveryUrl`: remote Google Discovery document URL. +- `discoveryPath`: local Google Discovery document path. +- `baseUrl`: optional request base URL override; default is inferred from the document. + +Common Caplet fields match other backend families: `name`, `description`, `auth`, `tags`, `exposure`, `shadowing`, `useWhen`, `avoidWhen`, `setup`, `projectBinding`, `runtime`, `requestTimeoutMs`, `operationCacheTtlMs`, and `disabled`. + +Caplet files use `googleDiscoveryApi`: + +```yaml +googleDiscoveryApi: + discoveryUrl: https://www.googleapis.com/discovery/v1/apis/drive/v3/rest + auth: + type: oidc + issuer: https://accounts.google.com +``` + +The normalized backend discriminator is `backend: "googleDiscovery"`. + +## Discovery Mapping + +`GoogleDiscoveryManager` loads `discoveryUrl` or `discoveryPath`, validates that the document is a Google Discovery document, and recursively walks `resources.*.methods.*`. + +Each Discovery method becomes a Caplets tool: + +- tool name comes from `method.id`, such as `drive.files.list` +- HTTP method comes from `method.httpMethod` +- request path comes from `method.path` +- base URL is inferred from `baseUrl`, or `rootUrl + servicePath`, with config override support +- path/query parameters come from method parameters plus global parameters +- request body schema comes from `method.request.$ref` +- response body schema comes from `method.response.$ref` +- schemas come from top-level Discovery `schemas`, converted into the JSON Schema-like shape Caplets tools expose + +The manager preserves Google-specific metadata internally, including `supportsMediaUpload`, `mediaUpload.protocols`, `supportsMediaDownload`, `parameterOrder`, and operation scopes. + +## Operation Filters + +Google Discovery APIs support operation filters: + +```json +{ + "includeOperations": ["drive.files.*", "drive.permissions.list"], + "excludeOperations": ["*.delete", "drive.files.emptyTrash"] +} +``` + +Rules: + +- Operation IDs are Discovery `method.id` values. +- If `includeOperations` is absent, all operations are included. +- `excludeOperations` applies after include. +- Filtering controls tool discovery, tool execution, and inferred scopes. +- Glob matching is simple and documented; it is not regular-expression matching. + +## Auth And Scope Inference + +Google Discovery APIs infer OAuth scopes from the Discovery document unless `auth.scopes` overrides inference. + +Rules: + +- If `auth.scopes` is configured, use it exactly. +- Otherwise infer scopes from the final exposed operation set after operation filters. +- For `oidc`, include `openid profile email` plus inferred Google API scopes. +- De-duplicate and sort inferred API scopes for stable authorization URLs. +- Store the requested or granted scope string in the OAuth token bundle. +- Tool descriptors expose operation-level possible scopes as guidance. +- If config changes cause the inferred scope set to differ from the stored bundle, require re-login rather than silently calling with insufficient scopes. + +The generic OAuth flow needs a way for backends to supply resolved scopes at login time; today it only reads static `auth.scopes`. + +## Safety Annotations + +Safety annotations mirror existing HTTP-like behavior: + +- `GET` and `HEAD` operations are `readOnlyHint: true`. +- `DELETE` operations are `destructiveHint: true`. +- Known destructive non-DELETE methods also receive destructive hints using method ID and description patterns, such as `emptyTrash`. +- Caplets does not add confirmation prompts. + +Broad Google APIs are still exposed by default. Users can narrow them with operation filters. + +## Shared Media Artifact Contract + +Media-capable backends use a shared Media artifact contract. + +Small JSON and text responses remain inline. Binary responses, media downloads, and oversized text responses are written to Caplets-managed artifact storage and returned as metadata: + +```json +{ + "status": 200, + "headers": { "content-type": "application/pdf" }, + "body": { + "artifact": { + "path": "/Users/ianpascoe/.local/state/caplets/artifacts/google-drive/...", + "mimeType": "application/pdf", + "byteLength": 12345, + "sha256": "..." + } + } +} +``` + +Default artifact storage: + +```text +~/.local/state/caplets/artifacts/// +``` + +Rules: + +- Caller-provided `outputPath` is allowed for explicit download destinations. +- Local execution returns absolute local paths. +- Remote and hosted execution return artifact references or links, not pretend-local paths. +- Artifact metadata should also appear in `_meta.caplets.artifacts` when useful for clients. +- HTTP, OpenAPI, Google Discovery, and future media-capable backends share the infrastructure. + +## Google Media Download And Upload + +Downloads and exports: + +- `supportsMediaDownload` methods can return inline JSON/text when appropriate or Media artifacts for binary/large content. +- Download/export tools accept optional `outputPath` and `filename`. +- Response metadata includes status, content type, byte count, hash, and artifact information. + +Uploads use an agent-first media input contract: + +```json +{ + "body": { "name": "report.pdf" }, + "media": { "path": "/abs/path/report.pdf", "mimeType": "application/pdf" } +} +``` + +Supported media sources, in priority order: + +- `media.path`: primary for local coding agents. +- `media.artifact`: primary for chaining prior Caplets media outputs into uploads. +- `media.dataUrl`: supported only as a small-input fallback. + +Rules: + +- Exactly one of `path`, `artifact`, or `dataUrl` is accepted. +- Raw `dataUrl` content must not be echoed in output, logs, errors, or descriptors. +- Max decoded or input size is enforced. +- MIME type is inferred from file, artifact, or data URL when possible; explicit `mimeType` can override inference. + +Google upload protocols: + +- simple upload for media-only requests +- multipart upload for metadata plus media +- resumable upload for large files or when selected by protocol/defaults + +Resumable uploads are internal to one Caplets call. The backend may retry transient chunk failures within that call, but v1 does not expose persisted resumable session resume or cancel commands. + +## CLI And Docs + +CLI additions: + +- `caplets add google-discovery --discovery-url ` +- optional parity form: `--discovery ` +- existing `list`, `inspect`, `list-tools`, `get-tool`, `call-tool`, `auth login`, and Code Mode surfaces work with the new backend + +Docs updates: + +- configuration reference for `googleDiscoveryApis` +- Caplet file reference for `googleDiscoveryApi` +- capabilities docs for Google Discovery API backends +- media artifact documentation shared across HTTP-like backends +- ADR for path/artifact-based media result handling + +## Verification + +Implementation should include: + +- unit tests for Discovery parsing, schema conversion, operation filtering, scope inference, and request construction +- tests for media artifact writing and inline-vs-artifact response selection +- tests for simple, multipart, and single-call resumable upload behavior using local fixtures +- CLI tests for add/list/get/call/auth URL behavior +- schema generation and schema checks after config changes +- focused tests first, then `pnpm verify` once implementation is complete diff --git a/mise.toml b/mise.toml index bbf696ba..7588d63c 100644 --- a/mise.toml +++ b/mise.toml @@ -1,3 +1,3 @@ [tools] -node = "24" -pnpm = "11.5.0" +node = "22" +pnpm = "11.7.0" diff --git a/package.json b/package.json index 3de366c5..d4200988 100644 --- a/package.json +++ b/package.json @@ -11,13 +11,15 @@ "benchmark:live": "pnpm --filter @caplets/benchmarks benchmark:live", "benchmark:live:opencode": "pnpm --filter @caplets/benchmarks benchmark:live:opencode", "benchmark:live:pi": "pnpm --filter @caplets/benchmarks benchmark:live:pi", + "benchmark:live:pi-eval": "pnpm --filter @caplets/benchmarks benchmark:live:pi-eval", + "benchmark:live:pi-eval:competitors": "pnpm --filter @caplets/benchmarks benchmark:live:pi-eval:competitors", "build": "turbo build", "build:watch": "turbo build:watch", "changeset": "changeset", "clean": "turbo clean", "clean-install": "rm -rf **/node_modules pnpm-lock.yaml && pnpm install", - "code-mode:check-api": "node scripts/generate-code-mode-runtime-api.mjs --check", - "code-mode:generate-api": "node scripts/generate-code-mode-runtime-api.mjs", + "code-mode:check-api": "node scripts/generate-code-mode-runtime-api.mjs --check && node scripts/generate-code-mode-platform-runtime.mjs --check", + "code-mode:generate-api": "node scripts/generate-code-mode-runtime-api.mjs && node scripts/generate-code-mode-platform-runtime.mjs", "dev": "tsx ./scripts/dev.ts", "docs:check": "tsx ./scripts/generate-docs-reference.ts --check && tsx ./scripts/check-public-docs.ts", "docs:generate": "tsx ./scripts/generate-docs-reference.ts", @@ -33,9 +35,7 @@ "test": "vitest run", "typecheck": "tsgo --noEmit && turbo typecheck", "verify": "pnpm format:check && pnpm lint && pnpm code-mode:check-api && pnpm schema:check && pnpm docs:check && pnpm typecheck && pnpm test && pnpm benchmark:check && pnpm build", - "version-packages": "changeset version && oxlint --fix --quiet && oxfmt --write .", - "benchmark:live:pi-eval": "pnpm --filter @caplets/benchmarks benchmark:live:pi-eval", - "benchmark:live:pi-eval:competitors": "pnpm --filter @caplets/benchmarks benchmark:live:pi-eval:competitors" + "version-packages": "changeset version && oxlint --fix --quiet && oxfmt --write ." }, "devDependencies": { "@changesets/cli": "^2.31.0", @@ -55,7 +55,8 @@ "vitest": "^4.1.8" }, "engines": { - "node": ">=24" + "bun": ">=1", + "node": ">=22" }, - "packageManager": "pnpm@11.5.0" + "packageManager": "pnpm@11.7.0" } diff --git a/packages/cli/package.json b/packages/cli/package.json index 28a1d340..2d59c323 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -52,6 +52,7 @@ "vitest": "^4.1.8" }, "engines": { - "node": ">=24" + "bun": ">=1", + "node": ">=22" } } diff --git a/packages/core/package.json b/packages/core/package.json index 3f85348d..c83d8763 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -94,17 +94,23 @@ "dependencies": { "@apidevtools/swagger-parser": "^12.1.0", "@babel/parser": "^7.29.7", + "@exodus/bytes": "^1.15.1", "@hono/mcp": "^0.3.0", "@hono/node-server": "^2.0.4", "@modelcontextprotocol/sdk": "^1.29.0", + "@ungap/structured-clone": "^1.3.1", "ajv": "^8.20.0", "commander": "^15.0.0", + "formdata-node": "^6.0.3", "graphql": "^16.14.2", + "headers-polyfill": "^5.0.1", "hono": "^4.12.25", "quickjs-emscripten": "^0.32.0", "typescript": "^6.0.3", "vfile": "^6.0.3", "vfile-matter": "^5.0.1", + "web-streams-polyfill": "^4.3.0", + "whatwg-url": "^16.0.1", "yaml": "^2.9.0", "zod": "^4.4.3" }, @@ -115,6 +121,7 @@ "vitest": "^4.1.8" }, "engines": { - "node": ">=24" + "bun": ">=1", + "node": ">=22" } } diff --git a/packages/core/src/attach/server.ts b/packages/core/src/attach/server.ts index 5859ec0f..56b363cb 100644 --- a/packages/core/src/attach/server.ts +++ b/packages/core/src/attach/server.ts @@ -64,6 +64,7 @@ function createAttachNativeService(options: AttachServeOptions, io: AttachServeI url: options.selection.remote.attachUrl, ...(options.selection.remote.fetch ? { fetch: options.selection.remote.fetch } : {}), }), + exposeLocalArtifactPaths: false, ...(io.writeErr ? { writeErr: io.writeErr } : {}), }); } diff --git a/packages/core/src/auth.ts b/packages/core/src/auth.ts index b66388f4..c03812bc 100644 --- a/packages/core/src/auth.ts +++ b/packages/core/src/auth.ts @@ -47,10 +47,11 @@ type OAuthLikeAuthConfig = { export type GenericAuthTarget = { server: string; - backend: "openapi" | "graphql" | "http"; + backend: "openapi" | "googleDiscovery" | "graphql" | "http"; url?: string | undefined; baseUrl?: string | undefined; specUrl?: string | undefined; + resolvedScopes?: string[] | undefined; auth?: OAuthLikeAuthConfig | { type: string } | undefined; requestTimeoutMs?: number | undefined; }; @@ -557,7 +558,7 @@ export async function startGenericOAuthFlow( redirectUri, allowLoopbackHttp, ); - const scope = scopesFor(authConfig); + const scope = scopesFor(authConfig, target.resolvedScopes); const authorizationUrl = new URL(authorizationEndpoint); authorizationUrl.searchParams.set("response_type", "code"); authorizationUrl.searchParams.set("client_id", client.clientId); @@ -621,6 +622,7 @@ export async function startGenericOAuthFlow( metadata: redactSecrets({ protectedResource: target.url ?? target.baseUrl ?? target.specUrl, authorizationServer: metadata, + requestedScopes: scope?.split(/\s+/u).filter(Boolean), dynamicClient: client.dynamic ? { client_id: client.clientId } : undefined, }) as Record, }), @@ -680,7 +682,7 @@ export async function runGenericOAuthFlow( redirectUri, allowLoopbackHttp, ); - const scope = scopesFor(authConfig); + const scope = scopesFor(authConfig, target.resolvedScopes); const authorizationUrl = new URL(authorizationEndpoint); authorizationUrl.searchParams.set("response_type", "code"); authorizationUrl.searchParams.set("client_id", client.clientId); @@ -755,6 +757,7 @@ export async function runGenericOAuthFlow( metadata: redactSecrets({ protectedResource: target.url ?? target.baseUrl ?? target.specUrl, authorizationServer: metadata, + requestedScopes: scope?.split(/\s+/u).filter(Boolean), dynamicClient: client.dynamic ? { client_id: client.clientId } : undefined, }) as Record, }); @@ -1098,7 +1101,8 @@ async function refreshGenericOAuthBundle( refreshToken: asString(tokenResponse.refresh_token) ?? bundle.refreshToken, tokenType: asString(tokenResponse.token_type) ?? bundle.tokenType, expiresAt: refreshedExpiresAt(tokenResponse.expires_in, bundle.expiresAt), - scope: asString(tokenResponse.scope) ?? bundle.scope ?? scopesFor(authConfig), + scope: + asString(tokenResponse.scope) ?? bundle.scope ?? scopesFor(authConfig, target.resolvedScopes), idToken: idToken ?? bundle.idToken, issuer: asString(idClaims?.iss) ?? bundle.issuer ?? metadata.issuer ?? authConfig.issuer, subject: asString(idClaims?.sub) ?? bundle.subject, @@ -1201,7 +1205,8 @@ function assertTokenBundleMatchesTarget( bundle.authType !== authConfig.type || (expectedOrigin && bundle.protectedResourceOrigin !== expectedOrigin) || (configuredClientId && bundle.clientId !== configuredClientId) || - (authConfig.issuer && bundle.issuer !== authConfig.issuer); + (authConfig.issuer && bundle.issuer !== authConfig.issuer) || + tokenBundleMissingScopes(bundle, authConfig, target.resolvedScopes); if (mismatch) { throw new CapletsError( "AUTH_REQUIRED", @@ -1242,10 +1247,58 @@ function isLoopbackHttpUrl(value: string): boolean { ); } -function scopesFor(authConfig: OAuthLikeAuthConfig): string | undefined { +function tokenBundleMissingScopes( + bundle: StoredOAuthTokenBundle, + authConfig: OAuthLikeAuthConfig, + resolvedScopes: string[] | undefined, +): boolean { + const required = requiredStoredScopes(authConfig, resolvedScopes); + if (required.length === 0) return false; + const metadataScopes = requestedScopesFromMetadata(bundle.metadata); + const actual = new Set(bundle.scope?.split(/\s+/u).filter(Boolean) ?? metadataScopes ?? []); + return required.some( + (scope) => ![...actual].some((grantedScope) => oauthScopeSatisfies(grantedScope, scope)), + ); +} + +function oauthScopeSatisfies(grantedScope: string, requiredScope: string): boolean { + if (grantedScope === requiredScope) return true; + const googleScopePrefix = "https://www.googleapis.com/auth/"; + if (!grantedScope.startsWith(googleScopePrefix) || !requiredScope.startsWith(googleScopePrefix)) { + return false; + } + return requiredScope.startsWith(`${grantedScope}.`); +} + +function requiredStoredScopes( + authConfig: OAuthLikeAuthConfig, + resolvedScopes: string[] | undefined, +): string[] { + if (authConfig.scopes?.length) return authConfig.scopes; + return resolvedScopes?.length ? [...new Set(resolvedScopes)].sort() : []; +} + +function requestedScopesFromMetadata(metadata: unknown): string[] | undefined { + if (!metadata || typeof metadata !== "object" || Array.isArray(metadata)) return undefined; + const value = (metadata as Record).requestedScopes; + return Array.isArray(value) && value.every((entry) => typeof entry === "string") + ? value + : undefined; +} + +function scopesFor( + authConfig: OAuthLikeAuthConfig, + resolvedScopes?: string[] | undefined, +): string | undefined { if (authConfig.scopes?.length) { return authConfig.scopes.join(" "); } + if (resolvedScopes?.length) { + const apiScopes = [...new Set(resolvedScopes)].sort(); + return authConfig.type === "oidc" + ? ["openid", "profile", "email", ...apiScopes].join(" ") + : apiScopes.join(" "); + } return authConfig.type === "oidc" ? "openid profile email" : undefined; } diff --git a/packages/core/src/caplet-files-bundle.ts b/packages/core/src/caplet-files-bundle.ts index a86f15cd..dd268859 100644 --- a/packages/core/src/caplet-files-bundle.ts +++ b/packages/core/src/caplet-files-bundle.ts @@ -342,6 +342,67 @@ const capletOpenApiEndpointSchema = z validateEndpointAuthHeaders(endpoint.auth, ctx); }); +const capletGoogleDiscoveryOperationFilterSchema = z.array(z.string().trim().min(1).max(160)); + +const capletGoogleDiscoveryApiSchema = z + .object({ + discoveryPath: z.string().min(1).optional().describe("Local Google Discovery document path."), + discoveryUrl: z.string().min(1).optional().describe("Remote Google Discovery document URL."), + baseUrl: z.string().min(1).optional().describe("Override base URL for Google API requests."), + auth: capletEndpointAuthSchema.describe( + 'Explicit Google API request auth config. Use {"type":"none"} for public APIs.', + ), + requestTimeoutMs: z + .number() + .int() + .positive() + .optional() + .describe("Timeout in milliseconds for Google API HTTP requests."), + operationCacheTtlMs: z + .number() + .int() + .nonnegative() + .optional() + .describe( + "Milliseconds Google Discovery operation metadata stays fresh. Set 0 to refresh every time.", + ), + includeOperations: capletGoogleDiscoveryOperationFilterSchema.optional(), + excludeOperations: capletGoogleDiscoveryOperationFilterSchema.optional(), + disabled: z.boolean().optional().describe("When true, omit this Caplet from discovery."), + projectBinding: capletProjectBindingSchema.optional(), + runtime: capletRuntimeRequirementsSchema.optional(), + }) + .strict() + .superRefine((api, ctx) => { + if (Boolean(api.discoveryPath) === Boolean(api.discoveryUrl)) { + ctx.addIssue({ + code: "custom", + message: + "googleDiscoveryApi must define exactly one discovery source: discoveryPath or discoveryUrl", + }); + } + if ( + api.discoveryUrl && + !hasEnvReference(api.discoveryUrl) && + !isAllowedRemoteUrl(api.discoveryUrl) + ) { + ctx.addIssue({ + code: "custom", + path: ["discoveryUrl"], + message: "Google Discovery discoveryUrl must use https except loopback development urls", + }); + } + if (api.baseUrl && !hasEnvReference(api.baseUrl) && !isAllowedHttpBaseUrl(api.baseUrl)) { + ctx.addIssue({ + code: "custom", + path: ["baseUrl"], + message: + "Google Discovery baseUrl must use https except loopback development urls and must not include credentials, query, or fragment", + }); + } + validateEndpointAuthHeaders(api.auth, ctx); + }); + const capletGraphQlOperationSchema = z .object({ document: z.string().min(1).optional().describe("Inline GraphQL operation document."), @@ -623,11 +684,7 @@ const capletSetSchema = z export const capletFileSchema = z .object({ - $schema: z - .string() - .url() - .optional() - .describe("Optional JSON Schema URL for editor validation."), + $schema: z.string().optional().describe("Optional JSON Schema for editor validation."), name: z.string().trim().min(1).max(80).describe("Human-readable Caplet display name."), description: z .string() @@ -653,6 +710,9 @@ export const capletFileSchema = z openapiEndpoint: capletOpenApiEndpointSchema .describe("OpenAPI endpoint backend configuration for this Caplet.") .optional(), + googleDiscoveryApi: capletGoogleDiscoveryApiSchema + .describe("Google Discovery API backend configuration for this Caplet.") + .optional(), graphqlEndpoint: capletGraphQlEndpointSchema .describe("GraphQL endpoint backend configuration for this Caplet.") .optional(), @@ -671,6 +731,7 @@ export const capletFileSchema = z const backendCount = Number(Boolean(frontmatter.mcpServer)) + Number(Boolean(frontmatter.openapiEndpoint)) + + Number(Boolean(frontmatter.googleDiscoveryApi)) + Number(Boolean(frontmatter.graphqlEndpoint)) + Number(Boolean(frontmatter.httpApi)) + Number(Boolean(frontmatter.cliTools)) + @@ -679,7 +740,7 @@ export const capletFileSchema = z ctx.addIssue({ code: "custom", message: - "Caplet file must define exactly one backend: mcpServer, openapiEndpoint, graphqlEndpoint, httpApi, cliTools, or capletSet", + "Caplet file must define exactly one backend: mcpServer, openapiEndpoint, googleDiscoveryApi, graphqlEndpoint, httpApi, cliTools, or capletSet", }); } }); @@ -689,7 +750,7 @@ type CapletFileFrontmatter = z.infer; export function capletJsonSchema(): unknown { return patchCapletJsonSchema({ $schema: "https://json-schema.org/draft/2020-12/schema", - $id: "https://caplets.dev/caplet-frontmatter.schema.json", + $id: "https://caplets.dev/caplet.schema.json", title: "Caplet file frontmatter", description: "YAML frontmatter schema for a Markdown Caplet file.", ...z.toJSONSchema(capletFileSchema, { io: "input" }), @@ -699,6 +760,7 @@ export function capletJsonSchema(): unknown { export type CapletFileConfig = { mcpServers?: Record; openapiEndpoints?: Record; + googleDiscoveryApis?: Record; graphqlEndpoints?: Record; httpApis?: Record; cliTools?: Record; @@ -756,6 +818,7 @@ export function buildCapletFileLoadResultFromEntries( ): BestEffortCapletFileLoadResult | undefined { const servers: Record = {}; const openapiEndpoints: Record = {}; + const googleDiscoveryApis: Record = {}; const graphqlEndpoints: Record = {}; const httpApis: Record = {}; const cliTools: Record = {}; @@ -766,6 +829,7 @@ export function buildCapletFileLoadResultFromEntries( return Boolean( servers[id] || openapiEndpoints[id] || + googleDiscoveryApis[id] || graphqlEndpoints[id] || httpApis[id] || cliTools[id] || @@ -804,6 +868,9 @@ export function buildCapletFileLoadResultFromEntries( if (isPlainObject(config) && config.backend === "openapi") { const { backend: _backend, ...endpoint } = config; openapiEndpoints[candidate.id] = endpoint; + } else if (isPlainObject(config) && config.backend === "googleDiscovery") { + const { backend: _backend, ...api } = config; + googleDiscoveryApis[candidate.id] = api; } else if (isPlainObject(config) && config.backend === "graphql") { const { backend: _backend, ...endpoint } = config; graphqlEndpoints[candidate.id] = endpoint; @@ -823,6 +890,7 @@ export function buildCapletFileLoadResultFromEntries( const hasServers = Object.keys(servers).length > 0; const hasOpenApi = Object.keys(openapiEndpoints).length > 0; + const hasGoogleDiscovery = Object.keys(googleDiscoveryApis).length > 0; const hasGraphQl = Object.keys(graphqlEndpoints).length > 0; const hasHttpApis = Object.keys(httpApis).length > 0; const hasCliTools = Object.keys(cliTools).length > 0; @@ -830,6 +898,7 @@ export function buildCapletFileLoadResultFromEntries( const config = { ...(hasServers ? { mcpServers: servers } : {}), ...(hasOpenApi ? { openapiEndpoints } : {}), + ...(hasGoogleDiscovery ? { googleDiscoveryApis } : {}), ...(hasGraphQl ? { graphqlEndpoints } : {}), ...(hasHttpApis ? { httpApis } : {}), ...(hasCliTools ? { cliTools } : {}), @@ -920,6 +989,18 @@ function capletToServerConfig( }; } + if (frontmatter.googleDiscoveryApi) { + return { + ...frontmatter.googleDiscoveryApi, + discoveryPath: normalizePath(frontmatter.googleDiscoveryApi.discoveryPath, baseDir), + backend: "googleDiscovery", + name: frontmatter.name, + description: frontmatter.description, + ...sharedCapletFields(frontmatter), + body, + }; + } + if (frontmatter.graphqlEndpoint) { return { ...frontmatter.graphqlEndpoint, diff --git a/packages/core/src/caplet-sets.ts b/packages/core/src/caplet-sets.ts index 1b64c4ce..34e97342 100644 --- a/packages/core/src/caplet-sets.ts +++ b/packages/core/src/caplet-sets.ts @@ -10,6 +10,7 @@ import { type CompactTool, } from "./downstream"; import { CapletsError, errorResult, toSafeError } from "./errors"; +import { GoogleDiscoveryManager } from "./google-discovery"; import { GraphQLManager } from "./graphql"; import { HttpActionManager } from "./http-actions"; import { OpenApiManager } from "./openapi"; @@ -25,6 +26,7 @@ type ChildRuntime = { graphql: GraphQLManager; http: HttpActionManager; cli: CliToolsManager; + googleDiscovery: GoogleDiscoveryManager; capletSets: CapletSetManager; cacheKey: string; configFingerprint: string; @@ -37,7 +39,12 @@ export class CapletSetManager { constructor( private registry: ServerRegistry, - private readonly options: { authDir?: string; ancestry?: Set } = {}, + private readonly options: { + authDir?: string; + artifactDir?: string; + exposeLocalArtifactPaths?: boolean; + ancestry?: Set; + } = {}, ) {} updateRegistry(registry: ServerRegistry): void { @@ -144,6 +151,8 @@ export class CapletSetManager { child.http, child.cli, child.capletSets, + {}, + child.googleDiscovery, )) as CompatibilityCallToolResult; } catch (error) { return errorResult(error) as CompatibilityCallToolResult; @@ -211,17 +220,24 @@ export class CapletSetManager { maxSearchLimit: config.maxSearchLimit, }); const registry = new ServerRegistry(childConfig); - const authOptions = this.options.authDir ? { authDir: this.options.authDir } : {}; + const sharedOptions = { + ...(this.options.authDir ? { authDir: this.options.authDir } : {}), + ...(this.options.artifactDir ? { artifactDir: this.options.artifactDir } : {}), + ...(this.options.exposeLocalArtifactPaths === false + ? { exposeLocalArtifactPaths: false } + : {}), + }; const childAncestry = new Set([...ancestry, cacheKey]); child = { registry, - downstream: new DownstreamManager(registry, authOptions), - openapi: new OpenApiManager(registry, authOptions), - graphql: new GraphQLManager(registry, authOptions), - http: new HttpActionManager(registry, authOptions), + downstream: new DownstreamManager(registry, sharedOptions), + openapi: new OpenApiManager(registry, sharedOptions), + graphql: new GraphQLManager(registry, sharedOptions), + http: new HttpActionManager(registry, sharedOptions), cli: new CliToolsManager(registry), + googleDiscovery: new GoogleDiscoveryManager(registry, sharedOptions), capletSets: new CapletSetManager(registry, { - ...authOptions, + ...sharedOptions, ancestry: childAncestry, }), cacheKey, diff --git a/packages/core/src/caplet-source/parse.ts b/packages/core/src/caplet-source/parse.ts index 37bca581..1e9fe8a4 100644 --- a/packages/core/src/caplet-source/parse.ts +++ b/packages/core/src/caplet-source/parse.ts @@ -135,6 +135,7 @@ function capletsFromConfig(config: CapletsConfig): CapletConfig[] { return [ ...Object.values(config.mcpServers), ...Object.values(config.openapiEndpoints), + ...Object.values(config.googleDiscoveryApis ?? {}), ...Object.values(config.graphqlEndpoints), ...Object.values(config.httpApis), ...Object.values(config.cliTools), @@ -146,6 +147,9 @@ function localReferencePaths(caplet: CapletConfig): string[] { if (caplet.backend === "openapi") { return filterLocalReferences([caplet.specPath]); } + if (caplet.backend === "googleDiscovery") { + return filterLocalReferences([caplet.discoveryPath]); + } if (caplet.backend === "graphql") { return filterLocalReferences([ caplet.schemaPath, diff --git a/packages/core/src/cli.ts b/packages/core/src/cli.ts index ea5f2c6b..e2e287c9 100644 --- a/packages/core/src/cli.ts +++ b/packages/core/src/cli.ts @@ -4,6 +4,7 @@ import { createInterface } from "node:readline/promises"; import { version as packageJsonVersion } from "../package.json"; import { addCliCaplet, + addGoogleDiscoveryCaplet, addGraphqlCaplet, addHttpCaplet, addMcpCaplet, @@ -93,6 +94,7 @@ export { initConfig, starterConfig } from "./cli/init"; export { installCaplets, normalizeGitRepo } from "./cli/install"; export { addCliCaplet, + addGoogleDiscoveryCaplet, addGraphqlCaplet, addHttpCaplet, addMcpCaplet, @@ -1197,6 +1199,49 @@ export function createProgram(io: CliIO = {}): Command { }, ); + add + .command("google-discovery") + .description("Add a Google Discovery API backend Caplet.") + .argument("", "Caplet ID/display seed") + .option("--discovery ", "Google Discovery document path or URL") + .option("--discovery-url ", "remote Google Discovery document URL") + .option("--base-url ", "request base URL override") + .option("--token-env ", "bearer token environment variable reference") + .option("--project", "write to the project Caplets root") + .option("-g, --global", "write to the user Caplets root") + .option("--remote", "add through remote control") + .option("--print", "print generated Caplet text without writing a file") + .option("--output ", "output path") + .option("--force", "overwrite an existing destination file") + .action( + async ( + id: string, + options: AddBackendCliOptions & { + discovery?: string; + discoveryUrl?: string; + baseUrl?: string; + tokenEnv?: string; + }, + ) => { + const target = parseMutationTarget(options); + if (target === "remote") { + const remote = requireRemoteClientForTarget(io); + const result = await remote.request("add", { + kind: "googleDiscovery", + id, + options: remoteAddOptions(options), + }); + writeAddResult(writeOut, "Google Discovery", result as AddCliResult); + return; + } + const result = addGoogleDiscoveryCaplet(id, { + ...options, + destinationRoot: addDestinationRoot(target, currentConfigPath(), env), + }); + writeAddResult(writeOut, `${localMutationTargetLabel(target, io)}Google Discovery`, result); + }, + ); + add .command("graphql") .description("Add a GraphQL backend Caplet.") @@ -2368,6 +2413,7 @@ function mergePartialLocalOverlays( const capletConfigKinds = [ "mcpServers", "openapiEndpoints", + "googleDiscoveryApis", "graphqlEndpoints", "httpApis", "cliTools", @@ -2445,6 +2491,7 @@ function hasEnabledCaplet(config: CapletsConfig, id: string): boolean { const caplet = config.mcpServers[id] ?? config.openapiEndpoints[id] ?? + config.googleDiscoveryApis[id] ?? config.graphqlEndpoints[id] ?? config.httpApis[id] ?? config.cliTools[id] ?? diff --git a/packages/core/src/cli/add.ts b/packages/core/src/cli/add.ts index 2a8463c7..86cf8f10 100644 --- a/packages/core/src/cli/add.ts +++ b/packages/core/src/cli/add.ts @@ -50,6 +50,13 @@ type AddOpenApiOptions = AddDestinationOptions & { tokenEnv?: string; }; +type AddGoogleDiscoveryOptions = AddDestinationOptions & { + discovery?: string; + discoveryUrl?: string; + baseUrl?: string; + tokenEnv?: string; +}; + type AddGraphqlOptions = AddDestinationOptions & { endpointUrl?: string; schema?: string; @@ -142,6 +149,35 @@ export function addOpenApiCaplet( ); } +export function addGoogleDiscoveryCaplet( + id: string, + options: AddGoogleDiscoveryOptions, +): { path?: string; text: string } { + const discovery = options.discovery ?? options.discoveryUrl; + if (!discovery) { + throw new CapletsError( + "REQUEST_INVALID", + "Google Discovery Caplet requires --discovery or --discovery-url", + ); + } + const discoveryField = options.discoveryUrl + ? "discoveryUrl" + : isUrlLike(discovery) + ? "discoveryUrl" + : "discoveryPath"; + return writeGeneratedCaplet( + id, + "Google Discovery", + "googleDiscoveryApi", + [ + [discoveryField, discovery], + ["baseUrl", options.baseUrl], + ["auth", authFromTokenEnv(options.tokenEnv) ?? { type: "none" }], + ], + options, + ); +} + export function addGraphqlCaplet( id: string, options: AddGraphqlOptions, @@ -366,7 +402,10 @@ function resolvePrintOutputPath(id: string, options: AddDestinationOptions): str function renderLocalPaths(fields: YamlField[], outputDir: string): YamlField[] { return fields.map(([key, value]) => { - if ((key !== "specPath" && key !== "schemaPath") || typeof value !== "string") { + if ( + (key !== "specPath" && key !== "schemaPath" && key !== "discoveryPath") || + typeof value !== "string" + ) { return [key, value]; } return [key, localPathRelativeToOutput(value, outputDir)]; @@ -453,7 +492,7 @@ function renderBackendCaplet( const description = `${label} backend Caplet generated by caplets add.`; const lines = [ "---", - "$schema: https://caplets.dev/caplet-frontmatter.schema.json", + "$schema: https://caplets.dev/caplet.schema.json", `name: ${yamlString(name)}`, `description: ${yamlString(description)}`, "tags:", diff --git a/packages/core/src/cli/auth.ts b/packages/core/src/cli/auth.ts index ce09f2cf..3b6ca006 100644 --- a/packages/core/src/cli/auth.ts +++ b/packages/core/src/cli/auth.ts @@ -14,10 +14,13 @@ import { loadGlobalConfig, loadProjectConfig, type CapletsConfig, + type GoogleDiscoveryApiConfig, type GraphQlEndpointConfig, type HttpApiConfig, } from "../config"; import { CapletsError, toSafeError } from "../errors"; +import { GoogleDiscoveryManager } from "../google-discovery"; +import { ServerRegistry } from "../registry"; type AuthTarget = ReturnType[number]; type AuthListFormat = "plain" | "markdown" | "json"; @@ -43,7 +46,7 @@ export async function loginAuth( }, ): Promise { const config = options.config ?? loadConfig(options.configPath); - const server = findAuthTarget(serverId, config); + const server = await resolveAuthTarget(serverId, config, options.authDir); assertLoginTarget(server, serverId); try { @@ -108,7 +111,11 @@ export async function refreshAuthResult( serverId: string, options: { authDir?: string; configPath?: string; config?: CapletsConfig }, ): Promise<{ server: string }> { - const target = findAuthTarget(serverId, options.config ?? loadConfig(options.configPath)); + const target = await resolveAuthTarget( + serverId, + options.config ?? loadConfig(options.configPath), + options.authDir, + ); assertLoginTarget(target, serverId); await refreshOAuthTokenBundle(target, options.authDir); return { server: serverId }; @@ -260,6 +267,28 @@ export function findAuthTarget(serverId: string, config = loadConfig()): AuthTar return authTargets(config).find((server) => server.server === serverId); } +export async function resolveAuthTarget( + serverId: string, + config: CapletsConfig, + authDir?: string, +): Promise { + const target = findAuthTarget(serverId, config); + if (target?.backend !== "googleDiscovery") return target; + const api = config.googleDiscoveryApis[serverId]; + if (!api || (api.auth.type !== "oauth2" && api.auth.type !== "oidc")) return target; + const manager = new GoogleDiscoveryManager( + new ServerRegistry(config), + authDir ? { authDir } : {}, + ); + const baseUrl = + api.baseUrl ?? (await manager.resolveBaseUrl(api).catch(() => undefined)) ?? api.discoveryUrl; + return { + ...target, + ...(baseUrl ? { baseUrl } : {}), + ...(api.auth.scopes?.length ? {} : { resolvedScopes: await manager.resolveAuthScopes(api) }), + }; +} + function authTargets(config: ReturnType) { return [ ...Object.values(config.mcpServers).filter( @@ -270,6 +299,9 @@ function authTargets(config: ReturnType) { ...Object.values(config.openapiEndpoints).filter( (endpoint) => endpoint.auth?.type === "oauth2" || endpoint.auth?.type === "oidc", ), + ...Object.values(config.googleDiscoveryApis) + .filter((api) => api.auth?.type === "oauth2" || api.auth?.type === "oidc") + .map(googleDiscoveryAuthTarget), ...Object.values(config.graphqlEndpoints) .filter((endpoint) => endpoint.auth?.type === "oauth2" || endpoint.auth?.type === "oidc") .map(graphQlAuthTarget), @@ -279,6 +311,16 @@ function authTargets(config: ReturnType) { ]; } +function googleDiscoveryAuthTarget( + api: GoogleDiscoveryApiConfig, +): GoogleDiscoveryApiConfig & GenericAuthTarget { + const baseUrl = api.baseUrl ?? api.discoveryUrl; + return { + ...api, + ...(baseUrl ? { baseUrl } : {}), + }; +} + function graphQlAuthTarget( endpoint: GraphQlEndpointConfig, ): GraphQlEndpointConfig & GenericAuthTarget { diff --git a/packages/core/src/cli/author.ts b/packages/core/src/cli/author.ts index aeea66eb..ac7c69c2 100644 --- a/packages/core/src/cli/author.ts +++ b/packages/core/src/cli/author.ts @@ -211,7 +211,7 @@ function renderCaplet(input: { }): string { const lines = [ "---", - "$schema: https://caplets.dev/caplet-frontmatter.schema.json", + "# yaml-language-server: $schema=https://caplets.dev/caplet.schema.json", `name: ${yamlString(input.name)}`, `description: ${yamlString(input.description)}`, "tags:", diff --git a/packages/core/src/cli/commands.ts b/packages/core/src/cli/commands.ts index 2e398ff5..0b23b7da 100644 --- a/packages/core/src/cli/commands.ts +++ b/packages/core/src/cli/commands.ts @@ -63,7 +63,7 @@ export const topLevelCommandNames = [ ] as const; export const cliSubcommands = { - [cliCommands.add]: ["cli", "mcp", "openapi", "graphql", "http"], + [cliCommands.add]: ["cli", "mcp", "openapi", "google-discovery", "graphql", "http"], [cliCommands.auth]: ["login", "logout", "list", "refresh"], [cliCommands.cloud]: ["auth"], [cliCommands.codeMode]: ["types"], diff --git a/packages/core/src/cli/completion-discovery.ts b/packages/core/src/cli/completion-discovery.ts index 69777a3e..43efddcc 100644 --- a/packages/core/src/cli/completion-discovery.ts +++ b/packages/core/src/cli/completion-discovery.ts @@ -183,6 +183,17 @@ function secretFreeServerShape(server: CapletConfig): Record { authType: server.auth.type, requestTimeoutMs: server.requestTimeoutMs, }; + case "googleDiscovery": + return { + ...base, + discoveryPath: server.discoveryPath, + discoveryUrl: server.discoveryUrl, + baseUrl: server.baseUrl, + includeOperations: server.includeOperations, + excludeOperations: server.excludeOperations, + authType: server.auth.type, + requestTimeoutMs: server.requestTimeoutMs, + }; case "graphql": return { ...base, @@ -271,6 +282,7 @@ function enabledServer(serverId: string, config: CapletsConfig): CapletConfig | const server = config.mcpServers[serverId] ?? config.openapiEndpoints[serverId] ?? + config.googleDiscoveryApis[serverId] ?? config.graphqlEndpoints[serverId] ?? config.httpApis[serverId] ?? config.cliTools[serverId] ?? diff --git a/packages/core/src/cli/doctor.ts b/packages/core/src/cli/doctor.ts index 4b870758..5bf655d9 100644 --- a/packages/core/src/cli/doctor.ts +++ b/packages/core/src/cli/doctor.ts @@ -391,6 +391,7 @@ function allCaplets(config: { [key: string]: unknown }): CapletConfig[] { const typed = config as { mcpServers?: Record; openapiEndpoints?: Record; + googleDiscoveryApis?: Record; graphqlEndpoints?: Record; httpApis?: Record; cliTools?: Record; @@ -399,6 +400,7 @@ function allCaplets(config: { [key: string]: unknown }): CapletConfig[] { return [ ...Object.values(typed.mcpServers ?? {}), ...Object.values(typed.openapiEndpoints ?? {}), + ...Object.values(typed.googleDiscoveryApis ?? {}), ...Object.values(typed.graphqlEndpoints ?? {}), ...Object.values(typed.httpApis ?? {}), ...Object.values(typed.cliTools ?? {}), diff --git a/packages/core/src/cli/inspection.ts b/packages/core/src/cli/inspection.ts index c68a3da3..55c53954 100644 --- a/packages/core/src/cli/inspection.ts +++ b/packages/core/src/cli/inspection.ts @@ -62,6 +62,7 @@ function allCaplets(config: CapletsConfig): CapletConfig[] { return [ ...Object.values(config.mcpServers), ...Object.values(config.openapiEndpoints), + ...Object.values(config.googleDiscoveryApis ?? {}), ...Object.values(config.graphqlEndpoints), ...Object.values(config.httpApis), ...Object.values(config.cliTools), diff --git a/packages/core/src/cli/setup-caplet.ts b/packages/core/src/cli/setup-caplet.ts index e4c0c4eb..1b2bf0fe 100644 --- a/packages/core/src/cli/setup-caplet.ts +++ b/packages/core/src/cli/setup-caplet.ts @@ -34,6 +34,7 @@ export async function runCapletSetupCli( const caplet = Object.values({ ...config.mcpServers, ...config.openapiEndpoints, + ...config.googleDiscoveryApis, ...config.graphqlEndpoints, ...config.httpApis, ...config.cliTools, diff --git a/packages/core/src/cli/setup.ts b/packages/core/src/cli/setup.ts index c95af732..200fb858 100644 --- a/packages/core/src/cli/setup.ts +++ b/packages/core/src/cli/setup.ts @@ -142,6 +142,10 @@ export async function runSetup(integration: string, options: SetupOptions = {}): return await runCapletSetupCli(integration, { ...(options.yes === undefined ? {} : { yes: options.yes }), target: resolveSetupTargetKind(options), + ...(options.env?.CAPLETS_CONFIG ? { configPath: options.env.CAPLETS_CONFIG } : {}), + ...(options.env?.CAPLETS_PROJECT_CONFIG + ? { projectConfigPath: options.env.CAPLETS_PROJECT_CONFIG } + : {}), ...(options.remote === undefined && !isRemoteSetup(options) ? {} : { remote: isRemoteSetup(options) }), diff --git a/packages/core/src/cloud/runtime-adapter.ts b/packages/core/src/cloud/runtime-adapter.ts index 34eaa10e..aeacdb17 100644 --- a/packages/core/src/cloud/runtime-adapter.ts +++ b/packages/core/src/cloud/runtime-adapter.ts @@ -45,6 +45,7 @@ class DefaultCloudRuntimeAdapter implements CloudRuntimeAdapter { ? {} : { projectConfigPath: options.projectConfigPath }), ...(options.authDir === undefined ? {} : { authDir: options.authDir }), + exposeLocalArtifactPaths: false, watch: false, }); this.setupStore = options.setupStore ?? new LocalSetupStore(); @@ -134,6 +135,7 @@ class DefaultCloudRuntimeAdapter implements CloudRuntimeAdapter { return Object.values({ ...this.engine.currentConfig().mcpServers, ...this.engine.currentConfig().openapiEndpoints, + ...this.engine.currentConfig().googleDiscoveryApis, ...this.engine.currentConfig().graphqlEndpoints, ...this.engine.currentConfig().httpApis, ...this.engine.currentConfig().cliTools, diff --git a/packages/core/src/code-mode/declarations.ts b/packages/core/src/code-mode/declarations.ts index 6c72143f..e4bfca4a 100644 --- a/packages/core/src/code-mode/declarations.ts +++ b/packages/core/src/code-mode/declarations.ts @@ -47,6 +47,7 @@ function capletHintText(caplet: CodeModeDeclarationInput["caplets"][number]): st export function minifyCodeModeDeclarationText(value: string): string { return value .replace(/^\s*export\s*\{\s*\}\s*;?\s*/u, "") + .replace(/\s*export\s*\{\s*\}\s*;?\s*$/u, "") .replace(/\r\n?/gu, "\n") .split("\n") .map((line) => line.trim()) diff --git a/packages/core/src/code-mode/diagnostics-builtins.d.ts b/packages/core/src/code-mode/diagnostics-builtins.d.ts new file mode 100644 index 00000000..70bff025 --- /dev/null +++ b/packages/core/src/code-mode/diagnostics-builtins.d.ts @@ -0,0 +1,278 @@ +type CodeModeBufferEncoding = "utf8" | "utf-8" | "base64" | "base64url" | "hex"; + +interface CodeModeBuffer { + readonly byteLength: number; + readonly length: number; + toString(encoding?: CodeModeBufferEncoding): string; + toUint8Array(): Uint8Array; +} + +interface CodeModeBufferConstructor { + from( + input: string | ArrayLike | ArrayBuffer | ArrayBufferView | CodeModeBuffer, + encoding?: CodeModeBufferEncoding, + ): CodeModeBuffer; + isBuffer(value: unknown): value is CodeModeBuffer; + byteLength( + input: string | ArrayLike | ArrayBuffer | ArrayBufferView | CodeModeBuffer, + encoding?: CodeModeBufferEncoding, + ): number; +} + +declare const Buffer: CodeModeBufferConstructor; +declare function atob(input: string): string; +declare function btoa(input: string): string; + +type CodeModeURLSearchParamsInit = + | string + | Array<[string, string]> + | Record + | URLSearchParams; + +declare class URLSearchParams { + constructor(init?: CodeModeURLSearchParamsInit); + append(name: string, value: string): void; + delete(name: string): void; + entries(): IterableIterator<[string, string]>; + forEach(callback: (value: string, key: string, parent: URLSearchParams) => void): void; + get(name: string): string | null; + getAll(name: string): string[]; + has(name: string): boolean; + keys(): IterableIterator; + set(name: string, value: string): void; + toString(): string; + values(): IterableIterator; + [Symbol.iterator](): IterableIterator<[string, string]>; +} + +declare class URL { + constructor(input: string | URL, base?: string | URL); + readonly hash: string; + readonly host: string; + readonly hostname: string; + readonly href: string; + readonly origin: string; + readonly password: string; + readonly pathname: string; + readonly port: string; + readonly protocol: string; + readonly search: string; + readonly searchParams: URLSearchParams; + readonly username: string; + toJSON(): string; + toString(): string; +} + +declare class TextEncoder { + readonly encoding: "utf-8"; + encode(input?: string): Uint8Array; +} + +declare class TextDecoder { + readonly encoding: "utf-8"; + constructor(label?: string); + decode(input?: ArrayBuffer | ArrayBufferView): string; +} + +interface CodeModeCrypto { + randomUUID(): string; + getRandomValues(typedArray: T): T; +} + +declare const crypto: CodeModeCrypto; +declare function structuredClone(value: T): T; + +type CodeModeHeadersInit = Headers | Record | Array<[string, string]>; + +declare class Headers { + constructor(init?: CodeModeHeadersInit); + append(name: string, value: string): void; + delete(name: string): void; + entries(): IterableIterator<[string, string]>; + forEach(callback: (value: string, key: string, parent: Headers) => void): void; + get(name: string): string | null; + has(name: string): boolean; + keys(): IterableIterator; + set(name: string, value: string): void; + values(): IterableIterator; + [Symbol.iterator](): IterableIterator<[string, string]>; +} + +type CodeModeBlobPart = string | ArrayBuffer | ArrayBufferView | Blob; +type CodeModeEndingType = "transparent" | "native"; + +declare class Blob { + constructor( + parts?: CodeModeBlobPart[], + options?: { type?: string; endings?: CodeModeEndingType }, + ); + readonly size: number; + readonly type: string; + arrayBuffer(): Promise; + slice(start?: number, end?: number, type?: string): Blob; + text(): Promise; +} + +declare class File extends Blob { + constructor( + parts: CodeModeBlobPart[], + name: string, + options?: { type?: string; lastModified?: number }, + ); + readonly lastModified: number; + readonly name: string; +} + +declare class FormData { + constructor(); + append(name: string, value: string | Blob, filename?: string): void; + delete(name: string): void; + entries(): IterableIterator<[string, string | File]>; + get(name: string): string | File | null; + getAll(name: string): Array; + has(name: string): boolean; + keys(): IterableIterator; + set(name: string, value: string | Blob, filename?: string): void; + values(): IterableIterator; + [Symbol.iterator](): IterableIterator<[string, string | File]>; +} + +type CodeModeReadableStreamReadResult = + | { done: false; value: T } + | { done: true; value?: undefined }; + +interface ReadableStreamDefaultController { + enqueue(value: T): void; + close(): void; +} + +interface ReadableStreamDefaultReader { + read(): Promise>; +} + +declare class ReadableStream { + constructor(source?: { start?: (controller: ReadableStreamDefaultController) => void }); + getReader(): ReadableStreamDefaultReader; +} + +interface WritableStreamDefaultWriter { + write(chunk: T): Promise; + close(): Promise; +} + +declare class WritableStream { + constructor(sink?: { write?: (chunk: T) => unknown; close?: () => unknown }); + getWriter(): WritableStreamDefaultWriter; +} + +interface TransformStreamDefaultController { + enqueue(value: T): void; +} + +declare class TransformStream { + constructor(transformer?: { + transform?: (chunk: I, controller: TransformStreamDefaultController) => void; + flush?: (controller: ReadableStreamDefaultController) => void; + }); + readonly readable: ReadableStream; + readonly writable: WritableStream; +} + +type CodeModeAbortListener = (event: { type: "abort"; target: AbortSignal }) => void; + +declare class AbortSignal { + readonly aborted: boolean; + readonly reason: unknown; + onabort: CodeModeAbortListener | null; + addEventListener(type: "abort", listener: CodeModeAbortListener | null): void; + removeEventListener(type: "abort", listener: CodeModeAbortListener | null): void; + dispatchEvent(event: { type: "abort"; target: AbortSignal }): boolean; + throwIfAborted(): void; + static abort(reason?: unknown): AbortSignal; +} + +declare class AbortController { + readonly signal: AbortSignal; + abort(reason?: unknown): void; +} + +type CodeModeBodyInit = Blob | FormData | string | ArrayBuffer | ArrayBufferView | null | undefined; + +interface Body { + readonly body: null; + readonly bodyUsed: boolean; + arrayBuffer(): Promise; + blob(): Promise; + formData(): Promise; + json(): Promise; + text(): Promise; +} + +declare class Request implements Body { + constructor( + input: string | URL | Request, + init?: { + method?: string; + headers?: CodeModeHeadersInit; + body?: CodeModeBodyInit; + signal?: AbortSignal; + }, + ); + readonly body: null; + readonly bodyUsed: boolean; + readonly headers: Headers; + readonly method: string; + readonly signal: AbortSignal; + readonly url: string; + arrayBuffer(): Promise; + blob(): Promise; + clone(): Request; + formData(): Promise; + json(): Promise; + text(): Promise; +} + +declare class Response implements Body { + constructor( + body?: CodeModeBodyInit, + init?: { headers?: CodeModeHeadersInit; status?: number; statusText?: string }, + ); + readonly body: null; + readonly bodyUsed: boolean; + readonly headers: Headers; + readonly ok: boolean; + readonly redirected: false; + readonly status: number; + readonly statusText: string; + readonly type: "default"; + readonly url: ""; + arrayBuffer(): Promise; + blob(): Promise; + clone(): Response; + formData(): Promise; + json(): Promise; + text(): Promise; + static json( + data: unknown, + init?: { headers?: CodeModeHeadersInit; status?: number; statusText?: string }, + ): Response; +} + +declare function queueMicrotask(callback: () => void): void; + +type CodeModeTimerHandler = (...args: unknown[]) => void; + +declare function setTimeout( + callback: CodeModeTimerHandler, + delay?: number, + ...args: unknown[] +): number; +declare function clearTimeout(timerId: number): void; +declare function setInterval( + callback: CodeModeTimerHandler, + delay?: number, + ...args: unknown[] +): number; +declare function clearInterval(timerId: number): void; + +export {}; diff --git a/packages/core/src/code-mode/diagnostics-builtins.generated.ts b/packages/core/src/code-mode/diagnostics-builtins.generated.ts new file mode 100644 index 00000000..ecf57bcb --- /dev/null +++ b/packages/core/src/code-mode/diagnostics-builtins.generated.ts @@ -0,0 +1,3 @@ +// Generated by scripts/generate-code-mode-runtime-api.mjs. Do not edit by hand. +export const CODE_MODE_DIAGNOSTICS_BUILTINS_DECLARATION = + 'type CodeModeBufferEncoding="utf8"|"utf-8"|"base64"|"base64url"|"hex";interface CodeModeBuffer{readonly byteLength:number;readonly length:number;toString(encoding?:CodeModeBufferEncoding):string;toUint8Array():Uint8Array;}interface CodeModeBufferConstructor{from(input:string|ArrayLike|ArrayBuffer|ArrayBufferView|CodeModeBuffer,encoding?:CodeModeBufferEncoding,):CodeModeBuffer;isBuffer(value:unknown):value is CodeModeBuffer;byteLength(input:string|ArrayLike|ArrayBuffer|ArrayBufferView|CodeModeBuffer,encoding?:CodeModeBufferEncoding,):number;}declare const Buffer:CodeModeBufferConstructor;declare function atob(input:string):string;declare function btoa(input:string):string;type CodeModeURLSearchParamsInit=|string|Array<[string,string]>|Record|URLSearchParams;declare class URLSearchParams{constructor(init?:CodeModeURLSearchParamsInit);append(name:string,value:string):void;delete(name:string):void;entries():IterableIterator<[string,string]>;forEach(callback:(value:string,key:string,parent:URLSearchParams)=>void):void;get(name:string):string|null;getAll(name:string):string[];has(name:string):boolean;keys():IterableIterator;set(name:string,value:string):void;toString():string;values():IterableIterator;[Symbol.iterator]():IterableIterator<[string,string]>;}declare class URL{constructor(input:string|URL,base?:string|URL);readonly hash:string;readonly host:string;readonly hostname:string;readonly href:string;readonly origin:string;readonly password:string;readonly pathname:string;readonly port:string;readonly protocol:string;readonly search:string;readonly searchParams:URLSearchParams;readonly username:string;toJSON():string;toString():string;}declare class TextEncoder{readonly encoding:"utf-8";encode(input?:string):Uint8Array;}declare class TextDecoder{readonly encoding:"utf-8";constructor(label?:string);decode(input?:ArrayBuffer|ArrayBufferView):string;}interface CodeModeCrypto{randomUUID():string;getRandomValues(typedArray:T):T;}declare const crypto:CodeModeCrypto;declare function structuredClone(value:T):T;type CodeModeHeadersInit=Headers|Record|Array<[string,string]>;declare class Headers{constructor(init?:CodeModeHeadersInit);append(name:string,value:string):void;delete(name:string):void;entries():IterableIterator<[string,string]>;forEach(callback:(value:string,key:string,parent:Headers)=>void):void;get(name:string):string|null;has(name:string):boolean;keys():IterableIterator;set(name:string,value:string):void;values():IterableIterator;[Symbol.iterator]():IterableIterator<[string,string]>;}type CodeModeBlobPart=string|ArrayBuffer|ArrayBufferView|Blob;type CodeModeEndingType="transparent"|"native";declare class Blob{constructor(parts?:CodeModeBlobPart[],options?:{type?:string;endings?:CodeModeEndingType},);readonly size:number;readonly type:string;arrayBuffer():Promise;slice(start?:number,end?:number,type?:string):Blob;text():Promise;}declare class File extends Blob{constructor(parts:CodeModeBlobPart[],name:string,options?:{type?:string;lastModified?:number},);readonly lastModified:number;readonly name:string;}declare class FormData{constructor();append(name:string,value:string|Blob,filename?:string):void;delete(name:string):void;entries():IterableIterator<[string,string|File]>;get(name:string):string|File|null;getAll(name:string):Array;has(name:string):boolean;keys():IterableIterator;set(name:string,value:string|Blob,filename?:string):void;values():IterableIterator;[Symbol.iterator]():IterableIterator<[string,string|File]>;}type CodeModeReadableStreamReadResult=|{done:false;value:T}|{done:true;value?:undefined};interface ReadableStreamDefaultController{enqueue(value:T):void;close():void;}interface ReadableStreamDefaultReader{read():Promise>;}declare class ReadableStream{constructor(source?:{start?:(controller:ReadableStreamDefaultController)=>void});getReader():ReadableStreamDefaultReader;}interface WritableStreamDefaultWriter{write(chunk:T):Promise;close():Promise;}declare class WritableStream{constructor(sink?:{write?:(chunk:T)=>unknown;close?:()=>unknown});getWriter():WritableStreamDefaultWriter;}interface TransformStreamDefaultController{enqueue(value:T):void;}declare class TransformStream{constructor(transformer?:{transform?:(chunk:I,controller:TransformStreamDefaultController)=>void;flush?:(controller:ReadableStreamDefaultController)=>void;});readonly readable:ReadableStream;readonly writable:WritableStream;}type CodeModeAbortListener=(event:{type:"abort";target:AbortSignal})=>void;declare class AbortSignal{readonly aborted:boolean;readonly reason:unknown;onabort:CodeModeAbortListener|null;addEventListener(type:"abort",listener:CodeModeAbortListener|null):void;removeEventListener(type:"abort",listener:CodeModeAbortListener|null):void;dispatchEvent(event:{type:"abort";target:AbortSignal}):boolean;throwIfAborted():void;static abort(reason?:unknown):AbortSignal;}declare class AbortController{readonly signal:AbortSignal;abort(reason?:unknown):void;}type CodeModeBodyInit=Blob|FormData|string|ArrayBuffer|ArrayBufferView|null|undefined;interface Body{readonly body:null;readonly bodyUsed:boolean;arrayBuffer():Promise;blob():Promise;formData():Promise;json():Promise;text():Promise;}declare class Request implements Body{constructor(input:string|URL|Request,init?:{method?:string;headers?:CodeModeHeadersInit;body?:CodeModeBodyInit;signal?:AbortSignal;},);readonly body:null;readonly bodyUsed:boolean;readonly headers:Headers;readonly method:string;readonly signal:AbortSignal;readonly url:string;arrayBuffer():Promise;blob():Promise;clone():Request;formData():Promise;json():Promise;text():Promise;}declare class Response implements Body{constructor(body?:CodeModeBodyInit,init?:{headers?:CodeModeHeadersInit;status?:number;statusText?:string},);readonly body:null;readonly bodyUsed:boolean;readonly headers:Headers;readonly ok:boolean;readonly redirected:false;readonly status:number;readonly statusText:string;readonly type:"default";readonly url:"";arrayBuffer():Promise;blob():Promise;clone():Response;formData():Promise;json():Promise;text():Promise;static json(data:unknown,init?:{headers?:CodeModeHeadersInit;status?:number;statusText?:string},):Response;}declare function queueMicrotask(callback:()=>void):void;type CodeModeTimerHandler=(...args:unknown[])=>void;declare function setTimeout(callback:CodeModeTimerHandler,delay?:number,...args:unknown[]):number;declare function clearTimeout(timerId:number):void;declare function setInterval(callback:CodeModeTimerHandler,delay?:number,...args:unknown[]):number;declare function clearInterval(timerId:number):void;' as const; diff --git a/packages/core/src/code-mode/diagnostics.ts b/packages/core/src/code-mode/diagnostics.ts index 6758b473..b228f014 100644 --- a/packages/core/src/code-mode/diagnostics.ts +++ b/packages/core/src/code-mode/diagnostics.ts @@ -1,4 +1,5 @@ import ts from "typescript"; +import { CODE_MODE_DIAGNOSTICS_BUILTINS_DECLARATION } from "./diagnostics-builtins.generated"; import { hasDirectFetchCall, hasExecutableImport } from "./static-analysis"; import type { CodeModeDiagnostic } from "./types"; @@ -48,7 +49,7 @@ export function diagnoseCodeModeTypeScript( const host = createVirtualCompilerHost(compilerOptions, { [CODE_FILE]: wrappedCode, [DECLARATION_FILE]: input.declaration, - [AMBIENT_FILE]: ambientDeclarations(), + [AMBIENT_FILE]: CODE_MODE_DIAGNOSTICS_BUILTINS_DECLARATION, }); const program = ts.createProgram( [CODE_FILE, DECLARATION_FILE, AMBIENT_FILE], @@ -107,7 +108,8 @@ function preflightDiagnostics(code: string): CodeModeDiagnostic[] { diagnostics.push({ code: "FETCH_UNAVAILABLE", severity: "error", - message: "Direct fetch is not available in Code Mode; use a Caplet instead.", + message: + "Direct fetch is not available in Code Mode; use a Caplet instead. Cannot find name 'fetch'.", }); } return diagnostics; @@ -165,21 +167,3 @@ function formatDiagnostic( : {}), }; } - -function ambientDeclarations(): string { - return [ - "declare class URL {", - " constructor(input: string, base?: string);", - " readonly href: string;", - " readonly searchParams: URLSearchParams;", - " toString(): string;", - "}", - "declare class URLSearchParams {", - " constructor(init?: string | Record | Array<[string, string]>);", - " get(name: string): string | null;", - " set(name: string, value: string): void;", - " has(name: string): boolean;", - " toString(): string;", - "}", - ].join("\n"); -} diff --git a/packages/core/src/code-mode/platform-entry.ts b/packages/core/src/code-mode/platform-entry.ts new file mode 100644 index 00000000..361aae4b --- /dev/null +++ b/packages/core/src/code-mode/platform-entry.ts @@ -0,0 +1,1169 @@ +// @ts-expect-error package has no bundled types in this repo +import structuredClone from "@ungap/structured-clone"; +import { Blob, File, FormData } from "formdata-node"; +import { Headers } from "headers-polyfill"; + +declare function __caplets_log(level: string, message: string): void; + +type PlatformBridgeGlobals = typeof globalThis & { + __caplets_platform_random_uuid?: () => string; + __caplets_platform_random_values?: (length: number) => number[]; + __caplets_platform_sleep?: (timerId: number, delayMs: number) => Promise; + __caplets_platform_clear_timer?: (timerId: number) => boolean; +}; + +const platformBridgeGlobalThis = globalThis as PlatformBridgeGlobals; +const capletsPlatformHost = { + randomUUID: platformBridgeGlobalThis.__caplets_platform_random_uuid, + randomValues: platformBridgeGlobalThis.__caplets_platform_random_values, + sleep: platformBridgeGlobalThis.__caplets_platform_sleep, + clearTimer: platformBridgeGlobalThis.__caplets_platform_clear_timer, +}; + +delete platformBridgeGlobalThis.__caplets_platform_random_uuid; +delete platformBridgeGlobalThis.__caplets_platform_random_values; +delete platformBridgeGlobalThis.__caplets_platform_sleep; +delete platformBridgeGlobalThis.__caplets_platform_clear_timer; + +const DISABLED_FETCH_MESSAGE = "Direct fetch is not available in Code Mode; use a Caplet instead."; +const BASE64_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; +const base64Lookup = new Map(BASE64_ALPHABET.split("").map((char, index) => [char, index])); + +type BufferEncoding = "utf8" | "utf-8" | "base64" | "base64url" | "hex"; +type BodyValue = Blob | FormData | string | ArrayBuffer | ArrayBufferView | null | undefined; +type HeadersValue = Headers | Record | Array<[string, string]>; +type AbortListener = (event: { type: "abort"; target: AbortSignalShim }) => void; +type UrlParamInit = string | Array<[string, string]> | Record | URLSearchParamsShim; +type TimerCallback = (...args: unknown[]) => void; + +function utf8Encode(input: string): Uint8Array { + const encoded = encodeURIComponent(input); + const bytes: number[] = []; + for (let index = 0; index < encoded.length; index += 1) { + if (encoded[index] === "%") { + bytes.push(Number.parseInt(encoded.slice(index + 1, index + 3), 16)); + index += 2; + continue; + } + bytes.push(encoded.charCodeAt(index)); + } + return Uint8Array.from(bytes); +} + +function utf8Decode(input: Uint8Array): string { + let output = ""; + for (let index = 0; index < input.length; index += 1) { + const first = input[index] ?? 0; + if (first < 0x80) { + output += String.fromCharCode(first); + continue; + } + if (first >= 0xc2 && first <= 0xdf) { + const second = input[index + 1]; + if (isUtf8Continuation(second)) { + output += String.fromCodePoint(((first & 0x1f) << 6) | (second & 0x3f)); + index += 1; + continue; + } + output += "\uFFFD"; + continue; + } + if (first >= 0xe0 && first <= 0xef) { + const second = input[index + 1]; + const third = input[index + 2]; + if (isValidUtf8SecondForThreeByte(first, second) && isUtf8Continuation(third)) { + output += String.fromCodePoint( + ((first & 0x0f) << 12) | ((second & 0x3f) << 6) | (third & 0x3f), + ); + index += 2; + continue; + } + output += "\uFFFD"; + if (isValidUtf8SecondForThreeByte(first, second)) { + index += 1; + } + continue; + } + if (first >= 0xf0 && first <= 0xf4) { + const second = input[index + 1]; + const third = input[index + 2]; + const fourth = input[index + 3]; + if ( + isValidUtf8SecondForFourByte(first, second) && + isUtf8Continuation(third) && + isUtf8Continuation(fourth) + ) { + output += String.fromCodePoint( + ((first & 0x07) << 18) | + ((second & 0x3f) << 12) | + ((third & 0x3f) << 6) | + (fourth & 0x3f), + ); + index += 3; + continue; + } + output += "\uFFFD"; + if (isValidUtf8SecondForFourByte(first, second)) { + index += isUtf8Continuation(third) ? 2 : 1; + } + continue; + } + output += "\uFFFD"; + } + return output; +} + +function isUtf8Continuation(value: number | undefined): value is number { + return value !== undefined && value >= 0x80 && value <= 0xbf; +} + +function isValidUtf8SecondForThreeByte( + first: number, + second: number | undefined, +): second is number { + if (second === undefined) return false; + if (first === 0xe0) return second >= 0xa0 && second <= 0xbf; + if (first === 0xed) return second >= 0x80 && second <= 0x9f; + return second >= 0x80 && second <= 0xbf; +} + +function isValidUtf8SecondForFourByte(first: number, second: number | undefined): second is number { + if (second === undefined) return false; + if (first === 0xf0) return second >= 0x90 && second <= 0xbf; + if (first === 0xf4) return second >= 0x80 && second <= 0x8f; + return second >= 0x80 && second <= 0xbf; +} + +class TextEncoderShim { + readonly encoding = "utf-8"; + + encode(input = ""): Uint8Array { + return utf8Encode(String(input)); + } +} + +class TextDecoderShim { + readonly encoding: string; + + constructor(label = "utf-8") { + const normalized = label.toLowerCase(); + if (!["utf-8", "utf8", "unicode-1-1-utf-8"].includes(normalized)) { + throw new TypeError(`Unsupported encoding: ${label}`); + } + this.encoding = "utf-8"; + } + + decode(input?: ArrayBuffer | ArrayBufferView): string { + if (input === undefined) { + return ""; + } + return utf8Decode(copyBytes(input)); + } +} + +const textEncoder = new TextEncoderShim(); +const textDecoder = new TextDecoderShim(); + +class URLSearchParamsShim { + #entries: Array<[string, string]> = []; + #onChange: (() => void) | undefined; + + constructor(init: UrlParamInit = "", onChange?: () => void) { + this.#onChange = onChange; + if (typeof init === "string") { + const source = init.startsWith("?") ? init.slice(1) : init; + if (!source) { + return; + } + for (const pair of source.split("&")) { + if (!pair) { + continue; + } + const separatorIndex = pair.indexOf("="); + const key = separatorIndex >= 0 ? pair.slice(0, separatorIndex) : pair; + const value = separatorIndex >= 0 ? pair.slice(separatorIndex + 1) : ""; + this.#entries.push([decodeUrlParam(key), decodeUrlParam(value)]); + } + return; + } + if (init instanceof URLSearchParamsShim) { + this.#entries = [...init.#entries]; + return; + } + if (Array.isArray(init)) { + this.#entries = init.map(([key, value]) => [String(key), String(value)]); + return; + } + this.#entries = Object.entries(init).map(([key, value]) => [key, String(value)]); + } + + append(name: string, value: string): void { + this.#entries.push([String(name), String(value)]); + this.#onChange?.(); + } + + delete(name: string): void { + const normalized = String(name); + this.#entries = this.#entries.filter(([key]) => key !== normalized); + this.#onChange?.(); + } + + entries(): IterableIterator<[string, string]> { + return this.#entries[Symbol.iterator](); + } + + forEach(callback: (value: string, key: string, parent: URLSearchParamsShim) => void): void { + for (const [key, value] of this.#entries) { + callback(value, key, this); + } + } + + get(name: string): string | null { + return this.#entries.find(([key]) => key === String(name))?.[1] ?? null; + } + + getAll(name: string): string[] { + return this.#entries.filter(([key]) => key === String(name)).map(([, value]) => value); + } + + has(name: string): boolean { + return this.#entries.some(([key]) => key === String(name)); + } + + keys(): IterableIterator { + return this.#entries.map(([key]) => key)[Symbol.iterator](); + } + + set(name: string, value: string): void { + const normalizedName = String(name); + const normalizedValue = String(value); + const next: Array<[string, string]> = []; + let replaced = false; + for (const entry of this.#entries) { + if (entry[0] !== normalizedName) { + next.push(entry); + continue; + } + if (!replaced) { + next.push([normalizedName, normalizedValue]); + replaced = true; + } + } + if (!replaced) { + next.push([normalizedName, normalizedValue]); + } + this.#entries = next; + this.#onChange?.(); + } + + toString(): string { + return this.#entries + .map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`) + .join("&"); + } + + values(): IterableIterator { + return this.#entries.map(([, value]) => value)[Symbol.iterator](); + } + + [Symbol.iterator](): IterableIterator<[string, string]> { + return this.entries(); + } +} + +function decodeUrlParam(value: string): string { + return decodeURIComponent(value.replace(/\+/gu, " ")); +} + +type ParsedUrl = { + protocol: string; + host: string; + pathname: string; + search: string; + hash: string; +}; + +function parseAbsoluteUrl(input: string): ParsedUrl | undefined { + const match = + /^(?[a-zA-Z][a-zA-Z\d+.-]*:)\/\/(?[^/?#]*)(?[^?#]*)?(?\?[^#]*)?(?#.*)?$/u.exec( + input, + ); + if (!match?.groups?.protocol || !match.groups.host) { + return undefined; + } + return { + protocol: match.groups.protocol, + host: match.groups.host, + pathname: match.groups.pathname || "/", + search: match.groups.search || "", + hash: match.groups.hash || "", + }; +} + +function normalizePathname(pathname: string): string { + const segments: string[] = []; + for (const part of pathname.split("/")) { + if (!part || part === ".") { + continue; + } + if (part === "..") { + segments.pop(); + continue; + } + segments.push(part); + } + return `/${segments.join("/")}`; +} + +function resolveUrl(input: string, base?: string | URLShim): ParsedUrl { + const absolute = parseAbsoluteUrl(input); + if (absolute) { + return absolute; + } + if (base === undefined) { + throw new TypeError(`Invalid URL: ${input}`); + } + const baseUrl = base instanceof URLShim ? base : new URLShim(String(base)); + const basePath = baseUrl.pathname.endsWith("/") + ? baseUrl.pathname + : baseUrl.pathname.slice(0, baseUrl.pathname.lastIndexOf("/") + 1); + const [beforeHash, rawHash = ""] = input.split("#"); + const hasExplicitSearch = beforeHash?.includes("?") ?? false; + const [rawPath, rawSearch = ""] = (beforeHash ?? "").split("?"); + const pathname = + rawPath === "" && (input === "" || input.startsWith("?") || input.startsWith("#")) + ? baseUrl.pathname + : rawPath?.startsWith("/") + ? rawPath + : `${basePath}${rawPath || ""}`; + return { + protocol: baseUrl.protocol, + host: baseUrl.host, + pathname: normalizePathname(pathname), + search: hasExplicitSearch + ? `?${rawSearch}` + : rawPath === "" && (input === "" || input.startsWith("#")) + ? baseUrl.search + : "", + hash: rawHash ? `#${rawHash}` : "", + }; +} + +class URLShim { + readonly host: string; + readonly hostname: string; + readonly origin: string; + readonly password = ""; + readonly port: string; + readonly protocol: string; + readonly searchParams: URLSearchParamsShim; + readonly username = ""; + hash: string; + pathname: string; + #search: string; + + constructor(input: string, base?: string | URLShim) { + const parsed = resolveUrl(String(input), base); + this.protocol = parsed.protocol; + this.host = parsed.host; + const portIndex = this.host.lastIndexOf(":"); + this.hostname = portIndex >= 0 ? this.host.slice(0, portIndex) : this.host; + this.port = portIndex >= 0 ? this.host.slice(portIndex + 1) : ""; + this.pathname = parsed.pathname; + this.#search = parsed.search; + this.hash = parsed.hash; + this.origin = `${this.protocol}//${this.host}`; + this.searchParams = new URLSearchParamsShim(this.#search, () => { + const next = this.searchParams.toString(); + this.#search = next ? `?${next}` : ""; + }); + } + + get href(): string { + return `${this.origin}${this.pathname}${this.#search}${this.hash}`; + } + + get search(): string { + return this.#search; + } + + toString(): string { + return this.href; + } + + toJSON(): string { + return this.href; + } +} + +class ReadableStreamShim { + #queue: T[] = []; + #closed = false; + #pulling = false; + #source: { + pull?: (controller: { enqueue: (value: T) => void; close: () => void }) => unknown; + }; + #controller: { enqueue: (value: T) => void; close: () => void }; + #pending: + | { + resolve: (value: { done: boolean; value?: T }) => void; + } + | undefined; + + constructor( + source: { + start?: (controller: { enqueue: (value: T) => void; close: () => void }) => void; + pull?: (controller: { enqueue: (value: T) => void; close: () => void }) => unknown; + } = {}, + ) { + this.#source = source; + this.#controller = { + enqueue: (value) => { + if (this.#pending) { + this.#pending.resolve({ done: false, value }); + this.#pending = undefined; + return; + } + this.#queue.push(value); + }, + close: () => { + this.#closed = true; + if (this.#pending) { + this.#pending.resolve({ done: true }); + this.#pending = undefined; + } + }, + }; + source.start?.(this.#controller); + } + + async #pull(): Promise { + if (this.#pulling || this.#closed || !this.#source.pull) { + return; + } + this.#pulling = true; + try { + await this.#source.pull(this.#controller); + } finally { + this.#pulling = false; + } + } + + getReader() { + return { + read: async () => { + if (this.#queue.length > 0) { + return { done: false, value: this.#queue.shift() as T }; + } + await this.#pull(); + if (this.#queue.length > 0) { + return { done: false, value: this.#queue.shift() as T }; + } + if (this.#closed) { + return { done: true, value: undefined }; + } + return await new Promise<{ done: boolean; value?: T }>((resolve) => { + this.#pending = { resolve }; + void this.#pull(); + }); + }, + }; + } +} + +class WritableStreamShim { + #sink: { + write?: (chunk: T) => unknown; + close?: () => unknown; + }; + + constructor(sink: { write?: (chunk: T) => unknown; close?: () => unknown } = {}) { + this.#sink = sink; + } + + getWriter() { + return { + write: async (chunk: T) => { + await this.#sink.write?.(chunk); + }, + close: async () => { + await this.#sink.close?.(); + }, + }; + } +} + +class TransformStreamShim { + readonly readable: ReadableStreamShim; + readonly writable: WritableStreamShim; + + constructor( + transformer: { + transform?: (chunk: I, controller: { enqueue: (value: O) => void }) => void; + flush?: (controller: { enqueue: (value: O) => void; close: () => void }) => void; + } = {}, + ) { + const queue: O[] = []; + let closed = false; + let pending: + | { + resolve: (value: { done: boolean; value?: O }) => void; + } + | undefined; + + const controller = { + enqueue: (value: O) => { + if (pending) { + pending.resolve({ done: false, value }); + pending = undefined; + return; + } + queue.push(value); + }, + close: () => { + closed = true; + if (pending) { + pending.resolve({ done: true }); + pending = undefined; + } + }, + }; + + this.readable = { + getReader() { + return { + read: async () => { + if (queue.length > 0) { + return { done: false, value: queue.shift() as O }; + } + if (closed) { + return { done: true, value: undefined }; + } + return await new Promise<{ done: boolean; value?: O }>((resolve) => { + pending = { resolve }; + }); + }, + }; + }, + } as ReadableStreamShim; + + this.writable = new WritableStreamShim({ + write: async (chunk) => { + transformer.transform?.(chunk, controller); + }, + close: async () => { + transformer.flush?.(controller); + controller.close(); + }, + }); + } +} + +function definePlatformGlobal( + name: string, + value: unknown, + options: { overwrite?: boolean } = {}, +): void { + if (!options.overwrite && name in globalThis) { + return; + } + Object.defineProperty(globalThis, name, { + value, + writable: true, + configurable: true, + }); +} + +function formatLogArg(value: unknown): string { + if (typeof value === "string") return value; + try { + return JSON.stringify(value); + } catch { + return String(value); + } +} + +function formatLogLine(args: unknown[]): string { + return args.map(formatLogArg).join(" "); +} + +const platformConsole = { + log: (...args: unknown[]) => __caplets_log("log", formatLogLine(args)), + info: (...args: unknown[]) => __caplets_log("info", formatLogLine(args)), + warn: (...args: unknown[]) => __caplets_log("warn", formatLogLine(args)), + error: (...args: unknown[]) => __caplets_log("error", formatLogLine(args)), + debug: (...args: unknown[]) => __caplets_log("debug", formatLogLine(args)), +}; + +function normalizeEncoding(encoding: string | undefined): BufferEncoding { + const normalized = (encoding ?? "utf8").toLowerCase(); + switch (normalized) { + case "utf8": + case "utf-8": + case "base64": + case "base64url": + case "hex": + return normalized; + default: + throw new TypeError(`Unsupported Buffer encoding: ${encoding}`); + } +} + +function hexToBytes(value: string): Uint8Array { + if (value.length % 2 !== 0) { + throw new TypeError("Invalid hex string length"); + } + const bytes = new Uint8Array(value.length / 2); + for (let index = 0; index < value.length; index += 2) { + const parsed = Number.parseInt(value.slice(index, index + 2), 16); + if (Number.isNaN(parsed)) { + throw new TypeError("Invalid hex string"); + } + bytes[index / 2] = parsed; + } + return bytes; +} + +function bytesToHex(bytes: Uint8Array): string { + return Array.from(bytes, (value) => value.toString(16).padStart(2, "0")).join(""); +} + +function base64ToBytes(value: string, encoding: BufferEncoding): Uint8Array { + let normalized = value.replace(/\s+/gu, ""); + if (encoding === "base64url") { + normalized = normalized.replace(/-/gu, "+").replace(/_/gu, "/"); + } + const padding = normalized.length % 4; + if (padding === 1) { + throw new TypeError("Invalid base64 string"); + } + if (padding > 0) { + normalized = normalized.padEnd(normalized.length + (4 - padding), "="); + } + const output: number[] = []; + for (let index = 0; index < normalized.length; index += 4) { + const chunk = normalized.slice(index, index + 4); + const values = chunk + .split("") + .map((char) => (char === "=" ? 64 : (base64Lookup.get(char) ?? Number.NaN))); + if (values.some((entry) => Number.isNaN(entry))) { + throw new TypeError("Invalid base64 string"); + } + const [a = 0, b = 0, c = 64, d = 64] = values; + const triple = (a << 18) | (b << 12) | ((c & 63) << 6) | (d & 63); + output.push((triple >> 16) & 255); + if (c !== 64) { + output.push((triple >> 8) & 255); + } + if (d !== 64) { + output.push(triple & 255); + } + } + return Uint8Array.from(output); +} + +function bytesToBase64(bytes: Uint8Array, encoding: BufferEncoding): string { + let output = ""; + for (let index = 0; index < bytes.length; index += 3) { + const a = bytes[index] ?? 0; + const b = bytes[index + 1] ?? 0; + const c = bytes[index + 2] ?? 0; + const triple = (a << 16) | (b << 8) | c; + output += BASE64_ALPHABET[(triple >> 18) & 63]; + output += BASE64_ALPHABET[(triple >> 12) & 63]; + output += index + 1 < bytes.length ? BASE64_ALPHABET[(triple >> 6) & 63] : "="; + output += index + 2 < bytes.length ? BASE64_ALPHABET[triple & 63] : "="; + } + if (encoding === "base64url") { + return output.replace(/\+/gu, "-").replace(/\//gu, "_").replace(/=+$/gu, ""); + } + return output; +} + +function copyBytes(input: ArrayBuffer | ArrayBufferView): Uint8Array { + if (input instanceof ArrayBuffer) { + return new Uint8Array(input.slice(0)); + } + return new Uint8Array(input.buffer.slice(input.byteOffset, input.byteOffset + input.byteLength)); +} + +function toBytes(input: unknown, encoding?: string): Uint8Array { + if (input instanceof BufferShim) { + return input.toUint8Array(); + } + if (typeof input === "string") { + switch (normalizeEncoding(encoding)) { + case "utf8": + case "utf-8": + return textEncoder.encode(input); + case "base64": + case "base64url": + return base64ToBytes(input, normalizeEncoding(encoding)); + case "hex": + return hexToBytes(input); + } + } + if (input instanceof ArrayBuffer || ArrayBuffer.isView(input)) { + return copyBytes(input); + } + if (Array.isArray(input)) { + return Uint8Array.from(input); + } + throw new TypeError("Buffer.from only supports strings, arrays, ArrayBuffers, and typed arrays"); +} + +class BufferShim { + readonly #bytes: Uint8Array; + readonly byteLength: number; + readonly length: number; + + private constructor(bytes: Uint8Array) { + this.#bytes = bytes; + this.byteLength = bytes.byteLength; + this.length = bytes.length; + } + + static from(input: unknown, encoding?: string): BufferShim { + return new BufferShim(toBytes(input, encoding)); + } + + static isBuffer(value: unknown): value is BufferShim { + return value instanceof BufferShim; + } + + static byteLength(input: unknown, encoding?: string): number { + return toBytes(input, encoding).byteLength; + } + + toUint8Array(): Uint8Array { + return new Uint8Array(this.#bytes); + } + + toString(encoding?: string): string { + switch (normalizeEncoding(encoding)) { + case "utf8": + case "utf-8": + return textDecoder.decode(this.#bytes); + case "base64": + case "base64url": + return bytesToBase64(this.#bytes, normalizeEncoding(encoding)); + case "hex": + return bytesToHex(this.#bytes); + } + } +} + +function atobShim(input: string): string { + return Array.from(base64ToBytes(String(input), "base64"), (value) => + String.fromCharCode(value), + ).join(""); +} + +function btoaShim(input: string): string { + const bytes = new Uint8Array(input.length); + for (let index = 0; index < input.length; index += 1) { + const codePoint = input.charCodeAt(index); + if (codePoint > 255) { + throw new TypeError("The string to be encoded contains characters outside of Latin1"); + } + bytes[index] = codePoint; + } + return bytesToBase64(bytes, "base64"); +} + +function queueMicrotaskShim(callback: () => void): void { + Promise.resolve().then(callback); +} + +class AbortSignalShim { + aborted = false; + reason: unknown; + onabort: AbortListener | null = null; + #listeners = new Set(); + + addEventListener(type: string, listener: AbortListener | null): void { + if (type === "abort" && listener) { + this.#listeners.add(listener); + } + } + + removeEventListener(type: string, listener: AbortListener | null): void { + if (type === "abort" && listener) { + this.#listeners.delete(listener); + } + } + + dispatchEvent(event: { type: "abort"; target: AbortSignalShim }): boolean { + if (event.type !== "abort") { + return true; + } + this.onabort?.(event); + for (const listener of this.#listeners) { + listener(event); + } + return true; + } + + throwIfAborted(): void { + if (this.aborted) { + throw this.reason ?? new Error("Operation was aborted"); + } + } + + static abort(reason?: unknown): AbortSignalShim { + const signal = new AbortSignalShim(); + signal.abort(reason); + return signal; + } + + abort(reason?: unknown): void { + if (this.aborted) { + return; + } + this.aborted = true; + this.reason = reason; + this.dispatchEvent({ type: "abort", target: this }); + } +} + +class AbortControllerShim { + readonly signal = new AbortSignalShim(); + + abort(reason?: unknown): void { + this.signal.abort(reason); + } +} + +function cloneFormData(input: FormData): FormData { + const clone = new FormData(); + for (const [name, value] of input.entries()) { + if (value instanceof File) { + clone.append(name, new File([value], value.name, { type: value.type }), value.name); + continue; + } + clone.append(name, value); + } + return clone; +} + +function cloneBodyValue(input: BodyValue): BodyValue { + if (input === null || input === undefined || typeof input === "string") { + return input; + } + if (input instanceof FormData) { + return cloneFormData(input); + } + if (input instanceof Blob) { + return input.slice(0, input.size, input.type); + } + if (input instanceof ArrayBuffer || ArrayBuffer.isView(input)) { + return copyBytes(input); + } + return input; +} + +function blobFromBody(input: BodyValue): Blob { + if (input instanceof Blob) { + return input; + } + if (typeof input === "string") { + return new Blob([input], { type: "text/plain;charset=utf-8" }); + } + if (input instanceof FormData) { + throw new TypeError("FormData body reading is not supported in Code Mode"); + } + if (input instanceof ArrayBuffer || ArrayBuffer.isView(input)) { + return new Blob([copyBytes(input)]); + } + return new Blob([]); +} + +abstract class BodyMixin { + protected readonly _bodyInit: BodyValue; + bodyUsed = false; + readonly body = null; + + protected constructor(body?: BodyValue) { + this._bodyInit = cloneBodyValue(body); + } + + async arrayBuffer(): Promise { + this.bodyUsed = true; + return await blobFromBody(this._bodyInit).arrayBuffer(); + } + + async blob(): Promise { + this.bodyUsed = true; + return blobFromBody(this._bodyInit); + } + + async formData(): Promise { + this.bodyUsed = true; + if (this._bodyInit instanceof FormData) { + return cloneFormData(this._bodyInit); + } + throw new TypeError("Body does not contain FormData"); + } + + async json(): Promise { + return JSON.parse(await this.text()); + } + + async text(): Promise { + this.bodyUsed = true; + return await blobFromBody(this._bodyInit).text(); + } +} + +function toHeaders(init?: HeadersValue): Headers { + return init instanceof Headers ? new Headers(init) : new Headers(init ?? {}); +} + +function normalizeMethod(method: string | undefined): string { + return String(method ?? "GET").toUpperCase(); +} + +class RequestShim extends BodyMixin { + readonly headers: Headers; + readonly method: string; + readonly signal: AbortSignalShim; + readonly url: string; + + constructor( + input: string | URLShim | RequestShim, + init: { + method?: string; + headers?: HeadersValue; + body?: BodyValue; + signal?: AbortSignalShim; + } = {}, + ) { + const sourceBody = input instanceof RequestShim ? input._bodyInit : undefined; + super(init.body ?? sourceBody); + if (input instanceof RequestShim) { + this.url = input.url; + } else { + this.url = input instanceof URLShim ? input.href : new URLShim(String(input)).href; + } + this.method = normalizeMethod( + init.method ?? (input instanceof RequestShim ? input.method : "GET"), + ); + this.headers = toHeaders( + init.headers ?? (input instanceof RequestShim ? input.headers : undefined), + ); + this.signal = + init.signal ?? + (input instanceof RequestShim ? input.signal : new AbortControllerShim().signal); + } + + clone(): RequestShim { + return new RequestShim(this, { + method: this.method, + headers: this.headers, + body: this._bodyInit, + signal: this.signal, + }); + } +} + +class ResponseShim extends BodyMixin { + readonly headers: Headers; + readonly ok: boolean; + readonly redirected = false; + readonly status: number; + readonly statusText: string; + readonly type = "default"; + readonly url = ""; + + constructor( + body?: BodyValue, + init: { + headers?: HeadersValue; + status?: number; + statusText?: string; + } = {}, + ) { + super(body); + this.status = init.status ?? 200; + this.statusText = init.statusText ?? ""; + this.headers = toHeaders(init.headers); + this.ok = this.status >= 200 && this.status <= 299; + } + + clone(): ResponseShim { + return new ResponseShim(this._bodyInit, { + headers: this.headers, + status: this.status, + statusText: this.statusText, + }); + } + + static json( + data: unknown, + init?: { headers?: HeadersValue; status?: number; statusText?: string }, + ) { + const headers = toHeaders(init?.headers); + if (!headers.has("content-type")) { + headers.set("content-type", "application/json"); + } + const responseInit: { headers: Headers; status?: number; statusText?: string } = { headers }; + if (init?.status !== undefined) { + responseInit.status = init.status; + } + if (init?.statusText !== undefined) { + responseInit.statusText = init.statusText; + } + return new ResponseShim(JSON.stringify(data), responseInit); + } +} + +function disabledFetch(): never { + throw new Error(DISABLED_FETCH_MESSAGE); +} + +const integerTypedArrayConstructors = new Set([ + Int8Array, + Uint8Array, + Uint8ClampedArray, + Int16Array, + Uint16Array, + Int32Array, + Uint32Array, + typeof BigInt64Array === "undefined" ? undefined : BigInt64Array, + typeof BigUint64Array === "undefined" ? undefined : BigUint64Array, +]); + +class QuotaExceededErrorShim extends Error { + constructor(message: string) { + super(message); + this.name = "QuotaExceededError"; + } +} + +const platformCrypto = { + randomUUID(): string { + const randomUUID = capletsPlatformHost.randomUUID; + if (!randomUUID) { + throw new Error("Code Mode platform random UUID bridge is not installed"); + } + return randomUUID(); + }, + + getRandomValues(typedArray: T): T { + if (!ArrayBuffer.isView(typedArray) || typedArray instanceof DataView) { + throw new TypeError("crypto.getRandomValues requires an integer typed array"); + } + if (!integerTypedArrayConstructors.has(typedArray.constructor)) { + throw new TypeError("crypto.getRandomValues requires an integer typed array"); + } + if (typedArray.byteLength > 65_536) { + throw new QuotaExceededErrorShim( + "crypto.getRandomValues cannot generate more than 65,536 bytes", + ); + } + const randomValues = capletsPlatformHost.randomValues; + if (!randomValues) { + throw new Error("Code Mode platform random values bridge is not installed"); + } + const bytes = randomValues(typedArray.byteLength); + const target = new Uint8Array(typedArray.buffer, typedArray.byteOffset, typedArray.byteLength); + target.set(bytes); + return typedArray; + }, +}; + +let nextTimerId = 1; +const activeTimers = new Set(); + +function normalizeTimerDelay(delay: unknown): number { + const value = Number(delay ?? 0); + if (!Number.isFinite(value) || value <= 0) { + return 0; + } + return Math.trunc(value); +} + +function setTimeoutShim(callback: TimerCallback, delay?: unknown, ...args: unknown[]): number { + if (typeof callback !== "function") { + throw new TypeError("setTimeout callback must be a function"); + } + const timerId = nextTimerId++; + activeTimers.add(timerId); + const sleep = capletsPlatformHost.sleep; + if (!sleep) { + throw new Error("Code Mode platform sleep bridge is not installed"); + } + void sleep(timerId, normalizeTimerDelay(delay)).then((fired) => { + activeTimers.delete(timerId); + if (fired) { + callback(...args); + } + }); + return timerId; +} + +function clearTimeoutShim(timerId: unknown): void { + const id = Number(timerId); + activeTimers.delete(id); + capletsPlatformHost.clearTimer?.(id); +} + +function setIntervalShim(callback: TimerCallback, delay?: unknown, ...args: unknown[]): number { + if (typeof callback !== "function") { + throw new TypeError("setInterval callback must be a function"); + } + const timerId = nextTimerId++; + const intervalDelay = normalizeTimerDelay(delay); + activeTimers.add(timerId); + + const tick = () => { + const sleep = capletsPlatformHost.sleep; + if (!sleep) { + throw new Error("Code Mode platform sleep bridge is not installed"); + } + void sleep(timerId, intervalDelay).then((fired) => { + if (!fired || !activeTimers.has(timerId)) { + activeTimers.delete(timerId); + return; + } + callback(...args); + if (activeTimers.has(timerId)) { + tick(); + } + }); + }; + tick(); + return timerId; +} + +function clearIntervalShim(timerId: unknown): void { + clearTimeoutShim(timerId); +} + +definePlatformGlobal("atob", atobShim); +definePlatformGlobal("btoa", btoaShim); +definePlatformGlobal("Buffer", BufferShim); +definePlatformGlobal("TextEncoder", TextEncoderShim); +definePlatformGlobal("TextDecoder", TextDecoderShim); +definePlatformGlobal("URL", URLShim); +definePlatformGlobal("URLSearchParams", URLSearchParamsShim); +definePlatformGlobal("structuredClone", structuredClone); +definePlatformGlobal("Headers", Headers); +definePlatformGlobal("Blob", Blob); +definePlatformGlobal("File", File); +definePlatformGlobal("FormData", FormData); +definePlatformGlobal("ReadableStream", ReadableStreamShim); +definePlatformGlobal("WritableStream", WritableStreamShim); +definePlatformGlobal("TransformStream", TransformStreamShim); +definePlatformGlobal("AbortController", AbortControllerShim); +definePlatformGlobal("AbortSignal", AbortSignalShim); +definePlatformGlobal("Request", RequestShim); +definePlatformGlobal("Response", ResponseShim); +definePlatformGlobal("crypto", platformCrypto); +definePlatformGlobal("setTimeout", setTimeoutShim); +definePlatformGlobal("clearTimeout", clearTimeoutShim); +definePlatformGlobal("setInterval", setIntervalShim); +definePlatformGlobal("clearInterval", clearIntervalShim); +definePlatformGlobal("queueMicrotask", queueMicrotaskShim); +definePlatformGlobal("console", platformConsole); +definePlatformGlobal("fetch", disabledFetch, { overwrite: true }); diff --git a/packages/core/src/code-mode/platform-host.ts b/packages/core/src/code-mode/platform-host.ts new file mode 100644 index 00000000..aab5801e --- /dev/null +++ b/packages/core/src/code-mode/platform-host.ts @@ -0,0 +1,125 @@ +import { randomBytes, randomUUID } from "node:crypto"; +import type { QuickJSContext, QuickJSDeferredPromise, QuickJSHandle } from "quickjs-emscripten"; + +type PlatformTimer = { + deferred: QuickJSDeferredPromise; + timeout: ReturnType; +}; + +const MAX_RANDOM_VALUES_BYTES = 65_536; + +export type CodeModePlatformHost = { + dispose(): void; +}; + +export type CodeModePlatformHostOptions = Record; + +export function installCodeModePlatformHost( + context: QuickJSContext, + pendingDeferreds: Set, + _options: CodeModePlatformHostOptions, +): CodeModePlatformHost { + const timers = new Map(); + + const randomUuidBridge = context.newFunction("__caplets_platform_random_uuid", () => { + const value = context.newString(randomUUID()); + return value; + }); + context.setProp(context.global, "__caplets_platform_random_uuid", randomUuidBridge); + randomUuidBridge.dispose(); + + const randomValuesBridge = context.newFunction( + "__caplets_platform_random_values", + (lengthHandle) => { + const length = context.dump(lengthHandle); + if (!Number.isSafeInteger(length) || length < 0) { + const error = context.newError("Random byte length must be a non-negative safe integer"); + return error; + } + if (length > MAX_RANDOM_VALUES_BYTES) { + return context.newError("Random byte length cannot exceed 65,536 bytes"); + } + return numberArrayHandle(context, [...randomBytes(length)]); + }, + ); + context.setProp(context.global, "__caplets_platform_random_values", randomValuesBridge); + randomValuesBridge.dispose(); + + const sleepBridge = context.newFunction( + "__caplets_platform_sleep", + (timerIdHandle, delayHandle) => { + const timerId = context.dump(timerIdHandle); + const delayMs = context.dump(delayHandle); + const deferred = context.newPromise(); + pendingDeferreds.add(deferred); + deferred.settled.finally(() => pendingDeferreds.delete(deferred)); + + const timeout = setTimeout( + () => { + timers.delete(timerId); + resolveTimer(context, deferred, true); + }, + Math.max(0, Number(delayMs) || 0), + ); + + const existing = timers.get(timerId); + if (existing) { + clearTimeout(existing.timeout); + resolveTimer(context, existing.deferred, false); + } + timers.set(timerId, { deferred, timeout }); + + return deferred.handle; + }, + ); + context.setProp(context.global, "__caplets_platform_sleep", sleepBridge); + sleepBridge.dispose(); + + const clearTimerBridge = context.newFunction( + "__caplets_platform_clear_timer", + (timerIdHandle) => { + const timerId = context.dump(timerIdHandle); + const timer = timers.get(timerId); + if (!timer) { + return context.false; + } + timers.delete(timerId); + clearTimeout(timer.timeout); + resolveTimer(context, timer.deferred, false); + return context.true; + }, + ); + context.setProp(context.global, "__caplets_platform_clear_timer", clearTimerBridge); + clearTimerBridge.dispose(); + + return { + dispose() { + for (const timer of timers.values()) { + clearTimeout(timer.timeout); + resolveTimer(context, timer.deferred, false); + } + timers.clear(); + }, + }; +} + +function numberArrayHandle(context: QuickJSContext, values: number[]): QuickJSHandle { + const arrayHandle = context.newArray(); + for (let index = 0; index < values.length; index += 1) { + const valueHandle = context.newNumber(values[index] ?? 0); + context.setProp(arrayHandle, index, valueHandle); + valueHandle.dispose(); + } + return arrayHandle; +} + +function resolveTimer( + context: QuickJSContext, + deferred: QuickJSDeferredPromise, + fired: boolean, +): void { + if (!deferred.alive) { + return; + } + deferred.resolve(fired ? context.true : context.false); +} diff --git a/packages/core/src/code-mode/platform-runtime.generated.ts b/packages/core/src/code-mode/platform-runtime.generated.ts new file mode 100644 index 00000000..21fce777 --- /dev/null +++ b/packages/core/src/code-mode/platform-runtime.generated.ts @@ -0,0 +1,3 @@ +// Generated by scripts/generate-code-mode-platform-runtime.mjs. Do not edit by hand. +export const CODE_MODE_PLATFORM_RUNTIME_SOURCE = + '(function() {\n\t//#region node_modules/.pnpm/@ungap+structured-clone@1.3.1/node_modules/@ungap/structured-clone/esm/deserialize.js\n\tconst env = typeof self === "object" ? self : globalThis;\n\tconst guard = (name, init) => {\n\t\tswitch (name) {\n\t\t\tcase "Function":\n\t\t\tcase "SharedWorker":\n\t\t\tcase "Worker":\n\t\t\tcase "eval":\n\t\t\tcase "setInterval":\n\t\t\tcase "setTimeout": throw new TypeError("unable to deserialize " + name);\n\t\t}\n\t\treturn new env[name](init);\n\t};\n\tconst deserializer = ($, _) => {\n\t\tconst as = (out, index) => {\n\t\t\t$.set(index, out);\n\t\t\treturn out;\n\t\t};\n\t\tconst unpair = (index) => {\n\t\t\tif ($.has(index)) return $.get(index);\n\t\t\tconst [type, value] = _[index];\n\t\t\tswitch (type) {\n\t\t\t\tcase 0:\n\t\t\t\tcase -1: return as(value, index);\n\t\t\t\tcase 1: {\n\t\t\t\t\tconst arr = as([], index);\n\t\t\t\t\tfor (const index of value) arr.push(unpair(index));\n\t\t\t\t\treturn arr;\n\t\t\t\t}\n\t\t\t\tcase 2: {\n\t\t\t\t\tconst object = as({}, index);\n\t\t\t\t\tfor (const [key, index] of value) object[unpair(key)] = unpair(index);\n\t\t\t\t\treturn object;\n\t\t\t\t}\n\t\t\t\tcase 3: return as(new Date(value), index);\n\t\t\t\tcase 4: {\n\t\t\t\t\tconst { source, flags } = value;\n\t\t\t\t\treturn as(new RegExp(source, flags), index);\n\t\t\t\t}\n\t\t\t\tcase 5: {\n\t\t\t\t\tconst map = as(/* @__PURE__ */ new Map(), index);\n\t\t\t\t\tfor (const [key, index] of value) map.set(unpair(key), unpair(index));\n\t\t\t\t\treturn map;\n\t\t\t\t}\n\t\t\t\tcase 6: {\n\t\t\t\t\tconst set = as(/* @__PURE__ */ new Set(), index);\n\t\t\t\t\tfor (const index of value) set.add(unpair(index));\n\t\t\t\t\treturn set;\n\t\t\t\t}\n\t\t\t\tcase 7: {\n\t\t\t\t\tconst { name, message } = value;\n\t\t\t\t\treturn as(guard(name, message), index);\n\t\t\t\t}\n\t\t\t\tcase 8: return as(BigInt(value), index);\n\t\t\t\tcase "BigInt": return as(Object(BigInt(value)), index);\n\t\t\t\tcase "ArrayBuffer": return as(new Uint8Array(value).buffer, value);\n\t\t\t\tcase "DataView": {\n\t\t\t\t\tconst { buffer } = new Uint8Array(value);\n\t\t\t\t\treturn as(new DataView(buffer), value);\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn as(guard(type, value), index);\n\t\t};\n\t\treturn unpair;\n\t};\n\t/**\n\t* @typedef {Array} Record a type representation\n\t*/\n\t/**\n\t* Returns a deserialized value from a serialized array of Records.\n\t* @param {Record[]} serialized a previously serialized value.\n\t* @returns {any}\n\t*/\n\tconst deserialize = (serialized) => deserializer(/* @__PURE__ */ new Map(), serialized)(0);\n\t//#endregion\n\t//#region node_modules/.pnpm/@ungap+structured-clone@1.3.1/node_modules/@ungap/structured-clone/esm/serialize.js\n\tconst EMPTY = "";\n\tconst { toString } = {};\n\tconst { keys } = Object;\n\tconst typeOf = (value) => {\n\t\tconst type = typeof value;\n\t\tif (type !== "object" || !value) return [0, type];\n\t\tconst asString = toString.call(value).slice(8, -1);\n\t\tswitch (asString) {\n\t\t\tcase "Array": return [1, EMPTY];\n\t\t\tcase "Object": return [2, EMPTY];\n\t\t\tcase "Date": return [3, EMPTY];\n\t\t\tcase "RegExp": return [4, EMPTY];\n\t\t\tcase "Map": return [5, EMPTY];\n\t\t\tcase "Set": return [6, EMPTY];\n\t\t\tcase "DataView": return [1, asString];\n\t\t}\n\t\tif (asString.includes("Array")) return [1, asString];\n\t\tif (asString.includes("Error")) return [7, asString];\n\t\treturn [2, asString];\n\t};\n\tconst shouldSkip = ([TYPE, type]) => TYPE === 0 && (type === "function" || type === "symbol");\n\tconst serializer = (strict, json, $, _) => {\n\t\tconst as = (out, value) => {\n\t\t\tconst index = _.push(out) - 1;\n\t\t\t$.set(value, index);\n\t\t\treturn index;\n\t\t};\n\t\tconst pair = (value) => {\n\t\t\tif ($.has(value)) return $.get(value);\n\t\t\tlet [TYPE, type] = typeOf(value);\n\t\t\tswitch (TYPE) {\n\t\t\t\tcase 0: {\n\t\t\t\t\tlet entry = value;\n\t\t\t\t\tswitch (type) {\n\t\t\t\t\t\tcase "bigint":\n\t\t\t\t\t\t\tTYPE = 8;\n\t\t\t\t\t\t\tentry = value.toString();\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase "function":\n\t\t\t\t\t\tcase "symbol":\n\t\t\t\t\t\t\tif (strict) throw new TypeError("unable to serialize " + type);\n\t\t\t\t\t\t\tentry = null;\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase "undefined": return as([-1], value);\n\t\t\t\t\t}\n\t\t\t\t\treturn as([TYPE, entry], value);\n\t\t\t\t}\n\t\t\t\tcase 1: {\n\t\t\t\t\tif (type) {\n\t\t\t\t\t\tlet spread = value;\n\t\t\t\t\t\tif (type === "DataView") spread = new Uint8Array(value.buffer);\n\t\t\t\t\t\telse if (type === "ArrayBuffer") spread = new Uint8Array(value);\n\t\t\t\t\t\treturn as([type, [...spread]], value);\n\t\t\t\t\t}\n\t\t\t\t\tconst arr = [];\n\t\t\t\t\tconst index = as([TYPE, arr], value);\n\t\t\t\t\tfor (const entry of value) arr.push(pair(entry));\n\t\t\t\t\treturn index;\n\t\t\t\t}\n\t\t\t\tcase 2: {\n\t\t\t\t\tif (type) switch (type) {\n\t\t\t\t\t\tcase "BigInt": return as([type, value.toString()], value);\n\t\t\t\t\t\tcase "Boolean":\n\t\t\t\t\t\tcase "Number":\n\t\t\t\t\t\tcase "String": return as([type, value.valueOf()], value);\n\t\t\t\t\t}\n\t\t\t\t\tif (json && "toJSON" in value) return pair(value.toJSON());\n\t\t\t\t\tconst entries = [];\n\t\t\t\t\tconst index = as([TYPE, entries], value);\n\t\t\t\t\tfor (const key of keys(value)) if (strict || !shouldSkip(typeOf(value[key]))) entries.push([pair(key), pair(value[key])]);\n\t\t\t\t\treturn index;\n\t\t\t\t}\n\t\t\t\tcase 3: return as([TYPE, value.toISOString()], value);\n\t\t\t\tcase 4: {\n\t\t\t\t\tconst { source, flags } = value;\n\t\t\t\t\treturn as([TYPE, {\n\t\t\t\t\t\tsource,\n\t\t\t\t\t\tflags\n\t\t\t\t\t}], value);\n\t\t\t\t}\n\t\t\t\tcase 5: {\n\t\t\t\t\tconst entries = [];\n\t\t\t\t\tconst index = as([TYPE, entries], value);\n\t\t\t\t\tfor (const [key, entry] of value) if (strict || !(shouldSkip(typeOf(key)) || shouldSkip(typeOf(entry)))) entries.push([pair(key), pair(entry)]);\n\t\t\t\t\treturn index;\n\t\t\t\t}\n\t\t\t\tcase 6: {\n\t\t\t\t\tconst entries = [];\n\t\t\t\t\tconst index = as([TYPE, entries], value);\n\t\t\t\t\tfor (const entry of value) if (strict || !shouldSkip(typeOf(entry))) entries.push(pair(entry));\n\t\t\t\t\treturn index;\n\t\t\t\t}\n\t\t\t}\n\t\t\tconst { message } = value;\n\t\t\treturn as([TYPE, {\n\t\t\t\tname: type,\n\t\t\t\tmessage\n\t\t\t}], value);\n\t\t};\n\t\treturn pair;\n\t};\n\t/**\n\t* @typedef {Array} Record a type representation\n\t*/\n\t/**\n\t* Returns an array of serialized Records.\n\t* @param {any} value a serializable value.\n\t* @param {{json?: boolean, lossy?: boolean}?} options an object with a `lossy` or `json` property that,\n\t* if `true`, will not throw errors on incompatible types, and behave more\n\t* like JSON stringify would behave. Symbol and Function will be discarded.\n\t* @returns {Record[]}\n\t*/\n\tconst serialize = (value, { json, lossy } = {}) => {\n\t\tconst _ = [];\n\t\treturn serializer(!(json || lossy), !!json, /* @__PURE__ */ new Map(), _)(value), _;\n\t};\n\t//#endregion\n\t//#region node_modules/.pnpm/@ungap+structured-clone@1.3.1/node_modules/@ungap/structured-clone/esm/index.js\n\t/**\n\t* @typedef {Array} Record a type representation\n\t*/\n\t/**\n\t* Returns an array of serialized Records.\n\t* @param {any} any a serializable value.\n\t* @param {{transfer?: any[], json?: boolean, lossy?: boolean}?} options an object with\n\t* a transfer option (ignored when polyfilled) and/or non standard fields that\n\t* fallback to the polyfill if present.\n\t* @returns {Record[]}\n\t*/\n\tvar esm_default = typeof structuredClone === "function" ? (any, options) => options && ("json" in options || "lossy" in options) ? deserialize(serialize(any, options)) : structuredClone(any) : (any, options) => deserialize(serialize(any, options));\n\t//#endregion\n\t//#region node_modules/.pnpm/formdata-node@6.0.3/node_modules/formdata-node/lib/form-data.js\n\tvar __accessCheck = (obj, member, msg) => {\n\t\tif (!member.has(obj)) throw TypeError("Cannot " + msg);\n\t};\n\tvar __privateGet = (obj, member, getter) => {\n\t\t__accessCheck(obj, member, "read from private field");\n\t\treturn getter ? getter.call(obj) : member.get(obj);\n\t};\n\tvar __privateAdd = (obj, member, value) => {\n\t\tif (member.has(obj)) throw TypeError("Cannot add the same private member more than once");\n\t\tmember instanceof WeakSet ? member.add(obj) : member.set(obj, value);\n\t};\n\tvar __privateSet = (obj, member, value, setter) => {\n\t\t__accessCheck(obj, member, "write to private field");\n\t\tsetter ? setter.call(obj, value) : member.set(obj, value);\n\t\treturn value;\n\t};\n\tvar __privateMethod = (obj, member, method) => {\n\t\t__accessCheck(obj, member, "access private method");\n\t\treturn method;\n\t};\n\tvar isFunction = (value) => typeof value === "function";\n\tvar isObject = (value) => typeof value === "object" && value != null && !Array.isArray(value);\n\tvar isAsyncIterable = (value) => isObject(value) && isFunction(value[Symbol.asyncIterator]);\n\tvar MAX_CHUNK_SIZE = 65536;\n\tasync function* clonePart(value) {\n\t\tif (value.byteLength <= MAX_CHUNK_SIZE) {\n\t\t\tyield value;\n\t\t\treturn;\n\t\t}\n\t\tlet offset = 0;\n\t\twhile (offset < value.byteLength) {\n\t\t\tconst size = Math.min(value.byteLength - offset, MAX_CHUNK_SIZE);\n\t\t\tconst buffer = value.buffer.slice(offset, offset + size);\n\t\t\toffset += buffer.byteLength;\n\t\t\tyield new Uint8Array(buffer);\n\t\t}\n\t}\n\tasync function* readStream(readable) {\n\t\tconst reader = readable.getReader();\n\t\twhile (true) {\n\t\t\tconst { done, value } = await reader.read();\n\t\t\tif (done) break;\n\t\t\tyield value;\n\t\t}\n\t}\n\tasync function* chunkStream(stream) {\n\t\tfor await (const value of stream) yield* clonePart(value);\n\t}\n\tvar getStreamIterator = (source) => {\n\t\tif (isAsyncIterable(source)) return chunkStream(source);\n\t\tif (isFunction(source.getReader)) return chunkStream(readStream(source));\n\t\tthrow new TypeError("Unsupported data source: Expected either ReadableStream or async iterable.");\n\t};\n\tasync function* consumeNodeBlob(blob) {\n\t\tlet position = 0;\n\t\twhile (position !== blob.size) {\n\t\t\tconst buffer = await blob.slice(position, Math.min(blob.size, position + MAX_CHUNK_SIZE)).arrayBuffer();\n\t\t\tposition += buffer.byteLength;\n\t\t\tyield new Uint8Array(buffer);\n\t\t}\n\t}\n\tasync function* consumeBlobParts(parts, clone = false) {\n\t\tfor (const part of parts) if (ArrayBuffer.isView(part)) if (clone) yield* clonePart(part);\n\t\telse yield part;\n\t\telse if (isFunction(part.stream)) yield* getStreamIterator(part.stream());\n\t\telse yield* consumeNodeBlob(part);\n\t}\n\tfunction* sliceBlob(blobParts, blobSize, start = 0, end) {\n\t\tend ??= blobSize;\n\t\tlet relativeStart = start < 0 ? Math.max(blobSize + start, 0) : Math.min(start, blobSize);\n\t\tlet relativeEnd = end < 0 ? Math.max(blobSize + end, 0) : Math.min(end, blobSize);\n\t\tconst span = Math.max(relativeEnd - relativeStart, 0);\n\t\tlet added = 0;\n\t\tfor (const part of blobParts) {\n\t\t\tif (added >= span) break;\n\t\t\tconst partSize = ArrayBuffer.isView(part) ? part.byteLength : part.size;\n\t\t\tif (relativeStart && partSize <= relativeStart) {\n\t\t\t\trelativeStart -= partSize;\n\t\t\t\trelativeEnd -= partSize;\n\t\t\t} else {\n\t\t\t\tlet chunk;\n\t\t\t\tif (ArrayBuffer.isView(part)) {\n\t\t\t\t\tchunk = part.subarray(relativeStart, Math.min(partSize, relativeEnd));\n\t\t\t\t\tadded += chunk.byteLength;\n\t\t\t\t} else {\n\t\t\t\t\tchunk = part.slice(relativeStart, Math.min(partSize, relativeEnd));\n\t\t\t\t\tadded += chunk.size;\n\t\t\t\t}\n\t\t\t\trelativeEnd -= partSize;\n\t\t\t\trelativeStart = 0;\n\t\t\t\tyield chunk;\n\t\t\t}\n\t\t}\n\t}\n\tvar _parts, _type, _size;\n\tvar _Blob = class _Blob {\n\t\t/**\n\t\t* Returns a new [`Blob`](https://developer.mozilla.org/en-US/docs/Web/API/Blob) object.\n\t\t* The content of the blob consists of the concatenation of the values given in the parameter array.\n\t\t*\n\t\t* @param blobParts An `Array` strings, or [`ArrayBuffer`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer), [`ArrayBufferView`](https://developer.mozilla.org/en-US/docs/Web/API/ArrayBufferView), [`Blob`](https://developer.mozilla.org/en-US/docs/Web/API/Blob) objects, or a mix of any of such objects, that will be put inside the [`Blob`](https://developer.mozilla.org/en-US/docs/Web/API/Blob).\n\t\t* @param options An optional object of type `BlobPropertyBag`.\n\t\t*/\n\t\tconstructor(blobParts = [], options = {}) {\n\t\t\t/**\n\t\t\t* An `Array` of [`ArrayBufferView`](https://developer.mozilla.org/en-US/docs/Web/API/ArrayBufferView) or [`Blob`](https://developer.mozilla.org/en-US/docs/Web/API/Blob) objects, or a mix of any of such objects, that will be put inside the [`Blob`](https://developer.mozilla.org/en-US/docs/Web/API/Blob).\n\t\t\t*/\n\t\t\t__privateAdd(this, _parts, []);\n\t\t\t/**\n\t\t\t* Returns the [`MIME type`](https://developer.mozilla.org/en-US/docs/Glossary/MIME_type) of the [`Blob`](https://developer.mozilla.org/en-US/docs/Web/API/Blob) or [`File`](https://developer.mozilla.org/en-US/docs/Web/API/File).\n\t\t\t*/\n\t\t\t__privateAdd(this, _type, "");\n\t\t\t/**\n\t\t\t* Returns the size of the [`Blob`](https://developer.mozilla.org/en-US/docs/Web/API/Blob) or [`File`](https://developer.mozilla.org/en-US/docs/Web/API/File) in bytes.\n\t\t\t*/\n\t\t\t__privateAdd(this, _size, 0);\n\t\t\toptions ??= {};\n\t\t\tif (typeof blobParts !== "object" || blobParts === null) throw new TypeError("Failed to construct \'Blob\': The provided value cannot be converted to a sequence.");\n\t\t\tif (!isFunction(blobParts[Symbol.iterator])) throw new TypeError("Failed to construct \'Blob\': The object must have a callable @@iterator property.");\n\t\t\tif (typeof options !== "object" && !isFunction(options)) throw new TypeError("Failed to construct \'Blob\': parameter 2 cannot convert to dictionary.");\n\t\t\tconst encoder = new TextEncoder();\n\t\t\tfor (const raw of blobParts) {\n\t\t\t\tlet part;\n\t\t\t\tif (ArrayBuffer.isView(raw)) part = new Uint8Array(raw.buffer.slice(raw.byteOffset, raw.byteOffset + raw.byteLength));\n\t\t\t\telse if (raw instanceof ArrayBuffer) part = new Uint8Array(raw.slice(0));\n\t\t\t\telse if (raw instanceof _Blob) part = raw;\n\t\t\t\telse part = encoder.encode(String(raw));\n\t\t\t\t__privateSet(this, _size, __privateGet(this, _size) + (ArrayBuffer.isView(part) ? part.byteLength : part.size));\n\t\t\t\t__privateGet(this, _parts).push(part);\n\t\t\t}\n\t\t\tconst type = options.type === void 0 ? "" : String(options.type);\n\t\t\t__privateSet(this, _type, /^[\\x20-\\x7E]*$/.test(type) ? type : "");\n\t\t}\n\t\tstatic [Symbol.hasInstance](value) {\n\t\t\treturn Boolean(value && typeof value === "object" && isFunction(value.constructor) && (isFunction(value.stream) || isFunction(value.arrayBuffer)) && /^(Blob|File)$/.test(value[Symbol.toStringTag]));\n\t\t}\n\t\t/**\n\t\t* Returns the [`MIME type`](https://developer.mozilla.org/en-US/docs/Glossary/MIME_type) of the [`Blob`](https://developer.mozilla.org/en-US/docs/Web/API/Blob) or [`File`](https://developer.mozilla.org/en-US/docs/Web/API/File).\n\t\t*/\n\t\tget type() {\n\t\t\treturn __privateGet(this, _type);\n\t\t}\n\t\t/**\n\t\t* Returns the size of the [`Blob`](https://developer.mozilla.org/en-US/docs/Web/API/Blob) or [`File`](https://developer.mozilla.org/en-US/docs/Web/API/File) in bytes.\n\t\t*/\n\t\tget size() {\n\t\t\treturn __privateGet(this, _size);\n\t\t}\n\t\t/**\n\t\t* Creates and returns a new [`Blob`](https://developer.mozilla.org/en-US/docs/Web/API/Blob) object which contains data from a subset of the blob on which it\'s called.\n\t\t*\n\t\t* @param start An index into the Blob indicating the first byte to include in the new Blob. If you specify a negative value, it\'s treated as an offset from the end of the Blob toward the beginning. For example, -10 would be the 10th from last byte in the Blob. The default value is 0. If you specify a value for start that is larger than the size of the source Blob, the returned Blob has size 0 and contains no data.\n\t\t* @param end An index into the Blob indicating the first byte that will *not* be included in the new Blob (i.e. the byte exactly at this index is not included). If you specify a negative value, it\'s treated as an offset from the end of the Blob toward the beginning. For example, -10 would be the 10th from last byte in the Blob. The default value is size.\n\t\t* @param contentType The content type to assign to the new Blob; this will be the value of its type property. The default value is an empty string.\n\t\t*/\n\t\tslice(start, end, contentType) {\n\t\t\treturn new _Blob(sliceBlob(__privateGet(this, _parts), this.size, start, end), { type: contentType });\n\t\t}\n\t\t/**\n\t\t* Returns a [`Promise`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) that resolves with a string containing the contents of the blob, interpreted as UTF-8.\n\t\t*/\n\t\tasync text() {\n\t\t\tconst decoder = new TextDecoder();\n\t\t\tlet result = "";\n\t\t\tfor await (const chunk of consumeBlobParts(__privateGet(this, _parts))) result += decoder.decode(chunk, { stream: true });\n\t\t\tresult += decoder.decode();\n\t\t\treturn result;\n\t\t}\n\t\t/**\n\t\t* Returns a [`Promise`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) that resolves with the contents of the blob as binary data contained in an [`ArrayBuffer`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer).\n\t\t*/\n\t\tasync arrayBuffer() {\n\t\t\tconst view = new Uint8Array(this.size);\n\t\t\tlet offset = 0;\n\t\t\tfor await (const chunk of consumeBlobParts(__privateGet(this, _parts))) {\n\t\t\t\tview.set(chunk, offset);\n\t\t\t\toffset += chunk.length;\n\t\t\t}\n\t\t\treturn view.buffer;\n\t\t}\n\t\t/**\n\t\t* Returns a [`ReadableStream`](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream) which upon reading returns the data contained within the [`Blob`](https://developer.mozilla.org/en-US/docs/Web/API/Blob).\n\t\t*/\n\t\tstream() {\n\t\t\tconst iterator = consumeBlobParts(__privateGet(this, _parts), true);\n\t\t\treturn new ReadableStream({\n\t\t\t\tasync pull(controller) {\n\t\t\t\t\tconst { value, done } = await iterator.next();\n\t\t\t\t\tif (done) return queueMicrotask(() => controller.close());\n\t\t\t\t\tcontroller.enqueue(value);\n\t\t\t\t},\n\t\t\t\tasync cancel() {\n\t\t\t\t\tawait iterator.return();\n\t\t\t\t}\n\t\t\t});\n\t\t}\n\t\tget [Symbol.toStringTag]() {\n\t\t\treturn "Blob";\n\t\t}\n\t};\n\t_parts = /* @__PURE__ */ new WeakMap();\n\t_type = /* @__PURE__ */ new WeakMap();\n\t_size = /* @__PURE__ */ new WeakMap();\n\tvar Blob = _Blob;\n\tObject.defineProperties(Blob.prototype, {\n\t\ttype: { enumerable: true },\n\t\tsize: { enumerable: true },\n\t\tslice: { enumerable: true },\n\t\tstream: { enumerable: true },\n\t\ttext: { enumerable: true },\n\t\tarrayBuffer: { enumerable: true }\n\t});\n\tvar isBlob = (value) => value instanceof Blob;\n\tvar _name, _lastModified;\n\tvar File = class extends Blob {\n\t\t/**\n\t\t* Creates a new File instance.\n\t\t*\n\t\t* @param fileBits An `Array` strings, or [`ArrayBuffer`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer), [`ArrayBufferView`](https://developer.mozilla.org/en-US/docs/Web/API/ArrayBufferView), [`Blob`](https://developer.mozilla.org/en-US/docs/Web/API/Blob) objects, or a mix of any of such objects, that will be put inside the [`File`](https://developer.mozilla.org/en-US/docs/Web/API/File).\n\t\t* @param name The name of the file.\n\t\t* @param options An options object containing optional attributes for the file.\n\t\t*/\n\t\tconstructor(fileBits, name, options = {}) {\n\t\t\tsuper(fileBits, options);\n\t\t\t/**\n\t\t\t* Returns the name of the file referenced by the File object.\n\t\t\t*/\n\t\t\t__privateAdd(this, _name, void 0);\n\t\t\t/**\n\t\t\t* The last modified date of the file as the number of milliseconds since the Unix epoch (January 1, 1970 at midnight). Files without a known last modified date return the current date.\n\t\t\t*/\n\t\t\t__privateAdd(this, _lastModified, 0);\n\t\t\tif (arguments.length < 2) throw new TypeError(`Failed to construct \'File\': 2 arguments required, but only ${arguments.length} present.`);\n\t\t\t__privateSet(this, _name, String(name));\n\t\t\tconst lastModified = options.lastModified === void 0 ? Date.now() : Number(options.lastModified);\n\t\t\tif (!Number.isNaN(lastModified)) __privateSet(this, _lastModified, lastModified);\n\t\t}\n\t\tstatic [Symbol.hasInstance](value) {\n\t\t\treturn value instanceof Blob && value[Symbol.toStringTag] === "File" && typeof value.name === "string";\n\t\t}\n\t\t/**\n\t\t* Name of the file referenced by the File object.\n\t\t*/\n\t\tget name() {\n\t\t\treturn __privateGet(this, _name);\n\t\t}\n\t\t/* c8 ignore next 3 */\n\t\tget webkitRelativePath() {\n\t\t\treturn "";\n\t\t}\n\t\t/**\n\t\t* The last modified date of the file as the number of milliseconds since the Unix epoch (January 1, 1970 at midnight). Files without a known last modified date return the current date.\n\t\t*/\n\t\tget lastModified() {\n\t\t\treturn __privateGet(this, _lastModified);\n\t\t}\n\t\tget [Symbol.toStringTag]() {\n\t\t\treturn "File";\n\t\t}\n\t};\n\t_name = /* @__PURE__ */ new WeakMap();\n\t_lastModified = /* @__PURE__ */ new WeakMap();\n\tvar isFile = (value) => value instanceof File;\n\tvar _entries, _setEntry, setEntry_fn;\n\tvar FormData = class {\n\t\tconstructor() {\n\t\t\t__privateAdd(this, _setEntry);\n\t\t\t/**\n\t\t\t* Stores internal data for every entry\n\t\t\t*/\n\t\t\t__privateAdd(this, _entries, /* @__PURE__ */ new Map());\n\t\t}\n\t\tstatic [Symbol.hasInstance](value) {\n\t\t\tif (!value) return false;\n\t\t\tconst val = value;\n\t\t\treturn Boolean(isFunction(val.constructor) && val[Symbol.toStringTag] === "FormData" && isFunction(val.append) && isFunction(val.set) && isFunction(val.get) && isFunction(val.getAll) && isFunction(val.has) && isFunction(val.delete) && isFunction(val.entries) && isFunction(val.values) && isFunction(val.keys) && isFunction(val[Symbol.iterator]) && isFunction(val.forEach));\n\t\t}\n\t\t/**\n\t\t* Appends a new value onto an existing key inside a FormData object,\n\t\t* or adds the key if it does not already exist.\n\t\t*\n\t\t* The difference between `set()` and `append()` is that if the specified key already exists, `set()` will overwrite all existing values with the new one, whereas `append()` will append the new value onto the end of the existing set of values.\n\t\t*\n\t\t* @param name The name of the field whose data is contained in `value`.\n\t\t* @param value The field\'s value. This can be [`Blob`](https://developer.mozilla.org/en-US/docs/Web/API/Blob)\n\t\tor [`File`](https://developer.mozilla.org/en-US/docs/Web/API/File). If none of these are specified the value is converted to a string.\n\t\t* @param fileName The filename reported to the server, when a Blob or File is passed as the second parameter. The default filename for Blob objects is "blob". The default filename for File objects is the file\'s filename.\n\t\t*/\n\t\tappend(name, value, fileName) {\n\t\t\t__privateMethod(this, _setEntry, setEntry_fn).call(this, {\n\t\t\t\tname,\n\t\t\t\tfileName,\n\t\t\t\tappend: true,\n\t\t\t\trawValue: value,\n\t\t\t\targsLength: arguments.length\n\t\t\t});\n\t\t}\n\t\t/**\n\t\t* Set a new value for an existing key inside FormData,\n\t\t* or add the new field if it does not already exist.\n\t\t*\n\t\t* @param name The name of the field whose data is contained in `value`.\n\t\t* @param value The field\'s value. This can be [`Blob`](https://developer.mozilla.org/en-US/docs/Web/API/Blob)\n\t\tor [`File`](https://developer.mozilla.org/en-US/docs/Web/API/File). If none of these are specified the value is converted to a string.\n\t\t* @param fileName The filename reported to the server, when a Blob or File is passed as the second parameter. The default filename for Blob objects is "blob". The default filename for File objects is the file\'s filename.\n\t\t*\n\t\t*/\n\t\tset(name, value, fileName) {\n\t\t\t__privateMethod(this, _setEntry, setEntry_fn).call(this, {\n\t\t\t\tname,\n\t\t\t\tfileName,\n\t\t\t\tappend: false,\n\t\t\t\trawValue: value,\n\t\t\t\targsLength: arguments.length\n\t\t\t});\n\t\t}\n\t\t/**\n\t\t* Returns the first value associated with a given key from within a `FormData` object.\n\t\t* If you expect multiple values and want all of them, use the `getAll()` method instead.\n\t\t*\n\t\t* @param {string} name A name of the value you want to retrieve.\n\t\t*\n\t\t* @returns A `FormDataEntryValue` containing the value. If the key doesn\'t exist, the method returns null.\n\t\t*/\n\t\tget(name) {\n\t\t\tconst field = __privateGet(this, _entries).get(String(name));\n\t\t\tif (!field) return null;\n\t\t\treturn field[0];\n\t\t}\n\t\t/**\n\t\t* Returns all the values associated with a given key from within a `FormData` object.\n\t\t*\n\t\t* @param {string} name A name of the value you want to retrieve.\n\t\t*\n\t\t* @returns An array of `FormDataEntryValue` whose key matches the value passed in the `name` parameter. If the key doesn\'t exist, the method returns an empty list.\n\t\t*/\n\t\tgetAll(name) {\n\t\t\tconst field = __privateGet(this, _entries).get(String(name));\n\t\t\tif (!field) return [];\n\t\t\treturn field.slice();\n\t\t}\n\t\t/**\n\t\t* Returns a boolean stating whether a `FormData` object contains a certain key.\n\t\t*\n\t\t* @param name A string representing the name of the key you want to test for.\n\t\t*\n\t\t* @return A boolean value.\n\t\t*/\n\t\thas(name) {\n\t\t\treturn __privateGet(this, _entries).has(String(name));\n\t\t}\n\t\t/**\n\t\t* Deletes a key and its value(s) from a `FormData` object.\n\t\t*\n\t\t* @param name The name of the key you want to delete.\n\t\t*/\n\t\tdelete(name) {\n\t\t\t__privateGet(this, _entries).delete(String(name));\n\t\t}\n\t\t/**\n\t\t* Returns an [`iterator`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols) allowing to go through all keys contained in this `FormData` object.\n\t\t* Each key is a `string`.\n\t\t*/\n\t\t*keys() {\n\t\t\tfor (const key of __privateGet(this, _entries).keys()) yield key;\n\t\t}\n\t\t/**\n\t\t* Returns an [`iterator`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols) allowing to go through the `FormData` key/value pairs.\n\t\t* The key of each pair is a string; the value is a [`FormDataValue`](https://developer.mozilla.org/en-US/docs/Web/API/FormDataEntryValue).\n\t\t*/\n\t\t*entries() {\n\t\t\tfor (const name of this.keys()) {\n\t\t\t\tconst values = this.getAll(name);\n\t\t\t\tfor (const value of values) yield [name, value];\n\t\t\t}\n\t\t}\n\t\t/**\n\t\t* Returns an [`iterator`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols) allowing to go through all values contained in this object `FormData` object.\n\t\t* Each value is a [`FormDataValue`](https://developer.mozilla.org/en-US/docs/Web/API/FormDataEntryValue).\n\t\t*/\n\t\t*values() {\n\t\t\tfor (const [, value] of this) yield value;\n\t\t}\n\t\t/**\n\t\t* An alias for FormData#entries()\n\t\t*/\n\t\t[Symbol.iterator]() {\n\t\t\treturn this.entries();\n\t\t}\n\t\t/**\n\t\t* Executes given callback function for each field of the FormData instance\n\t\t*/\n\t\tforEach(callback, thisArg) {\n\t\t\tfor (const [name, value] of this) callback.call(thisArg, value, name, this);\n\t\t}\n\t\tget [Symbol.toStringTag]() {\n\t\t\treturn "FormData";\n\t\t}\n\t};\n\t_entries = /* @__PURE__ */ new WeakMap();\n\t_setEntry = /* @__PURE__ */ new WeakSet();\n\tsetEntry_fn = function({ name, rawValue, append, fileName, argsLength }) {\n\t\tconst methodName = append ? "append" : "set";\n\t\tif (argsLength < 2) throw new TypeError(`Failed to execute \'${methodName}\' on \'FormData\': 2 arguments required, but only ${argsLength} present.`);\n\t\tname = String(name);\n\t\tlet value;\n\t\tif (isFile(rawValue)) value = fileName === void 0 ? rawValue : new File([rawValue], fileName, {\n\t\t\ttype: rawValue.type,\n\t\t\tlastModified: rawValue.lastModified\n\t\t});\n\t\telse if (isBlob(rawValue)) value = new File([rawValue], fileName === void 0 ? "blob" : fileName, { type: rawValue.type });\n\t\telse if (fileName) throw new TypeError(`Failed to execute \'${methodName}\' on \'FormData\': parameter 2 is not of type \'Blob\'.`);\n\t\telse value = String(rawValue);\n\t\tconst values = __privateGet(this, _entries).get(name);\n\t\tif (!values) {\n\t\t\t__privateGet(this, _entries).set(name, [value]);\n\t\t\treturn;\n\t\t}\n\t\tif (!append) {\n\t\t\t__privateGet(this, _entries).set(name, [value]);\n\t\t\treturn;\n\t\t}\n\t\tvalues.push(value);\n\t};\n\t/*! Based on fetch-blob. MIT License. Jimmy Wärting & David Frank */\n\t//#endregion\n\t//#region node_modules/.pnpm/set-cookie-parser@3.1.0/node_modules/set-cookie-parser/lib/set-cookie.js\n\tvar defaultParseOptions = {\n\t\tdecodeValues: true,\n\t\tmap: false,\n\t\tsilent: false,\n\t\tsplit: "auto"\n\t};\n\tfunction isForbiddenKey(key) {\n\t\treturn typeof key !== "string" || key in {};\n\t}\n\tfunction createNullObj() {\n\t\treturn Object.create(null);\n\t}\n\tfunction isNonEmptyString(str) {\n\t\treturn typeof str === "string" && !!str.trim();\n\t}\n\tfunction parseString(setCookieValue, options) {\n\t\tvar parts = setCookieValue.split(";").filter(isNonEmptyString);\n\t\tvar parsed = parseNameValuePair(parts.shift());\n\t\tvar name = parsed.name;\n\t\tvar value = parsed.value;\n\t\toptions = options ? Object.assign({}, defaultParseOptions, options) : defaultParseOptions;\n\t\tif (isForbiddenKey(name)) return null;\n\t\ttry {\n\t\t\tvalue = options.decodeValues ? decodeURIComponent(value) : value;\n\t\t} catch (e) {\n\t\t\tconsole.error("set-cookie-parser: failed to decode cookie value. Set options.decodeValues=false to disable decoding.", e);\n\t\t}\n\t\tvar cookie = createNullObj();\n\t\tcookie.name = name;\n\t\tcookie.value = value;\n\t\tparts.forEach(function(part) {\n\t\t\tvar sides = part.split("=");\n\t\t\tvar key = sides.shift().trimLeft().toLowerCase();\n\t\t\tif (isForbiddenKey(key)) return;\n\t\t\tvar value = sides.join("=");\n\t\t\tif (key === "expires") cookie.expires = new Date(value);\n\t\t\telse if (key === "max-age") {\n\t\t\t\tvar n = parseInt(value, 10);\n\t\t\t\tif (!Number.isNaN(n)) cookie.maxAge = n;\n\t\t\t} else if (key === "secure") cookie.secure = true;\n\t\t\telse if (key === "httponly") cookie.httpOnly = true;\n\t\t\telse if (key === "samesite") cookie.sameSite = value;\n\t\t\telse if (key === "partitioned") cookie.partitioned = true;\n\t\t\telse if (key) cookie[key] = value;\n\t\t});\n\t\treturn cookie;\n\t}\n\tfunction parseNameValuePair(nameValuePairStr) {\n\t\tvar name = "";\n\t\tvar value = "";\n\t\tvar nameValueArr = nameValuePairStr.split("=");\n\t\tif (nameValueArr.length > 1) {\n\t\t\tname = nameValueArr.shift();\n\t\t\tvalue = nameValueArr.join("=");\n\t\t} else value = nameValuePairStr;\n\t\treturn {\n\t\t\tname,\n\t\t\tvalue\n\t\t};\n\t}\n\tfunction parseSetCookie(input, options) {\n\t\toptions = options ? Object.assign({}, defaultParseOptions, options) : defaultParseOptions;\n\t\tif (!input) if (!options.map) return [];\n\t\telse return createNullObj();\n\t\tif (input.headers) if (typeof input.headers.getSetCookie === "function") input = input.headers.getSetCookie();\n\t\telse if (input.headers["set-cookie"]) input = input.headers["set-cookie"];\n\t\telse {\n\t\t\tvar sch = input.headers[Object.keys(input.headers).find(function(key) {\n\t\t\t\treturn key.toLowerCase() === "set-cookie";\n\t\t\t})];\n\t\t\tif (!sch && input.headers.cookie && !options.silent) console.warn("Warning: set-cookie-parser appears to have been called on a request object. It is designed to parse Set-Cookie headers from responses, not Cookie headers from requests. Set the option {silent: true} to suppress this warning.");\n\t\t\tinput = sch;\n\t\t}\n\t\tvar split = options.split;\n\t\tvar isArray = Array.isArray(input);\n\t\tif (split === "auto") split = !isArray;\n\t\tif (!isArray) input = [input];\n\t\tinput = input.filter(isNonEmptyString);\n\t\tif (split) input = input.map(splitCookiesString).flat();\n\t\tif (!options.map) return input.map(function(str) {\n\t\t\treturn parseString(str, options);\n\t\t}).filter(Boolean);\n\t\telse {\n\t\t\tvar cookies = createNullObj();\n\t\t\treturn input.reduce(function(cookies, str) {\n\t\t\t\tvar cookie = parseString(str, options);\n\t\t\t\tif (cookie && !isForbiddenKey(cookie.name)) cookies[cookie.name] = cookie;\n\t\t\t\treturn cookies;\n\t\t\t}, cookies);\n\t\t}\n\t}\n\tfunction splitCookiesString(cookiesString) {\n\t\tif (Array.isArray(cookiesString)) return cookiesString;\n\t\tif (typeof cookiesString !== "string") return [];\n\t\tvar cookiesStrings = [];\n\t\tvar pos = 0;\n\t\tvar start;\n\t\tvar ch;\n\t\tvar lastComma;\n\t\tvar nextStart;\n\t\tvar cookiesSeparatorFound;\n\t\tfunction skipWhitespace() {\n\t\t\twhile (pos < cookiesString.length && /\\s/.test(cookiesString.charAt(pos))) pos += 1;\n\t\t\treturn pos < cookiesString.length;\n\t\t}\n\t\tfunction notSpecialChar() {\n\t\t\tch = cookiesString.charAt(pos);\n\t\t\treturn ch !== "=" && ch !== ";" && ch !== ",";\n\t\t}\n\t\twhile (pos < cookiesString.length) {\n\t\t\tstart = pos;\n\t\t\tcookiesSeparatorFound = false;\n\t\t\twhile (skipWhitespace()) {\n\t\t\t\tch = cookiesString.charAt(pos);\n\t\t\t\tif (ch === ",") {\n\t\t\t\t\tlastComma = pos;\n\t\t\t\t\tpos += 1;\n\t\t\t\t\tskipWhitespace();\n\t\t\t\t\tnextStart = pos;\n\t\t\t\t\twhile (pos < cookiesString.length && notSpecialChar()) pos += 1;\n\t\t\t\t\tif (pos < cookiesString.length && cookiesString.charAt(pos) === "=") {\n\t\t\t\t\t\tcookiesSeparatorFound = true;\n\t\t\t\t\t\tpos = nextStart;\n\t\t\t\t\t\tcookiesStrings.push(cookiesString.substring(start, lastComma));\n\t\t\t\t\t\tstart = pos;\n\t\t\t\t\t} else pos = lastComma + 1;\n\t\t\t\t} else pos += 1;\n\t\t\t}\n\t\t\tif (!cookiesSeparatorFound || pos >= cookiesString.length) cookiesStrings.push(cookiesString.substring(start, cookiesString.length));\n\t\t}\n\t\treturn cookiesStrings;\n\t}\n\tparseSetCookie.parseSetCookie = parseSetCookie;\n\tparseSetCookie.parse = parseSetCookie;\n\tparseSetCookie.parseString = parseString;\n\tparseSetCookie.splitCookiesString = splitCookiesString;\n\t//#endregion\n\t//#region node_modules/.pnpm/headers-polyfill@5.0.1/node_modules/headers-polyfill/lib/index.mjs\n\tconst HEADERS_INVALID_CHARACTERS = /[^a-z0-9\\-#$%&\'*+.^_`|~]/i;\n\tfunction normalizeHeaderName(name) {\n\t\tif (HEADERS_INVALID_CHARACTERS.test(name) || name.trim() === "") throw new TypeError("Invalid character in header field name");\n\t\treturn name.trim().toLowerCase();\n\t}\n\tconst charCodesToRemove = [\n\t\tString.fromCharCode(10),\n\t\tString.fromCharCode(13),\n\t\tString.fromCharCode(9),\n\t\tString.fromCharCode(32)\n\t];\n\tconst HEADER_VALUE_REMOVE_REGEXP = new RegExp(`(^[${charCodesToRemove.join("")}]|$[${charCodesToRemove.join("")}])`, "g");\n\t/**\n\t* Normalize the given header value.\n\t* @see https://fetch.spec.whatwg.org/#concept-header-value-normalize\n\t*/\n\tfunction normalizeHeaderValue(value) {\n\t\treturn value.replace(HEADER_VALUE_REMOVE_REGEXP, "");\n\t}\n\t/**\n\t* Validate the given header name.\n\t* @see https://fetch.spec.whatwg.org/#header-name\n\t*/\n\tfunction isValidHeaderName(value) {\n\t\tif (typeof value !== "string") return false;\n\t\tif (value.length === 0) return false;\n\t\tfor (let i = 0; i < value.length; i++) {\n\t\t\tconst character = value.charCodeAt(i);\n\t\t\tif (character > 127 || !isToken(character)) return false;\n\t\t}\n\t\treturn true;\n\t}\n\tfunction isToken(value) {\n\t\treturn ![\n\t\t\t127,\n\t\t\t32,\n\t\t\t"(",\n\t\t\t")",\n\t\t\t"<",\n\t\t\t">",\n\t\t\t"@",\n\t\t\t",",\n\t\t\t";",\n\t\t\t":",\n\t\t\t"\\\\",\n\t\t\t"\\"",\n\t\t\t"/",\n\t\t\t"[",\n\t\t\t"]",\n\t\t\t"?",\n\t\t\t"=",\n\t\t\t"{",\n\t\t\t"}"\n\t\t].includes(value);\n\t}\n\t/**\n\t* Validate the given header value.\n\t* @see https://fetch.spec.whatwg.org/#header-value\n\t*/\n\tfunction isValidHeaderValue(value) {\n\t\tif (typeof value !== "string") return false;\n\t\tif (value.trim() !== value) return false;\n\t\tfor (let i = 0; i < value.length; i++) {\n\t\t\tconst character = value.charCodeAt(i);\n\t\t\tif (character === 0 || character === 10 || character === 13) return false;\n\t\t}\n\t\treturn true;\n\t}\n\tlet _Symbol$toStringTag;\n\tconst NORMALIZED_HEADERS = Symbol("normalizedHeaders");\n\tconst RAW_HEADER_NAMES = Symbol("rawHeaderNames");\n\tconst HEADER_VALUE_DELIMITER = ", ";\n\tvar Headers = class Headers {\n\t\tconstructor(init) {\n\t\t\tthis[NORMALIZED_HEADERS] = {};\n\t\t\tthis[RAW_HEADER_NAMES] = /* @__PURE__ */ new Map();\n\t\t\tthis[_Symbol$toStringTag] = "Headers";\n\t\t\t/**\n\t\t\t* @note Cannot necessarily check if the `init` is an instance of the\n\t\t\t* `Headers` because that class may not be defined in Node or jsdom.\n\t\t\t*/\n\t\t\tif (["Headers", "HeadersPolyfill"].includes(init?.constructor?.name) || init instanceof Headers || typeof globalThis.Headers !== "undefined" && init instanceof globalThis.Headers) init.forEach((value, name) => {\n\t\t\t\tthis.append(name, value);\n\t\t\t}, this);\n\t\t\telse if (Array.isArray(init)) init.forEach(([name, value]) => {\n\t\t\t\tthis.append(name, Array.isArray(value) ? value.join(HEADER_VALUE_DELIMITER) : value);\n\t\t\t});\n\t\t\telse if (init) Object.getOwnPropertyNames(init).forEach((name) => {\n\t\t\t\tconst value = init[name];\n\t\t\t\tthis.append(name, Array.isArray(value) ? value.join(HEADER_VALUE_DELIMITER) : value);\n\t\t\t});\n\t\t}\n\t\t[(_Symbol$toStringTag = Symbol.toStringTag, Symbol.iterator)]() {\n\t\t\treturn this.entries();\n\t\t}\n\t\t*keys() {\n\t\t\tfor (const [name] of this.entries()) yield name;\n\t\t}\n\t\t*values() {\n\t\t\tfor (const [, value] of this.entries()) yield value;\n\t\t}\n\t\t*entries() {\n\t\t\tlet sortedKeys = Object.keys(this[NORMALIZED_HEADERS]).sort((a, b) => a.localeCompare(b));\n\t\t\tfor (const name of sortedKeys) if (name === "set-cookie") for (const value of this.getSetCookie()) yield [name, value];\n\t\t\telse yield [name, this.get(name)];\n\t\t}\n\t\t/**\n\t\t* Returns a boolean stating whether a `Headers` object contains a certain header.\n\t\t*/\n\t\thas(name) {\n\t\t\tif (!isValidHeaderName(name)) throw new TypeError(`Invalid header name "${name}"`);\n\t\t\treturn this[NORMALIZED_HEADERS].hasOwnProperty(normalizeHeaderName(name));\n\t\t}\n\t\t/**\n\t\t* Returns a `ByteString` sequence of all the values of a header with a given name.\n\t\t*/\n\t\tget(name) {\n\t\t\tif (!isValidHeaderName(name)) throw TypeError(`Invalid header name "${name}"`);\n\t\t\treturn this[NORMALIZED_HEADERS][normalizeHeaderName(name)] ?? null;\n\t\t}\n\t\t/**\n\t\t* Sets a new value for an existing header inside a `Headers` object, or adds the header if it does not already exist.\n\t\t*/\n\t\tset(name, value) {\n\t\t\tif (!isValidHeaderName(name) || !isValidHeaderValue(value)) return;\n\t\t\tconst normalizedName = normalizeHeaderName(name);\n\t\t\tconst normalizedValue = normalizeHeaderValue(value);\n\t\t\tthis[NORMALIZED_HEADERS][normalizedName] = normalizeHeaderValue(normalizedValue);\n\t\t\tthis[RAW_HEADER_NAMES].set(normalizedName, name);\n\t\t}\n\t\t/**\n\t\t* Appends a new value onto an existing header inside a `Headers` object, or adds the header if it does not already exist.\n\t\t*/\n\t\tappend(name, value) {\n\t\t\tif (!isValidHeaderName(name) || !isValidHeaderValue(value)) return;\n\t\t\tconst normalizedName = normalizeHeaderName(name);\n\t\t\tconst normalizedValue = normalizeHeaderValue(value);\n\t\t\tlet resolvedValue = this.has(normalizedName) ? `${this.get(normalizedName)}, ${normalizedValue}` : normalizedValue;\n\t\t\tthis.set(name, resolvedValue);\n\t\t}\n\t\t/**\n\t\t* Deletes a header from the `Headers` object.\n\t\t*/\n\t\tdelete(name) {\n\t\t\tif (!isValidHeaderName(name)) return;\n\t\t\tif (!this.has(name)) return;\n\t\t\tconst normalizedName = normalizeHeaderName(name);\n\t\t\tdelete this[NORMALIZED_HEADERS][normalizedName];\n\t\t\tthis[RAW_HEADER_NAMES].delete(normalizedName);\n\t\t}\n\t\t/**\n\t\t* Traverses the `Headers` object,\n\t\t* calling the given callback for each header.\n\t\t*/\n\t\tforEach(callback, thisArg) {\n\t\t\tfor (const [name, value] of this.entries()) callback.call(thisArg, value, name, this);\n\t\t}\n\t\t/**\n\t\t* Returns an array containing the values\n\t\t* of all Set-Cookie headers associated\n\t\t* with a response\n\t\t*/\n\t\tgetSetCookie() {\n\t\t\tconst setCookieHeader = this.get("set-cookie");\n\t\t\tif (setCookieHeader === null) return [];\n\t\t\tif (setCookieHeader === "") return [""];\n\t\t\treturn splitCookiesString(setCookieHeader);\n\t\t}\n\t};\n\t//#endregion\n\t//#region packages/core/src/code-mode/platform-entry.ts\n\tconst platformBridgeGlobalThis = globalThis;\n\tconst capletsPlatformHost = {\n\t\trandomUUID: platformBridgeGlobalThis.__caplets_platform_random_uuid,\n\t\trandomValues: platformBridgeGlobalThis.__caplets_platform_random_values,\n\t\tsleep: platformBridgeGlobalThis.__caplets_platform_sleep,\n\t\tclearTimer: platformBridgeGlobalThis.__caplets_platform_clear_timer\n\t};\n\tdelete platformBridgeGlobalThis.__caplets_platform_random_uuid;\n\tdelete platformBridgeGlobalThis.__caplets_platform_random_values;\n\tdelete platformBridgeGlobalThis.__caplets_platform_sleep;\n\tdelete platformBridgeGlobalThis.__caplets_platform_clear_timer;\n\tconst DISABLED_FETCH_MESSAGE = "Direct fetch is not available in Code Mode; use a Caplet instead.";\n\tconst BASE64_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";\n\tconst base64Lookup = new Map(BASE64_ALPHABET.split("").map((char, index) => [char, index]));\n\tfunction utf8Encode(input) {\n\t\tconst encoded = encodeURIComponent(input);\n\t\tconst bytes = [];\n\t\tfor (let index = 0; index < encoded.length; index += 1) {\n\t\t\tif (encoded[index] === "%") {\n\t\t\t\tbytes.push(Number.parseInt(encoded.slice(index + 1, index + 3), 16));\n\t\t\t\tindex += 2;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tbytes.push(encoded.charCodeAt(index));\n\t\t}\n\t\treturn Uint8Array.from(bytes);\n\t}\n\tfunction utf8Decode(input) {\n\t\tlet output = "";\n\t\tfor (let index = 0; index < input.length; index += 1) {\n\t\t\tconst first = input[index] ?? 0;\n\t\t\tif (first < 128) {\n\t\t\t\toutput += String.fromCharCode(first);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (first >= 194 && first <= 223) {\n\t\t\t\tconst second = input[index + 1];\n\t\t\t\tif (isUtf8Continuation(second)) {\n\t\t\t\t\toutput += String.fromCodePoint((first & 31) << 6 | second & 63);\n\t\t\t\t\tindex += 1;\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\toutput += "�";\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (first >= 224 && first <= 239) {\n\t\t\t\tconst second = input[index + 1];\n\t\t\t\tconst third = input[index + 2];\n\t\t\t\tif (isValidUtf8SecondForThreeByte(first, second) && isUtf8Continuation(third)) {\n\t\t\t\t\toutput += String.fromCodePoint((first & 15) << 12 | (second & 63) << 6 | third & 63);\n\t\t\t\t\tindex += 2;\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\toutput += "�";\n\t\t\t\tif (isValidUtf8SecondForThreeByte(first, second)) index += 1;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (first >= 240 && first <= 244) {\n\t\t\t\tconst second = input[index + 1];\n\t\t\t\tconst third = input[index + 2];\n\t\t\t\tconst fourth = input[index + 3];\n\t\t\t\tif (isValidUtf8SecondForFourByte(first, second) && isUtf8Continuation(third) && isUtf8Continuation(fourth)) {\n\t\t\t\t\toutput += String.fromCodePoint((first & 7) << 18 | (second & 63) << 12 | (third & 63) << 6 | fourth & 63);\n\t\t\t\t\tindex += 3;\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\toutput += "�";\n\t\t\t\tif (isValidUtf8SecondForFourByte(first, second)) index += isUtf8Continuation(third) ? 2 : 1;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\toutput += "�";\n\t\t}\n\t\treturn output;\n\t}\n\tfunction isUtf8Continuation(value) {\n\t\treturn value !== void 0 && value >= 128 && value <= 191;\n\t}\n\tfunction isValidUtf8SecondForThreeByte(first, second) {\n\t\tif (second === void 0) return false;\n\t\tif (first === 224) return second >= 160 && second <= 191;\n\t\tif (first === 237) return second >= 128 && second <= 159;\n\t\treturn second >= 128 && second <= 191;\n\t}\n\tfunction isValidUtf8SecondForFourByte(first, second) {\n\t\tif (second === void 0) return false;\n\t\tif (first === 240) return second >= 144 && second <= 191;\n\t\tif (first === 244) return second >= 128 && second <= 143;\n\t\treturn second >= 128 && second <= 191;\n\t}\n\tvar TextEncoderShim = class {\n\t\tencoding = "utf-8";\n\t\tencode(input = "") {\n\t\t\treturn utf8Encode(String(input));\n\t\t}\n\t};\n\tvar TextDecoderShim = class {\n\t\tencoding;\n\t\tconstructor(label = "utf-8") {\n\t\t\tconst normalized = label.toLowerCase();\n\t\t\tif (![\n\t\t\t\t"utf-8",\n\t\t\t\t"utf8",\n\t\t\t\t"unicode-1-1-utf-8"\n\t\t\t].includes(normalized)) throw new TypeError(`Unsupported encoding: ${label}`);\n\t\t\tthis.encoding = "utf-8";\n\t\t}\n\t\tdecode(input) {\n\t\t\tif (input === void 0) return "";\n\t\t\treturn utf8Decode(copyBytes(input));\n\t\t}\n\t};\n\tconst textEncoder = new TextEncoderShim();\n\tconst textDecoder = new TextDecoderShim();\n\tvar URLSearchParamsShim = class URLSearchParamsShim {\n\t\t#entries = [];\n\t\t#onChange;\n\t\tconstructor(init = "", onChange) {\n\t\t\tthis.#onChange = onChange;\n\t\t\tif (typeof init === "string") {\n\t\t\t\tconst source = init.startsWith("?") ? init.slice(1) : init;\n\t\t\t\tif (!source) return;\n\t\t\t\tfor (const pair of source.split("&")) {\n\t\t\t\t\tif (!pair) continue;\n\t\t\t\t\tconst separatorIndex = pair.indexOf("=");\n\t\t\t\t\tconst key = separatorIndex >= 0 ? pair.slice(0, separatorIndex) : pair;\n\t\t\t\t\tconst value = separatorIndex >= 0 ? pair.slice(separatorIndex + 1) : "";\n\t\t\t\t\tthis.#entries.push([decodeUrlParam(key), decodeUrlParam(value)]);\n\t\t\t\t}\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (init instanceof URLSearchParamsShim) {\n\t\t\t\tthis.#entries = [...init.#entries];\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (Array.isArray(init)) {\n\t\t\t\tthis.#entries = init.map(([key, value]) => [String(key), String(value)]);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tthis.#entries = Object.entries(init).map(([key, value]) => [key, String(value)]);\n\t\t}\n\t\tappend(name, value) {\n\t\t\tthis.#entries.push([String(name), String(value)]);\n\t\t\tthis.#onChange?.();\n\t\t}\n\t\tdelete(name) {\n\t\t\tconst normalized = String(name);\n\t\t\tthis.#entries = this.#entries.filter(([key]) => key !== normalized);\n\t\t\tthis.#onChange?.();\n\t\t}\n\t\tentries() {\n\t\t\treturn this.#entries[Symbol.iterator]();\n\t\t}\n\t\tforEach(callback) {\n\t\t\tfor (const [key, value] of this.#entries) callback(value, key, this);\n\t\t}\n\t\tget(name) {\n\t\t\treturn this.#entries.find(([key]) => key === String(name))?.[1] ?? null;\n\t\t}\n\t\tgetAll(name) {\n\t\t\treturn this.#entries.filter(([key]) => key === String(name)).map(([, value]) => value);\n\t\t}\n\t\thas(name) {\n\t\t\treturn this.#entries.some(([key]) => key === String(name));\n\t\t}\n\t\tkeys() {\n\t\t\treturn this.#entries.map(([key]) => key)[Symbol.iterator]();\n\t\t}\n\t\tset(name, value) {\n\t\t\tconst normalizedName = String(name);\n\t\t\tconst normalizedValue = String(value);\n\t\t\tconst next = [];\n\t\t\tlet replaced = false;\n\t\t\tfor (const entry of this.#entries) {\n\t\t\t\tif (entry[0] !== normalizedName) {\n\t\t\t\t\tnext.push(entry);\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tif (!replaced) {\n\t\t\t\t\tnext.push([normalizedName, normalizedValue]);\n\t\t\t\t\treplaced = true;\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (!replaced) next.push([normalizedName, normalizedValue]);\n\t\t\tthis.#entries = next;\n\t\t\tthis.#onChange?.();\n\t\t}\n\t\ttoString() {\n\t\t\treturn this.#entries.map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`).join("&");\n\t\t}\n\t\tvalues() {\n\t\t\treturn this.#entries.map(([, value]) => value)[Symbol.iterator]();\n\t\t}\n\t\t[Symbol.iterator]() {\n\t\t\treturn this.entries();\n\t\t}\n\t};\n\tfunction decodeUrlParam(value) {\n\t\treturn decodeURIComponent(value.replace(/\\+/gu, " "));\n\t}\n\tfunction parseAbsoluteUrl(input) {\n\t\tconst match = /^(?[a-zA-Z][a-zA-Z\\d+.-]*:)\\/\\/(?[^/?#]*)(?[^?#]*)?(?\\?[^#]*)?(?#.*)?$/u.exec(input);\n\t\tif (!match?.groups?.protocol || !match.groups.host) return;\n\t\treturn {\n\t\t\tprotocol: match.groups.protocol,\n\t\t\thost: match.groups.host,\n\t\t\tpathname: match.groups.pathname || "/",\n\t\t\tsearch: match.groups.search || "",\n\t\t\thash: match.groups.hash || ""\n\t\t};\n\t}\n\tfunction normalizePathname(pathname) {\n\t\tconst segments = [];\n\t\tfor (const part of pathname.split("/")) {\n\t\t\tif (!part || part === ".") continue;\n\t\t\tif (part === "..") {\n\t\t\t\tsegments.pop();\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tsegments.push(part);\n\t\t}\n\t\treturn `/${segments.join("/")}`;\n\t}\n\tfunction resolveUrl(input, base) {\n\t\tconst absolute = parseAbsoluteUrl(input);\n\t\tif (absolute) return absolute;\n\t\tif (base === void 0) throw new TypeError(`Invalid URL: ${input}`);\n\t\tconst baseUrl = base instanceof URLShim ? base : new URLShim(String(base));\n\t\tconst basePath = baseUrl.pathname.endsWith("/") ? baseUrl.pathname : baseUrl.pathname.slice(0, baseUrl.pathname.lastIndexOf("/") + 1);\n\t\tconst [beforeHash, rawHash = ""] = input.split("#");\n\t\tconst hasExplicitSearch = beforeHash?.includes("?") ?? false;\n\t\tconst [rawPath, rawSearch = ""] = (beforeHash ?? "").split("?");\n\t\tconst pathname = rawPath === "" && (input === "" || input.startsWith("?") || input.startsWith("#")) ? baseUrl.pathname : rawPath?.startsWith("/") ? rawPath : `${basePath}${rawPath || ""}`;\n\t\treturn {\n\t\t\tprotocol: baseUrl.protocol,\n\t\t\thost: baseUrl.host,\n\t\t\tpathname: normalizePathname(pathname),\n\t\t\tsearch: hasExplicitSearch ? `?${rawSearch}` : rawPath === "" && (input === "" || input.startsWith("#")) ? baseUrl.search : "",\n\t\t\thash: rawHash ? `#${rawHash}` : ""\n\t\t};\n\t}\n\tvar URLShim = class {\n\t\thost;\n\t\thostname;\n\t\torigin;\n\t\tpassword = "";\n\t\tport;\n\t\tprotocol;\n\t\tsearchParams;\n\t\tusername = "";\n\t\thash;\n\t\tpathname;\n\t\t#search;\n\t\tconstructor(input, base) {\n\t\t\tconst parsed = resolveUrl(String(input), base);\n\t\t\tthis.protocol = parsed.protocol;\n\t\t\tthis.host = parsed.host;\n\t\t\tconst portIndex = this.host.lastIndexOf(":");\n\t\t\tthis.hostname = portIndex >= 0 ? this.host.slice(0, portIndex) : this.host;\n\t\t\tthis.port = portIndex >= 0 ? this.host.slice(portIndex + 1) : "";\n\t\t\tthis.pathname = parsed.pathname;\n\t\t\tthis.#search = parsed.search;\n\t\t\tthis.hash = parsed.hash;\n\t\t\tthis.origin = `${this.protocol}//${this.host}`;\n\t\t\tthis.searchParams = new URLSearchParamsShim(this.#search, () => {\n\t\t\t\tconst next = this.searchParams.toString();\n\t\t\t\tthis.#search = next ? `?${next}` : "";\n\t\t\t});\n\t\t}\n\t\tget href() {\n\t\t\treturn `${this.origin}${this.pathname}${this.#search}${this.hash}`;\n\t\t}\n\t\tget search() {\n\t\t\treturn this.#search;\n\t\t}\n\t\ttoString() {\n\t\t\treturn this.href;\n\t\t}\n\t\ttoJSON() {\n\t\t\treturn this.href;\n\t\t}\n\t};\n\tvar ReadableStreamShim = class {\n\t\t#queue = [];\n\t\t#closed = false;\n\t\t#pulling = false;\n\t\t#source;\n\t\t#controller;\n\t\t#pending;\n\t\tconstructor(source = {}) {\n\t\t\tthis.#source = source;\n\t\t\tthis.#controller = {\n\t\t\t\tenqueue: (value) => {\n\t\t\t\t\tif (this.#pending) {\n\t\t\t\t\t\tthis.#pending.resolve({\n\t\t\t\t\t\t\tdone: false,\n\t\t\t\t\t\t\tvalue\n\t\t\t\t\t\t});\n\t\t\t\t\t\tthis.#pending = void 0;\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tthis.#queue.push(value);\n\t\t\t\t},\n\t\t\t\tclose: () => {\n\t\t\t\t\tthis.#closed = true;\n\t\t\t\t\tif (this.#pending) {\n\t\t\t\t\t\tthis.#pending.resolve({ done: true });\n\t\t\t\t\t\tthis.#pending = void 0;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t};\n\t\t\tsource.start?.(this.#controller);\n\t\t}\n\t\tasync #pull() {\n\t\t\tif (this.#pulling || this.#closed || !this.#source.pull) return;\n\t\t\tthis.#pulling = true;\n\t\t\ttry {\n\t\t\t\tawait this.#source.pull(this.#controller);\n\t\t\t} finally {\n\t\t\t\tthis.#pulling = false;\n\t\t\t}\n\t\t}\n\t\tgetReader() {\n\t\t\treturn { read: async () => {\n\t\t\t\tif (this.#queue.length > 0) return {\n\t\t\t\t\tdone: false,\n\t\t\t\t\tvalue: this.#queue.shift()\n\t\t\t\t};\n\t\t\t\tawait this.#pull();\n\t\t\t\tif (this.#queue.length > 0) return {\n\t\t\t\t\tdone: false,\n\t\t\t\t\tvalue: this.#queue.shift()\n\t\t\t\t};\n\t\t\t\tif (this.#closed) return {\n\t\t\t\t\tdone: true,\n\t\t\t\t\tvalue: void 0\n\t\t\t\t};\n\t\t\t\treturn await new Promise((resolve) => {\n\t\t\t\t\tthis.#pending = { resolve };\n\t\t\t\t\tthis.#pull();\n\t\t\t\t});\n\t\t\t} };\n\t\t}\n\t};\n\tvar WritableStreamShim = class {\n\t\t#sink;\n\t\tconstructor(sink = {}) {\n\t\t\tthis.#sink = sink;\n\t\t}\n\t\tgetWriter() {\n\t\t\treturn {\n\t\t\t\twrite: async (chunk) => {\n\t\t\t\t\tawait this.#sink.write?.(chunk);\n\t\t\t\t},\n\t\t\t\tclose: async () => {\n\t\t\t\t\tawait this.#sink.close?.();\n\t\t\t\t}\n\t\t\t};\n\t\t}\n\t};\n\tvar TransformStreamShim = class {\n\t\treadable;\n\t\twritable;\n\t\tconstructor(transformer = {}) {\n\t\t\tconst queue = [];\n\t\t\tlet closed = false;\n\t\t\tlet pending;\n\t\t\tconst controller = {\n\t\t\t\tenqueue: (value) => {\n\t\t\t\t\tif (pending) {\n\t\t\t\t\t\tpending.resolve({\n\t\t\t\t\t\t\tdone: false,\n\t\t\t\t\t\t\tvalue\n\t\t\t\t\t\t});\n\t\t\t\t\t\tpending = void 0;\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tqueue.push(value);\n\t\t\t\t},\n\t\t\t\tclose: () => {\n\t\t\t\t\tclosed = true;\n\t\t\t\t\tif (pending) {\n\t\t\t\t\t\tpending.resolve({ done: true });\n\t\t\t\t\t\tpending = void 0;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t};\n\t\t\tthis.readable = { getReader() {\n\t\t\t\treturn { read: async () => {\n\t\t\t\t\tif (queue.length > 0) return {\n\t\t\t\t\t\tdone: false,\n\t\t\t\t\t\tvalue: queue.shift()\n\t\t\t\t\t};\n\t\t\t\t\tif (closed) return {\n\t\t\t\t\t\tdone: true,\n\t\t\t\t\t\tvalue: void 0\n\t\t\t\t\t};\n\t\t\t\t\treturn await new Promise((resolve) => {\n\t\t\t\t\t\tpending = { resolve };\n\t\t\t\t\t});\n\t\t\t\t} };\n\t\t\t} };\n\t\t\tthis.writable = new WritableStreamShim({\n\t\t\t\twrite: async (chunk) => {\n\t\t\t\t\ttransformer.transform?.(chunk, controller);\n\t\t\t\t},\n\t\t\t\tclose: async () => {\n\t\t\t\t\ttransformer.flush?.(controller);\n\t\t\t\t\tcontroller.close();\n\t\t\t\t}\n\t\t\t});\n\t\t}\n\t};\n\tfunction definePlatformGlobal(name, value, options = {}) {\n\t\tif (!options.overwrite && name in globalThis) return;\n\t\tObject.defineProperty(globalThis, name, {\n\t\t\tvalue,\n\t\t\twritable: true,\n\t\t\tconfigurable: true\n\t\t});\n\t}\n\tfunction formatLogArg(value) {\n\t\tif (typeof value === "string") return value;\n\t\ttry {\n\t\t\treturn JSON.stringify(value);\n\t\t} catch {\n\t\t\treturn String(value);\n\t\t}\n\t}\n\tfunction formatLogLine(args) {\n\t\treturn args.map(formatLogArg).join(" ");\n\t}\n\tconst platformConsole = {\n\t\tlog: (...args) => __caplets_log("log", formatLogLine(args)),\n\t\tinfo: (...args) => __caplets_log("info", formatLogLine(args)),\n\t\twarn: (...args) => __caplets_log("warn", formatLogLine(args)),\n\t\terror: (...args) => __caplets_log("error", formatLogLine(args)),\n\t\tdebug: (...args) => __caplets_log("debug", formatLogLine(args))\n\t};\n\tfunction normalizeEncoding(encoding) {\n\t\tconst normalized = (encoding ?? "utf8").toLowerCase();\n\t\tswitch (normalized) {\n\t\t\tcase "utf8":\n\t\t\tcase "utf-8":\n\t\t\tcase "base64":\n\t\t\tcase "base64url":\n\t\t\tcase "hex": return normalized;\n\t\t\tdefault: throw new TypeError(`Unsupported Buffer encoding: ${encoding}`);\n\t\t}\n\t}\n\tfunction hexToBytes(value) {\n\t\tif (value.length % 2 !== 0) throw new TypeError("Invalid hex string length");\n\t\tconst bytes = new Uint8Array(value.length / 2);\n\t\tfor (let index = 0; index < value.length; index += 2) {\n\t\t\tconst parsed = Number.parseInt(value.slice(index, index + 2), 16);\n\t\t\tif (Number.isNaN(parsed)) throw new TypeError("Invalid hex string");\n\t\t\tbytes[index / 2] = parsed;\n\t\t}\n\t\treturn bytes;\n\t}\n\tfunction bytesToHex(bytes) {\n\t\treturn Array.from(bytes, (value) => value.toString(16).padStart(2, "0")).join("");\n\t}\n\tfunction base64ToBytes(value, encoding) {\n\t\tlet normalized = value.replace(/\\s+/gu, "");\n\t\tif (encoding === "base64url") normalized = normalized.replace(/-/gu, "+").replace(/_/gu, "/");\n\t\tconst padding = normalized.length % 4;\n\t\tif (padding === 1) throw new TypeError("Invalid base64 string");\n\t\tif (padding > 0) normalized = normalized.padEnd(normalized.length + (4 - padding), "=");\n\t\tconst output = [];\n\t\tfor (let index = 0; index < normalized.length; index += 4) {\n\t\t\tconst values = normalized.slice(index, index + 4).split("").map((char) => char === "=" ? 64 : base64Lookup.get(char) ?? NaN);\n\t\t\tif (values.some((entry) => Number.isNaN(entry))) throw new TypeError("Invalid base64 string");\n\t\t\tconst [a = 0, b = 0, c = 64, d = 64] = values;\n\t\t\tconst triple = a << 18 | b << 12 | (c & 63) << 6 | d & 63;\n\t\t\toutput.push(triple >> 16 & 255);\n\t\t\tif (c !== 64) output.push(triple >> 8 & 255);\n\t\t\tif (d !== 64) output.push(triple & 255);\n\t\t}\n\t\treturn Uint8Array.from(output);\n\t}\n\tfunction bytesToBase64(bytes, encoding) {\n\t\tlet output = "";\n\t\tfor (let index = 0; index < bytes.length; index += 3) {\n\t\t\tconst a = bytes[index] ?? 0;\n\t\t\tconst b = bytes[index + 1] ?? 0;\n\t\t\tconst c = bytes[index + 2] ?? 0;\n\t\t\tconst triple = a << 16 | b << 8 | c;\n\t\t\toutput += BASE64_ALPHABET[triple >> 18 & 63];\n\t\t\toutput += BASE64_ALPHABET[triple >> 12 & 63];\n\t\t\toutput += index + 1 < bytes.length ? BASE64_ALPHABET[triple >> 6 & 63] : "=";\n\t\t\toutput += index + 2 < bytes.length ? BASE64_ALPHABET[triple & 63] : "=";\n\t\t}\n\t\tif (encoding === "base64url") return output.replace(/\\+/gu, "-").replace(/\\//gu, "_").replace(/=+$/gu, "");\n\t\treturn output;\n\t}\n\tfunction copyBytes(input) {\n\t\tif (input instanceof ArrayBuffer) return new Uint8Array(input.slice(0));\n\t\treturn new Uint8Array(input.buffer.slice(input.byteOffset, input.byteOffset + input.byteLength));\n\t}\n\tfunction toBytes(input, encoding) {\n\t\tif (input instanceof BufferShim) return input.toUint8Array();\n\t\tif (typeof input === "string") switch (normalizeEncoding(encoding)) {\n\t\t\tcase "utf8":\n\t\t\tcase "utf-8": return textEncoder.encode(input);\n\t\t\tcase "base64":\n\t\t\tcase "base64url": return base64ToBytes(input, normalizeEncoding(encoding));\n\t\t\tcase "hex": return hexToBytes(input);\n\t\t}\n\t\tif (input instanceof ArrayBuffer || ArrayBuffer.isView(input)) return copyBytes(input);\n\t\tif (Array.isArray(input)) return Uint8Array.from(input);\n\t\tthrow new TypeError("Buffer.from only supports strings, arrays, ArrayBuffers, and typed arrays");\n\t}\n\tvar BufferShim = class BufferShim {\n\t\t#bytes;\n\t\tbyteLength;\n\t\tlength;\n\t\tconstructor(bytes) {\n\t\t\tthis.#bytes = bytes;\n\t\t\tthis.byteLength = bytes.byteLength;\n\t\t\tthis.length = bytes.length;\n\t\t}\n\t\tstatic from(input, encoding) {\n\t\t\treturn new BufferShim(toBytes(input, encoding));\n\t\t}\n\t\tstatic isBuffer(value) {\n\t\t\treturn value instanceof BufferShim;\n\t\t}\n\t\tstatic byteLength(input, encoding) {\n\t\t\treturn toBytes(input, encoding).byteLength;\n\t\t}\n\t\ttoUint8Array() {\n\t\t\treturn new Uint8Array(this.#bytes);\n\t\t}\n\t\ttoString(encoding) {\n\t\t\tswitch (normalizeEncoding(encoding)) {\n\t\t\t\tcase "utf8":\n\t\t\t\tcase "utf-8": return textDecoder.decode(this.#bytes);\n\t\t\t\tcase "base64":\n\t\t\t\tcase "base64url": return bytesToBase64(this.#bytes, normalizeEncoding(encoding));\n\t\t\t\tcase "hex": return bytesToHex(this.#bytes);\n\t\t\t}\n\t\t}\n\t};\n\tfunction atobShim(input) {\n\t\treturn Array.from(base64ToBytes(String(input), "base64"), (value) => String.fromCharCode(value)).join("");\n\t}\n\tfunction btoaShim(input) {\n\t\tconst bytes = new Uint8Array(input.length);\n\t\tfor (let index = 0; index < input.length; index += 1) {\n\t\t\tconst codePoint = input.charCodeAt(index);\n\t\t\tif (codePoint > 255) throw new TypeError("The string to be encoded contains characters outside of Latin1");\n\t\t\tbytes[index] = codePoint;\n\t\t}\n\t\treturn bytesToBase64(bytes, "base64");\n\t}\n\tfunction queueMicrotaskShim(callback) {\n\t\tPromise.resolve().then(callback);\n\t}\n\tvar AbortSignalShim = class AbortSignalShim {\n\t\taborted = false;\n\t\treason;\n\t\tonabort = null;\n\t\t#listeners = /* @__PURE__ */ new Set();\n\t\taddEventListener(type, listener) {\n\t\t\tif (type === "abort" && listener) this.#listeners.add(listener);\n\t\t}\n\t\tremoveEventListener(type, listener) {\n\t\t\tif (type === "abort" && listener) this.#listeners.delete(listener);\n\t\t}\n\t\tdispatchEvent(event) {\n\t\t\tif (event.type !== "abort") return true;\n\t\t\tthis.onabort?.(event);\n\t\t\tfor (const listener of this.#listeners) listener(event);\n\t\t\treturn true;\n\t\t}\n\t\tthrowIfAborted() {\n\t\t\tif (this.aborted) throw this.reason ?? /* @__PURE__ */ new Error("Operation was aborted");\n\t\t}\n\t\tstatic abort(reason) {\n\t\t\tconst signal = new AbortSignalShim();\n\t\t\tsignal.abort(reason);\n\t\t\treturn signal;\n\t\t}\n\t\tabort(reason) {\n\t\t\tif (this.aborted) return;\n\t\t\tthis.aborted = true;\n\t\t\tthis.reason = reason;\n\t\t\tthis.dispatchEvent({\n\t\t\t\ttype: "abort",\n\t\t\t\ttarget: this\n\t\t\t});\n\t\t}\n\t};\n\tvar AbortControllerShim = class {\n\t\tsignal = new AbortSignalShim();\n\t\tabort(reason) {\n\t\t\tthis.signal.abort(reason);\n\t\t}\n\t};\n\tfunction cloneFormData(input) {\n\t\tconst clone = new FormData();\n\t\tfor (const [name, value] of input.entries()) {\n\t\t\tif (value instanceof File) {\n\t\t\t\tclone.append(name, new File([value], value.name, { type: value.type }), value.name);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tclone.append(name, value);\n\t\t}\n\t\treturn clone;\n\t}\n\tfunction cloneBodyValue(input) {\n\t\tif (input === null || input === void 0 || typeof input === "string") return input;\n\t\tif (input instanceof FormData) return cloneFormData(input);\n\t\tif (input instanceof Blob) return input.slice(0, input.size, input.type);\n\t\tif (input instanceof ArrayBuffer || ArrayBuffer.isView(input)) return copyBytes(input);\n\t\treturn input;\n\t}\n\tfunction blobFromBody(input) {\n\t\tif (input instanceof Blob) return input;\n\t\tif (typeof input === "string") return new Blob([input], { type: "text/plain;charset=utf-8" });\n\t\tif (input instanceof FormData) throw new TypeError("FormData body reading is not supported in Code Mode");\n\t\tif (input instanceof ArrayBuffer || ArrayBuffer.isView(input)) return new Blob([copyBytes(input)]);\n\t\treturn new Blob([]);\n\t}\n\tvar BodyMixin = class {\n\t\t_bodyInit;\n\t\tbodyUsed = false;\n\t\tbody = null;\n\t\tconstructor(body) {\n\t\t\tthis._bodyInit = cloneBodyValue(body);\n\t\t}\n\t\tasync arrayBuffer() {\n\t\t\tthis.bodyUsed = true;\n\t\t\treturn await blobFromBody(this._bodyInit).arrayBuffer();\n\t\t}\n\t\tasync blob() {\n\t\t\tthis.bodyUsed = true;\n\t\t\treturn blobFromBody(this._bodyInit);\n\t\t}\n\t\tasync formData() {\n\t\t\tthis.bodyUsed = true;\n\t\t\tif (this._bodyInit instanceof FormData) return cloneFormData(this._bodyInit);\n\t\t\tthrow new TypeError("Body does not contain FormData");\n\t\t}\n\t\tasync json() {\n\t\t\treturn JSON.parse(await this.text());\n\t\t}\n\t\tasync text() {\n\t\t\tthis.bodyUsed = true;\n\t\t\treturn await blobFromBody(this._bodyInit).text();\n\t\t}\n\t};\n\tfunction toHeaders(init) {\n\t\treturn init instanceof Headers ? new Headers(init) : new Headers(init ?? {});\n\t}\n\tfunction normalizeMethod(method) {\n\t\treturn String(method ?? "GET").toUpperCase();\n\t}\n\tvar RequestShim = class RequestShim extends BodyMixin {\n\t\theaders;\n\t\tmethod;\n\t\tsignal;\n\t\turl;\n\t\tconstructor(input, init = {}) {\n\t\t\tconst sourceBody = input instanceof RequestShim ? input._bodyInit : void 0;\n\t\t\tsuper(init.body ?? sourceBody);\n\t\t\tif (input instanceof RequestShim) this.url = input.url;\n\t\t\telse this.url = input instanceof URLShim ? input.href : new URLShim(String(input)).href;\n\t\t\tthis.method = normalizeMethod(init.method ?? (input instanceof RequestShim ? input.method : "GET"));\n\t\t\tthis.headers = toHeaders(init.headers ?? (input instanceof RequestShim ? input.headers : void 0));\n\t\t\tthis.signal = init.signal ?? (input instanceof RequestShim ? input.signal : new AbortControllerShim().signal);\n\t\t}\n\t\tclone() {\n\t\t\treturn new RequestShim(this, {\n\t\t\t\tmethod: this.method,\n\t\t\t\theaders: this.headers,\n\t\t\t\tbody: this._bodyInit,\n\t\t\t\tsignal: this.signal\n\t\t\t});\n\t\t}\n\t};\n\tvar ResponseShim = class ResponseShim extends BodyMixin {\n\t\theaders;\n\t\tok;\n\t\tredirected = false;\n\t\tstatus;\n\t\tstatusText;\n\t\ttype = "default";\n\t\turl = "";\n\t\tconstructor(body, init = {}) {\n\t\t\tsuper(body);\n\t\t\tthis.status = init.status ?? 200;\n\t\t\tthis.statusText = init.statusText ?? "";\n\t\t\tthis.headers = toHeaders(init.headers);\n\t\t\tthis.ok = this.status >= 200 && this.status <= 299;\n\t\t}\n\t\tclone() {\n\t\t\treturn new ResponseShim(this._bodyInit, {\n\t\t\t\theaders: this.headers,\n\t\t\t\tstatus: this.status,\n\t\t\t\tstatusText: this.statusText\n\t\t\t});\n\t\t}\n\t\tstatic json(data, init) {\n\t\t\tconst headers = toHeaders(init?.headers);\n\t\t\tif (!headers.has("content-type")) headers.set("content-type", "application/json");\n\t\t\tconst responseInit = { headers };\n\t\t\tif (init?.status !== void 0) responseInit.status = init.status;\n\t\t\tif (init?.statusText !== void 0) responseInit.statusText = init.statusText;\n\t\t\treturn new ResponseShim(JSON.stringify(data), responseInit);\n\t\t}\n\t};\n\tfunction disabledFetch() {\n\t\tthrow new Error(DISABLED_FETCH_MESSAGE);\n\t}\n\tconst integerTypedArrayConstructors = new Set([\n\t\tInt8Array,\n\t\tUint8Array,\n\t\tUint8ClampedArray,\n\t\tInt16Array,\n\t\tUint16Array,\n\t\tInt32Array,\n\t\tUint32Array,\n\t\ttypeof BigInt64Array === "undefined" ? void 0 : BigInt64Array,\n\t\ttypeof BigUint64Array === "undefined" ? void 0 : BigUint64Array\n\t]);\n\tvar QuotaExceededErrorShim = class extends Error {\n\t\tconstructor(message) {\n\t\t\tsuper(message);\n\t\t\tthis.name = "QuotaExceededError";\n\t\t}\n\t};\n\tconst platformCrypto = {\n\t\trandomUUID() {\n\t\t\tconst randomUUID = capletsPlatformHost.randomUUID;\n\t\t\tif (!randomUUID) throw new Error("Code Mode platform random UUID bridge is not installed");\n\t\t\treturn randomUUID();\n\t\t},\n\t\tgetRandomValues(typedArray) {\n\t\t\tif (!ArrayBuffer.isView(typedArray) || typedArray instanceof DataView) throw new TypeError("crypto.getRandomValues requires an integer typed array");\n\t\t\tif (!integerTypedArrayConstructors.has(typedArray.constructor)) throw new TypeError("crypto.getRandomValues requires an integer typed array");\n\t\t\tif (typedArray.byteLength > 65536) throw new QuotaExceededErrorShim("crypto.getRandomValues cannot generate more than 65,536 bytes");\n\t\t\tconst randomValues = capletsPlatformHost.randomValues;\n\t\t\tif (!randomValues) throw new Error("Code Mode platform random values bridge is not installed");\n\t\t\tconst bytes = randomValues(typedArray.byteLength);\n\t\t\tnew Uint8Array(typedArray.buffer, typedArray.byteOffset, typedArray.byteLength).set(bytes);\n\t\t\treturn typedArray;\n\t\t}\n\t};\n\tlet nextTimerId = 1;\n\tconst activeTimers = /* @__PURE__ */ new Set();\n\tfunction normalizeTimerDelay(delay) {\n\t\tconst value = Number(delay ?? 0);\n\t\tif (!Number.isFinite(value) || value <= 0) return 0;\n\t\treturn Math.trunc(value);\n\t}\n\tfunction setTimeoutShim(callback, delay, ...args) {\n\t\tif (typeof callback !== "function") throw new TypeError("setTimeout callback must be a function");\n\t\tconst timerId = nextTimerId++;\n\t\tactiveTimers.add(timerId);\n\t\tconst sleep = capletsPlatformHost.sleep;\n\t\tif (!sleep) throw new Error("Code Mode platform sleep bridge is not installed");\n\t\tsleep(timerId, normalizeTimerDelay(delay)).then((fired) => {\n\t\t\tactiveTimers.delete(timerId);\n\t\t\tif (fired) callback(...args);\n\t\t});\n\t\treturn timerId;\n\t}\n\tfunction clearTimeoutShim(timerId) {\n\t\tconst id = Number(timerId);\n\t\tactiveTimers.delete(id);\n\t\tcapletsPlatformHost.clearTimer?.(id);\n\t}\n\tfunction setIntervalShim(callback, delay, ...args) {\n\t\tif (typeof callback !== "function") throw new TypeError("setInterval callback must be a function");\n\t\tconst timerId = nextTimerId++;\n\t\tconst intervalDelay = normalizeTimerDelay(delay);\n\t\tactiveTimers.add(timerId);\n\t\tconst tick = () => {\n\t\t\tconst sleep = capletsPlatformHost.sleep;\n\t\t\tif (!sleep) throw new Error("Code Mode platform sleep bridge is not installed");\n\t\t\tsleep(timerId, intervalDelay).then((fired) => {\n\t\t\t\tif (!fired || !activeTimers.has(timerId)) {\n\t\t\t\t\tactiveTimers.delete(timerId);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tcallback(...args);\n\t\t\t\tif (activeTimers.has(timerId)) tick();\n\t\t\t});\n\t\t};\n\t\ttick();\n\t\treturn timerId;\n\t}\n\tfunction clearIntervalShim(timerId) {\n\t\tclearTimeoutShim(timerId);\n\t}\n\tdefinePlatformGlobal("atob", atobShim);\n\tdefinePlatformGlobal("btoa", btoaShim);\n\tdefinePlatformGlobal("Buffer", BufferShim);\n\tdefinePlatformGlobal("TextEncoder", TextEncoderShim);\n\tdefinePlatformGlobal("TextDecoder", TextDecoderShim);\n\tdefinePlatformGlobal("URL", URLShim);\n\tdefinePlatformGlobal("URLSearchParams", URLSearchParamsShim);\n\tdefinePlatformGlobal("structuredClone", esm_default);\n\tdefinePlatformGlobal("Headers", Headers);\n\tdefinePlatformGlobal("Blob", Blob);\n\tdefinePlatformGlobal("File", File);\n\tdefinePlatformGlobal("FormData", FormData);\n\tdefinePlatformGlobal("ReadableStream", ReadableStreamShim);\n\tdefinePlatformGlobal("WritableStream", WritableStreamShim);\n\tdefinePlatformGlobal("TransformStream", TransformStreamShim);\n\tdefinePlatformGlobal("AbortController", AbortControllerShim);\n\tdefinePlatformGlobal("AbortSignal", AbortSignalShim);\n\tdefinePlatformGlobal("Request", RequestShim);\n\tdefinePlatformGlobal("Response", ResponseShim);\n\tdefinePlatformGlobal("crypto", platformCrypto);\n\tdefinePlatformGlobal("setTimeout", setTimeoutShim);\n\tdefinePlatformGlobal("clearTimeout", clearTimeoutShim);\n\tdefinePlatformGlobal("setInterval", setIntervalShim);\n\tdefinePlatformGlobal("clearInterval", clearIntervalShim);\n\tdefinePlatformGlobal("queueMicrotask", queueMicrotaskShim);\n\tdefinePlatformGlobal("console", platformConsole);\n\tdefinePlatformGlobal("fetch", disabledFetch, { overwrite: true });\n\t//#endregion\n})();' as const; diff --git a/packages/core/src/code-mode/sandbox.ts b/packages/core/src/code-mode/sandbox.ts index 0771edae..ed275b8c 100644 --- a/packages/core/src/code-mode/sandbox.ts +++ b/packages/core/src/code-mode/sandbox.ts @@ -7,6 +7,8 @@ import { type QuickJSRuntime, } from "quickjs-emscripten"; import ts from "typescript"; +import { installCodeModePlatformHost } from "./platform-host"; +import { CODE_MODE_PLATFORM_RUNTIME_SOURCE } from "./platform-runtime.generated"; import type { CodeModeLogEntry } from "./types"; export type CodeModeSandboxInvokeInput = { @@ -76,6 +78,7 @@ async function evaluateInQuickJs(input: CodeModeSandboxInput): Promise {", - " if (typeof value === 'string') return value;", - " try { return JSON.stringify(value); } catch { return String(value); }", - "};", - "const __formatLogLine = (args) => args.map(__formatLogArg).join(' ');", - "const console = {", - " log: (...args) => __caplets_log('log', __formatLogLine(args)),", - " info: (...args) => __caplets_log('info', __formatLogLine(args)),", - " warn: (...args) => __caplets_log('warn', __formatLogLine(args)),", - " error: (...args) => __caplets_log('error', __formatLogLine(args)),", - " debug: (...args) => __caplets_log('debug', __formatLogLine(args)),", - "};", - "const fetch = () => { throw new Error('fetch is disabled in Code Mode'); };", + CODE_MODE_PLATFORM_RUNTIME_SOURCE, "const __invoke = (capletId, method, args) => Promise.resolve(__caplets_invoke(capletId, method, args)).then(JSON.parse);", "const __handle = (capletId) => ({", " id: capletId,", diff --git a/packages/core/src/code-mode/static-analysis.ts b/packages/core/src/code-mode/static-analysis.ts index cdf88436..84d7b5d0 100644 --- a/packages/core/src/code-mode/static-analysis.ts +++ b/packages/core/src/code-mode/static-analysis.ts @@ -6,6 +6,11 @@ type AstNode = { [key: string]: unknown; }; +type AstParent = { + node: AstNode; + key: string; +}; + const PARSER_OPTIONS: ParserOptions = { sourceType: "module", errorRecovery: true, @@ -14,21 +19,24 @@ const PARSER_OPTIONS: ParserOptions = { }; export function hasDirectFetchCall(code: string): boolean { - return hasMatchingAstNode(code, (node) => isCallExpression(node) && isFetchCallee(node.callee)); + return hasMatchingAstNode(code, isDirectFetchCallNode); } export function hasExecutableImport(code: string): boolean { return hasMatchingAstNode(code, isExecutableImportNode); } -function hasMatchingAstNode(code: string, predicate: (node: AstNode) => boolean): boolean { +function hasMatchingAstNode( + code: string, + predicate: (node: AstNode, parent?: AstParent) => boolean, +): boolean { const ast = parseCode(code); if (!ast) return false; let found = false; - visitAst(ast, (node) => { + visitAst(ast, (node, parent) => { if (found) return; - found = predicate(node); + found = predicate(node, parent); }); return found; } @@ -58,13 +66,13 @@ function isCallExpression(node: AstNode): node is AstNode & { callee: unknown } ); } -function isFetchCallee(value: unknown): boolean { - if (!isNode(value)) return false; - if (value.type === "Identifier") return value.name === "fetch"; - if (value.type === "MemberExpression" || value.type === "OptionalMemberExpression") { - return isGlobalFetchMember(value); - } - return false; +function isDirectFetchCallNode(node: AstNode): boolean { + if (!isCallExpression(node) || !isNode(node.callee)) return false; + if (isIdentifierNamed(node.callee, "fetch")) return true; + return ( + (node.callee.type === "MemberExpression" || node.callee.type === "OptionalMemberExpression") && + isGlobalFetchMember(node.callee) + ); } function isGlobalFetchMember(node: AstNode): boolean { @@ -81,17 +89,21 @@ function isExportDeclaration(node: AstNode): boolean { ); } -function visitAst(value: unknown, visit: (node: AstNode) => void): void { +function visitAst( + value: unknown, + visit: (node: AstNode, parent?: AstParent) => void, + parent?: AstParent, +): void { if (!isNode(value)) return; - visit(value); + visit(value, parent); for (const [key, child] of Object.entries(value)) { if (key === "loc" || key === "start" || key === "end" || key === "extra") continue; if (Array.isArray(child)) { - for (const item of child) visitAst(item, visit); + for (const item of child) visitAst(item, visit, { node: value, key }); continue; } - visitAst(child, visit); + visitAst(child, visit, { node: value, key }); } } diff --git a/packages/core/src/config-runtime.ts b/packages/core/src/config-runtime.ts index 70bbfcff..6ef34d42 100644 --- a/packages/core/src/config-runtime.ts +++ b/packages/core/src/config-runtime.ts @@ -92,6 +92,18 @@ export type OpenApiEndpointConfig = CommonCapletConfig & { operationCacheTtlMs: number; }; +export type GoogleDiscoveryApiConfig = CommonCapletConfig & { + backend: "googleDiscovery"; + discoveryPath?: string | undefined; + discoveryUrl?: string | undefined; + baseUrl?: string | undefined; + includeOperations?: string[] | undefined; + excludeOperations?: string[] | undefined; + auth: OpenApiAuthConfig; + requestTimeoutMs: number; + operationCacheTtlMs: number; +}; + export type GraphQlOperationConfig = AgentSelectionHintsConfig & { document?: string | undefined; documentPath?: string | undefined; @@ -174,6 +186,7 @@ export type CapletSetConfig = CommonCapletConfig & { export type CapletConfig = | CapletServerConfig | OpenApiEndpointConfig + | GoogleDiscoveryApiConfig | GraphQlEndpointConfig | HttpApiConfig | CliToolsConfig @@ -196,6 +209,7 @@ export type CapletsConfig = { }; mcpServers: Record; openapiEndpoints: Record; + googleDiscoveryApis: Record; graphqlEndpoints: Record; httpApis: Record; cliTools: Record; @@ -321,6 +335,20 @@ const openApiEndpointSchema = z operationCacheTtlMs: z.number().int().nonnegative().default(30_000), }) .strict(); +const operationFilterSchema = z.array(z.string().trim().min(1).max(160)); +const googleDiscoveryApiSchema = z + .object({ + ...commonSchema, + discoveryPath: z.string().min(1).optional(), + discoveryUrl: z.string().min(1).optional(), + baseUrl: z.string().min(1).optional(), + includeOperations: operationFilterSchema.optional(), + excludeOperations: operationFilterSchema.optional(), + auth: authSchema, + requestTimeoutMs: z.number().int().positive().default(60_000), + operationCacheTtlMs: z.number().int().nonnegative().default(30_000), + }) + .strict(); const graphQlOperationSchema = z .object({ document: z.string().min(1).optional(), @@ -479,6 +507,9 @@ const configSchema = z openapiEndpoints: z .record(z.string().regex(SERVER_ID_PATTERN), openApiEndpointSchema) .default({}), + googleDiscoveryApis: z + .record(z.string().regex(SERVER_ID_PATTERN), googleDiscoveryApiSchema) + .default({}), graphqlEndpoints: z .record(z.string().regex(SERVER_ID_PATTERN), graphQlEndpointSchema) .default({}), @@ -523,6 +554,7 @@ export function parseConfig(input: unknown): CapletsConfig { }; }), openapiEndpoints: mapBackend(config.openapiEndpoints, "openapi"), + googleDiscoveryApis: mapBackend(config.googleDiscoveryApis, "googleDiscovery"), graphqlEndpoints: mapBackend(config.graphqlEndpoints, "graphql"), httpApis: mapBackend(config.httpApis, "http"), cliTools: mapBackend(config.cliTools, "cli"), @@ -608,6 +640,36 @@ function validateBackends(config: z.infer, ctx: z.Refinemen } validateAuthHeaders(raw.auth, ctx, ["openapiEndpoints", server, "auth"]); } + for (const [server, raw] of Object.entries(config.googleDiscoveryApis)) { + if (Boolean(raw.discoveryPath) === Boolean(raw.discoveryUrl)) { + ctx.addIssue({ + code: "custom", + path: ["googleDiscoveryApis", server], + message: "Google Discovery API must define exactly one discovery source", + }); + } + if ( + raw.discoveryUrl && + !hasEnvReference(raw.discoveryUrl) && + !isAllowedRemoteUrl(raw.discoveryUrl) + ) { + ctx.addIssue({ + code: "custom", + path: ["googleDiscoveryApis", server, "discoveryUrl"], + message: + "Google Discovery API discoveryUrl must use https except loopback development urls", + }); + } + if (raw.baseUrl && !hasEnvReference(raw.baseUrl) && !isAllowedHttpBaseUrl(raw.baseUrl)) { + ctx.addIssue({ + code: "custom", + path: ["googleDiscoveryApis", server, "baseUrl"], + message: + "Google Discovery API baseUrl must use https except loopback development urls and must not include credentials, query, or fragment", + }); + } + validateAuthHeaders(raw.auth, ctx, ["googleDiscoveryApis", server, "auth"]); + } for (const [server, raw] of Object.entries(config.graphqlEndpoints)) { const sourceCount = Number(Boolean(raw.schemaPath)) + diff --git a/packages/core/src/config.ts b/packages/core/src/config.ts index 74f2babb..d109a416 100644 --- a/packages/core/src/config.ts +++ b/packages/core/src/config.ts @@ -217,6 +217,29 @@ export type HttpApiConfig = AgentSelectionHintsConfig & { runtime?: RuntimeRequirementsConfig | undefined; }; +export type GoogleDiscoveryApiConfig = AgentSelectionHintsConfig & { + server: string; + backend: "googleDiscovery"; + name: string; + description: string; + exposure?: CapletExposure | undefined; + shadowing?: CapletShadowingPolicy | undefined; + tags?: string[] | undefined; + body?: string | undefined; + discoveryPath?: string | undefined; + discoveryUrl?: string | undefined; + baseUrl?: string | undefined; + includeOperations?: string[] | undefined; + excludeOperations?: string[] | undefined; + auth: OpenApiAuthConfig; + requestTimeoutMs: number; + operationCacheTtlMs: number; + disabled: boolean; + setup?: CapletSetupConfig | undefined; + projectBinding?: ProjectBindingConfig | undefined; + runtime?: RuntimeRequirementsConfig | undefined; +}; + export type CliToolOutputConfig = { type: "text" | "json"; }; @@ -285,6 +308,7 @@ export type CapletSetConfig = AgentSelectionHintsConfig & { export type CapletConfig = | CapletServerConfig | OpenApiEndpointConfig + | GoogleDiscoveryApiConfig | GraphQlEndpointConfig | HttpApiConfig | CliToolsConfig @@ -311,6 +335,7 @@ export type CapletsConfig = { options: CapletsOptions; mcpServers: Record; openapiEndpoints: Record; + googleDiscoveryApis: Record; graphqlEndpoints: Record; httpApis: Record; cliTools: Record; @@ -619,6 +644,66 @@ const normalizedOpenApiEndpointSchema = publicOpenApiEndpointSchema.extend({ body: z.string().optional(), }); +const operationFilterSchema = z.array(z.string().trim().min(1).max(160)); + +const publicGoogleDiscoveryApiSchema = z + .object({ + name: z + .string() + .trim() + .min(1) + .max(80) + .describe("Human-readable Google Discovery API display name."), + description: z + .string() + .describe( + "Capability description shown to agents before Google Discovery operations are disclosed.", + ) + .refine( + (value) => value.trim().length >= 10, + "description must contain at least 10 non-whitespace characters", + ) + .refine((value) => value.length <= 1500, "description must be at most 1500 characters"), + discoveryPath: z.string().min(1).optional().describe("Local Google Discovery document path."), + discoveryUrl: z.string().url().optional().describe("Remote Google Discovery document URL."), + baseUrl: z.string().url().optional().describe("Override base URL for Google API requests."), + includeOperations: operationFilterSchema.optional(), + excludeOperations: operationFilterSchema.optional(), + auth: openApiAuthSchema.describe( + 'Explicit Google API request auth config. Use {"type":"none"} for public APIs.', + ), + tags: z.array(z.string().trim().min(1).max(80)).optional(), + exposure: exposureSchema.optional(), + shadowing: shadowingSchema, + ...agentSelectionHintsSchema, + setup: setupSchema.optional(), + projectBinding: projectBindingSchema.optional(), + runtime: runtimeRequirementsSchema.optional(), + requestTimeoutMs: z + .number() + .int() + .positive() + .default(60_000) + .describe("Timeout in milliseconds for Google Discovery HTTP requests."), + operationCacheTtlMs: z + .number() + .int() + .nonnegative() + .default(30_000) + .describe( + "Milliseconds Google Discovery operation metadata stays fresh. Set 0 to refresh every time.", + ), + disabled: z + .boolean() + .default(false) + .describe("When true, omit this Google Discovery Caplet from discovery."), + }) + .strict(); + +const normalizedGoogleDiscoveryApiSchema = publicGoogleDiscoveryApiSchema.extend({ + body: z.string().optional(), +}); + const graphQlOperationSchema = z .object({ document: z.string().min(1).optional().describe("Inline GraphQL operation document."), @@ -973,6 +1058,7 @@ const normalizedCapletSetSchema = publicCapletSetSchema.extend({ type ConfigSchemaServerValue = z.infer; type ConfigSchemaOpenApiEndpointValue = z.infer; +type ConfigSchemaGoogleDiscoveryApiValue = z.infer; type ConfigSchemaGraphQlEndpointValue = z.infer; type ConfigSchemaHttpApiValue = z.infer; type ConfigSchemaCliToolsValue = z.infer; @@ -980,6 +1066,7 @@ type ConfigSchemaCapletSetValue = z.infer; type ConfigInput = { mcpServers?: Record; openapiEndpoints?: Record; + googleDiscoveryApis?: Record; graphqlEndpoints?: Record; httpApis?: Record; cliTools?: Record; @@ -990,6 +1077,7 @@ type ConfigInput = { function configSchemaFor( serverValueSchema: z.ZodTypeAny, openApiEndpointValueSchema: z.ZodTypeAny, + googleDiscoveryApiValueSchema: z.ZodTypeAny, graphQlEndpointValueSchema: z.ZodTypeAny, httpApiValueSchema: z.ZodTypeAny, cliToolsValueSchema: z.ZodTypeAny, @@ -997,11 +1085,7 @@ function configSchemaFor( ) { return z .object({ - $schema: z - .string() - .url() - .optional() - .describe("Optional JSON Schema URL for editor validation."), + $schema: z.string().optional().describe("Optional JSON Schema for editor validation."), version: z.literal(1).default(1).describe("Caplets config schema version."), defaultSearchLimit: z .number() @@ -1052,6 +1136,10 @@ function configSchemaFor( .record(z.string().regex(SERVER_ID_PATTERN), openApiEndpointValueSchema) .default({}) .describe("OpenAPI endpoints keyed by stable Caplet ID."), + googleDiscoveryApis: z + .record(z.string().regex(SERVER_ID_PATTERN), googleDiscoveryApiValueSchema) + .default({}) + .describe("Google Discovery APIs keyed by stable Caplet ID."), graphqlEndpoints: z .record(z.string().regex(SERVER_ID_PATTERN), graphQlEndpointValueSchema) .default({}) @@ -1189,13 +1277,82 @@ function configSchemaFor( } } + for (const [api, rawValue] of Object.entries(config.googleDiscoveryApis)) { + const raw = rawValue as ConfigSchemaGoogleDiscoveryApiValue; + const duplicateBackend = config.mcpServers[api] + ? "mcpServers" + : config.openapiEndpoints[api] + ? "openapiEndpoints" + : config.graphqlEndpoints[api] + ? "graphqlEndpoints" + : config.httpApis[api] + ? "httpApis" + : config.cliTools[api] + ? "cliTools" + : config.capletSets[api] + ? "capletSets" + : undefined; + if (duplicateBackend) { + ctx.addIssue({ + code: "custom", + path: ["googleDiscoveryApis", api], + message: `Caplet ID ${api} is already used by ${duplicateBackend}`, + }); + } + if (!SERVER_ID_PATTERN.test(api)) { + ctx.addIssue({ + code: "custom", + path: ["googleDiscoveryApis", api], + message: "Google Discovery API ID must match ^[a-zA-Z0-9_-]{1,64}$", + }); + } + if (Boolean(raw.discoveryPath) === Boolean(raw.discoveryUrl)) { + ctx.addIssue({ + code: "custom", + path: ["googleDiscoveryApis", api], + message: + "Google Discovery API must define exactly one discovery source: discoveryPath or discoveryUrl", + }); + } + if (raw.discoveryUrl && !isAllowedRemoteUrl(raw.discoveryUrl)) { + ctx.addIssue({ + code: "custom", + path: ["googleDiscoveryApis", api, "discoveryUrl"], + message: + "Google Discovery API discoveryUrl must use https except loopback development urls", + }); + } + if (raw.baseUrl && !isAllowedHttpBaseUrl(raw.baseUrl)) { + ctx.addIssue({ + code: "custom", + path: ["googleDiscoveryApis", api, "baseUrl"], + message: + "Google Discovery API baseUrl must use https except loopback development urls and must not include credentials, query, or fragment", + }); + } + if (raw.auth?.type === "headers") { + for (const headerName of Object.keys(raw.auth.headers)) { + const normalized = headerName.toLowerCase(); + if (!HEADER_NAME_PATTERN.test(headerName) || FORBIDDEN_HEADERS.has(normalized)) { + ctx.addIssue({ + code: "custom", + path: ["googleDiscoveryApis", api, "auth", "headers", headerName], + message: `header ${headerName} is not allowed`, + }); + } + } + } + } + for (const [endpoint, rawValue] of Object.entries(config.graphqlEndpoints)) { const raw = rawValue as ConfigSchemaGraphQlEndpointValue; const duplicateBackend = config.mcpServers[endpoint] ? "mcpServers" : config.openapiEndpoints[endpoint] ? "openapiEndpoints" - : undefined; + : config.googleDiscoveryApis[endpoint] + ? "googleDiscoveryApis" + : undefined; if (duplicateBackend) { ctx.addIssue({ code: "custom", @@ -1245,9 +1402,11 @@ function configSchemaFor( ? "mcpServers" : config.openapiEndpoints[endpoint] ? "openapiEndpoints" - : config.graphqlEndpoints[endpoint] - ? "graphqlEndpoints" - : undefined; + : config.googleDiscoveryApis[endpoint] + ? "googleDiscoveryApis" + : config.graphqlEndpoints[endpoint] + ? "graphqlEndpoints" + : undefined; if (duplicateBackend) { ctx.addIssue({ code: "custom", @@ -1290,11 +1449,13 @@ function configSchemaFor( ? "mcpServers" : config.openapiEndpoints[server] ? "openapiEndpoints" - : config.graphqlEndpoints[server] - ? "graphqlEndpoints" - : config.httpApis[server] - ? "httpApis" - : undefined; + : config.googleDiscoveryApis[server] + ? "googleDiscoveryApis" + : config.graphqlEndpoints[server] + ? "graphqlEndpoints" + : config.httpApis[server] + ? "httpApis" + : undefined; if (duplicateBackend) { ctx.addIssue({ code: "custom", @@ -1326,13 +1487,15 @@ function configSchemaFor( ? "mcpServers" : config.openapiEndpoints[server] ? "openapiEndpoints" - : config.graphqlEndpoints[server] - ? "graphqlEndpoints" - : config.httpApis[server] - ? "httpApis" - : config.cliTools[server] - ? "cliTools" - : undefined; + : config.googleDiscoveryApis[server] + ? "googleDiscoveryApis" + : config.graphqlEndpoints[server] + ? "graphqlEndpoints" + : config.httpApis[server] + ? "httpApis" + : config.cliTools[server] + ? "cliTools" + : undefined; if (duplicateBackend) { ctx.addIssue({ code: "custom", @@ -1361,6 +1524,7 @@ function configSchemaFor( export const configFileSchema = configSchemaFor( publicServerSchema, publicOpenApiEndpointSchema, + publicGoogleDiscoveryApiSchema, publicGraphQlEndpointSchema, publicHttpApiSchema, publicCliToolsSchema, @@ -1369,6 +1533,7 @@ export const configFileSchema = configSchemaFor( const normalizedConfigFileSchema = configSchemaFor( normalizedServerSchema, normalizedOpenApiEndpointSchema, + normalizedGoogleDiscoveryApiSchema, normalizedGraphQlEndpointSchema, normalizedHttpApiSchema, normalizedCliToolsSchema, @@ -1423,7 +1588,7 @@ export function loadConfigWithSources( : undefined, ], `Caplets config not found at ${path} or ${projectPath}`, - "Caplets config must define at least one MCP server, OpenAPI endpoint, GraphQL endpoint, HTTP API, CLI tools backend, or Caplet set", + "Caplets config must define at least one MCP server, OpenAPI endpoint, Google Discovery API, GraphQL endpoint, HTTP API, CLI tools backend, or Caplet set", ); } @@ -1483,6 +1648,7 @@ function buildConfigWithSources( emptyMessage && Object.keys(config.mcpServers).length === 0 && Object.keys(config.openapiEndpoints).length === 0 && + Object.keys(config.googleDiscoveryApis).length === 0 && Object.keys(config.graphqlEndpoints).length === 0 && Object.keys(config.httpApis).length === 0 && Object.keys(config.cliTools).length === 0 && @@ -1614,6 +1780,7 @@ export function loadIsolatedConfig(options: { if ( Object.keys(config.mcpServers).length === 0 && Object.keys(config.openapiEndpoints).length === 0 && + Object.keys(config.googleDiscoveryApis).length === 0 && Object.keys(config.graphqlEndpoints).length === 0 && Object.keys(config.httpApis).length === 0 && Object.keys(config.cliTools).length === 0 && @@ -1660,6 +1827,11 @@ function normalizeLocalPaths(input: ConfigInput, baseDir: string): ConfigInput { return stripUndefined({ ...input, openapiEndpoints: normalizeEndpointPaths(input.openapiEndpoints, baseDir, normalizeOpenApiPath), + googleDiscoveryApis: normalizeEndpointPaths( + input.googleDiscoveryApis, + baseDir, + normalizeGoogleDiscoveryPath, + ), graphqlEndpoints: normalizeEndpointPaths(input.graphqlEndpoints, baseDir, normalizeGraphQlPath), cliTools: normalizeEndpointPaths(input.cliTools, baseDir, normalizeCliToolsPaths), capletSets: normalizeEndpointPaths(input.capletSets, baseDir, normalizeCapletSetPaths), @@ -1692,6 +1864,16 @@ function normalizeOpenApiPath( }; } +function normalizeGoogleDiscoveryPath( + endpoint: Record, + baseDir: string, +): Record { + return { + ...endpoint, + discoveryPath: normalizeLocalPath(endpoint.discoveryPath, baseDir), + }; +} + function normalizeGraphQlPath( endpoint: Record, baseDir: string, @@ -1765,6 +1947,12 @@ function rejectProjectConfigExecutableBackendMaps(input: ConfigInput, path: stri `Project config at ${path} cannot define executable backend map openapiEndpoints; use project Markdown Caplet files or user config instead`, ); } + if (input.googleDiscoveryApis && Object.keys(input.googleDiscoveryApis).length > 0) { + throw new CapletsError( + "CONFIG_INVALID", + `Project config at ${path} cannot define executable backend map googleDiscoveryApis; use project Markdown Caplet files or user config instead`, + ); + } if (input.graphqlEndpoints && Object.keys(input.graphqlEndpoints).length > 0) { throw new CapletsError( "CONFIG_INVALID", @@ -1809,6 +1997,10 @@ function mergeConfigInputs(...inputs: Array): ConfigInp ...merged?.openapiEndpoints, ...input.openapiEndpoints, }, + googleDiscoveryApis: { + ...merged?.googleDiscoveryApis, + ...input.googleDiscoveryApis, + }, graphqlEndpoints: { ...merged?.graphqlEndpoints, ...input.graphqlEndpoints, @@ -1860,6 +2052,7 @@ function mergeConfigInputsWithSources(...inputs: Array = {}; + for (const [server, raw] of Object.entries(parsed.data.googleDiscoveryApis)) { + const interpolated = raw as ConfigSchemaGoogleDiscoveryApiValue; + googleDiscoveryApis[server] = stripUndefined({ + ...interpolated, + server, + backend: "googleDiscovery", + }) as GoogleDiscoveryApiConfig; + } + const graphqlEndpoints: Record = {}; for (const [server, raw] of Object.entries(parsed.data.graphqlEndpoints)) { const interpolated = raw as ConfigSchemaGraphQlEndpointValue; @@ -1973,6 +2178,7 @@ export function parseConfig(input: unknown): CapletsConfig { }, mcpServers: servers, openapiEndpoints, + googleDiscoveryApis, graphqlEndpoints, httpApis, cliTools, @@ -2032,6 +2238,7 @@ function isPublicMetadataPath(path: string[]): boolean { path.length < 3 || (path[0] !== "mcpServers" && path[0] !== "openapiEndpoints" && + path[0] !== "googleDiscoveryApis" && path[0] !== "graphqlEndpoints" && path[0] !== "httpApis" && path[0] !== "cliTools" && diff --git a/packages/core/src/config/paths.ts b/packages/core/src/config/paths.ts index c09b9c29..65768be1 100644 --- a/packages/core/src/config/paths.ts +++ b/packages/core/src/config/paths.ts @@ -74,6 +74,15 @@ export function defaultAuthDir( return pathJoin(defaultStateBaseDir(env, home, platform), "caplets", "auth"); } +export function defaultArtifactDir( + env: PathEnv = process.env, + home = homedir(), + platform: Platform = process.platform, +): string { + const pathJoin = platform === "win32" ? win32.join : posix.join; + return pathJoin(defaultStateBaseDir(env, home, platform), "caplets", "artifacts"); +} + export function defaultCompletionCacheDir( env: PathEnv = process.env, home = homedir(), @@ -98,6 +107,7 @@ export function defaultObservedOutputShapeCacheDir( export const DEFAULT_CONFIG_PATH = defaultConfigPath(); export const DEFAULT_AUTH_DIR = defaultAuthDir(); +export const DEFAULT_ARTIFACT_DIR = defaultArtifactDir(); export const DEFAULT_COMPLETION_CACHE_DIR = defaultCompletionCacheDir(); export const DEFAULT_OBSERVED_OUTPUT_SHAPE_CACHE_DIR = defaultObservedOutputShapeCacheDir(); export const PROJECT_CONFIG_FILE = join(".caplets", "config.json"); diff --git a/packages/core/src/engine.ts b/packages/core/src/engine.ts index a3a52253..dd5a4ed5 100644 --- a/packages/core/src/engine.ts +++ b/packages/core/src/engine.ts @@ -15,6 +15,7 @@ import { DEFAULT_OBSERVED_OUTPUT_SHAPE_CACHE_DIR } from "./config/paths"; import { DownstreamManager } from "./downstream"; import { CapletsError, errorResult, toSafeError } from "./errors"; import { GraphQLManager } from "./graphql"; +import { GoogleDiscoveryManager } from "./google-discovery"; import { HttpActionManager } from "./http-actions"; import { OpenApiManager } from "./openapi"; import { @@ -32,6 +33,8 @@ export type CapletsEngineOptions = { configPath?: string; projectConfigPath?: string; authDir?: string; + artifactDir?: string; + exposeLocalArtifactPaths?: boolean; watchDebounceMs?: number; watch?: boolean; writeErr?: (value: string) => void; @@ -62,6 +65,7 @@ export class CapletsEngine { private registry: ServerRegistry; private readonly downstream: DownstreamManager; private readonly openapi: OpenApiManager; + private readonly googleDiscovery: GoogleDiscoveryManager; private readonly graphql: GraphQLManager; private readonly http: HttpActionManager; private readonly cli: CliToolsManager; @@ -92,11 +96,15 @@ export class CapletsEngine { const config = this.configLoader(this.paths.configPath, this.paths.projectConfigPath); this.registry = new ServerRegistry(config); this.downstream = new DownstreamManager(this.registry, selectAuthOptions(options.authDir)); - this.openapi = new OpenApiManager(this.registry, selectAuthOptions(options.authDir)); + this.openapi = new OpenApiManager(this.registry, selectHttpLikeOptions(options)); + this.googleDiscovery = new GoogleDiscoveryManager( + this.registry, + selectHttpLikeOptions(options), + ); this.graphql = new GraphQLManager(this.registry, selectAuthOptions(options.authDir)); - this.http = new HttpActionManager(this.registry, selectAuthOptions(options.authDir)); + this.http = new HttpActionManager(this.registry, selectHttpLikeOptions(options)); this.cli = new CliToolsManager(this.registry); - this.capletSets = new CapletSetManager(this.registry, selectAuthOptions(options.authDir)); + this.capletSets = new CapletSetManager(this.registry, selectHttpLikeOptions(options)); this.watchDebounceMs = options.watchDebounceMs ?? 250; this.watchEnabled = options.watch ?? true; this.writeErr = options.writeErr ?? ((value: string) => process.stderr.write(value)); @@ -200,6 +208,7 @@ export class CapletsEngine { observedOutputShapeScope: this.observedOutputShapeScope, projectFingerprint: this.projectFingerprint, }, + this.googleDiscovery, ); } catch (error) { return errorResult(error); @@ -307,13 +316,15 @@ export class CapletsEngine { ? await this.downstream.listTools(server) : server.backend === "openapi" ? await this.openapi.listTools(server) - : server.backend === "graphql" - ? await this.graphql.listTools(server) - : server.backend === "http" - ? await this.http.listTools(server) - : server.backend === "cli" - ? await this.cli.listTools(server) - : await this.capletSets.listTools(server); + : server.backend === "googleDiscovery" + ? await this.googleDiscovery.listTools(server) + : server.backend === "graphql" + ? await this.graphql.listTools(server) + : server.backend === "http" + ? await this.http.listTools(server) + : server.backend === "cli" + ? await this.cli.listTools(server) + : await this.capletSets.listTools(server); return tools.map((tool) => ({ name: tool.name, ...(tool.description ? { description: tool.description } : {}), @@ -325,13 +336,15 @@ export class CapletsEngine { ? await this.downstream.listTools(server) : server.backend === "openapi" ? await this.openapi.listTools(server) - : server.backend === "graphql" - ? await this.graphql.listTools(server) - : server.backend === "http" - ? await this.http.listTools(server) - : server.backend === "cli" - ? await this.cli.listTools(server) - : await this.capletSets.listTools(server); + : server.backend === "googleDiscovery" + ? await this.googleDiscovery.listTools(server) + : server.backend === "graphql" + ? await this.graphql.listTools(server) + : server.backend === "http" + ? await this.http.listTools(server) + : server.backend === "cli" + ? await this.cli.listTools(server) + : await this.capletSets.listTools(server); } private async callTool(server: CapletConfig, toolName: string, args: Record) { @@ -339,13 +352,15 @@ export class CapletsEngine { ? await this.downstream.callTool(server, toolName, args) : server.backend === "openapi" ? await this.openapi.callTool(server, toolName, args) - : server.backend === "graphql" - ? await this.graphql.callTool(server, toolName, args) - : server.backend === "http" - ? await this.http.callTool(server, toolName, args) - : server.backend === "cli" - ? await this.cli.callTool(server, toolName, args) - : await this.capletSets.callTool(server, toolName, args); + : server.backend === "googleDiscovery" + ? await this.googleDiscovery.callTool(server, toolName, args) + : server.backend === "graphql" + ? await this.graphql.callTool(server, toolName, args) + : server.backend === "http" + ? await this.http.callTool(server, toolName, args) + : server.backend === "cli" + ? await this.cli.callTool(server, toolName, args) + : await this.capletSets.callTool(server, toolName, args); } private async optionalMcpList( @@ -381,6 +396,7 @@ export class CapletsEngine { this.registry = nextRegistry; this.downstream.updateRegistry(nextRegistry); this.openapi.updateRegistry(nextRegistry); + this.googleDiscovery.updateRegistry(nextRegistry); this.graphql.updateRegistry(nextRegistry); this.http.updateRegistry(nextRegistry); this.cli.updateRegistry(nextRegistry); @@ -451,6 +467,9 @@ export class CapletsEngine { if (before?.backend === "openapi" || after?.backend === "openapi" || !after) { this.openapi.invalidate(serverId); } + if (before?.backend === "googleDiscovery" || after?.backend === "googleDiscovery" || !after) { + this.googleDiscovery.invalidate(serverId); + } if (before?.backend === "graphql" || after?.backend === "graphql" || !after) { this.graphql.invalidate(serverId); } @@ -549,6 +568,18 @@ function selectAuthOptions(authDir: string | undefined): { authDir?: string } { return authDir ? { authDir } : {}; } +function selectHttpLikeOptions(options: CapletsEngineOptions): { + authDir?: string; + artifactDir?: string; + exposeLocalArtifactPaths?: boolean; +} { + return { + ...selectAuthOptions(options.authDir), + ...(options.artifactDir ? { artifactDir: options.artifactDir } : {}), + ...(options.exposeLocalArtifactPaths === false ? { exposeLocalArtifactPaths: false } : {}), + }; +} + function safeProjectFingerprint(): string | undefined { try { return fingerprintProjectRoot(findProjectRoot()); @@ -584,6 +615,7 @@ function allCaplets(config: CapletsConfig): CapletConfig[] { return [ ...Object.values(config.mcpServers), ...Object.values(config.openapiEndpoints), + ...Object.values(config.googleDiscoveryApis ?? {}), ...Object.values(config.graphqlEndpoints), ...Object.values(config.httpApis), ...Object.values(config.cliTools), diff --git a/packages/core/src/google-discovery/index.ts b/packages/core/src/google-discovery/index.ts new file mode 100644 index 00000000..6449d8d2 --- /dev/null +++ b/packages/core/src/google-discovery/index.ts @@ -0,0 +1,5 @@ +export * from "./types"; +export * from "./schema"; +export * from "./operations"; +export * from "./request"; +export * from "./manager"; diff --git a/packages/core/src/google-discovery/manager.ts b/packages/core/src/google-discovery/manager.ts new file mode 100644 index 00000000..d16fd9c0 --- /dev/null +++ b/packages/core/src/google-discovery/manager.ts @@ -0,0 +1,789 @@ +import { randomUUID } from "node:crypto"; +import { readFile } from "node:fs/promises"; +import type { CompatibilityCallToolResult, Tool } from "@modelcontextprotocol/sdk/types"; +import { genericOAuthHeaders, readTokenBundle } from "../auth"; +import type { GoogleDiscoveryApiConfig } from "../config"; +import { + compactToolSafetyHints, + compactToolSchemaHints, + compactToolSelectionHints, + type CompactTool, +} from "../downstream"; +import { CapletsError, toSafeError } from "../errors"; +import { readHttpLikeResponse } from "../http/response"; +import { isAbortError, readLimitedText } from "../http/utils"; +import { readMediaInput, type ResolvedMediaInput } from "../media"; +import type { ServerRegistry } from "../registry"; +import { markdownStructuredContent } from "../result-content"; +import { searchToolList } from "../tool-search"; +import { + discoveryOperations, + googleDiscoveryScopesForOperations, + type GoogleDiscoveryOperation, +} from "./operations"; +import { + buildGoogleDiscoveryUploadUrl, + buildGoogleDiscoveryUrl, + buildJsonRequestInit, +} from "./request"; +import type { GoogleDiscoveryDocument } from "./types"; + +const DEFAULT_RESUMABLE_THRESHOLD_BYTES = 8 * 1024 * 1024; +const DEFAULT_MEDIA_RESPONSE_MAX_BYTES = 100 * 1024 * 1024; +const DEFAULT_DISCOVERY_DOCUMENT_MAX_BYTES = 20 * 1024 * 1024; + +type ManagedGoogleDiscovery = { + operations?: GoogleDiscoveryOperation[]; + baseUrl?: string; + fetchedAt?: number; + cacheKey: string; +}; + +export class GoogleDiscoveryManager { + private readonly cache = new Map(); + + constructor( + private registry: ServerRegistry, + private readonly options: { + authDir?: string; + artifactDir?: string; + exposeLocalArtifactPaths?: boolean; + } = {}, + ) {} + + updateRegistry(registry: ServerRegistry): void { + this.registry = registry; + } + + invalidate(serverId: string): void { + this.cache.delete(serverId); + } + + async checkApi(api: GoogleDiscoveryApiConfig): Promise<{ + id: string; + status: string; + toolCount?: number; + elapsedMs: number; + error?: unknown; + }> { + const startedAt = Date.now(); + try { + const operations = await this.refreshOperations(api, true); + this.registry.setStatus(api.server, "available"); + return { + id: api.server, + status: "available", + toolCount: operations.length, + elapsedMs: Date.now() - startedAt, + }; + } catch (error) { + const safe = toSafeError(error, "SERVER_UNAVAILABLE"); + this.registry.setStatus(api.server, "unavailable", safe); + return { + id: api.server, + status: "unavailable", + elapsedMs: Date.now() - startedAt, + error: safe, + }; + } + } + + async listTools(api: GoogleDiscoveryApiConfig): Promise { + const operations = await this.refreshOperations(api, false); + return operations.map((operation) => this.toTool(operation)); + } + + async getTool(api: GoogleDiscoveryApiConfig, toolName: string): Promise { + return this.toTool(await this.getOperation(api, toolName)); + } + + async callTool( + api: GoogleDiscoveryApiConfig, + toolName: string, + args: Record, + ): Promise { + const operation = await this.getOperation(api, toolName); + const requestApi = await this.resolveRequestApi(api); + if (operation.supportsMediaUpload && "media" in args) { + return this.callMediaUpload(requestApi, operation, args); + } + const requestArgs = shouldRequestMediaDownload(operation, args) + ? withMediaDownloadQuery(args) + : args; + const url = buildGoogleDiscoveryUrl(requestApi, operation, requestArgs); + const headers = new Headers( + await authHeaders(requestApi, this.options.authDir, operation.scopes), + ); + const init = buildJsonRequestInit(operation, args, headers); + const controller = new AbortController(); + const timeout = setTimeout(() => controller.abort(), requestApi.requestTimeoutMs); + try { + const response = await fetch(url, { ...init, signal: controller.signal }); + if (response.status >= 300 && response.status < 400) { + throw new CapletsError( + "DOWNSTREAM_PROTOCOL_ERROR", + "Google Discovery request returned a redirect", + { + server: requestApi.server, + status: response.status, + location: response.headers.get("location") ? "[REDACTED]" : undefined, + }, + ); + } + if (response.status === 401 || response.status === 403) { + throw googleAuthError(requestApi, response); + } + const parsed = await readHttpLikeResponse(response, { + capletId: requestApi.server, + method: operation.method, + ...(this.options.artifactDir ? { artifactDir: this.options.artifactDir } : {}), + ...(this.options.exposeLocalArtifactPaths === false ? { exposeLocalPath: false } : {}), + ...(typeof args.filename === "string" ? { filename: args.filename } : {}), + ...(typeof args.outputPath === "string" ? { outputPath: args.outputPath } : {}), + maxBytes: DEFAULT_MEDIA_RESPONSE_MAX_BYTES, + ...(operation.supportsMediaDownload && + (typeof args.filename === "string" || typeof args.outputPath === "string") + ? { forceArtifact: true } + : {}), + }); + return { + content: markdownStructuredContent(parsed, { + title: `${requestApi.name} call_tool ${toolName}`, + backend: "googleDiscovery", + operation: "call_tool", + tool: toolName, + }), + structuredContent: parsed as Record, + isError: !response.ok, + }; + } catch (error) { + if (isAbortError(error)) { + throw new CapletsError( + "TOOL_CALL_TIMEOUT", + `Google Discovery request timed out for ${requestApi.server}/${toolName}`, + ); + } + if (error instanceof CapletsError) throw error; + throw new CapletsError( + "DOWNSTREAM_TOOL_ERROR", + `Google Discovery request failed for ${requestApi.server}/${toolName}`, + toSafeError(error), + ); + } finally { + clearTimeout(timeout); + } + } + + private async callMediaUpload( + api: GoogleDiscoveryApiConfig, + operation: GoogleDiscoveryOperation, + args: Record, + ): Promise { + const mediaOptions: { + artifactRoot?: string; + allowLocalPaths?: boolean; + } = {}; + if (this.options.artifactDir) mediaOptions.artifactRoot = this.options.artifactDir; + if (this.options.exposeLocalArtifactPaths === false) mediaOptions.allowLocalPaths = false; + const media = await readMediaInput(args.media, mediaOptions); + const headers = new Headers(await authHeaders(api, this.options.authDir, operation.scopes)); + const protocol = selectUploadProtocol(operation, media, args); + const controller = new AbortController(); + const timeout = setTimeout(() => controller.abort(), api.requestTimeoutMs); + try { + const response = + protocol === "resumable" + ? await this.callResumableUpload(api, operation, args, media, headers, controller.signal) + : await this.callSingleUpload( + api, + operation, + args, + media, + headers, + protocol, + controller.signal, + ); + const parsed = await readHttpLikeResponse(response, { + capletId: api.server, + method: operation.method, + ...(this.options.artifactDir ? { artifactDir: this.options.artifactDir } : {}), + ...(this.options.exposeLocalArtifactPaths === false ? { exposeLocalPath: false } : {}), + }); + return { + content: markdownStructuredContent(parsed, { + title: `${api.name} call_tool ${operation.name}`, + backend: "googleDiscovery", + operation: "call_tool", + tool: operation.name, + }), + structuredContent: parsed as Record, + isError: !response.ok, + }; + } catch (error) { + if (isAbortError(error)) { + throw new CapletsError( + "TOOL_CALL_TIMEOUT", + `Google Discovery request timed out for ${api.server}/${operation.name}`, + ); + } + if (error instanceof CapletsError) throw error; + throw new CapletsError( + "DOWNSTREAM_TOOL_ERROR", + `Google Discovery request failed for ${api.server}/${operation.name}`, + toSafeError(error), + ); + } finally { + clearTimeout(timeout); + } + } + + private async callSingleUpload( + api: GoogleDiscoveryApiConfig, + operation: GoogleDiscoveryOperation, + args: Record, + media: ResolvedMediaInput, + headers: Headers, + protocol: "simple" | "multipart", + signal?: AbortSignal, + ): Promise { + const upload = operation.mediaUploadProtocols[protocol]; + if (!upload?.path) { + throw new CapletsError( + "CONFIG_INVALID", + `Google Discovery ${protocol} upload path is missing`, + ); + } + const url = buildGoogleDiscoveryUploadUrl( + api, + operation, + upload.path, + protocol === "simple" ? "media" : "multipart", + args, + ); + const init = + protocol === "simple" + ? simpleUploadInit(operation, media, headers) + : multipartUploadInit(operation, args.body, media, headers); + return fetchGoogleRequest(api, operation, url, init, { signal }); + } + + private async callResumableUpload( + api: GoogleDiscoveryApiConfig, + operation: GoogleDiscoveryOperation, + args: Record, + media: ResolvedMediaInput, + headers: Headers, + signal?: AbortSignal, + ): Promise { + const upload = operation.mediaUploadProtocols.resumable; + if (!upload?.path) { + throw new CapletsError("CONFIG_INVALID", "Google Discovery resumable upload path is missing"); + } + const contentType = safeMediaContentType(media.mimeType); + const startUrl = buildGoogleDiscoveryUploadUrl(api, operation, upload.path, "resumable", args); + headers.set("content-type", "application/json; charset=UTF-8"); + headers.set("x-upload-content-type", contentType); + headers.set("x-upload-content-length", String(media.bytes.byteLength)); + const started = await fetchGoogleRequest( + api, + operation, + startUrl, + { + method: operation.method.toUpperCase(), + headers, + body: JSON.stringify(args.body ?? {}), + redirect: "manual", + }, + { signal }, + ); + if (!started.ok) { + return started; + } + const location = started.headers.get("location"); + if (!location) { + throw new CapletsError( + "DOWNSTREAM_PROTOCOL_ERROR", + "Google resumable upload missing Location", + ); + } + const uploadUrl = new URL(location); + const uploadHeaders = new Headers(); + uploadHeaders.set("content-type", contentType); + uploadHeaders.set("content-length", String(media.bytes.byteLength)); + uploadHeaders.set("content-range", resumableContentRange(media.bytes.byteLength)); + copySessionAuthorization(api, startUrl, uploadUrl, headers, uploadHeaders); + return fetchGoogleRequest( + api, + operation, + uploadUrl, + { + method: "PUT", + headers: uploadHeaders, + body: media.bytes, + redirect: "manual", + }, + { signal }, + ); + } + + compact(_api: GoogleDiscoveryApiConfig, tool: Tool): CompactTool { + return { + name: tool.name, + ...(tool.description ? { description: tool.description } : {}), + hasInputSchema: Boolean(tool.inputSchema), + hasOutputSchema: Boolean(tool.outputSchema), + supportsFields: Boolean(tool.outputSchema), + ...compactToolSelectionHints(tool), + ...compactToolSchemaHints(tool), + ...compactToolSafetyHints(tool), + }; + } + + search( + api: GoogleDiscoveryApiConfig, + tools: Tool[], + query: string, + limit: number, + ): CompactTool[] { + return searchToolList(tools, query, limit, (tool) => this.compact(api, tool)); + } + + async resolveAuthScopes(api: GoogleDiscoveryApiConfig): Promise { + return googleDiscoveryScopesForOperations(await this.refreshOperations(api, false)); + } + + private async getOperation( + api: GoogleDiscoveryApiConfig, + toolName: string, + ): Promise { + const operations = await this.refreshOperations(api, false); + const operation = operations.find((candidate) => candidate.name === toolName); + if (!operation) { + throw new CapletsError("TOOL_NOT_FOUND", `Tool ${toolName} was not found on ${api.server}`, { + server: api.server, + tool: toolName, + suggestions: operations + .map((candidate) => candidate.name) + .filter((name) => + name.toLocaleLowerCase().includes(toolName.toLocaleLowerCase()[0] ?? ""), + ) + .slice(0, 5), + }); + } + return operation; + } + + private async refreshOperations( + api: GoogleDiscoveryApiConfig, + force: boolean, + ): Promise { + const cached = this.cache.get(api.server); + const cacheKey = googleDiscoveryCacheKey(api); + const now = Date.now(); + const isFresh = + cached?.operations && + cached.cacheKey === cacheKey && + cached.fetchedAt !== undefined && + api.operationCacheTtlMs > 0 && + now - cached.fetchedAt <= api.operationCacheTtlMs; + if (!force && isFresh) return cached.operations ?? []; + + try { + const document = await loadGoogleDiscoveryDocument(api, this.options.authDir); + const baseUrl = googleDiscoveryBaseUrl(api, document); + const operations = discoveryOperations({ + server: api.server, + document, + ...(api.includeOperations ? { includeOperations: api.includeOperations } : {}), + ...(api.excludeOperations ? { excludeOperations: api.excludeOperations } : {}), + }); + this.cache.set(api.server, { + operations, + ...(baseUrl ? { baseUrl } : {}), + fetchedAt: Date.now(), + cacheKey, + }); + this.registry.setStatus(api.server, "available"); + return operations; + } catch (error) { + const safe = toSafeError(error, "DOWNSTREAM_PROTOCOL_ERROR"); + this.registry.setStatus(api.server, "unavailable", safe); + throw new CapletsError( + safe.code, + `Could not load Google Discovery operations for ${api.server}`, + safe, + ); + } + } + + private toTool(operation: GoogleDiscoveryOperation): Tool { + return { + name: operation.name, + ...(operation.description ? { description: operation.description } : {}), + inputSchema: operation.inputSchema as Tool["inputSchema"], + ...(operation.outputSchema + ? { outputSchema: operation.outputSchema as Tool["outputSchema"] } + : {}), + annotations: { + readOnlyHint: operation.readOnlyHint, + destructiveHint: operation.destructiveHint, + }, + }; + } + + async resolveBaseUrl(api: GoogleDiscoveryApiConfig): Promise { + await this.refreshOperations(api, false); + return this.cache.get(api.server)?.baseUrl; + } + + private async resolveRequestApi( + api: GoogleDiscoveryApiConfig, + ): Promise { + if (api.baseUrl) return api; + const baseUrl = await this.resolveBaseUrl(api); + if (!baseUrl) { + throw new CapletsError("CONFIG_INVALID", `${api.server} is missing Google Discovery baseUrl`); + } + return { ...api, baseUrl }; + } +} + +function selectUploadProtocol( + operation: GoogleDiscoveryOperation, + media: ResolvedMediaInput, + args: Record, +): "simple" | "multipart" | "resumable" { + if ( + media.bytes.byteLength > DEFAULT_RESUMABLE_THRESHOLD_BYTES && + operation.mediaUploadProtocols.resumable + ) { + return "resumable"; + } + if ("body" in args) { + if (operation.mediaUploadProtocols.multipart) return "multipart"; + if (operation.mediaUploadProtocols.resumable) return "resumable"; + throw new CapletsError( + "CONFIG_INVALID", + "Google Discovery media upload metadata requires multipart or resumable upload", + ); + } + if (operation.mediaUploadProtocols.simple) return "simple"; + if (operation.mediaUploadProtocols.resumable) return "resumable"; + throw new CapletsError( + "CONFIG_INVALID", + "Google Discovery media upload has no supported protocol", + ); +} + +function simpleUploadInit( + operation: GoogleDiscoveryOperation, + media: ResolvedMediaInput, + headers: Headers, +): RequestInit { + headers.set("content-type", safeMediaContentType(media.mimeType)); + headers.set("content-length", String(media.bytes.byteLength)); + return { + method: operation.method.toUpperCase(), + headers, + body: media.bytes, + redirect: "manual", + }; +} + +function multipartUploadInit( + operation: GoogleDiscoveryOperation, + body: unknown, + media: ResolvedMediaInput, + headers: Headers, +): RequestInit { + const boundary = `caplets_${randomUUID().replace(/-/gu, "")}`; + const contentType = safeMediaContentType(media.mimeType); + const payload = Buffer.concat([ + Buffer.from( + `--${boundary}\r\ncontent-type: application/json; charset=UTF-8\r\n\r\n${JSON.stringify( + body ?? {}, + )}\r\n`, + ), + Buffer.from(`--${boundary}\r\ncontent-type: ${contentType}\r\n\r\n`), + media.bytes, + Buffer.from(`\r\n--${boundary}--\r\n`), + ]); + headers.set("content-type", `multipart/related; boundary=${boundary}`); + headers.set("content-length", String(payload.byteLength)); + return { + method: operation.method.toUpperCase(), + headers, + body: payload, + redirect: "manual", + }; +} + +function resumableContentRange(byteLength: number): string { + return byteLength === 0 ? "bytes */0" : `bytes 0-${byteLength - 1}/${byteLength}`; +} + +function safeMediaContentType(mimeType: string | undefined): string { + const contentType = mimeType ?? "application/octet-stream"; + if (/[\r\n]/u.test(contentType)) { + throw new CapletsError("REQUEST_INVALID", "media.mimeType must not contain line breaks"); + } + return contentType; +} + +function googleDiscoveryBaseUrl( + api: GoogleDiscoveryApiConfig, + document: GoogleDiscoveryDocument, +): string | undefined { + if (api.baseUrl) return api.baseUrl; + if (document.baseUrl) return document.baseUrl; + if (document.rootUrl && document.servicePath) { + return new URL(document.servicePath, document.rootUrl).toString(); + } + return undefined; +} + +async function fetchGoogleRequest( + api: GoogleDiscoveryApiConfig, + operation: GoogleDiscoveryOperation, + url: URL, + init: RequestInit, + options: { signal?: AbortSignal | undefined } = {}, +): Promise { + const controller = options.signal ? undefined : new AbortController(); + const timeout = controller + ? setTimeout(() => controller.abort(), api.requestTimeoutMs) + : undefined; + try { + const response = await fetch(url, { ...init, signal: options.signal ?? controller!.signal }); + if (response.status >= 300 && response.status < 400) { + throw new CapletsError( + "DOWNSTREAM_PROTOCOL_ERROR", + "Google Discovery request returned a redirect", + { + server: api.server, + status: response.status, + location: response.headers.get("location") ? "[REDACTED]" : undefined, + }, + ); + } + if (response.status === 401 || response.status === 403) { + throw googleAuthError(api, response); + } + return response; + } catch (error) { + if (isAbortError(error)) { + throw new CapletsError( + "TOOL_CALL_TIMEOUT", + `Google Discovery request timed out for ${api.server}/${operation.name}`, + ); + } + throw error; + } finally { + if (timeout) clearTimeout(timeout); + } +} + +async function loadGoogleDiscoveryDocument( + api: GoogleDiscoveryApiConfig, + authDir?: string, +): Promise { + const source = await loadGoogleDiscoverySource(api, authDir); + let parsed: unknown; + try { + parsed = JSON.parse(source); + } catch (error) { + throw new CapletsError("DOWNSTREAM_PROTOCOL_ERROR", "Google Discovery document is not JSON", { + server: api.server, + error: error instanceof Error ? error.message : String(error), + }); + } + if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) { + throw new CapletsError( + "DOWNSTREAM_PROTOCOL_ERROR", + "Google Discovery document is not an object", + ); + } + return parsed as GoogleDiscoveryDocument; +} + +async function loadGoogleDiscoverySource( + api: GoogleDiscoveryApiConfig, + authDir?: string, +): Promise { + if (api.discoveryPath) { + return readFile(api.discoveryPath, "utf8"); + } + if (!api.discoveryUrl) { + throw new CapletsError( + "CONFIG_INVALID", + `${api.server} is missing Google Discovery document source`, + ); + } + return fetchDiscoverySource(api, await discoveryAuthHeaders(api, authDir)); +} + +async function fetchDiscoverySource( + api: GoogleDiscoveryApiConfig, + headers: Record, +): Promise { + const controller = new AbortController(); + const timeout = setTimeout(() => controller.abort(), api.requestTimeoutMs); + try { + const response = await fetch(api.discoveryUrl!, { + headers, + redirect: "manual", + signal: controller.signal, + }); + if (response.status >= 300 && response.status < 400) { + throw new CapletsError( + "DOWNSTREAM_PROTOCOL_ERROR", + "Google Discovery document request returned a redirect", + ); + } + if (!response.ok) { + throw new CapletsError( + "DOWNSTREAM_PROTOCOL_ERROR", + "Google Discovery document request failed", + { status: response.status }, + ); + } + return await readLimitedText(response, { + maxBytes: DEFAULT_DISCOVERY_DOCUMENT_MAX_BYTES, + errorMessage: "Google Discovery document exceeded byte limit", + }); + } catch (error) { + if (isAbortError(error)) { + throw new CapletsError("TOOL_CALL_TIMEOUT", "Google Discovery document request timed out"); + } + throw error; + } finally { + clearTimeout(timeout); + } +} + +async function discoveryAuthHeaders( + api: GoogleDiscoveryApiConfig, + authDir?: string, +): Promise> { + if (api.auth.type === "none") return {}; + if (api.auth.type === "oauth2" || api.auth.type === "oidc") { + const protectedOrigin = discoveryProtectedResourceOrigin(api, authDir); + if (!protectedOrigin || !shouldSendDiscoveryAuth(api, protectedOrigin)) return {}; + const bundle = readTokenBundle(api.server, authDir); + if (!bundle?.accessToken && !bundle?.refreshToken) return {}; + return genericOAuthHeaders({ ...api, baseUrl: protectedOrigin }, authDir); + } + if (!api.baseUrl || !shouldSendDiscoveryAuth(api, new URL(api.baseUrl).origin)) return {}; + return authHeaders(api, authDir); +} + +async function authHeaders( + api: GoogleDiscoveryApiConfig, + authDir?: string, + resolvedScopes?: string[], +): Promise> { + switch (api.auth.type) { + case "none": + return {}; + case "bearer": + return { authorization: `Bearer ${api.auth.token}` }; + case "headers": + return api.auth.headers; + case "oauth2": + case "oidc": + return genericOAuthHeaders({ ...api, resolvedScopes }, authDir); + } +} + +function discoveryProtectedResourceOrigin( + api: GoogleDiscoveryApiConfig, + authDir?: string, +): string | undefined { + if (api.baseUrl) return new URL(api.baseUrl).origin; + const bundle = readTokenBundle(api.server, authDir); + return bundle?.protectedResourceOrigin; +} + +function copySessionAuthorization( + api: GoogleDiscoveryApiConfig, + startUrl: URL, + uploadUrl: URL, + source: Headers, + target: Headers, +): void { + const authorization = source.get("authorization"); + if (!authorization || !isAllowedUploadSessionOrigin(api, startUrl, uploadUrl)) return; + target.set("authorization", authorization); +} + +function isAllowedUploadSessionOrigin( + api: GoogleDiscoveryApiConfig, + startUrl: URL, + uploadUrl: URL, +): boolean { + if (uploadUrl.origin === startUrl.origin) return true; + if (api.baseUrl && uploadUrl.origin === new URL(api.baseUrl).origin) return true; + return isGoogleApiOrigin(uploadUrl); +} + +function isGoogleApiOrigin(url: URL): boolean { + return url.protocol === "https:" && url.hostname.endsWith(".googleapis.com"); +} + +function shouldRequestMediaDownload( + operation: GoogleDiscoveryOperation, + args: Record, +): boolean { + return ( + operation.supportsMediaDownload && + (typeof args.filename === "string" || typeof args.outputPath === "string") + ); +} + +function withMediaDownloadQuery(args: Record): Record { + const query = + args.query && typeof args.query === "object" && !Array.isArray(args.query) + ? (args.query as Record) + : {}; + return { + ...args, + query: { + ...query, + alt: "media", + }, + }; +} + +function googleAuthError(api: GoogleDiscoveryApiConfig, response: Response): CapletsError { + return new CapletsError( + response.status === 401 ? "AUTH_REQUIRED" : "AUTH_FAILED", + "Google Discovery authentication failed", + { + server: api.server, + status: response.status, + message: response.statusText, + authType: api.auth.type, + challenge: response.headers.get("www-authenticate") ? "[REDACTED]" : undefined, + ...(api.auth.type === "oauth2" || api.auth.type === "oidc" + ? { nextAction: "run_caplets_auth_login" } + : {}), + }, + ); +} + +function shouldSendDiscoveryAuth( + api: GoogleDiscoveryApiConfig, + protectedResourceOrigin: string, +): boolean { + return Boolean(api.discoveryUrl && new URL(api.discoveryUrl).origin === protectedResourceOrigin); +} + +function googleDiscoveryCacheKey(api: GoogleDiscoveryApiConfig): string { + return JSON.stringify({ + discoveryPath: api.discoveryPath, + discoveryUrl: api.discoveryUrl, + baseUrl: api.baseUrl, + includeOperations: api.includeOperations, + excludeOperations: api.excludeOperations, + }); +} diff --git a/packages/core/src/google-discovery/operations.ts b/packages/core/src/google-discovery/operations.ts new file mode 100644 index 00000000..6988d509 --- /dev/null +++ b/packages/core/src/google-discovery/operations.ts @@ -0,0 +1,334 @@ +import { googleDiscoverySchemaToJsonSchema } from "./schema"; +import type { + GoogleDiscoveryDocument, + GoogleDiscoveryMethod, + GoogleDiscoveryParameter, + GoogleDiscoveryResource, + GoogleDiscoverySchema, +} from "./types"; + +type GoogleDiscoveryHttpMethod = "get" | "put" | "post" | "delete" | "patch" | "head"; +type ParameterLocation = "path" | "query" | "header" | "body" | "media"; + +export type GoogleDiscoveryOperation = { + name: string; + method: GoogleDiscoveryHttpMethod; + path: string; + description?: string; + inputSchema: Record; + outputSchema?: Record; + readOnlyHint: boolean; + destructiveHint: boolean; + scopes: string[]; + supportsMediaUpload: boolean; + supportsMediaDownload: boolean; + mediaUpload?: { + accept?: string[]; + maxSize?: string; + }; + mediaUploadProtocols: Record; + parameterOrder: string[]; +}; + +export type DiscoveryOperationsOptions = { + server: string; + document: unknown; + includeOperations?: string[]; + excludeOperations?: string[]; +}; + +type MethodEntry = { + resourcePath: string[]; + methodKey: string; + method: GoogleDiscoveryMethod; +}; + +export function discoveryOperations( + options: DiscoveryOperationsOptions, +): GoogleDiscoveryOperation[] { + const document = validateGoogleDiscoveryDocument(options.document); + const schemas = document.schemas ?? {}; + const operations = collectDocumentMethods(document) + .map((entry) => operationFromMethod(options.server, document, schemas, entry)) + .filter((operation) => isIncluded(operation.name, options.includeOperations)) + .filter((operation) => !isExcluded(operation.name, options.excludeOperations)); + + return operations.sort((left, right) => left.name.localeCompare(right.name)); +} + +export function googleDiscoveryScopesForOperations( + operations: GoogleDiscoveryOperation[], +): string[] { + return [...new Set(operations.flatMap((operation) => operation.scopes))].sort(); +} + +function validateGoogleDiscoveryDocument(value: unknown): GoogleDiscoveryDocument { + if (!isRecord(value)) { + throw new Error("Invalid Google Discovery document: expected an object"); + } + if (value.kind !== undefined && value.kind !== "discovery#restDescription") { + throw new Error("Invalid Google Discovery document: expected kind discovery#restDescription"); + } + if (value.resources !== undefined && !isRecord(value.resources)) { + throw new Error("Invalid Google Discovery document: expected resources object"); + } + if (value.methods !== undefined && !isRecord(value.methods)) { + throw new Error("Invalid Google Discovery document: expected methods object"); + } + if (!isRecord(value.resources) && !isRecord(value.methods)) { + throw new Error("Invalid Google Discovery document: expected resources or methods object"); + } + if (value.schemas !== undefined && !isRecord(value.schemas)) { + throw new Error("Invalid Google Discovery document: expected schemas object"); + } + if (value.parameters !== undefined && !isRecord(value.parameters)) { + throw new Error("Invalid Google Discovery document: expected parameters object"); + } + return value as GoogleDiscoveryDocument; +} + +function collectDocumentMethods(document: GoogleDiscoveryDocument): MethodEntry[] { + const topLevel = Object.entries(document.methods ?? {}) + .filter((entry): entry is [string, GoogleDiscoveryMethod] => isRecord(entry[1])) + .map(([methodKey, method]) => ({ resourcePath: [], methodKey, method })); + return [...topLevel, ...collectMethods(document.resources ?? {})]; +} + +function collectMethods( + resources: Record, + resourcePath: string[] = [], +): MethodEntry[] { + const entries: MethodEntry[] = []; + for (const [resourceName, resource] of Object.entries(resources)) { + if (!isRecord(resource)) continue; + const nextPath = [...resourcePath, resourceName]; + for (const [methodKey, method] of Object.entries(resource.methods ?? {})) { + if (isRecord(method)) { + entries.push({ resourcePath: nextPath, methodKey, method }); + } + } + entries.push(...collectMethods(resource.resources ?? {}, nextPath)); + } + return entries; +} + +function operationFromMethod( + server: string, + document: GoogleDiscoveryDocument, + schemas: Record, + entry: MethodEntry, +): GoogleDiscoveryOperation { + const method = normalizedHttpMethod(entry.method.httpMethod); + const name = entry.method.id ?? [server, ...entry.resourcePath, entry.methodKey].join("."); + const scopes = selectGoogleDiscoveryScopes(entry.method.scopes); + const inputSchema = buildInputSchema(document.parameters ?? {}, entry.method, schemas); + const bodyOutputSchema = entry.method.response?.$ref + ? googleDiscoverySchemaToJsonSchema(entry.method.response, schemas) + : undefined; + const outputSchema = bodyOutputSchema ? structuredOutputSchema(bodyOutputSchema) : undefined; + const mediaUpload = + entry.method.mediaUpload?.accept || entry.method.mediaUpload?.maxSize + ? { + ...(entry.method.mediaUpload.accept ? { accept: entry.method.mediaUpload.accept } : {}), + ...(entry.method.mediaUpload.maxSize + ? { maxSize: entry.method.mediaUpload.maxSize } + : {}), + } + : undefined; + + return { + name, + method, + path: entry.method.path ?? entry.method.flatPath ?? "", + ...(entry.method.description + ? { description: collapseWhitespace(entry.method.description) } + : {}), + inputSchema, + ...(outputSchema ? { outputSchema } : {}), + readOnlyHint: method === "get" || method === "head", + destructiveHint: method === "delete" || /\.(delete|emptyTrash)$/u.test(name), + scopes, + supportsMediaUpload: entry.method.supportsMediaUpload === true, + supportsMediaDownload: entry.method.supportsMediaDownload === true, + ...(mediaUpload ? { mediaUpload } : {}), + mediaUploadProtocols: mediaUploadProtocols(entry.method), + parameterOrder: entry.method.parameterOrder ?? [], + }; +} + +function mediaUploadProtocols( + method: GoogleDiscoveryMethod, +): Record { + const protocols = { ...method.mediaUpload?.protocols }; + if (!protocols.multipart && protocols.simple?.multipart === true) { + protocols.multipart = protocols.simple; + } + return protocols; +} + +function selectGoogleDiscoveryScopes(scopes: string[] | undefined): string[] { + const unique = [...new Set(scopes ?? [])].sort(); + const preferred = unique.toSorted(compareScopePreference)[0]; + return preferred ? [preferred] : []; +} + +function compareScopePreference(left: string, right: string): number { + const leftRank = scopePreferenceRank(left); + const rightRank = scopePreferenceRank(right); + return leftRank - rightRank || right.length - left.length || left.localeCompare(right); +} + +function scopePreferenceRank(scope: string): number { + const suffix = scope.toLowerCase().split("/").pop() ?? scope.toLowerCase(); + const tokens = suffix.split(/[._:-]+/u); + if (tokens.includes("readonly")) return 0; + if (tokens.includes("file")) return 1; + if (tokens.includes("metadata") || tokens.includes("appdata")) return 2; + if (tokens.includes("read")) return 3; + if (suffix === "cloud-platform") return 5; + return 4; +} + +function structuredOutputSchema(bodySchema: Record): Record { + return { + type: "object", + additionalProperties: false, + required: ["status", "statusText", "headers"], + properties: { + status: { type: "number" }, + statusText: { type: "string" }, + headers: { + type: "object", + additionalProperties: false, + required: ["content-type"], + properties: { + "content-type": { type: "string" }, + }, + }, + body: bodySchema, + }, + }; +} + +function buildInputSchema( + globalParameters: Record, + method: GoogleDiscoveryMethod, + schemas: Record, +): Record { + const groups = new Map>(); + const requiredByGroup = new Map(); + const parameters = { ...globalParameters, ...method.parameters }; + + for (const [name, parameter] of Object.entries(parameters)) { + const location = parameter.location ?? "query"; + const group = groups.get(location) ?? {}; + group[name] = googleDiscoverySchemaToJsonSchema(parameter, schemas); + groups.set(location, group); + if (parameter.required === true) { + const required = requiredByGroup.get(location) ?? []; + required.push(name); + requiredByGroup.set(location, required); + } + } + + if (method.request?.$ref) { + groups.set("body", googleDiscoverySchemaToJsonSchema(method.request, schemas)); + } + if (method.supportsMediaUpload === true) { + groups.set("media", { + type: "object", + additionalProperties: false, + properties: { + path: { type: "string" }, + artifact: { type: "string" }, + dataUrl: { type: "string" }, + mimeType: { type: "string" }, + filename: { type: "string" }, + }, + }); + } + + const properties: Record = {}; + const required: string[] = []; + for (const location of ["path", "query", "header", "body", "media"] as const) { + const group = groups.get(location); + if (!group) continue; + if ((location === "body" || location === "media") && isJsonSchemaObject(group)) { + properties[location] = group; + } else { + const groupRequired = requiredByGroup.get(location) ?? []; + properties[location] = { + type: "object", + ...(groupRequired.length > 0 ? { required: groupRequired } : {}), + properties: group, + additionalProperties: false, + }; + } + if (location === "path" || requiredByGroup.has(location)) { + required.push(location); + } + } + if (method.supportsMediaDownload === true) { + properties.filename = { type: "string" }; + properties.outputPath = { type: "string" }; + } + + return { + type: "object", + ...(required.length > 0 ? { required } : {}), + properties, + additionalProperties: false, + }; +} + +function normalizedHttpMethod(method: string | undefined): GoogleDiscoveryHttpMethod { + const normalized = method?.toLowerCase(); + if ( + normalized === "get" || + normalized === "put" || + normalized === "post" || + normalized === "delete" || + normalized === "patch" || + normalized === "head" + ) { + return normalized; + } + return "get"; +} + +function isIncluded(name: string, includeOperations: string[] | undefined): boolean { + return ( + !includeOperations?.length || includeOperations.some((pattern) => globMatches(pattern, name)) + ); +} + +function isExcluded(name: string, excludeOperations: string[] | undefined): boolean { + return excludeOperations?.some((pattern) => globMatches(pattern, name)) === true; +} + +function globMatches(pattern: string, name: string): boolean { + const patternSegments = pattern.split("."); + const nameSegments = name.split("."); + if (patternSegments[0] === "*" && patternSegments.length < nameSegments.length) { + const suffix = patternSegments.slice(1); + return suffix.every( + (segment, index) => segment === nameSegments[nameSegments.length - suffix.length + index], + ); + } + if (patternSegments.length !== nameSegments.length) return false; + return patternSegments.every( + (segment, index) => segment === "*" || segment === nameSegments[index], + ); +} + +function isJsonSchemaObject(value: Record): boolean { + return value.type === "object" || "properties" in value || "additionalProperties" in value; +} + +function isRecord(value: unknown): value is Record { + return typeof value === "object" && value !== null && !Array.isArray(value); +} + +function collapseWhitespace(value: string): string { + return value.replace(/\s+/gu, " ").trim(); +} diff --git a/packages/core/src/google-discovery/request.ts b/packages/core/src/google-discovery/request.ts new file mode 100644 index 00000000..bdf6c5db --- /dev/null +++ b/packages/core/src/google-discovery/request.ts @@ -0,0 +1,208 @@ +import type { GoogleDiscoveryApiConfig } from "../config"; +import { FORBIDDEN_HEADERS, isAllowedRemoteUrl } from "../config/validation"; +import { CapletsError } from "../errors"; +import type { GoogleDiscoveryOperation } from "./operations"; + +export function buildGoogleDiscoveryUrl( + api: GoogleDiscoveryApiConfig, + operation: GoogleDiscoveryOperation, + args: Record, +): URL { + const base = api.baseUrl; + validateBaseUrl(api, base); + const url = buildOperationUrl( + base, + substitutePath(operation.path, asRecord(args.path), operation), + ); + appendQueryArgs(url, args); + return url; +} + +export function buildGoogleDiscoveryUploadUrl( + api: GoogleDiscoveryApiConfig, + operation: GoogleDiscoveryOperation, + uploadPath: string, + uploadType: "media" | "multipart" | "resumable", + args: Record, +): URL { + const base = api.baseUrl; + validateBaseUrl(api, base); + const url = buildUploadOperationUrl( + base, + substitutePath(uploadPath, asRecord(args.path), operation), + ); + appendQueryArgs(url, args); + url.searchParams.set("uploadType", uploadType); + return url; +} + +export function buildJsonRequestInit( + operation: GoogleDiscoveryOperation, + args: Record, + headers: Headers, +): RequestInit { + for (const [key, value] of Object.entries(asRecord(args.header))) { + if (value !== undefined && value !== null) { + const normalized = key.toLowerCase(); + if (FORBIDDEN_HEADERS.has(normalized)) { + throw new CapletsError("REQUEST_INVALID", `Header ${key} cannot be supplied by arguments`); + } + headers.set(key, serializeGoogleDiscoveryValue("header", key, value)); + } + } + if ("body" in args) { + headers.set("content-type", "application/json"); + return { + method: operation.method.toUpperCase(), + headers, + body: JSON.stringify(args.body), + redirect: "manual", + }; + } + return { method: operation.method.toUpperCase(), headers, redirect: "manual" }; +} + +function validateBaseUrl( + api: GoogleDiscoveryApiConfig, + base: string | undefined, +): asserts base is string { + if (!base) { + throw new CapletsError("CONFIG_INVALID", `${api.server} is missing Google Discovery baseUrl`); + } + if (!isAllowedRemoteUrl(base)) { + throw new CapletsError( + "CONFIG_INVALID", + `${api.server} Google Discovery baseUrl is not allowed`, + ); + } + const url = new URL(base); + if (url.username || url.password || url.search || url.hash) { + throw new CapletsError( + "CONFIG_INVALID", + `${api.server} Google Discovery baseUrl must not include credentials, query, or fragment`, + ); + } +} + +function buildOperationUrl(base: string, operationPath: string): URL { + if (/^[a-z][a-z0-9+.-]*:/iu.test(operationPath) || operationPath.startsWith("//")) { + throw new CapletsError( + "CONFIG_INVALID", + "Google Discovery operation path cannot change origin", + ); + } + const baseUrl = new URL(base); + const basePath = baseUrl.pathname.replace(/\/+$/u, ""); + const relativePath = operationPath.replace(/^\/+/u, ""); + assertSafeRelativePath(relativePath); + baseUrl.pathname = [basePath, relativePath].filter(Boolean).join("/"); + assertInsideBasePath(baseUrl, basePath); + return baseUrl; +} + +function buildUploadOperationUrl(base: string, uploadPath: string): URL { + if (/^[a-z][a-z0-9+.-]*:/iu.test(uploadPath) || uploadPath.startsWith("//")) { + throw new CapletsError("CONFIG_INVALID", "Google Discovery upload path cannot change origin"); + } + const baseUrl = new URL(base); + const relativePath = uploadPath.replace(/^\/+/u, ""); + assertSafeRelativePath(relativePath); + return uploadPath.startsWith("/") + ? new URL(`/${relativePath}`, baseUrl.origin) + : buildOperationUrl(base, uploadPath); +} + +function appendQueryArgs(url: URL, args: Record): void { + for (const [key, value] of Object.entries(asRecord(args.query))) { + if (value === undefined || value === null) continue; + if (Array.isArray(value)) { + for (const entry of value) { + url.searchParams.append(key, serializeGoogleDiscoveryValue("query", key, entry)); + } + continue; + } + url.searchParams.append(key, serializeGoogleDiscoveryValue("query", key, value)); + } +} + +function substitutePath( + path: string, + values: Record, + operation: GoogleDiscoveryOperation, +): string { + return path.replace(/\{([^}]+)\}/gu, (_match, expression: string) => { + const reserved = expression.startsWith("+"); + const name = reserved ? expression.slice(1) : expression; + const value = values[name]; + if (value === undefined || value === null || value === "") { + throw new CapletsError("REQUEST_INVALID", `Missing required path parameter ${name}`, { + tool: operation.name, + }); + } + const serialized = serializeGoogleDiscoveryValue("path", name, value); + return reserved ? encodeReservedPathValue(serialized) : encodeURIComponent(serialized); + }); +} + +function encodeReservedPathValue(value: string): string { + return value + .split("/") + .map((segment) => encodeURIComponent(segment)) + .join("/"); +} + +function assertSafeRelativePath(path: string): void { + for (const segment of path.split("/")) { + const decoded = safeDecodePathSegment(segment); + if (decoded === "." || decoded === "..") { + throw new CapletsError( + "CONFIG_INVALID", + "Google Discovery operation path cannot escape baseUrl", + ); + } + } +} + +function assertInsideBasePath(url: URL, basePath: string): void { + const normalizedBase = basePath === "" ? "/" : `${basePath}/`; + if (normalizedBase === "/") return; + const pathname = url.pathname.endsWith("/") ? url.pathname : `${url.pathname}/`; + if (pathname !== normalizedBase && !pathname.startsWith(normalizedBase)) { + throw new CapletsError( + "CONFIG_INVALID", + "Google Discovery operation path cannot escape baseUrl", + ); + } +} + +function safeDecodePathSegment(segment: string): string { + try { + return decodeURIComponent(segment); + } catch { + return segment; + } +} + +function serializeGoogleDiscoveryValue( + location: "path" | "query" | "header", + name: string, + value: unknown, +): string { + switch (typeof value) { + case "string": + case "number": + case "boolean": + return String(value); + default: + throw new CapletsError( + "REQUEST_INVALID", + `Google Discovery ${location} parameter ${name} must be a string, number, or boolean`, + ); + } +} + +function asRecord(value: unknown): Record { + return value && typeof value === "object" && !Array.isArray(value) + ? (value as Record) + : {}; +} diff --git a/packages/core/src/google-discovery/schema.ts b/packages/core/src/google-discovery/schema.ts new file mode 100644 index 00000000..0437049e --- /dev/null +++ b/packages/core/src/google-discovery/schema.ts @@ -0,0 +1,77 @@ +import type { GoogleDiscoverySchema } from "./types"; + +export function googleDiscoverySchemaToJsonSchema( + value: GoogleDiscoverySchema | undefined, + schemas: Record = {}, + seen = new Set(), +): Record { + if (!value) return {}; + if (value.$ref) { + const target = schemas[value.$ref]; + if (!target || seen.has(value.$ref)) return { type: "object", additionalProperties: true }; + return googleDiscoverySchemaToJsonSchema(target, schemas, new Set([...seen, value.$ref])); + } + + const type = discoveryTypeToJsonSchemaType(value.type); + const converted: Record = {}; + if (value.description) converted.description = collapseWhitespace(value.description); + if (type) converted.type = type; + if (value.format) converted.format = value.format; + if (value.enum) converted.enum = value.enum; + const defaultValue = convertedDefault(value.default, type); + if (defaultValue !== undefined) converted.default = defaultValue; + + if (value.repeated) { + return { + ...(converted.description ? { description: converted.description } : {}), + type: "array", + items: omit(converted, ["description", "default"]), + }; + } + + if (value.items) converted.items = googleDiscoverySchemaToJsonSchema(value.items, schemas, seen); + if (value.properties) { + converted.type = converted.type ?? "object"; + converted.properties = Object.fromEntries( + Object.entries(value.properties).map(([key, schema]) => [ + key, + googleDiscoverySchemaToJsonSchema(schema, schemas, seen), + ]), + ); + converted.additionalProperties = false; + } + if (typeof value.additionalProperties === "boolean") { + converted.additionalProperties = value.additionalProperties; + } else if (value.additionalProperties) { + converted.additionalProperties = googleDiscoverySchemaToJsonSchema( + value.additionalProperties, + schemas, + seen, + ); + } + + return converted; +} + +function discoveryTypeToJsonSchemaType(type: string | undefined): string | undefined { + if (type === "any") return "object"; + return type; +} + +function convertedDefault(value: unknown, type: string | undefined): unknown { + if (value === undefined) return undefined; + if (type === "boolean" && typeof value === "string") return value === "true"; + if ((type === "integer" || type === "number") && typeof value === "string") { + const number = Number(value); + return Number.isFinite(number) ? number : value; + } + return value; +} + +function collapseWhitespace(value: string): string { + return value.replace(/\s+/gu, " ").trim(); +} + +function omit(value: Record, keys: string[]): Record { + return Object.fromEntries(Object.entries(value).filter(([key]) => !keys.includes(key))); +} diff --git a/packages/core/src/google-discovery/types.ts b/packages/core/src/google-discovery/types.ts new file mode 100644 index 00000000..d4be7293 --- /dev/null +++ b/packages/core/src/google-discovery/types.ts @@ -0,0 +1,61 @@ +export type GoogleDiscoveryDocument = { + kind?: string; + id?: string; + name?: string; + version?: string; + title?: string; + rootUrl?: string; + servicePath?: string; + baseUrl?: string; + auth?: { oauth2?: { scopes?: Record } }; + parameters?: Record; + schemas?: Record; + methods?: Record; + resources?: Record; +}; + +export type GoogleDiscoveryResource = { + methods?: Record; + resources?: Record; +}; + +export type GoogleDiscoveryMethod = { + id?: string; + path?: string; + flatPath?: string; + httpMethod?: string; + description?: string; + parameters?: Record; + parameterOrder?: string[]; + request?: { $ref?: string }; + response?: { $ref?: string }; + scopes?: string[]; + supportsMediaUpload?: boolean; + supportsMediaDownload?: boolean; + mediaUpload?: { + accept?: string[]; + maxSize?: string; + protocols?: Record; + }; +}; + +export type GoogleDiscoveryParameter = GoogleDiscoverySchema & { + location?: "path" | "query" | "header" | "body" | "media"; + required?: boolean; + repeated?: boolean; + deprecated?: boolean; +}; + +export type GoogleDiscoverySchema = { + id?: string; + $ref?: string; + type?: string; + format?: string; + description?: string; + default?: unknown; + enum?: string[]; + repeated?: boolean; + properties?: Record; + items?: GoogleDiscoverySchema; + additionalProperties?: GoogleDiscoverySchema | boolean; +}; diff --git a/packages/core/src/http-actions.ts b/packages/core/src/http-actions.ts index 854c8d86..cdb37596 100644 --- a/packages/core/src/http-actions.ts +++ b/packages/core/src/http-actions.ts @@ -9,7 +9,8 @@ import { type CompactTool, } from "./downstream"; import { CapletsError, toSafeError } from "./errors"; -import { isAbortError, parseHttpBody, readLimitedText } from "./http/utils"; +import { readHttpLikeResponse } from "./http/response"; +import { isAbortError } from "./http/utils"; import type { ServerRegistry } from "./registry"; import { markdownStructuredContent } from "./result-content"; import { searchToolList } from "./tool-search"; @@ -20,7 +21,12 @@ type HttpActionOperation = HttpActionConfig & { name: string }; export class HttpActionManager { constructor( private registry: ServerRegistry, - private readonly options: { authDir?: string } = {}, + private readonly options: { + authDir?: string; + artifactDir?: string; + exposeLocalArtifactPaths?: boolean; + maxInlineBytes?: number; + } = {}, ) {} updateRegistry(registry: ServerRegistry): void { @@ -100,7 +106,17 @@ export class HttpActionManager { }, ); } - const parsed = await readResponse(response, api, Date.now() - startedAt); + const parsed = { + ...(await readHttpLikeResponse(response, { + capletId: api.server, + method: operation.method, + ...(this.options.artifactDir ? { artifactDir: this.options.artifactDir } : {}), + ...(this.options.exposeLocalArtifactPaths === false ? { exposeLocalPath: false } : {}), + maxInlineBytes: this.options.maxInlineBytes ?? api.maxResponseBytes, + maxBytes: api.maxResponseBytes, + })), + elapsedMs: Date.now() - startedAt, + }; return { content: markdownStructuredContent(parsed, { title: `${api.name} call_tool ${toolName}`, @@ -371,32 +387,6 @@ async function authHeaders(api: HttpApiConfig, authDir?: string): Promise> { - const contentType = response.headers.get("content-type") ?? ""; - const text = await readLimitedText(response, { - maxBytes: maxResponseBytes(api), - errorMessage: "HTTP action response exceeded byte limit", - }); - const body = parseHttpBody(contentType, text); - return { - status: response.status, - statusText: response.statusText, - headers: { - "content-type": contentType, - }, - ...(body === undefined ? {} : { body }), - elapsedMs, - }; -} - -function maxResponseBytes(api: HttpApiConfig): number { - return api.maxResponseBytes; -} - function validateBaseUrl(api: HttpApiConfig): void { if (!isAllowedRemoteUrl(api.baseUrl)) { throw new CapletsError("CONFIG_INVALID", `${api.server} HTTP API baseUrl is not allowed`); diff --git a/packages/core/src/http/response.ts b/packages/core/src/http/response.ts new file mode 100644 index 00000000..6866ca7b --- /dev/null +++ b/packages/core/src/http/response.ts @@ -0,0 +1,202 @@ +import type { MediaArtifact } from "../media"; +import { CapletsError } from "../errors"; +import { writeMediaArtifact } from "../media"; +import { DEFAULT_MAX_RESPONSE_BYTES, parseHttpBody } from "./utils"; + +export type ReadHttpLikeResponseOptions = { + capletId: string; + method?: string; + artifactDir?: string; + outputPath?: string; + filename?: string; + maxInlineBytes?: number; + maxBytes?: number; + forceArtifact?: boolean; + exposeLocalPath?: boolean; +}; + +export async function readHttpLikeResponse( + response: Response, + options: ReadHttpLikeResponseOptions, +): Promise> { + const contentType = response.headers.get("content-type") ?? ""; + const mimeType = mimeFromContentType(contentType); + const maxInlineBytes = options.maxInlineBytes ?? DEFAULT_MAX_RESPONSE_BYTES; + const maxBytes = options.maxBytes ?? DEFAULT_MAX_RESPONSE_BYTES; + const method = options.method?.toUpperCase(); + await rejectOversizedContentLength(response, maxBytes, method); + if (method === "HEAD") { + return responseEnvelope(response, contentType); + } + + if (!options.forceArtifact && shouldInline(response, mimeType)) { + const inline = await readInlineCandidate(response, { maxInlineBytes, maxBytes }); + if (!inline.exceeded) { + const body = parseHttpBody(contentType, new TextDecoder().decode(inline.bytes)); + return responseEnvelope(response, contentType, body); + } + const artifact = await writeResponseArtifact(response, options, mimeType, inline.bytes); + return responseEnvelope(response, contentType, { artifact }); + } + + const bytes = await readBoundedBytes(response, maxBytes); + const artifact = await writeResponseArtifact(response, options, mimeType, bytes); + return responseEnvelope(response, contentType, { artifact }); +} + +async function readInlineCandidate( + response: Response, + options: { maxInlineBytes: number; maxBytes: number }, +): Promise<{ bytes: Buffer; exceeded: boolean }> { + if (!response.body) { + return { bytes: Buffer.alloc(0), exceeded: false }; + } + const reader = response.body.getReader(); + const chunks: Uint8Array[] = []; + let bytes = 0; + let exceeded = false; + while (true) { + const { done, value } = await reader.read(); + if (done) break; + if (value) { + bytes += value.byteLength; + if (bytes > options.maxBytes) { + await reader.cancel(); + throw responseExceededLimit(options.maxBytes); + } + if (bytes > options.maxInlineBytes) exceeded = true; + chunks.push(value); + } + } + return { bytes: Buffer.concat(chunks), exceeded }; +} + +async function readBoundedBytes(response: Response, maxBytes: number): Promise { + if (!response.body) { + return Buffer.alloc(0); + } + const reader = response.body.getReader(); + const chunks: Uint8Array[] = []; + let bytes = 0; + while (true) { + const { done, value } = await reader.read(); + if (done) break; + if (value) { + bytes += value.byteLength; + if (bytes > maxBytes) { + await reader.cancel(); + throw responseExceededLimit(maxBytes); + } + chunks.push(value); + } + } + return Buffer.concat(chunks); +} + +async function rejectOversizedContentLength( + response: Response, + maxBytes: number, + method?: string, +): Promise { + if (method === "HEAD") return; + const contentLength = response.headers.get("content-length"); + if (!contentLength) return; + const byteLength = Number.parseInt(contentLength, 10); + if (Number.isFinite(byteLength) && byteLength > maxBytes) { + await response.body?.cancel().catch(() => {}); + throw responseExceededLimit(maxBytes); + } +} + +function responseExceededLimit(maxBytes: number): CapletsError { + return new CapletsError( + "DOWNSTREAM_PROTOCOL_ERROR", + `HTTP response exceeded byte limit ${maxBytes}`, + ); +} + +async function writeResponseArtifact( + response: Response, + options: ReadHttpLikeResponseOptions, + mimeType: string, + bytes: Buffer, +): Promise { + return await writeMediaArtifact({ + capletId: options.capletId, + ...(options.artifactDir ? { rootDir: options.artifactDir } : {}), + ...(response.ok && options.outputPath ? { outputPath: options.outputPath } : {}), + ...(options.exposeLocalPath === false ? { exposeLocalPath: false } : {}), + suggestedFilename: + options.filename ?? filenameFromContentDisposition(response) ?? "response.bin", + ...(mimeType ? { mimeType } : {}), + bytes, + }); +} + +function responseEnvelope( + response: Response, + contentType: string, + body?: unknown, +): Record { + return { + status: response.status, + statusText: response.statusText, + headers: { + "content-type": contentType, + }, + ...(body === undefined ? {} : { body }), + }; +} + +function shouldInline(response: Response, mimeType: string): boolean { + if (isAttachment(response)) { + return false; + } + return ( + mimeType === "" || + mimeType === "application/json" || + mimeType.endsWith("+json") || + mimeType.endsWith("/json") || + mimeType.startsWith("text/") + ); +} + +function isAttachment(response: Response): boolean { + return /\battachment\b/iu.test(response.headers.get("content-disposition") ?? ""); +} + +function mimeFromContentType(contentType: string): string { + return contentType.split(";")[0]?.toLowerCase().trim() ?? ""; +} + +function filenameFromContentDisposition(response: Response): string | undefined { + const contentDisposition = response.headers.get("content-disposition"); + if (!contentDisposition) { + return undefined; + } + return parseRfc5987Filename(contentDisposition) ?? parseQuotedFilename(contentDisposition); +} + +function parseRfc5987Filename(contentDisposition: string): string | undefined { + const match = /(?:^|;)\s*filename\*=([^;]+)/iu.exec(contentDisposition); + const value = match?.[1]?.trim(); + if (!value) { + return undefined; + } + const encoded = value.replace(/^UTF-8''/iu, ""); + try { + return decodeURIComponent(encoded.replace(/^"|"$/gu, "")); + } catch { + return encoded; + } +} + +function parseQuotedFilename(contentDisposition: string): string | undefined { + const quoted = /(?:^|;)\s*filename="([^"]+)"/iu.exec(contentDisposition)?.[1]; + if (quoted) { + return quoted; + } + return /(?:^|;)\s*filename=([^;]+)/iu.exec(contentDisposition)?.[1]?.trim(); +} + +export type { MediaArtifact }; diff --git a/packages/core/src/media/artifacts.ts b/packages/core/src/media/artifacts.ts new file mode 100644 index 00000000..d59a24dd --- /dev/null +++ b/packages/core/src/media/artifacts.ts @@ -0,0 +1,320 @@ +import { createHash, randomUUID } from "node:crypto"; +import { + chmodSync, + existsSync, + lstatSync, + mkdirSync, + readFileSync, + rmSync, + statSync, + writeFileSync, +} from "node:fs"; +import { basename, dirname, isAbsolute, relative, resolve } from "node:path"; +import { DEFAULT_ARTIFACT_DIR } from "../config/paths"; +import { CapletsError } from "../errors"; + +export type MediaArtifact = { + uri: string; + path?: string; + filename: string; + mimeType?: string; + byteLength: number; + sha256: string; +}; + +export type WriteMediaArtifactInput = { + rootDir?: string; + capletId: string; + callId?: string; + suggestedFilename?: string; + outputPath?: string; + mimeType?: string; + bytes: Uint8Array | Buffer; + exposeLocalPath?: boolean; +}; + +type StoredMediaArtifactMetadata = { + mimeType?: string; +}; + +type ParsedArtifactUri = { + capletId: string; + callId: string; + filename: string; +}; + +export function artifactUri(capletId: string, callId: string, filename: string): string { + return `caplets://artifacts/${encodeURIComponent(capletId)}/${encodeURIComponent( + callId, + )}/${encodeURIComponent(filename)}`; +} + +export async function writeMediaArtifact(input: WriteMediaArtifactInput): Promise { + const rootDir = resolve(input.rootDir ?? DEFAULT_ARTIFACT_DIR); + const capletId = requiredSafePathSegment(input.capletId, "capletId"); + const callId = safePathSegment(input.callId ?? defaultCallId(), "call"); + const filename = safeFilename( + input.suggestedFilename ?? (input.outputPath ? basename(input.outputPath) : "response.bin"), + ); + const target = input.outputPath + ? assertInsideRoot(rootDir, input.outputPath) + : assertInsideRoot(rootDir, resolve(rootDir, capletId, callId, filename)); + rejectSymlinkPathComponents(rootDir, target, true); + const uriParts = input.outputPath + ? uriPartsForOutputPath(rootDir, target) + : { capletId, callId, filename: safeFilename(basename(target)) }; + const bytes = Buffer.from(input.bytes); + + mkdirSync(dirname(target), { recursive: true, mode: 0o700 }); + writeFileSync(target, bytes, { mode: 0o600 }); + chmodSync(target, 0o600); + + const artifactFilename = uriParts.filename; + writeArtifactMetadata(target, input.mimeType ? { mimeType: input.mimeType } : {}); + return { + uri: artifactUri(uriParts.capletId, uriParts.callId, artifactFilename), + ...(input.exposeLocalPath === false ? {} : { path: target }), + filename: artifactFilename, + ...(input.mimeType ? { mimeType: input.mimeType } : {}), + byteLength: bytes.byteLength, + sha256: sha256(bytes), + }; +} + +export function resolveMediaArtifact( + uri: string, + options: { artifactRoot?: string; maxBytes?: number } = {}, +): MediaArtifact { + const parsed = parseArtifactUri(uri); + const rootDir = resolve(options.artifactRoot ?? DEFAULT_ARTIFACT_DIR); + const path = assertInsideRoot( + rootDir, + resolve(rootDir, parsed.capletId, parsed.callId, parsed.filename), + ); + rejectSymlinkPathComponents(rootDir, path, true); + + if (!existsSync(path)) { + throw new CapletsError("REQUEST_INVALID", "Media artifact was not found"); + } + + const stat = statSync(path); + if (!stat.isFile()) { + throw new CapletsError("REQUEST_INVALID", "Media artifact must resolve to a file"); + } + if (options.maxBytes !== undefined && stat.size > options.maxBytes) { + throw new CapletsError("REQUEST_INVALID", `media exceeds byte limit ${options.maxBytes}`); + } + + const bytes = readFileSync(path); + const metadata = readArtifactMetadata(path); + return { + uri, + path, + filename: parsed.filename, + ...(metadata?.mimeType ? { mimeType: metadata.mimeType } : {}), + byteLength: bytes.byteLength, + sha256: sha256(bytes), + }; +} + +function parseArtifactUri(uri: string): ParsedArtifactUri { + let url: URL; + try { + url = new URL(uri); + } catch { + throw new CapletsError("REQUEST_INVALID", "Media artifact URI is invalid"); + } + + if (url.protocol !== "caplets:" || url.hostname !== "artifacts") { + throw new CapletsError( + "REQUEST_INVALID", + "Media artifact URI must start with caplets://artifacts/", + ); + } + + const parts = url.pathname.split("/").filter(Boolean); + if (parts.length !== 3) { + throw new CapletsError("REQUEST_INVALID", "Media artifact URI is missing required parts"); + } + + return { + capletId: decodeSafePathSegment(parts[0]!, "capletId"), + callId: decodeSafePathSegment(parts[1]!, "callId"), + filename: decodeSafeFilename(parts[2]!), + }; +} + +function decodeSafePathSegment(value: string, label: string): string { + const decoded = decodeArtifactUriPart(value); + const safe = safePathSegment(decoded, ""); + if (!safe || safe !== decoded) { + throw new CapletsError("REQUEST_INVALID", `Media artifact URI ${label} is invalid`); + } + return safe; +} + +function decodeSafeFilename(value: string): string { + const decoded = decodeArtifactUriPart(value); + const safe = safeFilename(decoded); + if (safe !== decoded) { + throw new CapletsError("REQUEST_INVALID", "Media artifact URI filename is invalid"); + } + return safe; +} + +function decodeArtifactUriPart(value: string): string { + try { + return decodeURIComponent(value); + } catch { + throw new CapletsError("REQUEST_INVALID", "Media artifact URI contains invalid encoding"); + } +} + +function assertInsideRoot(rootDir: string, candidate: string): string { + if (!isAbsolute(candidate)) { + throw new CapletsError("REQUEST_INVALID", "Media artifact outputPath must be absolute"); + } + + const resolvedRoot = resolve(rootDir); + const resolved = resolve(candidate); + const rel = relative(resolvedRoot, resolved); + if (rel.startsWith("..") || isAbsolute(rel)) { + throw new CapletsError( + "REQUEST_INVALID", + "Media artifact outputPath must stay inside the artifact root", + ); + } + return resolved; +} + +function rejectSymlinkPathComponents( + rootDir: string, + target: string, + includeTarget: boolean, +): void { + const resolvedRoot = resolve(rootDir); + rejectSymlinkRoot(resolvedRoot); + const rel = relative(resolvedRoot, resolve(target)); + const parts = rel.split(/[\\/]+/u).filter(Boolean); + let current = resolvedRoot; + const limit = includeTarget ? parts.length : Math.max(0, parts.length - 1); + for (let index = 0; index < limit; index += 1) { + current = resolve(current, parts[index]!); + try { + if (lstatSync(current).isSymbolicLink()) { + throw new CapletsError("REQUEST_INVALID", "Media artifact path must not contain symlinks"); + } + } catch (error) { + if (error instanceof CapletsError) throw error; + if ((error as NodeJS.ErrnoException).code === "ENOENT") return; + throw error; + } + } +} + +function rejectSymlinkRoot(rootDir: string): void { + try { + if (lstatSync(rootDir).isSymbolicLink()) { + throw new CapletsError("REQUEST_INVALID", "Media artifact root must not be a symlink"); + } + } catch (error) { + if (error instanceof CapletsError) throw error; + if ((error as NodeJS.ErrnoException).code === "ENOENT") return; + throw error; + } +} + +function uriPartsForOutputPath( + rootDir: string, + target: string, +): { capletId: string; callId: string; filename: string } { + const rel = relative(resolve(rootDir), target); + const parts = rel.split(/[\\/]+/u).filter(Boolean); + if (parts.length !== 3) { + throw new CapletsError( + "REQUEST_INVALID", + "Media artifact outputPath must be under ///", + ); + } + return { + capletId: requireAlreadySafePathSegment(parts[0]!, "capletId"), + callId: requireAlreadySafePathSegment(parts[1]!, "callId"), + filename: requireAlreadySafeFilename(parts[2]!), + }; +} + +function requiredSafePathSegment(value: string, label: string): string { + const safe = safePathSegment(value, ""); + if (!safe) { + throw new CapletsError("REQUEST_INVALID", `Media artifact ${label} is required`); + } + return safe; +} + +function requireAlreadySafePathSegment(value: string, label: string): string { + const safe = requiredSafePathSegment(value, label); + if (safe !== value) { + throw new CapletsError("REQUEST_INVALID", `Media artifact outputPath ${label} is invalid`); + } + return safe; +} + +function requireAlreadySafeFilename(value: string): string { + const safe = safeFilename(value); + if (safe !== value) { + throw new CapletsError("REQUEST_INVALID", "Media artifact outputPath filename is invalid"); + } + return safe; +} + +function safePathSegment(value: string, fallback: string): string { + return safeFilename(value, fallback); +} + +function safeFilename(value: string, fallback = "response.bin"): string { + const name = basename(value) + .trim() + .replace(/[^\w.-]+/gu, "_"); + return name && name !== "." && name !== ".." ? name : fallback; +} + +function defaultCallId(): string { + return `${new Date().toISOString().replace(/[:.]/gu, "-")}-${randomUUID()}`; +} + +function sha256(bytes: Buffer): string { + return createHash("sha256").update(bytes).digest("hex"); +} + +function artifactMetadataPath(path: string): string { + return `${path}.caplets.json`; +} + +function writeArtifactMetadata(path: string, metadata: StoredMediaArtifactMetadata): void { + const metadataPath = artifactMetadataPath(path); + if (!metadata.mimeType) { + rmSync(metadataPath, { force: true }); + return; + } + writeFileSync(metadataPath, `${JSON.stringify(metadata)}\n`, { mode: 0o600 }); + chmodSync(metadataPath, 0o600); +} + +function readArtifactMetadata(path: string): StoredMediaArtifactMetadata | undefined { + const metadataPath = artifactMetadataPath(path); + if (!existsSync(metadataPath)) return undefined; + + let parsed: unknown; + try { + parsed = JSON.parse(readFileSync(metadataPath, "utf8")); + } catch { + throw new CapletsError("REQUEST_INVALID", "Media artifact metadata is invalid"); + } + + if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) { + throw new CapletsError("REQUEST_INVALID", "Media artifact metadata is invalid"); + } + + const value = parsed as Record; + return typeof value.mimeType === "string" && value.mimeType ? { mimeType: value.mimeType } : {}; +} diff --git a/packages/core/src/media/index.ts b/packages/core/src/media/index.ts new file mode 100644 index 00000000..61b743f2 --- /dev/null +++ b/packages/core/src/media/index.ts @@ -0,0 +1,2 @@ +export * from "./artifacts"; +export * from "./input"; diff --git a/packages/core/src/media/input.ts b/packages/core/src/media/input.ts new file mode 100644 index 00000000..8435d3ec --- /dev/null +++ b/packages/core/src/media/input.ts @@ -0,0 +1,168 @@ +import { readFileSync, statSync } from "node:fs"; +import type { Stats } from "node:fs"; +import { basename, extname } from "node:path"; +import { CapletsError } from "../errors"; +import { resolveMediaArtifact } from "./artifacts"; + +export type MediaInput = + | { path: string; artifact?: never; dataUrl?: never; filename?: string; mimeType?: string } + | { artifact: string; path?: never; dataUrl?: never; filename?: string; mimeType?: string } + | { dataUrl: string; path?: never; artifact?: never; filename?: string; mimeType?: string }; + +export type ResolvedMediaInput = { + bytes: Buffer; + filename: string; + mimeType?: string; +}; + +const DEFAULT_MAX_MEDIA_BYTES = 100 * 1024 * 1024; + +export async function readMediaInput( + input: unknown, + options: { artifactRoot?: string; maxBytes?: number; allowLocalPaths?: boolean } = {}, +): Promise { + if (!input || typeof input !== "object" || Array.isArray(input)) { + throw new CapletsError("REQUEST_INVALID", "media must be an object"); + } + + const media = input as Record; + const sources = ["path", "artifact", "dataUrl"].filter((key) => typeof media[key] === "string"); + if (sources.length !== 1) { + throw new CapletsError( + "REQUEST_INVALID", + "media must define exactly one of path, artifact, or dataUrl", + ); + } + + const filename = typeof media.filename === "string" ? media.filename : undefined; + const mimeType = typeof media.mimeType === "string" ? media.mimeType : undefined; + + if (typeof media.path === "string") { + if (options.allowLocalPaths === false) { + throw new CapletsError("REQUEST_INVALID", "media.path is not available in this runtime"); + } + const stat = statMediaFile(media.path); + enforceSize(stat.size, options.maxBytes); + const resolvedFilename = filename ?? basename(media.path); + const resolvedMimeType = mimeType ?? mimeTypeFromFilename(resolvedFilename); + return { + bytes: readMediaFile(media.path), + filename: resolvedFilename, + ...(resolvedMimeType ? { mimeType: resolvedMimeType } : {}), + }; + } + + if (typeof media.artifact === "string") { + const artifactOptions: { artifactRoot?: string; maxBytes?: number } = {}; + if (options.artifactRoot !== undefined) artifactOptions.artifactRoot = options.artifactRoot; + artifactOptions.maxBytes = options.maxBytes ?? DEFAULT_MAX_MEDIA_BYTES; + const artifact = resolveMediaArtifact(media.artifact, artifactOptions); + if (!artifact.path) { + throw new CapletsError("REQUEST_INVALID", "Media artifact cannot be read from this runtime"); + } + const resolvedMimeType = mimeType ?? artifact.mimeType; + return { + bytes: readMediaFile(artifact.path), + filename: filename ?? artifact.filename, + ...(resolvedMimeType ? { mimeType: resolvedMimeType } : {}), + }; + } + + const dataUrlOptions: { filename?: string; mimeType?: string; maxBytes?: number } = {}; + if (filename !== undefined) dataUrlOptions.filename = filename; + if (mimeType !== undefined) dataUrlOptions.mimeType = mimeType; + if (options.maxBytes !== undefined) dataUrlOptions.maxBytes = options.maxBytes; + return readDataUrl(media.dataUrl as string, dataUrlOptions); +} + +function mimeTypeFromFilename(filename: string): string | undefined { + switch (extname(filename).toLowerCase()) { + case ".csv": + return "text/csv"; + case ".gif": + return "image/gif"; + case ".htm": + case ".html": + return "text/html"; + case ".jpeg": + case ".jpg": + return "image/jpeg"; + case ".json": + return "application/json"; + case ".pdf": + return "application/pdf"; + case ".png": + return "image/png"; + case ".txt": + return "text/plain"; + case ".webp": + return "image/webp"; + case ".xml": + return "application/xml"; + default: + return undefined; + } +} + +function readDataUrl( + dataUrl: string, + options: { filename?: string; mimeType?: string; maxBytes?: number }, +): ResolvedMediaInput { + const match = /^data:([^;,]+)(?:;[^,;=]+=[^,;]*)*;base64,([A-Za-z0-9+/=]*)$/u.exec(dataUrl); + if (!match) { + throw new CapletsError("REQUEST_INVALID", "media.dataUrl must be a base64 data URL"); + } + + const dataMimeType = match[1] ?? ""; + const base64 = match[2] ?? ""; + if (!dataMimeType || !isStrictBase64(base64)) { + throw new CapletsError("REQUEST_INVALID", "media.dataUrl must be a base64 data URL"); + } + + enforceSize(decodedBase64Length(base64), options.maxBytes); + const bytes = Buffer.from(base64, "base64"); + return { + bytes, + filename: options.filename ?? "media.bin", + mimeType: options.mimeType ?? dataMimeType, + }; +} + +function isStrictBase64(value: string): boolean { + return ( + value.length % 4 === 0 && + /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/u.test(value) + ); +} + +function decodedBase64Length(value: string): number { + const padding = value.endsWith("==") ? 2 : value.endsWith("=") ? 1 : 0; + return (value.length / 4) * 3 - padding; +} + +function statMediaFile(path: string): Stats { + try { + const stat = statSync(path); + if (!stat.isFile()) { + throw new CapletsError("REQUEST_INVALID", "media.path must reference a file"); + } + return stat; + } catch (error) { + if (error instanceof CapletsError) throw error; + throw new CapletsError("REQUEST_INVALID", "media.path could not be read"); + } +} + +function readMediaFile(path: string): Buffer { + try { + return readFileSync(path); + } catch { + throw new CapletsError("REQUEST_INVALID", "media file could not be read"); + } +} + +function enforceSize(size: number, maxBytes = DEFAULT_MAX_MEDIA_BYTES): void { + if (size > maxBytes) { + throw new CapletsError("REQUEST_INVALID", `media exceeds byte limit ${maxBytes}`); + } +} diff --git a/packages/core/src/native/service.ts b/packages/core/src/native/service.ts index 87dbf9d6..3e86cce4 100644 --- a/packages/core/src/native/service.ts +++ b/packages/core/src/native/service.ts @@ -50,6 +50,7 @@ export type NativeCapletsServiceOptions = NativeCapletsServiceResolutionInput & configPath?: string; projectConfigPath?: string; authDir?: string; + exposeLocalArtifactPaths?: boolean; watchDebounceMs?: number; watch?: boolean; writeErr?: (value: string) => void; @@ -138,7 +139,7 @@ class DefaultNativeCapletsService implements NativeCapletsService { writeErr: this.writeErr, }); this.postReloadRefresh = this.refreshExposureSnapshot({ - emitToolsChanged: this.hasDirectMcpExposure(), + emitToolsChanged: this.hasSnapshotBackedDirectExposure(), }); this.unsubscribeEngineReload = this.engine.onReload(() => { this.postReloadRefresh = this.refreshExposureSnapshot({ emitToolsChanged: true }); @@ -251,7 +252,7 @@ class DefaultNativeCapletsService implements NativeCapletsService { const directTools = snapshot?.directTools .filter((entry) => entry.caplet.server === caplet.server) - .map((entry) => this.directMcpTool(caplet, entry)) ?? []; + .map((entry) => this.directDiscoveredTool(caplet, entry)) ?? []; return [ ...directTools, ...mcpPrimitiveNativeTools(caplet, snapshot).map((operationName) => @@ -262,10 +263,14 @@ class DefaultNativeCapletsService implements NativeCapletsService { ), ]; } - return []; + return ( + snapshot?.directTools + .filter((entry) => entry.caplet.server === caplet.server) + .map((entry) => this.directDiscoveredTool(caplet, entry)) ?? [] + ); } - private directMcpTool( + private directDiscoveredTool( caplet: ReturnType[number], entry: DirectToolRegistration, ): NativeCapletTool { @@ -325,9 +330,10 @@ class DefaultNativeCapletsService implements NativeCapletsService { } } - private hasDirectMcpExposure(): boolean { + private hasSnapshotBackedDirectExposure(): boolean { return this.engine.enabledServers().some((caplet) => { - if (caplet.backend !== "mcp" || caplet.setup || caplet.projectBinding?.required) return false; + if (caplet.setup || caplet.projectBinding?.required) return false; + if (caplet.backend === "http" || caplet.backend === "cli") return false; return resolveExposure(caplet.exposure, this.engine.currentConfig().options.exposure).direct; }); } diff --git a/packages/core/src/observed-output-shapes/key.ts b/packages/core/src/observed-output-shapes/key.ts index 08283d46..930a8d65 100644 --- a/packages/core/src/observed-output-shapes/key.ts +++ b/packages/core/src/observed-output-shapes/key.ts @@ -67,6 +67,16 @@ function nonSecretBackendIdentity(caplet: CapletConfig): unknown { specUrl: caplet.specUrl, baseUrl: caplet.baseUrl, }; + case "googleDiscovery": + return { + backend: caplet.backend, + server: caplet.server, + discoveryPath: caplet.discoveryPath, + discoveryUrl: caplet.discoveryUrl, + baseUrl: caplet.baseUrl, + includeOperations: caplet.includeOperations, + excludeOperations: caplet.excludeOperations, + }; case "graphql": return { backend: caplet.backend, diff --git a/packages/core/src/openapi.ts b/packages/core/src/openapi.ts index 86d828db..cbc22db4 100644 --- a/packages/core/src/openapi.ts +++ b/packages/core/src/openapi.ts @@ -11,12 +11,14 @@ import { type CompactTool, } from "./downstream"; import { CapletsError, toSafeError } from "./errors"; -import { isAbortError, parseHttpBody, readLimitedText } from "./http/utils"; +import { readHttpLikeResponse } from "./http/response"; +import { isAbortError, readLimitedText } from "./http/utils"; import type { ServerRegistry } from "./registry"; import { markdownStructuredContent } from "./result-content"; import { searchToolList } from "./tool-search"; const HTTP_METHODS = ["get", "put", "post", "delete", "options", "head", "patch", "trace"] as const; +const DEFAULT_OPENAPI_RESPONSE_MAX_BYTES = 100 * 1024 * 1024; const JSON_CONTENT_TYPES = ["application/json"]; const FORBIDDEN_ARGUMENT_HEADERS = new Set([ "accept", @@ -60,7 +62,11 @@ export class OpenApiManager { constructor( private registry: ServerRegistry, - private readonly options: { authDir?: string } = {}, + private readonly options: { + authDir?: string; + artifactDir?: string; + exposeLocalArtifactPaths?: boolean; + } = {}, ) {} updateRegistry(registry: ServerRegistry): void { @@ -151,7 +157,13 @@ export class OpenApiManager { }, ); } - const parsed = await readResponse(response); + const parsed = await readHttpLikeResponse(response, { + capletId: endpoint.server, + method: operation.method, + ...(this.options.artifactDir ? { artifactDir: this.options.artifactDir } : {}), + ...(this.options.exposeLocalArtifactPaths === false ? { exposeLocalPath: false } : {}), + maxBytes: DEFAULT_OPENAPI_RESPONSE_MAX_BYTES, + }); return { content: markdownStructuredContent(parsed, { title: `${endpoint.name} call_tool ${toolName}`, @@ -717,22 +729,6 @@ function shouldSendSpecAuth(endpoint: OpenApiEndpointConfig): boolean { ); } -async function readResponse(response: Response): Promise> { - const contentType = response.headers.get("content-type") ?? ""; - const text = await readLimitedText(response, { - errorMessage: "OpenAPI response exceeded byte limit", - }); - const body = parseHttpBody(contentType, text); - return { - status: response.status, - statusText: response.statusText, - headers: { - "content-type": contentType, - }, - ...(body === undefined ? {} : { body }), - }; -} - async function fetchWithLimit( url: string, timeoutMs: number, diff --git a/packages/core/src/registry.ts b/packages/core/src/registry.ts index deb1e480..47655bea 100644 --- a/packages/core/src/registry.ts +++ b/packages/core/src/registry.ts @@ -2,6 +2,7 @@ import type { CapletConfig, CapletsConfig, CapletServerConfig, + GoogleDiscoveryApiConfig, GraphQlEndpointConfig, } from "./config"; import type { SafeErrorSummary } from "./errors"; @@ -43,6 +44,13 @@ export type CapletServerDetail = { operationCacheTtlMs: number; source: "specPath" | "specUrl"; } + | { + type: "googleDiscovery"; + disabled: boolean; + requestTimeoutMs: number; + operationCacheTtlMs: number; + source: "discoveryPath" | "discoveryUrl"; + } | { type: "graphql"; disabled: boolean; @@ -94,6 +102,7 @@ export class ServerRegistry { const server = this.config.mcpServers[serverId] ?? this.config.openapiEndpoints[serverId] ?? + this.config.googleDiscoveryApis?.[serverId] ?? this.config.graphqlEndpoints[serverId] ?? this.config.httpApis[serverId] ?? this.config.cliTools[serverId] ?? @@ -148,6 +157,7 @@ export class ServerRegistry { return [ ...Object.values(this.config.mcpServers), ...Object.values(this.config.openapiEndpoints), + ...Object.values(this.config.googleDiscoveryApis ?? {}), ...Object.values(this.config.graphqlEndpoints), ...Object.values(this.config.httpApis), ...Object.values(this.config.cliTools), @@ -167,6 +177,16 @@ function backendDetail(server: CapletConfig): CapletServerDetail["backend"] { }; } + if (server.backend === "googleDiscovery") { + return { + type: "googleDiscovery", + disabled: server.disabled, + requestTimeoutMs: server.requestTimeoutMs, + operationCacheTtlMs: server.operationCacheTtlMs, + source: googleDiscoverySource(server), + }; + } + if (server.backend === "graphql") { return { type: "graphql", @@ -215,6 +235,10 @@ function backendDetail(server: CapletConfig): CapletServerDetail["backend"] { }; } +function googleDiscoverySource(server: GoogleDiscoveryApiConfig): "discoveryPath" | "discoveryUrl" { + return server.discoveryPath ? "discoveryPath" : "discoveryUrl"; +} + function capletSetSource( server: Extract, ): "configPath" | "capletsRoot" | "both" { diff --git a/packages/core/src/remote-control/dispatch.ts b/packages/core/src/remote-control/dispatch.ts index 52f28eb9..d2b7de62 100644 --- a/packages/core/src/remote-control/dispatch.ts +++ b/packages/core/src/remote-control/dispatch.ts @@ -1,6 +1,7 @@ import { randomUUID } from "node:crypto"; import { addCliCaplet, + addGoogleDiscoveryCaplet, addGraphqlCaplet, addHttpCaplet, addMcpCaplet, @@ -8,10 +9,10 @@ import { } from "./../cli/add"; import { assertLoginTarget, - findAuthTarget, listAuthRows, logoutAuthResult, refreshAuthResult, + resolveAuthTarget, } from "./../cli/auth"; import { completionShells, type CompletionShell } from "./../cli/completion"; import { initConfig } from "./../cli/init"; @@ -30,7 +31,14 @@ export type RemoteControlDispatchContext = CapletsEngineOptions & { controlCallbackBaseUrl?: string; }; -type AddKind = "cli" | "mcp" | "openapi" | "graphql" | "http"; +type AddKind = + | "cli" + | "mcp" + | "openapi" + | "google-discovery" + | "googleDiscovery" + | "graphql" + | "http"; const ENGINE_COMMANDS = new Set([ "inspect", @@ -172,7 +180,7 @@ async function startRemoteAuthLogin(serverId: string, context: RemoteControlDisp throw new CapletsError("REQUEST_INVALID", "Remote auth login is not available on this server"); } const config = loadConfigWithSources(context.configPath, context.projectConfigPath).config; - const target = findAuthTarget(serverId, config); + const target = await resolveAuthTarget(serverId, config, context.authDir); assertLoginTarget(target, serverId); const flowId = randomUUID(); const baseUrl = context.controlCallbackBaseUrl.endsWith("/") @@ -252,6 +260,17 @@ function dispatchAdd(args: Record, context: RemoteControlDispat print: false, }), }; + case "google-discovery": + case "googleDiscovery": + return { + remote: true, + label: "Google Discovery", + ...addGoogleDiscoveryCaplet(id, { + ...options, + destinationRoot: context.projectCapletsRoot, + print: false, + }), + }; case "graphql": return { remote: true, @@ -275,7 +294,7 @@ function dispatchAdd(args: Record, context: RemoteControlDispat default: throw new CapletsError( "REQUEST_INVALID", - "add.kind must be cli, mcp, openapi, graphql, or http", + "add.kind must be cli, mcp, openapi, google-discovery, googleDiscovery, graphql, or http", ); } } @@ -369,6 +388,15 @@ function remoteAddOptions( tokenEnv: "string", force: "boolean", }); + case "google-discovery": + case "googleDiscovery": + return pickOptions(options, { + discovery: "string", + discoveryUrl: "string", + baseUrl: "string", + tokenEnv: "string", + force: "boolean", + }); case "graphql": return pickOptions(options, { endpointUrl: "string", diff --git a/packages/core/src/runtime-plan/planner.ts b/packages/core/src/runtime-plan/planner.ts index 5751548f..5e53ecd0 100644 --- a/packages/core/src/runtime-plan/planner.ts +++ b/packages/core/src/runtime-plan/planner.ts @@ -69,7 +69,12 @@ export function classifyCapletRuntimeRoute(caplet: Record): Run if (caplet.backend === "mcp") { return caplet.transport === "stdio" || Boolean(caplet.command) ? "process" : "worker_safe"; } - if (caplet.backend === "openapi" || caplet.backend === "graphql" || caplet.backend === "http") { + if ( + caplet.backend === "openapi" || + caplet.backend === "googleDiscovery" || + caplet.backend === "graphql" || + caplet.backend === "http" + ) { return "worker_safe"; } if (caplet.backend === "caplets") { diff --git a/packages/core/src/runtime.ts b/packages/core/src/runtime.ts index 2f3b9394..62f2da84 100644 --- a/packages/core/src/runtime.ts +++ b/packages/core/src/runtime.ts @@ -7,6 +7,8 @@ type CapletsRuntimeOptions = { configPath?: string; projectConfigPath?: string; authDir?: string; + artifactDir?: string; + exposeLocalArtifactPaths?: boolean; watchDebounceMs?: number; server?: ToolServer; writeErr?: (value: string) => void; @@ -71,6 +73,12 @@ function engineOptions(options: CapletsRuntimeOptions): CapletsEngineOptions { if (options.authDir !== undefined) { engineOptions.authDir = options.authDir; } + if (options.artifactDir !== undefined) { + engineOptions.artifactDir = options.artifactDir; + } + if (options.exposeLocalArtifactPaths !== undefined) { + engineOptions.exposeLocalArtifactPaths = options.exposeLocalArtifactPaths; + } if (options.watchDebounceMs !== undefined) { engineOptions.watchDebounceMs = options.watchDebounceMs; } diff --git a/packages/core/src/serve/http.ts b/packages/core/src/serve/http.ts index 63676436..7a9bbc0f 100644 --- a/packages/core/src/serve/http.ts +++ b/packages/core/src/serve/http.ts @@ -440,12 +440,16 @@ export async function serveHttp( engineOptions: CapletsEngineOptions = {}, writeErr: (value: string) => void = (value) => process.stderr.write(value), ): Promise { - const engine = new CapletsEngine(engineOptions); + const resolvedEngineOptions = { + exposeLocalArtifactPaths: false, + ...engineOptions, + }; + const engine = new CapletsEngine(resolvedEngineOptions); const app = createHttpServeApp(options, engine, { writeErr, control: { - ...engineOptions, - projectCapletsRoot: projectCapletsRootForEngineOptions(engineOptions), + ...resolvedEngineOptions, + projectCapletsRoot: projectCapletsRootForEngineOptions(resolvedEngineOptions), }, }); const paths = servicePaths(options.path); @@ -470,12 +474,14 @@ export async function serveHttpWithSessionFactory( createSession: HttpMcpSessionFactory, writeErr: (value: string) => void = (value) => process.stderr.write(value), ): Promise { - const engine = new CapletsEngine({}); + const resolvedEngineOptions = { exposeLocalArtifactPaths: false }; + const engine = new CapletsEngine(resolvedEngineOptions); const app = createHttpServeApp(options, engine, { writeErr, exposeAttach: false, sessionFactory: createSession, control: { + ...resolvedEngineOptions, projectCapletsRoot: resolveProjectCapletsRoot(), }, }); diff --git a/packages/core/src/tools.ts b/packages/core/src/tools.ts index 9346b85d..4a769f53 100644 --- a/packages/core/src/tools.ts +++ b/packages/core/src/tools.ts @@ -5,6 +5,7 @@ import type { CapletConfig } from "./config"; import type { CliToolsManager } from "./cli-tools"; import type { DownstreamManager } from "./downstream"; import { CapletsError } from "./errors"; +import type { GoogleDiscoveryManager } from "./google-discovery"; import type { GraphQLManager } from "./graphql"; import type { HttpActionManager } from "./http-actions"; import type { OpenApiManager } from "./openapi"; @@ -61,6 +62,7 @@ export async function handleServerTool( cli?: CliToolsManager, caplets?: CapletSetManager, options: HandleServerToolOptions = {}, + googleDiscovery?: GoogleDiscoveryManager, ): Promise { const startedAt = Date.now(); const parsed = validateOperationRequest( @@ -84,11 +86,21 @@ export async function handleServerTool( http, cli, caplets, + googleDiscovery, ).check(server as never); return jsonResult(result, metadataFor(server, "check", undefined, startedAt)); } case "tools": { - const backend = backendFor(server, downstream, openapi, graphql, http, cli, caplets); + const backend = backendFor( + server, + downstream, + openapi, + graphql, + http, + cli, + caplets, + googleDiscovery, + ); const tools = await backend.listTools(server as never); const page = pageItems( tools.map((tool) => backend.compact(server as never, tool)), @@ -105,7 +117,16 @@ export async function handleServerTool( ); } case "search_tools": { - const backend = backendFor(server, downstream, openapi, graphql, http, cli, caplets); + const backend = backendFor( + server, + downstream, + openapi, + graphql, + http, + cli, + caplets, + googleDiscovery, + ); const tools = await backend.listTools(server as never); const limit = parsed.limit ?? registry.config.options.defaultSearchLimit; const matches = backend.search(server as never, tools, parsed.query, limit); @@ -121,7 +142,16 @@ export async function handleServerTool( ); } case "describe_tool": { - const backend = backendFor(server, downstream, openapi, graphql, http, cli, caplets); + const backend = backendFor( + server, + downstream, + openapi, + graphql, + http, + cli, + caplets, + googleDiscovery, + ); const tool = await backend.getTool(server as never, parsed.name); const observedOutputShape = await readObservedOutputShape( options, @@ -140,7 +170,16 @@ export async function handleServerTool( ); } case "call_tool": { - const backend = backendFor(server, downstream, openapi, graphql, http, cli, caplets); + const backend = backendFor( + server, + downstream, + openapi, + graphql, + http, + cli, + caplets, + googleDiscovery, + ); const tool = await maybeGetToolForValidation(backend, server, parsed.name); validateToolArgsForAgent(tool, parsed.name, parsed.args); if (parsed.fields === undefined) { @@ -1053,6 +1092,12 @@ export function projectCallToolResult( "Field selection requires the downstream tool to return object structuredContent", ); } + if (hasArtifactPlaceholderForSelectedFields(structuredContent, fields)) { + throw new CapletsError( + "REQUEST_INVALID", + "Field selection cannot project from an artifact response. Retry without fields and read the returned artifact.", + ); + } const projected = projectStructuredContent(structuredContent, outputSchema, fields); return { @@ -1062,13 +1107,39 @@ export function projectCallToolResult( } as T & CallToolResult; } +function hasArtifactPlaceholderForSelectedFields( + structuredContent: Record, + fields: string[], +): boolean { + const body = structuredContent.body; + return ( + isPlainObject(body) && + isPlainObject(body.artifact) && + isArtifactPlaceholder(body.artifact) && + fields.some((field) => field === "body" || field.startsWith("body.")) + ); +} + +function isArtifactPlaceholder(value: Record): boolean { + return ( + typeof value.uri === "string" && + value.uri.startsWith("caplets://artifacts/") && + typeof value.byteLength === "number" && + typeof value.sha256 === "string" + ); +} + export function extractArtifacts(result: unknown): CapletArtifact[] { - if (!isPlainObject(result) || !Array.isArray(result.content)) { + if (!isPlainObject(result)) { return []; } const artifacts: CapletArtifact[] = []; const seen = new Set(); + addStructuredArtifact(artifacts, seen, result.structuredContent); + if (!Array.isArray(result.content)) { + return artifacts; + } for (const item of result.content) { if (!isPlainObject(item) || item.type !== "text" || typeof item.text !== "string") { continue; @@ -1110,6 +1181,26 @@ export function extractArtifacts(result: unknown): CapletArtifact[] { return artifacts; } +function addStructuredArtifact( + artifacts: CapletArtifact[], + seen: Set, + structuredContent: unknown, +): void { + if (!isPlainObject(structuredContent)) return; + const body = structuredContent.body; + if (!isPlainObject(body) || !isPlainObject(body.artifact)) return; + const path = typeof body.artifact.path === "string" ? body.artifact.path : undefined; + const uri = typeof body.artifact.uri === "string" ? body.artifact.uri : undefined; + const displayPath = path ?? uri; + if (!displayPath || seen.has(displayPath)) return; + seen.add(displayPath); + artifacts.push({ + kind: "file", + displayPath, + pathResolution: path ? "absolute" : "relative-to-mcp-server", + }); +} + type MarkdownLink = { label: string; destination: string; @@ -1265,6 +1356,7 @@ function backendFor( http?: HttpActionManager, cli?: CliToolsManager, caplets?: CapletSetManager, + googleDiscovery?: GoogleDiscoveryManager, ) { if (server.backend === "mcp") { return { @@ -1279,6 +1371,25 @@ function backendFor( search: (...args: Parameters) => downstream.search(...args), }; } + if (server.backend === "googleDiscovery") { + if (!googleDiscovery) { + throw new CapletsError("INTERNAL_ERROR", "Google Discovery manager is not configured"); + } + return { + check: (...args: Parameters) => + googleDiscovery.checkApi(...args), + listTools: (...args: Parameters) => + googleDiscovery.listTools(...args), + getTool: (...args: Parameters) => + googleDiscovery.getTool(...args), + callTool: (...args: Parameters) => + googleDiscovery.callTool(...args), + compact: (...args: Parameters) => + googleDiscovery.compact(...args), + search: (...args: Parameters) => + googleDiscovery.search(...args), + }; + } if (server.backend === "graphql") { if (!graphql) { throw new CapletsError("INTERNAL_ERROR", "GraphQL manager is not configured"); diff --git a/packages/core/test/auth.test.ts b/packages/core/test/auth.test.ts index 4fad4e48..fab15c73 100644 --- a/packages/core/test/auth.test.ts +++ b/packages/core/test/auth.test.ts @@ -354,6 +354,86 @@ describe("auth helpers", () => { } }); + it("rejects token bundles whose granted scopes omit a required scope", async () => { + const dir = mkdtempSync(join(tmpdir(), "caplets-auth-scope-mismatch-")); + try { + writeTokenBundle( + { + server: "drive", + authType: "oauth2", + accessToken: "access-token", + tokenType: "Bearer", + expiresAt: "2999-01-01T00:00:00.000Z", + scope: "https://www.googleapis.com/auth/drive.metadata.readonly", + protectedResourceOrigin: "https://www.googleapis.com", + metadata: { + requestedScopes: [ + "https://www.googleapis.com/auth/drive", + "https://www.googleapis.com/auth/drive.metadata.readonly", + ], + }, + }, + dir, + ); + + await expect( + genericOAuthHeaders( + { + server: "drive", + backend: "googleDiscovery", + baseUrl: "https://www.googleapis.com/drive/v3/", + auth: { + type: "oauth2", + scopes: ["https://www.googleapis.com/auth/drive"], + }, + }, + dir, + ), + ).rejects.toMatchObject({ + code: "AUTH_REQUIRED", + details: { nextAction: "run_caplets_auth_login" }, + }); + } finally { + rmSync(dir, { recursive: true, force: true }); + } + }); + + it("accepts broader Google Discovery OAuth scope alternatives", async () => { + const dir = mkdtempSync(join(tmpdir(), "caplets-auth-google-scope-alternative-")); + try { + writeTokenBundle( + { + server: "drive", + authType: "oauth2", + accessToken: "access-token", + tokenType: "Bearer", + expiresAt: "2999-01-01T00:00:00.000Z", + scope: "https://www.googleapis.com/auth/drive", + protectedResourceOrigin: "https://www.googleapis.com", + metadata: { + requestedScopes: ["https://www.googleapis.com/auth/drive"], + }, + }, + dir, + ); + + await expect( + genericOAuthHeaders( + { + server: "drive", + backend: "googleDiscovery", + baseUrl: "https://www.googleapis.com/drive/v3/", + auth: { type: "oauth2" }, + resolvedScopes: ["https://www.googleapis.com/auth/drive.metadata.readonly"], + }, + dir, + ), + ).resolves.toEqual({ authorization: "Bearer access-token" }); + } finally { + rmSync(dir, { recursive: true, force: true }); + } + }); + it("rejects generic OAuth headers when refresh returns an expired token", async () => { const dir = mkdtempSync(join(tmpdir(), "caplets-auth-refresh-expired-")); const server = createServer((_request: IncomingMessage, response: ServerResponse) => { @@ -1102,6 +1182,115 @@ describe("auth helpers", () => { } }); + it("includes resolved Google Discovery scopes in generic OIDC authorization", async () => { + const dir = mkdtempSync(join(tmpdir(), "caplets-auth-google-scopes-")); + let baseUrl = ""; + let authorizationUrl = ""; + const server = createServer((request: IncomingMessage, response: ServerResponse) => { + let body = ""; + request.setEncoding("utf8"); + request.on("data", (chunk) => { + body += chunk; + }); + request.on("end", () => { + response.setHeader("content-type", "application/json"); + if (request.url === "/.well-known/oauth-protected-resource") { + response.end(JSON.stringify({ authorization_servers: [baseUrl] })); + return; + } + if (request.url === "/.well-known/oauth-authorization-server") { + response.statusCode = 404; + response.end(JSON.stringify({ error: "missing" })); + return; + } + if (request.url === "/.well-known/openid-configuration") { + response.end( + JSON.stringify({ + issuer: baseUrl, + authorization_endpoint: `${baseUrl}/authorize`, + token_endpoint: `${baseUrl}/token`, + registration_endpoint: `${baseUrl}/register`, + }), + ); + return; + } + if (request.url === "/register") { + response.statusCode = 201; + response.end(JSON.stringify({ client_id: "dynamic-client" })); + return; + } + if (request.url === "/token") { + const idToken = [ + "header", + Buffer.from( + JSON.stringify({ iss: baseUrl, sub: "subject-123", aud: "dynamic-client" }), + ).toString("base64url"), + "signature", + ].join("."); + response.end( + JSON.stringify({ + access_token: "new-access-token", + refresh_token: "new-refresh-token", + id_token: idToken, + token_type: "Bearer", + expires_in: 3600, + }), + ); + return; + } + response.end("{}"); + }); + }); + try { + await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve)); + const address = server.address(); + if (!address || typeof address === "string") { + throw new Error("test server did not bind"); + } + baseUrl = `http://127.0.0.1:${address.port}`; + + await runGenericOAuthFlow( + { + server: "drive", + backend: "googleDiscovery", + baseUrl, + auth: { type: "oidc" }, + resolvedScopes: [ + "https://www.googleapis.com/auth/drive.readonly", + "https://www.googleapis.com/auth/drive", + ], + }, + { + authDir: dir, + noOpen: true, + print: (line) => { + authorizationUrl = line.match(/https?:\/\/\S+/)?.[0] ?? ""; + }, + readManualInput: async () => { + const url = new URL(authorizationUrl); + return `http://127.0.0.1/callback?code=auth-code&state=${url.searchParams.get("state")}`; + }, + }, + ); + + expect(new URL(authorizationUrl).searchParams.get("scope")).toBe( + "openid profile email https://www.googleapis.com/auth/drive https://www.googleapis.com/auth/drive.readonly", + ); + expect(readTokenBundle("drive", dir)?.metadata).toMatchObject({ + requestedScopes: [ + "openid", + "profile", + "email", + "https://www.googleapis.com/auth/drive", + "https://www.googleapis.com/auth/drive.readonly", + ], + }); + } finally { + await new Promise((resolve) => server.close(() => resolve())); + rmSync(dir, { recursive: true, force: true }); + } + }); + it("rejects non-loopback plaintext OAuth endpoints before token exchange", async () => { await expect( runGenericOAuthFlow( diff --git a/packages/core/test/caplet-sets.test.ts b/packages/core/test/caplet-sets.test.ts index d47e210f..74904bd3 100644 --- a/packages/core/test/caplet-sets.test.ts +++ b/packages/core/test/caplet-sets.test.ts @@ -108,7 +108,8 @@ describe("CapletSetManager", () => { }, }); const caplet = config.capletSets.nested!; - const manager = new CapletSetManager(new ServerRegistry(config)); + const artifactDir = join(dir, "artifacts"); + const manager = new CapletSetManager(new ServerRegistry(config), { artifactDir }); await expect(manager.listTools(caplet)).resolves.toMatchObject([ { name: "configured" }, @@ -116,6 +117,55 @@ describe("CapletSetManager", () => { ]); }); + it("routes child Google Discovery Caplets through nested tool calls", async () => { + const dir = mkdtempSync(join(tmpdir(), "caplets-set-google-discovery-")); + dirs.push(dir); + const configPath = join(dir, "child.json"); + writeFileSync( + configPath, + JSON.stringify({ + googleDiscoveryApis: { + drive: { + name: "Google Drive", + description: "Access Google Drive files.", + discoveryPath: join(__dirname, "fixtures/google-discovery/drive.discovery.json"), + baseUrl: "https://www.googleapis.com/drive/v3/", + auth: { type: "none" }, + includeOperations: ["drive.files.list"], + }, + }, + }), + ); + const config = parseConfig({ + capletSets: { + nested: { + name: "Nested Caplets", + description: "Expose child Caplets through a nested collection.", + configPath, + }, + }, + }); + const caplet = config.capletSets.nested!; + const artifactDir = join(dir, "artifacts"); + const manager = new CapletSetManager(new ServerRegistry(config), { artifactDir }); + + const result = await manager.callTool(caplet, "drive", { operation: "tools" }); + + expect(result.isError).toBeUndefined(); + expect(result.structuredContent).toMatchObject({ + result: { + id: "drive", + items: [{ name: "drive.files.list" }], + }, + }); + const child = ( + manager as unknown as { + children: Map; + } + ).children.get("nested"); + expect(child?.googleDiscovery.options.artifactDir).toBe(artifactDir); + }); + it("serializes concurrent refreshes for one parent Caplet set", async () => { const { dir, childConfigPath } = childCliConfig(); dirs.push(dir); diff --git a/packages/core/test/caplet-source.test.ts b/packages/core/test/caplet-source.test.ts index 49b86f82..25268913 100644 --- a/packages/core/test/caplet-source.test.ts +++ b/packages/core/test/caplet-source.test.ts @@ -30,6 +30,32 @@ info: title: Weather version: 1.0.0 paths: {} +`, + }, + { + path: "drive/CAPLET.md", + content: `--- +name: Drive +description: Query Google Drive metadata. +googleDiscoveryApi: + discoveryPath: ./drive.discovery.json + auth: + type: oauth2 + issuer: https://accounts.google.com + scopes: + - https://www.googleapis.com/auth/drive.metadata.readonly +--- + +# Drive +`, + }, + { + path: "drive/drive.discovery.json", + content: `{ + "kind": "discovery#restDescription", + "name": "drive", + "version": "v3" +} `, }, { @@ -78,6 +104,8 @@ describe("CapletSource adapters", () => { const source = new BundleCapletSource(fixtureFiles); await expect(source.listFiles()).resolves.toEqual([ + expect.objectContaining({ path: "drive/CAPLET.md" }), + expect.objectContaining({ path: "drive/drive.discovery.json" }), expect.objectContaining({ path: "tools/CAPLET.md" }), expect.objectContaining({ path: "tools/scripts/list-files.js" }), expect.objectContaining({ path: "weather/CAPLET.md" }), @@ -95,6 +123,8 @@ describe("CapletSource adapters", () => { const source = new FilesystemCapletSource(root); await expect(source.listFiles()).resolves.toEqual([ + expect.objectContaining({ path: "drive/CAPLET.md" }), + expect.objectContaining({ path: "drive/drive.discovery.json" }), expect.objectContaining({ path: "tools/CAPLET.md" }), expect.objectContaining({ path: "tools/scripts/list-files.js" }), expect.objectContaining({ path: "weather/CAPLET.md" }), @@ -102,7 +132,7 @@ describe("CapletSource adapters", () => { ]); await expect(source.readFile("./tools/scripts/list-files.js")).resolves.toEqual({ path: "tools/scripts/list-files.js", - content: fixtureFiles[3]!.content, + content: fixtureFiles[5]!.content, }); await expect(source.readFile("/absolute.js")).resolves.toBeUndefined(); }); @@ -128,6 +158,21 @@ describe("CapletSource adapters", () => { }, localReferences: [{ path: "weather/openapi.yaml", exists: true }], }, + { + id: "drive", + backend: "googleDiscovery", + shadowing: "forbid", + setupRequired: false, + authRequired: true, + projectBindingRequired: false, + runtime: { + route: "worker_safe", + setupTarget: undefined, + features: [], + resources: { class: "small", cpu: 1, memoryMb: 1024, diskMb: 4096 }, + }, + localReferences: [{ path: "drive/drive.discovery.json", exists: true }], + }, { id: "tools", backend: "cli", diff --git a/packages/core/test/cli-completion.test.ts b/packages/core/test/cli-completion.test.ts index 16f398bd..2176a6a6 100644 --- a/packages/core/test/cli-completion.test.ts +++ b/packages/core/test/cli-completion.test.ts @@ -66,6 +66,7 @@ describe("CLI completion resolver", () => { "cli", "mcp", "openapi", + "google-discovery", "graphql", "http", ]); diff --git a/packages/core/test/cli-remote.test.ts b/packages/core/test/cli-remote.test.ts index 20b07bd6..665dfb85 100644 --- a/packages/core/test/cli-remote.test.ts +++ b/packages/core/test/cli-remote.test.ts @@ -90,6 +90,40 @@ describe("remote CLI routing", () => { expect(out.join("")).toBe("shared.echo\n"); }); + it("does not append remote completions for locally shadowed Google Discovery caplets", async () => { + const context = testContext("caplets-cli-remote-complete-google-shadowed-"); + const requests: unknown[] = []; + const out: string[] = []; + writeFileSync( + context.configPath, + JSON.stringify({ + googleDiscoveryApis: { + drive: { + name: "Drive API", + description: "Manage Drive files through Google Discovery.", + discoveryUrl: "https://www.googleapis.com/discovery/v1/apis/drive/v3/rest", + auth: { type: "none" }, + }, + }, + }), + ); + const fetch = vi.fn( + async (_url: Parameters[0], init?: RequestInit) => { + requests.push(JSON.parse(String(init?.body ?? "{}"))); + return Response.json({ ok: true, result: ["drive.remote_only"] }); + }, + ); + + await runCli(["__complete", "--shell", "bash", "--", "call-tool", "drive."], { + env: remoteEnv(context), + fetch, + writeOut: (value) => out.push(value), + }); + + expect(requests).toEqual([]); + expect(out.join("")).not.toContain("remote_only"); + }); + it("uses remote completions when a matching local overlay caplet is disabled", async () => { const context = testContext("caplets-cli-remote-complete-disabled-shadow-"); const requests: unknown[] = []; @@ -832,6 +866,24 @@ describe("remote CLI routing", () => { ], { spec: "./openapi.yaml", baseUrl: "https://api.example.com", tokenEnv: "API_TOKEN" }, ], + [ + "googleDiscovery", + [ + "google-discovery", + "drive", + "--discovery-url", + "https://www.googleapis.com/discovery/v1/apis/drive/v3/rest", + "--base-url", + "https://www.googleapis.com/drive/v3", + "--token-env", + "GOOGLE_TOKEN", + ], + { + discoveryUrl: "https://www.googleapis.com/discovery/v1/apis/drive/v3/rest", + baseUrl: "https://www.googleapis.com/drive/v3", + tokenEnv: "GOOGLE_TOKEN", + }, + ], [ "graphql", [ diff --git a/packages/core/test/cli.test.ts b/packages/core/test/cli.test.ts index c2288a40..20189c3d 100644 --- a/packages/core/test/cli.test.ts +++ b/packages/core/test/cli.test.ts @@ -256,7 +256,7 @@ describe("cli init", () => { await runCli(["list"], { writeOut: (value) => out.push(value) }); const text = out.join(""); - expect(text).toContain("Configured Caplets (3)"); + expect(text).toContain("Configured Caplets (4)"); expect(text).toContain("Source:"); expect(text).toContain("filesystem"); expect(text).toContain("mcp"); @@ -267,6 +267,8 @@ describe("cli init", () => { expect(text).toContain("openapi"); expect(text).toContain("catalog"); expect(text).toContain("graphql"); + expect(text).toContain("drive"); + expect(text).toContain("googleDiscovery"); expect(text).not.toContain("disabled_remote"); expect(text).not.toContain("secret-access-token"); expect(text).not.toContain("openapi-client"); @@ -324,6 +326,15 @@ describe("cli init", () => { path: configPath, shadows: [], }), + expect.objectContaining({ + server: "drive", + backend: "googleDiscovery", + disabled: false, + status: "not_started", + source: "global-config", + path: configPath, + shadows: [], + }), expect.objectContaining({ server: "filesystem", backend: "mcp", @@ -1061,7 +1072,7 @@ describe("cli init", () => { } }); - it("prints added MCP and OpenAPI backend Caplets", async () => { + it("prints added MCP, OpenAPI, and Google Discovery backend Caplets", async () => { const out: string[] = []; await runCli( @@ -1092,12 +1103,32 @@ describe("cli init", () => { ], { writeOut: (value) => out.push(value) }, ); + await runCli( + [ + "add", + "google-discovery", + "drive", + "--discovery-url", + "https://www.googleapis.com/discovery/v1/apis/drive/v3/rest", + "--base-url", + "https://www.googleapis.com/drive/v3", + "--token-env", + "GOOGLE_TOKEN", + "--print", + ], + { writeOut: (value) => out.push(value) }, + ); expect(out.join("\n")).toContain("mcpServer:"); expect(out.join("\n")).toContain('transport: "sse"'); expect(out.join("\n")).toContain('token: "$env:MCP_TOKEN"'); expect(out.join("\n")).toContain("openapiEndpoint:"); expect(out.join("\n")).toContain('baseUrl: "https://api.example.com/v1"'); + expect(out.join("\n")).toContain("googleDiscoveryApi:"); + expect(out.join("\n")).toContain( + 'discoveryUrl: "https://www.googleapis.com/discovery/v1/apis/drive/v3/rest"', + ); + expect(out.join("\n")).toContain('token: "$env:GOOGLE_TOKEN"'); }); it("adds GraphQL and HTTP backend Caplets to the project root", async () => { @@ -1168,6 +1199,60 @@ describe("cli init", () => { } }); + it("writes Google Discovery local discovery paths that load from the original project file", async () => { + const dir = mkdtempSync(join(tmpdir(), "caplets-add-google-discovery-path-")); + const projectRoot = join(dir, "project"); + const cwd = process.cwd(); + try { + mkdirSync(projectRoot, { recursive: true }); + writeFileSync( + join(projectRoot, "drive.discovery.json"), + JSON.stringify({ kind: "discovery#restDescription", name: "drive", version: "v3" }), + ); + process.chdir(projectRoot); + + await runCli(["add", "google-discovery", "drive", "--discovery", "./drive.discovery.json"], { + writeOut: () => {}, + }); + + const config = loadConfig( + join(dir, "user", "config.json"), + join(projectRoot, ".caplets", "config.json"), + ); + expect(config.googleDiscoveryApis.drive?.discoveryPath).toBe( + join(projectRoot, "drive.discovery.json"), + ); + } finally { + process.chdir(cwd); + rmSync(dir, { recursive: true, force: true }); + } + }); + + it("writes Google Discovery env discovery URL references as URLs", async () => { + const dir = mkdtempSync(join(tmpdir(), "caplets-add-google-discovery-env-url-")); + const projectRoot = join(dir, "project"); + const cwd = process.cwd(); + try { + const out: string[] = []; + mkdirSync(projectRoot, { recursive: true }); + process.chdir(projectRoot); + + await runCli( + ["add", "google-discovery", "drive", "--discovery-url", "$env:DISCOVERY_URL", "--print"], + { + writeOut: (value) => out.push(value), + }, + ); + + const text = out.join("\n"); + expect(text).toContain('discoveryUrl: "$env:DISCOVERY_URL"'); + expect(text).not.toContain("discoveryPath:"); + } finally { + process.chdir(cwd); + rmSync(dir, { recursive: true, force: true }); + } + }); + it("writes GraphQL local schema paths that load from the original project file", async () => { const dir = mkdtempSync(join(tmpdir(), "caplets-add-graphql-path-")); const projectRoot = join(dir, "project"); @@ -2203,6 +2288,244 @@ describe("cli init", () => { } }); + it("refreshes Google Discovery OAuth credentials with explicit scopes when discovery is unavailable", async () => { + const dir = mkdtempSync(join(tmpdir(), "caplets-auth-google-refresh-cli-")); + const authDir = join(dir, "auth"); + const configPath = join(dir, "config.json"); + const out: string[] = []; + const fetchMock = vi + .spyOn(globalThis, "fetch") + .mockRejectedValueOnce(new Error("discovery unavailable")) + .mockResolvedValueOnce( + Response.json({ + access_token: "new-access-token", + refresh_token: "new-refresh-token", + token_type: "Bearer", + expires_in: 3600, + }), + ); + try { + writeFileSync( + configPath, + JSON.stringify({ + googleDiscoveryApis: { + drive: { + name: "Google Drive", + description: "Access Google Drive files.", + discoveryUrl: "https://discovery.example.invalid/drive/v3/rest", + auth: { + type: "oauth2", + clientId: "client", + tokenUrl: "https://auth.example.com/token", + scopes: ["https://www.googleapis.com/auth/drive.readonly"], + }, + }, + }, + }), + ); + process.env.CAPLETS_CONFIG = configPath; + writeTokenBundle( + { + server: "drive", + authType: "oauth2", + accessToken: "old-access-token", + refreshToken: "old-refresh-token", + expiresAt: "2999-01-01T00:00:00.000Z", + metadata: { + requestedScopes: ["https://www.googleapis.com/auth/drive.readonly"], + }, + }, + authDir, + ); + + await runCli(["auth", "refresh", "drive"], { + writeOut: (value) => out.push(value), + authDir, + }); + + expect(out.join("")).toBe("Refreshed OAuth credentials for `drive`.\n"); + expect(fetchMock).toHaveBeenCalledTimes(2); + expect(fetchMock).toHaveBeenCalledWith( + "https://auth.example.com/token", + expect.objectContaining({ + method: "POST", + body: expect.stringContaining("refresh_token=old-refresh-token"), + }), + ); + } finally { + rmSync(dir, { recursive: true, force: true }); + } + }); + + it("uses Discovery-derived base URL for Google Discovery OAuth refresh", async () => { + const dir = mkdtempSync(join(tmpdir(), "caplets-auth-google-base-url-cli-")); + const authDir = join(dir, "auth"); + const configPath = join(dir, "config.json"); + const discoveryPath = join(dir, "drive.discovery.json"); + const out: string[] = []; + const fetchMock = vi.spyOn(globalThis, "fetch").mockResolvedValue( + Response.json({ + access_token: "new-access-token", + refresh_token: "new-refresh-token", + token_type: "Bearer", + expires_in: 3600, + }), + ); + try { + writeFileSync( + discoveryPath, + JSON.stringify({ + kind: "discovery#restDescription", + baseUrl: "https://api.example.com/drive/v3/", + resources: { + files: { + methods: { + list: { + id: "drive.files.list", + path: "files", + httpMethod: "GET", + scopes: ["https://www.googleapis.com/auth/drive.readonly"], + }, + }, + }, + }, + }), + ); + writeFileSync( + configPath, + JSON.stringify({ + googleDiscoveryApis: { + drive: { + name: "Google Drive", + description: "Access Google Drive files.", + discoveryPath, + auth: { + type: "oauth2", + clientId: "client", + tokenUrl: "https://auth.example.com/token", + }, + }, + }, + }), + ); + process.env.CAPLETS_CONFIG = configPath; + writeTokenBundle( + { + server: "drive", + authType: "oauth2", + accessToken: "old-access-token", + refreshToken: "old-refresh-token", + expiresAt: "2999-01-01T00:00:00.000Z", + protectedResourceOrigin: "https://api.example.com", + metadata: { + requestedScopes: ["https://www.googleapis.com/auth/drive.readonly"], + }, + }, + authDir, + ); + + await runCli(["auth", "refresh", "drive"], { + writeOut: (value) => out.push(value), + authDir, + }); + + expect(out.join("")).toBe("Refreshed OAuth credentials for `drive`.\n"); + expect(fetchMock).toHaveBeenCalledWith( + "https://auth.example.com/token", + expect.objectContaining({ method: "POST" }), + ); + } finally { + rmSync(dir, { recursive: true, force: true }); + } + }); + + it("uses Discovery-derived base URL for explicit Google Discovery OAuth scopes", async () => { + const dir = mkdtempSync(join(tmpdir(), "caplets-auth-google-proxy-cli-")); + const authDir = join(dir, "auth"); + const configPath = join(dir, "config.json"); + const out: string[] = []; + const fetchMock = vi + .spyOn(globalThis, "fetch") + .mockResolvedValueOnce( + Response.json({ + kind: "discovery#restDescription", + baseUrl: "https://api.example.com/drive/v3/", + resources: { + files: { + methods: { + list: { + id: "drive.files.list", + path: "files", + httpMethod: "GET", + }, + }, + }, + }, + }), + ) + .mockResolvedValueOnce( + Response.json({ + access_token: "new-access-token", + refresh_token: "new-refresh-token", + token_type: "Bearer", + expires_in: 3600, + scope: "https://www.googleapis.com/auth/drive.readonly", + }), + ); + try { + writeFileSync( + configPath, + JSON.stringify({ + googleDiscoveryApis: { + drive: { + name: "Google Drive", + description: "Access Google Drive files.", + discoveryUrl: "https://discovery-proxy.example.com/drive/v3/rest", + auth: { + type: "oauth2", + clientId: "client", + tokenUrl: "https://auth.example.com/token", + scopes: ["https://www.googleapis.com/auth/drive.readonly"], + }, + }, + }, + }), + ); + process.env.CAPLETS_CONFIG = configPath; + writeTokenBundle( + { + server: "drive", + authType: "oauth2", + accessToken: "old-access-token", + refreshToken: "old-refresh-token", + expiresAt: "2999-01-01T00:00:00.000Z", + protectedResourceOrigin: "https://api.example.com", + scope: "https://www.googleapis.com/auth/drive.readonly", + }, + authDir, + ); + + await runCli(["auth", "refresh", "drive"], { + writeOut: (value) => out.push(value), + authDir, + }); + + expect(out.join("")).toBe("Refreshed OAuth credentials for `drive`.\n"); + expect(fetchMock).toHaveBeenNthCalledWith( + 1, + "https://discovery-proxy.example.com/drive/v3/rest", + expect.anything(), + ); + expect(fetchMock).toHaveBeenNthCalledWith( + 2, + "https://auth.example.com/token", + expect.objectContaining({ method: "POST" }), + ); + } finally { + rmSync(dir, { recursive: true, force: true }); + } + }); + it("logs out configured OpenAPI OAuth endpoints", async () => { const dir = mkdtempSync(join(tmpdir(), "caplets-auth-")); const authDir = join(dir, "auth"); @@ -2255,6 +2578,36 @@ describe("cli setup", () => { expect(text).not.toContain("caplets@caplets"); }); + it("resolves Google Discovery Caplets for Caplet setup", async () => { + const dir = mkdtempSync(join(tmpdir(), "caplets-setup-google-discovery-")); + const configPath = join(dir, "config.json"); + const out: string[] = []; + try { + writeFileSync( + configPath, + JSON.stringify({ + googleDiscoveryApis: { + drive: { + name: "Drive API", + description: "Manage Drive files through Google Discovery.", + discoveryUrl: "https://www.googleapis.com/discovery/v1/apis/drive/v3/rest", + auth: { type: "none" }, + }, + }, + }), + ); + + await runCli(["setup", "drive"], { + env: { CAPLETS_CONFIG: configPath }, + writeOut: (value) => out.push(value), + }); + + expect(out.join("")).toBe("No setup metadata is defined for Drive API (drive).\n"); + } finally { + rmSync(dir, { recursive: true, force: true }); + } + }); + it("prompts for integrations when stdin is available", async () => { const out: string[] = []; const commands: Array<{ command: string; args: string[] }> = []; @@ -2644,6 +2997,7 @@ describe("cli completion commands", () => { "cli", "mcp", "openapi", + "google-discovery", "graphql", "http", ]); @@ -2666,7 +3020,12 @@ describe("cli completion commands", () => { }, ); - expect(out.join("").split("\n").filter(Boolean)).toEqual(["catalog", "filesystem", "users"]); + expect(out.join("").split("\n").filter(Boolean)).toEqual([ + "catalog", + "drive", + "filesystem", + "users", + ]); } finally { rmSync(dir, { recursive: true, force: true }); } @@ -2683,7 +3042,12 @@ describe("cli completion commands", () => { writeOut: (value) => out.push(value), }); - expect(out.join("").split("\n").filter(Boolean)).toEqual(["catalog", "filesystem", "users"]); + expect(out.join("").split("\n").filter(Boolean)).toEqual([ + "catalog", + "drive", + "filesystem", + "users", + ]); } finally { rmSync(dir, { recursive: true, force: true }); } @@ -2736,6 +3100,14 @@ function writeInspectionConfig(path: string): void { auth: { type: "oauth2", clientId: "openapi-client" }, }, }, + googleDiscoveryApis: { + drive: { + name: "Drive API", + description: "Manage Drive files through Google Discovery.", + discoveryUrl: "https://www.googleapis.com/discovery/v1/apis/drive/v3/rest", + auth: { type: "none" }, + }, + }, graphqlEndpoints: { catalog: { name: "Catalog GraphQL", diff --git a/packages/core/test/code-mode-declarations.test.ts b/packages/core/test/code-mode-declarations.test.ts index 294f7dfe..58ebd939 100644 --- a/packages/core/test/code-mode-declarations.test.ts +++ b/packages/core/test/code-mode-declarations.test.ts @@ -65,6 +65,38 @@ describe("generateCodeModeDeclarations", () => { expect(declaration).not.toContain(" = "); }); + it("keeps platform globals out of generated declarations", () => { + const declaration = generateCodeModeDeclarations({ + caplets: [{ id: "github", name: "GitHub", description: "GitHub repo operations." }], + }); + + expect(declaration).toContain("declare const console:Console"); + expect(declaration).not.toContain("declare function atob"); + expect(declaration).not.toContain("declare function btoa"); + expect(declaration).not.toContain("declare const Buffer"); + expect(declaration).not.toContain("declare class URL"); + expect(declaration).not.toContain("declare class TextEncoder"); + expect(declaration).not.toContain("declare const crypto"); + expect(declaration).not.toContain("declare function structuredClone"); + expect(declaration).not.toContain("declare class Headers"); + expect(declaration).not.toContain("declare class Blob"); + expect(declaration).not.toContain("declare class File"); + expect(declaration).not.toContain("declare class FormData"); + expect(declaration).not.toContain("declare class ReadableStream"); + expect(declaration).not.toContain("declare class WritableStream"); + expect(declaration).not.toContain("declare class TransformStream"); + expect(declaration).not.toContain("declare class AbortController"); + expect(declaration).not.toContain("declare class AbortSignal"); + expect(declaration).not.toContain("declare class Request"); + expect(declaration).not.toContain("declare class Response"); + expect(declaration).not.toContain("declare function fetch"); + expect(declaration).not.toContain("declare function queueMicrotask"); + expect(declaration).not.toContain("declare function setTimeout"); + expect(declaration).not.toContain("declare function clearTimeout"); + expect(declaration).not.toContain("declare function setInterval"); + expect(declaration).not.toContain("declare function clearInterval"); + }); + it("builds the shared Code Mode tool description from generated declarations", () => { const declaration = 'declare const caplets:{docs:CapletHandle<"docs">;};'; const description = generateCodeModeRunToolDescription(declaration); diff --git a/packages/core/test/code-mode-diagnostics.test.ts b/packages/core/test/code-mode-diagnostics.test.ts index 27686411..1a3c066f 100644 --- a/packages/core/test/code-mode-diagnostics.test.ts +++ b/packages/core/test/code-mode-diagnostics.test.ts @@ -20,18 +20,42 @@ describe("diagnoseCodeModeTypeScript", () => { expect(diagnostics.map((diagnostic) => diagnostic.message).join("\n")).toContain("callTool"); }); - it("blocks direct fetch because the ambient lib omits network globals", () => { + it("blocks direct fetch calls before execution", () => { const diagnostics = diagnoseCodeModeTypeScript({ declaration, code: 'await fetch("https://example.com");', }); expect(diagnostics.some((diagnostic) => diagnostic.severity === "error")).toBe(true); + expect(diagnostics.map((diagnostic) => diagnostic.code)).toContain("FETCH_UNAVAILABLE"); + expect(diagnostics.map((diagnostic) => diagnostic.message).join("\n")).toContain( + "Direct fetch is not available", + ); + }); + + it("blocks direct globalThis.fetch calls", () => { + const diagnostics = diagnoseCodeModeTypeScript({ + declaration, + code: 'await globalThis.fetch("https://example.com");', + }); + + expect(diagnostics.some((diagnostic) => diagnostic.severity === "error")).toBe(true); + expect(diagnostics.map((diagnostic) => diagnostic.code)).toContain("FETCH_UNAVAILABLE"); expect(diagnostics.map((diagnostic) => diagnostic.message).join("\n")).toContain( "Direct fetch is not available", ); }); + it.each([ + ["globalThis bracket fetch", 'await globalThis["fetch"]("https://example.com");'], + ["window fetch", 'await window.fetch("https://example.com");'], + ["self fetch", 'await self.fetch("https://example.com");'], + ])("blocks direct global fetch calls through %s", (_name, code) => { + const diagnostics = diagnoseCodeModeTypeScript({ declaration, code }); + + expect(diagnostics.map((diagnostic) => diagnostic.code)).toContain("FETCH_UNAVAILABLE"); + }); + it("does not block fetch text or non-global fetch member calls", () => { const diagnostics = diagnoseCodeModeTypeScript({ declaration, @@ -39,6 +63,7 @@ describe("diagnoseCodeModeTypeScript", () => { const guidance = "Use the browser Caplet instead of await fetch('https://example.com')"; const client = { fetch: (value: string) => ({ value }) }; const result = client.fetch(guidance); + const f = client.fetch; return result; `, }); @@ -47,18 +72,19 @@ describe("diagnoseCodeModeTypeScript", () => { expect(diagnostics.filter((diagnostic) => diagnostic.severity === "error")).toEqual([]); }); - it("allows standard JavaScript, console, URL, JSON, and Caplet callTool", () => { + it("allows standard JavaScript, JSON, console, and Caplet callTool", () => { const diagnostics = diagnoseCodeModeTypeScript({ declaration, code: ` - const url = new URL("https://example.com/issues?state=open"); - console.log(JSON.stringify({ state: url.searchParams.get("state") })); + const state = "open"; + const issue = { state, labels: ["caplets"] }; + console.log(JSON.stringify(issue)); const result = await caplets.github.callTool("listIssues", { state: "open" }); return result; `, }); - expect(diagnostics.filter((diagnostic) => diagnostic.severity === "error")).toEqual([]); + expect(diagnostics).toEqual([]); }); it("blocks static and dynamic imports", () => { @@ -78,6 +104,77 @@ describe("diagnoseCodeModeTypeScript", () => { ); }); + it("keeps Node process and require unavailable", () => { + const diagnostics = diagnoseCodeModeTypeScript({ + declaration, + code: ` + process.cwd(); + require("fs"); + `, + }); + + const codes = diagnostics.map((diagnostic) => diagnostic.code); + expect(codes).toContain("2591"); + expect(diagnostics.map((diagnostic) => diagnostic.message).join("\n")).toContain( + "Cannot find name 'process'", + ); + expect(diagnostics.map((diagnostic) => diagnostic.message).join("\n")).toContain( + "Cannot find name 'require'", + ); + }); + + it("allows platform globals through diagnostics without generated declaration bloat", () => { + const diagnostics = diagnoseCodeModeTypeScript({ + declaration, + code: ` + const url = new URL("https://example.com/a?b=1"); + const text = new TextDecoder().decode(new TextEncoder().encode("ok")); + const bytes = Buffer.from(btoa(text), "base64"); + const headers = new Headers({ accept: "application/json" }); + const blob = new Blob([bytes.toString()], { type: "text/plain" }); + const file = new File([blob], "ok.txt", { type: blob.type }); + const form = new FormData(); + form.append("file", file, file.name); + const request = new Request(url, { method: "POST", headers, body: form }); + const response = Response.json({ ok: true }); + const reader = new ReadableStream({ + start(controller) { + controller.enqueue("ready"); + controller.close(); + }, + }).getReader(); + const writer = new WritableStream({ write() {} }).getWriter(); + const transform = new TransformStream({ + transform(chunk, controller) { + controller.enqueue(chunk); + }, + }); + const controller = new AbortController(); + controller.abort("done"); + controller.signal.throwIfAborted(); + const timeout = setTimeout(() => undefined, 0); + clearTimeout(timeout); + const interval = setInterval(() => undefined, 1); + clearInterval(interval); + queueMicrotask(() => undefined); + crypto.getRandomValues(new Uint8Array(4)); + crypto.randomUUID(); + structuredClone({ text, href: url.href }); + await reader.read(); + await writer.write(request.method); + await writer.close(); + transform.readable.getReader(); + await response.text(); + return { text, b: url.searchParams.get("b"), atob: atob("b2s=") }; + `, + }); + + expect(diagnostics.filter((diagnostic) => diagnostic.severity === "error")).toEqual([]); + expect(diagnostics.map((diagnostic) => diagnostic.message).join("\n")).not.toContain( + "Cannot find name", + ); + }); + it("honors per-line @ts-ignore for TypeScript diagnostics", () => { const diagnostics = diagnoseCodeModeTypeScript({ declaration, diff --git a/packages/core/test/code-mode-platform-api.test.ts b/packages/core/test/code-mode-platform-api.test.ts new file mode 100644 index 00000000..285dfdee --- /dev/null +++ b/packages/core/test/code-mode-platform-api.test.ts @@ -0,0 +1,845 @@ +import { getQuickJS } from "quickjs-emscripten"; +import { describe, expect, it, vi } from "vitest"; +import { CODE_MODE_PLATFORM_RUNTIME_SOURCE } from "../src/code-mode/platform-runtime.generated"; +import { runCodeMode } from "../src/code-mode/runner"; +import type { NativeCapletTool, NativeCapletsService } from "../src/native/service"; + +function service(): NativeCapletsService { + const tools: NativeCapletTool[] = [ + { + caplet: "github", + toolName: "caplets__github", + title: "GitHub", + description: "GitHub repo operations.", + promptGuidance: [], + }, + ]; + + return { + listTools: () => tools, + execute: vi.fn(async (capletId: string, request: unknown) => ({ + ok: true, + capletId, + request, + })), + reload: vi.fn(async () => true), + onToolsChanged: vi.fn(() => () => undefined), + close: vi.fn(async () => undefined), + }; +} + +async function runPlatformCode(code: string) { + return runCodeMode({ + code, + service: service(), + runtimeScope: "test", + }); +} + +describe("Code Mode platform API", () => { + it("exposes supported globals on top-level bindings and globalThis", async () => { + const result = await runPlatformCode(` + return { + atob: { + binding: typeof atob, + globalThis: typeof globalThis.atob, + same: typeof atob !== "undefined" && globalThis.atob === atob, + }, + btoa: { + binding: typeof btoa, + globalThis: typeof globalThis.btoa, + same: typeof btoa !== "undefined" && globalThis.btoa === btoa, + }, + Buffer: { + binding: typeof Buffer, + globalThis: typeof globalThis.Buffer, + same: typeof Buffer !== "undefined" && globalThis.Buffer === Buffer, + }, + URL: { + binding: typeof URL, + globalThis: typeof globalThis.URL, + same: typeof URL !== "undefined" && globalThis.URL === URL, + }, + URLSearchParams: { + binding: typeof URLSearchParams, + globalThis: typeof globalThis.URLSearchParams, + same: typeof URLSearchParams !== "undefined" && globalThis.URLSearchParams === URLSearchParams, + }, + TextEncoder: { + binding: typeof TextEncoder, + globalThis: typeof globalThis.TextEncoder, + same: typeof TextEncoder !== "undefined" && globalThis.TextEncoder === TextEncoder, + }, + TextDecoder: { + binding: typeof TextDecoder, + globalThis: typeof globalThis.TextDecoder, + same: typeof TextDecoder !== "undefined" && globalThis.TextDecoder === TextDecoder, + }, + crypto: { + binding: typeof crypto, + globalThis: typeof globalThis.crypto, + same: typeof crypto !== "undefined" && globalThis.crypto === crypto, + }, + structuredClone: { + binding: typeof structuredClone, + globalThis: typeof globalThis.structuredClone, + same: + typeof structuredClone !== "undefined" && globalThis.structuredClone === structuredClone, + }, + Headers: { + binding: typeof Headers, + globalThis: typeof globalThis.Headers, + same: typeof Headers !== "undefined" && globalThis.Headers === Headers, + }, + Blob: { + binding: typeof Blob, + globalThis: typeof globalThis.Blob, + same: typeof Blob !== "undefined" && globalThis.Blob === Blob, + }, + File: { + binding: typeof File, + globalThis: typeof globalThis.File, + same: typeof File !== "undefined" && globalThis.File === File, + }, + FormData: { + binding: typeof FormData, + globalThis: typeof globalThis.FormData, + same: typeof FormData !== "undefined" && globalThis.FormData === FormData, + }, + ReadableStream: { + binding: typeof ReadableStream, + globalThis: typeof globalThis.ReadableStream, + same: + typeof ReadableStream !== "undefined" && globalThis.ReadableStream === ReadableStream, + }, + WritableStream: { + binding: typeof WritableStream, + globalThis: typeof globalThis.WritableStream, + same: + typeof WritableStream !== "undefined" && globalThis.WritableStream === WritableStream, + }, + TransformStream: { + binding: typeof TransformStream, + globalThis: typeof globalThis.TransformStream, + same: + typeof TransformStream !== "undefined" && globalThis.TransformStream === TransformStream, + }, + AbortController: { + binding: typeof AbortController, + globalThis: typeof globalThis.AbortController, + same: + typeof AbortController !== "undefined" && + globalThis.AbortController === AbortController, + }, + AbortSignal: { + binding: typeof AbortSignal, + globalThis: typeof globalThis.AbortSignal, + same: typeof AbortSignal !== "undefined" && globalThis.AbortSignal === AbortSignal, + }, + Request: { + binding: typeof Request, + globalThis: typeof globalThis.Request, + same: typeof Request !== "undefined" && globalThis.Request === Request, + }, + Response: { + binding: typeof Response, + globalThis: typeof globalThis.Response, + same: typeof Response !== "undefined" && globalThis.Response === Response, + }, + console: { + binding: typeof console, + globalThis: typeof globalThis.console, + same: typeof console !== "undefined" && globalThis.console === console, + }, + queueMicrotask: { + binding: typeof queueMicrotask, + globalThis: typeof globalThis.queueMicrotask, + same: + typeof queueMicrotask !== "undefined" && + globalThis.queueMicrotask === queueMicrotask, + }, + setTimeout: { + binding: typeof setTimeout, + globalThis: typeof globalThis.setTimeout, + same: typeof setTimeout !== "undefined" && globalThis.setTimeout === setTimeout, + }, + clearTimeout: { + binding: typeof clearTimeout, + globalThis: typeof globalThis.clearTimeout, + same: typeof clearTimeout !== "undefined" && globalThis.clearTimeout === clearTimeout, + }, + setInterval: { + binding: typeof setInterval, + globalThis: typeof globalThis.setInterval, + same: typeof setInterval !== "undefined" && globalThis.setInterval === setInterval, + }, + clearInterval: { + binding: typeof clearInterval, + globalThis: typeof globalThis.clearInterval, + same: + typeof clearInterval !== "undefined" && + globalThis.clearInterval === clearInterval, + }, + }; + `); + + expect(result).toMatchObject({ + ok: true, + value: { + atob: { binding: "function", globalThis: "function", same: true }, + btoa: { binding: "function", globalThis: "function", same: true }, + Buffer: { binding: "function", globalThis: "function", same: true }, + URL: { binding: "function", globalThis: "function", same: true }, + URLSearchParams: { binding: "function", globalThis: "function", same: true }, + TextEncoder: { binding: "function", globalThis: "function", same: true }, + TextDecoder: { binding: "function", globalThis: "function", same: true }, + crypto: { binding: "object", globalThis: "object", same: true }, + structuredClone: { binding: "function", globalThis: "function", same: true }, + Headers: { binding: "function", globalThis: "function", same: true }, + Blob: { binding: "function", globalThis: "function", same: true }, + File: { binding: "function", globalThis: "function", same: true }, + FormData: { binding: "function", globalThis: "function", same: true }, + ReadableStream: { binding: "function", globalThis: "function", same: true }, + WritableStream: { binding: "function", globalThis: "function", same: true }, + TransformStream: { binding: "function", globalThis: "function", same: true }, + AbortController: { binding: "function", globalThis: "function", same: true }, + AbortSignal: { binding: "function", globalThis: "function", same: true }, + Request: { binding: "function", globalThis: "function", same: true }, + Response: { binding: "function", globalThis: "function", same: true }, + console: { binding: "object", globalThis: "object", same: true }, + queueMicrotask: { binding: "function", globalThis: "function", same: true }, + setTimeout: { binding: "function", globalThis: "function", same: true }, + clearTimeout: { binding: "function", globalThis: "function", same: true }, + setInterval: { binding: "function", globalThis: "function", same: true }, + clearInterval: { binding: "function", globalThis: "function", same: true }, + }, + }); + }); + + it("supports base64 and minimal Buffer conversions", async () => { + const result = await runPlatformCode(` + return { + globalThisBtoa: typeof globalThis.btoa, + globalThisAtob: typeof globalThis.atob, + globalThisBuffer: typeof globalThis.Buffer, + btoa: globalThis.btoa("hello"), + atob: globalThis.atob("aGVsbG8="), + bufferUtf8: globalThis.Buffer.from("hello", "utf8").toString("utf8"), + bufferBase64: globalThis.Buffer.from("hello", "utf8").toString("base64"), + bufferFromBase64: globalThis.Buffer.from("aGVsbG8=", "base64").toString("utf8"), + }; + `); + + expect(result).toMatchObject({ + ok: true, + value: { + globalThisBtoa: "function", + globalThisAtob: "function", + globalThisBuffer: "function", + btoa: "aGVsbG8=", + atob: "hello", + bufferUtf8: "hello", + bufferBase64: "aGVsbG8=", + bufferFromBase64: "hello", + }, + }); + }); + + it("supports URL and URLSearchParams", async () => { + const result = await runPlatformCode(` + const url = new globalThis.URL("https://example.com/path?q=1"); + url.searchParams.set("page", "2"); + + return { + globalThisURL: typeof globalThis.URL, + globalThisURLSearchParams: typeof globalThis.URLSearchParams, + href: url.href, + origin: url.origin, + pathname: url.pathname, + search: url.search, + params: [...url.searchParams.entries()], + }; + `); + + expect(result).toMatchObject({ + ok: true, + value: { + globalThisURL: "function", + globalThisURLSearchParams: "function", + href: "https://example.com/path?q=1&page=2", + origin: "https://example.com", + pathname: "/path", + search: "?q=1&page=2", + params: [ + ["q", "1"], + ["page", "2"], + ], + }, + }); + }); + + it("supports relative URL construction with live URLSearchParams mutation", async () => { + const result = await runPlatformCode(` + const url = new globalThis.URL("/child?x=1", "https://example.com/base"); + const queryOnly = new globalThis.URL("?page=2", "https://api.example.com/v1/items"); + const hashOnly = new globalThis.URL("#details", "https://api.example.com/v1/items?token=abc=="); + const params = new globalThis.URLSearchParams("token=abc==&space=a+b"); + const before = { + href: url.href, + toString: url.toString(), + search: url.search, + params: [...url.searchParams.entries()], + }; + url.searchParams.set("x", "2"); + url.searchParams.append("page", "3"); + + return { + before, + after: { + href: url.href, + toString: url.toString(), + json: url.toJSON(), + origin: url.origin, + pathname: url.pathname, + search: url.search, + params: [...url.searchParams.entries()], + }, + queryOnly: queryOnly.href, + hashOnly: hashOnly.href, + params: [...params.entries()], + }; + `); + + expect(result).toMatchObject({ + ok: true, + value: { + before: { + href: "https://example.com/child?x=1", + toString: "https://example.com/child?x=1", + search: "?x=1", + params: [["x", "1"]], + }, + after: { + href: "https://example.com/child?x=2&page=3", + toString: "https://example.com/child?x=2&page=3", + json: "https://example.com/child?x=2&page=3", + origin: "https://example.com", + pathname: "/child", + search: "?x=2&page=3", + params: [ + ["x", "2"], + ["page", "3"], + ], + }, + queryOnly: "https://api.example.com/v1/items?page=2", + hashOnly: "https://api.example.com/v1/items?token=abc==#details", + params: [ + ["token", "abc=="], + ["space", "a b"], + ], + }, + }); + }); + + it("uses href when constructing Request from URL objects", async () => { + const result = await runPlatformCode(` + const url = new globalThis.URL("/api?x=1", "https://example.com/base"); + const request = new globalThis.Request(url); + + return { + url: request.url, + }; + `); + + expect(result).toMatchObject({ + ok: true, + value: { + url: "https://example.com/api?x=1", + }, + }); + }); + + it("supports text encoding and decoding", async () => { + const result = await runPlatformCode(` + const encoder = new globalThis.TextEncoder(); + const decoder = new globalThis.TextDecoder(); + const bytes = encoder.encode("hello"); + + return { + globalThisTextEncoder: typeof globalThis.TextEncoder, + globalThisTextDecoder: typeof globalThis.TextDecoder, + encoded: Array.from(bytes), + decoded: decoder.decode(bytes), + malformedTextDecoder: decoder.decode(new Uint8Array([0xc3, 0x28])), + malformedBuffer: globalThis.Buffer.from([0xc3, 0x28]).toString("utf8"), + }; + `); + + expect(result).toMatchObject({ + ok: true, + value: { + globalThisTextEncoder: "function", + globalThisTextDecoder: "function", + encoded: [104, 101, 108, 108, 111], + decoded: "hello", + malformedTextDecoder: "\uFFFD(", + malformedBuffer: "\uFFFD(", + }, + }); + }); + + it("supports crypto randomUUID and getRandomValues", async () => { + const result = await runPlatformCode(` + const first = new Uint8Array(32); + const second = new Uint8Array(32); + globalThis.crypto.getRandomValues(first); + globalThis.crypto.getRandomValues(second); + + return { + globalThisCrypto: typeof globalThis.crypto, + sameGlobal: globalThis.crypto === crypto, + randomUUID: globalThis.crypto.randomUUID(), + first: Array.from(first), + second: Array.from(second), + firstHasNonZero: first.some((value) => value !== 0), + secondHasNonZero: second.some((value) => value !== 0), + samplesDiffer: first.some((value, index) => value !== second[index]), + }; + `); + + expect(result).toMatchObject({ + ok: true, + value: { + globalThisCrypto: "object", + sameGlobal: true, + randomUUID: expect.stringMatching( + /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/u, + ), + first: expect.any(Array), + second: expect.any(Array), + firstHasNonZero: true, + secondHasNonZero: true, + samplesDiffer: true, + }, + }); + }); + + it("hides host bridge globals from user code while public crypto and timer APIs work", async () => { + const result = await runPlatformCode(` + const bridgeNames = [ + "__caplets_platform_random_uuid", + "__caplets_platform_random_values", + "__caplets_platform_sleep", + "__caplets_platform_clear_timer", + ]; + const bytes = new Uint8Array(8); + globalThis.crypto.getRandomValues(bytes); + + const timeoutWorked = await new Promise((resolve) => { + const timeout = globalThis.setTimeout(() => resolve(true), 0); + globalThis.clearTimeout(timeout); + globalThis.setTimeout(() => resolve("rescheduled"), 0); + }); + + return { + bridges: bridgeNames.map((name) => ({ + name, + inGlobalThis: name in globalThis, + globalThisType: typeof globalThis[name], + topLevelType: eval(\`typeof \${name}\`), + })), + randomUUID: globalThis.crypto.randomUUID(), + bytes: Array.from(bytes), + bytesChanged: bytes.some((value) => value !== 0), + timeoutWorked, + }; + `); + + expect(result).toMatchObject({ + ok: true, + value: { + bridges: [ + { + name: "__caplets_platform_random_uuid", + inGlobalThis: false, + globalThisType: "undefined", + topLevelType: "undefined", + }, + { + name: "__caplets_platform_random_values", + inGlobalThis: false, + globalThisType: "undefined", + topLevelType: "undefined", + }, + { + name: "__caplets_platform_sleep", + inGlobalThis: false, + globalThisType: "undefined", + topLevelType: "undefined", + }, + { + name: "__caplets_platform_clear_timer", + inGlobalThis: false, + globalThisType: "undefined", + topLevelType: "undefined", + }, + ], + randomUUID: expect.stringMatching( + /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/u, + ), + bytes: expect.any(Array), + bytesChanged: true, + timeoutWorked: "rescheduled", + }, + }); + }); + + it("supports timers, intervals, and microtasks", async () => { + const result = await runPlatformCode(` + return await new Promise((resolve) => { + const events = []; + let ticks = 0; + const interval = globalThis.setInterval(() => { + ticks += 1; + events.push(\`interval-\${ticks}\`); + if (ticks === 2) { + globalThis.clearInterval(interval); + resolve(events); + } + }, 0); + + globalThis.queueMicrotask(() => { + events.push("microtask"); + }); + + globalThis.setTimeout(() => { + events.push("timeout"); + }, 0); + }); + `); + + expect(result.ok).toBe(true); + if (!result.ok) { + return; + } + + const events = result.value as string[]; + expect(events).toEqual(expect.arrayContaining(["microtask", "timeout"])); + expect(events).toEqual(expect.arrayContaining(["interval-1", "interval-2"])); + expect(events[0]).toBe("microtask"); + expect(events.filter((event) => event.startsWith("interval-"))).toEqual([ + "interval-1", + "interval-2", + ]); + }); + + it("supports structuredClone", async () => { + const result = await runPlatformCode(` + const source = { + nested: { count: 1 }, + values: [1, 2, 3], + }; + const clone = globalThis.structuredClone(source); + clone.nested.count = 2; + clone.values.push(4); + + return { + globalThisStructuredClone: typeof globalThis.structuredClone, + sameGlobal: globalThis.structuredClone === structuredClone, + source: source.nested.count, + clone: clone.nested.count, + values: clone.values, + }; + `); + + expect(result).toMatchObject({ + ok: true, + value: { + globalThisStructuredClone: "function", + sameGlobal: true, + source: 1, + clone: 2, + values: [1, 2, 3, 4], + }, + }); + }); + + it("supports Headers, Blob, File, FormData, streams, Request, and Response", async () => { + const result = await runPlatformCode(` + const headers = new globalThis.Headers([["x-one", "1"]]); + const blob = new globalThis.Blob(["hello"], { type: "text/plain" }); + const nestedBlobText = await new globalThis.Blob([new globalThis.Blob(["x"])]).text(); + const file = new globalThis.File(["hello"], "hello.txt", { type: "text/plain" }); + const formData = new globalThis.FormData(); + formData.set("name", "caplets"); + formData.set("fileName", file.name); + + const readableStream = new globalThis.ReadableStream({ + start(controller) { + controller.enqueue("hello"); + controller.close(); + }, + }); + const writableStream = new globalThis.WritableStream({ + write(chunk) { + writes.push(chunk); + }, + }); + const transformStream = new globalThis.TransformStream({ + transform(chunk, controller) { + controller.enqueue(String(chunk).toUpperCase()); + }, + }); + const writes = []; + const readableFirst = await readableStream.getReader().read(); + const writer = writableStream.getWriter(); + await writer.write("written"); + await writer.close(); + const transformWriter = transformStream.writable.getWriter(); + await transformWriter.write("mixed"); + await transformWriter.close(); + const transformFirst = await transformStream.readable.getReader().read(); + + const request = new globalThis.Request("https://example.com/api", { + method: "POST", + headers, + body: formData, + }); + + const response = new globalThis.Response(blob, { + status: 201, + headers, + }); + + return { + globalThisHeaders: typeof globalThis.Headers, + globalThisBlob: typeof globalThis.Blob, + globalThisFile: typeof globalThis.File, + globalThisFormData: typeof globalThis.FormData, + globalThisReadableStream: typeof globalThis.ReadableStream, + globalThisWritableStream: typeof globalThis.WritableStream, + globalThisTransformStream: typeof globalThis.TransformStream, + globalThisRequest: typeof globalThis.Request, + globalThisResponse: typeof globalThis.Response, + headers: headers.get("x-one"), + blobSize: blob.size, + blobType: blob.type, + nestedBlobText, + fileName: file.name, + fileType: file.type, + formEntries: [...formData.entries()], + readableStreamType: typeof readableStream.getReader, + writableStreamType: typeof writableStream.getWriter, + transformStreamType: typeof transformStream.readable, + readableFirst, + writes, + transformFirst, + requestMethod: request.method, + requestUrl: request.url, + responseStatus: response.status, + responseHeader: response.headers.get("x-one"), + }; + `); + + expect(result).toMatchObject({ + ok: true, + value: { + globalThisHeaders: "function", + globalThisBlob: "function", + globalThisFile: "function", + globalThisFormData: "function", + globalThisReadableStream: "function", + globalThisWritableStream: "function", + globalThisTransformStream: "function", + globalThisRequest: "function", + globalThisResponse: "function", + headers: "1", + blobSize: 5, + blobType: "text/plain", + nestedBlobText: "x", + fileName: "hello.txt", + fileType: "text/plain", + formEntries: [ + ["name", "caplets"], + ["fileName", "hello.txt"], + ], + readableStreamType: "function", + writableStreamType: "function", + transformStreamType: "object", + readableFirst: { value: "hello", done: false }, + writes: ["written"], + transformFirst: { value: "MIXED", done: false }, + requestMethod: "POST", + requestUrl: "https://example.com/api", + responseStatus: 201, + responseHeader: "1", + }, + }); + }); + + it("supports AbortController and AbortSignal", async () => { + const result = await runPlatformCode(` + const controller = new globalThis.AbortController(); + const { signal } = controller; + const before = signal.aborted; + controller.abort("done"); + + return { + globalThisAbortController: typeof globalThis.AbortController, + globalThisAbortSignal: typeof globalThis.AbortSignal, + sameAbortController: globalThis.AbortController === AbortController, + sameAbortSignal: globalThis.AbortSignal === AbortSignal, + before, + after: signal.aborted, + reason: signal.reason, + }; + `); + + expect(result).toMatchObject({ + ok: true, + value: { + globalThisAbortController: "function", + globalThisAbortSignal: "function", + sameAbortController: true, + sameAbortSignal: true, + before: false, + after: true, + reason: "done", + }, + }); + }); + + it("keeps fetch unavailable for direct calls", async () => { + const directResult = await runPlatformCode(` + console.log("fetch sentinel should not run"); + const response = await fetch("data:text/plain,blocked"); + return { + ok: response.ok, + status: response.status, + text: await response.text(), + }; + `); + const globalResult = await runPlatformCode(` + console.log("global fetch sentinel should not run"); + const response = await globalThis.fetch("data:text/plain,blocked"); + return { + ok: response.ok, + status: response.status, + text: await response.text(), + }; + `); + + expect(directResult.ok).toBe(false); + expect(globalResult.ok).toBe(false); + expect(directResult.diagnostics.map((diagnostic) => diagnostic.code)).toContain( + "FETCH_UNAVAILABLE", + ); + expect(globalResult.diagnostics.map((diagnostic) => diagnostic.code)).toContain( + "FETCH_UNAVAILABLE", + ); + expect(directResult.logs.entries).toEqual([]); + expect(globalResult.logs.entries).toEqual([]); + }); + + it("overwrites host fetch with the disabled Code Mode fetch during platform install", async () => { + const QuickJS = await getQuickJS(); + const runtime = QuickJS.newRuntime(); + const context = runtime.newContext(); + try { + const logBridge = context.newFunction("__caplets_log", () => context.undefined); + context.setProp(context.global, "__caplets_log", logBridge); + logBridge.dispose(); + + const result = context.evalCode(` + Object.defineProperty(globalThis, "fetch", { + value: () => "host-fetch-leaked", + writable: true, + configurable: true, + }); + ${CODE_MODE_PLATFORM_RUNTIME_SOURCE} + (() => { + try { + globalThis.fetch("data:text/plain,blocked"); + return { threw: false }; + } catch (error) { + return { + threw: true, + message: error && error.message, + }; + } + })() + `); + + expect(result.error).toBeUndefined(); + if (result.error) { + result.error.dispose(); + return; + } + expect(context.dump(result.value)).toEqual({ + threw: true, + message: "Direct fetch is not available in Code Mode; use a Caplet instead.", + }); + result.value.dispose(); + runtime.executePendingJobs(); + } finally { + context.dispose(); + runtime.dispose(); + } + }); + + it("keeps Node globals unavailable", async () => { + const result = await runPlatformCode(` + return { + process: typeof process, + globalThisProcess: typeof globalThis.process, + module: typeof module, + globalThisModule: typeof globalThis.module, + exports: typeof exports, + globalThisExports: typeof globalThis.exports, + require: typeof require, + globalThisRequire: typeof globalThis.require, + global: typeof global, + globalThisGlobal: typeof globalThis.global, + __dirname: typeof __dirname, + globalThisDirname: typeof globalThis.__dirname, + __filename: typeof __filename, + globalThisFilename: typeof globalThis.__filename, + }; + `); + + expect(result).toMatchObject({ + ok: true, + value: { + process: "undefined", + globalThisProcess: "undefined", + module: "undefined", + globalThisModule: "undefined", + exports: "undefined", + globalThisExports: "undefined", + require: "undefined", + globalThisRequire: "undefined", + global: "undefined", + globalThisGlobal: "undefined", + __dirname: "undefined", + globalThisDirname: "undefined", + __filename: "undefined", + globalThisFilename: "undefined", + }, + }); + }); + + it.each([ + ["node:fs", "filesystem"], + ["node:child_process", "child process"], + ["node:http", "direct network"], + ["left-pad", "arbitrary"], + ])("blocks %s imports with IMPORT_UNAVAILABLE", async (specifier) => { + const result = await runPlatformCode(` + await import(${JSON.stringify(specifier)}); + return { done: true }; + `); + + expect(result.ok).toBe(false); + expect(result.diagnostics.map((diagnostic) => diagnostic.code)).toContain("IMPORT_UNAVAILABLE"); + }); +}); diff --git a/packages/core/test/code-mode-static-analysis.test.ts b/packages/core/test/code-mode-static-analysis.test.ts index e1d833f8..85618881 100644 --- a/packages/core/test/code-mode-static-analysis.test.ts +++ b/packages/core/test/code-mode-static-analysis.test.ts @@ -7,6 +7,13 @@ describe("Code Mode static analysis", () => { expect(hasDirectFetchCall('return await fetch("https://example.com");')).toBe(true); expect(hasDirectFetchCall('await globalThis.fetch("https://example.com");')).toBe(true); expect(hasDirectFetchCall('await globalThis["fetch"]("https://example.com");')).toBe(true); + expect(hasDirectFetchCall('await window.fetch("https://example.com");')).toBe(true); + expect(hasDirectFetchCall('await self.fetch("https://example.com");')).toBe(true); + expect(hasDirectFetchCall("const f = fetch;")).toBe(false); + expect(hasDirectFetchCall("const f = globalThis.fetch;")).toBe(false); + expect(hasDirectFetchCall('const f = globalThis["fetch"];')).toBe(false); + expect(hasDirectFetchCall("const f = window.fetch;")).toBe(false); + expect(hasDirectFetchCall("const f = self.fetch;")).toBe(false); expect(hasDirectFetchCall('const note = "await fetch(\\"https://example.com\\")";')).toBe( false, ); @@ -14,6 +21,15 @@ describe("Code Mode static analysis", () => { hasDirectFetchCall("const fetch = client.fetch; await globalThis[fetch]('/issues');"), ).toBe(false); expect(hasDirectFetchCall("const result = client.fetch('/issues');")).toBe(false); + expect(hasDirectFetchCall("const f = client.fetch;")).toBe(false); + }); + + it("does not chase fetch aliases or wrapper methods", () => { + expect(hasDirectFetchCall('await fetch.call(globalThis, "https://example.com");')).toBe(false); + expect( + hasDirectFetchCall('await globalThis.fetch.call(globalThis, "https://example.com");'), + ).toBe(false); + expect(hasDirectFetchCall("const f = fetch; await f('/issues');")).toBe(false); }); it("detects executable imports without blocking import text", () => { diff --git a/packages/core/test/config.test.ts b/packages/core/test/config.test.ts index a00496e5..f7fc925c 100644 --- a/packages/core/test/config.test.ts +++ b/packages/core/test/config.test.ts @@ -25,6 +25,7 @@ import { } from "../src/config"; import { listCaplets } from "../src/cli/inspection"; import { CapletsError } from "../src/errors"; +import { ServerRegistry } from "../src/registry"; describe("config", () => { const originalEnv = process.env.EXAMPLE_TOKEN; @@ -375,6 +376,35 @@ describe("config", () => { delete process.env.PROJECT_OPENAPI_SECRET; }); + it("rejects Google Discovery executable backend maps from project config", () => { + const dir = mkdtempSync(join(tmpdir(), "caplets-project-google-discovery-")); + const projectConfigPath = join(dir, ".caplets", "config.json"); + mkdirSync(join(dir, ".caplets"), { recursive: true }); + writeFileSync( + projectConfigPath, + JSON.stringify({ + googleDiscoveryApis: { + drive: { + name: "Google Drive", + description: "Attempt to load Google Drive from project config.", + discoveryUrl: "https://www.googleapis.com/discovery/v1/apis/drive/v3/rest", + auth: { type: "none" }, + }, + }, + }), + ); + + expect(() => loadConfig(join(dir, "missing-user-config.json"), projectConfigPath)).toThrow( + expect.objectContaining({ + code: "CONFIG_INVALID", + message: expect.stringContaining( + "cannot define executable backend map googleDiscoveryApis; use project Markdown Caplet files or user config instead", + ) as string, + }) as CapletsError, + ); + rmSync(dir, { recursive: true, force: true }); + }); + it("rejects Caplet set executable backend maps from project config", () => { const dir = mkdtempSync(join(tmpdir(), "caplets-project-capletsets-")); const projectConfigPath = join(dir, ".caplets", "config.json"); @@ -1217,6 +1247,46 @@ describe("config", () => { rmSync(dir, { recursive: true, force: true }); }); + it("loads Google Discovery API-backed Caplet files", () => { + const dir = mkdtempSync(join(tmpdir(), "caplets-google-discovery-files-")); + const root = join(dir, ".caplets"); + mkdirSync(root, { recursive: true }); + writeFileSync( + join(root, "drive.md"), + [ + "---", + "name: Drive API", + "description: Manage Drive files through Google Discovery.", + "googleDiscoveryApi:", + " discoveryPath: ./drive.discovery.json", + " auth:", + " type: oauth2", + " issuer: https://accounts.google.com", + " scopes:", + " - https://www.googleapis.com/auth/drive.metadata.readonly", + " includeOperations:", + " - files.*", + "---", + "# Drive API", + ].join("\n"), + ); + + const config = loadConfig(join(root, "config.json"), join(dir, "missing", "config.json")); + expect(config.googleDiscoveryApis.drive).toMatchObject({ + server: "drive", + backend: "googleDiscovery", + discoveryPath: join(root, "drive.discovery.json"), + auth: { + type: "oauth2", + issuer: "https://accounts.google.com", + scopes: ["https://www.googleapis.com/auth/drive.metadata.readonly"], + }, + includeOperations: ["files.*"], + body: "# Drive API", + }); + rmSync(dir, { recursive: true, force: true }); + }); + it("loads GraphQL config and GraphQL-backed Caplet files", () => { const dir = mkdtempSync(join(tmpdir(), "caplets-graphql-files-")); const root = join(dir, ".caplets"); @@ -1941,6 +2011,49 @@ describe("config", () => { delete process.env.OPENAPI_PUBLIC_SECRET; }); + it("loads Google Discovery APIs with defaults and safe registry details", () => { + const config = parseConfig({ + googleDiscoveryApis: { + drive: { + name: "Google Drive", + description: "Access Google Drive files and permissions.", + discoveryUrl: "https://www.googleapis.com/discovery/v1/apis/drive/v3/rest", + auth: { type: "oidc", issuer: "https://accounts.google.com", clientId: "client" }, + includeOperations: ["drive.files.*"], + excludeOperations: ["drive.files.delete"], + }, + }, + }); + + expect(config.googleDiscoveryApis.drive).toMatchObject({ + server: "drive", + backend: "googleDiscovery", + discoveryUrl: "https://www.googleapis.com/discovery/v1/apis/drive/v3/rest", + requestTimeoutMs: 60000, + operationCacheTtlMs: 30000, + disabled: false, + }); + expect(new ServerRegistry(config).detail(config.googleDiscoveryApis.drive!)).toEqual({ + id: "drive", + name: "Google Drive", + description: "Access Google Drive files and permissions.", + backend: { + type: "googleDiscovery", + disabled: false, + requestTimeoutMs: 60000, + operationCacheTtlMs: 30000, + source: "discoveryUrl", + }, + }); + expect( + JSON.stringify(new ServerRegistry(config).detail(config.googleDiscoveryApis.drive!)), + ).not.toContain("client"); + expect( + JSON.stringify(new ServerRegistry(config).detail(config.googleDiscoveryApis.drive!)), + ).not.toContain("googleapis.com"); + expect(JSON.stringify(configJsonSchema())).toContain("googleDiscoveryApis"); + }); + it("rejects nested Caplets options", () => { expect(() => parseConfig({ @@ -2073,6 +2186,93 @@ describe("config", () => { } }); + it("rejects invalid Google Discovery sources and duplicate Caplet IDs", () => { + expect(() => + parseConfig({ + googleDiscoveryApis: { + drive: { + name: "Drive", + description: "Access Google Drive files.", + discoveryUrl: "ftp://example.com/discovery.json", + auth: { type: "none" }, + }, + }, + }), + ).toThrow(CapletsError); + expect(() => + parseConfig({ + openapiEndpoints: { + drive: { + name: "Drive OpenAPI", + description: "OpenAPI Drive wrapper.", + specUrl: "https://example.com/openapi.json", + baseUrl: "https://example.com", + auth: { type: "none" }, + }, + }, + googleDiscoveryApis: { + drive: { + name: "Drive", + description: "Access Google Drive files.", + discoveryUrl: "https://www.googleapis.com/discovery/v1/apis/drive/v3/rest", + auth: { type: "none" }, + }, + }, + }), + ).toThrow( + expect.objectContaining({ + details: expect.arrayContaining([ + expect.objectContaining({ message: expect.stringContaining("already used") }), + ]) as unknown, + }) as CapletsError, + ); + expect(() => + parseConfig({ + httpApis: { + drive: { + name: "Drive HTTP", + description: "HTTP Drive wrapper.", + baseUrl: "https://example.com", + auth: { type: "none" }, + actions: { + list: { method: "GET", path: "/files" }, + }, + }, + }, + googleDiscoveryApis: { + drive: { + name: "Drive", + description: "Access Google Drive files.", + discoveryUrl: "https://www.googleapis.com/discovery/v1/apis/drive/v3/rest", + auth: { type: "none" }, + }, + }, + }), + ).toThrow( + expect.objectContaining({ + details: expect.arrayContaining([ + expect.objectContaining({ + path: ["googleDiscoveryApis", "drive"], + message: expect.stringContaining("already used by httpApis"), + }), + ]) as unknown, + }) as CapletsError, + ); + expect(() => + parseConfig({ + googleDiscoveryApis: { + drive: { + name: "Drive", + description: "Access Google Drive files.", + discoveryPath: "/tmp/drive.discovery.json", + discoveryUrl: "https://www.googleapis.com/discovery/v1/apis/drive/v3/rest", + auth: { type: "none" }, + }, + }, + }), + ).toThrow(CapletsError); + }); + it("validates server IDs, required names, descriptions, and disabled default", () => { const valid = parseConfig({ mcpServers: { diff --git a/packages/core/test/engine.test.ts b/packages/core/test/engine.test.ts index 8f592c15..71c2937d 100644 --- a/packages/core/test/engine.test.ts +++ b/packages/core/test/engine.test.ts @@ -91,6 +91,24 @@ describe("CapletsEngine", () => { expect(engine.enabledServers().map((caplet) => caplet.server)).toEqual(["gamma"]); }); + it("includes enabled Google Discovery API Caplets", () => { + const { dir, configPath, projectConfigPath } = tempConfig({ + googleDiscoveryApis: { + drive: { + name: "Google Drive", + description: "Access Google Drive files and permissions.", + discoveryUrl: "https://www.googleapis.com/discovery/v1/apis/drive/v3/rest", + auth: { type: "none" }, + }, + }, + }); + dirs.push(dir); + const engine = new CapletsEngine({ configPath, projectConfigPath, watch: false }); + engines.push(engine); + + expect(engine.enabledServers().map((caplet) => caplet.server)).toEqual(["drive"]); + }); + it("keeps last known-good config when reload validation fails", async () => { const { dir, configPath, projectConfigPath } = tempConfig({ mcpServers: { diff --git a/packages/core/test/exposure-discovery.test.ts b/packages/core/test/exposure-discovery.test.ts index b5f868bb..b02048eb 100644 --- a/packages/core/test/exposure-discovery.test.ts +++ b/packages/core/test/exposure-discovery.test.ts @@ -120,6 +120,7 @@ function configFor( caplets.filter((caplet) => caplet.backend === "mcp").map((caplet) => [caplet.server, caplet]), ) as CapletsConfig["mcpServers"], openapiEndpoints: {}, + googleDiscoveryApis: {}, graphqlEndpoints: {}, httpApis: Object.fromEntries( caplets diff --git a/packages/core/test/fixtures/google-discovery/drive.discovery.json b/packages/core/test/fixtures/google-discovery/drive.discovery.json new file mode 100644 index 00000000..ee669ece --- /dev/null +++ b/packages/core/test/fixtures/google-discovery/drive.discovery.json @@ -0,0 +1,132 @@ +{ + "kind": "discovery#restDescription", + "id": "drive:v3", + "name": "drive", + "version": "v3", + "title": "Drive API", + "rootUrl": "https://www.googleapis.com/", + "servicePath": "drive/v3/", + "baseUrl": "https://www.googleapis.com/drive/v3/", + "auth": { + "oauth2": { + "scopes": { + "https://www.googleapis.com/auth/drive": { "description": "Full Drive access." }, + "https://www.googleapis.com/auth/drive.readonly": { "description": "Read Drive files." } + } + } + }, + "parameters": { + "fields": { + "type": "string", + "location": "query", + "description": "Partial response selector." + }, + "prettyPrint": { "type": "boolean", "location": "query", "default": "true" }, + "quotaUser": { "type": "string", "location": "header" } + }, + "schemas": { + "File": { + "id": "File", + "type": "object", + "properties": { + "id": { "type": "string" }, + "name": { "type": "string" }, + "parents": { "type": "array", "items": { "type": "string" } } + } + }, + "FileList": { + "id": "FileList", + "type": "object", + "properties": { + "files": { "type": "array", "items": { "$ref": "File" } }, + "nextPageToken": { "type": "string" } + } + } + }, + "resources": { + "files": { + "methods": { + "list": { + "id": "drive.files.list", + "path": "files", + "httpMethod": "GET", + "description": "Lists files.", + "scopes": ["https://www.googleapis.com/auth/drive.readonly"], + "parameters": { + "pageSize": { + "type": "integer", + "format": "int32", + "location": "query", + "default": "100" + } + }, + "response": { "$ref": "FileList" } + }, + "delete": { + "id": "drive.files.delete", + "path": "files/{fileId}", + "httpMethod": "DELETE", + "description": "Permanently deletes a file.", + "parameters": { + "fileId": { "type": "string", "location": "path", "required": true } + }, + "scopes": ["https://www.googleapis.com/auth/drive"] + }, + "create": { + "id": "drive.files.create", + "path": "files", + "httpMethod": "POST", + "description": "Creates a file.", + "request": { "$ref": "File" }, + "response": { "$ref": "File" }, + "scopes": ["https://www.googleapis.com/auth/drive"], + "supportsMediaUpload": true, + "mediaUpload": { + "accept": ["image/png"], + "maxSize": "10MB", + "protocols": { + "simple": { "path": "/upload/drive/v3/files", "multipart": false }, + "multipart": { "path": "/upload/drive/v3/files", "multipart": true }, + "resumable": { "path": "/upload/drive/v3/files", "multipart": true } + } + } + }, + "download": { + "id": "drive.files.download", + "path": "files/{fileId}/download", + "httpMethod": "GET", + "description": "Downloads file media.", + "supportsMediaDownload": true, + "parameters": { + "fileId": { "type": "string", "location": "path", "required": true } + }, + "scopes": ["https://www.googleapis.com/auth/drive.readonly"] + } + }, + "resources": { + "permissions": { + "methods": { + "list": { + "id": "drive.permissions.list", + "path": "files/{fileId}/permissions", + "httpMethod": "GET", + "parameters": { + "fileId": { "type": "string", "location": "path", "required": true } + }, + "scopes": ["https://www.googleapis.com/auth/drive.readonly"] + } + } + } + } + }, + "changes": { + "methods": { + "getStartPageToken": { + "path": "changes/startPageToken", + "httpMethod": "GET", + "scopes": ["https://www.googleapis.com/auth/drive.readonly"] + } + } + } + } +} diff --git a/packages/core/test/google-discovery.test.ts b/packages/core/test/google-discovery.test.ts new file mode 100644 index 00000000..829a9e22 --- /dev/null +++ b/packages/core/test/google-discovery.test.ts @@ -0,0 +1,1672 @@ +import { existsSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs"; +import { createServer, type IncomingMessage, type ServerResponse } from "node:http"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { afterEach, beforeEach, describe, expect, it } from "vitest"; +import { + buildGoogleDiscoveryUploadUrl, + buildGoogleDiscoveryUrl, + discoveryOperations, + GoogleDiscoveryManager, + googleDiscoveryScopesForOperations, +} from "../src/google-discovery"; +import { writeTokenBundle } from "../src/auth"; +import { parseConfig } from "../src/config"; +import { DownstreamManager } from "../src/downstream"; +import { ServerRegistry } from "../src/registry"; +import { handleServerTool } from "../src/tools"; + +const fixture = JSON.parse( + readFileSync(join(__dirname, "fixtures/google-discovery/drive.discovery.json"), "utf8"), +); + +let server: ReturnType | undefined; +let baseUrl = ""; +const requests: Array<{ + method: string; + url: string; + body: string; + headers: Record; +}> = []; + +beforeEach(async () => { + requests.length = 0; + server = createServer((request: IncomingMessage, response: ServerResponse) => { + const bodyChunks: Buffer[] = []; + request.on("data", (chunk) => bodyChunks.push(Buffer.from(chunk))); + request.on("end", () => { + const url = request.url ?? "/"; + requests.push({ + method: request.method ?? "GET", + url, + body: Buffer.concat(bodyChunks).toString("utf8"), + headers: request.headers, + }); + response.setHeader("content-type", "application/json"); + if (url === "/drive.discovery.json") { + response.end(JSON.stringify(fixture)); + return; + } + if (url === "/private.discovery.json") { + if ( + request.headers.authorization !== "Bearer private-discovery-token" && + request.headers.authorization !== "Bearer refreshed-discovery-token" + ) { + response.statusCode = 401; + response.end(JSON.stringify({ error: "auth required" })); + return; + } + response.end(JSON.stringify(fixture)); + return; + } + if (url === "/slow.discovery.json") { + response.writeHead(200, { "content-type": "application/json" }); + response.write('{"kind":"discovery#restDescription",'); + setTimeout(() => response.end('"methods":{}}'), 100); + return; + } + if (url === "/simple-multipart.discovery.json") { + response.end( + JSON.stringify({ + kind: "discovery#restDescription", + rootUrl: `${baseUrl}/`, + servicePath: "drive/v3/", + schemas: { + File: { + id: "File", + type: "object", + properties: { id: { type: "string" } }, + }, + }, + resources: { + files: { + methods: { + create: { + id: "drive.files.create", + path: "files", + httpMethod: "POST", + request: { $ref: "File" }, + supportsMediaUpload: true, + mediaUpload: { + protocols: { + simple: { path: "/upload/drive/v3/files", multipart: true }, + }, + }, + response: { $ref: "File" }, + }, + }, + }, + }, + }), + ); + return; + } + if (url === "/drive-inferred.discovery.json") { + response.end( + JSON.stringify({ + ...fixture, + baseUrl: undefined, + rootUrl: `${baseUrl}/`, + servicePath: "drive/v3/", + }), + ); + return; + } + if (url === "/reserved.discovery.json") { + response.end( + JSON.stringify({ + kind: "discovery#restDescription", + rootUrl: `${baseUrl}/`, + servicePath: "drive/v3/", + schemas: { + File: { + id: "File", + type: "object", + properties: { id: { type: "string" } }, + }, + }, + resources: { + files: { + methods: { + getReserved: { + id: "drive.files.getReserved", + path: "files/{+name}", + httpMethod: "GET", + parameters: { + name: { type: "string", location: "path", required: true }, + }, + response: { $ref: "File" }, + }, + }, + }, + }, + }), + ); + return; + } + if (url === "/upload-path.discovery.json") { + response.end( + JSON.stringify({ + kind: "discovery#restDescription", + rootUrl: `${baseUrl}/`, + servicePath: "drive/v3/", + schemas: { + File: { + id: "File", + type: "object", + properties: { id: { type: "string" } }, + }, + }, + resources: { + files: { + methods: { + update: { + id: "drive.files.update", + path: "files/{fileId}", + httpMethod: "PATCH", + supportsMediaUpload: true, + parameters: { + fileId: { type: "string", location: "path", required: true }, + fields: { type: "string", location: "query" }, + }, + mediaUpload: { + protocols: { + simple: { + path: "/upload/drive/v3/files/{fileId}", + multipart: false, + }, + }, + }, + response: { $ref: "File" }, + }, + }, + }, + }, + }), + ); + return; + } + if (url === "/upload-resumable.discovery.json") { + response.end( + JSON.stringify({ + kind: "discovery#restDescription", + rootUrl: `${baseUrl}/`, + servicePath: "drive/v3/", + schemas: { + File: { + id: "File", + type: "object", + properties: { id: { type: "string" } }, + }, + }, + resources: { + files: { + methods: { + create: { + id: "drive.files.create", + path: "files", + httpMethod: "POST", + request: { $ref: "File" }, + supportsMediaUpload: true, + mediaUpload: { + protocols: { + simple: { path: "/upload/drive/v3/files", multipart: false }, + resumable: { path: "/upload/drive/v3/files", multipart: true }, + }, + }, + response: { $ref: "File" }, + }, + }, + }, + }, + }), + ); + return; + } + if (url === "/auth-error.discovery.json") { + response.end( + JSON.stringify({ + kind: "discovery#restDescription", + rootUrl: `${baseUrl}/`, + servicePath: "drive/v3/", + resources: { + files: { + methods: { + protected: { + id: "drive.files.protected", + path: "protected", + httpMethod: "GET", + scopes: ["https://www.googleapis.com/auth/drive.readonly"], + }, + }, + }, + }, + }), + ); + return; + } + if (url === "/download-alt.discovery.json") { + response.end( + JSON.stringify({ + kind: "discovery#restDescription", + rootUrl: `${baseUrl}/`, + servicePath: "drive/v3/", + resources: { + files: { + methods: { + export: { + id: "drive.files.export", + path: "files/{fileId}", + httpMethod: "GET", + supportsMediaDownload: true, + parameters: { + fileId: { type: "string", location: "path", required: true }, + }, + }, + }, + }, + }, + }), + ); + return; + } + if (url === "/large-response.discovery.json") { + response.end( + JSON.stringify({ + kind: "discovery#restDescription", + rootUrl: `${baseUrl}/`, + servicePath: "drive/v3/", + resources: { + files: { + methods: { + largeList: { + id: "drive.files.largeList", + path: "files/large-list", + httpMethod: "GET", + }, + }, + }, + }, + }), + ); + return; + } + if (url === "/large-document.discovery.json") { + response.end( + JSON.stringify({ + kind: "discovery#restDescription", + description: "x".repeat(1024 * 1024 + 1), + rootUrl: `${baseUrl}/`, + servicePath: "drive/v3/", + resources: { + files: { + methods: { + list: { + id: "drive.files.list", + path: "files", + httpMethod: "GET", + }, + }, + }, + }, + }), + ); + return; + } + if (url === "/redirect.discovery.json") { + response.statusCode = 302; + response.setHeader("location", "/drive.discovery.json"); + response.end("{}"); + return; + } + if (url === "/token" && request.method === "POST") { + response.end( + JSON.stringify({ + access_token: "refreshed-discovery-token", + refresh_token: "rotated-discovery-refresh-token", + token_type: "Bearer", + expires_in: 3600, + }), + ); + return; + } + if (url.startsWith("/drive/v3/files?")) { + response.end(JSON.stringify({ files: [{ id: "1", name: "Report" }] })); + return; + } + if (url === "/drive/v3/files" && request.method === "POST") { + response.statusCode = 201; + response.end(JSON.stringify({ id: "2", name: "Created" })); + return; + } + if (url === "/drive/v3/files/1/download?alt=media") { + response.setHeader("content-type", "application/pdf"); + response.end("%PDF bytes"); + return; + } + if (url === "/drive/v3/files/text/download?alt=media") { + response.setHeader("content-type", "text/plain"); + response.end("plain text export"); + return; + } + if (url === "/drive/v3/files/large/download?alt=media") { + const bytes = Buffer.alloc(1024 * 1024 + 1, "x"); + response.setHeader("content-type", "application/pdf"); + response.setHeader("content-length", String(bytes.byteLength)); + response.end(bytes); + return; + } + if (url === "/drive/v3/files/folders/1") { + response.end(JSON.stringify({ id: "folders/1" })); + return; + } + if (url === "/drive/v3/files/1?alt=media") { + response.setHeader("content-type", "application/pdf"); + response.end("%PDF alt media"); + return; + } + if (url === "/drive/v3/files/1") { + response.end(JSON.stringify({ id: "1", name: "Metadata only" })); + return; + } + if (url === "/drive/v3/files/large-list") { + const body = JSON.stringify({ + files: [{ id: "1", name: "Report" }], + padding: "x".repeat(1024 * 1024 + 1), + }); + response.setHeader("content-type", "application/json"); + response.setHeader("content-length", String(Buffer.byteLength(body))); + response.end(body); + return; + } + if (url === "/drive/v3/protected") { + response.statusCode = 401; + response.statusMessage = "Unauthorized"; + response.setHeader( + "www-authenticate", + 'Bearer error="invalid_token", access_token="secret-google-token"', + ); + response.end(JSON.stringify({ error: "secret-google-token" })); + return; + } + if (url === "/upload/drive/v3/files?uploadType=media" && request.method === "POST") { + response.end(JSON.stringify({ id: "uploaded-media" })); + return; + } + if ( + url.startsWith("/upload/drive/v3/files/1?") && + url.includes("uploadType=media") && + url.includes("fields=id") && + request.method === "PATCH" + ) { + response.end(JSON.stringify({ id: "1" })); + return; + } + if (url === "/upload/drive/v3/files?uploadType=multipart" && request.method === "POST") { + response.end(JSON.stringify({ id: "uploaded" })); + return; + } + if (url === "/upload/drive/v3/files?uploadType=resumable" && request.method === "POST") { + const metadata = bodyChunks.length + ? (JSON.parse(Buffer.concat(bodyChunks).toString("utf8")) as { name?: string }) + : {}; + if (metadata.name === "invalid-metadata.pdf") { + response.statusCode = 400; + response.end(JSON.stringify({ error: "invalid metadata" })); + return; + } + if (metadata.name === "slow-response.pdf") { + response.statusCode = 200; + response.setHeader("location", `${baseUrl}/upload/session/slow`); + response.end("{}"); + return; + } + response.statusCode = 200; + response.setHeader("location", `${baseUrl}/upload/session/abc`); + response.end("{}"); + return; + } + if (url === "/upload/session/slow" && request.method === "PUT") { + response.writeHead(200, { "content-type": "application/json" }); + response.write("{"); + setTimeout(() => response.end('"id":"uploaded-resumable"}'), 100); + return; + } + if (url === "/upload/session/abc" && request.method === "PUT") { + response.end(JSON.stringify({ id: "uploaded-resumable" })); + return; + } + response.statusCode = 404; + response.end(JSON.stringify({ error: "not found" })); + }); + }); + await new Promise((resolve) => server!.listen(0, "127.0.0.1", resolve)); + const address = server.address(); + if (!address || typeof address === "string") { + throw new Error("Google Discovery test server did not bind"); + } + baseUrl = `http://127.0.0.1:${address.port}`; +}); + +afterEach(async () => { + if (server) { + await new Promise((resolve) => server!.close(() => resolve())); + server = undefined; + } +}); + +describe("Google Discovery parser", () => { + it("maps resources and methods to Caplets operations", () => { + const operations = discoveryOperations({ + server: "drive", + document: fixture, + includeOperations: ["drive.files.*"], + excludeOperations: ["drive.files.delete"], + }); + + expect(operations.map((operation) => operation.name)).toEqual([ + "drive.files.create", + "drive.files.download", + "drive.files.list", + ]); + expect(operations.find((operation) => operation.name === "drive.files.list")).toMatchObject({ + method: "get", + path: "files", + readOnlyHint: true, + destructiveHint: false, + inputSchema: { + type: "object", + properties: { + header: { + properties: { + quotaUser: { type: "string" }, + }, + }, + query: { + properties: { + fields: { type: "string" }, + pageSize: { type: "integer", default: 100 }, + prettyPrint: { type: "boolean", default: true }, + }, + }, + }, + }, + outputSchema: { + properties: { + body: { + properties: { + files: { + items: { + properties: { + id: { type: "string" }, + name: { type: "string" }, + }, + }, + }, + }, + }, + headers: { type: "object" }, + status: { type: "number" }, + statusText: { type: "string" }, + }, + }, + }); + }); + + it("marks destructive operations and resolves filtered scopes", () => { + const operations = discoveryOperations({ + server: "drive", + document: fixture, + excludeOperations: ["*.delete"], + }); + + expect(operations.find((operation) => operation.name === "drive.files.delete")).toBeUndefined(); + expect(googleDiscoveryScopesForOperations(operations)).toEqual([ + "https://www.googleapis.com/auth/drive", + "https://www.googleapis.com/auth/drive.readonly", + ]); + + const deleteOperation = discoveryOperations({ server: "drive", document: fixture }).find( + (operation) => operation.name === "drive.files.delete", + ); + expect(deleteOperation).toMatchObject({ + destructiveHint: true, + inputSchema: { + required: ["path"], + properties: { + path: { + required: ["fileId"], + properties: { + fileId: { type: "string" }, + }, + }, + }, + }, + }); + }); + + it("selects a minimal Google scope alternative instead of requiring every method scope", () => { + const operations = discoveryOperations({ + server: "drive", + document: { + kind: "discovery#restDescription", + resources: { + files: { + methods: { + list: { + id: "drive.files.list", + path: "files", + httpMethod: "GET", + scopes: [ + "https://www.googleapis.com/auth/drive", + "https://www.googleapis.com/auth/drive.metadata.readonly", + ], + }, + }, + }, + }, + }, + }); + + expect(operations).toEqual([ + expect.objectContaining({ + name: "drive.files.list", + scopes: ["https://www.googleapis.com/auth/drive.metadata.readonly"], + }), + ]); + expect(googleDiscoveryScopesForOperations(operations)).toEqual([ + "https://www.googleapis.com/auth/drive.metadata.readonly", + ]); + }); + + it("prefers API-specific scopes over catch-all cloud-platform", () => { + expect( + discoveryOperations({ + server: "bigquery", + document: { + kind: "discovery#restDescription", + methods: { + insert: { + id: "bigquery.jobs.insert", + path: "projects/{projectId}/jobs", + httpMethod: "POST", + scopes: [ + "https://www.googleapis.com/auth/bigquery", + "https://www.googleapis.com/auth/cloud-platform", + ], + }, + }, + }, + })[0]?.scopes, + ).toEqual(["https://www.googleapis.com/auth/bigquery"]); + }); + + it("walks nested resources and uses stable fallback operation names", () => { + const operations = discoveryOperations({ + server: "drive", + document: fixture, + includeOperations: ["drive.permissions.*", "drive.changes.*"], + }); + + expect(operations.map((operation) => operation.name)).toEqual([ + "drive.changes.getStartPageToken", + "drive.permissions.list", + ]); + }); + + it("preserves media upload and download metadata", () => { + const operations = discoveryOperations({ server: "drive", document: fixture }); + + expect(operations.find((operation) => operation.name === "drive.files.create")).toMatchObject({ + supportsMediaUpload: true, + supportsMediaDownload: false, + mediaUpload: { + accept: ["image/png"], + maxSize: "10MB", + }, + mediaUploadProtocols: { + simple: { path: "/upload/drive/v3/files", multipart: false }, + multipart: { path: "/upload/drive/v3/files", multipart: true }, + resumable: { path: "/upload/drive/v3/files", multipart: true }, + }, + inputSchema: { + properties: { + body: { + properties: { + name: { type: "string" }, + parents: { + type: "array", + items: { type: "string" }, + }, + }, + }, + media: { + type: "object", + additionalProperties: false, + properties: { + path: { type: "string" }, + artifact: { type: "string" }, + dataUrl: { type: "string" }, + mimeType: { type: "string" }, + filename: { type: "string" }, + }, + }, + }, + }, + }); + expect(operations.find((operation) => operation.name === "drive.files.download")).toMatchObject( + { + supportsMediaDownload: true, + inputSchema: { + properties: { + filename: { type: "string" }, + outputPath: { type: "string" }, + }, + }, + }, + ); + }); + + it("rejects invalid discovery documents clearly", () => { + expect(() => + discoveryOperations({ + server: "drive", + document: { kind: "not-discovery", resources: {} }, + }), + ).toThrow(/Invalid Google Discovery document/); + }); + + it("maps top-level Discovery methods", () => { + const operations = discoveryOperations({ + server: "oauth2", + document: { + kind: "discovery#restDescription", + methods: { + tokeninfo: { + path: "tokeninfo", + httpMethod: "GET", + scopes: ["openid"], + }, + }, + }, + }); + + expect(operations).toEqual([ + expect.objectContaining({ + name: "oauth2.tokeninfo", + method: "get", + path: "tokeninfo", + scopes: ["openid"], + }), + ]); + }); +}); + +describe("GoogleDiscoveryManager", () => { + it("lists, describes, searches, resolves scopes, and calls Google Discovery operations", async () => { + const config = parseConfig({ + googleDiscoveryApis: { + drive: { + name: "Google Drive", + description: "Access Google Drive files.", + discoveryUrl: `${baseUrl}/drive.discovery.json`, + baseUrl: `${baseUrl}/drive/v3/`, + auth: { type: "none" }, + includeOperations: ["drive.files.*"], + excludeOperations: ["drive.files.delete"], + }, + }, + }); + const registry = new ServerRegistry(config); + const manager = new GoogleDiscoveryManager(registry); + const caplet = config.googleDiscoveryApis.drive!; + + await expect(manager.listTools(caplet)).resolves.toEqual( + expect.arrayContaining([ + expect.objectContaining({ name: "drive.files.list" }), + expect.objectContaining({ name: "drive.files.create" }), + ]), + ); + await expect(manager.getTool(caplet, "drive.files.list")).resolves.toMatchObject({ + inputSchema: { properties: { query: { properties: { pageSize: { type: "integer" } } } } }, + annotations: { readOnlyHint: true, destructiveHint: false }, + }); + expect( + manager.search(caplet, await manager.listTools(caplet), "list", 5).map((tool) => tool.name), + ).toContain("drive.files.list"); + await expect(manager.resolveAuthScopes(caplet)).resolves.toEqual([ + "https://www.googleapis.com/auth/drive", + "https://www.googleapis.com/auth/drive.readonly", + ]); + + await expect( + manager.callTool(caplet, "drive.files.list", { query: { pageSize: 2 } }), + ).resolves.toMatchObject({ + structuredContent: { status: 200, body: { files: [{ id: "1", name: "Report" }] } }, + isError: false, + }); + expect(requests.find((request) => request.url.startsWith("/drive/v3/files?"))?.url).toContain( + "pageSize=2", + ); + }); + + it("loads Discovery documents larger than the default HTTP text cap", async () => { + const config = parseConfig({ + googleDiscoveryApis: { + drive: { + name: "Google Drive", + description: "Access Google Drive files.", + discoveryUrl: `${baseUrl}/large-document.discovery.json`, + auth: { type: "none" }, + }, + }, + }); + const manager = new GoogleDiscoveryManager(new ServerRegistry(config)); + + await expect(manager.listTools(config.googleDiscoveryApis.drive!)).resolves.toEqual([ + expect.objectContaining({ name: "drive.files.list" }), + ]); + }); + + it("fetches public remote Discovery documents before requiring OAuth credentials", async () => { + const dir = mkdtempSync(join(tmpdir(), "caplets-google-public-discovery-")); + const config = parseConfig({ + googleDiscoveryApis: { + drive: { + name: "Google Drive", + description: "Access Google Drive files.", + discoveryUrl: `${baseUrl}/drive.discovery.json`, + baseUrl: `${baseUrl}/drive/v3/`, + auth: { + type: "oauth2", + tokenUrl: `${baseUrl}/token`, + clientId: "client", + }, + }, + }, + }); + const manager = new GoogleDiscoveryManager(new ServerRegistry(config), { authDir: dir }); + + try { + await expect(manager.listTools(config.googleDiscoveryApis.drive!)).resolves.toEqual( + expect.arrayContaining([expect.objectContaining({ name: "drive.files.list" })]), + ); + expect( + requests.find((request) => request.url === "/drive.discovery.json")?.headers, + ).not.toHaveProperty("authorization"); + } finally { + rmSync(dir, { recursive: true, force: true }); + } + }); + + it("sends stored OAuth credentials for private remote Discovery documents without baseUrl", async () => { + const dir = mkdtempSync(join(tmpdir(), "caplets-google-private-discovery-")); + const config = parseConfig({ + googleDiscoveryApis: { + drive: { + name: "Google Drive", + description: "Access Google Drive files.", + discoveryUrl: `${baseUrl}/private.discovery.json`, + auth: { + type: "oauth2", + tokenUrl: `${baseUrl}/token`, + clientId: "client", + }, + }, + }, + }); + writeTokenBundle( + { + server: "drive", + authType: "oauth2", + accessToken: "private-discovery-token", + tokenType: "Bearer", + expiresAt: "2999-01-01T00:00:00.000Z", + clientId: "client", + protectedResourceOrigin: baseUrl, + }, + dir, + ); + const manager = new GoogleDiscoveryManager(new ServerRegistry(config), { authDir: dir }); + + try { + await expect(manager.listTools(config.googleDiscoveryApis.drive!)).resolves.toEqual( + expect.arrayContaining([expect.objectContaining({ name: "drive.files.list" })]), + ); + expect( + requests.find((request) => request.url === "/private.discovery.json")?.headers, + ).toHaveProperty("authorization", "Bearer private-discovery-token"); + } finally { + rmSync(dir, { recursive: true, force: true }); + } + }); + + it("does not send API OAuth credentials to cross-origin Discovery documents", async () => { + const dir = mkdtempSync(join(tmpdir(), "caplets-google-cross-origin-discovery-")); + const config = parseConfig({ + googleDiscoveryApis: { + drive: { + name: "Google Drive", + description: "Access Google Drive files.", + discoveryUrl: `${baseUrl}/drive.discovery.json`, + baseUrl: "https://www.googleapis.com/drive/v3/", + auth: { + type: "oauth2", + tokenUrl: `${baseUrl}/token`, + clientId: "client", + }, + }, + }, + }); + writeTokenBundle( + { + server: "drive", + authType: "oauth2", + accessToken: "api-origin-token", + tokenType: "Bearer", + expiresAt: "2999-01-01T00:00:00.000Z", + clientId: "client", + protectedResourceOrigin: "https://www.googleapis.com", + }, + dir, + ); + const manager = new GoogleDiscoveryManager(new ServerRegistry(config), { authDir: dir }); + + try { + await expect(manager.listTools(config.googleDiscoveryApis.drive!)).resolves.toEqual( + expect.arrayContaining([expect.objectContaining({ name: "drive.files.list" })]), + ); + expect( + requests.find((request) => request.url === "/drive.discovery.json")?.headers, + ).not.toHaveProperty("authorization"); + } finally { + rmSync(dir, { recursive: true, force: true }); + } + }); + + it("refreshes expired OAuth credentials before protected Discovery document fetches", async () => { + const dir = mkdtempSync(join(tmpdir(), "caplets-google-private-discovery-refresh-")); + const config = parseConfig({ + googleDiscoveryApis: { + drive: { + name: "Google Drive", + description: "Access Google Drive files.", + discoveryUrl: `${baseUrl}/private.discovery.json`, + auth: { + type: "oauth2", + tokenUrl: `${baseUrl}/token`, + clientId: "client", + }, + }, + }, + }); + writeTokenBundle( + { + server: "drive", + authType: "oauth2", + accessToken: "expired-discovery-token", + refreshToken: "old-discovery-refresh-token", + tokenType: "Bearer", + expiresAt: "2000-01-01T00:00:00.000Z", + clientId: "client", + protectedResourceOrigin: baseUrl, + }, + dir, + ); + const manager = new GoogleDiscoveryManager(new ServerRegistry(config), { authDir: dir }); + + try { + await expect(manager.listTools(config.googleDiscoveryApis.drive!)).resolves.toEqual( + expect.arrayContaining([expect.objectContaining({ name: "drive.files.list" })]), + ); + const refresh = requests.find((request) => request.url === "/token"); + const discovery = requests.find((request) => request.url === "/private.discovery.json"); + expect(new URLSearchParams(refresh?.body).get("refresh_token")).toBe( + "old-discovery-refresh-token", + ); + expect(discovery?.headers.authorization).toBe("Bearer refreshed-discovery-token"); + } finally { + rmSync(dir, { recursive: true, force: true }); + } + }); + + it("keeps Discovery document body reads within the request timeout", async () => { + const config = parseConfig({ + googleDiscoveryApis: { + drive: { + name: "Google Drive", + description: "Access Google Drive files.", + discoveryUrl: `${baseUrl}/slow.discovery.json`, + requestTimeoutMs: 20, + auth: { type: "none" }, + }, + }, + }); + const manager = new GoogleDiscoveryManager(new ServerRegistry(config)); + + await expect(manager.listTools(config.googleDiscoveryApis.drive!)).rejects.toMatchObject({ + code: "TOOL_CALL_TIMEOUT", + }); + }); + + it("infers the request base URL from Discovery rootUrl and servicePath", async () => { + const config = parseConfig({ + googleDiscoveryApis: { + drive: { + name: "Google Drive", + description: "Access Google Drive files.", + discoveryUrl: `${baseUrl}/drive-inferred.discovery.json`, + auth: { type: "none" }, + includeOperations: ["drive.files.list"], + }, + }, + }); + const manager = new GoogleDiscoveryManager(new ServerRegistry(config)); + + await expect( + manager.callTool(config.googleDiscoveryApis.drive!, "drive.files.list", { + query: { pageSize: 2 }, + }), + ).resolves.toMatchObject({ + structuredContent: { status: 200, body: { files: [{ id: "1", name: "Report" }] } }, + }); + expect(requests.find((request) => request.url.startsWith("/drive/v3/files?"))?.url).toContain( + "pageSize=2", + ); + }); + + it("expands reserved Discovery path templates without flattening resource names", async () => { + const config = parseConfig({ + googleDiscoveryApis: { + drive: { + name: "Google Drive", + description: "Access Google Drive files.", + discoveryUrl: `${baseUrl}/reserved.discovery.json`, + auth: { type: "none" }, + }, + }, + }); + const manager = new GoogleDiscoveryManager(new ServerRegistry(config)); + + await expect( + manager.callTool(config.googleDiscoveryApis.drive!, "drive.files.getReserved", { + path: { name: "folders/1" }, + }), + ).resolves.toMatchObject({ + structuredContent: { status: 200, body: { id: "folders/1" } }, + }); + expect(requests.find((request) => request.url === "/drive/v3/files/folders/1")).toBeDefined(); + }); + + it("rejects Discovery operation paths that escape the configured base path", () => { + const config = parseConfig({ + googleDiscoveryApis: { + drive: { + name: "Google Drive", + description: "Access Google Drive files.", + discoveryUrl: `${baseUrl}/drive.discovery.json`, + baseUrl: `${baseUrl}/drive/v3/`, + auth: { type: "none" }, + }, + }, + }); + const caplet = config.googleDiscoveryApis.drive!; + const operation = { + name: "drive.escape", + method: "get" as const, + path: "../admin", + description: "Escape", + inputSchema: {}, + readOnlyHint: true, + destructiveHint: false, + scopes: [], + supportsMediaUpload: false, + supportsMediaDownload: false, + mediaUploadProtocols: {}, + parameterOrder: [], + }; + + expect(() => buildGoogleDiscoveryUrl(caplet, operation, {})).toThrow(/cannot escape baseUrl/u); + expect(() => + buildGoogleDiscoveryUrl(caplet, { ...operation, path: "%2e%2e/admin" }, {}), + ).toThrow(/cannot escape baseUrl/u); + expect(() => + buildGoogleDiscoveryUrl( + caplet, + { ...operation, path: "files/{+name}" }, + { + path: { name: "../admin" }, + }, + ), + ).toThrow(/cannot escape baseUrl/u); + }); + + it("serializes repeated query parameters from arrays", () => { + const config = parseConfig({ + googleDiscoveryApis: { + drive: { + name: "Google Drive", + description: "Access Google Drive files.", + discoveryUrl: `${baseUrl}/drive.discovery.json`, + baseUrl: `${baseUrl}/drive/v3/`, + auth: { type: "none" }, + }, + }, + }); + const caplet = config.googleDiscoveryApis.drive!; + const operation = { + name: "drive.files.list", + method: "get" as const, + path: "files", + inputSchema: {}, + readOnlyHint: true, + destructiveHint: false, + scopes: [], + supportsMediaUpload: false, + supportsMediaDownload: false, + mediaUploadProtocols: {}, + parameterOrder: [], + }; + + const url = buildGoogleDiscoveryUrl(caplet, operation, { + query: { label: ["starred", "ownedByMe"] }, + }); + + expect(url.searchParams.getAll("label")).toEqual(["starred", "ownedByMe"]); + }); + + it("builds safe media upload URLs with path and query arguments", () => { + const config = parseConfig({ + googleDiscoveryApis: { + drive: { + name: "Google Drive", + description: "Access Google Drive files.", + discoveryUrl: `${baseUrl}/drive.discovery.json`, + baseUrl: `${baseUrl}/drive/v3/`, + auth: { type: "none" }, + }, + }, + }); + const caplet = config.googleDiscoveryApis.drive!; + const operation = { + name: "drive.files.update", + method: "patch" as const, + path: "files/{fileId}", + inputSchema: {}, + readOnlyHint: false, + destructiveHint: false, + scopes: [], + supportsMediaUpload: true, + supportsMediaDownload: false, + mediaUploadProtocols: {}, + parameterOrder: [], + }; + + const url = buildGoogleDiscoveryUploadUrl( + caplet, + operation, + "/upload/drive/v3/files/{fileId}", + "media", + { path: { fileId: "1" }, query: { fields: "id" } }, + ); + + expect(url.toString()).toBe(`${baseUrl}/upload/drive/v3/files/1?fields=id&uploadType=media`); + expect(() => + buildGoogleDiscoveryUploadUrl(caplet, operation, "https://evil.example/upload", "media", {}), + ).toThrow(/cannot change origin/u); + }); + + it("rejects redirected discovery documents", async () => { + const config = parseConfig({ + googleDiscoveryApis: { + drive: { + name: "Google Drive", + description: "Access Google Drive files.", + discoveryUrl: `${baseUrl}/redirect.discovery.json`, + baseUrl: `${baseUrl}/drive/v3/`, + auth: { type: "none" }, + }, + }, + }); + const manager = new GoogleDiscoveryManager(new ServerRegistry(config)); + + await expect(manager.listTools(config.googleDiscoveryApis.drive!)).rejects.toMatchObject({ + code: "DOWNSTREAM_PROTOCOL_ERROR", + }); + }); + + it("executes Google Discovery operations through handleServerTool", async () => { + const config = parseConfig({ + googleDiscoveryApis: { + drive: { + name: "Google Drive", + description: "Access Google Drive files.", + discoveryUrl: `${baseUrl}/drive.discovery.json`, + baseUrl: `${baseUrl}/drive/v3/`, + auth: { type: "none" }, + }, + }, + }); + const registry = new ServerRegistry(config); + const manager = new GoogleDiscoveryManager(registry); + const downstream = new DownstreamManager(registry); + const caplet = config.googleDiscoveryApis.drive!; + + const list = await handleServerTool( + caplet, + { operation: "tools" }, + registry, + downstream, + undefined, + undefined, + undefined, + undefined, + undefined, + {}, + manager, + ); + + expect( + list.structuredContent.result.items.map((tool: { name: string }) => tool.name), + ).toContain("drive.files.list"); + + const call = await handleServerTool( + caplet, + { + operation: "call_tool", + name: "drive.files.list", + args: { query: { pageSize: 2 } }, + fields: ["body.files"], + }, + registry, + downstream, + undefined, + undefined, + undefined, + undefined, + undefined, + {}, + manager, + ); + + expect(call.structuredContent).toEqual({ body: { files: [{ id: "1", name: "Report" }] } }); + + await downstream.close(); + }); + + it("writes Google media downloads as artifacts", async () => { + const config = parseConfig({ + googleDiscoveryApis: { + drive: { + name: "Google Drive", + description: "Access Google Drive files.", + discoveryUrl: `${baseUrl}/drive.discovery.json`, + baseUrl: `${baseUrl}/drive/v3/`, + auth: { type: "none" }, + }, + }, + }); + const manager = new GoogleDiscoveryManager(new ServerRegistry(config)); + const result = await manager.callTool( + config.googleDiscoveryApis.drive!, + "drive.files.download", + { + path: { fileId: "1" }, + filename: "report.pdf", + }, + ); + + expect(result.structuredContent).toMatchObject({ + status: 200, + body: { artifact: { filename: "report.pdf", mimeType: "application/pdf" } }, + }); + }); + + it("honors outputPath for inlineable Google media downloads", async () => { + const dir = mkdtempSync(join(tmpdir(), "caplets-google-download-")); + try { + const outputPath = join(dir, "drive", "call", "export.txt"); + const config = parseConfig({ + googleDiscoveryApis: { + drive: { + name: "Google Drive", + description: "Access Google Drive files.", + discoveryUrl: `${baseUrl}/drive.discovery.json`, + baseUrl: `${baseUrl}/drive/v3/`, + auth: { type: "none" }, + }, + }, + }); + const manager = new GoogleDiscoveryManager(new ServerRegistry(config), { artifactDir: dir }); + const result = await manager.callTool( + config.googleDiscoveryApis.drive!, + "drive.files.download", + { + path: { fileId: "text" }, + outputPath, + }, + ); + + expect(existsSync(outputPath)).toBe(true); + expect(result.structuredContent).toMatchObject({ + status: 200, + body: { artifact: { path: outputPath, filename: "export.txt", mimeType: "text/plain" } }, + }); + } finally { + rmSync(dir, { recursive: true, force: true }); + } + }); + + it("adds alt=media when writing Google media downloads as artifacts", async () => { + const config = parseConfig({ + googleDiscoveryApis: { + drive: { + name: "Google Drive", + description: "Access Google Drive files.", + discoveryUrl: `${baseUrl}/download-alt.discovery.json`, + auth: { type: "none" }, + }, + }, + }); + const manager = new GoogleDiscoveryManager(new ServerRegistry(config)); + const result = await manager.callTool(config.googleDiscoveryApis.drive!, "drive.files.export", { + path: { fileId: "1" }, + filename: "export.pdf", + }); + + expect(result.structuredContent).toMatchObject({ + status: 200, + body: { artifact: { filename: "export.pdf", mimeType: "application/pdf" } }, + }); + expect(requests.find((request) => request.url === "/drive/v3/files/1?alt=media")).toBeDefined(); + }); + + it("writes large Google media downloads as artifacts", async () => { + const config = parseConfig({ + googleDiscoveryApis: { + drive: { + name: "Google Drive", + description: "Access Google Drive files.", + discoveryUrl: `${baseUrl}/drive.discovery.json`, + baseUrl: `${baseUrl}/drive/v3/`, + auth: { type: "none" }, + }, + }, + }); + const manager = new GoogleDiscoveryManager(new ServerRegistry(config)); + const result = await manager.callTool( + config.googleDiscoveryApis.drive!, + "drive.files.download", + { + path: { fileId: "large" }, + filename: "large.pdf", + }, + ); + + expect(result.structuredContent).toMatchObject({ + status: 200, + body: { + artifact: { + filename: "large.pdf", + mimeType: "application/pdf", + byteLength: 1024 * 1024 + 1, + }, + }, + }); + }); + + it("writes large non-media Google responses as artifacts", async () => { + const config = parseConfig({ + googleDiscoveryApis: { + drive: { + name: "Google Drive", + description: "Access Google Drive files.", + discoveryUrl: `${baseUrl}/large-response.discovery.json`, + auth: { type: "none" }, + }, + }, + }); + const manager = new GoogleDiscoveryManager(new ServerRegistry(config)); + const result = await manager.callTool( + config.googleDiscoveryApis.drive!, + "drive.files.largeList", + {}, + ); + + expect(result.structuredContent).toMatchObject({ + status: 200, + body: { + artifact: { + mimeType: "application/json", + byteLength: expect.any(Number), + }, + }, + }); + expect( + (result.structuredContent as { body: { artifact: { byteLength: number } } }).body.artifact + .byteLength, + ).toBeGreaterThan(1024 * 1024); + }); + + it("uploads media from dataUrl using multipart when metadata body is present", async () => { + const config = parseConfig({ + googleDiscoveryApis: { + drive: { + name: "Google Drive", + description: "Access Google Drive files.", + discoveryUrl: `${baseUrl}/drive.discovery.json`, + baseUrl: `${baseUrl}/drive/v3/`, + auth: { type: "none" }, + }, + }, + }); + const manager = new GoogleDiscoveryManager(new ServerRegistry(config)); + const result = await manager.callTool(config.googleDiscoveryApis.drive!, "drive.files.create", { + body: { name: "report.pdf" }, + media: { dataUrl: "data:application/pdf;base64,cGRm", filename: "report.pdf" }, + }); + + expect(result.structuredContent).toMatchObject({ status: 200, body: { id: "uploaded" } }); + const upload = requests.find((request) => request.url.includes("uploadType=multipart")); + expect(upload?.headers["content-type"]).toContain("multipart/related"); + expect(upload?.body).not.toContain("cGRm"); + }); + + it("honors simple.multipart as multipart upload support for metadata uploads", async () => { + const config = parseConfig({ + googleDiscoveryApis: { + drive: { + name: "Google Drive", + description: "Access Google Drive files.", + discoveryUrl: `${baseUrl}/simple-multipart.discovery.json`, + auth: { type: "none" }, + }, + }, + }); + const manager = new GoogleDiscoveryManager(new ServerRegistry(config)); + const result = await manager.callTool(config.googleDiscoveryApis.drive!, "drive.files.create", { + body: { name: "report.pdf" }, + media: { dataUrl: "data:application/pdf;base64,cGRm", filename: "report.pdf" }, + }); + + expect(result.structuredContent).toMatchObject({ status: 200, body: { id: "uploaded" } }); + expect( + requests.find((request) => request.url === "/upload/drive/v3/files?uploadType=multipart"), + ).toBeDefined(); + }); + + it("rejects media MIME types that would inject multipart headers", async () => { + const config = parseConfig({ + googleDiscoveryApis: { + drive: { + name: "Google Drive", + description: "Access Google Drive files.", + discoveryUrl: `${baseUrl}/drive.discovery.json`, + baseUrl: `${baseUrl}/drive/v3/`, + auth: { type: "none" }, + }, + }, + }); + const manager = new GoogleDiscoveryManager(new ServerRegistry(config)); + + await expect( + manager.callTool(config.googleDiscoveryApis.drive!, "drive.files.create", { + body: { name: "report.pdf" }, + media: { + dataUrl: "data:application/pdf;base64,cGRm", + filename: "report.pdf", + mimeType: "application/pdf\r\n\r\ninjected-body-data", + }, + }), + ).rejects.toMatchObject({ code: "REQUEST_INVALID" }); + }); + + it("uses resumable upload to preserve metadata when multipart is unavailable", async () => { + const config = parseConfig({ + googleDiscoveryApis: { + drive: { + name: "Google Drive", + description: "Access Google Drive files.", + discoveryUrl: `${baseUrl}/upload-resumable.discovery.json`, + auth: { type: "none" }, + }, + }, + }); + const manager = new GoogleDiscoveryManager(new ServerRegistry(config)); + const result = await manager.callTool(config.googleDiscoveryApis.drive!, "drive.files.create", { + body: { name: "report.pdf" }, + media: { dataUrl: "data:application/pdf;base64,cGRm", filename: "report.pdf" }, + }); + + expect(result.structuredContent).toMatchObject({ + status: 200, + body: { id: "uploaded-resumable" }, + }); + const start = requests.find((request) => request.url.includes("uploadType=resumable")); + expect(start?.body).toBe(JSON.stringify({ name: "report.pdf" })); + expect(start?.headers["x-upload-content-type"]).toBe("application/pdf"); + expect(requests.find((request) => request.url.includes("uploadType=media"))).toBeUndefined(); + }); + + it("returns resumable upload initiation errors as downstream responses", async () => { + const config = parseConfig({ + googleDiscoveryApis: { + drive: { + name: "Google Drive", + description: "Access Google Drive files.", + discoveryUrl: `${baseUrl}/upload-resumable.discovery.json`, + auth: { type: "none" }, + }, + }, + }); + const manager = new GoogleDiscoveryManager(new ServerRegistry(config)); + + const result = await manager.callTool(config.googleDiscoveryApis.drive!, "drive.files.create", { + body: { name: "invalid-metadata.pdf" }, + media: { dataUrl: "data:application/pdf;base64,cGRm", filename: "report.pdf" }, + }); + + expect(result).toMatchObject({ + isError: true, + structuredContent: { + status: 400, + body: { error: "invalid metadata" }, + }, + }); + expect(requests.find((request) => request.url === "/upload/session/abc")).toBeUndefined(); + }); + + it("sends a valid content range for empty resumable uploads", async () => { + const dir = mkdtempSync(join(tmpdir(), "caplets-google-empty-upload-")); + const path = join(dir, "empty.txt"); + writeFileSync(path, ""); + const config = parseConfig({ + googleDiscoveryApis: { + drive: { + name: "Google Drive", + description: "Access Google Drive files.", + discoveryUrl: `${baseUrl}/upload-resumable.discovery.json`, + auth: { type: "none" }, + }, + }, + }); + const manager = new GoogleDiscoveryManager(new ServerRegistry(config)); + + try { + await manager.callTool(config.googleDiscoveryApis.drive!, "drive.files.create", { + body: { name: "empty.txt" }, + media: { path }, + }); + + const upload = requests.find((request) => request.url === "/upload/session/abc"); + expect(upload?.headers["content-length"]).toBe("0"); + expect(upload?.headers["content-range"]).toBe("bytes */0"); + } finally { + rmSync(dir, { recursive: true, force: true }); + } + }); + + it("keeps resumable upload response reads within the request timeout", async () => { + const config = parseConfig({ + googleDiscoveryApis: { + drive: { + name: "Google Drive", + description: "Access Google Drive files.", + discoveryUrl: `${baseUrl}/upload-resumable.discovery.json`, + requestTimeoutMs: 20, + auth: { type: "none" }, + }, + }, + }); + const manager = new GoogleDiscoveryManager(new ServerRegistry(config)); + + await expect( + manager.callTool(config.googleDiscoveryApis.drive!, "drive.files.create", { + body: { name: "slow-response.pdf" }, + media: { dataUrl: "data:application/pdf;base64,cGRm", filename: "report.pdf" }, + }), + ).rejects.toMatchObject({ code: "TOOL_CALL_TIMEOUT" }); + }); + + it("wraps media upload transport failures as downstream tool errors", async () => { + const config = parseConfig({ + googleDiscoveryApis: { + drive: { + name: "Google Drive", + description: "Access Google Drive files.", + discoveryUrl: `${baseUrl}/drive.discovery.json`, + baseUrl: "http://127.0.0.1:9/drive/v3/", + requestTimeoutMs: 200, + auth: { type: "none" }, + }, + }, + }); + const manager = new GoogleDiscoveryManager(new ServerRegistry(config)); + + await expect( + manager.callTool(config.googleDiscoveryApis.drive!, "drive.files.create", { + body: { name: "report.pdf" }, + media: { dataUrl: "data:application/pdf;base64,cGRm", filename: "report.pdf" }, + }), + ).rejects.toMatchObject({ code: "DOWNSTREAM_TOOL_ERROR" }); + }); + + it("preserves OAuth authorization on resumable upload session requests", async () => { + const dir = mkdtempSync(join(tmpdir(), "caplets-google-resumable-auth-")); + const config = parseConfig({ + googleDiscoveryApis: { + drive: { + name: "Google Drive", + description: "Access Google Drive files.", + discoveryUrl: `${baseUrl}/upload-resumable.discovery.json`, + auth: { + type: "oauth2", + tokenUrl: `${baseUrl}/token`, + clientId: "client", + }, + }, + }, + }); + writeTokenBundle( + { + server: "drive", + authType: "oauth2", + accessToken: "upload-token", + tokenType: "Bearer", + expiresAt: "2999-01-01T00:00:00.000Z", + clientId: "client", + protectedResourceOrigin: baseUrl, + }, + dir, + ); + const manager = new GoogleDiscoveryManager(new ServerRegistry(config), { authDir: dir }); + + try { + await manager.callTool(config.googleDiscoveryApis.drive!, "drive.files.create", { + body: { name: "report.pdf" }, + media: { dataUrl: "data:application/pdf;base64,cGRm", filename: "report.pdf" }, + }); + const start = requests.find((request) => request.url.includes("uploadType=resumable")); + const upload = requests.find((request) => request.url === "/upload/session/abc"); + + expect(start?.headers.authorization).toBe("Bearer upload-token"); + expect(upload?.headers.authorization).toBe("Bearer upload-token"); + } finally { + rmSync(dir, { recursive: true, force: true }); + } + }); + + it("substitutes path and query args into media upload URLs", async () => { + const config = parseConfig({ + googleDiscoveryApis: { + drive: { + name: "Google Drive", + description: "Access Google Drive files.", + discoveryUrl: `${baseUrl}/upload-path.discovery.json`, + auth: { type: "none" }, + }, + }, + }); + const manager = new GoogleDiscoveryManager(new ServerRegistry(config)); + const result = await manager.callTool(config.googleDiscoveryApis.drive!, "drive.files.update", { + path: { fileId: "1" }, + query: { fields: "id" }, + media: { dataUrl: "data:text/plain;base64,aGVsbG8=", filename: "hello.txt" }, + }); + + expect(result.structuredContent).toMatchObject({ status: 200, body: { id: "1" } }); + expect( + requests.find((request) => request.url.startsWith("/upload/drive/v3/files/1?"))?.url, + ).toContain("fields=id"); + }); + + it("surfaces Google OAuth failures as auth-required errors", async () => { + const dir = mkdtempSync(join(tmpdir(), "caplets-google-auth-error-")); + const config = parseConfig({ + googleDiscoveryApis: { + drive: { + name: "Google Drive", + description: "Access Google Drive files.", + discoveryUrl: `${baseUrl}/auth-error.discovery.json`, + auth: { + type: "oauth2", + tokenUrl: `${baseUrl}/token`, + clientId: "client", + }, + }, + }, + }); + writeTokenBundle( + { + server: "drive", + authType: "oauth2", + accessToken: "expired-access-token", + tokenType: "Bearer", + expiresAt: "2999-01-01T00:00:00.000Z", + clientId: "client", + protectedResourceOrigin: baseUrl, + metadata: { + requestedScopes: ["https://www.googleapis.com/auth/drive.readonly"], + }, + }, + dir, + ); + const manager = new GoogleDiscoveryManager(new ServerRegistry(config), { authDir: dir }); + + try { + await expect( + manager.callTool(config.googleDiscoveryApis.drive!, "drive.files.protected", {}), + ).rejects.toMatchObject({ + code: "AUTH_REQUIRED", + details: { + server: "drive", + status: 401, + nextAction: "run_caplets_auth_login", + challenge: "[REDACTED]", + }, + }); + } finally { + rmSync(dir, { recursive: true, force: true }); + } + }); +}); diff --git a/packages/core/test/http-actions.test.ts b/packages/core/test/http-actions.test.ts index 7d8d118f..af0eb906 100644 --- a/packages/core/test/http-actions.test.ts +++ b/packages/core/test/http-actions.test.ts @@ -1,5 +1,8 @@ import { afterAll, beforeAll, describe, expect, it } from "vitest"; import { createServer, type IncomingMessage, type ServerResponse } from "node:http"; +import { mkdtempSync, readFileSync, rmSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; import { parseConfig, type HttpApiConfig } from "../src/config"; import { DownstreamManager } from "../src/downstream"; import { HttpActionManager } from "../src/http-actions"; @@ -45,6 +48,23 @@ describe("HttpActionManager", () => { response.end("x".repeat(2 * 1024 * 1024)); return; } + if (request.url === "/large-stream") { + response.write("x".repeat(128)); + response.write("x".repeat(128)); + response.end(); + return; + } + if (request.url === "/pdf") { + response.setHeader("content-type", "application/pdf"); + response.end(Buffer.from("%PDF-1.7 test")); + return; + } + if (request.url === "/attachment") { + response.removeHeader("content-type"); + response.setHeader("content-disposition", 'attachment; filename="report.bin"'); + response.end(Buffer.from([0, 1, 2, 3])); + return; + } if (request.url === "/missing") { response.statusCode = 404; response.statusMessage = "Not Found"; @@ -280,6 +300,150 @@ describe("HttpActionManager", () => { expect(result.structuredContent).toHaveProperty("elapsedMs"); }); + it("writes binary HTTP responses as media artifacts", async () => { + const artifactDir = mkdtempSync(join(tmpdir(), "caplets-http-artifacts-")); + const manager = new HttpActionManager(registry(), { artifactDir }); + const api = httpApi({ actions: { pdf: { method: "GET", path: "/pdf" } } }); + + try { + const result = await manager.callTool(api, "pdf", {}); + const structured = result.structuredContent as { + status: number; + headers: { "content-type": string }; + body: { artifact: { path: string; mimeType: string; byteLength: number } }; + }; + + expect(structured).toMatchObject({ + status: 200, + headers: { "content-type": "application/pdf" }, + body: { + artifact: { + mimeType: "application/pdf", + byteLength: 13, + }, + }, + }); + expect(readFileSync(structured.body.artifact.path, "utf8")).toBe("%PDF-1.7 test"); + } finally { + rmSync(artifactDir, { recursive: true, force: true }); + } + }); + + it("writes attachment responses without content type as media artifacts", async () => { + const artifactDir = mkdtempSync(join(tmpdir(), "caplets-http-attachment-artifacts-")); + const manager = new HttpActionManager(registry(), { artifactDir }); + const api = httpApi({ actions: { attachment: { method: "GET", path: "/attachment" } } }); + + try { + const result = await manager.callTool(api, "attachment", {}); + const structured = result.structuredContent as { + body: { + artifact: { + uri: string; + path: string; + filename: string; + byteLength: number; + sha256: string; + }; + }; + }; + + expect(structured.body.artifact).toMatchObject({ + filename: "report.bin", + byteLength: 4, + }); + expect(structured.body.artifact.uri).toContain("caplets://artifacts/"); + expect(structured.body.artifact.sha256).toHaveLength(64); + expect(readFileSync(structured.body.artifact.path)).toEqual(Buffer.from([0, 1, 2, 3])); + } finally { + rmSync(artifactDir, { recursive: true, force: true }); + } + }); + + it("writes HTTP artifacts through handleServerTool", async () => { + const artifactDir = mkdtempSync(join(tmpdir(), "caplets-http-tool-artifacts-")); + const config = parseConfig({ + httpApis: { + http: { + name: "HTTP API", + description: "Call configured HTTP service actions.", + baseUrl, + auth: { type: "none" }, + actions: { pdf: { method: "GET", path: "/pdf" } }, + }, + }, + }); + const registry = new ServerRegistry(config); + const http = new HttpActionManager(registry, { artifactDir }); + const downstream = new DownstreamManager(registry); + + try { + const result = (await handleServerTool( + config.httpApis.http!, + { operation: "call_tool", name: "pdf", args: {} }, + registry, + downstream, + undefined, + undefined, + http, + )) as any; + + expect(result.structuredContent.body.artifact.path).toContain(artifactDir); + expect(readFileSync(result.structuredContent.body.artifact.path, "utf8")).toBe( + "%PDF-1.7 test", + ); + } finally { + rmSync(artifactDir, { recursive: true, force: true }); + } + }); + + it("writes oversized streamed text responses as media artifacts", async () => { + const artifactDir = mkdtempSync(join(tmpdir(), "caplets-http-stream-artifacts-")); + const manager = new HttpActionManager(registry(), { artifactDir, maxInlineBytes: 100 }); + const api = httpApi({ + actions: { large_stream: { method: "GET", path: "/large-stream" } }, + }); + + try { + const result = await manager.callTool(api, "large_stream", {}); + const structured = result.structuredContent as { + status: number; + headers: { "content-type": string }; + body: { artifact: { path: string; mimeType: string; byteLength: number } }; + }; + + expect(structured).toMatchObject({ + status: 200, + body: { + artifact: { + mimeType: "application/json", + byteLength: 256, + }, + }, + }); + expect(readFileSync(structured.body.artifact.path, "utf8")).toBe("x".repeat(256)); + } finally { + rmSync(artifactDir, { recursive: true, force: true }); + } + }); + + it("enforces maxResponseBytes as a hard response cap", async () => { + const artifactDir = mkdtempSync(join(tmpdir(), "caplets-http-cap-artifacts-")); + const manager = new HttpActionManager(registry(), { artifactDir, maxInlineBytes: 100 }); + const api = httpApi({ + maxResponseBytes: 200, + actions: { large_stream: { method: "GET", path: "/large-stream" } }, + }); + + try { + await expect(manager.callTool(api, "large_stream", {})).rejects.toMatchObject({ + code: "DOWNSTREAM_PROTOCOL_ERROR", + }); + } finally { + rmSync(artifactDir, { recursive: true, force: true }); + } + }); + it("rejects query and header mappings that resolve to non-objects", async () => { const manager = new HttpActionManager(registry()); const badQueryApi = httpApi({ actions: { bad_query: { method: "GET", path: "/ok" } } }); diff --git a/packages/core/test/media-artifacts.test.ts b/packages/core/test/media-artifacts.test.ts new file mode 100644 index 00000000..cee2373a --- /dev/null +++ b/packages/core/test/media-artifacts.test.ts @@ -0,0 +1,364 @@ +import { + mkdirSync, + mkdtempSync, + readFileSync, + rmSync, + symlinkSync, + truncateSync, + writeFileSync, +} from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { afterEach, describe, expect, it } from "vitest"; +import { + artifactUri, + readMediaInput, + resolveMediaArtifact, + writeMediaArtifact, +} from "../src/media"; +import { readHttpLikeResponse } from "../src/http/response"; + +describe("media artifacts", () => { + const dirs: string[] = []; + + afterEach(() => { + for (const dir of dirs.splice(0)) rmSync(dir, { recursive: true, force: true }); + }); + + function tempDir(prefix: string) { + const dir = mkdtempSync(join(tmpdir(), prefix)); + dirs.push(dir); + return dir; + } + + it("writes artifact files with stable metadata", async () => { + const root = tempDir("caplets-artifacts-"); + const artifact = await writeMediaArtifact({ + rootDir: root, + capletId: "google-drive", + suggestedFilename: "report.pdf", + mimeType: "application/pdf", + bytes: Buffer.from("pdf-bytes"), + }); + + expect(artifact).toMatchObject({ + mimeType: "application/pdf", + byteLength: 9, + filename: "report.pdf", + }); + expect(artifact.path).toContain(join(root, "google-drive")); + expect(artifact.sha256).toHaveLength(64); + expect(readFileSync(artifact.path!, "utf8")).toBe("pdf-bytes"); + }); + + it("can omit local artifact paths from returned metadata", async () => { + const root = tempDir("caplets-artifacts-"); + const artifact = await writeMediaArtifact({ + rootDir: root, + capletId: "google-drive", + suggestedFilename: "report.pdf", + mimeType: "application/pdf", + bytes: Buffer.from("pdf-bytes"), + exposeLocalPath: false, + }); + + expect(artifact).toMatchObject({ + uri: expect.stringMatching(/^caplets:\/\/artifacts\//u), + filename: "report.pdf", + byteLength: 9, + sha256: expect.any(String), + }); + expect(artifact).not.toHaveProperty("path"); + }); + + it("rejects output paths outside an allowed root", async () => { + const root = tempDir("caplets-artifacts-"); + await expect( + writeMediaArtifact({ + rootDir: root, + capletId: "drive", + outputPath: join(root, "..", "escape.bin"), + bytes: Buffer.from("x"), + }), + ).rejects.toMatchObject({ code: "REQUEST_INVALID" }); + }); + + it("resolves explicit output paths and rejects unsafe artifact path segments", async () => { + const root = tempDir("caplets-artifacts-"); + const outputPath = join(root, "drive", "call-1", "report.pdf"); + const artifact = await writeMediaArtifact({ + rootDir: root, + capletId: "drive", + outputPath, + mimeType: "application/pdf", + bytes: Buffer.from("pdf"), + }); + + expect(resolveMediaArtifact(artifact.uri, { artifactRoot: root })).toMatchObject({ + path: outputPath, + filename: "report.pdf", + mimeType: "application/pdf", + byteLength: 3, + }); + + await expect( + writeMediaArtifact({ + rootDir: root, + capletId: "drive", + outputPath: join(root, "drive", "bad call", "report.pdf"), + bytes: Buffer.from("x"), + }), + ).rejects.toMatchObject({ code: "REQUEST_INVALID" }); + }); + + it("clears stale artifact metadata when overwriting without a MIME type", async () => { + const root = tempDir("caplets-artifacts-"); + const outputPath = join(root, "drive", "call-1", "report.pdf"); + const first = await writeMediaArtifact({ + rootDir: root, + capletId: "drive", + outputPath, + mimeType: "application/pdf", + bytes: Buffer.from("pdf"), + }); + await writeMediaArtifact({ + rootDir: root, + capletId: "drive", + outputPath, + bytes: Buffer.from("plain"), + }); + + expect(resolveMediaArtifact(first.uri, { artifactRoot: root })).toMatchObject({ + filename: "report.pdf", + byteLength: 5, + }); + expect(resolveMediaArtifact(first.uri, { artifactRoot: root }).mimeType).toBeUndefined(); + }); + + it("does not overwrite explicit output paths with failed forced responses", async () => { + const root = tempDir("caplets-artifacts-"); + const outputDir = join(root, "drive", "call-1"); + const outputPath = join(outputDir, "report.pdf"); + mkdirSync(outputDir, { recursive: true }); + writeFileSync(outputPath, "previous-pdf"); + + const result = await readHttpLikeResponse( + new Response(JSON.stringify({ error: "missing" }), { + status: 404, + statusText: "Not Found", + headers: { "content-type": "application/json" }, + }), + { + capletId: "drive", + artifactDir: root, + outputPath, + forceArtifact: true, + }, + ); + + const artifact = (result.body as { artifact: { path?: string } }).artifact; + expect(result.status).toBe(404); + expect(readFileSync(outputPath, "utf8")).toBe("previous-pdf"); + expect(artifact.path).not.toBe(outputPath); + }); + + it("cancels oversized responses rejected by content-length before reading", async () => { + let cancelled = false; + const body = new ReadableStream({ + cancel() { + cancelled = true; + }, + }); + + await expect( + readHttpLikeResponse( + new Response(body, { + headers: { + "content-length": "5", + "content-type": "application/octet-stream", + }, + }), + { capletId: "drive", maxBytes: 4 }, + ), + ).rejects.toMatchObject({ code: "DOWNSTREAM_PROTOCOL_ERROR" }); + + expect(cancelled).toBe(true); + }); + + it("rejects oversized artifact and data URL inputs before reading decoded bytes", async () => { + const root = tempDir("caplets-artifacts-"); + const artifact = await writeMediaArtifact({ + rootDir: root, + capletId: "drive", + suggestedFilename: "large.txt", + bytes: Buffer.from("large"), + }); + + await expect( + readMediaInput({ artifact: artifact.uri }, { artifactRoot: root, maxBytes: 4 }), + ).rejects.toMatchObject({ code: "REQUEST_INVALID" }); + await expect( + readMediaInput( + { dataUrl: "data:text/plain;base64,bGFyZ2U=", filename: "large.txt" }, + { artifactRoot: root, maxBytes: 4 }, + ), + ).rejects.toMatchObject({ code: "REQUEST_INVALID" }); + }); + + it("applies the default size limit to artifact inputs", async () => { + const root = tempDir("caplets-artifacts-"); + const artifactPath = join(root, "drive", "call-1", "large.txt"); + mkdirSync(join(root, "drive", "call-1"), { recursive: true }); + writeFileSync(artifactPath, ""); + truncateSync(artifactPath, 100 * 1024 * 1024 + 1); + const artifact = artifactUri("drive", "call-1", "large.txt"); + + await expect(readMediaInput({ artifact }, { artifactRoot: root })).rejects.toMatchObject({ + code: "REQUEST_INVALID", + }); + }); + + it("rejects invalid base64 data URL padding", async () => { + const root = tempDir("caplets-artifacts-"); + await expect( + readMediaInput({ dataUrl: "data:text/plain;base64,====" }, { artifactRoot: root }), + ).rejects.toMatchObject({ code: "REQUEST_INVALID" }); + await expect( + readMediaInput({ dataUrl: "data:text/plain;base64,a===" }, { artifactRoot: root }), + ).rejects.toMatchObject({ code: "REQUEST_INVALID" }); + }); + + it("rejects artifact paths that escape the root through symlinks", async () => { + const root = tempDir("caplets-artifacts-"); + const outside = tempDir("caplets-artifacts-outside-"); + mkdirSync(join(root, "drive"), { recursive: true }); + symlinkSync(outside, join(root, "drive", "linked")); + + await expect( + writeMediaArtifact({ + rootDir: root, + capletId: "drive", + outputPath: join(root, "drive", "linked", "escape.bin"), + bytes: Buffer.from("x"), + }), + ).rejects.toMatchObject({ code: "REQUEST_INVALID" }); + }); + + it("rejects symlinked artifact roots", async () => { + const realRoot = tempDir("caplets-artifacts-real-"); + const parent = tempDir("caplets-artifacts-parent-"); + const linkedRoot = join(parent, "linked-root"); + symlinkSync(realRoot, linkedRoot); + + await expect( + writeMediaArtifact({ + rootDir: linkedRoot, + capletId: "drive", + suggestedFilename: "file.bin", + bytes: Buffer.from("x"), + }), + ).rejects.toMatchObject({ code: "REQUEST_INVALID" }); + }); + + it("reads media input from path, artifact reference, and data URL", async () => { + const root = tempDir("caplets-artifacts-"); + const file = join(root, "image.png"); + writeFileSync(file, Buffer.from("png")); + const artifact = await writeMediaArtifact({ + rootDir: root, + capletId: "drive", + suggestedFilename: "existing.png", + mimeType: "image/png", + bytes: Buffer.from("artifact"), + }); + expect(resolveMediaArtifact(artifact.uri, { artifactRoot: root })).toMatchObject({ + filename: "existing.png", + byteLength: 8, + }); + await expect(readMediaInput({ path: file }, { artifactRoot: root })).resolves.toMatchObject({ + bytes: Buffer.from("png"), + filename: "image.png", + mimeType: "image/png", + }); + await expect( + readMediaInput({ artifact: artifact.uri }, { artifactRoot: root }), + ).resolves.toMatchObject({ + bytes: Buffer.from("artifact"), + filename: "existing.png", + mimeType: "image/png", + }); + await expect( + readMediaInput( + { dataUrl: "data:text/plain;base64,aGVsbG8=", filename: "hello.txt" }, + { artifactRoot: root }, + ), + ).resolves.toMatchObject({ + bytes: Buffer.from("hello"), + filename: "hello.txt", + mimeType: "text/plain", + }); + await expect( + readMediaInput( + { dataUrl: "data:text/plain;charset=utf-8;base64,cGFyYW1z", filename: "params.txt" }, + { artifactRoot: root }, + ), + ).resolves.toMatchObject({ + bytes: Buffer.from("params"), + filename: "params.txt", + mimeType: "text/plain", + }); + await expect( + readMediaInput( + { dataUrl: "data:application/octet-stream;base64,", filename: "empty.bin" }, + { artifactRoot: root }, + ), + ).resolves.toMatchObject({ + bytes: Buffer.alloc(0), + filename: "empty.bin", + mimeType: "application/octet-stream", + }); + }); + + it("can forbid local media file paths for remote runtimes", async () => { + const root = tempDir("caplets-artifacts-"); + const file = join(root, "local.txt"); + writeFileSync(file, "local"); + + await expect( + readMediaInput({ path: file }, { artifactRoot: root, allowLocalPaths: false }), + ).rejects.toMatchObject({ + code: "REQUEST_INVALID", + message: "media.path is not available in this runtime", + }); + + await expect( + readMediaInput( + { dataUrl: "data:text/plain;base64,bG9jYWw=", filename: "local.txt" }, + { artifactRoot: root, allowLocalPaths: false }, + ), + ).resolves.toMatchObject({ + filename: "local.txt", + bytes: Buffer.from("local"), + }); + }); + + it("rejects multiple media input sources and non-base64 data URLs", async () => { + const root = tempDir("caplets-artifacts-"); + await expect( + readMediaInput( + { path: "/tmp/a", dataUrl: "data:text/plain;base64,eA==" }, + { + artifactRoot: root, + }, + ), + ).rejects.toMatchObject({ code: "REQUEST_INVALID" }); + await expect( + readMediaInput( + { dataUrl: "data:text/plain,hello" }, + { + artifactRoot: root, + }, + ), + ).rejects.toMatchObject({ code: "REQUEST_INVALID" }); + }); +}); diff --git a/packages/core/test/native.test.ts b/packages/core/test/native.test.ts index 99359dcc..62003aca 100644 --- a/packages/core/test/native.test.ts +++ b/packages/core/test/native.test.ts @@ -242,6 +242,76 @@ describe("native Caplets service", () => { } }); + it("discovers direct Google Discovery tools for native integrations", async () => { + const { dir, configPath, projectConfigPath } = tempConfig({ + googleDiscoveryApis: { + drive: { + name: "Google Drive", + description: "Access Google Drive files.", + exposure: "direct", + discoveryPath: join(fixturesDir, "google-discovery/drive.discovery.json"), + baseUrl: "http://127.0.0.1:1/drive/v3/", + auth: { type: "none" }, + includeOperations: ["drive.files.list"], + }, + }, + }); + dirs.push(dir); + const service = createNativeCapletsService({ configPath, projectConfigPath, watch: false }); + + try { + await expect(service.reload()).resolves.toBe(true); + expect(service.listTools()).toEqual([ + expect.objectContaining({ + caplet: "drive__drive.files.list", + toolName: "caplets__drive__drive.files.list", + title: "drive.files.list", + inputSchema: expect.objectContaining({ + properties: expect.objectContaining({ query: expect.any(Object) }), + }), + annotations: { readOnlyHint: true, destructiveHint: false }, + }), + ]); + await expect( + service.execute("drive__drive.files.list", { query: { pageSize: 1 } }), + ).resolves.toMatchObject({ + isError: true, + }); + } finally { + await service.close(); + } + }); + + it("notifies native tool listeners after cold-start direct Google Discovery refresh", async () => { + const { dir, configPath, projectConfigPath } = tempConfig({ + googleDiscoveryApis: { + drive: { + name: "Google Drive", + description: "Access Google Drive files.", + exposure: "direct", + discoveryPath: join(fixturesDir, "google-discovery/drive.discovery.json"), + baseUrl: "http://127.0.0.1:1/drive/v3/", + auth: { type: "none" }, + includeOperations: ["drive.files.list"], + }, + }, + }); + dirs.push(dir); + const service = createNativeCapletsService({ configPath, projectConfigPath, watch: false }); + const events: string[][] = []; + service.onToolsChanged((tools) => { + events.push(configuredCapletIds(tools)); + }); + + try { + await expect + .poll(() => events.at(-1), { timeout: 5_000 }) + .toEqual(["drive__drive.files.list"]); + } finally { + await service.close(); + } + }); + it("lists Code Mode only when exposure includes Code Mode", async () => { const { dir, configPath, projectConfigPath } = tempConfig({ httpApis: { diff --git a/packages/core/test/observed-output-shapes.test.ts b/packages/core/test/observed-output-shapes.test.ts index c2661e9e..d651b231 100644 --- a/packages/core/test/observed-output-shapes.test.ts +++ b/packages/core/test/observed-output-shapes.test.ts @@ -139,6 +139,34 @@ describe("Observed Output Shapes", () => { expect(JSON.stringify(key)).not.toContain("GH_TOKEN"); }); + it("builds Google Discovery backend fingerprints", () => { + const config = parseConfig({ + googleDiscoveryApis: { + drive: { + name: "Google Drive", + description: "Access Google Drive.", + discoveryUrl: "https://www.googleapis.com/discovery/v1/apis/drive/v3/rest", + baseUrl: "https://www.googleapis.com/drive/v3/", + auth: { type: "none" }, + includeOperations: ["drive.files.list"], + }, + }, + }); + + const key = observedOutputShapeKey({ + scope: "local", + caplet: config.googleDiscoveryApis.drive!, + toolName: "drive.files.list", + }); + + expect(key).toMatchObject({ + capletId: "drive", + backendKind: "googleDiscovery", + toolName: "drive.files.list", + }); + expect(key.backendFingerprint).toHaveLength(64); + }); + it("stores, expires, and prunes filesystem cache entries", async () => { const dir = mkdtempSync(join(tmpdir(), "caplets-observed-shapes-")); const ttlMs = 1_000; diff --git a/packages/core/test/openapi.test.ts b/packages/core/test/openapi.test.ts index e3ae56fe..d3a935c0 100644 --- a/packages/core/test/openapi.test.ts +++ b/packages/core/test/openapi.test.ts @@ -1,6 +1,6 @@ import { afterAll, beforeAll, describe, expect, it } from "vitest"; import { createServer, type IncomingMessage, type ServerResponse } from "node:http"; -import { mkdtempSync, rmSync, writeFileSync } from "node:fs"; +import { mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { parseConfig } from "../src/config"; @@ -77,6 +77,12 @@ describe("native OpenAPI Caplets", () => { response.end(JSON.stringify({ id: "42", active: true, name: "Ada" })); return; } + if (request.url?.startsWith("/users/large?active=true")) { + response.end( + JSON.stringify({ id: "large", name: "Ada", padding: "x".repeat(1024 * 1024) }), + ); + return; + } if (request.url?.startsWith("/api/v1/users/42?active=true")) { response.end(JSON.stringify({ id: "42", active: true, prefixed: true })); return; @@ -94,6 +100,24 @@ describe("native OpenAPI Caplets", () => { response.end(JSON.stringify({ public: "ok", secret: "hidden" })); return; } + if (request.url === "/reports/large") { + const bytes = Buffer.alloc(1024 * 1024 + 1, "x"); + response.setHeader("content-type", "application/pdf"); + response.setHeader("content-length", String(bytes.byteLength)); + response.end(bytes); + return; + } + if (request.url === "/reports/42" && request.method === "HEAD") { + response.setHeader("content-type", "application/pdf"); + response.setHeader("content-length", String(1024 * 1024 + 1)); + response.end(); + return; + } + if (request.url === "/reports/42") { + response.setHeader("content-type", "application/pdf"); + response.end(Buffer.from("%PDF-1.7 test")); + return; + } if (request.url === "/protected") { response.statusCode = 401; response.statusMessage = "Unauthorized"; @@ -186,7 +210,7 @@ describe("native OpenAPI Caplets", () => { )) as any; expect( list.structuredContent.result.items.map((tool: { name: string }) => tool.name), - ).toEqual(["createUser", "GET /users/{id}"]); + ).toEqual(["createUser", "GET /users/{id}", "getReport", "headReport"]); expect( list.structuredContent.result.items.find( (candidate: { name: string }) => candidate.name === "GET /users/{id}", @@ -690,7 +714,7 @@ describe("native OpenAPI Caplets", () => { const openapi = new OpenApiManager(registry); const remote = registry.config.openapiEndpoints.remote!; - await expect(openapi.listTools(remote)).resolves.toHaveLength(2); + await expect(openapi.listTools(remote)).resolves.toHaveLength(4); await expect( openapi.listTools({ ...remote, specUrl: `${baseUrl}/slow-openapi.json` }), ).rejects.toMatchObject({ code: "TOOL_CALL_TIMEOUT" }); @@ -821,6 +845,7 @@ describe("native OpenAPI Caplets", () => { }, mcpServers: {}, openapiEndpoints: { remote: endpoint }, + googleDiscoveryApis: {}, graphqlEndpoints: {}, httpApis: {}, cliTools: {}, @@ -940,6 +965,170 @@ describe("native OpenAPI Caplets", () => { rmSync(dir, { recursive: true, force: true }); } }); + + it("writes binary OpenAPI responses as media artifacts", async () => { + const dir = mkdtempSync(join(tmpdir(), "caplets-openapi-artifacts-")); + const specPath = join(dir, "openapi.json"); + const artifactDir = join(dir, "artifacts"); + writeFileSync(specPath, JSON.stringify(openApiSpec(baseUrl))); + const config = parseConfig({ + openapiEndpoints: { + reports: { + name: "Reports API", + description: "Download reports from the internal HTTP API.", + specPath, + baseUrl, + auth: { type: "none" }, + }, + }, + }); + const registry = new ServerRegistry(config); + const openapi = new OpenApiManager(registry, { artifactDir }); + + try { + const result = await openapi.callTool(config.openapiEndpoints.reports!, "getReport", { + path: { id: "42" }, + }); + const structured = result.structuredContent as { + status: number; + headers: { "content-type": string }; + body: { artifact: { path: string; mimeType: string; byteLength: number } }; + }; + + expect(structured).toMatchObject({ + status: 200, + headers: { "content-type": "application/pdf" }, + body: { + artifact: { + mimeType: "application/pdf", + byteLength: 13, + }, + }, + }); + expect(readFileSync(structured.body.artifact.path, "utf8")).toBe("%PDF-1.7 test"); + } finally { + rmSync(dir, { recursive: true, force: true }); + } + }); + + it("writes large OpenAPI responses as media artifacts", async () => { + const dir = mkdtempSync(join(tmpdir(), "caplets-openapi-large-artifacts-")); + const specPath = join(dir, "openapi.json"); + const artifactDir = join(dir, "artifacts"); + writeFileSync(specPath, JSON.stringify(openApiSpec(baseUrl))); + const config = parseConfig({ + openapiEndpoints: { + reports: { + name: "Reports API", + description: "Download reports from the internal HTTP API.", + specPath, + baseUrl, + auth: { type: "none" }, + }, + }, + }); + const registry = new ServerRegistry(config); + const openapi = new OpenApiManager(registry, { artifactDir }); + + try { + const result = await openapi.callTool(config.openapiEndpoints.reports!, "getReport", { + path: { id: "large" }, + }); + const structured = result.structuredContent as { + status: number; + body: { artifact: { path: string; mimeType: string; byteLength: number } }; + }; + + expect(structured).toMatchObject({ + status: 200, + body: { + artifact: { + mimeType: "application/pdf", + byteLength: 1024 * 1024 + 1, + }, + }, + }); + expect(readFileSync(structured.body.artifact.path).byteLength).toBe(1024 * 1024 + 1); + } finally { + rmSync(dir, { recursive: true, force: true }); + } + }); + + it("rejects field projection when OpenAPI JSON responses become artifacts", async () => { + const dir = mkdtempSync(join(tmpdir(), "caplets-openapi-large-json-fields-")); + const specPath = join(dir, "openapi.json"); + const artifactDir = join(dir, "artifacts"); + writeFileSync(specPath, JSON.stringify(openApiSpec(baseUrl))); + const config = parseConfig({ + openapiEndpoints: { + users: { + name: "Users API", + description: "Manage users through the internal HTTP API.", + specPath, + baseUrl, + auth: { type: "none" }, + }, + }, + }); + const registry = new ServerRegistry(config); + const caplet = config.openapiEndpoints.users!; + const openapi = new OpenApiManager(registry, { artifactDir }); + const downstream = new DownstreamManager(registry); + + try { + await expect( + handleServerTool( + caplet, + { + operation: "call_tool", + name: "GET /users/{id}", + args: { path: { id: "large" }, query: { active: true } }, + fields: ["body.name"], + }, + registry, + downstream, + openapi, + ), + ).rejects.toMatchObject({ code: "REQUEST_INVALID" }); + } finally { + rmSync(dir, { recursive: true, force: true }); + } + }); + + it("does not reject OpenAPI HEAD responses by content length", async () => { + const dir = mkdtempSync(join(tmpdir(), "caplets-openapi-head-")); + const specPath = join(dir, "openapi.json"); + writeFileSync(specPath, JSON.stringify(openApiSpec(baseUrl))); + const config = parseConfig({ + openapiEndpoints: { + reports: { + name: "Reports API", + description: "Download reports from the internal HTTP API.", + specPath, + baseUrl, + auth: { type: "none" }, + }, + }, + }); + const registry = new ServerRegistry(config); + const openapi = new OpenApiManager(registry); + + try { + const result = await openapi.callTool(config.openapiEndpoints.reports!, "headReport", { + path: { id: "42" }, + }); + + expect(result.structuredContent).toMatchObject({ + status: 200, + headers: { + "content-type": "application/pdf", + }, + }); + expect(result.structuredContent).not.toHaveProperty("body"); + } finally { + rmSync(dir, { recursive: true, force: true }); + } + }); }); function openApiSpec(baseUrl: string) { @@ -1003,6 +1192,48 @@ function openApiSpec(baseUrl: string) { responses: { "201": { description: "Created" } }, }, }, + "/reports/{id}": { + get: { + operationId: "getReport", + summary: "Download a report", + parameters: [ + { + name: "id", + in: "path", + required: true, + schema: { type: "string" }, + }, + ], + responses: { + "200": { + description: "OK", + content: { + "application/pdf": {}, + }, + }, + }, + }, + head: { + operationId: "headReport", + summary: "Inspect a report", + parameters: [ + { + name: "id", + in: "path", + required: true, + schema: { type: "string" }, + }, + ], + responses: { + "200": { + description: "OK", + content: { + "application/pdf": {}, + }, + }, + }, + }, + }, }, }; } diff --git a/packages/core/test/remote-control-dispatch.test.ts b/packages/core/test/remote-control-dispatch.test.ts index 57152373..d7bd533a 100644 --- a/packages/core/test/remote-control-dispatch.test.ts +++ b/packages/core/test/remote-control-dispatch.test.ts @@ -428,6 +428,114 @@ describe("dispatchRemoteCliRequest", () => { }); }); + it("resolves Google Discovery scopes before starting remote OAuth login", async () => { + const context = testContext(); + const authFlowStore = new RemoteAuthFlowStore(); + const authDir = join(context.tempRoot, "auth"); + mkdirSync(authDir, { recursive: true }); + const discoveryPath = join(context.tempRoot, "drive.discovery.json"); + writeFileSync( + discoveryPath, + JSON.stringify({ + kind: "discovery#restDescription", + name: "drive", + version: "v3", + title: "Drive API", + rootUrl: "https://www.googleapis.com/", + servicePath: "drive/v3/", + baseUrl: "https://www.googleapis.com/drive/v3/", + resources: { + files: { + methods: { + list: { + id: "drive.files.list", + path: "files", + httpMethod: "GET", + scopes: ["https://www.googleapis.com/auth/drive.metadata.readonly"], + }, + }, + }, + }, + }), + ); + writeFileSync( + context.configPath, + JSON.stringify({ + googleDiscoveryApis: { + drive: { + name: "Drive", + description: "Drive API.", + discoveryPath, + auth: { + type: "oauth2", + clientId: "client", + tokenUrl: "https://oauth2.googleapis.com/token", + authorizationUrl: "https://accounts.google.com/o/oauth2/v2/auth", + }, + }, + }, + }), + ); + + const response = await dispatchRemoteCliRequest( + { command: "auth_login_start", arguments: { server: "drive" } }, + { + ...context, + authDir, + controlCallbackBaseUrl: "http://127.0.0.1:5387/control", + authFlowStore, + }, + ); + + expect(response).toMatchObject({ ok: true }); + const result = response.ok + ? (response.result as { authorizationUrl: string; flowId: string }) + : undefined; + expect(result?.flowId).toBeTruthy(); + const authorizationUrl = new URL(result?.authorizationUrl ?? ""); + expect(authorizationUrl.searchParams.get("scope")).toBe( + "https://www.googleapis.com/auth/drive.metadata.readonly", + ); + const fetchMock = vi.spyOn(globalThis, "fetch").mockResolvedValue( + Response.json({ + access_token: "drive-access-token", + token_type: "Bearer", + expires_in: 3600, + }), + ); + + await dispatchRemoteCliRequest( + { + command: "auth_login_complete", + arguments: { + flowId: result?.flowId, + callbackUrl: `http://127.0.0.1/callback?code=code&state=${authorizationUrl.searchParams.get("state")}`, + }, + }, + { + ...context, + authDir, + authFlowStore, + controlCallbackBaseUrl: "http://127.0.0.1:5387/control", + }, + ); + + expect(fetchMock).toHaveBeenCalledWith( + "https://oauth2.googleapis.com/token", + expect.objectContaining({ + method: "POST", + body: expect.stringContaining("code=code"), + }), + ); + expect(readTokenBundle("drive", authDir)).toMatchObject({ + protectedResourceOrigin: "https://www.googleapis.com", + metadata: { + protectedResource: "https://www.googleapis.com/drive/v3/", + requestedScopes: ["https://www.googleapis.com/auth/drive.metadata.readonly"], + }, + }); + }); + it("does not create a pending auth flow when MCP auth is already authorized", async () => { const fixture = remoteFixtureWithOAuth(); const authFlowStore = new RemoteAuthFlowStore(); diff --git a/packages/core/test/tools.test.ts b/packages/core/test/tools.test.ts index e67667fe..3411d966 100644 --- a/packages/core/test/tools.test.ts +++ b/packages/core/test/tools.test.ts @@ -282,6 +282,33 @@ describe("generated tool handlers", () => { }); }); + it("fails explicitly for Google Discovery tools until the manager is configured", async () => { + const config = parseConfig({ + googleDiscoveryApis: { + drive: { + name: "Google Drive", + description: "Access Google Drive files and permissions.", + discoveryUrl: "https://www.googleapis.com/discovery/v1/apis/drive/v3/rest", + auth: { type: "none" }, + }, + }, + }); + const googleRegistry = new ServerRegistry(config); + const downstream = {} as unknown as DownstreamManager; + + await expect( + handleServerTool( + config.googleDiscoveryApis.drive!, + { operation: "tools" }, + googleRegistry, + downstream, + ), + ).rejects.toMatchObject({ + code: "INTERNAL_ERROR", + message: "Google Discovery manager is not configured", + }); + }); + it("returns HTTP inspect without requiring an HTTP manager", async () => { const httpConfig = parseConfig({ httpApis: { @@ -992,6 +1019,42 @@ describe("generated tool handlers", () => { ]); }); + it("extracts structured artifact envelopes from call_tool results", async () => { + const downstream = { + callTool: vi.fn().mockResolvedValue({ + content: [{ type: "text" as const, text: "downloaded" }], + structuredContent: { + status: 200, + body: { + artifact: { + uri: "caplets://artifacts/http/call-1/report.pdf", + path: "/tmp/caplets/report.pdf", + filename: "report.pdf", + mimeType: "application/pdf", + byteLength: 12, + sha256: "a".repeat(64), + }, + }, + }, + }), + } as unknown as DownstreamManager; + + const result = await handleServerTool( + server, + { operation: "call_tool", name: "read", args: {} }, + registry, + downstream, + ); + + expect(result._meta.caplets.artifacts).toEqual([ + { + kind: "file", + displayPath: "/tmp/caplets/report.pdf", + pathResolution: "absolute", + }, + ]); + }); + it("extracts artifact links with spaces, title attributes, and parentheses", async () => { const result = await callToolWithText( 'Saved artifact [Screenshot](./screenshots/final view.png), file [Trace](./trace.zip "trace"), and artifact [Archive](./run(1).zip)', @@ -1250,6 +1313,43 @@ describe("generated tool handlers", () => { }); }); + it("allows field projection from real API body.artifact objects", () => { + const result = projectCallToolResult( + { + content: [{ type: "text" as const, text: "full output" }], + structuredContent: { + body: { + artifact: { + id: "art_1", + name: "native response object", + }, + }, + }, + isError: false, + }, + { + type: "object", + properties: { + body: { + type: "object", + properties: { + artifact: { + type: "object", + properties: { + id: { type: "string" }, + name: { type: "string" }, + }, + }, + }, + }, + }, + }, + ["body.artifact.id"], + ); + + expect(result.structuredContent).toEqual({ body: { artifact: { id: "art_1" } } }); + }); + it("preserves downstream isError results when fields are requested", async () => { const downstreamResult = { content: [{ type: "text" as const, text: "downstream failed with details" }], diff --git a/packages/opencode/package.json b/packages/opencode/package.json index b527ebc6..425f9f0b 100644 --- a/packages/opencode/package.json +++ b/packages/opencode/package.json @@ -46,8 +46,5 @@ }, "peerDependencies": { "@opencode-ai/plugin": ">=1" - }, - "engines": { - "node": ">=24" } } diff --git a/packages/pi/package.json b/packages/pi/package.json index d574719a..9eb10477 100644 --- a/packages/pi/package.json +++ b/packages/pi/package.json @@ -48,9 +48,6 @@ "@earendil-works/pi-coding-agent": "*", "@earendil-works/pi-tui": "*" }, - "engines": { - "node": ">=24" - }, "pi": { "extensions": [ "dist/index.js" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0ad7b061..857739b8 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -186,6 +186,9 @@ importers: '@babel/parser': specifier: ^7.29.7 version: 7.29.7 + '@exodus/bytes': + specifier: ^1.15.1 + version: 1.15.1 '@hono/mcp': specifier: ^0.3.0 version: 0.3.0(@modelcontextprotocol/sdk@1.29.0(zod@4.4.3))(hono-rate-limiter@0.5.3(hono@4.12.25)(unstorage@1.17.5(aws4fetch@1.0.20)))(hono@4.12.25)(zod@4.4.3) @@ -195,15 +198,24 @@ importers: '@modelcontextprotocol/sdk': specifier: ^1.29.0 version: 1.29.0(zod@4.4.3) + '@ungap/structured-clone': + specifier: ^1.3.1 + version: 1.3.1 ajv: specifier: ^8.20.0 version: 8.20.0 commander: specifier: ^15.0.0 version: 15.0.0 + formdata-node: + specifier: ^6.0.3 + version: 6.0.3 graphql: specifier: ^16.14.2 version: 16.14.2 + headers-polyfill: + specifier: ^5.0.1 + version: 5.0.1 hono: specifier: ^4.12.25 version: 4.12.25 @@ -219,6 +231,12 @@ importers: vfile-matter: specifier: ^5.0.1 version: 5.0.1 + web-streams-polyfill: + specifier: ^4.3.0 + version: 4.3.0 + whatwg-url: + specifier: ^16.0.1 + version: 16.0.1 yaml: specifier: ^2.9.0 version: 2.9.0 @@ -1425,6 +1443,15 @@ packages: cpu: [x64] os: [win32] + '@exodus/bytes@1.15.1': + resolution: {integrity: sha512-S6mL0yNB/Abt9Ei4tq8gDhcczc4S3+vQ4ra7vxnAf+YHC02srtqxKKZghx2Dq6p0e66THKwR6r8N6P95wEty7Q==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + peerDependencies: + '@noble/hashes': ^1.8.0 || ^2.0.0 + peerDependenciesMeta: + '@noble/hashes': + optional: true + '@expressive-code/core@0.43.1': resolution: {integrity: sha512-H4rUJXKyS6y2q9Ig9bIp3dFhWhkZQIeH/jRGl3DROlslrGvfD4OC9qzmvKEFExm+/DtdvvHMQ8/Olmrcfxp+wQ==} @@ -2871,6 +2898,9 @@ packages: '@types/sax@1.2.7': resolution: {integrity: sha512-rO73L89PJxeYM3s3pPPjiPgVVcymqU490g0YO5n5By0k2Erzj6tay/4lr1CHAAU4JyOWd1rpQ8bCf6cZfHU96A==} + '@types/set-cookie-parser@2.4.10': + resolution: {integrity: sha512-GGmQVGpQWUe5qglJozEjZV/5dyxbOOZ0LHe/lqyWssB88Y4svNfst0uqBVscdDeIKl5Jy5+aPSvy7mI9tYRguw==} + '@types/stats.js@0.17.4': resolution: {integrity: sha512-jIBvWWShCvlBqBNIZt0KAshWpvSjhkwkEu4ZUcASoAvhmrgAUI2t1dXrjSL4xXVLB4FznPrIsX3nKXFl/Dt4vA==} @@ -3853,6 +3883,10 @@ packages: resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} engines: {node: '>=14'} + formdata-node@6.0.3: + resolution: {integrity: sha512-8e1++BCiTzUno9v5IZ2J6bv4RU+3UKDmqWUQD0MIMVCd9AdhWkO1gw57oo1mNEX1dMq2EGI+FbWz4B92pscSQg==} + engines: {node: '>= 18'} + formdata-polyfill@4.0.10: resolution: {integrity: sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==} engines: {node: '>=12.20.0'} @@ -4027,6 +4061,9 @@ packages: hastscript@9.0.1: resolution: {integrity: sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w==} + headers-polyfill@5.0.1: + resolution: {integrity: sha512-1TJ6Fih/b8h5TIcv+1+Hw0PDQWJTKDKzFZzcKOiW1wJza3XoAQlkCuXLbymPYB8+ZQyw8mHvdw560e8zVFIWyA==} + highlight.js@10.7.3: resolution: {integrity: sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==} @@ -4982,6 +5019,10 @@ packages: resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} engines: {node: '>= 0.10'} + punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} + pure-rand@8.4.0: resolution: {integrity: sha512-IoM8YF/jY0hiugFo/wOWqfmarlE6J0wc6fDK1PhftMk7MGhVZl88sZimmqBBFomLOCSmcCCpsfj7wXASCpvK9A==} @@ -5204,6 +5245,9 @@ packages: resolution: {integrity: sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==} engines: {node: '>= 18'} + set-cookie-parser@3.1.0: + resolution: {integrity: sha512-kjnC1DXBHcxaOaOXBHBeRtltsDG2nUiUni+jP92M9gYdW12rsmx92UsfpH7o5tDRs7I1ZZPSQJQGv3UaRfCiuw==} + setimmediate@1.0.5: resolution: {integrity: sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==} @@ -5438,6 +5482,10 @@ packages: resolution: {integrity: sha512-EBJnVBr3dTXdA89WVFoAIPUqkBjxPMwRqsfuo1r240tKFHXv3zgca4+NJib/h6TyvGF7vOawz0jGuryJCdNHrw==} engines: {node: '>=20'} + tr46@6.0.0: + resolution: {integrity: sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==} + engines: {node: '>=20'} + trim-lines@3.0.1: resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==} @@ -5886,6 +5934,18 @@ packages: resolution: {integrity: sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==} engines: {node: '>= 8'} + web-streams-polyfill@4.3.0: + resolution: {integrity: sha512-/Gnggvj9oSrEvJbDyyPtAnxBt5fGQM2iWOKQNu7ie1OxDgK40iZpyV3TKaRiEzVj1oA1UxKnEy9XPXh6PW3eVw==} + engines: {node: '>= 8'} + + webidl-conversions@8.0.1: + resolution: {integrity: sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ==} + engines: {node: '>=20'} + + whatwg-url@16.0.1: + resolution: {integrity: sha512-1to4zXBxmXHV3IiSSEInrreIlu02vUOvrhxJJH5vcxYTBDAx51cqZiKdyTxlecdKNSjj8EcxGBxNf6Vg+945gw==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + which-pm-runs@1.1.0: resolution: {integrity: sha512-n1brCuqClxfFfq/Rb0ICg9giSZqCS+pLtccdag6C2HyufBrh3fBOiy9nb6ggRMvWOVH5GrdJskj5iGTZNxd7SA==} engines: {node: '>=4'} @@ -7289,6 +7349,8 @@ snapshots: '@esbuild/win32-x64@0.28.1': optional: true + '@exodus/bytes@1.15.1': {} + '@expressive-code/core@0.43.1': dependencies: '@ctrl/tinycolor': 4.2.0 @@ -8411,6 +8473,10 @@ snapshots: dependencies: '@types/node': 25.9.3 + '@types/set-cookie-parser@2.4.10': + dependencies: + '@types/node': 25.9.3 + '@types/stats.js@0.17.4': {} '@types/three@0.184.1': @@ -9494,6 +9560,8 @@ snapshots: cross-spawn: 7.0.6 signal-exit: 4.1.0 + formdata-node@6.0.3: {} + formdata-polyfill@4.0.10: dependencies: fetch-blob: 3.2.0 @@ -9824,6 +9892,11 @@ snapshots: property-information: 7.2.0 space-separated-tokens: 2.0.2 + headers-polyfill@5.0.1: + dependencies: + '@types/set-cookie-parser': 2.4.10 + set-cookie-parser: 3.1.0 + highlight.js@10.7.3: {} hono-rate-limiter@0.5.3(hono@4.12.25)(unstorage@1.17.5(aws4fetch@1.0.20)): @@ -11006,6 +11079,8 @@ snapshots: forwarded: 0.2.0 ipaddr.js: 1.9.1 + punycode@2.3.1: {} + pure-rand@8.4.0: {} qs@6.15.2: @@ -11378,6 +11453,8 @@ snapshots: transitivePeerDependencies: - supports-color + set-cookie-parser@3.1.0: {} + setimmediate@1.0.5: {} setprototypeof@1.2.0: {} @@ -11627,6 +11704,10 @@ snapshots: toml@4.1.1: {} + tr46@6.0.0: + dependencies: + punycode: 2.3.1 + trim-lines@3.0.1: {} trough@2.2.0: {} @@ -11975,6 +12056,18 @@ snapshots: web-streams-polyfill@3.3.3: {} + web-streams-polyfill@4.3.0: {} + + webidl-conversions@8.0.1: {} + + whatwg-url@16.0.1: + dependencies: + '@exodus/bytes': 1.15.1 + tr46: 6.0.0 + webidl-conversions: 8.0.1 + transitivePeerDependencies: + - '@noble/hashes' + which-pm-runs@1.1.0: {} which@2.0.2: diff --git a/schemas/caplet.schema.json b/schemas/caplet.schema.json index 7200d110..73f6898d 100644 --- a/schemas/caplet.schema.json +++ b/schemas/caplet.schema.json @@ -1,14 +1,13 @@ { "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://caplets.dev/caplet-frontmatter.schema.json", + "$id": "https://caplets.dev/caplet.schema.json", "title": "Caplet file frontmatter", "description": "YAML frontmatter schema for a Markdown Caplet file.", "type": "object", "properties": { "$schema": { - "description": "Optional JSON Schema URL for editor validation.", - "type": "string", - "format": "uri" + "description": "Optional JSON Schema for editor validation.", + "type": "string" }, "name": { "type": "string", @@ -717,6 +716,265 @@ "additionalProperties": false, "description": "OpenAPI endpoint backend configuration for this Caplet." }, + "googleDiscoveryApi": { + "type": "object", + "properties": { + "discoveryPath": { + "description": "Local Google Discovery document path.", + "type": "string", + "minLength": 1 + }, + "discoveryUrl": { + "description": "Remote Google Discovery document URL.", + "type": "string", + "minLength": 1 + }, + "baseUrl": { + "description": "Override base URL for Google API requests.", + "type": "string", + "minLength": 1 + }, + "auth": { + "oneOf": [ + { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "none" + } + }, + "required": ["type"], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "bearer" + }, + "token": { + "type": "string", + "minLength": 1 + } + }, + "required": ["type", "token"], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "headers" + }, + "headers": { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": { + "type": "string", + "minLength": 1 + } + } + }, + "required": ["type", "headers"], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "oauth2" + }, + "authorizationUrl": { + "type": "string", + "minLength": 1 + }, + "tokenUrl": { + "type": "string", + "minLength": 1 + }, + "issuer": { + "type": "string", + "minLength": 1 + }, + "resourceMetadataUrl": { + "type": "string", + "minLength": 1 + }, + "authorizationServerMetadataUrl": { + "type": "string", + "minLength": 1 + }, + "openidConfigurationUrl": { + "type": "string", + "minLength": 1 + }, + "clientMetadataUrl": { + "type": "string", + "minLength": 1 + }, + "clientId": { + "type": "string", + "minLength": 1 + }, + "clientSecret": { + "type": "string", + "minLength": 1 + }, + "scopes": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + } + }, + "redirectUri": { + "type": "string", + "minLength": 1 + } + }, + "required": ["type"], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "oidc" + }, + "authorizationUrl": { + "type": "string", + "minLength": 1 + }, + "tokenUrl": { + "type": "string", + "minLength": 1 + }, + "issuer": { + "type": "string", + "minLength": 1 + }, + "resourceMetadataUrl": { + "type": "string", + "minLength": 1 + }, + "authorizationServerMetadataUrl": { + "type": "string", + "minLength": 1 + }, + "openidConfigurationUrl": { + "type": "string", + "minLength": 1 + }, + "clientMetadataUrl": { + "type": "string", + "minLength": 1 + }, + "clientId": { + "type": "string", + "minLength": 1 + }, + "clientSecret": { + "type": "string", + "minLength": 1 + }, + "scopes": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + } + }, + "redirectUri": { + "type": "string", + "minLength": 1 + } + }, + "required": ["type"], + "additionalProperties": false + } + ], + "description": "Explicit Google API request auth config. Use {\"type\":\"none\"} for public APIs." + }, + "requestTimeoutMs": { + "description": "Timeout in milliseconds for Google API HTTP requests.", + "type": "integer", + "exclusiveMinimum": 0, + "maximum": 9007199254740991 + }, + "operationCacheTtlMs": { + "description": "Milliseconds Google Discovery operation metadata stays fresh. Set 0 to refresh every time.", + "type": "integer", + "minimum": 0, + "maximum": 9007199254740991 + }, + "includeOperations": { + "type": "array", + "items": { + "type": "string", + "minLength": 1, + "maxLength": 160 + } + }, + "excludeOperations": { + "type": "array", + "items": { + "type": "string", + "minLength": 1, + "maxLength": 160 + } + }, + "disabled": { + "description": "When true, omit this Caplet from discovery.", + "type": "boolean" + }, + "projectBinding": { + "type": "object", + "properties": { + "required": { + "type": "boolean", + "const": true + } + }, + "required": ["required"], + "additionalProperties": false, + "description": "Project Binding requirements for Caplets that need an attached project." + }, + "runtime": { + "type": "object", + "properties": { + "features": { + "type": "array", + "items": { + "type": "string", + "enum": ["docker", "browser"] + } + }, + "resources": { + "type": "object", + "properties": { + "class": { + "type": "string", + "enum": ["standard", "large", "heavy"] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false, + "description": "Runtime feature and resource requirements for hosted execution." + } + }, + "required": ["auth"], + "additionalProperties": false, + "description": "Google Discovery API backend configuration for this Caplet." + }, "graphqlEndpoint": { "type": "object", "properties": { diff --git a/schemas/caplets-config.schema.json b/schemas/caplets-config.schema.json index 3ec7d1ce..9a6d84be 100644 --- a/schemas/caplets-config.schema.json +++ b/schemas/caplets-config.schema.json @@ -6,9 +6,8 @@ "type": "object", "properties": { "$schema": { - "description": "Optional JSON Schema URL for editor validation.", - "type": "string", - "format": "uri" + "description": "Optional JSON Schema for editor validation.", + "type": "string" }, "version": { "default": 1, @@ -962,6 +961,437 @@ "additionalProperties": false } }, + "googleDiscoveryApis": { + "default": {}, + "description": "Google Discovery APIs keyed by stable Caplet ID.", + "type": "object", + "propertyNames": { + "type": "string", + "pattern": "^[a-zA-Z0-9_-]{1,64}$" + }, + "additionalProperties": { + "type": "object", + "properties": { + "name": { + "type": "string", + "minLength": 1, + "maxLength": 80, + "description": "Human-readable Google Discovery API display name." + }, + "description": { + "type": "string", + "description": "Capability description shown to agents before Google Discovery operations are disclosed." + }, + "discoveryPath": { + "description": "Local Google Discovery document path.", + "type": "string", + "minLength": 1 + }, + "discoveryUrl": { + "description": "Remote Google Discovery document URL.", + "type": "string", + "format": "uri" + }, + "baseUrl": { + "description": "Override base URL for Google API requests.", + "type": "string", + "format": "uri" + }, + "includeOperations": { + "type": "array", + "items": { + "type": "string", + "minLength": 1, + "maxLength": 160 + } + }, + "excludeOperations": { + "type": "array", + "items": { + "type": "string", + "minLength": 1, + "maxLength": 160 + } + }, + "auth": { + "oneOf": [ + { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "none" + } + }, + "required": ["type"], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "bearer" + }, + "token": { + "type": "string", + "minLength": 1 + } + }, + "required": ["type", "token"], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "headers" + }, + "headers": { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": { + "type": "string", + "minLength": 1 + } + } + }, + "required": ["type", "headers"], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "oauth2" + }, + "authorizationUrl": { + "type": "string", + "format": "uri" + }, + "tokenUrl": { + "type": "string", + "format": "uri" + }, + "issuer": { + "type": "string", + "format": "uri" + }, + "resourceMetadataUrl": { + "type": "string", + "format": "uri" + }, + "authorizationServerMetadataUrl": { + "type": "string", + "format": "uri" + }, + "openidConfigurationUrl": { + "type": "string", + "format": "uri" + }, + "clientMetadataUrl": { + "type": "string", + "format": "uri" + }, + "clientId": { + "type": "string", + "minLength": 1 + }, + "clientSecret": { + "type": "string", + "minLength": 1 + }, + "scopes": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + } + }, + "redirectUri": { + "type": "string", + "format": "uri" + } + }, + "required": ["type"], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "oidc" + }, + "authorizationUrl": { + "type": "string", + "format": "uri" + }, + "tokenUrl": { + "type": "string", + "format": "uri" + }, + "issuer": { + "type": "string", + "format": "uri" + }, + "resourceMetadataUrl": { + "type": "string", + "format": "uri" + }, + "authorizationServerMetadataUrl": { + "type": "string", + "format": "uri" + }, + "openidConfigurationUrl": { + "type": "string", + "format": "uri" + }, + "clientMetadataUrl": { + "type": "string", + "format": "uri" + }, + "clientId": { + "type": "string", + "minLength": 1 + }, + "clientSecret": { + "type": "string", + "minLength": 1 + }, + "scopes": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + } + }, + "redirectUri": { + "type": "string", + "format": "uri" + } + }, + "required": ["type"], + "additionalProperties": false + } + ], + "description": "Explicit Google API request auth config. Use {\"type\":\"none\"} for public APIs." + }, + "tags": { + "type": "array", + "items": { + "type": "string", + "minLength": 1, + "maxLength": 80 + } + }, + "exposure": { + "type": "string", + "enum": [ + "direct", + "progressive", + "code_mode", + "direct_and_code_mode", + "progressive_and_code_mode" + ], + "description": "How this Caplet is exposed to agents." + }, + "shadowing": { + "default": "forbid", + "description": "Whether attached local Caplets may shadow this remote Caplet ID.", + "type": "string", + "enum": ["forbid", "allow"] + }, + "useWhen": { + "description": "When agents should prefer this Caplet or configured action.", + "type": "string", + "minLength": 1, + "maxLength": 500 + }, + "avoidWhen": { + "description": "When agents should avoid this Caplet or configured action.", + "type": "string", + "minLength": 1, + "maxLength": 500 + }, + "setup": { + "type": "object", + "properties": { + "commands": { + "type": "array", + "items": { + "type": "object", + "properties": { + "label": { + "type": "string", + "minLength": 1, + "description": "Human-readable setup or verification step label." + }, + "command": { + "type": "string", + "minLength": 1, + "description": "Executable command to spawn without a shell." + }, + "args": { + "description": "Arguments passed to the command.", + "type": "array", + "items": { + "type": "string" + } + }, + "env": { + "description": "Additional environment variables.", + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": { + "type": "string" + } + }, + "cwd": { + "description": "Working directory for this command.", + "type": "string", + "minLength": 1 + }, + "timeoutMs": { + "type": "integer", + "exclusiveMinimum": 0, + "maximum": 9007199254740991 + }, + "maxOutputBytes": { + "type": "integer", + "exclusiveMinimum": 0, + "maximum": 9007199254740991 + } + }, + "required": ["label", "command"], + "additionalProperties": false + } + }, + "verify": { + "type": "array", + "items": { + "type": "object", + "properties": { + "label": { + "type": "string", + "minLength": 1, + "description": "Human-readable setup or verification step label." + }, + "command": { + "type": "string", + "minLength": 1, + "description": "Executable command to spawn without a shell." + }, + "args": { + "description": "Arguments passed to the command.", + "type": "array", + "items": { + "type": "string" + } + }, + "env": { + "description": "Additional environment variables.", + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": { + "type": "string" + } + }, + "cwd": { + "description": "Working directory for this command.", + "type": "string", + "minLength": 1 + }, + "timeoutMs": { + "type": "integer", + "exclusiveMinimum": 0, + "maximum": 9007199254740991 + }, + "maxOutputBytes": { + "type": "integer", + "exclusiveMinimum": 0, + "maximum": 9007199254740991 + } + }, + "required": ["label", "command"], + "additionalProperties": false + } + } + }, + "additionalProperties": false + }, + "projectBinding": { + "type": "object", + "properties": { + "required": { + "type": "boolean", + "const": true, + "description": "Requires Project Binding before this Caplet can run." + } + }, + "required": ["required"], + "additionalProperties": false, + "description": "Project Binding requirements for Caplets that need an attached project." + }, + "runtime": { + "type": "object", + "properties": { + "features": { + "type": "array", + "items": { + "type": "string", + "enum": ["docker", "browser"] + }, + "description": "Runtime features required by this Caplet." + }, + "resources": { + "description": "Hosted sandbox resource requirements.", + "type": "object", + "properties": { + "class": { + "description": "Requested hosted sandbox resource class.", + "type": "string", + "enum": ["standard", "large", "heavy"] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false, + "description": "Runtime feature and resource requirements for hosted execution." + }, + "requestTimeoutMs": { + "default": 60000, + "description": "Timeout in milliseconds for Google Discovery HTTP requests.", + "type": "integer", + "exclusiveMinimum": 0, + "maximum": 9007199254740991 + }, + "operationCacheTtlMs": { + "default": 30000, + "description": "Milliseconds Google Discovery operation metadata stays fresh. Set 0 to refresh every time.", + "type": "integer", + "minimum": 0, + "maximum": 9007199254740991 + }, + "disabled": { + "default": false, + "description": "When true, omit this Google Discovery Caplet from discovery.", + "type": "boolean" + } + }, + "required": ["name", "description", "auth"], + "additionalProperties": false + } + }, "graphqlEndpoints": { "default": {}, "description": "GraphQL endpoints keyed by stable Caplet ID.", diff --git a/scripts/check-public-docs.ts b/scripts/check-public-docs.ts index ae2a69a0..9e3738c7 100644 --- a/scripts/check-public-docs.ts +++ b/scripts/check-public-docs.ts @@ -49,10 +49,7 @@ const requiredContent = new Map([ ["changelog.mdx", ["Changelog", "GitHub releases"]], ["reference/config.mdx", ["https://caplets.dev/config.schema.json", "Required"]], ["reference/code-mode-api.mdx", ["CapletHandle", "DebugApi", "CapletsResult"]], - [ - "reference/caplet-files.mdx", - ["https://caplets.dev/caplet-frontmatter.schema.json", "CAPLET.md"], - ], + ["reference/caplet-files.mdx", ["https://caplets.dev/caplet.schema.json", "CAPLET.md"]], ]); const forbiddenPatterns = [ diff --git a/scripts/commit-byterover-context.test.ts b/scripts/commit-byterover-context.test.ts deleted file mode 100644 index 40ae6fd5..00000000 --- a/scripts/commit-byterover-context.test.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { expect, test } from "vitest"; - -import { - buildCommitArgs, - checkByteRoverStatus, - formatCheckWarning, -} from "./commit-byterover-context"; - -test("ByteRover status check reports clean when git returns no .brv changes", () => { - expect(checkByteRoverStatus("")).toEqual({ hasChanges: false }); -}); - -test("ByteRover status check reports changes from porcelain output", () => { - expect(checkByteRoverStatus(" M .brv/context.md\n?? .brv/new.md\n")).toEqual({ - hasChanges: true, - }); -}); - -test("ByteRover check warning is advisory and includes manual commit command", () => { - expect(formatCheckWarning()).toContain("ByteRover context has uncommitted changes"); - expect(formatCheckWarning()).toContain("pnpm exec tsx ./scripts/commit-byterover-context.ts"); -}); - -test("ByteRover context commits use a conventional docs commit message", () => { - expect(buildCommitArgs()).toEqual([ - "commit", - "--no-verify", - "-m", - "docs(agents): byterover context", - ]); -}); diff --git a/scripts/commit-byterover-context.ts b/scripts/commit-byterover-context.ts deleted file mode 100644 index d6b4e7c5..00000000 --- a/scripts/commit-byterover-context.ts +++ /dev/null @@ -1,84 +0,0 @@ -import { spawnSync } from "node:child_process"; -import { fileURLToPath } from "node:url"; - -const recursionGuardEnv = "BYTEROVER_CONTEXT_COMMIT"; -const commitMessage = "docs(agents): byterover context"; - -export type ByteRoverStatus = { - hasChanges: boolean; -}; - -export function checkByteRoverStatus(porcelainOutput: string): ByteRoverStatus { - return { hasChanges: porcelainOutput.trim().length > 0 }; -} - -export function buildCommitArgs(): string[] { - return ["commit", "--no-verify", "-m", commitMessage]; -} - -export function formatCheckWarning(): string { - return [ - "ByteRover context has uncommitted changes.", - "These changes are advisory and will not block this push.", - "Run `pnpm exec tsx ./scripts/commit-byterover-context.ts` to commit them.", - ].join("\n"); -} - -function runGit(args: string[], env: NodeJS.ProcessEnv = process.env): string { - const result = spawnSync("git", args, { - encoding: "utf8", - env, - stdio: ["ignore", "pipe", "pipe"], - }); - - if (result.status !== 0) { - const detail = result.stderr.trim() || result.stdout.trim(); - throw new Error(`git ${args.join(" ")} failed${detail ? `: ${detail}` : ""}`); - } - - return result.stdout; -} - -function isCheckMode(args: string[]): boolean { - return args.includes("--check"); -} - -export function main(args = process.argv.slice(2)): number { - if (process.env[recursionGuardEnv] === "1") { - return 0; - } - - const status = checkByteRoverStatus(runGit(["status", "--porcelain", "--", ".brv"])); - - if (!status.hasChanges) { - return 0; - } - - if (isCheckMode(args)) { - console.warn(formatCheckWarning()); - return 0; - } - - runGit(["add", "--", ".brv"]); - const stagedStatus = checkByteRoverStatus( - runGit(["diff", "--cached", "--name-status", "--", ".brv"]), - ); - - if (!stagedStatus.hasChanges) { - return 0; - } - - runGit(buildCommitArgs(), { ...process.env, [recursionGuardEnv]: "1" }); - return 0; -} - -const currentFilePath = fileURLToPath(import.meta.url); - -if (process.argv[1] === currentFilePath) { - try { - process.exitCode = main(); - } catch (error) { - console.error(error instanceof Error ? error.message : String(error)); - process.exitCode = 1; - } -} diff --git a/scripts/generate-code-mode-platform-runtime.mjs b/scripts/generate-code-mode-platform-runtime.mjs new file mode 100644 index 00000000..870f1021 --- /dev/null +++ b/scripts/generate-code-mode-platform-runtime.mjs @@ -0,0 +1,65 @@ +#!/usr/bin/env node +import { readFileSync, writeFileSync } from "node:fs"; +import { dirname, join } from "node:path"; +import { fileURLToPath } from "node:url"; +import { build } from "rolldown"; + +const root = dirname(dirname(fileURLToPath(import.meta.url))); +const check = process.argv.includes("--check"); +const input = "packages/core/src/code-mode/platform-entry.ts"; +const output = "packages/core/src/code-mode/platform-runtime.generated.ts"; +const exportName = "CODE_MODE_PLATFORM_RUNTIME_SOURCE"; + +const bundled = await generateBundle(); +const generated = [ + "// Generated by scripts/generate-code-mode-platform-runtime.mjs. Do not edit by hand.", + `export const ${exportName} =`, + ` '${singleQuoted(bundled)}' as const;`, + "", +].join("\n"); + +if (check) { + const current = readFileSync(join(root, output), "utf8"); + if (current !== generated) { + console.error(`${output} is out of date. Run pnpm code-mode:generate-api.`); + process.exitCode = 1; + } +} else { + writeFileSync(join(root, output), generated); +} + +async function generateBundle() { + const bundle = await build({ + input: join(root, input), + platform: "browser", + resolve: { + conditionNames: ["node", "import", "default"], + }, + output: { + format: "iife", + name: "__caplets_platform_runtime", + }, + }); + + try { + const chunk = bundle.output.find((item) => item.type === "chunk"); + if (!chunk) { + throw new Error("Rolldown did not produce a platform runtime chunk."); + } + return normalizeBundle(chunk.code); + } finally { + await bundle.close?.(); + } +} + +function normalizeBundle(value) { + return value.replace(/\r\n?/gu, "\n").trimEnd(); +} + +function singleQuoted(value) { + return value + .replace(/\\/gu, "\\\\") + .replace(/'/gu, "\\'") + .replace(/\n/gu, "\\n") + .replace(/\t/gu, "\\t"); +} diff --git a/scripts/generate-code-mode-runtime-api.mjs b/scripts/generate-code-mode-runtime-api.mjs index 63fe7d3d..aa7ff2eb 100644 --- a/scripts/generate-code-mode-runtime-api.mjs +++ b/scripts/generate-code-mode-runtime-api.mjs @@ -5,26 +5,42 @@ import { fileURLToPath } from "node:url"; const root = dirname(dirname(fileURLToPath(import.meta.url))); const check = process.argv.includes("--check"); -const template = "packages/core/src/code-mode/runtime-api.d.ts"; -const output = "packages/core/src/code-mode/runtime-api.generated.ts"; -const exportName = "CODE_MODE_RUNTIME_API_DECLARATION"; +const declarations = [ + { + template: "packages/core/src/code-mode/runtime-api.d.ts", + output: "packages/core/src/code-mode/runtime-api.generated.ts", + exportName: "CODE_MODE_RUNTIME_API_DECLARATION", + }, + { + template: "packages/core/src/code-mode/diagnostics-builtins.d.ts", + output: "packages/core/src/code-mode/diagnostics-builtins.generated.ts", + exportName: "CODE_MODE_DIAGNOSTICS_BUILTINS_DECLARATION", + }, +]; -const declaration = minifyDeclarationText(readFileSync(join(root, template), "utf8")); -const generated = [ - "// Generated by scripts/generate-code-mode-runtime-api.mjs. Do not edit by hand.", - `export const ${exportName} =`, - ` '${singleQuoted(declaration)}' as const;`, - "", -].join("\n"); +const generatedDeclarations = declarations.map(({ template, output, exportName }) => { + const declaration = minifyDeclarationText(readFileSync(join(root, template), "utf8")); + const generated = [ + "// Generated by scripts/generate-code-mode-runtime-api.mjs. Do not edit by hand.", + `export const ${exportName} =`, + ` '${singleQuoted(declaration)}' as const;`, + "", + ].join("\n"); + return { output, generated }; +}); if (check) { - const current = readFileSync(join(root, output), "utf8"); - if (current !== generated) { - console.error(`${output} is out of date. Run pnpm code-mode:generate-api.`); - process.exitCode = 1; + for (const { output, generated } of generatedDeclarations) { + const current = readFileSync(join(root, output), "utf8"); + if (current !== generated) { + console.error(`${output} is out of date. Run pnpm code-mode:generate-api.`); + process.exitCode = 1; + } } } else { - writeFileSync(join(root, output), generated); + for (const { output, generated } of generatedDeclarations) { + writeFileSync(join(root, output), generated); + } } function singleQuoted(value) { @@ -34,6 +50,7 @@ function singleQuoted(value) { function minifyDeclarationText(value) { return value .replace(/^\s*export\s*\{\s*\}\s*;?\s*/u, "") + .replace(/\s*export\s*\{\s*\}\s*;?\s*$/u, "") .replace(/\r\n?/gu, "\n") .split("\n") .map((line) => line.trim()) diff --git a/scripts/generate-config-schema.ts b/scripts/generate-config-schema.ts index 2b5231d4..affe40dd 100644 --- a/scripts/generate-config-schema.ts +++ b/scripts/generate-config-schema.ts @@ -18,7 +18,7 @@ const schemas = [ { paths: [ join(repoRoot, "schemas/caplet.schema.json"), - join(repoRoot, "apps/landing/public/caplet-frontmatter.schema.json"), + join(repoRoot, "apps/landing/public/caplet.schema.json"), ], schema: capletJsonSchema(), }, diff --git a/scripts/generate-docs-reference.ts b/scripts/generate-docs-reference.ts index e8ebb7fb..6dbeee36 100644 --- a/scripts/generate-docs-reference.ts +++ b/scripts/generate-docs-reference.ts @@ -69,7 +69,7 @@ const outputs = new Map([ title: "Caplet files", description: "Generated reference for Markdown Caplet file frontmatter.", sourcePath: "schemas/caplet.schema.json", - canonicalUrl: "https://caplets.dev/caplet-frontmatter.schema.json", + canonicalUrl: "https://caplets.dev/caplet.schema.json", intro: "Caplet files are Markdown files with YAML frontmatter. Store a single-file Caplet as `osv.md`, or use a folder with `CAPLET.md` when the capability needs nearby assets.", }), @@ -412,11 +412,13 @@ function codeModeApiPage(): string { .map((name) => extractDeclaration(source, name)) .filter((block): block is string => Boolean(block)); - return `${frontmatter("Code Mode API", "Generated reference for the TypeScript surface available inside `caplets__code_mode`.")} + return `${frontmatter("Code Mode API", "Generated reference for the lean TypeScript handle surface available inside `caplets__code_mode`.")} ${generatedMarkerComment} Code Mode runs TypeScript with generated \`caplets.\` handles. Prefer discovery methods first, then call downstream tools with compact, decision-ready inputs and outputs. +This reference intentionally lists the lean generated declaration surface: Caplet handles, debug helpers, shared result types, and \`console\`. Runtime platform globals such as \`URL\`, \`TextEncoder\`, \`Headers\`, timers, and \`crypto.randomUUID()\` are available for ordinary JavaScript data shaping, but they are not enumerated here or in the Code Mode tool prompt. + ${codeModeApiRecipes()} ## Runtime handles