@angar2/taskery 0.1.0 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (30) hide show
  1. package/README.md +207 -65
  2. package/bin/init.js +45 -2
  3. package/bin/lib.js +397 -3
  4. package/bin/prune.js +189 -0
  5. package/bin/status.js +148 -0
  6. package/bin/taskery.js +25 -0
  7. package/bin/update.js +26 -2
  8. package/package.json +7 -3
  9. package/template/.claude/hooks/closed-immutable.sh +5 -4
  10. package/template/.claude/hooks/git-guard.sh +1 -1
  11. package/template/.claude/settings.json +1 -2
  12. package/template/.claude/skills/add-backlog/SKILL.md +117 -0
  13. package/template/.claude/skills/log-friction/SKILL.md +76 -0
  14. package/template/.claude/skills/{plan-init.md → plan-init/SKILL.md} +28 -4
  15. package/template/.claude/skills/{project-init.md → project-init/SKILL.md} +60 -7
  16. package/template/.claude/skills/task-close/SKILL.md +351 -0
  17. package/template/.claude/skills/{task-dev.md → task-dev/SKILL.md} +54 -8
  18. package/template/.claude/skills/task-init/SKILL.md +283 -0
  19. package/template/.claude/skills/{task-plan.md → task-plan/SKILL.md} +115 -18
  20. package/template/.claude/skills/{task-test.md → task-test/SKILL.md} +60 -18
  21. package/template/.project/FRICTION_LOG.md +6 -13
  22. package/template/.project/rules/CHANGELOG_RULE.md +91 -0
  23. package/template/.project/rules/GIT_RULE.md +119 -8
  24. package/template/.project/rules/MOCKUP_RULE.md +85 -0
  25. package/template/.project/rules/TASK_DOC_RULE.md +78 -76
  26. package/template/CLAUDE.md +77 -12
  27. package/template/.claude/hooks/pre-commit-verify.sh +0 -97
  28. package/template/.claude/skills/refine.md +0 -177
  29. package/template/.claude/skills/task-close.md +0 -178
  30. package/template/.claude/skills/task-init.md +0 -142
