From 859ea82f5a450bfc207705ce0bfe1e4bd5e5a9c1 Mon Sep 17 00:00:00 2001 From: toki Date: Wed, 27 May 2026 09:13:59 +0900 Subject: [PATCH] sync: from oto v1.1.71 --- agent-ops/.version | 2 +- agent-ops/bin/roadmap-dependency-checker.sh | 153 +++++++++++++++++- agent-ops/rules/common/rules-roadmap.md | 21 ++- agent-ops/rules/common/rules.md | 1 + .../common/check-roadmap-dependency/SKILL.md | 46 ++++-- .../skills/common/create-roadmap/SKILL.md | 6 +- .../skills/common/update-roadmap/SKILL.md | 50 ++++-- 7 files changed, 232 insertions(+), 47 deletions(-) diff --git a/agent-ops/.version b/agent-ops/.version index ccc2150..222c44e 100644 --- a/agent-ops/.version +++ b/agent-ops/.version @@ -1 +1 @@ -1.1.70 +1.1.71 diff --git a/agent-ops/bin/roadmap-dependency-checker.sh b/agent-ops/bin/roadmap-dependency-checker.sh index b9382cb..71a3dfc 100755 --- a/agent-ops/bin/roadmap-dependency-checker.sh +++ b/agent-ops/bin/roadmap-dependency-checker.sh @@ -10,15 +10,20 @@ LOCKS_FILE="${AGENT_ROADMAP_LOCKS_FILE:-$WORKSPACE_ROOT/.agent-roadmap-sync/lock usage() { cat <<'EOF' Usage: roadmap-dependency-checker.sh [locks.yaml] + roadmap-dependency-checker.sh --find-milestone [locked|rely-on|both] [locks.yaml] Validates locked, rely-on.target, and rely-on.status for . Prints true when every rely-on.status is enable. Prints false when at least one rely-on.status is disable. +With --find-milestone, prints matching lock ids as locked: or rely-on:, +or none when the milestone identity is not referenced. Exit codes: 0 all dependencies are enabled 1 at least one dependency is still disabled 2 configuration, input, or parse error +For --find-milestone, exit code 0 means the lookup completed; stdout is either +matching entries or none. EOF } @@ -27,17 +32,51 @@ if [[ "${1:-}" == "-h" || "${1:-}" == "--help" ]]; then exit 0 fi -if [[ $# -lt 1 || $# -gt 2 ]]; then +MODE="check" +LOCK_ID="" +FIND_IDENTITY="" +FIND_DIRECTION="both" + +if [[ "${1:-}" == "--find-milestone" ]]; then + MODE="find" + if [[ $# -lt 2 || $# -gt 4 ]]; then + usage >&2 + exit 2 + fi + FIND_IDENTITY="$2" + if [[ -z "$FIND_IDENTITY" ]]; then + echo "error: milestone identity is required" >&2 + exit 2 + fi + if [[ $# -ge 3 ]]; then + case "$3" in + locked|rely-on|both) + FIND_DIRECTION="$3" + if [[ $# -eq 4 ]]; then + LOCKS_FILE="$4" + fi + ;; + *) + if [[ $# -eq 3 ]]; then + LOCKS_FILE="$3" + else + usage >&2 + exit 2 + fi + ;; + esac + fi +elif [[ $# -lt 1 || $# -gt 2 ]]; then usage >&2 exit 2 +else + LOCK_ID="$1" + if [[ -n "${2:-}" ]]; then + LOCKS_FILE="$2" + fi fi -LOCK_ID="$1" -if [[ -n "${2:-}" ]]; then - LOCKS_FILE="$2" -fi - -if [[ -z "$LOCK_ID" ]]; then +if [[ "$MODE" == "check" && -z "$LOCK_ID" ]]; then echo "error: lock id is required" >&2 exit 2 fi @@ -47,6 +86,106 @@ if [[ ! -r "$LOCKS_FILE" ]]; then exit 2 fi +if [[ "$MODE" == "find" ]]; then + awk -v identity="$FIND_IDENTITY" -v direction="$FIND_DIRECTION" ' +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 reset_entry() { + entry_id = "" + locked = "" + in_rely_on = 0 + rely_count = 0 + delete rely_targets +} + +function finish_entry( i) { + if (entry_id == "") { + return + } + if ((direction == "both" || direction == "locked") && locked == identity) { + print "locked:" entry_id + found = 1 + } + if (direction == "both" || direction == "rely-on") { + for (i = 1; i <= rely_count; i++) { + if (rely_targets[i] == identity) { + print "rely-on:" entry_id + found = 1 + break + } + } + } +} + +BEGIN { + found = 0 + reset_entry() +} + +/^[[:space:]]*($|#)/ { + next +} + +/^[[:space:]]*-[[:space:]]*id:[[:space:]]*/ { + finish_entry() + reset_entry() + value = $0 + sub(/^[[:space:]]*-[[:space:]]*id:[[:space:]]*/, "", value) + entry_id = unquote(value) + next +} + +entry_id != "" && /^[[:space:]]*locked:[[:space:]]*/ { + value = $0 + sub(/^[[:space:]]*locked:[[:space:]]*/, "", value) + locked = unquote(value) + next +} + +entry_id != "" && /^[[:space:]]*rely-on:[[:space:]]*/ { + in_rely_on = 1 + next +} + +entry_id != "" && in_rely_on && /^[[:space:]]*-[[:space:]]*target:[[:space:]]*/ { + value = $0 + sub(/^[[:space:]]*-[[:space:]]*target:[[:space:]]*/, "", value) + rely_targets[++rely_count] = unquote(value) + next +} + +entry_id != "" && in_rely_on && /^[[:space:]]*target:[[:space:]]*/ { + value = $0 + sub(/^[[:space:]]*target:[[:space:]]*/, "", value) + rely_targets[++rely_count] = unquote(value) + next +} + +END { + finish_entry() + if (!found) { + print "none" + } +} +' "$LOCKS_FILE" + exit 0 +fi + awk -v wanted="$LOCK_ID" ' function trim(value) { gsub(/\r/, "", value) diff --git a/agent-ops/rules/common/rules-roadmap.md b/agent-ops/rules/common/rules-roadmap.md index 3ab0776..d38775d 100644 --- a/agent-ops/rules/common/rules-roadmap.md +++ b/agent-ops/rules/common/rules-roadmap.md @@ -67,18 +67,25 @@ ## 프로젝트 간 잠금 -- 프로젝트 상위 `.agent-roadmap-sync/locks.yaml`은 프로젝트 간 Milestone 잠금 인덱스다. +- 프로젝트 상위 `.agent-roadmap-sync/locks.yaml`은 프로젝트 간 Milestone 잠금 인덱스다. 외부 의존 잠금을 생성하거나 동기화해야 하면 파일이 없어도 `update-roadmap`이 디렉터리와 파일을 만든다. - 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`은 `:agent-ops/roadmap/phase//milestones/.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이 `[검토중]` 또는 `[완료]`가 되면 `<현재-project>:<갱신-milestone-path>`와 일치하는 `rely-on.target`의 `status`만 `enable`로 바꾼다. -- 잠금 해제 조건 충족 여부만 확인할 때는 `agent-ops/skills/common/check-roadmap-dependency/SKILL.md`를 읽고 `agent-ops/bin/roadmap-dependency-checker.sh ""`를 사용한다. checker는 `locked`, `rely-on.target`, `rely-on.status` 필수 필드를 검증한 뒤 모든 `status`가 `enable`일 때만 `true`를 반환한다. +- Milestone 경로가 `agent-ops/roadmap/...` 상대 경로이면 현재 프로젝트명을 prefix로 붙인다. workspace 하위 절대/상대 경로이면 workspace 바로 아래 디렉터리명을 project로 삼고, 그 뒤 `agent-ops/roadmap/...` 경로를 붙인다. +- `locked`는 잠긴 Milestone, `rely-on.target`은 선행 조건 Milestone이다. 둘 다 같은 workspace의 어느 활성 Phase 하위 Milestone이어도 된다. 의존 대상이 `current.md`에 있어야 한다고 가정하지 않는다. +- "현재 마일스톤은 X 프로젝트 작업 뒤에 진행", "X 프로젝트 때문에 현재 작업 잠금", "의존성 설정해"처럼 잠긴 Milestone을 생략한 외부 의존 잠금 요청은 현재 프로젝트 `current.md`의 활성 Milestone 단일 후보를 잠긴 대상으로 삼는다. +- 의존 대상은 명시 경로, 명시 slug, 명시 제목, 잠긴 Milestone 문서의 선행 Milestone 힌트, 대상 프로젝트 `current.md` 단일 후보 순서로 확정한다. +- 정규화 비교는 소문자 변환, backtick/따옴표 제거, 영문/숫자가 아닌 연속 문자를 `-` 하나로 치환, 앞뒤 `-` 제거 후 Milestone 파일 slug와 정규화한 제목에 대조한다. +- 의존 대상 탐색은 대상 프로젝트의 `agent-ops/roadmap/phase/*/milestones/*.md` 활성 문서만 대상으로 한다. archive 문서는 사용자가 archive 경로를 명시한 경우 외에는 읽거나 후보로 삼지 않는다. +- 후보가 없거나 둘 이상이면 locks.yaml을 만들거나 고치지 말고 사용자에게 대상 Milestone 선택을 요청한다. +- 외부 의존 잠금을 만들 때 대상 Milestone의 `구현 잠금`은 `잠금`으로 둔다. +- 새 `rely-on.status`는 선행 Milestone 상태에서 파생한다. `[검토중]` 또는 `[완료]`이면 `enable`, 그 외 상태거나 상태를 확인할 수 없으면 `disable`이다. +- 같은 `id` entry를 upsert할 때 기존 `rely-on` 항목을 삭제하지 않는다. 같은 `rely-on.target`만 status/note를 갱신하고, 없는 target은 추가하며, `locked` 경로가 바뀐 경우에만 `locked`를 갱신한다. +- `update-roadmap`이 갱신한 Milestone identity가 어느 entry의 `rely-on.target`과 일치하면 Milestone 상태 기준으로 `status`를 동기화한다. `[검토중]` 또는 `[완료]`이면 `enable`, 그 외 상태면 `disable`이다. +- `update-roadmap`이 갱신하거나 선택한 Milestone identity가 어느 entry의 `locked`와 일치하면 모든 `rely-on.status`가 `enable`인지 결과 보고에 남긴다. 모든 조건이 충족되어도 잠금 해제 실행은 런타임이 별도 `update-roadmap` 호출로 처리한다. +- 잠금 해제 조건 충족 여부만 확인할 때는 `agent-ops/skills/common/check-roadmap-dependency/SKILL.md`를 읽는다. lock id가 없으면 해당 스킬은 `agent-ops/bin/roadmap-dependency-checker.sh --find-milestone "" "" ""`로 현재 Milestone이 `locked`인지 `rely-on.target`인지 양방향으로 찾은 뒤 `agent-ops/bin/roadmap-dependency-checker.sh "" ""`를 사용한다. - checker exit code는 `0=true`, `1=false`, `2=설정/입력/파싱 오류`로 해석한다. -- 모든 `rely-on.status`가 `enable`이어도 잠금 해제 실행은 런타임이 별도 `update-roadmap` 호출로 처리한다. ## Epic과 Task id diff --git a/agent-ops/rules/common/rules.md b/agent-ops/rules/common/rules.md index 8aba3d5..cbbc8c7 100644 --- a/agent-ops/rules/common/rules.md +++ b/agent-ops/rules/common/rules.md @@ -18,6 +18,7 @@ # 프로젝트 간 잠금 - 프로젝트 상위 폴더에 `.agent-roadmap-sync/locks.yaml`이 있으면 프로젝트 간 Milestone 잠금 인덱스로 본다. +- 외부 의존 잠금 생성/동기화 요청은 `.agent-roadmap-sync/locks.yaml`이 없어도 `update-roadmap`이 생성한다. - "이 Milestone은 X가 끝나야 가능하다", "A 전까지 B를 잠근다", "잠금 해제 조건은 X다", "현재 마일스톤은 X 프로젝트 작업 뒤에 진행되어야 한다", "의존성 설정해"는 `update-roadmap`으로 처리한다. - `plan`과 `code-review`는 `.agent-roadmap-sync`를 읽거나 갱신하지 않는다. diff --git a/agent-ops/skills/common/check-roadmap-dependency/SKILL.md b/agent-ops/skills/common/check-roadmap-dependency/SKILL.md index b815243..e9a0917 100644 --- a/agent-ops/skills/common/check-roadmap-dependency/SKILL.md +++ b/agent-ops/skills/common/check-roadmap-dependency/SKILL.md @@ -1,7 +1,7 @@ --- name: check-roadmap-dependency -version: 1.0.0 -description: roadmap dependency lock 상태를 확인한다. 사용자가 roadmap-dependency-checker.sh, locks.yaml, 외부 의존 잠금 충족 여부, unlock-ready 판별, 프로젝트 간 잠금 해제 조건 확인을 요청할 때 사용한다. +version: 1.2.0 +description: roadmap dependency lock 상태를 확인한다. 사용자가 roadmap-dependency-checker.sh, locks.yaml, 외부 의존 잠금 충족 여부, unlock-ready 판별, 프로젝트 간 잠금 해제 조건 확인을 요청할 때 사용하며, 현재 Milestone이 locked 쪽인지 rely-on.target 쪽인지 양방향으로 판별한다. --- # check-roadmap-dependency @@ -9,47 +9,64 @@ description: roadmap dependency lock 상태를 확인한다. 사용자가 roadma ## 목적 `.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`에 맡기고, 스킬은 입력 확정, 실행, 결과 해석만 담당한다. +lock id가 없으면 현재 Milestone identity가 `locked`에 있는지, `rely-on.target`에 있는지 모두 찾아 양방향으로 보고한다. +Milestone identity 역검색과 개별 lock 판별은 `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 확인을 요청할 때 +- 사용자가 현재 Milestone이 다른 Milestone을 잠그는지 또는 다른 Milestone에 의해 잠겼는지 확인할 때 ## 입력 -- `lock-id`: 확인할 lock entry id. 예: `oto:oto-agent-registration`. 사용자가 "현재 마일스톤"이라고만 말하면 `<현재-project>:`로 추론한다. `<현재-project>`는 현재 프로젝트 루트 디렉터리 이름이다 (선택, 추론 불가 시 필수) +- `lock-id`: 확인할 lock entry id. 예: `oto:oto-agent-registration`. 명시되면 이 id만 판별한다 (선택) +- `target-milestone`: 확인할 Milestone path 또는 `:agent-ops/roadmap/phase/.../milestones/.md`. 없으면 현재 프로젝트 `current.md`의 활성 Milestone 단일 후보를 사용한다 (선택) +- `direction`: `locked` / `rely-on` / `both`. 기본값은 `both`다. `locked`는 대상 Milestone이 잠긴 entry만, `rely-on`은 대상 Milestone을 선행 조건으로 삼는 entry만 확인한다 (선택) - `locks-file`: 기본값은 프로젝트 상위 `.agent-roadmap-sync/locks.yaml`이며, 다른 파일을 검사할 때만 지정한다 (선택) ## 먼저 확인할 것 - [ ] `agent-ops/bin/roadmap-dependency-checker.sh`가 있는지 확인한다. -- [ ] `lock-id`가 명시되었거나 현재 프로젝트 `current.md`의 활성 Milestone 하나로 추론 가능한지 확인한다. +- [ ] `lock-id`가 명시되었거나 `target-milestone` 또는 현재 프로젝트 `current.md`의 활성 Milestone 하나로 Milestone identity를 추론 가능한지 확인한다. - [ ] 기본 경로 또는 지정된 `locks-file`을 읽을 수 있는지 확인한다. ## 실행 절차 1. **입력 확정** - 사용자 요청에서 `lock-id`를 추출한다. - - `lock-id`가 없고 "현재 마일스톤" 확인 요청이면 현재 프로젝트 `agent-ops/roadmap/current.md`의 활성 Milestone 경로에서 milestone slug를 추출해 `<현재-project>:`를 만든다. - - 활성 Milestone 후보가 없거나 둘 이상이면 추정하지 말고 사용자에게 lock id 또는 Milestone 선택을 요청한다. - `locks-file`이 명시되지 않았으면 프로젝트 상위 `.agent-roadmap-sync/locks.yaml`을 사용한다. + - `locks-file`이 없거나 읽을 수 없으면 결과를 `error`로 보고한다. 이를 미충족 의존성으로 해석하지 않는다. + - `lock-id`가 있으면 그 id만 판별한다. + - `lock-id`가 없으면 `target-milestone` 또는 현재 프로젝트 `agent-ops/roadmap/current.md`의 활성 Milestone 단일 후보로 Milestone identity를 만든다. + - Milestone identity는 `:agent-ops/roadmap/phase//milestones/.md` 형식이다. + - `target-milestone`이 `agent-ops/roadmap/...` 상대 경로이면 현재 프로젝트명을 prefix로 붙인다. + - `target-milestone`이 workspace 하위 절대/상대 경로이면 workspace 바로 아래 디렉터리명을 project로 삼고, 그 뒤 `agent-ops/roadmap/...` 경로를 붙인다. + - `target-milestone`이 archive 경로이면 일반 의존 판별 후보로 쓰지 말고 사용자에게 활성 Milestone 선택을 요청한다. + - 활성 Milestone 후보가 없거나 둘 이상이면 추정하지 말고 사용자에게 lock id 또는 Milestone 선택을 요청한다. + - `lock-id`가 없으면 `agent-ops/bin/roadmap-dependency-checker.sh --find-milestone "" "" ""`로 Milestone identity와 일치하는 entry를 찾는다. + - find 출력의 `locked:`는 locked entries로, `rely-on:`는 rely-on entries로 분류한다. + - find 출력이 `none`이면 결과를 `error`가 아니라 `no-lock`으로 보고한다. 2. **판별 실행** - - 기본 파일이면 `agent-ops/bin/roadmap-dependency-checker.sh ""`를 실행한다. - - 지정 파일이면 `agent-ops/bin/roadmap-dependency-checker.sh "" ""`를 실행한다. + - 확인 대상 lock id마다 `agent-ops/bin/roadmap-dependency-checker.sh "" ""`를 실행한다. + - `locked` 방향의 결과는 대상 Milestone의 잠금 해제 조건 충족 여부다. + - `rely-on` 방향의 결과는 대상 Milestone이 완료될 때 영향을 받는 downstream lock의 현재 충족 여부다. 3. **결과 해석** - stdout `true`와 exit code `0`은 모든 의존 조건 충족으로 보고한다. - stdout `false`와 exit code `1`은 아직 충족되지 않은 의존 조건이 있음을 보고한다. - exit code `2`는 설정 오류, 입력 오류, 필수 필드 누락, 파싱 오류로 보고한다. + - `rely-on` 방향에서 대상 Milestone이 `[검토중]` 또는 `[완료]`인데 status가 `disable`이면 `update-roadmap` 동기화가 필요한 stale lock으로 보고한다. 이 스킬은 status를 바꾸지 않는다. ## 실행 결과 검증 -- [ ] 정상 판별 결과가 stdout에서 `true` 또는 `false` 하나로만 출력되었는가 +- [ ] 각 checker 실행의 stdout이 `true` 또는 `false` 하나로만 출력되었는가 - [ ] exit code `0`, `1`, `2` 의미를 구분해 보고했는가 - [ ] exit code `2`를 unlock 대기 상태로 해석하지 않았는가 +- [ ] lock id가 없을 때 대상 Milestone을 `locked`와 `rely-on.target` 양쪽에서 찾았는가 +- [ ] `locks.yaml`이 없는 경우 `false`가 아니라 `error`로 보고했는가 - [ ] 이 스킬이 `locks.yaml`이나 roadmap 문서를 수정하지 않았는가 - 검증 실패 시: 오류 메시지와 사용한 `lock-id`, `locks-file` 경로를 보고하고 멈춘다. @@ -58,10 +75,13 @@ description: roadmap dependency lock 상태를 확인한다. 사용자가 roadma ```markdown ## 의존성 판별 결과 -- lock-id: `` +- target-milestone: `` +- direction: `` - locks-file: `` -- 결과: `` -- 의미: <모든 의존 조건 충족 | 미충족 의존 조건 있음 | 설정/입력/필수 필드/파싱 오류> +- 결과: `` +- locked entries: `` +- rely-on entries: `` +- 의미: <모든 의존 조건 충족 | 미충족 의존 조건 있음 | 설정/입력/필수 필드/파싱 오류 | 대상 Milestone 관련 lock 없음> ``` ## 금지 사항 diff --git a/agent-ops/skills/common/create-roadmap/SKILL.md b/agent-ops/skills/common/create-roadmap/SKILL.md index 29c38a5..c294a1f 100644 --- a/agent-ops/skills/common/create-roadmap/SKILL.md +++ b/agent-ops/skills/common/create-roadmap/SKILL.md @@ -1,6 +1,6 @@ --- name: create-roadmap -version: 1.14.0 +version: 1.15.0 description: AI-first 개인/소규모 프로젝트의 전체 목표, Phase scaffold, Phase 하위 Milestone 문서, current.md 활성 Phase/Milestone 창, archive Phase scaffold를 처음 생성하는 공통 스킬 --- @@ -87,7 +87,7 @@ agent-ops/ - 새 `[스케치]` Milestone은 `구현 잠금`을 `잠금`으로 둔다. - Milestone 전체에서 사용자만 결정할 항목이 더 이상 없고 에이전트가 표준선에 따라 실행하면 되는 상태라면 `해제`로 둔다. - `구현 잠금`에는 상태와 `결정 필요` 체크리스트만 적는다. 결정할 항목이 없으면 `결정 필요: 없음`으로 적는다. -- 새 Milestone이 다른 프로젝트 Milestone 완료 전까지 잠겨야 하면 `구현 잠금`을 `잠금`으로 두고 프로젝트 상위 `.agent-roadmap-sync/locks.yaml`에 entry를 만든다. +- 새 Milestone이 다른 프로젝트 Milestone 완료 전까지 잠겨야 하면 `구현 잠금`을 `잠금`으로 두고 프로젝트 상위 `.agent-roadmap-sync/locks.yaml`에 entry를 만든다. 의존 대상 확정과 entry 형식은 `update-roadmap`의 프로젝트 간 잠금 규칙을 따른다. - `기능`은 Epic heading과 Task 체크리스트로 작성한다. - Epic heading은 `### Epic: [epic-id] <이름>` 형식으로 작성한다. - Task는 `- [ ] [item-id] 설명` 형식으로 작성한다. @@ -129,7 +129,7 @@ agent-ops/ - 완료된 Phase가 초기 구조에 포함되어야 하는 경우 `archive/phase//PHASE.md`로 만들고 `ROADMAP.md`에서 archive 경로를 가리킨다. - 완료된 Milestone이 진행중 Phase에 포함되어야 하는 경우 `archive/phase//milestones/.md`로 만들고 해당 활성 `PHASE.md`에서 archive 경로를 가리킨다. - archive `PHASE.md`는 Phase 자체가 완료/폐기된 경우에만 만든다. 진행중 Phase의 완료 Milestone만 archive된 경우 archive Phase 디렉터리에 `milestones/`만 둘 수 있다. - - 외부 의존 잠금이 있으면 프로젝트 상위 `.agent-roadmap-sync/locks.yaml`을 생성하거나 기존 entry를 upsert한다. + - 외부 의존 잠금이 있으면 프로젝트 상위 `.agent-roadmap-sync/locks.yaml`을 생성하거나 기존 entry를 upsert한다. 의존 대상이 명시 경로, slug, 제목, 문서 힌트, current 단일 후보 중 하나로 확정되지 않으면 lock entry를 만들지 않고 TODO로 남긴다. 5. **검증** - 생성한 링크가 실제 파일을 가리키는지 확인한다. diff --git a/agent-ops/skills/common/update-roadmap/SKILL.md b/agent-ops/skills/common/update-roadmap/SKILL.md index eb5f8a6..129042e 100644 --- a/agent-ops/skills/common/update-roadmap/SKILL.md +++ b/agent-ops/skills/common/update-roadmap/SKILL.md @@ -1,7 +1,7 @@ --- name: update-roadmap -version: 1.19.0 -description: 로드맵 업데이트, 로드맵에 추가, 마일스톤 추가/갱신, phase/페이즈 변경 요청에 사용한다. Roadmap-Phase-Milestone scaffold에서 target 없는 신규 작업의 규모를 판정하고 기존 Phase/Milestone/Epic/Task를 검색해 upsert한 뒤, 없을 때만 새 항목을 만들고 current.md 동기화, runtime m-task 완료 이벤트 반영, 완료 후보 검토중 전환, 승인된 archive 이동을 처리한다. +version: 1.20.0 +description: 로드맵 업데이트, 로드맵에 추가, 마일스톤 추가/갱신, phase/페이즈 변경 요청에 사용한다. Roadmap-Phase-Milestone scaffold에서 target 없는 신규 작업의 규모를 판정하고 기존 Phase/Milestone/Epic/Task를 검색해 upsert한 뒤, 없을 때만 새 항목을 만들고 current.md 동기화, runtime m-task 완료 이벤트 반영, 완료 후보 검토중 전환, 승인된 archive 이동, workspace 외부 의존 잠금 양방향 동기화를 처리한다. --- # 로드맵 업데이트 @@ -47,7 +47,7 @@ Epic과 Task는 별도 파일로 분리하지 않고 Milestone 문서의 `기능 - `review-comment`: 완료 리뷰에 남길 사용자 확인, 보완, 보류, 폐기 방향성 (선택) - `origin-task`: 런타임 완료 이벤트가 전달한 `agent-task/m-` 또는 `agent-task/m-/` 형식의 원래 active task 경로. 이벤트가 최종 archive 경로만 갖고 있으면 런타임이 이 형식으로 정규화해 전달한다 (선택) - `archive-date`: Phase/Milestone 아카이브 날짜. 없으면 현재 날짜를 사용한다 (선택) -- `workspace-lock`: 프로젝트 상위 `.agent-roadmap-sync/locks.yaml`에 기록할 외부 의존 잠금 설명 (선택) +- `workspace-lock`: 프로젝트 상위 `.agent-roadmap-sync/locks.yaml`에 기록하거나 동기화할 외부 의존 잠금 설명 (선택) ## 표준 구조 @@ -123,16 +123,28 @@ agent-ops/roadmap/ ## 프로젝트 간 잠금 -- 프로젝트 상위 `.agent-roadmap-sync/locks.yaml`은 프로젝트 간 Milestone 잠금 인덱스다. +- 프로젝트 상위 `.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`은 `:agent-ops/roadmap/phase//milestones/.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이 `[검토중]` 또는 `[완료]`가 되면 `<현재-project>:<갱신-milestone-path>`와 일치하는 `rely-on.target`의 `status`만 `enable`로 바꾼다. +- Milestone 경로가 `agent-ops/roadmap/...` 상대 경로이면 현재 프로젝트명을 prefix로 붙인다. workspace 하위 절대/상대 경로이면 workspace 바로 아래 디렉터리명을 project로 삼고, 그 뒤 `agent-ops/roadmap/...` 경로를 붙인다. +- `locked`는 잠긴 Milestone, `rely-on.target`은 선행 조건 Milestone이다. 둘 다 같은 workspace의 어느 활성 Phase 하위 Milestone이어도 된다. 의존 대상이 `current.md`에 있어야 한다고 가정하지 않는다. +- 의존 Milestone 확정 순서: + 1. 사용자가 명시한 `:agent-ops/roadmap/phase/.../milestones/.md` 또는 파일 경로 + 2. 사용자가 명시한 프로젝트와 Milestone slug + 3. 사용자가 명시한 프로젝트와 Milestone 제목의 정규화 일치 + 4. 잠긴 Milestone 문서의 `선행 Milestone: ...`, `관련 Milestone: ...`, `외부 의존 잠금: ...`에 적힌 slug/제목 힌트 + 5. 프로젝트명만 있고 Milestone 힌트가 없을 때만 해당 프로젝트 `current.md`의 활성 Milestone 단일 후보 +- 정규화 비교는 소문자 변환, backtick/따옴표 제거, 영문/숫자가 아닌 연속 문자를 `-` 하나로 치환, 앞뒤 `-` 제거 후 비교한다. 정규화한 힌트는 Milestone 파일 slug와 정규화한 제목 둘 다에 대조한다. +- 2-4번 탐색은 대상 프로젝트의 `agent-ops/roadmap/phase/*/milestones/*.md` 활성 문서만 대상으로 한다. archive 문서는 사용자가 archive 경로를 명시한 경우 외에는 읽거나 후보로 삼지 않는다. +- 후보가 없거나 둘 이상이면 `locks.yaml`을 만들거나 고치지 말고 사용자에게 대상 Milestone 선택을 요청한다. +- 외부 의존 잠금 요청이 있거나, 갱신 대상 Milestone이 `구현 잠금: 잠금`이며 문서에 외부 의존 잠금/선행 Milestone 컨텍스트가 있으면 `.agent-roadmap-sync/locks.yaml`을 upsert한다. resolvable한 외부 의존 문구를 Milestone에 남기고 lock entry를 누락하지 않는다. +- 외부 의존 잠금을 만들 때 대상 Milestone의 `구현 잠금`은 `잠금`으로 둔다. +- 새 `rely-on.status`는 선행 Milestone 상태에서 파생한다. `[검토중]` 또는 `[완료]`이면 `enable`, 그 외 상태거나 상태를 확인할 수 없으면 `disable`이다. +- 같은 `id` entry를 upsert할 때 기존 `rely-on` 항목을 삭제하지 않는다. 같은 `rely-on.target`만 status/note를 갱신하고, 없는 target은 추가하며, `locked` 경로가 바뀐 경우에만 `locked`를 갱신한다. +- 이 스킬이 갱신한 Milestone identity가 어느 entry의 `rely-on.target`과 일치하면 해당 `rely-on.status`를 Milestone 상태 기준으로 동기화한다. `[검토중]` 또는 `[완료]`이면 `enable`, 그 외 상태면 `disable`이다. +- 이 스킬이 갱신하거나 선택한 Milestone identity가 어느 entry의 `locked`와 일치하면 모든 `rely-on.status`가 `enable`인지 확인하고 결과 보고의 `Workspace 잠금`에 `런타임 해제 대기` 또는 `미충족`으로 남긴다. - 모든 `rely-on.status`가 `enable`이어도 여기서 다른 프로젝트 Milestone을 직접 해제하지 않는다. 잠금 해제 실행은 런타임이 별도 `update-roadmap` 호출로 처리한다. ## 완료 리뷰와 검토중 상태 @@ -246,9 +258,10 @@ target 없는 신규 추가 요청은 append가 아니라 upsert로 처리한다 - 새 기능 배치, Epic/Task 추가는 `milestone` 또는 `phase`로 본다. - "로드맵에 추가"처럼 target이 없는 신규 작업 요청은 `placement=auto`, `placement-unit=auto`, `new-feature=<요청 내용>`으로 본다. - 외부 의존 잠금 요청이면 `workspace-lock` 갱신으로 본다. - - 외부 의존 잠금 요청에서 "현재 마일스톤" 또는 target 생략 표현이 있으면 `current.md`의 활성 Milestone을 잠긴 대상으로 확정한다. - - 외부 의존 잠금 요청에서 의존 프로젝트명만 있으면 같은 workspace의 `/agent-ops/roadmap/current.md`를 읽고 활성 Milestone을 의존 대상으로 확정한다. - - 잠긴 대상 또는 의존 대상 활성 Milestone 후보가 없거나 둘 이상이면 `locks.yaml`을 수정하지 말고 사용자에게 Milestone 선택을 요청한다. + - 외부 의존 잠금 요청이 아니어도, 갱신 대상 Milestone이 `구현 잠금: 잠금`이고 외부 의존 잠금/선행 Milestone 컨텍스트가 있으면 `workspace-lock` 동기화 후보로 본다. + - 외부 의존 잠금 요청에서 "현재 마일스톤" 또는 target 생략 표현이 있으면 `current.md`의 활성 Milestone 단일 후보를 잠긴 대상으로 확정한다. + - 의존 대상은 명시 경로, 명시 slug, 명시 제목, 잠긴 Milestone 문서의 선행 Milestone 힌트, 대상 프로젝트 `current.md` 단일 후보 순서로 확정한다. + - 잠긴 대상 또는 의존 대상 후보가 없거나 둘 이상이면 `locks.yaml`을 수정하지 말고 사용자에게 Milestone 선택을 요청한다. 2. **요청 정규화와 규모 판정** - 요청에서 기능명, 목표, 관련 경로, 명시 anchor, 완료 기대, 제약을 추출한다. @@ -287,9 +300,12 @@ target 없는 신규 추가 요청은 append가 아니라 upsert로 처리한다 - 모든 기능 Task와 Task 안에 명시된 검증이 evidence와 함께 `[x]`이면 Milestone 상태를 `[검토중]`으로 바꾸고 `완료 리뷰`에 리뷰 요청과 근거를 남긴다. - `[검토중]` 전환만으로 archive 이동, `current.md` 제거, archive 링크 변경을 수행하지 않는다. - 사용자 승인 근거가 있으면 `[검토중]`을 `[완료]`로 전환하고 archive 모드를 수행할 수 있다. - - 외부 의존 잠금 요청이면 대상 Milestone의 `구현 잠금`을 `잠금`으로 두고 `.agent-roadmap-sync/locks.yaml`을 upsert한다. - - `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`로 바꾼다. + - 외부 의존 잠금 요청 또는 외부 의존 컨텍스트 동기화가 필요하면 대상 Milestone의 `구현 잠금`을 `잠금`으로 두고 `.agent-roadmap-sync/locks.yaml`을 upsert한다. + - `.agent-roadmap-sync/locks.yaml`이 없으면 `.agent-roadmap-sync/` 디렉터리와 `locks.yaml` 파일을 만든다. + - `locks.yaml` entry는 `id=<잠긴-project>:<잠긴-milestone-slug>`, `locked=<잠긴-project>:<잠긴-milestone-path>`, `rely-on.target=<의존-project>:<의존-milestone-path>`, `rely-on.status=`, `rely-on.note=<사용자 요청 요약>`으로 기록한다. + - 같은 `id` entry가 있으면 기존 `rely-on` 목록을 보존하고 같은 `target`만 갱신하거나 새 `target`을 추가한다. + - 갱신한 Milestone identity와 일치하는 `rely-on.target`은 Milestone 상태 기준으로 `enable` 또는 `disable`을 동기화한다. + - 갱신하거나 선택한 Milestone identity와 일치하는 `locked` entry가 있으면 모든 `rely-on.status`가 `enable`인지 확인하고 결과 보고에 남긴다. 6. **검증** - `current.md`의 활성 Phase/Milestone 경로가 실제 파일을 가리키는지 확인한다. @@ -308,6 +324,8 @@ target 없는 신규 추가 요청은 append가 아니라 upsert로 처리한다 - 동일/유사 기존 항목을 검색했고 신규/업데이트 판정이 결과 보고에 남았는지 확인한다. - 자동 배치한 신규 작업이면 선택한 후보와 밀린 후보의 근거가 결과 보고에 포함되는지 확인한다. - `.agent-roadmap-sync/locks.yaml`을 갱신했다면 `locked`, `rely-on.target`, `rely-on.status`가 채워졌는지 확인한다. + - 갱신 대상 Milestone에 resolvable한 외부 의존 잠금/선행 Milestone 컨텍스트가 있으면 해당 lock entry가 존재하는지 확인한다. + - 갱신 대상 Milestone identity가 `locked` 또는 `rely-on.target` 어느 쪽에 있든 결과 보고의 `Workspace 잠금`에 반영했는지 확인한다. - `git diff --check`로 공백 오류를 확인한다. 7. **결과 보고** @@ -352,7 +370,7 @@ target 없는 신규 추가 요청은 append가 아니라 upsert로 처리한다 - 승격 조건: <해당 없음 | 추가/수정/미충족 유지/충족 요약> - 완료 리뷰: <변경 없음 | 요청됨 | 승인됨 | 보완 필요 | 보류 | 폐기> - runtime m-task 라우팅: <해당 없음 | origin-task -> target Milestone | target 불명확> -- Workspace 잠금: <변경 없음 | entry 생성/갱신 | rely-on enable | 런타임 해제 대기> +- Workspace 잠금: <변경 없음 | entry 생성/갱신 | rely-on enable | rely-on disable | 미충족 | 런타임 해제 대기> - 활성 항목: <변경 없음 | Phase/Milestone 추가/제거 요약> - 아카이브: <변경 없음 | 이동 경로와 남긴 링크> - 상태: <변경 없음 | 이전 -> 이후>