sync: agent-ops from agentic-framework v1.1.70

This commit is contained in:
toki 2026-05-26 16:24:28 +09:00
parent 409699ad60
commit ed62235994
12 changed files with 333 additions and 12 deletions

View file

@ -18,7 +18,7 @@
# 프로젝트 간 잠금
- 프로젝트 상위 폴더에 `.agent-roadmap-sync/locks.yaml`이 있으면 프로젝트 간 Milestone 잠금 인덱스로 본다.
- "이 Milestone은 X가 끝나야 가능하다", "A 전까지 B를 잠근다", "잠금 해제 조건은 X다"는 `update-roadmap`으로 처리한다.
- "이 Milestone은 X가 끝나야 가능하다", "A 전까지 B를 잠근다", "잠금 해제 조건은 X다", "현재 마일스톤은 X 프로젝트 작업 뒤에 진행되어야 한다", "의존성 설정해"는 `update-roadmap`으로 처리한다.
- `plan`과 `code-review`는 `.agent-roadmap-sync`를 읽거나 갱신하지 않는다.
# 스킬 규칙

View file

@ -18,7 +18,7 @@
# 프로젝트 간 잠금
- 프로젝트 상위 폴더에 `.agent-roadmap-sync/locks.yaml`이 있으면 프로젝트 간 Milestone 잠금 인덱스로 본다.
- "이 Milestone은 X가 끝나야 가능하다", "A 전까지 B를 잠근다", "잠금 해제 조건은 X다"는 `update-roadmap`으로 처리한다.
- "이 Milestone은 X가 끝나야 가능하다", "A 전까지 B를 잠근다", "잠금 해제 조건은 X다", "현재 마일스톤은 X 프로젝트 작업 뒤에 진행되어야 한다", "의존성 설정해"는 `update-roadmap`으로 처리한다.
- `plan`과 `code-review`는 `.agent-roadmap-sync`를 읽거나 갱신하지 않는다.
# 스킬 규칙

View file

@ -18,7 +18,7 @@
# 프로젝트 간 잠금
- 프로젝트 상위 폴더에 `.agent-roadmap-sync/locks.yaml`이 있으면 프로젝트 간 Milestone 잠금 인덱스로 본다.
- "이 Milestone은 X가 끝나야 가능하다", "A 전까지 B를 잠근다", "잠금 해제 조건은 X다"는 `update-roadmap`으로 처리한다.
- "이 Milestone은 X가 끝나야 가능하다", "A 전까지 B를 잠근다", "잠금 해제 조건은 X다", "현재 마일스톤은 X 프로젝트 작업 뒤에 진행되어야 한다", "의존성 설정해"`update-roadmap`으로 처리한다.
- `plan``code-review``.agent-roadmap-sync`를 읽거나 갱신하지 않는다.
# 스킬 규칙

View file

@ -18,7 +18,7 @@
# 프로젝트 간 잠금
- 프로젝트 상위 폴더에 `.agent-roadmap-sync/locks.yaml`이 있으면 프로젝트 간 Milestone 잠금 인덱스로 본다.
- "이 Milestone은 X가 끝나야 가능하다", "A 전까지 B를 잠근다", "잠금 해제 조건은 X다"는 `update-roadmap`으로 처리한다.
- "이 Milestone은 X가 끝나야 가능하다", "A 전까지 B를 잠근다", "잠금 해제 조건은 X다", "현재 마일스톤은 X 프로젝트 작업 뒤에 진행되어야 한다", "의존성 설정해"`update-roadmap`으로 처리한다.
- `plan``code-review``.agent-roadmap-sync`를 읽거나 갱신하지 않는다.
# 스킬 규칙

View file

