Skip to content

refactor(cli): decouple scaffolding logic with a planner#998

Merged
yamcodes merged 11 commits into
979-epic-arkenv-cli-architectural-deepeningfrom
982-decouple-scaffolding-logic-with-a-planner-1
May 14, 2026
Merged

refactor(cli): decouple scaffolding logic with a planner#998
yamcodes merged 11 commits into
979-epic-arkenv-cli-architectural-deepeningfrom
982-decouple-scaffolding-logic-with-a-planner-1

Conversation

@yamcodes

@yamcodes yamcodes commented May 14, 2026

Copy link
Copy Markdown
Owner

Fixes #982\n\nThis PR refactors the InitCommand to follow a declarative "Planner" pattern, decoupling state collection, planning, and execution.\n\n### Changes\n- Extracted a Collector phase to gather environment state and user input.\n- Implemented a pure Planner function that produces a ScaffoldingPlan object.\n- Implemented an Executor that applies the plan using Workspace and Reporter abstractions.\n- Added comprehensive unit tests for the Planner and Executor.\n- Cleaned up the legacy scaffold function.\n\n### Benefits\n- Pure logic in the Planner is now easily unit-testable.\n- Side effects are isolated in the Executor.\n- Improved separation of concerns and maintainability.

Summary by CodeRabbit

Release Notes

  • Refactor
    • Restructured the initialization command to use a new planning and execution architecture for improved code organization and maintainability, while preserving all existing initialization functionality.

Review Change Stack

@yamcodes yamcodes added refactor A change to the codebase that's neither a bug fix nor added functionality @arkenv/cli Issues or Pull Requests involving the ArkEnv CLI labels May 14, 2026
@yamcodes yamcodes linked an issue May 14, 2026 that may be closed by this pull request
@changeset-bot

changeset-bot Bot commented May 14, 2026

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: 2985657

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@coderabbitai

coderabbitai Bot commented May 14, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 987d7c08-fa32-4be1-b436-7205ea418949

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This PR refactors the ArkEnv CLI initialization command using a three-phase architecture: a collector gathers project state and user input, a pure planner generates a declarative scaffolding plan, and an executor applies the plan via workspace and reporter abstractions. New type contracts and implementations decouple logic from side effects, enabling testability and dry-run capability.

Changes

Scaffolding Architecture Refactoring

Layer / File(s) Summary
Scaffolding plan type contracts
packages/cli/src/plan.ts
Defines ScaffoldingPlan (file operations, TS config updates, dependency/skill installation, framework bootstrapping, metadata), CollectedState (project context with framework/package manager detection and TS config status), Workspace (async filesystem/process/config operations interface), and Reporter (spinner/logging interface).
Workspace and Reporter implementations
packages/cli/src/lib/workspace.ts, packages/cli/src/lib/reporter.ts
NodeWorkspace implements file operations, command execution with optional stdout/stderr capture, TS config updates, Vite/Bun discovery and bootstrap, and safeAppend injection. CliReporter delegates all reporting methods to an injected logger.
Scaffolding plan builder
packages/cli/src/planner.ts, packages/cli/src/planner.test.ts
createPlan generates declarative plans from collected state: conditionally creates/overwrites env schema, configures framework-specific dependencies and type definitions, schedules optional TS strictness updates and framework bootstrapping, includes optional skill DLX installation, and populates metadata. Tests validate plan outputs across frameworks (node/vite/bun), file-handling modes, and optional features.
Plan execution orchestrator
packages/cli/src/executor.ts, packages/cli/src/executor.test.ts
Executor orchestrates plan execution: writes files with status reporting, installs dependencies (unless skipped), enforces TS strictness, bootstraps framework configs, installs optional skill with error-catching, reports usage instructions, and completes with summary. Tests verify execution flow, config updates, bootstrapping, and file appending.
InitCommand planner/executor integration
packages/cli/src/commands/init.ts, packages/cli/src/commands/init.test.ts
InitCommand now uses collect() + createPlan() + executor.execute() pipeline. collect() gathers project context (TS strictness detection, prompts, env schema overwrite confirmation, package manager, existing files) with proper stdout redirection cleanup. Tests updated to mock detectPackageManager and inspect spawn calls for skill-install verification.
Scaffold helper function exports
packages/cli/src/scaffold.ts, packages/cli/src/scaffold.test.ts
updateTsConfigToStrict and detectPackageManager promoted to exported public APIs; getInstallCommand removed (moved to Executor). Adds jsonc-parser imports for config updates. Removes obsolete scaffold function test suite.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • yamcodes/arkenv#945: Modifies init command flow with --agent/--quiet/--json flags and gates prompts; this PR refactors the underlying scaffolding pipeline to support such behavioral variations.

