makdoong2-team 2.3.2 → 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.
Files changed (42) hide show
  1. package/README.md +4 -12
  2. package/agents/makdoong2-engineer.md +1 -0
  3. package/agents/makdoong2-planner.md +17 -61
  4. package/agents/makdoong2-team-leader.md +14 -4
  5. package/agents/makdoong2-verifier.md +6 -6
  6. package/assets/makdoong2-team.schema.json +0 -18
  7. package/bin/cli.js +27 -1
  8. package/bin/cli.ts +27 -1
  9. package/dist/agent-stage-config.d.ts +1 -1
  10. package/dist/agent-stage-config.js +0 -11
  11. package/dist/apply-patch-paths.d.ts +46 -0
  12. package/dist/apply-patch-paths.js +114 -0
  13. package/dist/config.d.ts +21 -8
  14. package/dist/config.js +34 -0
  15. package/dist/model-fallback-policy.js +0 -4
  16. package/dist/opencode-plugin.js +121 -307
  17. package/dist/poll-sub-session.d.ts +2 -0
  18. package/dist/poll-sub-session.js +37 -9
  19. package/gates/stage-analysis-verify.sh +63 -5
  20. package/gates/stage4-dev-verify.sh +6 -6
  21. package/gates/verify.sh +0 -2
  22. package/opencode.json.example +0 -1
  23. package/package.json +1 -1
  24. package/scripts/gate-policy-test.sh +15 -14
  25. package/scripts/install-lib.mjs +1 -1
  26. package/scripts/install-lib.mts +1 -1
  27. package/scripts/model-policy.mjs +0 -4
  28. package/scripts/model-policy.mts +0 -4
  29. package/scripts/release.sh +13 -1
  30. package/scripts/run-tests.mts +3 -1
  31. package/scripts/state.sh +24 -2
  32. package/skills/_lib/load-secret.sh +15 -5
  33. package/src/hooks/session-start.sh +0 -1
  34. package/stages/01-planning.md +35 -54
  35. package/stages/02-requirements.md +53 -35
  36. package/stages/04-analysis.md +2 -2
  37. package/stages/05-worktree-dev.md +15 -0
  38. package/agents/makdoong2-researcher.md +0 -68
  39. package/dist/research-fanout.d.ts +0 -165
  40. package/dist/research-fanout.js +0 -341
  41. package/gates/stage3-scope-verify.sh +0 -69
  42. package/stages/03-scope.md +0 -81
@@ -1,4 +1,4 @@
1
- # 1–3단계 통합 Planning — `1_planning.{jira,requirements,scope}` 연속 처리
1
+ # 통합 Planning — `1_planning.{jira,requirements}` 연속 처리
2
2
 
3
3
  **목적**: Jira 조회 → 요구사항 확정 → 개발 범위 파악을 **단일 세션**에서 완료한다.
4
4
  **속도**: 3개 substage dispatch(모델 호출 3회)를 1회로 단축.
@@ -15,12 +15,10 @@
15
15
  ```bash
16
16
  bash <SCRIPTS_DIR>/state.sh get <이슈키> '.stages."1_planning".substages."jira".done'
17
17
  bash <SCRIPTS_DIR>/state.sh get <이슈키> '.stages."1_planning".substages."requirements".done'
18
- bash <SCRIPTS_DIR>/state.sh get <이슈키> '.stages."1_planning".substages."scope".done'
19
18
  ```
20
19
 
21
20
  - `jira.done=true` → Phase 1 건너뛰고 Phase 2로 직행
22
- - `jira.done=true` + `requirements.done=true` → Phase 3으로 직행
23
- - 셋 다 `true` → 모두 완료. 완료 메시지 출력 후 종료
21
+ - 둘 다 `true` → 모두 완료. 완료 메시지 출력 후 종료
24
22
 
25
23
  ---
26
24
 
@@ -89,7 +87,11 @@ mkdir -p .makdoong2-team/<이슈키>
89
87
 
90
88
  파일 (repo/worktree root 기준 상대경로): `.makdoong2-team/<이슈키>/requirements-draft.md`
91
89
 
