---
phase: 05-cli-scaffolding
plan: 01
type: execute
wave: 1
depends_on: []
files_modified:
  - build.mill
  - cli/src/Main.scala
  - cli/src/commands/PluginCommands.scala
  - cli/src/commands/VersionCommand.scala
  - cli/src/scaffold/ScaffoldService.scala
  - cli/src/scaffold/TemplateRenderer.scala
  - cli/src/scaffold/templates/PluginTemplates.scala
  - cli/src/output/ConsoleOutput.scala
  - cli/src/config/ConfigDetector.scala
  - cli/src/errors/ScaffoldError.scala
autonomous: true

must_haves:
  truths:
    - "Developer can run `./mill cli.run version` and see version info with Scala/JVM versions"
    - "Developer can run `./mill cli.run plugin create Author.Name` and get a complete plugin scaffold"
    - "Developer can run `./mill cli.run plugin create Author.Name --dry-run` to preview without creating"
    - "Generated plugin scaffold has correct directory structure (Plugin.scala, plugin.yaml, empty dirs)"
    - "Invalid plugin name format produces clear error message with suggestion"
  artifacts:
    - path: "cli/src/Main.scala"
      provides: "CLI entry point with ZIO CLI"
      contains: "ZIOCliDefault"
    - path: "cli/src/commands/PluginCommands.scala"
      provides: "Plugin subcommands"
      exports: ["command"]
    - path: "cli/src/scaffold/ScaffoldService.scala"
      provides: "File generation service"
      exports: ["ScaffoldService"]
    - path: "cli/src/output/ConsoleOutput.scala"
      provides: "Colored console output"
      exports: ["ConsoleOutput"]
  key_links:
    - from: "cli/src/Main.scala"
      to: "cli/src/commands/PluginCommands.scala"
      via: "subcommands composition"
      pattern: "PluginCommands\\.command"
    - from: "cli/src/commands/PluginCommands.scala"
      to: "cli/src/scaffold/ScaffoldService.scala"
      via: "ZIO service dependency"
      pattern: "ZIO\\.service\\[ScaffoldService\\]"
---

<objective>
Create the `summer` CLI tool with ZIO CLI framework and implement plugin scaffolding command.

Purpose: Establish CLI foundation for all developer tooling and deliver the first scaffolding capability (plugins).
Output: Working `summer` CLI with `version` and `plugin create` commands.
</objective>

<execution_context>
@/home/jin/.claude/get-shit-done/workflows/execute-plan.md
@/home/jin/.claude/get-shit-done/templates/summary.md
</execution_context>

<context>
@.planning/PROJECT.md
@.planning/ROADMAP.md
@.planning/STATE.md
@.planning/phases/05-cli-scaffolding/05-CONTEXT.md
@.planning/phases/05-cli-scaffolding/05-RESEARCH.md
@build.mill
</context>

<tasks>

