install: default jj setup in one-click flow
Some checks failed
publish-site / deploy-public-site (push) Has been cancelled
preview-slot-reclaim / reclaim (push) Has been cancelled

This commit is contained in:
2026-03-13 14:14:09 +08:00
parent ccfdda4342
commit 594c7e1a4d
6 changed files with 711 additions and 10 deletions

View File

@@ -33,6 +33,9 @@
## 一键安装
安装器现在会先安装 skill再默认尝试安装 `jj`
如果 `jj` 因本机环境、包管理器或网络原因安装失败,安装器不会失败,只会给出手动安装提示。
### Linux
```bash
@@ -55,6 +58,41 @@ powershell -NoProfile -ExecutionPolicy Bypass -Command "iwr -useb https://fun-md
- `~/.codex/skills/gitea-issue-devops-agent`
默认 `jj` 安装顺序:
- Linux / macOS`brew -> cargo-binstall -> cargo`
- Windows`winget -> scoop -> cargo`
安装控制项:
### Bash
```bash
INSTALL_JJ=0
JJ_INSTALL_METHOD=auto|brew|binstall|cargo
JJ_CHANNEL=release|prerelease
```
### PowerShell
```powershell
$env:INSTALL_JJ='0'
$env:JJ_INSTALL_METHOD='auto' # 或 winget / scoop / cargo
$env:JJ_CHANNEL='release' # 或 prerelease
```
验证命令:
```bash
jj --version
jj config set --user user.name "Your Name"
jj config set --user user.email "you@example.com"
```
详细说明:
- `skills/gitea-issue-devops-agent/references/jj-default-usage.md`
## 工具使用说明
### issue_audit.py
@@ -97,6 +135,11 @@ python skills/gitea-issue-devops-agent/scripts/preview_slot_allocator.py --state
- `issue` 或固定 issue 触发来源
- 可选:`target_base``plan_path``reviewers``test_entry``deploy_env``health_endpoint``min_quality_score``jj_policy`
推荐默认值:
- `jj_policy=optional-internal`
- 先固定 issue再进入 `Plan -> Draft PR -> 提测 -> 人工确认合并`
### Claude Code
Skills 目录(官方支持):
@@ -161,6 +204,14 @@ api_key=<TOKEN>
mode=manual
```
## `jj` 在工作流中的定位
默认安装 `jj`,但不要求非工程角色理解 `jj`
- 对外:继续使用 `issue / git branch / PR / CI/CD / review apps`
- 对内:用 `jj` 承担本地执行、回退、并行 workspace、变更重写
- 原则:`jj` 是内部可靠性增强层,不替代你们对外的 Git/Gitea 协作界面
## `skills` 命令参数释义(重点补充)
> 本节把“`skills` 命令”统一理解为:在 Claude/Codex/OpenCode 中显式调用 `gitea-issue-devops-agent` 时提交的参数块。
@@ -213,6 +264,14 @@ min_quality_score=70
适用:问题描述完整、团队希望最大化自动化吞吐。
典型流程:
1. 人工在 Gitea 选中 issue。
2. MajorAgent 生成 Plan 并创建分支、Draft PR。
3. SubAgent 只读取必要上下文并修改计划内路径。
4. TestAgent 跑单测、集成测试、issue 级 e2e。
5. 通过后进入 preview slot 和人工复核。
#### 场景 2生产敏感仓库人工确认每一步
```text
@@ -227,6 +286,13 @@ health_endpoint: /healthz
适用:高风险改动、强合规流程、需要逐步确认分支/提交/提测/关闭。
典型流程:
1. 先生成 Plan。
2. 每次代码改动前都确认允许范围。
3. 提测、回写 issue、关闭 issue、最终 merge 都要人工确认。
4. 如 AI 偏航,可用 `jj` 做本地回退而不破坏外部 PR。
#### 场景 3半自动协作先评审后提测
```text
@@ -243,6 +309,13 @@ preview_slots=preview-a,preview-b,preview-c
适用:多人协作项目,需要评审人显式批准后再进入提测和环境分配。
典型流程:
1. AI 先产出初始 Draft PR。
2. 工程师在 AI 编码工具里继续白盒调整。
3. reviewer 回复 `review-approved` 后才进入提测。
4. maintainer 最后确认 merge。
#### 场景 4仅文档改动或轻量改动资源最省策略
```text
@@ -251,10 +324,34 @@ preview_slots=preview-a,preview-b,preview-c
配合 `change_scope.py` 可自动得到 `skip``client_only`,避免不必要的服务端重启和环境开销。
#### 场景 5多 Agent 并行,但上下文不脑裂
```text
issue=48
mode=semi-automatic
jj_policy=optional-internal
plan_path=.tmp/devops-plans/devops-skills__issue-48.md
```
典型流程:
1. MajorAgent 只负责 issue 语义分析和 Plan。
2. SubAgent 只负责修改代码。
3. TestAgent 在独立 `jj workspace` 里验证。
4. 人工 reviewer 再决定是否继续迭代或合并。
适用:长流程、多角色协作、希望降低 token 消耗和上下文漂移。
## 技能路径
- `skills/gitea-issue-devops-agent/SKILL.md`
## 核心文档
- `skills/gitea-issue-devops-agent/references/issue-template-standard.md`
- `skills/gitea-issue-devops-agent/references/plan-template.md`
- `skills/gitea-issue-devops-agent/references/jj-default-usage.md`
## 核心脚本
- `skills/gitea-issue-devops-agent/scripts/issue_audit.py`

View File

@@ -0,0 +1,188 @@
# Jj Default Installation Implementation Plan
> **For agentic workers:** REQUIRED: Use superpowers:subagent-driven-development (if subagents available) or superpowers:executing-plans to implement this plan. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Make `jj` a default installation target in this repository's one-command installers without making installer success depend on `jj` success, and expand documentation with clearer usage instructions and scenario-based examples.
**Architecture:** Keep the public installer entrypoints unchanged while adding an internal second phase that attempts platform-appropriate `jj` installation. Preserve deterministic skill installation first, then run best-effort `jj` installation with explicit verification and manual fallback instructions.
**Tech Stack:** Bash, PowerShell, Git, README Markdown, skill reference docs
---
## Chunk 1: Installer Behavior
### Task 1: Add plan-aware installer controls to the Bash installer
**Files:**
- Modify: `install/install.sh`
- Test: `install/install.sh` syntax via `bash -n`
- [ ] **Step 1: Define installer controls**
Add environment-driven controls near the top of `install/install.sh`:
- `INSTALL_JJ` default `1`
- `JJ_INSTALL_METHOD` default `auto`
- `JJ_CHANNEL` default `release`
- [ ] **Step 2: Add `jj` install attempt helpers**
Implement focused functions for:
- logging
- command existence checks
- `brew` install path
- `cargo-binstall` install path
- `cargo install` fallback path
- manual fallback message
- [ ] **Step 3: Add OS-aware default attempt flow**
Implement this order:
- macOS: `brew`, then `cargo-binstall`, then `cargo`
- Linux: `brew` if available, then `cargo-binstall`, then `cargo`
- [ ] **Step 4: Keep skill installation authoritative**
Ensure the script:
- installs the skill first
- attempts `jj` only after the skill copy succeeds
- never exits non-zero only because `jj` failed
- [ ] **Step 5: Verify shell syntax**
Run: `bash -n install/install.sh`
Expected: no output, zero exit code
### Task 2: Add plan-aware installer controls to the PowerShell installer
**Files:**
- Modify: `install/install.ps1`
- Test: `install/install.ps1` parse check via PowerShell
- [ ] **Step 1: Define parameters and defaults**
Add parameters:
- `SkipJj`
- `JjInstallMethod = "auto"`
- `JjChannel = "release"`
- [ ] **Step 2: Add `jj` install attempt helpers**
Implement functions for:
- logging
- command existence checks
- `winget` install path
- `scoop` install path
- `cargo install` fallback path
- manual fallback message
- [ ] **Step 3: Add Windows attempt flow**
Implement this order:
- `winget`
- `scoop`
- `cargo`
- [ ] **Step 4: Keep installer non-blocking for `jj`**
Ensure skill installation still succeeds even if all `jj` attempts fail.
- [ ] **Step 5: Verify script parses**
Run: `powershell -NoProfile -Command "[void][scriptblock]::Create((Get-Content -Raw 'install/install.ps1'))"`
Expected: no output, zero exit code
## Chunk 2: Documentation
### Task 3: Expand README installation and usage guidance
**Files:**
- Modify: `README.md`
- [ ] **Step 1: Update one-command install sections**
Document that installers now:
- install the skill
- attempt `jj` by default
- continue with warnings if `jj` cannot be installed automatically
- [ ] **Step 2: Add installer controls and verification**
Document:
- `INSTALL_JJ=0`
- `JJ_INSTALL_METHOD`
- `JJ_CHANNEL`
- PowerShell equivalents
- `jj --version`
- initial `jj config` commands
- [ ] **Step 3: Add richer scenario examples**
Include examples for:
- first-time team setup
- automatic bug fix flow
- semi-automatic engineering review flow
- manual hotfix flow
- multi-agent flow with `jj` workspaces
### Task 4: Add detailed `jj` usage guide
**Files:**
- Create: `skills/gitea-issue-devops-agent/references/jj-default-usage.md`
- [ ] **Step 1: Explain repository policy**
Document:
- `jj` is default to install
- `jj` is internal execution infrastructure
- Git branches/PRs/CI remain public system of record
- [ ] **Step 2: Explain verification and fallback**
Document:
- how to verify installation
- how to skip or force methods
- what to do when package managers are unavailable
- [ ] **Step 3: Add workflow examples**
Provide concrete examples for:
- issue -> plan -> draft PR
- engineer review after initial AI PR
- `jj workspace` for TestAgent and human reviewer
- rollback with operation log
## Chunk 3: Verification and Delivery
### Task 5: Verify, commit, and push
**Files:**
- Modify: repo index and git history
- [ ] **Step 1: Run syntax checks**
Run:
- `bash -n install/install.sh`
- `powershell -NoProfile -Command "[void][scriptblock]::Create((Get-Content -Raw 'install/install.ps1'))"`
Expected: both succeed
- [ ] **Step 2: Review diff**
Run: `git diff --check`
Expected: no diff formatting errors
- [ ] **Step 3: Commit**
Run:
```bash
git add README.md install/install.sh install/install.ps1 docs/superpowers/plans/2026-03-13-jj-default-installation-plan.md skills/gitea-issue-devops-agent/references/jj-default-usage.md
git commit -m "docs: default jj installation in installers"
```
- [ ] **Step 4: Push**
Run:
```bash
git push origin main
```

View File

@@ -1,6 +1,11 @@
param(
[string]$RepoUrl = "https://fun-md.com/Fun_MD/devops-skills.git",
[string]$CodexHome = "$HOME\.codex"
[string]$CodexHome = "$HOME\.codex",
[switch]$SkipJj,
[ValidateSet("auto", "winget", "scoop", "cargo")]
[string]$JjInstallMethod = $(if ($env:JJ_INSTALL_METHOD) { $env:JJ_INSTALL_METHOD } else { "auto" }),
[ValidateSet("release", "prerelease")]
[string]$JjChannel = $(if ($env:JJ_CHANNEL) { $env:JJ_CHANNEL } else { "release" })
)
$ErrorActionPreference = "Stop"
@@ -8,13 +13,136 @@ $ErrorActionPreference = "Stop"
$skillName = "gitea-issue-devops-agent"
$targetDir = Join-Path $CodexHome "skills\$skillName"
$tmpRoot = Join-Path $env:TEMP ("devops-skills-" + [Guid]::NewGuid().ToString("N"))
$installJj = $true
if ($SkipJj.IsPresent) {
$installJj = $false
}
if ($env:INSTALL_JJ -and $env:INSTALL_JJ -eq "0") {
$installJj = $false
}
function Write-InstallLog {
param([string]$Message)
Write-Host "[install] $Message"
}
function Write-InstallWarn {
param([string]$Message)
Write-Warning "[install] $Message"
}
function Test-CommandAvailable {
param([string]$Name)
return $null -ne (Get-Command $Name -ErrorAction SilentlyContinue)
}
function Show-JjManualHelp {
Write-InstallWarn "jj was not installed automatically."
Write-InstallWarn "Manual options:"
Write-InstallWarn " - winget install jj-vcs.jj"
Write-InstallWarn " - scoop install main/jj"
if ($JjChannel -eq "prerelease") {
Write-InstallWarn " - cargo install --git https://github.com/jj-vcs/jj.git --locked --bin jj jj-cli"
}
else {
Write-InstallWarn " - cargo install --locked --bin jj jj-cli"
}
Write-InstallWarn "After installation, verify with: jj --version"
}
function Install-JjWithWinget {
if (-not (Test-CommandAvailable winget)) {
return $false
}
if ($JjChannel -ne "release") {
Write-InstallWarn "winget path skipped because prerelease jj is requested."
return $false
}
Write-InstallLog "attempting jj installation via winget"
& winget install --id jj-vcs.jj -e --accept-source-agreements --accept-package-agreements
return $LASTEXITCODE -eq 0
}
function Install-JjWithScoop {
if (-not (Test-CommandAvailable scoop)) {
return $false
}
if ($JjChannel -ne "release") {
Write-InstallWarn "scoop path skipped because prerelease jj is requested."
return $false
}
Write-InstallLog "attempting jj installation via scoop"
& scoop install main/jj
return $LASTEXITCODE -eq 0
}
function Install-JjWithCargo {
if (-not (Test-CommandAvailable cargo)) {
return $false
}
Write-InstallLog "attempting jj installation via cargo"
if ($JjChannel -eq "prerelease") {
& cargo install --git https://github.com/jj-vcs/jj.git --locked --bin jj jj-cli
}
else {
& cargo install --locked --bin jj jj-cli
}
return $LASTEXITCODE -eq 0
}
function Invoke-JjInstall {
if (-not $installJj) {
Write-InstallLog "skipping jj installation because INSTALL_JJ=0 or -SkipJj was provided"
return
}
if (Test-CommandAvailable jj) {
Write-InstallLog "jj already installed: $(& jj --version)"
return
}
$installed = $false
switch ($JjInstallMethod) {
"auto" {
$installed = (Install-JjWithWinget) -or (Install-JjWithScoop) -or (Install-JjWithCargo)
break
}
"winget" {
$installed = Install-JjWithWinget
break
}
"scoop" {
$installed = Install-JjWithScoop
break
}
"cargo" {
$installed = Install-JjWithCargo
break
}
}
if (-not $installed) {
Show-JjManualHelp
return
}
if (Test-CommandAvailable jj) {
Write-InstallLog "jj installation succeeded: $(& jj --version)"
}
else {
Show-JjManualHelp
}
}
try {
if (-not (Get-Command git -ErrorAction SilentlyContinue)) {
if (-not (Test-CommandAvailable git)) {
throw "[install] git is required but not found."
}
Write-Host "[install] downloading $skillName from $RepoUrl"
Write-InstallLog "downloading $skillName from $RepoUrl"
git clone --depth 1 $RepoUrl $tmpRoot | Out-Null
$sourceDir = Join-Path $tmpRoot "skills\$skillName"
@@ -28,8 +156,10 @@ try {
}
Copy-Item -Path $sourceDir -Destination $targetDir -Recurse -Force
Write-Host "[install] done"
Write-Host "[install] installed path: $targetDir"
Write-InstallLog "skill installed"
Write-InstallLog "installed path: $targetDir"
Invoke-JjInstall
Write-InstallLog "done"
}
finally {
if (Test-Path $tmpRoot) {

View File

@@ -6,6 +6,119 @@ SKILL_NAME="gitea-issue-devops-agent"
CODEX_HOME="${CODEX_HOME:-$HOME/.codex}"
TARGET_DIR="${CODEX_HOME}/skills/${SKILL_NAME}"
TMP_DIR="$(mktemp -d)"
INSTALL_JJ="${INSTALL_JJ:-1}"
JJ_INSTALL_METHOD="${JJ_INSTALL_METHOD:-auto}"
JJ_CHANNEL="${JJ_CHANNEL:-release}"
log() {
echo "[install] $*"
}
warn() {
echo "[install] warning: $*" >&2
}
manual_jj_help() {
warn "jj was not installed automatically."
warn "Manual options:"
warn " - Homebrew: brew install jj"
warn " - cargo-binstall: cargo binstall --strategies crate-meta-data jj-cli"
if [ "$JJ_CHANNEL" = "prerelease" ]; then
warn " - cargo prerelease: cargo install --git https://github.com/jj-vcs/jj.git --locked --bin jj jj-cli"
else
warn " - cargo release: cargo install --locked --bin jj jj-cli"
fi
warn "After installation, verify with: jj --version"
}
install_jj_with_brew() {
if ! command -v brew >/dev/null 2>&1; then
return 1
fi
if [ "$JJ_CHANNEL" != "release" ]; then
warn "brew path skipped because prerelease jj is requested."
return 1
fi
log "attempting jj installation via Homebrew"
brew install jj
}
install_jj_with_binstall() {
if ! command -v cargo >/dev/null 2>&1 || ! command -v cargo-binstall >/dev/null 2>&1; then
return 1
fi
if [ "$JJ_CHANNEL" != "release" ]; then
warn "cargo-binstall path skipped because prerelease jj is requested."
return 1
fi
log "attempting jj installation via cargo-binstall"
cargo binstall --strategies crate-meta-data jj-cli
}
install_jj_with_cargo() {
if ! command -v cargo >/dev/null 2>&1; then
return 1
fi
log "attempting jj installation via cargo"
if [ "$JJ_CHANNEL" = "prerelease" ]; then
cargo install --git https://github.com/jj-vcs/jj.git --locked --bin jj jj-cli
else
cargo install --locked --bin jj jj-cli
fi
}
attempt_jj_install() {
if [ "$INSTALL_JJ" = "0" ]; then
log "skipping jj installation because INSTALL_JJ=0"
return 0
fi
if command -v jj >/dev/null 2>&1; then
log "jj already installed: $(jj --version)"
return 0
fi
case "$JJ_INSTALL_METHOD" in
auto)
install_jj_with_brew || install_jj_with_binstall || install_jj_with_cargo || {
manual_jj_help
return 0
}
;;
brew)
install_jj_with_brew || {
manual_jj_help
return 0
}
;;
binstall)
install_jj_with_binstall || {
manual_jj_help
return 0
}
;;
cargo)
install_jj_with_cargo || {
manual_jj_help
return 0
}
;;
*)
warn "unsupported JJ_INSTALL_METHOD='$JJ_INSTALL_METHOD'; skipping jj install."
manual_jj_help
return 0
;;
esac
if command -v jj >/dev/null 2>&1; then
log "jj installation succeeded: $(jj --version)"
else
manual_jj_help
fi
}
cleanup() {
rm -rf "$TMP_DIR"
@@ -13,15 +126,15 @@ cleanup() {
trap cleanup EXIT
if ! command -v git >/dev/null 2>&1; then
echo "[install] git is required but not found."
log "git is required but not found."
exit 1
fi
echo "[install] downloading ${SKILL_NAME} from ${REPO_URL}"
log "downloading ${SKILL_NAME} from ${REPO_URL}"
git clone --depth 1 "$REPO_URL" "$TMP_DIR/repo" >/dev/null 2>&1
if [ ! -d "$TMP_DIR/repo/skills/${SKILL_NAME}" ]; then
echo "[install] skill directory not found in repository."
log "skill directory not found in repository."
exit 1
fi
@@ -29,5 +142,7 @@ mkdir -p "${CODEX_HOME}/skills"
rm -rf "$TARGET_DIR"
cp -R "$TMP_DIR/repo/skills/${SKILL_NAME}" "$TARGET_DIR"
echo "[install] done"
echo "[install] installed path: ${TARGET_DIR}"
log "skill installed"
log "installed path: ${TARGET_DIR}"
attempt_jj_install
log "done"

View File

@@ -414,6 +414,7 @@ Use `jj` only under these rules:
- `references/triage-standard.md`: scoring rubric and templates for needs-info, review request, test submission, and merge approval.
- `references/issue-template-standard.md`: standard issue templates for `bug`, `enhancement`, and `feature`.
- `references/plan-template.md`: default plan structure and status machine for MajorAgent/SubAgent/TestAgent handoff.
- `references/jj-default-usage.md`: default `jj` installation strategy, verification, and scenario-based usage guidance.
## Operational Constraints

View File

@@ -0,0 +1,170 @@
# Jj Default Installation and Usage
This repository installs the `gitea-issue-devops-agent` skill first, then attempts to install `jj` by default.
`jj` is the internal execution layer for agent reliability. Git branches, PRs, CI/CD pipelines, and merge approvals remain the external system of record.
## Default Install Strategy
The one-command installers now behave like this:
1. Install the skill into the target Codex directory.
2. Check whether `jj` is already available.
3. If not, try OS-specific install methods.
4. If all methods fail, keep the skill installed and print manual fallback instructions.
## OS-Specific Attempt Order
### Linux
- `brew install jj`
- `cargo binstall --strategies crate-meta-data jj-cli`
- `cargo install --locked --bin jj jj-cli`
For prerelease:
- `cargo install --git https://github.com/jj-vcs/jj.git --locked --bin jj jj-cli`
### macOS
- `brew install jj`
- `cargo binstall --strategies crate-meta-data jj-cli`
- `cargo install --locked --bin jj jj-cli`
For prerelease:
- `cargo install --git https://github.com/jj-vcs/jj.git --locked --bin jj jj-cli`
### Windows
- `winget install jj-vcs.jj`
- `scoop install main/jj`
- `cargo install --locked --bin jj jj-cli`
For prerelease:
- `cargo install --git https://github.com/jj-vcs/jj.git --locked --bin jj jj-cli`
## Installer Controls
### Bash installers
- `INSTALL_JJ=0`: skip `jj` installation
- `JJ_INSTALL_METHOD=auto|brew|binstall|cargo`
- `JJ_CHANNEL=release|prerelease`
Example:
```bash
INSTALL_JJ=0 curl -fsSL https://fun-md.com/Fun_MD/devops-skills/raw/branch/main/install/install.sh | bash
```
```bash
JJ_INSTALL_METHOD=cargo JJ_CHANNEL=prerelease curl -fsSL https://fun-md.com/Fun_MD/devops-skills/raw/branch/main/install/install.sh | bash
```
### PowerShell installers
With the one-liner, prefer environment variables:
- `$env:INSTALL_JJ='0'`
- `$env:JJ_INSTALL_METHOD='auto'|'winget'|'scoop'|'cargo'`
- `$env:JJ_CHANNEL='release'|'prerelease'`
Example:
```powershell
$env:JJ_INSTALL_METHOD='winget'
iwr -useb https://fun-md.com/Fun_MD/devops-skills/raw/branch/main/install/install.ps1 | iex
```
```powershell
$env:INSTALL_JJ='0'
iwr -useb https://fun-md.com/Fun_MD/devops-skills/raw/branch/main/install/install.ps1 | iex
```
If you save the script locally first, you can also use the `-SkipJj`, `-JjInstallMethod`, and `-JjChannel` parameters directly.
## Verification
After installation:
```bash
jj --version
```
Set identity:
```bash
jj config set --user user.name "Your Name"
jj config set --user user.email "you@example.com"
```
Optional shell completion:
```bash
source <(jj util completion bash)
```
PowerShell completion:
```powershell
jj util completion power-shell | Out-String | Invoke-Expression
```
## Working Model
Use `jj` as an internal operator tool:
- issue selection, branch naming, PR creation, CI, and merge stay Git/Gitea-native
- `jj` handles local history rewrites, workspace isolation, and recovery
- each issue branch can map to one `jj` bookmark
## Scenario Examples
### 1) First-Time Team Setup
1. Run the one-command installer.
2. Verify `jj --version`.
3. Configure `user.name` and `user.email`.
4. Start with one fixed issue in `manual` or `semi-automatic` mode.
Recommended when a team is new to AI-assisted delivery and wants controlled adoption.
### 2) Initial AI PR for a Bug
1. Human selects issue `#48`.
2. MajorAgent creates the plan.
3. Issue branch and draft PR are created.
4. SubAgent changes only the planned paths.
5. TestAgent validates build, targeted tests, and issue e2e.
6. Engineer reviews and refines before merge approval.
Recommended default flow for day-to-day bug fixing.
### 3) Semi-Automatic Review Flow
1. AI produces the initial draft PR.
2. Reviewer inspects the plan, diff scope, and evidence.
3. Engineer uses the AI coding tool for follow-up edits if needed.
4. Only after review approval does the branch enter preview-slot testing.
Recommended when engineering review must happen before environment allocation.
### 4) Human + TestAgent Parallel Verification with Workspaces
1. SubAgent works in the main issue workspace.
2. TestAgent creates a separate `jj workspace` for validation.
3. Human reviewer can create another workspace for white-box adjustments.
4. All three flows remain tied to the same issue branch and plan.
Recommended for larger issues where testing and code refinement happen in parallel.
### 5) Recovery After AI Drift
1. AI rewrites the change incorrectly or edits too broadly.
2. Engineer inspects `jj op log`.
3. Engineer uses `jj undo`, `jj op revert`, or `jj op restore`.
4. The issue branch and PR remain intact while local execution history is repaired.
Recommended when AI behavior is fast but unreliable and quick recovery matters.