For AI tools (Claude Code, GitHub Copilot, etc.) and human contributors. These rules apply to ALL files and ALL requests.
dart pub get # Install dependencies
dart analyze # Static analysis
dart test # Run test suite
dart run bin/webtrit_phone_tools.dart --help # Run CLI locally- No Cyrillic — strictly prohibited everywhere: source files, comments, strings, logs, identifiers, JSON/YAML keys, and commit messages. English only.
- Self-documenting code — code must be clean and self-explanatory. Do not write comments to describe logic or use them as visual separators. Use DartDoc strictly for public APIs.
- No conversational filler — output only commit-ready code without unnecessary explanations.
- Branch naming —
feature/*,refactor/*,fix/*,chore/*,build/*,style/*,docs/*, orrelease/*. - Commit messages — Conventional Commits format:
feat(keystore): add verify command,fix: resolve crash on missing config.
- Page width: 120 characters (strict).
- Quote style: single quotes (
prefer_single_quotes: true). - Callbacks: must be single-expression only. If a callback requires multiple statements, extract the logic into a private method.
- Parameters: required named parameters MUST always be declared before optional named parameters.
- Doubles: avoid unnecessary
.0literals. - No dead code: remove unused variables, imports, and commented-out code before committing.
- Avoid
dynamic: use explicit types wherever possible. Only usedynamicorObject?when genuinely necessary (e.g., JSON parsing boundaries).
Groups separated by exactly one blank line, sorted alphabetically within each group. Omit any section if empty. No section comments.
// 1. Dart SDK
import 'dart:async';
import 'dart:io';
// 2. External dependencies
import 'package:args/command_runner.dart';
import 'package:mason_logger/mason_logger.dart';
// 3. Internal/project package imports
import 'package:webtrit_phone_tools/src/commands/commands.dart';
// 4. Relative imports
import '../models/models.dart';- Each directory with multiple files must export its contents via a
<directory_name>.dartbarrel. - Barrel files list exports in alphabetical order.
- Do not re-export symbols that are purely internal implementation details.
- Use the injected
Loggerinstance (frommason_logger) for all terminal output. Never useprint()orstdout.write()directly. - Log levels:
logger.info()— progress/status updateslogger.success()— completion messageslogger.err()— error messages visible to the userlogger.detail()— verbose/debug outputlogger.progress()— spinner for long-running operations (always call.complete()or.fail())
- Exit codes: commands must return
ExitCode.success.code(0) orExitCode.software.code(1) fromrun(). Never callexit()directly from within a command. - Exceptions: catch specific exception types, not bare
catch (e). Always log before rethrowing.
- Branch prefixes:
feature/,refactor/,fix/,chore/,build/,style/,docs/,release/. - Commit types:
feat,fix,chore,refactor,test,docs,style,ci,perf,build,revert. - Example:
feat(keystore): add init command.
- All leaf commands extend
Command<int>. - Sub-command groups extend
Command<int>and register children viaaddSubcommand(...). run()always returnsFuture<int>orint.- All top-level commands are registered in
WebtritPhoneToolsCommandRunnerviaaddCommand(...).
- Prefer named options over positional args for all non-trivial inputs.
- Use
argParser.addOption()withmandatory: truefor required inputs. - Provide sensible
defaultsTovalues for optional flags. - Every
addOption()andaddFlag()must include a descriptivehelp:string. - Always resolve
argResultsinsiderun(), not in the constructor.
- Validate all user-provided inputs (file paths, URLs, identifiers) at the beginning of
run()before any I/O or process spawning. - Log a clear error via
logger.err()and returnExitCode.usage.codeon invalid input.
- Commands receive all external dependencies (
Logger,ConfiguratorBackandDatasource,HttpClient) via their constructor. - Store as
finalprivate fields (e.g.,final Logger _logger). - The
CommandRunnerconstructs and injects all dependencies.
- Lowercase, hyphen-separated:
configurator-setup,configurator-resources. descriptionandsummarymust be set on every command for--helpoutput.
- Every command has a corresponding test file in
test/src/commands/. - Tests use
mocktailto mockLogger,PubUpdater, datasource, and other injected dependencies. - Test end-to-end: construct
WebtritPhoneToolsCommandRunnerwith mocked deps, invokerun(['command', '--flag', 'value']), and assert on exit code and logger interactions. - Use
setUp/tearDownfor lifecycle management.
- Simple commands (single responsibility, minimal I/O): implement logic directly in the
Commandsubclass without subdirectories. Examples:update_command.dart - Complex commands (multi-step: API + file I/O + external processes): use the
Orchestrator Pattern described in section 8 below.
Examples:
app_resources/,app_configure/,app_setup/
| Layer | Naming | Responsibility |
|---|---|---|
| Command | <Name>Command |
Parses CLI args, validates input, builds Context, delegates to layers below |
| Context | <Name>Context |
Immutable validated state; passed between all layers |
| Service | <Name>Fetcher / <Name>Service |
Fetches remote data (API calls); pure I/O, no business logic |
| Processor | <Name>Processor |
Transforms/writes data (file I/O, JSON manipulation, asset downloads) |
| Runner | <Name>Runner |
Executes external processes (flutter, dart, keytool, git) |
| Factory/Util | <Name>Factory / <Name>Util |
Pure stateless helpers; no side effects |
- Context is immutable — never mutate a
Contextafter creation. Pass additional data explicitly. - No cross-layer skipping —
Commandmust not call aRunnerdirectly without going through aProcessororServicewhen applicable. - Services are pure fetchers — no file I/O or process spawning; only remote API calls.
- Processors own file I/O — all file reading/writing lives in
Processorclasses only. - Runners own process spawning — all
Process.start()/Process.runSync()calls live inRunnerclasses only.
lib/src/commands/<feature_name>/
├── <feature_name>.dart # Barrel export
├── <name>_command.dart # Main command class
├── models/
│ ├── models.dart
│ └── <name>_context.dart
├── services/ # optional — only if API calls are needed
│ ├── services.dart
│ └── <name>_fetcher.dart
├── processors/ # optional — only if file I/O is needed
│ ├── processors.dart
│ └── <name>_processor.dart
├── runners/ # optional — only if external processes are needed
│ ├── runners.dart
│ └── <name>_runner.dart
├── interceptors/ # optional — only if Dio interceptors are needed
│ ├── interceptors.dart
│ └── <name>_interceptor.dart
└── utils/ # optional — only if feature-specific helpers are needed
├── utils.dart
└── <name>_util.dart
- Use
Process.start()for long-running processes that stream output (e.g.,flutter build). - Use
Process.runSync()for short blocking operations (e.g.,keytool -list). - Always check exit code and log errors before propagating failures.
- Never hardcode absolute binary paths; rely on the system
PATH.
- All HTTP interactions with the WebTrit Configurator backend use
ConfiguratorBackandDatasourcewrapped with Dio. - Retry logic (exponential backoff, max 3 retries) for 5xx errors and connection timeouts must be
implemented as a Dio
Interceptorsubclass insideinterceptors/. - Fast-fail (do not retry) on 4xx client errors, especially 401 Unauthorized.
- Extension classes live in
lib/src/extension/. - Each file contains exactly one extension, named
<type>_extension.dart. - Extensions must be pure (no side effects, no I/O).
- All extensions are exported via
lib/src/extension/extension.dart.
| Dependency | Version | Purpose |
|---|---|---|
args |
^2.7.0 | CLI argument parsing |
cli_completion |
^0.5.1 | Shell completion support |
mason_logger |
^0.3.3 | Terminal output with colors/spinners |
path |
^1.9.1 | Cross-platform path handling |
pub_updater |
^0.5.0 | Check for pub.dev updates |
http |
^1.6.0 | Simple GET requests |
archive |
^4.0.7 | ZIP/archive handling |
yaml |
^3.1.3 | YAML parsing |
data (local) |
path: … | Configurator API datasource (Dio-based) |
test |
^1.25.15 | Unit/integration tests |
mocktail |
^1.0.4 | Mocking in tests |