- Add testing section to CLAUDE.md and README.md - Update architecture.md with Testing Architecture section - Create docs/testing.md with fixture and harness documentation - Document __VARNAME__ path syntax for CI compatibility
9.8 KiB
9.8 KiB
qctl Vision & Architecture
Purpose
qctl is the unified CLI for the QRun platform - a complete developer workflow tool for building, packaging, deploying, and managing QQQ-based applications. It consolidates project scaffolding, dependency management, artifact publishing, and AI-assisted development into a single binary.
Core Principles
- Single Binary: One native executable for all QRun platform operations
- Offline-First: Full functionality without network when possible (hermetic mode)
- Declarative Config: YAML-driven configuration with sensible defaults
- Platform Parity: Consistent behavior across macOS, Linux, Windows
- Developer Experience: Fast startup (native), helpful errors, shell completion
Command Structure
qctl
├── qqq # Project scaffolding
│ ├── new <name> # Create new project from template
│ ├── list # List available templates
│ └── upgrade # Upgrade project to newer template version
├── qbit # Dependency management (QQQ modules)
│ ├── search <query> # Search registry for qBits
│ ├── add <pkg>[@ver] # Add qBit to project
│ ├── remove <pkg> # Remove qBit
│ ├── update [pkg] # Update qBit(s)
│ ├── list # List installed qBits
│ └── resolve # Resolve and lock dependencies
├── qrun # Build, package, deploy
│ ├── package # Build OCI image via Jib
│ ├── publish # Push to registry, create release
│ ├── status # Check deployment status
│ └── logs # Stream application logs
├── qstudio # AI-assisted development
│ └── plan # Generate implementation plan
├── auth # Authentication
│ ├── login # Authenticate (device flow or API key)
│ ├── logout # Clear credentials
│ └── whoami # Show current identity
└── cache # Local cache management
├── ls # List cached items
├── prune # Remove old entries
└── clean # Clear entire cache
Module Architecture
┌─────────────────────────────────────────────────────────────────┐
│ qctl-cli │
│ (Native image aggregator, Main class) │
└─────────────────────────────────────────────────────────────────┘
│ │ │ │
▼ ▼ ▼ ▼
┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ qctl-qqq │ │ qctl-qbit │ │ qctl-qrun │ │qctl-qstudio │
│ Templates │ │ Packages │ │ Deploy │ │ AI Plan │
└─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘
│ │ │ │
└──────────────┴──────┬───────┴──────────────┘
▼
┌─────────────────────────────────────────────────────────────────┐
│ qctl-core │
│ Config, HTTP, Auth, Cache, Output, Logging, CLI framework │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ qctl-shared │
│ DTOs, SemVer, SPI, Utilities, Constants │
└─────────────────────────────────────────────────────────────────┘
Data Flow: qctl qqq init
User: qctl qqq init qqq-app my-project
1. Load config (defaults → global → project → env → flags)
2. Resolve template:
- Check local cache for "qqq-app" template
- If missing/outdated, fetch from templates-hub
- Verify signature (warn-only in V1)
3. Prompt for variables (or use --no-prompt defaults)
4. Evaluate computed variables (e.g., packagePath from packageName)
5. Render template via Apache Velocity:
- Process template.yaml manifest (schema v2)
- Substitute $variables in file content and paths
- Convert __VARNAME__ to $VARNAME in paths (CI-safe syntax)
- Apply transforms (delete patterns)
- Copy rendered files to target directory
6. Post-generation hooks (if defined)
7. Output success message with next steps
Configuration Precedence
Lowest ──────────────────────────────────────────────────► Highest
Built-in Global Config Project Config Env Vars CLI Flags
defaults ~/...qctl.yaml ./qctl.yaml QCTL_* --flag
Exit Codes
| Code | Meaning | When |
|---|---|---|
| 0 | Success | Operation completed |
| 1 | Generic error | Unexpected failure |
| 2 | Usage/config error | Invalid arguments or config |
| 3 | Network error | Connection failed, timeout |
| 4 | Auth error | 401/403 from API |
| 5 | Not found | 404 from API |
| 6 | Validation error | 400/422, bad input |
| 7 | Integrity error | Hash/signature mismatch |
| 8 | Conflict | 409, state conflict |
| 9 | Cancelled | User interrupted (Ctrl+C) |
V1 Scope (Current Target)
Included:
qqq newfrom local and HTTPS templatesqbitsearch/add/list/update/remove with lockfileqrun package(Jib) andpublishto registryqstudio plan(offline, writes plan.md)- Config loading, validation, profiles
- Native builds for macOS/Linux; jlink for Windows
Deferred (Post-V1):
- Sigstore enforcement (currently warn-only)
- Plugin system
- Self-update mechanism
qstudiodiff apply (AI code changes)- Streaming logs
- Promote/rollback workflows
Template Structure (new-qqq-application)
templates/new-qqq-application/
├── template.yaml # Manifest with prompts, computed vars, transforms
├── template/ # Template files directory
│ ├── README.md # Velocity template with $variables
│ ├── pom.xml # Maven POM with $groupId, $artifactId, etc.
│ └── src/
│ └── main/
│ └── java/
│ └── $packagePath/ # Path variable substitution
│ └── Application.java # Uses $packageName, $projectName
template.yaml Schema v2:
schemaVersion: 2
id: new-qqq-application
name: New QQQ Application
version: 1.0.0
prompts:
- name: packageName
message: Java package name
type: text
defaultValue: com.example
required: true
computed:
- name: packagePath
expression: "$str.replace($packageName, '.', '/')"
transforms:
- type: delete
pattern: "**/.gitkeep"
Key Files
qctl.yaml- Project and global configurationqbits.lock- Locked dependency versions (JSON)vendor/qbits/- Vendored qBit packages.qctl/- Project-local qctl state
Technology Stack
- Language: Java 21 (Temurin/GraalVM)
- CLI: Picocli 4.7.5
- Config: Jackson YAML + JSON Schema validation
- HTTP: JDK HttpClient with retry/backoff
- Templates: Apache Velocity 2.4.1
- Build: Maven 3.9+, GraalVM native-image
- Test: JUnit 5, AssertJ, Mockito, Testcontainers
Testing Architecture
qctl-integration-tests/
├── src/test/java/io/qrun/qctl/e2e/
│ ├── InitCommandE2ETest.java # CLI flag tests
│ ├── InitCommandFullE2ETest.java # Full variable/transform tests
│ └── harness/
│ ├── CommandTestHarness.java # In-process command execution
│ ├── TestTemplateRegistry.java # Fixture-based registry
│ └── TestableConsoleUI.java # Simulated user input
└── src/test/resources/fixtures/templates/
├── test-minimal/ # Basic template
├── test-computed-vars/ # Computed variable tests
├── test-full-substitution/ # Variable replacement tests
└── test-transforms/ # Transform operation tests
Path Variable Convention: Use __VARNAME__ in directory names for templates. The engine converts this to $VARNAME before Velocity processing, avoiding shell/Maven interpretation issues.