@juho0719/cckit 0.2.3 → 0.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/assets/hooks/agent-track.sh +53 -0
- package/assets/hooks/auto-commit-push.sh +123 -0
- package/assets/hooks/on-prompt-start.sh +6 -0
- package/assets/hooks/skill-track.sh +26 -0
- package/assets/hooks/subagent-notify.sh +20 -0
- package/assets/skills/plan-viewer/SKILL.md +162 -0
- package/assets/skills/plan-viewer/scripts/frame-template.html +266 -0
- package/assets/skills/plan-viewer/scripts/helper.js +84 -0
- package/assets/skills/plan-viewer/scripts/server.js +334 -0
- package/assets/skills/plan-viewer/scripts/start-server.sh +122 -0
- package/assets/skills/plan-viewer/scripts/stop-server.sh +27 -0
- package/assets/skills/plan-viewer/spec-reviewer-prompt.md +47 -0
- package/assets/skills/plan-viewer/visual-guide.md +234 -0
- package/assets/skills/scaffold/SKILL.md +119 -0
- package/assets/skills/scaffold/presets.md +94 -0
- package/assets/skills/scaffold/scripts/common.sh +73 -0
- package/assets/skills/scaffold/scripts/monorepo.sh +449 -0
- package/assets/skills/scaffold/scripts/nextjs-fullstack.sh +305 -0
- package/assets/statusline/statusline.sh +186 -0
- package/dist/{chunk-OLLOS3GG.js → chunk-E3INXQNO.js} +38 -8
- package/dist/{chunk-SLVASXTF.js → chunk-W7RWPDBH.js} +13 -3
- package/dist/{cli-6WQMAFNA.js → cli-KZYBSIXO.js} +43 -14
- package/dist/{hooks-S73JTX2I.js → hooks-A2WQ2LGG.js} +1 -1
- package/dist/index.js +10 -10
- package/dist/{registry-BU55RMHU.js → registry-KRLOB4TH.js} +1 -1
- package/dist/{uninstall-cli-7XGNDIUC.js → uninstall-cli-GLYJG5V2.js} +2 -2
- package/package.json +1 -1
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Agent 툴 실행 추적 — 여러 에이전트 동시 지원
|
|
3
|
+
# @hook-event PreToolUse|Agent|pre
|
|
4
|
+
# @hook-event PostToolUse|Agent|post
|
|
5
|
+
|
|
6
|
+
AGENT_STATE_DIR="/tmp/claude-agents"
|
|
7
|
+
mkdir -p "$AGENT_STATE_DIR"
|
|
8
|
+
|
|
9
|
+
EVENT="${1:-post}"
|
|
10
|
+
INPUT=$(cat)
|
|
11
|
+
|
|
12
|
+
# subagent_type + description으로 고유 파일명 생성
|
|
13
|
+
AGENT_ID=$(echo "$INPUT" | python3 -c "
|
|
14
|
+
import sys, json, hashlib
|
|
15
|
+
try:
|
|
16
|
+
d = json.load(sys.stdin)
|
|
17
|
+
ti = d.get('tool_input', {})
|
|
18
|
+
key = (ti.get('subagent_type', '') or '') + '|' + (ti.get('description', '') or '')
|
|
19
|
+
print(hashlib.md5(key.encode()).hexdigest()[:12])
|
|
20
|
+
except:
|
|
21
|
+
print('')
|
|
22
|
+
" 2>/dev/null)
|
|
23
|
+
|
|
24
|
+
if [ -z "$AGENT_ID" ]; then
|
|
25
|
+
exit 0
|
|
26
|
+
fi
|
|
27
|
+
|
|
28
|
+
if [ "$EVENT" = "pre" ]; then
|
|
29
|
+
SUBAGENT_TYPE=$(echo "$INPUT" | python3 -c "
|
|
30
|
+
import sys, json
|
|
31
|
+
try:
|
|
32
|
+
d = json.load(sys.stdin)
|
|
33
|
+
print(d.get('tool_input', {}).get('subagent_type', '') or '')
|
|
34
|
+
except:
|
|
35
|
+
print('')
|
|
36
|
+
" 2>/dev/null)
|
|
37
|
+
|
|
38
|
+
DESCRIPTION=$(echo "$INPUT" | python3 -c "
|
|
39
|
+
import sys, json
|
|
40
|
+
try:
|
|
41
|
+
d = json.load(sys.stdin)
|
|
42
|
+
desc = d.get('tool_input', {}).get('description', '') or ''
|
|
43
|
+
print(desc[:50] if len(desc) > 50 else desc)
|
|
44
|
+
except:
|
|
45
|
+
print('')
|
|
46
|
+
" 2>/dev/null)
|
|
47
|
+
|
|
48
|
+
if [ -n "$SUBAGENT_TYPE" ]; then
|
|
49
|
+
echo "${SUBAGENT_TYPE}|${DESCRIPTION}" > "${AGENT_STATE_DIR}/${AGENT_ID}"
|
|
50
|
+
fi
|
|
51
|
+
else
|
|
52
|
+
rm -f "${AGENT_STATE_DIR}/${AGENT_ID}"
|
|
53
|
+
fi
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Auto commit & push hook - runs when Claude Code finishes a response
|
|
3
|
+
# @hook-event Stop||
|
|
4
|
+
# Compatible with bash 3.2 (macOS default)
|
|
5
|
+
|
|
6
|
+
# Get the working directory from the hook input
|
|
7
|
+
WORK_DIR=$(echo "$CLAUDE_HOOK_INPUT" | jq -r '.cwd // empty' 2>/dev/null)
|
|
8
|
+
if [ -z "$WORK_DIR" ]; then
|
|
9
|
+
WORK_DIR="$(pwd)"
|
|
10
|
+
fi
|
|
11
|
+
|
|
12
|
+
cd "$WORK_DIR" 2>/dev/null || exit 0
|
|
13
|
+
|
|
14
|
+
# Check if we're in a git repo
|
|
15
|
+
if ! git rev-parse --is-inside-work-tree &>/dev/null; then
|
|
16
|
+
exit 0
|
|
17
|
+
fi
|
|
18
|
+
|
|
19
|
+
# Check if there are any changes
|
|
20
|
+
if git diff --quiet && git diff --cached --quiet && [ -z "$(git ls-files --others --exclude-standard)" ]; then
|
|
21
|
+
exit 0
|
|
22
|
+
fi
|
|
23
|
+
|
|
24
|
+
BRANCH=$(git symbolic-ref --short HEAD 2>/dev/null)
|
|
25
|
+
if [ -z "$BRANCH" ]; then
|
|
26
|
+
exit 0
|
|
27
|
+
fi
|
|
28
|
+
|
|
29
|
+
# Stage all changes
|
|
30
|
+
git add -A
|
|
31
|
+
|
|
32
|
+
# ── 변경 파일 목록 수집 ───────────────────────────────
|
|
33
|
+
ADDED_FILES=$(git diff --cached --diff-filter=A --name-only)
|
|
34
|
+
MODIFIED_FILES=$(git diff --cached --diff-filter=M --name-only)
|
|
35
|
+
DELETED_FILES=$(git diff --cached --diff-filter=D --name-only)
|
|
36
|
+
|
|
37
|
+
ALL_FILES=$(git diff --cached --name-only)
|
|
38
|
+
ADDED_COUNT=$(echo "$ADDED_FILES" | grep -c . || true)
|
|
39
|
+
MODIFIED_COUNT=$(echo "$MODIFIED_FILES" | grep -c . || true)
|
|
40
|
+
DELETED_COUNT=$(echo "$DELETED_FILES" | grep -c . || true)
|
|
41
|
+
|
|
42
|
+
# ── 커밋 타입 감지 ────────────────────────────────────
|
|
43
|
+
# test: 파일이 모두 테스트/설정 관련이면 test/chore
|
|
44
|
+
HAS_TEST=$(echo "$ALL_FILES" | grep -E "(\.test\.|\.spec\.|__tests__|e2e/)" | head -1)
|
|
45
|
+
HAS_CONFIG=$(echo "$ALL_FILES" | grep -E "\.(config|json|yaml|yml|env|toml)$" | head -1)
|
|
46
|
+
HAS_ROADMAP=$(echo "$ALL_FILES" | grep -E "roadmap|ROADMAP|\.md$" | head -1)
|
|
47
|
+
HAS_SRC=$(echo "$ALL_FILES" | grep -E "^src/" | head -1)
|
|
48
|
+
|
|
49
|
+
if [ -n "$HAS_TEST" ] && [ -z "$HAS_SRC" ]; then
|
|
50
|
+
COMMIT_TYPE="test"
|
|
51
|
+
elif [ -n "$HAS_CONFIG" ] && [ -z "$HAS_SRC" ]; then
|
|
52
|
+
COMMIT_TYPE="chore"
|
|
53
|
+
elif [ -n "$HAS_ROADMAP" ] && [ -z "$HAS_SRC" ]; then
|
|
54
|
+
COMMIT_TYPE="docs"
|
|
55
|
+
else
|
|
56
|
+
COMMIT_TYPE="feat"
|
|
57
|
+
fi
|
|
58
|
+
|
|
59
|
+
# ── 스코프: 가장 많이 변경된 2단계 디렉토리 ────────────
|
|
60
|
+
SCOPE=$(echo "$ALL_FILES" \
|
|
61
|
+
| awk -F'/' 'NF>=2 {print $1"/"$2} NF==1 {print $1}' \
|
|
62
|
+
| sort | uniq -c | sort -rn \
|
|
63
|
+
| awk 'NR==1{print $2}')
|
|
64
|
+
[ -z "$SCOPE" ] && SCOPE="root"
|
|
65
|
+
|
|
66
|
+
# ── Subject: 주요 파일명 나열 (확장자 제거, 최대 3개) ───
|
|
67
|
+
key_files() {
|
|
68
|
+
echo "$1" | while IFS= read -r f; do
|
|
69
|
+
[ -n "$f" ] && basename "$f" | sed 's/\.[^.]*$//'
|
|
70
|
+
done | head -3 | tr '\n' ',' | sed 's/,$//' | sed 's/,/, /g'
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
ADDED_NAMES=$(key_files "$ADDED_FILES")
|
|
74
|
+
MODIFIED_NAMES=$(key_files "$MODIFIED_FILES")
|
|
75
|
+
DELETED_NAMES=$(key_files "$DELETED_FILES")
|
|
76
|
+
|
|
77
|
+
SUBJECT_PARTS=""
|
|
78
|
+
[ -n "$ADDED_NAMES" ] && SUBJECT_PARTS="${SUBJECT_PARTS}add ${ADDED_NAMES}"
|
|
79
|
+
[ -n "$MODIFIED_NAMES" ] && SUBJECT_PARTS="${SUBJECT_PARTS:+$SUBJECT_PARTS; }update ${MODIFIED_NAMES}"
|
|
80
|
+
[ -n "$DELETED_NAMES" ] && SUBJECT_PARTS="${SUBJECT_PARTS:+$SUBJECT_PARTS; }remove ${DELETED_NAMES}"
|
|
81
|
+
|
|
82
|
+
# 파일이 4개 이상이면 개수도 병기
|
|
83
|
+
TOTAL=$((ADDED_COUNT + MODIFIED_COUNT + DELETED_COUNT))
|
|
84
|
+
if [ "$TOTAL" -gt 3 ]; then
|
|
85
|
+
SUBJECT_PARTS="${SUBJECT_PARTS} (+$((TOTAL - 3)) more)"
|
|
86
|
+
fi
|
|
87
|
+
|
|
88
|
+
SUBJECT="${COMMIT_TYPE}(${SCOPE}): ${SUBJECT_PARTS}"
|
|
89
|
+
|
|
90
|
+
# ── Body: 전체 파일 목록 ─────────────────────────────
|
|
91
|
+
BODY=""
|
|
92
|
+
if [ -n "$ADDED_FILES" ]; then
|
|
93
|
+
BODY="${BODY}New files:\n"
|
|
94
|
+
while IFS= read -r f; do
|
|
95
|
+
[ -n "$f" ] && BODY="${BODY} + ${f}\n"
|
|
96
|
+
done <<< "$ADDED_FILES"
|
|
97
|
+
fi
|
|
98
|
+
if [ -n "$MODIFIED_FILES" ]; then
|
|
99
|
+
[ -n "$BODY" ] && BODY="${BODY}\n"
|
|
100
|
+
BODY="${BODY}Modified:\n"
|
|
101
|
+
while IFS= read -r f; do
|
|
102
|
+
[ -n "$f" ] && BODY="${BODY} ~ ${f}\n"
|
|
103
|
+
done <<< "$MODIFIED_FILES"
|
|
104
|
+
fi
|
|
105
|
+
if [ -n "$DELETED_FILES" ]; then
|
|
106
|
+
[ -n "$BODY" ] && BODY="${BODY}\n"
|
|
107
|
+
BODY="${BODY}Deleted:\n"
|
|
108
|
+
while IFS= read -r f; do
|
|
109
|
+
[ -n "$f" ] && BODY="${BODY} - ${f}\n"
|
|
110
|
+
done <<< "$DELETED_FILES"
|
|
111
|
+
fi
|
|
112
|
+
|
|
113
|
+
FULL_MSG="${SUBJECT}\n\n${BODY}"
|
|
114
|
+
|
|
115
|
+
git commit -m "$(printf "%b" "$FULL_MSG")" --no-verify 2>/dev/null
|
|
116
|
+
|
|
117
|
+
# Push to remote if available
|
|
118
|
+
REMOTE=$(git remote 2>/dev/null | head -1)
|
|
119
|
+
if [ -n "$REMOTE" ]; then
|
|
120
|
+
git push "$REMOTE" "$BRANCH" --no-verify 2>/dev/null
|
|
121
|
+
fi
|
|
122
|
+
|
|
123
|
+
exit 0
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Skill 툴 실행 추적
|
|
3
|
+
# @hook-event PreToolUse|Skill|pre
|
|
4
|
+
# @hook-event PostToolUse|Skill|post
|
|
5
|
+
|
|
6
|
+
SKILL_STATE_FILE="/tmp/claude-active-skill.txt"
|
|
7
|
+
|
|
8
|
+
EVENT="${1:-post}"
|
|
9
|
+
INPUT=$(cat)
|
|
10
|
+
|
|
11
|
+
if [ "$EVENT" = "pre" ]; then
|
|
12
|
+
SKILL_NAME=$(echo "$INPUT" | python3 -c "
|
|
13
|
+
import sys, json
|
|
14
|
+
try:
|
|
15
|
+
d = json.load(sys.stdin)
|
|
16
|
+
print(d.get('tool_input', {}).get('skill', '') or '')
|
|
17
|
+
except:
|
|
18
|
+
print('')
|
|
19
|
+
" 2>/dev/null)
|
|
20
|
+
|
|
21
|
+
if [ -n "$SKILL_NAME" ]; then
|
|
22
|
+
echo "$SKILL_NAME" > "$SKILL_STATE_FILE"
|
|
23
|
+
fi
|
|
24
|
+
else
|
|
25
|
+
rm -f "$SKILL_STATE_FILE"
|
|
26
|
+
fi
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# 응답 완료 시 macOS 알림 발송 — 마지막 커밋 메시지를 요약으로 표시
|
|
3
|
+
# @hook-event Stop||
|
|
4
|
+
|
|
5
|
+
WORK_DIR=$(echo "$CLAUDE_HOOK_INPUT" | jq -r '.cwd // empty' 2>/dev/null)
|
|
6
|
+
if [ -z "$WORK_DIR" ]; then
|
|
7
|
+
WORK_DIR="$(pwd)"
|
|
8
|
+
fi
|
|
9
|
+
|
|
10
|
+
# 마지막 git 커밋 메시지를 알림 본문으로 사용
|
|
11
|
+
MSG=""
|
|
12
|
+
if git -C "$WORK_DIR" rev-parse --is-inside-work-tree &>/dev/null; then
|
|
13
|
+
MSG=$(git -C "$WORK_DIR" log --oneline -1 --format="%s" 2>/dev/null)
|
|
14
|
+
fi
|
|
15
|
+
|
|
16
|
+
if [ -z "$MSG" ]; then
|
|
17
|
+
MSG="응답이 완료됐습니다."
|
|
18
|
+
fi
|
|
19
|
+
|
|
20
|
+
osascript -e "display notification \"${MSG}\" with title \"Claude Code\" subtitle \"응답 완료\" sound name \"Glass\""
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: plan-viewer
|
|
3
|
+
description: "아키텍처/계획 단계의 구성을 브라우저 기반 비주얼 뷰어로 보여주는 스킬. 컨텍스트 탐색 → 질문 → 접근안 제시 → 디자인 제시 → 스펙 문서 → 리뷰 루프 → 계획 작성 순서로 진행. 시각 자료가 필요한 질문에서만 브라우저를 활성화."
|
|
4
|
+
argument-hint: [topic or feature description]
|
|
5
|
+
allowed-tools: Bash, Read, Write, Edit, Glob, Grep, Agent
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Plan Viewer
|
|
9
|
+
|
|
10
|
+
아키텍처와 계획 단계의 구성을 브라우저 기반 비주얼 뷰어로 보여주는 스킬.
|
|
11
|
+
세션 전체를 브라우저 모드로 바꾸지 않고, **질문 단위로 시각 자료가 더 나을 때만** 브라우저를 활성화한다.
|
|
12
|
+
|
|
13
|
+
<HARD-GATE>
|
|
14
|
+
이 스킬은 설계/계획 단계 전용이다. 코드 구현, 스캐폴딩 등 실행 작업은 수행하지 않는다.
|
|
15
|
+
설계가 완료되고 사용자 승인을 받은 후에야 구현 단계로 넘어갈 수 있다.
|
|
16
|
+
</HARD-GATE>
|
|
17
|
+
|
|
18
|
+
## 체크리스트
|
|
19
|
+
|
|
20
|
+
반드시 아래 순서를 따른다:
|
|
21
|
+
|
|
22
|
+
1. **컨텍스트 탐색** — 프로젝트 파일, 문서, 최근 커밋 확인
|
|
23
|
+
2. **비주얼 뷰어 제안** — 시각적 질문이 예상될 때 사용자 동의 요청 (별도 메시지)
|
|
24
|
+
3. **질문** — 한 번에 하나씩, 목적/제약/성공 기준 파악
|
|
25
|
+
4. **접근안 2~3개 제시** — 트레이드오프와 추천안 포함
|
|
26
|
+
5. **디자인 제시** — 섹션별로 나눠서 제시, 각 섹션마다 사용자 승인
|
|
27
|
+
6. **스펙 문서 작성** — `docs/specs/YYYY-MM-DD-<topic>-design.md`에 저장
|
|
28
|
+
7. **리뷰 루프** — spec-reviewer 서브에이전트로 검증, 문제 발견 시 수정 후 재검증 (최대 5회)
|
|
29
|
+
8. **사용자 스펙 리뷰** — 사용자에게 작성된 스펙 검토 요청
|
|
30
|
+
9. **계획 작성** — `docs/plans/YYYY-MM-DD-<topic>-plan.md`에 구현 계획 저장
|
|
31
|
+
|
|
32
|
+
## 프로세스 흐름
|
|
33
|
+
|
|
34
|
+
```
|
|
35
|
+
컨텍스트 탐색
|
|
36
|
+
↓
|
|
37
|
+
시각적 질문 예상? ─yes─→ 비주얼 뷰어 제안 (별도 메시지)
|
|
38
|
+
│no ↓
|
|
39
|
+
↓ 사용자 수락?
|
|
40
|
+
질문 (한 번에 하나) ←──────────┘
|
|
41
|
+
↓
|
|
42
|
+
접근안 2~3개 제시
|
|
43
|
+
↓
|
|
44
|
+
디자인 제시 (섹션별)
|
|
45
|
+
↓
|
|
46
|
+
사용자 승인? ─no─→ 수정 후 재제시
|
|
47
|
+
│yes
|
|
48
|
+
↓
|
|
49
|
+
스펙 문서 작성
|
|
50
|
+
↓
|
|
51
|
+
리뷰 루프 (최대 5회)
|
|
52
|
+
↓
|
|
53
|
+
사용자 스펙 리뷰
|
|
54
|
+
↓
|
|
55
|
+
계획(md) 작성
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## 비주얼 뷰어
|
|
59
|
+
|
|
60
|
+
브라우저 기반 비주얼 뷰어. 세션 모드가 아닌 **도구**로 사용한다.
|
|
61
|
+
사용자가 수락하더라도 **매 질문마다** 브라우저/터미널 중 적절한 것을 판단한다.
|
|
62
|
+
|
|
63
|
+
### 제안 메시지
|
|
64
|
+
|
|
65
|
+
시각적 질문이 예상될 때 **별도 메시지**로 제안한다 (다른 내용과 섞지 않는다):
|
|
66
|
+
|
|
67
|
+
> "이후 논의에서 아키텍처 다이어그램, 레이아웃 비교, 플로우차트 등 시각 자료가 도움이 될 수 있습니다. 브라우저에서 시각 자료를 보여드릴까요? (로컬 URL을 열어야 합니다)"
|
|
68
|
+
|
|
69
|
+
### 브라우저 vs 터미널 판단 기준
|
|
70
|
+
|
|
71
|
+
**브라우저 사용** — 콘텐츠 자체가 시각적일 때:
|
|
72
|
+
- 아키텍처 다이어그램 — 시스템 컴포넌트, 데이터 흐름, 관계도
|
|
73
|
+
- UI 목업 — 와이어프레임, 레이아웃, 네비게이션 구조
|
|
74
|
+
- 비교 뷰 — 두 아키텍처 비교, 두 레이아웃 비교
|
|
75
|
+
- 플로우차트 — 상태 머신, 프로세스 흐름
|
|
76
|
+
- 시퀀스 다이어그램 — 서비스 간 통신 흐름
|
|
77
|
+
|
|
78
|
+
**터미널 사용** — 텍스트/표로 충분할 때:
|
|
79
|
+
- 요구사항, 스코프 질문
|
|
80
|
+
- 개념적 A/B/C 선택
|
|
81
|
+
- 트레이드오프 비교표
|
|
82
|
+
- 기술 의사결정 (API 설계, 데이터 모델링)
|
|
83
|
+
- 명확화 질문
|
|
84
|
+
|
|
85
|
+
> 아키텍처 "주제"에 대한 질문이라고 해서 자동으로 시각적 질문이 되는 것은 아니다.
|
|
86
|
+
> "마이크로서비스로 갈까요, 모놀리스로 갈까요?"는 개념적 질문 → 터미널.
|
|
87
|
+
> "이 두 아키텍처 다이어그램 중 어느 것이 적합할까요?"는 시각적 질문 → 브라우저.
|
|
88
|
+
|
|
89
|
+
### 서버 시작
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
# 프로젝트 디렉터리에 영구 저장
|
|
93
|
+
scripts/start-server.sh --project-dir /path/to/project
|
|
94
|
+
|
|
95
|
+
# 반환: {"type":"server-started","port":52341,"url":"http://localhost:52341",
|
|
96
|
+
# "screen_dir":"/path/to/project/.plan-viewer/sessions/12345-1706000000"}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
`screen_dir`을 저장하고, 사용자에게 URL을 알려준다.
|
|
100
|
+
|
|
101
|
+
### 비주얼 뷰어 사용 시 상세 가이드
|
|
102
|
+
|
|
103
|
+
사용자가 뷰어를 수락하면, 상세 가이드를 읽고 진행한다:
|
|
104
|
+
`skills/plan-viewer/visual-guide.md`
|
|
105
|
+
|
|
106
|
+
## 프로세스 상세
|
|
107
|
+
|
|
108
|
+
### 1. 컨텍스트 탐색
|
|
109
|
+
|
|
110
|
+
- 프로젝트 구조, 기존 문서, 최근 커밋을 확인
|
|
111
|
+
- 프로젝트의 기술 스택, 아키텍처 패턴을 파악
|
|
112
|
+
|
|
113
|
+
### 2. 질문
|
|
114
|
+
|
|
115
|
+
- **한 번에 하나씩** — 여러 질문을 한꺼번에 하지 않는다
|
|
116
|
+
- 가능하면 **객관식** 형태로 제시
|
|
117
|
+
- 목적, 제약 조건, 성공 기준에 집중
|
|
118
|
+
- 범위가 너무 넓으면 먼저 분해를 제안
|
|
119
|
+
|
|
120
|
+
### 3. 접근안 2~3개 제시
|
|
121
|
+
|
|
122
|
+
- 각 접근안의 트레이드오프를 명확히 제시
|
|
123
|
+
- 추천안을 먼저 제시하고 이유를 설명
|
|
124
|
+
- 시각적 비교가 유용하면 브라우저로 보여줌
|
|
125
|
+
|
|
126
|
+
### 4. 디자인 제시
|
|
127
|
+
|
|
128
|
+
- 복잡도에 비례하여 섹션 크기 조절
|
|
129
|
+
- 각 섹션 후 사용자 승인 확인
|
|
130
|
+
- 다루는 내용: 아키텍처, 컴포넌트, 데이터 흐름, 에러 처리, 테스트
|
|
131
|
+
|
|
132
|
+
### 5. 스펙 문서 작성
|
|
133
|
+
|
|
134
|
+
- `docs/specs/YYYY-MM-DD-<topic>-design.md`에 저장
|
|
135
|
+
- git commit
|
|
136
|
+
|
|
137
|
+
### 6. 리뷰 루프
|
|
138
|
+
|
|
139
|
+
스펙 문서 작성 후 spec-reviewer 서브에이전트를 실행:
|
|
140
|
+
|
|
141
|
+
1. 서브에이전트 실행 (spec-reviewer-prompt.md 참조)
|
|
142
|
+
2. 문제 발견 시: 수정 → 재실행 반복
|
|
143
|
+
3. 5회 초과 시 사용자에게 에스컬레이션
|
|
144
|
+
|
|
145
|
+
### 7. 사용자 스펙 리뷰
|
|
146
|
+
|
|
147
|
+
> "스펙이 `<path>`에 작성되었습니다. 구현 계획 작성 전에 검토해 주세요."
|
|
148
|
+
|
|
149
|
+
### 8. 계획 작성
|
|
150
|
+
|
|
151
|
+
- `docs/plans/YYYY-MM-DD-<topic>-plan.md`에 구현 계획 저장
|
|
152
|
+
- 구현 순서, 의존성, 마일스톤 포함
|
|
153
|
+
- git commit
|
|
154
|
+
|
|
155
|
+
## 핵심 원칙
|
|
156
|
+
|
|
157
|
+
- **한 번에 하나의 질문** — 사용자를 압도하지 않는다
|
|
158
|
+
- **시각 자료는 선택적** — 세션 전체가 아닌 질문 단위로 판단
|
|
159
|
+
- **YAGNI** — 불필요한 기능은 설계에서 제외
|
|
160
|
+
- **접근안 탐색** — 반드시 2~3가지 접근안을 비교
|
|
161
|
+
- **점진적 검증** — 섹션별 승인, 스펙 리뷰 루프
|
|
162
|
+
- **계획으로 마무리** — 최종 산출물은 항상 md 계획 파일
|
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html>
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<title>Plan Viewer</title>
|
|
6
|
+
<style>
|
|
7
|
+
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
8
|
+
html, body { height: 100%; overflow: hidden; }
|
|
9
|
+
|
|
10
|
+
/* ===== THEME VARIABLES ===== */
|
|
11
|
+
:root {
|
|
12
|
+
--bg-primary: #f5f5f7;
|
|
13
|
+
--bg-secondary: #ffffff;
|
|
14
|
+
--bg-tertiary: #e5e5e7;
|
|
15
|
+
--border: #d1d1d6;
|
|
16
|
+
--text-primary: #1d1d1f;
|
|
17
|
+
--text-secondary: #86868b;
|
|
18
|
+
--text-tertiary: #aeaeb2;
|
|
19
|
+
--accent: #5856d6;
|
|
20
|
+
--accent-hover: #6866e0;
|
|
21
|
+
--success: #34c759;
|
|
22
|
+
--warning: #ff9f0a;
|
|
23
|
+
--error: #ff3b30;
|
|
24
|
+
--selected-bg: #eeedf9;
|
|
25
|
+
--selected-border: #5856d6;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
@media (prefers-color-scheme: dark) {
|
|
29
|
+
:root {
|
|
30
|
+
--bg-primary: #1d1d1f;
|
|
31
|
+
--bg-secondary: #2d2d2f;
|
|
32
|
+
--bg-tertiary: #3d3d3f;
|
|
33
|
+
--border: #424245;
|
|
34
|
+
--text-primary: #f5f5f7;
|
|
35
|
+
--text-secondary: #86868b;
|
|
36
|
+
--text-tertiary: #636366;
|
|
37
|
+
--accent: #7d7aff;
|
|
38
|
+
--accent-hover: #9492ff;
|
|
39
|
+
--selected-bg: rgba(125, 122, 255, 0.15);
|
|
40
|
+
--selected-border: #7d7aff;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
body {
|
|
45
|
+
font-family: system-ui, -apple-system, BlinkMacSystemFont, sans-serif;
|
|
46
|
+
background: var(--bg-primary);
|
|
47
|
+
color: var(--text-primary);
|
|
48
|
+
display: flex;
|
|
49
|
+
flex-direction: column;
|
|
50
|
+
line-height: 1.5;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/* ===== FRAME STRUCTURE ===== */
|
|
54
|
+
.header {
|
|
55
|
+
background: var(--bg-secondary);
|
|
56
|
+
padding: 0.5rem 1.5rem;
|
|
57
|
+
display: flex;
|
|
58
|
+
justify-content: space-between;
|
|
59
|
+
align-items: center;
|
|
60
|
+
border-bottom: 1px solid var(--border);
|
|
61
|
+
flex-shrink: 0;
|
|
62
|
+
}
|
|
63
|
+
.header h1 { font-size: 0.85rem; font-weight: 500; color: var(--text-secondary); }
|
|
64
|
+
.header .status { font-size: 0.7rem; color: var(--success); display: flex; align-items: center; gap: 0.4rem; }
|
|
65
|
+
.header .status::before { content: ''; width: 6px; height: 6px; background: var(--success); border-radius: 50%; }
|
|
66
|
+
|
|
67
|
+
.main { flex: 1; overflow-y: auto; }
|
|
68
|
+
#claude-content { padding: 2rem; min-height: 100%; }
|
|
69
|
+
|
|
70
|
+
.indicator-bar {
|
|
71
|
+
background: var(--bg-secondary);
|
|
72
|
+
border-top: 1px solid var(--border);
|
|
73
|
+
padding: 0.5rem 1.5rem;
|
|
74
|
+
flex-shrink: 0;
|
|
75
|
+
text-align: center;
|
|
76
|
+
}
|
|
77
|
+
.indicator-bar span {
|
|
78
|
+
font-size: 0.75rem;
|
|
79
|
+
color: var(--text-secondary);
|
|
80
|
+
}
|
|
81
|
+
.indicator-bar .selected-text {
|
|
82
|
+
color: var(--accent);
|
|
83
|
+
font-weight: 500;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/* ===== TYPOGRAPHY ===== */
|
|
87
|
+
h2 { font-size: 1.5rem; font-weight: 600; margin-bottom: 0.5rem; }
|
|
88
|
+
h3 { font-size: 1.1rem; font-weight: 600; margin-bottom: 0.25rem; }
|
|
89
|
+
.subtitle { color: var(--text-secondary); margin-bottom: 1.5rem; }
|
|
90
|
+
.section { margin-bottom: 2rem; }
|
|
91
|
+
.label { font-size: 0.7rem; color: var(--text-secondary); text-transform: uppercase; letter-spacing: 0.05em; margin-bottom: 0.5rem; }
|
|
92
|
+
|
|
93
|
+
/* ===== OPTIONS (A/B/C choices) ===== */
|
|
94
|
+
.options { display: flex; flex-direction: column; gap: 0.75rem; }
|
|
95
|
+
.option {
|
|
96
|
+
background: var(--bg-secondary);
|
|
97
|
+
border: 2px solid var(--border);
|
|
98
|
+
border-radius: 12px;
|
|
99
|
+
padding: 1rem 1.25rem;
|
|
100
|
+
cursor: pointer;
|
|
101
|
+
transition: all 0.15s ease;
|
|
102
|
+
display: flex;
|
|
103
|
+
align-items: flex-start;
|
|
104
|
+
gap: 1rem;
|
|
105
|
+
}
|
|
106
|
+
.option:hover { border-color: var(--accent); }
|
|
107
|
+
.option.selected { background: var(--selected-bg); border-color: var(--selected-border); }
|
|
108
|
+
.option .letter {
|
|
109
|
+
background: var(--bg-tertiary);
|
|
110
|
+
color: var(--text-secondary);
|
|
111
|
+
width: 1.75rem; height: 1.75rem;
|
|
112
|
+
border-radius: 6px;
|
|
113
|
+
display: flex; align-items: center; justify-content: center;
|
|
114
|
+
font-weight: 600; font-size: 0.85rem; flex-shrink: 0;
|
|
115
|
+
}
|
|
116
|
+
.option.selected .letter { background: var(--accent); color: white; }
|
|
117
|
+
.option .content { flex: 1; }
|
|
118
|
+
.option .content h3 { font-size: 0.95rem; margin-bottom: 0.15rem; }
|
|
119
|
+
.option .content p { color: var(--text-secondary); font-size: 0.85rem; margin: 0; }
|
|
120
|
+
|
|
121
|
+
/* ===== CARDS ===== */
|
|
122
|
+
.cards { display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 1rem; }
|
|
123
|
+
.card {
|
|
124
|
+
background: var(--bg-secondary);
|
|
125
|
+
border: 1px solid var(--border);
|
|
126
|
+
border-radius: 12px;
|
|
127
|
+
overflow: hidden;
|
|
128
|
+
cursor: pointer;
|
|
129
|
+
transition: all 0.15s ease;
|
|
130
|
+
}
|
|
131
|
+
.card:hover { border-color: var(--accent); transform: translateY(-2px); box-shadow: 0 4px 12px rgba(0,0,0,0.1); }
|
|
132
|
+
.card.selected { border-color: var(--selected-border); border-width: 2px; }
|
|
133
|
+
.card-image { background: var(--bg-tertiary); aspect-ratio: 16/10; display: flex; align-items: center; justify-content: center; }
|
|
134
|
+
.card-body { padding: 1rem; }
|
|
135
|
+
.card-body h3 { margin-bottom: 0.25rem; }
|
|
136
|
+
.card-body p { color: var(--text-secondary); font-size: 0.85rem; }
|
|
137
|
+
|
|
138
|
+
/* ===== MOCKUP CONTAINER ===== */
|
|
139
|
+
.mockup {
|
|
140
|
+
background: var(--bg-secondary);
|
|
141
|
+
border: 1px solid var(--border);
|
|
142
|
+
border-radius: 12px;
|
|
143
|
+
overflow: hidden;
|
|
144
|
+
margin-bottom: 1.5rem;
|
|
145
|
+
}
|
|
146
|
+
.mockup-header {
|
|
147
|
+
background: var(--bg-tertiary);
|
|
148
|
+
padding: 0.5rem 1rem;
|
|
149
|
+
font-size: 0.75rem;
|
|
150
|
+
color: var(--text-secondary);
|
|
151
|
+
border-bottom: 1px solid var(--border);
|
|
152
|
+
}
|
|
153
|
+
.mockup-body { padding: 1.5rem; }
|
|
154
|
+
|
|
155
|
+
/* ===== SPLIT VIEW ===== */
|
|
156
|
+
.split { display: grid; grid-template-columns: 1fr 1fr; gap: 1.5rem; }
|
|
157
|
+
@media (max-width: 700px) { .split { grid-template-columns: 1fr; } }
|
|
158
|
+
|
|
159
|
+
/* ===== PROS/CONS ===== */
|
|
160
|
+
.pros-cons { display: grid; grid-template-columns: 1fr 1fr; gap: 1rem; margin: 1rem 0; }
|
|
161
|
+
.pros, .cons { background: var(--bg-secondary); border-radius: 8px; padding: 1rem; }
|
|
162
|
+
.pros h4 { color: var(--success); font-size: 0.85rem; margin-bottom: 0.5rem; }
|
|
163
|
+
.cons h4 { color: var(--error); font-size: 0.85rem; margin-bottom: 0.5rem; }
|
|
164
|
+
.pros ul, .cons ul { margin-left: 1.25rem; font-size: 0.85rem; color: var(--text-secondary); }
|
|
165
|
+
.pros li, .cons li { margin-bottom: 0.25rem; }
|
|
166
|
+
|
|
167
|
+
/* ===== PLACEHOLDER ===== */
|
|
168
|
+
.placeholder {
|
|
169
|
+
background: var(--bg-tertiary);
|
|
170
|
+
border: 2px dashed var(--border);
|
|
171
|
+
border-radius: 8px;
|
|
172
|
+
padding: 2rem;
|
|
173
|
+
text-align: center;
|
|
174
|
+
color: var(--text-tertiary);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/* ===== ARCHITECTURE DIAGRAM HELPERS ===== */
|
|
178
|
+
.arch-diagram {
|
|
179
|
+
display: flex;
|
|
180
|
+
flex-direction: column;
|
|
181
|
+
align-items: center;
|
|
182
|
+
gap: 0.5rem;
|
|
183
|
+
padding: 1.5rem;
|
|
184
|
+
}
|
|
185
|
+
.arch-row {
|
|
186
|
+
display: flex;
|
|
187
|
+
gap: 1rem;
|
|
188
|
+
justify-content: center;
|
|
189
|
+
flex-wrap: wrap;
|
|
190
|
+
}
|
|
191
|
+
.arch-box {
|
|
192
|
+
background: var(--bg-secondary);
|
|
193
|
+
border: 2px solid var(--accent);
|
|
194
|
+
border-radius: 10px;
|
|
195
|
+
padding: 1rem 1.5rem;
|
|
196
|
+
text-align: center;
|
|
197
|
+
min-width: 120px;
|
|
198
|
+
}
|
|
199
|
+
.arch-box strong { display: block; font-size: 0.9rem; }
|
|
200
|
+
.arch-box small { color: var(--text-secondary); font-size: 0.75rem; }
|
|
201
|
+
.arch-arrow {
|
|
202
|
+
color: var(--text-secondary);
|
|
203
|
+
font-size: 1.2rem;
|
|
204
|
+
line-height: 1;
|
|
205
|
+
}
|
|
206
|
+
.arch-connector {
|
|
207
|
+
width: 2px;
|
|
208
|
+
height: 24px;
|
|
209
|
+
background: var(--border);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/* ===== FLOW CHART HELPERS ===== */
|
|
213
|
+
.flow-step {
|
|
214
|
+
background: var(--bg-secondary);
|
|
215
|
+
border: 1px solid var(--border);
|
|
216
|
+
border-radius: 8px;
|
|
217
|
+
padding: 0.75rem 1.25rem;
|
|
218
|
+
display: inline-flex;
|
|
219
|
+
align-items: center;
|
|
220
|
+
gap: 0.5rem;
|
|
221
|
+
}
|
|
222
|
+
.flow-step .step-num {
|
|
223
|
+
background: var(--accent);
|
|
224
|
+
color: white;
|
|
225
|
+
width: 1.5rem; height: 1.5rem;
|
|
226
|
+
border-radius: 50%;
|
|
227
|
+
display: flex; align-items: center; justify-content: center;
|
|
228
|
+
font-size: 0.75rem; font-weight: 600; flex-shrink: 0;
|
|
229
|
+
}
|
|
230
|
+
.flow-step.decision {
|
|
231
|
+
border-color: var(--warning);
|
|
232
|
+
background: rgba(255, 159, 10, 0.08);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/* ===== MOCK ELEMENTS ===== */
|
|
236
|
+
.mock-nav { background: var(--accent); color: white; padding: 0.75rem 1rem; display: flex; gap: 1.5rem; font-size: 0.9rem; }
|
|
237
|
+
.mock-sidebar { background: var(--bg-tertiary); padding: 1rem; min-width: 180px; }
|
|
238
|
+
.mock-content { padding: 1.5rem; flex: 1; }
|
|
239
|
+
.mock-button { background: var(--accent); color: white; border: none; padding: 0.5rem 1rem; border-radius: 6px; font-size: 0.85rem; cursor: pointer; }
|
|
240
|
+
.mock-input { background: var(--bg-primary); border: 1px solid var(--border); border-radius: 6px; padding: 0.5rem; width: 100%; }
|
|
241
|
+
|
|
242
|
+
/* ===== SEQUENCE DIAGRAM HELPERS ===== */
|
|
243
|
+
.seq-diagram { font-family: monospace; font-size: 0.85rem; line-height: 2; }
|
|
244
|
+
.seq-diagram .seq-actor { font-weight: bold; color: var(--accent); }
|
|
245
|
+
.seq-diagram .seq-arrow { color: var(--text-secondary); }
|
|
246
|
+
.seq-diagram .seq-msg { color: var(--text-primary); }
|
|
247
|
+
</style>
|
|
248
|
+
</head>
|
|
249
|
+
<body>
|
|
250
|
+
<div class="header">
|
|
251
|
+
<h1>Plan Viewer</h1>
|
|
252
|
+
<div class="status">Connected</div>
|
|
253
|
+
</div>
|
|
254
|
+
|
|
255
|
+
<div class="main">
|
|
256
|
+
<div id="claude-content">
|
|
257
|
+
<!-- CONTENT -->
|
|
258
|
+
</div>
|
|
259
|
+
</div>
|
|
260
|
+
|
|
261
|
+
<div class="indicator-bar">
|
|
262
|
+
<span id="indicator-text">옵션을 클릭한 후 터미널로 돌아가세요</span>
|
|
263
|
+
</div>
|
|
264
|
+
|
|
265
|
+
</body>
|
|
266
|
+
</html>
|