Keep master router-style SKILL.md (full #333 split) and merge #334 review-merge test coverage into test_llm_workflow_split.py. Conflicts resolved in: - skills/llm-project-workflow/SKILL.md - tests/test_llm_workflow_split.py
This commit is contained in:
@@ -3738,6 +3738,19 @@ _GUIDE_RULES = {
|
||||
"paths (e.g. gitea_commit_files) for remaining mutations. Shell "
|
||||
"unavailability never authorizes WebFetch/browser/manual-encoding "
|
||||
"fallbacks (#258)."),
|
||||
"subagent_delegation": (
|
||||
"Deterministic write workflows (issue claim, branch creation, code "
|
||||
"edits, commits, PR creation, review, merge, cleanup) run inline in "
|
||||
"the parent session — subagents are blocked for them unless "
|
||||
"explicitly allowed with a recorded justification. An authorized "
|
||||
"write subagent must inherit the full gate context: issue lock, "
|
||||
"branch/worktree path, identity/profile, allowed tool class, "
|
||||
"command deny list, validation ledger requirement, and final report "
|
||||
"schema. Subagent output is accepted only with the same proof "
|
||||
"fields as the parent workflow; read-only delegation (search, "
|
||||
"inventory, summarize) needs no explicit authorization. Enforced "
|
||||
"fail closed via subagent_gate.assess_subagent_delegation and "
|
||||
"validate_subagent_report (#266)."),
|
||||
}
|
||||
|
||||
_COMMON_WORKFLOWS = [
|
||||
|
||||
@@ -704,6 +704,270 @@ def assess_live_state_recheck(recheck):
|
||||
return {"proven": proven, "block": not proven, "reasons": reasons}
|
||||
|
||||
|
||||
_REQUEST_CHANGES_OVERRIDE_REASONS = frozenset({
|
||||
"incorrect_blocker",
|
||||
"wrong_validation_environment",
|
||||
"resolved_externally",
|
||||
})
|
||||
|
||||
|
||||
def _blocking_request_changes_reviews(feedback: dict) -> list[dict]:
|
||||
"""Return undismissed REQUEST_CHANGES reviews (latest verdict per reviewer)."""
|
||||
latest_by_reviewer: dict[str, dict] = {}
|
||||
for entry in feedback.get("reviews") or []:
|
||||
verdict = (entry.get("verdict") or "").upper()
|
||||
reviewer = (entry.get("reviewer") or "").strip()
|
||||
if verdict not in ("APPROVED", "REQUEST_CHANGES") or not reviewer:
|
||||
continue
|
||||
latest_by_reviewer[reviewer] = entry
|
||||
return [
|
||||
entry for entry in latest_by_reviewer.values()
|
||||
if entry.get("verdict") == "REQUEST_CHANGES" and not entry.get("dismissed")
|
||||
]
|
||||
|
||||
|
||||
def _primary_blocking_review(blockers: list[dict]) -> dict | None:
|
||||
if not blockers:
|
||||
return None
|
||||
return sorted(
|
||||
blockers,
|
||||
key=lambda entry: (entry.get("submitted_at") or "", entry.get("reviewer") or ""),
|
||||
)[-1]
|
||||
|
||||
|
||||
def assess_request_changes_approval_proof(
|
||||
feedback: dict | None,
|
||||
*,
|
||||
override_reason: str | None = None,
|
||||
override_explanation: str | None = None,
|
||||
report_text: str = "",
|
||||
) -> dict:
|
||||
"""#326: prove approval is safe when prior REQUEST_CHANGES exists on same head.
|
||||
|
||||
Before approving, workflows must fetch ``gitea_get_pr_review_feedback`` and
|
||||
pass the result here. When a prior undismissed REQUEST_CHANGES targets the
|
||||
current head, approval requires explicit override proof; otherwise fail closed.
|
||||
"""
|
||||
if not feedback or feedback.get("success") is not True:
|
||||
return {
|
||||
"approve_allowed": False,
|
||||
"block": True,
|
||||
"reasons": ["PR review feedback missing or unreadable; fail closed"],
|
||||
"blocking_review": None,
|
||||
"head_changed_since_blocker": None,
|
||||
"override_required": None,
|
||||
"override_proof": None,
|
||||
}
|
||||
|
||||
blockers = _blocking_request_changes_reviews(feedback)
|
||||
primary = _primary_blocking_review(blockers)
|
||||
current_head = (feedback.get("current_head_sha") or "").strip().lower()
|
||||
|
||||
if not blockers:
|
||||
return {
|
||||
"approve_allowed": True,
|
||||
"block": False,
|
||||
"reasons": [],
|
||||
"blocking_review": None,
|
||||
"head_changed_since_blocker": False,
|
||||
"override_required": False,
|
||||
"override_proof": None,
|
||||
}
|
||||
|
||||
blocking_head = (primary.get("reviewed_head_sha") or "").strip().lower()
|
||||
head_changed = bool(
|
||||
feedback.get("author_pushed_after_request_changes")
|
||||
or (blocking_head and current_head and blocking_head != current_head)
|
||||
)
|
||||
|
||||
blocker_report = {
|
||||
"blocking_reviewer": primary.get("reviewer"),
|
||||
"blocking_review_timestamp": primary.get("submitted_at"),
|
||||
"blocking_head_sha": primary.get("reviewed_head_sha"),
|
||||
"current_head_sha": feedback.get("current_head_sha"),
|
||||
"blocker_text": (primary.get("body") or "").strip(),
|
||||
"head_changed_since_blocker": head_changed,
|
||||
}
|
||||
|
||||
if head_changed:
|
||||
return {
|
||||
"approve_allowed": True,
|
||||
"block": False,
|
||||
"reasons": [],
|
||||
"blocking_review": blocker_report,
|
||||
"head_changed_since_blocker": True,
|
||||
"override_required": False,
|
||||
"override_proof": None,
|
||||
}
|
||||
|
||||
reason = (override_reason or "").strip().lower()
|
||||
explanation = (override_explanation or "").strip()
|
||||
report_lower = (report_text or "").lower()
|
||||
blocker_text = blocker_report["blocker_text"]
|
||||
reasons: list[str] = []
|
||||
|
||||
if reason not in _REQUEST_CHANGES_OVERRIDE_REASONS:
|
||||
reasons.append(
|
||||
"unchanged head after REQUEST_CHANGES requires override_reason "
|
||||
f"in {sorted(_REQUEST_CHANGES_OVERRIDE_REASONS)}"
|
||||
)
|
||||
if not explanation:
|
||||
reasons.append(
|
||||
"unchanged head after REQUEST_CHANGES requires override_explanation"
|
||||
)
|
||||
if blocker_text and blocker_text.lower() not in report_lower:
|
||||
reasons.append(
|
||||
"report must include the blocking REQUEST_CHANGES body text"
|
||||
)
|
||||
if reason and reason.replace("_", " ") not in report_lower and reason not in report_lower:
|
||||
reasons.append(
|
||||
"report must state the override reason for unchanged-head approval"
|
||||
)
|
||||
|
||||
override_proof = {
|
||||
"override_reason": reason or None,
|
||||
"override_explanation": explanation or None,
|
||||
"blocker_text_in_report": bool(
|
||||
blocker_text and blocker_text.lower() in report_lower
|
||||
),
|
||||
}
|
||||
approve_allowed = not reasons
|
||||
return {
|
||||
"approve_allowed": approve_allowed,
|
||||
"block": not approve_allowed,
|
||||
"reasons": reasons,
|
||||
"blocking_review": blocker_report,
|
||||
"head_changed_since_blocker": False,
|
||||
"override_required": True,
|
||||
"override_proof": override_proof,
|
||||
}
|
||||
|
||||
|
||||
_PERFORMED_FILE_ACTIONS = frozenset({"edited", "created", "wrote", "generated"})
|
||||
_FILE_EDITS_FIELD_RE = re.compile(
|
||||
r"^\s*file edits by reviewer\s*:\s*(.+?)\s*$",
|
||||
re.I | re.M,
|
||||
)
|
||||
_WALKTHROUGH_ARTIFACT_RE = re.compile(r"walkthrough\.md", re.I)
|
||||
|
||||
|
||||
def _performed_file_mutations(action_log: list[dict] | None) -> list[dict]:
|
||||
"""Return performed local file mutations, excluding gated rejections."""
|
||||
performed: list[dict] = []
|
||||
for entry in action_log or []:
|
||||
if entry.get("gated_rejected") or entry.get("performed") is False:
|
||||
continue
|
||||
action = (entry.get("action") or "").strip().lower()
|
||||
if action not in _PERFORMED_FILE_ACTIONS:
|
||||
continue
|
||||
path = (entry.get("path") or "").strip()
|
||||
if not path:
|
||||
continue
|
||||
performed.append({**entry, "action": action, "path": path})
|
||||
return performed
|
||||
|
||||
|
||||
def assess_mutation_ledger_report(
|
||||
report_text: str,
|
||||
*,
|
||||
action_log: list[dict] | None = None,
|
||||
final_git_status_reported: bool | None = None,
|
||||
walkthrough_explicitly_requested: bool = False,
|
||||
) -> dict:
|
||||
"""#331: verify reviewer final reports match observed file mutations.
|
||||
|
||||
Compares an action log of local file writes/edits against the report's
|
||||
mutation ledger and ``File edits by reviewer`` field. Gated rejections
|
||||
(``performed: false`` or ``gated_rejected: true``) are excluded from the
|
||||
performed mutation set but should still appear in a separate rejected-calls
|
||||
section when reported.
|
||||
"""
|
||||
text = report_text or ""
|
||||
lower = text.lower()
|
||||
reasons: list[str] = []
|
||||
performed = _performed_file_mutations(action_log)
|
||||
|
||||
field_match = _FILE_EDITS_FIELD_RE.search(text)
|
||||
file_edits_value = (field_match.group(1).strip() if field_match else None)
|
||||
claimed_none = bool(
|
||||
file_edits_value and file_edits_value.lower() == "none"
|
||||
)
|
||||
|
||||
if performed and claimed_none:
|
||||
reasons.append(
|
||||
"report claims 'File edits by reviewer: none' but action log "
|
||||
"records performed file mutations"
|
||||
)
|
||||
|
||||
unreported: list[str] = []
|
||||
for entry in performed:
|
||||
path = entry["path"]
|
||||
path_lower = path.lower()
|
||||
if path_lower not in lower:
|
||||
unreported.append(path)
|
||||
reasons.append(
|
||||
f"performed mutation path '{path}' missing from report "
|
||||
"mutation ledger"
|
||||
)
|
||||
continue
|
||||
|
||||
if entry.get("outside_repo"):
|
||||
if "outside repo" not in lower or path_lower not in lower:
|
||||
reasons.append(
|
||||
f"outside-repo mutation '{path}' must be reported with "
|
||||
"'outside repo' label"
|
||||
)
|
||||
elif entry.get("in_repo", True):
|
||||
tracked = entry.get("tracked")
|
||||
if tracked is True and "tracked" not in lower:
|
||||
reasons.append(
|
||||
f"in-repo tracked mutation '{path}' must state tracked/"
|
||||
"untracked status in the ledger"
|
||||
)
|
||||
elif tracked is False and "untracked" not in lower:
|
||||
reasons.append(
|
||||
f"in-repo untracked mutation '{path}' must state tracked/"
|
||||
"untracked status in the ledger"
|
||||
)
|
||||
|
||||
if (
|
||||
_WALKTHROUGH_ARTIFACT_RE.search(path)
|
||||
and not walkthrough_explicitly_requested
|
||||
):
|
||||
reasons.append(
|
||||
"walkthrough artifact created without explicit workflow or "
|
||||
"operator request"
|
||||
)
|
||||
|
||||
if entry.get("after_git_status") and final_git_status_reported is not True:
|
||||
reasons.append(
|
||||
f"mutation '{path}' occurred after an earlier git status; "
|
||||
"report must include a final git status"
|
||||
)
|
||||
|
||||
rejected = [
|
||||
e for e in (action_log or [])
|
||||
if e.get("gated_rejected") or e.get("performed") is False
|
||||
]
|
||||
rejected_reported = "rejected" in lower or "gated" in lower or "no-op" in lower
|
||||
if rejected and performed and not rejected_reported:
|
||||
reasons.append(
|
||||
"rejected/no-op gated tool calls must be reported separately from "
|
||||
"performed mutations"
|
||||
)
|
||||
|
||||
proven = not reasons
|
||||
return {
|
||||
"proven": proven,
|
||||
"block": not proven,
|
||||
"reasons": reasons,
|
||||
"performed_mutations": performed,
|
||||
"unreported_paths": unreported,
|
||||
"file_edits_claimed_none": claimed_none,
|
||||
"rejected_calls": rejected,
|
||||
}
|
||||
|
||||
|
||||
def assess_role_boundary(proof=None, *, task_role=None, namespaces_used=None,
|
||||
justification=None):
|
||||
"""Assess reviewer/author role separation for blind queue workflows.
|
||||
@@ -2471,3 +2735,84 @@ def build_review_mutation_proof(run_log: list[dict]) -> dict:
|
||||
"missing_fields": [],
|
||||
"reasons": [],
|
||||
}
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Identity disclosure (#305)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
EMAIL_ADDRESS_RE = re.compile(
|
||||
r"[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}")
|
||||
|
||||
EMAIL_JUSTIFICATION_MARKERS = (
|
||||
"email required",
|
||||
"email is required",
|
||||
"email necessary",
|
||||
"necessary to disambiguate",
|
||||
"disambiguate identity",
|
||||
"tool requires the email",
|
||||
"user explicitly asked",
|
||||
)
|
||||
|
||||
|
||||
def format_identity_summary(username, profile, role=None, remote=None):
|
||||
"""Return the no-email identity line for workflow reports (#305).
|
||||
|
||||
Standard reports identify actors as ``<username> / <profile>`` (plus
|
||||
optional role/remote). Personal email is never part of the summary; if
|
||||
an email lands in the username slot, only its local part is kept.
|
||||
"""
|
||||
name = (username or "").strip()
|
||||
if "@" in name:
|
||||
name = name.split("@", 1)[0]
|
||||
summary = f"{name} / {(profile or '').strip()}"
|
||||
extras = [str(part).strip() for part in (role, remote)
|
||||
if part and str(part).strip()]
|
||||
if extras:
|
||||
summary += f" ({', '.join(extras)})"
|
||||
return summary
|
||||
|
||||
|
||||
def assess_email_disclosure(
|
||||
report_text,
|
||||
*,
|
||||
justification_markers=EMAIL_JUSTIFICATION_MARKERS,
|
||||
):
|
||||
"""Flag unnecessary personal-email disclosure in a report (#305).
|
||||
|
||||
Username/profile identity is sufficient for normal workflow reports.
|
||||
An email address is tolerated only when the report itself explains why
|
||||
it is necessary (tool proof, explicit request, or disambiguation).
|
||||
"""
|
||||
text = report_text or ""
|
||||
emails = sorted(set(EMAIL_ADDRESS_RE.findall(text)))
|
||||
if not emails:
|
||||
return {
|
||||
"proven": True,
|
||||
"flagged": False,
|
||||
"justified": False,
|
||||
"emails": [],
|
||||
"reasons": [],
|
||||
}
|
||||
lower = text.lower()
|
||||
justified = any(marker in lower for marker in justification_markers)
|
||||
if justified:
|
||||
return {
|
||||
"proven": True,
|
||||
"flagged": False,
|
||||
"justified": True,
|
||||
"emails": emails,
|
||||
"reasons": [
|
||||
"email disclosure present but justified in the report",
|
||||
],
|
||||
}
|
||||
return {
|
||||
"proven": False,
|
||||
"flagged": True,
|
||||
"justified": False,
|
||||
"emails": emails,
|
||||
"reasons": [
|
||||
f"unnecessary personal email disclosure: {email}"
|
||||
for email in emails
|
||||
],
|
||||
}
|
||||
|
||||
@@ -1,89 +1,74 @@
|
||||
---
|
||||
name: llm-project-workflow
|
||||
description: >-
|
||||
Portable, safe operating workflow for LLMs working on any Git/forge project:
|
||||
issue-first, isolated branch worktrees, no self-review/self-merge, distinct
|
||||
author/reviewer profiles, cleanup after merge, and fail-closed behavior.
|
||||
Use at the start of any implementation, review, or merge task on a repo.
|
||||
Router skill for safe LLM project work: identify task mode, load the matching
|
||||
canonical workflow file, enforce mode isolation, and emit the correct final
|
||||
report schema. Use at the start of any implementation, review, merge,
|
||||
reconciliation, or issue-filing task.
|
||||
---
|
||||
|
||||
# LLM Project Workflow
|
||||
# LLM Project Workflow Skill
|
||||
|
||||
A reusable workflow any LLM can follow to work on any repository safely. Copy
|
||||
this `skills/llm-project-workflow/` directory into another project unchanged;
|
||||
adapt only the forge-specific names in [Adapting to a project](#adapting-to-a-project).
|
||||
This skill is a **router**. Do not perform project work from this file alone.
|
||||
|
||||
The core promise: **an LLM never does unsafe or untracked work.** Every change
|
||||
is tracked by an issue, isolated in its own worktree, reviewed by a different
|
||||
identity, and cleaned up only after a real merge.
|
||||
Before any project mutation, identify the task mode and load the matching
|
||||
workflow file.
|
||||
|
||||
## Task mode router (#333)
|
||||
|
||||
**Identify task mode before any mutation.** Load the matching workflow file and
|
||||
final-report schema; do not apply rules from other modes.
|
||||
## Workflow modes
|
||||
|
||||
| Task mode | Workflow | Final report schema |
|
||||
|-----------|----------|---------------------|
|
||||
| `review-merge-pr` | [`workflows/review-merge-pr.md`](workflows/review-merge-pr.md) (canonical; §0–§38) | [`schemas/review-merge-final-report.md`](schemas/review-merge-final-report.md) |
|
||||
| `create-issue` | `workflows/create-issue.md` (planned — #333) | `schemas/create-issue-final-report.md` (planned) |
|
||||
| `work-issue` | `workflows/work-issue.md` (planned — #333) | `schemas/work-issue-final-report.md` (planned) |
|
||||
| PR review / approval / merge | [`workflows/review-merge-pr.md`](workflows/review-merge-pr.md) | [`schemas/review-merge-final-report.md`](schemas/review-merge-final-report.md) |
|
||||
| Reconcile already-landed open PRs | [`workflows/reconcile-landed-pr.md`](workflows/reconcile-landed-pr.md) | [`schemas/reconcile-landed-final-report.md`](schemas/reconcile-landed-final-report.md) |
|
||||
| Create or update Gitea issues | [`workflows/create-issue.md`](workflows/create-issue.md) | [`schemas/create-issue-final-report.md`](schemas/create-issue-final-report.md) |
|
||||
| Work on an assigned issue / author code | [`workflows/work-issue.md`](workflows/work-issue.md) | [`schemas/work-issue-final-report.md`](schemas/work-issue-final-report.md) |
|
||||
|
||||
Mode isolation:
|
||||
## Universal rules
|
||||
|
||||
- **review-merge-pr** — reviewer namespace only; no issue creation, no code edits, no approve/merge replay after terminal review
|
||||
- **create-issue** — author namespace; duplicate search + `create_issue` capability; no PR review/merge
|
||||
- **work-issue** — author namespace; implement, validate, commit, PR; no approve/merge
|
||||
- Prove identity, active profile, runtime context, and **exact** capability before
|
||||
mutation.
|
||||
- A nearby capability does not count.
|
||||
- Do not self-review or self-merge.
|
||||
- Do not mix modes in one run.
|
||||
- If the required workflow cannot be loaded, stop and produce a recovery handoff
|
||||
only.
|
||||
- Final report must use the schema for the loaded workflow.
|
||||
- If a task requires a different mode, stop and produce a handoff for the
|
||||
correct workflow.
|
||||
|
||||
Shared gate references (all modes): `gates/identity-capability-rules.md`,
|
||||
`gates/mutation-ledger-rules.md`, `gates/proof-wording-rules.md` (planned — #333).
|
||||
## Mode isolation
|
||||
|
||||
Until `create-issue.md` and `work-issue.md` land, author rules remain in this
|
||||
file (§A, §E) and review rules live in
|
||||
[`workflows/review-merge-pr.md`](workflows/review-merge-pr.md).
|
||||
A run that starts in `review-merge-pr` mode may not create process issues,
|
||||
implement fixes, or edit source files.
|
||||
|
||||
A run that starts in `reconcile-landed-pr` mode may not approve, request
|
||||
changes, merge, implement fixes, or create normal issues.
|
||||
|
||||
A run that starts in `create-issue` mode may not review, approve, request
|
||||
changes, merge, implement fixes, create branches, commit, push, or create PRs.
|
||||
|
||||
A run that starts in `work-issue` mode may not review, approve, request changes,
|
||||
merge, close PRs, or act as reviewer.
|
||||
|
||||
If the task requires a different mode, stop and produce a handoff for the
|
||||
correct workflow.
|
||||
|
||||
---
|
||||
|
||||
|
||||
## Definitions
|
||||
|
||||
- **Merged**: Gitea PR metadata says `merged=true`.
|
||||
- **Landed**: Equivalent content is present on remote `master`, but PR metadata may not say merged.
|
||||
- **Landed**: Equivalent content is present on remote `master`, but PR metadata
|
||||
may not say merged.
|
||||
- **Closed-not-merged**: PR state is closed and `merged=false`.
|
||||
- **Reconciled**: A human/LLM verified whether closed-not-merged content landed, partially landed, or was lost, and repaired issue/label/tracker state.
|
||||
|
||||
## A. Issue-first rule
|
||||
|
||||
**No repository change without a tracking issue.** This includes creating,
|
||||
editing, deleting, or `chmod`-ing files; docs; scripts; commits; pushes; and PRs.
|
||||
|
||||
1. Before any change, confirm a tracking issue exists.
|
||||
2. If none exists, create one first (title + problem + scope + acceptance).
|
||||
3. Claim it (assign yourself or apply the `status:in-progress` label) and comment
|
||||
that work is starting, including the planned branch name.
|
||||
4. **If the issue cannot be created or claimed, stop.** Do not touch files.
|
||||
|
||||
Reading the repo, running read-only status/`git log`, and creating/claiming the
|
||||
issue itself are allowed from the orchestration checkout without a prior issue.
|
||||
|
||||
Additional issue-first rules:
|
||||
|
||||
- Do not implement code without an issue unless explicitly authorized.
|
||||
- **Design-only work uses a discussion/RFC issue** — create one or comment on
|
||||
the existing one. Design debates belong on the issue, where other LLMs
|
||||
comment directly. Discussion-only tasks must **not** create branches or PRs;
|
||||
their comments should include recommendations, risks, open questions, and a
|
||||
Controller Handoff (§K; compact format unless high-risk).
|
||||
- **If the repo/tracker home for the work is unclear, stop and ask for an
|
||||
owner decision.** Do not create a new repository or a new tracker unless
|
||||
explicitly approved by the owner.
|
||||
- **Reconciled**: Verified whether closed-not-merged or already-landed content
|
||||
is present on the target branch; issue/label/tracker state repaired.
|
||||
|
||||
## Work Selection Rule for LLMs
|
||||
|
||||
Before starting any issue or PR work, acquire or verify a work lease.
|
||||
|
||||
Do not begin coding, reviewing, fixing, branching, committing, pushing,
|
||||
commenting, or creating a PR until you prove the target is not already being
|
||||
worked.
|
||||
Before starting any issue or PR work, acquire or verify a work lease. Do not
|
||||
begin coding, reviewing, fixing, branching, committing, pushing, commenting, or
|
||||
creating a PR until you prove the target is not already being worked.
|
||||
|
||||
Required checks:
|
||||
|
||||
@@ -95,540 +80,75 @@ Required checks:
|
||||
6. Check active leases or recent handoffs.
|
||||
7. Check whether the issue was already completed by a merged PR.
|
||||
|
||||
If another active LLM/session owns the lease, stop.
|
||||
|
||||
Allowed responses:
|
||||
|
||||
- continue as the lease owner,
|
||||
- review the existing PR if reviewer capability allows,
|
||||
- produce a handoff,
|
||||
- request takeover after lease expiry,
|
||||
- stop with "work already claimed."
|
||||
|
||||
Never create a parallel branch or PR for the same issue unless the old branch
|
||||
is proven abandoned and the takeover is recorded.
|
||||
If another active session owns the lease, stop with "work already claimed" or
|
||||
produce a handoff.
|
||||
|
||||
For Gitea-Tools: `gitea_lock_issue` is the fail-closed lease gate before author
|
||||
mutations; `status:in-progress` and claim comments are supporting lease signals.
|
||||
Use `review_proofs.classify_issue_for_selection` when reporting fresh issue
|
||||
selection (#188).
|
||||
|
||||
## Global LLM Worktree Rule
|
||||
|
||||
The main project checkout is a stable control checkout. It must stay on the
|
||||
configured stable branch: `master`, `main`, or `dev`.
|
||||
The main project checkout is a stable control checkout on `master`, `main`, or
|
||||
`dev`. All LLM task work must happen inside the project's `branches/` directory.
|
||||
|
||||
All LLM task work must happen inside the project's `branches/` directory.
|
||||
|
||||
Before any mutation, prove:
|
||||
|
||||
1. current project root
|
||||
2. current working directory
|
||||
3. current branch
|
||||
4. stable branch for the main checkout
|
||||
5. session-owned worktree path under `branches/`
|
||||
|
||||
If `cwd` is not inside `branches/`, stop. Do not edit, create, delete, format,
|
||||
test-write, commit, merge, rebase, checkout task branches, resolve conflicts,
|
||||
or run cleanup.
|
||||
|
||||
There are no exceptions for small fixes, docs, tests, cleanup, PR review fixes,
|
||||
conflict resolution, or emergencies.
|
||||
|
||||
The main checkout may only be used for read-only inspection, fetching,
|
||||
stable-branch update after merged PRs, creating `branches/` worktrees, or
|
||||
explicit control-checkout repair.
|
||||
If `cwd` is not inside `branches/`, stop before any file edit, test write,
|
||||
commit, merge, rebase, or cleanup. The main checkout is orchestration-only.
|
||||
|
||||
## Shell Spawn Hard-Stop Rule
|
||||
|
||||
A shell tool result of `exit_code: -1` with empty stdout/stderr means the
|
||||
executor failed to spawn — it is not a command failure, and retrying the same
|
||||
call cannot succeed.
|
||||
`exit_code: -1` with empty stdout/stderr means the shell failed to spawn — not a
|
||||
command failure. After two consecutive spawn failures, hard-stop shell use for
|
||||
the session and emit a recovery report (#258).
|
||||
|
||||
1. On the first spawn failure, run one trivial probe (`echo ok` or `pwd`).
|
||||
If the probe also fails, mark shell unavailable for the session.
|
||||
2. After two consecutive spawn failures, hard-stop all further shell tool
|
||||
use for the session. Never retry the same failing spawn.
|
||||
3. Stop and emit a recovery report instead of improvising fallbacks. The
|
||||
report must tell the operator to: restart the session, kill hung
|
||||
background terminals (a hung test runner is a known contributor), and
|
||||
prefer MCP-native paths (for example `gitea_commit_files`) for any
|
||||
remaining mutations.
|
||||
## Isolated worktree naming
|
||||
|
||||
Retry spirals are a real failure mode (issue #258: 100+ tool calls on a
|
||||
trivial encode-and-commit task). The hard-stop is fail-closed: no shell means
|
||||
stop-and-report, not workarounds.
|
||||
Implementation: `(fix|feat|docs|chore)/issue-<number>-<short-description>`
|
||||
|
||||
## B. Isolated worktree rule
|
||||
Review: `review/pr-<number>-<short-description>`
|
||||
|
||||
**Never implement or review in the main checkout** (Global LLM Worktree Rule).
|
||||
The main checkout is for orchestration and status only (issue creation,
|
||||
`git status`, creating worktrees) and must remain on the stable branch.
|
||||
Worktree folder: branch with `/` replaced by `-` under `branches/`.
|
||||
|
||||
- Each issue gets its own branch worktree under an ignored `branches/` directory.
|
||||
- Review work uses a **separate** review worktree, never the author's folder.
|
||||
- Dirty work in one branch folder must not block starting another issue.
|
||||
- No LLM may edit another issue's worktree unless explicitly assigned to it.
|
||||
- Branch folders are removed only after the PR is merged/closed **and** cleanup
|
||||
is explicitly part of the task.
|
||||
Helpers: `scripts/worktree-start`, `scripts/worktree-review`,
|
||||
`scripts/worktree-clean`.
|
||||
|
||||
Every implementation branch **must include its issue number** so it is
|
||||
traceable end to end: **issue → branch → worktree folder → PR → cleanup.**
|
||||
## Identity and profile safety
|
||||
|
||||
Allowed implementation patterns:
|
||||
- Author and reviewer identities must be distinct.
|
||||
- Never place raw tokens in LLM/MCP config.
|
||||
- Use `gitea_whoami` and `gitea_resolve_task_capability` before mutating.
|
||||
|
||||
- `fix/issue-123-short-description`
|
||||
- `feat/issue-123-short-description`
|
||||
- `docs/issue-123-short-description`
|
||||
- `chore/issue-123-short-description`
|
||||
|
||||
Review-only branches:
|
||||
|
||||
- `review/pr-456-short-description`
|
||||
|
||||
Use a filesystem-safe folder under `branches/` by replacing slashes with
|
||||
hyphens, for example `branches/fix-issue-123-short-description`.
|
||||
|
||||
`scripts/worktree-start` **enforces** this: it rejects an implementation branch
|
||||
that does not match `(fix|feat|docs|chore)/issue-<number>-…` (or a
|
||||
`review/pr-<number>-…` branch), unless `--allow-unlinked` is passed. Traceability
|
||||
is maintained by:
|
||||
|
||||
- the branch name (contains the issue number),
|
||||
- a claim comment on the issue, e.g.
|
||||
`Claimed. Branch: fix/issue-123-short-description. Worktree: branches/fix-issue-123-short-description.`,
|
||||
- the PR body — `Closes #123` or `Fixes #123` when the PR should close the issue
|
||||
(do NOT use `Implements #123` or `Refs #123` to close, as Gitea will not auto-close),
|
||||
- cleanup after merge — remove the remote branch, local branch, and the issue
|
||||
worktree folder, and drop `status:in-progress`.
|
||||
|
||||
For projects using `Gitea-Tools` helpers:
|
||||
|
||||
```bash
|
||||
scripts/worktree-start fix/issue-123-example # → branches/fix-issue-123-example
|
||||
scripts/worktree-review fix/issue-123-example # → branches/review-fix-issue-123-example (detached)
|
||||
scripts/worktree-clean --delete-branch fix/issue-123-example
|
||||
```
|
||||
|
||||
Manual equivalent:
|
||||
|
||||
```bash
|
||||
git fetch <remote> --prune
|
||||
git worktree add -b fix/issue-123-example branches/fix-issue-123-example <remote>/master
|
||||
cd branches/fix-issue-123-example
|
||||
```
|
||||
|
||||
`venv/` and similar are not copied into new worktrees — run checks with a known
|
||||
interpreter path, or create a venv inside the branch folder.
|
||||
|
||||
## C. Identity and profile safety
|
||||
|
||||
- Use canonical execution profiles where available; the profile is the role, not the LLM. A task selects a profile; a profile is not permanently assigned.
|
||||
- **Author and reviewer identities must be distinct.**
|
||||
- Never place raw tokens/passwords in an LLM/MCP client config. Reference secrets by keychain id or environment variable name only. Prefer a single canonical config file selected by two env vars, e.g.:
|
||||
- `GITEA_MCP_CONFIG` — path to the canonical profiles file
|
||||
- `GITEA_MCP_PROFILE` — the profile to activate
|
||||
- **Dual-Profile MCP Launcher Pattern (Recommended):** To avoid relaunch bottlenecks and PR-author deadlocks, register multiple instances of the same MCP server in the client's configuration simultaneously (e.g., `gitea-author` and `gitea-reviewer`), each pointing to its respective `GITEA_MCP_PROFILE`.
|
||||
- Tool calls become namespace-scoped: `mcp__gitea-author__*` and `mcp__gitea-reviewer__*`.
|
||||
- **Trust Model:** Separate tokens remain separate. Profile gates enforce allowed operations, `whoami` is still checked, and self-review/self-merge prevention remains mandatory. This pattern is for convenience and does not bypass security gates.
|
||||
- **Deadlock Warning:** Reviewer/merge identities must not be used to create PRs, as this makes the reviewer the PR author in Gitea and blocks independent review. PRs should normally be created by the author/work identity, keeping the reviewer identity available for reviews.
|
||||
- **Fallback:** If a dual-server launcher is not available in the client, relaunch or restart the client with the correct profile environment variable before claiming work.
|
||||
- **If the authenticated user equals the PR author, stop** — no self-review, no self-merge.
|
||||
|
||||
## D. Branch naming
|
||||
|
||||
```text
|
||||
fix/issue-123-short-description
|
||||
feat/issue-123-short-description
|
||||
docs/issue-123-short-description
|
||||
review/pr-456-scope-check
|
||||
```
|
||||
|
||||
Worktree folder = branch with `/` replaced by `-`
|
||||
(`branches/fix-issue-123-short-description`).
|
||||
|
||||
## E. Start-work workflow
|
||||
|
||||
0. Acquire or verify a work lease (Work Selection Rule) — complete all seven
|
||||
checks before any claim, branch, or PR work.
|
||||
0b. Global LLM Worktree Rule — prove project root, `cwd`, branch, main-checkout
|
||||
stable branch, and session-owned `branches/` worktree path. If `cwd` is not
|
||||
under `branches/`, stop before any mutation (no exceptions).
|
||||
1. Verify the orchestration checkout (right repo, clean tree, on stable branch).
|
||||
2. Fetch/prune: `git fetch <remote> --prune`.
|
||||
3. Confirm local `master` equals remote `master` (`git rev-list --left-right --count <remote>/master...master` → `0 0`).
|
||||
4. Create/claim the issue (§A).
|
||||
4b. **Issue lock from your scratch clone (#249):** when using
|
||||
`gitea_lock_issue`, pass `worktree_path` pointing at your own clean
|
||||
scratch clone (or set `GITEA_AUTHOR_WORKTREE`). The lock gate validates
|
||||
*that* path — clean tree on `master`/`main`, no tracked edits yet —
|
||||
not the shared MCP/orchestration checkout. Another session's dirty
|
||||
feature branch in the shared dev worktree must not block your lock.
|
||||
Never stash, reset, or checkout files in the shared worktree to satisfy
|
||||
the gate. Pass the same `worktree_path` to `gitea_create_pr` so the PR
|
||||
gate matches the lock record.
|
||||
5. Create the isolated worktree (§B) from latest remote `master`.
|
||||
6. Implement the narrow scope only — no unrelated refactors or formatting churn.
|
||||
7. Add/update focused tests when behavior changes.
|
||||
8. Run the checks (tests, compile/lint, `git diff --check`, secret scan).
|
||||
Record the branch name and `HEAD` SHA at validation time — the drift
|
||||
check in step 9 compares against exactly this state.
|
||||
9. **Branch proof before commit (#177):** prove and state, immediately
|
||||
before staging/committing (`author_proofs.verify_branch_for_commit`,
|
||||
`author_proofs.detect_branch_drift`):
|
||||
- current branch (`git branch --show-current`) equals the intended
|
||||
feature branch from the issue claim
|
||||
- current branch is not `master`, `main`, `develop`, `development`, or
|
||||
`dev`
|
||||
- branch and `HEAD` have not changed since validation (step 8) — in a
|
||||
shared checkout another session may switch branches mid-session;
|
||||
treat that as expected and **stop before committing** when detected
|
||||
If any check fails, stop and reconcile; do not commit.
|
||||
10. Commit with an issue-linked message.
|
||||
11. **Branch proof before push (#177):** prove that the local branch, the
|
||||
push target branch, and the intended issue branch all match, and that
|
||||
none of them is a protected branch
|
||||
(`author_proofs.verify_push_target`). If a commit accidentally landed
|
||||
on a protected branch, do **not** push: report the accident and the
|
||||
exact repair steps (`author_proofs.assess_protected_branch_commit`) —
|
||||
never silently continue after a repair.
|
||||
12. Push the branch.
|
||||
13. Open a PR to `master`. The final report must include the branch proofs
|
||||
from steps 9 and 11 (`author_proofs.build_commit_push_report`).
|
||||
14. **If you are the author, stop before review/merge.**
|
||||
15. **Normal issue work must not directly push to `master`.** PR content should be merged through the forge PR merge mechanism.
|
||||
16. Direct push to `master` is allowed only as a documented recovery exception. If used, the final report must include:
|
||||
- why the PR merge path could not be used
|
||||
- exact commits pushed
|
||||
- PR metadata state
|
||||
- issue labels/state repaired
|
||||
- whether the PR is closed-not-merged
|
||||
|
||||
|
||||
## F. Review workflow
|
||||
|
||||
Moved to [`workflows/review-merge-pr.md`](workflows/review-merge-pr.md) (#334).
|
||||
Load that file for PR inventory, validation, review mutation, merge preflight,
|
||||
cleanup, and reviewer hard walls. Final report:
|
||||
[`schemas/review-merge-final-report.md`](schemas/review-merge-final-report.md).
|
||||
|
||||
## G. Merge / cleanup workflow
|
||||
|
||||
Moved to [`workflows/review-merge-pr.md`](workflows/review-merge-pr.md) §6–§7
|
||||
and [`templates/worktree-cleanup.md`](templates/worktree-cleanup.md). Load the
|
||||
workflow file before any merge mutation.
|
||||
|
||||
## H. Fail-closed cases
|
||||
|
||||
**Stop and report — take no mutating action — if:**
|
||||
|
||||
- No issue exists and one cannot be created.
|
||||
- Worktree state is unclear or unexpected.
|
||||
- Branch/PR state conflicts with the prompt (e.g. prompt says "merged" but it is not).
|
||||
- A PR is closed but not merged (closed with `merged=false`). In this case:
|
||||
- stop normal review/merge
|
||||
- do not delete branches/worktrees
|
||||
- do not start dependent work
|
||||
- run reconciliation
|
||||
- Local `master` is ahead of remote unexpectedly.
|
||||
- The authenticated user is the PR author (for review/merge).
|
||||
- Secrets/tokens appear in the diff.
|
||||
- Tests fail.
|
||||
- A cleanup step would delete unmerged work.
|
||||
|
||||
When in doubt, stop and surface the discrepancy; do not guess or work around a gate.
|
||||
|
||||
## I. Recovery patterns
|
||||
|
||||
- **Dirty worktree from another issue:** do not touch it. Start your issue in its
|
||||
own new worktree; unrelated dirty work must not block you. For Gitea-Tools
|
||||
author flows, lock the issue from your scratch clone (`worktree_path` on
|
||||
`gitea_lock_issue`) — do not manipulate the shared dev checkout.
|
||||
- **Local `master` ahead of remote unexpectedly:** do not push `master`. Confirm
|
||||
the commits are preserved on a feature branch (local + remote) first, then
|
||||
`git reset --hard <remote>/master` to realign. Never discard commits that are
|
||||
not safely pushed elsewhere.
|
||||
- **PR closed but not merged (`merged=false`):** do not merge. Run reconciliation: compare PR content to remote `master` and decide:
|
||||
- **fully landed:** comment that content is present on `master`, remove `status:in-progress`, keep/close issue as appropriate, clean up only after content equivalence is confirmed.
|
||||
- **partially landed:** do not clean up, reopen issue if needed, create corrective issue/PR for missing pieces.
|
||||
- **not landed:** reopen issue if needed, reopen PR or create replacement PR, do not clean up source branch/worktree.
|
||||
- **Branch deleted before merge:** if the commits still exist locally (a branch or
|
||||
reflog), re-push them and reopen the PR; otherwise recover via
|
||||
`git fsck --lost-found`. Preserve first, then proceed.
|
||||
- **Unauthorized/untracked file created:** do not commit it. Leave pre-existing
|
||||
untracked artifacts (e.g. editor/agent dirs, reports) alone; stage only the
|
||||
files your issue names (`git add <files>`, never blind `git add -A`).
|
||||
- **Preserve commits before a reset:** confirm the target commits are reachable
|
||||
from a branch that is pushed to the remote, then reset. Verify with
|
||||
`git branch --contains <sha>` and `git log <remote>/<branch>`.
|
||||
|
||||
## J. Prompt snippets
|
||||
|
||||
Ready-to-copy templates live in [`templates/`](templates/):
|
||||
|
||||
- [`start-issue.md`](templates/start-issue.md) — start a new issue.
|
||||
- [`review-pr.md`](templates/review-pr.md) — review a PR.
|
||||
- [`merge-pr.md`](templates/merge-pr.md) — merge a PR (eligible reviewer only).
|
||||
- [`recover-bad-state.md`](templates/recover-bad-state.md) — recover from bad state.
|
||||
- [`reconcile-closed-not-merged-pr.md`](templates/reconcile-closed-not-merged-pr.md) — reconcile a closed-not-merged PR.
|
||||
- [`worktree-cleanup.md`](templates/worktree-cleanup.md) — clean up after merge.
|
||||
- [`release-tag.md`](templates/release-tag.md) — create a release tag.
|
||||
|
||||
## K. Controller Handoff (required, every task)
|
||||
|
||||
Every LLM task **must end with a `Controller Handoff`** (exact title) — whether the
|
||||
task was implementation, review, merge, issue triage, documentation,
|
||||
discussion-only, or blocked planning. It lets a controller LLM understand the
|
||||
current state immediately, without rereading the conversation.
|
||||
|
||||
The section title must be exactly "Controller Handoff" (or "Controller Handoff Summary" for long form). Reports without it are downgraded (see review_proofs.assess_controller_handoff).
|
||||
|
||||
**The compact format is the default.** It is written for controller-LLM
|
||||
readability, not as a full human status report. PR bodies still carry the
|
||||
full review detail — the handoff never replaces PR documentation.
|
||||
|
||||
Compact format (default, canonical field set per issue #182):
|
||||
|
||||
```md
|
||||
## Controller Handoff
|
||||
|
||||
- Task:
|
||||
- Repo:
|
||||
- Role:
|
||||
- Identity:
|
||||
- Issue/PR:
|
||||
- Branch/SHA:
|
||||
- Files changed:
|
||||
- Validation:
|
||||
- Mutations:
|
||||
- Current status:
|
||||
- Blockers:
|
||||
- Next:
|
||||
- Safety:
|
||||
```
|
||||
Every task must end with a section titled exactly `Controller Handoff`. Compact
|
||||
format canonical field set per issue #182; mode-specific schemas in
|
||||
`schemas/*-final-report.md` define required fields. Use the final report schema
|
||||
for the loaded workflow mode — not the legacy compact block alone.
|
||||
`review_proofs.assess_controller_handoff()` validates presence.
|
||||
|
||||
Role-specific fields (append to the compact block):
|
||||
## Prompt templates
|
||||
|
||||
- review/merge tasks: use
|
||||
[`schemas/review-merge-final-report.md`](schemas/review-merge-final-report.md)
|
||||
(includes `Selected PR:`, `Reviewer eligibility:`, `Pinned reviewed head:`,
|
||||
`Review decision:`, `Merge result:`, `Linked issue status:`, `Cleanup status:`,
|
||||
and precise mutation categories — not `Workspace mutations`)
|
||||
- issue-filing tasks (#191): `Issue created or updated:`, `Related issues:`;
|
||||
body must cite exact issue number/title, duplicate-search summary (issues
|
||||
searched, closest matches, why update rejected / new issue justified), full
|
||||
40-char SHAs when citing commits, exact mutation capability per change, and
|
||||
`Only mutation(s):` when a single mutation was performed
|
||||
(`review_proofs.assess_issue_filing_final_report`).
|
||||
- author tasks: `Selected issue:`, `Claim/comment status:`,
|
||||
`PR number opened:`, `No review/merge:` (explicit confirmation)
|
||||
- continuation tasks (#188): `Continuation mode:`, `Existing PR:`,
|
||||
`PR author:`, `Branch:`, `Old PR head:`, `New PR head:`,
|
||||
`Session authored PR:`, `Why continuation allowed:` — issues with open
|
||||
PRs are excluded from fresh selection unless operator explicitly requests
|
||||
continuation (`review_proofs.classify_issue_for_selection`,
|
||||
`assess_issue_selection_final_report`)
|
||||
- queue/inventory tasks: `Repositories checked:`, `Open PR counts:`,
|
||||
`Selected PR or reason none selected:`, `Inventory completeness:`
|
||||
Ready-to-copy task prompts live in [`templates/`](templates/):
|
||||
|
||||
The section title must be exactly `Controller Handoff`.
|
||||
`review_proofs.assess_controller_handoff()` validates this section; reports
|
||||
missing it (or missing required fields) are downgraded. The handoff never
|
||||
replaces the full report — it is the compact continuation summary at the end,
|
||||
and the full report must still carry exact validation results and mutation
|
||||
confirmation.
|
||||
|
||||
The `Safety:` line is never omitted; it is usually:
|
||||
|
||||
```text
|
||||
no self-review; no self-merge; no tags; no secrets; no prod
|
||||
```
|
||||
|
||||
Rules (both formats):
|
||||
|
||||
- Never omit the handoff, and never omit the safety confirmations.
|
||||
- Never bury blockers in earlier text only — they must appear here.
|
||||
- If you opened a PR, state clearly that review is needed.
|
||||
- If you reviewed but could not merge, name the exact gate that blocked it.
|
||||
- If you only commented on a discussion issue, say no code review is needed
|
||||
but owner/design feedback may be needed.
|
||||
- If release state was touched, state exactly which tag/commit changed and why.
|
||||
- If blocked (permissions, missing repo, missing second reviewer identity,
|
||||
stale dependency, unclear tracker home): stop and report clearly; **never
|
||||
bypass classifiers, profile gates, missing permissions, or live-consent
|
||||
requirements**; give the owner concrete options.
|
||||
|
||||
**Use the long format below instead of the compact one only when the task was
|
||||
high-risk or complex** — i.e. when any of these happened:
|
||||
|
||||
- a merge, tag, or release
|
||||
- failed validation
|
||||
- permissions/profile gates blocked work
|
||||
- secrets or production access were involved
|
||||
- a complicated owner decision
|
||||
- multiple repos or cross-issue state
|
||||
- the owner explicitly asks for the full format
|
||||
|
||||
Long format (high-risk/complex tasks only):
|
||||
|
||||
```md
|
||||
## Controller Handoff Summary
|
||||
|
||||
### Work performed
|
||||
|
||||
Briefly state what was done.
|
||||
|
||||
### Current state
|
||||
|
||||
Include:
|
||||
- current repo
|
||||
- current branch or master commit
|
||||
- issue number(s)
|
||||
- PR number(s), if any
|
||||
- whether work is complete, blocked, ready for review, or discussion-only
|
||||
|
||||
### Files changed
|
||||
|
||||
List files changed, or say `None`.
|
||||
|
||||
### Validation
|
||||
|
||||
List commands run and results, or say `Not applicable — discussion only`.
|
||||
|
||||
### Issues encountered
|
||||
|
||||
List errors, confusing state, permission/profile problems, stale branches,
|
||||
failing tests, missing labels, or blocked decisions.
|
||||
|
||||
### Review needed?
|
||||
|
||||
Say one of:
|
||||
- `No review needed — discussion/comment only`
|
||||
- `Review needed — PR is open`
|
||||
- `Independent non-author review needed`
|
||||
- `Owner decision needed`
|
||||
- `Blocked`
|
||||
|
||||
### Next recommended action
|
||||
|
||||
State exactly what should happen next.
|
||||
|
||||
### Safety confirmations
|
||||
|
||||
Confirm:
|
||||
- no self-review
|
||||
- no self-merge
|
||||
- no release/tag changes unless explicitly requested
|
||||
- no secrets committed
|
||||
- no production access used unless explicitly authorized
|
||||
```
|
||||
|
||||
### Example blocked handoff
|
||||
|
||||
```md
|
||||
## Example blocked handoff
|
||||
|
||||
### Work performed
|
||||
|
||||
Audited phase-2 MCP Control Plane planning. Found target repo
|
||||
`mcp-control-plane` does not exist. Prepared issue pack but did not file it.
|
||||
|
||||
### Current state
|
||||
|
||||
- Repo: `Scaled-Tech-Consulting/Gitea-Tools`, unmodified
|
||||
- Target repo: `mcp-control-plane`, missing
|
||||
- Issues: none open in Gitea-Tools
|
||||
- PRs: none open
|
||||
- Status: blocked pending owner decision
|
||||
|
||||
### Files changed
|
||||
|
||||
None.
|
||||
|
||||
### Validation
|
||||
|
||||
Tracker/repo audit only. No code validation required.
|
||||
|
||||
### Issues encountered
|
||||
|
||||
Repo creation was denied by permission/classifier because it would be scope
|
||||
escalation without live consent.
|
||||
|
||||
### Review needed?
|
||||
|
||||
Owner decision needed.
|
||||
|
||||
### Next recommended action
|
||||
|
||||
Owner must choose:
|
||||
1. create `Scaled-Tech-Consulting/mcp-control-plane`
|
||||
2. authorize repo creation while present
|
||||
3. file phase-2 issues in Gitea-Tools instead
|
||||
|
||||
### Safety confirmations
|
||||
|
||||
- no self-review
|
||||
- no self-merge
|
||||
- no release/tag changes
|
||||
- no secrets committed
|
||||
- no production access used
|
||||
```
|
||||
- [`start-issue.md`](templates/start-issue.md) — author work (loads `work-issue.md`)
|
||||
- [`review-pr.md`](templates/review-pr.md) — review (loads `review-merge-pr.md`)
|
||||
- [`merge-pr.md`](templates/merge-pr.md) — merge (loads `review-merge-pr.md`)
|
||||
- [`recover-bad-state.md`](templates/recover-bad-state.md)
|
||||
- [`reconcile-closed-not-merged-pr.md`](templates/reconcile-closed-not-merged-pr.md)
|
||||
- [`worktree-cleanup.md`](templates/worktree-cleanup.md)
|
||||
- [`release-tag.md`](templates/release-tag.md)
|
||||
|
||||
## Adapting to a project
|
||||
|
||||
Replace these project-specific names when copying the skill elsewhere:
|
||||
|
||||
| Placeholder | Meaning | Example here |
|
||||
|-------------|---------|--------------|
|
||||
| `<remote>` | Git remote for the forge | `prgs` |
|
||||
| default branch | Integration branch | `master` |
|
||||
| profile env vars | Canonical config + profile selectors | `GITEA_MCP_CONFIG`, `GITEA_MCP_PROFILE` |
|
||||
| `branches/` | Ignored worktree directory | `branches/` |
|
||||
| helper scripts | Worktree helpers | `scripts/worktree-start` / `-review` / `-clean` |
|
||||
|
||||
The rules in §A–§K are project-agnostic and should not change.
|
||||
| Placeholder | Example here |
|
||||
|-------------|--------------|
|
||||
| `<remote>` | `prgs` |
|
||||
| default branch | `master` |
|
||||
| profile env vars | `GITEA_MCP_CONFIG`, `GITEA_MCP_PROFILE` |
|
||||
| `branches/` | `branches/` |
|
||||
| helpers | `scripts/worktree-start` / `-review` / `-clean` |
|
||||
|
||||
## Versioning And Tagging
|
||||
|
||||
Releases follow SemVer: **`vMAJOR.MINOR.PATCH`** (use **`v0.x.y`** while
|
||||
unstable). Choose the bump by the largest change since the last tag:
|
||||
|
||||
- **PATCH** — bug fixes, docs, tests, wrappers, non-breaking workflow polish.
|
||||
- **MINOR** — new tools/helpers/config features; backward-compatible behavior.
|
||||
- **MAJOR** — breaking config/schema/API behavior or a changed MCP contract.
|
||||
|
||||
Tags must:
|
||||
|
||||
- be created **only from `master`** (the exact commit on remote `master`),
|
||||
- be created **only after the full test suite passes**,
|
||||
- be **annotated** tags (`git tag -a`), never lightweight,
|
||||
- include release notes / a changelog summary referencing the merged PRs/issues.
|
||||
|
||||
**Never tag** feature branches, dirty worktrees, unreviewed or self-authored
|
||||
work, or commits not present on remote `master`.
|
||||
|
||||
Additional tag rules:
|
||||
|
||||
- Do **not** create, move, delete, or push tags unless explicitly instructed.
|
||||
- Tag only **after** the intended PR is merged, and tag only the **verified
|
||||
final master merge commit** (never the PR branch head unless the merge
|
||||
commit is exactly that commit).
|
||||
- Always **report the tag target commit** in the final report / handoff.
|
||||
|
||||
Release process (see [`templates/release-tag.md`](templates/release-tag.md)):
|
||||
|
||||
1. `git fetch <remote> --prune`.
|
||||
2. Verify local `master` equals remote `master` (`0 0`) and the tree is clean.
|
||||
3. Run the full test suite; stop on any failure.
|
||||
4. Inspect merged issues/PRs since the last tag
|
||||
(`git log --oneline <last-tag>..<remote>/master`).
|
||||
5. Choose the version bump.
|
||||
6. Create the annotated tag on remote `master` with release notes.
|
||||
7. Push the tag.
|
||||
8. Create/update release notes if the forge supports it.
|
||||
|
||||
Where present, `scripts/release-tag` automates this with all gates built in
|
||||
(SemVer, fetch/prune, on-master, clean tree, local==remote master, HEAD on
|
||||
remote master, no duplicate tag, tests, annotated-only). Safe by default: no
|
||||
push without `--push`; `--dry-run` changes nothing; `--skip-tests` must be
|
||||
explicit and warns.
|
||||
Releases follow SemVer from remote `master` only, after full test suite passes.
|
||||
See [`templates/release-tag.md`](templates/release-tag.md) and
|
||||
`scripts/release-tag`.
|
||||
@@ -0,0 +1,46 @@
|
||||
# Create-issue controller handoff schema
|
||||
|
||||
**Task mode:** `create-issue`
|
||||
|
||||
End every create-issue run with a section titled exactly `Controller Handoff`.
|
||||
Use this canonical field set. Do not omit fields — use `none` or
|
||||
`not verified in this session` where appropriate.
|
||||
|
||||
Do not use legacy fields: `Workspace mutations`, `Mutations: None` (when
|
||||
mutations occurred).
|
||||
|
||||
```md
|
||||
## Controller Handoff
|
||||
|
||||
- Task:
|
||||
- Repo:
|
||||
- Role:
|
||||
- Identity:
|
||||
- Active profile:
|
||||
- Runtime context:
|
||||
- Requested issue task:
|
||||
- Workflow source:
|
||||
- Capability proof:
|
||||
- Duplicate search terms:
|
||||
- Duplicate search pagination proof:
|
||||
- Duplicates found:
|
||||
- Issues created:
|
||||
- Issues commented:
|
||||
- Issues edited:
|
||||
- Issues skipped as duplicates:
|
||||
- Labels/assignees/milestones changed:
|
||||
- File edits by issue creator:
|
||||
- Worktree/index mutations:
|
||||
- Git ref mutations:
|
||||
- MCP/Gitea mutations:
|
||||
- Issue mutations:
|
||||
- Label/assignment/milestone mutations:
|
||||
- External-state mutations:
|
||||
- Read-only diagnostics:
|
||||
- Blockers:
|
||||
- Current status:
|
||||
- Safe next action:
|
||||
- Safety statement:
|
||||
```
|
||||
|
||||
Identity format: `username / profile` (not personal email unless required — #305).
|
||||
@@ -0,0 +1,53 @@
|
||||
# Reconcile-landed controller handoff schema
|
||||
|
||||
**Task mode:** `reconcile-landed-pr`
|
||||
|
||||
End every reconciliation run with a section titled exactly `Controller Handoff`.
|
||||
Use this canonical field set. Do not omit fields — use `none` or
|
||||
`not verified in this session` where appropriate.
|
||||
|
||||
Reject stale author/reviewer fields: `PR number opened`, `Pinned reviewed head`,
|
||||
`Scratch worktree used`, `Workspace mutations`, `Mutations: None` (when mutations
|
||||
occurred).
|
||||
|
||||
```md
|
||||
## Controller Handoff
|
||||
|
||||
- Task:
|
||||
- Repo:
|
||||
- Role:
|
||||
- Identity:
|
||||
- Active profile:
|
||||
- Runtime context:
|
||||
- Selected PR:
|
||||
- PR live state:
|
||||
- Candidate head SHA:
|
||||
- Target branch:
|
||||
- Target branch SHA:
|
||||
- Ancestor proof:
|
||||
- Linked issue:
|
||||
- Linked issue live status:
|
||||
- Eligibility class:
|
||||
- Capabilities proven:
|
||||
- Missing capabilities:
|
||||
- PR comments posted:
|
||||
- Issue comments posted:
|
||||
- PRs closed:
|
||||
- Issues closed:
|
||||
- File edits by reconciler:
|
||||
- Worktree/index mutations:
|
||||
- Git ref mutations:
|
||||
- MCP/Gitea mutations:
|
||||
- Reconciliation mutations:
|
||||
- External-state mutations:
|
||||
- Read-only diagnostics:
|
||||
- Blockers:
|
||||
- Current status:
|
||||
- Safe next action:
|
||||
- Safety statement:
|
||||
- No review/merge confirmation:
|
||||
```
|
||||
|
||||
Identity format: `username / profile` (not personal email unless required — #305).
|
||||
|
||||
`git fetch` belongs under `Git ref mutations`, not read-only diagnostics (#297).
|
||||
@@ -0,0 +1,73 @@
|
||||
# Work-issue controller handoff schema
|
||||
|
||||
**Task mode:** `work-issue`
|
||||
|
||||
End every work-issue run with a section titled exactly `Controller Handoff`.
|
||||
Use this canonical field set. Do not omit fields — use `none` or
|
||||
`not verified in this session` where appropriate.
|
||||
|
||||
Do not use legacy fields: `Workspace mutations`, `Mutations: None` (when
|
||||
mutations occurred).
|
||||
|
||||
```md
|
||||
## Controller Handoff
|
||||
|
||||
- Task:
|
||||
- Repo:
|
||||
- Role:
|
||||
- Identity:
|
||||
- Active profile:
|
||||
- Runtime context:
|
||||
- Selected issue:
|
||||
- Eligibility class:
|
||||
- Issue ordering policy:
|
||||
- Issue inventory pagination proof:
|
||||
- Earlier issues skipped:
|
||||
- Duplicate active work proof:
|
||||
- Claim/lock state:
|
||||
- Stable branch:
|
||||
- Stable branch SHA:
|
||||
- Branch name:
|
||||
- Worktree path:
|
||||
- Worktree inside branches:
|
||||
- Worktree branch/HEAD state:
|
||||
- Worktree dirty before implementation:
|
||||
- Files changed:
|
||||
- Validation:
|
||||
- Baseline comparison:
|
||||
- Commit SHA:
|
||||
- Push result:
|
||||
- PR number:
|
||||
- PR URL:
|
||||
- PR verification:
|
||||
- Main checkout branch:
|
||||
- Main checkout dirty state:
|
||||
- Main checkout used for task work:
|
||||
- File edits by author:
|
||||
- Worktree/index mutations:
|
||||
- Git ref mutations:
|
||||
- MCP/Gitea mutations:
|
||||
- Issue mutations:
|
||||
- Branch mutations:
|
||||
- Commit mutations:
|
||||
- Push mutations:
|
||||
- PR mutations:
|
||||
- Cleanup mutations:
|
||||
- External-state mutations:
|
||||
- Read-only diagnostics:
|
||||
- Blockers:
|
||||
- Current status:
|
||||
- Safe next action:
|
||||
- Safety statement:
|
||||
```
|
||||
|
||||
Identity format: `username / profile` (not personal email unless required — #305).
|
||||
|
||||
Narrative final report and controller handoff must agree on eligibility class,
|
||||
selected issue, and mutation ledger categories (#319, #320).
|
||||
|
||||
`git fetch` and ref-updating commands belong under `Git ref mutations`, not
|
||||
`Read-only diagnostics` (#297).
|
||||
|
||||
Forbidden claims without proof (#330): `next eligible issue`, `issue claimed`,
|
||||
`validation passed`, `PR created`, `worktree clean`, `all gates passed`, etc.
|
||||
@@ -5,6 +5,10 @@ Copy, fill the `<...>` fields, and paste as the task prompt.
|
||||
```text
|
||||
Task: implement <issue title / one-line goal>.
|
||||
|
||||
Load canonical workflow: skills/llm-project-workflow/workflows/work-issue.md
|
||||
Final report schema: skills/llm-project-workflow/schemas/work-issue-final-report.md
|
||||
Router: skills/llm-project-workflow/SKILL.md (task mode: work-issue)
|
||||
|
||||
Rules (llm-project-workflow):
|
||||
- No repo changes without a tracking issue. If none exists, create one first;
|
||||
if it can't be created, stop.
|
||||
|
||||
@@ -0,0 +1,666 @@
|
||||
---
|
||||
task_mode: create-issue
|
||||
canonical: true
|
||||
final_report_schema: ../schemas/create-issue-final-report.md
|
||||
---
|
||||
|
||||
# Create issue workflow (canonical)
|
||||
|
||||
**Task mode:** `create-issue`
|
||||
|
||||
This file is the canonical issue-creation workflow for Gitea-Tools. Load it
|
||||
before any issue mutation. Final report schema:
|
||||
[`schemas/create-issue-final-report.md`](../schemas/create-issue-final-report.md).
|
||||
|
||||
**Default task prompt:**
|
||||
|
||||
> Create or update Gitea issues in this project only if every identity,
|
||||
> capability, duplicate-search, issue-scope, final-report, mutation-ledger,
|
||||
> and proof-wording gate passes.
|
||||
|
||||
Do not improvise around the gates. Follow project skills, MCP gates, and
|
||||
workflow rules exactly.
|
||||
|
||||
This is an issue-creation workflow. It is not a reviewer workflow and not an
|
||||
implementation workflow.
|
||||
|
||||
---
|
||||
|
||||
## 0. Load the canonical workflow first
|
||||
|
||||
Before starting issue creation or issue update work, check whether the project provides a canonical create-issue workflow through a project skill, runbook, or MCP helper.
|
||||
|
||||
If available, load it first and report:
|
||||
|
||||
* workflow source
|
||||
* workflow version, commit, or hash
|
||||
* whether this prompt conflicts with the loaded workflow
|
||||
|
||||
If the canonical workflow cannot be loaded and the project requires it, stop and produce a recovery handoff only.
|
||||
|
||||
## 1. Mode isolation
|
||||
|
||||
This run is `create-issue` mode only.
|
||||
|
||||
Do not:
|
||||
|
||||
* review PRs
|
||||
* approve PRs
|
||||
* request changes
|
||||
* merge PRs
|
||||
* close PRs
|
||||
* close issues unless the user explicitly asks and exact close capability is proven
|
||||
* implement code
|
||||
* edit repo files
|
||||
* create branches
|
||||
* create commits
|
||||
* push branches
|
||||
* create PRs
|
||||
* run tests unless the canonical workflow explicitly requires validation for issue creation
|
||||
* perform reviewer-only actions
|
||||
* perform author/coder-only actions
|
||||
* perform MCP repair
|
||||
|
||||
If the task requires review, merge, issue implementation, or MCP repair mode, stop and produce a handoff for the correct workflow.
|
||||
|
||||
Do not mix modes in one run.
|
||||
|
||||
## 2. Start with live identity, profile, runtime, and capability checks
|
||||
|
||||
Prove:
|
||||
|
||||
* authenticated identity
|
||||
* active profile
|
||||
* repo/project
|
||||
* runtime context
|
||||
* exact capability for reading/searching issues
|
||||
* exact capability for creating issues, if creating issues
|
||||
* exact capability for commenting on issues, if commenting on existing issues
|
||||
* exact capability for editing issues, if editing existing issues
|
||||
* exact capability for applying labels, if applying labels
|
||||
* exact capability for assigning issues, if assigning issues
|
||||
* exact capability for closing issues, only if explicitly requested
|
||||
|
||||
A nearby capability does not count.
|
||||
|
||||
Examples:
|
||||
|
||||
* `create_issue` does not authorize `issue_comment`
|
||||
* `issue_comment` does not authorize `create_issue`
|
||||
* `create_pr` does not authorize `create_issue`
|
||||
* `review_pr` does not authorize `create_issue`
|
||||
* `merge_pr` does not authorize `issue_comment`
|
||||
* `gitea.read` does not authorize creating, commenting, editing, labeling, assigning, or closing issues
|
||||
|
||||
If exact capability cannot be proven, stop and produce a recovery handoff only.
|
||||
|
||||
## 3. Stop immediately on blocked infrastructure
|
||||
|
||||
If any of the following appears, stop immediately:
|
||||
|
||||
* `infra_stop`
|
||||
* MCP reconnect failure
|
||||
* stale capability state
|
||||
* missing capability
|
||||
* workspace mismatch
|
||||
* dirty control checkout, if the canonical workflow treats that as blocking
|
||||
* broken canonical workflow loading
|
||||
* failed required preflight
|
||||
* capability resolver warning that says the current state may be unsafe
|
||||
* stale or inconsistent runtime context
|
||||
|
||||
Do not continue duplicate search, issue creation, issue commenting, issue editing, labeling, assignment, or cleanup.
|
||||
|
||||
Produce an executable recovery handoff only.
|
||||
|
||||
Blocked recovery handoffs must not include direct issue-create or issue-comment replay commands.
|
||||
|
||||
Blocked handoffs must say to rerun the full workflow after the blocker clears.
|
||||
|
||||
## 4. Main checkout rule
|
||||
|
||||
This workflow should not mutate repo files.
|
||||
|
||||
Do not edit files in the main checkout.
|
||||
|
||||
Do not create branches.
|
||||
|
||||
Do not create commits.
|
||||
|
||||
Do not push.
|
||||
|
||||
Do not run implementation work.
|
||||
|
||||
Do not run reviewer validation.
|
||||
|
||||
Reading repository files is allowed only when needed to understand the requested issue and only if the canonical workflow permits it.
|
||||
|
||||
If the main checkout is dirty and the project treats dirty control checkout as blocking, stop and produce a recovery handoff.
|
||||
|
||||
## 5. No raw MCP repair during issue creation
|
||||
|
||||
Do not run `pkill`, kill MCP processes, edit MCP config, restart servers, or perform control-checkout repair during issue creation.
|
||||
|
||||
If MCP repair is required, stop issue creation and produce a separate `CONTROL-CHECKOUT REPAIR MODE` handoff.
|
||||
|
||||
Do not mix MCP repair mode with create-issue mode.
|
||||
|
||||
After repair, rerun the full workflow from the beginning.
|
||||
|
||||
## 6. No background task tools
|
||||
|
||||
Do not use `schedule`, `manage_task`, background jobs, async waits, delayed task tools, or monitoring tasks during issue creation.
|
||||
|
||||
Use direct commands and MCP tools only.
|
||||
|
||||
If a required action cannot complete synchronously, stop and produce a recovery handoff.
|
||||
|
||||
Do not say “I will check later,” “I will monitor,” or “I will continue in the background.”
|
||||
|
||||
## 7. No local Gitea fallback during normal issue creation
|
||||
|
||||
During normal issue-creation workflows, do not read Gitea profile secret files.
|
||||
|
||||
Do not inspect or open files such as:
|
||||
|
||||
* `profiles.json`
|
||||
* local token stores
|
||||
* credential files
|
||||
* local Gitea auth/profile config files
|
||||
* `.env` files containing Gitea credentials
|
||||
* keychain dumps
|
||||
* token helper outputs
|
||||
|
||||
Do not run local Gitea helper scripts when MCP tools are available.
|
||||
|
||||
Use MCP tools for Gitea operations.
|
||||
|
||||
Local fallback is allowed only in explicit recovery mode when MCP is unavailable and identity/profile/capability can be independently proven.
|
||||
|
||||
If local fallback is used, report:
|
||||
|
||||
* why MCP was unavailable
|
||||
* exact identity proof
|
||||
* exact profile proof
|
||||
* exact repo proof
|
||||
* exact capability proof
|
||||
* exact local command used
|
||||
|
||||
Do not use local fallback to bypass MCP gates.
|
||||
|
||||
## 8. Understand the requested issue work
|
||||
|
||||
Before searching or creating issues, restate the requested issue-creation task in operational terms.
|
||||
|
||||
Identify:
|
||||
|
||||
* target repo/project
|
||||
* issue topic
|
||||
* issue type, if known
|
||||
* whether this is a new issue, duplicate check, issue update, or issue-comment task
|
||||
* whether the user provided exact title/body text
|
||||
* whether acceptance criteria were provided
|
||||
* whether multiple issues are requested
|
||||
* whether labels, assignees, milestones, or links are requested
|
||||
* whether any requested action requires capability beyond issue creation
|
||||
|
||||
Do not invent missing requirements.
|
||||
|
||||
If the request is ambiguous but safe to proceed, make a reasonable best-effort issue with clear assumptions.
|
||||
|
||||
If ambiguity would cause unsafe or wrong mutation, stop and ask for clarification or produce a recovery handoff according to project policy.
|
||||
|
||||
## 9. Duplicate search before mutation
|
||||
|
||||
Before creating any issue, search open and closed issues for duplicates.
|
||||
|
||||
Duplicate search must happen before each issue creation unless a batch search clearly covers all proposed issues.
|
||||
|
||||
Search terms must include:
|
||||
|
||||
* exact proposed issue title
|
||||
* key noun phrase from the problem
|
||||
* key workflow/tool name
|
||||
* key failure phrase or error phrase
|
||||
* likely alternate wording
|
||||
* linked PR number, issue number, or file name, if relevant
|
||||
|
||||
If the user supplied search terms, use those too.
|
||||
|
||||
For each proposed issue, report:
|
||||
|
||||
* search terms used
|
||||
* matching issue numbers/titles
|
||||
* whether each match is open or closed
|
||||
* whether any match fully covers the requested issue
|
||||
* whether any match partially covers the requested issue
|
||||
* whether a new issue is still needed
|
||||
|
||||
Do not create a duplicate issue if an existing issue fully covers the problem.
|
||||
|
||||
If a duplicate exists and fully covers the problem, stop issue creation for that topic and report the duplicate.
|
||||
|
||||
If a duplicate exists but is missing important acceptance criteria, comment on the existing issue only if exact `issue_comment` capability is proven and the user/task authorizes commenting.
|
||||
|
||||
If commenting is not authorized, report the existing issue and the missing criteria in the final handoff.
|
||||
|
||||
## 10. Issue inventory pagination rule
|
||||
|
||||
If listing/searching issues returns paginated results, follow pagination until the tool proves there are no more pages.
|
||||
|
||||
Do not assume search results or issue inventory are complete.
|
||||
|
||||
Pagination proof must not rely on assumed default API page size.
|
||||
|
||||
Search/inventory is complete only if one of the following is proven:
|
||||
|
||||
* the MCP response explicitly says there is no next page / `has_more=false` / final page
|
||||
* the workflow traversed pages until an empty page or explicit final page was returned
|
||||
* the tool response includes total-count or pagination metadata proving all relevant issues were returned
|
||||
* the request explicitly set `page` / `limit` / `per_page`, and the response explicitly proves the server honored that page size and did not truncate results
|
||||
|
||||
Do not say “duplicate search complete” merely because the result count is less than an assumed default page size.
|
||||
|
||||
If pagination metadata is absent and the tool cannot page, report `ISSUE_SEARCH_PAGINATION_UNPROVEN`.
|
||||
|
||||
If duplicate search cannot be trusted, do not create the issue unless the canonical workflow explicitly permits best-effort issue creation with that limitation disclosed.
|
||||
|
||||
## 11. Issue creation scope rule
|
||||
|
||||
Create only issues within the requested scope.
|
||||
|
||||
Do not create extra issues just because related problems are noticed.
|
||||
|
||||
Do not create process-hardening issues unless the user explicitly requested process-hardening or the current task is explicitly about workflow/tooling gaps.
|
||||
|
||||
Do not create implementation issues during reviewer mode.
|
||||
|
||||
Do not create reviewer issues during work-on-issue mode.
|
||||
|
||||
Do not create issues in a different repository unless the user explicitly asked and exact capability is proven.
|
||||
|
||||
If multiple issues are requested, create only the requested issues and only after duplicate search for each one.
|
||||
|
||||
If a proposed issue is too broad, split it only if the user requested splitting or the canonical workflow requires issue granularity.
|
||||
|
||||
## 12. Issue content quality rule
|
||||
|
||||
Every created issue must be actionable.
|
||||
|
||||
Include, when applicable:
|
||||
|
||||
* title
|
||||
* problem statement
|
||||
* observed evidence
|
||||
* expected behavior
|
||||
* required behavior
|
||||
* acceptance criteria
|
||||
* affected workflow/tool/files
|
||||
* safety or security considerations
|
||||
* duplicate search summary
|
||||
* related issues or PRs
|
||||
* non-goals, if useful
|
||||
|
||||
Acceptance criteria must be concrete and testable.
|
||||
|
||||
Avoid vague issues like:
|
||||
|
||||
* “make workflow better”
|
||||
* “fix LLM behavior”
|
||||
* “improve process”
|
||||
* “handle this better”
|
||||
|
||||
Instead, describe the exact wall, gate, verifier, test, schema, helper, or prompt change required.
|
||||
|
||||
## 13. Issue title rule
|
||||
|
||||
Use concise, specific titles.
|
||||
|
||||
Good title patterns:
|
||||
|
||||
* `Enforce <specific gate>`
|
||||
* `Add verifier for <specific report/proof problem>`
|
||||
* `Split <large workflow> into <specific components>`
|
||||
* `Block <unsafe action> during <workflow mode>`
|
||||
* `Require <proof type> before <claim/action>`
|
||||
|
||||
Avoid titles that are too broad or emotional.
|
||||
|
||||
The title should be unique enough that duplicate search can find it later.
|
||||
|
||||
## 14. Issue body rule
|
||||
|
||||
Issue body must include the full acceptance criteria.
|
||||
|
||||
Do not create placeholder issues.
|
||||
|
||||
Do not create issues with only a title unless the user explicitly requested title-only creation.
|
||||
|
||||
If the user supplied exact issue body text, preserve it unless it contains unsafe instructions, stale facts, or contradictions.
|
||||
|
||||
If edits are needed, make the smallest correction necessary and report the correction.
|
||||
|
||||
Do not silently change requested meaning.
|
||||
|
||||
## 15. Labels, assignees, and metadata
|
||||
|
||||
Apply labels, assignees, milestones, or project fields only if:
|
||||
|
||||
* the user requested them, or
|
||||
* the canonical workflow requires them, and
|
||||
* exact capability is proven.
|
||||
|
||||
Do not guess labels if project label policy is unknown.
|
||||
|
||||
If labels are useful but capability or policy is unclear, mention recommended labels in the final report instead of applying them.
|
||||
|
||||
Do not assign issues to people unless explicitly requested or required by project workflow.
|
||||
|
||||
## 16. Comment-on-existing issue rule
|
||||
|
||||
Comment on an existing issue only if:
|
||||
|
||||
* an existing issue partially covers the requested work, or
|
||||
* the user asked to add information to an existing issue, or
|
||||
* the canonical workflow requires duplicate consolidation comments, and
|
||||
* exact `issue_comment` capability is proven.
|
||||
|
||||
Comment must be specific and useful.
|
||||
|
||||
Include:
|
||||
|
||||
* why the existing issue is relevant
|
||||
* what acceptance criteria or evidence should be added
|
||||
* whether this avoids creating a duplicate
|
||||
|
||||
Do not comment just to say “duplicate found” unless the project workflow requires it.
|
||||
|
||||
Do not close duplicate issues unless explicitly requested and exact close capability is proven.
|
||||
|
||||
## 17. No hidden mutations
|
||||
|
||||
Do not perform unreported mutations.
|
||||
|
||||
Every issue creation, issue comment, issue edit, label change, assignment, milestone change, close/reopen action, or external-state change must be reported.
|
||||
|
||||
If a tool call is dry-run-only, confirmation-gated, rejected, or no-op, report it separately from performed mutations.
|
||||
|
||||
A dry run is not a mutation.
|
||||
|
||||
A rejected call is not a performed mutation.
|
||||
|
||||
A successful issue creation is an issue mutation.
|
||||
|
||||
A successful issue comment is an issue mutation.
|
||||
|
||||
A successful label/assignment/milestone update is an issue mutation or external-state mutation.
|
||||
|
||||
## 18. Issue creation gate
|
||||
|
||||
Before creating each issue, verify:
|
||||
|
||||
* identity is still valid
|
||||
* active profile is still valid
|
||||
* runtime context is still safe
|
||||
* exact `create_issue` capability is still valid
|
||||
* duplicate search was completed or limitation was explicitly allowed
|
||||
* proposed title is not a duplicate
|
||||
* proposed body includes actionable acceptance criteria
|
||||
* target repo is correct
|
||||
* no mode switch has occurred
|
||||
|
||||
If any gate fails, do not create the issue.
|
||||
|
||||
Produce a recovery handoff or duplicate report.
|
||||
|
||||
## 19. Issue commenting gate
|
||||
|
||||
Before commenting on an existing issue, verify:
|
||||
|
||||
* identity is still valid
|
||||
* active profile is still valid
|
||||
* runtime context is still safe
|
||||
* exact `issue_comment` capability is still valid
|
||||
* target issue number is correct
|
||||
* comment body is specific and useful
|
||||
* comment will not duplicate an existing comment
|
||||
* no mode switch has occurred
|
||||
|
||||
If any gate fails, do not comment.
|
||||
|
||||
Produce a recovery handoff or report the intended comment as a recommendation only.
|
||||
|
||||
## 20. Issue edit/update gate
|
||||
|
||||
Before editing an existing issue, verify:
|
||||
|
||||
* identity is still valid
|
||||
* active profile is still valid
|
||||
* runtime context is still safe
|
||||
* exact edit capability is still valid
|
||||
* target issue number is correct
|
||||
* update is explicitly requested or required by canonical workflow
|
||||
* update does not erase useful existing content
|
||||
* no mode switch has occurred
|
||||
|
||||
If any gate fails, do not edit.
|
||||
|
||||
Prefer commenting over editing unless the user explicitly requested an edit or the canonical workflow requires issue body updates.
|
||||
|
||||
## 21. Final report must be precise
|
||||
|
||||
Include:
|
||||
|
||||
* canonical workflow source/version/hash, if available
|
||||
* authenticated identity/profile
|
||||
* repo/project
|
||||
* runtime context summary
|
||||
* exact capability proof summary
|
||||
* requested issue-creation task
|
||||
* duplicate search terms used
|
||||
* duplicate search result
|
||||
* pagination/final-page proof for issue search, if applicable
|
||||
* issues created, with issue numbers and URLs
|
||||
* existing issues commented, with issue numbers and URLs
|
||||
* existing issues edited, with issue numbers and URLs
|
||||
* issues skipped as duplicates, with issue numbers and titles
|
||||
* labels/assignees/milestones applied, if any
|
||||
* blockers, if stopped
|
||||
* confirmation that no PR review, approval, request-changes, merge, branch, checkout, commit, push, or repo-file mutation was performed
|
||||
|
||||
If the report and actual tool/command log disagree, fix the report before final output.
|
||||
|
||||
## 22. Final report must distinguish mutation types
|
||||
|
||||
Do not use the legacy field `Workspace mutations`.
|
||||
|
||||
Use only precise categories:
|
||||
|
||||
* File edits by issue creator:
|
||||
* Worktree/index mutations:
|
||||
* Git ref mutations:
|
||||
* MCP/Gitea mutations:
|
||||
* Issue mutations:
|
||||
* Label/assignment/milestone mutations:
|
||||
* External-state mutations:
|
||||
* Read-only diagnostics:
|
||||
|
||||
Use precise wording:
|
||||
|
||||
* `File edits by issue creator: none`
|
||||
* `Worktree/index mutations: none`
|
||||
* `Git ref mutations: none`
|
||||
* `Issue mutations: ...`
|
||||
|
||||
If no repo files were edited, say:
|
||||
|
||||
`File edits by issue creator: none`
|
||||
|
||||
If no branches/worktrees were touched, say:
|
||||
|
||||
`Worktree/index mutations: none`
|
||||
|
||||
If no git refs were updated, say:
|
||||
|
||||
`Git ref mutations: none`
|
||||
|
||||
Do not hide issue mutations inside vague `MCP/Gitea mutations`.
|
||||
|
||||
## 23. Local artifact and report consistency rule
|
||||
|
||||
Do not create local walkthrough, notes, markdown, JSON, or report artifacts during issue-creation runs unless the canonical workflow or operator explicitly requires it.
|
||||
|
||||
If any file is edited, created, generated, or written, report it under `File edits by issue creator`.
|
||||
|
||||
For each file write, report:
|
||||
|
||||
* exact path
|
||||
* whether it was inside the repo
|
||||
* whether it was tracked or untracked
|
||||
* why it was created
|
||||
* whether final status was checked after the write
|
||||
|
||||
Do not say `File edits by issue creator: none` if any file write occurred.
|
||||
|
||||
Do not write files after the final clean-status check unless you rerun and report a new final clean-status check.
|
||||
|
||||
Default behavior: do not create local artifacts during issue creation.
|
||||
|
||||
## 24. Forbidden final-report claims unless proven
|
||||
|
||||
Do not claim:
|
||||
|
||||
* `duplicate search complete`
|
||||
* `no duplicate found`
|
||||
* `issue created`
|
||||
* `issue commented`
|
||||
* `issue updated`
|
||||
* `label applied`
|
||||
* `capability proven`
|
||||
* `runtime safe`
|
||||
* `all gates passed`
|
||||
* `no file edits`
|
||||
* `no unsafe mutation`
|
||||
* `no PR mutation`
|
||||
* `no repo mutation`
|
||||
* `pagination complete`
|
||||
* `final page`
|
||||
* `no next page`
|
||||
|
||||
unless the corresponding proof is included.
|
||||
|
||||
If anything blocks safe issue creation or issue update, stop immediately and produce an executable recovery handoff.
|
||||
|
||||
Do not improvise around the gates.
|
||||
|
||||
## 25. Proof wording enforcement
|
||||
|
||||
The following phrases are forbidden unless directly supported by current-session evidence:
|
||||
|
||||
* duplicate search complete
|
||||
* no duplicate found
|
||||
* issue created
|
||||
* issue commented
|
||||
* issue updated
|
||||
* labels applied
|
||||
* capability proven
|
||||
* runtime safe
|
||||
* all gates passed
|
||||
* no file edits
|
||||
* no unsafe mutation
|
||||
* no PR mutation
|
||||
* no repo mutation
|
||||
* pagination complete
|
||||
* final page
|
||||
* no next page
|
||||
|
||||
If the proof comes from prior state rather than a command/tool run in the current session, label it as prior proof, not live proof.
|
||||
|
||||
If a tool call was rejected, confirmation-gated, dry-run-only, or no-op, report it separately from performed mutations.
|
||||
|
||||
## 26. Final self-check before output
|
||||
|
||||
Before final output, check the report for contradictions.
|
||||
|
||||
Verify:
|
||||
|
||||
* if any file was edited, `File edits by issue creator` is not `none`
|
||||
* if any worktree was added/removed, `Worktree/index mutations` lists it
|
||||
* if any fetch happened, `Git ref mutations` lists it
|
||||
* if any issue was created, `Issue mutations` lists it
|
||||
* if any issue was commented, `Issue mutations` lists it
|
||||
* if any issue was edited, `Issue mutations` lists it
|
||||
* if any labels/assignees/milestones were changed, the correct mutation category lists it
|
||||
* if duplicate search is claimed complete, pagination/final-page proof is present or limitation is disclosed
|
||||
* if no duplicate is claimed, search terms and results are present
|
||||
* if issue created is claimed, issue number and URL are present
|
||||
* if no PR mutation is claimed, no PR tool/action was used
|
||||
* if no repo mutation is claimed, no branch/worktree/file/commit/push action occurred
|
||||
* if all gates passed is claimed, every required gate has proof
|
||||
|
||||
If any contradiction exists, fix the final report before output.
|
||||
|
||||
## 27. Controller handoff schema
|
||||
|
||||
End every run with a controller handoff using this schema.
|
||||
|
||||
Do not omit fields. Use `none` or `not verified in this session` where appropriate.
|
||||
|
||||
Controller Handoff:
|
||||
|
||||
* Task:
|
||||
* Repo:
|
||||
* Role:
|
||||
* Identity:
|
||||
* Active profile:
|
||||
* Runtime context:
|
||||
* Requested issue task:
|
||||
* Workflow source:
|
||||
* Capability proof:
|
||||
* Duplicate search terms:
|
||||
* Duplicate search pagination proof:
|
||||
* Duplicates found:
|
||||
* Issues created:
|
||||
* Issues commented:
|
||||
* Issues edited:
|
||||
* Issues skipped as duplicates:
|
||||
* Labels/assignees/milestones changed:
|
||||
* File edits by issue creator:
|
||||
* Worktree/index mutations:
|
||||
* Git ref mutations:
|
||||
* MCP/Gitea mutations:
|
||||
* Issue mutations:
|
||||
* Label/assignment/milestone mutations:
|
||||
* External-state mutations:
|
||||
* Read-only diagnostics:
|
||||
* Blockers:
|
||||
* Current status:
|
||||
* Safe next action:
|
||||
* Safety statement:
|
||||
|
||||
## 28. Stop conditions summary
|
||||
|
||||
Stop immediately and produce a recovery handoff if:
|
||||
|
||||
* canonical workflow is required but cannot be loaded
|
||||
* identity/profile/capability cannot be proven
|
||||
* runtime context is blocked
|
||||
* infra stop appears
|
||||
* MCP reconnect fails
|
||||
* capability state is stale
|
||||
* duplicate search cannot be performed and best-effort creation is not allowed
|
||||
* issue search pagination cannot be proven and best-effort creation is not allowed
|
||||
* duplicate fully covers the requested issue
|
||||
* requested issue body is unsafe or not actionable
|
||||
* target repo cannot be proven
|
||||
* create_issue capability is missing
|
||||
* issue_comment capability is missing for a required comment
|
||||
* issue edit capability is missing for a required edit
|
||||
* mode switch would be required
|
||||
* any report contradiction cannot be resolved
|
||||
|
||||
Blocked handoffs must not include direct issue-create or issue-comment replay commands.
|
||||
|
||||
Blocked handoffs must say to rerun the full workflow after the blocker clears.
|
||||
|
||||
Do not improvise around the gates.
|
||||
@@ -0,0 +1,387 @@
|
||||
---
|
||||
task_mode: reconcile-landed-pr
|
||||
canonical: true
|
||||
final_report_schema: ../schemas/reconcile-landed-final-report.md
|
||||
---
|
||||
|
||||
# Reconcile already-landed open PR workflow (canonical)
|
||||
|
||||
**Task mode:** `reconcile-landed-pr`
|
||||
|
||||
This file is the canonical reconciliation workflow for open PRs whose head SHA
|
||||
is already an ancestor of the target branch. Load it before any reconciliation
|
||||
mutation. Final report schema:
|
||||
[`schemas/reconcile-landed-final-report.md`](../schemas/reconcile-landed-final-report.md).
|
||||
|
||||
**Default task prompt:**
|
||||
|
||||
> Reconcile already-landed open PRs in this project. Do not review or merge
|
||||
> normal PRs. Close or comment only when exact capability is proven.
|
||||
|
||||
Do not improvise around the gates. Follow project skills, MCP gates, and
|
||||
workflow rules exactly.
|
||||
|
||||
This is a reconciliation workflow. It is not a normal PR review/merge workflow.
|
||||
|
||||
---
|
||||
|
||||
## 0. Load the canonical workflow first
|
||||
|
||||
Before starting reconciliation work, check whether the project provides a
|
||||
canonical reconcile-landed-PR workflow through a project skill, runbook, or MCP
|
||||
helper.
|
||||
|
||||
If available, load it first and report:
|
||||
|
||||
* workflow source
|
||||
* workflow version, commit, or hash
|
||||
* whether this prompt conflicts with the loaded workflow
|
||||
|
||||
If the canonical workflow cannot be loaded and the project requires it, stop and
|
||||
produce a recovery handoff only.
|
||||
|
||||
## 1. Mode isolation
|
||||
|
||||
This run is `reconcile-landed-pr` mode only.
|
||||
|
||||
**Do not review or merge normal PRs.**
|
||||
|
||||
Do not:
|
||||
|
||||
* approve PRs
|
||||
* request changes on PRs
|
||||
* merge PRs
|
||||
* implement code
|
||||
* edit repo files
|
||||
* create branches
|
||||
* create commits
|
||||
* push branches
|
||||
* create PRs
|
||||
* run normal PR validation as review approval input
|
||||
* perform author/coder implementation work
|
||||
* perform raw MCP repair
|
||||
|
||||
If the task requires review, merge, issue implementation, or MCP repair mode,
|
||||
stop and produce a handoff for the correct workflow.
|
||||
|
||||
Do not mix modes in one run.
|
||||
|
||||
## 2. Start with live identity, profile, runtime, and capability checks
|
||||
|
||||
Prove:
|
||||
|
||||
* authenticated identity
|
||||
* active profile (reconciler or author with close capabilities, as required)
|
||||
* repo/project
|
||||
* runtime context
|
||||
* exact capability for reading/listing PRs and issues
|
||||
* exact capability for PR inspect (`gitea.read` / view PR)
|
||||
* exact capability for issue inspect
|
||||
* exact capability for PR comment, if commenting
|
||||
* exact capability for issue comment, if commenting
|
||||
* exact capability for PR close, if closing PRs
|
||||
* exact capability for issue close, if closing issues
|
||||
|
||||
A nearby capability does not count.
|
||||
|
||||
Examples:
|
||||
|
||||
* `review_pr` does not authorize PR close
|
||||
* `merge_pr` does not authorize PR close or issue close
|
||||
* `create_issue` does not authorize `issue_comment`
|
||||
* `issue_comment` does not authorize PR close
|
||||
* `gitea.read` does not authorize close or comment mutations
|
||||
|
||||
If exact capability cannot be proven, stop and produce a recovery handoff only.
|
||||
|
||||
## 3. Stop immediately on blocked infrastructure
|
||||
|
||||
If any of the following appears, stop immediately:
|
||||
|
||||
* `infra_stop`
|
||||
* MCP reconnect failure
|
||||
* stale capability state
|
||||
* missing capability
|
||||
* workspace mismatch
|
||||
* broken canonical workflow loading
|
||||
* failed required preflight
|
||||
* capability resolver warning that says the current state may be unsafe
|
||||
* stale or inconsistent runtime context
|
||||
|
||||
Do not continue inventory, ancestry proof, commenting, closing, or cleanup.
|
||||
|
||||
Produce an executable recovery handoff only.
|
||||
|
||||
Blocked recovery handoffs must not include direct close or comment replay
|
||||
commands.
|
||||
|
||||
Blocked handoffs must say to rerun the full workflow after the blocker clears.
|
||||
|
||||
## 4. Main checkout rule
|
||||
|
||||
This workflow should not mutate repo files.
|
||||
|
||||
Do not edit files in the main checkout.
|
||||
|
||||
Do not create branches, commits, or pushes.
|
||||
|
||||
Do not run implementation or reviewer validation worktrees for code edits.
|
||||
|
||||
Reading repository files is allowed only when needed to understand
|
||||
reconciliation scope and only if this workflow permits it.
|
||||
|
||||
## 5. No raw MCP repair during reconciliation
|
||||
|
||||
Do not run `pkill`, kill MCP processes, edit MCP config, restart servers, or
|
||||
perform control-checkout repair during reconciliation.
|
||||
|
||||
If MCP repair is required, stop and produce a separate `CONTROL-CHECKOUT REPAIR
|
||||
MODE` handoff.
|
||||
|
||||
After repair, rerun the full workflow from the beginning.
|
||||
|
||||
## 6. No background task tools
|
||||
|
||||
Do not use `schedule`, `manage_task`, background jobs, async waits, delayed task
|
||||
tools, or monitoring tasks during reconciliation.
|
||||
|
||||
Use direct commands and MCP tools only.
|
||||
|
||||
If a required action cannot complete synchronously, stop and produce a recovery
|
||||
handoff.
|
||||
|
||||
## 7. No local Gitea fallback during normal reconciliation
|
||||
|
||||
During normal reconciliation workflows, do not read Gitea profile secret files.
|
||||
|
||||
Do not inspect `profiles.json`, local token stores, credential files, `.env`
|
||||
Gitea credentials, keychain dumps, or token helper outputs.
|
||||
|
||||
Do not run local Gitea helper scripts when MCP tools are available.
|
||||
|
||||
Use MCP tools for Gitea operations.
|
||||
|
||||
Local fallback is allowed only in explicit recovery mode when MCP is unavailable
|
||||
and identity/profile/capability can be independently proven.
|
||||
|
||||
## 8. Build a complete live open PR inventory
|
||||
|
||||
List open PRs for the target repo according to project policy.
|
||||
|
||||
Follow pagination until the tool proves there are no more pages.
|
||||
|
||||
Do not assume inventory is complete.
|
||||
|
||||
Pagination proof must not rely on assumed default API page size.
|
||||
|
||||
Inventory is complete only if one of the following is proven:
|
||||
|
||||
* the MCP response explicitly says there is no next page / `has_more=false` /
|
||||
final page
|
||||
* the workflow traversed pages until an empty page or explicit final page was
|
||||
returned
|
||||
* the tool response includes total-count or pagination metadata proving all
|
||||
relevant PRs were returned
|
||||
* the request explicitly set `page` / `limit` / `per_page`, and the response
|
||||
explicitly proves the server honored that page size and did not truncate results
|
||||
|
||||
If pagination cannot be proven, report `INVENTORY_PAGINATION_UNPROVEN` and stop
|
||||
unless project policy allows best-effort reconciliation with that limitation
|
||||
disclosed.
|
||||
|
||||
## 9. Already-landed proof
|
||||
|
||||
For each candidate PR, prove whether the PR head SHA is already landed on the
|
||||
target branch.
|
||||
|
||||
**Already-landed proof** must include:
|
||||
|
||||
* PR number and title
|
||||
* candidate head SHA (full 40-hex)
|
||||
* target branch name
|
||||
* target branch SHA (full 40-hex) after fetch
|
||||
* ancestor proof method (`git merge-base --is-ancestor`, equivalent forge API, or
|
||||
documented project helper)
|
||||
* ancestor proof result (true/false)
|
||||
* live PR state (open/closed, merged flag)
|
||||
|
||||
Do not classify a PR as already-landed without live ancestor proof.
|
||||
|
||||
If ancestry cannot be proven, classify as `ANCESTRY_UNPROVEN` and skip close
|
||||
mutations.
|
||||
|
||||
## 10. Linked issue live verification
|
||||
|
||||
If the PR claims to close or link an issue, fetch the linked issue live before
|
||||
reporting its status.
|
||||
|
||||
If the linked issue was not fetched live in the current session, report:
|
||||
|
||||
`Linked issue status: not verified in this session`
|
||||
|
||||
Do not claim `issue open`, `issue closed`, or `issue resolved` without live
|
||||
proof.
|
||||
|
||||
## 11. Reconciliation selection rules
|
||||
|
||||
Select PRs eligible for reconciliation:
|
||||
|
||||
* open PR state
|
||||
* `merged=false` unless project policy says otherwise
|
||||
* head SHA is ancestor of target branch (already-landed proof passed)
|
||||
* not selected for normal review/merge in this run
|
||||
|
||||
Eligibility class for selected PRs: `ALREADY_LANDED_RECONCILE_REQUIRED`
|
||||
|
||||
Do not select PRs that fail already-landed proof for normal review/merge
|
||||
treatment in this mode.
|
||||
|
||||
## 12. Reconciliation comment policy
|
||||
|
||||
Post a reconciliation comment only if:
|
||||
|
||||
* exact PR-comment or issue-comment capability is proven
|
||||
* the comment adds durable evidence (ancestor proof summary, recommended close
|
||||
action, linked issue status)
|
||||
* the comment will not duplicate an equivalent recent reconciliation comment
|
||||
|
||||
If comment capability is missing, record the intended comment in the final
|
||||
handoff only.
|
||||
|
||||
## 13. PR close rules
|
||||
|
||||
Close a PR only if:
|
||||
|
||||
* already-landed proof passed in this session
|
||||
* exact PR-close capability is proven
|
||||
* PR is still open at mutation time (live re-fetch)
|
||||
* head SHA still matches the proved candidate head SHA
|
||||
|
||||
If PR-close capability is missing, produce a recovery handoff with exact PR,
|
||||
proof, and required capability. Do not loop forever re-blocking the reviewer
|
||||
queue.
|
||||
|
||||
## 14. Issue close rules
|
||||
|
||||
Close a linked issue only if:
|
||||
|
||||
* exact issue-close capability is proven
|
||||
* linked issue was fetched live
|
||||
* issue resolution is justified by landed content and project policy
|
||||
* issue is still open at mutation time
|
||||
|
||||
If issue-close capability is missing, report the gap in the handoff.
|
||||
|
||||
## 15. Missing capability behavior
|
||||
|
||||
If any required mutation capability is missing:
|
||||
|
||||
* do not improvise with review/merge tools
|
||||
* do not ask the operator to bypass capability gates
|
||||
* produce a recovery handoff listing exact missing capabilities
|
||||
* include safe next action (profile switch, human close, or dedicated reconciler
|
||||
profile)
|
||||
|
||||
## 16. Mutation classification
|
||||
|
||||
Use precise mutation categories in the final report:
|
||||
|
||||
* File edits by reconciler: (expect `none`)
|
||||
* Worktree/index mutations:
|
||||
* Git ref mutations: (`git fetch` belongs here, not read-only diagnostics)
|
||||
* MCP/Gitea mutations:
|
||||
* Reconciliation mutations: (PR comment, issue comment, PR close, issue close)
|
||||
* External-state mutations:
|
||||
* Read-only diagnostics:
|
||||
|
||||
Do not use legacy `Workspace mutations`.
|
||||
|
||||
## 17. Identity privacy rule
|
||||
|
||||
Report identity as `username / profile` (#305).
|
||||
|
||||
Do not disclose personal email in final reports unless explicitly required.
|
||||
|
||||
## 18. Precise final report
|
||||
|
||||
Include:
|
||||
|
||||
* canonical workflow source/version/hash
|
||||
* authenticated identity/profile
|
||||
* repo/project
|
||||
* capability proof summary (separate lines for inspect, comment, PR close,
|
||||
issue close)
|
||||
* inventory pagination proof
|
||||
* selected PR(s) with already-landed proof
|
||||
* linked issue live status
|
||||
* mutations performed or blocked
|
||||
* missing capabilities
|
||||
* confirmation that no normal review, approval, request-changes, or merge was
|
||||
performed
|
||||
|
||||
## 19. Local artifact and report consistency rule
|
||||
|
||||
Do not create local walkthrough, notes, markdown, JSON, or report artifacts
|
||||
during reconciliation unless explicitly required.
|
||||
|
||||
If any file is edited, report under `File edits by reconciler`.
|
||||
|
||||
Default: no repo file edits.
|
||||
|
||||
## 20. Forbidden unsupported claims unless proven
|
||||
|
||||
Do not claim:
|
||||
|
||||
* `already-landed`
|
||||
* `PR closed`
|
||||
* `issue closed`
|
||||
* `pagination complete`
|
||||
* `inventory complete`
|
||||
* `all gates passed`
|
||||
* `no unsafe mutation`
|
||||
|
||||
unless the corresponding proof is included.
|
||||
|
||||
## 21. Proof wording enforcement
|
||||
|
||||
Forbidden unless supported by current-session evidence:
|
||||
|
||||
* pagination complete
|
||||
* final page
|
||||
* no next page
|
||||
* PR closed
|
||||
* issue closed
|
||||
* all gates passed
|
||||
|
||||
If proof comes from prior state, label as prior proof, not live proof.
|
||||
|
||||
## 22. Final self-check before output
|
||||
|
||||
Verify:
|
||||
|
||||
* no normal review/merge mutations occurred
|
||||
* `git fetch` is under Git ref mutations if it occurred
|
||||
* already-landed proof is present for each selected PR
|
||||
* handoff uses reconciliation schema, not author/reviewer merge schema
|
||||
* no contradiction between narrative report and controller handoff
|
||||
|
||||
## 23. Controller handoff schema
|
||||
|
||||
End every run with `Controller Handoff` per
|
||||
[`schemas/reconcile-landed-final-report.md`](../schemas/reconcile-landed-final-report.md).
|
||||
|
||||
## 24. Stop conditions summary
|
||||
|
||||
Stop immediately and produce a recovery handoff if:
|
||||
|
||||
* canonical workflow cannot be loaded
|
||||
* identity/profile/capability cannot be proven
|
||||
* runtime context is blocked
|
||||
* `infra_stop` appears
|
||||
* inventory pagination cannot be proven and best-effort is not allowed
|
||||
* already-landed proof cannot be completed
|
||||
* required close capability is missing and mutation was attempted
|
||||
* live PR/issue state contradicts proof
|
||||
* any report contradiction cannot be resolved
|
||||
|
||||
Do not improvise around the gates.
|
||||
@@ -0,0 +1,888 @@
|
||||
---
|
||||
task_mode: work-issue
|
||||
canonical: true
|
||||
final_report_schema: ../schemas/work-issue-final-report.md
|
||||
---
|
||||
|
||||
# Work issue workflow (canonical)
|
||||
|
||||
**Task mode:** `work-issue`
|
||||
|
||||
This file is the canonical author/coder workflow for Gitea-Tools. Load it
|
||||
before any issue implementation mutation. Final report schema:
|
||||
[`schemas/work-issue-final-report.md`](../schemas/work-issue-final-report.md).
|
||||
|
||||
**Default task prompt:**
|
||||
|
||||
> Find the next eligible issue in this project, work on it only if all gates
|
||||
> pass, and create a PR when complete.
|
||||
|
||||
Do not improvise around the gates. Follow project skills, MCP gates, and
|
||||
workflow rules exactly.
|
||||
|
||||
This is an author/coder workflow. It is not a reviewer workflow.
|
||||
|
||||
---
|
||||
|
||||
## 0. Load the canonical workflow first
|
||||
|
||||
Before starting issue work, check whether the project provides a canonical work-on-issue workflow through a project skill, runbook, or MCP helper.
|
||||
|
||||
If available, load it first and report:
|
||||
|
||||
* workflow source
|
||||
* workflow version, commit, or hash
|
||||
* whether this prompt conflicts with the loaded workflow
|
||||
|
||||
If the canonical workflow cannot be loaded and the project requires it, stop and produce a recovery handoff only.
|
||||
|
||||
## 1. Mode isolation
|
||||
|
||||
This run is `work-issue` mode only.
|
||||
|
||||
Do not:
|
||||
|
||||
* review PRs
|
||||
* approve PRs
|
||||
* request changes
|
||||
* merge PRs
|
||||
* close PRs unless the PR creation workflow explicitly does so through Gitea automation
|
||||
* close unrelated issues
|
||||
* mutate reviewer state
|
||||
* perform reviewer-only actions
|
||||
* create process-hardening issues unless explicitly authorized and the workflow switches to issue-creation mode
|
||||
|
||||
If the task requires review, merge, issue creation, or MCP repair mode, stop and produce a handoff for the correct workflow.
|
||||
|
||||
Do not mix modes in one run.
|
||||
|
||||
## 2. Start with live identity, profile, runtime, and capability checks
|
||||
|
||||
Prove:
|
||||
|
||||
* authenticated identity
|
||||
* active author/coder profile
|
||||
* repo/project
|
||||
* runtime context
|
||||
* exact capability for reading issues
|
||||
* exact capability for claiming/locking issues, if available
|
||||
* exact capability for branch creation, if handled through MCP
|
||||
* exact capability for pushing branches, if applicable
|
||||
* exact capability for creating PRs
|
||||
* exact capability for commenting on issues or PRs, if needed
|
||||
|
||||
A nearby capability does not count.
|
||||
|
||||
Examples:
|
||||
|
||||
* `create_issue` does not authorize `issue_comment`
|
||||
* `review_pr` does not authorize `merge_pr`
|
||||
* `create_pr` does not authorize `merge_pr`
|
||||
* `issue_comment` does not authorize `create_issue`
|
||||
* `gitea.read` does not authorize issue claim, PR creation, or branch mutation
|
||||
|
||||
If capability cannot be proven, stop and produce a recovery handoff only.
|
||||
|
||||
## 3. Stop immediately on blocked infrastructure
|
||||
|
||||
If any of the following appears, stop immediately:
|
||||
|
||||
* `infra_stop`
|
||||
* MCP reconnect failure
|
||||
* stale capability state
|
||||
* dirty control checkout
|
||||
* dirty task worktree
|
||||
* missing capability
|
||||
* workspace mismatch
|
||||
* stale target branch state
|
||||
* broken canonical workflow loading
|
||||
* failed required preflight
|
||||
* capability resolver warning that says the current state may be unsafe
|
||||
* stale or inconsistent runtime context
|
||||
|
||||
Do not continue issue selection, claiming, implementation, validation, commit, push, PR creation, cleanup, or handoff mutation.
|
||||
|
||||
Produce an executable recovery handoff only.
|
||||
|
||||
Blocked recovery handoffs must not include direct commit, push, or PR replay commands.
|
||||
|
||||
Blocked handoffs must say to rerun the full workflow after the blocker clears.
|
||||
|
||||
## 4. Main checkout rule
|
||||
|
||||
The main project checkout must stay on `master`, `main`, or `dev`.
|
||||
|
||||
Do not do task work in the main checkout.
|
||||
|
||||
Do not edit files in the main checkout.
|
||||
|
||||
Do not run tests in the main checkout.
|
||||
|
||||
Do not commit from the main checkout.
|
||||
|
||||
Do not create PRs from the main checkout.
|
||||
|
||||
All task work must happen under the project’s `branches/` directory.
|
||||
|
||||
No exceptions for small fixes, docs, tests, cleanup, conflict resolution, emergencies, or “just one file.”
|
||||
|
||||
If the main checkout is dirty before selection, stop and produce a recovery handoff.
|
||||
|
||||
If the main checkout becomes dirty during the run, stop and produce a recovery handoff unless the change is explicitly allowed by the canonical workflow.
|
||||
|
||||
## 5. No raw MCP repair during normal issue work
|
||||
|
||||
Do not run `pkill`, kill MCP processes, edit MCP config, restart servers, or perform control-checkout repair during normal issue work.
|
||||
|
||||
If MCP repair is required, stop issue work and produce a separate `CONTROL-CHECKOUT REPAIR MODE` handoff.
|
||||
|
||||
Do not mix MCP repair mode with work-on-issue mode.
|
||||
|
||||
Do not use successful repair as permission to resume the same issue workflow. After repair, rerun the full workflow from the beginning.
|
||||
|
||||
## 6. No background task tools
|
||||
|
||||
Do not use `schedule`, `manage_task`, background jobs, async waits, delayed task tools, or monitoring tasks during issue work.
|
||||
|
||||
Use direct commands and MCP tools only.
|
||||
|
||||
If a required action cannot complete synchronously, stop and produce a recovery handoff.
|
||||
|
||||
Long synchronous commands, such as a test suite, are allowed only if they are run directly and reported with exact command, working directory, and result.
|
||||
|
||||
Do not say “I will check later,” “I will monitor,” or “I will continue in the background.”
|
||||
|
||||
## 7. No local Gitea fallback during normal issue work
|
||||
|
||||
During normal author/coder workflows, do not read Gitea profile secret files.
|
||||
|
||||
Do not inspect or open files such as:
|
||||
|
||||
* `profiles.json`
|
||||
* local token stores
|
||||
* credential files
|
||||
* local Gitea auth/profile config files
|
||||
* `.env` files containing Gitea credentials
|
||||
* keychain dumps
|
||||
* token helper outputs
|
||||
|
||||
Do not run local Gitea helper scripts when MCP tools are available.
|
||||
|
||||
Use MCP tools for Gitea operations.
|
||||
|
||||
Local fallback is allowed only in explicit recovery mode when MCP is unavailable and identity/profile/capability can be independently proven.
|
||||
|
||||
If local fallback is used, report:
|
||||
|
||||
* why MCP was unavailable
|
||||
* exact identity proof
|
||||
* exact profile proof
|
||||
* exact repo proof
|
||||
* exact capability proof
|
||||
* exact local command used
|
||||
|
||||
Do not use local fallback to bypass MCP gates.
|
||||
|
||||
## 8. Build a complete live issue inventory
|
||||
|
||||
List open issues according to the project’s issue selection policy.
|
||||
|
||||
Follow pagination until the tool proves there are no more pages.
|
||||
|
||||
Do not assume inventory is complete.
|
||||
|
||||
Do not claim `next eligible issue`, `oldest eligible issue`, or complete issue inventory unless pagination is proven.
|
||||
|
||||
Pagination proof must not rely on assumed default API page size.
|
||||
|
||||
Inventory is complete only if one of the following is proven:
|
||||
|
||||
* the MCP response explicitly says there is no next page / `has_more=false` / final page
|
||||
* the workflow traversed pages until an empty page or explicit final page was returned
|
||||
* the tool response includes total-count or pagination metadata proving all relevant issues were returned
|
||||
* the request explicitly set `page` / `limit` / `per_page`, and the response explicitly proves the server honored that page size and did not truncate results
|
||||
|
||||
Do not say “inventory complete” merely because the result count is less than an assumed default page size.
|
||||
|
||||
For each candidate issue, identify:
|
||||
|
||||
* issue number
|
||||
* title
|
||||
* labels
|
||||
* status
|
||||
* author/requester, if relevant
|
||||
* assignee/owner, if any
|
||||
* linked PRs, if any
|
||||
* dependency/blocker labels, if any
|
||||
* whether it appears already claimed
|
||||
* whether it appears already implemented or superseded
|
||||
* whether it is eligible under project rules
|
||||
|
||||
Final report must include pagination/final-page proof.
|
||||
|
||||
## 9. Issue selection rules
|
||||
|
||||
State the issue ordering policy before selecting an issue.
|
||||
|
||||
If the project uses oldest-first, explicitly sort or reason by issue number or created date.
|
||||
|
||||
Do not rely on API response order unless the tool proves that order matches the project policy.
|
||||
|
||||
Do not pick:
|
||||
|
||||
* already-claimed issues
|
||||
* issues assigned to another active worker
|
||||
* issues with an open PR already covering the work
|
||||
* duplicate issues
|
||||
* blocked issues
|
||||
* dependency-blocked issues
|
||||
* already implemented issues
|
||||
* issues outside the current requested scope
|
||||
* process-hardening issues unless this run was explicitly started for process-hardening work
|
||||
* reviewer-only issues if this is author/coder mode
|
||||
|
||||
For every earlier issue skipped, report:
|
||||
|
||||
* issue number
|
||||
* current status
|
||||
* blocking category
|
||||
* proof used
|
||||
* whether there is an open PR
|
||||
* whether there is an active claim
|
||||
* reason it is not eligible
|
||||
|
||||
If eligibility cannot be proven, classify it as:
|
||||
|
||||
`ISSUE_ELIGIBILITY_UNVERIFIED`
|
||||
|
||||
Then stop or produce a recovery handoff according to project policy.
|
||||
|
||||
Do not select an issue based only on memory from a previous session.
|
||||
|
||||
## 10. Linked PR / duplicate active work proof
|
||||
|
||||
Before claiming or working on an issue, check whether there is already an open PR, branch, or active claim for that issue.
|
||||
|
||||
If an open PR already exists for the issue, do not implement duplicate work.
|
||||
|
||||
Classify the issue as:
|
||||
|
||||
`OPEN_PR_EXISTS`
|
||||
|
||||
and skip it only if project policy allows skipping.
|
||||
|
||||
If branch naming or PR title convention links issues to branches, search for matching branches or PRs.
|
||||
|
||||
Report:
|
||||
|
||||
* issue number
|
||||
* linked/open PRs found
|
||||
* matching branches found, if checked
|
||||
* active claims found
|
||||
* duplicate work status
|
||||
|
||||
Do not create another branch/PR for the same issue unless the project explicitly allows taking over or updating existing work and exact capability is proven.
|
||||
|
||||
## 11. Claim or lock the issue before implementation
|
||||
|
||||
Claim/lock the issue before implementation if the project provides a claim/lock mechanism.
|
||||
|
||||
If claim/lock requires a Gitea mutation, prove exact capability first.
|
||||
|
||||
If claim/lock fails, stop.
|
||||
|
||||
Do not implement unclaimed work.
|
||||
|
||||
If the claim/lock gates are broken, produce a recovery handoff.
|
||||
|
||||
Create a tooling issue only if this run is explicitly authorized to switch to issue-creation mode and exact `create_issue` capability is proven.
|
||||
|
||||
Report:
|
||||
|
||||
* claim mechanism used
|
||||
* claim result
|
||||
* claim timestamp, if available
|
||||
* issue owner/assignee after claim, if available
|
||||
|
||||
## 12. Refresh stable branch before branch/worktree creation
|
||||
|
||||
Fetch the stable target branch from the remote before creating a task branch or worktree.
|
||||
|
||||
Do not rely on stale local `master`, `main`, or `dev`.
|
||||
|
||||
Record the fetched stable branch SHA.
|
||||
|
||||
If the stable branch cannot be fetched or verified, stop and produce a recovery handoff.
|
||||
|
||||
`git fetch`, `git remote update`, and any command that updates refs must be reported under `Git ref mutations`, not read-only diagnostics.
|
||||
|
||||
## 13. Branch and worktree ownership rule
|
||||
|
||||
Create a fresh session-owned worktree under `branches/`.
|
||||
|
||||
Prefer a branch name that includes the issue number, for example:
|
||||
|
||||
`feat/issue-<ISSUE_NUMBER>-short-description`
|
||||
|
||||
or:
|
||||
|
||||
`fix/issue-<ISSUE_NUMBER>-short-description`
|
||||
|
||||
Prefer a worktree path like:
|
||||
|
||||
`branches/issue-<ISSUE_NUMBER>-short-description`
|
||||
|
||||
Before any file edits, prove:
|
||||
|
||||
* project root
|
||||
* current working directory
|
||||
* main checkout branch
|
||||
* stable branch
|
||||
* stable branch SHA
|
||||
* task branch name
|
||||
* session-owned worktree path
|
||||
* worktree path is inside `branches/`
|
||||
* worktree is not the main checkout
|
||||
* clean tracked state
|
||||
* clean untracked state
|
||||
* worktree HEAD/branch state
|
||||
|
||||
Do not reuse an existing worktree unless safe-reuse proof passes.
|
||||
|
||||
Safe-reuse proof must include:
|
||||
|
||||
* exact worktree path
|
||||
* worktree is inside `branches/`
|
||||
* worktree is not the main checkout
|
||||
* worktree is not owned by another active task/session
|
||||
* clean tracked state
|
||||
* clean untracked state
|
||||
* current branch/head before reset
|
||||
* reset target SHA
|
||||
* explicit project policy allowing reuse/reset
|
||||
|
||||
Do not run `git reset --hard`, `git clean`, checkout, or other destructive commands unless the worktree is session-owned or safe-reuse proof passes.
|
||||
|
||||
If safe-reuse proof cannot be produced, create a fresh session-owned worktree.
|
||||
|
||||
## 14. Implementation scope rule
|
||||
|
||||
Implement only what is required for the selected issue.
|
||||
|
||||
Do not perform opportunistic refactors.
|
||||
|
||||
Do not fix unrelated tests unless they are required for the selected issue and clearly documented.
|
||||
|
||||
Do not modify reviewer workflow files unless the selected issue explicitly requires workflow changes.
|
||||
|
||||
Do not modify Gitea profiles, MCP authorization, tokens, secrets, deployment config, production config, or credentials unless the selected issue explicitly requires it and exact capability/proof gates pass.
|
||||
|
||||
Do not introduce provenance markers, agent signatures, temporary files, debug dumps, or generated artifacts unless required.
|
||||
|
||||
If implementation uncovers a separate issue, note it in the final report or create a follow-up issue only if exact capability is proven and project policy allows it.
|
||||
|
||||
## 15. File edit rule
|
||||
|
||||
All edits must happen only inside the session-owned issue worktree.
|
||||
|
||||
Do not edit files in the main checkout.
|
||||
|
||||
Do not edit files in reviewer worktrees.
|
||||
|
||||
Do not edit unrelated worktrees.
|
||||
|
||||
Track every edited, created, deleted, or generated file.
|
||||
|
||||
If any file is edited, created, generated, or written, report it under `File edits by author`.
|
||||
|
||||
For each file write, report:
|
||||
|
||||
* exact path
|
||||
* whether it was inside the repo
|
||||
* whether it was tracked or untracked
|
||||
* why it was created
|
||||
* whether final `git status` was run after the write
|
||||
|
||||
Do not say `File edits by author: none` if any file write occurred.
|
||||
|
||||
Do not write files after the final clean-status check unless you rerun and report a new final clean-status check.
|
||||
|
||||
## 16. Validation rule
|
||||
|
||||
Run appropriate validation for the selected issue.
|
||||
|
||||
Validation may include:
|
||||
|
||||
* targeted tests
|
||||
* full test suite
|
||||
* compile checks
|
||||
* lint checks
|
||||
* type checks
|
||||
* diff checks
|
||||
* secret/provenance checks
|
||||
* dangerous artifact checks
|
||||
* project-specific validation
|
||||
|
||||
If validation cannot run, explain why and include the exact failure.
|
||||
|
||||
Do not hide failures.
|
||||
|
||||
Do not claim success if tests failed.
|
||||
|
||||
Do not skip required validation silently.
|
||||
|
||||
Do not bypass MCP gates.
|
||||
|
||||
Report every validation command with:
|
||||
|
||||
* exact command
|
||||
* working directory
|
||||
* exit code or pass/fail result
|
||||
* summary count if available
|
||||
* whether it was targeted, full-suite, compile, lint, diff, secret/provenance, or diagnostic validation
|
||||
|
||||
If using bare `pytest`, also report:
|
||||
|
||||
* `which pytest`
|
||||
* `pytest --version`
|
||||
* whether it resolves to the project venv
|
||||
|
||||
Prefer the project venv executable when available.
|
||||
|
||||
## 17. Baseline comparison rule
|
||||
|
||||
Do not run tests in the main checkout.
|
||||
|
||||
If the full suite fails and you need to prove failures are pre-existing, create a clean baseline worktree under `branches/`, such as:
|
||||
|
||||
`branches/baseline-master-issue-<ISSUE_NUMBER>`
|
||||
|
||||
Baseline comparison must include:
|
||||
|
||||
* baseline worktree path
|
||||
* baseline target SHA
|
||||
* task branch SHA
|
||||
* exact command run on both worktrees
|
||||
* baseline failures
|
||||
* task branch failures
|
||||
* proof the failure signatures match
|
||||
* proof the baseline worktree was clean before and after validation
|
||||
* proof the issue worktree was clean before and after validation
|
||||
|
||||
Do not claim “same as master” unless the clean baseline worktree proof is included.
|
||||
|
||||
Do not claim “full-suite failures are pre-existing” unless baseline proof is complete and the failure signatures match.
|
||||
|
||||
If full-suite failures differ or proof is incomplete, do not create a PR unless project policy explicitly allows PR creation with documented validation failures.
|
||||
|
||||
## 18. Pre-commit review
|
||||
|
||||
Before committing, review the actual diff.
|
||||
|
||||
Check:
|
||||
|
||||
* correctness
|
||||
* tests
|
||||
* scope
|
||||
* security boundaries
|
||||
* workflow rule compliance
|
||||
* whether the implementation really satisfies the selected issue
|
||||
* unrelated changes
|
||||
* dangerous generated artifacts
|
||||
* secrets
|
||||
* provenance markers
|
||||
* temporary agent files
|
||||
* debug output
|
||||
* formatting-only churn
|
||||
* docs/tests consistency
|
||||
|
||||
Run:
|
||||
|
||||
* `git status`
|
||||
* `git diff --stat`
|
||||
* `git diff`
|
||||
* project-required diff checks
|
||||
|
||||
Do not commit if unrelated or unsafe changes are present.
|
||||
|
||||
## 19. Commit rules
|
||||
|
||||
Commit only from the session-owned issue worktree.
|
||||
|
||||
Do not commit from the main checkout.
|
||||
|
||||
Commit only after implementation and required validation pass, unless project policy explicitly allows draft PRs with failing validation.
|
||||
|
||||
Commit message must reference the issue number.
|
||||
|
||||
Preferred format:
|
||||
|
||||
`fix: short summary (Closes #<ISSUE_NUMBER>)`
|
||||
|
||||
or:
|
||||
|
||||
`feat: short summary (Closes #<ISSUE_NUMBER>)`
|
||||
|
||||
Before commit, prove:
|
||||
|
||||
* worktree path
|
||||
* branch name
|
||||
* selected issue number
|
||||
* staged files
|
||||
* diff summary
|
||||
* validation status
|
||||
|
||||
After commit, record:
|
||||
|
||||
* commit SHA
|
||||
* commit message
|
||||
* changed files
|
||||
|
||||
Do not amend, reset, rebase, squash, or force-push unless the project workflow explicitly allows it and the worktree is session-owned.
|
||||
|
||||
## 20. Push rules
|
||||
|
||||
Push only the session-owned task branch.
|
||||
|
||||
Do not push `master`, `main`, `dev`, tags, or unrelated branches.
|
||||
|
||||
Before push, prove:
|
||||
|
||||
* current branch
|
||||
* upstream/remote target
|
||||
* commit SHA being pushed
|
||||
* selected issue number
|
||||
* branch name matches the issue
|
||||
|
||||
After push, report:
|
||||
|
||||
* remote
|
||||
* branch
|
||||
* pushed commit SHA
|
||||
* push result
|
||||
|
||||
If push fails, stop and produce a recovery handoff.
|
||||
|
||||
## 21. PR creation rules
|
||||
|
||||
Create a PR only if implementation and validation pass, unless project policy explicitly allows draft PRs with documented validation failures.
|
||||
|
||||
Do not create a PR if:
|
||||
|
||||
* issue was not claimed/locked
|
||||
* issue eligibility was unproven
|
||||
* duplicate open PR exists
|
||||
* task branch does not reference the issue
|
||||
* implementation is incomplete
|
||||
* validation failed without allowed exception
|
||||
* worktree is dirty
|
||||
* secrets/provenance/dangerous artifacts are present
|
||||
* capability for PR creation is missing
|
||||
* runtime context is blocked
|
||||
* authenticated identity/profile changed unexpectedly
|
||||
|
||||
PR must reference or close the issue.
|
||||
|
||||
PR body must include:
|
||||
|
||||
* summary
|
||||
* linked issue
|
||||
* files changed
|
||||
* validation commands and results
|
||||
* risk
|
||||
* exact worktree path
|
||||
* branch name
|
||||
* commit SHA
|
||||
* known limitations, if any
|
||||
|
||||
Do not merge your own PR.
|
||||
|
||||
Do not approve your own PR.
|
||||
|
||||
Do not request changes on your own PR.
|
||||
|
||||
After PR creation, fetch or view the PR to verify:
|
||||
|
||||
* PR number
|
||||
* PR URL
|
||||
* PR title
|
||||
* base branch
|
||||
* head branch
|
||||
* linked issue
|
||||
* head SHA
|
||||
* open status
|
||||
|
||||
## 22. Cleanup rules
|
||||
|
||||
Clean only session-owned temporary/baseline worktrees if the project workflow explicitly allows cleanup.
|
||||
|
||||
Do not delete unrelated branches/worktrees.
|
||||
|
||||
Do not delete the task worktree if the project expects it to remain for handoff unless policy says cleanup is allowed after PR creation.
|
||||
|
||||
Do not update the main checkout unless the canonical workflow explicitly allows it.
|
||||
|
||||
Any cleanup is a mutation and must be reported.
|
||||
|
||||
## 23. Recovery handoff rules
|
||||
|
||||
If blocked, produce a recovery handoff with:
|
||||
|
||||
* exact blocker
|
||||
* failed tool/function, if any
|
||||
* repo/project
|
||||
* selected issue, if one was safely selected
|
||||
* eligibility class
|
||||
* claim/lock state
|
||||
* branch name, if created
|
||||
* worktree path, if created
|
||||
* stable branch and stable branch SHA, if known
|
||||
* files changed, if any
|
||||
* validation state
|
||||
* commit SHA, if committed
|
||||
* PR number/URL, if created
|
||||
* exact state reached before stopping
|
||||
* safe next action
|
||||
* statement that no unsafe mutation was attempted
|
||||
|
||||
Blocked handoffs must not include direct commit, push, or PR replay commands.
|
||||
|
||||
Blocked handoffs must say to rerun the full workflow after the blocker clears.
|
||||
|
||||
## 24. Final report must be precise
|
||||
|
||||
Include:
|
||||
|
||||
* canonical workflow source/version/hash, if available
|
||||
* authenticated identity/profile
|
||||
* repo/project
|
||||
* capability proof summary
|
||||
* issue inventory proof, including pagination/final-page proof
|
||||
* issue ordering policy used
|
||||
* selected issue number/title
|
||||
* eligibility class
|
||||
* skipped earlier issues and proof, if any
|
||||
* duplicate active work proof
|
||||
* claim/lock result
|
||||
* stable branch and stable branch SHA
|
||||
* branch name
|
||||
* worktree path
|
||||
* worktree inside `branches/`: true/false
|
||||
* worktree branch/HEAD state
|
||||
* worktree dirty before implementation: true/false
|
||||
* files changed
|
||||
* validation commands and results
|
||||
* baseline comparison result, if used
|
||||
* pre-commit diff review result
|
||||
* commit SHA and commit message, if committed
|
||||
* push result, if pushed
|
||||
* PR number and URL, if created
|
||||
* PR verification result, if created
|
||||
* cleanup result
|
||||
* blockers, if stopped
|
||||
* confirmation that the main checkout was not used for task work
|
||||
|
||||
If the report and actual tool/command log disagree, fix the report before final output.
|
||||
|
||||
## 25. Final report must distinguish mutation types
|
||||
|
||||
Do not use the legacy field `Workspace mutations`.
|
||||
|
||||
Use only precise categories:
|
||||
|
||||
* File edits by author:
|
||||
* Worktree/index mutations:
|
||||
* Git ref mutations:
|
||||
* MCP/Gitea mutations:
|
||||
* Issue mutations:
|
||||
* Branch mutations:
|
||||
* Commit mutations:
|
||||
* Push mutations:
|
||||
* PR mutations:
|
||||
* Cleanup mutations:
|
||||
* External-state mutations:
|
||||
* Read-only diagnostics:
|
||||
|
||||
`git fetch`, `git remote update`, and any command that updates refs must be listed under `Git ref mutations`, not read-only diagnostics.
|
||||
|
||||
If `git reset --hard`, checkout, clean, worktree add/remove, merge simulation, merge abort, or similar commands occurred, report them under `Worktree/index mutations`.
|
||||
|
||||
Use precise wording:
|
||||
|
||||
* `File edits by author: none`
|
||||
* `Worktree/index mutations: ...`
|
||||
* `Git ref mutations: ...`
|
||||
* `MCP/Gitea mutations: ...`
|
||||
|
||||
Do not collapse issue, branch, commit, push, PR, cleanup, or external-state mutations into vague wording.
|
||||
|
||||
## 26. Forbidden final-report claims unless proven
|
||||
|
||||
Do not claim:
|
||||
|
||||
* `next eligible issue`
|
||||
* `oldest eligible issue`
|
||||
* `issue claimed`
|
||||
* `no duplicate work`
|
||||
* `no open PR`
|
||||
* `worktree clean`
|
||||
* `validation passed`
|
||||
* `same as master`
|
||||
* `full-suite failures are pre-existing`
|
||||
* `committed`
|
||||
* `pushed`
|
||||
* `PR created`
|
||||
* `issue closed`
|
||||
* `main checkout untouched`
|
||||
* `no file edits`
|
||||
* `no unsafe mutation`
|
||||
* `all gates passed`
|
||||
* `target branch up to date`
|
||||
|
||||
unless the corresponding proof is included.
|
||||
|
||||
If anything blocks safe work or PR creation, stop immediately and produce an executable recovery handoff.
|
||||
|
||||
Do not improvise around the gates.
|
||||
|
||||
## 27. Proof wording enforcement
|
||||
|
||||
The following phrases are forbidden unless directly supported by current-session evidence:
|
||||
|
||||
* next eligible issue
|
||||
* oldest eligible issue
|
||||
* inventory complete
|
||||
* no duplicate work
|
||||
* issue claimed
|
||||
* worktree clean
|
||||
* validation passed
|
||||
* same as master
|
||||
* full-suite failures are pre-existing
|
||||
* committed
|
||||
* pushed
|
||||
* PR created
|
||||
* issue closed
|
||||
* target branch up to date
|
||||
* all gates passed
|
||||
* no unsafe mutation
|
||||
* no file edits
|
||||
|
||||
If the proof comes from prior state rather than a command/tool run in the current session, label it as prior proof, not live proof.
|
||||
|
||||
If a tool call was rejected, confirmation-gated, dry-run-only, or no-op, report it separately from performed mutations.
|
||||
|
||||
## 28. Final self-check before output
|
||||
|
||||
Before final output, check the report for contradictions.
|
||||
|
||||
Verify:
|
||||
|
||||
* if any file was edited, `File edits by author` is not `none`
|
||||
* if any worktree was added/removed, `Worktree/index mutations` lists it
|
||||
* if any fetch happened, `Git ref mutations` lists it
|
||||
* if any issue was claimed/commented/updated, `Issue mutations` lists it
|
||||
* if any branch was created, `Branch mutations` lists it
|
||||
* if any commit was created, `Commit mutations` lists it
|
||||
* if any push occurred, `Push mutations` lists it
|
||||
* if any PR was created, `PR mutations` lists it
|
||||
* if any cleanup happened, `Cleanup mutations` lists it
|
||||
* if any issue/PR external state changed, `External-state mutations` lists it
|
||||
* if pagination is claimed complete, final-page proof is present
|
||||
* if same-as-master is claimed, baseline proof is complete
|
||||
* if selected issue is claimed next eligible, every earlier issue has proof-backed skip reasoning
|
||||
* if PR created is claimed, PR verification proof is present
|
||||
* if main checkout untouched is claimed, main checkout status proof is present
|
||||
|
||||
If any contradiction exists, fix the final report before output.
|
||||
|
||||
## 29. Controller handoff schema
|
||||
|
||||
End every run with a controller handoff using this schema.
|
||||
|
||||
Do not omit fields. Use `none` or `not verified in this session` where appropriate.
|
||||
|
||||
Controller Handoff:
|
||||
|
||||
* Task:
|
||||
* Repo:
|
||||
* Role:
|
||||
* Identity:
|
||||
* Active profile:
|
||||
* Runtime context:
|
||||
* Selected issue:
|
||||
* Eligibility class:
|
||||
* Issue ordering policy:
|
||||
* Issue inventory pagination proof:
|
||||
* Earlier issues skipped:
|
||||
* Duplicate active work proof:
|
||||
* Claim/lock state:
|
||||
* Stable branch:
|
||||
* Stable branch SHA:
|
||||
* Branch name:
|
||||
* Worktree path:
|
||||
* Worktree inside branches:
|
||||
* Worktree branch/HEAD state:
|
||||
* Worktree dirty before implementation:
|
||||
* Files changed:
|
||||
* Validation:
|
||||
* Baseline comparison:
|
||||
* Commit SHA:
|
||||
* Push result:
|
||||
* PR number:
|
||||
* PR URL:
|
||||
* PR verification:
|
||||
* Main checkout branch:
|
||||
* Main checkout dirty state:
|
||||
* Main checkout used for task work:
|
||||
* File edits by author:
|
||||
* Worktree/index mutations:
|
||||
* Git ref mutations:
|
||||
* MCP/Gitea mutations:
|
||||
* Issue mutations:
|
||||
* Branch mutations:
|
||||
* Commit mutations:
|
||||
* Push mutations:
|
||||
* PR mutations:
|
||||
* Cleanup mutations:
|
||||
* External-state mutations:
|
||||
* Read-only diagnostics:
|
||||
* Blockers:
|
||||
* Current status:
|
||||
* Safe next action:
|
||||
* Safety statement:
|
||||
|
||||
## 30. Stop conditions summary
|
||||
|
||||
Stop immediately and produce a recovery handoff if:
|
||||
|
||||
* canonical workflow is required but cannot be loaded
|
||||
* identity/profile/capability cannot be proven
|
||||
* runtime context is blocked
|
||||
* infra stop appears
|
||||
* MCP reconnect fails
|
||||
* capability state is stale
|
||||
* issue inventory pagination cannot be proven
|
||||
* issue ordering cannot be proven
|
||||
* issue eligibility cannot be proven
|
||||
* duplicate active work cannot be checked
|
||||
* selected issue is already claimed by another worker
|
||||
* selected issue already has an open PR
|
||||
* claim/lock fails
|
||||
* stable branch cannot be fetched
|
||||
* task worktree cannot be created safely
|
||||
* task worktree is dirty before implementation
|
||||
* validation cannot run
|
||||
* validation fails without allowed exception
|
||||
* baseline comparison is required but incomplete
|
||||
* diff review finds unrelated or unsafe changes
|
||||
* commit fails
|
||||
* push fails
|
||||
* PR creation fails
|
||||
* PR verification fails
|
||||
* any report contradiction cannot be resolved
|
||||
|
||||
Blocked handoffs must not include direct commit, push, or PR replay commands.
|
||||
|
||||
Blocked handoffs must say to rerun the full workflow after the blocker clears.
|
||||
|
||||
Do not improvise around the gates.
|
||||
@@ -0,0 +1,166 @@
|
||||
"""Fail-closed subagent delegation gates (#266).
|
||||
|
||||
Subagents can bypass or lose context for worktree, capability, mutation,
|
||||
retry, and reporting rules. These helpers make delegation an explicit,
|
||||
provable decision instead of a default: deterministic write tasks stay
|
||||
inline unless the parent session records why a subagent is needed and
|
||||
hands the subagent the full gate context it must operate under.
|
||||
|
||||
Like ``author_proofs``/``review_proofs``, the helpers are pure (no git,
|
||||
no API calls): the parent workflow gathers the facts and passes them in,
|
||||
so the same logic works from prompts, harness assertions, and tests.
|
||||
Nothing here weakens the review/merge/permission gates — a delegated
|
||||
subagent is subject to the same gates as its parent.
|
||||
"""
|
||||
|
||||
# AC1: deterministic write workflows a subagent must never run by default.
|
||||
DETERMINISTIC_WRITE_TASKS = frozenset({
|
||||
"claim_issue",
|
||||
"create_branch",
|
||||
"edit_code",
|
||||
"commit",
|
||||
"push_branch",
|
||||
"create_pr",
|
||||
"review_pr",
|
||||
"merge_pr",
|
||||
"cleanup_branch",
|
||||
"close_issue",
|
||||
})
|
||||
|
||||
# Read-only delegation that needs no explicit authorization.
|
||||
READ_ONLY_TASKS = frozenset({
|
||||
"read_files",
|
||||
"code_search",
|
||||
"inventory_prs",
|
||||
"summarize_issue",
|
||||
"explore_codebase",
|
||||
})
|
||||
|
||||
# AC3: context a subagent must inherit from the parent session before any
|
||||
# authorized write delegation may proceed.
|
||||
REQUIRED_INHERITED_CONTEXT = (
|
||||
"issue_lock",
|
||||
"branch",
|
||||
"worktree_path",
|
||||
"identity_profile",
|
||||
"allowed_tool_class",
|
||||
"command_deny_list",
|
||||
"validation_ledger_requirement",
|
||||
"final_report_schema",
|
||||
)
|
||||
|
||||
# AC4: proof fields a subagent final report must carry — the same fields a
|
||||
# parent workflow's final report requires.
|
||||
REQUIRED_SUBAGENT_REPORT_FIELDS = (
|
||||
"identity_profile",
|
||||
"worktree_path",
|
||||
"branch",
|
||||
"changed_files",
|
||||
"validation_results",
|
||||
"workspace_mutations",
|
||||
)
|
||||
|
||||
|
||||
def _clean(value):
|
||||
return (value or "").strip() if isinstance(value, str) else value
|
||||
|
||||
|
||||
def _classify_task(task_type):
|
||||
task = _clean(task_type)
|
||||
if not task:
|
||||
return "", "unknown"
|
||||
if task in DETERMINISTIC_WRITE_TASKS:
|
||||
return task, "deterministic_write"
|
||||
if task in READ_ONLY_TASKS:
|
||||
return task, "read_only"
|
||||
return task, "unknown"
|
||||
|
||||
|
||||
def _missing_context_fields(inherited_context):
|
||||
context = inherited_context or {}
|
||||
missing = []
|
||||
for field in REQUIRED_INHERITED_CONTEXT:
|
||||
value = context.get(field)
|
||||
if not isinstance(value, str) or not value.strip():
|
||||
missing.append(field)
|
||||
return missing
|
||||
|
||||
|
||||
def assess_subagent_delegation(task_type, *, explicitly_allowed=False,
|
||||
justification=None, inherited_context=None):
|
||||
"""Decide whether delegating *task_type* to a subagent may proceed.
|
||||
|
||||
Fail closed: unknown tasks are blocked; deterministic write tasks are
|
||||
blocked unless explicitly allowed (AC1) with a recorded justification
|
||||
(AC2) and the full inherited gate context (AC3). Read-only delegation
|
||||
is allowed without explicit authorization.
|
||||
|
||||
Returns {'block', 'allowed', 'task_type', 'task_class', 'reasons',
|
||||
'missing_context'}.
|
||||
"""
|
||||
task, task_class = _classify_task(task_type)
|
||||
reasons = []
|
||||
missing_context = []
|
||||
|
||||
if task_class == "unknown":
|
||||
reasons.append(
|
||||
f"task type '{task}' is not a recognized delegation class; "
|
||||
"run it inline in the parent session (fail closed)"
|
||||
)
|
||||
elif task_class == "deterministic_write":
|
||||
if not explicitly_allowed:
|
||||
reasons.append(
|
||||
f"deterministic write task '{task}' must run inline unless "
|
||||
"subagent use is explicitly allowed by the parent session"
|
||||
)
|
||||
if not _clean(justification):
|
||||
reasons.append(
|
||||
"no recorded justification for why a subagent is needed; "
|
||||
"the parent session must record one before delegating"
|
||||
)
|
||||
missing_context = _missing_context_fields(inherited_context)
|
||||
if missing_context:
|
||||
reasons.append(
|
||||
"subagent would not inherit required gate context: "
|
||||
+ ", ".join(missing_context)
|
||||
)
|
||||
|
||||
block = bool(reasons)
|
||||
return {
|
||||
"block": block,
|
||||
"allowed": not block,
|
||||
"task_type": task,
|
||||
"task_class": task_class,
|
||||
"reasons": reasons,
|
||||
"missing_context": missing_context,
|
||||
}
|
||||
|
||||
|
||||
def validate_subagent_report(report_fields):
|
||||
"""AC4: accept subagent output only with the parent-grade proof fields.
|
||||
|
||||
*report_fields* maps field name -> reported value. Missing or blank
|
||||
proof fields make the report invalid (fail closed).
|
||||
|
||||
Returns {'valid', 'block', 'reasons', 'missing_fields'}.
|
||||
"""
|
||||
reasons = []
|
||||
report = report_fields or {}
|
||||
missing = []
|
||||
for field in REQUIRED_SUBAGENT_REPORT_FIELDS:
|
||||
value = report.get(field)
|
||||
if not isinstance(value, str) or not value.strip():
|
||||
missing.append(field)
|
||||
if missing:
|
||||
reasons.append(
|
||||
"subagent final report missing required proof fields: "
|
||||
+ ", ".join(missing)
|
||||
)
|
||||
|
||||
valid = not reasons
|
||||
return {
|
||||
"valid": valid,
|
||||
"block": not valid,
|
||||
"reasons": reasons,
|
||||
"missing_fields": missing,
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
"""Identity-disclosure checks for workflow reports (#305).
|
||||
|
||||
Workflow reports sometimes included the authenticated user's personal
|
||||
email even though username/profile identity is sufficient (observed:
|
||||
``Identity: jcwalker3 / jcwalker3@yahoo.com`` in a reconciliation
|
||||
handoff). These tests pin the no-email identity summary helper and the
|
||||
final-report validator that flags unnecessary email disclosure.
|
||||
"""
|
||||
import sys
|
||||
import unittest
|
||||
|
||||
sys.path.insert(0, str(__import__("pathlib").Path(__file__).resolve().parent.parent))
|
||||
|
||||
import review_proofs
|
||||
|
||||
|
||||
class TestIdentitySummary(unittest.TestCase):
|
||||
def test_author_summary_uses_username_and_profile_only(self):
|
||||
summary = review_proofs.format_identity_summary(
|
||||
"jcwalker3", "prgs-author")
|
||||
self.assertEqual(summary, "jcwalker3 / prgs-author")
|
||||
self.assertNotIn("@", summary)
|
||||
|
||||
def test_reviewer_summary_uses_username_and_profile_only(self):
|
||||
summary = review_proofs.format_identity_summary(
|
||||
"sysadmin", "prgs-reviewer")
|
||||
self.assertEqual(summary, "sysadmin / prgs-reviewer")
|
||||
|
||||
def test_summary_appends_role_and_remote_without_email(self):
|
||||
summary = review_proofs.format_identity_summary(
|
||||
"jcwalker3", "prgs-author", role="author", remote="prgs")
|
||||
self.assertIn("jcwalker3 / prgs-author", summary)
|
||||
self.assertIn("author", summary)
|
||||
self.assertIn("prgs", summary)
|
||||
self.assertNotIn("@", summary)
|
||||
|
||||
def test_summary_never_leaks_email_passed_as_username(self):
|
||||
"""Defense in depth: an email in the username slot is reduced."""
|
||||
summary = review_proofs.format_identity_summary(
|
||||
"[email protected]", "prgs-author")
|
||||
self.assertNotIn("@", summary)
|
||||
self.assertIn("jcwalker3", summary)
|
||||
|
||||
|
||||
class TestEmailDisclosureAssessment(unittest.TestCase):
|
||||
def test_report_without_email_passes(self):
|
||||
report = "\n".join([
|
||||
"Controller Handoff",
|
||||
"Identity: jcwalker3 / prgs-author",
|
||||
"Role: author",
|
||||
])
|
||||
res = review_proofs.assess_email_disclosure(report)
|
||||
self.assertTrue(res["proven"])
|
||||
self.assertFalse(res["flagged"])
|
||||
self.assertEqual(res["emails"], [])
|
||||
self.assertEqual(res["reasons"], [])
|
||||
|
||||
def test_unnecessary_email_is_flagged(self):
|
||||
report = "\n".join([
|
||||
"Controller Handoff",
|
||||
"Identity: jcwalker3 / [email protected]",
|
||||
])
|
||||
res = review_proofs.assess_email_disclosure(report)
|
||||
self.assertFalse(res["proven"])
|
||||
self.assertTrue(res["flagged"])
|
||||
self.assertIn("[email protected]", res["emails"])
|
||||
self.assertTrue(res["reasons"])
|
||||
self.assertFalse(res["justified"])
|
||||
|
||||
def test_multiple_emails_all_reported(self):
|
||||
report = (
|
||||
"Identity: [email protected] author\n"
|
||||
"Reviewer: [email protected]\n"
|
||||
)
|
||||
res = review_proofs.assess_email_disclosure(report)
|
||||
self.assertTrue(res["flagged"])
|
||||
self.assertEqual(
|
||||
sorted(res["emails"]),
|
||||
["[email protected]", "[email protected]"],
|
||||
)
|
||||
|
||||
def test_justified_email_with_explanation_is_not_flagged(self):
|
||||
report = "\n".join([
|
||||
"Identity: jcwalker3 / prgs-author",
|
||||
"Contact email: [email protected]",
|
||||
"Email required because two accounts share the username and the",
|
||||
"address is necessary to disambiguate identity.",
|
||||
])
|
||||
res = review_proofs.assess_email_disclosure(report)
|
||||
self.assertFalse(res["flagged"])
|
||||
self.assertTrue(res["justified"])
|
||||
self.assertIn("[email protected]", res["emails"])
|
||||
|
||||
def test_email_without_justification_language_is_flagged(self):
|
||||
report = "Contact email: [email protected] just in case.\n"
|
||||
res = review_proofs.assess_email_disclosure(report)
|
||||
self.assertTrue(res["flagged"])
|
||||
self.assertFalse(res["justified"])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -3,15 +3,62 @@ from pathlib import Path
|
||||
|
||||
REPO_ROOT = Path(__file__).resolve().parent.parent
|
||||
SKILL_DIR = REPO_ROOT / "skills" / "llm-project-workflow"
|
||||
SKILL = (SKILL_DIR / "SKILL.md").read_text(encoding="utf-8")
|
||||
|
||||
|
||||
def test_review_merge_workflow_file_exists():
|
||||
path = SKILL_DIR / "workflows" / "review-merge-pr.md"
|
||||
text = path.read_text(encoding="utf-8")
|
||||
def test_skill_md_exists():
|
||||
assert SKILL_DIR.joinpath("SKILL.md").is_file()
|
||||
|
||||
|
||||
def test_all_workflow_files_exist():
|
||||
for name in (
|
||||
"review-merge-pr.md",
|
||||
"reconcile-landed-pr.md",
|
||||
"create-issue.md",
|
||||
"work-issue.md",
|
||||
):
|
||||
assert (SKILL_DIR / "workflows" / name).is_file(), name
|
||||
|
||||
|
||||
def test_skill_references_all_workflow_files():
|
||||
for name in (
|
||||
"workflows/review-merge-pr.md",
|
||||
"workflows/reconcile-landed-pr.md",
|
||||
"workflows/create-issue.md",
|
||||
"workflows/work-issue.md",
|
||||
):
|
||||
assert name in SKILL
|
||||
|
||||
|
||||
def test_skill_contains_mode_isolation_language():
|
||||
assert "## Mode isolation" in SKILL
|
||||
assert "review-merge-pr" in SKILL
|
||||
assert "reconcile-landed-pr" in SKILL
|
||||
assert "create-issue" in SKILL
|
||||
assert "work-issue" in SKILL
|
||||
assert "Do not mix modes" in SKILL or "do not mix modes" in SKILL.lower()
|
||||
|
||||
|
||||
def test_skill_is_router_not_monolithic_review_body():
|
||||
assert "This skill is a **router**" in SKILL or "router" in SKILL.lower()
|
||||
assert "## F. Review workflow" not in SKILL
|
||||
assert "gitea_mark_final_review_decision" not in SKILL
|
||||
assert "gitea_submit_pr_review" not in SKILL
|
||||
|
||||
|
||||
def test_skill_still_declares_controller_handoff_contract():
|
||||
assert "## Controller Handoff" in SKILL
|
||||
assert "assess_controller_handoff" in SKILL
|
||||
assert "## Global LLM Worktree Rule" in SKILL
|
||||
|
||||
|
||||
def test_review_merge_workflow_contract():
|
||||
text = (SKILL_DIR / "workflows" / "review-merge-pr.md").read_text(encoding="utf-8")
|
||||
assert "canonical: true" in text
|
||||
assert "review-merge-pr" in text
|
||||
assert "## 0. Load the canonical workflow first" in text
|
||||
assert "## 26A. Terminal review mutation hard-stop" in text
|
||||
assert "## 11A. Skipped PRs are read-only" in text
|
||||
assert "## 35. Duplicate request-changes prevention" in text
|
||||
assert "## 37. Controller handoff schema" in text
|
||||
assert "INVENTORY_PAGINATION_UNPROVEN" in text
|
||||
assert "ALREADY_LANDED_RECONCILE_REQUIRED" in text
|
||||
@@ -23,29 +70,49 @@ def test_review_merge_final_report_schema_exists():
|
||||
assert "Controller Handoff" in text
|
||||
assert "Candidate head SHA:" in text
|
||||
assert "Terminal review mutation:" in text
|
||||
assert "Workspace mutations" in text # documented as rejected
|
||||
assert "Workspace mutations" in text
|
||||
|
||||
|
||||
def test_skill_router_points_to_review_merge_workflow():
|
||||
skill = (SKILL_DIR / "SKILL.md").read_text(encoding="utf-8")
|
||||
assert "## Task mode router" in skill
|
||||
assert "workflows/review-merge-pr.md" in skill
|
||||
assert "schemas/review-merge-final-report.md" in skill
|
||||
assert "Identify task mode before any mutation" in skill
|
||||
def test_reconcile_landed_workflow_contract():
|
||||
text = (SKILL_DIR / "workflows" / "reconcile-landed-pr.md").read_text(encoding="utf-8")
|
||||
assert "canonical: true" in text
|
||||
assert "Do not review or merge normal PRs" in text
|
||||
assert "Already-landed proof" in text
|
||||
|
||||
|
||||
def test_skill_still_declares_controller_handoff_contract():
|
||||
skill = (SKILL_DIR / "SKILL.md").read_text(encoding="utf-8")
|
||||
assert "## Controller Handoff" in skill
|
||||
assert "assess_controller_handoff" in skill
|
||||
assert "## Global LLM Worktree Rule" in skill
|
||||
def test_create_issue_workflow_contract():
|
||||
text = (SKILL_DIR / "workflows" / "create-issue.md").read_text(encoding="utf-8")
|
||||
assert "canonical: true" in text
|
||||
assert "## 9. Duplicate search before mutation" in text
|
||||
|
||||
|
||||
def test_review_pr_template_references_extracted_workflow():
|
||||
def test_work_issue_workflow_contract():
|
||||
text = (SKILL_DIR / "workflows" / "work-issue.md").read_text(encoding="utf-8")
|
||||
assert "canonical: true" in text
|
||||
assert "Do not merge your own PR" in text
|
||||
assert "## 21. PR creation rules" in text
|
||||
|
||||
|
||||
def test_final_report_schemas_exist():
|
||||
for name in (
|
||||
"review-merge-final-report.md",
|
||||
"reconcile-landed-final-report.md",
|
||||
"create-issue-final-report.md",
|
||||
"work-issue-final-report.md",
|
||||
):
|
||||
assert (SKILL_DIR / "schemas" / name).is_file(), name
|
||||
|
||||
|
||||
def test_review_pr_template_references_workflow():
|
||||
text = (SKILL_DIR / "templates" / "review-pr.md").read_text(encoding="utf-8")
|
||||
assert "workflows/review-merge-pr.md" in text
|
||||
|
||||
|
||||
def test_merge_pr_template_references_extracted_workflow():
|
||||
def test_merge_pr_template_references_workflow():
|
||||
text = (SKILL_DIR / "templates" / "merge-pr.md").read_text(encoding="utf-8")
|
||||
assert "workflows/review-merge-pr.md" in text
|
||||
assert "workflows/review-merge-pr.md" in text
|
||||
|
||||
|
||||
def test_start_issue_template_references_work_issue_workflow():
|
||||
text = (SKILL_DIR / "templates" / "start-issue.md").read_text(encoding="utf-8")
|
||||
assert "workflows/work-issue.md" in text
|
||||
@@ -0,0 +1,102 @@
|
||||
"""Tests for final-report mutation ledger verifier (#331)."""
|
||||
import sys
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
|
||||
|
||||
from review_proofs import assess_mutation_ledger_report # noqa: E402
|
||||
|
||||
|
||||
class TestMutationLedgerReport(unittest.TestCase):
|
||||
def test_none_claim_with_performed_edit_blocks(self):
|
||||
report = (
|
||||
"Review decision: request_changes.\n"
|
||||
"File edits by reviewer: none\n"
|
||||
)
|
||||
action_log = [
|
||||
{"action": "Edited", "path": "walkthrough.md", "tracked": False},
|
||||
]
|
||||
result = assess_mutation_ledger_report(report, action_log=action_log)
|
||||
self.assertFalse(result["proven"])
|
||||
self.assertTrue(result["file_edits_claimed_none"])
|
||||
|
||||
def test_reported_edit_passes(self):
|
||||
report = (
|
||||
"File edits by reviewer: Edited walkthrough.md (untracked)\n"
|
||||
"Mutation ledger: Edited walkthrough.md untracked in review worktree\n"
|
||||
)
|
||||
action_log = [
|
||||
{
|
||||
"action": "Edited",
|
||||
"path": "walkthrough.md",
|
||||
"tracked": False,
|
||||
"in_repo": True,
|
||||
},
|
||||
]
|
||||
result = assess_mutation_ledger_report(
|
||||
report,
|
||||
action_log=action_log,
|
||||
walkthrough_explicitly_requested=True,
|
||||
)
|
||||
self.assertTrue(result["proven"])
|
||||
|
||||
def test_unreported_mutation_blocks(self):
|
||||
report = "File edits by reviewer: none\n"
|
||||
action_log = [{"action": "Wrote", "path": "/tmp/review-notes.txt"}]
|
||||
result = assess_mutation_ledger_report(report, action_log=action_log)
|
||||
self.assertFalse(result["proven"])
|
||||
self.assertIn("/tmp/review-notes.txt", result["unreported_paths"])
|
||||
|
||||
def test_outside_repo_requires_label(self):
|
||||
report = (
|
||||
"File edits by reviewer: Wrote /tmp/review-notes.txt\n"
|
||||
)
|
||||
action_log = [
|
||||
{
|
||||
"action": "Wrote",
|
||||
"path": "/tmp/review-notes.txt",
|
||||
"outside_repo": True,
|
||||
},
|
||||
]
|
||||
result = assess_mutation_ledger_report(report, action_log=action_log)
|
||||
self.assertFalse(result["proven"])
|
||||
self.assertIn("outside repo", " ".join(result["reasons"]).lower())
|
||||
|
||||
def test_gated_rejection_excluded_from_performed(self):
|
||||
report = "File edits by reviewer: none\nRejected gated calls: submit_pr_review blocked\n"
|
||||
action_log = [
|
||||
{
|
||||
"action": "Edited",
|
||||
"path": "walkthrough.md",
|
||||
"performed": False,
|
||||
"gated_rejected": True,
|
||||
},
|
||||
]
|
||||
result = assess_mutation_ledger_report(report, action_log=action_log)
|
||||
self.assertTrue(result["proven"])
|
||||
self.assertEqual(result["performed_mutations"], [])
|
||||
|
||||
def test_post_status_artifact_requires_final_git_status(self):
|
||||
report = (
|
||||
"File edits by reviewer: Created scratch/notes.md (untracked)\n"
|
||||
)
|
||||
action_log = [
|
||||
{
|
||||
"action": "Created",
|
||||
"path": "scratch/notes.md",
|
||||
"tracked": False,
|
||||
"after_git_status": True,
|
||||
},
|
||||
]
|
||||
result = assess_mutation_ledger_report(
|
||||
report,
|
||||
action_log=action_log,
|
||||
final_git_status_reported=False,
|
||||
)
|
||||
self.assertFalse(result["proven"])
|
||||
self.assertIn("final git status", " ".join(result["reasons"]).lower())
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -0,0 +1,104 @@
|
||||
"""Tests for REQUEST_CHANGES override proof before approval (#326)."""
|
||||
import sys
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
|
||||
|
||||
from review_proofs import assess_request_changes_approval_proof # noqa: E402
|
||||
|
||||
HEAD_A = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
|
||||
HEAD_B = "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
|
||||
|
||||
|
||||
def _feedback(**overrides):
|
||||
base = {
|
||||
"success": True,
|
||||
"current_head_sha": HEAD_A,
|
||||
"has_blocking_change_requests": False,
|
||||
"author_pushed_after_request_changes": False,
|
||||
"reviews": [],
|
||||
}
|
||||
base.update(overrides)
|
||||
return base
|
||||
|
||||
|
||||
def _blocking_review(**overrides):
|
||||
entry = {
|
||||
"reviewer": "reviewer-a",
|
||||
"verdict": "REQUEST_CHANGES",
|
||||
"body": "Tests fail on pinned head; fix test_commit_files_gate.",
|
||||
"submitted_at": "2026-07-07T01:00:00-05:00",
|
||||
"reviewed_head_sha": HEAD_A,
|
||||
"dismissed": False,
|
||||
"stale": False,
|
||||
}
|
||||
entry.update(overrides)
|
||||
return entry
|
||||
|
||||
|
||||
class TestRequestChangesApprovalProof(unittest.TestCase):
|
||||
def test_missing_feedback_blocks_approval(self):
|
||||
result = assess_request_changes_approval_proof(None)
|
||||
self.assertFalse(result["approve_allowed"])
|
||||
self.assertTrue(result["block"])
|
||||
|
||||
def test_no_prior_request_changes_allows_approval(self):
|
||||
feedback = _feedback(
|
||||
reviews=[{
|
||||
"reviewer": "sysadmin",
|
||||
"verdict": "APPROVED",
|
||||
"body": "LGTM",
|
||||
"submitted_at": "2026-07-07T02:00:00-05:00",
|
||||
"reviewed_head_sha": HEAD_A,
|
||||
"dismissed": False,
|
||||
}]
|
||||
)
|
||||
result = assess_request_changes_approval_proof(feedback)
|
||||
self.assertTrue(result["approve_allowed"])
|
||||
self.assertIsNone(result["blocking_review"])
|
||||
|
||||
def test_changed_head_since_blocker_allows_approval(self):
|
||||
feedback = _feedback(
|
||||
current_head_sha=HEAD_B,
|
||||
author_pushed_after_request_changes=True,
|
||||
has_blocking_change_requests=True,
|
||||
reviews=[_blocking_review(reviewed_head_sha=HEAD_A)],
|
||||
)
|
||||
result = assess_request_changes_approval_proof(feedback)
|
||||
self.assertTrue(result["approve_allowed"])
|
||||
self.assertTrue(result["head_changed_since_blocker"])
|
||||
|
||||
def test_unchanged_head_without_override_blocks_approval(self):
|
||||
feedback = _feedback(
|
||||
has_blocking_change_requests=True,
|
||||
reviews=[_blocking_review()],
|
||||
)
|
||||
result = assess_request_changes_approval_proof(feedback)
|
||||
self.assertFalse(result["approve_allowed"])
|
||||
self.assertIn("override_reason", " ".join(result["reasons"]))
|
||||
|
||||
def test_unchanged_head_with_valid_override_allows_approval(self):
|
||||
blocker = _blocking_review()
|
||||
feedback = _feedback(
|
||||
has_blocking_change_requests=True,
|
||||
reviews=[blocker],
|
||||
)
|
||||
report = (
|
||||
"Blocker text: Tests fail on pinned head; fix test_commit_files_gate.\n"
|
||||
"Override: wrong_validation_environment — CI used stale worktree."
|
||||
)
|
||||
result = assess_request_changes_approval_proof(
|
||||
feedback,
|
||||
override_reason="wrong_validation_environment",
|
||||
override_explanation="CI used stale worktree; local rerun passed.",
|
||||
report_text=report,
|
||||
)
|
||||
self.assertTrue(result["approve_allowed"])
|
||||
self.assertEqual(
|
||||
result["blocking_review"]["blocking_reviewer"], "reviewer-a"
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -0,0 +1,152 @@
|
||||
"""Tests for fail-closed subagent delegation gates (#266).
|
||||
|
||||
Covers the acceptance criteria: blocked write delegation, allowed read-only
|
||||
delegation, missing inherited context, and invalid subagent final reports.
|
||||
"""
|
||||
import unittest
|
||||
|
||||
import subagent_gate
|
||||
|
||||
|
||||
def _full_context():
|
||||
return {
|
||||
"issue_lock": "issue #266 locked to feat/issue-266-subagent-gate-inheritance",
|
||||
"branch": "feat/issue-266-subagent-gate-inheritance",
|
||||
"worktree_path": "branches/feat-issue-266-subagent-gate-inheritance",
|
||||
"identity_profile": "jcwalker3/prgs-author",
|
||||
"allowed_tool_class": "read_write_files",
|
||||
"command_deny_list": "git push --force; rm -rf",
|
||||
"validation_ledger_requirement": "record command/exit/output for every validation claim",
|
||||
"final_report_schema": "controller-handoff-v1",
|
||||
}
|
||||
|
||||
|
||||
def _full_report():
|
||||
return {
|
||||
"identity_profile": "jcwalker3/prgs-author",
|
||||
"worktree_path": "branches/feat-issue-266-subagent-gate-inheritance",
|
||||
"branch": "feat/issue-266-subagent-gate-inheritance",
|
||||
"changed_files": "subagent_gate.py, tests/test_subagent_gate.py",
|
||||
"validation_results": "pytest tests/test_subagent_gate.py -q: exit 0, 14 passed",
|
||||
"workspace_mutations": "edited subagent_gate.py",
|
||||
}
|
||||
|
||||
|
||||
class TestAssessSubagentDelegation(unittest.TestCase):
|
||||
# AC1: deterministic write tasks are blocked by default
|
||||
def test_write_delegation_blocked_by_default(self):
|
||||
for task in ("claim_issue", "create_branch", "edit_code", "commit",
|
||||
"push_branch", "create_pr", "review_pr", "merge_pr",
|
||||
"cleanup_branch"):
|
||||
res = subagent_gate.assess_subagent_delegation(task)
|
||||
self.assertTrue(res["block"], task)
|
||||
self.assertFalse(res["allowed"], task)
|
||||
self.assertEqual(res["task_class"], "deterministic_write", task)
|
||||
self.assertTrue(
|
||||
any("explicitly allowed" in r for r in res["reasons"]), task)
|
||||
|
||||
# AC2: explicit allowance without a recorded justification still blocks
|
||||
def test_write_delegation_requires_recorded_justification(self):
|
||||
res = subagent_gate.assess_subagent_delegation(
|
||||
"create_pr", explicitly_allowed=True,
|
||||
inherited_context=_full_context())
|
||||
self.assertTrue(res["block"])
|
||||
self.assertTrue(
|
||||
any("justification" in r for r in res["reasons"]))
|
||||
|
||||
# AC3: explicit allowance + justification but missing inherited context
|
||||
def test_write_delegation_blocks_on_missing_inherited_context(self):
|
||||
context = _full_context()
|
||||
del context["issue_lock"]
|
||||
del context["command_deny_list"]
|
||||
res = subagent_gate.assess_subagent_delegation(
|
||||
"commit", explicitly_allowed=True,
|
||||
justification="parent session proved batch commit needs isolation",
|
||||
inherited_context=context)
|
||||
self.assertTrue(res["block"])
|
||||
self.assertIn("issue_lock", res["missing_context"])
|
||||
self.assertIn("command_deny_list", res["missing_context"])
|
||||
|
||||
def test_write_delegation_blocks_on_empty_context_values(self):
|
||||
context = _full_context()
|
||||
context["worktree_path"] = " "
|
||||
res = subagent_gate.assess_subagent_delegation(
|
||||
"commit", explicitly_allowed=True,
|
||||
justification="isolation required",
|
||||
inherited_context=context)
|
||||
self.assertTrue(res["block"])
|
||||
self.assertIn("worktree_path", res["missing_context"])
|
||||
|
||||
def test_write_delegation_allowed_with_authorization_and_full_context(self):
|
||||
res = subagent_gate.assess_subagent_delegation(
|
||||
"edit_code", explicitly_allowed=True,
|
||||
justification="parallel mechanical rename across many files",
|
||||
inherited_context=_full_context())
|
||||
self.assertFalse(res["block"])
|
||||
self.assertTrue(res["allowed"])
|
||||
self.assertEqual(res["missing_context"], [])
|
||||
|
||||
# Allowed read-only delegation needs no explicit authorization
|
||||
def test_read_only_delegation_allowed(self):
|
||||
for task in ("read_files", "code_search", "inventory_prs",
|
||||
"summarize_issue"):
|
||||
res = subagent_gate.assess_subagent_delegation(task)
|
||||
self.assertFalse(res["block"], task)
|
||||
self.assertTrue(res["allowed"], task)
|
||||
self.assertEqual(res["task_class"], "read_only", task)
|
||||
|
||||
# Unknown task types fail closed
|
||||
def test_unknown_task_fails_closed(self):
|
||||
res = subagent_gate.assess_subagent_delegation("launch_missiles")
|
||||
self.assertTrue(res["block"])
|
||||
self.assertEqual(res["task_class"], "unknown")
|
||||
|
||||
def test_blank_task_fails_closed(self):
|
||||
res = subagent_gate.assess_subagent_delegation(" ")
|
||||
self.assertTrue(res["block"])
|
||||
self.assertEqual(res["task_class"], "unknown")
|
||||
|
||||
|
||||
class TestValidateSubagentReport(unittest.TestCase):
|
||||
# AC4: subagent output must carry the same proof fields as the parent
|
||||
def test_full_report_valid(self):
|
||||
res = subagent_gate.validate_subagent_report(_full_report())
|
||||
self.assertTrue(res["valid"])
|
||||
self.assertFalse(res["block"])
|
||||
self.assertEqual(res["missing_fields"], [])
|
||||
|
||||
def test_missing_proof_fields_invalid(self):
|
||||
report = _full_report()
|
||||
del report["validation_results"]
|
||||
del report["worktree_path"]
|
||||
res = subagent_gate.validate_subagent_report(report)
|
||||
self.assertFalse(res["valid"])
|
||||
self.assertTrue(res["block"])
|
||||
self.assertIn("validation_results", res["missing_fields"])
|
||||
self.assertIn("worktree_path", res["missing_fields"])
|
||||
|
||||
def test_empty_field_values_invalid(self):
|
||||
report = _full_report()
|
||||
report["changed_files"] = ""
|
||||
res = subagent_gate.validate_subagent_report(report)
|
||||
self.assertFalse(res["valid"])
|
||||
self.assertIn("changed_files", res["missing_fields"])
|
||||
|
||||
def test_none_report_invalid(self):
|
||||
res = subagent_gate.validate_subagent_report(None)
|
||||
self.assertFalse(res["valid"])
|
||||
self.assertTrue(res["block"])
|
||||
|
||||
|
||||
class TestOperatorGuideRule(unittest.TestCase):
|
||||
def test_operator_guide_declares_subagent_rule(self):
|
||||
import gitea_mcp_server
|
||||
|
||||
rule = gitea_mcp_server._GUIDE_RULES["subagent_delegation"].lower()
|
||||
for phrase in ("deterministic write", "inherit", "read-only",
|
||||
"fail closed"):
|
||||
self.assertIn(phrase, rule)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user