Skip to content

refactor(registry)!: restructure entries as Entry → Package → Source (ADR-0001)#43

Merged
amondnet merged 6 commits into
mainfrom
refactor/registry-entry-package-source-schema
Apr 9, 2026
Merged

refactor(registry)!: restructure entries as Entry → Package → Source (ADR-0001)#43
amondnet merged 6 commits into
mainfrom
refactor/registry-entry-package-source-schema

Conversation

@amondnet

@amondnet amondnet commented Apr 9, 2026

Copy link
Copy Markdown
Contributor

Summary

The old registry schema used a flat strategies array where each entry carried both dispatch logic and fallback ordering, forcing the server to apply runtime heuristics to decide which strategy to pick. This made intent ambiguous and complicated the CLI's source-selection path.

This PR introduces a three-level hierarchy — Entry → Package → Source — that makes each layer's responsibility explicit:

  • Entry: identity and aliases (ecosystem lookups)
  • Package: versioning and doc path metadata
  • Source: concrete fetch instructions (github / npm / web / llms-txt)

The shape is defined in ADR-0001 (.please/docs/decisions/0001-registry-entry-schema-entry-package-source.md).

Changes

  • ADR: Add ADR-0001 documenting the Entry → Package → Source decision and migration rationale
  • Schema (packages/schema): Restructure registry entry types — RegistryEntry, RegistryPackage, RegistrySource; remove legacy RegistryStrategy, expandStrategies, top-level strategies/aliases/docsPath
  • Server + CLI (apps/registry, packages/cli): Adopt new types in the Nuxt Content server routes (/api/registry/<slug>) and CLI registry resolver (selectBestStrategy)
  • Content migration: Migrate all 50 registry entries from the old shape to the new Entry → Package → Source layout

Breaking Changes

BREAKING CHANGE — this PR contains breaking changes to the registry API and schema.

Removed Replacement
RegistryStrategy type RegistrySource
expandStrategies() helper Direct entry.packages[].sources[] traversal
Top-level strategies field in entries packages[].sources[]
Top-level aliases field entry.aliases
Top-level docsPath field packages[].docsPath
/api/registry/<slug> previous response shape New shape with packages array

Consumers calling /api/registry/<slug> must update to read packages[0].sources instead of strategies.

Validation

  • bun run build — 3/3 packages build successfully
  • Schema tests — 29/29 passing
  • CLI tests — 242/242 passing

ADR Reference

.please/docs/decisions/0001-registry-entry-schema-entry-package-source.md

Test Plan

  • bun run build passes with no type errors across all packages
  • GET /api/registry/facebook/react returns new packages array shape
  • GET /api/registry/npm/react (alias lookup) resolves correctly
  • ask docs add npm:react resolves via new registry response shape
  • ask docs add vercel/ai falls back correctly using packages[0].sources
  • Registry browser (apps/registry/) renders entries without errors
  • 50 migrated content entries load without schema validation errors

Summary by cubic

Refactors the registry to an explicit Entry → Package → Source hierarchy, removes runtime heuristics, and updates the server and CLI to the new shape. Adds strict schema validation, a clearer API response, and guidance for monorepo lookups. This is a breaking change to the registry schema and the /api/registry response.

  • Refactors

    • packages/schema: add RegistryPackage/RegistrySource; drop RegistryStrategy/expandStrategies; add findPackageByAlias, isMonorepoEntry, slugifyPackageName; validate duplicate aliases/package names and slug collisions; reject GitHub branch+tag; rename fields sourcetype, docsPathpath.
    • apps/registry: /api/registry/<slug> now returns { name, description, repo, resolvedName, package, sources }; owner/repo on monorepos returns 409 with alias guidance; deletes disambiguateStrategies and monorepo heuristics.
    • packages/cli: remove client-side strategy selection; take sources[0] in declared order; add sourceConfigFromRegistrySource; ask docs add owner/repo uses returned GitHub source path when available; surface 409 Conflict messages to guide alias-based disambiguation.
    • Content: migrate 50 entries to packages[].sources[] with field renames; ADR-0001 added under .please/docs/decisions/.
  • Migration

    • Schema: replace top-level strategies/aliases with packages[].aliases and packages[].sources; update fields sourcetype and docsPathpath.
    • API consumers: stop reading strategies; read sources and resolvedName; for monorepos, look up via alias /<ecosystem>/<name> (e.g., npm/@mastra/core) instead of owner/repo.
    • CLI/SDKs: remove any use of RegistryStrategy, expandStrategies, or client-side reordering; trust sources declaration order for primary and fallbacks.