Suggested labels

arkenv

Poem

🐰 The scaffolding splits neat,
Collector, planner, executor complete!
No more tangled god methods,
Each phase pure, each path unambiguous—
Plans declared, then run. Hooray! 🎉

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Linked Issues check ✅ Passed The PR fully implements all requirements from issue #982: introduces a Collector phase (collect() in InitCommand), implements a pure Planner function (createPlan in planner.ts), and implements an Executor (Executor class with plan execution logic). All three core architectural components are present and integrated.
Out of Scope Changes check ✅ Passed All changes are directly scoped to implementing the three-phase architecture (Collector/Planner/Executor) defined in issue #982. Supporting abstractions (Workspace, Reporter, ScaffoldingPlan types) and test coverage are necessary implementation details. No unrelated refactoring detected.
Title check ✅ Passed The title 'refactor(cli): decouple scaffolding logic with a planner' accurately reflects the PR's primary objective: decoupling scaffolding logic by introducing a planner pattern.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch 982-decouple-scaffolding-logic-with-a-planner-1

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@arkenv-bot

arkenv-bot Bot commented May 14, 2026

Copy link
Copy Markdown
Contributor

🤖 Hi @yamcodes, I've received your request, and I'm working on it now! You can track my progress in the logs for more details.

@yamcodes
yamcodes force-pushed the 982-decouple-scaffolding-logic-with-a-planner-1 branch from c86db84 to 144a45c Compare May 14, 2026 18:53
@github-actions github-actions Bot added the tests This issue or PR is about adding, removing or changing tests label May 14, 2026
@pkg-pr-new

pkg-pr-new Bot commented May 14, 2026

Copy link
Copy Markdown

Open in StackBlitz

arkenv

npm i https://pkg.pr.new/arkenv@998

@arkenv/bun-plugin

npm i https://pkg.pr.new/@arkenv/bun-plugin@998

@arkenv/cli

npm i https://pkg.pr.new/@arkenv/cli@998

@arkenv/fumadocs-ui

npm i https://pkg.pr.new/@arkenv/fumadocs-ui@998

@arkenv/vite-plugin

npm i https://pkg.pr.new/@arkenv/vite-plugin@998

commit: 98a72dc

@arkenv-bot

arkenv-bot Bot commented May 14, 2026

Copy link
Copy Markdown
Contributor

📦 Bundle Size Report

No results found

All size limits passed!

@arkenv-bot

arkenv-bot Bot commented May 14, 2026

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Review Updated (Asia/Almaty)
arkenv Ready Ready Preview, Comment May 15 2026, 12:47 AM (Asia/Almaty)

@arkenv-bot

arkenv-bot Bot commented May 14, 2026

Copy link
Copy Markdown
Contributor

🤖 I'm sorry @yamcodes, but I was unable to process your request. Please see the logs for more details.

@yamcodes
yamcodes marked this pull request as draft May 14, 2026 18:55
@yamcodes
yamcodes marked this pull request as ready for review May 14, 2026 18:55

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🧹 Nitpick comments (1)
packages/cli/src/plan.ts (1)

67-73: ⚡ Quick win

Consider unifying bootstrap return types.

The bootstrapViteConfig and bootstrapBunConfig methods return different shapes:

  • Vite: { success, updated, error? }
  • Bun: { success, instructions?, error? }

This asymmetry requires callers to handle framework-specific property names. Consider a unified shape such as:

{ success: boolean; updated?: boolean; instructions?: string; error?: string }

This would simplify executor logic and make the API more predictable.

