makdoong2-team 1.3.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 (97) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +193 -0
  3. package/agents/makdoong2-analyzer.md +135 -0
  4. package/agents/makdoong2-engineer.md +165 -0
  5. package/agents/makdoong2-planner.md +267 -0
  6. package/agents/makdoong2-publisher.md +481 -0
  7. package/agents/makdoong2-team-leader.md +249 -0
  8. package/agents/makdoong2-verifier.md +353 -0
  9. package/assets/makdoong2-team.default.json +46 -0
  10. package/assets/makdoong2-team.schema.json +357 -0
  11. package/bin/cli.js +404 -0
  12. package/dist/agent-stage-config.d.ts +15 -0
  13. package/dist/agent-stage-config.js +119 -0
  14. package/dist/config.d.ts +79 -0
  15. package/dist/config.js +96 -0
  16. package/dist/logger.d.ts +14 -0
  17. package/dist/logger.js +131 -0
  18. package/dist/mcp-secret-injector.d.ts +56 -0
  19. package/dist/mcp-secret-injector.js +89 -0
  20. package/dist/model-chain-cli.d.ts +1 -0
  21. package/dist/model-chain-cli.js +21 -0
  22. package/dist/model-fallback-policy.d.ts +69 -0
  23. package/dist/model-fallback-policy.js +211 -0
  24. package/dist/opencode-plugin.d.ts +8 -0
  25. package/dist/opencode-plugin.js +2457 -0
  26. package/dist/poll-sub-session.d.ts +139 -0
  27. package/dist/poll-sub-session.js +494 -0
  28. package/dist/redact-secrets.d.ts +3 -0
  29. package/dist/redact-secrets.js +68 -0
  30. package/dist/session-index.d.ts +11 -0
  31. package/dist/session-index.js +71 -0
  32. package/dist/skill-mcp-registry.d.ts +59 -0
  33. package/dist/skill-mcp-registry.js +178 -0
  34. package/dist/stall-escalation.d.ts +1 -0
  35. package/dist/stall-escalation.js +22 -0
  36. package/dist/tmux-monitor.d.ts +193 -0
  37. package/dist/tmux-monitor.js +694 -0
  38. package/dist/verdict-hash.d.ts +1 -0
  39. package/dist/verdict-hash.js +62 -0
  40. package/gates/stage-analysis-verify.sh +84 -0
  41. package/gates/stage2-requirements-verify.sh +13 -0
  42. package/gates/stage3-scope-verify.sh +45 -0
  43. package/gates/stage4-dev-post-verify.sh +64 -0
  44. package/gates/stage4-dev-verify.sh +36 -0
  45. package/gates/stage5-coverage-verify.sh +36 -0
  46. package/gates/stage5-test-verify.sh +24 -0
  47. package/gates/stage6-commit-verify.sh +41 -0
  48. package/gates/stage6-post-commit-verify.sh +131 -0
  49. package/gates/stage7-post-pr-verify.sh +53 -0
  50. package/gates/stage7-pr-verify.sh +48 -0
  51. package/gates/stage8-post-review-verify.sh +84 -0
  52. package/gates/stage8-review-verify.sh +45 -0
  53. package/gates/verify.sh +44 -0
  54. package/opencode.json.example +40 -0
  55. package/package.json +84 -0
  56. package/postinstall.mjs +56 -0
  57. package/references/commit-convention.md +130 -0
  58. package/references/jira-issue-templates.md +203 -0
  59. package/references/pr-template.md +381 -0
  60. package/scripts/config.sh +46 -0
  61. package/scripts/coverage-record.sh +67 -0
  62. package/scripts/gate-policy-test.sh +152 -0
  63. package/scripts/install-lib.mjs +1029 -0
  64. package/scripts/lint-agent-prompts.sh +74 -0
  65. package/scripts/log-event.sh +44 -0
  66. package/scripts/model-policy.mjs +183 -0
  67. package/scripts/publish-if-changed.sh +207 -0
  68. package/scripts/release.sh +276 -0
  69. package/scripts/rollback-commits.sh +35 -0
  70. package/scripts/smoke-test.mjs +194 -0
  71. package/scripts/state.sh +192 -0
  72. package/scripts/test-postinstall.mjs +141 -0
  73. package/scripts/with-fallback.sh +56 -0
  74. package/scripts/wt-sync-ignored.sh +193 -0
  75. package/skills/_lib/load-secret.sh +149 -0
  76. package/skills/bamboo-ci/SKILL.md +81 -0
  77. package/skills/bamboo-ci/run-bamboo.sh +23 -0
  78. package/skills/bitbucket-research/SKILL.md +87 -0
  79. package/skills/bitbucket-research/run-repos.sh +23 -0
  80. package/skills/confluence-research/SKILL.md +75 -0
  81. package/skills/confluence-research/run-docs.sh +23 -0
  82. package/skills/github-oss-research/SKILL.md +59 -0
  83. package/skills/jira-research/SKILL.md +75 -0
  84. package/skills/jira-research/run-works.sh +23 -0
  85. package/src/hooks/guard-bash.sh +67 -0
  86. package/src/hooks/session-start.sh +96 -0
  87. package/src/hooks/sync-state.sh +47 -0
  88. package/stages/01-jira.md +43 -0
  89. package/stages/01-planning.md +229 -0
  90. package/stages/02-requirements.md +298 -0
  91. package/stages/03-scope.md +81 -0
  92. package/stages/04-analysis.md +281 -0
  93. package/stages/05-worktree-dev.md +124 -0
  94. package/stages/06-test.md +161 -0
  95. package/stages/07-commit.md +229 -0
  96. package/stages/08-pr.md +177 -0
  97. package/stages/09-review-comments.md +277 -0
