feat(core): extract config into opt-in middleware with layered resolution#155
feat(core): extract config into opt-in middleware with layered resolution#155zrosenbauer wants to merge 1 commit intomainfrom
Conversation
…tion (#154) Move config loading out of the core runtime into `@kidd-cli/core/config` middleware. Config is no longer baked into CommandContext — it is added via module augmentation when the middleware is imported. The new middleware supports: - Single-mode: loads config from cwd (existing behavior) - Layered mode: global > project > local resolution with deep merge Co-Authored-By: Claude <noreply@anthropic.com>
🦋 Changeset detectedLatest commit: 7f264f6 The changes in this PR will be included in the next version bump. This PR includes changesets to release 2 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
Merging this PR will not alter performance
Comparing Footnotes
|
📝 WalkthroughWalkthroughThis PR refactors the CLI's configuration system from a built-in, upfront-loading model to a middleware-based approach. The Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 6
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
packages/core/src/test/types.ts (1)
13-24:⚠️ Potential issue | 🟠 Major
createTestContext()/runHandler()no longer have a safe path for config-aware handlers.After importing
@kidd-cli/core/config,CommandContextis augmented withconfig, but Lines 13-24 and 80-83 no longer let tests seed that field.packages/core/src/test/context.ts, Lines 24-49, still builds the context throughcreateContext(), andpackages/core/src/context/create-context.ts, Lines 15-51, do not attachconfig. That means handler tests can typecheck againstctx.configand then readundefinedat runtime. Please keep a config/decorated override path or add a middleware-aware test helper before removing the old config option.Also applies to: 31-45, 80-83
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/core/src/test/types.ts` around lines 13 - 24, The TestContextOptions interface (in types.ts) and the test helpers createTestContext() / runHandler() lack a way to seed a config property, so tests that import `@kidd-cli/core/config` get ctx.config === undefined at runtime; update TestContextOptions to include an optional readonly config?: ConfigType (or a generic decorated field), and adjust createTestContext(), runHandler(), and the test context builder in packages/core/src/test/context.ts to pass that config value through into the object created by createContext() (or add a middleware-aware override that attaches config before returning the context) so handlers can safely read ctx.config during tests; reference the symbols TestContextOptions, createTestContext, runHandler, and createContext when making the change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/core/src/middleware/config/config.ts`:
- Around line 24-25: The code returns early in the "no configs found" and
"merged-validation failure" paths without populating the layered-mode provenance
field, so ensure ctx.raw.configLayers is always assigned before any early
return: set ctx.raw.configLayers to the appropriate metadata (e.g., an empty
array or the partial layer list you computed) in the "no configs found" path and
right before returning on merged-validation failure so callers still receive
provenance; update the same behavior wherever configLayers may be skipped (the
other early-return branches that check layered mode) and keep the symbol
ctx.raw.configLayers as the single source of truth.
- Around line 65-69: The current checks conflate loadError with “no config” and
silently return an empty config; instead, change the logic around client.load()
so that loadError (or any validation/merged-schema error) is surfaced (throw or
propagate the error) rather than falling back to Object.freeze({}), and only
fall back to an empty config when there is no error and the result is genuinely
absent. Update the branches that use the pattern (loadError || !result) —
including the places that call client.load(), inspect loadError and result
separately, throw or pass the load/validation error up (or call the existing
error-handling path), and only call decorateContext(ctx, 'config',
Object.freeze({})) when result is undefined/null with no loadError.
- Around line 129-135: The code tries to defineProperty('configLayers') on a
frozen ctx.raw (created by createContext()), causing a TypeError; fix by either
adding configLayers to ctx.raw before createContext() freezes it, or stop
mutating ctx.raw and instead attach the metadata to an extensible object (for
example ctx.configLayers or ctx._configLayers) so you do not call
Object.defineProperty on a non-extensible object; update the logic around
createContext()/ctx.raw and the property access sites to read from the new
location (ctx.configLayers or the pre-freeze assignment) so JSDoc promise is
satisfied.
In `@packages/core/src/middleware/config/types.ts`:
- Line 36: Replace the inline import('zod').ZodTypeAny usage with a proper
top-level type import: add "import type { ZodTypeAny } from 'zod';" at the top
of the file and change the generic constraint in ConfigMiddlewareOptions<TSchema
extends import('zod').ZodTypeAny> to ConfigMiddlewareOptions<TSchema extends
ZodTypeAny>; also update any other occurrences in this file that use
import('zod').ZodTypeAny (the two additional spots flagged) to use the imported
ZodTypeAny instead.
- Around line 69-79: The current declaration exposes CommandContext.config as
DeepReadonly<Record<string, unknown>> which conflicts with consumer
redeclarations using ConfigType<typeof configSchema>; change the exported
augmentation surface to a registry pattern instead: add and export an interface
named ConfigContextRegistry (or similar) in the config types module and update
the CommandContext declaration to type config as
ConfigContextRegistry['config']; update the docs to instruct consumers to
augment ConfigContextRegistry (using ConfigType<typeof configSchema>) rather
than redeclaring CommandContext.config so consumer-specific typed configs will
merge cleanly with the library declaration (refer to symbols: CommandContext,
ConfigType, ConfigContextRegistry, configSchema).
In `@packages/core/src/types/utility.ts`:
- Around line 59-61: The inline import in the InferSchema conditional type
should be replaced with a type-only import to satisfy consistent-type-imports:
add a top-level type-only import "import type { ZodType } from 'zod';" and
update the conditional to use ZodType directly (i.e. change "TSchema extends
import('zod').ZodType<infer TOutput>" to "TSchema extends ZodType<infer
TOutput>"). Keep the fallback AnyRecord and ensure the import is type-only so no
runtime require is introduced.
---
Outside diff comments:
In `@packages/core/src/test/types.ts`:
- Around line 13-24: The TestContextOptions interface (in types.ts) and the test
helpers createTestContext() / runHandler() lack a way to seed a config property,
so tests that import `@kidd-cli/core/config` get ctx.config === undefined at
runtime; update TestContextOptions to include an optional readonly config?:
ConfigType (or a generic decorated field), and adjust createTestContext(),
runHandler(), and the test context builder in packages/core/src/test/context.ts
to pass that config value through into the object created by createContext() (or
add a middleware-aware override that attaches config before returning the
context) so handlers can safely read ctx.config during tests; reference the
symbols TestContextOptions, createTestContext, runHandler, and createContext
when making the change.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: 7ca997a0-39be-4381-a43e-05cc9a927c73
⛔ Files ignored due to path filters (1)
.changeset/config-middleware.mdis excluded by!.changeset/**
📒 Files selected for processing (25)
packages/core/package.jsonpackages/core/src/cli.test.tspackages/core/src/cli.tspackages/core/src/command.tspackages/core/src/context/create-context.test.tspackages/core/src/context/create-context.tspackages/core/src/context/types.tspackages/core/src/index.tspackages/core/src/middleware/config/config.tspackages/core/src/middleware/config/index.tspackages/core/src/middleware/config/types.tspackages/core/src/runtime/runtime.test.tspackages/core/src/runtime/runtime.tspackages/core/src/runtime/types.tspackages/core/src/test/command.tspackages/core/src/test/context.test.tspackages/core/src/test/context.tspackages/core/src/test/handler.tspackages/core/src/test/middleware.tspackages/core/src/test/types.tspackages/core/src/types/cli.tspackages/core/src/types/command.tspackages/core/src/types/index.tspackages/core/src/types/utility.tspackages/core/tsdown.config.ts
💤 Files with no reviewable changes (6)
- packages/core/src/cli.test.ts
- packages/core/src/index.ts
- packages/core/src/test/context.test.ts
- packages/core/src/test/command.ts
- packages/core/src/types/index.ts
- packages/core/src/runtime/runtime.test.ts
| * When layered mode is enabled, per-layer metadata is available on `ctx.raw.configLayers`. | ||
| * |
There was a problem hiding this comment.
configLayers is skipped on the early-return paths.
The JSDoc says layered mode exposes ctx.raw.configLayers, but both the “no configs found” path and the merged-validation failure path return before that metadata is assigned. Consumers lose provenance exactly when they need it most.
Also applies to: 106-109, 122-124
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/core/src/middleware/config/config.ts` around lines 24 - 25, The code
returns early in the "no configs found" and "merged-validation failure" paths
without populating the layered-mode provenance field, so ensure
ctx.raw.configLayers is always assigned before any early return: set
ctx.raw.configLayers to the appropriate metadata (e.g., an empty array or the
partial layer list you computed) in the "no configs found" path and right before
returning on merged-validation failure so callers still receive provenance;
update the same behavior wherever configLayers may be skipped (the other
early-return branches that check layered mode) and keep the symbol
ctx.raw.configLayers as the single source of truth.
| const [loadError, result] = await client.load() | ||
|
|
||
| if (loadError || !result) { | ||
| decorateContext(ctx, 'config', Object.freeze({})) | ||
| return |
There was a problem hiding this comment.
Load/validation failures are being treated as “no config found.”
These branches conflate loadError with !result, and merged-schema failures also fall back to {}. That silently ignores malformed config files instead of surfacing them, and in layered mode it can drop a broken higher-precedence layer from the merge with no user-visible failure.
Also applies to: 116-124, 150-154
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/core/src/middleware/config/config.ts` around lines 65 - 69, The
current checks conflate loadError with “no config” and silently return an empty
config; instead, change the logic around client.load() so that loadError (or any
validation/merged-schema error) is surfaced (throw or propagate the error)
rather than falling back to Object.freeze({}), and only fall back to an empty
config when there is no error and the result is genuinely absent. Update the
branches that use the pattern (loadError || !result) — including the places that
call client.load(), inspect loadError and result separately, throw or pass the
load/validation error up (or call the existing error-handling path), and only
call decorateContext(ctx, 'config', Object.freeze({})) when result is
undefined/null with no loadError.
| // Attach layer metadata to raw — use defineProperty to extend the frozen raw object | ||
| Object.defineProperty(ctx.raw, 'configLayers', { | ||
| configurable: false, | ||
| enumerable: true, | ||
| value: Object.freeze(layerResults), | ||
| writable: false, | ||
| }) |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
fd config.ts | grep -E "packages/core"Repository: joggrdocs/kidd
Length of output: 165
🏁 Script executed:
fd create-context.ts | grep -E "packages/core"Repository: joggrdocs/kidd
Length of output: 101
🏁 Script executed:
cat -n packages/core/src/middleware/config/config.ts | sed -n '120,145p'Repository: joggrdocs/kidd
Length of output: 907
🏁 Script executed:
cat -n packages/core/src/context/create-context.tsRepository: joggrdocs/kidd
Length of output: 6388
🏁 Script executed:
rg "configLayers" packages/core/src --context 3Repository: joggrdocs/kidd
Length of output: 1421
🏁 Script executed:
cat -n packages/core/src/middleware/config/config.ts | sed -n '80,136p'Repository: joggrdocs/kidd
Length of output: 2371
🏁 Script executed:
python3 <<'EOF'
# Verify JavaScript behavior: Object.defineProperty on frozen object
import subprocess
result = subprocess.run(['node', '-e', '''
const obj = Object.freeze({ argv: [] });
try {
Object.defineProperty(obj, "newProp", {
value: 123,
writable: false,
enumerable: true,
configurable: false
});
console.log("SUCCESS: property added");
} catch (e) {
console.log("ERROR:", e.message);
}
'''], capture_output=True, text=True)
print(result.stdout)
print(result.stderr)
EOFRepository: joggrdocs/kidd
Length of output: 123
Object.defineProperty() cannot add configLayers to the frozen ctx.raw object.
createContext() freezes ctx.raw at line 107, making it non-extensible. Calling Object.defineProperty() at line 130 throws TypeError: Cannot define property configLayers, object is not extensible when valid layered config is found. The JSDoc at line 9 promises ctx.raw.configLayers in layered mode, but this implementation cannot fulfill it. Either populate the property before freezing raw, or attach the metadata to an extensible surface.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/core/src/middleware/config/config.ts` around lines 129 - 135, The
code tries to defineProperty('configLayers') on a frozen ctx.raw (created by
createContext()), causing a TypeError; fix by either adding configLayers to
ctx.raw before createContext() freezes it, or stop mutating ctx.raw and instead
attach the metadata to an extensible object (for example ctx.configLayers or
ctx._configLayers) so you do not call Object.defineProperty on a non-extensible
object; update the logic around createContext()/ctx.raw and the property access
sites to read from the new location (ctx.configLayers or the pre-freeze
assignment) so JSDoc promise is satisfied.
| * | ||
| * @typeParam TSchema - Zod schema type used to validate the loaded config. | ||
| */ | ||
| export interface ConfigMiddlewareOptions<TSchema extends import('zod').ZodTypeAny> { |
There was a problem hiding this comment.
Use a proper type import for Zod here.
Lines 36, 81, and 82 currently fail the Code Standards check because consistent-type-imports forbids import() type annotations in this repo.
Suggested fix
+import type * as z from 'zod'
+
import type { ConfigFormat } from '@/lib/config/types.js'
import type { DeepReadonly } from '@/types/index.js'
@@
-export interface ConfigMiddlewareOptions<TSchema extends import('zod').ZodTypeAny> {
+export interface ConfigMiddlewareOptions<TSchema extends z.ZodTypeAny> {
@@
-export type ConfigType<TSchema extends import('zod').ZodType> = DeepReadonly<
- import('zod').infer<TSchema>
->
+export type ConfigType<TSchema extends z.ZodType> = DeepReadonly<z.infer<TSchema>>Also applies to: 81-82
🧰 Tools
🪛 GitHub Check: Code Standards & Tests
[failure] 36-36: typescript-eslint(consistent-type-imports)
import() type annotations are forbidden.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/core/src/middleware/config/types.ts` at line 36, Replace the inline
import('zod').ZodTypeAny usage with a proper top-level type import: add "import
type { ZodTypeAny } from 'zod';" at the top of the file and change the generic
constraint in ConfigMiddlewareOptions<TSchema extends import('zod').ZodTypeAny>
to ConfigMiddlewareOptions<TSchema extends ZodTypeAny>; also update any other
occurrences in this file that use import('zod').ZodTypeAny (the two additional
spots flagged) to use the imported ZodTypeAny instead.
| * Use this in a `declare module` block to type `ctx.config`: | ||
| * | ||
| * ```ts | ||
| * import type { ConfigType } from '@kidd-cli/core/config' | ||
| * | ||
| * declare module '@kidd-cli/core' { | ||
| * interface CommandContext { | ||
| * readonly config: ConfigType<typeof configSchema> | ||
| * } | ||
| * } | ||
| * ``` |
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
In TypeScript declaration merging, can two merged interfaces declare the same non-function property name with different but compatible types, or must the property types be identical?
💡 Result:
No, in TypeScript declaration merging, two merged interfaces cannot declare the same non-function property name with different types, even if compatible. The types must be identical, or the compiler issues an error.
Citations:
🏁 Script executed:
cat -n packages/core/src/middleware/config/types.ts | head -100Repository: joggrdocs/kidd
Length of output: 4021
🏁 Script executed:
rg -i "contextregistry|ConfigContextRegistry" --type ts --type tsxRepository: joggrdocs/kidd
Length of output: 85
🏁 Script executed:
rg -i "contextregistry|ConfigContextRegistry" -t tsRepository: joggrdocs/kidd
Length of output: 40
🏁 Script executed:
rg "declare module '@kidd-cli/core'" -A 5 -t tsRepository: joggrdocs/kidd
Length of output: 3702
🏁 Script executed:
rg "CommandContext" -A 3 examples/advanced/src/index.ts -t tsRepository: joggrdocs/kidd
Length of output: 120
🏁 Script executed:
cat -n examples/advanced/src/index.ts | grep -A 10 -B 10 "CliConfig"Repository: joggrdocs/kidd
Length of output: 625
🏁 Script executed:
rg "interface CliConfig|export interface CliConfig|type CliConfig" -t tsRepository: joggrdocs/kidd
Length of output: 153
🏁 Script executed:
rg "interface CliConfig|export interface CliConfig" packages/core -t tsRepository: joggrdocs/kidd
Length of output: 40
🏁 Script executed:
rg "CliConfig" packages/core/src/context -t ts -B 2 -A 2Repository: joggrdocs/kidd
Length of output: 40
🏁 Script executed:
rg "export interface CliConfig|declare.*CliConfig" packages/core/src -t tsRepository: joggrdocs/kidd
Length of output: 40
🏁 Script executed:
cat -n packages/core/src/context/types.ts | head -80Repository: joggrdocs/kidd
Length of output: 2916
🏁 Script executed:
cat -n packages/core/src/context/types.ts | sed -n '100,150p'Repository: joggrdocs/kidd
Length of output: 1483
🏁 Script executed:
cat -n packages/core/src/context/types.ts | tail -100Repository: joggrdocs/kidd
Length of output: 3962
Documentation augmentation path conflicts with actual declaration type.
Lines 71-79 instruct consumers to redeclare CommandContext.config using ConfigType<typeof schema>, but lines 89-96 declare the property as DeepReadonly<Record<string, unknown>>. TypeScript declaration merging requires identical property types. Following the documented path will fail with a type error because ConfigType<TSchema> produces specific typed records (e.g. DeepReadonly<{name: string}>) which do not match the generic DeepReadonly<Record<string, unknown>> declaration.
The suggested registry pattern fixes this by making the declaration type flexible:
+export interface ConfigContextRegistry {}
+
+export type ResolvedConfig =
+ keyof ConfigContextRegistry extends never
+ ? DeepReadonly<Record<string, unknown>>
+ : ConfigContextRegistry[keyof ConfigContextRegistry]
+
declare module '@kidd-cli/core' {
interface CommandContext {
- readonly config: DeepReadonly<Record<string, unknown>>
+ readonly config: ResolvedConfig
}
}Consumers then augment ConfigContextRegistry from @kidd-cli/core/config instead of redeclaring CommandContext.config, making the documented pattern actually work.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/core/src/middleware/config/types.ts` around lines 69 - 79, The
current declaration exposes CommandContext.config as DeepReadonly<Record<string,
unknown>> which conflicts with consumer redeclarations using ConfigType<typeof
configSchema>; change the exported augmentation surface to a registry pattern
instead: add and export an interface named ConfigContextRegistry (or similar) in
the config types module and update the CommandContext declaration to type config
as ConfigContextRegistry['config']; update the docs to instruct consumers to
augment ConfigContextRegistry (using ConfigType<typeof configSchema>) rather
than redeclaring CommandContext.config so consumer-specific typed configs will
merge cleanly with the library declaration (refer to symbols: CommandContext,
ConfigType, ConfigContextRegistry, configSchema).
| export type InferSchema<TSchema> = TSchema extends import('zod').ZodType<infer TOutput> | ||
| ? TOutput | ||
| : AnyRecord |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
cat -n packages/core/src/types/utility.ts | sed -n '50,70p'Repository: joggrdocs/kidd
Length of output: 482
🏁 Script executed:
head -70 packages/core/src/types/utility.ts | tail -20Repository: joggrdocs/kidd
Length of output: 620
🏁 Script executed:
rg "import.*zod" packages/core/src/types/utility.ts -iRepository: joggrdocs/kidd
Length of output: 145
🏁 Script executed:
head -30 packages/core/src/types/utility.tsRepository: joggrdocs/kidd
Length of output: 1099
🏁 Script executed:
rg "import.*from.*zod" packages/core/src/types/utility.tsRepository: joggrdocs/kidd
Length of output: 40
🏁 Script executed:
rg "from\s+['\"]zod['\"]" packages/core/src --type ts -A 2 | head -30Repository: joggrdocs/kidd
Length of output: 1826
🏁 Script executed:
rg "ZodType" packages/core/src --type ts | head -10Repository: joggrdocs/kidd
Length of output: 976
🏁 Script executed:
# Check if ZodType is exported from zod
npm list zod 2>/dev/null || echo "checking package.json for zod version"Repository: joggrdocs/kidd
Length of output: 143
🏁 Script executed:
# Verify the eslint rule configuration
cat -n .eslintrc.json 2>/dev/null || cat -n .eslintrc.js 2>/dev/null || echo "no eslint config found"Repository: joggrdocs/kidd
Length of output: 80
🏁 Script executed:
# Check if there's a tsconfig for consistent-type-imports
find . -name "tsconfig.json" -o -name ".eslintrc*" | head -5 | xargs grep -l "consistent-type-imports" 2>/dev/null || echo "Searching for rule config"Repository: joggrdocs/kidd
Length of output: 83
🏁 Script executed:
rg "consistent-type-imports" . --type json --type js | head -5Repository: joggrdocs/kidd
Length of output: 125
🏁 Script executed:
cat .oxlintrc.jsonRepository: joggrdocs/kidd
Length of output: 4233
🏁 Script executed:
# Check if ZodType exists and can be imported
rg "ZodType" packages/core/src/stories/schema.ts -B 2 -A 2 | head -20Repository: joggrdocs/kidd
Length of output: 763
🏁 Script executed:
# Verify the exact nature of the rule violation by checking TypeScript documentation
python3 << 'EOF'
# The consistent-type-imports rule flags:
# 1. import() in type position without 'type' keyword - these should be type-only imports
# 2. Mixing type and value imports from same module
print("Rule analysis:")
print("- import('zod').ZodType in conditional type (type position)")
print("- This should be flagged by consistent-type-imports rule")
print("- Solution: Add 'import type { ZodType } from \"zod\"'")
print("- Then use 'ZodType' directly in the extends clause")
EOFRepository: joggrdocs/kidd
Length of output: 294
Replace inline import('zod') with a type-only import.
Line 59 violates typescript/consistent-type-imports (error-level rule in .oxlintrc.json). Use a static type-only import and reference ZodType directly.
Fix
+import type { ZodType } from 'zod'
+
// ---------------------------------------------------------------------------
// Generic type utilities
// ---------------------------------------------------------------------------
@@
-export type InferSchema<TSchema> = TSchema extends import('zod').ZodType<infer TOutput>
+export type InferSchema<TSchema> = TSchema extends ZodType<infer TOutput>
? TOutput
: AnyRecord🧰 Tools
🪛 GitHub Check: Code Standards & Tests
[failure] 59-59: typescript-eslint(consistent-type-imports)
import() type annotations are forbidden.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/core/src/types/utility.ts` around lines 59 - 61, The inline import
in the InferSchema conditional type should be replaced with a type-only import
to satisfy consistent-type-imports: add a top-level type-only import "import
type { ZodType } from 'zod';" and update the conditional to use ZodType directly
(i.e. change "TSchema extends import('zod').ZodType<infer TOutput>" to "TSchema
extends ZodType<infer TOutput>"). Keep the fallback AnyRecord and ensure the
import is type-only so no runtime require is introduced.
Summary
Closes #154
@kidd-cli/core/config)ctx.config,CliConfig,CliConfigOptions,ConfigType, andTConfiggeneric from core typesconfig()middleware factory with single-mode (cwd) and layered-mode (global > project > local deep merge)ctx.configonly when middleware is imported — keeping builds lean for CLIs that don't need config./configentrypoint to package.json and tsdown configBreaking change
ctx.configis no longer available by default. CLI authors must use the config middleware:For layered resolution (global > project > local):
Test plan
pnpm typecheckpasses@kidd-cli/cli(downstream) typechecks clean@kidd-cli/coredoes NOT pull in c12