Written for commit c562fdb. Summary will update on new commits.

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Apr 9, 2026

Copy link
Copy Markdown

Deploying ask-registry with  Cloudflare Pages  Cloudflare Pages

Latest commit: c562fdb
Status: ✅  Deploy successful!
Preview URL: https://708c968f.ask-registry.pages.dev
Branch Preview URL: https://refactor-registry-entry-pack.ask-registry.pages.dev

View logs

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 59 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="packages/schema/src/registry.ts">

<violation number="1" location="packages/schema/src/registry.ts:207">
P2: `String.replace` with a string argument only replaces the first `/`. Use `replaceAll` to match the stated intent that the output must not contain `/`.</violation>
</file>
Architecture diagram
sequenceDiagram
    participant CLI as "CLI (packages/cli)"
    participant API as "Registry API (apps/registry)"
    participant Schema as "Schema Helpers (packages/schema)"
    participant Content as "Registry Content (Markdown/YAML)"

    Note over CLI,Content: ADR-0001: Entry -> Package -> Source Flow

    rect rgb(240, 240, 240)
        Note right of CLI: Standard Lookup (Alias)
        CLI->>API: GET /api/registry/{ecosystem}/{name}\n(e.g., npm/@mastra/core)
        API->>Content: Fetch all entries
        Content-->>API: Entry list
        
        API->>Schema: NEW: findPackageByAlias(entry, ecosystem, name)
        Schema-->>API: Matched RegistryPackage
        
        API->>Schema: NEW: slugifyPackageName(package.name)
        Schema-->>API: resolvedName (e.g., mastra-core)
        
        API-->>CLI: 200 RegistryApiResponse\n(Metadata + Package + Ordered Sources)
    end

    rect rgb(255, 240, 240)
        Note right of CLI: Unhappy Path: Monorepo Ambiguity
        CLI->>API: GET /api/registry/{owner}/{repo}\n(e.g., mastra-ai/mastra)
        API->>Schema: NEW: isMonorepoEntry(entry)
        alt entry.packages.length > 1
            API-->>CLI: CHANGED: 409 Conflict\n(Must disambiguate via alias)
        else single package
            API-->>CLI: 200 RegistryApiResponse
        end
    end

    Note over CLI,Schema: CLI Processing
    
    CLI->>CLI: NEW: sourceConfigFromRegistrySource(sources[0])
    Note right of CLI: Trusting author-declared priority order\n(Removes legacy client-side reordering)
    
    opt Primary source fails
        CLI->>CLI: Walk remainder of sources[] fallback chain
    end

    Note over CLI,Content: File System Result
    CLI->>CLI: Write to .ask/docs/{resolvedName}@{version}/
Loading

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Comment thread packages/schema/src/registry.ts
amondnet added a commit that referenced this pull request Apr 9, 2026
…g to null

`fetchRegistryEntry` used to short-circuit every non-2xx response to
`null`, which was correct for 404 (entry not found) but hid the
registry server's deliberate 409 Conflict response on direct
`owner/repo` lookups of monorepo entries. The 409 carries an
actionable `statusMessage` telling the user to disambiguate via an
ecosystem alias (e.g. `npm:@mastra/core` instead of
`mastra-ai/mastra`) — that message was being silently dropped, and
the caller's github fast-path fell through to downloading the whole
monorepo archive.