@@ -0,0 +1,203 @@
1
+ # Jira Issue Templates — Team Standard
2
+
3
+ > makdoong2-jira 막내가 본 문서를 참조해서 Jira 본문 + 메타데이터를 검증한다.
4
+ > 6가지 검사 중 하나라도 실패하면 stage 2 진입 게이트가 차단되며, 인터뷰 완료 후
5
+ > `validation_passed=true` 마커가 기록되어야 진행 가능.
6
+
7
+ ## 0. 이슈 유형 → 템플릿 매핑
8
+
9
+ Jira "Type" 필드 값이 다음 중 하나여야 한다:
10
+
11
+ | Type | 템플릿 섹션 |
12
+ |---|---|
13
+ | Task | [§1](#1-task) |
14
+ | Improvement | [§2](#2-improvement) |
15
+ | New Feature | [§3](#3-new-feature) |
16
+ | Bug | [§4](#4-bug) |
17
+
18
+ 그 외 유형 — 사용자에게 "어느 템플릿으로 검증할까요?" 질의 후 결정.
19
+
20
+ ---
21
+
22
+ ## 1. Task
23
+
24
+ **필수 섹션 (순서 무관, 모두 존재해야 함)**:
25
+
26
+ | # | 섹션 헤더 (굵게) | 내용 가이드 |
27
+ |---|---|---|
28
+ | 1 | **작업 개요·배경/목적** | 수행할 작업의 요약, 필요한 이유와 목표 |
29
+ | 2 | **산출물 및 저장 위치** | 코드/문서/스크립트 등 결과물과 경로 |
30
+ | 3 | **추가 사항** | 참고할 만한 문서, 관련 이슈 링크, 유사 사례 등 |
31
+
32
+ ### 예시
33
+ ```
34
+ 1. *작업 개요·배경/목적*
35
+ : 캐시 모듈 추가 — 업스트림 폴링 부하 절감 위함
36
+
37
+ 2. *산출물 및 저장 위치*
38
+ : example-cache/src/main/scala/.../ItemCache.scala
39
+ + example-cache/src/test/scala/.../ItemCacheSpec.scala
40
+
41
+ 3. *추가 사항*
42
+ : 관련 이슈 PROJ-38120 / 설계 문서 https://{CONFLUENCE_HOST}/.../item-cache
43
+ ```
44
+
45
+ ---
46
+
47
+ ## 2. Improvement
48
+
49
+ **필수 섹션**:
50
+
51
+ | # | 섹션 헤더 | 내용 가이드 |
52
+ |---|---|---|
53
+ | 1 | **기존 동작** | 현재 기능 또는 동작 방식 설명 |
54
+ | 2 | **기능 개선이 필요한 문제점 (배경/근거)** | 사용자 불편/성능 저하/운영 비효율 등 구체 원인 |
55
+ | 3 | **문제 해결 방안** | 기술적 접근, 설계 아이디어 |
56
+ | 4 | **수정 내용** | 변경될 기능/모듈/API 등 변경 범위 |
57
+ | 5 | **추가 사항** | 참고 문서, 관련 이슈 |
58
+
59
+ ---
60
+
61
+ ## 3. New Feature
62
+
63
+ **필수 섹션**:
64
+
65
+ | # | 섹션 헤더 | 내용 가이드 |
66
+ |---|---|---|
67
+ | 1 | **기능 개요** | 추가하고자 하는 새 기능의 간단한 설명 |
68
+ | 2 | **기능 추가 배경/요청 근거** | 이 기능이 왜 필요한지, 어떤 문제/요구사항에서 출발했는지 |
69
+ | 3 | **요구사항 및 기대효과** | 기능이 충족해야 할 명세 + 도입 시 예상 효과 |
70
+ | 4 | **기능 상세 설계/내용** | 기능 동작 방식, 주요 처리 흐름, API/화면 구성 등 |
71
+ | 5 | **추가사항** | 타 시스템 연동, 정책 논의 필요 여부 |
72
+
73
+ ---
74
+
75
+ ## 4. Bug
76
+
77
+ **필수 섹션**:
78
+
79
+ | # | 섹션 헤더 | 내용 가이드 |
80
+ |---|---|---|
81
+ | 1 | **버그 설명** | 발생한 문제에 대한 요약 |
82
+ | 2 | **재현 방법** | 단계별 절차 (1) 2) 3) ...) |
83
+ | 3 | **기대 동작** | 정상적으로 동작했어야 할 기능 |
84
+ | 4 | **실제 동작** | 버그 발생 시 시스템의 실제 동작 |
85
+ | 5 | **오류 로그 및 스크린샷** (선택) | 관련 로그/오류 메시지/캡처 |
86
+ | 6 | **영향 범위** | 영향 받는 기능/사용자/시스템 |
87
+ | 7 | **조치 내용** | 버그 수정 방안 및 조치 계획 |
88
+ | 8 | **추가사항** | 기타 참고 사항, 관련 이슈 |
89
+
90
+ ---
91
+
92
+ ## 5. 검증 체크리스트 (6개 항목, 모두 통과해야 stage 2 진입)
93
+
94
+ | 마커 키 (`.stages."1_planning".substages."jira".template_validation.*`) | 통과 기준 |
95
+ |---|---|
96
+ | `content_template_match` | Jira 본문에 해당 유형 템플릿의 **필수 섹션 헤더가 전부 존재** (선택 섹션 제외) |
97
+ | `content_quality_adequate` | 각 섹션이 placeholder("(...)")만 있지 않고 substantive 내용 보유 |
98
+ | `priority_set` | priority ∈ {Highest, High, Medium, Low, Lowest}, 비어있지 않음 |
99
+ | `assignee_set` | assignee가 Unassigned가 아닌 실제 사용자 |
100
+ | `reporter_set` | reporter가 실제 사용자로 설정됨 (Jira 기본은 생성자) |
101
+ | `fix_version_handled` | fix version이 명시되어 있음 OR 사용자가 "N/A 진행" 명시 결정 |
102
+
103
+ ### 섹션 헤더 검출 가이드
104
+ - Jira 마크다운에서 `1. *작업 개요·배경/목적*`, `**작업 개요·배경/목적**`, `## 작업 개요·배경/목적` 등 다양한 형식 인정
105
+ - 헤더 텍스트의 정확한 한글/공백 일치 — 띄어쓰기·괄호 표기까지 동일해야 함
106
+ - 인접 콜론(`:`) 뒤가 비어있거나 placeholder만 있으면 → `content_quality_adequate=false`
107
+
108
+ ---
109
+
110
+ ## 6. 인터뷰 프롬프트 템플릿 (검사 실패 시)
111
+
112
+ 각 실패 항목별로 사용자에게 한 번에 하나씩 질문. 모든 응답 수렴 후
113
+ `.stages."1_planning".substages."jira".interview_completed=true` + `.stages."1_planning".substages."jira".validation_passed=true` 기록.
114
+
115
+ ### 6.1 `content_template_match=false`
116
+ ```
117
+ 이슈 본문에 다음 섹션이 누락되어 있습니다: <missing_sections>
118
+ - A) Jira를 업데이트하고 알려주시면 재검증합니다
119
+ - B) 인터뷰로 직접 알려주시면 stage 2로 전달합니다 (Jira 본문 그대로)
120
+ - C) 표준 템플릿 적용 예외 (예: 긴급 hotfix) — 검증 건너뛰기 동의
121
+ ```
122
+
123
+ ### 6.2 `content_quality_adequate=false`
124
+ ```
125
+ 다음 섹션이 placeholder만 있고 substantive 내용이 부족합니다:
126
+ <section_name>: "<현재 내용 발췌>"
127
+ - A) 어떤 내용이 들어가야 하는지 알려주세요 (인터뷰로 stage 2 전달)
128
+ - B) Jira를 업데이트한 뒤 재검증
129
+ - C) 의도적으로 비워둠 — 명시 동의 후 진행
130
+ ```
131
+
132
+ ### 6.3 `priority_set=false` 또는 우선순위 적정성 의심
133
+ ```
134
+ 현재 priority='<X>' (또는 비어있음)입니다.
135
+ - A) 적정함, 그대로 진행
136
+ - B) 변경 필요 → 어떤 값? (Highest/High/Medium/Low/Lowest)
137
+ - C) Jira에서 직접 수정 후 재검증
138
+ ```
139
+
140
+ ### 6.4 `assignee_set=false` 또는 `reporter_set=false`
141
+ ```
142
+ 현재 assignee='<X>', reporter='<Y>'입니다.
143
+ - A) 올바름, 그대로 진행
144
+ - B) assignee 변경 필요 → 누구로?
145
+ - C) reporter 변경 필요 → 누구로?
146
+ - D) Jira에서 직접 수정 후 재검증
147
+ ```
148
+
149
+ ### 6.5 `fix_version_handled=false`
150
+ ```
151
+ fix version이 비어있습니다.
152
+ - A) 특정 버전 설정 — 어떤 버전?
153
+ - B) 다음 정기 릴리스에 포함 (현재 릴리스 + 1)
154
+ - C) N/A로 명시 진행 (예: 내부 도구/실험적 작업)
155
+ - D) Jira에서 직접 수정 후 재검증
156
+ ```
157
+
158
+ ### 6.6 인터뷰 응답 처리
159
+ - **A 응답**: 해당 검사 항목을 `true`로 갱신 (사용자 명시 수용)
160
+ - **B/C 응답**: 사용자가 알려준 정보를 별도 마커(`.stages."1_planning".substages."jira".interview_outcomes.<key>`)에 기록 → stage 2 에이전트가 참조
161
+ - **D 응답**: 사용자 업데이트 완료 후 1-3 재검증
162
+
163
+ 모든 인터뷰 완료 + 모든 항목 (자체 통과 OR 사용자 수용) 후에만:
164
+ ```bash
165
+ state.sh set <ISSUE> '.stages."1_planning".substages."jira".interview_completed' 'true'
166
+ state.sh set <ISSUE> '.stages."1_planning".substages."jira".validation_passed' 'true'
167
+ ```
168
+
169
+ ---
170
+
171
+ ## 7. state.json 스키마 (`1_planning.jira` substage 부분)
172
+
173
+ ```json
174
+ {
175
+ "stages": {
176
+ "1_planning": {
177
+ "done": false,
178
+ "substages": {
179
+ "jira": {
180
+ "done": false,
181
+ "issue_type": null,
182
+ "template_validation": {
183
+ "content_template_match": false,
184
+ "content_quality_adequate": false,
185
+ "priority_set": false,
186
+ "assignee_set": false,
187
+ "reporter_set": false,
188
+ "fix_version_handled": false,
189
+ "missing_sections": [],
190
+ "issues": []
191
+ },
192
+ "interview_required": false,
193
+ "interview_completed": false,
194
+ "interview_outcomes": {},
195
+ "validation_passed": false
196
+ }
197
+ }
198
+ }
199
+ }
200
+ }
201
+ ```
202
+
203
+ > 게이트는 `validation_passed`만 본다. 다른 필드는 진단·stage 2 전달용.
@@ -0,0 +1,381 @@
1
+ # Pull Request Template
2
+
3
+ 이 문서는 AGENT 가 Bitbucket Data Center (설정된 `BITBUCKET_API_BASE_PATH` 호스트) 에 **자신이 작성한 Pull Request** 를 생성하고, 각 커밋의 변경 지점에 **변경된 내용과 반영한 의도를 설명하는 인라인 코멘트(live)** 를 기록할 때 따르는 템플릿이다.
4
+
5
+ > 인라인 코멘트의 목적은 PR **작성자 본인이 자기 변경사항을 설명**하는 것이다. 타인 PR 에 대한 리뷰 코멘트가 아니다.
6
+
7
+ ---
8
+
9
+ ## 발동 조건 (Trigger)
10
+
11
+ 다음 **두 조건이 모두 충족될 때만** 이 템플릿을 적용한다.
12
+
13
+ 1. 사용자 메시지에 PR 생성 명시 트리거가 포함된다.
14
+ - 예: `"PR 만들어줘"`, `"Pull Request 생성"`, `"이 브랜치 PR 올려"`, `"draft PR 올리고 인라인 달아줘"`
15
+ 2. 대상 브랜치·repo 가 식별 가능하다 (`fromRef`, `toRef`, `projectKey`, `repositorySlug`). 모호하면 즉시 질문.
16
+
17
+ ---
18
+
19
+ ## 1. PR 본문 포맷
20
+
21
+ ```markdown
22
+ #### 목표
23
+ - <이슈의 최종 목적 1>
24
+ - <이슈의 최종 목적 2>
25
+
26
+ #### 구현내용
27
+ - <실제 구현한 것 1>
28
+ - <실제 구현한 것 2>
29
+ - ...
30
+
31
+ #### Test
32
+ - "<테스트 시나리오 1>"
33
+ - "<테스트 시나리오 2>"
34
+ - ...
35
+ ```
36
+
37
+ ---
38
+
39
+ ## 2. 섹션별 작성 가이드
40
+
41
+ ### `#### 목표`
42
+ - Jira 이슈의 **acceptance criteria** 또는 최종 목적을 자연어로 풀어 쓴다.
43
+ - 구현 세부사항이 아니라 **비즈니스 또는 기술 목표** 중심이다.
44
+ - 1~4줄 정도가 적절하다.
45
+
46
+ ### `#### 구현내용`
47
+ - 실제로 추가하거나 수정한 **기능 단위**로 나열한다.
48
+ - 파일 단위로 쓰지 않는다 (`"xxx.java 추가"` ✗).
49
+ - 커밋 메시지들을 종합하여 PR 전체의 변경 내역을 압축한다.
50
+ - 추상적이지 않게 구체적인 기능명을 사용한다 (`"캐시 조회 로직 개선"` 정도로는 부족하다).
51
+
52
+ ### `#### Test`
53
+ - 실제 테스트 시나리오를 **큰따옴표로 감싼 한글 문장**으로 작성한다.
54
+ - `"~한 경우 ~한다"` 형태의 완전한 문장을 사용한다.
55
+ - 단위 테스트 메서드명을 그대로 쓰지 않고 **의도**를 자연어로 풀어 쓴다.
56
+
57
+ ---
58
+
59
+ ## 3. 실제 예시
60
+
61
+ ```markdown
62
+ #### 목표
63
+ - GetUpdatedItems API 요청 시 전달받은 Polling 시퀀스를 기준으로 캐시 키를 계산한다.
64
+ - 캐시 키를 기준으로 Redis에 조회하여 캐시 갱신 목록을 fetch-out 한다. (Redis에는 이미 데이터가 존재한다고 가정)
65
+
66
+ #### 구현내용
67
+ - GetUpdatedItems API 테스트 코드 추가
68
+ - Redis Cache Entity → DB Entity 변환 정의 및 구현
69
+ - Redis Cache 구조체 정의
70
+ - Redis Cache Key 생성 함수 구현
71
+ - DB Entity → API Response 변환 헬퍼 메서드 추가
72
+ - DB Entity → API Response 변환 메서드 정의 및 구현
73
+ - GetUpdatedItems API 기능 구현
74
+
75
+ #### Test
76
+ - "클라이언트 질의 시 Redis 캐시에 갱신 목록이 존재하는 경우 응답으로 제공한다."
77
+ - "클라이언트 질의 시 Redis 캐시에 가장 최근 갱신 목록이 존재하는 경우 응답으로 제공한다."
78
+ - "클라이언트 질의 시 갱신 목록을 모두 제공하지 않았다면 isFullList는 false 여야 한다."
79
+ ```
80
+
81
+ ---
82
+
83
+ ## 4. PR 생성 규칙
84
+
85
+ 1. 반드시 **Draft 로 생성**한다 → `bitbucket_createPullRequest(..., draft=true)`.
86
+ 2. **리뷰어를 추가하지 않는다** → `reviewers` 파라미터 자체를 생략한다 (`[]` 도 아니라 미전달).
87
+ 3. 생성 후 각 커밋별 diff 를 분석하여 **인라인 코멘트** 를 **개별 live 코멘트** 로 작성한다 (아래 §5–6).
88
+
89
+ ### 4-1. 토큰 소유자 식별 (리뷰어 추가용)
90
+
91
+ 토큰 소유자의 username을 `X-AUSERNAME` 응답 헤더로 식별한다:
92
+
93
+ ```bash
94
+ TOKEN=$(jq -r '.secrets.BITBUCKET_API_TOKEN' ~/.config/opencode/makdoong2-team.json)
95
+ BB_BASE=$(jq -r '.hosts.BITBUCKET_API_BASE_PATH' ~/.config/opencode/makdoong2-team.json)
96
+ USERNAME=$(curl -sI -H "Authorization: Bearer $TOKEN" \
97
+ "$BB_BASE/api/latest/application-properties" \
98
+ | grep -i '^X-AUSERNAME:' | awk '{print $2}' | tr -d '\r')
99
+ ```
100
+
101
+ `X-AUSERNAME` 헤더는 API 응답 헤더에 인증된 사용자명이 포함되므로, 권한 불필요하고 토큰 소유자 본인 확인용으로 사용할 수 있다. 이 username을 `bitbucket_createPullRequest`의 `reviewers` 파라미터에 전달한다.
102
+
103
+ ### 4-2. 생성 호출 예
104
+ ```
105
+ bitbucket_createPullRequest(
106
+ projectKey="PROJ",
107
+ repositorySlug="<repo>",
108
+ title="<Jira 키> [<모듈명>] <간단한 변경 요약>",
109
+ description=<§1 포맷 그대로>,
110
+ fromRefId="refs/heads/<feature branch>",
111
+ toRefId="refs/heads/master", # toRef 는 사용자에게 확인
112
+ draft=true,
113
+ reviewers=["<§4-1에서 식별한 username>"],
114
+ output="full" # PR id / 응답 메타데이터 보존
115
+ )
116
+ ```
117
+ 응답에서 `id` (pullRequestId), `links.self[0].href` (PR URL), `version` 을 저장한다.
118
+
119
+ ---
120
+
121
+ ## 5. 인라인 코멘트 작성 가이드 — 내용
122
+
123
+ **목적**: PR 작성자 본인이 **무엇을 어떻게 변경했고 왜 그렇게 했는지** 를 변경 지점 옆에 직접 기록한다. 미래의 자신·동료·리뷰어가 코드만 봐서는 이해하기 어려운 의도와 맥락을 남기는 것이 목표.
124
+
125
+ ### 톤
126
+ - 자연어와 일상어 중심으로 작성한다.
127
+ - 전문 용어가 필요하면 그대로 사용한다.
128
+ - 필요하면 마크다운을 사용한다 (코드 블록, 리스트 등).
129
+ - 작성자 1 인칭/평문 (`"~합니다"`, `"~했습니다"`) 톤을 유지한다. 리뷰어 톤 (`"~해주세요"`, `"~필요해 보입니다"`) 금지.
130
+
131
+ ### 내용 선정 기준
132
+ 다음 중 **하나라도 해당하면** 코멘트 대상이다. 해당 없으면 코멘트하지 않는다.
133
+ - 비자명한 설계 결정 (왜 A 대신 B 를 선택했는지)
134
+ - 도메인 지식이 필요한 부분
135
+ - 성능, 보안, 동시성 고려 사항
136
+ - 외부 의존성과의 상호작용
137
+ - 기존 동작과 달라진 부분의 호환성·마이그레이션 메모
138
+ - 미해결 TODO 또는 후속 작업 예고
139
+
140
+ ### 코멘트 작성 금지 대상
141
+ - 자명한 리네임/포맷팅/임포트 정렬
142
+ - 테스트 추가 그 자체 (어떤 시나리오를 검증했는지가 새로운 정보일 때만 작성)
143
+ - 커밋 메시지로 이미 충분히 설명된 내용을 그대로 복붙
144
+ - 라이브러리 호출 시그니처 그대로 설명 (코드가 곧 의도)
145
+
146
+ ### 예시
147
+ > 여기서 Redis TTL 을 10 분으로 잡은 이유는 캐시 갱신 주기가 업스트림 폴링 주기(5 분)의 2 배를 넘지 않도록 맞춘 것입니다. 폴링 주기가 바뀌면 이 값도 같이 조정해야 합니다.
148
+
149
+ > Cassandra 조회 시 `LOCAL_QUORUM` 을 명시적으로 지정했습니다. 기본값인 `ONE` 을 사용하면 복제 지연으로 인해 최신 캐시 상태를 놓칠 수 있기 때문입니다.
150
+
151
+ > 이 부분은 일단 동기 방식으로 구현했습니다. 성능 이슈가 보이면 `CompletableFuture` 기반 비동기로 리팩토링할 예정이고, 관련 내용을 PROJ-38400 후속 이슈에 기록해두었습니다.
152
+
153
+ > `get_item_cache()` 가 V1/V2 여부와 무관하게 공용 타입 `item_entry` 를 반환하므로, 로컬 변수도 `ITEM_ENTRY_V1` 에서 `item_entry` 로 교체합니다. 덕분에 아래에서 제거되는 중간 변환 코드(`ITEM_ENTRY_V1_to_item_entry`)가 불필요해집니다.
154
+
155
+ > 기존에 Status Report 를 Thrift → JSON 과정으로 변환하여 발행하던 구조를 Thrift 구조체로 변환하는 것으로 수정합니다. 프로듀서가 데이터를 전송하는 토픽 또한 `StatusReportJson` 이 아닌 `StatusReport` 로 수정합니다. 참고: https://{CONFLUENCE_HOST}/pages/viewpage.action?pageId=123456789
156
+
157
+ ---
158
+
159
+ ## 6. 인라인 코멘트 작성 워크플로 — 실행 (기술)
160
+
161
+ §5 가 **무엇을·어떤 톤으로** 쓸지를 정의한다면, §6 은 **정확한 위치에 어떻게 anchor 할지** 를 정의한다.
162
+
163
+ > ⚠️ **CRITICAL**: 코멘트는 반드시 **특정 커밋의 변경 지점** 에 anchor 해야 한다 (PR latest 의 통합 diff 가 아님). Bitbucket UI 의 `Pull request → Commits → <개별 커밋> → 파일 diff` 뷰에 노출되려면 `anchor.diffType = "COMMIT"` 이 필수다.
164
+ >
165
+ > `bitbucket_postPullRequestComment` **MCP 도구는 기본적으로 `diffType=EFFECTIVE` (PR latest 통합 diff) 로 anchor 한다**. EFFECTIVE 는 PR 의 `Diff` 탭에서만 보이고 개별 커밋 뷰에서는 노출되지 않으므로 **이 워크플로에서는 MCP 도구를 사용하지 않는다**. 대신 Bitbucket REST API 를 `curl` 로 직접 호출한다.
166
+
167
+ ### 6.1 입력
168
+ - `projectKey`, `repositorySlug`, `pullRequestId` → §4 의 `createPullRequest` 응답에서 확보
169
+ - `BITBUCKET_API_TOKEN` → `~/.config/opencode/opencode.json` 의 `mcp.repos.environment.BITBUCKET_API_TOKEN`
170
+ - BASE URL: `https://{BITBUCKET_HOST}/rest/api/1.0/projects/{projectKey}/repos/{repositorySlug}/pull-requests/{pullRequestId}`
171
+
172
+ ### 6.2 코멘트 위치 → 대상 커밋 매핑
173
+
174
+ 각 코멘트마다 **어느 커밋에 anchor 할 것인가** 를 먼저 결정한다.
175
+
176
+ **파일 신규 추가의 경우** (대부분):
177
+ ```bash
178
+ target=$(git log <branch> --diff-filter=A --format='%H' -- <path> | tail -1)
179
+ parent=$(git rev-parse ${target}^)
180
+ ```
181
+ `target` 은 그 파일을 최초로 추가한 커밋. `parent` 는 그 직전 커밋.
182
+
183
+ **기존 파일 수정의 경우**:
184
+ - 코멘트할 라인의 도입 커밋을 `git blame -L <line>,<line> <path>` 또는 `git log -L <line>,<line>:<path>` 로 식별한다.
185
+ - 식별한 커밋이 PR 범위 안에 있어야 한다 (`git log <base>..<head>` 에 포함).
186
+
187
+ ### 6.3 파일별 diff → 정확한 line / lineType 확정
188
+
189
+ 대상 커밋의 변경에서 코멘트할 라인 번호와 타입을 확정한다.
190
+
191
+ ```bash
192
+ git show <target> -- <path> # 변경 후 라인 번호 + segment 타입 확인
193
+ git diff <parent> <target> -- <path> # 동일 (parent..target 의 diff)
194
+ ```
195
+
196
+ **lineType 결정 규칙** (반드시 diff 에서 segment 타입 확정. 추측 금지):
197
+
198
+ | diff segment | `lineType` | `line` 번호 기준 |
199
+ |---|---|---|
200
+ | 녹색 `+` 라인 | `ADDED` | `toHash` 기준 (변경 후 라인 번호) |
201
+ | 빨간 `-` 라인 | `REMOVED` | `fromHash` 기준 (변경 전 라인 번호) |
202
+ | 변경 없는 컨텍스트 | `CONTEXT` | 양쪽 모두 유효 |
203
+
204
+ **인라인 코멘트는 보통 `ADDED` 라인에 단다** (자기가 추가한 코드의 설계 의도를 설명하므로). `REMOVED` 라인 코멘트는 "이 코드를 왜 제거했는지" 가 자명하지 않은 경우에만.
205
+
206
+ ### 6.4 multiline 미지원
207
+
208
+ **Bitbucket Data Center REST API 는 multiline 코멘트를 거부한다.** `anchor.multilineMarker` 와 `anchor.multilineSpan` 두 키 모두 거부되며, 에러 메시지가 서로 상대 키를 요구하는 모순 상태로 떨어진다 (실측 확인). 따라서:
209
+
210
+ - 의미상 여러 라인에 걸친 설계 결정도 **가장 핵심인 단일 라인** 에 anchor 한다.
211
+ - 예: enum 정의 전체가 아닌 `InProgress = 5` 한 줄에 anchor
212
+ - 예: `setSerialConsistencyLevel(LOCAL_SERIAL)` 한 줄에 anchor
213
+ - 코멘트 본문에서 자연어로 라인 범위를 언급할 수 있다 (`"L4–L7 의 enum 정의 전체에서..."`).
214
+
215
+ ### 6.5 commit-anchored 코멘트 POST (live)
216
+
217
+ ```bash
218
+ TOKEN=$(jq -r '.secrets.BITBUCKET_API_TOKEN' ~/.config/opencode/makdoong2-team.json)
219
+ BB_BASE=$(jq -r '.hosts.BITBUCKET_API_BASE_PATH' ~/.config/opencode/makdoong2-team.json)
220
+ BASE="https://{BITBUCKET_HOST}/rest/api/1.0/projects/{projectKey}/repos/{repositorySlug}/pull-requests/{prid}"
221
+
222
+ # 코멘트 본문은 임시 파일에 저장 (긴 markdown 본문을 안전하게 jq 로 주입)
223
+ # 본문 파일은 발행 직후 삭제한다.
224
+ cat > /tmp/comment-body.md <<'EOF'
225
+ <§5 가이드에 따라 작성한 본문>
226
+ EOF
227
+
228
+ payload=$(jq -n \
229
+ --arg text "$(cat /tmp/comment-body.md)" \
230
+ --arg path "<§6.2 의 파일 경로>" \
231
+ --arg fromHash "<§6.2 의 parent 전체 해시>" \
232
+ --arg toHash "<§6.2 의 target 전체 해시>" \
233
+ --argjson line <§6.3 의 라인 번호> \
234
+ '{
235
+ text: $text,
236
+ severity: "NORMAL",
237
+ anchor: {
238
+ diffType: "COMMIT",
239
+ fromHash: $fromHash,
240
+ toHash: $toHash,
241
+ fileType: "TO",
242
+ path: $path,
243
+ line: $line,
244
+ lineType: "ADDED"
245
+ }
246
+ }')
247
+
248
+ curl -sS -X POST \
249
+ -H "Authorization: Bearer $TOKEN" \
250
+ -H "Content-Type: application/json" \
251
+ --data "$payload" \
252
+ "$BASE/comments"
253
+ ```
254
+
255
+ **anchor 필드 의미**:
256
+
257
+ | 필드 | 값 | 설명 |
258
+ |---|---|---|
259
+ | `diffType` | `"COMMIT"` | **필수**. 특정 커밋의 diff 에 anchor. `"EFFECTIVE"` (PR latest 통합) / `"RANGE"` 는 commit 뷰에서 노출 안 됨 |
260
+ | `fromHash` | parent commit 전체 SHA | 대상 커밋의 직전 상태 |
261
+ | `toHash` | target commit 전체 SHA | 코멘트가 anchor 될 커밋 |
262
+ | `fileType` | `"TO"` (ADDED/CONTEXT 시) / `"FROM"` (REMOVED 시) | "변경 후" 또는 "변경 전" 파일 기준 |
263
+ | `path` | 파일 경로 (변경 후 기준) | RENAME 의 경우 `srcPath` 도 함께 전달 |
264
+ | `line` | 라인 번호 (lineType 에 맞는 기준) | |
265
+ | `lineType` | `"ADDED"` / `"REMOVED"` / `"CONTEXT"` | §6.3 에서 확정 |
266
+ | `severity` | `"NORMAL"` | 항상. `"BLOCKER"` 금지 (자기 PR 자기 차단 금지) |
267
+
268
+ 응답에서 `id`, `version`, `anchor.diffType` (정상이면 `"COMMIT"`) 확인. `diffType` 이 `"EFFECTIVE"` 로 떨어졌다면 잘못된 호출이다 → §6.6 으로 정리.
269
+
270
+ ### 6.6 잘못 anchor 된 코멘트 정리
271
+
272
+ `bitbucket_postPullRequestComment` MCP 도구로 실수 발행했거나 `diffType` 누락으로 `EFFECTIVE` 가 됐다면:
273
+
274
+ ```bash
275
+ # 1. 잘못된 코멘트의 version 확인 (생성 직후는 보통 0)
276
+ curl -sS -H "Authorization: Bearer $TOKEN" "$BASE/comments/{id}" | jq '.version'
277
+
278
+ # 2. DELETE → HTTP 204 기대
279
+ curl -sS -o /dev/null -w "%{http_code}\n" \
280
+ -X DELETE -H "Authorization: Bearer $TOKEN" \
281
+ "$BASE/comments/{id}?version={version}"
282
+
283
+ # 3. §6.5 로 commit-anchor 재게시
284
+ ```
285
+
286
+ **bash 스크립트로 여러 건 정리 시 주의**: `set -euo pipefail` 상태에서 한 건의 `jq` / `curl` 오류가 전체 루프를 중단시키면 부분 삭제 + 부분 미삭제 상태로 끝난다. **루프 직후 전체 코멘트 목록을 다시 조회하여 잔여물이 없는지 검증한다**:
287
+
288
+ ```bash
289
+ curl -sS -H "Authorization: Bearer $TOKEN" "$BASE/activities?limit=100" \
290
+ | jq '[.values[] | select(.action == "COMMENTED" and .commentAction == "ADDED")
291
+ | .comment | {id, diffType: .anchor.diffType}]'
292
+ ```
293
+
294
+ `diffType` 이 `"COMMIT"` 이 아닌 항목이 보이면 위 DELETE 절차로 추가 정리한다.
295
+
296
+ ### 6.7 보안
297
+
298
+ - 토큰을 response echo, 로그, 임시 파일, 커밋 메시지 어디에도 출력하지 말 것.
299
+ - 토큰은 항상 `$TOKEN` 변수로 참조. 페이로드 문자열에 인라인 삽입 금지.
300
+ - 임시 본문 파일(`/tmp/comment-body.md` 등) 은 발행 후 즉시 삭제.
301
+
302
+ ### 6.8 발행 후 사용자 보고
303
+
304
+ 발행 완료 후 다음 형식으로 사용자에게 보고한다. **각 항목은 commit-specific URL 로 연결**한다.
305
+
306
+ ```
307
+ PR <PR URL> 을 draft 로 생성했습니다.
308
+
309
+ 생성된 인라인 코멘트 (총 N개, 전부 diffType=COMMIT):
310
+ [1] <commit short> <filePath>:L<line>
311
+ → <BASE_URL>/pull-requests/<prid>/commits/<full hash>#<URL-encoded path>
312
+ > <본문 앞 80자>...
313
+ [2] ...
314
+
315
+ 리뷰어 추가 또는 draft 해제는 사용자가 직접 진행해주세요.
316
+ ```
317
+
318
+ commit-specific URL 형식:
319
+ ```
320
+ https://{BITBUCKET_HOST}/projects/<projectKey>/repos/<repositorySlug>/pull-requests/<prid>/commits/<full hash>#<URL-encoded file path>
321
+ ```
322
+
323
+ ---
324
+
325
+ ## 7. 함정 체크리스트 (위반 시 잘못된 PR / 잘못된 코멘트)
326
+
327
+ ### PR 생성 관련
328
+ - ❌ **`createPullRequest` 에 `reviewers` 전달 금지.** 필요하다고 판단해도 추가하지 않는다 (§4 의 규칙).
329
+ - ❌ **`draft=true` 누락 금지.** PR 은 항상 draft 로 생성한다.
330
+ - ❌ **사용자가 제공하지 않은 Jira 키·이슈 번호 추측 금지.** §1 의 `목표` 와 PR 제목에 들어가는 이슈 키는 사용자 제공 또는 브랜치명에서 추출한 것만 사용.
331
+
332
+ ### 인라인 코멘트 anchor 관련
333
+ - ❌ **`bitbucket_postPullRequestComment` MCP 도구 사용 금지.** 이 도구는 `anchor.diffType` 을 노출하지 않아 PR latest 통합 diff(`EFFECTIVE`) 로 anchor 된다. 커밋 뷰에서 노출이 안 되므로 §6.5 의 REST API 직접 호출만 사용한다.
334
+ - ❌ **`anchor.diffType` 누락 금지.** 기본값은 `EFFECTIVE` (PR latest) 다. **반드시 `"COMMIT"` 을 명시**한다.
335
+ - ❌ **`fromHash` 를 PR base (예: master tip) 로 지정 금지.** 대상 커밋의 **직전 커밋** 이어야 한다 (`git rev-parse <target>^`).
336
+ - ❌ **`toHash` 를 PR head (예: feature branch tip) 로 지정 금지.** 코멘트를 보일 **개별 커밋** 의 해시여야 한다.
337
+ - ❌ **`anchor.multilineMarker` / `anchor.multilineSpan` 사용 금지.** Bitbucket DC 가 거부한다. multiline 의도는 단일 핵심 라인 anchor + 본문에서 자연어 범위 언급으로 대체한다 (§6.4).
338
+ - ❌ **`lineType` 추측 금지.** 반드시 `git show <target> -- <path>` 또는 `git diff <parent> <target>` 의 segment 타입에서 확정.
339
+ - ❌ **file rename 시 `srcPath` 누락 금지.** anchor 에 `srcPath` 필드를 함께 전달한다.
340
+
341
+ ### 코멘트 발행 정책
342
+ - ❌ **top-level 코멘트(`anchor` 없음) 발행 금지.** PR 본문(§1) 이 이미 그 역할. 인라인 코멘트만 작성한다.
343
+ - ❌ **`severity="BLOCKER"` 사용 금지.** 자기 PR 을 자기가 차단할 이유 없음.
344
+ - ❌ **pending(draft) 코멘트 발행 금지.** §6.5 의 단순 POST 는 즉시 live 다. `pending` 필드를 false 이외로 설정하지 않는다.
345
+ - ❌ **`submitPullRequestReview` 호출 금지.** verdict 는 리뷰어가 결정한다.
346
+ - ❌ **§5 의 "작성 금지 대상" 에 해당하는 지점에 코멘트 작성 금지.** 자명한 변경에 노이즈 코멘트를 달지 않는다.
347
+
348
+ ### 보안 · 운영
349
+ - ❌ **`BITBUCKET_API_TOKEN` 노출 금지.** 응답 echo, 로그, 임시 파일, 페이로드 문자열 인라인 어디에도 토큰을 쓰지 않는다.
350
+ - ❌ **배치 정리 후 검증 누락 금지.** `set -euo pipefail` bash 루프는 한 건 실패로 중단되어 잔여물이 남을 수 있다. §6.6 의 활동 목록 재조회로 `diffType=COMMIT` 이 아닌 항목이 0 개임을 확인한다.
351
+
352
+ ---
353
+
354
+ ## 8. 실패 처리
355
+
356
+ | 상황 | 대응 |
357
+ |---|---|
358
+ | HTTP 401 / 403 | 토큰 만료 가능성. `~/.config/opencode/opencode.json` 의 `mcp.repos.environment.BITBUCKET_API_TOKEN` 갱신 안내 후 중단. |
359
+ | `createPullRequest` 실패 (브랜치 미존재) | 사용자에게 `fromRef` / `toRef` 정정 요청. 임의 브랜치명 추측 금지. |
360
+ | 코멘트 POST 응답에 `anchor.diffType = "EFFECTIVE"` | `diffType` 누락 또는 잘못된 페이로드. §6.6 으로 즉시 삭제 후 `diffType: "COMMIT"` 명시하여 재게시. |
361
+ | 코멘트 POST 응답에 `errors: [...]` + HTTP 400 | 페이로드 검증 실패. 에러 메시지 그대로 사용자에게 보고하고 수정 후 재시도. multiline 관련 에러면 §6.4 에 따라 단일 라인으로 전환. |
362
+ | 코멘트 POST 응답에 `id: null` (HTTP 200 임에도) | 페이로드는 통과했지만 서버가 코멘트를 만들지 못함. 응답 전체를 사용자에게 보고하고 페이로드 점검. |
363
+ | 대상 커밋의 diff 에 해당 라인이 없음 | 해당 지점 코멘트 스킵하고 사용자에게 보고. 가장 가까운 라인으로 대체 금지. 다른 커밋이 그 라인을 도입한 경우 §6.2 로 다시 매핑. |
364
+ | 코멘트 작성 중 일부 성공 / 일부 실패 | 성공한 것은 그대로 두고, 실패 항목 목록을 사용자에게 보고. 자동 롤백 금지. **§6.6 의 검증 쿼리로 잔여 EFFECTIVE 코멘트가 없는지 확인**. |
365
+ | bash 루프가 `set -e` 로 중간 중단 | 어디까지 진행됐는지 알 수 없음. 활동 목록 재조회로 잔여물 식별 후 개별 정리. 동일 스크립트 재실행 금지 (중복 발행 위험). |
366
+ | 3 회 연속 API 실패 | 모든 시도 중단. 로그를 정리해 사용자에게 보고. 임의 재시도 금지. |
367
+ | PR 은 만들어졌지만 모든 코멘트 작성 실패 | PR URL 은 보고하고, 코멘트는 사용자가 UI 에서 작성하거나 다시 시도하도록 안내. PR 자동 삭제 금지. |
368
+
369
+ ---
370
+
371
+ ## 9. AGENT 가 절대 하지 않는 것
372
+
373
+ - 리뷰어 자동 추가
374
+ - draft 해제 자동화
375
+ - PR 머지
376
+ - BLOCKER 코멘트 / verdict 발행
377
+ - 타인 PR 에 대한 리뷰 코멘트 작성 (이 문서는 작성자 본인의 PR 한정)
378
+ - 사용자 메시지에 없는 Jira 키·issue link 임의 삽입
379
+ - §1 포맷 외의 추가 섹션 임의 추가 (예: `#### 변경 영향`, `#### 배포 체크리스트` 등) — 사용자가 명시 요청한 경우만 추가
380
+ - 코멘트 본문에 회사 외부 링크 무단 삽입 (조직 내부 도메인만 허용)
381
+ - 토큰 노출
@@ -0,0 +1,46 @@
1
+ #!/usr/bin/env bash
2
+ # config.sh — shell-side reader for makdoong2-team.json (mirror of src/config.ts).
3
+ #
4
+ # All makdoong2-team settings live in ONE JSON file; gates/hooks read values
5
+ # from here instead of environment variables.
6
+ #
7
+ # config.sh path # absolute path to makdoong2-team.json
8
+ # config.sh dir # npm module root directory
9
+ # config.sh get <dotted.key> [default] # echo value (or default if missing/null)
10
+ #
11
+ # pkg_root() returns the npm module root by resolving the script's location.
12
+ # This ensures the script works correctly regardless of installation path.
13
+ #
14
+ # Note: `get` returns the default for null/false (jq `//`), so it is intended
15
+ # for scalar string/number keys (coverage.threshold, worktree.extra_exclude).
16
+ # Boolean toggles (tmux.enabled) are consumed by the TS plugin, not the shell.
17
+ #
18
+ # 의존: jq
19
+ set -euo pipefail
20
+
21
+ pkg_root() {
22
+ # Resolve script location and return parent directory (npm module root)
23
+ # Script is at <pkg>/scripts/config.sh, so dirname twice gives <pkg>
24
+ dirname "$(dirname "$(readlink -f "$0")")"
25
+ }
26
+
27
+ config_dir() {
28
+ pkg_root
29
+ }
30
+
31
+ CFG="${XDG_CONFIG_HOME:-$HOME/.config}/opencode/makdoong2-team.json"
32
+
33
+ cmd="${1:-}"; shift || true
34
+ case "$cmd" in
35
+ dir) config_dir ;;
36
+ path) echo "$CFG" ;;
37
+ get)
38
+ KEY="${1:?usage: config.sh get <dotted.key> [default]}"; DEF="${2:-}"
39
+ if [ -f "$CFG" ]; then
40
+ v="$(jq -r ".$KEY // empty" "$CFG" 2>/dev/null || true)"
41
+ if [ -n "$v" ]; then echo "$v"; else echo "$DEF"; fi
42
+ else
43
+ echo "$DEF"
44
+ fi ;;
45
+ *) echo "usage: config.sh {get <dotted.key> [default]|dir|path}" >&2; exit 64 ;;
46
+ esac