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,99 @@
|
|
|
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
|
+
CATALOG_PATH = Path(".agentic/patterns/catalog.yaml")
|
|
11
|
+
VALID_KINDS = {"pattern", "anti-pattern"}
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class PatternCatalogError(ValueError):
|
|
15
|
+
"""Raised when the local pattern catalog is missing or invalid."""
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@dataclass(frozen=True)
|
|
19
|
+
class PatternEntry:
|
|
20
|
+
id: str
|
|
21
|
+
title: str
|
|
22
|
+
kind: str
|
|
23
|
+
summary: str
|
|
24
|
+
doc: Path
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@dataclass(frozen=True)
|
|
28
|
+
class PatternCatalog:
|
|
29
|
+
version: int
|
|
30
|
+
patterns: list[PatternEntry]
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@dataclass(frozen=True)
|
|
34
|
+
class PatternDetail:
|
|
35
|
+
entry: PatternEntry
|
|
36
|
+
markdown: str
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def load_pattern_catalog(root: Path) -> PatternCatalog:
|
|
40
|
+
catalog_path = root / CATALOG_PATH
|
|
41
|
+
if not catalog_path.exists():
|
|
42
|
+
raise PatternCatalogError(f"missing pattern catalog: {CATALOG_PATH}")
|
|
43
|
+
|
|
44
|
+
data = yaml.safe_load(catalog_path.read_text(encoding="utf-8"))
|
|
45
|
+
if not isinstance(data, dict):
|
|
46
|
+
raise PatternCatalogError("pattern catalog must be a mapping")
|
|
47
|
+
|
|
48
|
+
version = data.get("version")
|
|
49
|
+
if version != 1:
|
|
50
|
+
raise PatternCatalogError(f"unsupported pattern catalog version: {version}")
|
|
51
|
+
|
|
52
|
+
raw_patterns = data.get("patterns")
|
|
53
|
+
if not isinstance(raw_patterns, list):
|
|
54
|
+
raise PatternCatalogError("pattern catalog must contain a patterns list")
|
|
55
|
+
|
|
56
|
+
seen: set[str] = set()
|
|
57
|
+
entries: list[PatternEntry] = []
|
|
58
|
+
for raw in raw_patterns:
|
|
59
|
+
if not isinstance(raw, dict):
|
|
60
|
+
raise PatternCatalogError("pattern entry must be a mapping")
|
|
61
|
+
entry_id = _required_string(raw, "id")
|
|
62
|
+
if entry_id in seen:
|
|
63
|
+
raise PatternCatalogError(f"duplicate pattern id: {entry_id}")
|
|
64
|
+
seen.add(entry_id)
|
|
65
|
+
kind = _required_string(raw, "kind")
|
|
66
|
+
if kind not in VALID_KINDS:
|
|
67
|
+
raise PatternCatalogError(f"invalid pattern kind: {kind}")
|
|
68
|
+
entries.append(
|
|
69
|
+
PatternEntry(
|
|
70
|
+
id=entry_id,
|
|
71
|
+
title=_required_string(raw, "title"),
|
|
72
|
+
kind=kind,
|
|
73
|
+
summary=_required_string(raw, "summary"),
|
|
74
|
+
doc=Path(_required_string(raw, "doc")),
|
|
75
|
+
)
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
return PatternCatalog(version=version, patterns=entries)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def load_pattern_detail(root: Path, entry: PatternEntry) -> PatternDetail:
|
|
82
|
+
doc_path = root / entry.doc
|
|
83
|
+
if not doc_path.exists():
|
|
84
|
+
raise PatternCatalogError(f"missing pattern document: {entry.doc}")
|
|
85
|
+
return PatternDetail(entry=entry, markdown=doc_path.read_text(encoding="utf-8"))
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def find_pattern(catalog: PatternCatalog, pattern_id: str) -> PatternEntry:
|
|
89
|
+
for entry in catalog.patterns:
|
|
90
|
+
if entry.id == pattern_id:
|
|
91
|
+
return entry
|
|
92
|
+
raise PatternCatalogError(f"unknown pattern id: {pattern_id}")
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def _required_string(raw: dict[str, Any], key: str) -> str:
|
|
96
|
+
value = raw.get(key)
|
|
97
|
+
if not isinstance(value, str) or not value.strip():
|
|
98
|
+
raise PatternCatalogError(f"missing or invalid pattern field: {key}")
|
|
99
|
+
return value
|
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import asdict, dataclass
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
PLANNING_ROOTS = (
|
|
8
|
+
"docs/planning",
|
|
9
|
+
"docs/handoff",
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
ACTIVE_MARKERS = (
|
|
13
|
+
"active",
|
|
14
|
+
"current",
|
|
15
|
+
"next",
|
|
16
|
+
"pending",
|
|
17
|
+
"todo",
|
|
18
|
+
"roadmap",
|
|
19
|
+
"slice",
|
|
20
|
+
"gate",
|
|
21
|
+
"pre-gui",
|
|
22
|
+
"gui",
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
STALE_MARKERS = (
|
|
26
|
+
"obsolete",
|
|
27
|
+
"superseded",
|
|
28
|
+
"archived",
|
|
29
|
+
"deprecated",
|
|
30
|
+
"removed",
|
|
31
|
+
"legacy",
|
|
32
|
+
"done",
|
|
33
|
+
"completed",
|
|
34
|
+
"closed",
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
POST_V049_MARKERS = (
|
|
38
|
+
"v0.4.9",
|
|
39
|
+
"0.4.9",
|
|
40
|
+
"post-v0.4.9",
|
|
41
|
+
"release command authority",
|
|
42
|
+
"audit-doc-currency",
|
|
43
|
+
"audit-absolute-path",
|
|
44
|
+
"audit-ns",
|
|
45
|
+
"gui",
|
|
46
|
+
"0.5.0",
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
AUTHORITATIVE_PLANNING_DOCS = {
|
|
50
|
+
"docs/planning/PROJECT_DIRECTION.yaml",
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
PLANNING_VIEW_DOCS = {
|
|
54
|
+
"docs/planning/PROJECT_DIRECTION.md",
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
SCOPED_AUTHORITATIVE_PLANNING_DOCS = {
|
|
58
|
+
"docs/archive/PRE_GUI_HARDENING_TASKS.md",
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
HANDOFF_PROJECTION_DOCS = {
|
|
62
|
+
"docs/handoff/NEXT_CHAT_BOOTSTRAP.md",
|
|
63
|
+
"docs/handoff/START_NEW_CHAT_PROMPT.md",
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
HISTORICAL_PLANNING_DOCS = {
|
|
67
|
+
"docs/archive/WORKFLOW_REDUCTION_FOCUS.md",
|
|
68
|
+
"docs/planning/WORKFLOW_REDUCTION_FOCUS.md",
|
|
69
|
+
"docs/planning/GUI_DETERMINISTIC_GATEKEEPER_PLAN.md",
|
|
70
|
+
"docs/planning/V0.3.8_SCOPE.md",
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
KNOWN_ACTIVE_PLANNING_DOCS = set()
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
LEGACY_REVIEW_DOCS = {
|
|
77
|
+
"docs/handoff/CLOSEOUT_BEFORE_CHAT_SWITCH_PROMPT.md",
|
|
78
|
+
"docs/governance/WORK_ORDER_WORKFLOW_CONTRACT.md",
|
|
79
|
+
"docs/archive/FAILURE_MODE_REVIEW_AUTOMATION_PLAN.md",
|
|
80
|
+
"docs/archive/RULE_REFRESH_HANDSHAKE_PLAN.md",
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
DUPLICATION_TOPICS = {
|
|
86
|
+
"release_command_authority": (
|
|
87
|
+
"release command authority",
|
|
88
|
+
"release metadata",
|
|
89
|
+
"release-prep",
|
|
90
|
+
"release publish",
|
|
91
|
+
"doi closeout",
|
|
92
|
+
),
|
|
93
|
+
"ns_migration": (
|
|
94
|
+
"./ns",
|
|
95
|
+
"ns-menu",
|
|
96
|
+
"ns migration",
|
|
97
|
+
"legacy ns",
|
|
98
|
+
),
|
|
99
|
+
"handoff": (
|
|
100
|
+
"handoff",
|
|
101
|
+
"successor",
|
|
102
|
+
"next chat",
|
|
103
|
+
"bootstrap",
|
|
104
|
+
),
|
|
105
|
+
"gui": (
|
|
106
|
+
"gui",
|
|
107
|
+
"cockpit",
|
|
108
|
+
"tkinter",
|
|
109
|
+
),
|
|
110
|
+
"docs_currency": (
|
|
111
|
+
"doc currency",
|
|
112
|
+
"documentation currency",
|
|
113
|
+
"current state",
|
|
114
|
+
"status doc",
|
|
115
|
+
),
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
@dataclass(frozen=True)
|
|
120
|
+
class PlanningDocRecord:
|
|
121
|
+
path: str
|
|
122
|
+
title: str | None
|
|
123
|
+
line_count: int
|
|
124
|
+
active_score: int
|
|
125
|
+
stale_score: int
|
|
126
|
+
post_v049_score: int
|
|
127
|
+
topics: tuple[str, ...]
|
|
128
|
+
classification: str
|
|
129
|
+
reason: str
|
|
130
|
+
|
|
131
|
+
def as_dict(self) -> dict[str, object]:
|
|
132
|
+
return asdict(self)
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
@dataclass(frozen=True)
|
|
136
|
+
class PlanningDocsConsolidationAuditResult:
|
|
137
|
+
root: str
|
|
138
|
+
records: tuple[PlanningDocRecord, ...]
|
|
139
|
+
blockers: tuple[PlanningDocRecord, ...]
|
|
140
|
+
|
|
141
|
+
@property
|
|
142
|
+
def ok(self) -> bool:
|
|
143
|
+
return not self.blockers
|
|
144
|
+
|
|
145
|
+
@property
|
|
146
|
+
def status(self) -> str:
|
|
147
|
+
return "PASS" if self.ok else "FAIL"
|
|
148
|
+
|
|
149
|
+
@property
|
|
150
|
+
def returncode(self) -> int:
|
|
151
|
+
return 0 if self.ok else 1
|
|
152
|
+
|
|
153
|
+
def as_dict(self) -> dict[str, object]:
|
|
154
|
+
return {
|
|
155
|
+
"schema_version": 1,
|
|
156
|
+
"kind": "planning_docs_consolidation_audit",
|
|
157
|
+
"root": self.root,
|
|
158
|
+
"status": self.status,
|
|
159
|
+
"record_count": len(self.records),
|
|
160
|
+
"blocker_count": len(self.blockers),
|
|
161
|
+
"records": [item.as_dict() for item in self.records],
|
|
162
|
+
"blockers": [item.as_dict() for item in self.blockers],
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def _title(lines: list[str]) -> str | None:
|
|
167
|
+
for line in lines[:40]:
|
|
168
|
+
stripped = line.strip()
|
|
169
|
+
if stripped.startswith("#"):
|
|
170
|
+
return stripped.lstrip("#").strip() or None
|
|
171
|
+
return None
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def _score(text: str, markers: tuple[str, ...]) -> int:
|
|
175
|
+
lowered = text.lower()
|
|
176
|
+
return sum(lowered.count(marker.lower()) for marker in markers)
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def _topics(text: str) -> tuple[str, ...]:
|
|
180
|
+
lowered = text.lower()
|
|
181
|
+
found: list[str] = []
|
|
182
|
+
for topic, markers in DUPLICATION_TOPICS.items():
|
|
183
|
+
if any(marker.lower() in lowered for marker in markers):
|
|
184
|
+
found.append(topic)
|
|
185
|
+
return tuple(found)
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def _classify(relative: str, text: str, active_score: int, stale_score: int, post_v049_score: int, topics: tuple[str, ...]) -> tuple[str, str]:
|
|
189
|
+
lowered = text.lower()
|
|
190
|
+
|
|
191
|
+
if relative.endswith("CURRENT_HANDOFF.md"):
|
|
192
|
+
return "authoritative_current_handoff", "current handoff is an authoritative state carrier"
|
|
193
|
+
|
|
194
|
+
if relative in AUTHORITATIVE_PLANNING_DOCS:
|
|
195
|
+
return "authoritative_planning_anchor", "explicit current planning authority"
|
|
196
|
+
|
|
197
|
+
if relative in PLANNING_VIEW_DOCS:
|
|
198
|
+
return "planning_authority_view", "human-readable view of the current planning authority"
|
|
199
|
+
|
|
200
|
+
if relative in SCOPED_AUTHORITATIVE_PLANNING_DOCS:
|
|
201
|
+
return "authoritative_scoped_planning_anchor", "explicit scoped planning authority"
|
|
202
|
+
|
|
203
|
+
if relative in HANDOFF_PROJECTION_DOCS:
|
|
204
|
+
return "handoff_projection", "generated handoff prompt projection, not planning authority"
|
|
205
|
+
|
|
206
|
+
if relative in HISTORICAL_PLANNING_DOCS:
|
|
207
|
+
return "historical_planning_doc", "known historical planning/handoff artifact"
|
|
208
|
+
|
|
209
|
+
if relative in LEGACY_REVIEW_DOCS:
|
|
210
|
+
return "legacy_review_candidate", "known follow-up review candidate, not current authority"
|
|
211
|
+
|
|
212
|
+
if relative in KNOWN_ACTIVE_PLANNING_DOCS:
|
|
213
|
+
return "active_planning_candidate", "known active planning document, not current overall authority"
|
|
214
|
+
|
|
215
|
+
if "current source of truth" in lowered or "source of truth" in lowered:
|
|
216
|
+
return "possible_authoritative_plan", "document claims source-of-truth status"
|
|
217
|
+
|
|
218
|
+
if stale_score > active_score and post_v049_score == 0:
|
|
219
|
+
return "stale_or_archival_candidate", "stale markers dominate and no current release marker was found"
|
|
220
|
+
|
|
221
|
+
if post_v049_score > 0 and active_score > 0:
|
|
222
|
+
return "active_planning_candidate", "current release and active planning markers found"
|
|
223
|
+
|
|
224
|
+
if len(topics) >= 3 and active_score > 0:
|
|
225
|
+
return "consolidation_candidate", "multiple active planning topics in one document"
|
|
226
|
+
|
|
227
|
+
if active_score > 0:
|
|
228
|
+
return "active_but_needs_review", "active markers found but no current release anchor"
|
|
229
|
+
|
|
230
|
+
return "low_priority_reference", "no strong active or stale signal"
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
def _iter_docs(root: Path) -> list[Path]:
|
|
234
|
+
docs: list[Path] = []
|
|
235
|
+
for planning_root in PLANNING_ROOTS:
|
|
236
|
+
base = root / planning_root
|
|
237
|
+
if not base.exists():
|
|
238
|
+
continue
|
|
239
|
+
docs.extend(path for path in base.rglob("*") if path.is_file() and path.suffix in {".md", ".yaml", ".yml"})
|
|
240
|
+
return sorted(docs)
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
def audit_planning_docs_consolidation(root: Path = Path(".")) -> PlanningDocsConsolidationAuditResult:
|
|
244
|
+
root = root.resolve()
|
|
245
|
+
records: list[PlanningDocRecord] = []
|
|
246
|
+
|
|
247
|
+
for path in _iter_docs(root):
|
|
248
|
+
relative = path.relative_to(root).as_posix()
|
|
249
|
+
text = path.read_text(encoding="utf-8", errors="replace")
|
|
250
|
+
lines = text.splitlines()
|
|
251
|
+
active_score = _score(text, ACTIVE_MARKERS)
|
|
252
|
+
stale_score = _score(text, STALE_MARKERS)
|
|
253
|
+
post_v049_score = _score(text, POST_V049_MARKERS)
|
|
254
|
+
topics = _topics(text)
|
|
255
|
+
classification, reason = _classify(
|
|
256
|
+
relative,
|
|
257
|
+
text,
|
|
258
|
+
active_score,
|
|
259
|
+
stale_score,
|
|
260
|
+
post_v049_score,
|
|
261
|
+
topics,
|
|
262
|
+
)
|
|
263
|
+
records.append(
|
|
264
|
+
PlanningDocRecord(
|
|
265
|
+
path=relative,
|
|
266
|
+
title=_title(lines),
|
|
267
|
+
line_count=len(lines),
|
|
268
|
+
active_score=active_score,
|
|
269
|
+
stale_score=stale_score,
|
|
270
|
+
post_v049_score=post_v049_score,
|
|
271
|
+
topics=topics,
|
|
272
|
+
classification=classification,
|
|
273
|
+
reason=reason,
|
|
274
|
+
)
|
|
275
|
+
)
|
|
276
|
+
|
|
277
|
+
blockers = tuple(
|
|
278
|
+
record
|
|
279
|
+
for record in records
|
|
280
|
+
if record.classification in {
|
|
281
|
+
"possible_authoritative_plan",
|
|
282
|
+
"consolidation_candidate",
|
|
283
|
+
"active_but_needs_review",
|
|
284
|
+
}
|
|
285
|
+
)
|
|
286
|
+
|
|
287
|
+
return PlanningDocsConsolidationAuditResult(
|
|
288
|
+
root=root.as_posix(),
|
|
289
|
+
records=tuple(records),
|
|
290
|
+
blockers=blockers,
|
|
291
|
+
)
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
def render_planning_docs_consolidation_audit(result: PlanningDocsConsolidationAuditResult) -> str:
|
|
295
|
+
lines = [
|
|
296
|
+
"PLANNING_DOCS_CONSOLIDATION_AUDIT",
|
|
297
|
+
f"STATUS={result.status}",
|
|
298
|
+
f"RECORD_COUNT={len(result.records)}",
|
|
299
|
+
f"BLOCKER_COUNT={len(result.blockers)}",
|
|
300
|
+
]
|
|
301
|
+
for item in result.blockers:
|
|
302
|
+
lines.append(
|
|
303
|
+
"BLOCKER="
|
|
304
|
+
f"{item.classification}|{item.path}|topics={','.join(item.topics)}|"
|
|
305
|
+
f"active={item.active_score}|stale={item.stale_score}|post_v049={item.post_v049_score}|{item.title}"
|
|
306
|
+
)
|
|
307
|
+
for item in result.records:
|
|
308
|
+
lines.append(
|
|
309
|
+
"RECORD="
|
|
310
|
+
f"{item.classification}|{item.path}|topics={','.join(item.topics)}|"
|
|
311
|
+
f"active={item.active_score}|stale={item.stale_score}|post_v049={item.post_v049_score}|{item.title}"
|
|
312
|
+
)
|
|
313
|
+
return "\n".join(lines) + "\n"
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
DEFAULT_EXCLUDED_PARTS = {".git", ".venv", "dist", "build", "__pycache__"}
|
|
7
|
+
DEFAULT_ALLOWED_PREFIXES = ("tmp/",)
|
|
8
|
+
|
|
9
|
+
@dataclass(frozen=True)
|
|
10
|
+
class PortabilityShellGateResult:
|
|
11
|
+
ok: bool
|
|
12
|
+
shell_files: tuple[str, ...]
|
|
13
|
+
blocking_shell_files: tuple[str, ...]
|
|
14
|
+
|
|
15
|
+
def _is_excluded(path: Path) -> bool:
|
|
16
|
+
return any(part in DEFAULT_EXCLUDED_PARTS for part in path.parts)
|
|
17
|
+
|
|
18
|
+
def _is_allowed(path_text: str, allowed_prefixes: tuple[str, ...]) -> bool:
|
|
19
|
+
normalized = path_text.removeprefix("./")
|
|
20
|
+
return any(normalized.startswith(prefix) for prefix in allowed_prefixes)
|
|
21
|
+
|
|
22
|
+
def build_portability_shell_gate_report(project_root: Path, allowed_prefixes: tuple[str, ...] = DEFAULT_ALLOWED_PREFIXES) -> PortabilityShellGateResult:
|
|
23
|
+
shell_files: list[str] = []
|
|
24
|
+
for path in sorted(project_root.rglob("*.sh")):
|
|
25
|
+
rel = path.relative_to(project_root)
|
|
26
|
+
if _is_excluded(rel):
|
|
27
|
+
continue
|
|
28
|
+
shell_files.append(rel.as_posix())
|
|
29
|
+
blocking = tuple(item for item in shell_files if not _is_allowed(item, allowed_prefixes))
|
|
30
|
+
return PortabilityShellGateResult(ok=not blocking, shell_files=tuple(shell_files), blocking_shell_files=blocking)
|
|
31
|
+
|
|
32
|
+
def render_portability_shell_gate_report(result: PortabilityShellGateResult) -> str:
|
|
33
|
+
lines = ["Portability shell gate report"]
|
|
34
|
+
lines.append(f"shell_file_count={len(result.shell_files)}")
|
|
35
|
+
lines.append(f"blocking_shell_file_count={len(result.blocking_shell_files)}")
|
|
36
|
+
if result.blocking_shell_files:
|
|
37
|
+
lines.append("Blocking shell files:")
|
|
38
|
+
lines.extend(f"- {item}" for item in result.blocking_shell_files)
|
|
39
|
+
lines.append("### RESULT: PASS ###" if result.ok else "### RESULT: FAIL ###")
|
|
40
|
+
return "\n".join(lines)
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
from dataclasses import dataclass
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
import subprocess
|
|
5
|
+
from typing import Any
|
|
6
|
+
from agentic_project_kit.handoff_freshness import assess_handoff_prompt_freshness
|
|
7
|
+
from agentic_project_kit.handoff_prompt import render_handoff_prompt
|
|
8
|
+
from agentic_project_kit.handoff_state import load_handoff_state
|
|
9
|
+
|
|
10
|
+
@dataclass(frozen=True)
|
|
11
|
+
class PostMergeHandoffRefreshStatus:
|
|
12
|
+
current_head: str
|
|
13
|
+
freshness_warning_present: bool
|
|
14
|
+
refresh_required: bool
|
|
15
|
+
latest_successor_prompt: str | None
|
|
16
|
+
result: str
|
|
17
|
+
next_safe_action: str
|
|
18
|
+
|
|
19
|
+
def evaluate_post_merge_handoff_refresh(project_root: Path = Path('.'), *, state_path: str = '.agentic/handoff_state.yaml') -> PostMergeHandoffRefreshStatus:
|
|
20
|
+
state_file = project_root / state_path
|
|
21
|
+
data = load_handoff_state(str(state_file))
|
|
22
|
+
rendered_prompt = render_handoff_prompt(data)
|
|
23
|
+
current_head = _git_short_head(project_root)
|
|
24
|
+
current_subject = _git_commit_subject(project_root)
|
|
25
|
+
warnings = assess_handoff_prompt_freshness(
|
|
26
|
+
data,
|
|
27
|
+
str(state_file),
|
|
28
|
+
current_head=current_head,
|
|
29
|
+
current_subject=current_subject,
|
|
30
|
+
successor_prompt_text=rendered_prompt,
|
|
31
|
+
)
|
|
32
|
+
warning = bool(warnings)
|
|
33
|
+
return PostMergeHandoffRefreshStatus(current_head, warning, warning, _latest_successor_prompt(data), 'REFRESH_REQUIRED' if warning else 'NOOP', 'create_administrative_handoff_refresh' if warning else 'continue_without_post_merge_handoff_refresh')
|
|
34
|
+
|
|
35
|
+
def render_post_merge_handoff_refresh_status(status: PostMergeHandoffRefreshStatus) -> str:
|
|
36
|
+
return '\n'.join(['POST_MERGE_HANDOFF_REFRESH', f'current_head={status.current_head}', f'freshness_warning_present={str(status.freshness_warning_present)}', f'refresh_required={str(status.refresh_required)}', f'latest_successor_prompt={status.latest_successor_prompt or ""}', f'result={status.result}', f'next_safe_action={status.next_safe_action}']) + '\n'
|
|
37
|
+
|
|
38
|
+
def _git_short_head(project_root: Path) -> str:
|
|
39
|
+
try:
|
|
40
|
+
return subprocess.check_output(['git', 'rev-parse', '--short', 'HEAD'], cwd=project_root, stderr=subprocess.DEVNULL, text=True).strip()
|
|
41
|
+
except (OSError, subprocess.CalledProcessError):
|
|
42
|
+
return ''
|
|
43
|
+
|
|
44
|
+
def _git_commit_subject(project_root: Path) -> str:
|
|
45
|
+
try:
|
|
46
|
+
return subprocess.check_output(['git', 'log', '-1', '--pretty=%s'], cwd=project_root, stderr=subprocess.DEVNULL, text=True).strip()
|
|
47
|
+
except (OSError, subprocess.CalledProcessError):
|
|
48
|
+
return ''
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def _latest_successor_prompt(data: dict[str, Any]) -> str | None:
|
|
52
|
+
for key in ('handoff_maintenance', 'administrative_evidence_state'):
|
|
53
|
+
section = data.get(key, {})
|
|
54
|
+
if isinstance(section, dict) and section.get('latest_successor_prompt'):
|
|
55
|
+
return str(section['latest_successor_prompt'])
|
|
56
|
+
return None
|