Split the branches:
- 404 still maps to `null` (legitimate miss, caller tries other paths)
- other non-OK statuses log a warning via consola and still return
  `null`, but the user now sees the server's message so they can
  correct the command

Found by silent-failure-hunter review of PR #43.
amondnet added a commit that referenced this pull request Apr 9, 2026
Two schema-level invariants surfaced by the type-analyzer review of
PR #43. Both close narrow but genuine footguns by failing parse early
instead of silently picking one interpretation at runtime.

1. GithubSource: `branch` and `tag` are now mutually exclusive. The
   prose comment previously documented that `tag` takes precedence,
   which meant a caller-supplied branch was silently dropped whenever
   both were set. Implemented as a superRefine on the sourceSchema
   discriminated union (refining inside the variant breaks
   z.discriminatedUnion which requires raw ZodObject options).

2. registryEntrySchema: reject package names that slugify to the
   same directory. Different canonical names can collide after
   `slugifyPackageName` (e.g. `@a/b-c` and `a-b-c` both → `a-b-c`),
   which would cause `.ask/docs/<slug>@<ver>/` directory clashes on
   the CLI side. The existing `packages[].name` uniqueness check did
   not catch this because the names themselves differ. Added a new
   slug-uniqueness pass alongside the alias and name checks.

Two new tests cover the branch/tag rejection and the slug collision
case. All 32 schema tests and 242 CLI tests still pass.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

0 issues found across 3 files (changes from recent commits).

Requires human review: Auto-approval blocked by 1 unresolved issue from previous reviews.

amondnet added 6 commits April 9, 2026 16:22
Captures the decision to restructure registry entries around an explicit
three-level hierarchy (Entry → Package → Source), replacing the flat
strategies list whose dual "dispatch + fallback" meaning forced runtime
heuristics (disambiguateStrategies, npmStrategyCount > 1) in the server.
Rewrite packages/schema/src/registry.ts per ADR-0001. Replaces the flat
`strategies` list with an explicit three-level hierarchy where each entry
owns one or more packages, and each package owns an ordered source
fallback chain.

Changes:
- sourceSchema: discriminated union on `type` (npm/github/web/llms-txt)
  with per-variant requirements (npm.package required, github.repo regex,
  web.urls min(1), etc.)
- packageSchema: enforces min(1) aliases and min(1) sources
- registryEntrySchema: drops top-level `aliases`/`strategies`/`docsPath`
  in favor of `packages[]`; superRefine rejects duplicate aliases and
  package names across packages
- New helpers: findPackageByAlias, isMonorepoEntry, slugifyPackageName —
  shared by CLI and registry server
- Removes expandStrategies (no longer needed; packages always carry
  explicit sources)

BREAKING CHANGE: all consumers of RegistryStrategy/expandStrategies must
migrate to the new Package/Source types. See ADR-0001.
Follow-up to the schema rewrite in the previous commit. Deletes the
runtime disambiguation logic the old shape required and replaces it
with direct alias-based package lookup.

Server (apps/registry/server/api/registry/[...slug].get.ts):
- Drop disambiguateStrategies, the local slugifyPackageName copy, and
  the `npmStrategyCount > 1` monorepo heuristic
- Direct owner/repo lookup now returns 409 Conflict for monorepo entries
  so callers must disambiguate via an ecosystem alias
- Alias lookup uses findPackageByAlias to return the one package that
  declared the alias; resolvedName is slugified from pkg.name for
  monorepo entries
- New response shape: { entry-metadata, resolvedName, package, sources }

CLI (packages/cli/src/registry.ts, index.ts):
- Remove selectBestStrategy, SOURCE_PRIORITY, isCuratedNpm, the
  expandStrategies re-export, and the RegistryApiResponse = RegistryEntry
  alias in favor of an explicit shape matching the server
- resolveFromRegistry returns `{ ecosystem, name, version, source }`
  taking `sources[0]` in declaration order — source priority is now
  author-decided, not client-reordered
