Tasks: pc-init CLI Scaffold#
Input: Design documents from /specs/001-pc-init-cli/
Prerequisites: plan.md (required), spec.md (required for user stories), research.md, data-model.md, contracts/
Tests: Test tasks are REQUIRED.
For every behavior change, tests MUST be written first and fail before implementation.
Organization: Tasks are grouped by user story to enable independent implementation and testing of each story.
Format: [ID] [P?] [Story] Description#
- [P]: Can run in parallel (different files, no dependencies)
- [Story]: Which user story this task belongs to (e.g., US1, US2, US3)
- Include exact file paths in descriptions
Path Conventions#
- Single project structure:
gpc_init/for modules,tests/for test suite - Presets:
lang/andframework/directories at repository root
Phase 1: Setup (Shared Infrastructure)#
Purpose: Project initialization and basic structure
- T001 Create
gpc_init/package with__init__.pyand type stubs - T002 Create
tests/directory structure withunit/andintegration/subdirectories - T003 Create
tests/conftest.pywith pytest fixtures for preset loading and temp directories - T004 Create
tests/fixtures/directory with minimal YAML preset samples for testing - T005 Configure
pyproject.tomlto enable pytest discovery and pyrefly strict checking - T006 Create
.github/workflows/or equivalent CI configuration to run tests and type checks on PR
Phase 2: Foundational (Blocking Prerequisites)#
Purpose: Core loading, merging, and rendering infrastructure for all user stories
Profile Loading and Type Definitions#
- T007 Define
gpc_init/profiles.pydata classes:LanguageProfile,FrameworkProfile,RepoConfig,HookConfig,GenerationRequest,GenerationResultwith type annotations - T008 Create
tests/unit/test_profiles.pywith unit tests for profile entity validation (missing repos, invalid hook ids, etc.) - T009 Implement
gpc_init/profiles.pydata classes with field validation and immutability guards
Preset Loader#
- T010 [P] Write
tests/unit/test_loader.pywith tests for: discoveringlang/<lang>/baseline.yaml,framework/<fw>/preset.yaml,lang/common/default.yaml; handling missing presets; parsing invalid YAML - T011 [P] Write
tests/unit/test_loader.pyadditional tests for: loading preset as Python dict, preserving hook order, detecting repos key - T012 Implement
gpc_init/loader.pywithload_language_preset(lang_id),load_framework_preset(framework_id),load_common_preset()functions; return raw dicts with validation - T013 Implement
gpc_init/loader.pyerror handling: raisePresetNotFoundErrorfor missing files,PresetParseErrorfor invalid YAML
Preset Merger#
- T014 [P] Write
tests/unit/test_merger.pywith tests for: merging common + single language preset; preserving repo order and hook order - T015 [P] Write
tests/unit/test_merger.pytests for: merging multiple languages in CLI order; merging frameworks on top of languages; handling duplicate (repo, rev) pairs with hook merging by id - T016 [P] Write
tests/unit/test_merger.pytests for: replacing hook fields when id matches in higher layer; appending new hook ids; deep-merging top-level keys likedefault_language_version; deterministic output - T017 Implement
gpc_init/merger.pywithmerge_presets(common, langs, frameworks)function; enforce exact merge order and semantics per plan.md - T018 Implement
gpc_init/merger.pyhook merging logic:_merge_repos_list(lower, higher)and_merge_hook(lower, higher)internal functions with stable ordering
YAML Renderer#
- T019 [P] Write
tests/unit/test_renderer.pywith tests for: rendering merged dict to valid YAML string; preserving key order for determinism; rendering repos, hooks, args arrays correctly - T020 [P] Write
tests/unit/test_renderer.pytests for: renderingNonevalues properly; handling special YAML characters in args/ids; two identical inputs yield identical YAML strings - T021 Implement
gpc_init/renderer.pywithrender_yaml(merged_dict, sort_keys=True)function; useyaml.dump()with deterministic settings (default_flow_style=False, sort_keys=True, width=large) - T022 Implement
gpc_init/renderer.pyto ensure YAML parsing round-trip produces semantically identical dict (test withyaml.safe_load())
Phase 3: User Story 1 - Generate Base Configuration (P1)#
Purpose: Enable pc-init --lang=<language> to generate baseline .pre-commit-config.yaml
CLI Interface with typer#
- T023 [P] Write
tests/integration/test_cli_generation.pywith tests for:pc-init --lang=pyin clean directory creates.pre-commit-config.yaml; exit code is 0; file is valid YAML - T024 [P] Write
tests/integration/test_cli_generation.pytests for:pc-init --lang=py --lang=jsmerges both baselines in correct order; exit code is 0 - T025 [P] Write
tests/integration/test_cli_generation.pytests for: help text shows--langas required, repeatable; shows--frameworkas optional; shows--forceas optional flag - T026 Implement
gpc_init/cli.pyas typer app with@typer.command()onmain()function acceptinglang: list[str](required),framework: list[str] = None,force: bool = False,target_path: str = ".pre-commit-config.yaml" - T027 [P] [US1] Implement
gpc_init/cli.pyargument normalization: lowercase all lang/framework values; deduplicate preserving first-occurrence order; validate non-empty langs after dedup - T028 [P] [US1] Implement
gpc_init/cli.pyto output success message format:"Generated .pre-commit-config.yaml with languages: <lang1, lang2> and frameworks: <fw1, fw2>"or similar
Request Resolver#
- T029 [P] Write
tests/unit/test_resolver.pywith tests for: validating lang exists inlang/*/baseline.yamlcatalog; validating framework exists inframework/*/preset.yamlcatalog; raisingUnsupportedLanguageErrorwith list of supported options - T030 [P] Write
tests/unit/test_resolver.pytests for: raisingUnsupportedFrameworkErrorwith list of supported options - T031 [P] Implement
gpc_init/resolver.pywithvalidate_and_resolve(request)function: discover supported languages fromlang/directory; discover supported frameworks fromframework/directory; validate request against catalogs - T032 [P] Implement
gpc_init/resolver.pycatalog discovery to scanlang/<lang>/baseline.yamlandframework/<fw>/preset.yamlfiles at startup; cache catalogs; exposeget_supported_languages()andget_supported_frameworks()
Generation Flow Integration#
- T033 Write
tests/integration/test_cli_generation.pyend-to-end test:pc-init --lang=pyin temp dir → produces file → is valid pre-commit YAML → contains Python hooks - T034 [P] [US1] Implement
gpc_init/cli.pymain function to: parse args → validate with resolver → load common + lang presets → merge → render → write to file → report success - T035 [P] [US1] Implement
gpc_init/cli.pyto invoke loader, merger, resolver, renderer in sequence with error propagation; catch exceptions and output actionable error messages to stderr
Phase 4: User Story 2 - Apply Framework-Specific Enhancements (P2)#
Purpose: Enable pc-init --lang=<lang> --framework=<fw> to merge framework presets
Framework Support#
- T036 [P] Write
tests/unit/test_merger.pyadditional tests for: merging framework presets on top of language baselines; framework hooks appended after language hooks; framework args override language args for same hook id - T037 [P] Write
tests/integration/test_cli_generation.pytests for:pc-init --lang=js --framework=reactproduces output with JS hooks + React hooks; exit code is 0 - T038 [P] Write
tests/integration/test_cli_generation.pytests for:pc-init --lang=py --lang=js --framework=django --framework=reactmerges in deterministic order (common → py → js → django → react) - T039 Write
tests/integration/test_cli_generation.pytest for Scenario B2 from quickstart:pc-init --lang=py --lang=js --framework=django --framework=reactproduces valid config with all four contributions - T040 [P] [US2] Populate
framework/react/preset.yamlwith React-specific hooks andprimary_languages: [js]metadata (informational only) - T041 [P] [US2] Populate
framework/bevy/preset.yamlwith Bevy-specific hooks andprimary_languages: [ru]metadata (informational only) - T042 [P] [US2] Implement framework merging in
gpc_init/merger.pyto handle optional frameworks; framework presets are purely additive
Framework Validation (Informational Only)#
- T043 [P] [US2] Implement
gpc_init/resolver.pyto note but not enforceprimary_languagesfrom framework presets; output informational message if user-selected languages don't match any framework's primary languages (non-blocking, informational only) - T044 [P] [US2] Write
tests/integration/test_cli_generation.pytest for:pc-init --lang=go --framework=reactsucceeds (no validation error); produces config with Go baseline + React additions (even though Go not in React's primary_languages)
Phase 5: User Story 3 - Safe and Clear CLI Behavior (P3)#
Purpose: Implement error messages, overwrite behavior, and file safety
Error Handling#
- T045 [P] Write
tests/integration/test_cli_generation.pytests for:pc-init --lang=unsupported-langfails with exit code non-zero; stderr includes list of supported languages - T046 [P] Write
tests/integration/test_cli_generation.pytests for:pc-init --lang=py --framework=unsupported-fwfails with exit code non-zero; stderr includes list of supported frameworks - T047 [P] Implement
gpc_init/cli.pyerror handling to catchUnsupportedLanguageErrorandUnsupportedFrameworkError; format as"Error: unsupported language 'xxx'. Supported: py, js, go, ru"and similar for frameworks - T048 [P] Implement
gpc_init/cli.pyerror handling for YAML parse errors; output"Error: failed to parse preset YAML: <detail>"
Overwrite Behavior#
- T049 [P] Write
tests/integration/test_cli_generation.pytests for: file exists,pc-init --lang=py(no force) exits non-zero; file unchanged; stderr tells user to use--force - T050 [P] Write
tests/integration/test_cli_generation.pytests for: file exists,pc-init --lang=py --forceexits 0; file overwritten; stdout confirms overwrite - T051 [P] Implement
gpc_init/cli.pyto check file existence before write; if exists and notforce, raiseFileExistsErrorwith guidance; ifforce, proceed with write - T052 [P] Implement
gpc_init/cli.pyto track and reportoverwritten: Truein success message when--forceused and file existed
Argument Normalization#
- T053 [P] Write
tests/integration/test_cli_generation.pytests for:pc-init --lang=py --lang=pythonnormalizes alias and deduplicates to a singlepy;pc-init --lang=py --lang=pysingle result - T054 [P] Write
tests/integration/test_cli_generation.pytests for: mixed case--lang=Pythonnormalized to canonicalpy - T055 [P] [US3] Implement
gpc_init/cli.pyargument normalization in resolver: deduplicate--langand--frameworkvalues preserving first occurrence; convert to lowercase; resolve aliases to canonical ids; validate after normalization
File Permission Handling#
- T056 [P] Write
tests/integration/test_cli_generation.pytests for: target directory not writable → exit non-zero; stderr includes path and permission error detail - T057 [P] [US3] Implement
gpc_init/cli.pytry-catch around file write; catchPermissionError,OSError; output"Error: cannot write to <path>: <detail>"
Phase 6: Polish & Cross-Cutting Concerns#
Purpose: Type safety, comprehensive testing, documentation, and deployment readiness
Type Safety & Code Quality#
- T058 Implement complete type annotations across all modules:
gpc_init/profiles.py,gpc_init/loader.py,gpc_init/merger.py,gpc_init/renderer.py,gpc_init/resolver.py,gpc_init/cli.py - T059 Run
uv run pyrefly --stricton all modules; resolve all type errors and warnings; ensure strict mode passes with no exceptions - T060 [P] [US1] [US2] [US3] Add docstrings to all public functions and classes explaining purpose, arguments, return values, exceptions
- T061 [P] Implement custom exceptions:
PresetNotFoundError,PresetParseError,UnsupportedLanguageError,UnsupportedFrameworkError,FileExistsErroringpc_init/exceptions.py
Comprehensive Testing#
- T062 Write
tests/integration/test_cli_scenarios.pycovering all quickstart scenarios: A (baseline), B (framework), B2 (polyglot), D (existing file), E (force), with fixture setup - T063 [P] Write
tests/integration/test_determinism.pyto verify: same input twice produces identical output file; different language orders produce different but valid outputs; framework order matters for output order - T064 [P] Add edge case tests to
tests/integration/: empty framework list, single language, three+ languages, three+ frameworks, all combinations - T065 Verify full test coverage with
pytest --cov=gpc_init --cov-report=term-missing; aim for >= 90% coverage
Build & Deployment#
- T066 Update
main.pyor add entry point topyproject.tomlto invokegpc_init.cli.main()when runningpc-initcommand - T067 Test
uv run pc-init --helpdisplays typer help with all flags and examples - T068 Update
README.mdwith usage examples:pc-init --lang=py,pc-init --lang=js --framework=react, error case explanations - T069 Add quickstart validation script or CI check to run all Scenario A-E tests from
specs/001-pc-init-cli/quickstart.md
Code Review & Documentation#
- T070 Review
gpc_init/merger.pymerge order implementation against plan.md Merge Semantics section; verify all hook replacement and append logic is correct - T071 Review
gpc_init/resolver.pycatalog discovery to ensure it is efficient and deterministic (filesystem order neutral) - T072 Document preset structure and extension patterns in
PRESET_DEVELOPMENT.mdfor future language/framework additions
Dependency Graph#
User Story Completion Order#
- US1 (P1 - MVP): Can run independently.
Prerequisite: Phase 2 (foundational loader/merger/renderer). - US2 (P2): Depends on US1.
Adds framework support on top of working language baseline. - US3 (P3): Depends on US1.
Adds safety checks and error messaging.
Suggested MVP Scope#
- MVP Release: Complete Phase 1, Phase 2, Phase 3 (US1 only)
- Follow-up Release: Phase 4 (US2)
- Final Release: Phase 5 (US3) + Phase 6 (Polish)
Parallel Execution Opportunities#
Phase 2 Parallelization (after Phase 1):
- T007-T009 (profiles) can run in parallel with T010-T013 (loader)
- T014-T018 (merger tests + impl) can run in parallel with T019-T022 (renderer) once profiles are done
Phase 3 Parallelization (after Phase 2):
- T023-T025 (CLI tests) can run in parallel with T029-T032 (resolver)
- T026-T028 (CLI impl) depends on both CLI tests and resolver; proceed after both are drafted
Phase 4 Parallelization (after Phase 3):
- T036-T044 (framework tests + impl) can all run in parallel; no inter-dependencies
Phase 5 Parallelization (after Phase 3):
- T045-T048 (error handling) and T049-T057 (overwrite + file ops) can run in parallel
Implementation Strategy#
- Red-Green-Refactor TDD: Write test task first (T0XX with description ending in "write tests"), watch fail, implement (T0XX with "implement"), watch pass, refactor as needed.
- Type-Safe From Start: Add type annotations as part of each implementation task; do not defer to Phase 6.
- Determinism Verification: After each merge/render task, manually verify identical inputs produce identical output using
diff -u. - Error Messages First: Before implementing a feature, decide error messages (per CLI contract); tests should verify message content.
- Incremental Integration: After Phase 3 (US1), pause and run full quickstart scenario A; debug before proceeding to Phase 4.
Testing Requirements Summary#
- Unit tests (test_*.py files): Profile validation, loader, merger, renderer, resolver logic in isolation.
- Integration tests (tests/integration/): CLI end-to-end with real preset files and filesystem I/O.
- Determinism tests: Verify identical inputs → identical YAML output.
- Error case tests: Unsupported lang, unsupported fw, file exists, permission denied, invalid YAML, etc.
- Quickstart validation: Run all Scenario A-E commands and verify expected outcomes.
Total test count target: 40+ tests (unit + integration) covering all user stories and error cases.
Files to Create/Modify#
Created#
gpc_init/__init__.pygpc_init/profiles.pygpc_init/loader.pygpc_init/merger.pygpc_init/renderer.pygpc_init/resolver.pygpc_init/cli.pygpc_init/exceptions.pytests/__init__.pytests/conftest.pytests/fixtures/(preset samples)tests/unit/test_profiles.pytests/unit/test_loader.pytests/unit/test_merger.pytests/unit/test_renderer.pytests/unit/test_resolver.pytests/integration/test_cli_generation.pytests/integration/test_cli_scenarios.pytests/integration/test_determinism.pyframework/react/preset.yamlframework/bevy/preset.yamlPRESET_DEVELOPMENT.md
Modified#
main.py(add entry point)pyproject.toml(ensure pytest, pyrefly configured)README.md(add usage examples)