@bifos/dooray-cli 0.11.0 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -50,7 +50,7 @@ dooray project members <project> # 멤버 목록
50
50
  dooray project workflows <project> # 워크플로우 목록
51
51
  dooray project groups <project> # 멤버 그룹 목록 (ID / Code)
52
52
  dooray project tags <project> # 태그 목록 (ID / Color / Name / Group / Mandatory)
53
- dooray project templates <project> # 템플릿 목록 (ID / Template Name, ADR-027)
53
+ dooray project templates <project> # 템플릿 목록 (ID / Template Name)
54
54
  ```
55
55
 
56
56
  > **태그 캐시 갱신**: 이전 버전에서 캐시한 태그가 색상 없이 표시되면 `dooray cache clear` 실행 후 다시 조회하세요.
@@ -59,7 +59,7 @@ dooray project templates <project> # 템플릿 목록 (ID / Template Na
59
59
 
60
60
  ```bash
61
61
  dooray member list <project> # 프로젝트 멤버 목록 (이름·organizationMemberId)
62
- dooray member get <organizationMemberId> # 멤버 상세 (cache 우회, ADR-021)
62
+ dooray member get <organizationMemberId> # 멤버 상세 (cache 우회)
63
63
 
64
64
  # organization 전체 멤버 검색
65
65
  dooray member search 홍길동 # 이름 검색
@@ -83,15 +83,20 @@ dooray post get <project> 42 --json # JSON 출력
83
83
  | 방식 | 예시 |
84
84
  |---|---|
85
85
  | `<project> <number>` | `dooray post get <project> 42` |
86
- | Dooray URL positional (`/task/to/<postId>`) | `dooray post get https://x.dooray.com/task/to/<postId>` |
87
- | Dooray URL positional (브라우저 주소창 복사본) | `dooray post get https://x.dooray.com/task/<projectId>/<postId>` |
86
+ | Dooray URL positional | `dooray post get https://x.dooray.com/task/to/<postId>` |
88
87
  | `--id <postId>` | `dooray post get --id <postId>` |
89
- | `--url <url>` | `dooray post get --url https://x.dooray.com/task/to/...` |
88
+ | `--url <url>` | `dooray post get --url https://x.dooray.com/task/to/<postId>` |
89
+
90
+ 지원 URL 형식 3종 (positional 첫 인자 / `--url` 공통):
91
+
92
+ - `https://*.dooray.com/task/to/<postId>`
93
+ - `https://*.dooray.com/task/<projectId>/<postId>` — 브라우저 주소창 복사본
94
+ - `https://*.dooray.com/project/tasks/<postId>` — 프로젝트 업무 목록에서 열기
90
95
 
91
96
  대상: `post get`/`edit`/`done`/`workflow`, `post comment list`/`add`/`edit`/`delete`, `post file list`/`upload`/`download`/`download-all`/`delete`.
92
- AI 에이전트는 사용자 메시지의 Dooray URL을 그대로 첫 인자로 전달하면 가장 빠르다 (ADR-020).
97
+ AI 에이전트는 사용자 메시지의 Dooray URL을 그대로 첫 인자로 전달하면 가장 빠르다.
93
98
 