- New sourceConfigFromRegistrySource helper adapts the registry's
  RegistrySource discriminated union into the internal SourceConfig
- github fast-path docsPath enrichment now reads the returned package's
  github source `path` (single-package entries only; 409 from monorepo
  entries falls through to no enrichment)

Tests:
- packages/cli/test/registry-schema.test.ts removed (expandStrategies
  no longer exists)
- packages/cli/test/registry.test.ts drops the selectBestStrategy block
  with an explanatory note; parseEcosystem/parseDocSpec coverage kept
Mechanical rewrite of every entry under apps/registry/content/registry/
to match the schema adopted in the previous two commits. No entries
were added or removed.

Transformations applied per entry:
- Top-level `strategies`/`aliases`/`docsPath` moved into `packages[]`
- Field renames: `source: X` → `type: X`, `docsPath` → `path` (on the
  source), top-level `docsPath` inlined into the derived github source
- Monorepo detection: entries with multiple aliases AND at least one
  npm strategy are split into one package per alias, each receiving
  its matching npm strategy first and the shared non-npm strategies
  (e.g. github fallback) behind it. mastra-ai/mastra is the primary
  beneficiary — `@mastra/core` and `@mastra/memory` now live in
  distinct packages instead of sharing one strategies array
- Single-package entries: sources mirror the old strategies in the
  author's declared order; entries without explicit strategies get a
  single github source derived from `repo` (+ optional `docsPath`)

All 50 entries pass the new schema validation (Nuxt Content build
succeeds, registry bundle regenerates cleanly).
…g to null

`fetchRegistryEntry` used to short-circuit every non-2xx response to
`null`, which was correct for 404 (entry not found) but hid the
registry server's deliberate 409 Conflict response on direct
`owner/repo` lookups of monorepo entries. The 409 carries an
actionable `statusMessage` telling the user to disambiguate via an
ecosystem alias (e.g. `npm:@mastra/core` instead of
`mastra-ai/mastra`) — that message was being silently dropped, and
the caller's github fast-path fell through to downloading the whole
monorepo archive.

Split the branches:
- 404 still maps to `null` (legitimate miss, caller tries other paths)
- other non-OK statuses log a warning via consola and still return
  `null`, but the user now sees the server's message so they can
  correct the command

Found by silent-failure-hunter review of PR #43.
Two schema-level invariants surfaced by the type-analyzer review of
PR #43. Both close narrow but genuine footguns by failing parse early
instead of silently picking one interpretation at runtime.

1. GithubSource: `branch` and `tag` are now mutually exclusive. The
   prose comment previously documented that `tag` takes precedence,
   which meant a caller-supplied branch was silently dropped whenever
   both were set. Implemented as a superRefine on the sourceSchema
   discriminated union (refining inside the variant breaks
   z.discriminatedUnion which requires raw ZodObject options).

2. registryEntrySchema: reject package names that slugify to the
   same directory. Different canonical names can collide after
   `slugifyPackageName` (e.g. `@a/b-c` and `a-b-c` both → `a-b-c`),
   which would cause `.ask/docs/<slug>@<ver>/` directory clashes on
   the CLI side. The existing `packages[].name` uniqueness check did
   not catch this because the names themselves differ. Added a new
   slug-uniqueness pass alongside the alias and name checks.

Two new tests cover the branch/tag rejection and the slug collision
case. All 32 schema tests and 242 CLI tests still pass.
@amondnet
amondnet force-pushed the refactor/registry-entry-package-source-schema branch from 6b37e99 to c562fdb Compare April 9, 2026 07:24
@codecov

codecov Bot commented Apr 9, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@amondnet
amondnet merged commit 9da66eb into main Apr 9, 2026
4 checks passed
@amondnet
amondnet deleted the refactor/registry-entry-package-source-schema branch April 9, 2026 07:29
This was referenced Apr 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant