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,140 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from typing import Iterable, Protocol
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
TERMINAL_BAD_STATUSES = {"FAIL", "BLOCKED"}
|
|
8
|
+
DEFAULT_EFFECTFUL_ACTIONS_AFTER_FAILURE = {
|
|
9
|
+
"commit",
|
|
10
|
+
"push",
|
|
11
|
+
"push-current",
|
|
12
|
+
"pr-create",
|
|
13
|
+
"pr-create-complete",
|
|
14
|
+
"pr-complete",
|
|
15
|
+
"pr-merge",
|
|
16
|
+
"pr-merge-safe",
|
|
17
|
+
"merge",
|
|
18
|
+
"admin-refresh-pr",
|
|
19
|
+
"post-merge-complete",
|
|
20
|
+
"prepare-successor-handoff",
|
|
21
|
+
"successor-handoff",
|
|
22
|
+
"handoff",
|
|
23
|
+
"release",
|
|
24
|
+
"publish",
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class StepLike(Protocol):
|
|
29
|
+
name: str
|
|
30
|
+
result: object
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@dataclass(frozen=True)
|
|
34
|
+
class CompositeShortCircuitViolation:
|
|
35
|
+
wrapper: str
|
|
36
|
+
failed_step_name: str
|
|
37
|
+
failed_step_status: str
|
|
38
|
+
later_step_name: str
|
|
39
|
+
later_action: str
|
|
40
|
+
message: str
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _status_of(result: object) -> str:
|
|
44
|
+
return str(getattr(result, "result_status", "") or "").upper()
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _action_of(step: StepLike) -> str:
|
|
48
|
+
result = getattr(step, "result", None)
|
|
49
|
+
action = getattr(result, "action", "")
|
|
50
|
+
if action:
|
|
51
|
+
return str(action)
|
|
52
|
+
return str(getattr(step, "name", ""))
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _is_allowed_recovery_step(action: str, allowed_recovery_actions: tuple[str, ...]) -> bool:
|
|
56
|
+
normalized = action.lower()
|
|
57
|
+
return any(item.lower() == normalized for item in allowed_recovery_actions)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _is_effectful_after_failure(action: str, effectful_actions: tuple[str, ...]) -> bool:
|
|
61
|
+
normalized = action.lower()
|
|
62
|
+
return any(item.lower() in normalized for item in effectful_actions)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def validate_no_effectful_steps_after_required_failure(
|
|
66
|
+
*,
|
|
67
|
+
wrapper: str,
|
|
68
|
+
steps: Iterable[StepLike],
|
|
69
|
+
allowed_recovery_actions: Iterable[str] = (),
|
|
70
|
+
effectful_actions: Iterable[str] = DEFAULT_EFFECTFUL_ACTIONS_AFTER_FAILURE,
|
|
71
|
+
) -> list[CompositeShortCircuitViolation]:
|
|
72
|
+
"""Validate composite-wrapper short-circuit discipline.
|
|
73
|
+
|
|
74
|
+
After a required substep reports FAIL/BLOCKED, a composite command must not
|
|
75
|
+
execute later effectful steps such as commit, push, PR creation, merge,
|
|
76
|
+
handoff refresh, release, or publish. Explicit recovery steps may be
|
|
77
|
+
allowlisted by the wrapper.
|
|
78
|
+
"""
|
|
79
|
+
|
|
80
|
+
steps_tuple = tuple(steps)
|
|
81
|
+
allowed = tuple(str(item) for item in allowed_recovery_actions)
|
|
82
|
+
effectful = tuple(str(item) for item in effectful_actions)
|
|
83
|
+
violations: list[CompositeShortCircuitViolation] = []
|
|
84
|
+
|
|
85
|
+
first_failure: tuple[str, str] | None = None
|
|
86
|
+
for step in steps_tuple:
|
|
87
|
+
status = _status_of(getattr(step, "result", None))
|
|
88
|
+
if status in TERMINAL_BAD_STATUSES:
|
|
89
|
+
first_failure = (str(getattr(step, "name", "")), status)
|
|
90
|
+
break
|
|
91
|
+
|
|
92
|
+
if first_failure is None:
|
|
93
|
+
return []
|
|
94
|
+
|
|
95
|
+
failed_step_name, failed_status = first_failure
|
|
96
|
+
failure_seen = False
|
|
97
|
+
for step in steps_tuple:
|
|
98
|
+
if not failure_seen:
|
|
99
|
+
if str(getattr(step, "name", "")) == failed_step_name:
|
|
100
|
+
failure_seen = True
|
|
101
|
+
continue
|
|
102
|
+
|
|
103
|
+
action = _action_of(step)
|
|
104
|
+
if _is_allowed_recovery_step(action, allowed):
|
|
105
|
+
continue
|
|
106
|
+
if not _is_effectful_after_failure(action, effectful):
|
|
107
|
+
continue
|
|
108
|
+
|
|
109
|
+
violations.append(
|
|
110
|
+
CompositeShortCircuitViolation(
|
|
111
|
+
wrapper=wrapper,
|
|
112
|
+
failed_step_name=failed_step_name,
|
|
113
|
+
failed_step_status=failed_status,
|
|
114
|
+
later_step_name=str(getattr(step, "name", "")),
|
|
115
|
+
later_action=action,
|
|
116
|
+
message=(
|
|
117
|
+
f"{wrapper} executed effectful step {getattr(step, 'name', '')!s} "
|
|
118
|
+
f"after {failed_step_name} returned {failed_status}."
|
|
119
|
+
),
|
|
120
|
+
)
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
return violations
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def assert_no_effectful_steps_after_required_failure(
|
|
127
|
+
*,
|
|
128
|
+
wrapper: str,
|
|
129
|
+
steps: Iterable[StepLike],
|
|
130
|
+
allowed_recovery_actions: Iterable[str] = (),
|
|
131
|
+
effectful_actions: Iterable[str] = DEFAULT_EFFECTFUL_ACTIONS_AFTER_FAILURE,
|
|
132
|
+
) -> None:
|
|
133
|
+
violations = validate_no_effectful_steps_after_required_failure(
|
|
134
|
+
wrapper=wrapper,
|
|
135
|
+
steps=steps,
|
|
136
|
+
allowed_recovery_actions=allowed_recovery_actions,
|
|
137
|
+
effectful_actions=effectful_actions,
|
|
138
|
+
)
|
|
139
|
+
if violations:
|
|
140
|
+
raise AssertionError("; ".join(item.message for item in violations))
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
import yaml
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
CONTRACT_PATH = ".agentic/project.yaml"
|
|
11
|
+
CONTRACT_VERSION = 1
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@dataclass(frozen=True)
|
|
15
|
+
class ProfileDefinition:
|
|
16
|
+
id: str
|
|
17
|
+
title: str
|
|
18
|
+
description: str
|
|
19
|
+
required_files: tuple[str, ...] = ()
|
|
20
|
+
recommended_commands: tuple[str, ...] = ()
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@dataclass(frozen=True)
|
|
24
|
+
class PolicyPackDefinition:
|
|
25
|
+
id: str
|
|
26
|
+
title: str
|
|
27
|
+
description: str
|
|
28
|
+
strictness: str
|
|
29
|
+
recommended_for: tuple[str, ...] = ()
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
PROFILE_DEFINITIONS: dict[str, ProfileDefinition] = {
|
|
33
|
+
"generic-git-repo": ProfileDefinition(
|
|
34
|
+
id="generic-git-repo",
|
|
35
|
+
title="Generic Git repository",
|
|
36
|
+
description="Language-neutral repository governance, status files, handoff files, and review gates.",
|
|
37
|
+
required_files=("README.md", "AGENTS.md", "docs/STATUS.md", "docs/TEST_GATES.md"),
|
|
38
|
+
recommended_commands=("agentic-kit check-docs", "agentic-kit doctor"),
|
|
39
|
+
),
|
|
40
|
+
"python-cli": ProfileDefinition(
|
|
41
|
+
id="python-cli",
|
|
42
|
+
title="Python CLI",
|
|
43
|
+
description="Python package with command-line behavior, pytest tests, and optional ruff linting.",
|
|
44
|
+
required_files=("pyproject.toml", "tests"),
|
|
45
|
+
recommended_commands=("python -m pytest -q", "ruff check ."),
|
|
46
|
+
),
|
|
47
|
+
"python-lib": ProfileDefinition(
|
|
48
|
+
id="python-lib",
|
|
49
|
+
title="Python library",
|
|
50
|
+
description="Python import package with pytest tests and packaging metadata.",
|
|
51
|
+
required_files=("pyproject.toml", "tests"),
|
|
52
|
+
recommended_commands=("python -m pytest -q", "ruff check ."),
|
|
53
|
+
),
|
|
54
|
+
"markdown-docs": ProfileDefinition(
|
|
55
|
+
id="markdown-docs",
|
|
56
|
+
title="Markdown documentation",
|
|
57
|
+
description="Documentation-heavy repository with required state docs and coverage checks.",
|
|
58
|
+
required_files=("docs/STATUS.md", "docs/TEST_GATES.md", "docs/handoff/CURRENT_HANDOFF.md"),
|
|
59
|
+
recommended_commands=("agentic-kit check-docs",),
|
|
60
|
+
),
|
|
61
|
+
"git-github": ProfileDefinition(
|
|
62
|
+
id="git-github",
|
|
63
|
+
title="GitHub workflow",
|
|
64
|
+
description="GitHub-oriented review workflow with PR templates, Actions, and branch discipline.",
|
|
65
|
+
required_files=(".github/pull_request_template.md",),
|
|
66
|
+
recommended_commands=("git status --short",),
|
|
67
|
+
),
|
|
68
|
+
"release-managed": ProfileDefinition(
|
|
69
|
+
id="release-managed",
|
|
70
|
+
title="Release-managed project",
|
|
71
|
+
description="Repository with changelog, release planning, release validation, and citation/archive metadata.",
|
|
72
|
+
required_files=("CHANGELOG.md",),
|
|
73
|
+
recommended_commands=("agentic-kit release-plan", "agentic-kit release-check"),
|
|
74
|
+
),
|
|
75
|
+
"governance-wrapper": ProfileDefinition(
|
|
76
|
+
id="governance-wrapper",
|
|
77
|
+
title="Governance wrapper",
|
|
78
|
+
description="Strict human-AI wrapper project with output contracts, validation, repair boundaries, and auditability.",
|
|
79
|
+
required_files=("AGENTS.md", "docs/architecture/ARCHITECTURE_CONTRACT.md", "docs/TEST_GATES.md"),
|
|
80
|
+
recommended_commands=("agentic-kit check-docs", "agentic-kit doctor", "python -m pytest -q"),
|
|
81
|
+
),
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
POLICY_PACK_DEFINITIONS: dict[str, PolicyPackDefinition] = {
|
|
86
|
+
"starter": PolicyPackDefinition(
|
|
87
|
+
id="starter",
|
|
88
|
+
title="Starter",
|
|
89
|
+
description="Low-friction project start with visible state files and basic gates.",
|
|
90
|
+
strictness="low",
|
|
91
|
+
recommended_for=("new repositories", "small projects"),
|
|
92
|
+
),
|
|
93
|
+
"prototype": PolicyPackDefinition(
|
|
94
|
+
id="prototype",
|
|
95
|
+
title="Prototype",
|
|
96
|
+
description="Fast exploration with explicit non-production status and lightweight checks.",
|
|
97
|
+
strictness="low",
|
|
98
|
+
recommended_for=("experiments", "vibe-coding exploration"),
|
|
99
|
+
),
|
|
100
|
+
"solo-maintainer": PolicyPackDefinition(
|
|
101
|
+
id="solo-maintainer",
|
|
102
|
+
title="Solo maintainer",
|
|
103
|
+
description="Single-maintainer workflow with explicit handoff, reviewability, and local evidence gates.",
|
|
104
|
+
strictness="medium",
|
|
105
|
+
recommended_for=("single-user development", "personal tools"),
|
|
106
|
+
),
|
|
107
|
+
"agentic-development": PolicyPackDefinition(
|
|
108
|
+
id="agentic-development",
|
|
109
|
+
title="Agentic development",
|
|
110
|
+
description="Human-AI development workflow with architecture review, documentation coverage, and doctor checks.",
|
|
111
|
+
strictness="medium-high",
|
|
112
|
+
recommended_for=("coding agents", "AI-assisted development"),
|
|
113
|
+
),
|
|
114
|
+
"release-managed": PolicyPackDefinition(
|
|
115
|
+
id="release-managed",
|
|
116
|
+
title="Release managed",
|
|
117
|
+
description="Release-state validation for versions, tags, changelog, release artifacts, and citation metadata.",
|
|
118
|
+
strictness="high",
|
|
119
|
+
recommended_for=("published packages", "GitHub releases"),
|
|
120
|
+
),
|
|
121
|
+
"documentation-governed": PolicyPackDefinition(
|
|
122
|
+
id="documentation-governed",
|
|
123
|
+
title="Documentation governed",
|
|
124
|
+
description="Documentation coverage and drift checks for documentation-heavy or governance-heavy repositories.",
|
|
125
|
+
strictness="medium-high",
|
|
126
|
+
recommended_for=("docs repositories", "architecture-governed repositories"),
|
|
127
|
+
),
|
|
128
|
+
"output-contracts": PolicyPackDefinition(
|
|
129
|
+
id="output-contracts",
|
|
130
|
+
title="Output contracts",
|
|
131
|
+
description="Strict response/output governance with schemas, validators, bounded repair, and evidence-oriented failure handling.",
|
|
132
|
+
strictness="high",
|
|
133
|
+
recommended_for=("LLM wrappers", "governance-heavy assistants", "auditable output pipelines"),
|
|
134
|
+
),
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def default_profiles(project_type: str, *, github_actions: bool) -> tuple[str, ...]:
|
|
139
|
+
profiles = ["generic-git-repo", "markdown-docs"]
|
|
140
|
+
if project_type in {"python-cli", "python-lib", "governance-wrapper"}:
|
|
141
|
+
profiles.append(project_type)
|
|
142
|
+
if github_actions:
|
|
143
|
+
profiles.append("git-github")
|
|
144
|
+
return tuple(dict.fromkeys(profiles))
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def recommended_policy_packs(
|
|
148
|
+
project_type: str,
|
|
149
|
+
*,
|
|
150
|
+
github_actions: bool,
|
|
151
|
+
logging_evidence: bool,
|
|
152
|
+
) -> tuple[str, ...]:
|
|
153
|
+
packs = ["starter", "solo-maintainer"]
|
|
154
|
+
if logging_evidence or github_actions:
|
|
155
|
+
packs.append("agentic-development")
|
|
156
|
+
if project_type == "generic":
|
|
157
|
+
packs.append("documentation-governed")
|
|
158
|
+
if project_type == "governance-wrapper":
|
|
159
|
+
packs.append("documentation-governed")
|
|
160
|
+
packs.append("output-contracts")
|
|
161
|
+
return tuple(dict.fromkeys(packs))
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def validate_ids(kind: str, selected: tuple[str, ...], known: dict[str, object]) -> list[str]:
|
|
165
|
+
return [f"unknown {kind}: {item}" for item in selected if item not in known]
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def build_contract_data(
|
|
169
|
+
*,
|
|
170
|
+
name: str,
|
|
171
|
+
description: str,
|
|
172
|
+
project_type: str,
|
|
173
|
+
profiles: tuple[str, ...],
|
|
174
|
+
policy_packs: tuple[str, ...],
|
|
175
|
+
) -> dict[str, Any]:
|
|
176
|
+
return {
|
|
177
|
+
"version": CONTRACT_VERSION,
|
|
178
|
+
"project": {
|
|
179
|
+
"name": name,
|
|
180
|
+
"description": description,
|
|
181
|
+
"type": project_type,
|
|
182
|
+
},
|
|
183
|
+
"profiles": list(profiles),
|
|
184
|
+
"policy_packs": list(policy_packs),
|
|
185
|
+
"governance": {
|
|
186
|
+
"architecture_contract_required": True,
|
|
187
|
+
"documentation_coverage_required": True,
|
|
188
|
+
"doctor_before_merge": True,
|
|
189
|
+
"human_owns_merge_release_and_publication": True,
|
|
190
|
+
},
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def render_contract_yaml(data: dict[str, Any]) -> str:
|
|
195
|
+
return yaml.safe_dump(data, sort_keys=False, allow_unicode=True)
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
def load_project_contract(project_root: Path) -> dict[str, Any] | None:
|
|
199
|
+
path = project_root / CONTRACT_PATH
|
|
200
|
+
if not path.exists():
|
|
201
|
+
return None
|
|
202
|
+
data = yaml.safe_load(path.read_text(encoding="utf-8")) or {}
|
|
203
|
+
if not isinstance(data, dict):
|
|
204
|
+
raise ValueError(f"{CONTRACT_PATH} must contain a YAML mapping")
|
|
205
|
+
return data
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def validate_project_contract(data: dict[str, Any]) -> list[str]:
|
|
209
|
+
errors: list[str] = []
|
|
210
|
+
if data.get("version") != CONTRACT_VERSION:
|
|
211
|
+
errors.append(f"unsupported contract version: {data.get('version')!r}")
|
|
212
|
+
|
|
213
|
+
project = data.get("project")
|
|
214
|
+
if not isinstance(project, dict):
|
|
215
|
+
errors.append("project must be a mapping")
|
|
216
|
+
else:
|
|
217
|
+
for field in ("name", "description", "type"):
|
|
218
|
+
if not str(project.get(field, "")).strip():
|
|
219
|
+
errors.append(f"project.{field} is required")
|
|
220
|
+
|
|
221
|
+
profiles = _string_list(data.get("profiles"), "profiles", errors)
|
|
222
|
+
policy_packs = _string_list(data.get("policy_packs"), "policy_packs", errors)
|
|
223
|
+
errors.extend(validate_ids("profile", profiles, PROFILE_DEFINITIONS))
|
|
224
|
+
errors.extend(validate_ids("policy pack", policy_packs, POLICY_PACK_DEFINITIONS))
|
|
225
|
+
|
|
226
|
+
governance = data.get("governance")
|
|
227
|
+
if not isinstance(governance, dict):
|
|
228
|
+
errors.append("governance must be a mapping")
|
|
229
|
+
|
|
230
|
+
return errors
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
def contract_summary(data: dict[str, Any]) -> str:
|
|
234
|
+
project = data.get("project", {}) if isinstance(data.get("project"), dict) else {}
|
|
235
|
+
profiles = ", ".join(_string_list(data.get("profiles"), "profiles", [])) or "none"
|
|
236
|
+
packs = ", ".join(_string_list(data.get("policy_packs"), "policy_packs", [])) or "none"
|
|
237
|
+
name = project.get("name", "unknown")
|
|
238
|
+
return f"{name}; profiles: {profiles}; policy packs: {packs}"
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
def _string_list(value: object, field_name: str, errors: list[str]) -> tuple[str, ...]:
|
|
242
|
+
if not isinstance(value, list):
|
|
243
|
+
errors.append(f"{field_name} must be a list")
|
|
244
|
+
return ()
|
|
245
|
+
result: list[str] = []
|
|
246
|
+
for item in value:
|
|
247
|
+
if not isinstance(item, str) or not item.strip():
|
|
248
|
+
errors.append(f"{field_name} entries must be non-empty strings")
|
|
249
|
+
else:
|
|
250
|
+
result.append(item.strip())
|
|
251
|
+
return tuple(result)
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"""Deterministic workflow control states.
|
|
2
|
+
|
|
3
|
+
This module separates command execution results from target-state outcomes.
|
|
4
|
+
A workflow step may be successful because the desired target state already
|
|
5
|
+
exists, even if a low-level command would otherwise fail or be unnecessary.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from dataclasses import dataclass
|
|
11
|
+
from enum import Enum
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class ControlStatus(str, Enum):
|
|
15
|
+
"""Machine-readable status values for deterministic workflow steps."""
|
|
16
|
+
|
|
17
|
+
PASS = "PASS"
|
|
18
|
+
DONE = "DONE"
|
|
19
|
+
NOOP = "NOOP"
|
|
20
|
+
ALREADY_ON_MAIN = "ALREADY_ON_MAIN"
|
|
21
|
+
ALREADY_MERGED = "ALREADY_MERGED"
|
|
22
|
+
ALREADY_RELEASED = "ALREADY_RELEASED"
|
|
23
|
+
DOI_VERIFIED = "DOI_VERIFIED"
|
|
24
|
+
SUPERSEDED = "SUPERSEDED"
|
|
25
|
+
PENDING = "PENDING"
|
|
26
|
+
WAIT = "WAIT"
|
|
27
|
+
FAIL = "FAIL"
|
|
28
|
+
NEEDS_HUMAN_REVIEW = "NEEDS_HUMAN_REVIEW"
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
SUCCESS_STATUSES = frozenset({
|
|
32
|
+
ControlStatus.PASS,
|
|
33
|
+
ControlStatus.DONE,
|
|
34
|
+
ControlStatus.NOOP,
|
|
35
|
+
ControlStatus.ALREADY_ON_MAIN,
|
|
36
|
+
ControlStatus.ALREADY_MERGED,
|
|
37
|
+
ControlStatus.ALREADY_RELEASED,
|
|
38
|
+
ControlStatus.DOI_VERIFIED,
|
|
39
|
+
ControlStatus.SUPERSEDED,
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
RETRYABLE_STATUSES = frozenset({
|
|
43
|
+
ControlStatus.PENDING,
|
|
44
|
+
ControlStatus.WAIT,
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
FAILURE_STATUSES = frozenset({
|
|
48
|
+
ControlStatus.FAIL,
|
|
49
|
+
ControlStatus.NEEDS_HUMAN_REVIEW,
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@dataclass(frozen=True)
|
|
54
|
+
class StepResult:
|
|
55
|
+
"""Result of a workflow step after target-state classification."""
|
|
56
|
+
|
|
57
|
+
status: ControlStatus
|
|
58
|
+
message: str
|
|
59
|
+
detail: str = ""
|
|
60
|
+
|
|
61
|
+
@property
|
|
62
|
+
def is_success(self) -> bool:
|
|
63
|
+
return self.status in SUCCESS_STATUSES
|
|
64
|
+
|
|
65
|
+
@property
|
|
66
|
+
def is_retryable(self) -> bool:
|
|
67
|
+
return self.status in RETRYABLE_STATUSES
|
|
68
|
+
|
|
69
|
+
@property
|
|
70
|
+
def is_failure(self) -> bool:
|
|
71
|
+
return self.status in FAILURE_STATUSES
|
|
72
|
+
|
|
73
|
+
@property
|
|
74
|
+
def should_continue(self) -> bool:
|
|
75
|
+
return self.is_success
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def normalize_status(value: str | ControlStatus) -> ControlStatus:
|
|
79
|
+
"""Return a ControlStatus from a user/tool supplied value."""
|
|
80
|
+
if isinstance(value, ControlStatus):
|
|
81
|
+
return value
|
|
82
|
+
normalized = value.strip().upper().replace("-", "_")
|
|
83
|
+
return ControlStatus(normalized)
|