Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions .x/coordinator.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,13 @@ For this repository, apply the generic loop priorities as follows:
`Azure/azure-cli` before normal work. Never act on a dispute from another
repository.
2. Handle explicit, deduplicated human feedback on an Agent-managed PR.
3. Promote completed Copilot fork work and complete any required AAZ source
promotion before downstream readiness.
3. Promote completed Copilot fork work. After a downstream CLI PR exists, use
the repository-owned `start_aaz_source_task` custom skill when generated
AAZ output requires a durable source change. Discover completed source work
with `find_aaz_fork_prs_ready_for_promotion`, promote it with
`promote_aaz_fork_pr`, and confirm the live source PR with
`find_promoted_aaz_source_pr` before downstream readiness. Do not invoke the
neutral generation-source bridge primitives directly.
4. Trigger missing CI for a ready fork PR.
5. Send an actionable in-flight PR to Tester, then Reviewer after required
live tests and CI complete.
Expand Down
9 changes: 6 additions & 3 deletions .x/fixer.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,18 @@ for a due single follow-up. Stop after either write.

## Target and implementation routing

For sufficient reports, call `infer_target_for_repo` with the sanitized text.
Verify the returned target against current repository structure.
For sufficient reports, call the repository-owned `infer_target_for_repo`
custom skill with `repo_full_name="Azure/azure-cli"`, the sanitized text, and
an empty `pr_files` list. It resolves only against the configured live module
and extension roots. Verify the returned target against current repository
structure.

- A core module remains in `Azure/azure-cli`. Build the exact
`[Component] Fix #N: \`az ...\`: Summary` title with `pr_title_for`, include
`pr_format_guidance`, post the evidence-based bug analysis, then start the
configured Copilot fork task.
- An extension is routed with the idempotent
`start_extension_tracker_task` workflow to
repository-owned `start_extension_tracker_task` custom skill to
`Azure/azure-cli-extensions`. It creates or resumes the tracker, records a
pending source marker, starts Copilot in the extension fork, and finalizes
the source backlink only after dispatch succeeds. Include the complete
Expand Down
6 changes: 3 additions & 3 deletions .x/reviewer.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ human reviews, and live-test state once. Pending required CI or live tests are
waiting, not failure. If the current decisive human review requests changes,
preserve that state and do not post an Agent pass.

Run `get_pr_regression_coverage_summary` and `get_pr_review_skill_summary`
against
the current diff. Deterministic findings are requirements. Semantic candidates
Run the repository-owned `get_pr_regression_coverage_summary` custom skill
with the PR number, then run `get_pr_review_skill_summary` against the current
diff. Deterministic findings are requirements. Semantic candidates
become findings only when changed-line evidence confirms them. Diagnose each
failed check as PR-related, unrelated, or uncertain and include the exact
evidence, practical correction, and focused verification.
Expand Down
11 changes: 7 additions & 4 deletions .x/skills/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
# Custom skills

This repository currently uses the approved X Engineering Agent base skill
library. A repository-owned custom skill must be one Python file containing
one public top-level function and must be mapped in `.x/x.yml`. Markdown files
in this directory are documentation and are never executable.
Each Python file contains one repository-owned public function mapped by
`.x/x.yml`. Custom skills own Azure CLI target inference, regression policy,
AAZ source routing, and the CLI-to-Extensions handoff. Authentication,
sensitive-data checks, repository scoping, and narrow GitHub mutations remain
in the approved base primitives. Repository directory discovery resolves roots
and branches from central trusted configuration rather than repository-supplied
arguments. Markdown files are never executable.
23 changes: 23 additions & 0 deletions .x/skills/changed_test_files.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
"""Select changed Azure CLI pytest modules."""


def changed_test_files(pr_files):
"""Return unique changed test filename stems outside azure-cli-core."""
stems = []
seen = set()
for path in pr_files or []:
normalized = str(path).replace("\\", "/")
lowered = normalized.casefold()
name = normalized.rsplit("/", 1)[-1]
if (
"/tests/" not in f"/{lowered}"
or not name.casefold().startswith("test_")
or not name.casefold().endswith(".py")
or "azure-cli-core" in lowered.split("/")
):
continue
stem = name[:-3]
if stem not in seen:
seen.add(stem)
stems.append(stem)
return stems
8 changes: 8 additions & 0 deletions .x/skills/find_aaz_fork_prs_ready_for_promotion.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
"""Bind generation-source candidate discovery to Azure CLI."""


def find_aaz_fork_prs_ready_for_promotion():
"""Find completed AAZ fork pull requests ready for promotion."""
return find_generation_source_fork_prs_ready_for_promotion(
repository=None,
)
9 changes: 9 additions & 0 deletions .x/skills/find_promoted_aaz_source_pr.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
"""Bind promoted generation-source lookup to Azure CLI."""


