agentic-project-kit 1.0.1__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.
- agentic_project_kit/__init__.py +1 -0
- agentic_project_kit/absolute_path_portability_audit.py +241 -0
- agentic_project_kit/access_level_policy.py +14 -0
- agentic_project_kit/access_levels.py +22 -0
- agentic_project_kit/action_registry.py +195 -0
- agentic_project_kit/action_specs.py +286 -0
- agentic_project_kit/agent_command_runner.py +516 -0
- agentic_project_kit/audit_test_matrix.py +36 -0
- agentic_project_kit/audit_test_matrix_cli.py +43 -0
- agentic_project_kit/chat_bootloader.py +257 -0
- agentic_project_kit/chat_entrypoint_contract.py +297 -0
- agentic_project_kit/checks.py +511 -0
- agentic_project_kit/ci_readiness.py +211 -0
- agentic_project_kit/cli.py +147 -0
- agentic_project_kit/cli_commands/__init__.py +1 -0
- agentic_project_kit/cli_commands/absolute_path_audit.py +25 -0
- agentic_project_kit/cli_commands/actions.py +21 -0
- agentic_project_kit/cli_commands/artifact_gc.py +240 -0
- agentic_project_kit/cli_commands/boot.py +55 -0
- agentic_project_kit/cli_commands/chat.py +34 -0
- agentic_project_kit/cli_commands/checks.py +305 -0
- agentic_project_kit/cli_commands/cockpit.py +103 -0
- agentic_project_kit/cli_commands/command_taxonomy.py +25 -0
- agentic_project_kit/cli_commands/commands.py +129 -0
- agentic_project_kit/cli_commands/dev.py +23 -0
- agentic_project_kit/cli_commands/direction.py +92 -0
- agentic_project_kit/cli_commands/doc_currency_audit.py +25 -0
- agentic_project_kit/cli_commands/doc_registry.py +126 -0
- agentic_project_kit/cli_commands/docs.py +223 -0
- agentic_project_kit/cli_commands/dpa.py +1338 -0
- agentic_project_kit/cli_commands/evidence.py +251 -0
- agentic_project_kit/cli_commands/github.py +34 -0
- agentic_project_kit/cli_commands/governance.py +14 -0
- agentic_project_kit/cli_commands/gui.py +23 -0
- agentic_project_kit/cli_commands/gui_readiness_gate.py +27 -0
- agentic_project_kit/cli_commands/handoff.py +183 -0
- agentic_project_kit/cli_commands/human_workflows.py +430 -0
- agentic_project_kit/cli_commands/init.py +132 -0
- agentic_project_kit/cli_commands/instruction.py +75 -0
- agentic_project_kit/cli_commands/mutation_lock_audit.py +24 -0
- agentic_project_kit/cli_commands/ns_legacy_audit.py +25 -0
- agentic_project_kit/cli_commands/pass_already_done.py +66 -0
- agentic_project_kit/cli_commands/patch_failure_discipline_audit.py +26 -0
- agentic_project_kit/cli_commands/patch_preflight.py +37 -0
- agentic_project_kit/cli_commands/path_literal_audit.py +39 -0
- agentic_project_kit/cli_commands/patterns.py +40 -0
- agentic_project_kit/cli_commands/planning_docs_audit.py +25 -0
- agentic_project_kit/cli_commands/pr.py +133 -0
- agentic_project_kit/cli_commands/pr_hygiene.py +27 -0
- agentic_project_kit/cli_commands/profiles.py +26 -0
- agentic_project_kit/cli_commands/program_redundancy_audit.py +25 -0
- agentic_project_kit/cli_commands/project_direction.py +25 -0
- agentic_project_kit/cli_commands/release.py +392 -0
- agentic_project_kit/cli_commands/release_publish_orchestration.py +36 -0
- agentic_project_kit/cli_commands/remote_branch_hygiene.py +152 -0
- agentic_project_kit/cli_commands/remote_next.py +57 -0
- agentic_project_kit/cli_commands/removed_ns_commands.py +65 -0
- agentic_project_kit/cli_commands/rule_registry.py +129 -0
- agentic_project_kit/cli_commands/rules.py +222 -0
- agentic_project_kit/cli_commands/scaffold.py +46 -0
- agentic_project_kit/cli_commands/slice.py +21 -0
- agentic_project_kit/cli_commands/standard_gates_audit_suite.py +27 -0
- agentic_project_kit/cli_commands/state.py +51 -0
- agentic_project_kit/cli_commands/status_current_state_audit.py +30 -0
- agentic_project_kit/cli_commands/todo.py +53 -0
- agentic_project_kit/cli_commands/transfer.py +19 -0
- agentic_project_kit/cli_commands/transfer_context_flow.py +398 -0
- agentic_project_kit/cli_commands/transfer_context_helpers.py +398 -0
- agentic_project_kit/cli_commands/transfer_diagnostics.py +767 -0
- agentic_project_kit/cli_commands/transfer_evidence_flow.py +368 -0
- agentic_project_kit/cli_commands/transfer_handoff_flow.py +594 -0
- agentic_project_kit/cli_commands/transfer_order_contract.py +74 -0
- agentic_project_kit/cli_commands/transfer_post_merge_complete.py +442 -0
- agentic_project_kit/cli_commands/transfer_post_merge_settle.py +171 -0
- agentic_project_kit/cli_commands/transfer_pr_closeout_complete.py +124 -0
- agentic_project_kit/cli_commands/transfer_pr_create_flow.py +743 -0
- agentic_project_kit/cli_commands/transfer_pr_merge_flow.py +547 -0
- agentic_project_kit/cli_commands/transfer_repo_after_pr.py +160 -0
- agentic_project_kit/cli_commands/transfer_repo_pre_pr.py +664 -0
- agentic_project_kit/cli_commands/transfer_runner_flow.py +131 -0
- agentic_project_kit/cli_commands/transfer_runtime_initial.py +194 -0
- agentic_project_kit/cli_commands/transfer_shared.py +274 -0
- agentic_project_kit/cli_commands/validation.py +200 -0
- agentic_project_kit/cli_commands/version.py +27 -0
- agentic_project_kit/cli_commands/work_orders.py +219 -0
- agentic_project_kit/cli_commands/workflow.py +471 -0
- agentic_project_kit/cli_commands/workflow_guard.py +26 -0
- agentic_project_kit/cli_commands/workspace.py +252 -0
- agentic_project_kit/cockpit.py +608 -0
- agentic_project_kit/cockpit_readiness.py +67 -0
- agentic_project_kit/command_authority_audit.py +192 -0
- agentic_project_kit/command_inbox_check.py +103 -0
- agentic_project_kit/command_manifest.py +591 -0
- agentic_project_kit/command_queue_contract.py +161 -0
- agentic_project_kit/command_selector.py +245 -0
- agentic_project_kit/command_taxonomy.py +264 -0
- agentic_project_kit/commit_guard.py +67 -0
- agentic_project_kit/communication_artifact_gc.py +495 -0
- agentic_project_kit/communication_rule_context.py +437 -0
- agentic_project_kit/communication_state.py +115 -0
- agentic_project_kit/composed_wrapper_invariants.py +97 -0
- agentic_project_kit/composite_short_circuit.py +140 -0
- agentic_project_kit/contract.py +251 -0
- agentic_project_kit/control_state.py +83 -0
- agentic_project_kit/doc_currency_audit.py +536 -0
- agentic_project_kit/doc_lifecycle.py +1179 -0
- agentic_project_kit/doc_lifecycle_signals.py +446 -0
- agentic_project_kit/doc_lifecycle_sweep.py +639 -0
- agentic_project_kit/doc_mesh.py +491 -0
- agentic_project_kit/doctor.py +379 -0
- agentic_project_kit/documentation_registry.py +1122 -0
- agentic_project_kit/documentation_system_audit.py +310 -0
- agentic_project_kit/dpa_current_handoff_lifecycle.py +549 -0
- agentic_project_kit/dpa_dp2_decision_readiness.py +362 -0
- agentic_project_kit/dpa_dp3_dp4_adjudication.py +604 -0
- agentic_project_kit/dpa_dp5_block_new_gate.py +294 -0
- agentic_project_kit/dpa_dp5_stage_adoption.py +615 -0
- agentic_project_kit/dpa_dp5_strict_gate.py +187 -0
- agentic_project_kit/dpa_final_closeout.py +627 -0
- agentic_project_kit/dpa_fixture_evidence.py +680 -0
- agentic_project_kit/dpa_maintainer_record_check.py +711 -0
- agentic_project_kit/dpa_post_dp2_scope_assessment.py +734 -0
- agentic_project_kit/dpa_probe_002_readiness.py +509 -0
- agentic_project_kit/dpa_probe_003_readiness.py +417 -0
- agentic_project_kit/dpa_probe_004_readiness.py +437 -0
- agentic_project_kit/dpa_readiness.py +505 -0
- agentic_project_kit/dpa_readonly_probe_execution.py +500 -0
- agentic_project_kit/dpa_registry_contracts.py +312 -0
- agentic_project_kit/dpa_renderer_readiness.py +407 -0
- agentic_project_kit/dpa_repo_adoption_assessment.py +574 -0
- agentic_project_kit/dpa_stable_readiness.py +743 -0
- agentic_project_kit/dpa_successor_projection.py +168 -0
- agentic_project_kit/dpa_workspace_init_projection.py +93 -0
- agentic_project_kit/dpa_wrt_ch001_evidence.py +327 -0
- agentic_project_kit/entrypoint_slice_runner.py +153 -0
- agentic_project_kit/evidence_clean.py +152 -0
- agentic_project_kit/evidence_commit_paths.py +214 -0
- agentic_project_kit/evidence_finalize_log.py +204 -0
- agentic_project_kit/evidence_guard.py +114 -0
- agentic_project_kit/evidence_inspector.py +507 -0
- agentic_project_kit/evidence_state_contract.py +99 -0
- agentic_project_kit/execution_mode_state.py +148 -0
- agentic_project_kit/final_summary_contract.py +85 -0
- agentic_project_kit/finalize_guard.py +139 -0
- agentic_project_kit/gatekeeper_core.py +232 -0
- agentic_project_kit/github.py +25 -0
- agentic_project_kit/governance.py +59 -0
- agentic_project_kit/gui_action_execution.py +105 -0
- agentic_project_kit/gui_action_renderer.py +81 -0
- agentic_project_kit/gui_action_run_intent.py +98 -0
- agentic_project_kit/gui_action_views.py +195 -0
- agentic_project_kit/gui_activity_log.py +76 -0
- agentic_project_kit/gui_button_catalog.py +605 -0
- agentic_project_kit/gui_cockpit.py +365 -0
- agentic_project_kit/gui_cockpit_actions.py +695 -0
- agentic_project_kit/gui_cockpit_common.py +172 -0
- agentic_project_kit/gui_cockpit_header.py +501 -0
- agentic_project_kit/gui_cockpit_sidebar.py +387 -0
- agentic_project_kit/gui_cockpit_task.py +748 -0
- agentic_project_kit/gui_command_projection.py +355 -0
- agentic_project_kit/gui_communication_modes.py +130 -0
- agentic_project_kit/gui_dry_run.py +219 -0
- agentic_project_kit/gui_gatekeeper_status.py +259 -0
- agentic_project_kit/gui_layout_plan.py +148 -0
- agentic_project_kit/gui_open_folder.py +18 -0
- agentic_project_kit/gui_output_status_panel.py +61 -0
- agentic_project_kit/gui_panel_state.py +47 -0
- agentic_project_kit/gui_presenter.py +62 -0
- agentic_project_kit/gui_readiness_gate.py +206 -0
- agentic_project_kit/gui_release_flow.py +59 -0
- agentic_project_kit/gui_task_editor.py +1077 -0
- agentic_project_kit/gui_tk_widgets.py +177 -0
- agentic_project_kit/gui_tkinter_renderer.py +87 -0
- agentic_project_kit/gui_tkinter_shell.py +1176 -0
- agentic_project_kit/gui_transfer_contract.py +28 -0
- agentic_project_kit/gui_viewmodel.py +698 -0
- agentic_project_kit/gui_window_guard.py +46 -0
- agentic_project_kit/handoff_freshness.py +390 -0
- agentic_project_kit/handoff_prompt.py +180 -0
- agentic_project_kit/handoff_state.py +254 -0
- agentic_project_kit/instruction_lint.py +455 -0
- agentic_project_kit/llm_context_carriers.py +66 -0
- agentic_project_kit/llm_execution_context.py +285 -0
- agentic_project_kit/local_command_stack.py +133 -0
- agentic_project_kit/local_feature_gate.py +82 -0
- agentic_project_kit/local_garbage_collector.py +269 -0
- agentic_project_kit/models.py +18 -0
- agentic_project_kit/mutation_lock_audit.py +421 -0
- agentic_project_kit/next_turn_evidence.py +253 -0
- agentic_project_kit/next_turn_merge_if_green.py +459 -0
- agentic_project_kit/next_turn_pr_status.py +255 -0
- agentic_project_kit/next_turn_result.py +183 -0
- agentic_project_kit/next_turn_runner.py +159 -0
- agentic_project_kit/next_turn_slot.py +107 -0
- agentic_project_kit/next_turn_status.py +274 -0
- agentic_project_kit/ns_legacy_reference_audit.py +258 -0
- agentic_project_kit/ns_slice_runner.py +16 -0
- agentic_project_kit/operational_handoff_projection.py +124 -0
- agentic_project_kit/output_contract.py +80 -0
- agentic_project_kit/output_repair.py +61 -0
- agentic_project_kit/pass_already_done.py +216 -0
- agentic_project_kit/patch_artifact_preflight.py +147 -0
- agentic_project_kit/patch_cycle_workflow.py +440 -0
- agentic_project_kit/patch_failure_discipline_audit.py +223 -0
- agentic_project_kit/patch_preflight.py +215 -0
- agentic_project_kit/path_literal_audit.py +612 -0
- agentic_project_kit/patterns.py +99 -0
- agentic_project_kit/planning_docs_consolidation_audit.py +313 -0
- agentic_project_kit/portability_shell_gate.py +40 -0
- agentic_project_kit/post_merge_handoff_refresh.py +56 -0
- agentic_project_kit/post_release.py +252 -0
- agentic_project_kit/post_release_closeout.py +616 -0
- agentic_project_kit/pr_cleanup.py +172 -0
- agentic_project_kit/pr_closeout.py +50 -0
- agentic_project_kit/pr_closeout_complete.py +344 -0
- agentic_project_kit/pr_create_or_skip.py +71 -0
- agentic_project_kit/pr_hygiene.py +210 -0
- agentic_project_kit/program_redundancy_audit.py +251 -0
- agentic_project_kit/project_direction.py +840 -0
- agentic_project_kit/protected_change_planner.py +194 -0
- agentic_project_kit/release.py +401 -0
- agentic_project_kit/release_doi_safety.py +24 -0
- agentic_project_kit/release_gate_core.py +174 -0
- agentic_project_kit/release_metadata_authority_gate.py +328 -0
- agentic_project_kit/release_metadata_prep.py +43 -0
- agentic_project_kit/release_notes.py +823 -0
- agentic_project_kit/release_prep_core.py +204 -0
- agentic_project_kit/release_prepare.py +369 -0
- agentic_project_kit/release_process_guardrails.py +247 -0
- agentic_project_kit/release_publish_core.py +114 -0
- agentic_project_kit/release_publish_orchestration.py +469 -0
- agentic_project_kit/release_state.py +552 -0
- agentic_project_kit/release_verify_core.py +175 -0
- agentic_project_kit/remote_branch_hygiene.py +223 -0
- agentic_project_kit/remote_next.py +89 -0
- agentic_project_kit/remote_next_closeout.py +101 -0
- agentic_project_kit/remote_next_order_contract.py +391 -0
- agentic_project_kit/removed_ns_commands.py +458 -0
- agentic_project_kit/removed_source_audit.py +225 -0
- agentic_project_kit/rendering.py +13 -0
- agentic_project_kit/repair_report.py +79 -0
- agentic_project_kit/repo_identity.py +59 -0
- agentic_project_kit/result_report_classifier.py +57 -0
- agentic_project_kit/rule_ack.py +105 -0
- agentic_project_kit/rule_preservation.py +58 -0
- agentic_project_kit/rule_refresh.py +174 -0
- agentic_project_kit/rule_registry_registration.py +322 -0
- agentic_project_kit/rule_registry_report.py +206 -0
- agentic_project_kit/rule_registry_validator.py +704 -0
- agentic_project_kit/rule_snapshot.py +98 -0
- agentic_project_kit/rule_source_validator.py +133 -0
- agentic_project_kit/run_summary.py +68 -0
- agentic_project_kit/run_summary_renderer.py +238 -0
- agentic_project_kit/runtime_validator.py +68 -0
- agentic_project_kit/safe_file_replace.py +35 -0
- agentic_project_kit/safe_patch.py +47 -0
- agentic_project_kit/safe_push.py +218 -0
- agentic_project_kit/safe_remove_diagnostic.py +51 -0
- agentic_project_kit/scaffold.py +69 -0
- agentic_project_kit/site_claims.py +444 -0
- agentic_project_kit/site_generator.py +1140 -0
- agentic_project_kit/slice_gate.py +225 -0
- agentic_project_kit/slice_runner.py +75 -0
- agentic_project_kit/standard_gates_audit_suite.py +167 -0
- agentic_project_kit/state_freshness.py +135 -0
- agentic_project_kit/status_current_state_audit.py +762 -0
- agentic_project_kit/successor_handoff_package.py +1763 -0
- agentic_project_kit/templates.py +292 -0
- agentic_project_kit/terminal_block_guard.py +166 -0
- agentic_project_kit/terminal_logging.py +275 -0
- agentic_project_kit/todo.py +97 -0
- agentic_project_kit/transfer_closeout.py +136 -0
- agentic_project_kit/transfer_continue.py +215 -0
- agentic_project_kit/transfer_local_runner.py +101 -0
- agentic_project_kit/transfer_operation_monitor.py +236 -0
- agentic_project_kit/transfer_post_merge_lifecycle.py +356 -0
- agentic_project_kit/transfer_post_merge_settle.py +383 -0
- agentic_project_kit/transfer_pr_actions.py +164 -0
- agentic_project_kit/transfer_remote_next.py +959 -0
- agentic_project_kit/transfer_repo_actions.py +2253 -0
- agentic_project_kit/transfer_runner.py +347 -0
- agentic_project_kit/transfer_safety_context.py +291 -0
- agentic_project_kit/transfer_state.py +483 -0
- agentic_project_kit/transfer_uplink.py +471 -0
- agentic_project_kit/transfer_workflow_next.py +148 -0
- agentic_project_kit/typed_work_order_evidence.py +58 -0
- agentic_project_kit/typed_work_order_queue.py +166 -0
- agentic_project_kit/typed_work_order_runner.py +168 -0
- agentic_project_kit/work_cycle.py +317 -0
- agentic_project_kit/work_discard_changes.py +129 -0
- agentic_project_kit/work_order_patch.py +199 -0
- agentic_project_kit/work_order_runner.py +120 -0
- agentic_project_kit/work_order_uploader.py +220 -0
- agentic_project_kit/work_order_validator.py +185 -0
- agentic_project_kit/work_orders.py +246 -0
- agentic_project_kit/workflow_guard.py +262 -0
- agentic_project_kit/workflow_summary_runner.py +124 -0
- agentic_project_kit/workspace.py +665 -0
- agentic_project_kit/workspace_adopt.py +471 -0
- agentic_project_kit/workspace_dpa_intake.py +406 -0
- agentic_project_kit/workspace_init.py +487 -0
- agentic_project_kit/workspace_lock.py +129 -0
- agentic_project_kit/workspace_remove.py +381 -0
- agentic_project_kit/workspace_upgrade.py +324 -0
- agentic_project_kit/wrapper_live_status.py +104 -0
- agentic_project_kit-1.0.1.dist-info/METADATA +724 -0
- agentic_project_kit-1.0.1.dist-info/RECORD +310 -0
- agentic_project_kit-1.0.1.dist-info/WHEEL +4 -0
- agentic_project_kit-1.0.1.dist-info/entry_points.txt +3 -0
- agentic_project_kit-1.0.1.dist-info/licenses/LICENSE +10 -0
|
@@ -0,0 +1,471 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import subprocess
|
|
5
|
+
import sys
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
import typer
|
|
10
|
+
|
|
11
|
+
from agentic_project_kit.workspace import LEGACY_DEFAULTS, load_workspace
|
|
12
|
+
|
|
13
|
+
workflow_app = typer.Typer(help="Run and inspect bounded local workflow handoff states.")
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _clean_git_env() -> dict[str, str]:
|
|
17
|
+
env = os.environ.copy()
|
|
18
|
+
for key in ("GIT_DIR", "GIT_WORK_TREE", "GIT_INDEX_FILE", "GIT_OBJECT_DIRECTORY", "GIT_ALTERNATE_OBJECT_DIRECTORIES"):
|
|
19
|
+
env.pop(key, None)
|
|
20
|
+
return env
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
VALID_STATES = {"IDLE", "TEST", "UPLOAD", "CLEANUP", "REQUESTED", "RUNNING", "UPLOADED", "FAILED"}
|
|
24
|
+
STATE_FILE = Path(".agentic/workflow_state")
|
|
25
|
+
WORK_FILE = Path(".agentic/current_work.yaml")
|
|
26
|
+
WORK_ITEMS_DIR = Path(".agentic/work_items")
|
|
27
|
+
BRANCH_FILE = Path(LEGACY_DEFAULTS.tmp_root) / "agent-evidence" / "latest-branch.txt"
|
|
28
|
+
REPORT_FILE = Path(LEGACY_DEFAULTS.reports_root) / "CURRENT_WORKFLOW_OUTPUT.md"
|
|
29
|
+
NEXT_STEP_SCRIPT = Path("tools/next-step.py")
|
|
30
|
+
TEMP_PREFIX = "temp/workflow-evidence-"
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _rooted(path: Path, root: Path) -> Path:
|
|
34
|
+
return root.resolve() / path
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _read_state(root: Path) -> str:
|
|
38
|
+
state_path = _rooted(STATE_FILE, root)
|
|
39
|
+
if not state_path.exists():
|
|
40
|
+
raise typer.BadParameter(f"missing state file: {STATE_FILE}")
|
|
41
|
+
state = state_path.read_text(encoding="utf-8").strip()
|
|
42
|
+
if state not in VALID_STATES:
|
|
43
|
+
raise typer.BadParameter(f"invalid workflow state: {state}")
|
|
44
|
+
return state
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _work_items_dir(root: Path) -> Path:
|
|
49
|
+
return _rooted(WORK_ITEMS_DIR, root)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _safe_work_item_name(name: str) -> str:
|
|
53
|
+
normalized = name.strip()
|
|
54
|
+
if not normalized:
|
|
55
|
+
raise typer.BadParameter("missing workflow item name")
|
|
56
|
+
allowed = set("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_")
|
|
57
|
+
if any(char not in allowed for char in normalized):
|
|
58
|
+
raise typer.BadParameter(f"unsafe workflow item name: {name}")
|
|
59
|
+
if normalized in {".", ".."}:
|
|
60
|
+
raise typer.BadParameter(f"unsafe workflow item name: {name}")
|
|
61
|
+
return normalized
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _work_item_path(root: Path, name: str) -> Path:
|
|
65
|
+
return _work_items_dir(root) / f"{_safe_work_item_name(name)}.yaml"
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def _available_work_items(root: Path) -> list[str]:
|
|
69
|
+
directory = _work_items_dir(root)
|
|
70
|
+
if not directory.exists():
|
|
71
|
+
return []
|
|
72
|
+
return sorted(path.stem for path in directory.glob("*.yaml"))
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def _copy_work_item_to_temp_run_file(root: Path, name: str) -> Path:
|
|
76
|
+
source = _work_item_path(root, name)
|
|
77
|
+
if not source.exists():
|
|
78
|
+
raise typer.BadParameter(f"unknown workflow item: {_safe_work_item_name(name)}")
|
|
79
|
+
target = load_workspace(root).agent_evidence_dir() / f"run-{_safe_work_item_name(name)}.yaml"
|
|
80
|
+
target.parent.mkdir(parents=True, exist_ok=True)
|
|
81
|
+
target.write_text(source.read_text(encoding="utf-8"), encoding="utf-8")
|
|
82
|
+
return target
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def _workflow_request_state(root: Path) -> str:
|
|
86
|
+
work_path = _rooted(WORK_FILE, root)
|
|
87
|
+
if not work_path.exists():
|
|
88
|
+
raise typer.BadParameter(f"missing declarative workflow file: {WORK_FILE}")
|
|
89
|
+
for line in work_path.read_text(encoding="utf-8").splitlines():
|
|
90
|
+
stripped = line.strip()
|
|
91
|
+
if stripped.startswith("state:"):
|
|
92
|
+
return stripped.split(":", 1)[1].strip().upper() or "READY"
|
|
93
|
+
return "READY"
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def _set_workflow_file_request_state(work_path: Path, request_state: str) -> None:
|
|
97
|
+
if not work_path.exists():
|
|
98
|
+
raise typer.BadParameter(f"missing declarative workflow file: {work_path}")
|
|
99
|
+
normalized = request_state.upper()
|
|
100
|
+
if normalized not in {"READY", "REQUESTED"}:
|
|
101
|
+
raise typer.BadParameter(f"invalid workflow request state: {request_state}")
|
|
102
|
+
lines = work_path.read_text(encoding="utf-8").splitlines()
|
|
103
|
+
replaced = False
|
|
104
|
+
output: list[str] = []
|
|
105
|
+
for line in lines:
|
|
106
|
+
if line.strip().startswith("state:") and not replaced:
|
|
107
|
+
indent = line[: len(line) - len(line.lstrip())]
|
|
108
|
+
output.append(f"{indent}state: {normalized}")
|
|
109
|
+
replaced = True
|
|
110
|
+
else:
|
|
111
|
+
output.append(line)
|
|
112
|
+
if not replaced:
|
|
113
|
+
output.insert(1 if output else 0, f"state: {normalized}")
|
|
114
|
+
work_path.write_text("\n".join(output) + "\n", encoding="utf-8")
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def _set_workflow_request_state(root: Path, request_state: str) -> None:
|
|
118
|
+
_set_workflow_file_request_state(_rooted(WORK_FILE, root), request_state)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def _run_next_step(root: Path, extra_args: list[str] | None = None, env: dict[str, str] | None = None) -> int:
|
|
122
|
+
script = _rooted(NEXT_STEP_SCRIPT, root)
|
|
123
|
+
if not script.exists():
|
|
124
|
+
raise typer.BadParameter(f"missing workflow entrypoint: {NEXT_STEP_SCRIPT}")
|
|
125
|
+
run_env = os.environ.copy()
|
|
126
|
+
if env:
|
|
127
|
+
run_env.update(env)
|
|
128
|
+
completed = subprocess.run([sys.executable, str(script), *(extra_args or [])], cwd=root.resolve(), env=run_env, check=False)
|
|
129
|
+
return int(completed.returncode)
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def _run_git(root: Path, args: list[str]) -> subprocess.CompletedProcess[str]:
|
|
133
|
+
return subprocess.run(
|
|
134
|
+
["git", *args],
|
|
135
|
+
cwd=root.resolve(),
|
|
136
|
+
text=True,
|
|
137
|
+
capture_output=True,
|
|
138
|
+
check=False,
|
|
139
|
+
env=_clean_git_env(),
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def _is_git_repository(root: Path) -> bool:
|
|
144
|
+
result = _run_git(root, ["rev-parse", "--is-inside-work-tree"])
|
|
145
|
+
return result.returncode == 0 and result.stdout.strip() == "true"
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def _working_tree_dirty(root: Path) -> bool:
|
|
151
|
+
resolved = root.resolve()
|
|
152
|
+
if not (resolved / ".git").exists():
|
|
153
|
+
return False
|
|
154
|
+
if not _is_git_repository(resolved):
|
|
155
|
+
return False
|
|
156
|
+
result = _run_git(resolved, ["status", "--porcelain"])
|
|
157
|
+
return result.returncode == 0 and bool(result.stdout.strip())
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def _current_branch(root: Path) -> str:
|
|
161
|
+
result = _run_git(root.resolve(), ["branch", "--show-current"])
|
|
162
|
+
if result.returncode != 0:
|
|
163
|
+
return ""
|
|
164
|
+
return result.stdout.strip()
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def _current_work_mentions_git_pull_ff_only(root: Path) -> bool:
|
|
168
|
+
work_path = _rooted(WORK_FILE, root)
|
|
169
|
+
if not work_path.exists():
|
|
170
|
+
return False
|
|
171
|
+
return "git_pull_ff_only" in work_path.read_text(encoding="utf-8")
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def _workflow_go_guard_reason(root: Path) -> str | None:
|
|
175
|
+
branch = _current_branch(root)
|
|
176
|
+
if branch == "main":
|
|
177
|
+
return None
|
|
178
|
+
if not _working_tree_dirty(root):
|
|
179
|
+
return None
|
|
180
|
+
if not _current_work_mentions_git_pull_ff_only(root):
|
|
181
|
+
return None
|
|
182
|
+
return "refusing workflow go on a dirty non-main branch because current_work includes git_pull_ff_only"
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def _status_interpretation(state: str, work_state: str | None, dirty: bool) -> tuple[list[str], list[str]]:
|
|
186
|
+
interpretation: list[str] = []
|
|
187
|
+
recommendation: list[str] = []
|
|
188
|
+
if dirty:
|
|
189
|
+
interpretation.append("Working tree has uncommitted changes.")
|
|
190
|
+
recommendation.append("Run: git status --short")
|
|
191
|
+
recommendation.append("Do not start workflow automation until the working tree is clean or intentionally staged.")
|
|
192
|
+
return interpretation, recommendation
|
|
193
|
+
if work_state == "REQUESTED":
|
|
194
|
+
interpretation.append("A workflow request is pending.")
|
|
195
|
+
recommendation.append("Run: agentic-kit workflow run")
|
|
196
|
+
elif state == "UPLOADED":
|
|
197
|
+
interpretation.append("Workflow evidence was uploaded and cleanup is pending.")
|
|
198
|
+
recommendation.append("Run: agentic-kit workflow cleanup")
|
|
199
|
+
elif state == "FAILED":
|
|
200
|
+
interpretation.append("The last workflow step failed.")
|
|
201
|
+
recommendation.append("Run: agentic-kit workflow fail-report")
|
|
202
|
+
recommendation.append("Inspect docs/reports/CURRENT_WORKFLOW_OUTPUT.md and git status before cleanup or retry.")
|
|
203
|
+
recommendation.append("Do not rerun workflow automation until the failure cause is understood.")
|
|
204
|
+
elif state == "IDLE" and work_state in {None, "READY"}:
|
|
205
|
+
interpretation.append("No active workflow request.")
|
|
206
|
+
recommendation.append("Define one concrete slice before requesting workflow automation.")
|
|
207
|
+
recommendation.append("Run: agentic-kit workflow go")
|
|
208
|
+
recommendation.append("For explicit two-step control: agentic-kit workflow request, then agentic-kit workflow run")
|
|
209
|
+
else:
|
|
210
|
+
interpretation.append("Workflow state requires manual review.")
|
|
211
|
+
recommendation.append("Inspect workflow status and evidence before changing state.")
|
|
212
|
+
return interpretation, recommendation
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
def _print_status_explanation(root: Path, state: str, work_state: str | None, report_present: bool) -> None:
|
|
216
|
+
dirty = _working_tree_dirty(root)
|
|
217
|
+
interpretation, recommendation = _status_interpretation(state, work_state, dirty)
|
|
218
|
+
typer.echo("")
|
|
219
|
+
typer.echo("Safety:")
|
|
220
|
+
typer.echo("- This command is read-only.")
|
|
221
|
+
if report_present:
|
|
222
|
+
typer.echo("- current_report points to the latest workflow-output summary, not necessarily uploadable local evidence.")
|
|
223
|
+
typer.echo("")
|
|
224
|
+
typer.echo("Interpretation:")
|
|
225
|
+
for line in interpretation:
|
|
226
|
+
typer.echo(f"- {line}")
|
|
227
|
+
typer.echo("")
|
|
228
|
+
typer.echo("Recommended next step:")
|
|
229
|
+
for line in recommendation:
|
|
230
|
+
typer.echo(f"- {line}")
|
|
231
|
+
typer.echo("")
|
|
232
|
+
typer.echo("Optional checks:")
|
|
233
|
+
typer.echo("- agentic-kit doctor")
|
|
234
|
+
typer.echo("- agentic-kit check-docs")
|
|
235
|
+
|
|
236
|
+
def _safe_temp_branch(branch: str) -> str | None:
|
|
237
|
+
normalized = branch.strip()
|
|
238
|
+
if not normalized.startswith(TEMP_PREFIX):
|
|
239
|
+
return None
|
|
240
|
+
if ".." in normalized or normalized.endswith("/"):
|
|
241
|
+
return None
|
|
242
|
+
return normalized
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
def _local_temp_branches(root: Path) -> list[str]:
|
|
246
|
+
result = _run_git(root, ["branch", "--list", f"{TEMP_PREFIX}*"])
|
|
247
|
+
if result.returncode != 0:
|
|
248
|
+
raise typer.BadParameter(result.stderr.strip() or "failed to list local workflow evidence branches")
|
|
249
|
+
branches: list[str] = []
|
|
250
|
+
for line in result.stdout.splitlines():
|
|
251
|
+
branch = _safe_temp_branch(line.replace("*", "", 1).strip())
|
|
252
|
+
if branch is not None:
|
|
253
|
+
branches.append(branch)
|
|
254
|
+
return branches
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
def _remote_temp_branches(root: Path) -> list[str]:
|
|
258
|
+
result = _run_git(root, ["ls-remote", "--heads", "origin", f"{TEMP_PREFIX}*"])
|
|
259
|
+
if result.returncode != 0:
|
|
260
|
+
return []
|
|
261
|
+
branches: list[str] = []
|
|
262
|
+
for line in result.stdout.splitlines():
|
|
263
|
+
ref = line.split()[-1] if line.split() else ""
|
|
264
|
+
prefix = "refs/heads/"
|
|
265
|
+
branch = ref[len(prefix) :] if ref.startswith(prefix) else ref
|
|
266
|
+
safe = _safe_temp_branch(branch)
|
|
267
|
+
if safe is not None:
|
|
268
|
+
branches.append(safe)
|
|
269
|
+
return branches
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
def _cleanup_stale_temp_branches(root: Path) -> int:
|
|
273
|
+
if not _is_git_repository(root):
|
|
274
|
+
return 0
|
|
275
|
+
local_branches = _local_temp_branches(root)
|
|
276
|
+
remote_branches = _remote_temp_branches(root)
|
|
277
|
+
branches = sorted(set(local_branches + remote_branches))
|
|
278
|
+
if not branches:
|
|
279
|
+
return 0
|
|
280
|
+
for branch in branches:
|
|
281
|
+
safe_branch = _safe_temp_branch(branch)
|
|
282
|
+
if safe_branch is None:
|
|
283
|
+
raise typer.BadParameter(f"refusing unsafe workflow evidence branch cleanup target: {branch}")
|
|
284
|
+
if safe_branch in local_branches:
|
|
285
|
+
local_delete = _run_git(root, ["branch", "-D", safe_branch])
|
|
286
|
+
if local_delete.returncode != 0:
|
|
287
|
+
raise typer.BadParameter(local_delete.stderr.strip() or f"failed to delete local branch: {safe_branch}")
|
|
288
|
+
typer.echo(f"Deleted local stale workflow evidence branch: {safe_branch}")
|
|
289
|
+
if safe_branch in remote_branches:
|
|
290
|
+
remote_delete = _run_git(root, ["push", "origin", "--delete", safe_branch])
|
|
291
|
+
if remote_delete.returncode != 0:
|
|
292
|
+
raise typer.BadParameter(remote_delete.stderr.strip() or f"failed to delete remote branch: {safe_branch}")
|
|
293
|
+
typer.echo(f"Deleted remote stale workflow evidence branch: {safe_branch}")
|
|
294
|
+
return len(branches)
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
@workflow_app.command("request")
|
|
298
|
+
def workflow_request(
|
|
299
|
+
project_root: Path = typer.Option(Path("."), "--root", help="Project root containing .agentic/current_work.yaml."),
|
|
300
|
+
) -> None:
|
|
301
|
+
"""Request the configured declarative workflow without running it."""
|
|
302
|
+
root = project_root.resolve()
|
|
303
|
+
state = _read_state(root)
|
|
304
|
+
if state != "IDLE":
|
|
305
|
+
raise typer.BadParameter(f"refusing to request workflow from state: {state}")
|
|
306
|
+
_workflow_request_state(root)
|
|
307
|
+
_set_workflow_request_state(root, "REQUESTED")
|
|
308
|
+
typer.echo(f"Current workflow request file: {WORK_FILE}")
|
|
309
|
+
typer.echo("Workflow request state: REQUESTED")
|
|
310
|
+
typer.echo("Next state: IDLE")
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
@workflow_app.command("run")
|
|
314
|
+
def workflow_run(
|
|
315
|
+
name: str | None = typer.Argument(None, help="Optional stored workflow item name to set before running."),
|
|
316
|
+
project_root: Path = typer.Option(Path("."), "--root", help="Project root containing tools/next-step.py."),
|
|
317
|
+
) -> None:
|
|
318
|
+
"""Run the current workflow, or set a stored workflow item and run it."""
|
|
319
|
+
root = project_root.resolve()
|
|
320
|
+
if name is None:
|
|
321
|
+
raise typer.Exit(code=_run_next_step(root))
|
|
322
|
+
state = _read_state(root)
|
|
323
|
+
if state != "IDLE":
|
|
324
|
+
raise typer.BadParameter(f"refusing to run workflow item from state: {state}")
|
|
325
|
+
run_file = _copy_work_item_to_temp_run_file(root, name)
|
|
326
|
+
_set_workflow_file_request_state(run_file, "REQUESTED")
|
|
327
|
+
typer.echo(f"Workflow item selected: {_safe_work_item_name(name)}")
|
|
328
|
+
typer.echo(f"Workflow run file: {run_file.relative_to(root.resolve())}")
|
|
329
|
+
typer.echo("Workflow request state: REQUESTED")
|
|
330
|
+
try:
|
|
331
|
+
exit_code = _run_next_step(root, env={"AGENTIC_WORKFLOW_FILE": str(run_file)})
|
|
332
|
+
finally:
|
|
333
|
+
run_file.unlink(missing_ok=True)
|
|
334
|
+
raise typer.Exit(code=exit_code)
|
|
335
|
+
|
|
336
|
+
@workflow_app.command("state")
|
|
337
|
+
def workflow_state(
|
|
338
|
+
project_root: Path = typer.Option(Path("."), "--root", help="Project root containing .agentic/workflow_state."),
|
|
339
|
+
) -> None:
|
|
340
|
+
"""Show guided workflow state; shortcut for workflow status --explain."""
|
|
341
|
+
workflow_status(project_root=project_root, explain=True)
|
|
342
|
+
|
|
343
|
+
|
|
344
|
+
@workflow_app.command("list")
|
|
345
|
+
def workflow_list(
|
|
346
|
+
project_root: Path = typer.Option(Path("."), "--root", help="Project root containing .agentic/work_items."),
|
|
347
|
+
) -> None:
|
|
348
|
+
"""List stored local workflow items."""
|
|
349
|
+
root = project_root.resolve()
|
|
350
|
+
items = _available_work_items(root)
|
|
351
|
+
typer.echo(f"work_items_dir={WORK_ITEMS_DIR}")
|
|
352
|
+
if not items:
|
|
353
|
+
typer.echo("No stored workflow items found.")
|
|
354
|
+
return
|
|
355
|
+
for item in items:
|
|
356
|
+
typer.echo(f"- {item}")
|
|
357
|
+
|
|
358
|
+
|
|
359
|
+
@workflow_app.command("show")
|
|
360
|
+
def workflow_show(
|
|
361
|
+
name: str | None = typer.Argument(None, help="Optional stored workflow item name."),
|
|
362
|
+
project_root: Path = typer.Option(Path("."), "--root", help="Project root containing .agentic/current_work.yaml."),
|
|
363
|
+
) -> None:
|
|
364
|
+
"""Show the current workflow request or one stored workflow item."""
|
|
365
|
+
root = project_root.resolve()
|
|
366
|
+
path = _rooted(WORK_FILE, root) if name is None else _work_item_path(root, name)
|
|
367
|
+
if not path.exists():
|
|
368
|
+
if name is None:
|
|
369
|
+
raise typer.BadParameter(f"missing current workflow file: {WORK_FILE}")
|
|
370
|
+
raise typer.BadParameter(f"unknown workflow item: {_safe_work_item_name(name)}")
|
|
371
|
+
shown_path = WORK_FILE if name is None else WORK_ITEMS_DIR / f"{_safe_work_item_name(name)}.yaml"
|
|
372
|
+
typer.echo(f"workflow_file={shown_path}")
|
|
373
|
+
typer.echo(path.read_text(encoding="utf-8").rstrip())
|
|
374
|
+
|
|
375
|
+
@workflow_app.command("go")
|
|
376
|
+
def workflow_go(
|
|
377
|
+
project_root: Path = typer.Option(Path("."), "--root", help="Project root containing .agentic/current_work.yaml and tools/next-step.py."),
|
|
378
|
+
) -> None:
|
|
379
|
+
"""Request the configured workflow and run one bounded step."""
|
|
380
|
+
root = project_root.resolve()
|
|
381
|
+
state = _read_state(root)
|
|
382
|
+
if state != "IDLE":
|
|
383
|
+
raise typer.BadParameter(f"refusing to start workflow from state: {state}")
|
|
384
|
+
guard_reason = _workflow_go_guard_reason(root)
|
|
385
|
+
if guard_reason is not None:
|
|
386
|
+
raise typer.BadParameter(guard_reason)
|
|
387
|
+
_workflow_request_state(root)
|
|
388
|
+
_set_workflow_request_state(root, "REQUESTED")
|
|
389
|
+
typer.echo(f"Current workflow request file: {WORK_FILE}")
|
|
390
|
+
typer.echo("Workflow request state: REQUESTED")
|
|
391
|
+
typer.echo("Running one bounded workflow step.")
|
|
392
|
+
raise typer.Exit(code=_run_next_step(root))
|
|
393
|
+
|
|
394
|
+
|
|
395
|
+
@workflow_app.command("status")
|
|
396
|
+
def workflow_status(
|
|
397
|
+
project_root: Path = typer.Option(Path("."), "--root", help="Project root containing .agentic/workflow_state."),
|
|
398
|
+
explain: bool = typer.Option(False, "--explain", help="Explain the current state and recommend a safe next step."),
|
|
399
|
+
) -> None:
|
|
400
|
+
"""Print the current workflow state and bounded evidence pointers."""
|
|
401
|
+
root = project_root.resolve()
|
|
402
|
+
state = _read_state(root)
|
|
403
|
+
typer.echo(f"workflow_state={state}")
|
|
404
|
+
work_file = _rooted(WORK_FILE, root)
|
|
405
|
+
typer.echo(f"current_work={'present' if work_file.exists() else 'missing'}")
|
|
406
|
+
work_state: str | None = None
|
|
407
|
+
if work_file.exists():
|
|
408
|
+
work_state = _workflow_request_state(root)
|
|
409
|
+
typer.echo(f"current_work_state={work_state}")
|
|
410
|
+
workspace = load_workspace(root)
|
|
411
|
+
branch_file = workspace.agent_evidence_dir() / "latest-branch.txt"
|
|
412
|
+
if branch_file.exists():
|
|
413
|
+
typer.echo(f"evidence_branch={branch_file.read_text(encoding='utf-8').strip()}")
|
|
414
|
+
report_file = workspace.current_workflow_output_path()
|
|
415
|
+
report_present = report_file.exists()
|
|
416
|
+
if report_present:
|
|
417
|
+
typer.echo(f"current_report={workspace.path_text(report_file)}")
|
|
418
|
+
if explain:
|
|
419
|
+
_print_status_explanation(root, state, work_state, report_present)
|
|
420
|
+
|
|
421
|
+
|
|
422
|
+
@workflow_app.command("fail-report")
|
|
423
|
+
def workflow_fail_report(
|
|
424
|
+
project_root: Path = typer.Option(Path("."), "--root", help="Project root containing tools/next-step.py."),
|
|
425
|
+
) -> None:
|
|
426
|
+
"""Upload preserved FAILED workflow evidence without cleanup or retry."""
|
|
427
|
+
root = project_root.resolve()
|
|
428
|
+
state = _read_state(root)
|
|
429
|
+
if state != "FAILED":
|
|
430
|
+
raise typer.BadParameter(f"fail-report requires FAILED state, got {state}")
|
|
431
|
+
raise typer.Exit(code=_run_next_step(root, ["--fail-report"]))
|
|
432
|
+
|
|
433
|
+
|
|
434
|
+
@workflow_app.command("upload-output")
|
|
435
|
+
@workflow_app.command("upload")
|
|
436
|
+
def workflow_upload(
|
|
437
|
+
project_root: Path = typer.Option(Path("."), "--root", help="Project root containing tools/next-step.py."),
|
|
438
|
+
) -> None:
|
|
439
|
+
"""Alias for upload-output."""
|
|
440
|
+
workflow_upload_output(project_root=project_root)
|
|
441
|
+
|
|
442
|
+
def workflow_upload_output(
|
|
443
|
+
project_root: Path = typer.Option(Path("."), "--root", help="Project root containing tools/next-step.py."),
|
|
444
|
+
) -> None:
|
|
445
|
+
"""Upload the latest bounded local workflow output evidence for review."""
|
|
446
|
+
root = project_root.resolve()
|
|
447
|
+
state = _read_state(root)
|
|
448
|
+
if state == "UPLOADED":
|
|
449
|
+
raise typer.BadParameter("output evidence is already uploaded; run workflow cleanup after review")
|
|
450
|
+
if state == "CLEANUP":
|
|
451
|
+
raise typer.BadParameter("workflow cleanup is already pending")
|
|
452
|
+
typer.echo("Uploading latest bounded workflow output evidence.")
|
|
453
|
+
raise typer.Exit(code=_run_next_step(root, ["--upload-output"]))
|
|
454
|
+
|
|
455
|
+
|
|
456
|
+
@workflow_app.command("cleanup")
|
|
457
|
+
def workflow_cleanup(
|
|
458
|
+
project_root: Path = typer.Option(Path("."), "--root", help="Project root containing tools/next-step.py."),
|
|
459
|
+
) -> None:
|
|
460
|
+
"""Cleanup completed or stale temporary workflow evidence branches."""
|
|
461
|
+
root = project_root.resolve()
|
|
462
|
+
state = _read_state(root)
|
|
463
|
+
if state in {"UPLOADED", "CLEANUP"}:
|
|
464
|
+
raise typer.Exit(code=_run_next_step(root))
|
|
465
|
+
stale_count = _cleanup_stale_temp_branches(root)
|
|
466
|
+
typer.echo(f"workflow_state={state}")
|
|
467
|
+
if stale_count:
|
|
468
|
+
typer.echo(f"Cleaned stale workflow evidence branches: {stale_count}")
|
|
469
|
+
else:
|
|
470
|
+
typer.echo("No cleanup action available for current state.")
|
|
471
|
+
raise typer.Exit(code=0)
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
5
|
+
import typer
|
|
6
|
+
|
|
7
|
+
from agentic_project_kit.workflow_guard import render_findings, run_workflow_guard
|
|
8
|
+
|
|
9
|
+
workflow_guard_app = typer.Typer(help="Diagnose recurring workflow failure patterns before mutation.")
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@workflow_guard_app.command("check")
|
|
13
|
+
def workflow_guard_check(paths: Optional[list[str]] = typer.Argument(None)) -> None:
|
|
14
|
+
findings = run_workflow_guard(paths or [])
|
|
15
|
+
typer.echo(render_findings(findings))
|
|
16
|
+
if findings:
|
|
17
|
+
raise typer.Exit(1)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@workflow_guard_app.command("diagnose")
|
|
21
|
+
def workflow_guard_diagnose(paths: Optional[list[str]] = typer.Argument(None)) -> None:
|
|
22
|
+
findings = run_workflow_guard(paths or [])
|
|
23
|
+
typer.echo(render_findings(findings))
|
|
24
|
+
if findings:
|
|
25
|
+
typer.echo("Repair mode: produce a repair plan first; only narrow safe repairs may be automated.")
|
|
26
|
+
raise typer.Exit(1)
|