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,107 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
FIXED_SLOT_YAML = Path(".agentic/commands/inbox/next-turn.yaml")
|
|
8
|
+
FIXED_SLOT_SCRIPT = Path(".agentic/commands/inbox/next-turn.py")
|
|
9
|
+
|
|
10
|
+
@dataclass(frozen=True)
|
|
11
|
+
class FixedSlotStatus:
|
|
12
|
+
state: str
|
|
13
|
+
yaml_exists: bool
|
|
14
|
+
script_exists: bool
|
|
15
|
+
overwrite_allowed: bool
|
|
16
|
+
reason: str
|
|
17
|
+
yaml_path: str
|
|
18
|
+
script_path: str
|
|
19
|
+
|
|
20
|
+
def fixed_slot_status(root: Path | str = ".") -> FixedSlotStatus:
|
|
21
|
+
root_path = Path(root)
|
|
22
|
+
yaml_path = root_path / FIXED_SLOT_YAML
|
|
23
|
+
script_path = root_path / FIXED_SLOT_SCRIPT
|
|
24
|
+
yaml_exists = yaml_path.exists()
|
|
25
|
+
script_exists = script_path.exists()
|
|
26
|
+
if yaml_exists and script_exists:
|
|
27
|
+
return FixedSlotStatus("ready", True, True, False, "fixed-slot work order exists", str(FIXED_SLOT_YAML), str(FIXED_SLOT_SCRIPT))
|
|
28
|
+
if yaml_exists or script_exists:
|
|
29
|
+
return FixedSlotStatus("partial", yaml_exists, script_exists, False, "fixed-slot work order is incomplete; use --force only after inspection", str(FIXED_SLOT_YAML), str(FIXED_SLOT_SCRIPT))
|
|
30
|
+
return FixedSlotStatus("empty", False, False, True, "no fixed-slot work order exists", str(FIXED_SLOT_YAML), str(FIXED_SLOT_SCRIPT))
|
|
31
|
+
|
|
32
|
+
def render_status(status: FixedSlotStatus) -> str:
|
|
33
|
+
return "\n".join([
|
|
34
|
+
"NEXT_TURN_FIXED_SLOT_STATUS",
|
|
35
|
+
f"state={status.state}",
|
|
36
|
+
f"yaml_exists={str(status.yaml_exists).lower()}",
|
|
37
|
+
f"script_exists={str(status.script_exists).lower()}",
|
|
38
|
+
f"overwrite_allowed={str(status.overwrite_allowed).lower()}",
|
|
39
|
+
f"yaml_path={status.yaml_path}",
|
|
40
|
+
f"script_path={status.script_path}",
|
|
41
|
+
f"reason={status.reason}",
|
|
42
|
+
"### RESULT: PASS ###",
|
|
43
|
+
])
|
|
44
|
+
|
|
45
|
+
def default_yaml(command_id: str = "next-turn") -> str:
|
|
46
|
+
return "\n".join([
|
|
47
|
+
"schema_version: 1",
|
|
48
|
+
f"command_id: {command_id}",
|
|
49
|
+
"kind: fixed-slot-work-order",
|
|
50
|
+
"slot: next-turn",
|
|
51
|
+
"entrypoint: .agentic/commands/inbox/next-turn.py",
|
|
52
|
+
"safety_class: bounded",
|
|
53
|
+
"status: pending",
|
|
54
|
+
"evidence:",
|
|
55
|
+
" local_terminal_log: ${AGENTIC_KIT_NEXT_TURN_LOG:-tmp/next-turn-latest.log}",
|
|
56
|
+
" remote_terminal_log: docs/reports/terminal/next-turn-latest.log",
|
|
57
|
+
" command_report: docs/reports/command_runs/next-turn-latest.json",
|
|
58
|
+
"",
|
|
59
|
+
])
|
|
60
|
+
|
|
61
|
+
def default_script() -> str:
|
|
62
|
+
return "\n".join([
|
|
63
|
+
"#!/usr/bin/env python3",
|
|
64
|
+
"from __future__ import annotations",
|
|
65
|
+
"",
|
|
66
|
+
"def main() -> int:",
|
|
67
|
+
" print(\"NEXT_TURN_FIXED_SLOT_PLACEHOLDER\")",
|
|
68
|
+
" print(\"status=pending_runner\")",
|
|
69
|
+
" print(\"### RESULT: PASS ###\")",
|
|
70
|
+
" return 0",
|
|
71
|
+
"",
|
|
72
|
+
"if __name__ == \"__main__\":",
|
|
73
|
+
" raise SystemExit(main())",
|
|
74
|
+
"",
|
|
75
|
+
])
|
|
76
|
+
|
|
77
|
+
def write_fixed_slot(root: Path | str = ".", *, force: bool = False, command_id: str = "next-turn") -> FixedSlotStatus:
|
|
78
|
+
root_path = Path(root)
|
|
79
|
+
status = fixed_slot_status(root_path)
|
|
80
|
+
if status.state != "empty" and not force:
|
|
81
|
+
raise RuntimeError(f"fixed slot is not empty: {status.state}")
|
|
82
|
+
yaml_path = root_path / FIXED_SLOT_YAML
|
|
83
|
+
script_path = root_path / FIXED_SLOT_SCRIPT
|
|
84
|
+
yaml_path.parent.mkdir(parents=True, exist_ok=True)
|
|
85
|
+
yaml_path.write_text(default_yaml(command_id), encoding="utf-8")
|
|
86
|
+
script_path.write_text(default_script(), encoding="utf-8")
|
|
87
|
+
script_path.chmod(0o755)
|
|
88
|
+
return fixed_slot_status(root_path)
|
|
89
|
+
|
|
90
|
+
def main() -> int:
|
|
91
|
+
parser = argparse.ArgumentParser(prog="next-turn-slot")
|
|
92
|
+
sub = parser.add_subparsers(dest="command", required=True)
|
|
93
|
+
sub.add_parser("status")
|
|
94
|
+
write_parser = sub.add_parser("write")
|
|
95
|
+
write_parser.add_argument("--force", action="store_true")
|
|
96
|
+
write_parser.add_argument("--command-id", default="next-turn")
|
|
97
|
+
args = parser.parse_args()
|
|
98
|
+
if args.command == "status":
|
|
99
|
+
print(render_status(fixed_slot_status()))
|
|
100
|
+
return 0
|
|
101
|
+
if args.command == "write":
|
|
102
|
+
print(render_status(write_fixed_slot(force=args.force, command_id=args.command_id)))
|
|
103
|
+
return 0
|
|
104
|
+
raise AssertionError(args.command)
|
|
105
|
+
|
|
106
|
+
if __name__ == "__main__":
|
|
107
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
import json
|
|
5
|
+
import re
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Iterable
|
|
9
|
+
|
|
10
|
+
from agentic_project_kit.evidence_inspector import EvidenceVerdict
|
|
11
|
+
from agentic_project_kit.evidence_inspector import inspect_evidence
|
|
12
|
+
from agentic_project_kit.next_turn_slot import FIXED_SLOT_SCRIPT
|
|
13
|
+
from agentic_project_kit.next_turn_slot import FIXED_SLOT_YAML
|
|
14
|
+
|
|
15
|
+
VALID_STATES = {"empty", "prepared", "running", "completed", "failed", "blocked", "recovery_needed"}
|
|
16
|
+
|
|
17
|
+
EVIDENCE_LOOKUP_ORDER = (
|
|
18
|
+
"docs/reports/command_runs/next-turn-latest.json",
|
|
19
|
+
"docs/reports/command_runs/next-turn-latest.md",
|
|
20
|
+
"docs/reports/terminal/next-turn-latest.log",
|
|
21
|
+
".agentic/commands/executed.jsonl",
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
@dataclass(frozen=True)
|
|
25
|
+
class NextTurnStatus:
|
|
26
|
+
state: str
|
|
27
|
+
yaml_exists: bool
|
|
28
|
+
script_exists: bool
|
|
29
|
+
overwrite_allowed: bool
|
|
30
|
+
reason: str
|
|
31
|
+
|
|
32
|
+
def _extract_state(yaml_text: str) -> str | None:
|
|
33
|
+
match = re.search(r"(?m)^state:\s*([A-Za-z0-9_-]+)\s*$", yaml_text)
|
|
34
|
+
return match.group(1) if match else None
|
|
35
|
+
|
|
36
|
+
def detect_next_turn_status(root: Path | str = ".") -> NextTurnStatus:
|
|
37
|
+
root_path = Path(root)
|
|
38
|
+
yaml_path = root_path / FIXED_SLOT_YAML
|
|
39
|
+
script_path = root_path / FIXED_SLOT_SCRIPT
|
|
40
|
+
yaml_exists = yaml_path.exists()
|
|
41
|
+
script_exists = script_path.exists()
|
|
42
|
+
if not yaml_exists and not script_exists:
|
|
43
|
+
return NextTurnStatus("empty", False, False, True, "no fixed-slot files exist")
|
|
44
|
+
if yaml_exists != script_exists:
|
|
45
|
+
return NextTurnStatus("blocked", yaml_exists, script_exists, False, "partial fixed-slot state")
|
|
46
|
+
try:
|
|
47
|
+
declared = _extract_state(yaml_path.read_text(encoding="utf-8"))
|
|
48
|
+
except UnicodeDecodeError:
|
|
49
|
+
return NextTurnStatus("blocked", True, True, False, "next-turn yaml is not valid utf-8")
|
|
50
|
+
if declared is None:
|
|
51
|
+
return NextTurnStatus("blocked", True, True, False, "next-turn yaml has no state field")
|
|
52
|
+
if declared not in VALID_STATES:
|
|
53
|
+
return NextTurnStatus("blocked", True, True, False, f"unknown state: {declared}")
|
|
54
|
+
overwrite_allowed = declared in {"completed", "failed"}
|
|
55
|
+
reason = "completed or failed slot may be replaced" if overwrite_allowed else f"{declared} slot must not be overwritten"
|
|
56
|
+
return NextTurnStatus(declared, True, True, overwrite_allowed, reason)
|
|
57
|
+
|
|
58
|
+
def _bool(value: bool) -> str:
|
|
59
|
+
return "true" if value else "false"
|
|
60
|
+
|
|
61
|
+
def render_status(status: NextTurnStatus) -> str:
|
|
62
|
+
lines = [
|
|
63
|
+
"NEXT_TURN_STATUS",
|
|
64
|
+
f"state={status.state}",
|
|
65
|
+
f"yaml_exists={_bool(status.yaml_exists)}",
|
|
66
|
+
f"script_exists={_bool(status.script_exists)}",
|
|
67
|
+
f"overwrite_allowed={_bool(status.overwrite_allowed)}",
|
|
68
|
+
f"reason={status.reason}",
|
|
69
|
+
"evidence_lookup_order:",
|
|
70
|
+
]
|
|
71
|
+
lines.extend(f"- {path}" for path in EVIDENCE_LOOKUP_ORDER)
|
|
72
|
+
lines.append("### RESULT: PASS ###")
|
|
73
|
+
return "\n".join(lines)
|
|
74
|
+
|
|
75
|
+
def _read_preview(path: Path) -> str:
|
|
76
|
+
text = path.read_text(encoding="utf-8", errors="replace")
|
|
77
|
+
if path.suffix == ".json":
|
|
78
|
+
try:
|
|
79
|
+
return json.dumps(json.loads(text), indent=2, sort_keys=True)
|
|
80
|
+
except json.JSONDecodeError:
|
|
81
|
+
return text[:4000]
|
|
82
|
+
if path.name == "executed.jsonl":
|
|
83
|
+
lines = [line for line in text.splitlines() if line.strip()]
|
|
84
|
+
return lines[-1] if lines else ""
|
|
85
|
+
return text[:4000]
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def _classify_executed_jsonl(preview: str) -> tuple[str, str, str, str]:
|
|
89
|
+
if not preview:
|
|
90
|
+
return (
|
|
91
|
+
"FOUND_UNUSABLE",
|
|
92
|
+
"MISSING_EVIDENCE_UPLOAD_FIRST",
|
|
93
|
+
"paste-output",
|
|
94
|
+
"executed ledger exists but contains no usable entry; inspect terminal evidence before requesting pasted output",
|
|
95
|
+
)
|
|
96
|
+
try:
|
|
97
|
+
payload = json.loads(preview)
|
|
98
|
+
except json.JSONDecodeError:
|
|
99
|
+
return (
|
|
100
|
+
"FOUND_UNUSABLE",
|
|
101
|
+
"AMBIGUOUS_SUMMARY_REVIEW_REQUIRED",
|
|
102
|
+
"paste-output",
|
|
103
|
+
"executed ledger latest entry is unreadable; inspect terminal evidence before requesting pasted output",
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
outcome = str(payload.get("outcome") or "")
|
|
107
|
+
exit_code = payload.get("exit_code")
|
|
108
|
+
|
|
109
|
+
if outcome == "PASS_EXECUTED" or exit_code == 0:
|
|
110
|
+
return (
|
|
111
|
+
"FOUND_PASS",
|
|
112
|
+
"PASS_CONTINUE",
|
|
113
|
+
"d",
|
|
114
|
+
"continue from executed command ledger",
|
|
115
|
+
)
|
|
116
|
+
if outcome.startswith("FAIL") or isinstance(exit_code, int) and exit_code != 0:
|
|
117
|
+
return (
|
|
118
|
+
"FOUND_FAIL",
|
|
119
|
+
"FAIL_DIAGNOSE",
|
|
120
|
+
"f",
|
|
121
|
+
"diagnose from executed command ledger and inspect terminal evidence if needed",
|
|
122
|
+
)
|
|
123
|
+
return (
|
|
124
|
+
"FOUND_AMBIGUOUS",
|
|
125
|
+
"AMBIGUOUS_SUMMARY_REVIEW_REQUIRED",
|
|
126
|
+
"paste-output",
|
|
127
|
+
"executed ledger latest entry is ambiguous; inspect terminal evidence before requesting pasted output",
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
@dataclass(frozen=True)
|
|
131
|
+
class LastResult:
|
|
132
|
+
path: Path | None
|
|
133
|
+
checked_paths: tuple[str, ...]
|
|
134
|
+
preview: str
|
|
135
|
+
status: str
|
|
136
|
+
evidence_verdict: str
|
|
137
|
+
recommended_chat_reply: str
|
|
138
|
+
recovery: str
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def _classify_json_result(path: Path, preview: str) -> tuple[str, str, str, str]:
|
|
142
|
+
try:
|
|
143
|
+
payload = json.loads(path.read_text(encoding="utf-8"))
|
|
144
|
+
except json.JSONDecodeError:
|
|
145
|
+
return (
|
|
146
|
+
"FOUND_UNUSABLE",
|
|
147
|
+
"AMBIGUOUS_SUMMARY_REVIEW_REQUIRED",
|
|
148
|
+
"paste-output",
|
|
149
|
+
"JSON result is unreadable; inspect terminal log before requesting pasted output.",
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
overall = str(payload.get("overall_result") or payload.get("work_result") or payload.get("outcome") or "")
|
|
153
|
+
remote = str(payload.get("remote_evidence") or "")
|
|
154
|
+
reply = str(payload.get("next_chat_reply") or "")
|
|
155
|
+
|
|
156
|
+
if reply:
|
|
157
|
+
recommended = reply
|
|
158
|
+
elif overall in {"PASS", "PASS_ALREADY_DONE", "NOOP", "PASS_EXECUTED"}:
|
|
159
|
+
recommended = "d"
|
|
160
|
+
elif remote in {"PASS", "PARTIAL"}:
|
|
161
|
+
recommended = "f"
|
|
162
|
+
else:
|
|
163
|
+
recommended = "paste-output"
|
|
164
|
+
|
|
165
|
+
if overall in {"PASS", "PASS_ALREADY_DONE", "NOOP", "PASS_EXECUTED"}:
|
|
166
|
+
verdict = "PASS_CONTINUE"
|
|
167
|
+
status = "FOUND_PASS"
|
|
168
|
+
recovery = "continue from structured command result"
|
|
169
|
+
elif overall in {"FAIL", "HARD_FAIL", "FAIL_EXECUTED", "FAIL_NO_FIXED_SLOT"}:
|
|
170
|
+
verdict = "FAIL_DIAGNOSE"
|
|
171
|
+
status = "FOUND_FAIL"
|
|
172
|
+
recovery = "diagnose from structured command result and then inspect terminal evidence if needed"
|
|
173
|
+
else:
|
|
174
|
+
verdict = "AMBIGUOUS_SUMMARY_REVIEW_REQUIRED"
|
|
175
|
+
status = "FOUND_AMBIGUOUS"
|
|
176
|
+
recovery = "inspect terminal evidence before requesting pasted output"
|
|
177
|
+
|
|
178
|
+
if recommended == "paste-output" and "docs/reports/terminal/next-turn-latest.log" in preview:
|
|
179
|
+
recovery = "inspect the referenced terminal log before requesting pasted output"
|
|
180
|
+
|
|
181
|
+
return status, verdict, recommended, recovery
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def _classify_terminal_result(path: Path, root: Path) -> tuple[str, str, str, str]:
|
|
185
|
+
inspection = inspect_evidence(path, root=root, require_summary=False)
|
|
186
|
+
verdict = inspection.verdict.value
|
|
187
|
+
if inspection.verdict == EvidenceVerdict.PASS_CONTINUE:
|
|
188
|
+
return "FOUND_PASS", verdict, "d", "continue from inspected terminal evidence"
|
|
189
|
+
if inspection.verdict == EvidenceVerdict.FAIL_DIAGNOSE:
|
|
190
|
+
return "FOUND_FAIL", verdict, "f", "diagnose from inspected terminal evidence"
|
|
191
|
+
if inspection.verdict == EvidenceVerdict.MISSING_EVIDENCE_UPLOAD_FIRST:
|
|
192
|
+
return "FOUND_UNUSABLE", verdict, "paste-output", "upload or locate evidence before requesting pasted output"
|
|
193
|
+
return "FOUND_AMBIGUOUS", verdict, "paste-output", "review ambiguous terminal evidence before requesting pasted output"
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
def find_last_result(root: Path | str = ".") -> LastResult:
|
|
197
|
+
root_path = Path(root)
|
|
198
|
+
checked: list[str] = []
|
|
199
|
+
for relative in EVIDENCE_LOOKUP_ORDER:
|
|
200
|
+
checked.append(relative)
|
|
201
|
+
candidate = root_path / relative
|
|
202
|
+
if not candidate.exists():
|
|
203
|
+
continue
|
|
204
|
+
preview = _read_preview(candidate)
|
|
205
|
+
if candidate.suffix == ".json":
|
|
206
|
+
status, verdict, reply, recovery = _classify_json_result(candidate, preview)
|
|
207
|
+
elif candidate.name == "executed.jsonl":
|
|
208
|
+
status, verdict, reply, recovery = _classify_executed_jsonl(preview)
|
|
209
|
+
elif candidate.suffix in {".log", ".md"}:
|
|
210
|
+
status, verdict, reply, recovery = _classify_terminal_result(candidate, root_path)
|
|
211
|
+
else:
|
|
212
|
+
status, verdict, reply, recovery = (
|
|
213
|
+
"FOUND_AMBIGUOUS",
|
|
214
|
+
"AMBIGUOUS_SUMMARY_REVIEW_REQUIRED",
|
|
215
|
+
"paste-output",
|
|
216
|
+
"review result source before requesting pasted output",
|
|
217
|
+
)
|
|
218
|
+
return LastResult(candidate, tuple(checked), preview, status, verdict, reply, recovery)
|
|
219
|
+
return LastResult(
|
|
220
|
+
None,
|
|
221
|
+
tuple(checked),
|
|
222
|
+
"",
|
|
223
|
+
"NO_RESULT_FOUND",
|
|
224
|
+
"MISSING_EVIDENCE_UPLOAD_FIRST",
|
|
225
|
+
"paste-output",
|
|
226
|
+
"run ./ns next-turn --status before requesting pasted terminal output",
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
def render_last_result(root: Path | str = ".") -> str:
|
|
231
|
+
result = find_last_result(root)
|
|
232
|
+
lines: list[str] = ["NEXT_TURN_LAST_RESULT"]
|
|
233
|
+
if result.path is None:
|
|
234
|
+
lines.append(f"status={result.status}")
|
|
235
|
+
lines.append(f"evidence_verdict={result.evidence_verdict}")
|
|
236
|
+
lines.append(f"recommended_chat_reply={result.recommended_chat_reply}")
|
|
237
|
+
lines.append("checked_paths:")
|
|
238
|
+
lines.extend(f"- {path}" for path in result.checked_paths)
|
|
239
|
+
lines.append(f"recovery={result.recovery}")
|
|
240
|
+
lines.append("### RESULT: PASS ###")
|
|
241
|
+
return "\n".join(lines)
|
|
242
|
+
try:
|
|
243
|
+
relative = result.path.relative_to(Path(root))
|
|
244
|
+
except ValueError:
|
|
245
|
+
relative = result.path
|
|
246
|
+
lines.append(f"status={result.status}")
|
|
247
|
+
lines.append(f"path={relative}")
|
|
248
|
+
lines.append(f"evidence_verdict={result.evidence_verdict}")
|
|
249
|
+
lines.append(f"recommended_chat_reply={result.recommended_chat_reply}")
|
|
250
|
+
lines.append(f"recovery={result.recovery}")
|
|
251
|
+
lines.append("checked_paths:")
|
|
252
|
+
lines.extend(f"- {path}" for path in result.checked_paths)
|
|
253
|
+
lines.append("preview:")
|
|
254
|
+
lines.append(result.preview)
|
|
255
|
+
lines.append("### RESULT: PASS ###")
|
|
256
|
+
return "\n".join(lines)
|
|
257
|
+
|
|
258
|
+
def main(argv: Iterable[str] | None = None) -> int:
|
|
259
|
+
parser = argparse.ArgumentParser(prog="next-turn-status")
|
|
260
|
+
subparsers = parser.add_subparsers(dest="command", required=True)
|
|
261
|
+
next_turn = subparsers.add_parser("next-turn")
|
|
262
|
+
next_turn.add_argument("--status", action="store_true")
|
|
263
|
+
subparsers.add_parser("last-result")
|
|
264
|
+
args = parser.parse_args(list(argv) if argv is not None else None)
|
|
265
|
+
if args.command == "next-turn":
|
|
266
|
+
print(render_status(detect_next_turn_status()))
|
|
267
|
+
return 0
|
|
268
|
+
if args.command == "last-result":
|
|
269
|
+
print(render_last_result())
|
|
270
|
+
return 0
|
|
271
|
+
raise AssertionError(args.command)
|
|
272
|
+
|
|
273
|
+
if __name__ == "__main__":
|
|
274
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
import re
|
|
6
|
+
from typing import Iterable
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
LEGACY_PATTERNS: tuple[re.Pattern[str], ...] = (
|
|
10
|
+
re.compile(r"\./ns\b"),
|
|
11
|
+
re.compile(r"\bns-menu\b"),
|
|
12
|
+
re.compile(r"\bns_release_[A-Za-z0-9_]*"),
|
|
13
|
+
re.compile(r"\btools/ns_[A-Za-z0-9_./-]*"),
|
|
14
|
+
re.compile(r"\bNS RELEASE\b"),
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
TEXT_SUFFIXES = {
|
|
18
|
+
".md",
|
|
19
|
+
".rst",
|
|
20
|
+
".txt",
|
|
21
|
+
".py",
|
|
22
|
+
".toml",
|
|
23
|
+
".yaml",
|
|
24
|
+
".yml",
|
|
25
|
+
".json",
|
|
26
|
+
".cff",
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
SKIP_DIR_PARTS = {
|
|
30
|
+
".git",
|
|
31
|
+
".venv",
|
|
32
|
+
"__pycache__",
|
|
33
|
+
".pytest_cache",
|
|
34
|
+
".ruff_cache",
|
|
35
|
+
"node_modules",
|
|
36
|
+
"tmp",
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
SKIP_PATH_PREFIXES = (
|
|
40
|
+
"docs/reports/",
|
|
41
|
+
".agentic/transfer/outbox/",
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
CURRENT_INSTRUCTION_PATTERNS: tuple[re.Pattern[str], ...] = (
|
|
45
|
+
re.compile(r"\b(run|execute|use|start|call|invoke|rerun|copy|paste|command|befehl|ausführen|starte)\b", re.I),
|
|
46
|
+
re.compile(r"^\s*(?:[-*]\s*)?(?:cd\s+\S+\s*&&\s*)?\./ns\b", re.I),
|
|
47
|
+
re.compile(r"^\s*(?:[-*]\s*)?tools/ns_", re.I),
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
HISTORICAL_MARKERS: tuple[re.Pattern[str], ...] = (
|
|
51
|
+
re.compile(r"\b(historical|legacy|obsolete|removed|deprecated|archiv|archived|historisch|veraltet|entfernt)\b", re.I),
|
|
52
|
+
re.compile(r"\b(before|formerly|previously|früher|ehemalig)\b", re.I),
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
SAFE_CONTEXT_PATH_PARTS = (
|
|
56
|
+
"tests",
|
|
57
|
+
"docs/reports",
|
|
58
|
+
"docs/releases",
|
|
59
|
+
"docs/archive",
|
|
60
|
+
"CHANGELOG.md",
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
@dataclass(frozen=True)
|
|
65
|
+
class LegacyReference:
|
|
66
|
+
path: str
|
|
67
|
+
line: int
|
|
68
|
+
text: str
|
|
69
|
+
classification: str
|
|
70
|
+
reason: str
|
|
71
|
+
|
|
72
|
+
def as_dict(self) -> dict[str, object]:
|
|
73
|
+
return {
|
|
74
|
+
"path": self.path,
|
|
75
|
+
"line": self.line,
|
|
76
|
+
"text": self.text,
|
|
77
|
+
"classification": self.classification,
|
|
78
|
+
"reason": self.reason,
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
@dataclass(frozen=True)
|
|
83
|
+
class NsLegacyReferenceAuditResult:
|
|
84
|
+
status: str
|
|
85
|
+
returncode: int
|
|
86
|
+
references: tuple[LegacyReference, ...]
|
|
87
|
+
blockers: tuple[LegacyReference, ...]
|
|
88
|
+
|
|
89
|
+
@property
|
|
90
|
+
def ok(self) -> bool:
|
|
91
|
+
return self.returncode == 0
|
|
92
|
+
|
|
93
|
+
def as_dict(self) -> dict[str, object]:
|
|
94
|
+
return {
|
|
95
|
+
"status": self.status,
|
|
96
|
+
"returncode": self.returncode,
|
|
97
|
+
"references": [reference.as_dict() for reference in self.references],
|
|
98
|
+
"blockers": [reference.as_dict() for reference in self.blockers],
|
|
99
|
+
"ok": self.ok,
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def _is_text_file(path: Path) -> bool:
|
|
104
|
+
if path.suffix in TEXT_SUFFIXES:
|
|
105
|
+
return True
|
|
106
|
+
if path.name in {"CHANGELOG", "README", "LICENSE"}:
|
|
107
|
+
return True
|
|
108
|
+
return False
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def iter_candidate_files(project_root: Path) -> Iterable[Path]:
|
|
112
|
+
for path in sorted(project_root.rglob("*")):
|
|
113
|
+
if not path.is_file():
|
|
114
|
+
continue
|
|
115
|
+
relative = path.relative_to(project_root).as_posix()
|
|
116
|
+
relative_parts = path.relative_to(project_root).parts
|
|
117
|
+
if any(part in SKIP_DIR_PARTS for part in relative_parts):
|
|
118
|
+
continue
|
|
119
|
+
if any(relative.startswith(prefix) for prefix in SKIP_PATH_PREFIXES):
|
|
120
|
+
continue
|
|
121
|
+
if not _is_text_file(path):
|
|
122
|
+
continue
|
|
123
|
+
yield path
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def _has_legacy_reference(line: str) -> bool:
|
|
127
|
+
return any(pattern.search(line) for pattern in LEGACY_PATTERNS)
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def _safe_context_by_path(relative: str) -> bool:
|
|
131
|
+
return relative.startswith(SAFE_CONTEXT_PATH_PARTS) or any(
|
|
132
|
+
part and relative.startswith(part + "/") for part in SAFE_CONTEXT_PATH_PARTS
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def _legacy_documentation_context(relative_path: str) -> bool:
|
|
137
|
+
return (
|
|
138
|
+
relative_path.startswith("docs/workflow/")
|
|
139
|
+
or relative_path.startswith("docs/governance/")
|
|
140
|
+
or relative_path.startswith("docs/strategy/")
|
|
141
|
+
or relative_path.startswith("docs/architecture/")
|
|
142
|
+
or relative_path.startswith("docs/planning/")
|
|
143
|
+
or relative_path == "docs/DOCUMENTATION_COVERAGE.yaml"
|
|
144
|
+
or relative_path == "docs/TEST_GATES.md"
|
|
145
|
+
or relative_path == "docs/WORKFLOW_OUTPUT_CYCLE.md"
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def _compatibility_implementation_context(relative_path: str) -> bool:
|
|
150
|
+
return relative_path in {
|
|
151
|
+
"src/agentic_project_kit/ns_legacy_reference_audit.py",
|
|
152
|
+
"src/agentic_project_kit/planning_docs_consolidation_audit.py",
|
|
153
|
+
"src/agentic_project_kit/release_gate_core.py",
|
|
154
|
+
"src/agentic_project_kit/release_verify_core.py",
|
|
155
|
+
"src/agentic_project_kit/removed_ns_commands.py",
|
|
156
|
+
"src/agentic_project_kit/work_order_validator.py",
|
|
157
|
+
"src/agentic_project_kit/gui_dry_run.py",
|
|
158
|
+
"src/agentic_project_kit/chat_bootloader.py",
|
|
159
|
+
"src/agentic_project_kit/doc_currency_audit.py",
|
|
160
|
+
"src/agentic_project_kit/entrypoint_slice_runner.py",
|
|
161
|
+
"src/agentic_project_kit/handoff_prompt.py",
|
|
162
|
+
"src/agentic_project_kit/release_prepare.py",
|
|
163
|
+
"src/agentic_project_kit/run_summary_renderer.py",
|
|
164
|
+
"src/agentic_project_kit/terminal_block_guard.py",
|
|
165
|
+
"tools/NEXT_TRANSFER_TASK.py",
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def classify_legacy_reference(relative_path: str, line: str) -> tuple[str, str]:
|
|
170
|
+
stripped = line.strip()
|
|
171
|
+
if _compatibility_implementation_context(relative_path):
|
|
172
|
+
return "compatibility_implementation", "legacy tokens are implementation/classifier compatibility patterns"
|
|
173
|
+
if relative_path.startswith("tests/"):
|
|
174
|
+
return "test_fixture", "test fixture/reference"
|
|
175
|
+
if _legacy_documentation_context(relative_path):
|
|
176
|
+
return "legacy_documentation_context", (
|
|
177
|
+
"planning/workflow/governance documentation; use follow-up documentation cleanup "
|
|
178
|
+
"rather than treating as current supported command surface"
|
|
179
|
+
)
|
|
180
|
+
if any(pattern.search(stripped) for pattern in HISTORICAL_MARKERS):
|
|
181
|
+
return "historical", "line contains explicit historical/legacy marker"
|
|
182
|
+
if relative_path.startswith("docs/reports/"):
|
|
183
|
+
return "generated_report_or_evidence", "report/evidence context"
|
|
184
|
+
if relative_path.startswith("docs/releases/") or relative_path == "CHANGELOG.md":
|
|
185
|
+
return "release_history", "release history context"
|
|
186
|
+
if _safe_context_by_path(relative_path) and not any(
|
|
187
|
+
pattern.search(stripped) for pattern in CURRENT_INSTRUCTION_PATTERNS
|
|
188
|
+
):
|
|
189
|
+
return "historical", "safe documentation context without imperative wording"
|
|
190
|
+
if any(pattern.search(stripped) for pattern in CURRENT_INSTRUCTION_PATTERNS):
|
|
191
|
+
return "current_instruction_blocker", "looks like a current instruction or executable route"
|
|
192
|
+
return "needs_review", "legacy reference is not explicitly historical or a test fixture"
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def audit_ns_legacy_references(project_root: Path | str = ".") -> NsLegacyReferenceAuditResult:
|
|
196
|
+
root = Path(project_root).resolve()
|
|
197
|
+
references: list[LegacyReference] = []
|
|
198
|
+
|
|
199
|
+
for path in iter_candidate_files(root):
|
|
200
|
+
relative = path.relative_to(root).as_posix()
|
|
201
|
+
try:
|
|
202
|
+
lines = path.read_text(encoding="utf-8", errors="replace").splitlines()
|
|
203
|
+
except OSError:
|
|
204
|
+
continue
|
|
205
|
+
for index, line in enumerate(lines, start=1):
|
|
206
|
+
if not _has_legacy_reference(line):
|
|
207
|
+
continue
|
|
208
|
+
classification, reason = classify_legacy_reference(relative, line)
|
|
209
|
+
references.append(
|
|
210
|
+
LegacyReference(
|
|
211
|
+
path=relative,
|
|
212
|
+
line=index,
|
|
213
|
+
text=line.strip(),
|
|
214
|
+
classification=classification,
|
|
215
|
+
reason=reason,
|
|
216
|
+
)
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
blockers = tuple(
|
|
220
|
+
reference
|
|
221
|
+
for reference in references
|
|
222
|
+
if reference.classification in {"current_instruction_blocker", "needs_review"}
|
|
223
|
+
)
|
|
224
|
+
return NsLegacyReferenceAuditResult(
|
|
225
|
+
status="PASS" if not blockers else "BLOCK",
|
|
226
|
+
returncode=0 if not blockers else 1,
|
|
227
|
+
references=tuple(references),
|
|
228
|
+
blockers=blockers,
|
|
229
|
+
)
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
def render_ns_legacy_reference_audit(result: NsLegacyReferenceAuditResult) -> str:
|
|
233
|
+
lines = [
|
|
234
|
+
"NS_LEGACY_REFERENCE_AUDIT",
|
|
235
|
+
f"STATUS={result.status}",
|
|
236
|
+
f"RETURNCODE={result.returncode}",
|
|
237
|
+
f"REFERENCE_COUNT={len(result.references)}",
|
|
238
|
+
f"BLOCKER_COUNT={len(result.blockers)}",
|
|
239
|
+
]
|
|
240
|
+
for reference in result.references:
|
|
241
|
+
lines.append(
|
|
242
|
+
"REFERENCE="
|
|
243
|
+
f"{reference.classification}|{reference.path}:{reference.line}|{reference.text}"
|
|
244
|
+
)
|
|
245
|
+
for blocker in result.blockers:
|
|
246
|
+
lines.append(f"BLOCKER={blocker.path}:{blocker.line}|{blocker.reason}|{blocker.text}")
|
|
247
|
+
lines.append("FINAL_SIGNAL=d" if result.ok else "FINAL_SIGNAL=f")
|
|
248
|
+
return "\n".join(lines) + "\n"
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
def main() -> int:
|
|
252
|
+
result = audit_ns_legacy_references(Path("."))
|
|
253
|
+
print(render_ns_legacy_reference_audit(result), end="")
|
|
254
|
+
return result.returncode
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
if __name__ == "__main__":
|
|
258
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"""Backward-compatible import shim for the neutral entrypoint slice runner.
|
|
2
|
+
|
|
3
|
+
The supported implementation lives in
|
|
4
|
+
:mod:`agentic_project_kit.entrypoint_slice_runner`.
|
|
5
|
+
|
|
6
|
+
This shim remains temporarily to keep historical imports stable while active
|
|
7
|
+
runtime paths move away from the ns-named module.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
from agentic_project_kit.entrypoint_slice_runner import * # noqa: F403
|
|
13
|
+
from agentic_project_kit.entrypoint_slice_runner import main
|
|
14
|
+
|
|
15
|
+
if __name__ == "__main__":
|
|
16
|
+
raise SystemExit(main())
|