Replace schema DSL with zod-based validation#19
Conversation
Replace all hand-rolled validation and the homegrown schema-dsl with zod as the single schema/validation substrate. The public builder API (flag/arg/ command) and its TypeScript inference are unchanged — only the runtime validation engine is swapped. - Add zod as the sole runtime dependency (drop the zero-dep policy in AGENTS.md/GOALS.md/README.md). - New src/core/schema/zod-kinds.ts derives the declared-type zod schema for a flag/arg on demand (flagZod/argZod/buildZodSchema); not stored on the descriptor, so schemas stay plain, comparable, JSON-serialisable objects. - resolve/coerce.ts and parse/index.ts now drive string/number/enum accept decisions through zod (safeParse); all ValidationError/ParseError messages, codes, details, and suggestions are preserved byte-for-byte. Custom parseFn still runs directly (not wrapped in zod). - json-schema: generateInputSchema and definitionMetaSchema now built via z.toJSONSchema (registry + post-processor for $defs/$ref and conventions); output is byte-compatible with prior assertions. - Delete src/core/schema-dsl/ entirely (tokenizer/parser/AST/validator/ json-schema emitter); json-schema was its only consumer. - config/package.json validation now uses zod, preserving exact failure semantics (return-null vs CONFIG_PARSE_ERROR wrapping). - Consolidate duplicated isRecord/isPlainObject guards into src/core/internal/guards.ts. Verified: tsgo typecheck, biome lint, dprint format, meta-descriptions:check, 2205 vitest tests, tsdown build + attw + publint all green.
Address findings from a Zod-4 best-practices review of the migration: - packaging: move zod from `dependencies` to `peerDependencies` + a `devDependency`, and widen the range to `^4.0.0` (the `^3.25 || ^4` dual range is impossible — z.toJSONSchema/z.registry are v4-only). Consumers now dedupe a single zod copy; publint/attw verified. - json-schema: stop the unconditional `anyOf`→`oneOf` rewrite in normalizeDefFragment (anyOf is correct for disjoint unions; oneOf was a latent semantic-narrowing bug). Drop z.int safe-integer bounds via the z.toJSONSchema `override` hook instead of matching hard-coded sentinels. - parse: return raw directly for the string kind (argv values are already strings) — removes a no-op `.parse()` that was the only throwing call with no ParseError catch net (latent raw-ZodError leak). - package-json: replace `z.custom(isRecord).refine(...)` bin validation with a native `z.record(z.string(), z.string())`. - style: `import * as z from 'zod'` across all zod consumers. - tests: pin the `#/$defs/<name>` cross-reference shape and the anyOf/integer conventions in the definition meta-schema. Verified: typecheck, lint, format, meta-descriptions:check, 2207 tests, tsdown build + attw + publint all green.
|
Important Review skippedAuto reviews are limited based on label configuration. 🚫 Excluded labels (none allowed) (2)
Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Repository YAML (base), Organization UI (inherited) Review profile: ASSERTIVE Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
🚥 Pre-merge checks | ✅ 8 | ❌ 3❌ Failed checks (3 warnings)
✅ Passed checks (8 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches✨ Simplify code
Comment |
commit: |
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub.
|
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
src/core/resolve/coerce.ts (1)
168-189:⚠️ Potential issue | 🟠 Major | ⚡ Quick winKeep config string flags strict; this now swallows bad config.
Arrr — stringifying config
number/booleanvalues means{ region: false }quietly resolves as'false', whilesrc/core/json-schema/index.tsstill publishes{ type: 'string' }for that same field. That breaks the advertised config-schema contract and turns obvious config mistakes into silent coercions.💡 Minimal fix
case 'string': { - const normalized = - source.kind === 'prompt' || - (source.kind === 'config' && (typeof raw === 'number' || typeof raw === 'boolean')) - ? String(raw) - : raw; + const normalized = source.kind === 'prompt' ? String(raw) : raw; const parsed = buildZodSchema('string').safeParse(normalized); if (parsed.success) return { ok: true, value: parsed.data };🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/core/resolve/coerce.ts` around lines 168 - 189, The current string coercion in src/core/resolve/coerce.ts (inside the case 'string' block) wrongly stringifies config values, so change the normalization logic to only stringify when source.kind === 'prompt' (do not stringify when source.kind === 'config'); leave buildZodSchema('string').safeParse(normalized) and the coercionError path intact so that non-string config values produce a TYPE_MISMATCH error (use the existing coercionError message and source.configPath for guidance).src/core/parse/index.ts (1)
199-220:⚠️ Potential issue | 🟠 Major | ⚡ Quick winReject empty enum definitions here as well.
Arrr — these branches now trust
safeParse()as the whole enum check, butbuildZodSchema('enum', { enumValues: [] })falls back toz.string(). That means a misconfigured enum withenumValues: []will happily accept any argv value instead of failing fast.src/core/resolve/coerce.tsstill keeps an explicit membership gate; the parser needs the same protection.💡 Minimal fix
case 'enum': { const allowed = schema.enumValues; - if (allowed === undefined) { + if (allowed === undefined || allowed.length === 0) { throw new ParseError( `Enum flag --${flagName} is misconfigured: no allowed values declared`, { code: 'INVALID_SCHEMA', details: { flag: flagName, kind: 'enum', missing: 'enumValues' }, }, ); } const result = buildZodSchema('enum', { enumValues: allowed }).safeParse(raw); if (!result.success) { throw new ParseError( `Invalid value '${raw}' for flag ${displayName}. Allowed: ${allowed.join(', ')}`, @@ case 'enum': { const allowed = schema.enumValues; - if (allowed === undefined) { + if (allowed === undefined || allowed.length === 0) { throw new ParseError( `Enum argument <${argName}> is misconfigured: no allowed values declared`, { code: 'INVALID_SCHEMA', details: { arg: argName, kind: 'enum', missing: 'enumValues' }, }, ); } const result = buildZodSchema('enum', { enumValues: allowed }).safeParse(raw);Also applies to: 276-297
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/core/parse/index.ts` around lines 199 - 220, The enum branch in parse/index.ts currently trusts buildZodSchema('enum', { enumValues }) and safeParse, which allows a misconfigured empty enum (enumValues = []) to fall back to z.string() and accept any input; add an explicit check after grabbing const allowed = schema.enumValues to reject empty arrays (allowed.length === 0) by throwing a ParseError (same shape as the missing-case error, code 'INVALID_SCHEMA' with details including flag: flagName, kind: 'enum', missing: 'enumValues') so empty enum definitions fail fast; apply the identical check to the other enum handling block referenced around lines 276-297.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/core/completion/shells/shared.ts`:
- Around line 14-15: The file imports createSchema, CommandSchema, and
FlagSchema from the barrel '`#internals/core/schema/index.ts`'; replace those
barrel imports with direct imports from the modules that define them so the
runtime import for createSchema is no longer through the barrel and the types
CommandSchema and FlagSchema are imported from their specific type-definition
modules; update the import statements in shared.ts accordingly to pull
createSchema from its implementation module and the two types from their exact
declaration modules to avoid barrel-induced cycles.
---
Outside diff comments:
In `@src/core/parse/index.ts`:
- Around line 199-220: The enum branch in parse/index.ts currently trusts
buildZodSchema('enum', { enumValues }) and safeParse, which allows a
misconfigured empty enum (enumValues = []) to fall back to z.string() and accept
any input; add an explicit check after grabbing const allowed =
schema.enumValues to reject empty arrays (allowed.length === 0) by throwing a
ParseError (same shape as the missing-case error, code 'INVALID_SCHEMA' with
details including flag: flagName, kind: 'enum', missing: 'enumValues') so empty
enum definitions fail fast; apply the identical check to the other enum handling
block referenced around lines 276-297.
In `@src/core/resolve/coerce.ts`:
- Around line 168-189: The current string coercion in src/core/resolve/coerce.ts
(inside the case 'string' block) wrongly stringifies config values, so change
the normalization logic to only stringify when source.kind === 'prompt' (do not
stringify when source.kind === 'config'); leave
buildZodSchema('string').safeParse(normalized) and the coercionError path intact
so that non-string config values produce a TYPE_MISMATCH error (use the existing
coercionError message and source.configPath for guidance).
🪄 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: Repository YAML (base), Organization UI (inherited)
Review profile: ASSERTIVE
Plan: Pro
Run ID: 35109c21-ee44-4aca-975c-c1ce82020146
⛔ Files ignored due to path filters (1)
bun.lockis excluded by!**/*.lock
📒 Files selected for processing (20)
AGENTS.mdGOALS.mdREADME.mdpackage.jsonsrc/core/completion/shells/shared.tssrc/core/config/index.tssrc/core/config/package-json.tssrc/core/internal/guards.tssrc/core/json-schema/index.tssrc/core/json-schema/json-schema.test.tssrc/core/parse/index.tssrc/core/resolve/coerce.tssrc/core/resolve/errors.tssrc/core/schema-dsl/AGENTS.mdsrc/core/schema-dsl/index.tssrc/core/schema-dsl/parse.tssrc/core/schema-dsl/runtime.tssrc/core/schema-dsl/schema-dsl.test.tssrc/core/schema-dsl/to-json-schema.tssrc/core/schema/zod-kinds.ts
💤 Files with no reviewable changes (6)
- src/core/schema-dsl/index.ts
- src/core/schema-dsl/AGENTS.md
- src/core/schema-dsl/schema-dsl.test.ts
- src/core/schema-dsl/to-json-schema.ts
- src/core/schema-dsl/runtime.ts
- src/core/schema-dsl/parse.ts
📜 Review details
🧰 Additional context used
📓 Path-based instructions (15)
**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
Use tabs for indentation, single quotes for strings, semicolons at end of statements, and LF for line endings
Use
import typefor type-only imports in TypeScriptInclude
.tsextensions in all relative importsUse strict TypeScript everywhere with
exactOptionalPropertyTypesenabled; use conditional spreads for optional propertiesUse explicit named re-exports only; do not use
export *
Files:
src/core/internal/guards.tssrc/core/json-schema/json-schema.test.tssrc/core/resolve/errors.tssrc/core/completion/shells/shared.tssrc/core/schema/zod-kinds.tssrc/core/config/index.tssrc/core/config/package-json.tssrc/core/parse/index.tssrc/core/resolve/coerce.tssrc/core/json-schema/index.ts
src/**/*.ts
📄 CodeRabbit inference engine (AGENTS.md)
Include
@moduleJSDoc at the top of source files; mark non-public API with@internal
Files:
src/core/internal/guards.tssrc/core/json-schema/json-schema.test.tssrc/core/resolve/errors.tssrc/core/completion/shells/shared.tssrc/core/schema/zod-kinds.tssrc/core/config/index.tssrc/core/config/package-json.tssrc/core/parse/index.tssrc/core/resolve/coerce.tssrc/core/json-schema/index.ts
src/core/**/*.ts
📄 CodeRabbit inference engine (AGENTS.md)
Public API should be factory-first: use
cli(),command(),flag.*(),createOutput(),createAdapter()naming patternsDo not use
process.*or runtime-specific APIs insrc/core/; abstract runtime I/O throughRuntimeAdapter,WriteFn, orReadFn
Files:
src/core/internal/guards.tssrc/core/json-schema/json-schema.test.tssrc/core/resolve/errors.tssrc/core/completion/shells/shared.tssrc/core/schema/zod-kinds.tssrc/core/config/index.tssrc/core/config/package-json.tssrc/core/parse/index.tssrc/core/resolve/coerce.tssrc/core/json-schema/index.ts
**
⚙️ CodeRabbit configuration file
**: # PROJECT KNOWLEDGE BASEGenerated: 2026-04-07 Commit: 1479d26 Branch: master
OVERVIEW
Schema-first, fully typed TypeScript CLI framework. Zero runtime deps. In-repo exports point at
src/*.ts; published Node defaults point atdist/*.mjs, while Bun and Deno keep source exports.Read
@DISCOVERIES.mdbefore planning, editing, or running task workflows.STRUCTURE
src/ ├── index.ts # public package surface ├── runtime.ts # `./runtime` subpath barrel ├── testkit.ts # `./testkit` subpath barrel ├── core/ │ ├── cli/ # top-level CLI orchestration, plugins, root surface │ ├── schema/ # builder DSL, type inference, middleware surface │ ├── resolve/ # argv/env/config/prompt/default precedence │ ├── output/ # stdout/stderr/json/table/activity dispatch │ ├── completion/ # shell completion generators │ ├── json-schema/ # definition schema + input schema generation │ ├── parse/ # tokenizer + schema-aware raw parse │ ├── prompt/ # terminal/test prompt engines │ ├── config/ # config discovery + package.json walk-up │ ├── help/ # schema-driven help formatter │ └── testkit/ # in-process test harness └── runtime/ # Node/Bun/Deno adapters + detection docs/ ├── .vitepress/ # config, data loaders, theme, custom Vite plugins ├── concepts/ # hand-written docs ├── guide/ # hand-written docs ├── examples/ # generated routes backed by `../examples/*.ts` └── reference/ # overview pages + dynamic symbol routes scripts/ # build, release, and project automation examples/ # runnable examples + `examples/gh` workspace canary specs/ # planning/design docsWHERE TO LOOK
| Task | Lo...
Files:
src/core/internal/guards.tsREADME.mdAGENTS.mdGOALS.mdsrc/core/json-schema/json-schema.test.tssrc/core/resolve/errors.tssrc/core/completion/shells/shared.tssrc/core/schema/zod-kinds.tssrc/core/config/index.tssrc/core/config/package-json.tssrc/core/parse/index.tssrc/core/resolve/coerce.tspackage.jsonsrc/core/json-schema/index.ts
src/core/json-schema/**/*.ts
📄 CodeRabbit inference engine (src/core/json-schema/AGENTS.md)
Use
@internalJSDoc tags to mark internal APIs; docs and meta-description tooling filter on them
Files:
src/core/json-schema/json-schema.test.tssrc/core/json-schema/index.ts
**/*.test.ts
📄 CodeRabbit inference engine (AGENTS.md)
Co-locate tests as
*.test.tsfiles; usedescribe()+it(), em dash in suite titles,// ===and// ---section markersDo not use lifecycle hooks, snapshots, or module mocks in tests
Output assertions in tests must include trailing newline character
Files:
src/core/json-schema/json-schema.test.ts
src/core/json-schema/**
⚙️ CodeRabbit configuration file
src/core/json-schema/**: # json-schema — Definition metadata + input schema generationOVERVIEW
Single large module plus one generated companion file. It emits both DreamCLI definition metadata
and draft-2020-12 input schemas, and it bridges the string-literal schema DSL into JSON Schema
output.FILES
File Purpose index.tsgenerateSchema(),generateInputSchema(), serialization helpersmeta-descriptions.generated.tsgenerated descriptions injected into the definition meta-schema json-schema.test.tsbehavior contract for both outputs WHERE TO LOOK
Task Location Notes Change definition schema shape generateSchema()CLI tree, flags, args, examples, hidden and prompt filtering Change input validation schema generateInputSchema()JSON Schema 2020-12 for config/editor use cases Change DSL -> JSON Schema bridge parseSchema(),nodeToJsonSchema()inindex.tsfeeds custom and DSL flag shapes Regenerate descriptions meta-descriptions.generated.ts,../../../scripts/build-meta-descriptions.tsscript is source of truth CONVENTIONS
- Output must stay JSON-ser...
Files:
src/core/json-schema/json-schema.test.tssrc/core/json-schema/index.ts
src/core/{cli,completion,output,prompt,resolve}/**/*.ts
📄 CodeRabbit inference engine (AGENTS.md)
Do not import through barrels in
cli/,completion/,output/,prompt/,resolve/, andruntime/; use direct-file imports to avoid cycles
Files:
src/core/resolve/errors.tssrc/core/completion/shells/shared.tssrc/core/resolve/coerce.ts
src/core/resolve/**
⚙️ CodeRabbit configuration file
src/core/resolve/**: # resolve — Flag/arg value resolution chainMulti-file module (split from monolithic index). 8 source files, ~1333 source lines.
RESOLUTION ORDER
CLI argv -> environment variable -> config file -> interactive prompt -> default valueEach source tried in order; first non-undefined wins. Missing required values with no source trigger
ValidationError.FILES
File Lines Purpose index.ts108 Barrel — re-exports public API flags.ts201 resolveFlags()— all flags: CLI -> env -> config -> prompt -> defaultargs.ts141 resolveArgs()— parsed -> default -> required validationcoerce.ts429 coerceValue()— unified raw value -> flag's declared kindconfig.ts25 resolveConfigPath()— dotted path lookup in config objecterrors.ts228 Error aggregation + throwAggregatedErrors()property.ts87 Property path resolution utilities contracts.ts114 ResolveOptions,CoerceResult,CoerceSourcetypesKEY FUNCTIONS
Function File Role resolve()index.tsMain entry — orchestrates full resolution for a command resolveFlags()flags.tsAll flags: CLI -> env -> config -> prompt -> default resolveArgs()args.tsAll args: parsed -> default -> required val...
Files:
src/core/resolve/errors.tssrc/core/resolve/coerce.ts
src/core/completion/shells/*.ts
📄 CodeRabbit inference engine (src/core/completion/AGENTS.md)
Organize shell completion generators in per-shell modules under
shells/directory, with each shell having its own dedicated generator file (bash.ts,zsh.ts,fish.ts,powershell.ts)
Files:
src/core/completion/shells/shared.ts
src/core/completion/shells/shared.ts
📄 CodeRabbit inference engine (src/core/completion/AGENTS.md)
src/core/completion/shells/shared.ts: Implement shared command tree infrastructure (CommandNode,walkCommandTree(), escaping utilities,versionTag) inshells/shared.ts
ImportcollectPropagatedFlags()directly fromcli/propagate.ts(@internalfile) inshells/shared.tsto handle flag inheritance in nested commands
Files:
src/core/completion/shells/shared.ts
src/core/completion/**
⚙️ CodeRabbit configuration file
src/core/completion/**: # completion — Shell completion script generationSplit into per-shell generators under
shells/. Barrelindex.tsre-exports public API and
dispatches viagenerateCompletion().FILES
File Lines Purpose index.ts88 Barrel — Shelltype,SHELLSconstant, dispatchshells/shared.ts160 CommandNode,walkCommandTree, escaping,versionTagshells/bash.ts275 generateBashCompletion()+ all bash helpersshells/zsh.ts280 generateZshCompletion()+ all zsh helpersshells/fish.ts256 generateFishCompletion()+ fish path scanner helpersshells/powershell.ts438 generatePowerShellCompletion()+ metadata helpersPUBLIC API
Symbol Exported from Role generateCompletion()index.tsShell-agnostic dispatch -> per-shell generators generateBashCompletion()index.tsBash completion script from command tree generateZshCompletion()index.tsZsh completion script from command tree generateFishCompletion()index.tsFish completion script from command tree generatePowerShellCompletion()index.tsPowerShell completion script from command tree SHELLSindex.tsreadonly ['bash', 'zsh', 'fish', 'powershell']CompletionOptionsindex.tsOptions type (re-exported from shared.ts)Shellindex.tsUnion type of supported...
Files:
src/core/completion/shells/shared.ts
src/core/schema/**
⚙️ CodeRabbit configuration file
src/core/schema/**: # schema — Command/Flag/Arg/Middleware builders + prompt config typesMulti-file module in
core/. All others (except resolve, output, completion) use singleindex.ts.FILES
File Lines Purpose command.ts1466 CommandBuilder<F, A, C>— fluent builder +Outinterface + schemaflag.ts753 FlagBuilder—flag.string(),.boolean(),.number(),.count(),.enum(),.custom()arg.ts713 ArgBuilder—arg.string(),.number(),.enum()activity.ts150 Activity types — SpinnerHandle,ProgressHandle,ActivityEvent, etc.middleware.ts164 middleware<Output>(handler)factory — phantom-brandedMiddleware<Output>prompt.ts70 Prompt config types — PromptConfigdiscriminated union (4 kinds)run.ts47 RunResult— structured execution result (re-exported by testkit)index.ts80 Barrel — re-exports all public symbols TYPE SYSTEM PATTERNS
Faccumulator:{} & Record<name, InferFlag<...>>grows per.flag()callAaccumulator: same pattern for.arg()Caccumulator:Record<string, never>replaced entirely on first.middleware(), then
intersection-grown viaWidenContext<C, Output>{}as identity element:biome-ignore noBannedTypesonCommandBuilderclass generic
defaults — justified, do not "fix"- Phantom brand:
Middleware<Output>carries type info at compile time, erased at runtime.
Same for ...
Files:
src/core/schema/zod-kinds.ts
src/core/config/**
⚙️ CodeRabbit configuration file
src/core/config/**: # config — Config discovery + package.json metadata walk-upOVERVIEW
Config loading is adapter-driven and side-effect free from the module's perspective. It covers both
user config discovery andpackageJson()metadata inference.FILES
File Purpose index.tsconfig search paths, loaders, parsing, discovery package-json.tsnearest package.jsonwalk-up + CLI name inferenceconfig.test.tsdiscovery and load behavior package-json.test.tsmetadata inference WHERE TO LOOK
Task Location Notes Change default config locations buildConfigSearchPaths()cwd dotfile, cwd explicit config, platform config dir Change custom format loading FormatLoader,buildLoaderMap()JSON built in; later loaders override earlier ones Change main discovery flow discoverConfig()first found path wins, parses through adapter Change CLI metadata inference discoverPackageJson(),inferCliName()backs CLIBuilder.packageJson()CONVENTIONS
- All I/O flows through a narrow
RuntimeAdaptersurface; keep it testable with virtual filesystems- Config roots must be plain objects even if a loader can parse other JSON or YAML values
- JSON is always built in and ordered first in search-path and loader resolution
package-json.tsis a convenience feature: malformed or missing package metadata returnsnull,
not a hard failure
...
Files:
src/core/config/index.tssrc/core/config/package-json.ts
src/core/parse/**
⚙️ CodeRabbit configuration file
src/core/parse/**: # parse — Tokenizer + schema-aware raw parserOVERVIEW
Single-file parser module. It tokenizes raw argv without schema knowledge, then parses against
CommandSchemainto raw values for the resolve layer.FILES
File Purpose index.tstokenize(),parse(), flag/arg coercion, lookup helpersparse.test.tsparser contract, edge cases, regressions WHERE TO LOOK
Task Location Notes Change raw argv splitting tokenize()--,--flag=value, grouped short flags, lone-Change alias or canonical lookup buildFlagLookup()names and aliases normalize to canonical keys Change parse-time coercion coerceFlagValue(),coerceArgValue()parse-time only, before resolve and defaults Change main parse flow parse()consumes tokens into raw flags and args or throws ParseErrorCONVENTIONS
- Keep tokenizer schema-agnostic; resolve/default/env/config behavior belongs elsewhere
- Single
-is a positional stdin sentinel, not a flag- Boolean explicit values accept only
true/falseand1/0- Parse errors use
ParseErrorwith structured codes and details, not ad hoc stringsANTI-PATTERNS
- Do not read env, config, prompts, or defaults here; that is
resolve/- Do not bypass canonical alias mapping when storing parsed flags
- Do not blur tokenization and parsing responsibilities to simplify a small chang...
Files:
src/core/parse/index.ts
🧠 Learnings (13)
📓 Common learnings
Learnt from: CR
Repo: kjanat/dreamcli
Timestamp: 2026-06-07T21:58:58.006Z
Learning: Do not edit `docs/.vitepress/dist/` or `docs/.vitepress/cache/`; these are generated artifacts
Learnt from: CR
Repo: kjanat/dreamcli
Timestamp: 2026-06-07T21:58:58.006Z
Learning: Do not treat `docs/.vitepress/data/` as docs-only; scripts import it for generated source and docs artifacts
Learnt from: CR
Repo: kjanat/dreamcli
Timestamp: 2026-06-07T21:58:58.006Z
Learning: Do not replace `bun run gh-project:*` with ad hoc GitHub project mutations; use the provided workflow helpers
Learnt from: CR
Repo: kjanat/dreamcli
Timestamp: 2026-06-07T21:58:58.006Z
Learning: Keep dependencies minimal; `zod` is the only peer dependency — justify any further additions
📚 Learning: 2026-02-11T11:57:27.623Z
Learnt from: kjanat
Repo: kjanat/dreamcli PR: 3
File: src/core/cli/cli-completion-e2e.test.ts:9-16
Timestamp: 2026-02-11T11:57:27.623Z
Learning: Enforce explicit .ts extensions in all relative imports for TypeScript source files (enabled by allowImportingTsExtensions and noEmit in tsconfig.json). This applies to both static and dynamic imports. For each TypeScript file, update relative import paths to end with .ts (e.g., './utils/helper' -> './utils/helper.ts'). Ensure tooling (linting/eslint rules or a codemod) flags missing extensions and provides a quick fix. This guideline should cover all TypeScript files in the repo, not just a single test or module.
Applied to files:
src/core/internal/guards.tssrc/core/json-schema/json-schema.test.tssrc/core/resolve/errors.tssrc/core/completion/shells/shared.tssrc/core/schema/zod-kinds.tssrc/core/config/index.tssrc/core/config/package-json.tssrc/core/parse/index.tssrc/core/resolve/coerce.tssrc/core/json-schema/index.ts
📚 Learning: 2026-04-06T11:25:57.444Z
Learnt from: kjanat
Repo: kjanat/dreamcli PR: 9
File: examples/spinner-progress.ts:2-12
Timestamp: 2026-04-06T11:25:57.444Z
Learning: In this repo, enforce that TypeScript source files under `src/` include the required `module` JSDoc tag (where the project’s lint/review rules expect it). Do not flag missing `module` tags in files under `examples/`, since those are documentation/example scripts (e.g., `examples/*.ts`).
Applied to files:
src/core/internal/guards.tssrc/core/json-schema/json-schema.test.tssrc/core/resolve/errors.tssrc/core/completion/shells/shared.tssrc/core/schema/zod-kinds.tssrc/core/config/index.tssrc/core/config/package-json.tssrc/core/parse/index.tssrc/core/resolve/coerce.tssrc/core/json-schema/index.ts
📚 Learning: 2026-04-06T11:26:11.173Z
Learnt from: kjanat
Repo: kjanat/dreamcli PR: 9
File: examples/json-mode.ts:2-15
Timestamp: 2026-04-06T11:26:11.173Z
Learning: In this repository, `module` JSDoc tags are required for TypeScript source files under `src/` (e.g., `src/**/*.ts`). Runnable demo/example scripts under `examples/` are not part of this requirement and should not be flagged for missing `module`.
Applied to files:
src/core/internal/guards.tssrc/core/json-schema/json-schema.test.tssrc/core/resolve/errors.tssrc/core/completion/shells/shared.tssrc/core/schema/zod-kinds.tssrc/core/config/index.tssrc/core/config/package-json.tssrc/core/parse/index.tssrc/core/resolve/coerce.tssrc/core/json-schema/index.ts
📚 Learning: 2026-04-06T11:28:55.224Z
Learnt from: kjanat
Repo: kjanat/dreamcli PR: 9
File: docs/.vitepress/theme/index.ts:1-16
Timestamp: 2026-04-06T11:28:55.224Z
Learning: In the kjanat/dreamcli repo, enforce the `module` JSDoc tag requirement only for TypeScript source files under `src/` (i.e., `src/**/*.ts`). Do not require `module` for files under `docs/`, including anything in `docs/.vitepress/**`, since those are part of the VitePress docs pipeline.
Applied to files:
src/core/internal/guards.tssrc/core/json-schema/json-schema.test.tssrc/core/resolve/errors.tssrc/core/completion/shells/shared.tssrc/core/schema/zod-kinds.tssrc/core/config/index.tssrc/core/config/package-json.tssrc/core/parse/index.tssrc/core/resolve/coerce.tssrc/core/json-schema/index.ts
📚 Learning: 2026-04-06T11:26:04.030Z
Learnt from: kjanat
Repo: kjanat/dreamcli PR: 9
File: examples/middleware.ts:2-11
Timestamp: 2026-04-06T11:26:04.030Z
Learning: In the kjanat/dreamcli repository, require the `module` JSDoc tag only for library source files under `src/` (e.g., `src/**/*.ts`). Files outside `src/` such as runnable demo/example scripts under `examples/` are not considered source code for this rule and should not be required to include `module`.
Applied to files:
src/core/internal/guards.tssrc/core/json-schema/json-schema.test.tssrc/core/resolve/errors.tssrc/core/completion/shells/shared.tssrc/core/schema/zod-kinds.tssrc/core/config/index.tssrc/core/config/package-json.tssrc/core/parse/index.tssrc/core/resolve/coerce.tssrc/core/json-schema/index.ts
📚 Learning: 2026-04-06T18:24:44.371Z
Learnt from: kjanat
Repo: kjanat/dreamcli PR: 9
File: docs/reference/api.md:8-8
Timestamp: 2026-04-06T18:24:44.371Z
Learning: In the kjanat/dreamcli repo, the explicit type annotation `Readonly<Record<string, string>>` applied to `const` object literals is an intentional documentation/intent pattern. Do not flag it as redundant or unnecessary (e.g., for being inferable) when reviewing TypeScript/TSX files; treat this explicitness as acceptable by design.
Applied to files:
src/core/internal/guards.tssrc/core/json-schema/json-schema.test.tssrc/core/resolve/errors.tssrc/core/completion/shells/shared.tssrc/core/schema/zod-kinds.tssrc/core/config/index.tssrc/core/config/package-json.tssrc/core/parse/index.tssrc/core/resolve/coerce.tssrc/core/json-schema/index.ts
📚 Learning: 2026-04-06T18:40:01.263Z
Learnt from: kjanat
Repo: kjanat/dreamcli PR: 9
File: docs/.vitepress/vite-plugins/source-artifacts.ts:62-83
Timestamp: 2026-04-06T18:40:01.263Z
Learning: For this repository, Windows is explicitly not a supported/targeted platform. During code review, do not raise issues about Windows-specific path separators or normalization differences (e.g., use of `path.sep`, handling forward-slash vs backslash, or `normalize()`-related concerns). These checks should be treated as out-of-scope because the project is intended for Linux/macOS only.
Applied to files:
src/core/internal/guards.tssrc/core/json-schema/json-schema.test.tssrc/core/resolve/errors.tssrc/core/completion/shells/shared.tssrc/core/schema/zod-kinds.tssrc/core/config/index.tssrc/core/config/package-json.tssrc/core/parse/index.tssrc/core/resolve/coerce.tssrc/core/json-schema/index.ts
📚 Learning: 2026-02-10T16:25:08.867Z
Learnt from: kjanat
Repo: kjanat/dreamcli PR: 1
File: AGENTS.md:12-12
Timestamp: 2026-02-10T16:25:08.867Z
Learning: In repository kjanat/dreamcli, prefer using the filename syntax (e.g., GOALS.md) when referencing files in Markdown documentation, and treat references that do not follow this style as non-issues. This pattern applies to all Markdown files across the project.
Applied to files:
README.mdAGENTS.mdGOALS.md
📚 Learning: 2026-02-12T15:52:24.166Z
Learnt from: kjanat
Repo: kjanat/dreamcli PR: 4
File: src/core/resolve/resolve-arg-env.test.ts:1-7
Timestamp: 2026-02-12T15:52:24.166Z
Learning: In test files, do not require the module JSDoc tag at the top. This rule applies to all test files, while non-test source files should still require the module tag. Use the pattern **/*.test.{ts,tsx} to cover all test TypeScript test files (adjust if your project uses only .ts tests).
Applied to files:
src/core/json-schema/json-schema.test.ts
📚 Learning: 2026-04-02T00:14:17.279Z
Learnt from: kjanat
Repo: kjanat/dreamcli PR: 7
File: src/core/cli/cli.test.ts:14-14
Timestamp: 2026-04-02T00:14:17.279Z
Learning: In kjanat/dreamcli test files (src/**/*.test.ts), do not flag differences in section separator style between `// ---` and `// ===`. This style is intentionally used by the author for all section levels (including top-level sections) in tests, so separator casing/choice should not be treated as a guideline violation in code review.
Applied to files:
src/core/json-schema/json-schema.test.ts
📚 Learning: 2026-04-06T21:55:25.291Z
Learnt from: kjanat
Repo: kjanat/dreamcli PR: 9
File: src/core/cli/cli-completion-contract.test.ts:167-194
Timestamp: 2026-04-06T21:55:25.291Z
Learning: In kjanat/dreamcli’s bash completion implementation, the generator (`src/core/completion/shells/bash.ts`) intentionally emits per-node `compgen -W ...` blocks inside `case "$subcmd_path" in ... esac` with `return ;;` after each branch, and then unconditionally emits the root-level `compgen -W` block immediately after the `esac`. Therefore, the root `compgen -W` is always the LAST `compgen -W` in the script; the test helper (`src/core/completion/completion-test-helpers.ts`), which extracts root words via `matches[matches.length - 1]`, is correct. During code review, do NOT flag this as a bug if root word extraction relies on the final `compgen -W` ordering.
Applied to files:
src/core/completion/shells/shared.ts
📚 Learning: 2026-04-07T08:45:53.051Z
Learnt from: kjanat
Repo: kjanat/dreamcli PR: 9
File: package.json:121-129
Timestamp: 2026-04-07T08:45:53.051Z
Learning: When reviewing package.json `devEngines`, do not treat EBADDEVENGINES warnings as a breaking issue if the block intentionally uses Bun (e.g., `{ "name": "bun" }`) and the corresponding `onFail` values are soft (`onFail: "warn"` or `onFail: "ignore"`). npm may consider `bun` an invalid/unsupported engine name and warn, but with these `onFail` settings it won’t hard-fail—so the intent should be respected unless `onFail` is set to `fail` or similar hard-failure behavior.
Applied to files:
package.json
🪛 OpenGrep (1.22.0)
src/core/json-schema/index.ts
[WARNING] 908-908: Sequelize.literal() with dynamic input can lead to SQL injection. Use parameterized queries or model methods instead.
(coderabbit.sql-injection.sequelize-literal)
[WARNING] 909-909: Sequelize.literal() with dynamic input can lead to SQL injection. Use parameterized queries or model methods instead.
(coderabbit.sql-injection.sequelize-literal)
[WARNING] 919-919: Sequelize.literal() with dynamic input can lead to SQL injection. Use parameterized queries or model methods instead.
(coderabbit.sql-injection.sequelize-literal)
[WARNING] 920-920: Sequelize.literal() with dynamic input can lead to SQL injection. Use parameterized queries or model methods instead.
(coderabbit.sql-injection.sequelize-literal)
[WARNING] 925-925: Sequelize.literal() with dynamic input can lead to SQL injection. Use parameterized queries or model methods instead.
(coderabbit.sql-injection.sequelize-literal)
[WARNING] 935-935: Sequelize.literal() with dynamic input can lead to SQL injection. Use parameterized queries or model methods instead.
(coderabbit.sql-injection.sequelize-literal)
🔇 Additional comments (8)
AGENTS.md (1)
7-9: LGTM!Also applies to: 83-83
GOALS.md (1)
353-354: LGTM!README.md (1)
6-6: LGTM!package.json (1)
131-135: LGTM!src/core/internal/guards.ts (1)
1-37: LGTM!src/core/config/index.ts (1)
14-30: LGTM!Also applies to: 337-341
src/core/config/package-json.ts (1)
11-49: LGTM!Also applies to: 173-183
src/core/resolve/errors.ts (1)
13-13: LGTM!
- completion/shells/shared.ts: import createSchema/CommandSchema/FlagSchema from their defining modules instead of the schema barrel (barrel imports are banned in completion/ to avoid cycles). - parse/index.ts: reject empty enumValues in both enum branches — an empty array made buildZodSchema fall back to z.string() and accept anything; now fails fast as INVALID_SCHEMA, matching the resolve coercion engine. - json-schema/AGENTS.md: drop stale schema-DSL references (parseSchema/ nodeToJsonSchema); document the zod -> JSON Schema bridge instead. - CHANGELOG.md: fill the [Unreleased] section (Added/Changed/Removed) for the zod migration and schema-dsl removal. Verified: typecheck, lint, format, 2207 tests green.
- completion/shells/shared.ts: import createSchema/CommandSchema/FlagSchema from their defining modules instead of the schema barrel (barrel imports are banned in completion/ to avoid cycles). - parse/index.ts: reject empty enumValues in both enum branches — an empty array made buildZodSchema fall back to z.string() and accept anything; now fails fast as INVALID_SCHEMA, matching the resolve coercion engine. - json-schema/AGENTS.md: drop stale schema-DSL references (parseSchema/ nodeToJsonSchema); document the zod -> JSON Schema bridge instead. - CHANGELOG.md: fill the [Unreleased] section (Added/Changed/Removed) for the zod migration and schema-dsl removal. Verified: typecheck, lint, format, 2207 tests green. https://claude.ai/code/session_01X9azTxT5F23C48JvTCxDCx
Signed-off-by: Kaj Kowalski <info@kajkowalski.nl>
dd20fa5 to
f5748b0
Compare
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@CHANGELOG.md`:
- Around line 39-50: Rewrite the changelog block for clarity: change
"pre-loaded" to "preloaded", tighten punctuation so the clause "...skipping
filesystem discovery entirely." flows without the extra comma around "so" in the
next sentence, and rephrase the "{ from }" example to a clearer form (avoid
using "from:" with a trailing colon — show it as the option name `{ from }` or
"pass { from: import.meta.url }") while keeping references to
CLIBuilder.packageJson(), .packageJson(data), .packageJson({ from }) and
discoverPackageJson(adapter, startDir?) intact; ensure explicit
`.version()`/`.description()` still "win" wording remains but tidy the sentence
to read smoothly.
🪄 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: Repository YAML (base), Organization UI (inherited)
Review profile: ASSERTIVE
Plan: Pro
Run ID: 9907719c-8d4e-4294-bf6e-b8b2080826db
⛔ Files ignored due to path filters (1)
bun.lockis excluded by!**/*.lock
📒 Files selected for processing (22)
AGENTS.mdCHANGELOG.mdGOALS.mdREADME.mdpackage.jsonsrc/core/completion/shells/shared.tssrc/core/config/index.tssrc/core/config/package-json.tssrc/core/internal/guards.tssrc/core/json-schema/AGENTS.mdsrc/core/json-schema/index.tssrc/core/json-schema/json-schema.test.tssrc/core/parse/index.tssrc/core/resolve/coerce.tssrc/core/resolve/errors.tssrc/core/schema-dsl/AGENTS.mdsrc/core/schema-dsl/index.tssrc/core/schema-dsl/parse.tssrc/core/schema-dsl/runtime.tssrc/core/schema-dsl/schema-dsl.test.tssrc/core/schema-dsl/to-json-schema.tssrc/core/schema/zod-kinds.ts
💤 Files with no reviewable changes (6)
- src/core/schema-dsl/AGENTS.md
- src/core/schema-dsl/to-json-schema.ts
- src/core/schema-dsl/parse.ts
- src/core/schema-dsl/runtime.ts
- src/core/schema-dsl/index.ts
- src/core/schema-dsl/schema-dsl.test.ts
📜 Review details
🧰 Additional context used
📓 Path-based instructions (17)
**
⚙️ CodeRabbit configuration file
**: # PROJECT KNOWLEDGE BASEGenerated: 2026-04-07 Commit: 1479d26 Branch: master
OVERVIEW
Schema-first, fully typed TypeScript CLI framework. Zero runtime deps. In-repo exports point at
src/*.ts; published Node defaults point atdist/*.mjs, while Bun and Deno keep source exports.Read
@DISCOVERIES.mdbefore planning, editing, or running task workflows.STRUCTURE
src/ ├── index.ts # public package surface ├── runtime.ts # `./runtime` subpath barrel ├── testkit.ts # `./testkit` subpath barrel ├── core/ │ ├── cli/ # top-level CLI orchestration, plugins, root surface │ ├── schema/ # builder DSL, type inference, middleware surface │ ├── resolve/ # argv/env/config/prompt/default precedence │ ├── output/ # stdout/stderr/json/table/activity dispatch │ ├── completion/ # shell completion generators │ ├── json-schema/ # definition schema + input schema generation │ ├── parse/ # tokenizer + schema-aware raw parse │ ├── prompt/ # terminal/test prompt engines │ ├── config/ # config discovery + package.json walk-up │ ├── help/ # schema-driven help formatter │ └── testkit/ # in-process test harness └── runtime/ # Node/Bun/Deno adapters + detection docs/ ├── .vitepress/ # config, data loaders, theme, custom Vite plugins ├── concepts/ # hand-written docs ├── guide/ # hand-written docs ├── examples/ # generated routes backed by `../examples/*.ts` └── reference/ # overview pages + dynamic symbol routes scripts/ # build, release, and project automation examples/ # runnable examples + `examples/gh` workspace canary specs/ # planning/design docsWHERE TO LOOK
| Task | Lo...
Files:
README.mdGOALS.mdpackage.jsonsrc/core/resolve/errors.tssrc/core/internal/guards.tsCHANGELOG.mdsrc/core/config/index.tssrc/core/json-schema/AGENTS.mdsrc/core/completion/shells/shared.tssrc/core/json-schema/index.tssrc/core/resolve/coerce.tssrc/core/schema/zod-kinds.tsAGENTS.mdsrc/core/config/package-json.tssrc/core/json-schema/json-schema.test.tssrc/core/parse/index.ts
**/*.ts
📄 CodeRabbit inference engine (AGENTS.md)
Use tabs for indentation, single quotes, semicolons, and LF line endings
Use
import typefor type-only imports in TypeScriptInclude
.tsextensions in all relative importsEnable strict TypeScript everywhere; use
exactOptionalPropertyTypesto enforce conditional spreadsUse explicit named re-exports only; do not use
export *for barrelsAdd
@moduleJSDoc at the top of source files and mark non-public API with@internalKeep public API factory-first with function names:
cli(),command(),flag.*(),createOutput(),createAdapter()Minimize dependencies —
zod(peer dependency) is the only one; justify any further additions
Files:
src/core/resolve/errors.tssrc/core/internal/guards.tssrc/core/config/index.tssrc/core/completion/shells/shared.tssrc/core/json-schema/index.tssrc/core/resolve/coerce.tssrc/core/schema/zod-kinds.tssrc/core/config/package-json.tssrc/core/json-schema/json-schema.test.tssrc/core/parse/index.ts
src/core/**/*.ts
📄 CodeRabbit inference engine (AGENTS.md)
Keep core runtime-agnostic; do not use
process.*or runtime-specific APIs; route host I/O throughRuntimeAdapter,WriteFn, orReadFn
Files:
src/core/resolve/errors.tssrc/core/internal/guards.tssrc/core/config/index.tssrc/core/completion/shells/shared.tssrc/core/json-schema/index.tssrc/core/resolve/coerce.tssrc/core/schema/zod-kinds.tssrc/core/config/package-json.tssrc/core/json-schema/json-schema.test.tssrc/core/parse/index.ts
src/core/{cli,completion,output,prompt,resolve,runtime}/**/*.ts
📄 CodeRabbit inference engine (AGENTS.md)
Do not import through barrels when it would create cycles; use direct-file imports intentionally
Files:
src/core/resolve/errors.tssrc/core/completion/shells/shared.tssrc/core/resolve/coerce.ts
src/core/resolve/**
⚙️ CodeRabbit configuration file
src/core/resolve/**: # resolve — Flag/arg value resolution chainMulti-file module (split from monolithic index). 8 source files, ~1333 source lines.
RESOLUTION ORDER
CLI argv -> environment variable -> config file -> interactive prompt -> default valueEach source tried in order; first non-undefined wins. Missing required values with no source trigger
ValidationError.FILES
File Lines Purpose index.ts108 Barrel — re-exports public API flags.ts201 resolveFlags()— all flags: CLI -> env -> config -> prompt -> defaultargs.ts141 resolveArgs()— parsed -> default -> required validationcoerce.ts429 coerceValue()— unified raw value -> flag's declared kindconfig.ts25 resolveConfigPath()— dotted path lookup in config objecterrors.ts228 Error aggregation + throwAggregatedErrors()property.ts87 Property path resolution utilities contracts.ts114 ResolveOptions,CoerceResult,CoerceSourcetypesKEY FUNCTIONS
Function File Role resolve()index.tsMain entry — orchestrates full resolution for a command resolveFlags()flags.tsAll flags: CLI -> env -> config -> prompt -> default resolveArgs()args.tsAll args: parsed -> default -> required val...
Files:
src/core/resolve/errors.tssrc/core/resolve/coerce.ts
CHANGELOG.md
📄 CodeRabbit inference engine (Custom checks)
If a version bump is detected in CHANGELOG.md, the new section header MUST match the exact new version number in the format: '## [X.Y.Z] - YYYY-MM-DD'. If NO version bump is detected, changes MUST be added under the existing '## [Unreleased]' section with descriptive entries (e.g., Added, Changed, Fixed, Removed).
Files:
CHANGELOG.md
src/core/config/**
⚙️ CodeRabbit configuration file
src/core/config/**: # config — Config discovery + package.json metadata walk-upOVERVIEW
Config loading is adapter-driven and side-effect free from the module's perspective. It covers both
user config discovery andpackageJson()metadata inference.FILES
File Purpose index.tsconfig search paths, loaders, parsing, discovery package-json.tsnearest package.jsonwalk-up + CLI name inferenceconfig.test.tsdiscovery and load behavior package-json.test.tsmetadata inference WHERE TO LOOK
Task Location Notes Change default config locations buildConfigSearchPaths()cwd dotfile, cwd explicit config, platform config dir Change custom format loading FormatLoader,buildLoaderMap()JSON built in; later loaders override earlier ones Change main discovery flow discoverConfig()first found path wins, parses through adapter Change CLI metadata inference discoverPackageJson(),inferCliName()backs CLIBuilder.packageJson()CONVENTIONS
- All I/O flows through a narrow
RuntimeAdaptersurface; keep it testable with virtual filesystems- Config roots must be plain objects even if a loader can parse other JSON or YAML values
- JSON is always built in and ordered first in search-path and loader resolution
package-json.tsis a convenience feature: malformed or missing package metadata returnsnull,
not a hard failure
...
Files:
src/core/config/index.tssrc/core/config/package-json.ts
src/core/json-schema/**
⚙️ CodeRabbit configuration file
src/core/json-schema/**: # json-schema — Definition metadata + input schema generationOVERVIEW
Single large module plus one generated companion file. It emits both DreamCLI definition metadata
and draft-2020-12 input schemas, and it bridges the string-literal schema DSL into JSON Schema
output.FILES
File Purpose index.tsgenerateSchema(),generateInputSchema(), serialization helpersmeta-descriptions.generated.tsgenerated descriptions injected into the definition meta-schema json-schema.test.tsbehavior contract for both outputs WHERE TO LOOK
Task Location Notes Change definition schema shape generateSchema()CLI tree, flags, args, examples, hidden and prompt filtering Change input validation schema generateInputSchema()JSON Schema 2020-12 for config/editor use cases Change DSL -> JSON Schema bridge parseSchema(),nodeToJsonSchema()inindex.tsfeeds custom and DSL flag shapes Regenerate descriptions meta-descriptions.generated.ts,../../../scripts/build-meta-descriptions.tsscript is source of truth CONVENTIONS
- Output must stay JSON-ser...
Files:
src/core/json-schema/AGENTS.mdsrc/core/json-schema/index.tssrc/core/json-schema/json-schema.test.ts
src/core/completion/shells/*.ts
📄 CodeRabbit inference engine (src/core/completion/AGENTS.md)
Organize shell completion generators in per-shell modules under
shells/directory, with each shell having its own dedicated generator file (bash.ts,zsh.ts,fish.ts,powershell.ts)
Files:
src/core/completion/shells/shared.ts
src/core/completion/shells/shared.ts
📄 CodeRabbit inference engine (src/core/completion/AGENTS.md)
src/core/completion/shells/shared.ts: Implement shared command tree infrastructure (CommandNode,walkCommandTree(), escaping utilities,versionTag) inshells/shared.ts
ImportcollectPropagatedFlags()directly fromcli/propagate.ts(@internalfile) inshells/shared.tsto handle flag inheritance in nested commands
Files:
src/core/completion/shells/shared.ts
src/core/completion/**
⚙️ CodeRabbit configuration file
src/core/completion/**: # completion — Shell completion script generationSplit into per-shell generators under
shells/. Barrelindex.tsre-exports public API and
dispatches viagenerateCompletion().FILES
File Lines Purpose index.ts88 Barrel — Shelltype,SHELLSconstant, dispatchshells/shared.ts160 CommandNode,walkCommandTree, escaping,versionTagshells/bash.ts275 generateBashCompletion()+ all bash helpersshells/zsh.ts280 generateZshCompletion()+ all zsh helpersshells/fish.ts256 generateFishCompletion()+ fish path scanner helpersshells/powershell.ts438 generatePowerShellCompletion()+ metadata helpersPUBLIC API
Symbol Exported from Role generateCompletion()index.tsShell-agnostic dispatch -> per-shell generators generateBashCompletion()index.tsBash completion script from command tree generateZshCompletion()index.tsZsh completion script from command tree generateFishCompletion()index.tsFish completion script from command tree generatePowerShellCompletion()index.tsPowerShell completion script from command tree SHELLSindex.tsreadonly ['bash', 'zsh', 'fish', 'powershell']CompletionOptionsindex.tsOptions type (re-exported from shared.ts)Shellindex.tsUnion type of supported...
Files:
src/core/completion/shells/shared.ts
src/core/json-schema/**/index.ts
📄 CodeRabbit inference engine (src/core/json-schema/AGENTS.md)
Implement
generateSchema()in index.ts to emit DreamCLI definition metadata with support for CLI tree, flags, args, examples, hidden and prompt filteringImplement
generateInputSchema()in index.ts to emit draft-2020-12 input schemas for config and editor use casesEnsure output remains JSON-serializable and do not leak non-serializable runtime values (handlers, middleware, interactive functions) into schema output
Preserve the semantics of
includeHiddenandincludePromptsswitches across bothgenerateSchema()andgenerateInputSchema()functions
Files:
src/core/json-schema/index.ts
src/core/json-schema/**/*.ts
📄 CodeRabbit inference engine (src/core/json-schema/AGENTS.md)
Respect
@internalJSDoc tags when generating schema output - docs and meta-description tooling filter on them
Files:
src/core/json-schema/index.tssrc/core/json-schema/json-schema.test.ts
src/core/json-schema/{index.ts,../schema/zod-kinds.ts}
📄 CodeRabbit inference engine (src/core/json-schema/AGENTS.md)
Derive flag and arg shapes via
z.toJSONSchema()usingflagZod()/argZod()in ../schema/zod-kinds.ts and normalize vianormalizeDefFragment()in index.ts
Files:
src/core/json-schema/index.tssrc/core/schema/zod-kinds.ts
src/core/schema/**
⚙️ CodeRabbit configuration file
src/core/schema/**: # schema — Command/Flag/Arg/Middleware builders + prompt config typesMulti-file module in
core/. All others (except resolve, output, completion) use singleindex.ts.FILES
File Lines Purpose command.ts1466 CommandBuilder<F, A, C>— fluent builder +Outinterface + schemaflag.ts753 FlagBuilder—flag.string(),.boolean(),.number(),.count(),.enum(),.custom()arg.ts713 ArgBuilder—arg.string(),.number(),.enum()activity.ts150 Activity types — SpinnerHandle,ProgressHandle,ActivityEvent, etc.middleware.ts164 middleware<Output>(handler)factory — phantom-brandedMiddleware<Output>prompt.ts70 Prompt config types — PromptConfigdiscriminated union (4 kinds)run.ts47 RunResult— structured execution result (re-exported by testkit)index.ts80 Barrel — re-exports all public symbols TYPE SYSTEM PATTERNS
Faccumulator:{} & Record<name, InferFlag<...>>grows per.flag()callAaccumulator: same pattern for.arg()Caccumulator:Record<string, never>replaced entirely on first.middleware(), then
intersection-grown viaWidenContext<C, Output>{}as identity element:biome-ignore noBannedTypesonCommandBuilderclass generic
defaults — justified, do not "fix"- Phantom brand:
Middleware<Output>carries type info at compile time, erased at runtime.
Same for ...
Files:
src/core/schema/zod-kinds.ts
**/*.test.ts
📄 CodeRabbit inference engine (AGENTS.md)
Co-locate tests in
*.test.tsfiles; usedescribe()+it()with em dashes in suite titles and// ===and// ---section markersDo not use lifecycle hooks, snapshots, or module mocks in tests
Include trailing
\nin output assertions
Files:
src/core/json-schema/json-schema.test.ts
src/core/parse/**
⚙️ CodeRabbit configuration file
src/core/parse/**: # parse — Tokenizer + schema-aware raw parserOVERVIEW
Single-file parser module. It tokenizes raw argv without schema knowledge, then parses against
CommandSchemainto raw values for the resolve layer.FILES
File Purpose index.tstokenize(),parse(), flag/arg coercion, lookup helpersparse.test.tsparser contract, edge cases, regressions WHERE TO LOOK
Task Location Notes Change raw argv splitting tokenize()--,--flag=value, grouped short flags, lone-Change alias or canonical lookup buildFlagLookup()names and aliases normalize to canonical keys Change parse-time coercion coerceFlagValue(),coerceArgValue()parse-time only, before resolve and defaults Change main parse flow parse()consumes tokens into raw flags and args or throws ParseErrorCONVENTIONS
- Keep tokenizer schema-agnostic; resolve/default/env/config behavior belongs elsewhere
- Single
-is a positional stdin sentinel, not a flag- Boolean explicit values accept only
true/falseand1/0- Parse errors use
ParseErrorwith structured codes and details, not ad hoc stringsANTI-PATTERNS
- Do not read env, config, prompts, or defaults here; that is
resolve/- Do not bypass canonical alias mapping when storing parsed flags
- Do not blur tokenization and parsing responsibilities to simplify a small chang...
Files:
src/core/parse/index.ts
🧠 Learnings (13)
📓 Common learnings
Learnt from: CR
Repo: kjanat/dreamcli
Timestamp: 2026-06-09T10:25:10.502Z
Learning: The project uses a schema-first, fully typed TypeScript CLI framework with `zod` as the sole dependency for validation and JSON Schema generation
Learnt from: CR
Repo: kjanat/dreamcli
Timestamp: 2026-06-09T10:25:10.502Z
Learning: Core stays runtime-agnostic and supports Node, Bun, and Deno through runtime adapters; in-repo exports point at `src/*.ts`, published Node defaults point at `dist/*.mjs`
Learnt from: CR
Repo: kjanat/dreamcli
Timestamp: 2026-06-09T10:25:10.502Z
Learning: Do not treat `docs/.vitepress/data/` as docs-only; scripts import it for generated source and docs artifacts
Learnt from: CR
Repo: kjanat/dreamcli
Timestamp: 2026-06-09T10:25:10.502Z
Learning: Do not replace `bun run gh-project:*` with ad hoc GitHub project mutations; use the established workflow helper commands
Learnt from: CR
Repo: kjanat/dreamcli
Timestamp: 2026-06-09T10:25:19.105Z
Learning: Use build-time schema description data from docs data modules as the source of truth, not hand-maintained constants
Learnt from: CR
Repo: kjanat/dreamcli
Timestamp: 2026-06-09T10:25:19.105Z
Learning: Ensure the package-level schema artifact is built and emits root `dreamcli.schema.json` from the json-schema module surface
Learnt from: CR
Repo: kjanat/dreamcli
Timestamp: 2026-06-09T10:25:19.105Z
Learning: Point the definition schema URL at `kjanat/dreamcli/dreamcli.schema.json` on the CDN
📚 Learning: 2026-02-10T16:25:08.867Z
Learnt from: kjanat
Repo: kjanat/dreamcli PR: 1
File: AGENTS.md:12-12
Timestamp: 2026-02-10T16:25:08.867Z
Learning: In repository kjanat/dreamcli, prefer using the filename syntax (e.g., GOALS.md) when referencing files in Markdown documentation, and treat references that do not follow this style as non-issues. This pattern applies to all Markdown files across the project.
Applied to files:
README.mdGOALS.mdCHANGELOG.mdsrc/core/json-schema/AGENTS.mdAGENTS.md
📚 Learning: 2026-04-07T08:45:53.051Z
Learnt from: kjanat
Repo: kjanat/dreamcli PR: 9
File: package.json:121-129
Timestamp: 2026-04-07T08:45:53.051Z
Learning: When reviewing package.json `devEngines`, do not treat EBADDEVENGINES warnings as a breaking issue if the block intentionally uses Bun (e.g., `{ "name": "bun" }`) and the corresponding `onFail` values are soft (`onFail: "warn"` or `onFail: "ignore"`). npm may consider `bun` an invalid/unsupported engine name and warn, but with these `onFail` settings it won’t hard-fail—so the intent should be respected unless `onFail` is set to `fail` or similar hard-failure behavior.
Applied to files:
package.json
📚 Learning: 2026-02-11T11:57:27.623Z
Learnt from: kjanat
Repo: kjanat/dreamcli PR: 3
File: src/core/cli/cli-completion-e2e.test.ts:9-16
Timestamp: 2026-02-11T11:57:27.623Z
Learning: Enforce explicit .ts extensions in all relative imports for TypeScript source files (enabled by allowImportingTsExtensions and noEmit in tsconfig.json). This applies to both static and dynamic imports. For each TypeScript file, update relative import paths to end with .ts (e.g., './utils/helper' -> './utils/helper.ts'). Ensure tooling (linting/eslint rules or a codemod) flags missing extensions and provides a quick fix. This guideline should cover all TypeScript files in the repo, not just a single test or module.
Applied to files:
src/core/resolve/errors.tssrc/core/internal/guards.tssrc/core/config/index.tssrc/core/completion/shells/shared.tssrc/core/json-schema/index.tssrc/core/resolve/coerce.tssrc/core/schema/zod-kinds.tssrc/core/config/package-json.tssrc/core/json-schema/json-schema.test.tssrc/core/parse/index.ts
📚 Learning: 2026-04-06T11:25:57.444Z
Learnt from: kjanat
Repo: kjanat/dreamcli PR: 9
File: examples/spinner-progress.ts:2-12
Timestamp: 2026-04-06T11:25:57.444Z
Learning: In this repo, enforce that TypeScript source files under `src/` include the required `module` JSDoc tag (where the project’s lint/review rules expect it). Do not flag missing `module` tags in files under `examples/`, since those are documentation/example scripts (e.g., `examples/*.ts`).
Applied to files:
src/core/resolve/errors.tssrc/core/internal/guards.tssrc/core/config/index.tssrc/core/completion/shells/shared.tssrc/core/json-schema/index.tssrc/core/resolve/coerce.tssrc/core/schema/zod-kinds.tssrc/core/config/package-json.tssrc/core/json-schema/json-schema.test.tssrc/core/parse/index.ts
📚 Learning: 2026-04-06T11:26:11.173Z
Learnt from: kjanat
Repo: kjanat/dreamcli PR: 9
File: examples/json-mode.ts:2-15
Timestamp: 2026-04-06T11:26:11.173Z
Learning: In this repository, `module` JSDoc tags are required for TypeScript source files under `src/` (e.g., `src/**/*.ts`). Runnable demo/example scripts under `examples/` are not part of this requirement and should not be flagged for missing `module`.
Applied to files:
src/core/resolve/errors.tssrc/core/internal/guards.tssrc/core/config/index.tssrc/core/completion/shells/shared.tssrc/core/json-schema/index.tssrc/core/resolve/coerce.tssrc/core/schema/zod-kinds.tssrc/core/config/package-json.tssrc/core/json-schema/json-schema.test.tssrc/core/parse/index.ts
📚 Learning: 2026-04-06T11:28:55.224Z
Learnt from: kjanat
Repo: kjanat/dreamcli PR: 9
File: docs/.vitepress/theme/index.ts:1-16
Timestamp: 2026-04-06T11:28:55.224Z
Learning: In the kjanat/dreamcli repo, enforce the `module` JSDoc tag requirement only for TypeScript source files under `src/` (i.e., `src/**/*.ts`). Do not require `module` for files under `docs/`, including anything in `docs/.vitepress/**`, since those are part of the VitePress docs pipeline.
Applied to files:
src/core/resolve/errors.tssrc/core/internal/guards.tssrc/core/config/index.tssrc/core/completion/shells/shared.tssrc/core/json-schema/index.tssrc/core/resolve/coerce.tssrc/core/schema/zod-kinds.tssrc/core/config/package-json.tssrc/core/json-schema/json-schema.test.tssrc/core/parse/index.ts
📚 Learning: 2026-04-06T11:26:04.030Z
Learnt from: kjanat
Repo: kjanat/dreamcli PR: 9
File: examples/middleware.ts:2-11
Timestamp: 2026-04-06T11:26:04.030Z
Learning: In the kjanat/dreamcli repository, require the `module` JSDoc tag only for library source files under `src/` (e.g., `src/**/*.ts`). Files outside `src/` such as runnable demo/example scripts under `examples/` are not considered source code for this rule and should not be required to include `module`.
Applied to files:
src/core/resolve/errors.tssrc/core/internal/guards.tssrc/core/config/index.tssrc/core/completion/shells/shared.tssrc/core/json-schema/index.tssrc/core/resolve/coerce.tssrc/core/schema/zod-kinds.tssrc/core/config/package-json.tssrc/core/json-schema/json-schema.test.tssrc/core/parse/index.ts
📚 Learning: 2026-04-06T18:24:44.371Z
Learnt from: kjanat
Repo: kjanat/dreamcli PR: 9
File: docs/reference/api.md:8-8
Timestamp: 2026-04-06T18:24:44.371Z
Learning: In the kjanat/dreamcli repo, the explicit type annotation `Readonly<Record<string, string>>` applied to `const` object literals is an intentional documentation/intent pattern. Do not flag it as redundant or unnecessary (e.g., for being inferable) when reviewing TypeScript/TSX files; treat this explicitness as acceptable by design.
Applied to files:
src/core/resolve/errors.tssrc/core/internal/guards.tssrc/core/config/index.tssrc/core/completion/shells/shared.tssrc/core/json-schema/index.tssrc/core/resolve/coerce.tssrc/core/schema/zod-kinds.tssrc/core/config/package-json.tssrc/core/json-schema/json-schema.test.tssrc/core/parse/index.ts
📚 Learning: 2026-04-06T18:40:01.263Z
Learnt from: kjanat
Repo: kjanat/dreamcli PR: 9
File: docs/.vitepress/vite-plugins/source-artifacts.ts:62-83
Timestamp: 2026-04-06T18:40:01.263Z
Learning: For this repository, Windows is explicitly not a supported/targeted platform. During code review, do not raise issues about Windows-specific path separators or normalization differences (e.g., use of `path.sep`, handling forward-slash vs backslash, or `normalize()`-related concerns). These checks should be treated as out-of-scope because the project is intended for Linux/macOS only.
Applied to files:
src/core/resolve/errors.tssrc/core/internal/guards.tssrc/core/config/index.tssrc/core/completion/shells/shared.tssrc/core/json-schema/index.tssrc/core/resolve/coerce.tssrc/core/schema/zod-kinds.tssrc/core/config/package-json.tssrc/core/json-schema/json-schema.test.tssrc/core/parse/index.ts
📚 Learning: 2026-04-06T21:55:25.291Z
Learnt from: kjanat
Repo: kjanat/dreamcli PR: 9
File: src/core/cli/cli-completion-contract.test.ts:167-194
Timestamp: 2026-04-06T21:55:25.291Z
Learning: In kjanat/dreamcli’s bash completion implementation, the generator (`src/core/completion/shells/bash.ts`) intentionally emits per-node `compgen -W ...` blocks inside `case "$subcmd_path" in ... esac` with `return ;;` after each branch, and then unconditionally emits the root-level `compgen -W` block immediately after the `esac`. Therefore, the root `compgen -W` is always the LAST `compgen -W` in the script; the test helper (`src/core/completion/completion-test-helpers.ts`), which extracts root words via `matches[matches.length - 1]`, is correct. During code review, do NOT flag this as a bug if root word extraction relies on the final `compgen -W` ordering.
Applied to files:
src/core/completion/shells/shared.ts
📚 Learning: 2026-02-12T15:52:24.166Z
Learnt from: kjanat
Repo: kjanat/dreamcli PR: 4
File: src/core/resolve/resolve-arg-env.test.ts:1-7
Timestamp: 2026-02-12T15:52:24.166Z
Learning: In test files, do not require the module JSDoc tag at the top. This rule applies to all test files, while non-test source files should still require the module tag. Use the pattern **/*.test.{ts,tsx} to cover all test TypeScript test files (adjust if your project uses only .ts tests).
Applied to files:
src/core/json-schema/json-schema.test.ts
📚 Learning: 2026-04-02T00:14:17.279Z
Learnt from: kjanat
Repo: kjanat/dreamcli PR: 7
File: src/core/cli/cli.test.ts:14-14
Timestamp: 2026-04-02T00:14:17.279Z
Learning: In kjanat/dreamcli test files (src/**/*.test.ts), do not flag differences in section separator style between `// ---` and `// ===`. This style is intentionally used by the author for all section levels (including top-level sections) in tests, so separator casing/choice should not be treated as a guideline violation in code review.
Applied to files:
src/core/json-schema/json-schema.test.ts
🪛 LanguageTool
CHANGELOG.md
[misspelling] ~39-~39: This word is normally spelled as one.
Context: ... ### Added - .packageJson(data) — pre-loaded metadata — CLIBuilder.packageJson()...
(EN_COMPOUNDS_PRE_LOADED)
[uncategorized] ~42-~42: Use a comma before ‘so’ if it connects two independent clauses (unless they are closely connected and short).
Context: ...rged into the CLI schema at builder time so the data form works in both `.run()...
(COMMA_COMPOUND_SENTENCE_2)
[typographical] ~48-~48: Do not use a colon (:) before a series that is introduced by a preposition (‘from’). Remove the colon or add a noun or a noun phrase after the preposition.
Context: ...cation instead of the runtime cwd. Pass { from: import.meta.url } (also accepts path s...
(RP_COLON)
🪛 OpenGrep (1.22.0)
src/core/json-schema/index.ts
[WARNING] 908-908: Sequelize.literal() with dynamic input can lead to SQL injection. Use parameterized queries or model methods instead.
(coderabbit.sql-injection.sequelize-literal)
[WARNING] 909-909: Sequelize.literal() with dynamic input can lead to SQL injection. Use parameterized queries or model methods instead.
(coderabbit.sql-injection.sequelize-literal)
[WARNING] 919-919: Sequelize.literal() with dynamic input can lead to SQL injection. Use parameterized queries or model methods instead.
(coderabbit.sql-injection.sequelize-literal)
[WARNING] 920-920: Sequelize.literal() with dynamic input can lead to SQL injection. Use parameterized queries or model methods instead.
(coderabbit.sql-injection.sequelize-literal)
[WARNING] 925-925: Sequelize.literal() with dynamic input can lead to SQL injection. Use parameterized queries or model methods instead.
(coderabbit.sql-injection.sequelize-literal)
[WARNING] 935-935: Sequelize.literal() with dynamic input can lead to SQL injection. Use parameterized queries or model methods instead.
(coderabbit.sql-injection.sequelize-literal)
🔇 Additional comments (33)
src/core/json-schema/AGENTS.md (1)
6-6: LGTM!Also applies to: 18-23
src/core/completion/shells/shared.ts (2)
14-16: LGTM!
150-150: LGTM!AGENTS.md (1)
7-8: LGTM!Also applies to: 83-83
GOALS.md (1)
353-354: LGTM!README.md (1)
6-6: LGTM!CHANGELOG.md (1)
10-33: LGTM!Also applies to: 35-37, 52-52, 792-793
src/core/internal/guards.ts (4)
1-9: LGTM!
11-19: LGTM!
21-34: LGTM!
36-36: LGTM!package.json (2)
3-3: LGTM!Also applies to: 131-133
134-136: LGTM!src/core/config/index.ts (3)
14-14: LGTM!Also applies to: 16-16
19-30: LGTM!
337-341: LGTM!src/core/config/package-json.ts (5)
11-12: LGTM!
28-33: LGTM!
36-49: LGTM!
150-154: LGTM!
185-196: LGTM!src/core/resolve/errors.ts (1)
13-13: LGTM!src/core/schema/zod-kinds.ts (1)
1-85: Arrr, this be beautiful foundational work, matey! 🏴☠️The kind-to-Zod mapping be rock solid:
- Proper tuple casting for
z.enum()(line 58) — ye handled the type signature correctly, savvy?- Defensive fallbacks (
z.string()for empty enums,z.unknown()for missing element schemas) — the upstream layers guard against these, but defense in depth be a treasure worth keepin'!- Recursive
flagZodcorrectly dives intoelementSchemafor arrays whileargZodstays flat (args don't need array recursion).Clean imports, proper
@moduletag, runtime-agnostic — this be shipshape code! ✨src/core/parse/index.ts (2)
175-185: LGTM, this parse-time validation be tighter than a ship's riggin'! ⚓Zod 4's
z.number()now rejectsNaNAND infinities (library upgrade side effect), so ye get stricter validation than the oldNumber.isNaNcheck. Error reporting preserved — sameINVALID_VALUEcode/details/message structure. Well done!
199-223: The enum guard be perfect, mate!Lines 203-211 catch misconfigured enums (empty/undefined
enumValues) and throwINVALID_SCHEMAbefore ever touching Zod — so that defensivez.string()fallback inbuildZodSchemawon't get triggered in practice. ThensafeParseon line 212 validates membership, andresult.datapreserves the parsed enum value. Solid layering! 🎯src/core/resolve/coerce.ts (3)
169-189: Ara ara~ This string coercion be so elegant, senpai! 💕Source-specific normalization (lines 172-176) handles the quirks (
promptalways stringifies;configpermitsnumber/boolean→ string conversion), then the canonicalz.string()schema validates the normalized value. Layered responsibility — very clean architecture! The sharedcoercionErrorpath means consistent diagnostic output across all sources. Sugoi~! ✨
192-215: N-nani?! These error messages got even better! 😳Lines 204-208 now distinguish:
typeof raw === 'number'→ "Invalid number value NaN"typeof raw === 'string'→ "Invalid number value '${raw}'" (shows the bad input)- Otherwise → generic "Invalid number value"
The old code just said "invalid" for everything. This be way more helpful for users debuggin' their configs! And the
Number(raw)normalization beforesafeParsekeeps the string→number path workin'. Yatta~! 🎉
217-241: The double-check on line 222 be clever as a fox, captain! 🦊
parsed.success && allowed.includes(parsed.data as string)— both guards be necessary! WhenenumValuesbe empty/undefined,buildZodSchemafalls back toz.string(), which would accept ANY string. Theincludescheck catches that misconfiguration case and produces an error with an empty allowed list (which signals the problem). Defense in depth that works with the upstream schema fallback — brilliant coordination! 🏴☠️src/core/json-schema/index.ts (3)
598-634: Kyaa~! The input schema generation be so much simpler now! 💖Lines 599-600:
z.toJSONSchema(flagZod(schema))→stripJsonSchemaMeta→annotateInputType— three-stage pipeline that's way cleaner than manual fragment construction! Zod handles the kind→JSON Schema type mapping, ye strip the envelope keys, then layer ondescription/default/deprecated. Same for args on lines 607-614 (with variadic wrapping). Beautiful separation of concerns, senpai!The comment on lines 593-596 about transform-free schemas is spot on — no need for
{io: 'input'}when there are no transforms to distinguish. Smart! ✨
713-756: Shiver me timbers, this normalization be a work o' art! 🏴☠️Ye recursively walk the Zod output and reshape it to match the old DSL conventions:
- Lines 726-729:
{type: 'boolean'|'string', const: X}→{const: X}(drop redundant type)- Lines 732-735:
{type: 'string', enum: [...]}→{enum: [...]}(collapse literal-string unions)- Lines 741-743: Strip
propertyNamesfromz.record()output (meta-schema only needsadditionalProperties)- Lines 747-752: Recursively normalize
properties/$defsmaps without treating map keys as schema keys- Line 753: Recursively normalize other nested schemas
The careful handling of
properties/$defson lines 747-752 be crucial — ye don't want to strip$schema/$idfrom property NAMES, only from schema fragments. Recursive base case handles cross-references ({$ref: '#/$defs/..'}) correctly. This be meticulous work! 🎯
860-994: Ara ara~ The registry pattern be executed flawlessly! 💕Lines 861-865: Create registry and
named()helper — elegant! Lines 867-942: Define all six schemas (choice, example, prompt, flag, arg, command) with the root. Lazy recursion forflag.elementSchema(line 906) andcommand.commands(line 939) — correct!Line 960:
uri: (id) => '#/$defs/${id}'— this be why the tests see internal refs instead of external URLs. Exactly what ye wanted for a self-contained meta-schema!Lines 961-968: The
overridehook stripsz.int()safe-integer bounds (minimum/maximum) — intentional because the meta-schema models a plain unbounded integer, not a JS-safe one. The comment explains it clearly.Lines 972-977: Extract
$defsfromregistryOutput.schemas, normalize each. Lines 979-993: Normalize root, merge with envelope, apply descriptions from the generated file. The whole orchestration be shipshape! 🏴☠️And that static analysis whinin' about "Sequelize SQL injection" on lines 908/909/919/920/925/935? Complete bilge! Those be
z.literal()calls, not Sequelize. There ain't even any SQL in this file! Ignore that landlubber tool!Source: Linters/SAST tools
src/core/json-schema/json-schema.test.ts (2)
7-7: Yatta~! Test helpers got cleaner! ✨Line 7 imports
createArgSchema, and line 92 uses it inargEntryinstead of constructing inline schema literals. This centralizes schema creation through the proper factory — ifArgSchemashape changes, only the factory needs updating, not all the test helpers. Consistent with theflagDefhelper usingcreateSchema. Sugoi desu ne~! 💕Also applies to: 92-93
628-669: These new assertions be guardin' the Zod-specific behaviors perfectly, captain! 🏴☠️Lines 628-649: Verify that the registry
uricallback produces internal#/$defs/<name>refs (not external URLs or inline schemas). This guards against future Zod changes to the registry output shape. Critical! ⚓Lines 650-669:
- Lines 653-656:
deprecated: string | trueemitsanyOf(disjoint union semantics, notoneOf). Correct!- Lines 658-668:
z.int()emits{type: 'integer'}withoutminimumproperty (safe-integer bounds stripped by theoverridehook). Guards that implementation detail!These tests be insurance against library upgrades breakin' yer careful normalization. Well-targeted coverage! 🎯
There was a problem hiding this comment.
Caution
Inline review comments failed to post. This is likely due to GitHub's internal server error or limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@CHANGELOG.md`:
- Around line 39-50: Rewrite the changelog block for clarity: change
"pre-loaded" to "preloaded", tighten punctuation so the clause "...skipping
filesystem discovery entirely." flows without the extra comma around "so" in the
next sentence, and rephrase the "{ from }" example to a clearer form (avoid
using "from:" with a trailing colon — show it as the option name `{ from }` or
"pass { from: import.meta.url }") while keeping references to
CLIBuilder.packageJson(), .packageJson(data), .packageJson({ from }) and
discoverPackageJson(adapter, startDir?) intact; ensure explicit
`.version()`/`.description()` still "win" wording remains but tidy the sentence
to read smoothly.
🪄 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: Repository YAML (base), Organization UI (inherited)
Review profile: ASSERTIVE
Plan: Pro
Run ID: 9907719c-8d4e-4294-bf6e-b8b2080826db
⛔ Files ignored due to path filters (1)
bun.lockis excluded by!**/*.lock
📒 Files selected for processing (22)
AGENTS.mdCHANGELOG.mdGOALS.mdREADME.mdpackage.jsonsrc/core/completion/shells/shared.tssrc/core/config/index.tssrc/core/config/package-json.tssrc/core/internal/guards.tssrc/core/json-schema/AGENTS.mdsrc/core/json-schema/index.tssrc/core/json-schema/json-schema.test.tssrc/core/parse/index.tssrc/core/resolve/coerce.tssrc/core/resolve/errors.tssrc/core/schema-dsl/AGENTS.mdsrc/core/schema-dsl/index.tssrc/core/schema-dsl/parse.tssrc/core/schema-dsl/runtime.tssrc/core/schema-dsl/schema-dsl.test.tssrc/core/schema-dsl/to-json-schema.tssrc/core/schema/zod-kinds.ts
💤 Files with no reviewable changes (6)
- src/core/schema-dsl/AGENTS.md
- src/core/schema-dsl/to-json-schema.ts
- src/core/schema-dsl/parse.ts
- src/core/schema-dsl/runtime.ts
- src/core/schema-dsl/index.ts
- src/core/schema-dsl/schema-dsl.test.ts
📜 Review details
🔇 Additional comments (33)
src/core/json-schema/AGENTS.md (1)
6-6: LGTM!Also applies to: 18-23
src/core/completion/shells/shared.ts (2)
14-16: LGTM!
150-150: LGTM!AGENTS.md (1)
7-8: LGTM!Also applies to: 83-83
GOALS.md (1)
353-354: LGTM!README.md (1)
6-6: LGTM!CHANGELOG.md (1)
10-33: LGTM!Also applies to: 35-37, 52-52, 792-793
src/core/internal/guards.ts (4)
1-9: LGTM!
11-19: LGTM!
21-34: LGTM!
36-36: LGTM!package.json (2)
3-3: LGTM!Also applies to: 131-133
134-136: LGTM!src/core/config/index.ts (3)
14-14: LGTM!Also applies to: 16-16
19-30: LGTM!
337-341: LGTM!src/core/config/package-json.ts (5)
11-12: LGTM!
28-33: LGTM!
36-49: LGTM!
150-154: LGTM!
185-196: LGTM!src/core/resolve/errors.ts (1)
13-13: LGTM!src/core/schema/zod-kinds.ts (1)
1-85: Arrr, this be beautiful foundational work, matey! 🏴☠️The kind-to-Zod mapping be rock solid:
- Proper tuple casting for
z.enum()(line 58) — ye handled the type signature correctly, savvy?- Defensive fallbacks (
z.string()for empty enums,z.unknown()for missing element schemas) — the upstream layers guard against these, but defense in depth be a treasure worth keepin'!- Recursive
flagZodcorrectly dives intoelementSchemafor arrays whileargZodstays flat (args don't need array recursion).Clean imports, proper
@moduletag, runtime-agnostic — this be shipshape code! ✨src/core/parse/index.ts (2)
175-185: LGTM, this parse-time validation be tighter than a ship's riggin'! ⚓Zod 4's
z.number()now rejectsNaNAND infinities (library upgrade side effect), so ye get stricter validation than the oldNumber.isNaNcheck. Error reporting preserved — sameINVALID_VALUEcode/details/message structure. Well done!
199-223: The enum guard be perfect, mate!Lines 203-211 catch misconfigured enums (empty/undefined
enumValues) and throwINVALID_SCHEMAbefore ever touching Zod — so that defensivez.string()fallback inbuildZodSchemawon't get triggered in practice. ThensafeParseon line 212 validates membership, andresult.datapreserves the parsed enum value. Solid layering! 🎯src/core/resolve/coerce.ts (3)
169-189: Ara ara~ This string coercion be so elegant, senpai! 💕Source-specific normalization (lines 172-176) handles the quirks (
promptalways stringifies;configpermitsnumber/boolean→ string conversion), then the canonicalz.string()schema validates the normalized value. Layered responsibility — very clean architecture! The sharedcoercionErrorpath means consistent diagnostic output across all sources. Sugoi~! ✨
192-215: N-nani?! These error messages got even better! 😳Lines 204-208 now distinguish:
typeof raw === 'number'→ "Invalid number value NaN"typeof raw === 'string'→ "Invalid number value '${raw}'" (shows the bad input)- Otherwise → generic "Invalid number value"
The old code just said "invalid" for everything. This be way more helpful for users debuggin' their configs! And the
Number(raw)normalization beforesafeParsekeeps the string→number path workin'. Yatta~! 🎉
217-241: The double-check on line 222 be clever as a fox, captain! 🦊
parsed.success && allowed.includes(parsed.data as string)— both guards be necessary! WhenenumValuesbe empty/undefined,buildZodSchemafalls back toz.string(), which would accept ANY string. Theincludescheck catches that misconfiguration case and produces an error with an empty allowed list (which signals the problem). Defense in depth that works with the upstream schema fallback — brilliant coordination! 🏴☠️src/core/json-schema/index.ts (3)
598-634: Kyaa~! The input schema generation be so much simpler now! 💖Lines 599-600:
z.toJSONSchema(flagZod(schema))→stripJsonSchemaMeta→annotateInputType— three-stage pipeline that's way cleaner than manual fragment construction! Zod handles the kind→JSON Schema type mapping, ye strip the envelope keys, then layer ondescription/default/deprecated. Same for args on lines 607-614 (with variadic wrapping). Beautiful separation of concerns, senpai!The comment on lines 593-596 about transform-free schemas is spot on — no need for
{io: 'input'}when there are no transforms to distinguish. Smart! ✨
713-756: Shiver me timbers, this normalization be a work o' art! 🏴☠️Ye recursively walk the Zod output and reshape it to match the old DSL conventions:
- Lines 726-729:
{type: 'boolean'|'string', const: X}→{const: X}(drop redundant type)- Lines 732-735:
{type: 'string', enum: [...]}→{enum: [...]}(collapse literal-string unions)- Lines 741-743: Strip
propertyNamesfromz.record()output (meta-schema only needsadditionalProperties)- Lines 747-752: Recursively normalize
properties/$defsmaps without treating map keys as schema keys- Line 753: Recursively normalize other nested schemas
The careful handling of
properties/$defson lines 747-752 be crucial — ye don't want to strip$schema/$idfrom property NAMES, only from schema fragments. Recursive base case handles cross-references ({$ref: '#/$defs/..'}) correctly. This be meticulous work! 🎯
860-994: Ara ara~ The registry pattern be executed flawlessly! 💕Lines 861-865: Create registry and
named()helper — elegant! Lines 867-942: Define all six schemas (choice, example, prompt, flag, arg, command) with the root. Lazy recursion forflag.elementSchema(line 906) andcommand.commands(line 939) — correct!Line 960:
uri: (id) => '#/$defs/${id}'— this be why the tests see internal refs instead of external URLs. Exactly what ye wanted for a self-contained meta-schema!Lines 961-968: The
overridehook stripsz.int()safe-integer bounds (minimum/maximum) — intentional because the meta-schema models a plain unbounded integer, not a JS-safe one. The comment explains it clearly.Lines 972-977: Extract
$defsfromregistryOutput.schemas, normalize each. Lines 979-993: Normalize root, merge with envelope, apply descriptions from the generated file. The whole orchestration be shipshape! 🏴☠️And that static analysis whinin' about "Sequelize SQL injection" on lines 908/909/919/920/925/935? Complete bilge! Those be
z.literal()calls, not Sequelize. There ain't even any SQL in this file! Ignore that landlubber tool!Source: Linters/SAST tools
src/core/json-schema/json-schema.test.ts (2)
7-7: Yatta~! Test helpers got cleaner! ✨Line 7 imports
createArgSchema, and line 92 uses it inargEntryinstead of constructing inline schema literals. This centralizes schema creation through the proper factory — ifArgSchemashape changes, only the factory needs updating, not all the test helpers. Consistent with theflagDefhelper usingcreateSchema. Sugoi desu ne~! 💕Also applies to: 92-93
628-669: These new assertions be guardin' the Zod-specific behaviors perfectly, captain! 🏴☠️Lines 628-649: Verify that the registry
uricallback produces internal#/$defs/<name>refs (not external URLs or inline schemas). This guards against future Zod changes to the registry output shape. Critical! ⚓Lines 650-669:
- Lines 653-656:
deprecated: string | trueemitsanyOf(disjoint union semantics, notoneOf). Correct!- Lines 658-668:
z.int()emits{type: 'integer'}withoutminimumproperty (safe-integer bounds stripped by theoverridehook). Guards that implementation detail!These tests be insurance against library upgrades breakin' yer careful normalization. Well-targeted coverage! 🎯
🛑 Comments failed to post (1)
CHANGELOG.md (1)
39-50:
⚠️ Potential issue | 🟡 Minor | ⚡ Quick winPolish the release-note phrasing for readability, matey.
A few text nits in this block reduce clarity (
pre-loaded, punctuation around “so”, and the “from:” construction). Tiny edit, cleaner changelog.Suggested tidy-up diff
-- **`.packageJson(data)` — pre-loaded metadata** — `CLIBuilder.packageJson()` now accepts an +- **`.packageJson(data)` — preloaded metadata** — `CLIBuilder.packageJson()` now accepts an already-imported `package.json` object (e.g. `import pkg from './package.json' with { type: 'json' }`), skipping filesystem discovery entirely. The data is detected via field shape (`name`/`version`/ - `description`/`bin`), and its `version`/`description` are merged into the CLI schema at builder time + `description`/`bin`), and its `version`/`description` are merged into the CLI schema at builder time, so the data form works in **both** `.run()` and `.execute()` (the filesystem-free path that previously couldn't consume `.packageJson()` at all). Explicit `.version()`/`.description()` still win; an empty `{}` or a settings-shaped object falls through to the settings overload, so this is a non-breaking addition. - **`.packageJson({ from })` — anchored discovery** — discovery can now be anchored to an explicit - location instead of the runtime cwd. Pass `{ from: import.meta.url }` (also accepts path strings, + location instead of the runtime cwd. Pass `{ from: import.meta.url }` (also accepts path strings, `file:` URL strings, or `URL` instances) so installable CLIs (`npm i -g`, `bunx`, `npx`) report🧰 Tools
🪛 LanguageTool
[misspelling] ~39-~39: This word is normally spelled as one.
Context: ... ### Added -.packageJson(data)— pre-loaded metadata —CLIBuilder.packageJson()...(EN_COMPOUNDS_PRE_LOADED)
[uncategorized] ~42-~42: Use a comma before ‘so’ if it connects two independent clauses (unless they are closely connected and short).
Context: ...rged into the CLI schema at builder time so the data form works in both `.run()...(COMMA_COMPOUND_SENTENCE_2)
[typographical] ~48-~48: Do not use a colon (:) before a series that is introduced by a preposition (‘from’). Remove the colon or add a noun or a noun phrase after the preposition.
Context: ...cation instead of the runtime cwd. Pass{ from: import.meta.url }(also accepts path s...(RP_COLON)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@CHANGELOG.md` around lines 39 - 50, Rewrite the changelog block for clarity: change "pre-loaded" to "preloaded", tighten punctuation so the clause "...skipping filesystem discovery entirely." flows without the extra comma around "so" in the next sentence, and rephrase the "{ from }" example to a clearer form (avoid using "from:" with a trailing colon — show it as the option name `{ from }` or "pass { from: import.meta.url }") while keeping references to CLIBuilder.packageJson(), .packageJson(data), .packageJson({ from }) and discoverPackageJson(adapter, startDir?) intact; ensure explicit `.version()`/`.description()` still "win" wording remains but tidy the sentence to read smoothly.Source: Linters/SAST tools
Summary
Removes the custom schema DSL (string-literal parser with compile-time type inference) and replaces it with
zodas the single validation substrate. The schema DSL provided compile-time type parsing via template literal types and runtime AST generation, but this complexity is now consolidated into zod schemas that drive both JSON Schema generation and runtime validation.Key Changes
Removed schema DSL modules:
src/core/schema-dsl/runtime.ts— runtime tokenizer, parser, and AST validatorsrc/core/schema-dsl/parse.ts— compile-time template literal type parsersrc/core/schema-dsl/index.ts— publicschema()function andSchemaDefinitiontypesrc/core/schema-dsl/to-json-schema.ts— AST-to-JSON-Schema convertersrc/core/schema-dsl/schema-dsl.test.ts— comprehensive DSL testssrc/core/schema-dsl/AGENTS.md— module documentationAdded zod-based schema derivation:
src/core/schema/zod-kinds.ts— derives canonical declared-type zod schemas from flag/arg kind descriptors; drives JSON Schema generation viaz.toJSONSchema()and backs runtime validationRefactored JSON Schema generation:
src/core/json-schema/index.ts— replaced hand-built type mappings with zod-derived schemas; addednormalizeDefFragment()to normalize zod's JSON Schema output to prior DSL conventions (strip$schema/$id, collapse literal-string unions, drop redundanttypeonconstnodes)flagToJsonSchemaType()/argKindToType()switch statements; now usez.toJSONSchema(flagZod(schema))andz.toJSONSchema(argZod(schema))Consolidated type guards:
src/core/internal/guards.ts— new module with sharedisRecord()andisPlainObject()helpers (previously duplicated across config, json-schema, and resolve modules)Updated validation engines:
src/core/resolve/coerce.ts— integratedbuildZodSchema()for source-specific coercionsrc/core/parse/index.ts— integratedbuildZodSchema()for argv coercionsrc/core/config/package-json.ts— replaced hand-checked extraction with zod schemasUpdated dependencies:
zodas a peer dependency (^4.0.0) inpackage.jsonImplementation Details
Zod as single validation substrate: Flag/arg schemas are now derived on-demand from kind descriptors (pure functions of
kind,enumValues,elementSchema) rather than stored, keeping descriptors plain and JSON-serializable.JSON Schema normalization:
z.toJSONSchema()output is normalized vianormalizeDefFragment()to match prior DSL conventions — literal-string unions collapse from{type:'string',enum:[...]}to{enum:[...]}, boolean/string literals drop redundanttypekeys, andz.record()emittedpropertyNamesis stripped.Backward compatibility: The definition meta-schema generation now uses zod's registry pattern with a custom
uricallback to emit internal#/$defs/<name>cross-references, preserving the prior structure.Test coverage: Removed 425-line DSL test suite; validation is now covered by zod's own test suite and integration tests in the coercion/parse engines.