def find_promoted_aaz_source_pr(issue_number):
"""Find the promoted AAZ source pull request for an Agent issue."""
return find_promoted_generation_source_pr(
repository=None,
issue_number=issue_number,
)
66 changes: 66 additions & 0 deletions .x/skills/get_pr_regression_coverage_summary.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
"""Evaluate Azure CLI command-module regression coverage."""


def get_pr_regression_coverage_summary(pr_number):
"""Find changed command modules without focused tests or recordings."""
changes = get_pr_file_changes(
owner=None,
repo=None,
pr_number=pr_number,
)
files = [
item.get("filename")
for item in changes
if isinstance(item, dict) and item.get("filename")
]
root = "src/azure-cli/azure/cli/command_modules/"
production_files = []
modules = set()
for path in files:
normalized = str(path).replace("\\", "/")
name = normalized.rsplit("/", 1)[-1]
if (
normalized.startswith(root)
and normalized.endswith(".py")
and "/tests/" not in normalized
and name not in {"__init__.py", "_help.py"}
):
production_files.append(normalized)
remainder = normalized[len(root):]
module = remainder.split("/", 1)[0].split(".", 1)[0].casefold()
if module:
modules.add(module)

test_files = []
recording_files = []
covered = set()
for path in files:
normalized = str(path).replace("\\", "/")
if not normalized.startswith(root) or "/tests/" not in normalized:
continue
module = (
normalized[len(root):]
.split("/", 1)[0]
.split(".", 1)[0]
.casefold()
)
if module not in modules:
continue
name = normalized.rsplit("/", 1)[-1]
if name.casefold().startswith("test_") and name.casefold().endswith(".py"):
test_files.append(normalized)
covered.add(module)
if "/recordings/" in normalized:
recording_files.append(normalized)
covered.add(module)

uncovered = sorted(modules - covered)
return {
"applicable": bool(production_files),
"gap": bool(uncovered),
"modules": sorted(modules),
"uncovered_modules": uncovered,
"production_files": production_files,
"test_files": test_files,
"recording_files": recording_files,
}
80 changes: 80 additions & 0 deletions .x/skills/infer_target_for_repo.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
"""Infer an Azure CLI module or extension from trusted repository structure."""


def infer_target_for_repo(repo_full_name, text, pr_files):
"""Resolve a sanitized issue or PR diff to a live CLI target."""
if repo_full_name != "Azure/azure-cli":
raise ValueError("infer_target_for_repo is restricted to Azure/azure-cli")

modules = list_repository_directories(
source_repository="Azure/azure-cli",
)
extensions = list_repository_directories(
source_repository="Azure/azure-cli-extensions",
)

def normalize(value):
return "".join(
character
for character in str(value or "").casefold()
if character.isalnum()
)

def resolve(candidate):
candidate_normalized = normalize(candidate)
for extension in extensions:
if normalize(extension) == candidate_normalized:
return {
"kind": "extension",
"name": extension,
"repo": "Azure/azure-cli-extensions",
}
for module in modules:
if normalize(module) == candidate_normalized:
return {
"kind": "module",
"name": module,
"repo": "Azure/azure-cli",
}
return None

scores = {}
for path in pr_files or []:
parts = str(path).replace("\\", "/").split("/")
if "command_modules" in parts:
index = parts.index("command_modules")
if index + 1 < len(parts):
candidate = parts[index + 1]
scores[candidate] = scores.get(candidate, 0) + 10
elif len(parts) > 1 and parts[0].casefold() == "src":
candidate = parts[1]
scores[candidate] = scores.get(candidate, 0) + 10
if pr_files:
for candidate in sorted(scores, key=lambda item: (-scores[item], item)):
target = resolve(candidate)
if target is not None:
return target
return {"kind": "none", "name": None, "repo": None}

cleaned = "".join(
character if character.isalnum() or character in "-_./" else " "
for character in str(text or "").casefold()
)
words = cleaned.split()
for index, word in enumerate(words):
if word == "az" and index + 1 < len(words):
candidate = words[index + 1]
scores[candidate] = scores.get(candidate, 0) + 5
if word.startswith("src/"):
parts = word.split("/")
if len(parts) > 1:
candidate = parts[1]
scores[candidate] = scores.get(candidate, 0) + 3
if "command_modules/" in word:
candidate = word.split("command_modules/", 1)[1].split("/", 1)[0]
scores[candidate] = scores.get(candidate, 0) + 3
for candidate in sorted(scores, key=lambda item: (-scores[item], item)):
target = resolve(candidate)
if target is not None:
return target
return {"kind": "unknown", "name": None, "repo": None}
11 changes: 11 additions & 0 deletions .x/skills/promote_aaz_fork_pr.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
"""Bind generation-source promotion to Azure CLI."""


