From e74bc614284a159af28d306116df7b0400ba24d1 Mon Sep 17 00:00:00 2001 From: Diego Mauricio Lagos Date: Mon, 15 Jun 2026 09:54:11 +0200 Subject: [PATCH 1/2] feat: Add human-friendly lane labels for install and bisync reports --- .../SKILL.md | 1 + .../references/sync-contract.md | 1 + .../scripts/sync_output.py | 16 ++++++++++++++-- tests/test_home_ai_resources_sync.py | 2 ++ 4 files changed, 18 insertions(+), 2 deletions(-) diff --git a/.github/skills/local-agent-sync-install-ai-resources/SKILL.md b/.github/skills/local-agent-sync-install-ai-resources/SKILL.md index 7149833..482da2b 100644 --- a/.github/skills/local-agent-sync-install-ai-resources/SKILL.md +++ b/.github/skills/local-agent-sync-install-ai-resources/SKILL.md @@ -128,6 +128,7 @@ Then follow the exact text layout in `references/sync-contract.md`: - `doctor`: readiness summary plus a blocker table that answers what failed, why it matters, and what the user must do next. - `plan`, `audit`, and `bisync plan`: a planned-changes table plus a blockers-and-skips table. For every proposed modification, explain the decision cause, for example repo copy is newer, home copy is newer, a managed resource is stale, or runtime support is not documented enough for apply. - `apply` and `bisync apply`: an actions-performed table plus a residual-issues table when needed. List each copied, updated, pruned, preserved, skipped, or unchanged resource and state why it was handled that way and how it was verified. +- Include a human-friendly lane label in the status line for install and bisync reports, such as `repo-to-home install` and `repo-home drift`, so the mode is easier to read at a glance. Never report blocker codes alone. Translate each code into a plain-language reason and the required follow-up. Never say a resource will change without stating what evidence selected the winner or triggered the recommendation. When nothing changes, say so explicitly and still report validation and `next_action`. diff --git a/.github/skills/local-agent-sync-install-ai-resources/references/sync-contract.md b/.github/skills/local-agent-sync-install-ai-resources/references/sync-contract.md index ef744a6..d07f27c 100644 --- a/.github/skills/local-agent-sync-install-ai-resources/references/sync-contract.md +++ b/.github/skills/local-agent-sync-install-ai-resources/references/sync-contract.md @@ -88,6 +88,7 @@ Text reports must use a table-first layout rather than a raw field dump. Always start with a short status line that includes: - mode +- human-friendly lane label, such as `repo-to-home install` for install or `repo-home drift` for bisync - selected targets - overall result or status - blocker count diff --git a/.github/skills/local-agent-sync-install-ai-resources/scripts/sync_output.py b/.github/skills/local-agent-sync-install-ai-resources/scripts/sync_output.py index 0972a0f..c97fda6 100644 --- a/.github/skills/local-agent-sync-install-ai-resources/scripts/sync_output.py +++ b/.github/skills/local-agent-sync-install-ai-resources/scripts/sync_output.py @@ -36,6 +36,14 @@ } +def _install_lane_label() -> str: + return "repo-to-home install" + + +def _bisync_lane_label() -> str: + return "repo-home drift" + + def build_compact_install_output(payload: dict[str, object]) -> dict[str, object]: compact: dict[str, object] = { "mode": payload.get("mode"), @@ -214,10 +222,11 @@ def render_install_report(payload: dict[str, object]) -> str: targets = _as_string_list(payload.get("selected_targets")) blocked_codes = _as_string_list(payload.get("blocked_codes")) status = str(payload.get("validation") or payload.get("status") or "unknown") + lane_label = _install_lane_label() lines: list[str] = [] lines.append( - f"Status: mode={mode}; targets={_join_or_none(targets)}; status={status}; blockers={len(blocked_codes)}" + f"Status: mode={mode}; label={lane_label}; targets={_join_or_none(targets)}; status={status}; blockers={len(blocked_codes)}" ) lines.append("") @@ -227,6 +236,7 @@ def render_install_report(payload: dict[str, object]) -> str: ["Field", "Value"], [ ["Mode", mode], + ["Lane label", lane_label], ["Selected targets", _join_or_none(targets)], ["Retired targets", _join_or_none(_as_string_list(payload.get("retired_targets")))], ["Source resources considered", str(payload.get("source_resources_considered") or 0)], @@ -305,10 +315,11 @@ def render_bisync_report(payload: dict[str, object]) -> str: status = "unknown" if isinstance(verification, dict): status = str(verification.get("status") or status) + lane_label = _bisync_lane_label() lines: list[str] = [] lines.append( - f"Status: mode={mode}; lane=bisync; status={status}; drift_total={drift_total}; blockers={len(blocked_codes)}" + f"Status: mode={mode}; label={lane_label}; lane=bisync; status={status}; drift_total={drift_total}; blockers={len(blocked_codes)}" ) lines.append("") @@ -318,6 +329,7 @@ def render_bisync_report(payload: dict[str, object]) -> str: ["Field", "Value"], [ ["Mode", mode], + ["Lane label", lane_label], ["Drift total", str(drift_total)], ["Blocked code count", str(len(blocked_codes))], ["Verification status", status], diff --git a/tests/test_home_ai_resources_sync.py b/tests/test_home_ai_resources_sync.py index 125d0f6..4e99196 100644 --- a/tests/test_home_ai_resources_sync.py +++ b/tests/test_home_ai_resources_sync.py @@ -631,6 +631,7 @@ def test_main_plan_report_output_has_fixed_sections( assert exit_code == 0 assert "## Current State" in output + assert "repo-to-home install" in output assert "## Differences Or Planned Work" in output assert "## Actions Completed" in output assert "## Blockers And Skips" in output @@ -666,6 +667,7 @@ def test_bisync_plan_report_output_has_fixed_sections( assert exit_code == 1 assert "## Current State" in output + assert "repo-home drift" in output assert "## Differences Or Planned Work" in output assert "## Actions Completed" in output assert "## Blockers And Skips" in output From 5db25f486517b7a457ddf6eaaefb4707cefeb148 Mon Sep 17 00:00:00 2001 From: Diego Mauricio Lagos Date: Wed, 17 Jun 2026 10:12:23 +0200 Subject: [PATCH 2/2] feat: Enhance bisync reporting and conflict resolution details in documentation and scripts --- .../SKILL.md | 2 ++ .../references/sync-contract.md | 5 +-- .../scripts/bisync_skills.py | 31 +++++++++++++------ 3 files changed, 27 insertions(+), 11 deletions(-) diff --git a/.github/skills/local-agent-sync-install-ai-resources/SKILL.md b/.github/skills/local-agent-sync-install-ai-resources/SKILL.md index 482da2b..c82479a 100644 --- a/.github/skills/local-agent-sync-install-ai-resources/SKILL.md +++ b/.github/skills/local-agent-sync-install-ai-resources/SKILL.md @@ -114,7 +114,9 @@ The `bisync` lane provides explicit bidirectional synchronization between `.gith - Excludes `local-agent-sync-*` bundles and runtime artifacts (`.venv`, `__pycache__`, `.pytest_cache`, `.pyc`, `.pyo`) from scanning and copying. ### Conflict Resolution + For bundle direct-copy, replace `./.github/scripts/sync_home_ai_resources.py` with `./scripts/run.sh`, keep `--format report` on model-facing runs, and omit `--home-root` (defaults to `$HOME`). + - `only-repo`: when not excluded, `bisync apply` can create the bundle in home from the repository side. - `only-home`: manual intervention required. Decide whether to keep it only in home, remove it, or add it to the repository. - `equal-mtime`: hashes differ but mtime is equal. Manual decision required because the winner cannot be determined from timestamps alone. diff --git a/.github/skills/local-agent-sync-install-ai-resources/references/sync-contract.md b/.github/skills/local-agent-sync-install-ai-resources/references/sync-contract.md index d07f27c..062c768 100644 --- a/.github/skills/local-agent-sync-install-ai-resources/references/sync-contract.md +++ b/.github/skills/local-agent-sync-install-ai-resources/references/sync-contract.md @@ -219,8 +219,9 @@ The `bisync` lane provides explicit bidirectional reconciliation between `.githu - `home_mtime > repo_mtime` -> `home-to-repo` - `repo_mtime == home_mtime` -> `equal-mtime` blocker 4. For skills only on one side: - - report `only-repo` as an actionable repo-to-home creation candidate; - - report `only-home` as a blocker that requires manual review. + +- report `only-repo` as an actionable repo-to-home creation candidate; +- report `only-home` as a blocker that requires manual review. ### Preflight diff --git a/.github/skills/local-agent-sync-install-ai-resources/scripts/bisync_skills.py b/.github/skills/local-agent-sync-install-ai-resources/scripts/bisync_skills.py index 516021b..d1c5a04 100644 --- a/.github/skills/local-agent-sync-install-ai-resources/scripts/bisync_skills.py +++ b/.github/skills/local-agent-sync-install-ai-resources/scripts/bisync_skills.py @@ -337,13 +337,15 @@ def build_bisync_plan( ) ) - all_blocked = sorted( - set( - code - for d in drifts - for code in d.blocked_codes - ) - ) + all_blocked_set = { + code + for d in drifts + for code in d.blocked_codes + } + for drift in drifts: + if drift.drift_type == "only-repo": + all_blocked_set.add("bisync-only-repo") + all_blocked = sorted(all_blocked_set) plan = BisyncPlan( source_root=source_root, @@ -371,7 +373,13 @@ def apply_bisync_plan( plan: BisyncPlan, ) -> BisyncPlan: if plan.blocked_codes: - return plan + resolvable_during_apply = {"bisync-only-repo"} + remaining_blockers = [ + code for code in plan.blocked_codes if code not in resolvable_during_apply + ] + if remaining_blockers: + return plan + plan.blocked_codes = [] clean, blocked_code, reason = is_repo_clean(source_root) if not clean: @@ -570,7 +578,12 @@ def _emit_bisync_output(plan: BisyncPlan, format_name: str) -> None: label = "only in repo" if dtype == "only-repo" else "only in home" path = drift.repo_path if dtype == "only-repo" else drift.home_path print(f" {skill}: {label} ({path})") - print(f" blocker: {', '.join(drift.blocked_codes)}") + blocker_codes = list(drift.blocked_codes) + if dtype == "only-repo" and not blocker_codes: + blocker_codes = ["bisync-only-repo"] + elif dtype == "only-home" and not blocker_codes: + blocker_codes = ["bisync-only-home"] + print(f" blocker: {', '.join(blocker_codes)}") elif dtype == "equal-mtime": print(f" {skill}: equal mtime (hashes differ)") print(f" blocker: {', '.join(drift.blocked_codes)}")