Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
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
15 changes: 9 additions & 6 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 All @@ -28,9 +28,12 @@ Require:
- owning-team review for high-risk auth, security, core runtime, generated
surface, or broad behavior changes.

Use `repair_pr_title_check` only for a confirmed metadata-gate failure,
then read the rerun in a later round. Combine CI, live-test, regression, risk,
and review-skill evidence in one review.
Use `repair_pr_title_check` only for a confirmed metadata-gate failure. Resolve
the component first with repository-owned `infer_target_for_repo` using the
current PR title, body, and changed filenames, then pass its name as
`component`; central title repair must not infer repository policy. Read the
rerun in a later round. Combine CI, live-test, regression, risk, and
review-skill evidence in one review.

For a human-requested PR, post one `COMMENT`. For a Copilot-authored PR with
relevant failures, use `request_copilot_changes`; after the iteration cap,
Expand Down
6 changes: 0 additions & 6 deletions .x/skills/README.md

This file was deleted.

27 changes: 27 additions & 0 deletions .x/skills/changed_test_files.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# --------------------------------------------------------------------------------------------
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License. See License.txt in the project root for license information.
# --------------------------------------------------------------------------------------------

"""Select changed Azure CLI live-test files."""


def changed_test_files(pr_files):
"""Return unique changed pytest paths outside azure-cli-core."""
selected = []
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
if normalized not in seen:
seen.add(normalized)
selected.append(normalized)
return selected
13 changes: 13 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,13 @@
# --------------------------------------------------------------------------------------------
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License. See License.txt in the project root for license information.
# --------------------------------------------------------------------------------------------

"""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="Azure/azure-cli",
)
14 changes: 14 additions & 0 deletions .x/skills/find_promoted_aaz_source_pr.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# --------------------------------------------------------------------------------------------
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License. See License.txt in the project root for license information.
# --------------------------------------------------------------------------------------------

"""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="Azure/azure-cli",
issue_number=issue_number,
)
71 changes: 71 additions & 0 deletions .x/skills/get_pr_regression_coverage_summary.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
# --------------------------------------------------------------------------------------------
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License. See License.txt in the project root for license information.
# --------------------------------------------------------------------------------------------

"""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="Azure",
repo="azure-cli",
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,
}
85 changes: 85 additions & 0 deletions .x/skills/infer_target_for_repo.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
# --------------------------------------------------------------------------------------------
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License. See License.txt in the project root for license information.
# --------------------------------------------------------------------------------------------

"""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}
16 changes: 16 additions & 0 deletions .x/skills/promote_aaz_fork_pr.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# --------------------------------------------------------------------------------------------
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License. See License.txt in the project root for license information.
# --------------------------------------------------------------------------------------------

"""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="Azure/azure-cli",
fork_pr_number=fork_pr_number,
title=title,
body=body,
)
17 changes: 17 additions & 0 deletions .x/skills/start_aaz_source_task.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# --------------------------------------------------------------------------------------------
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License. See License.txt in the project root for license information.
# --------------------------------------------------------------------------------------------

"""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="Azure/azure-cli",
issue_number=issue_number,
downstream_pr_url=downstream_pr_url,
changed_files=changed_files,
prompt_context=prompt_context,
)
27 changes: 27 additions & 0 deletions .x/skills/start_extension_tracker_task.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# --------------------------------------------------------------------------------------------
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License. See License.txt in the project root for license information.
# --------------------------------------------------------------------------------------------

"""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="Azure/azure-cli",
issue_number=issue_number,
view=view,
target=target,
prompt=prompt,
command=command,
summary=summary,
)
18 changes: 14 additions & 4 deletions .x/tester.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,20 @@ 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 `repo_full_name="Azure/azure-cli"`, `text` set to
the PR title/body, and `pr_files` set to those filenames. Use
`dispatch_live_test_workflow` with the PR number,
`pr_repo="Azure/azure-cli"`, the resolved module and target kind, and
`test_files` set to the paths returned by `changed_test_files`. Never guess a
module or test path; repository custom skills own both decisions and the
workflow validates them against the current PR.

If no test path is selected, call the dispatcher with the empty list so it
records a neutral skip for the current revision. If tests are selected but
target inference does not return a named `module` or `extension`, stop with a
pending result and do not dispatch.

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
Loading
Loading