def promote_aaz_fork_pr(fork_pr_number, title, body):
"""Promote one validated AAZ fork pull request."""
return promote_generation_source_fork_pr(
repository=None,
fork_pr_number=fork_pr_number,
title=title,
body=body,
)
12 changes: 12 additions & 0 deletions .x/skills/start_aaz_source_task.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
"""Bind durable generation-source task creation to Azure CLI."""


def start_aaz_source_task(issue_number, downstream_pr_url, changed_files, prompt_context):
"""Start the configured AAZ source task for an Azure CLI pull request."""
return start_generation_source_task(
repository=None,
issue_number=issue_number,
downstream_pr_url=downstream_pr_url,
changed_files=changed_files,
prompt_context=prompt_context,
)
22 changes: 22 additions & 0 deletions .x/skills/start_extension_tracker_task.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
"""Own the Azure CLI to CLI Extensions implementation handoff."""


def start_extension_tracker_task(issue_number, view, target, prompt, command, summary):
"""Create or resume the scoped extension tracker and Copilot task."""
if (
not isinstance(target, dict)
or target.get("repo") != "Azure/azure-cli-extensions"
or not target.get("name")
):
raise ValueError(
"start_extension_tracker_task requires a CLI Extensions target"
)
return start_repository_handoff_task(
repository=None,
issue_number=issue_number,
view=view,
target=target,
prompt=prompt,
command=command,
summary=summary,
)
11 changes: 7 additions & 4 deletions .x/tester.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,13 @@ Coordinator whose current head either has a completed Copilot task marker or
is a verified human-requested review candidate, and has no completed live-test
run for that head.

Use `dispatch_live_test_workflow` with the PR number and
`pr_repo="Azure/azure-cli"`. Do not provide a guessed module; the dispatcher
resolves changed files against the live module list and the workflow validates
the target.
Read the PR and `get_pr_file_changes` once. Pass the filenames to the
repository-owned `changed_test_files` custom skill and call
`infer_target_for_repo` with the PR title/body and those filenames. Use
`dispatch_live_test_workflow` with the PR number,
Comment thread
a0x1ab marked this conversation as resolved.
`pr_repo="Azure/azure-cli"`, and the resolved module and target kind. Never
guess a module; the custom skill resolves only against configured live roots
and the workflow validates the target.

Before dispatch, reuse any queued, in-progress, or completed run for the same
head SHA. A new dispatch counts as one action; a reused run is a read. Call
Expand Down
24 changes: 15 additions & 9 deletions .x/x.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,17 @@ agents:
skills:
- auto_trigger_pr_validation
- build_promoted_pr_body
- changed_test_files
- codegen_execution_guidance
- compare_issue_similarity
- copilot_iteration_cap_reached
- copilot_iteration_state
- daily_pr_cap_reached
- dispatch_live_test_workflow
- find_aaz_fork_prs_ready_for_promotion
- find_copilot_fork_prs_ready_for_promotion
- find_fork_prs_needing_ci
- find_generation_source_fork_prs_ready_for_promotion
- find_in_flight_prs
- find_promoted_aaz_source_pr
- find_promoted_generation_source_pr
- find_sensitive_redaction_dispute
- find_stale_prs
- follow_up_requirements
Expand All @@ -30,21 +29,20 @@ skills:
- get_pr_check_runs
- get_pr_check_summary
- get_pr_file_changes
- get_pr_regression_coverage_summary
- get_pr_review_skill_summary
- get_profile
- get_workflow_run
- handle_sensitive_redaction_dispute
- has_agent_reviewed_head
- infer_target_for_repo
- list_repository_directories
- mark_pr_ready_for_review
- post_bug_analysis
- post_comment
- post_copilot_human_review_handoff
- post_pr_review
- pr_format_guidance
- pr_title_for
- promote_aaz_fork_pr
- promote_generation_source_fork_pr
- promote_copilot_fork_pr
- recall_repository_memory
- remediate_sensitive_issue
Expand All @@ -60,10 +58,18 @@ skills:
- safe_issue_view
- select_triagable_issues_for_repo
- similar_issue_candidates
- start_aaz_source_task
- start_copilot_fork_task
- start_extension_tracker_task
- start_generation_source_task
- start_repository_handoff_task
- synchronize_pull_request_feedback
- synchronize_repository_feedback
- update_pr_branch
custom_skills: {}
custom_skills:
changed_test_files: changed_test_files
find_aaz_fork_prs_ready_for_promotion: find_aaz_fork_prs_ready_for_promotion
find_promoted_aaz_source_pr: find_promoted_aaz_source_pr
get_pr_regression_coverage_summary: get_pr_regression_coverage_summary
infer_target_for_repo: infer_target_for_repo
promote_aaz_fork_pr: promote_aaz_fork_pr
start_aaz_source_task: start_aaz_source_task
start_extension_tracker_task: start_extension_tracker_task
Loading