94
- **projectId 직접 입력** (Issue #78, ADR-030):
99
+ **projectId 직접 입력**:
95
100
 
96
101
  `member=me` 응답에 없는 프로젝트 (다른 팀 / 권한만 있는 프로젝트) 도 projectId (15+자리 numeric) 를 직접 입력하면 자동으로 cache 우회.
97
102
 
@@ -131,7 +136,18 @@ dooray post create <project> \
131
136
  > mandatory-tag 정책 프로젝트(예: `<project>`)에서는 mandatory 그룹마다 1개 이상 `--tag`로 지정해야 한다.
132
137
  > 누락 시 클라이언트가 사전 검증으로 후보 목록과 함께 에러 출력.
133
138
 
134
- #### 템플릿 기반 정형 task (ADR-027)
139
+ > **`post create` 출력의 `.id` 는 internal postId (19자리 숫자)입니다.**
140
+ > 이 ID 로 후속 조회·수정·댓글 작업을 할 때는 **`--id <postId>`** 를 사용하세요.
141
+ > `<project> <업무번호>` 의 번호 자리에 postId 를 넣으면 안내 에러가 발생합니다.
142
+ >
143
+ > ```bash
144
+ > # 생성 후 바로 조회하는 패턴
145
+ > POST_ID=$(dooray post create <project> --title "..." --json | jq -r '.id')
146
+ > dooray post get --id "$POST_ID"
147
+ > dooray post comment add --id "$POST_ID" --body "첫 댓글"
148
+ > ```
149
+
150
+ #### 템플릿 기반 정형 task
135
151
 
136
152
  ```bash
137
153
  # 프로젝트의 템플릿 목록
@@ -182,7 +198,7 @@ dooray post comment add <project> 42 --body "댓글 내용"
182
198
  dooray post comment add <project> 42 --body-file ./comment.md
183
199
  ```
184
200
 
185
- > table 출력의 Creator 컬럼은 프로젝트 멤버 캐시로 자동 enrich되며, `--json`은 raw 응답을 유지한다 (ADR-021).
201
+ > table 출력의 Creator 컬럼은 프로젝트 멤버 캐시로 자동 enrich되며, `--json`은 raw 응답을 유지한다.
186
202
 
187
203
  #### 멘션·내부 링크 자동 삽입
188
204
 
@@ -206,7 +222,7 @@ dooray post comment add <project> <post-number> --body "..." --link-task <projec
206
222
  > `--mention` / `--mention-group` / `--link-task` / `--dry-run` 은 `post create`, `post edit`, `post comment add`, `post comment edit` 4 명령 모두 지원.
207
223
  > 이전 버전 캐시는 orgId가 없으므로 첫 호출 시 자동 갱신됩니다 (또는 `dooray cache clear`).
208
224
 
209
- #### 참조자(cc) / 담당자(to) 변경 (ADR-025)
225
+ #### 참조자(cc) / 담당자(to) 변경
210
226
 
211
227
  ```bash
212
228
  # 멤버/그룹 추가 (기존 참조자 유지 + dedupe)
@@ -225,7 +241,7 @@ interactive ($EDITOR) 모드에서는 위 6개 옵션이 무시되고 stderr 경
225
241
  `post edit --dry-run --json` 사용 시 출력에 `users: { to, cc }` 가 포함되어 API 호출 없이 변경 결과 미리보기 가능.
226
242
  `post create --dry-run` 은 본문만 출력하며 `users` 는 포함하지 않음.
227
243
 
228
- **그룹 cc / mention 사용 예 (Issue #76 fix)**:
244
+ **그룹 cc / mention 사용 예**:
229
245
 
230
246
  ```bash
231
247
  # code 부분일치
@@ -246,7 +262,7 @@ dooray post edit <project> <post-number> --cc-group dev-team --dry-run --json |
246
262
  # 출력 예: [{ "type": "group", "group": { "projectMemberGroupId": "...", "members": [] } }, ...]
247
263
  ```
248
264
 
249
- #### 생성 후 태그 변경 (Issue #66)
265
+ #### 생성 후 태그 변경
250
266
 
251
267
  ```bash
252
268
  # 기존 태그 유지 + 신규 추가 (dedupe)
@@ -260,7 +276,7 @@ dooray post edit --id <postId> --tag-remove "<group>: <name>"
260
276
  ```
261
277
 
262
278
  `--title` / `--body` 없이 단독 호출 가능 — 기존 본문은 자동 재전송.
263
- mandatory tag 그룹은 `post create` 와 동일하게 사전 검증 (ADR-019).
279
+ mandatory tag 그룹은 `post create` 와 동일하게 사전 검증.
264
280
 
265
281
  #### 상위 업무 변경 (`--parent`)
266
282
 
@@ -277,7 +293,7 @@ dooray post edit --id <postId> --title "<원제목>" --parent <other-parent-post
277
293
 
278
294
  interactive ($EDITOR) 모드에서 `--parent` 사용 시 무시 + stderr 경고.
279
295
  **parent 만 단독 변경하려면 `--title "<원제목>"` 동반 필요** — `post edit` 는 본문 변경(`--title`/`--body`) 동반 시에만 non-interactive 분기로 들어감.
280
- parent 변경은 그 분기 안에서만 수행됨. (Issue #60)
296
+ parent 변경은 그 분기 안에서만 수행됨.
281
297
 
282
298
  `--dry-run --json` 출력의 `parentChange` 필드는 **사용자 입력 원문 그대로** (`<project>/<number>` 또는 raw postId).
283
299
  resolver 처리 전 미리보기 값이며 실제 호출 대상 `postId` 가 아님.
@@ -285,7 +301,7 @@ dry-run 은 API 미호출 원칙을 유지해 `resolvePostRef` 도 건너뜀.
285
301
 
286
302
  #### `--to` / `--cc` / `--mention` 입력 형식 (자동 분기)
287
303
 
288
- 이름 외에도 이메일 / organizationMemberId 직접 입력 가능 — **동명이인 우회 + ID 직접 입력** (Issue #58):
304
+ 이름 외에도 이메일 / organizationMemberId 직접 입력 가능 — **동명이인 우회 + ID 직접 입력**:
289
305
 
290
306
  ```bash
291
307
  # 이름 (이전부터 지원, 부분일치)
@@ -369,7 +385,7 @@ dooray wiki page edit <project> <page-id> --body "..." | --body-file <path> #
369
385
  dooray wiki page edit <project> <page-id> # $EDITOR (플래그 없을 때)
370
386
  ```
371
387
 
372
- #### 위키 페이지 첨부파일 (Issue #70)
388
+ #### 위키 페이지 첨부파일
373
389
 
374
390
  post 의 `post file` 명령군과 동일 패턴 — `<project> <page-id>` 외에도 `--id`/`--url`/positional URL 지원.
375
391
 
@@ -399,7 +415,7 @@ dooray wiki page file list "https://<tenant>.dooray.com/wiki/<wikiId>/<pageId>"
399
415
  dooray wiki page file upload --id <pageId> --project <project> --file ./README.md
400
416
  ```
401
417
 
402
- `--json` 옵션으로 자동화 파이프라인에서 동일 parse 코드를 사용할 수 있습니다 (ADR-031):
418
+ `--json` 옵션으로 자동화 파이프라인에서 동일 parse 코드를 사용할 수 있습니다:
403
419
 
404
420
  ```bash
405
421
  # download — { outputPath, fileName, size }
@@ -411,15 +427,30 @@ dooray wiki page file download-all <project> <page-id> -o ./ --json | jq '.faile
411
427
  # delete — { fileId, status: "deleted" }
412
428
  dooray wiki page file delete <project> <page-id> --file-id <id> --json
413
429
 
414
- # upload — res.result raw (--quiet 는 id 만)
430
+ # upload (general) — res.result raw (--quiet 는 id 만)
415
431
  dooray wiki page file upload <project> <page-id> --file ./report.pdf --json
432
+
433
+ # upload (inline_image) — res.result + markdownSnippet 필드 추가
434
+ dooray wiki page file upload <project> <page-id> --file ./diagram.png --type inline_image --json
435
+ # 출력 예:
436
+ # {
437
+ # "id": "<id>",
438
+ # "attachFileId": "<attachFileId>",
439
+ # "name": "diagram.png",
440
+ # "size": 12345,
441
+ # "type": "inline_image",
442
+ # "markdownSnippet": "![diagram.png](/wikis/<wikiId>/files/<attachFileId>)"
443
+ # }
444
+
445
+ # 자동화: markdownSnippet 을 jq 로 추출해 본문에 삽입
446
+ SNIPPET=$(dooray wiki page file upload <project> <page-id> --file ./diagram.png --type inline_image --json | jq -r '.markdownSnippet')
416
447
  ```
417
448
 
418
449
  **주의**:
419
450
  - `upload` 시 multipart 필드 순서 (`type` → `file`) 가 중요.
420
- 클라이언트가 자동으로 강제 (ADR-029 참조)
451
+ 클라이언트가 자동으로 강제
421
452
  - `inline_image` 로 올린 파일은 본문에 markdown 으로 박혀야 위키에서 보임.
422
- upload stdout snippet복사해서 `dooray wiki page edit` 으로 본문에 직접 추가
453
+ `--json``markdownSnippet`복사하거나 jq 로 추출해 `dooray wiki page edit` 본문에 직접 추가
423
454
  - `delete` 는 confirm 없이 즉시 삭제 (실수 방지 책임은 호출자)
424
455
 
425
456
  #### 위키 페이지 댓글
@@ -506,7 +537,7 @@ dooray post file upload <project> <number> ./report.pdf
506
537
  dooray post file delete <project> <number> <file-id>
507
538
  ```
508
539
 
509
- 자동화 스크립트에서 `--json` 옵션으로 구조화된 출력을 파이프로 가공할 수 있습니다 (ADR-031):
540
+ 자동화 스크립트에서 `--json` 옵션으로 구조화된 출력을 파이프로 가공할 수 있습니다:
510
541
 
511
542
  ```bash
512
543
  # download — { outputPath, fileName, size }
@@ -537,7 +568,7 @@ dooray post comment delete --url <url> --comment-id <commentId>
537
568
  ### 댓글 첨부 파일 (`post comment file *`)
538
569
 
539
570
  자동화로 댓글에 인라인 이미지 / 파일을 삽입할 때 사용.
540
- 4 명령 (list/upload/download/delete) 모두 `<project> <post-number> <comment-id>` 또는 `--id <postId> --comment-id <logId>` / `--url <url> --comment-id <logId>` 패턴 지원 (ADR-020).
571
+ 4 명령 (list/upload/download/delete) 모두 `<project> <post-number> <comment-id>` 또는 `--id <postId> --comment-id <logId>` / `--url <url> --comment-id <logId>` 패턴 지원.
541
572
 
542
573
  ```bash
543
574
  # 첨부 목록
@@ -554,7 +585,7 @@ dooray post comment file delete <project> <post-num> <comment-id> <file-id> --ye
554
585
  ```
555
586
 
556
587
  > Dooray REST API 가 댓글 전용 attachment endpoint 를 제공하지 않아 내부적으로
557
- > post-level files API 와 댓글 본문 PUT 의 합성으로 동작 (ADR-024). 단일 명령
588
+ > post-level files API 와 댓글 본문 PUT 의 합성으로 동작한다. 단일 명령
558
589
  > = 단일 파일 — 다중 파일은 호출자가 반복 호출.
559
590
 
560
591
  ## 출력 모드
@@ -601,7 +632,7 @@ dooray feedback --last --title "재현" --body "추가 설명" --dry-run # 미
601
632
  dooray feedback --dry-run
602
633
  ```
603
634
 
604
- > **개인정보 보호 (ADR-023)**: `--last` 모드에서 argv는 `--api-key`/`--token`/`Authorization` 등 시크릿 패턴을 자동 마스킹 후 저장합니다. cwd/env는 미저장.
635
+ > **개인정보 보호**: `--last` 모드에서 argv는 `--api-key`/`--token`/`Authorization` 등 시크릿 패턴을 자동 마스킹 후 저장합니다. cwd/env는 미저장.
605
636
 
606
637
  ## 캐시
607
638
 
package/dist/index.js CHANGED
@@ -1999,12 +1999,15 @@ async function resolvePost(client, projectId, postNumber) {
1999
1999
  // src/utils/dooray-url.ts
2000
2000
  var TASK_URL_RE = /^https?:\/\/[\w.-]+\.dooray\.com\/task\/to\/(\d+)(?:[/?#].*)?$/;
2001
2001
  var TASK_URL_ALT_RE = /^https?:\/\/[\w.-]+\.dooray\.com\/task\/(?:\d+)\/(\d+)(?:[/?#].*)?$/;
2002
+ var TASK_PROJECT_TASKS_RE = /^https?:\/\/[\w.-]+\.dooray\.com\/project\/tasks\/(\d+)(?:[/?#].*)?$/;
2002
2003
  var WIKI_URL_RE = /^https?:\/\/[\w.-]+\.dooray\.com\/wiki\/(\d+)\/(\d+)(?:[/?#].*)?$/;
2003
2004
  function parseDoorayTaskUrl(input2) {
2004
2005
  const m1 = TASK_URL_RE.exec(input2);
2005
2006
  if (m1) return m1[1];
2006
2007
  const m2 = TASK_URL_ALT_RE.exec(input2);
2007
2008
  if (m2) return m2[1];
2009
+ const m3 = TASK_PROJECT_TASKS_RE.exec(input2);
2010
+ if (m3) return m3[1];
2008
2011
  return null;
2009
2012
  }
2010
2013
  function parseDoorayWikiUrl(input2) {
@@ -2017,7 +2020,18 @@ function isLikelyDoorayUrl(input2) {
2017
2020
  }
2018
2021
 
2019
2022
  // src/resolvers/post-input.ts
2020
- var INPUT_HELP = "\uC5C5\uBB34\uB97C \uC2DD\uBCC4\uD560 \uC815\uBCF4\uAC00 \uBD80\uC871\uD569\uB2C8\uB2E4. \uB2E4\uC74C \uC911 \uD558\uB098\uB97C \uC785\uB825\uD558\uC138\uC694:\n - <project> <post-number> \uC608: tc-ocr 337\n - --id <postId> \uC608: --id 4319587406666362045\n - <Dooray URL> \uC608: https://x.dooray.com/task/to/4319587406666362045";
2023
+ function classifyPostInputToken(token) {
2024
+ if (/^https?:\/\//.test(token)) return "url";
2025
+ if (/^\d{15,}$/.test(token)) return "postId";
2026
+ if (/^\d+$/.test(token)) return "postNumber";
2027
+ return "project";
2028
+ }
2029
+ var URL_FORMAT_HINT = "\uC9C0\uC6D0 \uD615\uC2DD:\n https://x.dooray.com/task/to/{postId}\n https://x.dooray.com/task/{projectId}/{postId}\n https://x.dooray.com/project/tasks/{postId}";
2030
+ var INPUT_HELP = `\uC5C5\uBB34\uB97C \uC2DD\uBCC4\uD560 \uC815\uBCF4\uAC00 \uBD80\uC871\uD569\uB2C8\uB2E4. \uB2E4\uC74C \uC911 \uD558\uB098\uB97C \uC785\uB825\uD558\uC138\uC694:
2031
+ - <project> <post-number> \uC608: my-project 337
2032
+ - --id <postId> \uC608: --id 1234567890123456789
2033
+ - <Dooray URL>
2034
+ ${URL_FORMAT_HINT}`;
2021
2035
  async function resolveByPostId(client, postId) {
2022
2036
  const res = await client.getPostStandalone(postId);
2023
2037
  const d = res.result;
@@ -2048,13 +2062,28 @@ async function resolvePostInput(client, args) {
2048
2062
  if (!postId) {
2049
2063
  throw new DoorayCliError(
2050
2064
  `--url \uD615\uC2DD\uC774 \uC62C\uBC14\uB974\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4: "${urlOpt}"
2051
- \uC608: https://x.dooray.com/task/to/4319587406666362045`,
2065
+ ${URL_FORMAT_HINT}`,
2052
2066
  EXIT_PARAM_ERROR
2053
2067
  );
2054
2068
  }
2055
2069
  return resolveByPostId(client, postId);
2056
2070
  }
2057
2071
  if (idOpt) {
2072
+ const idType = classifyPostInputToken(idOpt);
2073
+ if (idType === "postNumber") {
2074
+ throw new DoorayCliError(
2075
+ `"${idOpt}" \uB294 \uC5C5\uBB34 \uBC88\uD638\uB85C \uBCF4\uC785\uB2C8\uB2E4. <project> <post-number> \uD615\uC2DD\uC744 \uC4F0\uC138\uC694:
2076
+ dooray post get <project> ${idOpt}`,
2077
+ EXIT_PARAM_ERROR
2078
+ );
2079
+ }
2080
+ if (idType === "url") {
2081
+ throw new DoorayCliError(
2082
+ `"${idOpt}" \uB294 URL \uD615\uC2DD\uC785\uB2C8\uB2E4. --url \uC635\uC158\uC744 \uC4F0\uC138\uC694:
2083
+ dooray post get --url "${idOpt}"`,
2084
+ EXIT_PARAM_ERROR
2085
+ );
2086
+ }
2058
2087
  return resolveByPostId(client, idOpt);
2059
2088
  }
2060
2089
  if (projectArg && !postNumberArg && isLikelyDoorayUrl(projectArg)) {
@@ -2062,13 +2091,28 @@ async function resolvePostInput(client, args) {
2062
2091
  if (!postId) {
2063
2092
  throw new DoorayCliError(
2064
2093
  `Dooray URL \uD615\uC2DD\uC774 \uC62C\uBC14\uB974\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4: "${projectArg}"
2065
- \uC608: https://x.dooray.com/task/to/4319587406666362045`,
2094
+ ${URL_FORMAT_HINT}`,
2066
2095
  EXIT_PARAM_ERROR
2067
2096
  );
2068
2097
  }
2069
2098
  return resolveByPostId(client, postId);
2070
2099
  }
2071
2100
  if (projectArg && postNumberArg) {
2101
+ const numType = classifyPostInputToken(postNumberArg);
2102
+ if (numType === "postId") {
2103
+ throw new DoorayCliError(
2104
+ `"${postNumberArg}" \uB294 \uB0B4\uBD80 ID(postId)\uB85C \uBCF4\uC785\uB2C8\uB2E4.
2105
+ \uC5C5\uBB34 \uBC88\uD638(#N)\uAC00 \uC544\uB2CC \uB0B4\uBD80 ID \uB85C \uC870\uD68C\uD558\uB824\uBA74 --id \uC635\uC158\uC744 \uC0AC\uC6A9\uD558\uC138\uC694:
2106
+ dooray post get --id ${postNumberArg}`,
2107
+ EXIT_PARAM_ERROR
2108
+ );
2109
+ }
2110
+ if (numType !== "postNumber") {
2111
+ throw new DoorayCliError(
2112
+ `<post-number>\uAC00 \uC62C\uBC14\uB974\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4: "${postNumberArg}"`,
2113
+ EXIT_PARAM_ERROR
2114
+ );
2115
+ }
2072
2116
  const projectId = await resolveProject(client, projectArg);
2073
2117
  const num = Number(postNumberArg);
2074
2118
  if (!Number.isFinite(num) || num <= 0) {
@@ -4341,6 +4385,13 @@ var wikiPageFileListCommand = new import_commander39.Command("list").description
4341
4385
 
4342
4386
  // src/commands/wiki/page-file/upload.ts
4343
4387
  var import_commander40 = require("commander");
4388
+
4389
+ // src/utils/wiki-snippet.ts
4390
+ function wikiInlineImageSnippet(wikiId, attachFileId, name) {
4391
+ return `![${name}](/wikis/${wikiId}/files/${attachFileId})`;
4392
+ }
4393
+
4394
+ // src/commands/wiki/page-file/upload.ts
4344
4395
  var wikiPageFileUploadCommand = new import_commander40.Command("upload").description("\uC704\uD0A4 \uD398\uC774\uC9C0 \uCCA8\uBD80\uD30C\uC77C \uC5C5\uB85C\uB4DC (multipart type \uC21C\uC11C \uAC15\uC81C, ADR-029)").argument("[arg1]", "\uD504\uB85C\uC81D\uD2B8 \uCF54\uB4DC, Dooray Wiki URL, \uB610\uB294 (`--id`/`--url` \uBAA8\uB4DC\uC77C \uB54C) \uD30C\uC77C \uACBD\uB85C").argument("[arg2]", "page-id \uB610\uB294 (`--id`/`--url` \uBAA8\uB4DC\uC77C \uB54C) \uD30C\uC77C \uACBD\uB85C").argument("[arg3]", "\uD30C\uC77C \uACBD\uB85C (positional 3\uAC1C \uBAA8\uB4DC)").option("--id <pageId>", "\uC704\uD0A4 \uD398\uC774\uC9C0 ID").option("--url <url>", "Dooray Wiki URL").option("--project <code>", "\uD504\uB85C\uC81D\uD2B8 \uCF54\uB4DC (--id \uBAA8\uB4DC\uC5D0\uC11C wikiId \uD574\uC11D\uC6A9)").option("--file <path>", "\uC5C5\uB85C\uB4DC\uD560 \uD30C\uC77C \uACBD\uB85C (positional \uB300\uCCB4)").option("--type <type>", "\uD30C\uC77C \uD0C0\uC785: general | inline_image (\uAE30\uBCF8 general)", "general").action(async (arg1, arg2, arg3, opts) => {
4345
4396
  const globalOpts = wikiPageFileUploadCommand.optsWithGlobals();
4346
4397
  const config = await getConfigOrThrow();
@@ -4400,7 +4451,15 @@ var wikiPageFileUploadCommand = new import_commander40.Command("upload").descrip
4400
4451
  const res = await client.uploadWikiPageFile(wikiId, pageId, filePath, fileType);
4401
4452
  stopSpinner(true, "\uC5C5\uB85C\uB4DC \uC644\uB8CC");
4402
4453
  if (globalOpts.json) {
4403
- printJson(res.result);
4454
+ const payload = fileType === "inline_image" ? {
4455
+ ...res.result,
4456
+ markdownSnippet: wikiInlineImageSnippet(
4457
+ wikiId,
4458
+ res.result.attachFileId,
4459
+ res.result.name
4460
+ )
4461
+ } : res.result;
4462
+ printJson(payload);
4404
4463
  } else if (globalOpts.quiet) {
4405
4464
  process.stdout.write(`${res.result.id}
4406
4465
  `);
@@ -4415,8 +4474,10 @@ var wikiPageFileUploadCommand = new import_commander40.Command("upload").descrip
4415
4474
  `);
4416
4475
  if (fileType === "inline_image") {
4417
4476
  process.stdout.write("\n\uBCF8\uBB38 \uC0BD\uC785\uC6A9 markdown snippet (\uC9C1\uC811 wiki page edit \uC73C\uB85C \uBCF8\uBB38\uC5D0 \uBC15\uC73C\uC138\uC694):\n");
4418
- process.stdout.write(` ![${res.result.name}](/wikis/${wikiId}/files/${res.result.attachFileId})
4419
- `);
4477
+ process.stdout.write(
4478
+ ` ${wikiInlineImageSnippet(wikiId, res.result.attachFileId, res.result.name)}
4479
+ `
4480
+ );
4420
4481
  }
4421
4482
  }