package/bin/status.js ADDED
@@ -0,0 +1,148 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * bin/status.js
4
+ * `npx @angar2/taskery status` — 멀티세션 현황 요약.
5
+ *
6
+ * 출력:
7
+ * - 메인 워크트리 + 현재 브랜치
8
+ * - projectId / stale_days / lock_timeout_ms
9
+ * - 진행중 태스크 (SSoT) + 각 워크트리 폴더 존재 여부 + 마지막 커밋 시각
10
+ * - stale 의심 항목 (케이스 A/B/C/D)
11
+ * - 머지 락 상태 (잡혀 있으면 누가 잡고 있는지)
12
+ *
13
+ * 본 명령은 *변경 X — 읽기 전용*. 정리는 `taskery prune`.
14
+ */
15
+
16
+ const fs = require('fs');
17
+ const path = require('path');
18
+ const os = require('os');
19
+
20
+ const {
21
+ MANIFEST_NAME,
22
+ readManifest,
23
+ gitCapture,
24
+ getMainWorktreePath,
25
+ getActiveTasks,
26
+ getWorktreesRoot,
27
+ getMergeLockPath,
28
+ DEFAULT_STALE_DAYS,
29
+ DEFAULT_LOCK_TIMEOUT_MS,
30
+ } = require('./lib');
31
+
32
+ function daysAgo(date) {
33
+ const now = Date.now();
34
+ const ms = now - date.getTime();
35
+ return Math.floor(ms / (1000 * 60 * 60 * 24));
36
+ }
37
+
38
+ function main() {
39
+ // 1. 메인 워크트리 + manifest
40
+ let mainWt;
41
+ try {
42
+ mainWt = getMainWorktreePath();
43
+ } catch (e) {
44
+ console.error(`taskery status 실패: ${e.message}`);
45
+ process.exit(1);
46
+ }
47
+
48
+ const manifest = readManifest(path.join(mainWt, MANIFEST_NAME));
49
+ if (!manifest) {
50
+ console.error(`${MANIFEST_NAME} 없음 — 'npx @angar2/taskery init' 먼저 실행 필요.`);
51
+ process.exit(1);
52
+ }
53
+
54
+ const projectId = manifest.projectId;
55
+ const staleDays = manifest.stale_days ?? DEFAULT_STALE_DAYS;
56
+ const lockTimeoutMs = manifest.lock_timeout_ms ?? DEFAULT_LOCK_TIMEOUT_MS;
57
+
58
+ const currentBranch = gitCapture(mainWt, ['rev-parse', '--abbrev-ref', 'HEAD']);
59
+ const isOnDev = currentBranch === 'dev';
60
+
61
+ console.log(`\ntaskery status\n`);
62
+ console.log(`메인 워크트리: ${mainWt}`);
63
+ console.log(`현재 브랜치: ${currentBranch}${isOnDev ? '' : ' ⚠ (메인 워크트리 = dev 정책 위배)'}`);
64
+ console.log(`projectId: ${projectId}`);
65
+ console.log(`stale_days: ${staleDays} / lock_timeout_ms: ${lockTimeoutMs}\n`);
66
+
67
+ // 2. 머지 락 상태
68
+ const lockPath = getMergeLockPath(projectId);
69
+ const lockDir = lockPath + '.lock'; // proper-lockfile은 <file>.lock 디렉토리 생성
70
+ if (fs.existsSync(lockDir)) {
71
+ let lockInfo = '';
72
+ try {
73
+ const stat = fs.statSync(lockDir);
74
+ const ageMs = Date.now() - stat.mtimeMs;
75
+ lockInfo = ` (잡힘 ${Math.round(ageMs / 1000)}초 전)`;
76
+ } catch (_) {}
77
+ console.log(`머지 락: 활성${lockInfo}`);
78
+ } else {
79
+ console.log(`머지 락: 비활성`);
80
+ }
81
+ console.log();
82
+
83
+ // 3. 진행중 태스크 (SSoT)
84
+ const active = getActiveTasks(mainWt);
85
+ console.log(`진행중 태스크 (SSoT, ${active.length}건):`);
86
+
87
+ if (active.length === 0) {
88
+ console.log(` (없음)`);
89
+ } else {
90
+ const worktreesRoot = getWorktreesRoot(projectId);
91
+ for (const t of active) {
92
+ const wtPath = path.join(worktreesRoot, `TASK-${String(t.taskNum).padStart(3, '0')}_${t.src}_${t.slug}`);
93
+ const wtExists = fs.existsSync(wtPath);
94
+ const lastCommit = gitCapture(mainWt, [
95
+ 'log', '-1', '--format=%ci', t.branch,
96
+ ], { allowFail: true });
97
+ const lastDate = lastCommit ? new Date(lastCommit) : null;
98
+ const daysOld = lastDate ? daysAgo(lastDate) : null;
99
+
100
+ console.log(` ${t.branch}`);
101
+ console.log(` 워크트리: ${wtPath}${wtExists ? '' : ' ⚠ 폴더 없음 (케이스 A — prune 필요)'}`);
102
+ if (lastDate) {
103
+ console.log(` 마지막 커밋: ${lastCommit} (${daysOld}일 전)${daysOld >= staleDays ? ' ⚠ stale 의심 (케이스 B/C)' : ''}`);
104
+ } else {
105
+ console.log(` 마지막 커밋: (조회 실패)`);
106
+ }
107
+ if (wtExists) {
108
+ const rebaseApply = path.join(wtPath, '.git', 'rebase-apply');
109
+ const rebaseMerge = path.join(wtPath, '.git', 'rebase-merge');
110
+ if (fs.existsSync(rebaseApply) || fs.existsSync(rebaseMerge)) {
111
+ console.log(` ⚠ rebase 잔여 상태 (케이스 D) — taskery prune 또는 워크트리에서 git rebase --abort`);
112
+ }
113
+ }
114
+ }
115
+ }
116
+ console.log();
117
+
118
+ // 4. 워크트리 폴더 vs SSoT mismatch (orphan)
119
+ const worktreesRoot = getWorktreesRoot(projectId);
120
+ if (fs.existsSync(worktreesRoot)) {
121
+ const folders = fs.readdirSync(worktreesRoot).filter((n) => n.startsWith('TASK-'));
122
+ const activeFolders = new Set(
123
+ active.map(
124
+ (t) => `TASK-${String(t.taskNum).padStart(3, '0')}_${t.src}_${t.slug}`,
125
+ ),
126
+ );
127
+ const orphans = folders.filter((f) => !activeFolders.has(f));
128
+ if (orphans.length > 0) {
129
+ console.log(`⚠ SSoT에 없는 워크트리 폴더 ${orphans.length}건 (이미 dev 머지된 잔존?):`);
130
+ for (const o of orphans) {
131
+ console.log(` ${path.join(worktreesRoot, o)}`);
132
+ }
133
+ console.log(` → 'taskery prune' 으로 정리`);
134
+ console.log();
135
+ }
136
+ }
137
+
138
+ console.log(`기타 명령:`);
139
+ console.log(` npx @angar2/taskery prune stale 워크트리 / 브랜치 대화형 정리`);
140
+ console.log();
141
+ }
142
+
143
+ try {
144
+ main();
145
+ } catch (e) {
146
+ console.error(`taskery status 실패: ${e.message}`);
147
+ process.exit(1);
148
+ }
package/bin/taskery.js CHANGED
@@ -6,6 +6,8 @@
6
6
  * 서브커맨드:
7
7
  * init — 현재 디렉토리에 taskery 자산 카피 + manifest 생성
8
8
  * update — 최신 버전 fetch + manifest 비교 + 머지 갱신
9
+ * status — 멀티세션 현황 요약 (진행중 태스크 / 워크트리 / 머지 락, 0.1.2+)
10
+ * prune — stale 워크트리 / 브랜치 대화형 정리 (0.1.2+)
9
11
  * help — 사용법 출력
10
12
  */
