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,511 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from typing import Any
|
|
4
|
+
import re
|
|
5
|
+
import yaml
|
|
6
|
+
|
|
7
|
+
from agentic_project_kit.documentation_registry import check_documentation_registry
|
|
8
|
+
from agentic_project_kit.workspace import Workspace, load_workspace
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
STATE_GATE_DOCUMENTS = (
|
|
12
|
+
"docs/STATUS.md",
|
|
13
|
+
"docs/TEST_GATES.md",
|
|
14
|
+
"docs/handoff/CURRENT_HANDOFF.md",
|
|
15
|
+
"docs/architecture/ARCHITECTURE_CONTRACT.md",
|
|
16
|
+
"docs/DOCUMENTATION_COVERAGE.yaml",
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
STATE_GATE_SECTIONS = {
|
|
20
|
+
"docs/STATUS.md": ("## Current Goal", "## Next Safe Step"),
|
|
21
|
+
"docs/TEST_GATES.md": ("## Gate Matrix", "## Outcome Reporting"),
|
|
22
|
+
"docs/handoff/CURRENT_HANDOFF.md": ("# Current Handoff", "## Current", "## Next"),
|
|
23
|
+
"docs/architecture/ARCHITECTURE_CONTRACT.md": (
|
|
24
|
+
"## 1. Executive Summary",
|
|
25
|
+
"## 2. How to Use This Document",
|
|
26
|
+
"## 4. Decision Rules",
|
|
27
|
+
"## 7. Architectural Contract",
|
|
28
|
+
"## 17. Acceptance Criteria for Future Work",
|
|
29
|
+
),
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
WORKSPACE_STATE_GATE_DOCUMENTS = (
|
|
33
|
+
".agentic/state/status.md",
|
|
34
|
+
".agentic/state/handoff/README.md",
|
|
35
|
+
".agentic/registries/documentation.yaml",
|
|
36
|
+
".agentic/registries/rules.yaml",
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
WORKSPACE_STATE_GATE_SECTIONS = {
|
|
40
|
+
".agentic/state/status.md": ("# Workspace Status",),
|
|
41
|
+
".agentic/state/handoff/README.md": ("# Workspace Handoff",),
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
STALE_HANDOFF_MARKERS = (
|
|
45
|
+
"Create project-level docs",
|
|
46
|
+
"Commit documentation-state files",
|
|
47
|
+
"Run the local gate, inspect the diff, then commit the documentation-state update",
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
CHANGELOG_QUALITY_MIN_VERSION = (0, 3, 36)
|
|
51
|
+
CHANGELOG_RELEASE_HEADING = re.compile(
|
|
52
|
+
r"^##\s+v(?P<version>\d+\.\d+\.\d+)(?:\s+-\s+(?P<date>\d{4}-\d{2}-\d{2}))?.*$",
|
|
53
|
+
re.MULTILINE,
|
|
54
|
+
)
|
|
55
|
+
CHANGELOG_GENERIC_BULLET_PATTERNS = (
|
|
56
|
+
re.compile(r"^- prepare release metadata for v\d+\.\d+\.\d+\.?$", re.IGNORECASE),
|
|
57
|
+
re.compile(r"^- post-release verification complete:.*$", re.IGNORECASE),
|
|
58
|
+
)
|
|
59
|
+
CHANGELOG_SUBSTANCE_CATEGORIES = {
|
|
60
|
+
"release-evidence": (
|
|
61
|
+
"zenodo",
|
|
62
|
+
"doi",
|
|
63
|
+
"post-release",
|
|
64
|
+
"github release",
|
|
65
|
+
"release metadata",
|
|
66
|
+
"verified",
|
|
67
|
+
),
|
|
68
|
+
"implementation-or-doc-change": (
|
|
69
|
+
"add",
|
|
70
|
+
"added",
|
|
71
|
+
"harden",
|
|
72
|
+
"hardened",
|
|
73
|
+
"close",
|
|
74
|
+
"closed",
|
|
75
|
+
"repair",
|
|
76
|
+
"repaired",
|
|
77
|
+
"wire",
|
|
78
|
+
"wired",
|
|
79
|
+
"extract",
|
|
80
|
+
"extracted",
|
|
81
|
+
"refresh",
|
|
82
|
+
"refreshed",
|
|
83
|
+
"record",
|
|
84
|
+
"recorded",
|
|
85
|
+
"translate",
|
|
86
|
+
"translated",
|
|
87
|
+
"preserve",
|
|
88
|
+
"preserved",
|
|
89
|
+
),
|
|
90
|
+
"safety-or-scope": (
|
|
91
|
+
"bounded",
|
|
92
|
+
"read-only",
|
|
93
|
+
"disabled",
|
|
94
|
+
"guard",
|
|
95
|
+
"safety",
|
|
96
|
+
"without",
|
|
97
|
+
"no tag",
|
|
98
|
+
"no release",
|
|
99
|
+
"deferred",
|
|
100
|
+
"scope",
|
|
101
|
+
),
|
|
102
|
+
"continuity-or-governance": (
|
|
103
|
+
"evidence",
|
|
104
|
+
"handoff",
|
|
105
|
+
"governance",
|
|
106
|
+
"contract",
|
|
107
|
+
"regression",
|
|
108
|
+
"successor",
|
|
109
|
+
"current-state",
|
|
110
|
+
"documentation boundary",
|
|
111
|
+
),
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def _open_marker_pattern(marker: str) -> re.Pattern[str]:
|
|
116
|
+
escaped_marker = re.escape(marker)
|
|
117
|
+
return re.compile(rf"(?:^|[\s\[{{(<]){escaped_marker}(?:\s*[:\-]|[\]}}>)])")
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
QUALITY_PLACEHOLDER_PATTERNS = {
|
|
121
|
+
"TODO": _open_marker_pattern("TODO"),
|
|
122
|
+
"TBD": _open_marker_pattern("TBD"),
|
|
123
|
+
"FIXME": _open_marker_pattern("FIXME"),
|
|
124
|
+
"Lorem ipsum": re.compile(r"Lorem ipsum", re.IGNORECASE),
|
|
125
|
+
"coming soon": re.compile(r"coming soon", re.IGNORECASE),
|
|
126
|
+
"to be written": re.compile(r"to be written", re.IGNORECASE),
|
|
127
|
+
"to be defined": re.compile(r"to be defined", re.IGNORECASE),
|
|
128
|
+
"placeholder marker": re.compile(r"placeholder\s+(text|section|content|value)", re.IGNORECASE),
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
SEMANTIC_QUALITY_BOUNDARY = (
|
|
133
|
+
"Deterministic checks can detect placeholders, stale markers, coverage gaps, "
|
|
134
|
+
"and structural drift. They cannot prove semantic perfection. LLM review, "
|
|
135
|
+
"if added later, must remain advisory and separate from hard doctor gates."
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
@dataclass(frozen=True)
|
|
140
|
+
class CheckContext:
|
|
141
|
+
root: str
|
|
142
|
+
mode: str
|
|
143
|
+
external_manifest_workspace: bool
|
|
144
|
+
gate_family: str
|
|
145
|
+
gate_documents: tuple[str, ...]
|
|
146
|
+
check_renders_statuses: bool
|
|
147
|
+
skip_status_renderer: str
|
|
148
|
+
invalid_manifest_error: str = ""
|
|
149
|
+
|
|
150
|
+
def as_dict(self) -> dict[str, object]:
|
|
151
|
+
return {
|
|
152
|
+
"root": self.root,
|
|
153
|
+
"mode": self.mode,
|
|
154
|
+
"external_manifest_workspace": self.external_manifest_workspace,
|
|
155
|
+
"gate_family": self.gate_family,
|
|
156
|
+
"gate_documents": list(self.gate_documents),
|
|
157
|
+
"check_renders_statuses": self.check_renders_statuses,
|
|
158
|
+
"skip_status_renderer": self.skip_status_renderer,
|
|
159
|
+
"invalid_manifest_error": self.invalid_manifest_error,
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def load_yaml(path: Path) -> dict[str, Any]:
|
|
164
|
+
if not path.exists():
|
|
165
|
+
raise FileNotFoundError(f"Missing config file: {path}")
|
|
166
|
+
data = yaml.safe_load(path.read_text(encoding="utf-8")) or {}
|
|
167
|
+
if not isinstance(data, dict):
|
|
168
|
+
raise ValueError(f"YAML root must be a mapping: {path}")
|
|
169
|
+
return data
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def load_optional_yaml(path: Path) -> dict[str, Any]:
|
|
173
|
+
if not path.exists():
|
|
174
|
+
return {}
|
|
175
|
+
return load_yaml(path)
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def count_words(text: str) -> int:
|
|
179
|
+
return len(text.split())
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
def check_docs(project_root: Path) -> list[str]:
|
|
183
|
+
config = load_optional_yaml(project_root / "sentinel.yaml")
|
|
184
|
+
errors: list[str] = []
|
|
185
|
+
|
|
186
|
+
for doc in config.get("documents", []):
|
|
187
|
+
path = project_root / doc["path"]
|
|
188
|
+
if not path.exists():
|
|
189
|
+
errors.append(f"Missing document: {doc['path']}")
|
|
190
|
+
continue
|
|
191
|
+
|
|
192
|
+
content = path.read_text(encoding="utf-8")
|
|
193
|
+
for section in doc.get("required_sections", []):
|
|
194
|
+
if section not in content:
|
|
195
|
+
errors.append(f"{doc['path']}: missing required section {section!r}")
|
|
196
|
+
|
|
197
|
+
max_words = doc.get("max_words")
|
|
198
|
+
if max_words is not None:
|
|
199
|
+
words = count_words(content)
|
|
200
|
+
if words > int(max_words):
|
|
201
|
+
errors.append(f"{doc['path']}: too long ({words}/{max_words} words)")
|
|
202
|
+
|
|
203
|
+
min_words = doc.get("min_words")
|
|
204
|
+
if min_words is not None:
|
|
205
|
+
words = count_words(content)
|
|
206
|
+
if words < int(min_words):
|
|
207
|
+
errors.append(f"{doc['path']}: too short ({words}/{min_words} words)")
|
|
208
|
+
|
|
209
|
+
if bool(doc.get("quality_checks", True)):
|
|
210
|
+
errors.extend(check_document_quality(doc["path"], content))
|
|
211
|
+
|
|
212
|
+
context = build_check_context(project_root)
|
|
213
|
+
if context.invalid_manifest_error:
|
|
214
|
+
errors.append(f"Invalid workspace manifest: {context.invalid_manifest_error}")
|
|
215
|
+
elif context.external_manifest_workspace:
|
|
216
|
+
errors.extend(check_workspace_state_gate_docs(project_root))
|
|
217
|
+
else:
|
|
218
|
+
errors.extend(check_state_gate_docs(project_root))
|
|
219
|
+
errors.extend(check_documentation_coverage(project_root))
|
|
220
|
+
errors.extend(check_documentation_registry(project_root))
|
|
221
|
+
errors.extend(check_changelog_quality(project_root))
|
|
222
|
+
return errors
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
def _load_manifest_workspace_for_checks(project_root: Path) -> Workspace | None | ValueError:
|
|
226
|
+
if _is_agentic_project_kit_development_checkout(project_root):
|
|
227
|
+
return None
|
|
228
|
+
if not (project_root / ".agentic/config.yaml").exists():
|
|
229
|
+
return None
|
|
230
|
+
try:
|
|
231
|
+
return load_workspace(project_root, suppress_legacy_profile_warning=True)
|
|
232
|
+
except RuntimeError as exc:
|
|
233
|
+
return ValueError(str(exc))
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
def build_check_context(project_root: Path) -> CheckContext:
|
|
237
|
+
project_root = project_root.resolve()
|
|
238
|
+
workspace = _load_manifest_workspace_for_checks(project_root)
|
|
239
|
+
if isinstance(workspace, ValueError):
|
|
240
|
+
return CheckContext(
|
|
241
|
+
root=project_root.as_posix(),
|
|
242
|
+
mode="invalid_manifest",
|
|
243
|
+
external_manifest_workspace=False,
|
|
244
|
+
gate_family="invalid_manifest",
|
|
245
|
+
gate_documents=(),
|
|
246
|
+
check_renders_statuses=False,
|
|
247
|
+
skip_status_renderer="agentic-kit doctor",
|
|
248
|
+
invalid_manifest_error=str(workspace),
|
|
249
|
+
)
|
|
250
|
+
if workspace is not None:
|
|
251
|
+
return CheckContext(
|
|
252
|
+
root=project_root.as_posix(),
|
|
253
|
+
mode="external_manifest_workspace",
|
|
254
|
+
external_manifest_workspace=True,
|
|
255
|
+
gate_family="workspace_state",
|
|
256
|
+
gate_documents=WORKSPACE_STATE_GATE_DOCUMENTS,
|
|
257
|
+
check_renders_statuses=False,
|
|
258
|
+
skip_status_renderer="agentic-kit doctor",
|
|
259
|
+
)
|
|
260
|
+
mode = (
|
|
261
|
+
"selfhosting_checkout"
|
|
262
|
+
if _is_agentic_project_kit_development_checkout(project_root)
|
|
263
|
+
else "repository_state"
|
|
264
|
+
)
|
|
265
|
+
return CheckContext(
|
|
266
|
+
root=project_root.as_posix(),
|
|
267
|
+
mode=mode,
|
|
268
|
+
external_manifest_workspace=False,
|
|
269
|
+
gate_family="repository_state",
|
|
270
|
+
gate_documents=STATE_GATE_DOCUMENTS,
|
|
271
|
+
check_renders_statuses=False,
|
|
272
|
+
skip_status_renderer="agentic-kit doctor",
|
|
273
|
+
)
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
def _is_agentic_project_kit_development_checkout(project_root: Path) -> bool:
|
|
277
|
+
return (
|
|
278
|
+
(project_root / "src" / "agentic_project_kit").is_dir()
|
|
279
|
+
and (project_root / "docs" / "reference" / "agentic-kit-commands.json").exists()
|
|
280
|
+
and (project_root / "pyproject.toml").exists()
|
|
281
|
+
)
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
def check_document_quality(relative_path: str, content: str) -> list[str]:
|
|
285
|
+
"""Return deterministic document-quality findings.
|
|
286
|
+
|
|
287
|
+
These checks intentionally cover only machine-checkable quality signals.
|
|
288
|
+
They are not a claim of semantic perfection.
|
|
289
|
+
"""
|
|
290
|
+
errors: list[str] = []
|
|
291
|
+
for marker, pattern in QUALITY_PLACEHOLDER_PATTERNS.items():
|
|
292
|
+
if pattern.search(content):
|
|
293
|
+
errors.append(f"{relative_path}: unresolved placeholder marker {marker!r}")
|
|
294
|
+
return errors
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
def _release_version_tuple(version: str) -> tuple[int, int, int]:
|
|
298
|
+
major, minor, patch = version.split(".")
|
|
299
|
+
return int(major), int(minor), int(patch)
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
def _release_sections(content: str) -> list[tuple[str, str | None, str]]:
|
|
303
|
+
matches = list(CHANGELOG_RELEASE_HEADING.finditer(content))
|
|
304
|
+
sections: list[tuple[str, str | None, str]] = []
|
|
305
|
+
for index, match in enumerate(matches):
|
|
306
|
+
start = match.end()
|
|
307
|
+
end = matches[index + 1].start() if index + 1 < len(matches) else len(content)
|
|
308
|
+
sections.append((match.group("version"), match.group("date"), content[start:end].strip()))
|
|
309
|
+
return sections
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
def _substance_categories(section_text: str) -> set[str]:
|
|
313
|
+
normalized = section_text.lower()
|
|
314
|
+
return {
|
|
315
|
+
category
|
|
316
|
+
for category, terms in CHANGELOG_SUBSTANCE_CATEGORIES.items()
|
|
317
|
+
if any(term in normalized for term in terms)
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
def _substantive_changelog_bullets(section_text: str) -> list[str]:
|
|
322
|
+
bullets = [line.strip() for line in section_text.splitlines() if line.strip().startswith("- ")]
|
|
323
|
+
return [
|
|
324
|
+
bullet
|
|
325
|
+
for bullet in bullets
|
|
326
|
+
if not any(pattern.match(bullet) for pattern in CHANGELOG_GENERIC_BULLET_PATTERNS)
|
|
327
|
+
]
|
|
328
|
+
|
|
329
|
+
|
|
330
|
+
def check_changelog_quality(project_root: Path) -> list[str]:
|
|
331
|
+
"""Check recent release-history quality without pretending to judge prose quality.
|
|
332
|
+
|
|
333
|
+
The guard is intentionally structural and category-based. It prevents recent
|
|
334
|
+
release entries from collapsing into generic metadata-only bullets, but it does
|
|
335
|
+
not require a naive minimum bullet count and does not rewrite historical evidence.
|
|
336
|
+
"""
|
|
337
|
+
path = project_root / "CHANGELOG.md"
|
|
338
|
+
if not path.exists():
|
|
339
|
+
return []
|
|
340
|
+
|
|
341
|
+
content = path.read_text(encoding="utf-8")
|
|
342
|
+
errors: list[str] = []
|
|
343
|
+
seen_versions: set[str] = set()
|
|
344
|
+
|
|
345
|
+
for version, date, body in _release_sections(content):
|
|
346
|
+
if version in seen_versions:
|
|
347
|
+
errors.append(f"CHANGELOG.md: duplicate release section v{version}")
|
|
348
|
+
seen_versions.add(version)
|
|
349
|
+
|
|
350
|
+
if _release_version_tuple(version) < CHANGELOG_QUALITY_MIN_VERSION:
|
|
351
|
+
continue
|
|
352
|
+
|
|
353
|
+
section_text = f"v{version}\n{body}"
|
|
354
|
+
normalized = section_text.lower()
|
|
355
|
+
if date is None:
|
|
356
|
+
errors.append(f"CHANGELOG.md: v{version} missing release date in heading")
|
|
357
|
+
|
|
358
|
+
has_zenodo_state = "zenodo" in normalized and ("doi" in normalized or "pending" in normalized)
|
|
359
|
+
if not has_zenodo_state:
|
|
360
|
+
errors.append(f"CHANGELOG.md: v{version} missing Zenodo DOI or pending verification marker")
|
|
361
|
+
|
|
362
|
+
if not _substantive_changelog_bullets(body):
|
|
363
|
+
errors.append(f"CHANGELOG.md: v{version} has no substantive release bullet beyond generic metadata")
|
|
364
|
+
|
|
365
|
+
categories = _substance_categories(section_text)
|
|
366
|
+
if len(categories) < 3:
|
|
367
|
+
found = ", ".join(sorted(categories)) or "none"
|
|
368
|
+
errors.append(f"CHANGELOG.md: v{version} lacks enough release-quality categories (found: {found})")
|
|
369
|
+
|
|
370
|
+
return errors
|
|
371
|
+
|
|
372
|
+
|
|
373
|
+
def check_state_gate_docs(project_root: Path) -> list[str]:
|
|
374
|
+
errors: list[str] = []
|
|
375
|
+
|
|
376
|
+
for relative_path in STATE_GATE_DOCUMENTS:
|
|
377
|
+
path = project_root / relative_path
|
|
378
|
+
if not path.exists():
|
|
379
|
+
errors.append(f"Missing state gate document: {relative_path}")
|
|
380
|
+
continue
|
|
381
|
+
|
|
382
|
+
content = path.read_text(encoding="utf-8")
|
|
383
|
+
for section in STATE_GATE_SECTIONS.get(relative_path, ()): # defensive for future docs
|
|
384
|
+
if section not in content:
|
|
385
|
+
errors.append(f"{relative_path}: missing state gate section {section!r}")
|
|
386
|
+
|
|
387
|
+
handoff_path = project_root / "docs/handoff/CURRENT_HANDOFF.md"
|
|
388
|
+
if handoff_path.exists():
|
|
389
|
+
handoff = handoff_path.read_text(encoding="utf-8")
|
|
390
|
+
for marker in STALE_HANDOFF_MARKERS:
|
|
391
|
+
if marker in handoff:
|
|
392
|
+
errors.append(f"docs/handoff/CURRENT_HANDOFF.md: stale handoff marker {marker!r}")
|
|
393
|
+
|
|
394
|
+
return errors
|
|
395
|
+
|
|
396
|
+
|
|
397
|
+
def check_workspace_state_gate_docs(project_root: Path) -> list[str]:
|
|
398
|
+
errors: list[str] = []
|
|
399
|
+
|
|
400
|
+
for relative_path in WORKSPACE_STATE_GATE_DOCUMENTS:
|
|
401
|
+
path = project_root / relative_path
|
|
402
|
+
if not path.exists():
|
|
403
|
+
errors.append(f"Missing workspace state document: {relative_path}")
|
|
404
|
+
continue
|
|
405
|
+
|
|
406
|
+
content = path.read_text(encoding="utf-8")
|
|
407
|
+
for section in WORKSPACE_STATE_GATE_SECTIONS.get(relative_path, ()):
|
|
408
|
+
if section not in content:
|
|
409
|
+
errors.append(f"{relative_path}: missing workspace state section {section!r}")
|
|
410
|
+
|
|
411
|
+
return errors
|
|
412
|
+
|
|
413
|
+
|
|
414
|
+
def check_documentation_coverage(project_root: Path) -> list[str]:
|
|
415
|
+
matrix_path = project_root / "docs/DOCUMENTATION_COVERAGE.yaml"
|
|
416
|
+
if not matrix_path.exists():
|
|
417
|
+
return ["Missing state gate document: docs/DOCUMENTATION_COVERAGE.yaml"]
|
|
418
|
+
|
|
419
|
+
try:
|
|
420
|
+
matrix = load_yaml(matrix_path)
|
|
421
|
+
except (FileNotFoundError, ValueError, yaml.YAMLError) as exc:
|
|
422
|
+
return [f"docs/DOCUMENTATION_COVERAGE.yaml: invalid coverage matrix ({exc})"]
|
|
423
|
+
|
|
424
|
+
errors: list[str] = []
|
|
425
|
+
rules = matrix.get("rules", [])
|
|
426
|
+
if not isinstance(rules, list) or not rules:
|
|
427
|
+
return ["docs/DOCUMENTATION_COVERAGE.yaml: rules must be a non-empty list"]
|
|
428
|
+
|
|
429
|
+
for rule_index, rule in enumerate(rules, start=1):
|
|
430
|
+
if not isinstance(rule, dict):
|
|
431
|
+
errors.append(f"docs/DOCUMENTATION_COVERAGE.yaml: rule {rule_index} must be a mapping")
|
|
432
|
+
continue
|
|
433
|
+
rule_id = str(rule.get("id") or f"rule-{rule_index}")
|
|
434
|
+
documents = rule.get("documents", [])
|
|
435
|
+
if not isinstance(documents, list) or not documents:
|
|
436
|
+
errors.append(f"docs/DOCUMENTATION_COVERAGE.yaml: {rule_id} documents must be a non-empty list")
|
|
437
|
+
continue
|
|
438
|
+
|
|
439
|
+
for doc_index, doc in enumerate(documents, start=1):
|
|
440
|
+
if not isinstance(doc, dict):
|
|
441
|
+
errors.append(f"docs/DOCUMENTATION_COVERAGE.yaml: {rule_id} document {doc_index} must be a mapping")
|
|
442
|
+
continue
|
|
443
|
+
relative_path = str(doc.get("path") or "").strip()
|
|
444
|
+
if not relative_path:
|
|
445
|
+
errors.append(f"docs/DOCUMENTATION_COVERAGE.yaml: {rule_id} document {doc_index} missing path")
|
|
446
|
+
continue
|
|
447
|
+
path = project_root / relative_path
|
|
448
|
+
if not path.exists():
|
|
449
|
+
errors.append(f"documentation coverage {rule_id}: missing document {relative_path}")
|
|
450
|
+
continue
|
|
451
|
+
|
|
452
|
+
terms = doc.get("terms", [])
|
|
453
|
+
if not isinstance(terms, list) or not terms:
|
|
454
|
+
errors.append(f"docs/DOCUMENTATION_COVERAGE.yaml: {rule_id} {relative_path} terms must be a non-empty list")
|
|
455
|
+
continue
|
|
456
|
+
|
|
457
|
+
content = path.read_text(encoding="utf-8")
|
|
458
|
+
for term in terms:
|
|
459
|
+
needle = str(term)
|
|
460
|
+
if needle not in content:
|
|
461
|
+
errors.append(f"documentation coverage {rule_id}: {relative_path} missing term {needle!r}")
|
|
462
|
+
|
|
463
|
+
return errors
|
|
464
|
+
|
|
465
|
+
|
|
466
|
+
def check_todo(project_root: Path) -> list[str]:
|
|
467
|
+
workspace = _load_manifest_workspace_for_checks(project_root)
|
|
468
|
+
if isinstance(workspace, ValueError) and not (project_root / "sentinel.yaml").exists():
|
|
469
|
+
return [f"Invalid workspace manifest: {workspace}"]
|
|
470
|
+
if workspace is not None and not (project_root / "sentinel.yaml").exists():
|
|
471
|
+
return []
|
|
472
|
+
config = load_yaml(project_root / "sentinel.yaml")
|
|
473
|
+
todo_path = project_root / config.get("todo", {}).get("path", ".agentic/todo.yaml")
|
|
474
|
+
data = load_yaml(todo_path)
|
|
475
|
+
errors: list[str] = []
|
|
476
|
+
|
|
477
|
+
items = data.get("items", [])
|
|
478
|
+
if not isinstance(items, list):
|
|
479
|
+
return [f"{todo_path}: items must be a list"]
|
|
480
|
+
|
|
481
|
+
required = ["id", "title", "owner", "priority", "status", "evidence_required"]
|
|
482
|
+
seen_ids: set[str] = set()
|
|
483
|
+
|
|
484
|
+
for index, item in enumerate(items, start=1):
|
|
485
|
+
if not isinstance(item, dict):
|
|
486
|
+
errors.append(f"{todo_path}: item {index} must be a mapping")
|
|
487
|
+
continue
|
|
488
|
+
|
|
489
|
+
item_id = str(item.get("id", "")).strip()
|
|
490
|
+
if not item_id:
|
|
491
|
+
errors.append(f"{todo_path}: item {index} has no id")
|
|
492
|
+
elif item_id in seen_ids:
|
|
493
|
+
errors.append(f"{todo_path}: duplicate id {item_id}")
|
|
494
|
+
else:
|
|
495
|
+
seen_ids.add(item_id)
|
|
496
|
+
|
|
497
|
+
for field in required:
|
|
498
|
+
if not str(item.get(field, "")).strip():
|
|
499
|
+
errors.append(f"{todo_path}: {item_id or index} missing {field}")
|
|
500
|
+
|
|
501
|
+
if str(item.get("status", "")).strip() not in {"open", "done", "blocked"}:
|
|
502
|
+
errors.append(f"{todo_path}: {item_id or index} has invalid status")
|
|
503
|
+
|
|
504
|
+
return errors
|
|
505
|
+
|
|
506
|
+
|
|
507
|
+
def check_all(project_root: Path) -> list[str]:
|
|
508
|
+
errors: list[str] = []
|
|
509
|
+
errors.extend(check_docs(project_root))
|
|
510
|
+
errors.extend(check_todo(project_root))
|
|
511
|
+
return errors
|