kedacode 0.2.0__py3-none-any.whl
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.
- backend/__init__.py +1 -0
- backend/__main__.py +11 -0
- backend/api/__init__.py +9 -0
- backend/api/app.py +48 -0
- backend/api/cli.py +291 -0
- backend/api/cli_completion.py +100 -0
- backend/api/cli_console.py +12 -0
- backend/api/cli_helpers.py +205 -0
- backend/api/cli_init.py +294 -0
- backend/api/cli_loop.py +485 -0
- backend/api/cli_parsed_commands/__init__.py +106 -0
- backend/api/cli_parsed_commands/agent.py +228 -0
- backend/api/cli_parsed_commands/container.py +212 -0
- backend/api/cli_parsed_commands/init_workflow_takeover.py +97 -0
- backend/api/cli_parsed_commands/labels_issue.py +234 -0
- backend/api/cli_parsed_commands/logs.py +24 -0
- backend/api/cli_parsed_commands/loop.py +27 -0
- backend/api/cli_parsed_commands/registry.py +109 -0
- backend/api/cli_parsed_commands/runner.py +303 -0
- backend/api/cli_parsed_commands/worktree.py +91 -0
- backend/api/cli_parsed_context.py +44 -0
- backend/api/cli_parser.py +792 -0
- backend/api/cli_prd_utils.py +145 -0
- backend/api/cli_reexports.py +131 -0
- backend/api/cli_registry.py +707 -0
- backend/api/cli_takeover.py +148 -0
- backend/api/cli_typer.py +69 -0
- backend/api/cli_typer_agent.py +137 -0
- backend/api/cli_typer_app.py +362 -0
- backend/api/cli_typer_console.py +147 -0
- backend/api/cli_typer_container.py +121 -0
- backend/api/cli_typer_init.py +65 -0
- backend/api/cli_typer_issue.py +174 -0
- backend/api/cli_typer_labels.py +40 -0
- backend/api/cli_typer_loop.py +155 -0
- backend/api/cli_typer_recover.py +71 -0
- backend/api/cli_typer_registry.py +165 -0
- backend/api/cli_typer_runner.py +309 -0
- backend/api/cli_typer_takeover.py +58 -0
- backend/api/cli_typer_workflow.py +56 -0
- backend/api/cli_typer_worktree.py +102 -0
- backend/api/cli_utils.py +62 -0
- backend/api/response_cache.py +53 -0
- backend/api/routes/__init__.py +1 -0
- backend/api/routes/agent_runner.py +385 -0
- backend/api/routes/agent_runner_console.py +427 -0
- backend/api/routes/agent_runner_idea_inbox.py +381 -0
- backend/api/routes/agent_runner_roadmap.py +361 -0
- backend/api/routes/local_auth.py +49 -0
- backend/api/static/__init__.py +5 -0
- backend/api/static/console/.gitkeep +0 -0
- backend/api/static/console/404/index.html +1 -0
- backend/api/static/console/404.html +1 -0
- backend/api/static/console/__next.__PAGE__.txt +9 -0
- backend/api/static/console/__next._full.txt +22 -0
- backend/api/static/console/__next._head.txt +6 -0
- backend/api/static/console/__next._index.txt +7 -0
- backend/api/static/console/__next._tree.txt +4 -0
- backend/api/static/console/_next/static/chunks/00_0ubg38g00u.js +1 -0
- backend/api/static/console/_next/static/chunks/03~yq9q893hmn.js +1 -0
- backend/api/static/console/_next/static/chunks/0522i1vm3-79j.js +1 -0
- backend/api/static/console/_next/static/chunks/06.h2f0yjf.d8.css +3 -0
- backend/api/static/console/_next/static/chunks/06k6o5~7u5tj3.js +1 -0
- backend/api/static/console/_next/static/chunks/09zferefp394n.js +1 -0
- backend/api/static/console/_next/static/chunks/0ai7bng0ja30..js +4 -0
- backend/api/static/console/_next/static/chunks/0b6ngsgplf53i.js +1 -0
- backend/api/static/console/_next/static/chunks/0b_renknboqtt.js +1 -0
- backend/api/static/console/_next/static/chunks/0dbns6mx.e.ls.js +4 -0
- backend/api/static/console/_next/static/chunks/0f0u6xzw58gc..js +1 -0
- backend/api/static/console/_next/static/chunks/0is4xrhy78.lh.js +1 -0
- backend/api/static/console/_next/static/chunks/0jo28sqy68hel.js +1 -0
- backend/api/static/console/_next/static/chunks/0ni-8dyxk08xj.js +1 -0
- backend/api/static/console/_next/static/chunks/0w-xjmc4f.h.n.js +1 -0
- backend/api/static/console/_next/static/chunks/0whxy3_ltg6hc.js +1 -0
- backend/api/static/console/_next/static/chunks/0xwuebh1xb6wq.js +1 -0
- backend/api/static/console/_next/static/chunks/0y-rmgf5-6rhh.js +1 -0
- backend/api/static/console/_next/static/chunks/0z2bteewx7_rx.js +31 -0
- backend/api/static/console/_next/static/chunks/115hsw~jukld1.js +1 -0
- backend/api/static/console/_next/static/chunks/15pf3h6qt7363.js +1 -0
- backend/api/static/console/_next/static/chunks/15rgb5mxa2_33.js +1 -0
- backend/api/static/console/_next/static/chunks/160~wye0f30~a.js +2 -0
- backend/api/static/console/_next/static/chunks/16a37o3pm9-wg.js +1 -0
- backend/api/static/console/_next/static/chunks/189e_dbuz95u6.js +1 -0
- backend/api/static/console/_next/static/chunks/turbopack-06acsgdi3eju7.js +1 -0
- backend/api/static/console/_next/static/media/4fa387ec64143e14-s.0wkzw~je483f-.woff2 +0 -0
- backend/api/static/console/_next/static/media/53b9e256198e5412-s.0-wfv7uh4i7h9.woff2 +0 -0
- backend/api/static/console/_next/static/media/5ce348bf30bf5439-s.0zgw-jeven.3w.woff2 +0 -0
- backend/api/static/console/_next/static/media/6306c77e7c8268e4-s.0rhz0arwfsn~5.woff2 +0 -0
- backend/api/static/console/_next/static/media/7178b3e590c64307-s.0nx0ww8fni_q3.woff2 +0 -0
- backend/api/static/console/_next/static/media/797e433ab948586e-s.p.08e28id.o-okb.woff2 +0 -0
- backend/api/static/console/_next/static/media/7d817b4c03b0c5f1-s.0l76wvqk9d84w.woff2 +0 -0
- backend/api/static/console/_next/static/media/8a480f0b521d4e75-s.0jzbimsg8vl84.woff2 +0 -0
- backend/api/static/console/_next/static/media/bbc41e54d2fcbd21-s.0k4k9394f2q-k.woff2 +0 -0
- backend/api/static/console/_next/static/media/caa3a2e1cccd8315-s.p.09~u27dqhyhd6.woff2 +0 -0
- backend/api/static/console/_next/static/media/favicon.0x3dzn~oxb6tn.ico +0 -0
- backend/api/static/console/_next/static/media/fef07dbb0973bf53-s.12tyk43_3sh9u.woff2 +0 -0
- backend/api/static/console/_next/static/vgeFyzQFXU9WnhDOmjqPq/_buildManifest.js +15 -0
- backend/api/static/console/_next/static/vgeFyzQFXU9WnhDOmjqPq/_clientMiddlewareManifest.js +1 -0
- backend/api/static/console/_next/static/vgeFyzQFXU9WnhDOmjqPq/_ssgManifest.js +1 -0
- backend/api/static/console/_not-found/__next._full.txt +18 -0
- backend/api/static/console/_not-found/__next._head.txt +6 -0
- backend/api/static/console/_not-found/__next._index.txt +7 -0
- backend/api/static/console/_not-found/__next._not-found.__PAGE__.txt +5 -0
- backend/api/static/console/_not-found/__next._not-found.txt +5 -0
- backend/api/static/console/_not-found/__next._tree.txt +2 -0
- backend/api/static/console/_not-found/index.html +1 -0
- backend/api/static/console/_not-found/index.txt +18 -0
- backend/api/static/console/app/dashboard/__next.!KGFwcCk.app.dashboard.__PAGE__.txt +9 -0
- backend/api/static/console/app/dashboard/__next.!KGFwcCk.app.dashboard.txt +5 -0
- backend/api/static/console/app/dashboard/__next.!KGFwcCk.app.txt +5 -0
- backend/api/static/console/app/dashboard/__next.!KGFwcCk.txt +7 -0
- backend/api/static/console/app/dashboard/__next._full.txt +29 -0
- backend/api/static/console/app/dashboard/__next._head.txt +6 -0
- backend/api/static/console/app/dashboard/__next._index.txt +7 -0
- backend/api/static/console/app/dashboard/__next._tree.txt +4 -0
- backend/api/static/console/app/dashboard/index.html +1 -0
- backend/api/static/console/app/dashboard/index.txt +29 -0
- backend/api/static/console/app/ideas/__next.!KGFwcCk.app.ideas.__PAGE__.txt +9 -0
- backend/api/static/console/app/ideas/__next.!KGFwcCk.app.ideas.txt +5 -0
- backend/api/static/console/app/ideas/__next.!KGFwcCk.app.txt +5 -0
- backend/api/static/console/app/ideas/__next.!KGFwcCk.txt +7 -0
- backend/api/static/console/app/ideas/__next._full.txt +30 -0
- backend/api/static/console/app/ideas/__next._head.txt +6 -0
- backend/api/static/console/app/ideas/__next._index.txt +7 -0
- backend/api/static/console/app/ideas/__next._tree.txt +4 -0
- backend/api/static/console/app/ideas/index.html +1 -0
- backend/api/static/console/app/ideas/index.txt +30 -0
- backend/api/static/console/app/processes/__next.!KGFwcCk.app.processes.__PAGE__.txt +9 -0
- backend/api/static/console/app/processes/__next.!KGFwcCk.app.processes.txt +5 -0
- backend/api/static/console/app/processes/__next.!KGFwcCk.app.txt +5 -0
- backend/api/static/console/app/processes/__next.!KGFwcCk.txt +7 -0
- backend/api/static/console/app/processes/__next._full.txt +32 -0
- backend/api/static/console/app/processes/__next._head.txt +6 -0
- backend/api/static/console/app/processes/__next._index.txt +7 -0
- backend/api/static/console/app/processes/__next._tree.txt +4 -0
- backend/api/static/console/app/processes/index.html +1 -0
- backend/api/static/console/app/processes/index.txt +32 -0
- backend/api/static/console/app/repositories/__next.!KGFwcCk.app.repositories.__PAGE__.txt +9 -0
- backend/api/static/console/app/repositories/__next.!KGFwcCk.app.repositories.txt +5 -0
- backend/api/static/console/app/repositories/__next.!KGFwcCk.app.txt +5 -0
- backend/api/static/console/app/repositories/__next.!KGFwcCk.txt +7 -0
- backend/api/static/console/app/repositories/__next._full.txt +29 -0
- backend/api/static/console/app/repositories/__next._head.txt +6 -0
- backend/api/static/console/app/repositories/__next._index.txt +7 -0
- backend/api/static/console/app/repositories/__next._tree.txt +4 -0
- backend/api/static/console/app/repositories/index.html +1 -0
- backend/api/static/console/app/repositories/index.txt +29 -0
- backend/api/static/console/app/roadmap/__next.!KGFwcCk.app.roadmap.__PAGE__.txt +9 -0
- backend/api/static/console/app/roadmap/__next.!KGFwcCk.app.roadmap.txt +5 -0
- backend/api/static/console/app/roadmap/__next.!KGFwcCk.app.txt +5 -0
- backend/api/static/console/app/roadmap/__next.!KGFwcCk.txt +7 -0
- backend/api/static/console/app/roadmap/__next._full.txt +30 -0
- backend/api/static/console/app/roadmap/__next._head.txt +6 -0
- backend/api/static/console/app/roadmap/__next._index.txt +7 -0
- backend/api/static/console/app/roadmap/__next._tree.txt +4 -0
- backend/api/static/console/app/roadmap/index.html +1 -0
- backend/api/static/console/app/roadmap/index.txt +30 -0
- backend/api/static/console/app/settings/__next.!KGFwcCk.app.settings.__PAGE__.txt +9 -0
- backend/api/static/console/app/settings/__next.!KGFwcCk.app.settings.txt +5 -0
- backend/api/static/console/app/settings/__next.!KGFwcCk.app.txt +5 -0
- backend/api/static/console/app/settings/__next.!KGFwcCk.txt +7 -0
- backend/api/static/console/app/settings/__next._full.txt +29 -0
- backend/api/static/console/app/settings/__next._head.txt +6 -0
- backend/api/static/console/app/settings/__next._index.txt +7 -0
- backend/api/static/console/app/settings/__next._tree.txt +4 -0
- backend/api/static/console/app/settings/index.html +1 -0
- backend/api/static/console/app/settings/index.txt +29 -0
- backend/api/static/console/app/stats/__next.!KGFwcCk.app.stats.__PAGE__.txt +9 -0
- backend/api/static/console/app/stats/__next.!KGFwcCk.app.stats.txt +5 -0
- backend/api/static/console/app/stats/__next.!KGFwcCk.app.txt +5 -0
- backend/api/static/console/app/stats/__next.!KGFwcCk.txt +7 -0
- backend/api/static/console/app/stats/__next._full.txt +29 -0
- backend/api/static/console/app/stats/__next._head.txt +6 -0
- backend/api/static/console/app/stats/__next._index.txt +7 -0
- backend/api/static/console/app/stats/__next._tree.txt +4 -0
- backend/api/static/console/app/stats/index.html +1 -0
- backend/api/static/console/app/stats/index.txt +29 -0
- backend/api/static/console/favicon.ico +0 -0
- backend/api/static/console/index.html +1 -0
- backend/api/static/console/index.txt +22 -0
- backend/api/static/console/login/__next.!KGF1dGgp.login.__PAGE__.txt +6 -0
- backend/api/static/console/login/__next.!KGF1dGgp.login.txt +5 -0
- backend/api/static/console/login/__next.!KGF1dGgp.txt +4 -0
- backend/api/static/console/login/__next._full.txt +28 -0
- backend/api/static/console/login/__next._head.txt +6 -0
- backend/api/static/console/login/__next._index.txt +7 -0
- backend/api/static/console/login/__next._tree.txt +4 -0
- backend/api/static/console/login/index.html +1 -0
- backend/api/static/console/login/index.txt +28 -0
- backend/api/static/console/register/__next.!KGF1dGgp.register.__PAGE__.txt +9 -0
- backend/api/static/console/register/__next.!KGF1dGgp.register.txt +5 -0
- backend/api/static/console/register/__next.!KGF1dGgp.txt +4 -0
- backend/api/static/console/register/__next._full.txt +24 -0
- backend/api/static/console/register/__next._head.txt +6 -0
- backend/api/static/console/register/__next._index.txt +7 -0
- backend/api/static/console/register/__next._tree.txt +4 -0
- backend/api/static/console/register/index.html +1 -0
- backend/api/static/console/register/index.txt +24 -0
- backend/core/__init__.py +9 -0
- backend/core/agent/__init__.py +5 -0
- backend/core/agent/memory/__init__.py +46 -0
- backend/core/agent/memory/_composition.py +155 -0
- backend/core/agent/memory/memory_loader.py +202 -0
- backend/core/agent/memory/protocols.py +151 -0
- backend/core/agent/memory/short_term_memory.py +96 -0
- backend/core/agent/memory/skill_catalog.py +48 -0
- backend/core/agent/memory/skill_distillation.py +263 -0
- backend/core/agent/orchestrator/__init__.py +1 -0
- backend/core/agent/planner/__init__.py +1 -0
- backend/core/shared/__init__.py +5 -0
- backend/core/shared/interfaces/__init__.py +10 -0
- backend/core/shared/interfaces/agent_output_view.py +136 -0
- backend/core/shared/interfaces/agent_runner.py +614 -0
- backend/core/shared/interfaces/container_runner.py +153 -0
- backend/core/shared/interfaces/loop_scheduler.py +67 -0
- backend/core/shared/interfaces/runner_console.py +397 -0
- backend/core/shared/interfaces/runner_live_view.py +95 -0
- backend/core/shared/models/__init__.py +1 -0
- backend/core/shared/models/agent_decision.py +146 -0
- backend/core/shared/models/agent_deliberation.py +132 -0
- backend/core/shared/models/agent_runner.py +833 -0
- backend/core/shared/models/idea_inbox.py +126 -0
- backend/core/shared/models/loop.py +249 -0
- backend/core/shared/models/roadmap.py +117 -0
- backend/core/shared/prd_change_log.py +119 -0
- backend/core/shared/prd_checklist.py +93 -0
- backend/core/use_cases/__init__.py +5 -0
- backend/core/use_cases/agent_review.py +982 -0
- backend/core/use_cases/agent_runner_blocked_claim.py +109 -0
- backend/core/use_cases/agent_runner_closeout.py +532 -0
- backend/core/use_cases/agent_runner_commit.py +499 -0
- backend/core/use_cases/agent_runner_container.py +242 -0
- backend/core/use_cases/agent_runner_deliberation_issues.py +404 -0
- backend/core/use_cases/agent_runner_dependencies.py +667 -0
- backend/core/use_cases/agent_runner_events.py +166 -0
- backend/core/use_cases/agent_runner_evidence_format.py +188 -0
- backend/core/use_cases/agent_runner_evidence_snapshot.py +132 -0
- backend/core/use_cases/agent_runner_failure.py +898 -0
- backend/core/use_cases/agent_runner_failure_marking.py +130 -0
- backend/core/use_cases/agent_runner_feedback.py +971 -0
- backend/core/use_cases/agent_runner_git.py +315 -0
- backend/core/use_cases/agent_runner_merge_queue.py +558 -0
- backend/core/use_cases/agent_runner_monitor.py +914 -0
- backend/core/use_cases/agent_runner_orchestrate.py +1003 -0
- backend/core/use_cases/agent_runner_orchestration_runtime.py +612 -0
- backend/core/use_cases/agent_runner_output_routing.py +162 -0
- backend/core/use_cases/agent_runner_publication.py +885 -0
- backend/core/use_cases/agent_runner_publish.py +321 -0
- backend/core/use_cases/agent_runner_reclaim.py +287 -0
- backend/core/use_cases/agent_runner_rework.py +30 -0
- backend/core/use_cases/agent_runner_run_history.py +168 -0
- backend/core/use_cases/agent_runner_structured_evidence.py +1020 -0
- backend/core/use_cases/agent_runner_supervisor.py +344 -0
- backend/core/use_cases/agent_runner_validation.py +814 -0
- backend/core/use_cases/agent_runner_validation_checklist.py +96 -0
- backend/core/use_cases/agent_runner_validation_gate.py +197 -0
- backend/core/use_cases/agent_runner_validation_parsing.py +339 -0
- backend/core/use_cases/agent_runner_validation_publication.py +348 -0
- backend/core/use_cases/agent_runner_verification_recovery.py +149 -0
- backend/core/use_cases/agent_runner_workflow.py +153 -0
- backend/core/use_cases/agent_runner_worktree_branch.py +316 -0
- backend/core/use_cases/agent_runner_worktree_probe.py +143 -0
- backend/core/use_cases/blocked_continue.py +172 -0
- backend/core/use_cases/console_actions.py +281 -0
- backend/core/use_cases/console_processes.py +179 -0
- backend/core/use_cases/console_stats.py +218 -0
- backend/core/use_cases/create_issue_from_prd.py +1145 -0
- backend/core/use_cases/create_prd_from_issue.py +455 -0
- backend/core/use_cases/daemon_single_instance.py +187 -0
- backend/core/use_cases/generated_content.py +1145 -0
- backend/core/use_cases/idea_inbox.py +458 -0
- backend/core/use_cases/idea_prd_drafts.py +487 -0
- backend/core/use_cases/interactive_decision.py +976 -0
- backend/core/use_cases/issue_pr_status.py +446 -0
- backend/core/use_cases/loop_create.py +178 -0
- backend/core/use_cases/loop_daemon.py +158 -0
- backend/core/use_cases/loop_fire.py +375 -0
- backend/core/use_cases/loop_recipe.py +413 -0
- backend/core/use_cases/loop_scheduler.py +203 -0
- backend/core/use_cases/pr_supervisor.py +985 -0
- backend/core/use_cases/pr_supervisor_repair.py +141 -0
- backend/core/use_cases/preview_deployment.py +89 -0
- backend/core/use_cases/recover_publish.py +620 -0
- backend/core/use_cases/repl_session.py +530 -0
- backend/core/use_cases/repository_registry.py +96 -0
- backend/core/use_cases/review_daemon.py +61 -0
- backend/core/use_cases/review_once.py +385 -0
- backend/core/use_cases/roadmap_actions.py +441 -0
- backend/core/use_cases/roadmap_dependencies.py +169 -0
- backend/core/use_cases/roadmap_prd_scanner.py +269 -0
- backend/core/use_cases/roadmap_state_resolver.py +219 -0
- backend/core/use_cases/run_agent_daemon.py +172 -0
- backend/core/use_cases/run_agent_deliberation.py +613 -0
- backend/core/use_cases/run_agent_execution_loop.py +843 -0
- backend/core/use_cases/run_agent_once.py +1046 -0
- backend/core/use_cases/run_agent_repositories_once.py +168 -0
- backend/core/use_cases/run_verifier_agent.py +613 -0
- backend/core/use_cases/sync_labels.py +25 -0
- backend/core/use_cases/worktree_cleanup.py +452 -0
- backend/core/use_cases/worktree_database.py +97 -0
- backend/core/use_cases/worktree_env.py +128 -0
- backend/core/use_cases/worktree_frontend.py +422 -0
- backend/engines/__init__.py +10 -0
- backend/engines/agent_runner/__init__.py +1 -0
- backend/engines/agent_runner/container_auth.py +365 -0
- backend/engines/agent_runner/container_ops.py +329 -0
- backend/engines/agent_runner/deliberation_outputs.py +109 -0
- backend/engines/agent_runner/factories/__init__.py +373 -0
- backend/engines/agent_runner/factories/content_generators.py +193 -0
- backend/engines/agent_runner/factory.py +140 -0
- backend/engines/agent_runner/factory_config_builder.py +276 -0
- backend/engines/agent_runner/factory_config_merge.py +318 -0
- backend/engines/agent_runner/factory_repository_resolver.py +465 -0
- backend/engines/agent_runner/failure_resolver.py +164 -0
- backend/engines/agent_runner/live_panels.py +92 -0
- backend/engines/agent_runner/live_terminal.py +240 -0
- backend/engines/agent_runner/persistence/__init__.py +0 -0
- backend/engines/agent_runner/persistence/loop_state_json.py +213 -0
- backend/engines/agent_runner/remote_template_skills.py +261 -0
- backend/engines/agent_runner/repl_command_executor.py +219 -0
- backend/engines/agent_runner/repository_gitignore.py +353 -0
- backend/engines/agent_runner/repository_local.py +999 -0
- backend/engines/agent_runner/runner_live_view.py +174 -0
- backend/engines/agent_runner/scheduler/__init__.py +0 -0
- backend/engines/agent_runner/scheduler/loop_clock.py +57 -0
- backend/engines/agent_runner/takeover.py +552 -0
- backend/engines/agent_runner/takeover_interactive.py +120 -0
- backend/engines/agent_runner/templates/preview/.github/workflows/deploy-preview.yml +327 -0
- backend/engines/agent_runner/templates/preview/deploy/vps-traefik/README.md +96 -0
- backend/engines/agent_runner/templates/preview/deploy/vps-traefik/deploy-preview.sh +97 -0
- backend/engines/agent_runner/templates/preview/deploy/vps-traefik/docker-compose.preview.yml +122 -0
- backend/engines/agent_runner/templates/preview/deploy/vps-traefik/preview.env.example +30 -0
- backend/engines/agent_runner/templates/preview/scripts/_apply.py +391 -0
- backend/engines/agent_runner/templates/preview/scripts/_remote.py +268 -0
- backend/engines/agent_runner/templates/preview/scripts/preview_env.py +66 -0
- backend/engines/agent_runner/templates/preview/scripts/provision_preview_server.py +864 -0
- backend/engines/agent_runner/templates/runner_container/.env.example +35 -0
- backend/engines/agent_runner/templates/runner_container/Dockerfile.runner +160 -0
- backend/engines/agent_runner/templates/runner_container/docker-compose.runner.yml +65 -0
- backend/engines/agent_runner/templates/runner_container/entrypoint.sh +47 -0
- backend/engines/agent_runner/transcript_runner.py +237 -0
- backend/engines/agent_runner/workflow_install.py +376 -0
- backend/engines/agent_runner/worktree_cli.py +42 -0
- backend/engines/rag/__init__.py +5 -0
- backend/infrastructure/__init__.py +30 -0
- backend/infrastructure/config/__init__.py +5 -0
- backend/infrastructure/config/registry_editor.py +115 -0
- backend/infrastructure/config/settings.py +1254 -0
- backend/infrastructure/console/__init__.py +1 -0
- backend/infrastructure/console/process_supervisor.py +527 -0
- backend/infrastructure/git/__init__.py +1 -0
- backend/infrastructure/git/worktree.py +172 -0
- backend/infrastructure/github_client.py +317 -0
- backend/infrastructure/github_helpers.py +266 -0
- backend/infrastructure/github_issue_ops.py +454 -0
- backend/infrastructure/github_labels.py +136 -0
- backend/infrastructure/github_models.py +106 -0
- backend/infrastructure/github_pr_ops.py +330 -0
- backend/infrastructure/helpers.py +132 -0
- backend/infrastructure/http_clients/__init__.py +9 -0
- backend/infrastructure/http_clients/proxy_manager.py +484 -0
- backend/infrastructure/logging/__init__.py +5 -0
- backend/infrastructure/logging/logger.py +105 -0
- backend/infrastructure/memory/__init__.py +39 -0
- backend/infrastructure/memory/_atomic_io.py +59 -0
- backend/infrastructure/memory/adapters.py +356 -0
- backend/infrastructure/memory/long_term_store.py +218 -0
- backend/infrastructure/memory/short_term_store.py +137 -0
- backend/infrastructure/memory/skill_draft_store.py +323 -0
- backend/infrastructure/models/__init__.py +31 -0
- backend/infrastructure/models/model_loader.py +408 -0
- backend/infrastructure/persistence/__init__.py +8 -0
- backend/infrastructure/persistence/console_store.py +538 -0
- backend/infrastructure/persistence/database.py +69 -0
- backend/infrastructure/process_runner.py +921 -0
- backend/main.py +22 -0
- kedacode-0.2.0.dist-info/METADATA +485 -0
- kedacode-0.2.0.dist-info/RECORD +382 -0
- kedacode-0.2.0.dist-info/WHEEL +5 -0
- kedacode-0.2.0.dist-info/entry_points.txt +2 -0
- kedacode-0.2.0.dist-info/licenses/LICENSE +201 -0
- kedacode-0.2.0.dist-info/top_level.txt +1 -0
backend/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Backend package boundary for the four-layer Python application."""
|
backend/__main__.py
ADDED
backend/api/__init__.py
ADDED
backend/api/app.py
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"""FastAPI application factory."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
|
|
7
|
+
from fastapi import FastAPI
|
|
8
|
+
from fastapi.staticfiles import StaticFiles
|
|
9
|
+
|
|
10
|
+
from backend.api.routes import (
|
|
11
|
+
agent_runner,
|
|
12
|
+
agent_runner_console,
|
|
13
|
+
agent_runner_idea_inbox,
|
|
14
|
+
agent_runner_roadmap,
|
|
15
|
+
local_auth,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
_logger = logging.getLogger(__name__)
|
|
19
|
+
|
|
20
|
+
app = FastAPI(title="keda backend")
|
|
21
|
+
app.include_router(agent_runner.router, prefix="/api/v1")
|
|
22
|
+
app.include_router(agent_runner_console.router, prefix="/api/v1")
|
|
23
|
+
app.include_router(agent_runner_roadmap.router, prefix="/api/v1")
|
|
24
|
+
app.include_router(agent_runner_idea_inbox.router, prefix="/api/v1")
|
|
25
|
+
app.include_router(local_auth.router, prefix="/api")
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _mount_console_static() -> None:
|
|
29
|
+
"""挂载随 wheel 分发的前端静态产物(源码模式缺产物时安静跳过)。
|
|
30
|
+
|
|
31
|
+
必须在全部 ``include_router`` 之后调用:``/`` 挂载是兜底路由,先注册
|
|
32
|
+
的 ``/api/*`` 路由优先匹配,不会被静态文件遮蔽。
|
|
33
|
+
"""
|
|
34
|
+
from importlib.resources import files
|
|
35
|
+
|
|
36
|
+
console_dir = files("backend.api.static").joinpath("console")
|
|
37
|
+
if not console_dir.is_dir():
|
|
38
|
+
_logger.warning(
|
|
39
|
+
"Console static assets not found at backend.api.static/console; "
|
|
40
|
+
"serving API-only mode. Use a release wheel (assets bundled) or "
|
|
41
|
+
"build frontend-public first: pnpm --filter frontend-public build "
|
|
42
|
+
"&& copy out/ into src/backend/api/static/console/."
|
|
43
|
+
)
|
|
44
|
+
return
|
|
45
|
+
app.mount("/", StaticFiles(directory=str(console_dir), html=True), name="console")
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
_mount_console_static()
|
backend/api/cli.py
ADDED
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
"""Command-line interface for issue-agent-runner.
|
|
2
|
+
|
|
3
|
+
NOTE: This argparse-based parser is still the execution backend for
|
|
4
|
+
``backend.api.cli_typer``. When adding or changing CLI options, defaults, or
|
|
5
|
+
argument structure, keep ``cli_typer.py`` in sync so the actual ``iar`` entry
|
|
6
|
+
point and its help text stay consistent.
|
|
7
|
+
|
|
8
|
+
After the line-split refactor the per-subcommand bodies of
|
|
9
|
+
``_run_parsed_command`` live in :mod:`backend.api.cli_parsed_commands`. The
|
|
10
|
+
top-level dispatch table maps each ``parsed.command`` to its focused
|
|
11
|
+
handler; the helpers and the public ``main()`` entrypoint stay here.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import argparse
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
from typing import TYPE_CHECKING
|
|
19
|
+
|
|
20
|
+
from backend.api.cli_console import error_console
|
|
21
|
+
from backend.api.cli_helpers import (
|
|
22
|
+
_handle_not_initialized_error,
|
|
23
|
+
_resolve_default_daemon_target,
|
|
24
|
+
)
|
|
25
|
+
from backend.api.cli_parsed_commands import (
|
|
26
|
+
ParsedCommandContext,
|
|
27
|
+
dispatch_parsed_command,
|
|
28
|
+
)
|
|
29
|
+
from backend.engines.agent_runner.factory import (
|
|
30
|
+
create_github_client,
|
|
31
|
+
create_process_runner,
|
|
32
|
+
logger,
|
|
33
|
+
)
|
|
34
|
+
from backend.engines.agent_runner.repository_local import (
|
|
35
|
+
IARRepositoryNotInitializedError,
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
if TYPE_CHECKING:
|
|
39
|
+
from backend.core.shared.interfaces.agent_runner import IGitHubClient
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _resolve_loop_target_repository(
|
|
43
|
+
*,
|
|
44
|
+
loop_repo_id: str | None,
|
|
45
|
+
loop_repo_path: str | None,
|
|
46
|
+
runner_settings,
|
|
47
|
+
) -> "object | None":
|
|
48
|
+
"""Resolve the repository that the loop command targets.
|
|
49
|
+
|
|
50
|
+
Loops may carry their own ``repo_id`` (resolved from the recipe when
|
|
51
|
+
not overridden on the CLI). When the user did not pass ``--repo-id``
|
|
52
|
+
or ``--repo``, we infer the target from the current working directory
|
|
53
|
+
the same way ``iar daemon`` does, falling back to ``None`` so the
|
|
54
|
+
per-loop ``repo_id`` is honored.
|
|
55
|
+
"""
|
|
56
|
+
if loop_repo_path:
|
|
57
|
+
contexts = resolve_repository_targets(
|
|
58
|
+
runner_settings,
|
|
59
|
+
repo_path_override=loop_repo_path,
|
|
60
|
+
fallback_path=loop_repo_path,
|
|
61
|
+
)
|
|
62
|
+
return contexts[0] if contexts else None
|
|
63
|
+
if loop_repo_id:
|
|
64
|
+
contexts = resolve_repository_targets(
|
|
65
|
+
runner_settings,
|
|
66
|
+
repo_id=loop_repo_id,
|
|
67
|
+
)
|
|
68
|
+
return contexts[0] if contexts else None
|
|
69
|
+
cwd_target = _resolve_default_daemon_target()
|
|
70
|
+
if cwd_target.repo_id:
|
|
71
|
+
contexts = resolve_repository_targets(
|
|
72
|
+
runner_settings,
|
|
73
|
+
repo_id=cwd_target.repo_id,
|
|
74
|
+
)
|
|
75
|
+
return contexts[0] if contexts else None
|
|
76
|
+
return None
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def _run_loop_command(parsed: argparse.Namespace, process_runner) -> int:
|
|
80
|
+
"""Dispatch ``iar loop ...`` and ``iar loop-daemon`` commands."""
|
|
81
|
+
from backend.api.cli_loop import build_schedule_from_args, logger as loop_logger
|
|
82
|
+
|
|
83
|
+
runner_settings = get_agent_runner_settings()
|
|
84
|
+
|
|
85
|
+
def _state_store_factory():
|
|
86
|
+
return create_loop_state_store()
|
|
87
|
+
|
|
88
|
+
def _github_client_factory(repo_path: Path) -> "IGitHubClient":
|
|
89
|
+
return create_github_client(repo_path, process_runner)
|
|
90
|
+
|
|
91
|
+
def _content_generator_factory(repo_path: Path):
|
|
92
|
+
return create_content_generator(process_runner)
|
|
93
|
+
|
|
94
|
+
def _repo_resolver(task):
|
|
95
|
+
"""Resolve the on-disk path of the repository a loop targets."""
|
|
96
|
+
explicit_repo_id = getattr(parsed, "loop_repo_id", None)
|
|
97
|
+
explicit_repo = getattr(parsed, "loop_repo", None)
|
|
98
|
+
context = _resolve_loop_target_repository(
|
|
99
|
+
loop_repo_id=explicit_repo_id or task.repo_id,
|
|
100
|
+
loop_repo_path=explicit_repo,
|
|
101
|
+
runner_settings=runner_settings,
|
|
102
|
+
)
|
|
103
|
+
if context is None:
|
|
104
|
+
raise ValueError(
|
|
105
|
+
f"Loop '{task.id}' targets repo_id {task.repo_id!r} which is "
|
|
106
|
+
"not registered. Run `iar registry list` or pass "
|
|
107
|
+
"`--repo-id` / `--repo` to target a specific repository."
|
|
108
|
+
)
|
|
109
|
+
return context.repo_path
|
|
110
|
+
|
|
111
|
+
command = parsed.command
|
|
112
|
+
|
|
113
|
+
if command == "loop create":
|
|
114
|
+
return run_loop_create_command(parsed, state_store_factory=_state_store_factory)
|
|
115
|
+
|
|
116
|
+
if command == "loop list":
|
|
117
|
+
return run_loop_list_command(state_store_factory=_state_store_factory)
|
|
118
|
+
|
|
119
|
+
if command == "loop cancel":
|
|
120
|
+
return run_loop_cancel_command(parsed, state_store_factory=_state_store_factory)
|
|
121
|
+
|
|
122
|
+
# The remaining commands need clock + repo resolution. Validate the
|
|
123
|
+
# --cron/--every combination up front so the user gets a friendly
|
|
124
|
+
# error before we touch the loop state.
|
|
125
|
+
if command == "loop run":
|
|
126
|
+
try:
|
|
127
|
+
build_schedule_from_args(parsed)
|
|
128
|
+
except ValueError as exc:
|
|
129
|
+
loop_logger.error("%s", exc)
|
|
130
|
+
return 1
|
|
131
|
+
return run_loop_run_now_command(
|
|
132
|
+
parsed,
|
|
133
|
+
state_store_factory=_state_store_factory,
|
|
134
|
+
github_client_factory=_github_client_factory,
|
|
135
|
+
process_runner=process_runner,
|
|
136
|
+
clock=create_loop_clock(),
|
|
137
|
+
repo_resolver=_repo_resolver,
|
|
138
|
+
content_generator_factory=_content_generator_factory,
|
|
139
|
+
labels_config=None,
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
if command == "loop-daemon":
|
|
143
|
+
return run_loop_daemon_command(
|
|
144
|
+
parsed,
|
|
145
|
+
state_store_factory=_state_store_factory,
|
|
146
|
+
github_client_factory=_github_client_factory,
|
|
147
|
+
process_runner=process_runner,
|
|
148
|
+
clock=create_loop_clock(),
|
|
149
|
+
repo_resolver=_repo_resolver,
|
|
150
|
+
content_generator_factory=_content_generator_factory,
|
|
151
|
+
labels_config=None,
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
loop_logger.error("Unsupported loop command: %s", command)
|
|
155
|
+
return 1
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def _run_parsed_command(parsed: argparse.Namespace) -> int:
|
|
159
|
+
"""Run a command after CLI arguments have been parsed.
|
|
160
|
+
|
|
161
|
+
Pre-dispatch validation runs here (deprecated flag warnings,
|
|
162
|
+
``--repo``/``--repo-id`` exclusivity, the cwd-based default for
|
|
163
|
+
daemon / review-daemon / logs), then the matching handler from
|
|
164
|
+
:mod:`backend.api.cli_parsed_commands` is invoked through
|
|
165
|
+
:func:`dispatch_parsed_command`.
|
|
166
|
+
"""
|
|
167
|
+
from backend.api.cli_utils import _format_cli_exception
|
|
168
|
+
|
|
169
|
+
if parsed.config:
|
|
170
|
+
logger.warning("The --config flag is deprecated. Use config.toml or env vars instead.")
|
|
171
|
+
|
|
172
|
+
repo_id: str | None = getattr(parsed, "repo_id", None)
|
|
173
|
+
repo_override: str | None = getattr(parsed, "repo", None)
|
|
174
|
+
|
|
175
|
+
# ``container up`` 把 ``--repo`` 用作容器挂载路径、``--repo-id`` 用作仓库
|
|
176
|
+
# registry id,二者语义互补(与其它命令里二者互为仓库选择器不同),
|
|
177
|
+
# 不适用互斥校验;否则文档主路径 ``iar container up --repo <path>
|
|
178
|
+
# --repo-id <id>`` 会被误拦。
|
|
179
|
+
if repo_id is not None and repo_override is not None and parsed.command != "container up":
|
|
180
|
+
logger.error("--repo and --repo-id are mutually exclusive.")
|
|
181
|
+
return 1
|
|
182
|
+
|
|
183
|
+
# daemon / review-daemon 在未指定仓库时:
|
|
184
|
+
# 1. cwd 命中唯一 enabled 注册仓 → 仅处理该仓(与 --repo-id 等价)
|
|
185
|
+
# 2. cwd 命中 disabled 注册仓 → 报错
|
|
186
|
+
# 3. cwd 命中多个 enabled 注册仓 → 报错,要求显式选择
|
|
187
|
+
# 4. cwd 未命中任何注册仓或未初始化 → 报错,不再回退到 --all
|
|
188
|
+
if parsed.command in ("daemon", "review-daemon", "logs"):
|
|
189
|
+
if (
|
|
190
|
+
repo_id is None
|
|
191
|
+
and repo_override is None
|
|
192
|
+
and not getattr(parsed, "all_repositories", False)
|
|
193
|
+
):
|
|
194
|
+
default_target = _resolve_default_daemon_target()
|
|
195
|
+
if default_target.error:
|
|
196
|
+
logger.error(default_target.error)
|
|
197
|
+
return 1
|
|
198
|
+
repo_id = default_target.repo_id
|
|
199
|
+
|
|
200
|
+
process_runner = create_process_runner()
|
|
201
|
+
runner_settings = get_agent_runner_settings()
|
|
202
|
+
|
|
203
|
+
def github_client_factory(repo_path: Path) -> "IGitHubClient":
|
|
204
|
+
return create_github_client(repo_path, process_runner)
|
|
205
|
+
|
|
206
|
+
parsed_ctx = ParsedCommandContext(
|
|
207
|
+
parsed=parsed,
|
|
208
|
+
process_runner=process_runner,
|
|
209
|
+
runner_settings=runner_settings,
|
|
210
|
+
repo_id=repo_id,
|
|
211
|
+
repo_override=repo_override,
|
|
212
|
+
github_client_factory=github_client_factory,
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
try:
|
|
216
|
+
exit_code = dispatch_parsed_command(parsed_ctx)
|
|
217
|
+
except IARRepositoryNotInitializedError as exc:
|
|
218
|
+
return _handle_not_initialized_error(exc)
|
|
219
|
+
except Exception as exc: # noqa: BLE001 - CLI should print concise failures.
|
|
220
|
+
error_detail = _format_cli_exception(exc)
|
|
221
|
+
logger.error("iar failed:\n%s", error_detail)
|
|
222
|
+
error_console.print("[red]iar failed:[/]")
|
|
223
|
+
error_console.print(error_detail, markup=False)
|
|
224
|
+
return 1
|
|
225
|
+
|
|
226
|
+
if exit_code is None:
|
|
227
|
+
logger.error("Unsupported command: %s", parsed.command)
|
|
228
|
+
return 1
|
|
229
|
+
return exit_code
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
def main(argv: list[str] | None = None) -> int:
|
|
233
|
+
"""Run the Typer-powered CLI."""
|
|
234
|
+
from backend.api.cli_typer import main as typer_main
|
|
235
|
+
|
|
236
|
+
return typer_main(argv)
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
# Backward-compatible re-exports. After the line-split refactor these
|
|
240
|
+
# helpers live in dedicated modules; tests and downstream callers can
|
|
241
|
+
# keep importing them through ``backend.api.cli``. The actual symbols are
|
|
242
|
+
# in :mod:`backend.api.cli_reexports` so that ``cli_parsed_commands`` and
|
|
243
|
+
# ``cli`` share a single module-level name binding — that way a test
|
|
244
|
+
# ``patch("backend.api.cli.run_agent_daemon")`` correctly shadows the
|
|
245
|
+
# function the handler will call.
|
|
246
|
+
from backend.api.cli_reexports import ( # noqa: E402,F401
|
|
247
|
+
DaemonAlreadyRunningError,
|
|
248
|
+
DeliberationRequest,
|
|
249
|
+
IssueFromPrdRequest,
|
|
250
|
+
ReplSessionDeps,
|
|
251
|
+
ReplSessionInputs,
|
|
252
|
+
_ensure_gh_auth_or_prompt,
|
|
253
|
+
_expand_prd_paths,
|
|
254
|
+
_prompt_and_publish_prd_if_needed,
|
|
255
|
+
_resolve_cli_repository_targets,
|
|
256
|
+
_resolve_run_trigger,
|
|
257
|
+
acquire_daemon_locks,
|
|
258
|
+
create_content_generator,
|
|
259
|
+
create_default_session_id,
|
|
260
|
+
create_event_sink,
|
|
261
|
+
create_issue_from_prd,
|
|
262
|
+
create_loop_clock,
|
|
263
|
+
create_loop_state_store,
|
|
264
|
+
create_planner_runner,
|
|
265
|
+
create_repl_command_executor,
|
|
266
|
+
create_transcript_runner,
|
|
267
|
+
daemon_lock_dir,
|
|
268
|
+
get_agent_runner_settings,
|
|
269
|
+
release_daemon_locks,
|
|
270
|
+
require_iar_repository_initialized,
|
|
271
|
+
resolve_issue_from_prd_target,
|
|
272
|
+
resolve_prd_paths,
|
|
273
|
+
resolve_repository_targets,
|
|
274
|
+
review_once,
|
|
275
|
+
run_agent_daemon,
|
|
276
|
+
run_agent_deliberation,
|
|
277
|
+
run_agent_repositories_once,
|
|
278
|
+
run_loop_cancel_command,
|
|
279
|
+
run_loop_create_command,
|
|
280
|
+
run_loop_daemon_command,
|
|
281
|
+
run_loop_list_command,
|
|
282
|
+
run_loop_run_now_command,
|
|
283
|
+
run_repl_session,
|
|
284
|
+
run_review_daemon,
|
|
285
|
+
sync_labels,
|
|
286
|
+
write_deliberation_outputs,
|
|
287
|
+
)
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
if __name__ == "__main__":
|
|
291
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
"""Shell completion helpers and command registration for the Typer CLI.
|
|
2
|
+
|
|
3
|
+
Keeping completion logic in a dedicated module prevents ``cli_typer.py`` from
|
|
4
|
+
growing with installation utilities that are only exercised once per shell.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from enum import Enum
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
|
|
12
|
+
import typer
|
|
13
|
+
from typer.completion import get_completion_script
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class CompletionShellChoice(str, Enum):
|
|
17
|
+
"""Shells supported by the explicit completion installer."""
|
|
18
|
+
|
|
19
|
+
bash = "bash"
|
|
20
|
+
zsh = "zsh"
|
|
21
|
+
fish = "fish"
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _completion_script(shell: CompletionShellChoice) -> str:
|
|
25
|
+
"""Return the shell completion script for the iAR executable."""
|
|
26
|
+
return get_completion_script(
|
|
27
|
+
prog_name="iar",
|
|
28
|
+
complete_var="_IAR_COMPLETE",
|
|
29
|
+
shell=shell.value,
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _append_unique_line(file_path: Path, line: str) -> bool:
|
|
34
|
+
"""Append a shell profile line when it is not already present."""
|
|
35
|
+
existing_text = ""
|
|
36
|
+
if file_path.exists():
|
|
37
|
+
existing_text = file_path.read_text(encoding="utf-8")
|
|
38
|
+
if line in existing_text.splitlines():
|
|
39
|
+
return False
|
|
40
|
+
file_path.parent.mkdir(parents=True, exist_ok=True)
|
|
41
|
+
with file_path.open("a", encoding="utf-8") as profile_file:
|
|
42
|
+
if existing_text and not existing_text.endswith("\n"):
|
|
43
|
+
profile_file.write("\n")
|
|
44
|
+
profile_file.write(f"{line}\n")
|
|
45
|
+
return True
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _install_completion_script(
|
|
49
|
+
shell: CompletionShellChoice,
|
|
50
|
+
) -> tuple[Path, Path | None]:
|
|
51
|
+
"""Install iAR shell completion and return the script/profile paths."""
|
|
52
|
+
script_content = _completion_script(shell)
|
|
53
|
+
home_path = Path.home()
|
|
54
|
+
if shell is CompletionShellChoice.zsh:
|
|
55
|
+
completion_dir = home_path / ".zsh" / "completions"
|
|
56
|
+
completion_path = completion_dir / "_iar"
|
|
57
|
+
completion_path.parent.mkdir(parents=True, exist_ok=True)
|
|
58
|
+
completion_path.write_text(f"{script_content}\n", encoding="utf-8")
|
|
59
|
+
zshrc_path = home_path / ".zshrc"
|
|
60
|
+
_append_unique_line(zshrc_path, "autoload -Uz compinit && compinit")
|
|
61
|
+
source_line = f'[ -f "{completion_path}" ] && source "{completion_path}"'
|
|
62
|
+
_append_unique_line(zshrc_path, source_line)
|
|
63
|
+
return completion_path, zshrc_path
|
|
64
|
+
if shell is CompletionShellChoice.bash:
|
|
65
|
+
completion_path = home_path / ".config" / "iar" / "iar_completion.bash"
|
|
66
|
+
completion_path.parent.mkdir(parents=True, exist_ok=True)
|
|
67
|
+
completion_path.write_text(f"{script_content}\n", encoding="utf-8")
|
|
68
|
+
bashrc_path = home_path / ".bashrc"
|
|
69
|
+
source_line = f'[ -f "{completion_path}" ] && source "{completion_path}"'
|
|
70
|
+
_append_unique_line(bashrc_path, source_line)
|
|
71
|
+
return completion_path, bashrc_path
|
|
72
|
+
completion_path = home_path / ".config" / "fish" / "completions" / "iar.fish"
|
|
73
|
+
completion_path.parent.mkdir(parents=True, exist_ok=True)
|
|
74
|
+
completion_path.write_text(f"{script_content}\n", encoding="utf-8")
|
|
75
|
+
return completion_path, None
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def register_completion_commands(completion_app: typer.Typer) -> None:
|
|
79
|
+
"""Register ``completion show`` and ``completion install`` commands."""
|
|
80
|
+
|
|
81
|
+
@completion_app.command("show")
|
|
82
|
+
def completion_show_command(
|
|
83
|
+
shell: CompletionShellChoice = CompletionShellChoice.zsh,
|
|
84
|
+
) -> int:
|
|
85
|
+
"""Print a shell completion script."""
|
|
86
|
+
typer.echo(_completion_script(shell))
|
|
87
|
+
return 0
|
|
88
|
+
|
|
89
|
+
@completion_app.command("install")
|
|
90
|
+
def completion_install_command(
|
|
91
|
+
shell: CompletionShellChoice = CompletionShellChoice.zsh,
|
|
92
|
+
) -> int:
|
|
93
|
+
"""Install shell completion for the current user."""
|
|
94
|
+
completion_path, profile_path = _install_completion_script(shell)
|
|
95
|
+
typer.echo(f"Installed {shell.value} completion: {completion_path}")
|
|
96
|
+
if profile_path is not None:
|
|
97
|
+
typer.echo(f"Reload your shell with: source {profile_path}")
|
|
98
|
+
else:
|
|
99
|
+
typer.echo("Open a new terminal session to activate completion.")
|
|
100
|
+
return 0
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"""Shared Rich console instances for the IAR CLI.
|
|
2
|
+
|
|
3
|
+
Centralising ``Console`` instances avoids circular imports when CLI command
|
|
4
|
+
implementations are split into separate modules.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from rich.console import Console
|
|
10
|
+
|
|
11
|
+
console = Console()
|
|
12
|
+
error_console = Console(stderr=True)
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
"""Shared CLI helper utilities used by ``backend.api.cli``.
|
|
2
|
+
|
|
3
|
+
These helpers are thin presentation-layer adapters (auth checks, output
|
|
4
|
+
formatting, target resolution) that do not themselves invoke long-running
|
|
5
|
+
business logic. Keeping them in a dedicated module lets the main dispatch
|
|
6
|
+
file stay focused on command wiring.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import argparse
|
|
12
|
+
import dataclasses
|
|
13
|
+
import os
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
from typing import TYPE_CHECKING, Any
|
|
16
|
+
|
|
17
|
+
from backend.api.cli_console import console, error_console
|
|
18
|
+
from backend.core.use_cases.worktree_cleanup import (
|
|
19
|
+
WorktreeCleanupResult,
|
|
20
|
+
WorktreeCleanupStatus,
|
|
21
|
+
)
|
|
22
|
+
from backend.engines.agent_runner.factory import (
|
|
23
|
+
create_console_store,
|
|
24
|
+
create_github_client,
|
|
25
|
+
find_repository_match_for_path,
|
|
26
|
+
load_fresh_agent_runner_settings,
|
|
27
|
+
logger,
|
|
28
|
+
resolve_repository_targets,
|
|
29
|
+
)
|
|
30
|
+
from backend.engines.agent_runner.repository_local import (
|
|
31
|
+
IARRepositoryNotInitializedError,
|
|
32
|
+
detect_git_repository_root,
|
|
33
|
+
require_iar_repository_initialized,
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
if TYPE_CHECKING:
|
|
37
|
+
from backend.core.shared.interfaces.agent_runner import IProcessRunner
|
|
38
|
+
from backend.core.shared.models.agent_runner import RepositoryRunContext
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _ensure_gh_auth_or_prompt(repo_path: Path, process_runner: "IProcessRunner") -> None:
|
|
42
|
+
"""Check gh auth status and exit with a friendly message if not authenticated."""
|
|
43
|
+
if os.environ.get("IAR_SKIP_GH_AUTH_CHECK") == "1":
|
|
44
|
+
return
|
|
45
|
+
github_client = create_github_client(repo_path, process_runner)
|
|
46
|
+
auth_status = github_client.check_auth_status()
|
|
47
|
+
if auth_status.authenticated:
|
|
48
|
+
return
|
|
49
|
+
error_console.print("[red]GitHub CLI 认证失败。[/]")
|
|
50
|
+
if auth_status.failure_reason:
|
|
51
|
+
error_console.print(f"[red]{auth_status.failure_reason}[/]")
|
|
52
|
+
error_console.print("[yellow]请运行: gh auth login -h github.com[/]")
|
|
53
|
+
raise SystemExit(1)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _print_worktree_cleanup_result(cleanup_result: WorktreeCleanupResult) -> None:
|
|
57
|
+
"""Print a concise branch cleanup summary."""
|
|
58
|
+
if not cleanup_result.branches:
|
|
59
|
+
console.print("[green]No local iAR issue branches found.[/]")
|
|
60
|
+
return
|
|
61
|
+
|
|
62
|
+
for branch_result in cleanup_result.branches:
|
|
63
|
+
worktree_suffix = f" ({branch_result.worktree_path})" if branch_result.worktree_path else ""
|
|
64
|
+
if branch_result.status is WorktreeCleanupStatus.WOULD_DELETE:
|
|
65
|
+
console.print(
|
|
66
|
+
f"[yellow]Would delete:[/] {branch_result.branch}{worktree_suffix} - "
|
|
67
|
+
f"{branch_result.reason}"
|
|
68
|
+
)
|
|
69
|
+
elif branch_result.status is WorktreeCleanupStatus.DELETED:
|
|
70
|
+
console.print(
|
|
71
|
+
f"[green]Deleted:[/] {branch_result.branch}{worktree_suffix} - "
|
|
72
|
+
f"{branch_result.reason}"
|
|
73
|
+
)
|
|
74
|
+
elif branch_result.status is WorktreeCleanupStatus.FAILED:
|
|
75
|
+
console.print(
|
|
76
|
+
f"[red]Failed:[/] {branch_result.branch}{worktree_suffix} - {branch_result.reason}"
|
|
77
|
+
)
|
|
78
|
+
else:
|
|
79
|
+
console.print(
|
|
80
|
+
f"[dim]Skipped:[/] {branch_result.branch}{worktree_suffix} - {branch_result.reason}"
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
console.print(
|
|
84
|
+
"Cleanup summary: "
|
|
85
|
+
f"deleted={cleanup_result.deleted_count}, "
|
|
86
|
+
f"would_delete={cleanup_result.would_delete_count}, "
|
|
87
|
+
f"skipped={cleanup_result.skipped_count}, "
|
|
88
|
+
f"failed={cleanup_result.failed_count}"
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def _resolve_cli_repository_targets(
|
|
93
|
+
*,
|
|
94
|
+
parsed: argparse.Namespace,
|
|
95
|
+
runner_settings: Any,
|
|
96
|
+
repo_id: str | None,
|
|
97
|
+
repo_override: str | None,
|
|
98
|
+
) -> list["RepositoryRunContext"]:
|
|
99
|
+
"""Resolve repository targets for parsed CLI selectors."""
|
|
100
|
+
return resolve_repository_targets(
|
|
101
|
+
runner_settings,
|
|
102
|
+
repo_id=repo_id,
|
|
103
|
+
repo_path_override=repo_override,
|
|
104
|
+
all_repositories=getattr(parsed, "all_repositories", False),
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
@dataclasses.dataclass(frozen=True)
|
|
109
|
+
class _DefaultDaemonTarget:
|
|
110
|
+
"""Result of inferring a daemon target from cwd."""
|
|
111
|
+
|
|
112
|
+
repo_id: str | None
|
|
113
|
+
error: str
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def _resolve_default_daemon_target() -> _DefaultDaemonTarget:
|
|
117
|
+
"""Infer the daemon target repository from the current working directory.
|
|
118
|
+
|
|
119
|
+
Returns:
|
|
120
|
+
_DefaultDaemonTarget: when ``repo_id`` is set, use that repository;
|
|
121
|
+
when ``error`` is set, fail early with the error message. This function
|
|
122
|
+
no longer falls back to ``--all``; callers must explicitly request all
|
|
123
|
+
enabled registry entries.
|
|
124
|
+
"""
|
|
125
|
+
try:
|
|
126
|
+
cwd_git_root = detect_git_repository_root(Path.cwd())
|
|
127
|
+
except ValueError:
|
|
128
|
+
return _DefaultDaemonTarget(
|
|
129
|
+
repo_id=None,
|
|
130
|
+
error=(
|
|
131
|
+
"Current directory is not a Git repository. "
|
|
132
|
+
"Run from an initialized iAR repository, or use --all to target all enabled registry entries."
|
|
133
|
+
),
|
|
134
|
+
)
|
|
135
|
+
settings = load_fresh_agent_runner_settings()
|
|
136
|
+
match = find_repository_match_for_path(settings, cwd_git_root)
|
|
137
|
+
if match.is_unique_enabled:
|
|
138
|
+
assert match.matched_repo_id is not None # noqa: S101
|
|
139
|
+
try:
|
|
140
|
+
require_iar_repository_initialized(cwd_git_root)
|
|
141
|
+
except IARRepositoryNotInitializedError:
|
|
142
|
+
return _DefaultDaemonTarget(
|
|
143
|
+
repo_id=None,
|
|
144
|
+
error=(
|
|
145
|
+
f"Repository '{match.matched_repo_id}' is not initialized. "
|
|
146
|
+
"Run 'iar init' in the repository root, or use --all to target all enabled registry entries."
|
|
147
|
+
),
|
|
148
|
+
)
|
|
149
|
+
return _DefaultDaemonTarget(repo_id=match.matched_repo_id, error="")
|
|
150
|
+
if match.is_disabled:
|
|
151
|
+
assert match.disabled_repo_id is not None # noqa: S101
|
|
152
|
+
return _DefaultDaemonTarget(
|
|
153
|
+
repo_id=None,
|
|
154
|
+
error=(
|
|
155
|
+
f"Repository '{match.disabled_repo_id}' is disabled. "
|
|
156
|
+
"Use --repo-id to target it explicitly, or enable it in config.toml."
|
|
157
|
+
),
|
|
158
|
+
)
|
|
159
|
+
if match.is_ambiguous:
|
|
160
|
+
candidates = ", ".join(repo_id for repo_id, _ in match.enabled_candidates)
|
|
161
|
+
return _DefaultDaemonTarget(
|
|
162
|
+
repo_id=None,
|
|
163
|
+
error=(
|
|
164
|
+
f"Current directory matches multiple enabled repositories: {candidates}. "
|
|
165
|
+
"Use --repo-id to target one, or --all to target all."
|
|
166
|
+
),
|
|
167
|
+
)
|
|
168
|
+
return _DefaultDaemonTarget(
|
|
169
|
+
repo_id=None,
|
|
170
|
+
error=(
|
|
171
|
+
"Current directory is not an enabled iAR registry target. "
|
|
172
|
+
"Use --repo-id to target a registered repository, or --all to target all enabled registry entries."
|
|
173
|
+
),
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def _resolve_run_trigger(command_kind: str) -> str:
|
|
178
|
+
"""解析运行记录的 trigger 来源。
|
|
179
|
+
|
|
180
|
+
管理终端托管的子进程带有 ``IAR_CONSOLE=1`` 环境标记,记为
|
|
181
|
+
``console_*``;否则记为 ``cli_*``。
|
|
182
|
+
|
|
183
|
+
Args:
|
|
184
|
+
command_kind: ``"run"`` 或 ``"daemon"``。
|
|
185
|
+
"""
|
|
186
|
+
prefix = "console" if os.environ.get("IAR_CONSOLE") == "1" else "cli"
|
|
187
|
+
return f"{prefix}_{command_kind}"
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def _create_run_history_store_or_none():
|
|
191
|
+
"""创建运行历史存储;初始化失败时降级为 None(不阻断 CLI)。"""
|
|
192
|
+
try:
|
|
193
|
+
return create_console_store()
|
|
194
|
+
except Exception as exc: # noqa: BLE001 - history is a side channel.
|
|
195
|
+
logger.warning("Run history store unavailable: %s", exc)
|
|
196
|
+
return None
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def _handle_not_initialized_error(exc: IARRepositoryNotInitializedError) -> int:
|
|
200
|
+
"""Print a friendly error and suggest running `iar init`."""
|
|
201
|
+
error_console.print("[red]Repository is not initialized for iar.[/]")
|
|
202
|
+
error_console.print(f"Expected local config: {exc.config_path}", soft_wrap=True)
|
|
203
|
+
error_console.print("Run the following command from the repository root:")
|
|
204
|
+
error_console.print(" iar init")
|
|
205
|
+
return 1
|