wade-cli 1.1.1__tar.gz → 1.1.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {wade_cli-1.1.1 → wade_cli-1.1.2}/AGENTS.md +7 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/CHANGELOG.md +6 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/PKG-INFO +19 -2
- {wade_cli-1.1.1 → wade_cli-1.1.2}/README.md +18 -1
- {wade_cli-1.1.1 → wade_cli-1.1.2}/docs/dev/architecture.md +18 -1
- {wade_cli-1.1.1 → wade_cli-1.1.2}/docs/dev/workflows-and-skills.md +27 -1
- {wade_cli-1.1.1 → wade_cli-1.1.2}/pyproject.toml +1 -1
- {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/__init__.py +1 -1
- {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/git/repo.py +12 -0
- wade_cli-1.1.2/src/wade/models/review_cycle.py +69 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/models/workflow.py +4 -4
- {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/services/implementation_service/done.py +13 -0
- wade_cli-1.1.2/src/wade/services/review_cycle_service.py +192 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/services/review_delegation_service.py +212 -16
- {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/services/review_record_service.py +45 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/services/review_service.py +108 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/skills/materializer.py +9 -3
- {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/utils/safe_state.py +46 -0
- wade_cli-1.1.2/templates/prompts/review-code.md +9 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/templates/workflows/implementation.md +3 -3
- {wade_cli-1.1.1 → wade_cli-1.1.2}/templates/workflows/review-pr-comments.md +3 -3
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/integration/test_skill_install.py +18 -1
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_services/test_done_gates.py +2 -2
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_services/test_done_idempotent.py +26 -6
- wade_cli-1.1.2/tests/unit/test_services/test_review_cycle_service.py +235 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_services/test_review_delegation_service.py +173 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_services/test_review_records.py +82 -3
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_services/test_review_status_block.py +2 -2
- wade_cli-1.1.1/templates/prompts/review-code.md +0 -7
- {wade_cli-1.1.1 → wade_cli-1.1.2}/.gitattributes +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/.github/workflows/auto-version.yml +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/.github/workflows/ci.yml +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/.github/workflows/pr-title-lint.yml +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/.github/workflows/publish.yml +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/.github/workflows/release.yml +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/.gitignore +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/.pre-commit-config.yaml +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/.wade.yml +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/CONTRIBUTING.md +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/KNOWLEDGE.md +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/KNOWLEDGE.ratings.jsonl +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/assets/wade.png +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/docker-compose.e2e.yml +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/docs/dev/documentation-policies.md +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/docs/dev/extending.md +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/docs/dev/skills-system.md +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/docs/dev/testing.md +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/install.sh +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/scripts/auto_version.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/scripts/changelog.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/scripts/check-all.sh +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/scripts/check.sh +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/scripts/fmt.sh +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/scripts/hooks/pre-push +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/scripts/install-hooks.sh +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/scripts/release.sh +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/scripts/setup-worktree.sh +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/scripts/test-concurrency.sh +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/scripts/test-e2e-docker.sh +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/scripts/test-e2e.sh +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/scripts/test-live-ai-taskr.sh +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/scripts/test-live-ai.sh +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/scripts/test-live-gh.sh +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/scripts/test.sh +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/__main__.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/cli/__init__.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/cli/admin.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/cli/autocomplete.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/cli/deps_session.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/cli/hook.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/cli/implementation_session.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/cli/knowledge.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/cli/main.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/cli/plan_session.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/cli/review.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/cli/review_pr_comments_session.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/cli/session.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/cli/session_shared.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/cli/skills.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/cli/task.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/cli/worktree.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/config/__init__.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/config/loader.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/config/migrations.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/config/schema.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/git/__init__.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/git/branch.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/git/hooks.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/git/pr.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/git/stash.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/git/sync.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/git/worktree.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/hooks/__init__.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/hooks/cli.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/hooks/policies.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/logging/__init__.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/logging/context.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/logging/setup.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/models/__init__.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/models/batch.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/models/config.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/models/delegation.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/models/deps.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/models/events.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/models/hooks.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/models/permission.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/models/readiness.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/models/review.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/models/session.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/models/session_manifest.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/models/skill.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/models/task.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/models/worktree.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/providers/__init__.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/providers/_pr_delegate.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/providers/base.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/providers/clickup.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/providers/github.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/providers/markdown.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/providers/registry.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/services/__init__.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/services/ai_resolution.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/services/batch_review_service.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/services/bot_trigger.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/services/check_service.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/services/delegation_service.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/services/deps_service.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/services/documentation_receipt_service.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/services/implementation_service/__init__.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/services/implementation_service/_shared.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/services/implementation_service/batch.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/services/implementation_service/bootstrap.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/services/implementation_service/cleanup.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/services/implementation_service/core.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/services/implementation_service/draft_pr.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/services/implementation_service/lifecycle.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/services/implementation_service/sync.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/services/implementation_service/usage_tracking.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/services/init_service/__init__.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/services/init_service/auth.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/services/init_service/commands.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/services/init_service/config_io.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/services/init_service/manifest.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/services/init_service/migrations.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/services/init_service/prompts_ai.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/services/init_service/prompts_setup.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/services/init_service/shell.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/services/knowledge_recovery.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/services/knowledge_search.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/services/knowledge_service.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/services/plan_service.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/services/prompt_delivery.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/services/review_settle.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/services/session_composition_service.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/services/skill_diagnostics_service.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/services/skill_invocation_service.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/services/smart_start.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/services/task_service.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/skills/__init__.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/skills/catalog.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/skills/discovery.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/skills/doc_targets.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/skills/installer.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/skills/pointer.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/skills/resolver.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/skills/validation.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/ui/__init__.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/ui/console.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/ui/prompts.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/utils/__init__.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/utils/body_markers.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/utils/clipboard.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/utils/conventional.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/utils/filelock.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/utils/gitref.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/utils/http.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/utils/install.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/utils/knowledge_file.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/utils/markdown.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/utils/markers.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/utils/paths.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/utils/plan_validation.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/utils/process.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/utils/runtime_env.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/utils/slug.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/utils/stale_base.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/utils/templates.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/utils/terminal.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/utils/token_usage_markdown.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/src/wade/utils/update_check.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/templates/agents-pointer.md +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/templates/hooks/commit-msg +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/templates/hooks/pre-commit +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/templates/hooks/pre-push +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/templates/prompts/deps-analysis.md +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/templates/prompts/implement-context.md +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/templates/prompts/plan-session.md +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/templates/prompts/review-batch.md +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/templates/prompts/review-plan.md +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/templates/prompts/review-pr-comments.md +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/templates/prompts/session-start-implement.md +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/templates/prompts/session-start-plan.md +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/templates/prompts/session-start-review.md +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/templates/setup-worktree.sh.example +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/templates/skills/batch-review/SKILL.md +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/templates/skills/code-review/SKILL.md +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/templates/skills/dependency-analysis/SKILL.md +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/templates/skills/implementation/SKILL.md +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/templates/skills/knowledge/SKILL.md +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/templates/skills/plan-review/SKILL.md +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/templates/skills/planning/SKILL.md +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/templates/skills/review-comments/SKILL.md +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/templates/skills/task/SKILL.md +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/templates/skills/task/examples.md +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/templates/skills/task/plan-format.md +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/templates/skills/task/reference/session-summary-format.md +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/templates/statusline-command.sh +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/templates/workflows/_partials/completion.md +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/templates/workflows/_partials/documentation-step.md +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/templates/workflows/_partials/interaction-policy.md +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/templates/workflows/_partials/knowledge-step.md +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/templates/workflows/_partials/review-budget.md +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/templates/workflows/plan.md +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/templates/workflows/reference/documentation.md +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/templates/workflows/reference/new-plan.md +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/templates/workflows/reference/plan-output-contract.md +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/templates/workflows/reference/pr-summary-format.md +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/templates/workflows/reference/recovery.md +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/templates/workflows/reference/session-summary-format.md +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/templates/workflows/reference/tracking-issues.md +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/__init__.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/concurrency/__init__.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/concurrency/test_batch_multi_pr.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/concurrency/test_knowledge_merge.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/concurrency/test_lock_retry.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/concurrency/test_stash_race.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/conftest.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/e2e/__init__.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/e2e/_support.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/e2e/conftest.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/e2e/mock_gh_script.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/e2e/test_admin_contract.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/e2e/test_check_contract.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/e2e/test_implement_work_done_contract.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/e2e/test_knowledge_contract.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/e2e/test_mock_gh_contract.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/e2e/test_plan_contract.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/e2e/test_review_contract.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/e2e/test_smart_start_contract.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/e2e/test_task_contract.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/e2e/test_work_sync_list_contract.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/fixtures/live/taskr_header_hi_body.md +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/fixtures/live/taskr_header_howdy_body.md +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/fixtures/transcripts/claude_session.txt +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/fixtures/transcripts/codex_session.txt +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/fixtures/transcripts/copilot_session.txt +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/fixtures/transcripts/generic_session.txt +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/integration/__init__.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/integration/test_autostash.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/integration/test_check.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/integration/test_dynamic_skill_sessions.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/integration/test_git.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/integration/test_implementation_lifecycle.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/integration/test_init.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/integration/test_knowledge.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/integration/test_markdown_provider.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/integration/test_skill_context_budget.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/live/__init__.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/live/test_wade_live_ai.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/live/test_wade_live_ai_taskr.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/live/test_wade_live_gh.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/test_cli_basics.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/__init__.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/conftest.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_cli/__init__.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_cli/test_autocomplete.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_cli/test_done_doc_pass_advisory.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_cli/test_done_review_hint.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_cli/test_knowledge_cli.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_cli/test_review_cli.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_cli/test_session_subapps.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_cli/test_shell_init.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_cli/test_skills_session_cli.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_cli/test_task_cli.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_cli/test_work_ai_flags.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_cli/test_work_cli.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_config/__init__.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_config/test_bot_review_config.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_config/test_dynamic_skill_config.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_config/test_loader.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_config/test_merge_strategy_migration.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_config/test_migrations.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_config/test_migrations_atomic.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_console.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_effort.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_git/__init__.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_git/test_all_patches_present.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_git/test_branch_list.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_git/test_branch_slugify.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_git/test_detached_worktree.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_git/test_is_merged_into.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_git/test_main_checkout_root.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_git/test_merge_count.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_git/test_pr_base.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_git/test_pr_lookup.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_git/test_pr_retry.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_git/test_pr_title.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_git/test_repo.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_git/test_reset_branch.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_git/test_scaffold_commit.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_git/test_stash_drop.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_git/test_sync_conflicted_files.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_git/test_tip_commit_is_empty.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_git/test_worktree_remove.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_hooks/__init__.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_hooks/test_hook_cli.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_hooks/test_policies.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_hooks/test_post_tool_use.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_hooks/test_session_start.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_models/__init__.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_models/test_batch.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_models/test_config_models.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_models/test_delegation.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_models/test_deps_models.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_models/test_deps_partition.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_models/test_review.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_models/test_review_expectation.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_models/test_task_models.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_models/test_workflow_models.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_permission_mode.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_providers/__init__.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_providers/test_clickup.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_providers/test_github.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_providers/test_github_pr.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_providers/test_markdown.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_scripts/__init__.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_scripts/test_changelog.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_services/__init__.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_services/test_ai_resolution.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_services/test_batch_build_cmd.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_services/test_batch_pr_index.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_services/test_batch_review_service.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_services/test_bootstrap_allowlist.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_services/test_bootstrap_guard_matcher.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_services/test_bootstrap_hook_timeout.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_services/test_bootstrap_post_tool_use.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_services/test_bootstrap_pre_push.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_services/test_bootstrap_session_start.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_services/test_bot_trigger.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_services/test_catchup.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_services/test_catchup_reconcile.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_services/test_check.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_services/test_cleanup_loss_guard.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_services/test_codex_worktree_launch_context.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_services/test_delegation_service.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_services/test_deps_service.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_services/test_documentation_receipt.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_services/test_done_plan_flag.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_services/test_done_pr_title_sync.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_services/test_draft_pr_base_fetch.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_services/test_draft_pr_retarget.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_services/test_implementation_done_sync.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_services/test_implementation_launch_context.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_services/test_implementation_service.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_services/test_implementation_service_resume.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_services/test_init.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_services/test_init_shell_integration.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_services/test_knowledge_recovery.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_services/test_knowledge_search.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_services/test_knowledge_service.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_services/test_managed_git_hooks.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_services/test_no_direct_git.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_services/test_plan_service.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_services/test_poll_for_reviews.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_services/test_post_implementation_lifecycle.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_services/test_pr_lookup_guards.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_services/test_pr_summary_path.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_services/test_pre_push_backstop.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_services/test_preserve_session_data.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_services/test_prompt_delivery.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_services/test_push_recovery.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_services/test_resolve_task_branch.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_services/test_resume_autonomy.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_services/test_review_receipts.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_services/test_review_service.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_services/test_skill_diagnostics_service.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_services/test_smart_start.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_services/test_smart_start_resume.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_services/test_stale_base_surface.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_services/test_sync_base_branch.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_services/test_task_service.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_services/test_tracked_managed_files.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_services/test_worktree_gitignore.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_skills/test_crossby_contract.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_skills/test_doc_targets.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_skills/test_dynamic_skills.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_skills/test_generic_builtin_boundaries.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_skills/test_knowledge_attributes.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_skills/test_managed_gitignore.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_skills/test_pointer.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_skills/test_review_budget_partial.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_utils/__init__.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_utils/test_body_markers.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_utils/test_conventional.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_utils/test_filelock.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_utils/test_gitref.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_utils/test_http.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_utils/test_install.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_utils/test_markdown.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_utils/test_markers.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_utils/test_plan_validation.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_utils/test_process.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_utils/test_prompts.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_utils/test_runtime_env.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_utils/test_slug.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_utils/test_stale_base.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_utils/test_terminal.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_utils/test_terminal_launch.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_utils/test_update_check.py +0 -0
- {wade_cli-1.1.1 → wade_cli-1.1.2}/tests/unit/test_yolo.py +0 -0
|
@@ -72,6 +72,13 @@ No circular dependencies. Models are pure data. Services orchestrate. **Never im
|
|
|
72
72
|
> GitHub (PR/issue body markers, labels) and worktree files, **not** SQLite. The
|
|
73
73
|
> code still exists but is inert; removal is tracked as #357 C5.
|
|
74
74
|
|
|
75
|
+
> **Closing-review state** lives in worktree files, not the immutable session
|
|
76
|
+
> bundle: binding-aware receipts are under `.wade/reviews/`, and an active
|
|
77
|
+
> PR-comment feedback cycle is under `.wade/review-cycles/`. The cycle preserves
|
|
78
|
+
> its pre-edit baseline and feedback history across refreshes and is cleared
|
|
79
|
+
> only after a successful PR-comment completion; unsafe state must fall back to
|
|
80
|
+
> a full review.
|
|
81
|
+
|
|
75
82
|
> **Deterministic git-hook install/reconcile/build logic lives in `git/hooks.py`**
|
|
76
83
|
> (git layer), not `skills/installer.py`. Packaged template-asset loaders
|
|
77
84
|
> (prompt/skill/hook templates) live in `utils/templates.py`, a leaf module.
|
|
@@ -4,6 +4,12 @@ All notable changes to this project will be documented in this file.
|
|
|
4
4
|
|
|
5
5
|
The format is based on [Conventional Commits](https://conventionalcommits.org/).
|
|
6
6
|
|
|
7
|
+
## [v1.1.2] — 2026-09-08
|
|
8
|
+
|
|
9
|
+
### Bug Fixes
|
|
10
|
+
|
|
11
|
+
- make closing reviews converge on final incremental changes (#503) (5599fb1)
|
|
12
|
+
|
|
7
13
|
## [v1.1.1] — 2026-09-08
|
|
8
14
|
|
|
9
15
|
### Bug Fixes
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: wade-cli
|
|
3
|
-
Version: 1.1.
|
|
3
|
+
Version: 1.1.2
|
|
4
4
|
Summary: WADE — Workflow for AI-Driven Engineering
|
|
5
5
|
Project-URL: Homepage, https://github.com/ivanviragine/wade
|
|
6
6
|
Project-URL: Repository, https://github.com/ivanviragine/wade
|
|
@@ -325,7 +325,7 @@ WADE splits review into an **AI review pass** you or the agent can invoke, and
|
|
|
325
325
|
| Command | What it reviews |
|
|
326
326
|
|---------|-----------------|
|
|
327
327
|
| `wade review plan <file>` | A plan file, before it becomes tasks |
|
|
328
|
-
| `wade review implementation` | The current implementation
|
|
328
|
+
| `wade review implementation` | The current implementation change (mandatory gate before `done`) |
|
|
329
329
|
| `wade review batch <N>` | Coherence across parallel implementation branches |
|
|
330
330
|
| `wade review pr-comments <N>` | Starts a session to address human/bot PR comments |
|
|
331
331
|
| `wade review trigger <N>` | Posts configured bot-review trigger comments on the task's PR |
|
|
@@ -341,10 +341,27 @@ implementation` exits successfully without launching another reviewer and direct
|
|
|
341
341
|
you to `wade implementation-session done`. The dispatch cap does not block a
|
|
342
342
|
valid `--ack-self-review`, which still writes its active-binding receipt.
|
|
343
343
|
|
|
344
|
+
The first implementation review for a frozen reviewer binding receives the
|
|
345
|
+
complete branch change. After a successful review and a new fix commit, a
|
|
346
|
+
re-review receives only the committed delta since that successful same-binding
|
|
347
|
+
review, plus any staged or unstaged changes and the local `PLAN.md` when safely
|
|
348
|
+
available. This narrows repeated review work without weakening `done`: the final
|
|
349
|
+
receipt is still written for the exact current commit. An unreadable record,
|
|
350
|
+
changed binding, unreachable baseline, or merge on the baseline-to-HEAD path
|
|
351
|
+
falls back to the complete branch change.
|
|
352
|
+
|
|
344
353
|
To fetch the unresolved comments themselves during a review session, the agent
|
|
345
354
|
runs `wade review-pr-comments-session fetch <N>`; it resolves individual threads with
|
|
346
355
|
`wade review-pr-comments-session resolve`.
|
|
347
356
|
|
|
357
|
+
When a PR-comment session starts, WADE records its pre-edit commit and a feedback
|
|
358
|
+
snapshot under local `.wade/review-cycles/` state, outside the immutable session
|
|
359
|
+
bundle. Re-fetching retains prior feedback while adding new feedback, but never
|
|
360
|
+
advances that baseline. Its closing review receives the accumulated feedback and
|
|
361
|
+
the cycle delta; unsafe, mismatched, stale, or merged lineage uses the full
|
|
362
|
+
branch change instead. A successful `review-pr-comments-session done` clears
|
|
363
|
+
the cycle so later feedback starts from the then-current PR head.
|
|
364
|
+
|
|
348
365
|
### The auto-launched review session
|
|
349
366
|
|
|
350
367
|
The auto-launched **review session** — the one started when you pick **"Wait
|
|
@@ -289,7 +289,7 @@ WADE splits review into an **AI review pass** you or the agent can invoke, and
|
|
|
289
289
|
| Command | What it reviews |
|
|
290
290
|
|---------|-----------------|
|
|
291
291
|
| `wade review plan <file>` | A plan file, before it becomes tasks |
|
|
292
|
-
| `wade review implementation` | The current implementation
|
|
292
|
+
| `wade review implementation` | The current implementation change (mandatory gate before `done`) |
|
|
293
293
|
| `wade review batch <N>` | Coherence across parallel implementation branches |
|
|
294
294
|
| `wade review pr-comments <N>` | Starts a session to address human/bot PR comments |
|
|
295
295
|
| `wade review trigger <N>` | Posts configured bot-review trigger comments on the task's PR |
|
|
@@ -305,10 +305,27 @@ implementation` exits successfully without launching another reviewer and direct
|
|
|
305
305
|
you to `wade implementation-session done`. The dispatch cap does not block a
|
|
306
306
|
valid `--ack-self-review`, which still writes its active-binding receipt.
|
|
307
307
|
|
|
308
|
+
The first implementation review for a frozen reviewer binding receives the
|
|
309
|
+
complete branch change. After a successful review and a new fix commit, a
|
|
310
|
+
re-review receives only the committed delta since that successful same-binding
|
|
311
|
+
review, plus any staged or unstaged changes and the local `PLAN.md` when safely
|
|
312
|
+
available. This narrows repeated review work without weakening `done`: the final
|
|
313
|
+
receipt is still written for the exact current commit. An unreadable record,
|
|
314
|
+
changed binding, unreachable baseline, or merge on the baseline-to-HEAD path
|
|
315
|
+
falls back to the complete branch change.
|
|
316
|
+
|
|
308
317
|
To fetch the unresolved comments themselves during a review session, the agent
|
|
309
318
|
runs `wade review-pr-comments-session fetch <N>`; it resolves individual threads with
|
|
310
319
|
`wade review-pr-comments-session resolve`.
|
|
311
320
|
|
|
321
|
+
When a PR-comment session starts, WADE records its pre-edit commit and a feedback
|
|
322
|
+
snapshot under local `.wade/review-cycles/` state, outside the immutable session
|
|
323
|
+
bundle. Re-fetching retains prior feedback while adding new feedback, but never
|
|
324
|
+
advances that baseline. Its closing review receives the accumulated feedback and
|
|
325
|
+
the cycle delta; unsafe, mismatched, stale, or merged lineage uses the full
|
|
326
|
+
branch change instead. A successful `review-pr-comments-session done` clears
|
|
327
|
+
the cycle so later feedback starts from the then-current PR head.
|
|
328
|
+
|
|
312
329
|
### The auto-launched review session
|
|
313
330
|
|
|
314
331
|
The auto-launched **review session** — the one started when you pick **"Wait
|
|
@@ -87,6 +87,7 @@ src/wade/
|
|
|
87
87
|
│ ├── skill_invocation_service.py # Bind methods to bounded operation contracts
|
|
88
88
|
│ ├── skill_diagnostics_service.py # Discovery and precedence diagnostics
|
|
89
89
|
│ ├── review_record_service.py # Binding-aware durable review outcomes
|
|
90
|
+
│ ├── review_cycle_service.py # Safe PR-comment feedback-cycle context
|
|
90
91
|
│ ├── documentation_receipt_service.py # Current-commit documentation decision
|
|
91
92
|
│ ├── batch_review_service.py # Batch issue review
|
|
92
93
|
│ ├── deps_service.py # Dependency analysis
|
|
@@ -809,6 +810,22 @@ still records the current binding receipt because it acknowledges work already
|
|
|
809
810
|
performed rather than dispatching a reviewer. Plan and PR-comment reviews are
|
|
810
811
|
uncapped.
|
|
811
812
|
|
|
813
|
+
Review input converges without weakening completion evidence. The first mapped
|
|
814
|
+
implementation review uses the full branch diff; later reviews may select the
|
|
815
|
+
nearest satisfying record for the same frozen REVIEW binding that is an ancestor
|
|
816
|
+
of `HEAD`, then inspect only the delta from that commit. A Git error, malformed
|
|
817
|
+
or foreign record, non-ancestor, or merge on that ancestry path forces the full
|
|
818
|
+
branch diff. Staged and unstaged changes remain separate inputs in either case,
|
|
819
|
+
and every review writes its ordinary exact-HEAD receipt.
|
|
820
|
+
|
|
821
|
+
`review_cycle_service.py` stores the PR-comment session's issue/PR identity,
|
|
822
|
+
pre-edit baseline, and normalized feedback in `.wade/review-cycles/`, outside
|
|
823
|
+
the immutable session bundle. Re-fetching retains prior feedback and adds new
|
|
824
|
+
feedback while preserving the baseline; a successful PR-comment `done` removes
|
|
825
|
+
it. The closing review receives the accumulated feedback and cycle delta only
|
|
826
|
+
after safe state, identity, ancestry, and merge-path checks. Any failed check
|
|
827
|
+
emits a diagnostic and reviews the complete branch diff instead.
|
|
828
|
+
|
|
812
829
|
The capability remediation is intentionally tool-neutral: retain the sandbox
|
|
813
830
|
and grant only the worktree Git metadata paths, GitHub credential/API route, or
|
|
814
831
|
local staging path named by the failure. Codex uses the resolved `ai.sandbox`
|
|
@@ -918,7 +935,7 @@ includes the active binding's pass count. The structured worktree records under
|
|
|
918
935
|
`.wade/reviews/` provide deterministic gate evidence; the PR-body projection is
|
|
919
936
|
the durable human-readable outcome.
|
|
920
937
|
|
|
921
|
-
Binding-aware review records and documentation receipts use the descriptor-safe
|
|
938
|
+
Binding-aware review records, review-cycle contexts, and documentation receipts use the descriptor-safe
|
|
922
939
|
state primitives in `utils/safe_state.py`: `.wade` and intermediate directories
|
|
923
940
|
must be real directories, final files are opened relative to trusted directory
|
|
924
941
|
handles, and malformed/symlinked/unreadable state is treated as absent. Record
|
|
@@ -175,6 +175,14 @@ An implementation-to-PR-comment transition is a new session and replaces the
|
|
|
175
175
|
manifest/workflow. Durable review records survive outside the session bundle,
|
|
176
176
|
but apply only if the new session's review binding is identical.
|
|
177
177
|
|
|
178
|
+
The implementation and PR-comment definitions currently use workflow revision
|
|
179
|
+
2. Their authoritative order is verification, documentation decision, knowledge,
|
|
180
|
+
then closing method review; an old frozen revision is rejected until an explicit
|
|
181
|
+
refresh replaces it. Expected tracked documentation and knowledge changes are
|
|
182
|
+
therefore normally present in the final review. If a review finding creates a
|
|
183
|
+
new tracked commit, the session repeats affected verification/documentation work
|
|
184
|
+
and reviews that new final commit.
|
|
185
|
+
|
|
178
186
|
## Bounded invocation contract
|
|
179
187
|
|
|
180
188
|
`skill_invocation_service.py` composes bounded prompts structurally in this
|
|
@@ -228,7 +236,25 @@ active binding. Switching A to B preserves A's history but makes it inapplicable
|
|
|
228
236
|
switching back to byte-identical A at the same commit reuses its record. Refresh
|
|
229
237
|
of only WORK does not invalidate a REVIEW record.
|
|
230
238
|
|
|
231
|
-
|
|
239
|
+
The first implementation review for a REVIEW binding uses the full branch diff.
|
|
240
|
+
A later one may use the nearest same-binding satisfying record that is a safe
|
|
241
|
+
ancestor of `HEAD`, reviewing only its committed delta while retaining staged and
|
|
242
|
+
unstaged sections. Git errors, malformed or other-binding records, non-ancestors,
|
|
243
|
+
and any merge on the selected ancestry path fail open to the full branch diff.
|
|
244
|
+
The record is only an input baseline: every completed review still writes the
|
|
245
|
+
normal exact-`HEAD`, binding-aware receipt.
|
|
246
|
+
|
|
247
|
+
PR-comment cycles persist separately at
|
|
248
|
+
`.wade/review-cycles/review-cycle@<issue>.json`, rather than in immutable
|
|
249
|
+
`.wade/session/` methodology snapshots. A cycle captures the open PR identity,
|
|
250
|
+
the pre-edit baseline, and normalized fetched feedback. Starting or resuming a
|
|
251
|
+
cycle preserves its baseline and retains prior feedback while adding newly
|
|
252
|
+
fetched feedback; successful `review-pr-comments-session done` removes the
|
|
253
|
+
context. The closing review uses that accumulated feedback plus the cycle delta
|
|
254
|
+
only after identity, ancestry, and merge-path validation; unreadable, mismatched,
|
|
255
|
+
malformed, or unsafe state falls back to a full diff.
|
|
256
|
+
|
|
257
|
+
Manifest, review, review-cycle, and documentation-gate state uses descriptor-relative,
|
|
232
258
|
no-follow filesystem operations. Unsafe/malformed/unreadable state is absent for
|
|
233
259
|
gate purposes and fails toward re-review, never toward completion. SHA marker
|
|
234
260
|
files are never accepted as review evidence. The completion classifier also
|
|
@@ -686,6 +686,18 @@ def diff_between_checked(repo_root: Path, base: str, head: str) -> str:
|
|
|
686
686
|
return _run_git("diff", f"{base}...{head}", cwd=repo_root).stdout
|
|
687
687
|
|
|
688
688
|
|
|
689
|
+
def has_merge_commit_between(repo_root: Path, base: str, head: str) -> bool:
|
|
690
|
+
"""Return whether the ancestry path from *base* to *head* contains a merge.
|
|
691
|
+
|
|
692
|
+
Callers first establish that ``base`` is an ancestor of ``head``. Keeping
|
|
693
|
+
this query in the git layer lets review-input selection fail open when git
|
|
694
|
+
cannot establish a trustworthy incremental lineage.
|
|
695
|
+
"""
|
|
696
|
+
|
|
697
|
+
result = _run_git("rev-list", "--merges", f"{base}..{head}", cwd=repo_root)
|
|
698
|
+
return bool(result.stdout.strip())
|
|
699
|
+
|
|
700
|
+
|
|
689
701
|
def diff_worktree(repo_root: Path, *, staged: bool = False) -> str:
|
|
690
702
|
"""Return ``git diff`` output for the working tree (or the staged index).
|
|
691
703
|
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"""Persisted PR-comment review-cycle context."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import re
|
|
7
|
+
|
|
8
|
+
from pydantic import BaseModel, Field, field_validator, model_validator
|
|
9
|
+
|
|
10
|
+
REVIEW_CYCLE_SCHEMA_VERSION = 1
|
|
11
|
+
# A persisted context includes JSON syntax and other fields, so its actual
|
|
12
|
+
# serialized byte length remains the authoritative limit.
|
|
13
|
+
MAX_REVIEW_CYCLE_PAYLOAD_BYTES = 256 * 1024
|
|
14
|
+
MAX_REVIEW_FEEDBACK_CHARS = MAX_REVIEW_CYCLE_PAYLOAD_BYTES
|
|
15
|
+
_COMMIT_RE = re.compile(r"^[0-9a-f]{7,64}$")
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class ReviewCycleContext(BaseModel, frozen=True):
|
|
19
|
+
"""The immutable baseline and mutable feedback snapshot for one PR cycle."""
|
|
20
|
+
|
|
21
|
+
schema_version: int = Field(default=REVIEW_CYCLE_SCHEMA_VERSION)
|
|
22
|
+
issue_number: str
|
|
23
|
+
pr_number: int = Field(gt=0)
|
|
24
|
+
baseline_commit: str
|
|
25
|
+
feedback: str = Field(min_length=1, max_length=MAX_REVIEW_FEEDBACK_CHARS)
|
|
26
|
+
|
|
27
|
+
@field_validator("schema_version")
|
|
28
|
+
@classmethod
|
|
29
|
+
def _schema(cls, value: int) -> int:
|
|
30
|
+
if value != REVIEW_CYCLE_SCHEMA_VERSION:
|
|
31
|
+
raise ValueError(f"Unsupported review-cycle schema {value}")
|
|
32
|
+
return value
|
|
33
|
+
|
|
34
|
+
@field_validator("issue_number")
|
|
35
|
+
@classmethod
|
|
36
|
+
def _issue_number(cls, value: str) -> str:
|
|
37
|
+
normalized = value.strip().lstrip("#")
|
|
38
|
+
if not normalized.isdigit() or int(normalized) <= 0:
|
|
39
|
+
raise ValueError("Review-cycle issue number must be positive")
|
|
40
|
+
return str(int(normalized))
|
|
41
|
+
|
|
42
|
+
@field_validator("baseline_commit")
|
|
43
|
+
@classmethod
|
|
44
|
+
def _commit(cls, value: str) -> str:
|
|
45
|
+
if not _COMMIT_RE.fullmatch(value):
|
|
46
|
+
raise ValueError("Review-cycle baseline must be a lowercase hexadecimal git object id")
|
|
47
|
+
return value
|
|
48
|
+
|
|
49
|
+
@field_validator("feedback")
|
|
50
|
+
@classmethod
|
|
51
|
+
def _feedback(cls, value: str) -> str:
|
|
52
|
+
normalized = value.replace("\r\n", "\n").replace("\r", "\n").strip()
|
|
53
|
+
if not normalized:
|
|
54
|
+
raise ValueError("Review-cycle feedback cannot be empty")
|
|
55
|
+
return normalized
|
|
56
|
+
|
|
57
|
+
def serialized_payload(self) -> bytes:
|
|
58
|
+
"""Return the exact UTF-8 state-file representation for this context."""
|
|
59
|
+
|
|
60
|
+
return (
|
|
61
|
+
json.dumps(self.model_dump(mode="json"), indent=2, sort_keys=True, ensure_ascii=True)
|
|
62
|
+
+ "\n"
|
|
63
|
+
).encode("utf-8")
|
|
64
|
+
|
|
65
|
+
@model_validator(mode="after")
|
|
66
|
+
def _serialized_payload_fits_state_file(self) -> ReviewCycleContext:
|
|
67
|
+
if len(self.serialized_payload()) > MAX_REVIEW_CYCLE_PAYLOAD_BYTES:
|
|
68
|
+
raise ValueError("Review-cycle serialized payload exceeds the 256 KiB limit")
|
|
69
|
+
return self
|
|
@@ -178,7 +178,7 @@ SESSION_DEFINITIONS: dict[SessionKind, SessionDefinition] = {
|
|
|
178
178
|
kind=SessionKind.IMPLEMENTATION,
|
|
179
179
|
ai_command=AICommandKey.IMPLEMENT,
|
|
180
180
|
workflow_template="implementation.md",
|
|
181
|
-
workflow_revision=
|
|
181
|
+
workflow_revision=2,
|
|
182
182
|
launch_prompt="implement-context.md",
|
|
183
183
|
session_phase=SessionPhase.IMPLEMENT,
|
|
184
184
|
steps=(
|
|
@@ -187,9 +187,9 @@ SESSION_DEFINITIONS: dict[SessionKind, SessionDefinition] = {
|
|
|
187
187
|
"understand-plan",
|
|
188
188
|
"implement",
|
|
189
189
|
"verify",
|
|
190
|
-
"method-review",
|
|
191
190
|
"documentation",
|
|
192
191
|
"knowledge",
|
|
192
|
+
"method-review",
|
|
193
193
|
"pr-summary",
|
|
194
194
|
"sync",
|
|
195
195
|
"done",
|
|
@@ -214,7 +214,7 @@ SESSION_DEFINITIONS: dict[SessionKind, SessionDefinition] = {
|
|
|
214
214
|
kind=SessionKind.REVIEW_PR_COMMENTS,
|
|
215
215
|
ai_command=AICommandKey.REVIEW_PR_COMMENTS,
|
|
216
216
|
workflow_template="review-pr-comments.md",
|
|
217
|
-
workflow_revision=
|
|
217
|
+
workflow_revision=2,
|
|
218
218
|
launch_prompt="review-pr-comments.md",
|
|
219
219
|
session_phase=SessionPhase.REVIEW,
|
|
220
220
|
steps=(
|
|
@@ -223,9 +223,9 @@ SESSION_DEFINITIONS: dict[SessionKind, SessionDefinition] = {
|
|
|
223
223
|
"verify-feedback",
|
|
224
224
|
"address-feedback",
|
|
225
225
|
"verify",
|
|
226
|
-
"method-review",
|
|
227
226
|
"documentation",
|
|
228
227
|
"knowledge",
|
|
228
|
+
"method-review",
|
|
229
229
|
"pr-summary",
|
|
230
230
|
"sync",
|
|
231
231
|
"resolve-threads",
|
|
@@ -46,6 +46,7 @@ from wade.services.implementation_service.lifecycle import (
|
|
|
46
46
|
_strip_summary_section,
|
|
47
47
|
)
|
|
48
48
|
from wade.services.implementation_service.usage_tracking import IMPL_USAGE_MARKER_START
|
|
49
|
+
from wade.services.review_cycle_service import clear_review_cycle
|
|
49
50
|
from wade.services.review_record_service import (
|
|
50
51
|
count_binding_passes,
|
|
51
52
|
has_other_satisfying_binding,
|
|
@@ -387,6 +388,18 @@ def done(
|
|
|
387
388
|
# block + skip-worktree still in place) so the user can fix and re-run.
|
|
388
389
|
return False
|
|
389
390
|
|
|
391
|
+
if session_type == "review-pr-comments" and not clear_review_cycle(
|
|
392
|
+
worktree_root, issue_number=issue_number
|
|
393
|
+
):
|
|
394
|
+
# The PR may already be finalized, but completion remains retryable
|
|
395
|
+
# until the active review-cycle state is gone or durably invalidated.
|
|
396
|
+
# Otherwise a later feedback cycle could inherit an obsolete baseline.
|
|
397
|
+
console.error(
|
|
398
|
+
"Could not clear the completed PR-comment review-cycle state safely; "
|
|
399
|
+
"restore filesystem access and re-run done."
|
|
400
|
+
)
|
|
401
|
+
return False
|
|
402
|
+
|
|
390
403
|
# Success only: strip the worktree gitignore block and restore .gitignore
|
|
391
404
|
# visibility now that there is nothing left to retry. The PR is already
|
|
392
405
|
# updated at this point, so a filesystem cleanup failure (read-only file,
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
"""Safe PR-comment review-cycle state outside immutable session bundles."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import re
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
from pydantic import ValidationError
|
|
10
|
+
|
|
11
|
+
from wade.models.review_cycle import ReviewCycleContext
|
|
12
|
+
from wade.utils.safe_state import (
|
|
13
|
+
MAX_STATE_FILE_BYTES,
|
|
14
|
+
atomic_write_state_file,
|
|
15
|
+
delete_state_file,
|
|
16
|
+
read_state_file,
|
|
17
|
+
state_file_present,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
_DIRECTORIES = ("review-cycles",)
|
|
21
|
+
_INVALIDATION_DIRECTORIES = ("review-cycle-invalidations",)
|
|
22
|
+
_PREFIX = "review-cycle@"
|
|
23
|
+
_FEEDBACK_SECTION = re.compile(r"(?m)(?=^### )")
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@dataclass(frozen=True)
|
|
27
|
+
class ReviewCycleLookup:
|
|
28
|
+
"""A context lookup that preserves an unsafe-state diagnostic."""
|
|
29
|
+
|
|
30
|
+
context: ReviewCycleContext | None
|
|
31
|
+
diagnostic: str | None = None
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def review_cycle_filename(issue_number: str) -> str:
|
|
35
|
+
"""Return the one canonical context filename for an issue's active cycle."""
|
|
36
|
+
|
|
37
|
+
normalized = issue_number.strip().lstrip("#")
|
|
38
|
+
if not normalized.isdigit() or int(normalized) <= 0:
|
|
39
|
+
raise ValueError("Review-cycle issue number must be positive")
|
|
40
|
+
return f"{_PREFIX}{int(normalized)}.json"
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def read_review_cycle(
|
|
44
|
+
root: Path,
|
|
45
|
+
*,
|
|
46
|
+
issue_number: str,
|
|
47
|
+
pr_number: int,
|
|
48
|
+
) -> ReviewCycleLookup:
|
|
49
|
+
"""Read a current identity-matched cycle, never trusting malformed state."""
|
|
50
|
+
|
|
51
|
+
try:
|
|
52
|
+
filename = review_cycle_filename(issue_number)
|
|
53
|
+
except ValueError as exc:
|
|
54
|
+
return ReviewCycleLookup(None, str(exc))
|
|
55
|
+
if state_file_present(root, _INVALIDATION_DIRECTORIES, filename):
|
|
56
|
+
return ReviewCycleLookup(
|
|
57
|
+
None,
|
|
58
|
+
"PR-comment review-cycle state was invalidated after an unsafe update",
|
|
59
|
+
)
|
|
60
|
+
raw = read_state_file(root, _DIRECTORIES, filename)
|
|
61
|
+
if raw is None:
|
|
62
|
+
if state_file_present(root, _DIRECTORIES, filename):
|
|
63
|
+
return ReviewCycleLookup(
|
|
64
|
+
None,
|
|
65
|
+
"PR-comment review-cycle state is unreadable or unsafe",
|
|
66
|
+
)
|
|
67
|
+
return ReviewCycleLookup(None)
|
|
68
|
+
try:
|
|
69
|
+
context = ReviewCycleContext.model_validate_json(raw)
|
|
70
|
+
except (ValidationError, ValueError):
|
|
71
|
+
return ReviewCycleLookup(None, "PR-comment review-cycle state is malformed")
|
|
72
|
+
if context.issue_number != str(int(issue_number.lstrip("#"))):
|
|
73
|
+
return ReviewCycleLookup(None, "PR-comment review-cycle issue identity does not match")
|
|
74
|
+
if context.pr_number != pr_number:
|
|
75
|
+
return ReviewCycleLookup(None, "PR-comment review-cycle PR identity does not match")
|
|
76
|
+
return ReviewCycleLookup(context)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def _feedback_sections(feedback: str) -> tuple[str, ...]:
|
|
80
|
+
"""Return individually rendered review entries from a feedback snapshot."""
|
|
81
|
+
|
|
82
|
+
return tuple(
|
|
83
|
+
section.strip() for section in _FEEDBACK_SECTION.split(feedback)[1:] if section.strip()
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def _merge_feedback(existing_feedback: str, refreshed_feedback: str) -> str:
|
|
88
|
+
"""Retain feedback already addressed while adding new feedback from a refresh."""
|
|
89
|
+
|
|
90
|
+
if refreshed_feedback in existing_feedback:
|
|
91
|
+
return existing_feedback
|
|
92
|
+
|
|
93
|
+
refreshed_sections = _feedback_sections(refreshed_feedback)
|
|
94
|
+
if refreshed_sections:
|
|
95
|
+
existing_sections = set(_feedback_sections(existing_feedback))
|
|
96
|
+
additions = [section for section in refreshed_sections if section not in existing_sections]
|
|
97
|
+
if not additions:
|
|
98
|
+
return existing_feedback
|
|
99
|
+
return f"{existing_feedback}\n\n## Feedback added during refresh\n\n" + "\n\n".join(
|
|
100
|
+
additions
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
return f"{existing_feedback}\n\n{refreshed_feedback}"
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def initialize_or_refresh_review_cycle(
|
|
107
|
+
root: Path,
|
|
108
|
+
*,
|
|
109
|
+
issue_number: str,
|
|
110
|
+
pr_number: int,
|
|
111
|
+
baseline_commit: str,
|
|
112
|
+
feedback: str,
|
|
113
|
+
) -> ReviewCycleLookup:
|
|
114
|
+
"""Create a cycle or retain its baseline and all feedback across refreshes."""
|
|
115
|
+
|
|
116
|
+
try:
|
|
117
|
+
filename = review_cycle_filename(issue_number)
|
|
118
|
+
except ValueError as exc:
|
|
119
|
+
return ReviewCycleLookup(None, f"Could not validate PR-comment review-cycle state: {exc}")
|
|
120
|
+
invalidation = _discard_invalidated_review_cycle(root, filename)
|
|
121
|
+
if invalidation is not None:
|
|
122
|
+
return ReviewCycleLookup(None, invalidation)
|
|
123
|
+
|
|
124
|
+
existing = read_review_cycle(root, issue_number=issue_number, pr_number=pr_number)
|
|
125
|
+
if existing.diagnostic is not None:
|
|
126
|
+
return existing
|
|
127
|
+
try:
|
|
128
|
+
context = (
|
|
129
|
+
ReviewCycleContext(
|
|
130
|
+
**(
|
|
131
|
+
existing.context.model_dump()
|
|
132
|
+
| {"feedback": _merge_feedback(existing.context.feedback, feedback)}
|
|
133
|
+
)
|
|
134
|
+
)
|
|
135
|
+
if existing.context is not None
|
|
136
|
+
else ReviewCycleContext(
|
|
137
|
+
issue_number=issue_number,
|
|
138
|
+
pr_number=pr_number,
|
|
139
|
+
baseline_commit=baseline_commit,
|
|
140
|
+
feedback=feedback,
|
|
141
|
+
)
|
|
142
|
+
)
|
|
143
|
+
except (ValidationError, ValueError) as exc:
|
|
144
|
+
return ReviewCycleLookup(None, f"Could not validate PR-comment review-cycle state: {exc}")
|
|
145
|
+
payload = context.serialized_payload()
|
|
146
|
+
if len(payload) > MAX_STATE_FILE_BYTES:
|
|
147
|
+
return ReviewCycleLookup(
|
|
148
|
+
None,
|
|
149
|
+
"Could not persist PR-comment review-cycle state: serialized payload exceeds "
|
|
150
|
+
"the 256 KiB limit",
|
|
151
|
+
)
|
|
152
|
+
if not atomic_write_state_file(root, _DIRECTORIES, filename, payload):
|
|
153
|
+
if clear_review_cycle(root, issue_number=issue_number):
|
|
154
|
+
return ReviewCycleLookup(
|
|
155
|
+
None,
|
|
156
|
+
"Could not persist PR-comment review-cycle state safely; the prior state was "
|
|
157
|
+
"invalidated",
|
|
158
|
+
)
|
|
159
|
+
return ReviewCycleLookup(None, "Could not persist PR-comment review-cycle state safely")
|
|
160
|
+
return ReviewCycleLookup(context)
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def clear_review_cycle(root: Path, *, issue_number: str) -> bool:
|
|
164
|
+
"""Remove an active cycle, or durably invalidate it when removal is unsafe."""
|
|
165
|
+
|
|
166
|
+
try:
|
|
167
|
+
filename = review_cycle_filename(issue_number)
|
|
168
|
+
except ValueError:
|
|
169
|
+
return False
|
|
170
|
+
if not state_file_present(root, _DIRECTORIES, filename):
|
|
171
|
+
return True
|
|
172
|
+
if delete_state_file(root, _DIRECTORIES, filename):
|
|
173
|
+
return True
|
|
174
|
+
# A successful marker makes every later read fail closed, even if the old
|
|
175
|
+
# regular JSON file remains readable after a failed deletion. It lives in
|
|
176
|
+
# a sibling directory so a read-only review-cycles directory can still be
|
|
177
|
+
# invalidated through its writable .wade parent.
|
|
178
|
+
return atomic_write_state_file(root, _INVALIDATION_DIRECTORIES, filename, b"")
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def _discard_invalidated_review_cycle(root: Path, filename: str) -> str | None:
|
|
182
|
+
"""Clear a previous invalidation before beginning a fresh feedback cycle."""
|
|
183
|
+
|
|
184
|
+
if not state_file_present(root, _INVALIDATION_DIRECTORIES, filename):
|
|
185
|
+
return None
|
|
186
|
+
if state_file_present(root, _DIRECTORIES, filename) and not delete_state_file(
|
|
187
|
+
root, _DIRECTORIES, filename
|
|
188
|
+
):
|
|
189
|
+
return "Could not discard invalidated PR-comment review-cycle state safely"
|
|
190
|
+
if not delete_state_file(root, _INVALIDATION_DIRECTORIES, filename):
|
|
191
|
+
return "Could not clear PR-comment review-cycle invalidation safely"
|
|
192
|
+
return None
|