<task type="auto">
  <name>Task 1: CLI Module Setup with ZIO CLI</name>
  <files>
    build.mill
    cli/src/Main.scala
    cli/src/commands/VersionCommand.scala
    cli/src/output/ConsoleOutput.scala
    cli/src/config/ConfigDetector.scala
    cli/src/errors/ScaffoldError.scala
  </files>
  <action>
    Create a new `cli` Mill module alongside the existing `summercms` module.

    1. **Update build.mill:**
       - Add `cli` module extending ScalaModule
       - Same scalaVersion as summercms (3.3.4)
       - Dependencies:
         - `mvn"dev.zio::zio-cli:0.7.4"` (command parsing)
         - `mvn"com.lihaoyi::os-lib:0.11.7"` (file operations)
         - `mvn"com.lihaoyi::fansi:0.5.1"` (colored output)
       - No dependency on summercms module (CLI is standalone)

    2. **Create cli/src/errors/ScaffoldError.scala:**
       - Sealed trait `ScaffoldError` extending Exception
       - Case classes: `InvalidName`, `AlreadyExists`, `FileSystemError`, `NotInProject`, `ConfigError`
       - Each has clear message field

    3. **Create cli/src/output/ConsoleOutput.scala:**
       - ZIO service trait with methods: `info`, `success`, `warning`, `error`, `nextSteps`
       - Live layer using fansi for colors
       - Accept `useColors: Boolean` parameter (for --no-color support)
       - Use fansi.Color.Cyan for info, Green for success, Yellow for warning, Red for error
       - `nextSteps` renders bold header with bulleted list

    4. **Create cli/src/config/ConfigDetector.scala:**
       - ZIO service trait with `findProjectRoot` method
       - Look for marker files: `build.mill`, `.summercms`
       - Walk up directory tree from cwd until found or reach root
       - Return `os.Path` of project root or fail with `NotInProject` error

    5. **Create cli/src/commands/VersionCommand.scala:**
       - Define `VersionCommand` case object
       - Create `command: Command[VersionCommand]` for "version" subcommand
       - Handler prints: "SummerCMS 0.1.0 (Scala 3.3.4, JVM {java.version})"
       - Note: Version hardcoded for now; BuildInfo generation deferred

    6. **Create cli/src/Main.scala:**
       - Extend ZIOCliDefault
       - Define global options:
         - `--no-color` (Boolean, disables colors)
         - `--verbose` / `-v` (Boolean)
         - `--quiet` / `-q` (Boolean)
         - `--no-interaction` (Boolean, for CI)
       - Create top-level `summer` command with VersionCommand as subcommand
       - Use CliApp.make with name="summer", version="0.1.0"
       - Wire ConsoleOutput layer based on --no-color flag

    **Do NOT use:** Complex dependency injection beyond ZIO layers. Keep it simple.
  </action>
  <verify>
    ```bash
    ./mill cli.compile
    ./mill cli.run version
    # Should output: SummerCMS 0.1.0 (Scala 3.3.4, JVM 21)
    ./mill cli.run --help
    # Should show summer command with version subcommand
    ```
  </verify>
  <done>
    CLI module compiles. `summer version` outputs version info. `summer --help` shows command structure.
  </done>
</task>

<task type="auto">
  <name>Task 2: Plugin Scaffolding Command</name>
  <files>
    cli/src/Main.scala
    cli/src/commands/PluginCommands.scala
    cli/src/scaffold/ScaffoldService.scala
    cli/src/scaffold/TemplateRenderer.scala
    cli/src/scaffold/templates/PluginTemplates.scala
  </files>
  <action>
    Implement `summer plugin create Author.Name` command.

    1. **Create cli/src/scaffold/ScaffoldService.scala:**
       - ZIO service trait with methods:
         - `createDirectory(path: os.Path): IO[ScaffoldError, Unit]`
         - `writeFile(path: os.Path, content: String): IO[ScaffoldError, Unit]`
         - `exists(path: os.Path): UIO[Boolean]`
       - Live layer using os-lib:
         - `createDirectory`: `os.makeDir.all(path)`
         - `writeFile`: `os.makeDir.all(path / os.up)` then `os.write(path, content)`
         - `exists`: `os.exists(path)`
       - Wrap all os-lib calls in `ZIO.attemptBlocking` with error mapping

    2. **Create cli/src/scaffold/TemplateRenderer.scala:**
       - Case class `TemplateVars` with fields:
         - `name`, `author`, `plugin` (raw inputs)
         - Derived: `lowerName`, `lowerAuthor`, `lowerPlugin`
         - Derived: `studlyName`, `studlyAuthor`, `studlyPlugin`
         - Derived: `snakeName` (e.g., "BlogPost" -> "blog_post")
         - Derived: `pluginId` (e.g., "acme.blog")
         - Derived: `pluginNamespace` (e.g., "Acme.Blog")
         - Derived: `timestamp` (yyyyMMdd_HHmmss format)
       - Method `render(template: String, vars: TemplateVars): String`
       - Replace placeholders: `{{name}}`, `{{lower_name}}`, `{{studly_name}}`, etc.
       - Use distinctive delimiters to avoid collision with Scala code

    3. **Create cli/src/scaffold/templates/PluginTemplates.scala:**
       - Object with template string vals:
         - `pluginScala`: Plugin.scala with SummerPlugin trait stub
         - `manifestYaml`: plugin.yaml with vendor, name, version, dependencies
         - `langYaml`: Basic lang.yaml for en locale
       - Include TODO comments showing where to add code
       - Templates reference plugin types that will exist in Phase 2

    4. **Create cli/src/commands/PluginCommands.scala:**
       - Sealed trait `PluginCommand` with case class `Create(name: String, dryRun: Boolean)`
       - Define options:
         - `--dry-run` / `-n`: Show what would be created
       - Define args:
         - `name`: Plugin name in Author.Name format
       - Create `createCommand: Command[PluginCommand.Create]`
       - Create `command: Command[PluginCommand]` with "plugin" as parent, "create" as subcommand
       - Implement handler:
         1. Validate name format (Author.Name with regex `^[A-Z][a-zA-Z0-9]*\\.[A-Z][a-zA-Z0-9]*$`)
         2. Parse into (author, plugin) tuple
         3. Build plugin directory path: `plugins/{lower_author}/{lower_plugin}/`
         4. Check if directory exists -> fail with `AlreadyExists`
         5. Build file list with template content:
            - `Plugin.scala`
            - `plugin.yaml`
            - `routes.scala` (empty routes placeholder)
            - `models/.gitkeep`
            - `controllers/.gitkeep`
            - `components/.gitkeep`
            - `resources/db/migration/.gitkeep`
            - `resources/lang/en/lang.yaml`
            - `resources/views/.gitkeep`
         6. If --dry-run: print what would be created
         7. Else: create files via ScaffoldService
         8. Print next steps (edit Plugin.scala, add models, add components)

    5. **Update cli/src/Main.scala:**
       - Add PluginCommands.command to subcommands
       - Wire ScaffoldService.live and ConfigDetector.live layers

    **Pattern for name validation:**
    ```scala
    private val pluginNamePattern = "^([A-Z][a-zA-Z0-9]*)\\.([A-Z][a-zA-Z0-9]*)$".r

    def parsePluginName(input: String): Either[String, (String, String)] =
      input match
        case pluginNamePattern(author, name) => Right((author, name))
        case _ => Left(s"Invalid plugin name '$input'. Use Author.Name format (e.g., Acme.Blog)")
    ```
  </action>
  <verify>
    ```bash
    # Test plugin creation
    ./mill cli.run plugin create Acme.Blog --dry-run
    # Should list files that would be created

    ./mill cli.run plugin create Acme.Blog
    # Should create plugins/acme/blog/ with all files

    ls -la plugins/acme/blog/
    # Should show Plugin.scala, plugin.yaml, directories

    cat plugins/acme/blog/Plugin.scala
    # Should show valid Scala code with TODO comments

    cat plugins/acme/blog/plugin.yaml
    # Should show valid YAML manifest

    # Test error case
    ./mill cli.run plugin create invalidname
    # Should error with suggestion

    # Test already exists
    ./mill cli.run plugin create Acme.Blog
    # Should error with "already exists"
    ```
  </verify>
  <done>
    `summer plugin create Author.Name` creates complete plugin scaffold. `--dry-run` previews changes. Invalid names produce helpful errors. Existing plugins are not overwritten.
  </done>