@ -18,7 +18,7 @@
# 프로젝트 간 잠금
- 프로젝트 상위 폴더에 `.agent-roadmap-sync/locks.yaml`이 있으면 프로젝트 간 Milestone 잠금 인덱스로 본다.
- "이 Milestone은 X가 끝나야 가능하다", "A 전까지 B를 잠근다", "잠금 해제 조건은 X다"는 `update-roadmap`으로 처리한다.
- "이 Milestone은 X가 끝나야 가능하다", "A 전까지 B를 잠근다", "잠금 해제 조건은 X다", "현재 마일스톤은 X 프로젝트 작업 뒤에 진행되어야 한다", "의존성 설정해"`update-roadmap`으로 처리한다.
- `plan``code-review``.agent-roadmap-sync`를 읽거나 갱신하지 않는다.
# 스킬 규칙

View file

@ -1 +1 @@
1.1.69
1.1.70

View file

@ -0,0 +1,231 @@
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
AGENT_OPS_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
PROJECT_ROOT="$(cd "$AGENT_OPS_DIR/.." && pwd)"
WORKSPACE_ROOT="$(cd "$PROJECT_ROOT/.." && pwd)"
LOCKS_FILE="${AGENT_ROADMAP_LOCKS_FILE:-$WORKSPACE_ROOT/.agent-roadmap-sync/locks.yaml}"
usage() {
cat <<'EOF'
Usage: roadmap-dependency-checker.sh <lock-id> [locks.yaml]
Validates locked, rely-on.target, and rely-on.status for <lock-id>.
Prints true when every rely-on.status is enable.
Prints false when at least one rely-on.status is disable.
Exit codes:
0 all dependencies are enabled
1 at least one dependency is still disabled
2 configuration, input, or parse error
EOF
}
if [[ "${1:-}" == "-h" || "${1:-}" == "--help" ]]; then
usage
exit 0
fi
if [[ $# -lt 1 || $# -gt 2 ]]; then
usage >&2
exit 2
fi
LOCK_ID="$1"
if [[ -n "${2:-}" ]]; then
LOCKS_FILE="$2"
fi
if [[ -z "$LOCK_ID" ]]; then
echo "error: lock id is required" >&2
exit 2
fi
if [[ ! -r "$LOCKS_FILE" ]]; then
echo "error: locks file not readable: $LOCKS_FILE" >&2
exit 2
fi
awk -v wanted="$LOCK_ID" '
function trim(value) {
gsub(/\r/, "", value)
sub(/^[[:space:]]+/, "", value)
sub(/[[:space:]]+$/, "", value)
return value
}
function unquote(value) {
value = trim(value)
sub(/[[:space:]]+#.*$/, "", value)
value = trim(value)
if (value ~ /^".*"$/ || value ~ /^'\''.*'\''$/) {
return substr(value, 2, length(value) - 2)
}
return trim(value)
}
function fail(message) {
error = message
printf "error: %s\n", error > "/dev/stderr"
exit 2
}
function finish_dependency() {
if (!selected || !in_dependency) {
return
}
if (!dependency_has_target) {
fail("missing rely-on.target for lock id: " wanted)
}
if (!dependency_has_status) {
fail("missing rely-on.status for lock id: " wanted)
}
in_dependency = 0
}
function finish_entry() {
if (!selected) {
return
}
finish_dependency()
if (!entry_has_locked) {
fail("missing locked for lock id: " wanted)
}
if (total == 0) {
fail("no rely-on.status entries for lock id: " wanted)
}
selected = 0
}
BEGIN {
selected = 0
matches = 0
total = 0
enabled = 0
error = ""
entry_has_locked = 0
in_rely_on = 0
in_dependency = 0
dependency_has_target = 0
dependency_has_status = 0
}
/^[[:space:]]*($|#)/ {
next
}
/^[[:space:]]*-[[:space:]]*id:[[:space:]]*/ {
finish_entry()
value = $0
sub(/^[[:space:]]*-[[:space:]]*id:[[:space:]]*/, "", value)
id = unquote(value)
if (id == wanted) {
if (matches > 0) {
fail("duplicate lock id: " wanted)
}
selected = 1
matches++
total = 0
enabled = 0
entry_has_locked = 0
in_rely_on = 0
in_dependency = 0
dependency_has_target = 0
dependency_has_status = 0
}
next
}
selected && /^[[:space:]]*locked:[[:space:]]*/ {
value = $0
sub(/^[[:space:]]*locked:[[:space:]]*/, "", value)
locked = unquote(value)
if (locked == "") {
fail("empty locked for lock id: " wanted)
}
entry_has_locked = 1
next
}
selected && /^[[:space:]]*rely-on:[[:space:]]*/ {
in_rely_on = 1
next
}
selected && in_rely_on && /^[[:space:]]*-[[:space:]]*target:[[:space:]]*/ {
finish_dependency()
in_dependency = 1
dependency_has_target = 0
dependency_has_status = 0
value = $0
sub(/^[[:space:]]*-[[:space:]]*target:[[:space:]]*/, "", value)
target = unquote(value)
if (target == "") {
fail("empty rely-on.target for lock id: " wanted)
}
dependency_has_target = 1
next
}
selected && in_rely_on && /^[[:space:]]*target:[[:space:]]*/ {
if (!in_dependency) {
fail("rely-on.target outside dependency item for lock id: " wanted)
}
value = $0
sub(/^[[:space:]]*target:[[:space:]]*/, "", value)
target = unquote(value)
if (target == "") {
fail("empty rely-on.target for lock id: " wanted)
}
dependency_has_target = 1
next
}
selected && in_rely_on && /^[[:space:]]*status:[[:space:]]*/ {
if (!in_dependency) {
fail("rely-on.status outside dependency item for lock id: " wanted)
}
value = $0
sub(/^[[:space:]]*status:[[:space:]]*/, "", value)
status = unquote(value)
if (status != "enable" && status != "disable") {
fail("invalid rely-on.status for " wanted ": " status)
}
if (dependency_has_status) {
fail("duplicate rely-on.status for lock id: " wanted)
}
dependency_has_status = 1
total++
if (status == "enable") {
enabled++
}
next
}
selected && in_rely_on && /^[[:space:]]*-[[:space:]]*/ {
finish_dependency()
fail("missing rely-on.target for lock id: " wanted)
}
selected && /^[[:space:]]*status:[[:space:]]*/ {
fail("status outside rely-on for lock id: " wanted)
}
END {
if (error != "") {
exit 2
}
if (matches == 0) {
printf "error: lock id not found: %s\n", wanted > "/dev/stderr"
exit 2
}
finish_entry()
if (total == enabled) {
print "true"
exit 0
}
print "false"
exit 1
}
' "$LOCKS_FILE"

View file

@ -69,8 +69,15 @@
- 프로젝트 상위 `.agent-roadmap-sync/locks.yaml`은 프로젝트 간 Milestone 잠금 인덱스다.
- entry는 `id`, `locked`, `rely-on[].target`, `rely-on[].status`, `rely-on[].note`만 사용한다.
- `locks.yaml`은 root sequence block style을 기본으로 작성한다. 예: `- id: ...` 아래에 `locked`, `rely-on`을 둔다.
- `id`는 기본적으로 `<잠긴-project>:<잠긴-milestone-slug>`로 만든다.
- `locked``rely-on[].target``<project>:agent-ops/roadmap/phase/<phase-slug>/milestones/<milestone-slug>.md` 형식으로 기록한다.
- "현재 마일스톤은 X 프로젝트 작업 뒤에 진행", "X 프로젝트 때문에 현재 작업 잠금", "의존성 설정해"처럼 대상 Milestone을 생략한 외부 의존 잠금 요청은 현재 프로젝트의 `current.md` 활성 Milestone을 잠긴 대상으로 삼는다.
- 의존 대상 프로젝트만 있고 Milestone이 생략되면 같은 workspace의 해당 프로젝트 `current.md` 활성 Milestone을 `rely-on.target` 후보로 삼는다. 후보가 없거나 둘 이상이면 locks.yaml을 만들지 말고 사용자에게 대상 Milestone 선택을 요청한다.
- 외부 의존 잠금을 만들 때 대상 Milestone의 `구현 잠금``잠금`으로 두고, `rely-on.status` 기본값은 `disable`이다.
- `locks.yaml`이 있고 `update-roadmap`이 갱신한 Milestone이 `[검토중]` 또는 `[완료]`가 되면 일치하는 `rely-on.target``status``enable`로 바꾼다.
- `locks.yaml`이 있고 `update-roadmap`이 갱신한 Milestone이 `[검토중]` 또는 `[완료]`가 되면 `<현재-project>:<갱신-milestone-path>`와 일치하는 `rely-on.target``status``enable`로 바꾼다.
- 잠금 해제 조건 충족 여부만 확인할 때는 `agent-ops/skills/common/check-roadmap-dependency/SKILL.md`를 읽고 `agent-ops/bin/roadmap-dependency-checker.sh "<lock-id>"`를 사용한다. checker는 `locked`, `rely-on.target`, `rely-on.status` 필수 필드를 검증한 뒤 모든 `status``enable`일 때만 `true`를 반환한다.
- checker exit code는 `0=true`, `1=false`, `2=설정/입력/파싱 오류`로 해석한다.
- 모든 `rely-on.status``enable`이어도 잠금 해제 실행은 런타임이 별도 `update-roadmap` 호출로 처리한다.
## Epic과 Task id

View file

@ -18,7 +18,7 @@
# 프로젝트 간 잠금
- 프로젝트 상위 폴더에 `.agent-roadmap-sync/locks.yaml`이 있으면 프로젝트 간 Milestone 잠금 인덱스로 본다.
- "이 Milestone은 X가 끝나야 가능하다", "A 전까지 B를 잠근다", "잠금 해제 조건은 X다"는 `update-roadmap`으로 처리한다.
- "이 Milestone은 X가 끝나야 가능하다", "A 전까지 B를 잠근다", "잠금 해제 조건은 X다", "현재 마일스톤은 X 프로젝트 작업 뒤에 진행되어야 한다", "의존성 설정해"`update-roadmap`으로 처리한다.
- `plan``code-review``.agent-roadmap-sync`를 읽거나 갱신하지 않는다.
# 스킬 규칙

View file

@ -0,0 +1,71 @@
---
name: check-roadmap-dependency
version: 1.0.0
description: roadmap dependency lock 상태를 확인한다. 사용자가 roadmap-dependency-checker.sh, locks.yaml, 외부 의존 잠금 충족 여부, unlock-ready 판별, 프로젝트 간 잠금 해제 조건 확인을 요청할 때 사용한다.
---
# check-roadmap-dependency
## 목적
`.agent-roadmap-sync/locks.yaml`에서 특정 lock `id``locked`, `rely-on.target`, `rely-on.status`가 유효한지 확인하고, 모든 `rely-on.status``enable`인지 판별한다.
판별은 `agent-ops/bin/roadmap-dependency-checker.sh`에 맡기고, 스킬은 입력 확정, 실행, 결과 해석만 담당한다.
## 언제 호출할지
- 사용자가 특정 roadmap lock id의 의존 조건 충족 여부를 확인할 때
- runtime에서 unlock-ready 트리거 전 `locks.yaml` 판별 절차를 확인할 때
- `.agent-roadmap-sync/locks.yaml``rely-on.status` 기반 true/false 확인을 요청할 때
## 입력
- `lock-id`: 확인할 lock entry id. 예: `oto:oto-agent-registration`. 사용자가 "현재 마일스톤"이라고만 말하면 `<현재-project>:<current.md 활성 milestone slug>`로 추론한다. `<현재-project>`는 현재 프로젝트 루트 디렉터리 이름이다 (선택, 추론 불가 시 필수)
- `locks-file`: 기본값은 프로젝트 상위 `.agent-roadmap-sync/locks.yaml`이며, 다른 파일을 검사할 때만 지정한다 (선택)
## 먼저 확인할 것
- [ ] `agent-ops/bin/roadmap-dependency-checker.sh`가 있는지 확인한다.
- [ ] `lock-id`가 명시되었거나 현재 프로젝트 `current.md`의 활성 Milestone 하나로 추론 가능한지 확인한다.
- [ ] 기본 경로 또는 지정된 `locks-file`을 읽을 수 있는지 확인한다.
## 실행 절차
1. **입력 확정**
- 사용자 요청에서 `lock-id`를 추출한다.
- `lock-id`가 없고 "현재 마일스톤" 확인 요청이면 현재 프로젝트 `agent-ops/roadmap/current.md`의 활성 Milestone 경로에서 milestone slug를 추출해 `<현재-project>:<milestone-slug>`를 만든다.
- 활성 Milestone 후보가 없거나 둘 이상이면 추정하지 말고 사용자에게 lock id 또는 Milestone 선택을 요청한다.
- `locks-file`이 명시되지 않았으면 프로젝트 상위 `.agent-roadmap-sync/locks.yaml`을 사용한다.
2. **판별 실행**
- 기본 파일이면 `agent-ops/bin/roadmap-dependency-checker.sh "<lock-id>"`를 실행한다.
- 지정 파일이면 `agent-ops/bin/roadmap-dependency-checker.sh "<lock-id>" "<locks-file>"`를 실행한다.
3. **결과 해석**
- stdout `true`와 exit code `0`은 모든 의존 조건 충족으로 보고한다.
- stdout `false`와 exit code `1`은 아직 충족되지 않은 의존 조건이 있음을 보고한다.
- exit code `2`는 설정 오류, 입력 오류, 필수 필드 누락, 파싱 오류로 보고한다.
## 실행 결과 검증
- [ ] 정상 판별 결과가 stdout에서 `true` 또는 `false` 하나로만 출력되었는가
- [ ] exit code `0`, `1`, `2` 의미를 구분해 보고했는가
- [ ] exit code `2`를 unlock 대기 상태로 해석하지 않았는가
- [ ] 이 스킬이 `locks.yaml`이나 roadmap 문서를 수정하지 않았는가
- 검증 실패 시: 오류 메시지와 사용한 `lock-id`, `locks-file` 경로를 보고하고 멈춘다.
## 출력 형식
```markdown
## 의존성 판별 결과
- lock-id: `<lock-id>`
- locks-file: `<locks-file>`
- 결과: `<true | false | error>`
- 의미: <모든 의존 조건 충족 | 미충족 의존 조건 있음 | 설정/입력/필수 필드/파싱 오류>
```
## 금지 사항
- `locks.yaml``status`를 변경하지 않는다.
- roadmap Milestone의 `구현 잠금` 상태를 변경하지 않는다.
- `false`와 설정 오류를 같은 상태로 보고하지 않는다.

View file

@ -8,7 +8,8 @@
| README 작성해줘, README 만들어줘, 프로젝트 설명 문서 만들어줘 | `agent-ops/skills/common/create-readme/SKILL.md` |
| 로드맵 만들어줘, roadmap 생성, 마일스톤 설계, goal/phase 구조 잡아줘 | `agent-ops/skills/common/create-roadmap/SKILL.md` |
| 로드맵 업데이트, roadmap 갱신, 로드맵에 추가, 로드맵 작업 추가, 로드맵 기능 추가, 로드맵 Epic 추가, 로드맵 에픽 추가, 로드맵 Task 추가, 로드맵 태스크 추가, 로드맵 테스크 추가, 로드맵 TODO 추가, 마일스톤에 추가, 마일스톤 추가, 마일스톤 갱신, 마일스톤 아카이브, phase 추가, phase 변경, 페이즈 추가, 페이즈 변경, 현재 마일스톤 변경, 로드맵 한국어 전환, 로드맵 번역 | `agent-ops/skills/common/update-roadmap/SKILL.md` |
| 이 마일스톤은 X가 끝나야 가능해, A 전까지 B 잠가둬, 잠금 해제 조건은 X야, 외부 의존 잠금 | `agent-ops/skills/common/update-roadmap/SKILL.md` |
| 이 마일스톤은 X가 끝나야 가능해, A 전까지 B 잠가둬, 잠금 해제 조건은 X야, X 프로젝트 작업 뒤에 현재 마일스톤 진행, 의존성 설정해, 외부 의존 잠금 | `agent-ops/skills/common/update-roadmap/SKILL.md` |
| roadmap dependency 확인, locks.yaml 판별, 외부 의존 잠금 확인, unlock-ready 판별, 잠금 해제 조건 충족 여부 확인, roadmap-dependency-checker.sh | `agent-ops/skills/common/check-roadmap-dependency/SKILL.md` |
| 지금 작업이 뭐지?, 현재 작업 분석, 어디까지 했지?, 로드맵상 현 위치, 현재 마일스톤 위치, current 기준 breadcrumb | `agent-ops/skills/common/analyze-roadmap-position/SKILL.md` |
| 계획 세워줘, 계획 작성해, 계획 만들어줘, 구현 계획, PLAN.md, plan, plan 작성해, plan 만들어줘 | `agent-ops/skills/common/plan/SKILL.md` |
| 코드 리뷰해줘, 리뷰 진행해, 리뷰해줘, code review, CODE_REVIEW.md, 리뷰 루프 | `agent-ops/skills/common/code-review/SKILL.md` |

View file

@ -27,7 +27,7 @@ Epic과 Task는 별도 파일로 분리하지 않고 Milestone 문서의 `기능
- 특정 기능이나 작업을 새 Milestone, 기존 Milestone의 Epic, 기존 Epic의 Task 중 적절한 위치에 추가해야 할 때
- 활성 Phase/Milestone 창에 포함할 목록이 달라졌을 때
- 기존 로드맵을 `phase/<phase-slug>/PHASE.md` scaffold로 마이그레이션하거나 표준화해야 할 때
- 사용자가 "이 Milestone은 X가 끝나야 가능하다", "A 전까지 B를 잠근다", "잠금 해제 조건은 X다"처럼 외부 의존 잠금을 말할 때
- 사용자가 "이 Milestone은 X가 끝나야 가능하다", "A 전까지 B를 잠근다", "잠금 해제 조건은 X다", "현재 마일스톤은 X 프로젝트 작업 뒤에 진행되어야 한다", "의존성 설정해"처럼 외부 의존 잠금을 말할 때
## 입력
@ -124,8 +124,15 @@ agent-ops/roadmap/
## 프로젝트 간 잠금
- 프로젝트 상위 `.agent-roadmap-sync/locks.yaml`은 프로젝트 간 Milestone 잠금 인덱스다.
- entry는 `id`, `locked`, `rely-on[].target`, `rely-on[].status`, `rely-on[].note`만 사용한다.
- `locks.yaml`은 root sequence block style을 기본으로 작성한다. 예: `- id: ...` 아래에 `locked`, `rely-on`을 둔다.
- `id`는 기본적으로 `<잠긴-project>:<잠긴-milestone-slug>`로 만든다.
- `locked``rely-on[].target``<project>:agent-ops/roadmap/phase/<phase-slug>/milestones/<milestone-slug>.md` 형식으로 기록한다.
- "현재 마일스톤은 X 프로젝트 작업 뒤에 진행", "X 프로젝트 때문에 현재 작업 잠금", "의존성 설정해"처럼 대상 Milestone이 생략된 외부 의존 잠금 요청은 현재 프로젝트의 `current.md` 활성 Milestone을 잠긴 대상으로 삼는다.
- 의존 대상 프로젝트명만 있고 Milestone이 생략되면 같은 workspace의 해당 프로젝트 `agent-ops/roadmap/current.md` 활성 Milestone을 `rely-on.target` 후보로 삼는다.
- 의존 대상 프로젝트가 없거나, 해당 프로젝트의 활성 Milestone 후보가 없거나 둘 이상이면 `locks.yaml`을 만들지 말고 사용자에게 대상 프로젝트/Milestone 선택을 요청한다.
- 외부 의존 잠금 요청은 대상 Milestone의 `구현 잠금``잠금`으로 두고, `locked`, `rely-on.target`, `rely-on.status=disable`로 upsert한다.
- `locks.yaml`이 있고 이 스킬이 갱신한 Milestone이 `[검토중]` 또는 `[완료]`가 되면 일치하는 `rely-on.target``status``enable`로 바꾼다.
- `locks.yaml`이 있고 이 스킬이 갱신한 Milestone이 `[검토중]` 또는 `[완료]`가 되면 `<현재-project>:<갱신-milestone-path>`일치하는 `rely-on.target``status``enable`로 바꾼다.
- 모든 `rely-on.status``enable`이어도 여기서 다른 프로젝트 Milestone을 직접 해제하지 않는다. 잠금 해제 실행은 런타임이 별도 `update-roadmap` 호출로 처리한다.
## 완료 리뷰와 검토중 상태
@ -239,6 +246,9 @@ target 없는 신규 추가 요청은 append가 아니라 upsert로 처리한다
- 새 기능 배치, Epic/Task 추가는 `milestone` 또는 `phase`로 본다.
- "로드맵에 추가"처럼 target이 없는 신규 작업 요청은 `placement=auto`, `placement-unit=auto`, `new-feature=<요청 내용>`으로 본다.
- 외부 의존 잠금 요청이면 `workspace-lock` 갱신으로 본다.
- 외부 의존 잠금 요청에서 "현재 마일스톤" 또는 target 생략 표현이 있으면 `current.md`의 활성 Milestone을 잠긴 대상으로 확정한다.
- 외부 의존 잠금 요청에서 의존 프로젝트명만 있으면 같은 workspace의 `<project>/agent-ops/roadmap/current.md`를 읽고 활성 Milestone을 의존 대상으로 확정한다.
- 잠긴 대상 또는 의존 대상 활성 Milestone 후보가 없거나 둘 이상이면 `locks.yaml`을 수정하지 말고 사용자에게 Milestone 선택을 요청한다.
2. **요청 정규화와 규모 판정**
- 요청에서 기능명, 목표, 관련 경로, 명시 anchor, 완료 기대, 제약을 추출한다.
@ -278,7 +288,8 @@ target 없는 신규 추가 요청은 append가 아니라 upsert로 처리한다
- `[검토중]` 전환만으로 archive 이동, `current.md` 제거, archive 링크 변경을 수행하지 않는다.
- 사용자 승인 근거가 있으면 `[검토중]``[완료]`로 전환하고 archive 모드를 수행할 수 있다.
- 외부 의존 잠금 요청이면 대상 Milestone의 `구현 잠금``잠금`으로 두고 `.agent-roadmap-sync/locks.yaml`을 upsert한다.
- `locks.yaml`이 있고 갱신한 Milestone이 `[검토중]` 또는 `[완료]`가 되었으면 일치하는 `rely-on.target``enable`로 바꾼다.
- `locks.yaml` entry는 `id=<잠긴-project>:<잠긴-milestone-slug>`, `locked=<잠긴-project>:<잠긴-milestone-path>`, `rely-on.target=<의존-project>:<의존-milestone-path>`, `rely-on.status=disable`, `rely-on.note=<사용자 요청 요약>`으로 기록한다.
- `locks.yaml`이 있고 갱신한 Milestone이 `[검토중]` 또는 `[완료]`가 되었으면 `<현재-project>:<갱신-milestone-path>`와 일치하는 `rely-on.target``enable`로 바꾼다.
6. **검증**
- `current.md`의 활성 Phase/Milestone 경로가 실제 파일을 가리키는지 확인한다.