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,455 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import asdict, dataclass
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
import shlex
|
|
6
|
+
from typing import Any, Iterable
|
|
7
|
+
|
|
8
|
+
import yaml
|
|
9
|
+
|
|
10
|
+
from agentic_project_kit.command_manifest import load_manifest
|
|
11
|
+
|
|
12
|
+
ACK_PREFIX = "COMMAND_MANIFEST_ACK "
|
|
13
|
+
COMMAND_PREFIXES = (
|
|
14
|
+
"git",
|
|
15
|
+
"gh",
|
|
16
|
+
"agentic-kit",
|
|
17
|
+
"python -m agentic_project_kit",
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@dataclass(frozen=True)
|
|
22
|
+
class InstructionLintFinding:
|
|
23
|
+
line: int
|
|
24
|
+
severity: str
|
|
25
|
+
rule: str
|
|
26
|
+
message: str
|
|
27
|
+
command: str
|
|
28
|
+
|
|
29
|
+
def to_dict(self) -> dict[str, Any]:
|
|
30
|
+
return asdict(self)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@dataclass(frozen=True)
|
|
34
|
+
class InstructionLintResult:
|
|
35
|
+
result_status: str
|
|
36
|
+
findings: tuple[InstructionLintFinding, ...]
|
|
37
|
+
checked_path: str
|
|
38
|
+
manifest_sha: str
|
|
39
|
+
|
|
40
|
+
@property
|
|
41
|
+
def blockers(self) -> list[str]:
|
|
42
|
+
return [finding.rule for finding in self.findings if finding.severity == "BLOCK"]
|
|
43
|
+
|
|
44
|
+
@property
|
|
45
|
+
def warnings(self) -> list[str]:
|
|
46
|
+
return [finding.rule for finding in self.findings if finding.severity == "WARN"]
|
|
47
|
+
|
|
48
|
+
@property
|
|
49
|
+
def returncode(self) -> int:
|
|
50
|
+
if self.blockers:
|
|
51
|
+
return 2
|
|
52
|
+
if self.warnings:
|
|
53
|
+
return 1
|
|
54
|
+
return 0
|
|
55
|
+
|
|
56
|
+
def rejection_block(self) -> str:
|
|
57
|
+
if not self.blockers:
|
|
58
|
+
return ""
|
|
59
|
+
lines = [
|
|
60
|
+
"INSTRUCTION_LINT_REJECTION",
|
|
61
|
+
f"manifest_sha={self.manifest_sha}",
|
|
62
|
+
f"checked_path={self.checked_path}",
|
|
63
|
+
]
|
|
64
|
+
for finding in self.findings:
|
|
65
|
+
if finding.severity != "BLOCK":
|
|
66
|
+
continue
|
|
67
|
+
line = f"line={finding.line}" if finding.line > 0 else "line=structured"
|
|
68
|
+
lines.append(f"{line} rule={finding.rule} message={finding.message}")
|
|
69
|
+
return "\n".join(lines)
|
|
70
|
+
|
|
71
|
+
def to_dict(self) -> dict[str, Any]:
|
|
72
|
+
return {
|
|
73
|
+
"schema_version": 1,
|
|
74
|
+
"kind": "instruction_lint_result",
|
|
75
|
+
"result_status": self.result_status,
|
|
76
|
+
"returncode": self.returncode,
|
|
77
|
+
"manifest_sha": self.manifest_sha,
|
|
78
|
+
"checked_path": self.checked_path,
|
|
79
|
+
"blocker_count": len(self.blockers),
|
|
80
|
+
"warning_count": len(self.warnings),
|
|
81
|
+
"blockers": self.blockers,
|
|
82
|
+
"warnings": self.warnings,
|
|
83
|
+
"findings": [finding.to_dict() for finding in self.findings],
|
|
84
|
+
"rejection_block": self.rejection_block(),
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
@dataclass(frozen=True)
|
|
89
|
+
class _CandidateCommand:
|
|
90
|
+
line: int
|
|
91
|
+
text: str
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def manifest_sha_from_manifest(manifest: dict[str, Any]) -> str:
|
|
95
|
+
meta = manifest.get("meta") if isinstance(manifest.get("meta"), dict) else {}
|
|
96
|
+
return str(meta.get("manifest_sha") or "")
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def command_manifest_ack_line(manifest: dict[str, Any]) -> str:
|
|
100
|
+
return f"{ACK_PREFIX}{manifest_sha_from_manifest(manifest)}"
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def strip_command_manifest_ack_header(text: str) -> str:
|
|
104
|
+
lines = text.splitlines(keepends=True)
|
|
105
|
+
for index, line in enumerate(lines):
|
|
106
|
+
if not line.strip():
|
|
107
|
+
continue
|
|
108
|
+
if line.rstrip("\r\n").startswith(ACK_PREFIX):
|
|
109
|
+
return "".join([*lines[:index], *lines[index + 1 :]])
|
|
110
|
+
return text
|
|
111
|
+
return text
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def _first_non_empty_line(text: str) -> str:
|
|
115
|
+
for line in text.splitlines():
|
|
116
|
+
if line.strip():
|
|
117
|
+
return line.rstrip()
|
|
118
|
+
return ""
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def _strip_prompt_prefixes(line: str) -> str:
|
|
122
|
+
text = line.strip()
|
|
123
|
+
while text and text[0] in {"$", "#", ">"}:
|
|
124
|
+
text = text[1:].lstrip()
|
|
125
|
+
return " ".join(text.split())
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def _starts_with_command_prefix(line: str) -> bool:
|
|
129
|
+
return any(line == prefix or line.startswith(prefix + " ") for prefix in COMMAND_PREFIXES)
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def _normalize_runtime_command(line: str) -> str:
|
|
133
|
+
normalized = _strip_prompt_prefixes(line)
|
|
134
|
+
python_prefixes = (
|
|
135
|
+
"python -m agentic_project_kit.cli",
|
|
136
|
+
"python -m agentic_project_kit",
|
|
137
|
+
)
|
|
138
|
+
for prefix in python_prefixes:
|
|
139
|
+
if normalized == prefix:
|
|
140
|
+
return "agentic-kit"
|
|
141
|
+
if normalized.startswith(prefix + " "):
|
|
142
|
+
return "agentic-kit " + normalized[len(prefix) :].strip()
|
|
143
|
+
return normalized
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def _iter_text_commands(text: str) -> Iterable[_CandidateCommand]:
|
|
147
|
+
in_fence = False
|
|
148
|
+
fence_marker = ""
|
|
149
|
+
for line_number, line in enumerate(text.splitlines(), start=1):
|
|
150
|
+
stripped = line.strip()
|
|
151
|
+
if stripped.startswith(("```", "~~~")):
|
|
152
|
+
marker = stripped[:3]
|
|
153
|
+
if not in_fence:
|
|
154
|
+
in_fence = True
|
|
155
|
+
fence_marker = marker
|
|
156
|
+
elif marker == fence_marker:
|
|
157
|
+
in_fence = False
|
|
158
|
+
fence_marker = ""
|
|
159
|
+
continue
|
|
160
|
+
normalized = _normalize_runtime_command(line)
|
|
161
|
+
if not normalized or not _starts_with_command_prefix(normalized):
|
|
162
|
+
continue
|
|
163
|
+
yield _CandidateCommand(line_number, normalized)
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def _iter_structured_commands(value: Any) -> Iterable[str]:
|
|
167
|
+
if isinstance(value, dict):
|
|
168
|
+
for key, item in value.items():
|
|
169
|
+
if key in {"run", "command", "cmd", "shell"}:
|
|
170
|
+
if isinstance(item, str):
|
|
171
|
+
yield item
|
|
172
|
+
continue
|
|
173
|
+
if isinstance(item, list) and item and all(isinstance(part, str) for part in item):
|
|
174
|
+
yield " ".join(shlex.quote(part) for part in item)
|
|
175
|
+
continue
|
|
176
|
+
yield from _iter_structured_commands(item)
|
|
177
|
+
elif isinstance(value, list):
|
|
178
|
+
for item in value:
|
|
179
|
+
yield from _iter_structured_commands(item)
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
def _iter_yaml_commands(text: str) -> Iterable[_CandidateCommand]:
|
|
183
|
+
try:
|
|
184
|
+
loaded = yaml.safe_load(strip_command_manifest_ack_header(text))
|
|
185
|
+
except Exception:
|
|
186
|
+
return
|
|
187
|
+
for command in _iter_structured_commands(loaded):
|
|
188
|
+
normalized = _normalize_runtime_command(command)
|
|
189
|
+
if normalized and _starts_with_command_prefix(normalized):
|
|
190
|
+
yield _CandidateCommand(0, normalized)
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def _matches_prefix(command_line: str, prefix: str) -> bool:
|
|
194
|
+
return command_line == prefix or command_line.startswith(prefix + " ")
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
def _manifest_commands(manifest: dict[str, Any]) -> list[dict[str, Any]]:
|
|
198
|
+
return [command for command in manifest.get("commands") or [] if isinstance(command, dict)]
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def _match_raw_replacement(manifest: dict[str, Any], command_line: str) -> dict[str, Any] | None:
|
|
202
|
+
matches: list[tuple[str, dict[str, Any]]] = []
|
|
203
|
+
for command in _manifest_commands(manifest):
|
|
204
|
+
for raw_prefix in command.get("replaces_raw") or []:
|
|
205
|
+
normalized_prefix = _strip_prompt_prefixes(str(raw_prefix))
|
|
206
|
+
if normalized_prefix and _matches_prefix(command_line, normalized_prefix):
|
|
207
|
+
matches.append((normalized_prefix, command))
|
|
208
|
+
if not matches:
|
|
209
|
+
return None
|
|
210
|
+
longest = max(len(prefix) for prefix, _command in matches)
|
|
211
|
+
selected = [command for prefix, command in matches if len(prefix) == longest]
|
|
212
|
+
selected.sort(key=lambda command: str(command.get("qualified_name") or ""))
|
|
213
|
+
return selected[0]
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
def _match_manifest_command(manifest: dict[str, Any], command_line: str) -> dict[str, Any] | None:
|
|
217
|
+
matches: list[dict[str, Any]] = []
|
|
218
|
+
for command in _manifest_commands(manifest):
|
|
219
|
+
qualified_name = str(command.get("qualified_name") or "")
|
|
220
|
+
if qualified_name and _matches_prefix(command_line, qualified_name):
|
|
221
|
+
matches.append(command)
|
|
222
|
+
if not matches:
|
|
223
|
+
return None
|
|
224
|
+
longest = max(len(str(command.get("qualified_name") or "")) for command in matches)
|
|
225
|
+
selected = [command for command in matches if len(str(command.get("qualified_name") or "")) == longest]
|
|
226
|
+
selected.sort(key=lambda command: str(command.get("qualified_name") or ""))
|
|
227
|
+
return selected[0]
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
def _command_options(command: dict[str, Any]) -> set[str]:
|
|
231
|
+
options: set[str] = set()
|
|
232
|
+
for param in command.get("params") or []:
|
|
233
|
+
if not isinstance(param, dict):
|
|
234
|
+
continue
|
|
235
|
+
options.update(str(opt) for opt in param.get("opts") or [])
|
|
236
|
+
options.update(str(opt) for opt in param.get("secondary_opts") or [])
|
|
237
|
+
return options
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
def _tokens(command_line: str) -> set[str]:
|
|
241
|
+
try:
|
|
242
|
+
return set(shlex.split(command_line))
|
|
243
|
+
except ValueError:
|
|
244
|
+
return set(command_line.split())
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
def _is_dry_run_variant(command_line: str, command: dict[str, Any]) -> bool:
|
|
248
|
+
tokens = _tokens(command_line)
|
|
249
|
+
options = _command_options(command)
|
|
250
|
+
if "--dry-run" in tokens:
|
|
251
|
+
return True
|
|
252
|
+
if "--execute" in options and "--execute" not in tokens and "--allow-execute" not in tokens:
|
|
253
|
+
return True
|
|
254
|
+
return False
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
def _finding(
|
|
258
|
+
candidate: _CandidateCommand,
|
|
259
|
+
*,
|
|
260
|
+
severity: str,
|
|
261
|
+
rule: str,
|
|
262
|
+
message: str,
|
|
263
|
+
) -> InstructionLintFinding:
|
|
264
|
+
return InstructionLintFinding(
|
|
265
|
+
line=candidate.line,
|
|
266
|
+
severity=severity,
|
|
267
|
+
rule=rule,
|
|
268
|
+
message=message,
|
|
269
|
+
command=candidate.text,
|
|
270
|
+
)
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
def unreadable_instruction_result(
|
|
274
|
+
*,
|
|
275
|
+
checked_path: str,
|
|
276
|
+
message: str,
|
|
277
|
+
manifest_sha: str = "",
|
|
278
|
+
) -> InstructionLintResult:
|
|
279
|
+
return InstructionLintResult(
|
|
280
|
+
result_status="BLOCKED",
|
|
281
|
+
findings=(
|
|
282
|
+
InstructionLintFinding(
|
|
283
|
+
line=1,
|
|
284
|
+
severity="BLOCK",
|
|
285
|
+
rule="INPUT_UNREADABLE",
|
|
286
|
+
message=f"REJECTED: instruction input is unreadable: {message}",
|
|
287
|
+
command="",
|
|
288
|
+
),
|
|
289
|
+
),
|
|
290
|
+
checked_path=checked_path,
|
|
291
|
+
manifest_sha=manifest_sha,
|
|
292
|
+
)
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
def lint_instruction_text(
|
|
296
|
+
text: str,
|
|
297
|
+
*,
|
|
298
|
+
manifest: dict[str, Any],
|
|
299
|
+
checked_path: str = "<stdin>",
|
|
300
|
+
require_ack: bool = True,
|
|
301
|
+
strict_unknown: bool = False,
|
|
302
|
+
include_structured_commands: bool = False,
|
|
303
|
+
) -> InstructionLintResult:
|
|
304
|
+
manifest_sha = manifest_sha_from_manifest(manifest)
|
|
305
|
+
findings: list[InstructionLintFinding] = []
|
|
306
|
+
if not text.strip():
|
|
307
|
+
findings.append(
|
|
308
|
+
InstructionLintFinding(
|
|
309
|
+
line=1,
|
|
310
|
+
severity="BLOCK",
|
|
311
|
+
rule="EMPTY_INPUT",
|
|
312
|
+
message="REJECTED: instruction input is empty",
|
|
313
|
+
command="",
|
|
314
|
+
)
|
|
315
|
+
)
|
|
316
|
+
elif require_ack and _first_non_empty_line(text) != command_manifest_ack_line(manifest):
|
|
317
|
+
findings.append(
|
|
318
|
+
InstructionLintFinding(
|
|
319
|
+
line=1,
|
|
320
|
+
severity="BLOCK",
|
|
321
|
+
rule="ACK",
|
|
322
|
+
message=(
|
|
323
|
+
"REJECTED: missing/stale COMMAND_MANIFEST_ACK - read "
|
|
324
|
+
f"docs/reference/agentic-kit-commands.json (sha {manifest_sha}) first"
|
|
325
|
+
),
|
|
326
|
+
command="",
|
|
327
|
+
)
|
|
328
|
+
)
|
|
329
|
+
|
|
330
|
+
seen_dry_run_bases: set[str] = set()
|
|
331
|
+
candidates = list(_iter_text_commands(text))
|
|
332
|
+
if include_structured_commands:
|
|
333
|
+
candidates.extend(_iter_yaml_commands(text))
|
|
334
|
+
|
|
335
|
+
for candidate in candidates:
|
|
336
|
+
raw_replacement = _match_raw_replacement(manifest, candidate.text)
|
|
337
|
+
if raw_replacement is not None:
|
|
338
|
+
wrapper = str(raw_replacement.get("qualified_name") or "")
|
|
339
|
+
safety = str(raw_replacement.get("safety") or "")
|
|
340
|
+
findings.append(
|
|
341
|
+
_finding(
|
|
342
|
+
candidate,
|
|
343
|
+
severity="BLOCK",
|
|
344
|
+
rule="RAW_REPLACED",
|
|
345
|
+
message=f"REJECTED: use `{wrapper}` instead of `{candidate.text}` (safety: {safety})",
|
|
346
|
+
)
|
|
347
|
+
)
|
|
348
|
+
continue
|
|
349
|
+
|
|
350
|
+
manifest_command = _match_manifest_command(manifest, candidate.text)
|
|
351
|
+
if candidate.text == "agentic-kit" or candidate.text.startswith("agentic-kit "):
|
|
352
|
+
if manifest_command is None:
|
|
353
|
+
findings.append(
|
|
354
|
+
_finding(
|
|
355
|
+
candidate,
|
|
356
|
+
severity="BLOCK",
|
|
357
|
+
rule="UNKNOWN_SUBCOMMAND",
|
|
358
|
+
message=f"REJECTED: unknown agentic-kit command `{candidate.text}`",
|
|
359
|
+
)
|
|
360
|
+
)
|
|
361
|
+
continue
|
|
362
|
+
qualified_name = str(manifest_command.get("qualified_name") or "")
|
|
363
|
+
if (
|
|
364
|
+
manifest_command.get("safety") == "DESTRUCTIVE"
|
|
365
|
+
and bool(manifest_command.get("dry_run_available"))
|
|
366
|
+
):
|
|
367
|
+
if _is_dry_run_variant(candidate.text, manifest_command):
|
|
368
|
+
seen_dry_run_bases.add(qualified_name)
|
|
369
|
+
elif qualified_name not in seen_dry_run_bases:
|
|
370
|
+
findings.append(
|
|
371
|
+
_finding(
|
|
372
|
+
candidate,
|
|
373
|
+
severity="BLOCK",
|
|
374
|
+
rule="DESTRUCTIVE_NO_DRYRUN",
|
|
375
|
+
message=(
|
|
376
|
+
"REJECTED: run a dry-run/plan variant before "
|
|
377
|
+
f"`{qualified_name}`"
|
|
378
|
+
),
|
|
379
|
+
)
|
|
380
|
+
)
|
|
381
|
+
continue
|
|
382
|
+
|
|
383
|
+
if candidate.text.startswith(("git ", "gh ")):
|
|
384
|
+
severity = "BLOCK" if strict_unknown else "WARN"
|
|
385
|
+
findings.append(
|
|
386
|
+
_finding(
|
|
387
|
+
candidate,
|
|
388
|
+
severity=severity,
|
|
389
|
+
rule="UNKNOWN_RAW",
|
|
390
|
+
message=f"UNKNOWN_RAW: no manifest mapping for `{candidate.text}`",
|
|
391
|
+
)
|
|
392
|
+
)
|
|
393
|
+
|
|
394
|
+
if any(finding.severity == "BLOCK" for finding in findings):
|
|
395
|
+
result_status = "BLOCKED"
|
|
396
|
+
elif any(finding.severity == "WARN" for finding in findings):
|
|
397
|
+
result_status = "WARN"
|
|
398
|
+
else:
|
|
399
|
+
result_status = "PASS"
|
|
400
|
+
return InstructionLintResult(
|
|
401
|
+
result_status=result_status,
|
|
402
|
+
findings=tuple(findings),
|
|
403
|
+
checked_path=checked_path,
|
|
404
|
+
manifest_sha=manifest_sha,
|
|
405
|
+
)
|
|
406
|
+
|
|
407
|
+
|
|
408
|
+
def lint_instruction_file(
|
|
409
|
+
path: str | Path,
|
|
410
|
+
*,
|
|
411
|
+
root: Path = Path("."),
|
|
412
|
+
require_ack: bool = True,
|
|
413
|
+
strict_unknown: bool = False,
|
|
414
|
+
include_structured_commands: bool = False,
|
|
415
|
+
) -> InstructionLintResult:
|
|
416
|
+
payload_path = Path(path)
|
|
417
|
+
try:
|
|
418
|
+
text = payload_path.read_text(encoding="utf-8")
|
|
419
|
+
except Exception as exc:
|
|
420
|
+
return unreadable_instruction_result(
|
|
421
|
+
checked_path=str(payload_path),
|
|
422
|
+
message=str(exc),
|
|
423
|
+
)
|
|
424
|
+
return lint_instruction_text(
|
|
425
|
+
text,
|
|
426
|
+
manifest=load_manifest(root.resolve()),
|
|
427
|
+
checked_path=str(payload_path),
|
|
428
|
+
require_ack=require_ack,
|
|
429
|
+
strict_unknown=strict_unknown,
|
|
430
|
+
include_structured_commands=include_structured_commands,
|
|
431
|
+
)
|
|
432
|
+
|
|
433
|
+
|
|
434
|
+
def lint_transfer_instruction(path: str | Path, *, root: Path = Path(".")) -> InstructionLintResult:
|
|
435
|
+
return lint_instruction_file(path, root=root, include_structured_commands=True)
|
|
436
|
+
|
|
437
|
+
|
|
438
|
+
def render_instruction_lint_result(result: InstructionLintResult) -> str:
|
|
439
|
+
lines = [
|
|
440
|
+
"INSTRUCTION_LINT",
|
|
441
|
+
f"STATUS={result.result_status}",
|
|
442
|
+
f"MANIFEST_SHA={result.manifest_sha}",
|
|
443
|
+
f"CHECKED_PATH={result.checked_path}",
|
|
444
|
+
f"FINDING_COUNT={len(result.findings)}",
|
|
445
|
+
f"BLOCKER_COUNT={len(result.blockers)}",
|
|
446
|
+
f"WARNING_COUNT={len(result.warnings)}",
|
|
447
|
+
]
|
|
448
|
+
for finding in result.findings:
|
|
449
|
+
line = finding.line if finding.line > 0 else "structured"
|
|
450
|
+
lines.append(
|
|
451
|
+
f"FINDING={line}|{finding.severity}|{finding.rule}|{finding.message}"
|
|
452
|
+
)
|
|
453
|
+
if result.rejection_block():
|
|
454
|
+
lines.extend(["", "REJECTION_BLOCK", result.rejection_block()])
|
|
455
|
+
return "\n".join(lines) + "\n"
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from datetime import datetime, timezone
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
from agentic_project_kit.transfer_safety_context import build_local_to_llm_payload
|
|
9
|
+
from agentic_project_kit.transfer_safety_context import write_transfer_outbox
|
|
10
|
+
from agentic_project_kit.workspace import LEGACY_DEFAULTS, load_workspace
|
|
11
|
+
|
|
12
|
+
LATEST_HANDOFF_REPORT_NAME = "latest-transfer-handoff-report.json"
|
|
13
|
+
LATEST_HANDOFF_LOG_NAME = "latest-transfer-handoff-report.log"
|
|
14
|
+
LATEST_HANDOFF_REPORT = Path(LEGACY_DEFAULTS.transfer_handoff_reports_root) / LATEST_HANDOFF_REPORT_NAME
|
|
15
|
+
LATEST_HANDOFF_LOG = Path(LEGACY_DEFAULTS.transfer_handoff_reports_root) / LATEST_HANDOFF_LOG_NAME
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def refresh_llm_context_carriers(root: Path | str = ".", *, label: str = "llm-context-carriers-refresh") -> dict[str, Any]:
|
|
19
|
+
"""Refresh both canonical LLM-context carriers used by running and successor chats."""
|
|
20
|
+
root_path = Path(root)
|
|
21
|
+
last_result: dict[str, Any] = {
|
|
22
|
+
"schema_version": 1,
|
|
23
|
+
"kind": "llm_context_carriers_refresh",
|
|
24
|
+
"action": "refresh-llm-context-carriers",
|
|
25
|
+
"label": label,
|
|
26
|
+
"created_at_utc": datetime.now(timezone.utc).isoformat(),
|
|
27
|
+
"result_status": "PASS",
|
|
28
|
+
"final_signal": "d",
|
|
29
|
+
"next_action": "LLM context carriers refreshed.",
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
outbox_path = write_transfer_outbox(root_path, last_result)
|
|
33
|
+
payload = build_local_to_llm_payload(root_path, last_result)
|
|
34
|
+
workspace = load_workspace(root_path)
|
|
35
|
+
|
|
36
|
+
latest_path = workspace.transfer_handoff_report_file(LATEST_HANDOFF_REPORT_NAME)
|
|
37
|
+
latest_path.parent.mkdir(parents=True, exist_ok=True)
|
|
38
|
+
latest_path.write_text(json.dumps(payload, indent=2, sort_keys=True) + "\n", encoding="utf-8")
|
|
39
|
+
|
|
40
|
+
latest_log = workspace.transfer_handoff_report_file(LATEST_HANDOFF_LOG_NAME)
|
|
41
|
+
latest_log.parent.mkdir(parents=True, exist_ok=True)
|
|
42
|
+
latest_log.write_text(
|
|
43
|
+
"\n".join(
|
|
44
|
+
[
|
|
45
|
+
"TRANSFER_REFRESH_LLM_CONTEXT_CARRIERS",
|
|
46
|
+
"STATE=PASS",
|
|
47
|
+
f"OUTBOX={outbox_path}",
|
|
48
|
+
f"LATEST={latest_path}",
|
|
49
|
+
"CHAT_REPLY=d | NEXT=LLM context carriers refreshed.",
|
|
50
|
+
"",
|
|
51
|
+
]
|
|
52
|
+
),
|
|
53
|
+
encoding="utf-8",
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
return {
|
|
57
|
+
"schema_version": 1,
|
|
58
|
+
"kind": "llm_context_carriers_refresh_result",
|
|
59
|
+
"action": "refresh-llm-context-carriers",
|
|
60
|
+
"result_status": "PASS",
|
|
61
|
+
"final_signal": "d",
|
|
62
|
+
"next_action": "LLM context carriers refreshed.",
|
|
63
|
+
"outbox_path": str(outbox_path),
|
|
64
|
+
"latest_handoff_report_path": str(latest_path),
|
|
65
|
+
"latest_handoff_log_path": str(latest_log),
|
|
66
|
+
}
|