</task>

</tasks>

<verification>
Full plan verification:

1. CLI module builds independently:
   ```bash
   ./mill cli.compile
   ```

2. Version command works:
   ```bash
   ./mill cli.run version
   ```

3. Help shows all commands:
   ```bash
   ./mill cli.run --help
   ./mill cli.run plugin --help
   ./mill cli.run plugin create --help
   ```

4. Plugin scaffolding works end-to-end:
   ```bash
   rm -rf plugins/test/example  # Cleanup if exists
   ./mill cli.run plugin create Test.Example
   ls plugins/test/example/
   # Plugin.scala, plugin.yaml, routes.scala, models/, controllers/, components/, resources/
   ```

5. Dry-run mode works:
   ```bash
   ./mill cli.run plugin create Another.Plugin --dry-run
   # Shows what would be created, but doesn't create
   ls plugins/another/plugin 2>/dev/null || echo "Directory not created (correct)"
   ```

6. Error handling:
   ```bash
   ./mill cli.run plugin create badname
   # Error: Invalid plugin name 'badname'. Use Author.Name format
   ```
</verification>

<success_criteria>
- [ ] CLI module exists as separate Mill module with ZIO CLI, os-lib, fansi deps
- [ ] `summer version` shows version with Scala/JVM info
- [ ] `summer plugin create Author.Name` creates valid plugin scaffold
- [ ] `--dry-run` shows preview without file creation
- [ ] Invalid plugin names produce clear error with format suggestion
- [ ] Existing plugin directories prevent overwrite with error
- [ ] Generated Plugin.scala compiles (references future types with stubs)
- [ ] Generated plugin.yaml is valid YAML
- [ ] Colored output works with --no-color fallback
- [ ] Next steps printed after successful scaffold
</success_criteria>

<output>
After completion, create `.planning/phases/05-cli-scaffolding/05-01-SUMMARY.md`
</output>
