feat(cli): theming + per-command help expansion + migration guides + lazy AI imports#109
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
DubStack AI evalsCheap-model eval status: skipped: Report artifact: workflow run |
There was a problem hiding this comment.
Pull request overview
This PR polishes the DubStack CLI for a v2.0-style release by adding configurable color theming, greatly expanding per-command --help content (Examples + See also), publishing new migration guides, and introducing a centralized lazy loader for AI SDK dependencies to improve cold-start performance.
Changes:
- Added theme detection/config (
auto|dark|light|none) plus a global--no-colorflag and wired startup theme resolution. - Expanded
--helpoutput for commands/subcommands and added a test to enforce the new help shape. - Introduced
lib/ai-deps.tsas a centralized lazy-import layer and refactored AI-using commands to depend on it; added tests for the lazy-load invariant. - Added/expanded migration guide MDX pages and updated the guides sidebar metadata.
Reviewed changes
Copilot reviewed 28 out of 28 changed files in this pull request and generated 10 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/cli/src/lib/theme.ts | New theme detection/resolution utilities and chalk application helpers |
| packages/cli/src/lib/theme.test.ts | Tests for COLORFGBG parsing, theme resolution, and themed chalk instances |
| packages/cli/src/lib/config.ts | Adds theme to config schema + normalization |
| packages/cli/src/lib/config.test.ts | Asserts theme default/persistence/normalization |
| packages/cli/src/lib/ai-prompt-decision.ts | Refactors prompt decision deps toward lazy AI loading |
| packages/cli/src/lib/ai-prompt-decision.test.ts | Updates config fixture to include new theme field |
| packages/cli/src/lib/ai-deps.ts | New centralized lazy loader for AI SDK constructors |
| packages/cli/src/lib/ai-deps.test.ts | Tests for AI deps loader surface and caching behavior |
| packages/cli/src/lazy-cold-start.test.ts | New test intended to assert entrypoint import doesn’t trigger AI deps load |
| packages/cli/src/index.ts | Wires global --no-color, theme application, entrypoint guard, and expanded help text |
| packages/cli/src/index.help.test.ts | New test enforcing Examples/See also sections in help output |
| packages/cli/src/commands/submit.ts | Refactors AI deps usage to rely on loadAiDeps() |
| packages/cli/src/commands/squash.ts | Refactors AI deps usage to rely on loadAiDeps() |
| packages/cli/src/commands/split.ts | Refactors AI deps usage to rely on loadAiDeps() |
| packages/cli/src/commands/ready.ts | Lazily resolves AI deps only when AI readiness checks are exercised |
| packages/cli/src/commands/mcp.ts | Switches AI deps wiring to a lazy default deps resolver |
| packages/cli/src/commands/flow.ts | Builds default deps via loadAiDeps() and composes flow dependencies |
| packages/cli/src/commands/create.ts | Removes eager AI imports and loads deps only when AI path is taken |
| packages/cli/src/commands/config.ts | Adds dub config theme implementation and parsing/validation |
| packages/cli/src/commands/ai.ts | Refactors to lazy-load AI + bash-tool deps via defaultDeps() |
| packages/cli/src/commands/ai-resolve.ts | Refactors AI deps wiring through loadAiDeps() |
| packages/cli/src/commands/absorb.ts | Refactors AI deps usage to rely on loadAiDeps() |
| apps/docs/content/docs/guides/migration-from-spr.mdx | New migration guide for spr users |
| apps/docs/content/docs/guides/migration-from-sapling.mdx | New migration guide for Sapling users |
| apps/docs/content/docs/guides/migration-from-graphite.mdx | Expanded Graphite migration guide |
| apps/docs/content/docs/guides/migration-from-ghstack.mdx | New migration guide for ghstack users |
| apps/docs/content/docs/guides/migration-from-charcoal.mdx | New migration guide for Charcoal users |
| apps/docs/content/docs/guides/meta.json | Adds new migration pages to the guides sidebar |
Comments suppressed due to low confidence (1)
packages/cli/src/lib/ai-prompt-decision.ts:73
resolveAiPromptDecisionalways callsloadDefaultDeps()(and thereforeloadAiDeps()) before checking whether AI prompt options are enabled in config. This defeats the lazy-import goal for commands likedub sync/dub post-mergewhen AI prompts are disabled. Consider reading config using the non-AI deps first, returningfallbackPrompt()early when disabled, and only then awaitingloadAiDeps()to build the full deps object for the AI path.
const deps = input.deps ?? (await loadDefaultDeps());
const config = await deps.readConfig(input.cwd);
if (!aiPromptOptionsEnabled(config)) {
return input.fallbackPrompt();
}
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
…lazy AI imports * Add `dub config theme auto|dark|light|none` with COLORFGBG-based auto-detection and a global --no-color flag that overrides the configured theme. The resolved theme controls chalk's color level at program startup. * Expand every command's --help with Examples + See also sections, enforced by src/index.help.test.ts which walks the program tree and asserts both sections via outputHelp(). Required gating the auto-running main() so vitest can import the program for introspection. * Publish four new migration guides (charcoal, ghstack, sapling, spr) and expand the existing graphite guide with a 30-minute scripted migration. * Defer @ai-sdk/* + @aws-sdk/credential-providers + ai package imports to a central lib/ai-deps.ts loader that dynamic-imports the SDKs only when an AI codepath actually runs. lazy-cold-start.test.ts asserts the entrypoint import never triggers SDK loading. Completes DUB-69
- submit/squash/split/absorb: defer loadAiDeps() to the AI branch so non-AI runs keep the fast cold-start path. submit() now passes a `getDeps` thunk into the PR-body helpers. - index.ts: use realpathSync when comparing import.meta.url against process.argv[1] so the published `dub` bin works through pnpm/npm's symlinked node_modules/.bin/dub shim. - index.help.test.ts: replace the global process.stdout.write override with Commander's per-command configureOutput() to avoid cross-worker races. - lazy-cold-start.test.ts: reset the AI-deps cache BEFORE importing the entrypoint so the assertion is meaningful. - lib/theme.ts: document that dark/light/auto produce identical output today and only `none` is observably different; the API surface is reserved for a future palette swap. - migration-from-charcoal.mdx: use the real `gt` binary (both Charcoal forks ship as `gt`) and the right cache file names (.graphite_cache_persist). - migration-from-ghstack.mdx: drop the fabricated "Charles" sentence and the unverifiable `ghstack rage` mapping. - migration-from-sapling.mdx: drop the invented `sl debugdiff` mapping. - migration-from-spr.mdx: spr's reviewer key is `defaultReviewers` in .spr.yml, not `spr config.reviewers`; spr does not install a commit-msg hook so don't tell users to remove one. Refs DUB-69
The DUB-69 help-test invariant walks every command and asserts an Examples plus See also section. The new `dub completion` and `dub man` commands from main only had Examples, so the test failed after rebase. Add the missing See also footers. Refs DUB-69
148f187 to
3ff5a53
Compare
|
🎉 This PR is included in version 1.10.0 🎉 The release is available on: Your semantic-release bot 📦🚀 |
TL;DR
Adds a configurable color theme (auto/dark/light/none), gives every CLI command a tutorial-grade --help with Examples + See also, publishes four new migration guides plus an expanded Graphite one, and defers @ai-sdk/* loading to first AI use. New tests lock the help shape, the lazy-load invariant, and theme detection.
Why
v2.0 release polish —
dub --helppreviously printed one-line synopses with no examples and no cross-references, leaving newcomers without a tutorial path.Cold-start cost — every
dubinvocation was eagerly loading @ai-sdk/{anthropic,google,openai,openai-compatible,amazon-bedrock} + @aws-sdk/credential-providers + the ai package, even fordub logordub status.Adoption — users coming from Graphite, Charcoal, ghstack, Sapling, or spr need a fast
gt → dubmapping plus a 30-minute migration recipe.Before
migration-from-graphite.mdxexisted (35 lines). No Charcoal/ghstack/Sapling/spr guides.dub logpaid the full SDK import cost.After
dub config theme auto|dark|light|noneplus a global--no-colorflag.autoparses COLORFGBG;nonedisables chalk via level=0.File-by-file
packages/cli/src/lib/theme.ts
new +64 / -0
New theme primitives.
detectTerminalThemereads COLORFGBG;resolveThemecollapses the ThemeMode + --no-color + env signal into one of dark/light/none;applyTheme('none')mutateschalk.level = 0at program startup;themedChalkreturns a non-global Chalk instance for callers that need it.packages/cli/src/lib/config.ts
mod +16 / -0
Adds
theme: ThemeModeto DubConfig with default'auto'and a normalizeTheme() that snaps unknown values back to auto.packages/cli/src/commands/config.ts
mod +55 / -0
Implements
configTheme(cwd, theme?)mirroring the other config setters: read → return current, write → persist + report changed flag. parseTheme() throws a DubError listing valid values.packages/cli/src/index.ts
mod +744 / -39
Three changes: (1)
--no-colorbecomes a global program option and main() resolves the theme + calls applyTheme() before parsing argv; (2) every command and config subcommand gains anaddHelpText('after', ...)block with Examples + See also; (3) the auto-running main() is now guarded byif (isCliEntrypoint())so vitest can import the program for introspection.packages/cli/src/lib/ai-deps.ts
new +88 / -0
Central lazy loader for the AI SDK.
loadAiDeps()parallel-imports @ai-sdk/{anthropic,google,openai,openai-compatible,amazon-bedrock} + @aws-sdk/credential-providers + ai once and caches the bundle.peekAiDeps()is the test seam used by lazy-cold-start.test.ts.packages/cli/src/commands/{absorb,ai,ai-resolve,create,flow,mcp,ready,split,squash,submit}.ts
mod +212 / -231
Every command that previously eager-imported the AI SDK now uses
import typeplus a call toloadAiDeps()(or a per-filedefaultDeps()wrapper) inside the function body. DEFAULT_DEPS constants are gone; the parameter signature shifts fromdeps = DEFAULT_DEPStodeps?: Twith anawait loadAiDeps()fallback. Tests still inject deps directly.packages/cli/src/lib/ai-prompt-decision.ts
mod +13 / -20
Same lazy treatment for the AI prompt decision helper. The CLI startup branch (
isAiPromptOptionEnabled) keeps a synchronous default that only needs readConfig — no SDK touch — so cold paths still skip the import.apps/docs/content/docs/guides/migration-from-graphite.mdx
mod +99 / -32
Expanded from 39 lines to 131. New sections: extended command mapping table, conceptual differences (local-first, refs mirror, AI features, safe merging, multi-trunk, MCP), common pitfalls, a 30-minute scripted migration, and See also.
apps/docs/content/docs/guides/migration-from-{charcoal,ghstack,sapling,spr}.mdx
new +374 / -0
Four new migration guides, each with: command mapping table, conceptual differences, common pitfalls, 30-minute scripted migration, and See also. Charcoal uses
gt(the upstream binary). Sapling usessl. ghstack and spr each describe the one-commit-per-PR vs one-branch-per-PR model shift.apps/docs/content/docs/guides/meta.json
mod +5 / -1
Adds the four new migration pages to the docs sidebar.
packages/cli/src/index.help.test.ts
new +63 / -0
Walks the program tree and asserts every command + subcommand's
--help(via captured stdout) contains anExamples:section, and every leaf also contains aSee also:section. 161 generated assertions.packages/cli/src/lazy-cold-start.test.ts
new +14 / -0
Resets the ai-deps cache, imports the CLI entrypoint, and asserts
peekAiDeps()is still null — proving the entrypoint does not eagerly evaluate any AI SDK package.packages/cli/src/lib/theme.test.ts
new +65 / -0
Locks down detectTerminalTheme (COLORFGBG parsing), resolveTheme (--no-color override, dark/light passthrough, auto fallback), and themedChalk (
none→ level 0).packages/cli/src/lib/ai-deps.test.ts
new +35 / -0
Covers loadAiDeps surface (all expected constructors present) and idempotence (second call returns the cached instance).
Where to focus review
packages/cli/src/lib/ai-deps.ts + every modified command: The function signatures arounddeps?: Tchanged in 10 files. Confirm every call site that consumesdepsdoes so AFTER awaitingloadAiDeps()(ordefaultDeps()) and that no synchronous code path indexes intodepsbefore the await.packages/cli/src/index.ts —isCliEntrypoint()near the bottom of the file:if (isCliEntrypoint()) main()replaces the previous unconditionalmain(). Vitest must be able to importprogramwithout running it, AND the publisheddubbin (./dist/index.js) must still launch. The URL-equality check covers both, but reviewers should sanity-check that the bin entry in package.json still works after apnpm build.packages/cli/src/index.help.test.ts —walk()and the it.each blocks: There is oneHELP_EXEMPTentry (dub help). Reviewers should verify the exemption is justified and no other commands quietly slip past the Examples/See also requirement.apps/docs/content/docs/guides/migration-from-*.mdx: Adversarial review caught three factual issues —chbinary (Charcoal actually usesgt),Charleshallucination + inventedghstack ragein the ghstack guide, and an inventedsl debugdiffmapping in the Sapling guide. All three have been fixed in the staged commit. Re-read the corrected sections to confirm no other invented commands slipped through.packages/cli/src/lib/theme.ts —applyTheme``:applyTheme('none')mutates the process-wide `chalk.level`. This is intentional (it lets every existing chalk consumer obey --no-color without a refactor) and is only called from `main()` inside the entrypoint guard. The JSDoc now warns that tests must use `themedChalk` instead.Test plan
pnpm buildruns next typegen + fumadocs-mdx + tsc + next build across the docs app. All 5 migration routes prerender as static HTML.dub mcpvia tsx and exchanges JSON-RPC. Passes after theisCliEntrypoint()change.Quality gates
pnpm checks- passed (Checked 334 files in 256ms. No fixes applied.)pnpm typecheck- passed (3 packages, all passingtsc --noEmit(cached after second run).)pnpm test- passed (133 test files, 1622/1622 tests passing in 52s.)pnpm build- passed (Docs (Next.js + Fumadocs) + CLI (tsup) + retarget action all build cleanly.)Self-QA
See QA fallback evidence.
Self-QA fallback record — see .reports/dub-69-qa.md for the verification matrix and evidence links.
Acceptance criteria
dub config themeworks with auto-detection - lib/theme.ts implements auto-detection via COLORFGBG; commands/config.ts persists the setting; theme.test.ts + config.test.ts lock the behavior; index.ts wires--no-colorand applies the resolved theme at startup.pnpm buildrenders them as static routes.import { createAnthropic }has been converted toimport type; lazy-cold-start.test.ts asserts the entrypoint import never triggers loadAiDeps().Adversarial review
Iterations: 1
Remaining critical/major: 0/0
Remaining minor/nitpick: 0/0
chbinary — rewritten to usegtsince both Charcoal forks ship the same binary as Graphite.Charlesand unverifiableghstack rage— both rewritten with accurate ghstack semantics.sl debugdiff— removed; mapping now lists onlysl pr.Dependencies
Chalkconstructor +ChalkInstancetype.Rollout
Backward-compatible patch release. The new config field defaults to
auto, so existing repos behave identically until the user runsdub config theme. The lazy-import refactor is invisible to callers and is exercised by the existing test matrix.dubstackv1.7.x bump on merge. Newdub config themesubcommand appears in the docs sidebar; existing installs see no behavior change until they set a non-auto theme or pass --no-color.dub log,dub status,dub info, etc.) will see ~50ms cold-start savings on the next install because no @ai-sdk/* module is evaluated until the first AI command.Commit