refactor(registry)!: restructure entries as Entry → Package → Source (ADR-0001)#43
Merged
Merged
Conversation
Deploying ask-registry with
|
| 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 |
Contributor
There was a problem hiding this comment.
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}/
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
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.
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
force-pushed
the
refactor/registry-entry-package-source-schema
branch
from
April 9, 2026 07:24
6b37e99 to
c562fdb
Compare
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
This was referenced Apr 9, 2026
Merged
Merged
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
The old registry schema used a flat
strategiesarray 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:
The shape is defined in ADR-0001 (
.please/docs/decisions/0001-registry-entry-schema-entry-package-source.md).Changes
packages/schema): Restructure registry entry types —RegistryEntry,RegistryPackage,RegistrySource; remove legacyRegistryStrategy,expandStrategies, top-levelstrategies/aliases/docsPathapps/registry,packages/cli): Adopt new types in the Nuxt Content server routes (/api/registry/<slug>) and CLI registry resolver (selectBestStrategy)Breaking Changes
RegistryStrategytypeRegistrySourceexpandStrategies()helperentry.packages[].sources[]traversalstrategiesfield in entriespackages[].sources[]aliasesfieldentry.aliasesdocsPathfieldpackages[].docsPath/api/registry/<slug>previous response shapepackagesarrayConsumers calling
/api/registry/<slug>must update to readpackages[0].sourcesinstead ofstrategies.Validation
bun run build— 3/3 packages build successfullyADR Reference
.please/docs/decisions/0001-registry-entry-schema-entry-package-source.mdTest Plan
bun run buildpasses with no type errors across all packagesGET /api/registry/facebook/reactreturns newpackagesarray shapeGET /api/registry/npm/react(alias lookup) resolves correctlyask docs add npm:reactresolves via new registry response shapeask docs add vercel/aifalls back correctly usingpackages[0].sourcesapps/registry/) renders entries without errorsSummary 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/registryresponse.Refactors
packages/schema: addRegistryPackage/RegistrySource; dropRegistryStrategy/expandStrategies; addfindPackageByAlias,isMonorepoEntry,slugifyPackageName; validate duplicate aliases/package names and slug collisions; reject GitHubbranch+tag; rename fieldssource→type,docsPath→path.apps/registry:/api/registry/<slug>now returns{ name, description, repo, resolvedName, package, sources };owner/repoon monorepos returns 409 with alias guidance; deletesdisambiguateStrategiesand monorepo heuristics.packages/cli: remove client-side strategy selection; takesources[0]in declared order; addsourceConfigFromRegistrySource;ask docs add owner/repouses returned GitHub sourcepathwhen available; surface 409 Conflict messages to guide alias-based disambiguation.packages[].sources[]with field renames; ADR-0001 added under.please/docs/decisions/.Migration
strategies/aliaseswithpackages[].aliasesandpackages[].sources; update fieldssource→typeanddocsPath→path.strategies; readsourcesandresolvedName; for monorepos, look up via alias/<ecosystem>/<name>(e.g.,npm/@mastra/core) instead ofowner/repo.RegistryStrategy,expandStrategies, or client-side reordering; trustsourcesdeclaration order for primary and fallbacks.Written for commit c562fdb. Summary will update on new commits.