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,46 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
import importlib
|
|
5
|
+
import os
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@dataclass(frozen=True)
|
|
9
|
+
class WindowGuardResult:
|
|
10
|
+
ok: bool
|
|
11
|
+
tkinter_importable: bool
|
|
12
|
+
native_tkinter_importable: bool
|
|
13
|
+
message: str
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def check_window_launch_ready() -> WindowGuardResult:
|
|
17
|
+
try:
|
|
18
|
+
importlib.import_module("tkinter")
|
|
19
|
+
tkinter_importable = True
|
|
20
|
+
except Exception as exc:
|
|
21
|
+
return WindowGuardResult(False, False, False, f"tkinter import failed: {exc}")
|
|
22
|
+
try:
|
|
23
|
+
importlib.import_module("_tkinter")
|
|
24
|
+
native_importable = True
|
|
25
|
+
except Exception as exc:
|
|
26
|
+
return WindowGuardResult(False, tkinter_importable, False, f"native _tkinter import failed: {exc}")
|
|
27
|
+
if os.environ.get("AGENTIC_KIT_ALLOW_TK_WINDOW_SMOKE") != "1":
|
|
28
|
+
return WindowGuardResult(
|
|
29
|
+
False,
|
|
30
|
+
tkinter_importable,
|
|
31
|
+
native_importable,
|
|
32
|
+
"real Tk window smoke requires AGENTIC_KIT_ALLOW_TK_WINDOW_SMOKE=1",
|
|
33
|
+
)
|
|
34
|
+
return WindowGuardResult(True, tkinter_importable, native_importable, "window launch guard passed")
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def render_window_guard_result(result: WindowGuardResult) -> str:
|
|
38
|
+
status = "READY" if result.ok else "BLOCKED"
|
|
39
|
+
return "\n".join((
|
|
40
|
+
"GUI WINDOW GUARD",
|
|
41
|
+
f"window_launch_ready={str(result.ok).lower()}",
|
|
42
|
+
f"tkinter_importable={str(result.tkinter_importable).lower()}",
|
|
43
|
+
f"native_tkinter_importable={str(result.native_tkinter_importable).lower()}",
|
|
44
|
+
f"message={result.message}",
|
|
45
|
+
f"window_guard_status={status}",
|
|
46
|
+
))
|
|
@@ -0,0 +1,390 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import subprocess
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from agentic_project_kit.handoff_state import is_administrative_evidence_subject
|
|
8
|
+
|
|
9
|
+
FRESHNESS_GUARD_FILES = [
|
|
10
|
+
"docs/STATUS.md",
|
|
11
|
+
".agentic/handoff_state.yaml",
|
|
12
|
+
"docs/handoff/CURRENT_HANDOFF.md",
|
|
13
|
+
]
|
|
14
|
+
OPERATIONAL_FRESHNESS_FILES = [
|
|
15
|
+
"docs/STATUS.md",
|
|
16
|
+
"docs/handoff/CURRENT_HANDOFF.md",
|
|
17
|
+
"docs/handoff/START_NEW_CHAT_PROMPT.md",
|
|
18
|
+
"docs/handoff/NEXT_CHAT_BOOTSTRAP.md",
|
|
19
|
+
]
|
|
20
|
+
SUCCESSOR_HANDOFF_GLOBS = [
|
|
21
|
+
"*successor*handoff*.md",
|
|
22
|
+
"*successor*handoff*.yaml",
|
|
23
|
+
"*successor*handoff*.yml",
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def assess_handoff_prompt_freshness(
|
|
28
|
+
data: dict[str, Any],
|
|
29
|
+
handoff_path: str | Path = ".agentic/handoff_state.yaml",
|
|
30
|
+
*,
|
|
31
|
+
current_head: str | None = None,
|
|
32
|
+
current_subject: str | None = None,
|
|
33
|
+
successor_prompt_text: str | None = None,
|
|
34
|
+
intended_successor_prompt_path: str | Path | None = None,
|
|
35
|
+
) -> list[str]:
|
|
36
|
+
"""Return warnings when a successor handoff prompt may be stale.
|
|
37
|
+
|
|
38
|
+
The guard is intentionally warning-based. A stale repository still needs to
|
|
39
|
+
render a repairable prompt; it must not silently present that prompt as
|
|
40
|
+
authoritative.
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
state_path = Path(handoff_path)
|
|
44
|
+
project_root = _project_root_for_handoff_path(state_path)
|
|
45
|
+
warnings: list[str] = []
|
|
46
|
+
|
|
47
|
+
for relative_path in FRESHNESS_GUARD_FILES:
|
|
48
|
+
if not (project_root / relative_path).exists():
|
|
49
|
+
warnings.append(f"required handoff freshness file missing: {relative_path}")
|
|
50
|
+
|
|
51
|
+
detected_head = _short_commit(current_head) if current_head else _git_short_head(project_root)
|
|
52
|
+
detected_subject = current_subject if current_subject is not None else _git_head_subject(project_root)
|
|
53
|
+
detected_message = (
|
|
54
|
+
current_subject
|
|
55
|
+
if current_subject is not None
|
|
56
|
+
else _git_head_message(project_root)
|
|
57
|
+
)
|
|
58
|
+
expected_commits = _expected_handoff_commits(data)
|
|
59
|
+
admin_refresh_head = bool(
|
|
60
|
+
detected_head
|
|
61
|
+
and expected_commits
|
|
62
|
+
and not _commit_matches(detected_head, expected_commits)
|
|
63
|
+
and (
|
|
64
|
+
is_administrative_evidence_subject(detected_subject)
|
|
65
|
+
or _is_administrative_merge_commit(
|
|
66
|
+
project_root,
|
|
67
|
+
detected_head,
|
|
68
|
+
detected_message,
|
|
69
|
+
expected_commits,
|
|
70
|
+
)
|
|
71
|
+
)
|
|
72
|
+
)
|
|
73
|
+
if (
|
|
74
|
+
detected_head
|
|
75
|
+
and expected_commits
|
|
76
|
+
and not _commit_matches(detected_head, expected_commits)
|
|
77
|
+
and not admin_refresh_head
|
|
78
|
+
):
|
|
79
|
+
joined = ", ".join(expected_commits)
|
|
80
|
+
warnings.append(
|
|
81
|
+
"current git HEAD "
|
|
82
|
+
f"{detected_head} is not represented by handoff safe/admin state ({joined})"
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
successor_prompt_path = _latest_successor_prompt_path(data, project_root)
|
|
86
|
+
if successor_prompt_path is None:
|
|
87
|
+
warnings.append("no successor handoff prompt was found under docs/reports/terminal")
|
|
88
|
+
return warnings
|
|
89
|
+
|
|
90
|
+
if not successor_prompt_path.exists():
|
|
91
|
+
intended_path = _resolve_project_path(project_root, intended_successor_prompt_path)
|
|
92
|
+
if successor_prompt_text is None or intended_path != successor_prompt_path:
|
|
93
|
+
relative_prompt = _relative_to_project(successor_prompt_path, project_root)
|
|
94
|
+
warnings.append(f"latest successor handoff prompt is missing: {relative_prompt}")
|
|
95
|
+
return warnings
|
|
96
|
+
|
|
97
|
+
marker_commits = expected_commits if admin_refresh_head else ([detected_head] if detected_head else expected_commits)
|
|
98
|
+
if successor_prompt_text is None:
|
|
99
|
+
prompt_text = successor_prompt_path.read_text(encoding="utf-8")
|
|
100
|
+
else:
|
|
101
|
+
prompt_text = successor_prompt_text
|
|
102
|
+
if marker_commits and not any(commit and commit in prompt_text for commit in marker_commits):
|
|
103
|
+
relative_prompt = _relative_to_project(successor_prompt_path, project_root)
|
|
104
|
+
joined = ", ".join(marker_commits)
|
|
105
|
+
warnings.append(
|
|
106
|
+
"latest successor handoff prompt "
|
|
107
|
+
f"{relative_prompt} does not mention current handoff commit marker(s): {joined}"
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
warnings.extend(
|
|
111
|
+
_assess_operational_document_freshness(
|
|
112
|
+
project_root,
|
|
113
|
+
expected_commits,
|
|
114
|
+
)
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
return warnings
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def _assess_operational_document_freshness(
|
|
122
|
+
project_root: Path,
|
|
123
|
+
marker_commits: list[str],
|
|
124
|
+
) -> list[str]:
|
|
125
|
+
"""Return warnings when live operational docs do not mention current markers.
|
|
126
|
+
|
|
127
|
+
This intentionally reuses the existing handoff-freshness mechanism. It does
|
|
128
|
+
not introduce a new rule layer: STATUS, CURRENT_HANDOFF, chat bootstrap
|
|
129
|
+
prompts, and the active roadmap are human-facing projections of the
|
|
130
|
+
machine-readable handoff state and must mention at least one current
|
|
131
|
+
safe/admin commit marker when they exist. Stable authority sources such as
|
|
132
|
+
PROJECT_DIRECTION.yaml are intentionally excluded; they are required
|
|
133
|
+
context, not per-PR refresh projections.
|
|
134
|
+
"""
|
|
135
|
+
|
|
136
|
+
markers = [marker for marker in marker_commits if marker]
|
|
137
|
+
if not markers:
|
|
138
|
+
return []
|
|
139
|
+
|
|
140
|
+
warnings: list[str] = []
|
|
141
|
+
for relative_path in OPERATIONAL_FRESHNESS_FILES:
|
|
142
|
+
path = project_root / relative_path
|
|
143
|
+
if not path.exists():
|
|
144
|
+
continue
|
|
145
|
+
content = path.read_text(encoding="utf-8", errors="replace")
|
|
146
|
+
if any(marker in content for marker in markers):
|
|
147
|
+
continue
|
|
148
|
+
joined = ", ".join(markers)
|
|
149
|
+
warnings.append(
|
|
150
|
+
f"operational handoff document {relative_path} does not mention "
|
|
151
|
+
f"current handoff commit marker(s): {joined}"
|
|
152
|
+
)
|
|
153
|
+
return warnings
|
|
154
|
+
|
|
155
|
+
def render_freshness_guard(warnings: list[str]) -> str:
|
|
156
|
+
if not warnings:
|
|
157
|
+
return ""
|
|
158
|
+
lines = [
|
|
159
|
+
"## Handoff Freshness Guard",
|
|
160
|
+
"",
|
|
161
|
+
"WARNING: this successor handoff prompt may be stale.",
|
|
162
|
+
"Refresh `docs/STATUS.md`, `.agentic/handoff_state.yaml`, "
|
|
163
|
+
"`docs/handoff/CURRENT_HANDOFF.md`, and the successor prompt before treating "
|
|
164
|
+
"this prompt as authoritative.",
|
|
165
|
+
"",
|
|
166
|
+
]
|
|
167
|
+
lines.extend(f"- {warning}" for warning in warnings)
|
|
168
|
+
return "\n".join(lines).rstrip() + "\n"
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def _short_commit(value: Any) -> str:
|
|
172
|
+
return str(value or "").strip()[:7]
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def _project_root_for_handoff_path(path: Path) -> Path:
|
|
176
|
+
if path.name == "handoff_state.yaml" and path.parent.name == ".agentic":
|
|
177
|
+
return path.parent.parent
|
|
178
|
+
return Path.cwd()
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def _resolve_project_path(project_root: Path, path: str | Path | None) -> Path | None:
|
|
182
|
+
if path is None:
|
|
183
|
+
return None
|
|
184
|
+
candidate = Path(path)
|
|
185
|
+
if candidate.is_absolute():
|
|
186
|
+
return candidate
|
|
187
|
+
return project_root / candidate
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def _git_short_head(project_root: Path) -> str:
|
|
191
|
+
try:
|
|
192
|
+
return subprocess.check_output(
|
|
193
|
+
["git", "rev-parse", "--short", "HEAD"],
|
|
194
|
+
cwd=project_root,
|
|
195
|
+
stderr=subprocess.DEVNULL,
|
|
196
|
+
text=True,
|
|
197
|
+
).strip()
|
|
198
|
+
except (OSError, subprocess.CalledProcessError):
|
|
199
|
+
return ""
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def _git_head_subject(project_root: Path) -> str:
|
|
203
|
+
try:
|
|
204
|
+
return subprocess.check_output(
|
|
205
|
+
["git", "log", "-1", "--pretty=%s"],
|
|
206
|
+
cwd=project_root,
|
|
207
|
+
stderr=subprocess.DEVNULL,
|
|
208
|
+
text=True,
|
|
209
|
+
).strip()
|
|
210
|
+
except (OSError, subprocess.CalledProcessError):
|
|
211
|
+
return ""
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def _git_head_message(project_root: Path) -> str:
|
|
215
|
+
try:
|
|
216
|
+
return subprocess.check_output(
|
|
217
|
+
["git", "log", "-1", "--pretty=%B"],
|
|
218
|
+
cwd=project_root,
|
|
219
|
+
stderr=subprocess.DEVNULL,
|
|
220
|
+
text=True,
|
|
221
|
+
).strip()
|
|
222
|
+
except (OSError, subprocess.CalledProcessError):
|
|
223
|
+
return ""
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
def _git_commit_subject(project_root: Path, commit: str) -> str:
|
|
227
|
+
try:
|
|
228
|
+
return subprocess.check_output(
|
|
229
|
+
["git", "log", "-1", "--pretty=%s", commit],
|
|
230
|
+
cwd=project_root,
|
|
231
|
+
stderr=subprocess.DEVNULL,
|
|
232
|
+
text=True,
|
|
233
|
+
).strip()
|
|
234
|
+
except (OSError, subprocess.CalledProcessError):
|
|
235
|
+
return ""
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
def _git_commit_message(project_root: Path, commit: str) -> str:
|
|
239
|
+
try:
|
|
240
|
+
return subprocess.check_output(
|
|
241
|
+
["git", "log", "-1", "--pretty=%B", commit],
|
|
242
|
+
cwd=project_root,
|
|
243
|
+
stderr=subprocess.DEVNULL,
|
|
244
|
+
text=True,
|
|
245
|
+
).strip()
|
|
246
|
+
except (OSError, subprocess.CalledProcessError):
|
|
247
|
+
return ""
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
def _is_administrative_merge_message(message: str) -> bool:
|
|
251
|
+
lowered = message.lower()
|
|
252
|
+
if not lowered.startswith("merge pull request #"):
|
|
253
|
+
return False
|
|
254
|
+
administrative_terms = (
|
|
255
|
+
"handoff",
|
|
256
|
+
"successor",
|
|
257
|
+
"closeout",
|
|
258
|
+
"evidence",
|
|
259
|
+
"refresh",
|
|
260
|
+
"freshness",
|
|
261
|
+
)
|
|
262
|
+
return any(term in lowered for term in administrative_terms)
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
def _git_parent_commits(project_root: Path, commit: str) -> list[str]:
|
|
266
|
+
try:
|
|
267
|
+
parents = subprocess.check_output(
|
|
268
|
+
["git", "show", "-s", "--pretty=%P", commit],
|
|
269
|
+
cwd=project_root,
|
|
270
|
+
stderr=subprocess.DEVNULL,
|
|
271
|
+
text=True,
|
|
272
|
+
).strip()
|
|
273
|
+
except (OSError, subprocess.CalledProcessError):
|
|
274
|
+
return []
|
|
275
|
+
return [_short_commit(parent) for parent in parents.split() if parent.strip()]
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
def _is_administrative_merge_commit(
|
|
279
|
+
project_root: Path,
|
|
280
|
+
detected_head: str,
|
|
281
|
+
detected_message: str,
|
|
282
|
+
expected_commits: list[str],
|
|
283
|
+
) -> bool:
|
|
284
|
+
"""Return true for GitHub merge commits that only merge administrative handoff/evidence work.
|
|
285
|
+
|
|
286
|
+
A normal product merge must still trigger the freshness guard. Therefore the
|
|
287
|
+
check requires both a GitHub-style merge message and administrative wording
|
|
288
|
+
in the full merge message, plus a parent already represented by handoff
|
|
289
|
+
safe/admin state.
|
|
290
|
+
"""
|
|
291
|
+
|
|
292
|
+
message = detected_message.lower()
|
|
293
|
+
if not _is_administrative_merge_message(message):
|
|
294
|
+
return False
|
|
295
|
+
parents = _git_parent_commits(project_root, detected_head)
|
|
296
|
+
if any(_commit_matches(parent, expected_commits) for parent in parents):
|
|
297
|
+
return True
|
|
298
|
+
return _has_expected_commit_in_first_parent_admin_chain(
|
|
299
|
+
project_root,
|
|
300
|
+
detected_head,
|
|
301
|
+
expected_commits,
|
|
302
|
+
)
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
def _has_expected_commit_in_first_parent_admin_chain(
|
|
306
|
+
project_root: Path,
|
|
307
|
+
start_commit: str,
|
|
308
|
+
expected_commits: list[str],
|
|
309
|
+
*,
|
|
310
|
+
max_depth: int = 20,
|
|
311
|
+
) -> bool:
|
|
312
|
+
"""Accept bounded chains of administrative GitHub merge commits.
|
|
313
|
+
|
|
314
|
+
This prevents a loop where every administrative merge commit requires a new
|
|
315
|
+
handoff-state refresh. The walk is deliberately first-parent and bounded:
|
|
316
|
+
product merges still warn unless every merge on the path is itself
|
|
317
|
+
administrative and the chain reaches an already represented commit.
|
|
318
|
+
"""
|
|
319
|
+
|
|
320
|
+
current = start_commit
|
|
321
|
+
for _ in range(max_depth):
|
|
322
|
+
parents = _git_parent_commits(project_root, current)
|
|
323
|
+
if not parents:
|
|
324
|
+
return False
|
|
325
|
+
first_parent = parents[0]
|
|
326
|
+
if _commit_matches(first_parent, expected_commits):
|
|
327
|
+
return True
|
|
328
|
+
subject = _git_commit_subject(project_root, first_parent)
|
|
329
|
+
message = _git_commit_message(project_root, first_parent)
|
|
330
|
+
if not _is_administrative_merge_message(message or subject):
|
|
331
|
+
return False
|
|
332
|
+
current = first_parent
|
|
333
|
+
return False
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
def _expected_handoff_commits(data: dict[str, Any]) -> list[str]:
|
|
337
|
+
commits: list[str] = []
|
|
338
|
+
safe_state = data.get("safe_state", {})
|
|
339
|
+
if isinstance(safe_state, dict):
|
|
340
|
+
safe_commit = _short_commit(safe_state.get("commit"))
|
|
341
|
+
if safe_commit:
|
|
342
|
+
commits.append(safe_commit)
|
|
343
|
+
admin_state = data.get("administrative_evidence_state", {})
|
|
344
|
+
if isinstance(admin_state, dict):
|
|
345
|
+
admin_commit = _short_commit(admin_state.get("current_head"))
|
|
346
|
+
if admin_commit and admin_commit not in commits:
|
|
347
|
+
commits.append(admin_commit)
|
|
348
|
+
return commits
|
|
349
|
+
|
|
350
|
+
|
|
351
|
+
def _commit_matches(candidate: str, expected: list[str]) -> bool:
|
|
352
|
+
candidate = _short_commit(candidate)
|
|
353
|
+
return any(
|
|
354
|
+
candidate.startswith(commit) or commit.startswith(candidate)
|
|
355
|
+
for commit in expected
|
|
356
|
+
if commit
|
|
357
|
+
)
|
|
358
|
+
|
|
359
|
+
|
|
360
|
+
def _configured_successor_prompt_path(data: dict[str, Any], project_root: Path) -> Path | None:
|
|
361
|
+
handoff_maintenance = data.get("handoff_maintenance", {})
|
|
362
|
+
if isinstance(handoff_maintenance, dict):
|
|
363
|
+
configured = handoff_maintenance.get("latest_successor_prompt")
|
|
364
|
+
if configured:
|
|
365
|
+
return project_root / str(configured)
|
|
366
|
+
return None
|
|
367
|
+
|
|
368
|
+
|
|
369
|
+
def _latest_successor_prompt_path(data: dict[str, Any], project_root: Path) -> Path | None:
|
|
370
|
+
configured = _configured_successor_prompt_path(data, project_root)
|
|
371
|
+
if configured is not None:
|
|
372
|
+
return configured
|
|
373
|
+
terminal_dir = project_root / "docs" / "reports" / "terminal"
|
|
374
|
+
if not terminal_dir.exists():
|
|
375
|
+
return None
|
|
376
|
+
candidates = sorted(
|
|
377
|
+
candidate
|
|
378
|
+
for pattern in SUCCESSOR_HANDOFF_GLOBS
|
|
379
|
+
for candidate in terminal_dir.glob(pattern)
|
|
380
|
+
)
|
|
381
|
+
if not candidates:
|
|
382
|
+
return None
|
|
383
|
+
return candidates[-1]
|
|
384
|
+
|
|
385
|
+
|
|
386
|
+
def _relative_to_project(path: Path, project_root: Path) -> Path:
|
|
387
|
+
try:
|
|
388
|
+
return path.relative_to(project_root)
|
|
389
|
+
except ValueError:
|
|
390
|
+
return path
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from agentic_project_kit.chat_bootloader import MANDATORY_BOOT_SOURCES
|
|
6
|
+
from agentic_project_kit.handoff_state import active_rules
|
|
7
|
+
|
|
8
|
+
MANDATORY_SUCCESSOR_CHAT_SOURCES = list(MANDATORY_BOOT_SOURCES) + [
|
|
9
|
+
"docs/TEST_GATES.md",
|
|
10
|
+
"relevant source files and tests for the requested slice",
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
COMMUNICATION_SHORTCUTS = [
|
|
14
|
+
"d/D: local block appears finished; verify evidence before treating it as success",
|
|
15
|
+
"f/F: failure reported; inspect or upload evidence before asking for pasted output",
|
|
16
|
+
"w/W: continue within the current governance and evidence rules",
|
|
17
|
+
"paste-output: manual paste only when repo-backed or local evidence is unavailable or unusable",
|
|
18
|
+
"stop: no further mutation or terminal instructions",
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
FORBIDDEN_PATTERNS = [
|
|
22
|
+
"mutating before reading the mandatory successor-chat sources",
|
|
23
|
+
"treating d as proof of success",
|
|
24
|
+
"asking for pasted output while usable local or remote evidence exists",
|
|
25
|
+
"using shell-only snippets as canonical cross-platform execution",
|
|
26
|
+
"using POSIX tools as correctness dependencies for portable workflows",
|
|
27
|
+
"printing REMOTE_EVIDENCE: PENDING in a final summary",
|
|
28
|
+
"adding handwritten legacy summary footers after the summary renderer",
|
|
29
|
+
"continuing product work after unresolved contract or evidence drift",
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
SUMMARY_VOCABULARY = [
|
|
33
|
+
"WORK RESULT: PASS|FAIL|PENDING|HARD-FAIL|NO-COMMAND",
|
|
34
|
+
"EVIDENCE RESULT: PASS|FAIL|PARTIAL|CHAT_ONLY|NOT_REQUIRED",
|
|
35
|
+
"OVERALL RESULT: PASS|FAIL|PENDING|HARD-FAIL|NO-COMMAND",
|
|
36
|
+
"REMOTE_EVIDENCE: PASS|FAIL|PARTIAL|NOT_REQUIRED",
|
|
37
|
+
"NEXT_CHAT_REPLY: p|f|paste-output|continue|stop",
|
|
38
|
+
]
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _bullet_lines(items: list[Any]) -> list[str]:
|
|
42
|
+
return [f"- {item}" for item in items]
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _rule_texts(data: dict[str, Any]) -> list[str]:
|
|
46
|
+
return [f"`{rule.get('id')}`: {rule.get('text')}" for rule in active_rules(data)]
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def render_handoff_prompt(data: dict[str, Any]) -> str:
|
|
50
|
+
repo = data.get("repo", {})
|
|
51
|
+
safe_state = data.get("safe_state", {})
|
|
52
|
+
release = data.get("release", {})
|
|
53
|
+
admin_state = data.get("administrative_evidence_state", {})
|
|
54
|
+
if isinstance(data.get("open_items"), dict):
|
|
55
|
+
open_prs = data.get("open_items", {}).get("prs", [])
|
|
56
|
+
else:
|
|
57
|
+
open_prs = []
|
|
58
|
+
lines: list[str] = []
|
|
59
|
+
lines.extend([
|
|
60
|
+
"# Übergabeprompt",
|
|
61
|
+
"",
|
|
62
|
+
"## 1. Arbeitsumgebung",
|
|
63
|
+
"",
|
|
64
|
+
f"Local path: `{repo.get('local_path', '')}`",
|
|
65
|
+
f"Remote: `{repo.get('remote', '')}`",
|
|
66
|
+
f"Default branch: `{repo.get('default_branch', 'main')}`",
|
|
67
|
+
])
|
|
68
|
+
lines.extend([
|
|
69
|
+
"",
|
|
70
|
+
"## 2. Sicherer Stand",
|
|
71
|
+
"",
|
|
72
|
+
f"Branch: `{safe_state.get('branch', '')}`",
|
|
73
|
+
f"Commit: `{safe_state.get('commit', '')}`",
|
|
74
|
+
f"Subject: {safe_state.get('commit_subject', '')}",
|
|
75
|
+
f"Semantics: `{safe_state.get('semantics', 'last_substantive_work_state')}`",
|
|
76
|
+
f"Working tree expected clean: `{safe_state.get('working_tree_expected_clean', '')}`",
|
|
77
|
+
])
|
|
78
|
+
if isinstance(admin_state, dict) and admin_state:
|
|
79
|
+
admin_current_head = admin_state.get("current_head", "")
|
|
80
|
+
admin_current_head_subject = admin_state.get("current_head_subject", "")
|
|
81
|
+
admin_allowed_after_safe_state = admin_state.get("allowed_after_safe_state", "")
|
|
82
|
+
admin_reason = admin_state.get("reason", "")
|
|
83
|
+
lines.extend([
|
|
84
|
+
"",
|
|
85
|
+
"## 2a. Administrative Evidence State",
|
|
86
|
+
"",
|
|
87
|
+
"Administrative Evidence Commits nach dem fachlichen Safe-State sind erlaubt, wenn sie nur Logs, Handoff, Summary oder Evidence aktualisieren. Sie ändern den fachlichen Safe-State nicht.",
|
|
88
|
+
"",
|
|
89
|
+
f"Current HEAD at generation time: `{admin_current_head}`",
|
|
90
|
+
f"HEAD subject: {admin_current_head_subject}",
|
|
91
|
+
f"Allowed after safe state: `{admin_allowed_after_safe_state}`",
|
|
92
|
+
f"Reason: {admin_reason}",
|
|
93
|
+
])
|
|
94
|
+
lines.extend([
|
|
95
|
+
"",
|
|
96
|
+
"## 3. Release- und Produktstand",
|
|
97
|
+
"",
|
|
98
|
+
f"Current version: `{release.get('current_version', '')}`",
|
|
99
|
+
f"Previous version: `{release.get('previous_version', '')}`",
|
|
100
|
+
f"Tag: `{release.get('tag', '')}`",
|
|
101
|
+
f"Zenodo concept DOI: `{release.get('zenodo_concept_doi', '')}`",
|
|
102
|
+
f"Zenodo version DOI: `{release.get('zenodo_version_doi', '')}`",
|
|
103
|
+
f"Post-release check: `{release.get('post_release_check', '')}`",
|
|
104
|
+
])
|
|
105
|
+
lines.extend([
|
|
106
|
+
"",
|
|
107
|
+
"## 4. Pflichtquellen vor jeder Mutation",
|
|
108
|
+
"",
|
|
109
|
+
"Lies diese Quellen zuerst. Wenn eine Quelle fehlt, widersprüchlich ist oder nicht gelesen werden kann, melde Drift und mutiere nicht außer zur Drift-Reparatur.",
|
|
110
|
+
"",
|
|
111
|
+
])
|
|
112
|
+
lines.extend(_bullet_lines(MANDATORY_SUCCESSOR_CHAT_SOURCES))
|
|
113
|
+
lines.extend([
|
|
114
|
+
"",
|
|
115
|
+
"## 4a. Bootloader",
|
|
116
|
+
"",
|
|
117
|
+
"Beim Chatwechsel zuerst den Bootloader ausführen: `agentic-kit boot prompt` oder `./ns chat-bootloader`.",
|
|
118
|
+
"",
|
|
119
|
+
"## 5. Kommunikations- und Summary-Regeln",
|
|
120
|
+
"",
|
|
121
|
+
"User-Kürzel sind Kommunikationssignale, keine Evidence:",
|
|
122
|
+
"",
|
|
123
|
+
])
|
|
124
|
+
lines.extend(_bullet_lines(COMMUNICATION_SHORTCUTS))
|
|
125
|
+
lines.extend(["", "Final-Summary-Vokabular:", ""])
|
|
126
|
+
lines.extend(_bullet_lines(SUMMARY_VOCABULARY))
|
|
127
|
+
lines.extend([
|
|
128
|
+
"",
|
|
129
|
+
"## 6. Aktive Regeln aus handoff_state",
|
|
130
|
+
"",
|
|
131
|
+
])
|
|
132
|
+
lines.extend(_bullet_lines(_rule_texts(data)))
|
|
133
|
+
lines.extend(["", "## 7. Offene Punkte", ""])
|
|
134
|
+
if open_prs:
|
|
135
|
+
for pr in open_prs:
|
|
136
|
+
lines.append(f"- PR #{pr.get('number')}: {pr.get('title', '')}")
|
|
137
|
+
else:
|
|
138
|
+
lines.append("- Keine offenen PRs im handoff_state.")
|
|
139
|
+
lines.extend(["", "## 8. Abgeschlossen seit letzter Übergabe", ""])
|
|
140
|
+
completed = data.get("completed_since_previous_handoff", [])
|
|
141
|
+
if completed:
|
|
142
|
+
lines.extend(_bullet_lines(completed))
|
|
143
|
+
else:
|
|
144
|
+
lines.append("- Keine Einträge.")
|
|
145
|
+
lines.extend(["", "## 9. Letzte bekannte Fehler- und Driftmuster", ""])
|
|
146
|
+
for item in data.get("recent_failure_patterns", []):
|
|
147
|
+
if isinstance(item, dict):
|
|
148
|
+
lines.append(f"- `{item.get('id')}`: {item.get('prevention') or item.get('description', '')}")
|
|
149
|
+
lines.extend(["", "## 10. Verbotene Muster", ""])
|
|
150
|
+
lines.extend(_bullet_lines(FORBIDDEN_PATTERNS))
|
|
151
|
+
lines.extend(["", "## 11. Nächste erlaubte Aufgaben", ""])
|
|
152
|
+
tasks = data.get("next_allowed_tasks", [])
|
|
153
|
+
if tasks:
|
|
154
|
+
for task in tasks:
|
|
155
|
+
if isinstance(task, dict):
|
|
156
|
+
lines.append(f"- {task.get('priority', '?')}. `{task.get('id', '')}` — {task.get('title', '')}")
|
|
157
|
+
else:
|
|
158
|
+
lines.append("- Keine Aufgaben im handoff_state eingetragen.")
|
|
159
|
+
lines.extend(["", "## 12. Gesperrte Aufgaben", ""])
|
|
160
|
+
blocked = data.get("blocked_until_closeout", [])
|
|
161
|
+
if blocked:
|
|
162
|
+
lines.extend(_bullet_lines(blocked))
|
|
163
|
+
else:
|
|
164
|
+
lines.append("- Keine gesperrten Aufgaben im handoff_state eingetragen.")
|
|
165
|
+
lines.extend([
|
|
166
|
+
"",
|
|
167
|
+
"## 13. Erste Arbeitsanweisung",
|
|
168
|
+
"",
|
|
169
|
+
data.get("first_instruction", ""),
|
|
170
|
+
"",
|
|
171
|
+
"## 14. Arbeitsmodus für den Nachfolge-Chat",
|
|
172
|
+
"",
|
|
173
|
+
"1. Führe zuerst den Bootloader aus.",
|
|
174
|
+
"2. Lies danach alle Pflichtquellen aus Abschnitt 4.",
|
|
175
|
+
"3. Rekonstruiere den aktuellen Stand aus Repo, PR/CI, Logs und Summary, nicht aus Chat-Erinnerung.",
|
|
176
|
+
"4. Prüfe Drift zwischen Regeln, Tests, Summary-Renderer, Status und Handoff.",
|
|
177
|
+
"5. Bei Drift: warnen, keine Produktmutation, Handoff-Prompt oder Drift-Fix anbieten.",
|
|
178
|
+
"6. Arbeite nur in kleinen, testbaren Slices mit ehrlicher Evidence.",
|
|
179
|
+
])
|
|
180
|
+
return "\n".join(lines).rstrip() + "\n"
|