projectops 4.2.3 โ†’ 4.2.5

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 CHANGED
@@ -2,12 +2,12 @@
2
2
 
3
3
  # ๐Ÿš€ Projectops
4
4
 
5
- **GitHub Actions ์ž๋™ํ™” + Agent AI Skills โ€” ๊ฐœ๋ฐœ ์‚ฌ์ดํด ์ „์ฒด๋ฅผ ์ž๋™ํ™”ํ•˜๋Š” DevOps ํ…œํ”Œ๋ฆฟ (๊ตฌ SUH-DEVOPS-TEMPLATE)**
5
+ **GitHub Actions ์ž๋™ํ™” + Agent AI Skills โ€” ๊ฐœ๋ฐœ ์‚ฌ์ดํด ์ „์ฒด๋ฅผ ์ž๋™ํ™”ํ•˜๋Š” DevOps ํ…œํ”Œ๋ฆฟ**
6
6
 
7
7
  > ์ด์Šˆ ๋“ฑ๋ก๋ถ€ํ„ฐ ์ปค๋ฐ‹, ๋ณด๊ณ ์„œ, ๋ฐฐํฌ๊นŒ์ง€. ๊ฐœ๋ฐœ์ž๋Š” ์ฝ”๋“œ๋งŒ ์ž‘์„ฑํ•˜์„ธ์š”.
8
8
 
9
9
  <!-- AUTO-VERSION-SECTION: DO NOT EDIT MANUALLY -->
10
- ## ์ตœ์‹  ๋ฒ„์ „ : v4.2.2 (2026-07-09)
10
+ ## ์ตœ์‹  ๋ฒ„์ „ : v4.2.4 (2026-07-09)
11
11
 
12
12
  [์ „์ฒด ๋ฒ„์ „ ๊ธฐ๋ก ๋ณด๊ธฐ](CHANGELOG.md)
13
13
 
@@ -27,9 +27,9 @@
27
27
  | ๋ฒ„์ „ ์ˆ˜๋™ ๊ด€๋ฆฌ, ํƒœ๊ทธ ์ง์ ‘ ์ƒ์„ฑ | ๋ฆด๋ฆฌ์Šค(developโ†’main PR) ์‹œ patch ๋ฒ„์ „ ์ž๋™ ์ฆ๊ฐ€ + ํƒœ๊ทธ ์ƒ์„ฑ |
28
28
  | ์ฒด์ธ์ง€๋กœ๊ทธ ์ง์ ‘ ์ž‘์„ฑ (30๋ถ„+) | CodeRabbit AI๊ฐ€ PR๋งˆ๋‹ค ์ž๋™ ์ƒ์„ฑ |
29
29
  | CI/CD ์ฒ˜์Œ๋ถ€ํ„ฐ ์„ค์ • | ํ”„๋กœ์ ํŠธ ํƒ€์ž…๋ณ„ ์›Œํฌํ”Œ๋กœ์šฐ ์ฆ‰์‹œ ๊ตฌ์„ฑ |
30
- | ์ด์Šˆ ๋งค๋ฒˆ ํ˜•์‹ ๋งž์ถฐ ์ž‘์„ฑ (5๋ถ„+) | `/cassiiopeia:suh-issue` ํ•œ ๋ฒˆ์— ํ‘œ์ค€ ํ…œํ”Œ๋ฆฟ ์ƒ์„ฑ |
31
- | ์ปค๋ฐ‹ ๋ฉ”์‹œ์ง€ ์ด์Šˆ URL ์ˆ˜๋™ ๋ณต์‚ฌ | `/cassiiopeia:suh-commit` ์ด์Šˆ ์ปจํ…์ŠคํŠธ ๊ธฐ๋ฐ˜ ์ž๋™ ์™„์„ฑ |
32
- | PR ์„ค๋ช…/๋ณด๊ณ ์„œ ์ง์ ‘ ์ž‘์„ฑ | `/cassiiopeia:suh-report` git diff ๋ถ„์„ ํ›„ ์ž๋™ ์ƒ์„ฑ |
30
+ | ์ด์Šˆ ๋งค๋ฒˆ ํ˜•์‹ ๋งž์ถฐ ์ž‘์„ฑ (5๋ถ„+) | `/pro-issue` ํ•œ ๋ฒˆ์— ํ‘œ์ค€ ํ…œํ”Œ๋ฆฟ ์ƒ์„ฑ |
31
+ | ์ปค๋ฐ‹ ๋ฉ”์‹œ์ง€ ์ด์Šˆ URL ์ˆ˜๋™ ๋ณต์‚ฌ | `/pro-commit` ์ด์Šˆ ์ปจํ…์ŠคํŠธ ๊ธฐ๋ฐ˜ ์ž๋™ ์™„์„ฑ |
32
+ | PR ์„ค๋ช…/๋ณด๊ณ ์„œ ์ง์ ‘ ์ž‘์„ฑ | `/pro-report` git diff ๋ถ„์„ ํ›„ ์ž๋™ ์ƒ์„ฑ |
33
33
  | ์ฝ”๋“œ ๋ฆฌ๋ทฐยท๋ถ„์„ ๋งค๋ฒˆ ํ”„๋กฌํ”„ํŠธ ์ž…๋ ฅ | 25์ข… Skills๋กœ ์ผ๊ด€๋œ ๊ฒฐ๊ณผ, ๋งค๋ฒˆ ์žฌ์ž…๋ ฅ ๋ถˆํ•„์š” |
34
34
 
35
35
  ---
@@ -40,23 +40,23 @@ Agent Skills๊ฐ€ ๊ฐœ๋ฐœ ์‚ฌ์ดํด ์ „์ฒด๋ฅผ ์ปค๋ฒ„ํ•ฉ๋‹ˆ๋‹ค.
40
40
 
41
41
  ```mermaid
42
42
  flowchart TD
43
- A([์ž‘์—… ์‹œ์ž‘]) --> B["/cassiiopeia:suh-issue\n์ด์Šˆ ๋“ฑ๋ก + GitHub ์ž๋™ ์ƒ์„ฑ"]
44
- B --> C["/cassiiopeia:suh-init-worktree\nworktree + ๋ฏผ๊ฐํŒŒ์ผ ์ž๋™ ๋ณต์‚ฌ"]
43
+ A([์ž‘์—… ์‹œ์ž‘]) --> B["/pro-issue\n์ด์Šˆ ๋“ฑ๋ก + GitHub ์ž๋™ ์ƒ์„ฑ"]
44
+ B --> C["/pro-init-worktree\nworktree + ๋ฏผ๊ฐํŒŒ์ผ ์ž๋™ ๋ณต์‚ฌ"]
45
45
  C --> D{์ž‘์—… ์œ ํ˜•}
46
46
 
47
- D -->|์ƒˆ ๊ธฐ๋Šฅ| E1["/cassiiopeia:suh-plan\n์ „๋žต ์ˆ˜๋ฆฝ"]
48
- D -->|๋ฒ„๊ทธ| E2["/cassiiopeia:suh-troubleshoot\n์›์ธ ๋ถ„์„"]
49
- D -->|๋ฆฌํŒฉํ† ๋ง| E3["/cassiiopeia:suh-refactor-analyze\nSmell ํƒ์ง€"]
47
+ D -->|์ƒˆ ๊ธฐ๋Šฅ| E1["/pro-plan\n์ „๋žต ์ˆ˜๋ฆฝ"]
48
+ D -->|๋ฒ„๊ทธ| E2["/pro-troubleshoot\n์›์ธ ๋ถ„์„"]
49
+ D -->|๋ฆฌํŒฉํ† ๋ง| E3["/pro-refactor-analyze\nSmell ํƒ์ง€"]
50
50
 
51
- E1 --> F["/cassiiopeia:suh-implement\n๊ตฌํ˜„"]
51
+ E1 --> F["/pro-implement\n๊ตฌํ˜„"]
52
52
  E2 --> F
53
53
  E3 --> F
54
- F --> G["/cassiiopeia:suh-test\nํ…Œ์ŠคํŠธ"]
55
- G --> H["/cassiiopeia:suh-review\n์…€ํ”„ ๋ฆฌ๋ทฐ"]
56
- H --> I["/cassiiopeia:suh-commit\n์ด์Šˆ ์—ฐ๋™ ์ปค๋ฐ‹ ์ž๋™ ์™„์„ฑ"]
57
- I --> J["/cassiiopeia:suh-report\n๊ตฌํ˜„ ๋ณด๊ณ ์„œ + GitHub ๋Œ“๊ธ€"]
54
+ F --> G["/pro-test\nํ…Œ์ŠคํŠธ"]
55
+ G --> H["/pro-review\n์…€ํ”„ ๋ฆฌ๋ทฐ"]
56
+ H --> I["/pro-commit\n์ด์Šˆ ์—ฐ๋™ ์ปค๋ฐ‹ ์ž๋™ ์™„์„ฑ"]
57
+ I --> J["/pro-report\n๊ตฌํ˜„ ๋ณด๊ณ ์„œ + GitHub ๋Œ“๊ธ€"]
58
58
  J --> K([PR ๋“ฑ๋ก])
59
- K --> L["/cassiiopeia:suh-changelog-deploy\ndevelop push โ†’ main ๋ฆด๋ฆฌ์Šค PR + automerge"]
59
+ K --> L["/pro-changelog-deploy\ndevelop push โ†’ main ๋ฆด๋ฆฌ์Šค PR + automerge"]
60
60
  ```
61
61
 
62
62
  > Skills ์ „์ฒด ๋ชฉ๋ก ๋ฐ ์ƒ์„ธ ์‚ฌ์šฉ๋ฒ•: **[docs/SKILLS.md](docs/SKILLS.md)**
@@ -94,7 +94,9 @@ npx projectops
94
94
  > Node.js 20.12+ ๋งŒ ์žˆ์œผ๋ฉด ๋ณ„๋„ ์„ค์น˜ ์—†์ด ๋Œ€ํ™”ํ˜• ๋งˆ๋ฒ•์‚ฌ๊ฐ€ ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค. ๋น„๋Œ€ํ™”ํ˜•: `npx projectops --mode full --type spring,react --force`
95
95
 
96
96
  <details>