92
- **초안 파일은 반드시 `write` 툴(filePath 인자)로 생성·갱신한다.** bash 리디렉션(`cat > …`, `printf > …` 등)과 `apply_patch` 는 같은 경로라도 훅이 차단한다 — planner 의 유일한 파일 쓰기 수단은 `write` 다. 이 경로의 `write` 는 planner READ-ONLY 원칙의 명시적 예외이며 훅이 허용한다 (issue #8).
90
+ **초안 파일은 반드시 쓰기 *툴* 로 생성·갱신한다 — bash 리디렉션(`cat > …`, `printf > …`, `tee`, `sed -i` 등)은 같은 경로라도 훅이 차단한다.**
91
+ - 세션에 `write` 툴이 있으면 `write(filePath=…)` 를 쓴다.
92
+ - **`write` 가 툴 목록에 없으면 `apply_patch` 를 쓴다.** opencode 는 모델에 따라 쓰기 툴을 바꿔 끼운다 — `gpt-5` 계열 세션에는 `write`·`edit` 가 아예 없고 `apply_patch` 만 있다. 이때 패치 본문은 `*** Begin Patch` / `*** Add File: .makdoong2-team/<이슈키>/requirements-draft.md` / `*** End Patch` 형식으로 정확히 작성한다 — 훅이 본문에서 대상 경로를 파싱해 허용 경로면 통과시킨다.
93
+ - **툴이 없다는 이유로 초안 생성을 포기하지 않는다.** 둘 중 실제로 존재하는 툴을 골라 즉시 실행하고, 차단 메시지를 받으면 그 메시지가 지시하는 형식으로 재시도한다.
94
+ - 이 경로의 파일 쓰기는 planner READ-ONLY 원칙의 명시적 예외이며 훅이 허용한다 (issue #8).
93
95
 
94
96
  초안 구조:
95
97
  ```markdown
@@ -104,36 +106,29 @@ mkdir -p .makdoong2-team/<이슈키>
104
106
  ```bash
105
107
  # state.json 에는 반드시 상대경로만 저장한다 (절대경로 저장 시 다른 cwd 에서 접근 불가 → Read hang 유발).
106
108
  # 이 마커는 §2-5b 의 spec_hash 와 **한 쌍**이다 — spec_hash 만 기록되고 이것이 빠지면
107
- # stage3-scope-verify.sh 가 1_planning.scope 진입을 하드 차단한다 (issue #6-①).
109
+ # stage-analysis-verify.sh 가 2_implementation.analysis 진입을 하드 차단한다 (issue #6-①).
108
110
  bash <SCRIPTS_DIR>/state.sh set <이슈키> \
109
111
  '.stages."1_planning".substages."requirements".draft_path' '".makdoong2-team/<이슈키>/requirements-draft.md"'
110
112
  ```
111
113
 
112
- ### 2-3. 다출처 교차 조사 (병렬)
114
+ ### 2-3. 다출처 교차 조사
113
115
 
114
- **`dispatch_research` 툴 1회 호출로 소스별 조사를 병렬 실행한다.** `skill_mcp` 를 직접 순차 호출하지 않는다 — 플러그인이 소스마다 별도 세션을 동시에 띄우므로 대기 시간이 가장 느린 소스 하나로 수렴하고, 각 소스의 원자료가 이 세션의 컨텍스트를 잠식하지 않는다. **outer-world 에이전트 위임 금지.**
116
+ **당신의 세션에서 직접 조사한다.** 소스마다 `skill(name=...)` 로 스킬을 먼저 로드한 뒤 그 스킬의 MCP 를 호출한다. 로드 전에 `skill_mcp` 를 부르면 `MCP server "<name>" not found` 로 실패한다. **outer-world 에이전트 위임 금지.**
115
117
 
116
- ```
117
- dispatch_research(
118
- issue = "<이슈키>",
119
- worktree = "<Working directory 절대경로>",
120
- context = "<Phase 1 에서 요약한 Jira 핵심 3~5줄>",
121
- queries = [
122
- {source: "jira", focus: "에픽/상위 이슈, 링크 이슈, 관련 코멘트에서 구체화된 요구"},
123
- {source: "confluence", focus: "관련 설계 문서, ADR, API 스펙, 운영 가이드"},
124
- {source: "bitbucket", focus: "수정 대상 파일/클래스 현재 구현, 관련 PR 이력, 테스트 패턴"}
125
- ]
126
- )
127
- ```
128
-
129
- 조사 세션은 서로를 보지 못한다. **한 focus 가 다른 조사 결과에 의존하면 안 된다** — 의존이 필요하면 라운드를 나눠 두 번 호출한다.
118
+ | 소스 | skill | mcp_name | 조사 범위 |
119
+ |---|---|---|---|
120
+ | Jira | `jira-research` | `works` | 에픽·상위 이슈·링크 이슈·서브태스크·코멘트에서 구체화된 요구와 결정 사항 |
121
+ | Confluence | `confluence-research` | `docs` | 설계 문서·ADR·API 스펙·운영 가이드의 제약과 합의된 규약 |
122
+ | Bitbucket | `bitbucket-research` | `repos` | 수정 대상 파일·클래스의 현재 구현, 관련 PR 이력, 기존 테스트 패턴 |
123
+ | GitHub OSS | `github-oss-research` | (없음 — WebFetch) | 외부 오픈소스의 사용 예시·알려진 이슈·업스트림 변경 |
130
124
 
131
- Simple 이슈는 조사 A + C만으로 축소 가능. 외부 라이브러리가 쟁점이면 `{source: "github-oss", ...}` 를 추가한다.
125
+ 기본 조사는 **Jira → Bitbucket** 두 소스다. 설계·규약이 쟁점이면 Confluence 를, 외부 라이브러리가 쟁점이면 GitHub OSS 를 더한다. Simple 이슈는 Jira 하나로 끝낼 수 있다.
132
126
 
133
- **결과 읽기**: 반환 JSON 의 `artifact_path` (`.makdoong2-team/<이슈키>/research-findings.json`) 를 Read 로 읽는다. `failed` 가 있어도 **부분 성공이 정상**이므로 나머지 결과로 진행하고, 실패 소스가 요구사항 확정에 필수인 경우에만 사유를 사용자에게 보고한다. 전 소스 실패(`ok: false`)면 추측으로 채우지 말고 보고한다.
127
+ **조사 예산은 소스당 최대 5회 호출이다.** 초과하면 그 소스는 거기서 멈추고 미확인 항목을 `미결 사항` 에 적는다.
134
128
 
135
- - **`status: "partial"` 은 그 자체로 정상 종료다.** 실패한 소스를 당신이 직접 조사해 메우려 하지 말 것 — `skill_mcp` 순차 호출로 결손을 메우려다 세션 예산을 전부 소진하고 **마커를 하나도 남기지 못한 채** 종료한 사고가 이틀 연속 재현됐다 (GitHub issue #9, 각 27분·17분 소모). 결손을 더 좁히고 싶으면 focus 를 좁혀 `dispatch_research` 를 **1회만** 다시 호출한다.
136
- - **조사 완결성을 이유로 마커 기록을 미루지 않는다 (hardrule).** 조사가 부분적이면 `gaps` 에 미확인 항목을 남기고 그 상태 그대로 산출물과 substage 마커를 기록한 뒤 종료한다. 마커가 없는 종료는 상위에서 `completion: "incomplete"` 로 분류되어 substage 전체가 재실행된다 — 부분 결과까지 함께 버려진다.
129
+ - **부분 조사는 그 자체로 정상 종료다.** 한 소스가 응답하지 않으면 그 소스를 포기하고 나머지로 진행한다. 결손을 메우려 같은 소스를 계속 두드리다 세션 예산을 전부 소진하고 **마커를 하나도 남기지 못한 채** 종료한 사고가 이틀 연속 재현됐다 (GitHub issue #9, 각 27분·17분 소모).
130
+ - **조사 완결성을 이유로 마커 기록을 미루지 않는다 (hardrule).** 조사가 부분적이면 미확인 항목을 초안의 `미결 사항` 에 남기고 **그 상태 그대로** 산출물과 substage 마커를 기록한 뒤 종료한다. 마커가 없는 종료는 상위에서 `completion: "incomplete"` 로 분류되어 substage 전체가 재실행된다 — 부분 결과까지 함께 버려진다.
131
+ - 조사 결과는 별도 파일로 만들지 않는다. `requirements-draft.md` 의 `## 수집된 정보` 에 소스별로 정리한다 (출처 URL 포함).
137
132
 
138
133
  ### 2-4. 요구사항 체크리스트 확인
139
134
 
@@ -176,7 +171,7 @@ category = (criticality == "critical" OR scope_size == "large") ? "major" : base
176
171
 
177
172
  ```bash
178
173
  bash <SCRIPTS_DIR>/state.sh set <이슈키> '.policy' \
179
- '{"intent_type":"Standard","change_type":"bugfix","scope_size":"small","criticality":"normal","category":"minor","auto_approve":{"1_planning.requirements":true,"1_planning.scope":true,"3_delivery.commit":true,"3_delivery.pr":true},"rationale":"<한 줄 근거>","categorized_by":"1_planning.requirements"}'
174
+ '{"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":"<한 줄 근거>","categorized_by":"1_planning.requirements"}'
180
175
  bash <SCRIPTS_DIR>/state.sh set <이슈키> '.policy.categorized_at' "\"$(date -u +%Y-%m-%dT%H:%M:%SZ)\""
181
176
  ```
182
177
 
@@ -184,7 +179,7 @@ major 로 판정된 경우에도 `auto_approve` 맵은 **모두 true** 로 두
184
179
 
185
180
  ### 2-5b. 요구사항 품질 마커 (ambiguity_score · spec_hash)
186
181
 
187
- `stage3-scope-verify.sh` 의 요구사항 품질 게이트는 **이 두 마커가 있을 때만** 검사한다
182
+ `stage-analysis-verify.sh` 의 요구사항 품질 게이트는 **이 두 마커가 있을 때만** 검사한다
188
183
  (구형 state 호환을 위한 조건부 검사). 즉 여기서 기록하지 않으면 통합 경로에서는
189
184
  품질 게이트가 통째로 사문화된다 — `02-requirements.md` 를 거치는 분리 경로에서는
190
185
  검사되는데 이 경로에서만 안 되는 비대칭이 생긴다. 반드시 기록한다.
@@ -206,23 +201,9 @@ bash <SCRIPTS_DIR>/state.sh get <이슈키> '.stages."1_planning".substages."req
206
201
  bash <SCRIPTS_DIR>/state.sh get <이슈키> '.stages."1_planning".substages."requirements".spec_hash'
207
202
  ```
208
203
 
209
- ### 2-6. Requirements 완료 기록
204
+ ### 2-5c. 개발 범위 확정 (구 `1_planning.scope` — 흡수됨)
210
205
 
211
- ```bash
212
- bash <SCRIPTS_DIR>/state.sh set <이슈키> '.stages."1_planning".substages."requirements".self_check' \
213
- '{"checklist_complete":true,"conflicts_resolved":true,"user_confirmed":true,"scope_clean":true,"draft_synced":true,"categorized":true,"ambiguity_scored":true,"spec_frozen":true,"draft_recorded":true}'
214
- bash <SCRIPTS_DIR>/state.sh set <이슈키> '.stages."1_planning".substages."requirements".verification_pending' 'false'
215
- bash <SCRIPTS_DIR>/state.sh set <이슈키> '.stages."1_planning".substages."requirements".done' 'true'
216
- bash <SCRIPTS_DIR>/state.sh set <이슈키> '.stages."1_planning".substages."requirements".done_at' "\"$(date -u +%Y-%m-%dT%H:%M:%SZ)\""
217
- ```
218
-
219
- ---
220
-
221
- ## Phase 3: 개발 범위 파악 (`1_planning.scope`)
222
-
223
- 2단계 조사 결과로 코드 수정 계획을 수립한다. 2단계 `bitbucket-research` 탐색을 이어서 사용한다.
224
-
225
- ### 3-1. 범위 출력
206
+ 범위 확정은 요구사항 확정과 **같은 판단의 연속**이라 별도 substage 를 두지 않는다. 조사 결과로 코드 수정 계획을 여기서 함께 확정한다. §2-3 의 `bitbucket-research` 탐색을 이어서 사용한다.
226
207
 
227
208
  ```
228
209
  ### 개발 범위
@@ -234,27 +215,27 @@ bash <SCRIPTS_DIR>/state.sh set <이슈키> '.stages."1_planning".substages."req
234
215
  **스코프 아웃**: <이번 이슈에서 다루지 않는 것>
235
216
  ```
236
217
 
237
- ### 3-2. 범주 재평가 (escalation — 하향 금지)
218
+ 이 블록은 `requirements-draft.md` 에도 그대로 남긴다 — dev 단계가 초안을 읽고 작업 단위를 잡는다.
238
219
 
239
- 실제 수정/추가 파일과 작업 단위가 확정된 뒤, `scope_size`·`criticality`를 재평가한다.
220
+ **범주 재평가 (escalation — 하향 금지)**: 실제 수정/추가 파일과 작업 단위가 확정된 뒤 `scope_size`·`criticality` 를 재평가한다. minor → major 상향인 경우에만 갱신하며, `auto_approve` 맵은 건드리지 않고 **모두 true 로 유지**한다 — 상향은 위험도 라벨 정정에 그치고 흐름은 무인 진행을 유지한다:
240
221
 
241
- minor → major로 상향될 경우에만 아래 항목들을 갱신한다. `auto_approve` 맵은 건드리지 않고 **모두 true 로 유지**한다 — 상향은 위험도 라벨 정정에 그치며 흐름 자체는 무인 진행으로 유지된다:
242
222
  ```bash
243
223
  bash <SCRIPTS_DIR>/state.sh set <이슈키> '.policy.category' '"major"'
244
224
  bash <SCRIPTS_DIR>/state.sh set <이슈키> '.policy.scope_size' '"large"'
245
- bash <SCRIPTS_DIR>/state.sh set <이슈키> '.policy.categorized_by' '"1_planning.scope"'
225
+ bash <SCRIPTS_DIR>/state.sh set <이슈키> '.policy.categorized_by' '"1_planning.requirements"'
246
226
  ```
247
227
 
248
- ### 3-3. Scope 완료 기록
228
+ ### 2-6. Requirements 완료 기록
249
229
 
250
230
  ```bash
251
- bash <SCRIPTS_DIR>/state.sh set <이슈키> '.stages."1_planning".substages."scope".self_check' \
252
- '{"paths_explicit":true,"test_scope_defined":true,"atomic_units":true,"scope_out_listed":true,"user_approved":true}'
253
- bash <SCRIPTS_DIR>/state.sh set <이슈키> '.stages."1_planning".substages."scope".verification_pending' 'false'
254
- bash <SCRIPTS_DIR>/state.sh set <이슈키> '.stages."1_planning".substages."scope".done' 'true'
255
- bash <SCRIPTS_DIR>/state.sh set <이슈키> '.stages."1_planning".substages."scope".done_at' "\"$(date -u +%Y-%m-%dT%H:%M:%SZ)\""
231
+ bash <SCRIPTS_DIR>/state.sh set <이슈키> '.stages."1_planning".substages."requirements".self_check' \
232
+ '{"checklist_complete":true,"conflicts_resolved":true,"user_confirmed":true,"scope_clean":true,"draft_synced":true,"categorized":true,"ambiguity_scored":true,"spec_frozen":true,"draft_recorded":true,"paths_explicit":true,"test_scope_defined":true,"atomic_units":true,"scope_out_listed":true}'
233
+ bash <SCRIPTS_DIR>/state.sh set <이슈키> '.stages."1_planning".substages."requirements".verification_pending' 'false'
234
+ bash <SCRIPTS_DIR>/state.sh set <이슈키> '.stages."1_planning".substages."requirements".done' 'true'
235
+ bash <SCRIPTS_DIR>/state.sh set <이슈키> '.stages."1_planning".substages."requirements".done_at' "\"$(date -u +%Y-%m-%dT%H:%M:%SZ)\""
256
236
  ```
257
237
 
238
+
258
239
  ---
259
240
 
260
241
  ## 최종 출력
@@ -62,7 +62,11 @@ mkdir -p .makdoong2-team/<ISSUE_KEY>
62
62
 
63
63
  파일 경로 (repo/worktree root 기준 상대경로): `.makdoong2-team/<ISSUE_KEY>/requirements-draft.md`
64
64
 
65
- **초안 파일은 반드시 `write` 툴(filePath 인자)로 생성·갱신한다.** bash 리디렉션(`cat > …`, `printf > …` 등)과 `apply_patch` 는 같은 경로라도 훅이 차단한다 — planner 의 유일한 파일 쓰기 수단은 `write` 다. 이 경로의 `write` 는 planner READ-ONLY 원칙의 명시적 예외이며 훅이 허용한다 (issue #8).
65
+ **초안 파일은 반드시 쓰기 *툴* 로 생성·갱신한다 — bash 리디렉션(`cat > …`, `printf > …`, `tee`, `sed -i` 등)은 같은 경로라도 훅이 차단한다.**
66
+ - 세션에 `write` 툴이 있으면 `write(filePath=…)` 를 쓴다.
67
+ - **`write` 가 툴 목록에 없으면 `apply_patch` 를 쓴다.** opencode 는 모델에 따라 쓰기 툴을 바꿔 끼운다 — `gpt-5` 계열 세션에는 `write`·`edit` 가 아예 없고 `apply_patch` 만 있다. 이때 패치 본문은 `*** Begin Patch` / `*** Add File: .makdoong2-team/<이슈키>/requirements-draft.md` / `*** End Patch` 형식으로 정확히 작성한다 — 훅이 본문에서 대상 경로를 파싱해 허용 경로면 통과시킨다.
68
+ - **툴이 없다는 이유로 초안 생성을 포기하지 않는다.** 둘 중 실제로 존재하는 툴을 골라 즉시 실행하고, 차단 메시지를 받으면 그 메시지가 지시하는 형식으로 재시도한다.
69
+ - 이 경로의 파일 쓰기는 planner READ-ONLY 원칙의 명시적 예외이며 훅이 허용한다 (issue #8).
66
70
 
67
71
  초안 초기 구조:
68
72
  ```markdown
@@ -90,42 +94,30 @@ bash <SCRIPTS_DIR>/state.sh set <이슈키> \
90
94
 
91
95
  ## 2-1. 다출처 교차 조사
92
96
 
93
- Jira 본문만 보고 판단하지 않는다. 세 출처를 **모두 교차 검증**한다.
97
+ Jira 본문만 보고 판단하지 않는다. 아래 출처를 **교차 검증**한다.
94
98
 
95
- **`dispatch_research` 툴 1회 호출로 소스별 조사를 병렬 실행한다.** 스스로 `skill_mcp` 를 순차 호출하지 않는다 — 플러그인이 소스마다 별도 세션을 동시에 띄우므로 (a) 대기 시간이 가장 느린 소스 하나로 수렴하고, (b) 각 소스의 원자료가 당신의 컨텍스트를 잠식하지 않는다. **outer-world 에이전트 위임(Sisyphus/Explore/Librarian, `task(subagent_type=...)`) 금지** — planner 에는 `Task` 툴이 없어 물리적으로도 불가하다.
99
+ **당신의 세션에서 직접 조사한다.** 소스마다 `skill(name=...)` 로 스킬을 먼저 로드한 뒤 그 스킬의 MCP 를 호출한다 — 로드 전에 `skill_mcp` 를 부르면 `MCP server "<name>" not found` 로 실패한다. **outer-world 에이전트 위임(Sisyphus/Explore/Librarian, `task(subagent_type=...)`) 금지** — planner 에는 `Task` 툴이 없어 물리적으로도 불가하다.
96
100
 
97
- ```
98
- dispatch_research(
99
- issue = "<이슈키>",
100
- worktree = "<Working directory 절대경로>",
101
- context = "<Jira 요약 3~5줄 — 모든 조사 세션에 공통 주입>",
102
- queries = [
103
- {source: "jira", focus: "에픽/상위 이슈, 링크 이슈(blocks/relates/causes), 같은 컴포넌트·라벨의 최근 해결 이슈, 코멘트에서 명확해진 요구"},
104
- {source: "confluence", focus: "<시스템·모듈명> 설계 문서, 아키텍처/API 스펙/운영 가이드/회의록, ADR·기술선택 기록"},
105
- {source: "bitbucket", focus: "<수정 대상 추정 파일/클래스>의 현재 구현, 유사 기능의 과거 구현, 관련 영역 최근 PR 의 변경 패턴·테스트 방식·리뷰 지적"}
106
- ]
107
- )
108
- ```
109
-
110
- - **조사 A — Jira 맥락 심화** (`source: "jira"`)
111
- - **조사 B — 설계 문서** (`source: "confluence"`). 키워드는 description 명사구·시스템명·프로토콜 번호.
112
- - **조사 C — 기존 코드·PR 이력** (`source: "bitbucket"`)
113
- - **(필요 시) 조사 D — 오픈소스** (`source: "github-oss"`): 외부 라이브러리 공식 예제·이슈 트래커, 버전 호환성·알려진 버그.
101
+ | 조사 | 소스 | skill | mcp_name | 무엇을 찾나 |
102
+ |---|---|---|---|---|
103
+ | A | Jira | `jira-research` | `works` | 에픽/상위 이슈, 링크 이슈(blocks/relates/causes), 같은 컴포넌트·라벨의 최근 해결 이슈, 코멘트에서 명확해진 요구 |
104
+ | B | Confluence | `confluence-research` | `docs` | `<시스템·모듈명>` 설계 문서, 아키텍처/API 스펙/운영 가이드/회의록, ADR·기술선택 기록 |
105
+ | C | Bitbucket | `bitbucket-research` | `repos` | `<수정 대상 추정 파일/클래스>` 의 현재 구현, 유사 기능의 과거 구현, 관련 영역 최근 PR 의 변경 패턴·테스트 방식·리뷰 지적 |
106
+ | D | GitHub OSS | `github-oss-research` | (없음 — WebFetch) | 외부 라이브러리 공식 예제·이슈 트래커, 버전 호환성·알려진 버그 |
114
107
 
115
- `focus` 는 구체적일수록 좋다. 조사 세션은 서로를 보지 못하므로 **한 focus 가 다른 조사 결과에 의존하면 안 된다.** 의존이 필요하면 라운드를 나눠 두 번 호출한다.
108
+ 조사 A/C 는 항상 수행한다. B 는 설계·규약이 쟁점일 때, D 는 외부 라이브러리가 쟁점일 때 더한다. Simple 유형은 A 만으로 끝낼 수 있다.
116
109
 
117
- 조사 A/B/C 는 이슈 유형과 무관하게 모두 시도한다 (Simple 유형만 A/C 로 축소 가능).
110
+ **조사 예산은 소스당 최대 5회 호출이다.** 초과하면 그 소스는 거기서 멈추고 미확인 항목을 초안의 `미결 사항` 에 적는다.
118
111
 
119
- ### 결과 읽기
112
+ ### 결과 정리
120
113
 
121
- 반환 JSON 의 `artifact_path` (기본 `.makdoong2-team/<이슈키>/research-findings.json`) 를 Read 로 읽어 종합한다.
114
+ 조사 결과는 **별도 파일로 만들지 않는다.** `requirements-draft.md` 의 `## 수집된 정보` 에 소스별로 정리하고, 근거가 있는 항목에만 출처 URL 을 단다. 확인하지 못한 것은 `## 미결 사항` 으로 보낸다 — 추측으로 채우지 않는다.
122
115
 
123
- - **부분 성공이 정상이다.** `failed` 배열에 실패 소스와 사유가 담긴다. 남은 소스의 결과는 그대로 유효하므로 실패 1건으로 조사를 통째로 다시 돌리지 않는다.
124
- - 실패한 소스가 **요구사항 확정에 필수**라면 그 사유(인증 실패·권한 부족 등)를 사용자에게 보고한다. 없어도 되는 소스면 `gaps` 로만 남기고 진행한다.
125
- - `deferred` 가 비어 있지 않으면 병렬 상한에 걸려 빠진 조사가 있다는 뜻이다. 필요하면 2차 호출한다.
126
- - 모든 소스가 실패하면(`ok: false`) 체크리스트를 추측으로 채우지 말고 사용자에게 보고한다.
127
- - **`status: "partial"` 은 그 자체로 정상 종료다.** 실패한 소스를 당신이 직접 조사해 메우려 하지 말 것 — `skill_mcp` 순차 호출로 결손을 메우려다 세션 예산을 전부 소진하고 **마커를 하나도 남기지 못한 채** 종료한 사고가 이틀 연속 재현됐다 (GitHub issue #9, 각 27분·17분 소모). 결손을 더 좁히고 싶으면 focus 를 좁혀 `dispatch_research` 를 **1회만** 다시 호출한다.
128
- - **조사 완결성을 이유로 마커 기록을 미루지 않는다 (hardrule).** 조사가 부분적이면 `gaps` 에 미확인 항목을 남기고 그 상태 그대로 산출물과 substage 마커를 기록한 뒤 종료한다. 마커가 없는 종료는 상위에서 `completion: "incomplete"` 로 분류되어 substage 전체가 재실행된다 — 부분 결과까지 함께 버려진다.
116
+ - **부분 조사가 정상 종료다.** 한 소스가 응답하지 않으면 그 소스를 포기하고 나머지로 진행한다. 실패 1건으로 조사를 통째로 다시 돌리지 않는다.
117
+ - 실패한 소스가 **요구사항 확정에 필수**라면 그 사유(인증 실패·권한 부족 등)를 사용자에게 보고한다. 없어도 되는 소스면 `미결 사항` 으로만 남기고 진행한다.
118
+ - 모든 소스가 실패하면 체크리스트를 추측으로 채우지 말고 사용자에게 보고한다.
119
+ - **결손을 메우려 같은 소스를 계속 두드리지 말 것.** 그러다 세션 예산을 전부 소진하고 **마커를 하나도 남기지 못한 채** 종료한 사고가 이틀 연속 재현됐다 (GitHub issue #9, 각 27분·17분 소모).
120
+ - **조사 완결성을 이유로 마커 기록을 미루지 않는다 (hardrule).** 조사가 부분적이면 미결 항목을 남기고 **그 상태 그대로** 산출물과 substage 마커를 기록한 뒤 종료한다. 마커가 없는 종료는 상위에서 `completion: "incomplete"` 로 분류되어 substage 전체가 재실행된다 — 부분 결과까지 함께 버려진다.
129
121
 
130
122
  ## 2-2. 요구사항 체크리스트
131
123
 
@@ -238,7 +230,7 @@ bash <SCRIPTS_DIR>/state.sh set <이슈키> '.stages."1_planning".substages."req
238
230
  bash <SCRIPTS_DIR>/state.sh set <이슈키> '.stages."1_planning".substages."requirements".spec_hash' \
239
231
  "\"$(sha256sum .makdoong2-team/<이슈키>/requirements-draft.md | cut -d' ' -f1)\""
240
232
  ```
241
- 3. **동결 후 변경 절차**: `done` 이후 요구사항 변경이 필요해지면 파일을 몰래 수정하지 않는다. 부장님에게 에스컬레이션 → 사용자 재승인 → requirements substage 재작업(명세 갱신 + `spec_hash` 재기록) 순서만 허용된다. `stage3-scope-verify.sh` 진입 게이트가 해시를 재계산해 무단 변경(spec drift)을 차단한다.
233
+ 3. **동결 후 변경 절차**: `done` 이후 요구사항 변경이 필요해지면 파일을 몰래 수정하지 않는다. 부장님에게 에스컬레이션 → 사용자 재승인 → requirements substage 재작업(명세 갱신 + `spec_hash` 재기록) 순서만 허용된다. `stage-analysis-verify.sh` 진입 게이트가 해시를 재계산해 무단 변경(spec drift)을 차단한다.
242
234
 
243
235
  ## 2-4b. 작업 범주화 (minor / major) — auto-approve 정책 결정
244
236
 
@@ -252,7 +244,7 @@ bash <SCRIPTS_DIR>/state.sh set <이슈키> '.stages."1_planning".substages."req
252
244
  | `scope_size` | `small` / `large` | 수정 파일 수·작업 단위·영향 모듈. 단일~소수 파일·국소 변경 = small |
253
245
  | `criticality` | `normal` / `critical` | 인증·결제·보안·데이터 무결성·마이그레이션·대외 API 등 실패 시 파급이 큰 영역 = critical |
254
246
 
255
- > `scope_size`는 2단계 시점엔 추정치다 — 3단계(범위 확정)에서 실제 변경 단위가 드러나면 minor→major로 **상향 조정(escalation)** 될 수 있다(하향은 금지). `03-scope.md` 참조.
247
+ > `scope_size`는 이 시점엔 추정치다 — §2-6(개발 범위 확정)에서 실제 변경 단위가 드러나면 minor→major로 **상향 조정(escalation)** 될 수 있다(하향은 금지).
256
248
 
257
249
  ### 범주 도출 규칙 (결정론)
258
250
 
@@ -276,7 +268,7 @@ category = (criticality == "critical" OR scope_size == "large") ? "major" : base
276
268
  ### 기록 (필수 — done 직전)
277
269
 
278
270
  ```bash
279
- bash <SCRIPTS_DIR>/state.sh set <이슈키> '.policy' '{"intent_type":"Standard","change_type":"bugfix","scope_size":"small","criticality":"normal","category":"minor","auto_approve":{"1_planning.requirements":true,"1_planning.scope":true,"3_delivery.commit":true,"3_delivery.pr":true},"rationale":"<한 줄 근거 — 왜 이 범주인지>","categorized_by":"1_planning.requirements"}'
271
+ bash <SCRIPTS_DIR>/state.sh set <이슈키> '.policy' '{"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":"<한 줄 근거 — 왜 이 범주인지>","categorized_by":"1_planning.requirements"}'
280
272
  bash <SCRIPTS_DIR>/state.sh set <이슈키> '.policy.categorized_at' "\"$(date -u +%Y-%m-%dT%H:%M:%SZ)\""
281
273
  ```
282
274
 
@@ -297,7 +289,7 @@ major 로 판정되어도 `auto_approve` 맵은 **모두 true** 로 두고 `"cat
297
289
  | 6 | 작업 범주화(2-4b)가 끝나 `.policy.category`(minor\|major)와 `auto_approve` 맵이 기록되었다 |
298
290
  | 7 | `ambiguity_score`가 산정·기록되었고 최종값 ≤ 0.2 이다 (2-3-2b) |
299
291
  | 8 | 확정 명세가 동결되어 `spec_hash`가 기록되었다 (2-4a) |
300
- | 9 | `draft_path` 마커가 state.json에 기록되었다 (2-0). **`spec_hash`와 한 쌍이다** — `stage3-scope-verify.sh`가 `spec_hash`만 있고 `draft_path`가 없으면 `1_planning.scope` 진입을 하드 차단한다 |
292
+ | 9 | `draft_path` 마커가 state.json에 기록되었다 (2-0). **`spec_hash`와 한 쌍이다** — `stage-analysis-verify.sh`가 `spec_hash`만 있고 `draft_path`가 없으면 `2_implementation.analysis` 진입을 하드 차단한다 |
301
293
 
302
294
  **9번은 자기선언이 아니라 실제 값을 읽어 확인한다** — `draft_synced`(파일 동기화)가 true 여도 마커는 빠질 수 있다. 실제로 `spec_hash`만 기록되고 `draft_path`가 누락된 채 8항목 전부 true 로 종료되어, 다음 게이트에서 워크플로우가 정지한 사례가 있다 (issue #6-①).
303
295
 
@@ -320,7 +312,7 @@ bash <SCRIPTS_DIR>/state.sh set <이슈키> '.stages."1_planning".substages."req
320
312
 
321
313
  **승인 경로** — 2-4b의 `.policy.auto_approve."1_planning.requirements"`를 따른다:
322
314
 
323
- - **auto_approve == true** (정상 — minor/major 공통): 사람 대기 없이 자동 진행. `verification_pending`을 즉시 `false`로 둔다. 게이트(`stage3-scope-verify.sh`)가 정책을 보고 사용자 승인 없이 통과시킨다.
315
+ - **auto_approve == true** (정상 — minor/major 공통): 사람 대기 없이 자동 진행. `verification_pending`을 즉시 `false`로 둔다. 게이트(`stage-analysis-verify.sh`)가 정책을 보고 사용자 승인 없이 통과시킨다.
324
316
  ```bash
325
317
  bash <SCRIPTS_DIR>/state.sh set <이슈키> '.stages."1_planning".substages."requirements".verification_pending' 'false'
326
318
  ```
@@ -334,3 +326,29 @@ bash <SCRIPTS_DIR>/state.sh set <이슈키> '.stages."1_planning".substages."req
334
326
  bash <SCRIPTS_DIR>/state.sh set <이슈키> '.stages."1_planning".substages."requirements".verification_pending' 'false'
335
327
  ```
336
328
 
329
+
330
+ ---
331
+
332
+ ## 2-6. 개발 범위 확정 (구 `1_planning.scope` — 흡수됨)
333
+
334
+ 범위 확정은 요구사항 확정과 **같은 판단의 연속**이라 별도 substage 를 두지 않는다. §2-1 조사 C(Bitbucket) 탐색을 이어서 사용해 코드 수정 계획을 여기서 함께 확정하고, 아래 블록을 `requirements-draft.md` 에도 남긴다 — dev 단계가 초안을 읽고 작업 단위를 잡는다.
335
+
336
+ ```
337
+ ### 개발 범위
338
+ **수정 파일**: <path>: <변경 요지>
339
+ **추가 파일**: <path>: <목적>
340
+ **테스트 범위**: 단위(<대상 클래스/메서드>), 통합(<빌드 플랜명/시나리오>)
341
+ **영향 범위**: <모듈>: <영향 요지>
342
+ **예상 작업 단위(커밋 후보)**: 1. <단위1> 2. <단위2>
343
+ **스코프 아웃**: <이번 이슈에서 다루지 않는 것>
344
+ ```
345
+
346
+ **범주 재평가 (escalation — 하향 금지)**: 실제 수정/추가 파일과 작업 단위가 확정된 뒤 `scope_size`·`criticality` 를 재평가한다. minor → major **상향만** 허용한다. `auto_approve` 맵은 건드리지 않고 모두 true 로 유지한다 — 상향은 위험도 라벨 정정에 그치고 흐름은 무인 진행을 유지한다.
347
+
348
+ ```bash
349
+ bash <SCRIPTS_DIR>/state.sh set <이슈키> '.policy.category' '"major"'
350
+ bash <SCRIPTS_DIR>/state.sh set <이슈키> '.policy.scope_size' '"large"'
351
+ bash <SCRIPTS_DIR>/state.sh set <이슈키> '.policy.categorized_by' '"1_planning.requirements"'
352
+ ```
353
+
354
+ self_check 에 `paths_explicit` / `test_scope_defined` / `atomic_units` / `scope_out_listed` 4항목을 함께 기록한다.
@@ -2,7 +2,7 @@
2
2
 
3
3
  **목적**: 개발 진입 전에 workspace 구조·의존성·관례·통합 지점을 결정론적으로 분석하고, 그 결과를 고정 JSON schema로 산출한다. 로컬 LLM 계열이 분석을 건너뛰고 코드 생성으로 직행하는 문제를 harness 레벨에서 차단하는 phase gating 이다.
4
4
 
5
- **진입 게이트**: `verify.sh <이슈키> 2_implementation.analysis` (3단계 scope 완료 필요).
5
+ **진입 게이트**: `verify.sh <이슈키> 2_implementation.analysis` (`1_planning.requirements` 완료 + 승인 + 요구사항 품질 게이트 통과 필요).
6
6
 
7
7
  > 게이트가 build tool 마커 파일 부재를 감지하면 자동 스킵 처리한다 (`skipped=true`, `done=true` 마킹 후 dispatch 없이 dev substage 로 진행). 본 명세는 게이트를 통과하여 dispatch 된 경우에만 실행된다.
8
8
 
@@ -84,7 +84,7 @@ Build tool 별 대응:
84
84
  3단계 scope substage 에서 확정한 수정 대상 파일 목록을 state.json 에서 조회한다.
85
85
 
86
86
  ```bash
87
- bash <SCRIPTS_DIR>/state.sh get <이슈키> '.stages."1_planning".substages."scope"' 2>/dev/null
87
+ bash <SCRIPTS_DIR>/state.sh get <이슈키> '.stages."1_planning".substages."requirements"' 2>/dev/null
88
88
  ```
89
89
 
90
90
  scope 결과의 각 파일에 대해 실제 파일을 read + grep 하여:
@@ -6,6 +6,21 @@
6
6
 
7
7
  > `<SCRIPTS_DIR>`는 부장님이 dispatch_stage 프롬프트로 주입한 절대경로다. 이 값을 그대로 대입하여 실행한다.
8
8
 
9
+ ## 4-0-pre. 임시 파일 경로 (hardrule)
10
+
11
+ 임시 파일·스크래치가 필요하면 **worktree 안**에만 만든다:
12
+
13
+ ```
14
+ <worktree>/.makdoong2-team/<이슈키>/tmp/
15
+ ```
16
+
17
+ **`/tmp` 을 비롯한 워크스페이스 밖 경로는 사용 금지다.** 두 가지 이유다:
18
+
19
+ 1. opencode 는 bash 명령이 참조하는 디렉토리마다 `external_directory` 승인을 묻는다. 서브에이전트 세션에서 그 요청은 답할 사람이 없어 **자동 거부되고 세션이 그 자리에서 종료된다** — 하던 작업이 통째로 날아간다.
20
+ 2. 워크스페이스 밖에 쓴 것은 worktree 동기화 대상도 커밋 대상도 아니다. **산출물이 조용히 사라진다.**
21
+
22
+ 위 경로는 cwd 안이라 승인이 필요 없고, `.git/info/exclude` 에 이미 등록돼 있어 `git status` 를 오염시키지 않는다.
23
+
9
24
  ## 4-0. Analysis 결과 읽기 (필수 — 개발 시작 전)
10
25
 
11
26
  analysis substage 가 산출한 `workspace-analysis.json` 을 읽어 구현 방향을 확정한다. **이 단계를 건너뛰면 기존 코드 패턴과 불일치한 구현이 발생할 수 있으므로 반드시 수행한다.**
@@ -1,68 +0,0 @@
1
- ---
2
- name: makdoong2-researcher
3
- description: workflow research fan-out worker — 단일 소스(Jira / Confluence / Bitbucket / GitHub OSS) 만 읽기 전용 조사하고 고정 스키마 JSON 을 반환한다. dispatch_research 툴이 소스별로 병렬 spawn 한다. 직접 호출하지 않는다.
4
- temperature: 0.1
5
- mode: subagent
6
- tools:
7
- Read: true
8
- Bash: true
9
- Grep: true
10
- Glob: true
11
- skill: true
12
- skill_mcp: true
13
- Write: false
14
- Edit: false
15
- Patch: false
16
- MultiEdit: false
17
- permission:
18
- bash:
19
- "*": "allow"
20
- "git commit*": "deny"
21
- "git push*": "deny"
22
- "git add*": "deny"
23
- "git rm*": "deny"
24
- "git reset --hard*": "deny"
25
- "git branch -D*": "deny"
26
- "git worktree add*": "deny"
27
- "git worktree remove*": "deny"
28
- "rm -rf*": "deny"
29
- # 정식 키는 `edit`. researcher 는 파일을 쓰지 않는다 — 결과는 응답 텍스트로 반환한다.
30
- edit:
31
- "**/*": "deny"
32
- ---
33
-
34
- 당신은 **리서치 막둥이**다. 배정받은 **소스 한 곳만** 조사하고 고정 스키마 JSON 을 반환한다.
35
-
36
- `dispatch_research` 툴이 소스마다 별도 세션을 병렬로 띄운다. 당신의 세션에는 당신이 맡은 소스의 자료만 쌓인다 — 이것이 fan-out 의 목적이므로 **다른 소스를 기웃거리지 않는다.**
37
-
38
- ## 하드룰
39
-
40
- 1. **읽기 전용.** 파일 생성·수정 불가 (Write/Edit 프론트매터 차단). state.json 도 건드리지 않는다 — 결과 저장은 플러그인이 한다.
41
- 2. **배정된 소스만.** 프롬프트의 `Research source` 에 적힌 소스 외의 skill 을 로드하지 않는다.
42
- 3. **skill 먼저, MCP 나중.** `skill(name=...)` 로 스킬을 로드하기 전에 `skill_mcp` 를 부르면 `MCP server "<name>" not found` 로 실패한다. 순서를 지킨다.
43
- 4. **추측 금지.** 확인하지 못한 것은 `findings` 에 넣지 않고 `gaps` 에 적는다. `url` 은 실제 출처가 있을 때만 넣는다.
44
- 5. **outer-world 에이전트 위임 금지.** Task 툴이 프론트매터에서 제거되어 물리적으로 불가하다.
45
-
46
- ## 실행 규약
47
-
48
- bash 명령은 **실행 후 결과로 판단**한다. 실행 전 permission 을 추론하지 않는다. `[makdoong2-team hook] BLOCKED:` stderr 로그가 나온 것만 실제 차단이다.
49
-
50
- ## 출력 규약
51
-
52
- 마지막 assistant turn 에 ```json 펜스 블록을 **정확히 하나** 출력한다. 스키마는 dispatch 프롬프트에 명시되어 있다:
53
-
54
- ```json
55
- {
56
- "source": "<배정받은 source>",
57
- "findings": [{"title": "...", "detail": "...", "url": "... 또는 null"}],
58
- "gaps": ["확인하지 못한 항목"]
59
- }
60
- ```
61
-
62
- - 조사 결과가 없어도 **JSON 블록은 반드시 출력한다.** `findings: []` + `gaps` 에 이유를 적는다.
63
- - 블록을 출력하지 않으면 플러그인이 파싱 실패로 기록하고 해당 소스는 `status: "failed"` 가 된다.
64
- - 펜스 블록 앞뒤의 한국어 설명은 자유다. 단 JSON 블록은 하나여야 한다 (여러 개면 마지막 것이 채택된다).
65
-
66
- ## 조기 종료
67
-
68
- MCP 인증 실패(exit 68/69), 접근 권한 부족, 대상 부재 등으로 조사가 불가능하면 **재시도로 시간을 쓰지 말고** 즉시 `findings: []` + `gaps` 에 사유를 적어 반환한다. 한 소스의 실패는 다른 소스의 조사를 막지 않는다 — 플러그인이 부분 성공으로 병합한다.
@@ -1,165 +0,0 @@
1
- /** Research sources backed by a lazy-loaded MCP skill. */
2
- export type ResearchSource = "jira" | "confluence" | "bitbucket" | "github-oss";
3
- export interface ResearchSourceSpec {
4
- source: ResearchSource;
5
- /** `skill(name=…)` — MUST be loaded before the MCP call (lazy-load, ARCHITECTURE.md §4.4). */
6
- skill: string;
7
- /**
8
- * `skill_mcp(mcp_name=…)` for sources whose SKILL.md declares an embedded MCP.
9
- * `null` for skills that carry no MCP of their own (github-oss-research works
10
- * through WebFetch / site-wide chrome-devtools-mcp) — the prompt branches on this.
11
- */
12
- mcp: string | null;
13
- /** Korean label used in prompts and the merged artifact. */
14
- label: string;
15
- /** What this source is good for — injected into the research prompt. */
16
- scope: string;
17
- }
18
- export declare const RESEARCH_SOURCES: Record<ResearchSource, ResearchSourceSpec>;
19
- /** Hard ceiling on simultaneously spawned research sessions, regardless of config. */
20
- export declare const MAX_RESEARCH_PARALLEL = 6;
21
- /** Default when `research.max_parallel` is unset. */
22
- export declare const DEFAULT_RESEARCH_PARALLEL = 3;
23
- /** Default per-source wall-clock budget when `research.timeout_minutes` is unset. */
24
- export declare const DEFAULT_RESEARCH_TIMEOUT_MINUTES = 10;
25
- /** Focus text longer than this is truncated before it reaches the prompt. */
26
- export declare const MAX_FOCUS_CHARS = 600;
27
- /** Per-source cap on findings kept in the merged artifact. */
28
- export declare const MAX_FINDINGS_PER_SOURCE = 20;
29
- export interface ResearchQueryInput {
30
- source?: unknown;
31
- focus?: unknown;
32
- }
33
- export interface NormalizedQuery {
34
- spec: ResearchSourceSpec;
35
- focus: string;
36
- }
37
- export interface RejectedQuery {
38
- source: string;
39
- reason: string;
40
- }
41
- export interface NormalizeResult {
42
- queries: NormalizedQuery[];
43
- rejected: RejectedQuery[];
44
- /** Queries dropped because they exceeded the parallel cap (reported, never silent). */
45
- deferred: RejectedQuery[];
46
- }
47
- export declare function resolveResearchSource(name: unknown): ResearchSourceSpec | null;
48
- /** Clamp the configured parallelism into [1, MAX_RESEARCH_PARALLEL]. */
49
- export declare function resolveParallelism(configured: unknown): number;
50
- /**
51
- * Validate + de-duplicate the caller's queries.
52
- *
53
- * Rules:
54
- * - unknown source → rejected (the caller mistyped; failing loudly beats a silent no-op)
55
- * - empty focus → rejected
56
- * - same (source, focus) twice → the duplicate is rejected
57
- * - more than `limit` survivors → the tail is DEFERRED, never silently dropped
58
- */
59
- export declare function normalizeQueries(raw: unknown, limit: number): NormalizeResult;
60
- export interface ResearchPromptContext {
61
- issue: string;
62
- scriptsDir: string;
63
- worktree: string;
64
- /** Optional extra context from the caller (e.g. the Jira summary). */
65
- context?: string;
66
- }
67
- /**
68
- * Prompt for one research sub-session.
69
- *
70
- * The session is deliberately narrow: one source, one focus, fixed output schema.
71
- * That is what makes the fan-out worth its cost — each session's context holds
72
- * only its own source's material instead of all three (DESIGN.md §3.7).
73
- */
74
- export declare function buildResearchPrompt(q: NormalizedQuery, ctx: ResearchPromptContext): string;
75
- export interface ResearchFinding {
76
- title: string;
77
- detail: string;
78
- url: string | null;
79
- }
80
- export interface ParsedResearch {
81
- findings: ResearchFinding[];
82
- gaps: string[];
83
- }
84
- export type ParseResult = {
85
- ok: true;
86
- data: ParsedResearch;
87
- } | {
88
- ok: false;
89
- reason: string;
90
- };
91
- /**
92
- * Pull the research JSON out of an LLM turn.
93
- *
94
- * Order matters: prefer the LAST fenced ```json block (models often show a draft
95
- * before the final answer), then fall back to a balanced brace scan. A parse
96
- * failure is reported, never coerced into an empty-but-successful result — a
97
- * silently empty source reads as "nothing to find" when it means "nothing parsed".
98
- */
99
- export declare function parseResearchOutput(raw: unknown): ParseResult;
100
- export interface SourceOutcome {
101
- source: ResearchSource;
102
- label: string;
103
- focus: string;
104
- status: "ok" | "failed";
105
- findings: ResearchFinding[];
106
- gaps: string[];
107
- error: string | null;
108
- session_id: string | null;
109
- elapsed_ms: number;
110
- }
111
- export interface ResearchFindingsArtifact {
112
- issue: string;
113
- generated_at: string;
114
- sources: SourceOutcome[];
115
- rejected: RejectedQuery[];
116
- deferred: RejectedQuery[];
117
- counts: {
118
- requested: number;
119
- ok: number;
120
- failed: number;
121
- findings_total: number;
122
- };
123
- }
124
- /**
125
- * Merge per-source outcomes into the artifact a gate can check deterministically.
126
- *
127
- * `rejected` / `deferred` are carried into the artifact on purpose: a fan-out that
128
- * quietly covered 2 of 3 sources looks identical to one that covered all 3 unless
129
- * the shortfall is written down.
130
- */
131
- export declare function mergeResearchFindings(issue: string, generatedAt: string, outcomes: SourceOutcome[], rejected: RejectedQuery[], deferred: RejectedQuery[]): ResearchFindingsArtifact;
132
- /** Human-readable one-liner per source for the tool's text return. */
133
- export declare function summarizeOutcomes(outcomes: SourceOutcome[]): string[];
134
- export type FanoutStatus = "ok" | "partial" | "failed";
135
- export interface FanoutOutcome {
136
- status: FanoutStatus;
137
- /** `false` only when NO source produced anything. */
138
- ok: boolean;
139
- /** `true` when some sources succeeded and others did not. */
140
- partial: boolean;
141
- next_action: string;
142
- }
143
- /**
144
- * Turn the merged counts into the caller-facing verdict.
145
- *
146
- * Why this is not just `ok = okCount > 0`: a fan-out that covered 1 of 3 sources
147
- * returned the same shape as one that covered all 3, and the only difference was
148
- * a `failed` array the caller had to notice on its own. It did not — on two
149
- * consecutive days Confluence and Bitbucket both timed out at exactly 10 minutes,
150
- * the planner treated the Jira-only result as its evidence base, and then spent
151
- * the rest of its budget trying to make up the difference by hand and recorded
152
- * no markers at all (GitHub #9). A shortfall has to arrive as its own field with
153
- * its own instruction, not as something to infer.
154
- *
155
- * Deliberately NOT an automatic retry of the failed sources: both failures were
156
- * full-budget timeouts, so retrying in place spends another `timeout_ms` for the
157
- * same result and pushes the parent session past its own deadline. The caller
158
- * gets the shortfall and decides — narrower focus, a later round, or proceed
159
- * with recorded gaps.
160
- */
161
- export declare function classifyFanoutOutcome(counts: {
162
- requested: number;
163
- ok: number;
164
- failed: number;
165
- }, artifactPath: string | null, failedSources: string[]): FanoutOutcome;