4422
4483
  } catch (e) {
@@ -5651,7 +5712,7 @@ function sanitizeArgv(argv) {
5651
5712
 
5652
5713
  // src/index.ts
5653
5714
  var program = new import_commander61.Command();
5654
- program.name("dooray").description("Dooray REST API CLI").version("0.11.0").option("--json", "JSON \uD615\uC2DD\uC73C\uB85C \uCD9C\uB825").option("--quiet", "ID\uB9CC \uCD9C\uB825").option("--no-color", "\uC0C9\uC0C1 \uBE44\uD65C\uC131\uD654");
5715
+ program.name("dooray").description("Dooray REST API CLI").version("0.12.0").option("--json", "JSON \uD615\uC2DD\uC73C\uB85C \uCD9C\uB825").option("--quiet", "ID\uB9CC \uCD9C\uB825").option("--no-color", "\uC0C9\uC0C1 \uBE44\uD65C\uC131\uD654");
5655
5716
  program.hook("preAction", () => {
5656
5717
  const opts = program.opts();
5657
5718
  if (opts.color === false || process.env.NO_COLOR) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bifos/dooray-cli",
3
- "version": "0.11.0",
3
+ "version": "0.12.0",
4
4
  "description": "CLI tool for Dooray project management — AI agent & terminal friendly",
5
5
  "keywords": [
6
6
  "dooray",
@@ -11,14 +11,14 @@
11
11
  ],
12
12
  "repository": {
13
13
  "type": "git",
14
- "url": "https://github.com/jon890/dooray-cli.git"
14
+ "url": "git+https://github.com/jon890/dooray-cli.git"
15
15
  },
16
16
  "homepage": "https://github.com/jon890/dooray-cli#readme",
17
17
  "bugs": {
18
18
  "url": "https://github.com/jon890/dooray-cli/issues"
19
19
  },
20
20
  "bin": {
21
- "dooray": "./dist/index.js"
21
+ "dooray": "dist/index.js"
22
22
  },
23
23
  "files": [
24
24
  "dist",
@@ -47,8 +47,13 @@ dooray doctor # 설정 검증
47
47
 
48
48
  > **공통 (post 하위 16개 명령)**: 아래 명령은 `<project> <number>` 외에도 `--id <postId>`, `--url <url>`, 또는 첫 인자에 Dooray URL 을 직접 받는다.
49
49
  > `post get`/`edit`/`done`/`workflow`, `post comment list`/`add`/`edit`/`delete`, `post file list`/`upload`/`download`/`download-all`/`delete`, `post comment file list`/`upload`/`download`/`delete`.
50
- > URL 형식: `https://*.dooray.com/task/to/<postId>` 또는 브라우저 주소창 복사본 `https://*.dooray.com/task/<projectId>/<postId>`.
51
- > **사용자가 URL 줬으면 그대로인자로 전달**하는 것이 가장 빠른 경로 (resolve 단계 단축, ADR-020).
50
+ >
51
+ > 지원 URL 형식 3종 (positional 인자 / `--url` 공통):
52
+ > - `https://*.dooray.com/task/to/<postId>`
53
+ > - `https://*.dooray.com/task/<projectId>/<postId>` — 브라우저 주소창 복사본
54
+ > - `https://*.dooray.com/project/tasks/<postId>` — 프로젝트 업무 목록 → 업무 열기
55
+ >
56
+ > **사용자가 URL을 줬으면 그대로 첫 인자로 전달**하는 것이 가장 빠른 경로 (resolve 단계 단축).
52
57
 
53
58
  | 의도 | 커맨드 |
54
59
  |------|--------|
@@ -59,17 +64,17 @@ dooray doctor # 설정 검증
59
64
  | 프로젝트 멤버 그룹 목록 | `dooray project groups <project>` (ID / Code) |
60
65
  | 프로젝트 태그 목록 | `dooray project tags <project>` (ID / Color / Name / Group / Mandatory) |
61
66
  | 프로젝트 템플릿 목록 | `dooray project templates <project>` (id / templateName) |
62
- | 멤버 상세 (organizationMemberId) | `dooray member get <organizationMemberId>` (cache 우회, ADR-021) |
67
+ | 멤버 상세 (organizationMemberId) | `dooray member get <organizationMemberId>` (cache 우회) |
63
68
  | organization 전체 멤버 검색 | `dooray member search <keyword>` (이름 기본), `--email`(이메일 exact), `--user-code`(사번 like), `--user-code-exact`(사번 exact), `--page`/`--size` |
64
69
  | 업무 목록 조회 | `dooray post list <project>` |
65
- | 업무 검색 | `dooray post search <project|projectId> "<keyword>"` — projectId (15+자리 numeric) 직접 입력 시 cache 우회 (ADR-030) |
66
- | 업무 상세 보기 | `dooray post get <project> <number>` |
70
+ | 업무 검색 | `dooray post search <project|projectId> "<keyword>"` — projectId (15+자리 numeric) 직접 입력 시 cache 우회 |
71
+ | 업무 상세 보기 | `dooray post get <project> <number>` (번호) / `dooray post get --id <postId>` (internal ID) |
67
72
  | 업무 생성 | `dooray post create <project> --title "..." [--body "..." \| --body-file <path>]` (`--tag`/`--parent`/`--workflow`/`--milestone` 지원) |
68
- | 템플릿 기반 업무 생성 | `dooray post create <project> --template <name\|id>` — body/users/tags 자동 채움 (사용자 옵션 우선 override, ADR-027) |
73
+ | 템플릿 기반 업무 생성 | `dooray post create <project> --template <name\|id>` — body/users/tags 자동 채움 (사용자 옵션 우선 override) |
69
74
  | 업무 제목/본문 수정 | `dooray post edit <project> <number> --title "..." --body "..."` 또는 `--body-file <path>` |
70
75
  | 업무 완료 처리 | `dooray post done <project> <number>` |
71
76
  | 업무 워크플로우 변경 | `dooray post workflow <project> <number> <workflow>` |
72
- | 댓글 조회 | `dooray post comment list <project> <number>` (`--sort`, `--reverse`, `--latest`, `--since`, `--from-author` 필터. table: Creator 자동 채움, `--json`: raw, ADR-021) |
77
+ | 댓글 조회 | `dooray post comment list <project> <number>` (`--sort`, `--reverse`, `--latest`, `--since`, `--from-author` 필터. table: Creator 자동 채움, `--json`: raw) |
73
78
  | 최신 댓글 조회 | `dooray post comment latest <project> <number>` — 최신 댓글 1개 빠른 조회. `-n <N>`으로 N개 지정 |
74
79
  | 단일 댓글 조회 | `dooray post comment get <project> <number> <comment-id>` — 본문·메타·attachments 직접 fetch. `--id`/`--url` + `--comment-id` 모드 지원 |
75
80
  | 댓글 추가 | `dooray post comment add <project> <number> --body "..."` 또는 `--body-file <path>` |
@@ -83,7 +88,7 @@ dooray doctor # 설정 검증
83
88
  | 위키 페이지 수정 (본문) | `dooray wiki page edit <project> <page-id> --body "..."` 또는 `--body-file ./new.md` |
84
89
  | 위키 페이지 수정 (에디터) | `dooray wiki page edit <project> <page-id>` (플래그 없으면 $EDITOR 열림) |
85
90
  | 위키 페이지 첨부 목록 | `dooray wiki page file list <project> <page-id>` (general + inline 합산, type 컬럼) |
86
- | 위키 페이지 첨부 업로드 | `dooray wiki page file upload <project> <page-id> --file <path> [--type inline_image]` (multipart type 순서 ADR-029) |
91
+ | 위키 페이지 첨부 업로드 | `dooray wiki page file upload <project> <page-id> --file <path> [--type inline_image]` (multipart `type` 필드를 `file` 앞에 전송) |
87
92
  | 위키 페이지 첨부 다운로드 | `dooray wiki page file download <project> <page-id> --file-id <id> -o <dir>` |
88
93
  | 위키 페이지 첨부 일괄 다운로드 | `dooray wiki page file download-all <project> <page-id> -o <dir>` (files + images 전부) |
89
94
  | 위키 페이지 첨부 삭제 | `dooray wiki page file delete <project> <page-id> --file-id <id>` (confirm 없음) |
@@ -104,15 +109,15 @@ dooray doctor # 설정 검증
104
109
  | 전체 첨부파일 다운로드 | `dooray post file download-all <project> <number>` |
105
110
  | 첨부파일 업로드 | `dooray post file upload <project> <number> <file-path>` |
106
111
  | 첨부파일 삭제 | `dooray post file delete <project> <number> <file-id>` |
107
- | file 명령군 자동화 파싱 | `dooray post file <verb> ... --json` — `download` = `{outputPath,fileName,size}`, `download-all` = `{count,succeeded,failed}` (부분 실패 시 exit 1), `delete` = `{fileId,status}`, `upload` = `res.result` raw (ADR-031) |
112
+ | file 명령군 자동화 파싱 | `dooray post file <verb> ... --json` — `download` = `{outputPath,fileName,size}`, `download-all` = `{count,succeeded,failed}` (부분 실패 시 exit 1), `delete` = `{fileId,status}`, `upload` = `res.result` raw |
108
113
  | 댓글 첨부 목록 | `dooray post comment file list <project> <number> <comment-id>` |
109
114
  | 댓글 파일 업로드 | `dooray post comment file upload <project> <number> <comment-id> <path>` |
110
115
  | 댓글 파일 다운로드 | `dooray post comment file download <project> <number> <comment-id> <file-id>` |
111
116
  | 댓글 파일 삭제 | `dooray post comment file delete <project> <number> <comment-id> <file-id> --yes` |
112
- | 참조자(cc) 멤버/그룹 추가 | `dooray post edit <project> <number> --cc-group <code>` — 기존 참조자 유지 + 그룹 추가 (dedupe, ADR-025) |
117
+ | 참조자(cc) 멤버/그룹 추가 | `dooray post edit <project> <number> --cc-group <code>` — 기존 참조자 유지 + 그룹 추가 (dedupe) |
113
118
  | 참조자 전체 교체 | `dooray post edit <project> <number> --cc-clear --cc <name>` — 기존 참조자 비우고 신규 멤버만 |
114
119
  | 신규 업무 + 그룹 cc | `dooray post create <project> --title "..." --cc-group <code>` — 생성 시 그룹 참조자 포함 |
115
- | `--cc-group <code\|id>` / `--mention-group <code\|id>` | 그룹 매칭 — 15+자리 numeric → id 직접 / 그 외 → code matchByName (부분일치, ADR-028) |
120
+ | `--cc-group <code\|id>` / `--mention-group <code\|id>` | 그룹 매칭 — 15+자리 numeric → id 직접 / 그 외 → code matchByName (부분일치) |
116
121
  | 상위 업무 설정/변경 | `dooray post edit <project> <number> --title "<원제목>" --parent <ref>` (`<ref>`: `<project>/<number>` 또는 raw postId. `--title` 필수, unset 미지원) |
117
122
  | `dooray post edit --id <postId> --tag <name>` | 태그 추가 (반복, dedupe) |
118
123
  | `dooray post edit --id <postId> --tag-clear --tag <name>` | 태그 전체 교체 |
@@ -187,7 +192,7 @@ dooray post comment add <project> 42 --body "진행 상황 업데이트: 80% 완
187
192
  ### 시나리오 — 댓글에 스크린샷 자동 첨부
188
193
 
189
194
  스크립트가 스크린샷을 댓글에 삽입하거나, 에이전트가 결과 파일을 첨부 댓글로 보고할 때 사용.
190
- Dooray REST API 가 댓글 전용 attachment endpoint 를 미지원하므로 내부적으로 post-level files API + 댓글 본문 PUT 합성으로 동작 (ADR-024).
195
+ Dooray REST API 가 댓글 전용 attachment endpoint 를 미지원하므로 내부적으로 post-level files API + 댓글 본문 PUT 합성으로 동작한다.
191
196
 
192
197
  ```bash
193
198
  # 1. 댓글을 먼저 만든다 (텍스트만, --json 으로 commentId 획득)
@@ -208,7 +213,7 @@ dooray wiki pages <project> --json
208
213
  dooray wiki page get <project> <pageId> --json
209
214
  ```
210
215
 
211
- ### 첨부파일 일괄 다운로드 후 실패 분리 (ADR-031)
216
+ ### 첨부파일 일괄 다운로드 후 실패 분리
212
217
 
213
218
  ```bash
214
219
  # --json 으로 구조화 출력 → jq 로 성공/실패 분리
@@ -218,7 +223,7 @@ echo "$RESULT" | jq -r '.succeeded[].path'
218
223
  # exit code 1 이 설정되어 있으면 실패 있는 상태
219
224
  ```
220
225
 
221
- ### 위키 페이지 첨부파일 — 스킬 파일 팀 공유 (Issue #70)
226
+ ### 위키 페이지 첨부파일 — 스킬 파일 팀 공유
222
227
 
223
228
  **스킬 파일 팀 공유**: 팀 위키에 스킬 파일 (예: `SKILL.md`) 을 `wiki page file upload` 로 첨부 → 팀원이 `wiki page file download-all` 로 일괄 받아 `~/.claude/skills/` 에 그대로 설치.
224
229
 
@@ -233,6 +238,30 @@ dooray wiki page file download-all <project> <page-id> -o ~/.claude/skills/my-sk
233
238
  dooray wiki page file list <project> <page-id>
234
239
  ```
235
240
 
241
+ ### 위키 페이지 인라인 이미지 업로드 후 본문 자동 삽입
242
+
243
+ `--type inline_image` 로 업로드 시 `--json` 응답에 `markdownSnippet` 필드가 포함됩니다.
244
+ jq 로 추출해 본문에 바로 삽입하는 자동화가 가능합니다.
245
+
246
+ ```bash
247
+ # 1. 인라인 이미지 업로드 — --json 으로 markdownSnippet 추출
248
+ SNIPPET=$(dooray wiki page file upload <project> <page-id> \
249
+ --file ./diagram.png --type inline_image --json \
250
+ | jq -r '.markdownSnippet')
251
+ # SNIPPET = "![diagram.png](/wikis/<wikiId>/files/<attachFileId>)"
252
+
253
+ # 2. 기존 본문 조회
254
+ CURRENT_BODY=$(dooray wiki page get <project> <page-id> --json | jq -r '.body.content')
255
+
256
+ # 3. snippet 을 본문 끝에 추가해 업데이트
257
+ NEW_BODY="${CURRENT_BODY}
258
+
259
+ ${SNIPPET}"
260
+ dooray wiki page edit <project> <page-id> --body "$NEW_BODY"
261
+ ```
262
+
263
+ **참고**: `general` 타입은 `markdownSnippet` 없음. `--quiet` 은 id 만 출력 (snippet 미포함).
264
+
236
265
  ### 위키 페이지 댓글 — 회의록 결정사항 자동 누적
237
266
 
238
267
  **회의록 결정사항 자동 누적**: 회의록 위키 페이지에 자동화 봇이 `wiki page comment add` 로 결정사항을 댓글로 누적, `wiki page comment list --latest 20` 으로 최근 토론 흐름 추적.
@@ -273,25 +302,39 @@ dooray wiki page comment latest <project> <page-id>
273
302
 
274
303
  ## 커맨드 상세
275
304
 
276
- ### 업무 식별 방식 (post 하위 16개 명령 공통, ADR-020)
305
+ ### 업무 식별 방식 (post 하위 16개 명령 공통)
277
306
 
278
307
  아래 16개 명령은 4가지 입력을 모두 받는다:
279
308
  `post get`/`edit`/`done`/`workflow`, `post comment list`/`add`/`edit`/`delete`, `post file list`/`upload`/`download`/`download-all`/`delete`, `post comment file list`/`upload`/`download`/`delete`.
280
309
 
281
310
  ```bash
282
- # (1) 기존 positional — 가장 익숙한 형태
311
+ # (1) 기존 positional — 가장 익숙한 형태 (<number> 는 업무 번호 #N)
283
312
  dooray post get <project> 42
284
313
 
285
314
  # (2) Dooray URL을 첫 인자로 — 사용자 메시지에서 URL을 그대로 복사할 때 최적
315
+ # 지원 형식 3종 모두 동일하게 작동
286
316
  dooray post get https://x.dooray.com/task/to/<postId>
317
+ dooray post get https://x.dooray.com/task/<projectId>/<postId>
318
+ dooray post get https://x.dooray.com/project/tasks/<postId>
287
319
 
288
- # (3) --id <postId>
320
+ # (3) --id <postId> — post create 결과의 .id 를 그대로 전달
289
321
  dooray post get --id <postId>
290
322
 
291
- # (4) --url <url>
323
+ # (4) --url <url> — URL 형식 3종 모두 지원
292
324
  dooray post get --url https://x.dooray.com/task/to/<postId>
293
325
  ```
294
326
 
327
+ > ⚠️ **`post create` 결과 `.id` 는 internal postId (19자리 숫자)입니다.**
328
+ > 이 숫자를 `<project> <업무번호>` 의 번호 자리에 넣으면 안내 에러가 발생합니다.
329
+ > 후속 조회·수정·댓글은 반드시 **`--id <postId>`** 를 사용하세요.
330
+ >
331
+ > ```bash
332
+ > POST_ID=$(dooray post create <project> --title "..." --json | jq -r '.id')
333
+ > dooray post get --id "$POST_ID" # ✅ --id 사용
334
+ > dooray post comment add --id "$POST_ID" --body "댓글" # ✅ --id 사용
335
+ > # dooray post get <project> "$POST_ID" # ❌ 안내 에러 발생
336
+ > ```
337
+
295
338
  **우선순위 / 충돌 규칙**: `--id`+`--url` 동시 지정 → 에러.
296
339
  `--id`/`--url`+positional 동시 지정 → 에러.
297
340
  URL/`--id`/`--url` 모드는 standalone API(`getPost(postId)`)로 resolve 단계를 단축.
@@ -350,7 +393,7 @@ dooray post edit <project> <number> --body "새 본문"
350
393
  dooray post edit <project> <number> --title "새 제목" --body-file ./updated.md
351
394
  ```
352
395
 
353
- ### 참조자(cc) / 담당자(to) 변경 — 멤버 · 그룹 (ADR-025)
396
+ ### 참조자(cc) / 담당자(to) 변경 — 멤버 · 그룹
354
397
 
355
398
  ```bash
356
399
  # 기존 참조자 유지 + 그룹 추가 (dedupe: organizationMemberId / projectMemberGroupId)
@@ -372,7 +415,7 @@ dooray post edit --id "$POST_ID" --cc-group qa-team --dry-run --json \
372
415
 
373
416
  > interactive (`$EDITOR`) 모드에서는 위 옵션이 무시되고 stderr 경고가 출력됩니다.
374
417
 
375
- ## 동명이인 우회 — 이메일 / memberId 직접 (Issue #58)
418
+ ## 동명이인 우회 — 이메일 / memberId 직접
376
419
 
377
420
  이름이 동일한 멤버가 여러 명이라 `--cc 홍길동` 이 모호로 실패할 때:
378
421
 
@@ -390,7 +433,7 @@ dooray post edit --id "$POST_ID" --cc "$MEMBER_ID"
390
433
  - 이메일 정규형 — searchMembers exact
391
434
  - 그 외 — 이름 부분일치
392
435
 
393
- ## 신규 업무 생성 후 그룹 cc 첨부 (ADR-025)
436
+ ## 신규 업무 생성 후 그룹 cc 첨부
394
437
 
395
438
  자동화 패턴:
396
439
  1. audit 리포트 분석
@@ -411,7 +454,7 @@ dooray post edit --id "$POST_ID" --cc-group qa-team
411
454
 
412
455
  ---
413
456
 
414
- ## 자식 업무 먼저 → 후속 부모 지정 (Issue #60)
457
+ ## 자식 업무 먼저 → 후속 부모 지정
415
458
 
416
459
  ```bash
417
460
  # 1. 자식 업무 생성 (parent 모르고)
@@ -425,7 +468,7 @@ dooray post edit --id "$CHILD_ID" --title "subtask A" --parent <project>/<parent
425
468
 
426
469
  ---
427
470
 
428
- ## 태그 사후 분류 자동화 (Issue #66)
471
+ ## 태그 사후 분류 자동화
429
472
 
430
473
  분류 분석 결과를 받아 태그를 재분류하는 자동화는 단독 호출 패턴이 효율적:
431
474
 
@@ -437,7 +480,7 @@ dooray post edit --id "$POST_ID" --tag "분류: $CATEGORY"
437
480
  ```
438
481
 
439
482
  태그만 변경하는 시나리오에서 `--title` / `--body` 강제 없음.
440
- mandatory 그룹은 친절한 에러 메시지로 안내 (ADR-019).
483
+ mandatory 그룹은 친절한 에러 메시지로 안내.
441
484
 
442
485
  ---
443
486
 
@@ -463,7 +506,7 @@ dooray post comment latest <project> <number>
463
506
 
464
507
  ---
465
508
 
466
- ## 그룹 멘션 / cc 시 AI agent 동선 (Issue #76, ADR-028)
509
+ ## 그룹 멘션 / cc 시 AI agent 동선
467
510
 
468
511
  자연어 그룹명을 사용자가 지칭했을 때 AI agent 의 의사결정 순서:
469
512
 
@@ -481,7 +524,7 @@ dooray post comment latest <project> <number>
481
524
  AI agent 가 자연어 의도와 가장 가까운 code 선택 후 재시도.
482
525
 
483
526
  3. **모든 컬럼이 빈값일 때 (response shape 이상) 회피**
484
- - ADR-028 fix 이후 거의 발생 안 함 (`fetchAllMemberGroups` 가 nested array 정규화)
527
+ - 최근 수정 이후 거의 발생 안 함 (`fetchAllMemberGroups` 가 nested array 정규화)
485
528
  - 만약 발생 시: 사용자에게 그룹 id (UI 의 그룹 URL 에서 19자리 numeric) 확인 요청
486
529
  - `--cc-group <id>` / `--mention-group <id>` 직접 입력
487
530
  - 또는 그룹 멤버를 개별 `--cc <member>` / `--mention <member>` 로 지정
@@ -565,7 +608,7 @@ dooray feedback --last --title "에러 제목" --body "추가 설명" --dry-run
565
608
  dooray feedback --last --title "에러 제목" --body "추가 설명" # 실제 등록
566
609
  ```
567
610
 
568
- > **참고**: `--last` 모드는 `trackLastRun: true` (ADR-023 opt-in)가 설정된 경우에만 직전 실패 명령이 자동 기록됨.
611
+ > **참고**: `--last` 모드는 `trackLastRun: true` (opt-in)가 설정된 경우에만 직전 실패 명령이 자동 기록됨.
569
612
  > argv는 시크릿 패턴(`--api-key`/`--token`/`Authorization`) 마스킹 후 저장.
570
613
 
571
614
  ## 에러 핸들링
@@ -582,7 +625,7 @@ CLI 에러 발생 시 복구 방법:
582
625
 
583
626
  ---
584
627
 
585
- ## 정형 task 자동화 (Issue #59 / ADR-027)
628
+ ## 정형 task 자동화
586
629
 
587
630
  매주 같은 형식의 task 를 만드는 자동화는 템플릿 + override 패턴이 효율적:
588
631
 
@@ -598,7 +641,7 @@ POST_ID=$(dooray post create <project> \
598
641
  템플릿 본문의 `${year}` / `${month}` 등 매크로는 Dooray 가 자동 치환 (`interpolation=true` 기본).
599
642
  사용자 정의 변수는 미지원 — 필요 시 client 측 string replace 로 처리.
600
643
 
601
- ## projectId 직접 입력 시나리오 (Issue #78, ADR-030)
644
+ ## projectId 직접 입력 시나리오
602
645
 
603
646
  AI agent 가 `member=me` 응답에 없는 프로젝트의 업무를 다뤄야 할 때:
604
647
 
@@ -608,13 +651,13 @@ AI agent 가 `member=me` 응답에 없는 프로젝트의 업무를 다뤄야
608
651
  ```
609
652
 
610
653
  2. **사용자가 코드만 줬고 cache 매칭 실패 (member 아닌 프로젝트)**:
611
- - 에러 메시지의 ADR-030 안내 확인
654
+ - 에러 메시지의 안내 확인
612
655
  - 사용자에게 "프로젝트 ID 가 필요합니다 — Dooray UI 의 프로젝트 URL 에서 확인 가능" 요청
613
656
  - 또는 `dooray project list --type private` 로 private 캐시 갱신 시도
614
657
 
615
658
  3. **권한 없는 projectId 입력 시**: resolver 통과 후 후속 API 4xx 발생 — 에러 메시지에서 권한 부재 확인 후 사용자에게 보고
616
659
 
617
- 권한 검증이 resolver 단보다 한 단계 지연되는 trade-off — AI 친화적 자동화 우선 (ADR-030).
660
+ 권한 검증이 resolver 단보다 한 단계 지연되는 trade-off — AI 친화적 자동화 우선.
618
661
 
619
662
  ## 캐시
620
663