🤖 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 `@packages/cli/src/plan.ts` around lines 67 - 73, Unify the return shapes of
bootstrapViteConfig and bootstrapBunConfig by introducing a single common return
type (e.g. { success: boolean; updated?: boolean; instructions?: string; error?:
string }) and update the method signatures for bootstrapViteConfig(path: string,
importPath: string) and bootstrapBunConfig(path: string) to both return Promise
of that type; adjust any callers to use the new optional fields (use updated
when applicable for Vite and instructions when applicable for Bun) and update
any related types or interfaces to reflect the unified shape.
🤖 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 `@packages/cli/src/commands/init.ts`:
- Around line 115-119: The code currently computes typeFileName via a ternary
(typeFileName = options.framework === "vite" ? "vite-env.d.ts" : "bun-env.d.ts")
which incorrectly falls back to "bun-env.d.ts" for "node"; change the logic to
only assign and check a type file when options.framework is "vite" or "bun"
(e.g., if options.framework === "vite" set "vite-env.d.ts", else if === "bun"
set "bun-env.d.ts"), and only compute typeFilePath and push into existingFiles
when that typeFileName is defined; ensure references are to typeFileName,
typeFilePath, options.framework and existingFiles so downstream
CollectedState.existingFiles does not get stray bun-env.d.ts entries for node
projects.

In `@packages/cli/src/executor.test.ts`:
- Around line 1-35: Add a beforeEach that isolates test state by clearing Vitest
mocks and resetting SKIP_INSTALL and the executor instance: call
vi.clearAllMocks() (or vi.resetAllMocks()) to reset mock call history for
mockWorkspace and mockReporter, delete or set process.env.SKIP_INSTALL =
undefined to ensure no env leakage, and reinstantiate the Executor (create a new
executor = new Executor(mockWorkspace, mockReporter)) inside beforeEach so each
test gets a fresh instance.