97
- <summary>๋Œ€์•ˆ โ€” ์Šคํฌ๋ฆฝํŠธ ์ง์ ‘ ์‹คํ–‰ (Node ์—†์ด)</summary>
97
+ <summary>๋Œ€์•ˆ โ€” ์Šคํฌ๋ฆฝํŠธ ์ง์ ‘ ์‹คํ–‰ (Node ์—†์ด, <b>deprecated</b>)</summary>
98
+
99
+ > โš ๏ธ **`template_integrator.sh` / `.ps1`์€ deprecated์ž…๋‹ˆ๋‹ค (#458).** ์‹คํ–‰ํ•˜๋ฉด `npx projectops`๋กœ ์•ˆ๋‚ดํ•˜๋ฉฐ, ๋‹ค์Œ minor์—์„œ ์ œ๊ฑฐ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. Node 20.12+๋ฅผ ์“ธ ์ˆ˜ ์žˆ์œผ๋ฉด ์œ„์˜ `npx projectops`๋ฅผ ์‚ฌ์šฉํ•˜์„ธ์š”.
98
100
 
99
101
  ```bash
100
102
  # macOS / Linux
@@ -113,7 +115,7 @@ $wc=New-Object Net.WebClient;$wc.Encoding=[Text.Encoding]::UTF8;iex $wc.Download
113
115
  ```bash
114
116
  # Claude Code
115
117
  claude plugin marketplace add Cassiiopeia/projectops
116
- claude plugin install cassiiopeia@cassiiopeia-marketplace --scope user
118
+ claude plugin install projectops@projectops-marketplace --scope user
117
119
  ```
118
120
 
119
121
  ```bash
@@ -144,7 +146,7 @@ bash <(curl -fsSL "https://raw.githubusercontent.com/Cassiiopeia/projectops/main
144
146
 
145
147
  </details>
146
148
 
147
- > Claude Code๋Š” `/cassiiopeia:` ์ž๋™์™„์„ฑ, Gemini๋Š” extension, Codex๋Š” plugin marketplace๋ฅผ ์šฐ์„  ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. ์ž์„ธํ•œ ์„ค์น˜ ๋ฐฉ์‹์€ [Skills ๊ฐ€์ด๋“œ](docs/SKILLS.md)๋ฅผ ํ™•์ธํ•˜์„ธ์š”.
149
+ > Claude Code๋Š” `/projectops:` ์ž๋™์™„์„ฑ, Gemini๋Š” extension, Codex๋Š” plugin marketplace๋ฅผ ์šฐ์„  ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. ์ž์„ธํ•œ ์„ค์น˜ ๋ฐฉ์‹์€ [Skills ๊ฐ€์ด๋“œ](docs/SKILLS.md)๋ฅผ ํ™•์ธํ•˜์„ธ์š”.
148
150
 
149
151
  ---
150
152
 
@@ -169,46 +171,46 @@ bash <(curl -fsSL "https://raw.githubusercontent.com/Cassiiopeia/projectops/main
169
171
 
170
172
  | ์Šคํ‚ฌ | ์šฉ๋„ |
171
173
  |------|------|
172
- | `/cassiiopeia:suh-issue` | ์„ค๋ช… ํ•œ ์ค„ โ†’ GitHub ์ด์Šˆ ํ…œํ”Œ๋ฆฟ ์ž๋™ ์ž‘์„ฑ + ๋“ฑ๋ก |
173
- | `/cassiiopeia:suh-init-worktree` | Git worktree ์ƒ์„ฑ + ๋ฏผ๊ฐ ํŒŒ์ผ ์ž๋™ ๋ณต์‚ฌ |
174
- | `/cassiiopeia:suh-commit` | ์ด์Šˆ ์ปจํ…์ŠคํŠธ ๊ธฐ๋ฐ˜ ์ปค๋ฐ‹ ๋ฉ”์‹œ์ง€ ์ž๋™ ์™„์„ฑ (superpowers ์ค€์ˆ˜) |
175
- | `/cassiiopeia:suh-report` | git diff ๋ถ„์„ โ†’ ๊ตฌํ˜„ ๋ณด๊ณ ์„œ ์ƒ์„ฑ + GitHub ๋Œ“๊ธ€ ์ž๋™ ํฌ์ŠคํŒ… |
176
- | `/cassiiopeia:suh-changelog-deploy` | develop push โ†’ main ๋ฆด๋ฆฌ์Šค PR ์ƒ์„ฑ + ๋ฆด๋ฆฌ์Šค ๋…ธํŠธ ์ž‘์„ฑ + automerge |
177
- | `/cassiiopeia:suh-github` | GitHub ์ด์Šˆ/PR/๋Œ“๊ธ€ ๋…๋ฆฝ ์กฐํšŒ ๋ฐ ๊ด€๋ฆฌ |
174
+ | `/pro-issue` | ์„ค๋ช… ํ•œ ์ค„ โ†’ GitHub ์ด์Šˆ ํ…œํ”Œ๋ฆฟ ์ž๋™ ์ž‘์„ฑ + ๋“ฑ๋ก |
175
+ | `/pro-init-worktree` | Git worktree ์ƒ์„ฑ + ๋ฏผ๊ฐ ํŒŒ์ผ ์ž๋™ ๋ณต์‚ฌ |
176
+ | `/pro-commit` | ์ด์Šˆ ์ปจํ…์ŠคํŠธ ๊ธฐ๋ฐ˜ ์ปค๋ฐ‹ ๋ฉ”์‹œ์ง€ ์ž๋™ ์™„์„ฑ (superpowers ์ค€์ˆ˜) |
177
+ | `/pro-report` | git diff ๋ถ„์„ โ†’ ๊ตฌํ˜„ ๋ณด๊ณ ์„œ ์ƒ์„ฑ + GitHub ๋Œ“๊ธ€ ์ž๋™ ํฌ์ŠคํŒ… |
178
+ | `/pro-changelog-deploy` | develop push โ†’ main ๋ฆด๋ฆฌ์Šค PR ์ƒ์„ฑ + ๋ฆด๋ฆฌ์Šค ๋…ธํŠธ ์ž‘์„ฑ + automerge |
179
+ | `/pro-github` | GitHub ์ด์Šˆ/PR/๋Œ“๊ธ€ ๋…๋ฆฝ ์กฐํšŒ ๋ฐ ๊ด€๋ฆฌ |
178
180
 
179
181
  ### ๐Ÿ“Š ๋ถ„์„ํ˜• (์ฝ”๋“œ ์ˆ˜์ • ์—†์Œ)
180
182
 
181
183
  | ์Šคํ‚ฌ | ์šฉ๋„ |
182
184
  |------|------|
183
- | `/cassiiopeia:suh-analyze` | ๊ตฌํ˜„ ์ „ ํ˜„์žฌ ์ฝ”๋“œ ์ƒํƒœ ๋ถ„์„ ๋ฐ ์˜ํ–ฅ ๋ฒ”์œ„ ํ‰๊ฐ€ |
184
- | `/cassiiopeia:suh-plan` | ์š”๊ตฌ์‚ฌํ•ญ ๋ช…ํ™•ํ™” + 2๊ฐ€์ง€ ์ด์ƒ ์ ‘๊ทผ ๋ฐฉ์‹ ๋น„๊ต๋กœ ์ „๋žต ์ˆ˜๋ฆฝ |
185
- | `/cassiiopeia:suh-design-analyze` | ์•„ํ‚คํ…์ฒ˜/API/DB/UI ์„ค๊ณ„ ๋ถ„์„ (๊ตฌํ˜„ X) |
186
- | `/cassiiopeia:suh-refactor-analyze` | Code Smell ํƒ์ง€ + Before/After ๊ธฐ๋ฐ˜ ๋ฆฌํŒฉํ† ๋ง ๊ณ„ํš |
187
- | `/cassiiopeia:suh-review` | ๋ณด์•ˆ/์„ฑ๋Šฅ/๋ฒ„๊ทธ/ํ’ˆ์งˆ 6๊ด€์  ๋ฆฌ๋ทฐ, Critical/Major/Minor ๋ถ„๋ฅ˜ |
188
- | `/cassiiopeia:suh-troubleshoot` | ๊ฐ€์„ค-๊ฒ€์ฆ ๋ฐฉ์‹ ๊ทผ๋ณธ ์›์ธ ๋ถ„์„, Quick Fix/Root Fix ์ œ์‹œ |
185
+ | `/pro-analyze` | ๊ตฌํ˜„ ์ „ ํ˜„์žฌ ์ฝ”๋“œ ์ƒํƒœ ๋ถ„์„ ๋ฐ ์˜ํ–ฅ ๋ฒ”์œ„ ํ‰๊ฐ€ |
186
+ | `/pro-plan` | ์š”๊ตฌ์‚ฌํ•ญ ๋ช…ํ™•ํ™” + 2๊ฐ€์ง€ ์ด์ƒ ์ ‘๊ทผ ๋ฐฉ์‹ ๋น„๊ต๋กœ ์ „๋žต ์ˆ˜๋ฆฝ |
187
+ | `/pro-design-analyze` | ์•„ํ‚คํ…์ฒ˜/API/DB/UI ์„ค๊ณ„ ๋ถ„์„ (๊ตฌํ˜„ X) |
188
+ | `/pro-refactor-analyze` | Code Smell ํƒ์ง€ + Before/After ๊ธฐ๋ฐ˜ ๋ฆฌํŒฉํ† ๋ง ๊ณ„ํš |
189
+ | `/pro-review` | ๋ณด์•ˆ/์„ฑ๋Šฅ/๋ฒ„๊ทธ/ํ’ˆ์งˆ 6๊ด€์  ๋ฆฌ๋ทฐ, Critical/Major/Minor ๋ถ„๋ฅ˜ |
190
+ | `/pro-troubleshoot` | ๊ฐ€์„ค-๊ฒ€์ฆ ๋ฐฉ์‹ ๊ทผ๋ณธ ์›์ธ ๋ถ„์„, Quick Fix/Root Fix ์ œ์‹œ |
189
191
 
190
192
  ### ๐Ÿ”ง ๊ตฌํ˜„ํ˜• (์‹ค์ œ ์ฝ”๋“œ ์ž‘์„ฑ)
191
193
 
192
194
  | ์Šคํ‚ฌ | ์šฉ๋„ |
193
195
  |------|------|
194
- | `/cassiiopeia:suh-implement` | ๊ณ„ํš/๋ถ„์„ ๊ฒฐ๊ณผ ๊ธฐ๋ฐ˜ ์ฝ”๋“œ ๊ตฌํ˜„ (๊ธฐ์กด ์Šคํƒ€์ผ 100% ์ค€์ˆ˜) |
195
- | `/cassiiopeia:suh-design` | ์•„ํ‚คํ…์ฒ˜/API/DB/UI ์„ค๊ณ„ + ๊ตฌํ˜„๊นŒ์ง€ |
196
- | `/cassiiopeia:suh-refactor` | Extract Method, DRY ๋“ฑ ๋ฆฌํŒฉํ† ๋ง ๊ธฐ๋ฒ• ๋‹จ๊ณ„๋ณ„ ์ ์šฉ |
197
- | `/cassiiopeia:suh-test` | AAA ํŒจํ„ด ๋‹จ์œ„/ํ†ตํ•ฉ/E2E ํ…Œ์ŠคํŠธ ์ฝ”๋“œ ์ž‘์„ฑ |
198
- | `/cassiiopeia:suh-figma` | Figma CSS โ†’ React/RN/Flutter ๋ฐ˜์‘ํ˜• ์ฝ”๋“œ ๋ณ€ํ™˜ |
199
- | `/cassiiopeia:suh-build` | ํ”„๋กœ์ ํŠธ ๋นŒ๋“œ ์‹คํ–‰, ์—๋Ÿฌ ๋ถ„์„, ์ตœ์ ํ™” ์ œ์•ˆ |
196
+ | `/pro-implement` | ๊ณ„ํš/๋ถ„์„ ๊ฒฐ๊ณผ ๊ธฐ๋ฐ˜ ์ฝ”๋“œ ๊ตฌํ˜„ (๊ธฐ์กด ์Šคํƒ€์ผ 100% ์ค€์ˆ˜) |
197
+ | `/pro-design` | ์•„ํ‚คํ…์ฒ˜/API/DB/UI ์„ค๊ณ„ + ๊ตฌํ˜„๊นŒ์ง€ |
198
+ | `/pro-refactor` | Extract Method, DRY ๋“ฑ ๋ฆฌํŒฉํ† ๋ง ๊ธฐ๋ฒ• ๋‹จ๊ณ„๋ณ„ ์ ์šฉ |
199
+ | `/pro-test` | AAA ํŒจํ„ด ๋‹จ์œ„/ํ†ตํ•ฉ/E2E ํ…Œ์ŠคํŠธ ์ฝ”๋“œ ์ž‘์„ฑ |
200
+ | `/pro-figma` | Figma CSS โ†’ React/RN/Flutter ๋ฐ˜์‘ํ˜• ์ฝ”๋“œ ๋ณ€ํ™˜ |
201
+ | `/pro-build` | ํ”„๋กœ์ ํŠธ ๋นŒ๋“œ ์‹คํ–‰, ์—๋Ÿฌ ๋ถ„์„, ์ตœ์ ํ™” ์ œ์•ˆ |
200
202
 
201
203
  ### ๐Ÿ“ ๋ฌธ์„œ/์‚ฐ์ถœ๋ฌผ ์ƒ์„ฑํ˜•
202
204
 
203
205
  | ์Šคํ‚ฌ | ์šฉ๋„ |
204
206
  |------|------|
205
- | `/cassiiopeia:suh-document` | ์ฝ”๋“œ ์ฃผ์„/README/API ๋ฌธ์„œ ์ž‘์„ฑ |
206
- | `/cassiiopeia:suh-testcase` | ์ด์Šˆ ๋ถ„์„ โ†’ QA ์ฒดํฌ๋ฆฌ์ŠคํŠธ ์ƒ์„ฑ |
207
- | `/cassiiopeia:suh-ppt` | ํŠธ๋Ÿฌ๋ธ”์ŠˆํŒ…/๊ตฌํ˜„ ์‚ฌ๋ก€ โ†’ 5์„น์…˜ ๋ฐœํ‘œ์ž๋ฃŒ |
208
- | `/cassiiopeia:suh-spring-test` | Spring Boot ํ…Œ์ŠคํŠธ ์ƒ˜ํ”Œ ์ฝ”๋“œ ์ƒ์„ฑ |
209
- | `/cassiiopeia:suh-synology-expose` | Synology NAS ์™ธ๋ถ€ ๋„๋ฉ”์ธ ๋…ธ์ถœ ์„ค์ • ๊ฐ€์ด๋“œ |
210
- | `/cassiiopeia:suh-ssh` | ์›๊ฒฉ ์„œ๋ฒ„ SSH ์ ‘์†ยท๋ช…๋ น ์‹คํ–‰ (AWS EC2, ์‹œ๋†€๋กœ์ง€ NAS, Linux ๋“ฑ ๋ฒ”์šฉ) |
211
- | `/cassiiopeia:suh-skill-creator` | Skill ์ƒ์„ฑ/๋ฆฌ๋ทฐ/๊ฐœ์„  (CREATEยทREVIEWยทIMPROVE 3๋ชจ๋“œ) |
207
+ | `/pro-document` | ์ฝ”๋“œ ์ฃผ์„/README/API ๋ฌธ์„œ ์ž‘์„ฑ |
208
+ | `/pro-testcase` | ์ด์Šˆ ๋ถ„์„ โ†’ QA ์ฒดํฌ๋ฆฌ์ŠคํŠธ ์ƒ์„ฑ |
209
+ | `/pro-ppt` | ํŠธ๋Ÿฌ๋ธ”์ŠˆํŒ…/๊ตฌํ˜„ ์‚ฌ๋ก€ โ†’ 5์„น์…˜ ๋ฐœํ‘œ์ž๋ฃŒ |
210
+ | `/pro-spring-test` | Spring Boot ํ…Œ์ŠคํŠธ ์ƒ˜ํ”Œ ์ฝ”๋“œ ์ƒ์„ฑ |
211
+ | `/pro-synology-expose` | Synology NAS ์™ธ๋ถ€ ๋„๋ฉ”์ธ ๋…ธ์ถœ ์„ค์ • ๊ฐ€์ด๋“œ |
212
+ | `/pro-ssh` | ์›๊ฒฉ ์„œ๋ฒ„ SSH ์ ‘์†ยท๋ช…๋ น ์‹คํ–‰ (AWS EC2, ์‹œ๋†€๋กœ์ง€ NAS, Linux ๋“ฑ ๋ฒ”์šฉ) |
213
+ | `/pro-skill-creator` | Skill ์ƒ์„ฑ/๋ฆฌ๋ทฐ/๊ฐœ์„  (CREATEยทREVIEWยทIMPROVE 3๋ชจ๋“œ) |
212
214
 
213
215
  ---
214
216
 
@@ -233,13 +235,13 @@ Issue๋‚˜ PR์— ๋Œ“๊ธ€๋กœ ์ž๋™ํ™”๋ฅผ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค.
233
235
 
234
236
  | ๋ช…๋ น์–ด | ๊ธฐ๋Šฅ | ๋Œ€์ƒ |
235
237
  |--------|------|------|
236
- | `@suh-lab server build` | ์ž„์‹œ ์„œ๋ฒ„ ๋ฐฐํฌ | Spring, Python |
237
- | `@suh-lab server destroy` | ์„œ๋ฒ„ ์‚ญ์ œ | Spring, Python |
238
- | `@suh-lab server status` | ์„œ๋ฒ„ ์ƒํƒœ ํ™•์ธ | Spring, Python |
239
- | `@suh-lab build app` | iOS + Android ๋นŒ๋“œ | Flutter |
240
- | `@suh-lab apk build` | Android๋งŒ ๋นŒ๋“œ | Flutter |
241
- | `@suh-lab ios build` | iOS๋งŒ ๋นŒ๋“œ | Flutter |
242
- | `@suh-lab create qa` | QA ์ด์Šˆ ์ž๋™ ์ƒ์„ฑ | ๋ชจ๋“  ํ”„๋กœ์ ํŠธ |
238
+ | `@projectops server build` | ์ž„์‹œ ์„œ๋ฒ„ ๋ฐฐํฌ | Spring, Python |
239
+ | `@projectops server destroy` | ์„œ๋ฒ„ ์‚ญ์ œ | Spring, Python |
240
+ | `@projectops server status` | ์„œ๋ฒ„ ์ƒํƒœ ํ™•์ธ | Spring, Python |
241
+ | `@projectops build app` | iOS + Android ๋นŒ๋“œ | Flutter |
242
+ | `@projectops apk build` | Android๋งŒ ๋นŒ๋“œ | Flutter |
243
+ | `@projectops ios build` | iOS๋งŒ ๋นŒ๋“œ | Flutter |
244
+ | `@projectops create qa` | QA ์ด์Šˆ ์ž๋™ ์ƒ์„ฑ | ๋ชจ๋“  ํ”„๋กœ์ ํŠธ |
243
245
 
244
246
  > ์ƒ์„ธ: [PR Preview](docs/PR-PREVIEW.md) | [Flutter ๋นŒ๋“œ](docs/FLUTTER-TEST-BUILD-TRIGGER.md) | [์ด์Šˆ ์ž๋™ํ™”](docs/ISSUE-AUTOMATION.md)
245
247
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "projectops",
3
- "version": "4.2.3",
4
- "description": "ProjectOps โ€” ์™„์ „ ์ž๋™ํ™” GitHub ํ”„๋กœ์ ํŠธ ๊ด€๋ฆฌ ํ…œํ”Œ๋ฆฟ ํ†ตํ•ฉ CLI (๊ตฌ SUH-DEVOPS-TEMPLATE ๋งˆ๋ฒ•์‚ฌ)",
3
+ "version": "4.2.5",
4
+ "description": "ProjectOps โ€” ์™„์ „ ์ž๋™ํ™” GitHub ํ”„๋กœ์ ํŠธ ๊ด€๋ฆฌ ํ…œํ”Œ๋ฆฟ ํ†ตํ•ฉ CLI",
5
5
  "keywords": [
6
6
  "devops",
7
7
  "automation",
package/src/cli/args.js CHANGED
@@ -13,6 +13,7 @@ export function parseArgs(argv) {
13
13
  primaryType: "",
14
14
  deployTarget: null, // ๋ฐฐํฌ ์ถ• (#439): docker-ssh|vercel|none, null=๋ฏธ์„ค์ •
15
15
  publishTargets: null, // publish ์ถ• (#439): ํƒ€๊ฒŸ ๋ฐฐ์—ด, null=๋ฏธ์„ค์ •
16
+ deployBranch: "", // ๋ฆด๋ฆฌ์Šค PR head ๋ธŒ๋žœ์น˜ (#456): --deploy-branch, ๋นˆ ๊ฐ’=๋ฏธ์ง€์ •
16
17
  includeSecretBackup: null,
17
18
  pathsCsv: "", // "flutter=app,react=client" ์›๋ฌธ (์ •๊ทœํ™”๋Š” resolve ๋‹จ๊ณ„)
18
19
  force: false,
@@ -72,6 +73,12 @@ export function parseArgs(argv) {
72
73
  result.publishTargets = targets;
73
74
  break;
74
75
  }
76
+ case "--deploy-branch": {
77
+ // ๋ฆด๋ฆฌ์Šค PR head ๋ธŒ๋žœ์น˜ (#456). default_branch์™€ ๋ณ„๊ฐœ.
78
+ const v = (args.shift() ?? "").trim();
79
+ if (v) result.deployBranch = v;
80
+ break;
81
+ }
75
82
  // โ”€โ”€ deprecated alias (1 minor ์œ ์ง€ โ€” #439) โ”€โ”€
76
83
  case "--nexus":
77
84
  process.stderr.write("โš ๏ธ --nexus๋Š” deprecated์ž…๋‹ˆ๋‹ค. --publish nexus --deploy none ์„ ์‚ฌ์šฉํ•˜์„ธ์š”.\n");
package/src/cli/help.js CHANGED
@@ -15,6 +15,7 @@ export const HELP_TEXT = `projectops โ€” GitHub ํ”„๋กœ์ ํŠธ ์ž๋™ํ™” ํ…œํ”Œ๋ฆฟ
15
15
  --paths "t=p,..." ํƒ€์ž…๋ณ„ ํ”„๋กœ์ ํŠธ ๊ฒฝ๋กœ (๋ชจ๋…ธ๋ ˆํฌ). ์˜ˆ: flutter=app,react=client
16
16
  --deploy TARGET ๋ฐฐํฌ ๋ฐฉ์‹ ํƒ1: docker-ssh(๊ธฐ๋ณธ) | vercel | none
17
17
  --publish CSV publish ํƒ€๊ฒŸ csv: nexus,npm,github-packages (๊ธฐ๋ณธ: ์—†์Œ)
18
+ --deploy-branch NAME ๋ฆด๋ฆฌ์Šค PR head ๋ธŒ๋žœ์น˜ (#456, ๊ธฐ๋ณธ: develop). default_branch์™€ ๋ณ„๊ฐœ
18
19
  --secret-backup / --no-secret-backup Secret ๋ฐฑ์—… ์›Œํฌํ”Œ๋กœ์šฐ ํฌํ•จ/์ œ์™ธ
19
20
  --nexus / --npm-publish (deprecated โ€” --publish nexus / --publish npm ์‚ฌ์šฉ)
20
21
  --force ๋ชจ๋“  ํ™•์ธ ์ƒ๋žต, ๋น„๋Œ€ํ™”ํ˜• ๊ธฐ๋ณธ๊ฐ’ ์‚ฌ์šฉ
@@ -22,7 +22,9 @@ import { ensureGitignore } from "../core/copy/gitignore.js";
22
22
  export function runFull(context, tempDir, targetRoot = ".", hooks = {}) {
23
23
  const { version, types = [], paths = new Map(), branch = "main", versionCode = 1,
24
24
  force = true, now, today, templateVersion = "unknown",
25
- deployTarget = "docker-ssh", publishTargets = [], includeSecretBackup = false } = context;
25
+ deployTarget = "docker-ssh", publishTargets = [], includeSecretBackup = false,
26
+ changelogProvider = "github-ai", changelogBaseUrl = "", codeReviewCoderabbit = true,
27
+ deployBranch = "" } = context;
26
28
 
27
29
  // project_paths ๋งˆ์ปค ๊ณ„์‚ฐ (.sh existing_marker_in_dir ๋“ฑ๊ฐ€ โ€” ๋Œ€ํ‘œ ๋งˆ์ปค๋ช…)
28
30
  const pathMarkers = new Map();
@@ -36,9 +38,10 @@ export function runFull(context, tempDir, targetRoot = ".", hooks = {}) {
36
38
  // 1. version.yml ์ƒ์„ฑ (์ „์ฒด ์žฌ์ƒ์„ฑ โ€” metadata โ†’ deploy โ†’ template ์ˆœ, .sh ์ตœ์ข…ํ˜•๊ณผ ๋™์ผ)
37
39
  writeText(join(targetRoot, PATHS.versionFile),
38
40
  buildVersionYml({
39
- version, types, paths, pathMarkers, branch, versionCode, now, today,
41
+ version, types, paths, pathMarkers, branch, deployBranch, versionCode, now, today,
40
42
  deployValues,
41
- templateOptions: { templateVersion, deployTarget, publishTargets, includeSecretBackup, optionsDate: today },
43
+ templateOptions: { templateVersion, deployTarget, publishTargets, includeSecretBackup, optionsDate: today,
44
+ changelogProvider, changelogBaseUrl, codeReviewCoderabbit },
42
45
  }));
43
46
 
44
47
  // 2. README ๋ฒ„์ „ ์„น์…˜
@@ -56,7 +59,8 @@ export function runFull(context, tempDir, targetRoot = ".", hooks = {}) {
56
59
  copyDiscussionTemplates(tempDir, targetRoot);
57
60
 
58
61
  // 8. coderabbit / gitignore / setup guide
59
- copyCoderabbit(tempDir, { force }, targetRoot);
62
+ // CodeRabbit ์ฝ”๋“œ๋ฆฌ๋ทฐ ๋ฏธ์‚ฌ์šฉ ์„ ํƒ(#457)์ด๋ฉด .coderabbit.yaml์„ ๋ณต์‚ฌํ•˜์ง€ ์•Š๋Š”๋‹ค.
63
+ copyCoderabbit(tempDir, { force, enabled: codeReviewCoderabbit }, targetRoot);
60
64
  ensureGitignore(targetRoot);
61
65
  copySetupGuide(tempDir, targetRoot);
62
66
 
@@ -83,6 +83,10 @@ export async function runInteractive(baseCtx, { cwd = process.cwd(), source = {
83
83
  let deployTarget = existing?.options?.deploy ?? "docker-ssh";
84
84
  let publishTargets = existing?.options?.publish ?? [];
85
85
  let includeSecretBackup = existing?.options?.secretBackup ?? false;
86
+ let codeReviewCoderabbit = existing?.options?.codeReviewCoderabbit ?? true;
87
+ let changelogProvider = existing?.options?.changelogProvider ?? "github-ai";
88
+ let changelogBaseUrl = existing?.options?.changelogBaseUrl ?? "";
89
+ let deployBranch = existing?.options?.deployBranch ?? "develop"; // #456
86
90
  const showOptional = mode === "full" || mode === "workflows";
87
91
  const realTty = process.stdout.isTTY === true;
88
92
 
@@ -93,17 +97,28 @@ export async function runInteractive(baseCtx, { cwd = process.cwd(), source = {
93
97
  if (showOptional) {
94
98
  const r = await askAllOptionalWorkflows({
95
99
  tempDir, types, targetRoot: cwd,
96
- current: { deploy: existing?.options?.deploy ?? null, publish: existing?.options?.publish ?? null, secretBackup: existing?.options?.secretBackup ?? null },
100
+ current: {
101
+ deploy: existing?.options?.deploy ?? null, publish: existing?.options?.publish ?? null, secretBackup: existing?.options?.secretBackup ?? null,
102
+ codeReviewCoderabbit: existing?.options?.codeReviewCoderabbit ?? null,
103
+ changelogProvider: existing?.options?.changelogProvider ?? null,
104
+ changelogBaseUrl: existing?.options?.changelogBaseUrl ?? null,
105
+ deployBranch: existing?.options?.deployBranch ?? null,
106
+ },
97
107
  force: false, tty: realTty,
98
108
  io: {
99
109
  confirm: ({ message, initialValue }) => io.askYesNo(message, initialValue),
100
110
  select: io.engineIo?.select,
101
111
  multiselect: io.engineIo?.multiselect,
112
+ text: io.engineIo?.text ?? (({ message }) => io.askText?.(message, "")),
102
113
  },
103
114
  });
104
115
  deployTarget = r.deploy;
105
116
  publishTargets = r.publish;
106
117
  includeSecretBackup = r.secretBackup;
118
+ codeReviewCoderabbit = r.codeReviewCoderabbit;
119
+ changelogProvider = r.changelogProvider;
120
+ changelogBaseUrl = r.changelogBaseUrl;
121
+ deployBranch = r.deployBranch;
107
122
  }
108
123
 
109
124
  // ํ™•์ธ/์ˆ˜์ • ๋ฃจํ”„ โ€” ESC๋Š” '๋จธ๋ฌด๋ฅด๊ธฐ' (.sh L1877~1881: ๋ช…์‹œ์  '์•„๋‹ˆ์˜ค'๋งŒ ์ข…๋ฃŒ)
@@ -114,7 +129,7 @@ export async function runInteractive(baseCtx, { cwd = process.cwd(), source = {
114
129
  if (io.analysisCard) {
115
130
  io.analysisCard({ mode, modeLabel: modeLabel(mode), types, version, branch, deployTarget, publishTargets, includeSecretBackup, showOptional, paths });
116
131
  } else {
117
- io.note?.(summarize({ mode, types, version, branch, deployTarget, publishTargets, includeSecretBackup, showOptional }), "ํ”„๋กœ์ ํŠธ ๋ถ„์„ ๊ฒฐ๊ณผ");
132
+ io.note?.(summarize({ mode, types, version, branch, deployTarget, publishTargets, includeSecretBackup, showOptional, changelogProvider, codeReviewCoderabbit }), "ํ”„๋กœ์ ํŠธ ๋ถ„์„ ๊ฒฐ๊ณผ");
118
133
  }
119
134
  const choice = await io.confirmProjectMenu();
120
135
  if (choice === "cancel") { io.cancelMessage?.("์„ค์น˜๋ฅผ ์ทจ์†Œํ–ˆ์Šต๋‹ˆ๋‹ค."); return 0; }
@@ -147,17 +162,25 @@ export async function runInteractive(baseCtx, { cwd = process.cwd(), source = {
147
162
  // ๋ฐฐํฌ/publish ์ถ• ์žฌ์งˆ๋ฌธ (#439 โ€” forceAsk)
148
163
  const r = await askAllOptionalWorkflows({
149
164
  tempDir, types, targetRoot: cwd,
150
- current: { deploy: deployTarget, publish: publishTargets, secretBackup: includeSecretBackup },
165
+ current: {
166
+ deploy: deployTarget, publish: publishTargets, secretBackup: includeSecretBackup,
167
+ codeReviewCoderabbit, changelogProvider, changelogBaseUrl, deployBranch,
168
+ },
151
169
  force: false, tty: realTty, forceAsk: true,
152
170
  io: {
153
171
  confirm: ({ message, initialValue }) => io.askYesNo(message, initialValue),
154
172
  select: io.engineIo?.select,
155
173
  multiselect: io.engineIo?.multiselect,
174
+ text: io.engineIo?.text ?? (({ message }) => io.askText?.(message, "")),
156
175
  },
157
176
  });
158
177
  deployTarget = r.deploy;
159
178
  publishTargets = r.publish;
160
179
  includeSecretBackup = r.secretBackup;
180
+ codeReviewCoderabbit = r.codeReviewCoderabbit;
181
+ changelogProvider = r.changelogProvider;
182
+ changelogBaseUrl = r.changelogBaseUrl;
183
+ deployBranch = r.deployBranch;
161
184
  } else if (what === "secret") {
162
185
  const y = await io.askYesNo("Secret ๋ฐฑ์—… ์›Œํฌํ”Œ๋กœ์šฐ๋ฅผ ํฌํ•จํ• ๊นŒ์š”?", includeSecretBackup);
163
186
  if (!isCancel(y)) includeSecretBackup = y === true;
@@ -190,6 +213,7 @@ export async function runInteractive(baseCtx, { cwd = process.cwd(), source = {
190
213
  const { now, today } = clock || utcNow();
191
214
  const ctx = createContext({
192
215
  mode, force: true, types, version, versionCode, branch, paths, deployTarget, publishTargets, includeSecretBackup,
216
+ codeReviewCoderabbit, changelogProvider, changelogBaseUrl, deployBranch,
193
217
  repoName, templateVersion, resolvers, envValues, envUseDefaults, now, today,
194
218
  });
195
219
  ctx.templateVersion = templateVersion;
@@ -240,7 +264,7 @@ export async function runInteractive(baseCtx, { cwd = process.cwd(), source = {
240
264
  }
241
265
  }
242
266
 
243
- function summarize({ mode, types, version, branch, deployTarget, publishTargets, includeSecretBackup, showOptional }) {
267
+ function summarize({ mode, types, version, branch, deployTarget, publishTargets, includeSecretBackup, showOptional, changelogProvider, codeReviewCoderabbit }) {
244
268
  const lines = [
245
269
  `ํ†ตํ•ฉ ๋ชจ๋“œ : ${modeLabel(mode)}`,
246
270
  `ํ”„๋กœ์ ํŠธ ํƒ€์ž… : ${types.join(", ")}${types.length > 1 ? " (๋ฉ€ํ‹ฐ)" : ""}`,
@@ -251,6 +275,8 @@ function summarize({ mode, types, version, branch, deployTarget, publishTargets,
251
275
  lines.push(`๋ฐฐํฌ ๋ฐฉ์‹ : ${deployTarget || "docker-ssh"}`);
252
276
  lines.push(`Publish : ${(publishTargets ?? []).join(",") || "์—†์Œ"}`);
253
277
  lines.push(`Secret ๋ฐฑ์—… : ${includeSecretBackup ? "ํฌํ•จ" : "์ œ์™ธ"}`);
278
+ lines.push(`Changelog : ${changelogProvider || "github-ai"}`);
279
+ lines.push(`CodeRabbit ๋ฆฌ๋ทฐ : ${codeReviewCoderabbit ? "์‚ฌ์šฉ" : "๋ฏธ์‚ฌ์šฉ"}`);
254
280
  }
255
281
  return lines.join("\n");
256
282
  }
@@ -12,15 +12,18 @@ import { ensureGitignore } from "../core/copy/gitignore.js";
12
12
 
13
13
  export function runVersion(context, tempDir, targetRoot = ".") {
14
14
  const { version, types = [], paths = new Map(), branch = "main", versionCode = 1,
15
- now, today, templateVersion = "unknown", deployTarget = "docker-ssh", publishTargets = [], includeSecretBackup = false } = context;
15
+ now, today, templateVersion = "unknown", deployTarget = "docker-ssh", publishTargets = [], includeSecretBackup = false,
16
+ changelogProvider = "github-ai", changelogBaseUrl = "", codeReviewCoderabbit = true,
17
+ deployBranch = "" } = context;
16
18
 
17
19
  const pathMarkers = new Map();
18
20
  for (const [t] of paths) pathMarkers.set(t, markerForType(t));
19
21
 
20
22
  writeText(join(targetRoot, PATHS.versionFile),
21
23
  buildVersionYml({
22
- version, types, paths, pathMarkers, branch, versionCode, now, today,
23
- templateOptions: { templateVersion, deployTarget, publishTargets, includeSecretBackup, optionsDate: today },
24
+ version, types, paths, pathMarkers, branch, deployBranch, versionCode, now, today,
25
+ templateOptions: { templateVersion, deployTarget, publishTargets, includeSecretBackup, optionsDate: today,
26
+ changelogProvider, changelogBaseUrl, codeReviewCoderabbit },
24
27
  }));
25
28
  addVersionSectionToReadme(version, targetRoot);
26
29
  copyScripts(tempDir, targetRoot);
package/src/context.js CHANGED
@@ -19,6 +19,11 @@ export function createContext(overrides = {}) {
19
19
  deployTarget: null, // 'docker-ssh'(๊ธฐ๋ณธ) | 'vercel' | 'none'
20
20
  publishTargets: null, // ['nexus','npm','github-packages'] ๋ถ€๋ถ„์ง‘ํ•ฉ
21
21
  includeSecretBackup: null,
22
+ // changelog provider ์ถ• (#455 โ€” null=๋ฏธ์„ค์ •)
23
+ changelogProvider: null, // 'github-ai'(๊ธฐ๋ณธ) | 'coderabbit' | 'openai' | 'gemini' | 'claude' | 'ollama' | 'commit'
24
+ changelogBaseUrl: null, // ollama์ผ ๋•Œ๋งŒ ๊ฐ’
25
+ codeReviewCoderabbit: null,
26
+ deployBranch: "", // ๋ฆด๋ฆฌ์Šค PR head ๋ธŒ๋žœ์น˜ (#456). ๋นˆ ๊ฐ’=metadata.deploy_branch ๋ฏธ์ถœ๋ ฅ
22
27
  templateVersion: "",
23
28
  tempDir: "",
24
29
  deployValues: new Map(), // "type.KEY" -> value
@@ -3,9 +3,12 @@
3
3
  import { join } from "node:path";
4
4
  import { exists, copyFileSync } from "../fsutil.js";
5
5
 
6
- // ๋ฐ˜ํ™˜: 'skip-no-src' | 'copied-new' | 'overwritten-backup' | 'skip-non-tty'
7
- // opts: { force, tty } โ€” SP2-B ๊ฒ€์ฆ์€ force:true ๊ฒฝ๋กœ.
8
- export function copyCoderabbit(tempDir, { force = false, tty = false } = {}, targetRoot = ".") {
6
+ // ๋ฐ˜ํ™˜: 'skip-disabled' | 'skip-no-src' | 'copied-new' | 'overwritten-backup' | 'skip-non-tty'
7
+ // opts: { force, tty, enabled } โ€” SP2-B ๊ฒ€์ฆ์€ force:true ๊ฒฝ๋กœ.
8
+ // enabled=false๋ฉด(#457 CodeRabbit ๋ฏธ์‚ฌ์šฉ ์„ ํƒ) ํŒŒ์ผ์„ ์•„์˜ˆ ๋ณต์‚ฌํ•˜์ง€ ์•Š๋Š”๋‹ค.
9
+ // enabled ๋ฏธ์ง€์ •(undefined)์ด๋ฉด ๊ธฐ์กด ๋™์ž‘(๋ณต์‚ฌ) ์œ ์ง€ โ€” ๊ตฌ ํ˜ธ์ถœ๋ถ€ ํ•˜์œ„ํ˜ธํ™˜.
10
+ export function copyCoderabbit(tempDir, { force = false, tty = false, enabled } = {}, targetRoot = ".") {
11
+ if (enabled === false) return "skip-disabled";
9
12
  const src = join(tempDir, ".coderabbit.yaml");
10
13
  if (!exists(src)) return "skip-no-src";
11
14
  const dst = join(targetRoot, ".coderabbit.yaml");
@@ -50,8 +50,8 @@ export function copyDiscussionTemplates(tempDir, targetRoot = ".") {
50
50
  return true;
51
51
  }
52
52
 
53
- // SUH-DEVOPS-TEMPLATE-SETUP-GUIDE.md ๋ฃจํŠธ๋กœ ๋ฎ์–ด์“ฐ๊ธฐ. ์—†์œผ๋ฉด ์Šคํ‚ต.
54
- export const SETUP_GUIDE_NAME = "SUH-DEVOPS-TEMPLATE-SETUP-GUIDE.md";
53
+ // PROJECTOPS-SETUP-GUIDE.md ๋ฃจํŠธ๋กœ ๋ฎ์–ด์“ฐ๊ธฐ. ์—†์œผ๋ฉด ์Šคํ‚ต.
54
+ export const SETUP_GUIDE_NAME = "PROJECTOPS-SETUP-GUIDE.md";
55
55
  export function copySetupGuide(tempDir, targetRoot = ".") {
56
56
  const src = join(tempDir, SETUP_GUIDE_NAME);
57
57
  if (!exists(src)) return false;
@@ -1,12 +1,12 @@
1
1
  // Claude Code ์–ด๋Œ‘ํ„ฐ (.sh _manage_claude_section / _do_claude_plugin_install /
2
2
  // _remove_claude_section / _remove_claude_plugin_data ๋“ฑ๊ฐ€).
3
- // ๋งˆ์ผ“ํ”Œ๋ ˆ์ด์Šค Cassiiopeia/projectops, ํ”Œ๋Ÿฌ๊ทธ์ธ cassiiopeia@cassiiopeia-marketplace.
3
+ // ๋งˆ์ผ“ํ”Œ๋ ˆ์ด์Šค Cassiiopeia/projectops, ํ”Œ๋Ÿฌ๊ทธ์ธ projectops@projectops-marketplace.
4
4
  import { join } from "node:path";
5
5
  import { existsSync, readdirSync, mkdirSync, cpSync, rmSync } from "node:fs";
6
6
  import { compareCacheName } from "../util.js";
7
7
 
8
8
  const MARKETPLACE = "Cassiiopeia/projectops";
9
- const PLUGIN = "cassiiopeia@cassiiopeia-marketplace";
9
+ const PLUGIN = "projectops@projectops-marketplace";
10
10
 
11
11
  function detect(io) {
12
12
  if (!io.which("claude")) return { installed: false, version: null, cliMissing: true, note: "CLI ์—†์Œ" };
@@ -17,7 +17,7 @@ function detect(io) {
17
17
  const list = Array.isArray(arr) ? arr : (arr.plugins || []);
18
18
  for (const p of list) {
19
19
  const name = String(p.name || p.id || "");
20
- if (name.includes("cassiiopeia@") || name === "cassiiopeia") { scope = p.scope || ""; version = p.version || null; break; }
20
+ if (name.includes("projectops@") || name === "projectops") { scope = p.scope || ""; version = p.version || null; break; }
21
21
  }
22
22
  } catch { /* ํŒŒ์‹ฑ ์‹คํŒจ โ†’ ๋ฏธ์„ค์น˜ ์ทจ๊ธ‰ */ }
23
23
  return { installed: !!scope, version, cliMissing: false, scope };
@@ -42,7 +42,7 @@ function install(io, scope = "user") {
42
42
  }
43
43
 
44
44
  function update(io, scope) {
45
- const cacheRoot = join(io.home(), ".claude/plugins/cache/cassiiopeia-marketplace/cassiiopeia");
45
+ const cacheRoot = join(io.home(), ".claude/plugins/cache/projectops-marketplace/projectops");
46
46
  const oldCache = latestCacheDir(cacheRoot);
47
47
  io.log("ํ”Œ๋Ÿฌ๊ทธ์ธ ์—…๋ฐ์ดํŠธ ์ค‘...");
48
48
  const up = io.run("claude", ["plugin", "update", PLUGIN, "--scope", scope]);
@@ -1,13 +1,13 @@
1
1
  // Codex CLI ์–ด๋Œ‘ํ„ฐ (.sh _manage_codex_skills / _do_codex_marketplace_register /
2
2
  // _remove_codex_section ๋“ฑ๊ฐ€).
3
- // marketplace ๋“ฑ๋ก/์—…๊ทธ๋ ˆ์ด๋“œ๊ฐ€ ์ฃผ ๊ฒฝ๋กœ. native ~/.agents/skills/cassiiopeia ์‹ฌ๋งํฌ๋Š” ๊ฐ์ง€ยท์ œ๊ฑฐ์— ์‚ฌ์šฉ.
3
+ // marketplace ๋“ฑ๋ก/์—…๊ทธ๋ ˆ์ด๋“œ๊ฐ€ ์ฃผ ๊ฒฝ๋กœ. native ~/.agents/skills/projectops ์‹ฌ๋งํฌ๋Š” ๊ฐ์ง€ยท์ œ๊ฑฐ์— ์‚ฌ์šฉ.
4
4
  import { join } from "node:path";
5
5
  import { existsSync, lstatSync, rmSync } from "node:fs";
6
6
 
7
7
  const MARKETPLACE = "Cassiiopeia/projectops";
8
- const PLUGIN = "cassiiopeia";
8
+ const PLUGIN = "projectops";
9
9
 
10
- function nativeTarget(io) { return join(io.home(), ".agents/skills/cassiiopeia"); }
10
+ function nativeTarget(io) { return join(io.home(), ".agents/skills/projectops"); }
11
11
 
12
12
  function detect(io) {
13
13
  const tgt = nativeTarget(io);
@@ -62,7 +62,7 @@ function writeMeta(io, destDir, templateVersion) {
62
62
  let installedAt = now;
63
63
  if (existsSync(file)) { try { installedAt = JSON.parse(readFileSync(file, "utf8")).installedAt || now; } catch { /* ๋ฌดํ•ด */ } }
64
64
  const meta = {
65
- name: "cassiiopeia", version, scope: "user",
65
+ name: "projectops", version, scope: "user",
66
66
  source: "https://github.com/Cassiiopeia/projectops",
67
67
  installPath: destDir, installedAt, lastUpdated: now,
68
68
  };
@@ -1,6 +1,6 @@
1
1
  // Gemini CLI ์–ด๋Œ‘ํ„ฐ (.sh _manage_gemini_extension / _remove_gemini_section ๋“ฑ๊ฐ€).
2
2
  // extension install/update/uninstall. ๋งˆ์ผ“ํ”Œ๋ ˆ์ด์Šค ๋Œ€์‹  git URL ํ™•์žฅ.
3
- const EXT = "cassiiopeia";
3
+ const EXT = "projectops";
4
4
  const URL = "https://github.com/Cassiiopeia/projectops";
5
5
 
6
6
  function detect(io) {
@@ -11,7 +11,7 @@ export function piInstalled(io) {
11
11
  if (!io.which("pi")) return false;
12
12
  const r = io.run("pi", ["list"]); // pi๋Š” ์ผ๋ถ€ ์ถœ๋ ฅ์„ stderr๋กœ ๋ณด๋ƒ„ โ†’ ๋‘˜ ๋‹ค ๊ฒ€์‚ฌ
13
13
  const out = (r.stdout || "") + (r.stderr || "");
14
- return /SUH-DEVOPS-TEMPLATE|projectops|cassiiopeia/i.test(out);
14
+ return /SUH-DEVOPS-TEMPLATE|projectops/i.test(out);
15
15
  }
16
16
 
17
17
  // pi ํด๋ก  ๊ฒฝ๋กœ(harness loader๊ฐ€ ์‚ฌ๋Š” ๊ณณ). ๋ ˆํฌ๋ช… ๋ณ€๊ฒฝ ์ „ ๊ตฌ ๊ฒฝ๋กœ ํ•˜์œ„ํ˜ธํ™˜.
@@ -18,7 +18,7 @@ function apply(io) {
18
18
  }
19
19
  if (piInstalled(io)) {
20
20
  io.log(" PI ํŒจํ‚ค์ง€ ์„ค์น˜ / ์—…๋ฐ์ดํŠธ ์™„๋ฃŒ");
21
- io.log(" โ†’ 'pi' ์žฌ์‹คํ–‰ ํ›„ 'pi list' ๋กœ ํ™•์ธ, ์ฑ„ํŒ…์ฐฝ์—์„œ /suh-analyze ๋“ฑ ํ˜ธ์ถœ");
21
+ io.log(" โ†’ 'pi' ์žฌ์‹คํ–‰ ํ›„ 'pi list' ๋กœ ํ™•์ธ, ์ฑ„ํŒ…์ฐฝ์—์„œ /projectops:analyze ๋“ฑ ํ˜ธ์ถœ");
22
22
  return true;
23
23
  }
24
24
  io.log(` PI ์„ค์น˜/์—…๋ฐ์ดํŠธ ์‹คํŒจ โ€” ์ˆ˜๋™: pi install ${PI_PACKAGE_URL}`);
@@ -17,6 +17,8 @@ export { parseTemplateOptions };
17
17
 
18
18
  export const DEPLOY_TARGETS = ["docker-ssh", "vercel", "none"];
19
19
  export const PUBLISH_TARGETS = ["nexus", "npm", "github-packages"];
20
+ // #455 โ€” changelog ์ƒ์„ฑ๊ธฐ provider. github-ai๊ฐ€ ๊ธฐ๋ณธ(์„ค์ • ์ œ๋กœ). openai/gemini/claude/ollama๋Š” openai ํ˜ธํ™˜ ํ•œ ๊ฐˆ๋ž˜.
21
+ export const CHANGELOG_PROVIDERS = ["github-ai", "coderabbit", "openai", "gemini", "claude", "ollama", "commit"];
20
22
 
21
23
  const isCancel = (v) => typeof v === "symbol";
22
24
 
@@ -62,6 +64,10 @@ export async function askAllOptionalWorkflows({
62
64
  let deploy = current.deploy ?? null;
63
65
  let publish = current.publish ?? null;
64
66
  let secretBackup = current.secretBackup ?? null;
67
+ let codeReviewCoderabbit = current.codeReviewCoderabbit ?? null;
68
+ let changelogProvider = current.changelogProvider ?? null;
69
+ let changelogBaseUrl = current.changelogBaseUrl ?? null;
70
+ let deployBranch = current.deployBranch ?? null; // #456 ๋ฆด๋ฆฌ์Šค PR head ๋ธŒ๋žœ์น˜
65
71
 
66
72
  // basic ๋‹จ๋… ํƒ€์ž…์€ ์„œ๋ฒ„ ๋ฐฐํฌ๋„ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ publish๋„ ๊ฐœ๋…์ƒ ์„ฑ๋ฆฝํ•˜์ง€ ์•Š๋Š”๋‹ค.
67
73
  // ๋ฐฐํฌ/publish ์งˆ๋ฌธ์„ ๊ฑด๋„ˆ๋›ฐ๊ณ  noneยท[]๋กœ ์กฐ์šฉํžˆ ํ™•์ •ํ•œ๋‹ค (ํƒ€์ž… ๋ณ€๊ฒฝ ์‹œ ์žฌ์งˆ๋ฌธ๋จ).
@@ -86,6 +92,12 @@ export async function askAllOptionalWorkflows({
86
92
  secretBackup = saved.secretBackup;
87
93
  say(`Secret ๋ฐฑ์—… ์˜ต์…˜: version.yml ์ €์žฅ๊ฐ’(${secretBackup}) ์œ ์ง€ โ€” ์žฌ์งˆ๋ฌธ ์ƒ๋žต`);
88
94
  }
95
+ // #455 changelog/code_review ์ €์žฅ๊ฐ’ ์žฌ์‚ฌ์šฉ
96
+ if (codeReviewCoderabbit === null && saved.codeReviewCoderabbit !== null) codeReviewCoderabbit = saved.codeReviewCoderabbit;
97
+ if (changelogProvider === null && saved.changelogProvider !== null) changelogProvider = saved.changelogProvider;
98
+ if (changelogBaseUrl === null && saved.changelogBaseUrl !== null) changelogBaseUrl = saved.changelogBaseUrl;
99
+ // #456 deploy_branch ์ €์žฅ๊ฐ’ ์žฌ์‚ฌ์šฉ
100
+ if (deployBranch === null && saved.deployBranch != null) deployBranch = saved.deployBranch;
89
101
  }
90
102
  }
91
103
 
@@ -140,6 +152,69 @@ export async function askAllOptionalWorkflows({
140
152
  }
141
153
  }
142
154
 
155
+ // โ”€โ”€ code_review: CodeRabbit AI ์ฝ”๋“œ ๋ฆฌ๋ทฐ (changelog์™€ ๋ฌด๊ด€ โ€” #455) โ”€โ”€
156
+ if (forceAsk || codeReviewCoderabbit === null) {
157
+ if (force || !tty || typeof io.confirm !== "function") {
158
+ codeReviewCoderabbit = codeReviewCoderabbit ?? false;
159
+ } else {
160
+ say("");
161
+ say("๐Ÿค– CodeRabbit AI ์ฝ”๋“œ ๋ฆฌ๋ทฐ๋ฅผ ์“ธ๊นŒ์š”? (PR ์˜ฌ๋ฆด ๋•Œ ์ฝ”๋“œ ๋ฆฌ๋ทฐ ๋Œ“๊ธ€์„ ๋‹ต๋‹ˆ๋‹ค)");
162
+ const ans = await io.confirm({ message: "CodeRabbit AI ์ฝ”๋“œ ๋ฆฌ๋ทฐ ์‚ฌ์šฉ", initialValue: false });
163
+ codeReviewCoderabbit = (ans === true && !isCancel(ans));
164
+ say(`CodeRabbit ์ฝ”๋“œ ๋ฆฌ๋ทฐ: ${codeReviewCoderabbit ? "์‚ฌ์šฉ" : "๋ฏธ์‚ฌ์šฉ"}`);
165
+ }
166
+ }
167
+
168
+ // โ”€โ”€ changelog: ๋ฆด๋ฆฌ์Šค ๋…ธํŠธ ์ƒ์„ฑ๊ธฐ (๊ธฐ๋ณธ ์ปค์„œ = github-ai โ€” #455) โ”€โ”€
169
+ if (forceAsk || changelogProvider === null) {
170
+ if (force || !tty || typeof io.select !== "function") {
171
+ changelogProvider = changelogProvider ?? "github-ai";
172
+ } else {
173
+ say("");
174
+ say("๐Ÿ“ ๋ฆด๋ฆฌ์Šค ๋…ธํŠธ(changelog)๋Š” ๋ญ˜๋กœ ๋งŒ๋“ค๊นŒ์š”?");
175
+ say(" GitHub AI๋Š” ์„ค์ • ์—†์ด ๋ฐ”๋กœ ๋ฉ๋‹ˆ๋‹ค. ๋‚˜๋จธ์ง€๋Š” ๋‚˜์ค‘์— GitHub Secret ๋“ฑ๋ก์ด ํ•„์š”ํ•  ์ˆ˜ ์žˆ์–ด์š”.");
176
+ const ans = await io.select({
177
+ message: "changelog ์ƒ์„ฑ๊ธฐ๋ฅผ ์„ ํƒํ•˜์„ธ์š”",
178
+ options: [
179
+ { value: "github-ai", label: "GitHub AI (์ถ”์ฒœ ยท ์„ค์ • ๋ถˆํ•„์š”)" },
180
+ { value: "coderabbit", label: "CodeRabbit" },
181
+ { value: "openai", label: "OpenAI ํ˜ธํ™˜ API (ํ‚ค ๋“ฑ๋ก ํ•„์š”)" },
182
+ { value: "commit", label: "์ปค๋ฐ‹ ๋ถ„์„๋งŒ (AI ์—†์Œ)" },
183
+ ],
184
+ });
185
+ changelogProvider = (!isCancel(ans) && CHANGELOG_PROVIDERS.includes(ans)) ? ans : (changelogProvider ?? "github-ai");
186
+ say(`changelog ์ƒ์„ฑ๊ธฐ: ${changelogProvider}`);
187
+ }
188
+ }
189
+
190
+ // ollama ์„ ํƒ ์‹œ์—๋งŒ base_url ์งˆ๋ฌธ (๋‚˜๋จธ์ง€ provider๋Š” preset base_url ์ž๋™ โ€” #455)
191
+ if (changelogProvider === "ollama" && (forceAsk || changelogBaseUrl === null || changelogBaseUrl === "")) {
192
+ if (force || !tty || typeof io.text !== "function") {
193
+ changelogBaseUrl = changelogBaseUrl ?? "";
194
+ } else {
195
+ const ans = await io.text({ message: "Ollama ์„œ๋ฒ„ base_url (์˜ˆ: https://ai.suhsaechan.kr/v1)" });
196
+ changelogBaseUrl = (typeof ans === "string" && !isCancel(ans)) ? ans.trim() : "";
197
+ say(`Ollama base_url: ${changelogBaseUrl || "(๋ฏธ์ง€์ •)"}`);
198
+ }
199
+ } else if (changelogBaseUrl === null) {
200
+ changelogBaseUrl = "";
201
+ }
202
+
203
+ // โ”€โ”€ deploy_branch: ๋ฆด๋ฆฌ์Šค PR์˜ head ๋ธŒ๋žœ์น˜ (#456 โ€” default_branch์™€ ๋ณ„๊ฐœ) โ”€โ”€
204
+ // ๋Œ€๋ถ€๋ถ„ developโ†’main ๋ฆด๋ฆฌ์Šค ๊ตฌ์กฐ๋ผ ๊ธฐ๋ณธ๊ฐ’ develop. ๋‹ค๋ฅธ head๋ฅผ ์“ฐ๋Š” ๋ ˆํฌ๋ฅผ ์œ„ํ•ด ๋ฌผ์–ด๋ณธ๋‹ค.
205
+ if (forceAsk || deployBranch === null) {
206
+ if (force || !tty || typeof io.text !== "function") {
207
+ deployBranch = deployBranch ?? "develop";
208
+ } else {
209
+ say("");
210
+ say("๐ŸŒฟ ๋ฆด๋ฆฌ์Šค ๋ฐฐํฌ ๋ธŒ๋žœ์น˜(๋ฆด๋ฆฌ์Šค PR์˜ head)๋Š” ๋ฌด์—‡์ธ๊ฐ€์š”?");
211
+ say(" developโ†’main ๋ฆด๋ฆฌ์Šค ๊ตฌ์กฐ๋ฉด develop ๊ทธ๋Œ€๋กœ ๋‘์„ธ์š”. ๋ฐฐํฌ ๋ธŒ๋žœ์น˜๊ฐ€ ๋”ฐ๋กœ๋ฉด ๊ทธ ์ด๋ฆ„์„ ์ ์–ด์ฃผ์„ธ์š”.");
212
+ const ans = await io.text({ message: "๋ฐฐํฌ ๋ธŒ๋žœ์น˜", initialValue: deployBranch ?? "develop" });
213
+ deployBranch = (typeof ans === "string" && !isCancel(ans) && ans.trim()) ? ans.trim() : (deployBranch ?? "develop");
214
+ say(`๋ฐฐํฌ ๋ธŒ๋žœ์น˜: ${deployBranch}`);
215
+ }
216
+ }
217
+
143
218
  // โ”€โ”€ โ‘ฃ Secret ๋ฐฑ์—…: ๊ณตํ†ต ํด๋” (๋ฐฐํฌ์ถ• ์•„๋‹˜ โ€” ๊ธฐ์กด ํด๋” ์งˆ๋ฌธ ์œ ์ง€) โ”€โ”€
144
219
  const real = join(tempDir, PATHS.workflowsDir, PATHS.projectTypesDir);
145
220
  const ptDir = existsSync(real) ? real : join(tempDir, PATHS.projectTypesDir);
@@ -149,5 +224,11 @@ export async function askAllOptionalWorkflows({
149
224
  current: secretBackup, force, tty, io, forceAsk, say,
150
225
  });
151
226
 
152
- return { deploy: deploy ?? "docker-ssh", publish: publish ?? [], secretBackup: secretBackup === true };
227
+ return {
228
+ deploy: deploy ?? "docker-ssh", publish: publish ?? [], secretBackup: secretBackup === true,
229
+ codeReviewCoderabbit: codeReviewCoderabbit === true,
230
+ changelogProvider: changelogProvider ?? "github-ai",
231
+ changelogBaseUrl: changelogBaseUrl ?? "",
232
+ deployBranch: deployBranch ?? "develop",
233
+ };
153
234
  }
@@ -32,7 +32,7 @@ const HEADER = `# ==============================================================
32
32
  # ์—ฐ๊ด€๋œ ์›Œํฌํ”Œ๋กœ์šฐ:
33
33
  # - .github/workflows/PROJECT-VERSION-CONTROL.yaml
34
34
  # - .github/workflows/PROJECT-README-VERSION-UPDATE.yaml
35
- # - .github/workflows/PROJECT-AUTO-CHANGELOG-CONTROL.yaml
35
+ # - .github/workflows/PROJECT-RELEASE-CHANGELOG.yaml
36
36
  #
37
37
  # ์ฃผ์˜์‚ฌํ•ญ:
38
38
  # - project_types๋Š” ์ตœ์ดˆ ์„ค์ • ํ›„ ๋ณ€๊ฒฝํ•˜์ง€ ๋งˆ์„ธ์š”
@@ -49,17 +49,40 @@ const HEADER = `# ==============================================================
49
49
  // ๊ตฌ synology ํ‚ค ๋“ฑ ๋‹ค๋ฅธ ํ‚ค๋Š” ์–ด๋А ๋ถ„๊ธฐ์—๋„ ์•ˆ ๊ฑธ๋ ค ์ž์—ฐํžˆ ๋ฌด์‹œ๋œ๋‹ค.
50
50
  // (options-ask.js๊ฐ€ ์ด ํ•จ์ˆ˜๋ฅผ importํ•œ๋‹ค โ€” ์ˆœํ™˜ ๋ฐฉ์ง€ ์œ„ํ•ด ์—ฌ๊ธฐ(version-yml)์— ์ •์˜.)
51
51
  export function parseTemplateOptions(content) {
52
- const out = { deploy: null, publish: null, secretBackup: null };
52
+ const out = { deploy: null, publish: null, secretBackup: null,
53
+ changelogProvider: null, changelogBaseUrl: null, codeReviewCoderabbit: null,
54
+ deployBranch: null };
55
+ // deploy_branch๋Š” metadata ์ง์†(#456) โ€” template.options ๋ฐ–์ด๋ผ ๋ณ„๋„๋กœ ์Šค์บ”ํ•œ๋‹ค.
56
+ for (const line of String(content || "").split("\n")) {
57
+ if (line.startsWith("#")) continue;
58
+ const m = line.match(/^\s+deploy_branch:\s*"([^"]*)"/);
59
+ if (m) { out.deployBranch = m[1]; break; }
60
+ }
53
61
  let legacyNexus = null;
54
62
  let legacyNpm = null;
55
63
  // ๊ฐ’ ์ •๊ทœํ™”: ๋”ฐ์˜ดํ‘œ ์ œ๊ฑฐ + ํŠธ๋ฆผ (.sh tr -d '"' | tr -d "'" | xargs ๋“ฑ๊ฐ€)
56
64
  const strip = (s) => String(s).replace(/["']/g, "").trim();
57
65
  let inTemplate = false;
58
66
  let inOptions = false;
67
+ let inCodeReview = false;
68
+ let inChangelog = false;
59
69
  for (const line of String(content || "").split("\n")) {
60
70
  if (/^\s*template:/.test(line)) { inTemplate = true; continue; }
61
71
  if (inTemplate && /^\s+options:/.test(line)) { inOptions = true; continue; }
62
72
  if (inTemplate && inOptions) {
73
+ // ์ค‘์ฒฉ ๋ธ”๋ก ํ—ค๋” ๊ฐ์ง€ (options ๋ฐ‘ ํ•œ ๋‹จ๊ณ„) โ€” code_review / changelog (#455)
74
+ if (/^\s+code_review:\s*$/.test(line)) { inCodeReview = true; inChangelog = false; continue; }
75
+ if (/^\s+changelog:\s*$/.test(line)) { inChangelog = true; inCodeReview = false; continue; }
76
+ if (inCodeReview) {
77
+ const cm = line.match(/^\s+coderabbit:\s*(.+)/);
78
+ if (cm) { const v = strip(cm[1]); if (v === "true") out.codeReviewCoderabbit = true; if (v === "false") out.codeReviewCoderabbit = false; continue; }
79
+ }
80
+ if (inChangelog) {
81
+ const pm = line.match(/^\s+provider:\s*(.+)/);
82
+ if (pm) { out.changelogProvider = strip(pm[1]); continue; }
83
+ const bm = line.match(/^\s+base_url:\s*(.*)/);
84
+ if (bm) { out.changelogBaseUrl = strip(bm[1]); continue; }
85
+ }
63
86
  let m = line.match(/^\s+deploy:\s*(.+)/);
64
87
  if (m) {
65
88
  const v = strip(m[1]);
@@ -152,9 +175,12 @@ export function parseExisting(content) {
152
175
  if (/^\S/.test(l)) break;
153
176
  }
154
177
  }
178
+ // ๋ธŒ๋žœ์น˜ ์ •๋ณด (#456) โ€” default_branch(๋ ˆํฌ ๊ธฐ๋ณธ)์™€ deploy_branch(๋ฆด๋ฆฌ์Šค PR head)๋Š” ๋ณ„๊ฐœ.
179
+ const defaultBranch = line(/^\s*default_branch:\s*"([^"]*)"/);
180
+ const deployBranch = line(/^\s*deploy_branch:\s*"([^"]*)"/);
155
181
  // ์„ ํƒ ์›Œํฌํ”Œ๋กœ์šฐ ์˜ต์…˜ (metadata.template.options โ€” nexus/secret_backup)
156
182
  const options = parseTemplateOptions(text);
157
- return { version, versionCode, types, paths, templateVersion, options };
183
+ return { version, versionCode, types, paths, templateVersion, options, defaultBranch, deployBranch };
158
184
  }
159
185
 
160
186
  // version.yml ์ „์ฒด ์ƒ์„ฑ (.sh create_version_yml + save_template_options ์‹ ๊ทœ ์ผ€์ด์Šค ๋“ฑ๊ฐ€).
@@ -164,7 +190,7 @@ export function parseExisting(content) {
164
190
  // today = "YYYY-MM-DD" (UTC)
165
191
  // pathMarkers = Map<type, markerFilename> (project_paths ์ฃผ์„์šฉ)
166
192
  // templateOptions = { templateVersion, deployTarget, publishTargets, includeSecretBackup, optionsDate } (template ๋ธ”๋ก)
167
- export function buildVersionYml({ version, types = [], paths = new Map(), pathMarkers = new Map(), branch = "main", versionCode = 1, now, today, templateOptions = null, deployValues = new Map() }) {
193
+ export function buildVersionYml({ version, types = [], paths = new Map(), pathMarkers = new Map(), branch = "main", deployBranch = "", versionCode = 1, now, today, templateOptions = null, deployValues = new Map() }) {
168
194
  const typesJson = types.length ? `[${types.map((t) => `"${t}"`).join(",")}]` : `["basic"]`;
169
195
 
170
196
  let out = HEADER + "\n";
@@ -186,6 +212,9 @@ export function buildVersionYml({ version, types = [], paths = new Map(), pathMa
186
212
  out += ` last_updated: "${now}"\n`;
187
213
  out += ` last_updated_by: "template_integrator"\n`;
188
214
  out += ` default_branch: "${branch}"\n`;
215
+ // deploy_branch: ๋ฆด๋ฆฌ์Šค PR์˜ head ๋ธŒ๋žœ์น˜(#456). default_branch(๋ ˆํฌ ๊ธฐ๋ณธ)์™€ ๋ณ„๊ฐœ ๊ฐœ๋….
216
+ // ์ง€์ •๋œ ๊ฒฝ์šฐ์—๋งŒ ์ถœ๋ ฅ โ€” ๋ฏธ์ง€์ •์ด๋ฉด ์Šคํ‚ฌ์ด develop๋กœ ํด๋ฐฑ(ํ•˜์œ„ํ˜ธํ™˜).
217
+ if (deployBranch) out += ` deploy_branch: "${deployBranch}"\n`;
189
218
  out += ` integrated_from: "projectops"\n`;
190
219
  out += ` integration_date: "${today}"\n`;
191
220
 
@@ -203,7 +232,8 @@ export function buildVersionYml({ version, types = [], paths = new Map(), pathMa
203
232
 
204
233
  // template ์˜ต์…˜ ๋ธ”๋ก (.sh save_template_options ์‹ ๊ทœ ์ถ”๊ฐ€ ์ผ€์ด์Šค). templateOptions ์ง€์ • ์‹œ.
205
234
  if (templateOptions) {
206
- const { templateVersion = "unknown", deployTarget = "docker-ssh", publishTargets = [], includeSecretBackup = false, optionsDate = today } = templateOptions;
235
+ const { templateVersion = "unknown", deployTarget = "docker-ssh", publishTargets = [], includeSecretBackup = false, optionsDate = today,
236
+ changelogProvider = "github-ai", changelogBaseUrl = "", codeReviewCoderabbit = true } = templateOptions;
207
237
  const publishJson = `[${publishTargets.map((t) => `"${t}"`).join(",")}]`;
208
238
  out += ` template:\n`;
209
239
  out += ` source: "projectops"\n`;
@@ -214,6 +244,11 @@ export function buildVersionYml({ version, types = [], paths = new Map(), pathMa
214
244
  out += ` deploy: "${deployTarget}"\n`;
215
245
  out += ` publish: ${publishJson}\n`;
216
246
  out += ` secret_backup: ${includeSecretBackup}\n`;
247
+ out += ` code_review:\n`;
248
+ out += ` coderabbit: ${codeReviewCoderabbit}\n`;
249
+ out += ` changelog:\n`;
250
+ out += ` provider: "${changelogProvider}"\n`;
251
+ out += ` base_url: "${changelogBaseUrl}"\n`;
217
252
  }
218
253
  return out;
219
254
  }
package/src/index.js CHANGED
@@ -109,6 +109,12 @@ export async function run(argv, { cwd = process.cwd(), source = { type: "git" },
109
109
  deployTarget: opts.deployTarget ?? existing?.options?.deploy ?? "docker-ssh",
110
110
  publishTargets: opts.publishTargets ?? existing?.options?.publish ?? [],
111
111
  includeSecretBackup: opts.includeSecretBackup ?? existing?.options?.secretBackup ?? false,
112
+ // ๋ฆด๋ฆฌ์Šค ๋ฐฐํฌ ๋ธŒ๋žœ์น˜(#456): CLI ํ”Œ๋ž˜๊ทธ โ†’ version.yml ์ €์žฅ๊ฐ’ โ†’ ๋นˆ ๊ฐ’(๋ฏธ์ถœ๋ ฅ, ์Šคํ‚ฌ์ด develop ํด๋ฐฑ)
113
+ deployBranch: opts.deployBranch || existing?.options?.deployBranch || "",
114
+ // changelog/code_review ์ถ•(#455): ๋น„๋Œ€ํ™”ํ˜•์€ ์ €์žฅ๊ฐ’ โ†’ ๊ธฐ๋ณธ๊ฐ’. null์ด ํ˜๋Ÿฌ provider:"null"๋กœ ๊ธฐ๋ก๋˜๋˜ ๋ฒ„๊ทธ ์ˆ˜์ •.
115
+ changelogProvider: existing?.options?.changelogProvider ?? "github-ai",
116
+ changelogBaseUrl: existing?.options?.changelogBaseUrl ?? "",
117
+ codeReviewCoderabbit: existing?.options?.codeReviewCoderabbit ?? false,
112
118
  repoName,
113
119
  // ์‹ค resolver 4์ข… (.sh resolve_token ๋“ฑ๊ฐ€ โ€” spring-app-yml ์Šคํ… ์ œ๊ฑฐ)
114
120
  resolvers: makeResolvers(cwd, repoName, paths),
package/src/ui/summary.js CHANGED
@@ -168,6 +168,6 @@ export function printSummary(ctx, targetRoot = ".") {
168
168
  err(SEPARATOR);
169
169
  err("");
170
170
  err(`${CYAN}๐Ÿ“– ์ž์„ธํ•œ ์„ค์ • ๋ฐฉ๋ฒ•์€ ๋‹ค์Œ ํŒŒ์ผ์„ ์ฐธ๊ณ ํ•˜์„ธ์š”:${NC}`);
171
- err(" โ†’ SUH-DEVOPS-TEMPLATE-SETUP-GUIDE.md");
171
+ err(" โ†’ PROJECTOPS-SETUP-GUIDE.md");
172
172
  err("");
173
173
  }