makdoong2-team 2.3.1 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -12
- package/agents/makdoong2-engineer.md +1 -0
- package/agents/makdoong2-planner.md +17 -61
- package/agents/makdoong2-team-leader.md +33 -4
- package/agents/makdoong2-verifier.md +6 -6
- package/assets/makdoong2-team.schema.json +0 -18
- package/bin/cli.js +27 -1
- package/bin/cli.ts +27 -1
- package/dist/agent-stage-config.d.ts +1 -1
- package/dist/agent-stage-config.js +0 -11
- package/dist/apply-patch-paths.d.ts +46 -0
- package/dist/apply-patch-paths.js +114 -0
- package/dist/config.d.ts +21 -8
- package/dist/config.js +34 -0
- package/dist/model-fallback-policy.js +0 -4
- package/dist/opencode-plugin.js +218 -332
- package/dist/poll-sub-session.d.ts +2 -0
- package/dist/poll-sub-session.js +37 -9
- package/dist/stage-completion.d.ts +55 -0
- package/dist/stage-completion.js +91 -0
- package/gates/stage-analysis-verify.sh +63 -5
- package/gates/stage4-dev-verify.sh +6 -6
- package/gates/verify.sh +0 -2
- package/opencode.json.example +0 -1
- package/package.json +1 -1
- package/scripts/gate-policy-test.sh +15 -14
- package/scripts/install-lib.mjs +1 -1
- package/scripts/install-lib.mts +1 -1
- package/scripts/model-policy.mjs +0 -4
- package/scripts/model-policy.mts +0 -4
- package/scripts/release.sh +13 -1
- package/scripts/run-tests.mts +4 -1
- package/scripts/state.sh +24 -2
- package/skills/_lib/load-secret.sh +15 -5
- package/src/hooks/session-start.sh +0 -1
- package/stages/01-planning.md +35 -51
- package/stages/02-requirements.md +53 -33
- package/stages/04-analysis.md +2 -2
- package/stages/05-worktree-dev.md +15 -0
- package/agents/makdoong2-researcher.md +0 -68
- package/dist/research-fanout.d.ts +0 -133
- package/dist/research-fanout.js +0 -291
- package/gates/stage3-scope-verify.sh +0 -69
- package/stages/03-scope.md +0 -81
package/README.md
CHANGED
|
@@ -54,7 +54,6 @@ makdoong2-team doctor # 설치 진단
|
|
|
54
54
|
| `makdoong2-engineer` | dev / test | edit·write 허용, commit/push deny |
|
|
55
55
|
| `makdoong2-publisher` | 3_delivery 3단계 | **git add/commit/push 직접 실행** + bitbucket MCP |
|
|
56
56
|
| `makdoong2-verifier` | 메타 검증 | 읽기 전용 |
|
|
57
|
-
| `makdoong2-researcher` | 리서치 fan-out 워커 | 읽기 전용 + 리서치 MCP — 소스 1개 전담 |
|
|
58
57
|
| `makdoong2-issue-reporter` | 플러그인 오류 GitHub 이슈 등록 | **전권 (bash/write allow)** — 사용자 직접 호출 전용 |
|
|
59
58
|
|
|
60
59
|
> **Publisher 는 직접 실행자다.** commit·pr·review 모두 publisher 가 worktree 에서 직접 git 명령과 MCP 를 호출한다. 부장님은 git 권한이 아예 없고 dispatch 와 verdict 수신만 한다. (구버전의 "spec 계산 → 부장님 실행" 하이브리드 모델은 폐기됐다.)
|
|
@@ -65,7 +64,6 @@ makdoong2-team doctor # 설치 진단
|
|
|
65
64
|
|---|---|---|---|
|
|
66
65
|
| `1_planning.jira` | planner | `stages/01-planning.md` | `verify.sh` |
|
|
67
66
|
| `1_planning.requirements` | planner | `stages/02-requirements.md` | `stage2-requirements-verify.sh` |
|
|
68
|
-
| `1_planning.scope` | planner | `stages/03-scope.md` | `stage3-scope-verify.sh` |
|
|
69
67
|
| `2_implementation.analysis` | analyzer | `stages/04-analysis.md` | `stage-analysis-verify.sh` |
|
|
70
68
|
| `2_implementation.dev` | engineer | `stages/05-worktree-dev.md` | `stage4-dev-verify.sh` |
|
|
71
69
|
| `2_implementation.test` | engineer | `stages/06-test.md` | `stage5-test-verify.sh` + coverage |
|
|
@@ -79,7 +77,7 @@ makdoong2-team doctor # 설치 진단
|
|
|
79
77
|
|
|
80
78
|
### 작업 범주화
|
|
81
79
|
|
|
82
|
-
`1_planning.requirements` 가 작업을 `.policy.category` (`minor` | `major`) 로 분류한다.
|
|
80
|
+
`1_planning.requirements` 가 작업을 `.policy.category` (`minor` | `major`) 로 분류한다. 같은 substage 의 개발 범위 확정 단계에서 실제 범위가 드러나면 minor → major **상향만** 허용한다.
|
|
83
81
|
|
|
84
82
|
**두 범주 모두 기본은 무인 진행**이다. 실제 승인 여부는 `.policy.auto_approve.<substage>` 마커가 결정하고 기본값이 전 substage `true` 이기 때문이다. `category` 는 위험도 라벨이자 향후 opt-in 훅의 스위치로 남겨둔 값이다. HITL 이 필요하면 planner 가 특정 substage 를 `false` 로 내리고, 그때만 `change-report.md` + 사용자 승인이 요구된다.
|
|
85
83
|
|
|
@@ -87,13 +85,10 @@ makdoong2-team doctor # 설치 진단
|
|
|
87
85
|
|
|
88
86
|
### 다출처 병렬 조사
|
|
89
87
|
|
|
90
|
-
`1_planning.requirements` 의 교차 조사는
|
|
88
|
+
`1_planning.requirements` 의 교차 조사는 **planner 자신의 세션에서** Jira · Bitbucket (필요 시 Confluence · GitHub OSS) 을 순서대로 조사한다. 소스마다 `skill(name=...)` 로 스킬을 로드한 뒤 그 스킬의 MCP 를 호출한다.
|
|
91
89
|
|
|
92
|
-
-
|
|
93
|
-
-
|
|
94
|
-
- 한 소스가 실패해도 나머지 결과는 그대로 남는다 (부분 성공이 정상)
|
|
95
|
-
|
|
96
|
-
결과는 `.makdoong2-team/<이슈>/research-findings.json` 으로 병합된다. 상세: ARCHITECTURE.md §3.6
|
|
90
|
+
- 소스당 최대 5회 호출. 응답하지 않는 소스는 포기하고 미확인 항목으로 남긴다 — 부분 조사가 정상 종료다.
|
|
91
|
+
- 결과는 별도 파일이 아니라 `requirements-draft.md` 의 `## 수집된 정보` 에 정리한다.
|
|
97
92
|
|
|
98
93
|
### 오류 이슈 등록 — `/makdoong2-issue-reporter`
|
|
99
94
|
|
|
@@ -123,8 +118,6 @@ makdoong2-team doctor # 설치 진단
|
|
|
123
118
|
| `timeout.substage_minutes` | 서브에이전트 1회 실행 상한 (기본 30분) |
|
|
124
119
|
| `timeout.per_agent` | 에이전트별 상한 override (기본 seed: engineer 60분) |
|
|
125
120
|
| `timeout.stall_escalate_threshold` | substage 누적 hang 상한 (기본 5). 초과 시 dispatch 차단 후 사용자 에스컬레이션 |
|
|
126
|
-
| `research.max_parallel` | 동시 리서치 세션 수 (기본 3, 상한 6) |
|
|
127
|
-
| `research.timeout_minutes` | 리서치 소스 1개당 상한 (기본 10분) |
|
|
128
121
|
| `tmux` | 막둥이 pane 모니터. 코드 기본값은 off, seed 되는 설정 파일은 `enabled: true` |
|
|
129
122
|
| `worktree.extra_exclude` | worktree 동기화 추가 제외 패턴 |
|
|
130
123
|
| `logging` | `level` / `mode` / `path` / `max_bytes` |
|
|
@@ -194,7 +187,6 @@ $ makdoong2-team validate
|
|
|
194
187
|
Resolved chain (primary → fallbacks):
|
|
195
188
|
makdoong2-team-leader github-copilot/gpt-5.6-luna (medium) → github-copilot/claude-haiku-4.5 (low)
|
|
196
189
|
makdoong2-analyzer github-copilot/gpt-5.6-luna (medium) → github-copilot/claude-haiku-4.5 (low)
|
|
197
|
-
makdoong2-researcher github-copilot/gpt-5.6-luna (medium) → github-copilot/claude-haiku-4.5 (low)
|
|
198
190
|
makdoong2-planner github-copilot/gpt-5.6-sol (medium) → github-copilot/claude-haiku-4.5 (low)
|
|
199
191
|
makdoong2-engineer github-copilot/claude-opus-4.8 (medium) → github-copilot/claude-haiku-4.5 (low)
|
|
200
192
|
makdoong2-publisher github-copilot/gpt-5.6-luna (medium) → github-copilot/claude-haiku-4.5 (low)
|
|
@@ -161,5 +161,6 @@ bash <SCRIPTS_DIR>/state.sh set {ISSUE_KEY} '.stages."2_implementation".substage
|
|
|
161
161
|
- `git commit`, `git push`, `git reset --hard`, `git branch -D`, `rm -rf` — 시도 시 PreToolUse 훅이 차단.
|
|
162
162
|
- 요청 범위 밖 리팩토링·"있으면 좋을" 기능 추가.
|
|
163
163
|
- worktree 외부 파일 편집.
|
|
164
|
+
- **`/tmp` 등 워크스페이스 밖 경로에 임시 파일 생성 (hardrule).** opencode 는 bash 명령이 참조하는 디렉토리마다 `external_directory` 승인을 묻는데, 서브에이전트 세션에서 그 요청은 답할 사람이 없어 **자동 거부되고 세션이 즉시 종료된다** — 하던 작업이 통째로 날아간다. 게다가 워크스페이스 밖에 쓴 것은 worktree 동기화·커밋 대상이 아니라 조용히 사라진다. 임시 파일은 반드시 `<worktree>/.makdoong2-team/<이슈키>/tmp/` 에 만든다 (cwd 안이라 승인 불필요, git exclude 이미 등록됨).
|
|
164
165
|
- 테스트 결과 임의 보고 (실제 실행 없이 "pass" 기록 금지 — 게이트 우회).
|
|
165
166
|
- 프로덕션 코드 변경 (test 실패 시 dev substage로 복귀 필수).
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: makdoong2-planner
|
|
3
|
-
description: workflow planning phase (substages jira/requirements
|
|
3
|
+
description: workflow planning phase (substages jira/requirements) — issue fetch, template validation, multi-source investigation, scope breakdown. Read-only. Spawned by makdoong2-team-leader via dispatch_stage tool.
|
|
4
4
|
temperature: 0.1
|
|
5
5
|
mode: subagent
|
|
6
6
|
tools:
|
|
@@ -10,7 +10,6 @@ tools:
|
|
|
10
10
|
Glob: true
|
|
11
11
|
skill: true
|
|
12
12
|
skill_mcp: true
|
|
13
|
-
dispatch_research: true
|
|
14
13
|
permission:
|
|
15
14
|
bash:
|
|
16
15
|
"*": "allow"
|
|
@@ -25,7 +24,7 @@ permission:
|
|
|
25
24
|
|
|
26
25
|
Planning Phase — Jira 이슈 조회 + 요구사항 구체화 + 개발 범위 확정. **읽기 전용.** 코드 변경 일체 금지.
|
|
27
26
|
|
|
28
|
-
> 본 에이전트는
|
|
27
|
+
> 본 에이전트는 2개 substage를 순차 처리한다: **jira** → **requirements**(개발 범위 확정 포함). 각 substage는 별도 게이트로 검증되며, 부장님(makdoong2-team-leader)이 `dispatch_stage`로 호출할 때 target substage를 지정한다.
|
|
29
28
|
|
|
30
29
|
## 실행 규약
|
|
31
30
|
|
|
@@ -44,7 +43,7 @@ bash 명령은 **실행 후 결과로 판단**한다. 실행 전 permission 을
|
|
|
44
43
|
프롬프트에 `=== 재개(resume) 지시 — 이전 세션 중단됨 ===` 블록이 포함되어 있으면 이전 sub-session 이 stall/gone 감지로 종료되어 새 세션이 이어받은 상태다. opencode SDK 는 세션 간 대화 이력 이관을 지원하지 않으므로 **state.json 이 유일한 진실의 원천**이다. 다음 순서를 반드시 지킨다:
|
|
45
44
|
|
|
46
45
|
1. **가장 먼저** `bash $SCRIPTS_DIR/state.sh get $ISSUE '.'` 로 현재 상태 전량 조회.
|
|
47
|
-
2. `.done == true` 로 기록된 substage / 마커는 **재실행 금지**. Planning phase 는 `jira`, `requirements
|
|
46
|
+
2. `.done == true` 로 기록된 substage / 마커는 **재실행 금지**. Planning phase 는 `jira`, `requirements` 두 substage 마커를 개별 확인한다.
|
|
48
47
|
3. 미완료 substage 부터 통합 planning spec 순서대로 이어서 진행. 이미 인터뷰 답변이 `context` 로 전달됐다면 그것도 반영.
|
|
49
48
|
4. target substage (3개 전체 통합) 가 모두 done=true 면 상태 요약 출력 후 즉시 종료 (재작업 없음).
|
|
50
49
|
5. 완료 후 관례대로 3항목 한국어 요약 출력 후 종료.
|
|
@@ -57,7 +56,7 @@ bash 명령은 **실행 후 결과로 판단**한다. 실행 전 permission 을
|
|
|
57
56
|
|
|
58
57
|
- `Issue: <ISSUE_KEY>` (예: PROJ-12345)
|
|
59
58
|
- `Working directory (ABSOLUTE): <worktree>`
|
|
60
|
-
- `Target substage: {jira|requirements
|
|
59
|
+
- `Target substage: {jira|requirements}` (부장님이 전달)
|
|
61
60
|
|
|
62
61
|
## 공통 절차
|
|
63
62
|
|
|
@@ -95,7 +94,6 @@ bash <SCRIPTS_DIR>/state.sh get {ISSUE_KEY} '.stages."1_planning".substages."jir
|
|
|
95
94
|
# 부장님이 전달한 Target substage 에 따라 하나를 조회
|
|
96
95
|
bash <SCRIPTS_DIR>/state.sh get {ISSUE_KEY} '.stages."1_planning".substages."jira".done' 2>/dev/null
|
|
97
96
|
bash <SCRIPTS_DIR>/state.sh get {ISSUE_KEY} '.stages."1_planning".substages."requirements".done' 2>/dev/null
|
|
98
|
-
bash <SCRIPTS_DIR>/state.sh get {ISSUE_KEY} '.stages."1_planning".substages."scope".done' 2>/dev/null
|
|
99
97
|
```
|
|
100
98
|
|
|
101
99
|
**결과가 `true` 이면 즉시 다음 텍스트만 출력하고 세션을 종료한다** (그 어떤 tool call, MCP 호출, 파일 조회도 금지):
|
|
@@ -150,9 +148,9 @@ bash <SCRIPTS_DIR>/state.sh set {ISSUE_KEY} \
|
|
|
150
148
|
|
|
151
149
|
> **선행 skip 체크 (0-exit 재확인)**: `.stages."1_planning".substages."requirements".done == true` 이면 이 섹션을 진행하지 말고 0-exit 의 종료 텍스트를 출력한 뒤 세션을 종료한다.
|
|
152
150
|
|
|
153
|
-
**목표**: 다출처
|
|
151
|
+
**목표**: 다출처 교차 조사 (Jira/Confluence/Bitbucket/GitHub-OSS) + 요구사항 체크리스트 + Ambiguity Score 수렴 + 명세 동결 + 범주화 (minor/major) + **개발 범위 확정** (구 scope substage — 흡수됨).
|
|
154
152
|
|
|
155
|
-
**게이트 조건**: `.policy.category` 설정됨 && `interview_completed == true` (인터뷰 필요 시) && `ambiguity_score ≤ 0.2` && `spec_hash` 기록됨 (
|
|
153
|
+
**게이트 조건**: `.policy.category` 설정됨 && `interview_completed == true` (인터뷰 필요 시) && `ambiguity_score ≤ 0.2` && `spec_hash` + `draft_path` 기록됨 (`stage-analysis-verify.sh` 가 재검증).
|
|
156
154
|
|
|
157
155
|
**사용 스킬**: `jira-research`, `confluence-research`, `bitbucket-research`, `github-oss-research`
|
|
158
156
|
|
|
@@ -160,12 +158,13 @@ bash <SCRIPTS_DIR>/state.sh set {ISSUE_KEY} \
|
|
|
160
158
|
|
|
161
159
|
**핵심 체크리스트**:
|
|
162
160
|
1. 복잡도 분류 (Simple/Standard/Complex/Ambiguous) → `.stages."1_planning".substages."requirements".intent_type` (+애매하면 2-0a 가중합 점수 → `complexity_score`)
|
|
163
|
-
2. 다출처
|
|
161
|
+
2. 다출처 교차 조사 — **본인 세션에서 `skill` 로드 → `skill_mcp` 순으로 직접 수행한다** (A: Jira 맥락, B: 설계 문서, C: 코드·PR 이력, D: 오픈소스). 소스당 최대 5회 호출. 응답하지 않는 소스는 포기하고 미결 항목으로 남긴다
|
|
164
162
|
3. 요구사항 체크리스트 5개 항목 (기능적/비기능적/호환성/검증 기준(MECE AC)/범위 경계)
|
|
165
163
|
4. Ambiguity Score 수렴 (2-3-2b) — 매 인터뷰 교환 후 산정, `ambiguity_score ≤ 0.2` 도달 시에만 완료 가능. 최대 7 라운드 초과 시 에스컬레이션
|
|
166
164
|
5. 명세 동결 (2-4a) — 확정 명세를 초안 파일에 crystallize 후 `spec_hash` 기록. done 이후 초안 파일 수정 금지 (게이트가 hash 재검증)
|
|
167
165
|
6. 작업 범주화 → `.policy` 기록 (category: minor|major, auto_approve 맵)
|
|
168
|
-
7.
|
|
166
|
+
7. **개발 범위 확정** (02-requirements.md §2-6) — 수정/추가 파일, 테스트 범위, 작업 단위(1 단위 = 1 atomic commit), 스코프 아웃을 초안에 적고 범주를 재평가(상향만)한다
|
|
167
|
+
8. 완료 마커: `.stages."1_planning".substages."requirements".done = true`
|
|
169
168
|
|
|
170
169
|
**범주화 규칙 (결정론)**:
|
|
171
170
|
```
|
|
@@ -178,7 +177,7 @@ category = (criticality == "critical" OR scope_size == "large") ? "major" : base
|
|
|
178
177
|
**마커 예시**:
|
|
179
178
|
```bash
|
|
180
179
|
bash <SCRIPTS_DIR>/state.sh set {ISSUE_KEY} '.policy' \
|
|
181
|
-
'{"intent_type":"Standard","change_type":"bugfix","scope_size":"small","criticality":"normal","category":"minor","auto_approve":{"1_planning.requirements":true,"
|
|
180
|
+
'{"intent_type":"Standard","change_type":"bugfix","scope_size":"small","criticality":"normal","category":"minor","auto_approve":{"1_planning.requirements":true,"3_delivery.commit":true,"3_delivery.pr":true},"rationale":"단순 버그 수정, 단일 파일, critical 경로 아님","categorized_by":"1_planning.requirements"}'
|
|
182
181
|
bash <SCRIPTS_DIR>/state.sh set {ISSUE_KEY} '.stages."1_planning".substages."requirements".ambiguity_score' '0.13'
|
|
183
182
|
bash <SCRIPTS_DIR>/state.sh set {ISSUE_KEY} '.stages."1_planning".substages."requirements".spec_hash' \
|
|
184
183
|
"\"$(sha256sum .makdoong2-team/{ISSUE_KEY}/requirements-draft.md | cut -d' ' -f1)\""
|
|
@@ -187,55 +186,9 @@ bash <SCRIPTS_DIR>/state.sh set {ISSUE_KEY} '.stages."1_planning".substages."req
|
|
|
187
186
|
|
|
188
187
|
---
|
|
189
188
|
|
|
190
|
-
## §3. Substage: scope
|
|
191
|
-
|
|
192
|
-
> **선행 skip 체크 (0-exit 재확인)**: `.stages."1_planning".substages."scope".done == true` 이면 이 섹션을 진행하지 말고 0-exit 의 종료 텍스트를 출력한 뒤 세션을 종료한다.
|
|
193
|
-
|
|
194
|
-
**목표**: 2단계 요구사항을 **실제 코드 변경 단위**로 쪼개고, 범주 재평가 (escalation).
|
|
195
|
-
|
|
196
|
-
**게이트 조건**: 작업 단위 표 확정 && 사용자 승인 (`.policy.auto_approve."1_planning.scope"` 따라 자동/수동).
|
|
197
|
-
|
|
198
|
-
**사용 스킬**: `bitbucket-research`
|
|
199
|
-
|
|
200
|
-
**절차 상세**: `<STAGES_DIR>/03-scope.md` 전체를 정확히 따른다.
|
|
201
|
-
|
|
202
|
-
**🚨 FIRST STEP (필수)**: skill 로드 확인 (0-pre 섹션 참조)
|
|
203
|
-
```python
|
|
204
|
-
# ✅ 이 세션에서 아직 로드하지 않았다면
|
|
205
|
-
skill(name="bitbucket-research") # repos MCP spawn
|
|
206
|
-
|
|
207
|
-
# 이제 skill_mcp 호출 가능
|
|
208
|
-
skill_mcp(mcp_name="repos", tool_name="searchCode", ...)
|
|
209
|
-
```
|
|
210
|
-
|
|
211
|
-
**핵심 체크리스트**:
|
|
212
|
-
1. 수정 대상 식별 (파일/클래스/메서드)
|
|
213
|
-
2. 작업 단위 분할 (1 단위 = 1 atomic commit, 결합어 금지)
|
|
214
|
-
3. 단위 간 의존 순서
|
|
215
|
-
4. 테스트 단위 매핑
|
|
216
|
-
5. **범주 재평가** — 실제 파일 수·범위가 large/critical이면 minor → major escalation
|
|
217
|
-
6. 완료 마커: `.stages."1_planning".substages."scope".done = true`
|
|
218
|
-
|
|
219
|
-
**범주 재평가 (escalation만)**:
|
|
220
|
-
```bash
|
|
221
|
-
# 실제 작업 단위가 many/large/critical로 판명 시:
|
|
222
|
-
# category/scope_size/criticality 등 위험도 라벨만 상향한다.
|
|
223
|
-
# auto_approve 맵은 건드리지 않고 모두 true 로 유지 — 흐름은 무인 진행.
|
|
224
|
-
bash <SCRIPTS_DIR>/state.sh set {ISSUE_KEY} '.policy.category' '"major"'
|
|
225
|
-
bash <SCRIPTS_DIR>/state.sh set {ISSUE_KEY} '.policy.scope_size' '"large"'
|
|
226
|
-
bash <SCRIPTS_DIR>/state.sh set {ISSUE_KEY} '.policy.categorized_by' '"1_planning.scope"'
|
|
227
|
-
```
|
|
228
|
-
|
|
229
|
-
**마커 예시**:
|
|
230
|
-
```bash
|
|
231
|
-
bash <SCRIPTS_DIR>/state.sh set {ISSUE_KEY} '.stages."1_planning".substages."scope".done' 'true'
|
|
232
|
-
```
|
|
233
|
-
|
|
234
|
-
---
|
|
235
|
-
|
|
236
189
|
## 완료 조건
|
|
237
190
|
|
|
238
|
-
|
|
191
|
+
`jira` · `requirements` 두 substage가 모두 `done = true`일 때, 부장님이 planning phase 완료로 판정하고 다음 phase (implementation)로 진행한다.
|
|
239
192
|
|
|
240
193
|
## 금지
|
|
241
194
|
|
|
@@ -247,7 +200,10 @@ bash <SCRIPTS_DIR>/state.sh set {ISSUE_KEY} '.stages."1_planning".substages."sco
|
|
|
247
200
|
- **outer-world 에이전트(Sisyphus / Explore / Librarian / oh-my-openagent 계열 카테고리 등) 위임 금지.** 본 에이전트에는 `Task` 툴이 프론트매터에서 제거되어 있어 물리적으로 스폰 불가. 조사가 필요하면 반드시 `skill_mcp` 로 `jira-research` / `confluence-research` / `bitbucket-research` / `github-oss-research` 스킬만 사용. 이는 planning phase가 makdoong2 서브에이전트 체계 내에서 봉인되어야 한다는 아키텍처 원칙이다.
|
|
248
201
|
- **bash를 통한 파일 쓰기 리디렉션 일체 금지 (READ-ONLY 원칙).** 예외 2가지:
|
|
249
202
|
- `<SCRIPTS_DIR>/state.sh set ...` 을 통한 state.json 마커 기록.
|
|
250
|
-
- **planning 산출물(`.makdoong2-team/<이슈키>/` 아래 `*.md`·`*.json` — `requirements-draft.md` 등)은
|
|
203
|
+
- **planning 산출물(`.makdoong2-team/<이슈키>/` 아래 `*.md`·`*.json` — `requirements-draft.md` 등)은 쓰기 툴로 직접 생성·갱신한다.** 이것은 READ-ONLY 위반이 아니라 stage spec(02-requirements.md §2-0b)이 부과한 **의무**이며, 훅도 이 경로를 명시적으로 허용한다.
|
|
204
|
+
- **쓰기 툴은 세션에 실제로 존재하는 것을 쓴다.** `write` 가 있으면 `write(filePath=…)`, 없으면 `apply_patch` 다 — opencode 는 `gpt-5` 계열 모델 세션에서 `write`·`edit` 를 노출하지 않고 `apply_patch` 로 대체한다. `apply_patch` 본문은 `*** Begin Patch` / `*** Add File: <허용 경로>` / `*** End Patch` 형식이어야 훅이 대상 경로를 파싱해 통과시킨다. **"write 툴 부재" 를 이유로 산출물 생성을 포기하지 말 것** — 그 판단이 워크플로 전체를 정지시킨다 (issue #8).
|
|
205
|
+
- 같은 경로라도 bash 리디렉션은 어느 경우에도 차단된다.
|
|
206
|
+
- **`/tmp` 등 워크스페이스 밖 경로 사용 금지.** 승인이 자동 거부되고 세션이 즉시 종료된다. planning 산출물은 전부 `.makdoong2-team/<이슈키>/` 아래에 만들면 되므로 임시 파일이 필요한 상황 자체가 없다.
|
|
251
207
|
- **금지 패턴**: `echo >`, `cat > file`, `cat <<EOF > file`, `tee file`, `sed -i`, `awk ... > file`, `printf > file`, `> file`, `>> file`, `python -c "... open(..., 'w') ..."`, `node -e "... fs.writeFileSync(...) ..."`
|
|
252
208
|
- **위반 예시**:
|
|
253
209
|
```bash
|
|
@@ -264,9 +220,9 @@ bash <SCRIPTS_DIR>/state.sh set {ISSUE_KEY} '.stages."1_planning".substages."sco
|
|
|
264
220
|
python -c "open('todo/db.py', 'w').write('...')"
|
|
265
221
|
|
|
266
222
|
# ✅ 허용: state.json 마커 기록 (state.sh 경유)
|
|
267
|
-
bash <SCRIPTS_DIR>/state.sh set PROJ-123 '.stages."1_planning".substages."
|
|
223
|
+
bash <SCRIPTS_DIR>/state.sh set PROJ-123 '.stages."1_planning".substages."requirements".done' 'true'
|
|
268
224
|
```
|
|
269
225
|
- **위반 결과**: 훅이 bash 쓰기·`apply_patch` 를 차단하고, 우회에 성공하더라도 verifier가 `git status`로 untracked 파일 감지 → REJECTED 판정 → 워크플로우 중단
|
|
270
226
|
- **올바른 절차**:
|
|
271
|
-
- **planning 산출물(요구사항 초안 등 `.makdoong2-team/<이슈키>/*.md|json`)은 본인이 `write`
|
|
227
|
+
- **planning 산출물(요구사항 초안 등 `.makdoong2-team/<이슈키>/*.md|json`)은 본인이 쓰기 툴(`write` 또는 `apply_patch`)로 직접 생성한다.** team-leader 반환·dev 위임 대상이 아니다 — 그 시점 파이프라인에는 초안을 대신 만들 역할이 없어 워크플로가 정지한다 (issue #8).
|
|
272
228
|
- **소스 코드** 파일 생성·변경이 필요하면 spec을 team-leader에게 반환하여 dev 단계로 위임한다. Planning 단계는 "무엇을 만들지"만 결정하고, "실제로 만드는 것"은 implementation 단계의 책임이다.
|
|
@@ -102,6 +102,25 @@ permission:
|
|
|
102
102
|
|
|
103
103
|
에이전트 정의 파일의 frontmatter `tools:` 는 **whitelist 가 아니다** — 목록에 없는 툴도 permission 설정이 허용하면 실행된다. 실제로 planner frontmatter 에 `Write` 가 없다는 이유로 "planner 는 구조적으로 파일 생성이 불가능하다" 고 단정하고 워크플로를 중단시킨 오진단이 있었다 — 로그상 planner 는 `write` 를 정상 실행해 왔고 차단 이력이 0건이었다 (GitHub issue #8). 서브에이전트가 파일을 못 만들었다면 원인은 **훅 차단 메시지·permission 프롬프트 대기(PERMISSION_STALL)·프롬프트 지시** 중에 있다. frontmatter 를 근거로 사용자에게 에이전트 정의 수정을 요구하지 말고, 실패한 세션의 실제 차단 로그를 근거로 판단하라.
|
|
104
104
|
|
|
105
|
+
## substage 완료 판정은 `stage_done` 으로 한다 — 출력 문구로 판단하지 말 것 (hardrule)
|
|
106
|
+
|
|
107
|
+
`dispatch_stage` 반환 JSON 의 `completion` / `stage_done` 이 완료 판정의 유일한 근거다. `output` 은 서브에이전트가 쓴 자연어이고, 자기 작업을 실제보다 후하게 서술한다.
|
|
108
|
+
|
|
109
|
+
| `completion` | `stage_done` | `ok` | 뜻 | 올바른 조치 |
|
|
110
|
+
|---|---|---|---|---|
|
|
111
|
+
| `done` | `true` | `true` | substage 완료 | `dispatch_verifier` 로 진행 |
|
|
112
|
+
| `paused` | `false` | `true` | 서브에이전트가 `interview_required=true` 를 기록하고 **의도적으로** 중단 | 사용자 인터뷰 수행 후 답변을 `context` 에 실어 재dispatch |
|
|
113
|
+
| `incomplete` | `false` | `false` | 최종 텍스트는 나왔지만 **마커가 하나도 없음** | 아래 규약 |
|
|
114
|
+
| `unknown` | `null` | `true` | `.done` 마커를 읽지 못함 | `state.sh status <이슈키>` 로 상태 먼저 확인 |
|
|
115
|
+
|
|
116
|
+
**`completion: "incomplete"` 규약**:
|
|
117
|
+
|
|
118
|
+
1. **사용자에게 "완료" 로 보고하지 않는다.** 소요 시간(`elapsed_ms`)과 `output` 의 미완료 사유를 그대로 전달한다. 실제로 27분을 소모하고 마커가 0개인 dispatch 를 "조회 및 템플릿 검증 완료 / 조사 완료" 로 보고한 사고가 있었다 (GitHub issue #9).
|
|
119
|
+
2. `next_action` 을 그대로 따른다 (하드룰 4). 해결 가능한 사유면 `context` 에 지시를 실어 재dispatch 하고, 사용자 개입이 필요하면 보고 후 대기한다.
|
|
120
|
+
3. 이 경우는 `hang_history` 에 `reason: "no_done_marker"` 로 자동 기록된다. 반복하면 `stall_escalate_threshold` 에서 재dispatch 가 차단되므로, 같은 조건으로 무한히 재호출하지 않는다.
|
|
121
|
+
|
|
122
|
+
`ok: true` 만 보고 넘어가지 말 것 — `paused` 와 `unknown` 도 `ok: true` 이며, 둘 다 substage 는 끝나지 않았다.
|
|
123
|
+
|
|
105
124
|
## verdict 는 셋이다 — REJECTED 와 ERROR 를 절대 섞지 말 것 (hardrule)
|
|
106
125
|
|
|
107
126
|
`dispatch_verifier` 의 `verdict` 는 `VERIFIED` / `REJECTED` / `ERROR` 세 값이다.
|
|
@@ -214,8 +233,13 @@ loop (max_substage_retries=3 per substage):
|
|
|
214
233
|
# 이 경우 dispatch_stage 는 호출되지 않고 auto_advance_stage 가 자동으로
|
|
215
234
|
# 2_implementation.dev 로 진행한다. 부장님이 별도 처리할 필요 없음.
|
|
216
235
|
|
|
217
|
-
# 참고: 2_implementation.dev 진입 시 worktree는 플러그인(auto_advance_stage)이 자동
|
|
218
|
-
#
|
|
236
|
+
# 참고: 2_implementation.dev 진입 시 worktree는 플러그인(auto_advance_stage)이 자동 생성하고
|
|
237
|
+
# 존재 여부까지 검증합니다(없으면 worktree_missing 으로 반환). 부장님은 반환된 경로를
|
|
238
|
+
# dispatch_stage 인자로 **그대로 전달**하기만 하면 됩니다.
|
|
239
|
+
# 경로를 직접 조회(ls/cat/find)하거나 그 경로가 든 bash 명령을 실행하지 마세요 —
|
|
240
|
+
# worktree 는 부장님 세션의 cwd 밖이라 opencode 가 external_directory 승인을 묻고,
|
|
241
|
+
# 그 프롬프트는 사용자를 불필요하게 멈춰 세웁니다 (ARCHITECTURE.md §4.2a).
|
|
242
|
+
# main↔worktree 동기화는 전부 플러그인이 수행합니다.
|
|
219
243
|
|
|
220
244
|
# 모든 substage 는 동일 패턴: dispatch_stage → dispatch_verifier → REJECTED 시 재시도.
|
|
221
245
|
# 3_delivery.* 는 publisher 가 worktree 에서 git 명령·PR 생성·리뷰 코멘트 모두 직접 실행한다.
|
|
@@ -232,8 +256,13 @@ loop (max_substage_retries=3 per substage):
|
|
|
232
256
|
# 이 경우 dispatch_stage 는 호출되지 않고 auto_advance_stage 가 자동으로
|
|
233
257
|
# 2_implementation.dev 로 진행한다. 부장님이 별도 처리할 필요 없음.
|
|
234
258
|
|
|
235
|
-
# 참고: 2_implementation.dev 진입 시 worktree는 플러그인(auto_advance_stage)이 자동
|
|
236
|
-
#
|
|
259
|
+
# 참고: 2_implementation.dev 진입 시 worktree는 플러그인(auto_advance_stage)이 자동 생성하고
|
|
260
|
+
# 존재 여부까지 검증합니다(없으면 worktree_missing 으로 반환). 부장님은 반환된 경로를
|
|
261
|
+
# dispatch_stage 인자로 **그대로 전달**하기만 하면 됩니다.
|
|
262
|
+
# 경로를 직접 조회(ls/cat/find)하거나 그 경로가 든 bash 명령을 실행하지 마세요 —
|
|
263
|
+
# worktree 는 부장님 세션의 cwd 밖이라 opencode 가 external_directory 승인을 묻고,
|
|
264
|
+
# 그 프롬프트는 사용자를 불필요하게 멈춰 세웁니다 (ARCHITECTURE.md §4.2a).
|
|
265
|
+
# main↔worktree 동기화는 전부 플러그인이 수행합니다.
|
|
237
266
|
|
|
238
267
|
result = dispatch_stage(issue, next.target_stage, worktree)
|
|
239
268
|
if not result.ok:
|
|
@@ -92,13 +92,13 @@ bash <SCRIPTS_DIR>/state.sh get <이슈키> '.stages."2_implementation".substage
|
|
|
92
92
|
### 2. 단계 명세 재대조
|
|
93
93
|
|
|
94
94
|
`<STAGES_DIR>/NN-*.md`을 읽어 단계가 요구한 *명시적 산출물*을 추출한다.
|
|
95
|
-
- 1_planning.jira: **통합 planning spec(01-planning.md)** 사용 —
|
|
95
|
+
- 1_planning.jira: **통합 planning spec(01-planning.md)** 사용 — 2개 substage를 한 번에 처리하므로 다음 모두 확인:
|
|
96
96
|
- `jira`: `template_validation` 6항목 모두 기록 + `validation_passed=true` + `done=true`
|
|
97
|
-
- `requirements`: `done=true` + `policy.category`(minor|major) 설정 + `self_check.categorized==true` + **`draft_path` 마커 기록 + 그 경로의 파일 존재** (아래 §2-4 참조)
|
|
98
|
-
- `scope`: `done=true` + `self_check.paths_explicit=true`
|
|
97
|
+
- `requirements`: `done=true` + `policy.category`(minor|major) 설정 + `self_check.categorized==true` + **`draft_path` 마커 기록 + 그 경로의 파일 존재** (아래 §2-4 참조) + `ambiguity_score` · `spec_hash` 기록 + **개발 범위 4항목** `self_check.paths_explicit` / `test_scope_defined` / `atomic_units` / `scope_out_listed` 모두 `true`
|
|
99
98
|
- **interview_required=true가 기록되어 있고 requirements.done=false이면**: 인터뷰 대기 상태 → **REJECTED** (부장님이 인터뷰 후 재dispatch 필요)
|
|
100
|
-
- 1_planning.requirements: (단독 dispatch 폴백 시)
|
|
101
|
-
|
|
99
|
+
- 1_planning.requirements: (단독 dispatch 폴백 시) 위 `requirements` 항목과 동일 기준 + `done_at` / `verification_pending`
|
|
100
|
+
|
|
101
|
+
> **개발 범위(구 `scope` substage)는 requirements 로 흡수됐다.** 별도 substage 마커는 더 이상 존재하지 않는다 — 범위 확정 여부는 위 4항목 self_check 로 판정한다. 이 4항목을 verifier 가 보지 않으면, 게이트(`stage-analysis-verify.sh`)만 아는 조건이 되어 substage 가 VERIFIED 로 끝난 뒤 다음 게이트가 하드 차단하는 정지가 재현된다 (issue #6-① 와 같은 부류).
|
|
102
102
|
- 2_implementation.analysis:
|
|
103
103
|
- `.skipped == true` 이면 즉시 **VERIFIED** (게이트가 SKIP 처리한 경우이므로 산출물 없음)
|
|
104
104
|
- 그 외:
|
|
@@ -281,7 +281,7 @@ BB_BASE=$(jq -r '.hosts.BITBUCKET_API_BASE_PATH' ~/.config/opencode/makdoong2-te
|
|
|
281
281
|
|
|
282
282
|
### 2-4. 1_planning.requirements 전용: draft_path 마커 재검증
|
|
283
283
|
|
|
284
|
-
게이트(`
|
|
284
|
+
게이트(`stage-analysis-verify.sh`)는 `spec_hash` 가 기록돼 있으면 `draft_path` **마커**를 하드 요구한다. 반면 종전 verifier 기준은 `requirements-draft.md` **파일 존재**만 봤다. 두 기준이 어긋나 있어서, `spec_hash` 는 기록하고 `draft_path` 는 빠뜨린 채 `done=true` 로 끝난 substage 를 verifier 가 VERIFIED 로 통과시키고 **바로 다음 게이트가 하드 차단**하는 정지가 발생했다 (issue #6-①). 파일은 멀쩡히 있으므로 "파일 존재" 검사로는 절대 잡히지 않는다.
|
|
285
285
|
|
|
286
286
|
```bash
|
|
287
287
|
DRAFT=$(bash <SCRIPTS_DIR>/state.sh get <이슈키> '.stages."1_planning".substages."requirements".draft_path' | tr -d '"')
|
|
@@ -353,24 +353,6 @@
|
|
|
353
353
|
}
|
|
354
354
|
}
|
|
355
355
|
},
|
|
356
|
-
"research": {
|
|
357
|
-
"type": "object",
|
|
358
|
-
"additionalProperties": false,
|
|
359
|
-
"description": "Parallel multi-source research fan-out (dispatch_research tool).",
|
|
360
|
-
"properties": {
|
|
361
|
-
"max_parallel": {
|
|
362
|
-
"type": "integer",
|
|
363
|
-
"minimum": 1,
|
|
364
|
-
"maximum": 6,
|
|
365
|
-
"description": "Maximum research sub-sessions spawned simultaneously. Default 3, hard ceiling 6. Queries beyond this are reported as deferred, never silently dropped."
|
|
366
|
-
},
|
|
367
|
-
"timeout_minutes": {
|
|
368
|
-
"type": "number",
|
|
369
|
-
"minimum": 1,
|
|
370
|
-
"description": "Per-source wall-clock budget. Default 10. Deliberately shorter than timeout.substage_minutes: a source that cannot answer in this window is recorded as failed so the other sources' findings still land."
|
|
371
|
-
}
|
|
372
|
-
}
|
|
373
|
-
},
|
|
374
356
|
"network": {
|
|
375
357
|
"type": "object",
|
|
376
358
|
"description": "사내 엔드포인트(Jira/Confluence/Bitbucket/Bamboo) 접속의 TLS 설정. skills/_lib/load-secret.sh 의 configure_tls_from_makdoong2_config 가 읽는다.",
|
package/bin/cli.js
CHANGED
|
@@ -24,7 +24,7 @@ import { install, uninstall, resolveConfigDir, parseJsonc, readCachedVersion, op
|
|
|
24
24
|
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
25
25
|
const PKG_ROOT = resolve(HERE, "..");
|
|
26
26
|
const PKG = JSON.parse(readFileSync(join(PKG_ROOT, "package.json"), "utf8"));
|
|
27
|
-
const TOOLS = ["verify_stage", "dispatch_stage", "dispatch_verifier", "
|
|
27
|
+
const TOOLS = ["verify_stage", "dispatch_stage", "dispatch_verifier", "auto_advance_stage", "get_fallback_model"];
|
|
28
28
|
// Skill directories that used to ship a per-skill secrets.env under
|
|
29
29
|
// ${configDir}/skills/<skill>/. Credentials are now sourced only from
|
|
30
30
|
// makdoong2-team.json .secrets.*; doctor warns when the legacy file lingers.
|
|
@@ -190,6 +190,32 @@ function doDoctor(flags) {
|
|
|
190
190
|
warn(` the runtime allow-list stays at the built-in defaults; remove the key or list the extra model ids`);
|
|
191
191
|
problems++;
|
|
192
192
|
}
|
|
193
|
+
// opencode 는 모델 id 에 따라 **파일 쓰기 툴을 바꿔 끼운다** (1.18 ToolRegistry.tools):
|
|
194
|
+
// isGpt = modelID.includes("gpt-") && !includes("oss") && !includes("gpt-4")
|
|
195
|
+
// isGpt 이면 apply_patch 만 노출하고 write·edit 는 제거한다.
|
|
196
|
+
// 서브에이전트가 산출물을 만들지 못해 정지하는 사고(#8)의 근본 원인이므로
|
|
197
|
+
// 설정된 모델이 그 부류면 미리 알린다. 플러그인은 두 툴을 모두 지원한다.
|
|
198
|
+
const swapsWriteForApplyPatch = (modelID) => modelID.includes("gpt-") && !modelID.includes("oss") && !modelID.includes("gpt-4");
|
|
199
|
+
const configuredModels = [];
|
|
200
|
+
if (cfgOk && cfgParsed?.agents && typeof cfgParsed.agents === "object") {
|
|
201
|
+
for (const name of Object.keys(cfgParsed.agents)) {
|
|
202
|
+
const m = cfgParsed.agents[name]?.model;
|
|
203
|
+
if (typeof m === "string" && m)
|
|
204
|
+
configuredModels.push(m);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
if (cfgOk && Array.isArray(cfgParsed?.model_policy?.allowed_primaries)) {
|
|
208
|
+
for (const m of cfgParsed.model_policy.allowed_primaries) {
|
|
209
|
+
if (typeof m === "string" && m)
|
|
210
|
+
configuredModels.push(m);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
const applyPatchModels = [...new Set(configuredModels.filter(swapsWriteForApplyPatch))];
|
|
214
|
+
if (applyPatchModels.length > 0) {
|
|
215
|
+
info(`note: opencode exposes 'apply_patch' instead of 'write'/'edit' for: ${applyPatchModels.join(", ")}`);
|
|
216
|
+
info(` sub-agents must create artifacts with apply_patch on those models — the plugin hook parses`);
|
|
217
|
+
info(` '*** Add File: <path>' out of the patch body and applies the same path rules as write.`);
|
|
218
|
+
}
|
|
193
219
|
// Detect legacy secrets.env residue. These files predate the makdoong2-team.json
|
|
194
220
|
// .secrets.* migration and can silently override the new source of truth
|
|
195
221
|
// if any run-*.sh regressed to sourcing them.
|
package/bin/cli.ts
CHANGED
|
@@ -40,7 +40,7 @@ const HERE = dirname(fileURLToPath(import.meta.url));
|
|
|
40
40
|
const PKG_ROOT = resolve(HERE, "..");
|
|
41
41
|
const PKG = JSON.parse(readFileSync(join(PKG_ROOT, "package.json"), "utf8"));
|
|
42
42
|
|
|
43
|
-
const TOOLS = ["verify_stage", "dispatch_stage", "dispatch_verifier", "
|
|
43
|
+
const TOOLS = ["verify_stage", "dispatch_stage", "dispatch_verifier", "auto_advance_stage", "get_fallback_model"];
|
|
44
44
|
|
|
45
45
|
// Skill directories that used to ship a per-skill secrets.env under
|
|
46
46
|
// ${configDir}/skills/<skill>/. Credentials are now sourced only from
|
|
@@ -212,6 +212,32 @@ function doDoctor(flags: Flags): never {
|
|
|
212
212
|
problems++;
|
|
213
213
|
}
|
|
214
214
|
|
|
215
|
+
// opencode 는 모델 id 에 따라 **파일 쓰기 툴을 바꿔 끼운다** (1.18 ToolRegistry.tools):
|
|
216
|
+
// isGpt = modelID.includes("gpt-") && !includes("oss") && !includes("gpt-4")
|
|
217
|
+
// isGpt 이면 apply_patch 만 노출하고 write·edit 는 제거한다.
|
|
218
|
+
// 서브에이전트가 산출물을 만들지 못해 정지하는 사고(#8)의 근본 원인이므로
|
|
219
|
+
// 설정된 모델이 그 부류면 미리 알린다. 플러그인은 두 툴을 모두 지원한다.
|
|
220
|
+
const swapsWriteForApplyPatch = (modelID: string): boolean =>
|
|
221
|
+
modelID.includes("gpt-") && !modelID.includes("oss") && !modelID.includes("gpt-4");
|
|
222
|
+
const configuredModels: string[] = [];
|
|
223
|
+
if (cfgOk && cfgParsed?.agents && typeof cfgParsed.agents === "object") {
|
|
224
|
+
for (const name of Object.keys(cfgParsed.agents)) {
|
|
225
|
+
const m = cfgParsed.agents[name]?.model;
|
|
226
|
+
if (typeof m === "string" && m) configuredModels.push(m);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
if (cfgOk && Array.isArray(cfgParsed?.model_policy?.allowed_primaries)) {
|
|
230
|
+
for (const m of cfgParsed.model_policy.allowed_primaries) {
|
|
231
|
+
if (typeof m === "string" && m) configuredModels.push(m);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
const applyPatchModels = [...new Set(configuredModels.filter(swapsWriteForApplyPatch))];
|
|
235
|
+
if (applyPatchModels.length > 0) {
|
|
236
|
+
info(`note: opencode exposes 'apply_patch' instead of 'write'/'edit' for: ${applyPatchModels.join(", ")}`);
|
|
237
|
+
info(` sub-agents must create artifacts with apply_patch on those models — the plugin hook parses`);
|
|
238
|
+
info(` '*** Add File: <path>' out of the patch body and applies the same path rules as write.`);
|
|
239
|
+
}
|
|
240
|
+
|
|
215
241
|
// Detect legacy secrets.env residue. These files predate the makdoong2-team.json
|
|
216
242
|
// .secrets.* migration and can silently override the new source of truth
|
|
217
243
|
// if any run-*.sh regressed to sourcing them.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type Stage = "1_planning.jira" | "1_planning.requirements" | "
|
|
1
|
+
export type Stage = "1_planning.jira" | "1_planning.requirements" | "2_implementation.analysis" | "2_implementation.dev" | "2_implementation.test" | "3_delivery.commit" | "3_delivery.pr" | "3_delivery.review";
|
|
2
2
|
export interface PermissionRule {
|
|
3
3
|
bash: Record<string, "allow" | "deny" | "ask">;
|
|
4
4
|
}
|
|
@@ -70,16 +70,6 @@ export const AGENTS = {
|
|
|
70
70
|
tools: ["bash", "read", "grep", "glob", "write"],
|
|
71
71
|
skills: [],
|
|
72
72
|
},
|
|
73
|
-
// Research fan-out worker. Not bound to a substage — dispatch_research spawns
|
|
74
|
-
// one session per source in parallel, each loading exactly one research skill.
|
|
75
|
-
"makdoong2-researcher": {
|
|
76
|
-
id: "makdoong2-researcher",
|
|
77
|
-
stage: "all",
|
|
78
|
-
primary_only: false,
|
|
79
|
-
permissions: RO_PERM,
|
|
80
|
-
tools: ["bash", "read", "grep", "glob", "skill", "skill_mcp"],
|
|
81
|
-
skills: ["jira-research", "confluence-research", "bitbucket-research", "github-oss-research"],
|
|
82
|
-
},
|
|
83
73
|
"makdoong2-engineer": {
|
|
84
74
|
id: "makdoong2-engineer",
|
|
85
75
|
stage: "all",
|
|
@@ -108,7 +98,6 @@ export const AGENTS = {
|
|
|
108
98
|
export const STAGE_SPEC_FILES = {
|
|
109
99
|
"1_planning.jira": "01-planning.md",
|
|
110
100
|
"1_planning.requirements": "02-requirements.md",
|
|
111
|
-
"1_planning.scope": "03-scope.md",
|
|
112
101
|
"2_implementation.analysis": "04-analysis.md",
|
|
113
102
|
"2_implementation.dev": "05-worktree-dev.md",
|
|
114
103
|
"2_implementation.test": "06-test.md",
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* apply_patch 대상 경로 추출 — 순수 계약.
|
|
3
|
+
*
|
|
4
|
+
* ── 왜 필요한가 ──
|
|
5
|
+
* opencode 1.18 의 `ToolRegistry.tools` 는 모델 id 에 따라 **파일 쓰기 툴 자체를
|
|
6
|
+
* 바꿔 끼운다**. 1.18.23 바이너리에서 확인한 필터:
|
|
7
|
+
*
|
|
8
|
+
* const isGpt = modelID.includes("gpt-") && !modelID.includes("oss") && !modelID.includes("gpt-4")
|
|
9
|
+
* if (tool.id === apply_patch) return isGpt // gpt-5 계열에만 노출
|
|
10
|
+
* if (tool.id === edit || tool.id === write) return !isGpt // 그 외 모델에만 노출
|
|
11
|
+
*
|
|
12
|
+
* 즉 `github-copilot/gpt-5.6-luna` 같은 세션에는 `write` · `edit` 가 **툴 스키마에
|
|
13
|
+
* 아예 없고** `apply_patch` 하나만 있다. "산출물은 write 툴로 만들어라" 는 stage
|
|
14
|
+
* 지침은 그 세션에서 물리적으로 수행 불가능한 지시가 된다 (GitHub #8 재발).
|
|
15
|
+
*
|
|
16
|
+
* ── 왜 경로 파싱인가 ──
|
|
17
|
+
* `apply_patch` 의 인자는 `{ patchText }` 한 덩어리라 `filePath` 인자가 없다.
|
|
18
|
+
* 그래서 산출물 제한 훅은 대상 파일을 몰라 **보수적으로 전부 차단**했고, 그
|
|
19
|
+
* 결과 planner 에게는 초안을 만들 합법적 수단이 하나도 남지 않아 워크플로가
|
|
20
|
+
* 구조적으로 정지했다. 패치 본문에서 대상 경로를 뽑아내면 write 와 동일한
|
|
21
|
+
* 정밀도로 허용/차단을 판정할 수 있다 — 우회가 아니라 같은 규칙의 확장이다.
|
|
22
|
+
*
|
|
23
|
+
* 파싱은 opencode 의 `Patch.parsePatch` 를 그대로 모사한다 (Begin/End 마커,
|
|
24
|
+
* `*** Add|Delete|Update File:`, `*** Move to:`). 훅과 실제 적용기가 서로 다른
|
|
25
|
+
* 파일을 본다고 믿게 되면 방어가 무너지므로 관대하게 파싱하지 않는다.
|
|
26
|
+
*/
|
|
27
|
+
export type ApplyPatchPaths = {
|
|
28
|
+
readonly ok: true;
|
|
29
|
+
readonly paths: readonly string[];
|
|
30
|
+
} | {
|
|
31
|
+
readonly ok: false;
|
|
32
|
+
readonly reason: string;
|
|
33
|
+
};
|
|
34
|
+
/** opencode 가 apply_patch 로 노출하는 툴 이름들 (소문자 비교 기준). */
|
|
35
|
+
export declare const APPLY_PATCH_TOOLS: ReadonlySet<string>;
|
|
36
|
+
export declare const isApplyPatchTool: (toolName: string) => boolean;
|
|
37
|
+
/** 패치 본문에서 대상 경로를 모두 뽑는다. 형식 위반이면 실패로 보고한다. */
|
|
38
|
+
export declare const parsePatchTargets: (patchText: string) => ApplyPatchPaths;
|
|
39
|
+
/**
|
|
40
|
+
* apply_patch 툴 인자에서 대상 경로를 뽑는다.
|
|
41
|
+
*
|
|
42
|
+
* 두 가지 인자 모양을 모두 받는다:
|
|
43
|
+
* - opencode 내장 툴: `{ patchText }` (별칭 `patch`)
|
|
44
|
+
* - OpenAI provider tool 경유: `{ callId, operation: { type, path, move_path } }`
|
|
45
|
+
*/
|
|
46
|
+
export declare const extractApplyPatchPaths: (args: unknown) => ApplyPatchPaths;
|