In `@packages/cli/src/executor.ts`:
- Around line 109-120: When plan.bootstrap.framework === "bun" and
this.workspace.findBunConfig() returns no path, add a user-facing fallback
message so users know bootstrap was skipped and how to proceed manually; update
the branch handling in executor (the block around this.workspace.findBunConfig
and this.workspace.bootstrapBunConfig) to call this.reporter.info or
this.reporter.warn with a short guidance string (e.g., "No Bun config found —
create a bun.config.ts or run `bun init` to bootstrap manually") so setup
doesn’t appear complete when Bun was skipped.
- Around line 68-70: The executor currently ignores plan.tsConfig.path and
always calls this.workspace.updateTsConfigToStrict() without a target, breaking
non-default tsconfig handling; change the call in the block that checks if
(plan.tsConfig) to pass the planned path (plan.tsConfig.path) into
updateTsConfigToStrict (or its equivalent parameter) so updateTsConfigToStrict
receives the target tsconfig file to modify; update any related handling of
tsResult (e.g., tsResult.status checks) to remain the same after passing the
path.

In `@packages/cli/src/lib/workspace.ts`:
- Around line 14-17: The constructor currently types stdio as any which bypasses
type checking; change the stdio parameter type to StdioOptions from
node:child_process (import StdioOptions) and update the constructor signature
(constructor(private isQuiet: boolean, private stdio: StdioOptions) {}) so the
value passed into spawn() in the method that calls spawn() is correctly typed
and validated.

---

Nitpick comments:
In `@packages/cli/src/plan.ts`:
- Around line 67-73: Unify the return shapes of bootstrapViteConfig and
bootstrapBunConfig by introducing a single common return type (e.g. { success:
boolean; updated?: boolean; instructions?: string; error?: string }) and update
the method signatures for bootstrapViteConfig(path: string, importPath: string)
and bootstrapBunConfig(path: string) to both return Promise of that type; adjust
any callers to use the new optional fields (use updated when applicable for Vite
and instructions when applicable for Bun) and update any related types or
interfaces to reflect the unified shape.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 0de8622b-1d50-4c91-b96e-e592b18a368a

📥 Commits

Reviewing files that changed from the base of the PR and between 5a87e2e and 98a72dc.

📒 Files selected for processing (11)
  • packages/cli/src/commands/init.test.ts
  • packages/cli/src/commands/init.ts
  • packages/cli/src/executor.test.ts
  • packages/cli/src/executor.ts
  • packages/cli/src/lib/reporter.ts
  • packages/cli/src/lib/workspace.ts
  • packages/cli/src/plan.ts
  • packages/cli/src/planner.test.ts
  • packages/cli/src/planner.ts
  • packages/cli/src/scaffold.test.ts
  • packages/cli/src/scaffold.ts

Comment thread packages/cli/src/commands/init.ts Outdated
Comment thread packages/cli/src/executor.test.ts Outdated
Comment thread packages/cli/src/executor.ts
Comment thread packages/cli/src/executor.ts
Comment thread packages/cli/src/lib/workspace.ts
@yamcodes
yamcodes changed the base branch from main to 979-epic-arkenv-cli-architectural-deepening May 14, 2026 19:02
@yamcodes yamcodes changed the title refactor: decouple scaffolding logic with a planner refactor(cli): decouple scaffolding logic with a planner May 14, 2026
@yamcodes

Copy link
Copy Markdown
Owner Author

@gemini-cli /review

@arkenv-bot

arkenv-bot Bot commented May 14, 2026

Copy link
Copy Markdown
Contributor

🤖 Hi @yamcodes, I've received your request, and I'm working on it now! You can track my progress in the logs for more details.

@yamcodes

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented May 14, 2026

Copy link
Copy Markdown
Contributor
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@arkenv-bot arkenv-bot Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

## 📋 Review Summary

This PR introduces a significant architectural improvement to the InitCommand by decoupling the scaffolding logic into Collector, Planner, and Executor phases. This makes the logic more declarative, easier to test, and improves maintainability by isolating side effects.

🔍 General Feedback

  • Well-Structured: The separation of concerns into Planner (pure logic) and Executor (side effects) is excellent and follows best practices.
  • Improved Testability: The addition of comprehensive unit tests for the Planner and Executor confirms the robustness of the new design.
  • Security Consideration: Note the suggestion regarding spawn and shell: true to avoid potential command injection vulnerabilities.
  • Clean Code: The refactoring removes a lot of complexity from the main command class and the legacy scaffold function.


let stdout = "";
let stderr = "";
const MAX_BUFFER = 10_000;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟢 10,000 characters for MAX_BUFFER is generally sufficient for error logs, but for extremely verbose installation failures, it might truncate useful context. Consider if this should be configurable or slightly larger.

Suggested change
const MAX_BUFFER = 10_000;
const MAX_BUFFER = 10_000;


if (file.label === "environment schema") {
// We'll report this at the end or as we go
} else if (file.label?.includes("types")) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟢 This block is empty and seems to be a placeholder. It could be removed if no action is needed for the "environment schema" label.

Suggested change
} else if (file.label?.includes("types")) {
if (file.label === "environment schema") {
// No specific reporting needed here
} else if (file.label?.includes("types")) {

Comment thread packages/cli/src/lib/workspace.ts Outdated
return new Promise<void>((resolve, reject) => {
const child = spawn(command, [], {
stdio: this.isQuiet ? "pipe" : this.stdio,
shell: true,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 The use of shell: true in spawn can be a security risk if any part of the command string is influenced by external input. While currently safe with internal inputs, it's generally better to avoid shell: true and pass arguments as an array to prevent command injection.

Suggested change
shell: true,
const child = spawn(command, [], {
stdio: this.isQuiet ? "pipe" : this.stdio,
});

Wait, if you remove shell: true, you must pass the command and args separately. Consider refactoring execute to accept args: string[].

@yamcodes
yamcodes merged commit 715c31b into 979-epic-arkenv-cli-architectural-deepening May 14, 2026
4 checks passed
@yamcodes
yamcodes deleted the 982-decouple-scaffolding-logic-with-a-planner-1 branch May 14, 2026 19:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

@arkenv/cli Issues or Pull Requests involving the ArkEnv CLI refactor A change to the codebase that's neither a bug fix nor added functionality tests This issue or PR is about adding, removing or changing tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Decouple Scaffolding Logic with a Planner

1 participant