11
13
 
@@ -28,6 +30,8 @@ function help() {
28
30
  사용법:
29
31
  npx @angar2/taskery init 현재 디렉토리에 taskery 자산 설치
30
32
  npx @angar2/taskery update 최신 버전 fetch + 머지 갱신
33
+ npx @angar2/taskery status 멀티세션 현황 (진행중 태스크 / 워크트리 / 머지 락)
34
+ npx @angar2/taskery prune stale 워크트리 / 브랜치 대화형 정리
31
35
  npx @angar2/taskery help 도움말
32
36
 
33
37
  새 프로젝트 시작:
@@ -44,6 +48,27 @@ switch (sub) {
44
48
  case 'update':
45
49
  runScript('update.js');
46
50
  break;
51
+ case 'status':
52
+ runScript('status.js');
53
+ break;
54
+ case 'prune':
55
+ if (process.argv[3] === '--help' || process.argv[3] === '-h') {
56
+ console.log(`taskery prune — stale 워크트리 / 브랜치 대화형 정리
57
+
58
+ 사용법:
59
+ npx @angar2/taskery prune
60
+
61
+ 설명:
62
+ .taskery-manifest.json의 stale_days (기본 30일) 기준으로 비활성 워크트리 + 브랜치를 대화형 정리합니다.
63
+ 케이스 분기: A (워크트리 + 브랜치 모두 stale) / B (브랜치만 stale) / C (워크트리 폴더 잔존) / D (정합 영역).
64
+ 각 케이스마다 y/n/k 선택 가능 (y = 삭제 / n = 건너뜀 / k = 보존 명시).
65
+
66
+ 상세: https://github.com/angar2/taskery
67
+ `);
68
+ break;
69
+ }
70
+ runScript('prune.js');
71
+ break;
47
72
  case 'help':
48
73
  case '--help':
49
74
  case '-h':
package/bin/update.js CHANGED
@@ -3,7 +3,7 @@
3
3
  * bin/update.js
4
4
  * `npx @angar2/taskery update` — template/ 최신 자산과 사용자 측 비교 + 머지 갱신.
5
5
  *
6
- * 머지 로직 (§9-1):
6
+ * 머지 로직 (DISTRIBUTION.md §6):
7
7
  * 1. .taskery-manifest.json (cwd) 읽기 — 없으면 에러 ('init' 먼저)
8
8
  * 2. template/ 정독 → 새 해시 맵
9
9
  * 3. 각 파일 분기:
@@ -31,6 +31,9 @@ const {
31
31
  readManifest,
32
32
  getPackageVersion,
33
33
  isLocalOverride,
34
+ generateProjectId,
35
+ DEFAULT_STALE_DAYS,
36
+ DEFAULT_LOCK_TIMEOUT_MS,
34
37
  } = require('./lib');
35
38
 
36
39
  async function confirm(msg) {
@@ -146,15 +149,36 @@ async function main() {
146
149
  }
147
150
  }
148
151
 
149
- // 5. manifest 갱신
152
+ // 5. manifest 갱신 — 멀티세션(0.1.2+) 필드 누락 시 자동 마이그레이션
153
+ const migration = { projectId: false, stale_days: false, lock_timeout_ms: false };
154
+ const projectId = oldManifest.projectId || (migration.projectId = true, generateProjectId());
155
+ const stale_days =
156
+ typeof oldManifest.stale_days === 'number'
157
+ ? oldManifest.stale_days
158
+ : (migration.stale_days = true, DEFAULT_STALE_DAYS);
159
+ const lock_timeout_ms =
160
+ typeof oldManifest.lock_timeout_ms === 'number'
161
+ ? oldManifest.lock_timeout_ms
162
+ : (migration.lock_timeout_ms = true, DEFAULT_LOCK_TIMEOUT_MS);
163
+
150
164
  const newManifest = {
151
165
  version: getPackageVersion(),
152
166
  installed_at: oldManifest.installed_at,
153
167
  updated_at: new Date().toISOString(),
168
+ projectId,
169
+ stale_days,
170
+ lock_timeout_ms,
154
171
  files: newFiles,
155
172
  };
156
173
  writeManifest(newManifest, manifestPath);
157
174
 
175
+ const migratedFields = Object.entries(migration)
176
+ .filter(([, added]) => added)
177
+ .map(([k]) => k);
178
+ if (migratedFields.length > 0) {
179
+ console.log(` manifest 마이그레이션: ${migratedFields.join(', ')} 필드 추가`);
180
+ }
181
+
158
182
  // 결과 보고
159
183
  console.log(`\n✅ taskery update 완료`);
160
184
  console.log(` 동일: ${summary.unchanged}, 신규: ${summary.new}, 자동 갱신: ${summary.autoUpdated}`);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@angar2/taskery",
3
- "version": "0.1.0",
4
- "description": "Lightweight task system for Claude Code — 1 메인 세션 + 스킬 8종 + catastrophic hook 안전망 3종",
3
+ "version": "0.1.2",
4
+ "description": "Lightweight task system for Claude Code — 1 메인 세션(또는 멀티세션) + 스킬 9종 + catastrophic hook 안전망 2종",
5
5
  "bin": {
6
6
  "taskery": "bin/taskery.js",
7
7
  "create-taskery": "bin/create.js"
@@ -38,6 +38,10 @@
38
38
  "access": "public"
39
39
  },
40
40
  "engines": {
41
- "node": ">=18.0.0"
41
+ "node": ">=18.0.0",
42
+ "git": ">=2.31.0"
43
+ },
44
+ "dependencies": {
45
+ "proper-lockfile": "^4.1.2"
42
46
  }
43
47
  }
@@ -10,7 +10,8 @@
10
10
  # 차단 안 함 (의도적):
11
11
  # - .project/tasks/<vX.X>/spec-diffs/*.md — 역사적 자료, 자유 수정
12
12
  # - .project/tasks/<vX.X>/screenshots/* — 자유
13
- # - 폴더 승격 task 폴더 안 추가 자료 — 자유
13
+ # - .project/tasks/<vX.X>/mockup/*.html — UX/UI 목업, 자유 수정 (vX.X 공통)
14
+ # - 폴더 승격 task 폴더 안 추가 자료 (서브 문서 등) — 자유
14
15
  # - 코드 영역 — closed task 코드 재수정은 *새 task로 처리*가 정상 흐름
15
16
  #
16
17
  # 우회 (의도적으로 closed task를 풀어 다시 진행해야 하는 경우):
@@ -32,9 +33,9 @@ except Exception:
32
33
  " 2>/dev/null)
33
34
 
34
35
  # task.md 본 파일 매칭 (단일 + 폴더 승격)
35
- # - 단일: .project/tasks/v*/<NNN>_<slug>.md (NNN_<slug> 패턴 — vX.X 직속, spec-diffs/screenshots 하위 X)
36
+ # - 단일: .project/tasks/v*/<NNN>_<slug>.md (NNN_<slug> 패턴 — vX.X 직속, spec-diffs/screenshots/mockup 하위 X)
36
37
  # - 폴더: .project/tasks/v*/TASK-<NNN>_<slug>/task.md
37
- # spec-diffs/ + screenshots/ 직속 파일은 매칭 안 됨 (보호 대상 X)
38
+ # spec-diffs/ + screenshots/ + mockup/ 직속 파일은 매칭 안 됨 (보호 대상 X)
38
39
  if ! echo "$FILE_PATH" | grep -qE '\.project/tasks/v[^/]+/([0-9]+_[^/]+\.md|TASK-[^/]+/task\.md)$'; then
39
40
  exit 0
40
41
  fi
@@ -67,7 +68,7 @@ print(m.group(1) if m else '')
67
68
  if [ "$STATUS" = "closed" ]; then
68
69
  echo "closed-immutable: '$FILE_PATH' 차단. task '$TASK_MD' 상태가 'closed' — 재수정 금지." >&2
69
70
  echo " 의도적 재진입 필요 시: sed로 헤더 status를 'developing' 또는 다른 상태로 직접 변경 후 Edit 가능." >&2
70
- echo " *closed task 코드 / spec-diffs / screenshots*는 보호 영역 외 — 자유 수정. 단 코드 변경은 *새 task로 처리*가 정상 흐름." >&2
71
+ echo " *closed task 코드 / spec-diffs / screenshots / mockup*은 보호 영역 외 — 자유 수정. 단 코드 변경은 *새 task로 처리*가 정상 흐름." >&2
71
72
  exit 2
72
73
  fi
73
74
 
@@ -43,7 +43,7 @@ fi
43
43
 
44
44
  # 2. git commit --no-verify 차단
45
45
  if echo "$CMD" | grep -qE 'git\s+commit\b.*--no-verify'; then
46
- echo "git-guard: 'git commit --no-verify' 차단. pre-commit-verify hook 우회 금지. (.project/rules/GIT_RULE.md §금지 사항)" >&2
46
+ echo "git-guard: 'git commit --no-verify' 차단. commit hook 우회 금지. (.project/rules/GIT_RULE.md §금지 사항)" >&2
47
47
  exit 2
48
48
  fi
49
49
 
@@ -4,8 +4,7 @@
4
4
  {
5
5
  "matcher": "Bash",
6
6
  "hooks": [
7
- { "type": "command", "command": ".claude/hooks/git-guard.sh" },
8
- { "type": "command", "command": ".claude/hooks/pre-commit-verify.sh" }
7
+ { "type": "command", "command": ".claude/hooks/git-guard.sh" }
9
8
  ]
10
9
  },
11
10
  {
@@ -0,0 +1,117 @@
1
+ ---
2
+ name: add-backlog
3
+ description: 사용자 발화로 *버전별* tasks/<vX.X>/BACKLOG.md에 task 후보 1건 추가 — 얕은 분석(개요/대상 영역) + BL-NNN 채번 + withMetaLock 직렬화 (0.1.2+)
4
+ ---
5
+
6
+ # /add-backlog
7
+
8
+ ## 개요
9
+
10
+ `/add-backlog`는 사용자 발화를 받아 *버전별 백로그* `.project/tasks/<vX.X>/BACKLOG.md`에 task 후보를 1건씩 누적한다. 멀티세션 환경에서 `withMetaLock`으로 동시 쓰기 직렬화. **얕은 분석**만 — 코드 직접 탐색 X, 추정 수준. 본격 분석은 다음 단계 `/task-plan`에서.
11
+
12
+ 글로벌 `.project/BACKLOG.md` (plan 기획 후보 카탈로그) 는 `/plan-init` 영역으로 본 스킬 무관.
13
+
14
+ ## 호출 시점
15
+
16
+ - 사용자 명시 호출: `/add-backlog 로그인 빈 화면 떠`
17
+ - 발화 캐치: *"~ 백로그에 추가해 줘"* / *"백로그에 ~"* 등 백로그 추가 의도 발화 description 매칭으로 메인이 자동 발동
18
+
19
+ ## 입력 처리
20
+
21
+ - 무인자 호출: 사용자가 *"백로그 추가"*만 말한 경우 — 어떤 항목인지 한 줄 인터뷰
22
+ - 인자 호출: 인자에 백로그 후보 주제. 1건 기본. 다건 명시 시 순차 처리
23
+
24
+ ## 단계 (Step 1~6)
25
+
26
+ ### Step 1 — 사전 검증
27
+
28
+ - 메인 워크트리 검출:
29
+ ```sh
30
+ MAIN_WT=$(dirname "$(git rev-parse --path-format=absolute --git-common-dir)")
31
+ ```
32
+ - 메인 워크트리 dev 체크아웃 검증 — 위배 시 사용자 호출 + 중단
33
+ - 활성 버전 검출 — `$MAIN_WT/.project/AGENT-GUIDE.md` `## 활성 plan 버전` 섹션 다음 비어 있지 않은 첫 줄에서 `vX.X` 패턴 추출
34
+ - BACKLOG.md 경로 = `$MAIN_WT/.project/tasks/<vX.X>/BACKLOG.md`. 부재 시 사용자 호출: *"`/plan-init` 먼저 호출해 활성 버전 디렉토리 + 빈 BACKLOG.md 생성 필요"* + 중단
35
+
36
+ ### Step 2 — 백로그 항목 후보 추출
37
+
38
+ - 사용자 발화에서 1건 추출. 다건 명시 시 순차 처리 (각 1건씩 본 흐름 반복)
39
+ - 입력이 비어 있으면 사용자에게 *"어떤 항목? 한 줄로 알려줘"* 인터뷰
40
+
41
+ ### Step 3 — 얕은 분석 (LLM, 코드 탐색 X)
42
+
43
+ 추정 수준의 메타 산출:
44
+ - **유형** (`feature` / `bug` / `improve` / `refactor` / `docs` / `chore`) — 자동 추정, 모호 시 사용자 confirm
45
+ - **제목** — 한 줄, 간결, 한국어
46
+ - **개요** — 한 줄. bug → 원인 추정 / feature → 구상 / improve → 개선 방향
47
+ - **대상 영역** — 한 줄. 이 파일 / 모듈일 거다 — 추정 수준
48
+
49
+ > *코드 직접 탐색 금지*. 본격 분석은 다음 단계 `/task-plan`에서. 본 스킬은 *메모지 누적*만.
50
+
51
+ ### Step 4 — 결정적 슬러그 산출
52
+
53
+ - 한국어 제목 → 영어 *의미 변환* → kebab-case (3 단어 이내, 음역 X)
54
+ - 예: *"로그인 페이지 빈 화면"* → `login-empty-page` / *"다크모드 토글"* → `dark-mode-toggle`
55
+
56
+ ### Step 5 — BL-NNN 채번 + append (withMetaLock 직렬화)
57
+
58
+ `bin/lib.js`의 `appendBacklogItem(mainWtPath, meta)` 호출.
59
+
60
+ ```js
61
+ const lib = require('@angar2/taskery/bin/lib'); // 또는 패키지 내부 경로
62
+ const blNum = await lib.appendBacklogItem(MAIN_WT, {
63
+ type: '<유형>',
64
+ title: '<제목>',
65
+ slug: '<슬러그>',
66
+ summary: '<개요>',
67
+ target: '<대상 영역>',
68
+ });
69
+ ```
70
+
71
+ 내부 흐름:
72
+ 1. `withMetaLock(backlogPath)` 안에서:
73
+ - BACKLOG.md 전체에서 `BL-(\d+)` 패턴 grep → 최대값 + 1. 빈 파일 → BL-001 시작
74
+ - 항목 블록 포맷팅:
75
+ ```
76
+ - [ ] **BL-NNN** [유형] 제목 `slug`
77
+ - 개요: ...
78
+ - 대상 영역: ...
79
+ ```
80
+ - plan-init 빈 골격의 placeholder 라인 (`(사용자 발화 또는 task-close 직후 메인 감지로 한 행씩 추가. 빈 상태 default.)`) 발견 시 → 그 자리에 치환. 미발견 시 → 파일 끝 빈 줄 + 블록 append
81
+
82
+ ### Step 6 — 결과 보고
83
+
84
+ ```
85
+ ✅ BL-<NNN> [<유형>] <제목> — <vX.X> 백로그 추가됐어.
86
+ - 개요: <개요>
87
+ - 대상 영역: <대상 영역>
88
+ - 슬러그: <slug>
89
+ 다음: 진행 의향이면 *"BL-<NNN> 진행"*이라고 말해. `/task-init`이 메타 그대로 받아 워크트리를 분기하고 항목을 `[x]` 확인 마킹할게.
90
+ ```
91
+
92
+ ## 도구 가이드
93
+
94
+ - **Bash**: 메인 워크트리 검출 / dev 체크아웃 검증
95
+ - **Read**: `$MAIN_WT/.project/AGENT-GUIDE.md` (활성 버전 검출) / 기존 BACKLOG.md 정독 (중복 / 유사 항목 사전 확인)
96
+ - **bin/lib.js**: `getActiveVersion` / `getBacklogPath` / `appendBacklogItem` 호출
97
+ - **AskUserQuestion**: 유형 모호 시 confirm — 한 번에 한 질문
98
+
99
+ ## 주의사항
100
+
101
+ - **얕은 분석만** — 본 스킬에서 도메인 코드 Read · Grep 금지. 본격 분석은 `/task-plan`
102
+ - **유형 자동 추정 + 모호 시 confirm** — *"메뉴 검색 개선"* 같이 feature/improve 모호 시 사용자 결정 받기
103
+ - **메인 워크트리 dev 검증 누락 X** — 위배 시 즉시 중단. 워크트리 안에서 호출돼도 메인 절대 경로의 BACKLOG.md에 쓰기
104
+ - **활성 버전 부재 시 즉시 중단** — `AGENT-GUIDE.md` 부재 / `## 활성 plan 버전` 섹션 부재 / vX.X 패턴 추출 실패 모두 명확한 에러 메시지 후 사용자 호출
105
+ - **결정적 슬러그 best-effort** — LLM 비결정성 수용. race 차단 본체는 SSoT 2층(`/task-init` §4.4)이 담당. 슬러그 변형 발생 시 사용자 confirm
106
+ - **글로벌 BACKLOG.md (plan 기획 후보)** 와 혼동 X — 본 스킬은 *버전별*만 다룸. 글로벌은 `/plan-init` 영역
107
+ - **자동 추정 진행 X** — 유형 / 제목 / 개요 / 대상 영역 모두 사용자 OK 또는 명백한 발화 매칭 후에만 BACKLOG.md 쓰기
108
+
109
+ ## 상태 전이
110
+
111
+ 해당 없음 (백로그 메모지 누적 — task 상태 X).
112
+
113
+ ## 멀티세션 정합
114
+
115
+ - **위치 = 메인 워크트리 절대 경로** — 워크트리 안에서 호출돼도 메인 절대 경로의 BACKLOG.md를 단일 소스로 사용
116
+ - **`withMetaLock` 직렬화** — 두 메인 세션이 동시 `/add-backlog` 호출 시 락으로 직렬화 (proper-lockfile, stale=30s, retries=5). 한 세션 `/add-backlog` + 다른 세션 `/task-init` (BL 마킹) 동시 시도 시 락 정합
117
+ - **체크박스 의미** — `[ ]` = 미확인 (task로 옮기지 않은 메모) / `[x]` = 확인 완료 (task로 옮김). **dev 머지 완료 의미 X** — 완료 추적은 `git log dev --grep 'BL-NNN'` + 브랜치명 + `taskery status`
@@ -0,0 +1,76 @@
1
+ ---
2
+ name: log-friction
3
+ description: 사용자가 작업 흐름 중 겪은 불편(friction)을 .project/FRICTION_LOG.md에 한 행 기록한다. 사용자가 짜증·불만·답답함을 표현하면 자동 발동(등록 제안). 명시 호출(`/log-friction`) 또는 `/task-close` 직후 마찰 신호 자체 감지 시에도 발동한다.
4
+ ---
5
+
6
+ # /log-friction
7
+
8
+ ## 개요
9
+
10
+ 사용자가 작업 흐름 중 겪은 불편을 `.project/FRICTION_LOG.md`에 한 행 기록한다.
11
+ 후속 조치 X — 분석 / 패턴 감지 / 룰 제안 자체를 본 스킬이 수행하지 않는다. 사용자가 직접 검토 후 판단한다.
12
+
13
+ ## 호출 시점 (3가지 트리거)
14
+
15
+ 1. **사용자 명시 호출** — `/log-friction "<불편 내용>"` 또는 인자 없이 호출
16
+ 2. **사용자 불만 발화 캐치** — 메인 세션이 사용자 발화에서 불편·짜증·답답함 신호를 감지하면 *"이거 FRICTION_LOG에 등록할까?"* 제안한 후 사용자 OK 시 자동 발동
17
+ 3. **task-close 직후 메인 자체 감지** — 작업 중 마찰 신호(동일 단계 재호출 / 실패 반복 / 사용자 부정 반응 누적 등)가 감지된 경우에만 *"이번 task에서 X 부분이 마찰이었어 보여 — 등록할까?"* 제안한 후 사용자 OK 시 발동. 감지 신호 없으면 호출 X
18
+
19
+ ## 입력 처리
20
+
21
+ - 인자 형태 (사용자 명시 호출):
22
+ - `/log-friction "<불편 내용>"` — 큰따옴표 인자에 본문 직접 전달
23
+ - `/log-friction` — 인자 없이 호출 시 메인이 사용자에게 *"어떤 점이 불편했는지?"* 묻고 답변을 받음
24
+ - 자동 발동 시: 메인이 캐치한 발화 또는 감지한 마찰 신호를 사용자에게 *확인 차원*으로 보여주고 등록 본문에 대해 합의를 받음
25
+
26
+ ## 단계
27
+
28
+ ### Step 1 — 호출 경로 확인
29
+
30
+ 호출 형태에 따라 분기한다.
31
+
32
+ - 직접 호출 (인자 있음) → 인자 본문을 등록 후보로 사용
33
+ - 직접 호출 (인자 없음) → 사용자에게 불편 내용을 질문
34
+ - 자동 발동 (불만 발화 캐치) → 캐치한 발화를 등록 후보로 보여주고 사용자 합의
35
+ - 자동 발동 (task-close 자체 감지) → 감지한 마찰 신호를 보여주고 사용자 합의
36
+
37
+ ### Step 2 — 등록 본문 확정
38
+
39
+ 사용자가 합의한 내용을 한 행(1~3 문장 이내)으로 정리한다.
40
+
41
+ 포맷:
42
+
43
+ ```
44
+ - YYYY-MM-DD | <맥락 — TASK 번호 또는 호출 시점> | <불편 내용 요약>
45
+ ```
46
+
47
+ ### Step 3 — FRICTION_LOG.md 한 행 추가
48
+
49
+ `.project/FRICTION_LOG.md`를 Read한 후 마지막 행 다음에 신규 행을 Append(Edit)한다.
50
+ 파일이 없거나 비어 있으면 안내 후 첫 행을 작성한다.
51
+
52
+ ### Step 4 — 결과 보고
53
+
54
+ ```
55
+ ✅ /log-friction 완료
56
+ - 추가된 행: <등록 본문>
57
+ - 누적 행 수: <N>건
58
+ - 다음 호출: 사용자 명시 / 불만 발화 캐치 / task-close 자체 감지 중 하나
59
+ ```
60
+
61
+ ## 도구 가이드
62
+
63
+ - **Read**: `.project/FRICTION_LOG.md` 정독
64
+ - **Edit**: 마지막 행 다음에 신규 행 Append
65
+ - **AskUserQuestion**: Step 1·2 사용자 합의
66
+
67
+ ## 주의사항
68
+
69
+ - 본 스킬은 *기록 행위*만 수행한다. 패턴 분석 / PLAYBOOK 매핑 / 룰 제안 X.
70
+ - task-close 자체 감지 트리거는 *마찰 신호 감지* 시에만 발동한다. 감지 없는 task-close에서 prompt 강제 X.
71
+ - 사용자 합의 없이 자동 등록 X. 자동 발동 경로도 반드시 사용자 OK 후 기록한다.
72
+ - 메인 자체 판단으로 등록 본문을 임의 가공·확장 X. 사용자 발화 또는 감지한 신호를 *그대로 인용* 원칙.
73
+
74
+ ## 상태 전이
75
+
76
+ 해당 없음 (meta 레벨 — task 상태 전이 X).
@@ -1,4 +1,5 @@
1
1
  ---
2
+ name: plan-init
2
3
  description: plan 버전 단위 9 기획 문서 작성 — 신규 vX.X/ 또는 기존 카피 후 변경 인터뷰
3
4
  ---
4
5
 
@@ -65,7 +66,11 @@ plan 버전(vX.X)마다 호출. `.project/plans/<vX.X>/` 안에 9 기획 문서
65
66
  - **ARCHITECTURE.md**: 시스템 구조 (멀티 리포 / 레이어드 / 마이크로서비스 등)
66
67
  - **DATA-MODEL.md** (해당 시): 주요 엔티티 + 관계
67
68
  - **API-SPEC.md**: 엔드포인트 목록 + 인증 방식
68
- - **ROADMAP.md**: 마일스톤 + 시기 + 우선순위
69
+ - **ROADMAP.md**: 마일스톤 + 시기 + 우선순위. *ROADMAP 작성 4룰* (stash FRICTION_LOG #1 반영):
70
+ 1. ROADMAP은 *현재 버전 한정* — 미래 버전 후보는 별도 `BACKLOG.md` (글로벌) 분리
71
+ 2. 진행 순서에 task 번호(TASK-NNN) 강제 금지 — *Stage(영역) 단위*로만 명시 (예측 불가 task 합류 시 번호 어긋남 방지)
72
+ 3. Stage 안 *작업 단위 명시* 필요 (한 task 분량 권장 — 다음 task 진행 시 메인 세션이 ROADMAP 보고 작업 범위 판단 가능)
73
+ 4. 작업 단위에 task 번호 컬럼 추가 X / *상태 컬럼만* (⏳ 대기 / 🔧 진행 중 / ✅ 완료 / ❌ 폐기) — Living document
69
74
  4. 받은 답 기반으로 각 문서 작성.
70
75
  5. 마지막에 **PLAN.md** 작성 — 9 문서 인덱스 + 체크리스트.
71
76
  6. **`.project/AGENT-GUIDE.md`의 활성 plan 버전 vX.X로 갱신** (신규도 동일 — 분기 2와 일관).
@@ -83,7 +88,26 @@ plan 버전(vX.X)마다 호출. `.project/plans/<vX.X>/` 안에 9 기획 문서
83
88
  ### Step 5 — `tasks/<vX.X>/` 디렉토리 준비
84
89
 
85
90
  1. `.project/tasks/<vX.X>/` 디렉토리 생성 (빈 폴더).
86
- 2. `.project/tasks/<vX.X>/spec-diffs/`, `.project/tasks/<vX.X>/screenshots/` 빈 폴더 미리 생성.
91
+ 2. `.project/tasks/<vX.X>/spec-diffs/`, `.project/tasks/<vX.X>/screenshots/`, `.project/tasks/<vX.X>/mockup/` 빈 폴더 미리 생성.
92
+ 3. `.project/tasks/<vX.X>/BACKLOG.md` 빈 골격 Write (stash FRICTION_LOG #17 반영) — *버전별 task 후보 누적용*, 글로벌 `.project/BACKLOG.md`와 별개:
93
+
94
+ ```markdown
95
+ # BACKLOG — <vX.X>
96
+
97
+ > 본 파일은 *<vX.X> plan 진행 중 누적되는 후속 task 후보* 추적용.
98
+ > 글로벌 `.project/BACKLOG.md` (plan 기획 후보 카탈로그) 와 별개.
99
+
100
+ | 위치 | 영역 | 역할 |
101
+ |------|------|------|
102
+ | `.project/BACKLOG.md` (글로벌) | plan 기획 후보 카탈로그 (다음 plan 버전에서 검토) | Living document |
103
+ | `.project/tasks/<vX.X>/BACKLOG.md` (본 파일) | 현재 plan 진행 중 누적된 후속 task 후보 | 한 버전 내 task close 직후 후보 누적 |
104
+
105
+ ## 후속 task 후보
106
+
107
+ (사용자 발화 또는 task-close 직후 메인 감지로 한 행씩 추가. 빈 상태 default.)
108
+ ```
109
+
110
+ 빈 골격만 작성. 본문은 task 진행하면서 사용자 발화 또는 메인 감지로 누적.
87
111
 
88
112
  ### Step 6 — `PLAN.md` 작성/갱신
89
113
 
@@ -118,12 +142,12 @@ plan 버전(vX.X)마다 호출. `.project/plans/<vX.X>/` 안에 9 기획 문서
118
142
  **결과 commit 흐름** (GIT_RULE 정합):
119
143
  - dev 직접 commit *금지* (git-guard.sh 차단). 두 가지 default 흐름:
120
144
  1. **첫 task에 묶기 (권장)**: 분기 1(신규)에서 `/project-init` 직후 본 스킬 호출이면 `/project-init` 산출물과 같은 작업 브랜치(보통 TASK-001 부트스트랩 chore)에 함께 commit. 분기 2(이어가기)면 새 task 브랜치 또는 임시 docs 브랜치에서.
121
- 2. **임시 docs 브랜치**: `git checkout -b docs/{개발자}_plan-{vX.X}` 후 commit → dev에 `--no-ff` 머지. 다음 task 생성 *전*에 plan 산출물을 깔끔히 박고 싶을 때.
145
+ 2. **임시 docs 브랜치**: `git checkout -b docs/{개발자}_plan-{vX.X}` 후 commit → dev에 `--no-ff` 머지. 다음 task 생성 *전*에 plan 산출물을 깔끔히 기록하고 싶을 때.
122
146
 
123
147
  ## 도구 가이드
124
148
 
125
149
  - **Read**: PROJECT.md / 기존 plans/vY.Y/ 정독
126
- - **Bash**: `ls .project/plans/`, `cp -r plans/vY.Y plans/vX.X`, `mkdir -p plans/<vX.X> tasks/<vX.X>/{spec-diffs,screenshots}`
150
+ - **Bash**: `ls .project/plans/`, `cp -r plans/vY.Y plans/vX.X`, `mkdir -p plans/<vX.X> tasks/<vX.X>/{spec-diffs,screenshots,mockup}`
127
151
  - **Write/Edit**: 9 기획 문서 작성/갱신
128
152
  - **AskUserQuestion**: 9 문서별 핵심 질문 + 변경 인터뷰
129
153