@angar2/taskery 0.6.0 → 0.6.1

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
@@ -16,6 +16,9 @@ AI 코딩 에이전트의 자율 개발을 위한 Task 기반의 가드레일
16
16
  | 에이전트가 catastrophic 사고를 일으킬 수 있음 | 필수 hook으로 차단 — 정상 흐름에는 무간섭 |
17
17
  | 에이전트가 작성한 코드의 자가검증으로 인해 문제를 놓침 (confirmation bias) | 테스트 시 별도 격리 세션 호출(`/task-test`) — 메인 세션의 가정 없이 독립 검증 |
18
18
  | 단일 메인 세션 운영으로 인해 task를 직렬로 진행해야 하는 병목 | 같은 프로젝트에서 여러 메인 세션이 독립 작업 폴더(worktree)로 병렬 진행 — 머지 시점에 직렬화로 정합성 유지 |
19
+ | 검수 중 한 줄만 고쳐도 전체 테스트를 다시 돌려 대기 시간이 길어짐 | 수정 구간에는 변경이 닿는 테스트만 실행 — 전체 실행은 task당 1회로 제한 |
20
+ | 테스트가 앱 화면을 띄우고 조작해 그동안 컴퓨터를 쓸 수 없음 | 화면·입력을 점유하는 테스트는 창을 띄우지 않는 실행 경로가 준비된 경우에만 자동 실행 — 없으면 사용자 승인 후 실행 |
21
+ | 프로젝트마다 개발·테스트 방식이 다른데 공통 규칙만 적용됨 | 프로젝트 전용 규칙 파일(`*.local.md`)을 각 스킬이 읽어 우선 적용 — 패키지를 갱신해도 덮어쓰지 않음 |
19
22
 
20
23
  > catastrophic 사고 예시 — git 운영 정책 위반, 검증 우회, 완료된 task 문서 재수정 등
21
24
 
@@ -125,6 +128,8 @@ my-app/
125
128
  │ ├─ GIT_RULE.md # 로컬 깃 운영 규칙
126
129
  │ ├─ CHANGELOG_RULE.md # CHANGELOG 작성 규칙
127
130
  │ ├─ MOCKUP_RULE.md # UX/UI HTML 목업 규칙
131
+ │ ├─ DEV_RULE.local.md # (선택) 이 프로젝트의 구현·테스트 실행 방식
132
+ │ ├─ TEST_RULE.local.md # (선택) 이 프로젝트의 검증 범위·방식
128
133
  │ └─ *.local.md # 사용자 오버라이드 규칙 (패키지 업데이트 대상 제외)
129
134
  ├─ plans/ # plan(기능 그룹)별 PLAN.md / ROADMAP.md (<NNN_slug>/)
130
135
  ├─ tasks/ # task 문서 (<NNN_slug>/BACKLOG.md / spec-diffs / screenshots / mockup 포함)
@@ -258,6 +263,7 @@ npx @angar2/taskery init
258
263
  - 사용자가 재구현을 요청하면 → `testing` → `developing`으로 회귀해 `/task-dev`가 자동 발동된다.
259
264
  - 사용자가 결함을 인지한 채 종결을 지시하면 → `testing` → `tested`로 진행하고 결함을 명시한 후 `/task-close`로 이행된다.
260
265
  - UNCERTAIN(자동 검증이 불가능한 시나리오)은 사용자가 직접 검수한 결과를 답하면 메인 세션이 PASS/FAIL로 해석해 위 두 흐름 중 하나로 합류시킨다.
266
+ - 검증에 필요한 테스트가 화면·입력을 점유해 실행을 보류한 경우에는, 지금 실행할지 / 자리를 비울 때로 미룰지 / 창을 띄우지 않는 실행 경로를 마련할지를 사용자가 고른다. 승인 없이 임의로 실행하지 않는다.
261
267
 
262
268
  ---
263
269
 
package/bin/fork.js CHANGED
@@ -57,8 +57,10 @@ async function main() {
57
57
  console.error(`type은 ${VALID_TYPES.join(' | ')} 중 하나 (받음: ${type})`);
58
58
  process.exit(1);
59
59
  }
60
- if (!/^(BL-\d+|RM-\d+|DR)$/.test(src)) {
61
- console.error(`src는 BL-NNN | RM-NNN | DR 형식 (받음: ${src})`);
60
+ // ST-N = 로드맵 Stage 출처 (ROADMAP은 Stage 단위·RM 채번 없음 — recordion FRICTION 2026-08-03 반영).
61
+ // RM-NNN은 구형 호환으로 계속 수용.
62
+ if (!/^(BL-\d+|RM-\d+|ST-\d+|DR)$/.test(src)) {
63
+ console.error(`src는 BL-NNN | ST-N(로드맵 Stage) | DR 형식 (받음: ${src}. 구형 RM-NNN도 호환)`);
62
64
  process.exit(1);
63
65
  }
64
66
  if (!/^[a-z0-9]+(-[a-z0-9]+)*$/.test(slug)) {
package/bin/lib.js CHANGED
@@ -247,7 +247,7 @@ function getWorktreesRoot(projectId) {
247
247
  }
248
248
 
249
249
  function getWorktreePath(projectId, { taskNum, src, slug }) {
250
- // src = 'BL-NNN' | 'RM-NNN' | 'DR'
250
+ // src = 'BL-NNN' | 'ST-N'(로드맵 Stage) | 'DR' (구형 'RM-NNN' 호환)
251
251
  const nnn = String(taskNum).padStart(3, '0');
252
252
  return path.join(getWorktreesRoot(projectId), `TASK-${nnn}_${src}_${slug}`);
253
253
  }
@@ -388,8 +388,9 @@ async function forkTask(mainWtPath, { type, dev, src, slug }) {
388
388
  if (parent === 'HEAD') {
389
389
  throw new Error('메인 워크트리가 detached HEAD — 부모로 삼을 브랜치에 체크아웃 필요.');
390
390
  }
391
- // SSoT 안전망 — 같은 출처(BL-NNN/RM-NNN)가 이미 진행중이면 거부. DR은 별도 ID 없어 검사 제외.
392
- if (src !== 'DR') {
391
+ // SSoT 안전망 — 같은 항목 출처(BL-NNN/구형 RM-NNN)가 이미 진행중이면 거부.
392
+ // DR(별도 ID 없음)·ST-N(Stage 하나에서 복수 task가 정상)은 검사 제외.
393
+ if (/^(BL|RM)-/.test(src)) {
393
394
  const dup = getActiveTasks(mainWtPath).find((t) => t.src === src);
394
395
  if (dup) {
395
396
  throw new Error(`${src} 이미 진행중 (${dup.branch}) — 다른 세션이 같은 항목 진행`);
@@ -438,12 +439,15 @@ const BACKLOG_PLACEHOLDER =
438
439
  function getActiveVersion(mainWtPath) {
439
440
  // .project/AGENT-GUIDE.md '## 활성 plan 버전' 섹션 다음 비어 있지 않은 첫 줄에서 활성 plan 식별자 추출.
440
441
  // plan = 기능 그룹 단위 → 식별자는 vX.X 버전명 또는 기능 그룹 slug(compare-products 등) 모두 가능.
442
+ // 헤딩 줄 끝 부가 텍스트(HTML 주석 등) 허용 — project-init 골격이 헤딩에 주석을 달므로
443
+ // 접두 일치로 판정(_setActivePlan의 findIndex 기준과 동일). 완전 일치 요구 시
444
+ // 자기 골격을 자기가 못 읽는 자기모순 (recordion FRICTION 2026-08-06, scaffoldError 6회).
441
445
  const guidePath = path.join(mainWtPath, '.project', 'AGENT-GUIDE.md');
442
446
  if (!fs.existsSync(guidePath)) {
443
447
  throw new Error(`.project/AGENT-GUIDE.md 부재. /project-init 먼저 호출.`);
444
448
  }
445
449
  const content = fs.readFileSync(guidePath, 'utf8');
446
- const sec = content.match(/##\s*활성 plan 버전\s*\n+([^\n]+)/);
450
+ const sec = content.match(/^##\s*활성 plan 버전[^\n]*\n+([^\n]+)/m);
447
451
  if (!sec) {
448
452
  throw new Error(`.project/AGENT-GUIDE.md '## 활성 plan 버전' 섹션 부재.`);
449
453
  }
@@ -1006,10 +1010,26 @@ function _porcelainFiles(wtPath) {
1006
1010
  .filter(Boolean);
1007
1011
  }
1008
1012
 
1013
+ // 빌드 산출물 판정 — 소스 변경에 도구가 따라 바꾸는 파생 파일 (Phase 소속을 묻는 것 자체가 무의미).
1014
+ // Dev Plan에 명시 안 돼도 미매핑 blocked 대신 마지막 Phase에 자동 편입 (stash·recordion FRICTION: pbxproj 미매핑만 6회).
1015
+ const BUILD_ARTIFACT_RE = [
1016
+ /\.pbxproj$/,
1017
+ /(^|\/)package-lock\.json$/,
1018
+ /(^|\/)yarn\.lock$/,
1019
+ /(^|\/)pnpm-lock\.yaml$/,
1020
+ /(^|\/)Cargo\.lock$/,
1021
+ /(^|\/)Gemfile\.lock$/,
1022
+ /(^|\/)Podfile\.lock$/,
1023
+ ];
1024
+
1009
1025
  function splitUncommittedByPhase(wtPath, taskMdPath, meta) {
1010
1026
  // 코드 변경분(.project/ 외)을 Dev Plan Phase에 매핑해 Phase별 자동 커밋. meta: { taskNum, type }.
1011
- // - 모든 코드 파일이 정확히 1 Phase에 매핑 → Phase 순서대로 자동 커밋.
1012
- // - 미매핑/다중매핑/Phase 파싱 불가 → { ambiguous } 반환(커밋 X) → 스킬이 수동 처리.
1027
+ // 매핑 폴백 3규칙 (stash 6회 + recordion 5회 blocked 마찰 반영 — 11회 전부 기계적 편입이었음):
1028
+ // 1. 다중 Phase 매핑 → 가장 이른 Phase에 편입 + 커밋 메시지에 '(Phase a·b 변경 포함)' 표기.
1029
+ // 뒤 Phase가 앞 Phase 타입·함수를 참조하므로 이른 쪽이 의존 방향과 일치. 중간 커밋 독립 빌드는 비보장(GIT_RULE 명문).
1030
+ // 2. 미매핑 + 빌드 산출물(BUILD_ARTIFACT_RE) → 매핑된 그룹 중 마지막 Phase(없으면 Dev Plan 마지막)에
1031
+ // 자동 편입 + '(빌드 산출물 자동 편입)' 표기.
1032
+ // 3. 미매핑 + 일반 파일 → { ambiguous } 반환(커밋 X) — 계획 누락 가능성이라 사람 판단으로 유지.
1013
1033
  // .project/ 메타(flows/문서/changelog)는 제외 — closeTask가 별도 커밋.
1014
1034
  const codeFiles = _porcelainFiles(wtPath).filter((f) => !f.startsWith('.project/'));
1015
1035
  if (codeFiles.length === 0) return { committed: [], empty: true };
@@ -1028,21 +1048,44 @@ function splitUncommittedByPhase(wtPath, taskMdPath, meta) {
1028
1048
  )
1029
1049
  .map(({ i }) => i);
1030
1050
  const fileToPhase = {};
1051
+ const multiPhase = []; // 규칙 1 적용분 — { file, phases:[num...], assignedTo }
1052
+ const artifactFiles = []; // 규칙 2 후보 — 매핑 확정 후 편입 Phase 결정
1031
1053
  for (const f of codeFiles) {
1032
1054
  const m = matchOf(f);
1033
- if (m.length !== 1) {
1055
+ if (m.length === 1) {
1056
+ fileToPhase[f] = m[0];
1057
+ } else if (m.length > 1) {
1058
+ const earliest = Math.min(...m);
1059
+ fileToPhase[f] = earliest;
1060
+ multiPhase.push({ file: f, phases: m.map((i) => phases[i].num), assignedTo: phases[earliest].num });
1061
+ } else if (BUILD_ARTIFACT_RE.some((re) => re.test(f))) {
1062
+ artifactFiles.push(f);
1063
+ } else {
1034
1064
  return {
1035
1065
  ambiguous: true,
1036
- reason: m.length === 0 ? `Phase 미매핑: ${f}` : `다중 Phase 매핑: ${f}`,
1066
+ reason: `Phase 미매핑: ${f}`,
1037
1067
  files: codeFiles,
1038
1068
  phases: phases.map((p) => ({ num: p.num, name: p.name, files: p.files })),
1039
1069
  };
1040
1070
  }
1041
- fileToPhase[f] = m[0];
1042
1071
  }
1072
+ // 규칙 2 — 산출물 편입: 매핑된 그룹 중 마지막 Phase, 하나도 없으면 Dev Plan 마지막 Phase
1073
+ const autoAssigned = [];
1074
+ if (artifactFiles.length > 0) {
1075
+ const mappedIdx = Object.values(fileToPhase);
1076
+ const lastIdx = mappedIdx.length > 0 ? Math.max(...mappedIdx) : phases.length - 1;
1077
+ for (const f of artifactFiles) {
1078
+ fileToPhase[f] = lastIdx;
1079
+ autoAssigned.push({ file: f, assignedTo: phases[lastIdx].num });
1080
+ }
1081
+ }
1082
+ // 파일별 커밋 메시지 표기 (자동 편입의 근거를 커밋에 남김 — 코드가 몰래 정한 걸 드러낸다)
1083
+ const noteOf = {};
1084
+ for (const x of multiPhase) noteOf[x.file] = ` (Phase ${x.phases.join('·')} 변경 포함)`;
1085
+ for (const x of autoAssigned) noteOf[x.file] = ' (빌드 산출물 자동 편입)';
1043
1086
  // Phase별 그룹 → 순서대로 커밋
1044
1087
  const groups = new Map();
1045
- for (const f of codeFiles) {
1088
+ for (const f of Object.keys(fileToPhase)) {
1046
1089
  const i = fileToPhase[f];
1047
1090
  if (!groups.has(i)) groups.set(i, []);
1048
1091
  groups.get(i).push(f);
@@ -1056,18 +1099,22 @@ function splitUncommittedByPhase(wtPath, taskMdPath, meta) {
1056
1099
  execFileSync('git', ['-C', wtPath, 'add', '--', ...grp], { stdio: 'pipe' });
1057
1100
  const msg =
1058
1101
  `${tag}: [TASK-${nnn}] Phase ${p.num} - ${p.name}\n\n` +
1059
- grp.map((f) => `- ${f}`).join('\n') +
1102
+ grp.map((f) => `- ${f}${noteOf[f] || ''}`).join('\n') +
1060
1103
  `\n- 사유: ${p.name}`;
1061
1104
  execFileSync('git', ['-C', wtPath, 'commit', '-m', msg], { stdio: 'pipe' });
1062
1105
  committed.push({ phase: p.num, name: p.name, files: grp });
1063
1106
  }
1064
- return { committed };
1107
+ const result = { committed };
1108
+ if (multiPhase.length > 0) result.multiPhase = multiPhase;
1109
+ if (autoAssigned.length > 0) result.autoAssigned = autoAssigned;
1110
+ return result;
1065
1111
  }
1066
1112
 
1067
1113
  async function closeTask(mainWtPath, { taskNum }, opts = {}) {
1068
1114
  // 결정적 준비만 — 비가역(머지·정리)·충돌 해결은 스킬이 후속 수행.
1069
1115
  // 반환: { blocked } (게이트 미충족/매핑 모호) 또는
1070
- // { prepped, branch, parent, wtPath, registered, commits, aheadOfParent }. parent = 스킬 머지 대상.
1116
+ // { prepped, branch, parent, wtPath, registered, commits, aheadOfParent,
1117
+ // multiPhase?, autoAssigned? (매핑 폴백 적용 시 내역) }. parent = 스킬 머지 대상.
1071
1118
  const active = getActiveTasks(mainWtPath).find((t) => t.taskNum === taskNum);
1072
1119
  if (!active) {
1073
1120
  throw new Error(`TASK-${String(taskNum).padStart(3, '0')} 진행중 브랜치 없음 (SSoT 조회).`);
@@ -1158,7 +1205,7 @@ async function closeTask(mainWtPath, { taskNum }, opts = {}) {
1158
1205
  gitCapture(wtPath, ['rev-list', '--count', `${parent}..HEAD`], { allowFail: true }) || '0',
1159
1206
  10,
1160
1207
  );
1161
- return {
1208
+ const prepResult = {
1162
1209
  prepped: true,
1163
1210
  taskNum,
1164
1211
  branch: active.branch,
@@ -1169,6 +1216,10 @@ async function closeTask(mainWtPath, { taskNum }, opts = {}) {
1169
1216
  commits,
1170
1217
  aheadOfParent: aheadFinal,
1171
1218
  };
1219
+ // 매핑 폴백 적용 내역 노출 — 코드가 자동 편입한 걸 스킬·사용자가 볼 수 있게
1220
+ if (split.multiPhase) prepResult.multiPhase = split.multiPhase;
1221
+ if (split.autoAssigned) prepResult.autoAssigned = split.autoAssigned;
1222
+ return prepResult;
1172
1223
  }
1173
1224
 
1174
1225
  module.exports = {
package/bin/mcp.js CHANGED
@@ -189,7 +189,7 @@ server.registerTool(
189
189
  inputSchema: {
190
190
  type: z.enum(VALID_TYPES),
191
191
  dev: z.string().describe('개발자 식별자 (claude / angar2 등)'),
192
- src: z.string().regex(/^(BL-\d+|RM-\d+|DR)$/, 'BL-NNN | RM-NNN | DR'),
192
+ src: z.string().regex(/^(BL-\d+|RM-\d+|ST-\d+|DR)$/, 'BL-NNN | ST-N(로드맵 Stage) | DR (구형 RM-NNN 호환)'),
193
193
  slug: z.string().regex(/^[a-z0-9]+(-[a-z0-9]+)*$/, '영어 kebab-case'),
194
194
  size: z.enum(VALID_SIZES).optional(),
195
195
  title: z.string().optional().describe('한국어 제목 (size와 함께 주면 골격 생성)'),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@angar2/taskery",
3
- "version": "0.6.0",
3
+ "version": "0.6.1",
4
4
  "description": "Lightweight task system for Claude Code & Codex — 1 메인 세션(또는 멀티세션) + 스킬 9종 + catastrophic hook 안전망 2종",
5
5
  "bin": {
6
6
  "taskery": "bin/taskery.js",
@@ -24,9 +24,12 @@ developer_instructions = """
24
24
  - task.md의 ## Test Plan 시나리오를 순서대로 그대로 수행한다(임의 추가/스킵/변형 금지).
25
25
  - 문 앞 검사(먼저) — 각 [AUTO] 시나리오가 [실행 명령/입력 + 구체적·관측 가능한 기대값(출력값/상태코드/어서션/조회 결과/화면 상태)] 한 쌍을 갖췄는지 확인한다. "정상 동작/잘 됨/확인한다" 같은 소원은 기대값이 아니다. 미달인 [AUTO]는 검사하지 말고 "시험문제 결함(검증 불가)"으로 분류 + 무엇이 빠졌는지 지목한다(PASS 반올림 금지).
26
26
  - 증거 일치 시만 PASS — 자격 갖춘 [AUTO]는 실제로 실행해 *관측 결과가 기대값과 일치할 때만* PASS. 코드 정독 "그럴듯함"·추정으로 PASS 금지. 증거(명령 출력/응답 로그/캡처/조회 결과)를 첨부한다.
27
- - [AUTO]/[USER] 분류 — [USER]는 정답지가 *주관*인 것만(시각 미세 취향 / 색조·2px / UX 느낌) → UNCERTAIN(사람 검수) + 증거 캡처로 리턴. 시각 *객관* 깨짐(요소 빠짐/빈 화면/배치 딴판/색 완전히 틀림)은 [USER]가 아니라 [AUTO] — 실제 화면 캡처 → 승인 목업과 대조해 네가 직접 판정한다(어긋나면 *구체적 어긋남 목록* + FAIL). 시각이라고 무조건 사람에게 넘기지 마라.
28
- - UNCERTAIN 두 종류 — (사람 검수)=정답지가 주관인 영역 / (검증 불가)=[AUTO]인데 대조할 기대값을 구성 못 함=시험문제 결함. 검증 불가는 PASS로 반올림하거나 사람 검수로 떠넘기지 말고, *무엇을 시도했고 왜 기대값을 구성할 수 없는지* 근거를 반드시 기록한다(task-plan으로 Test Plan을 보수해야 하는 신호).
27
+ - [AUTO]/[USER] 분류 — [USER]는 정답지가 *주관*인 것만(시각 미세 취향 / 색조·2px / UX 느낌) → UNCERTAIN(사람 검수) + 증거 캡처로 리턴. 시각 *객관* 깨짐(요소 빠짐/빈 화면/배치 딴판/색 완전히 틀림)은 [USER]가 아니라 [AUTO] — 실제 화면 캡처 → 승인 목업과 대조해 네가 직접 판정한다(어긋나면 *구체적 어긋남 목록* + FAIL). 시각이라고 무조건 사람에게 넘기지 마라. 단 시각 객관의 [AUTO] 승격은 TEST-GUIDE의 *시각 실행* 절에 캡처 실행 경로가 있을 때 한정이다 — 경로가 없어 task.md가 [USER]로 분류했으면 그 분류를 존중한다.
28
+ - UNCERTAIN 세 종류 — (사람 검수)=정답지가 주관인 영역 / (검증 불가)=[AUTO]인데 대조할 기대값을 구성 못 함=시험문제 결함 / (실행 보류)=화면·입력 점유 제약으로 실행하지 않음=환경 제약(사용자 승인 또는 격리 실행 경로 필요. 무엇을 왜 실행하지 않았는지 기록해 리턴). 검증 불가는 PASS로 반올림하거나 사람 검수로 떠넘기지 말고, *무엇을 시도했고 왜 기대값을 구성할 수 없는지* 근거를 반드시 기록한다(task-plan으로 Test Plan을 보수해야 하는 신호).
29
29
  - 신규 테스트 추가 시 카운트 +N / 모든 PASS만으로 단정 X — 신규 *테스트 식별자*(suite/class/function 등)가 runner 로그에 실제 등장했는지 grep으로 확인한다(1 hit 이상, 없으면 Suite 미실행 → FAIL). grep/Read 존재 확인은 보조용 — 실동작 PASS를 대체할 수 없다.
30
+ - 픽스처 현실성 — 픽스처·시드가 실제 산출물과 형식·정밀도가 같은지 의심하고, 의심되면 실제 코드(writer·생성기)로 재생성해 재실행한 결과로 판정한다. 재생성 경로가 없어 대조를 구성할 수 없으면 시험문제 결함(검증 불가)으로 분류한다.
31
+ - 어서션 정면성 — 기대값이 요구사항을 정면으로 겨누는지 확인한다("벗어날 수 없다"면 실제로 벗어나 볼 것. 버튼 disabled 확인은 우회 지표다). 우회 지표로 검사하는 [AUTO] 시나리오는 시험문제 결함으로 분류하고 무엇이 정면 관측인지 지목한다.
32
+ - 화면·입력 점유 금지 — 실제 앱을 띄워 UI를 자동 조작하는 스위트는 사용자의 화면·입력을 점유한다. TEST-GUIDE의 *테스트 실행 환경* 절에 격리 실행 경로(창 숨김·헤드리스 / 컨테이너 / VM / CI)가 있으면 그 경로로 실행하고, 없으면 비점유 부분만 실행하거나 실행 보류(사용자 승인 필요)로 표시해 리턴한다. 헤드리스 모드가 있으면 항상 그쪽을 쓴다.
30
33
  - 코드와 동작만 신뢰한다. 코드 파일 수정 금지. 임시 파일(테스트 스크립트 등)은 종료 후 삭제한다.
31
- - 검증을 마치면 넘겨받은 검사 지시의 결과 형식(종합 판정 PASS/FAIL/UNCERTAIN / 시나리오별 결과 / 검증·테스트 명령 결과 / UNCERTAIN 2종 / 시험문제 결함)을 그대로 리턴한다.
34
+ - 검증을 마치면 넘겨받은 검사 지시의 결과 형식(종합 판정 PASS/FAIL/UNCERTAIN / 시나리오별 결과 / 검증·테스트 명령 결과 / UNCERTAIN 3종 / 시험문제 결함 / 실행 보류)을 그대로 리턴한다.
32
35
  """
@@ -3,6 +3,7 @@
3
3
  > 이 프로젝트에서 각 테스트 방식을 *실제로 어떻게 실행하나*의 단일 소스.
4
4
  > **제품 관통 문서 아님 — 검증 방법 문서.** (제품 스펙은 `.project/` 루트 그룹 A/B 문서 참조.)
5
5
  > `/task-test` 격리 세션이 task.md와 함께 이 파일을 읽어, 명세된 방식을 *이 프로젝트의 실제 경로*로 실행한다.
6
+ > **역할 구분**: 본 문서 = *어떻게 실행하나*(경로·명령·셋업). `.project/rules/TEST_RULE.local.md` = *무엇을 얼마나 검증하나*(범위·방식 정책, 옵션·로컬 전용). 둘을 섞어 적으면 어느 쪽에도 안 남는다.
6
7
 
7
8
  **사용 규칙**:
8
9
  - 처음엔 빈 골격 — 검증 방법을 아직 모름. `/task-plan`이 방식별 실행 경로를 *사용자에게 확인받은 즉시* 채운다.
@@ -44,6 +45,16 @@
44
45
 
45
46
  ---
46
47
 
48
+ ## 테스트 실행 환경 (격리 실행 경로 — 화면·입력 점유 회피)
49
+
50
+ > 실제 앱을 띄워 UI를 자동 조작하는 스위트(XCUITest · headed 브라우저 E2E 등)를 **사용자의 화면·마우스·키보드를 뺏지 않고** 돌리는 경로. 여기에 경로가 적혀 있으면 자동 게이트·격리 세션이 그 경로로 자동 실행하고, 비어 있으면 자동 실행하지 않고 사용자 승인을 받는다 (`/task-dev` Step 6.5 · `/task-test` 격리 룰).
51
+ >
52
+ > 적을 수 있는 것: 창 숨김·헤드리스 플래그(예: 테스트 모드 환경변수로 창 표시·포커스 탈취 비활성) / 가상 디스플레이·컨테이너 / VM / CI 러너 위임 / 점유 스위트를 제외하는 한정 실행 옵션(번들·프로젝트·태그 지정).
53
+
54
+ <아직 미정 — 점유 스위트가 있는 프로젝트에서 `/task-dev`·`/task-test`가 사용자에게 확인 후 기록>
55
+
56
+ ---
57
+
47
58
  ## 기타
48
59
 
49
60
  > 위 분류에 안 맞는 프로젝트 고유 검증 방법.
@@ -51,7 +51,7 @@
51
51
  | 출처 | 의미 | 채번 주체 |
52
52
  |------|------|---------|
53
53
  | `BL-NNN` | 백로그 항목 | `/add-backlog` 스킬 (0.1.2). plan(기능 그룹)별 `.project/tasks/<NNN_slug>/BACKLOG.md`에 누적 |
54
- | `RM-NNN` | 로드맵 항목 | `/plan-init` (RM-NNN 채번 자동화는 후속 — 1차는 사용자 수동 명시) |
54
+ | `ST-N` | 로드맵 Stage | 별도 채번 없음 — ROADMAP의 Stage 번호 그대로 (`ST-2` = Stage 2). Stage 하나에서 복수 task 정상(동일 출처 중복 게이트 미적용). 구형 `RM-NNN`은 호환 수용만 |
55
55
  | `DR` | 직접 요구사항 (별도 ID 없음) | 사용자 발화 → `/task-init` |
56
56
 
57
57
  ### 브랜치 예시
@@ -59,7 +59,7 @@
59
59
  ```
60
60
  feature/claude_TASK-007_BL-003_login-feature
61
61
  bug/angar2_TASK-012_DR_mobile-form-refresh
62
- refactor/claude_TASK-018_RM-002_api-cleanup
62
+ refactor/claude_TASK-018_ST-2_api-cleanup
63
63
  docs/claude_TASK-021_DR_readme-rewrite
64
64
  ```
65
65
 
@@ -116,6 +116,15 @@ feat: [TASK-001] Phase 1 - 사용자 인증 로직 구현
116
116
  5. 워크트리 자동 제거 + 작업 브랜치 자동 삭제 (멀티세션 0.1.2+ — §"작업 브랜치 삭제 정책" 면제 조항)
117
117
  ```
118
118
 
119
+ ### Phase↔파일 매핑 규칙 (`close` 자동 커밋)
120
+
121
+ `close`가 코드 변경분을 Dev Plan Phase에 매핑해 Phase별 커밋을 자동 생성할 때 아래 규칙을 따른다 (한 파일 = 한 Phase가 성립하지 않는 정상 케이스의 blocked 반복 방지 — stash 6회·recordion 5회 마찰 반영):
122
+
123
+ - **한 파일이 여러 Phase에 걸치면 → 가장 이른 Phase 커밋에 편입** + 커밋 메시지에 `(Phase a·b 변경 포함)` 표기. 뒤 Phase가 앞 Phase의 타입·함수를 참조하므로 이른 쪽이 의존 방향과 일치한다.
124
+ - **Dev Plan에 없는 빌드 산출물**(`*.pbxproj` / `package-lock.json` 등 잠금파일 계열) **→ 마지막 Phase 커밋에 자동 편입** + `(빌드 산출물 자동 편입)` 표기. 산출물은 소스에 딸려 바뀌는 파생물이라 Phase 소속을 물을 대상이 아니다.
125
+ - **Dev Plan에 없는 일반 파일 → 자동 편입 없이 중단(blocked)** — 계획에 없던 작업일 수 있어 사람 판단으로 남긴다.
126
+ - **중간 커밋의 독립 빌드 가능성은 보장 대상이 아니다** — 다중 Phase 편입 시 어느 배치로도 중간 커밋 빌드는 성립하지 않을 수 있다(최종 트리 정상이면 충분). 이를 이유로 매핑을 수동 재배치하지 않는다.
127
+
119
128
  ### 태스크 문서 커밋 메시지
120
129
 
121
130
  ```
@@ -257,6 +266,8 @@ git -C "$MAIN_WT" branch --list \
257
266
  - **터널 (필수 아님)**: 모바일 검수 등 필요한 task만, 자기 포트를 가리키는 별도 프로세스로 판다 → task별 독립 URL.
258
267
  - **자기 것만 정리**: `/task-close` 시 본 task가 띄운 서버·터널(자기 포트 것)만 종료한다. **`pkill -f` 같은 광역 종료 금지** — 다른 세션의 서버까지 죽이는 원인.
259
268
  - **검수 기동 (자동)**: 사용자가 결과를 *눈으로 검수*하는 시점에 위 포트로 서버를 백그라운드 기동하고 접속 URL을 보고한다 (사용자가 매번 실행을 지시하지 않게). 정확한 발동 지점은 각 스킬 참조 — `/task-dev`는 세션이 거기서 끝날 때(Step 9), `/task-test`는 UNCERTAIN 검수 직전(Step 5)·PASS 후 종료 시(Step 7). FAIL→수정 흐름에서는 기동하지 않는다. 단계가 *기능상* 서버를 요구하면(E2E 등) 검수 시점과 무관하게 띄운다.
269
+ - **중복 기동 방지**: 새로 띄우기 전에 해당 포트·프로세스가 이미 살아 있는지 확인한다. `Port … is in use` 류 경고는 *이미 떠 있다*는 신호이므로 무시하고 재기동하지 않는다. 종료 시에는 부모 프로세스만이 아니라 자식 프로세스(브라우저·렌더러 창 등)까지 확인해 정리한다.
270
+ - **화면·입력 점유 회피**: 실제 앱을 띄워 UI를 자동 조작하는 테스트 스위트는 사용자의 화면·마우스·키보드를 점유해 작업을 중단시킨다. 자동 게이트·격리 세션은 `.project/TEST-GUIDE.md` *테스트 실행 환경* 절에 선언된 격리 실행 경로(창 숨김·헤드리스 / 컨테이너 / VM / CI)로만 자동 실행하고, 선언이 없으면 비점유 부분만 실행하거나 사용자 승인을 받는다.
260
271
  - **효과 (정직)**: 포트 충돌은 결정적으로 제거된다. 다른 세션의 종료·턴 경계 프로세스 소실은 위 규율·백그라운드 실행으로 완화되나 문서 규율이라 강제는 아니다(best-effort).
261
272
 
262
273
  ---
@@ -307,3 +318,5 @@ git -C "$MAIN_WT" branch --list \
307
318
  | 2026-06-28 | 워크트리 생성 메커니즘 갱신 — `git worktree add` 직접 실행 → `npx @angar2/taskery fork`(init 락 안에서 채번+분기 원자 실행). 병렬 task-init 번호 충돌(레이스) 차단 |
308
319
  | 2026-06-25 | §멀티세션 워크트리 정책에 "워크트리 실행 환경" 추가 — 워크트리에 의존성 심링크 금지(개발 서버 fs 접근 제한으로 로딩 실패), 실행·검수 task는 워크트리 안에 의존성 실제 마련. FRICTION 검토 F6 반영 |
309
320
  | 2026-06-25 | §멀티세션 워크트리 정책에 "멀티세션 검수 환경" 추가 — task별 dev 서버 포트 격리(메인=기준 포트, task=기준+TASK번호) + 터널 독립 + close 시 자기 것만 종료(광역 종료 금지) + 사용자 검수 시점 서버 자동 기동(task-test UNCERTAIN/PASS·task-dev 종료, FAIL 제외). FRICTION 검토 F7 반영 |
321
+ | 2026-08-08 | 출처 표기 개편 — `RM-NNN`(로드맵 항목 채번, 미구현) → `ST-N`(ROADMAP Stage 번호 그대로, 채번 불요·동일 출처 중복 게이트 미적용). 구형 RM-NNN은 호환 수용만. + §커밋 순서에 "Phase↔파일 매핑 규칙" 신설 — 다중 Phase 파일=이른 Phase 편입+표기 / 빌드 산출물=마지막 Phase 자동 편입 / 일반 미매핑만 blocked / 중간 커밋 독립 빌드 비보장 명문화. stash·recordion FRICTION_LOG 반영 |
322
+ | 2026-08-08 | §멀티세션 검수 환경에 "중복 기동 방지"·"화면·입력 점유 회피" 2항 추가 — 검수 앱 중복 기동(부모만 종료해 창 잔존) 및 UI 자동조작 스위트의 화면·입력 강탈 마찰 반영. stash·recordion FRICTION_LOG |
@@ -40,7 +40,7 @@
40
40
  - **메인 워크트리 = 부모 브랜치 고정**: 메인 워크트리는 `/task-init` 시점에 체크아웃돼 있는 브랜치(= 그 task의 *부모 브랜치*)를 유지한다. 개인 기본은 `dev`, 회사/로드맵은 `dev_feat_x` / `master` 등 — taskery는 *현재 서 있는 브랜치*를 부모로 삼는다(특정 이름 고정 X). 모든 태스크 작업은 *예외 없이* 별도 워크트리에서 수행. 진행 중 태스크가 있는 동안 메인 워크트리 HEAD를 옮기는 어떤 작업(`git checkout <task-branch>` / `git switch` / `git reset` HEAD 이동 / `git rebase` HEAD 이동 등)도 영구 금지 — close가 부모로 되병합해야 하므로 부모에 서 있어야 한다. *"잠깐만 메인에서"* / *"테스트 한 번만 메인에서"* 같은 예외 발화도 거부 — 별도 워크트리에서 처리. (부모 브랜치 자체를 바꾸려면 진행 중 태스크가 없을 때 원하는 브랜치로 체크아웃 후 새 task 시작.)
41
41
  - **워크트리 위치**: `~/.taskery/worktrees/<projectId>/TASK-NNN_<출처>_<슬러그>/`
42
42
  - `<projectId>`: `.taskery-manifest.json` `projectId` (8자 hex)
43
- - `<출처>`: `BL-NNN` / `RM-NNN` / `DR`
43
+ - `<출처>`: `BL-NNN`(백로그) / `ST-N`(로드맵 Stage) / `DR`(직접 요구사항)
44
44
  - **`/task-init`이 부모 브랜치에서 워크트리 생성** + **`/task-close`가 부모 브랜치 머지 + 워크트리 자동 제거** (자동 흐름. 보존 키워드 `keep` / `브랜치 남겨` 등 발화 시 둘 다 보존).
45
45
  - **메인 메타 접근**: 워크트리에서 `.project/`, `AGENTS.md` 등 접근 시 *메인 워크트리 절대 경로* 사용:
46
46
  ```sh
@@ -133,6 +133,8 @@
133
133
  | GIT_RULE | `.project/rules/GIT_RULE.md` (있으면) → 글로벌 `~/.claude/rules/GIT_RULE.md` (fallback) | git 정책 (브랜치 / 커밋 / 머지) |
134
134
  | CHANGELOG_RULE | `.project/rules/CHANGELOG_RULE.md` | CHANGELOG 작성 정책 (위치 / 형식 / 필수 필드) |
135
135
  | MOCKUP_RULE | `.project/rules/MOCKUP_RULE.md` | UX/UI task의 HTML 목업 위치 / 형식 / 네이밍 |
136
+ | DEV_RULE (옵션) | `.project/rules/DEV_RULE.local.md` | 이 프로젝트 고유의 구현·테스트 실행 정책. 있으면 `/task-dev`가 읽고 겹치는 조항은 로컬 우선 (코어 파일 없음 — 로컬 전용) |
137
+ | TEST_RULE (옵션) | `.project/rules/TEST_RULE.local.md` | 이 프로젝트 고유의 검증 범위·방식 정책. 있으면 `/task-dev`·`/task-test`·`/task-plan`이 읽고 겹치는 조항은 로컬 우선 (격리 세션에도 전달. 코어 파일 없음 — 로컬 전용) |
136
138
  | `*.local.md` | `.project/rules/` | (옵션) 사용자 오버라이드 — `*.local.md` suffix는 npx 미터치 |
137
139
 
138
140
  ---
@@ -35,7 +35,7 @@
35
35
  - **메인 워크트리 = 부모 브랜치 고정**: 메인 워크트리는 `/task-init` 시점에 체크아웃돼 있는 브랜치(= 그 task의 *부모 브랜치*)를 유지한다. 개인 기본은 `dev`, 회사/로드맵은 `dev_feat_x` / `master` 등 — taskery는 *현재 서 있는 브랜치*를 부모로 삼는다(특정 이름 고정 X). 모든 태스크 작업은 *예외 없이* 별도 워크트리에서 수행. 진행 중 태스크가 있는 동안 메인 워크트리 HEAD를 옮기는 어떤 작업(`git checkout <task-branch>` / `git switch` / `git reset` HEAD 이동 / `git rebase` HEAD 이동 등)도 영구 금지 — close가 부모로 되병합해야 하므로 부모에 서 있어야 한다. *"잠깐만 메인에서"* / *"테스트 한 번만 메인에서"* 같은 예외 발화도 거부 — 별도 워크트리에서 처리. (부모 브랜치 자체를 바꾸려면 진행 중 태스크가 없을 때 원하는 브랜치로 체크아웃 후 새 task 시작.)
36
36
  - **워크트리 위치**: `~/.taskery/worktrees/<projectId>/TASK-NNN_<출처>_<슬러그>/`
37
37
  - `<projectId>`: `.taskery-manifest.json` `projectId` (8자 hex)
38
- - `<출처>`: `BL-NNN` / `RM-NNN` / `DR`
38
+ - `<출처>`: `BL-NNN`(백로그) / `ST-N`(로드맵 Stage) / `DR`(직접 요구사항)
39
39
  - **`/task-init`이 부모 브랜치에서 워크트리 생성** + **`/task-close`가 부모 브랜치 머지 + 워크트리 자동 제거** (자동 흐름. 보존 키워드 `keep` / `브랜치 남겨` 등 발화 시 둘 다 보존).
40
40
  - **메인 메타 접근**: 워크트리에서 `.project/`, `CLAUDE.md` 등 접근 시 *메인 워크트리 절대 경로* 사용:
41
41
  ```sh
@@ -132,6 +132,8 @@
132
132
  | GIT_RULE | `.project/rules/GIT_RULE.md` (있으면) → 글로벌 `~/.claude/rules/GIT_RULE.md` (fallback) | git 정책 (브랜치 / 커밋 / 머지) |
133
133
  | CHANGELOG_RULE | `.project/rules/CHANGELOG_RULE.md` | CHANGELOG 작성 정책 (위치 / 형식 / 필수 필드) |
134
134
  | MOCKUP_RULE | `.project/rules/MOCKUP_RULE.md` | UX/UI task의 HTML 목업 위치 / 형식 / 네이밍 |
135
+ | DEV_RULE (옵션) | `.project/rules/DEV_RULE.local.md` | 이 프로젝트 고유의 구현·테스트 실행 정책. 있으면 `/task-dev`가 읽고 겹치는 조항은 로컬 우선 (코어 파일 없음 — 로컬 전용) |
136
+ | TEST_RULE (옵션) | `.project/rules/TEST_RULE.local.md` | 이 프로젝트 고유의 검증 범위·방식 정책. 있으면 `/task-dev`·`/task-test`·`/task-plan`이 읽고 겹치는 조항은 로컬 우선 (격리 세션에도 전달. 코어 파일 없음 — 로컬 전용) |
135
137
  | `*.local.md` | `.project/rules/` | (옵션) 사용자 오버라이드 — `*.local.md` suffix는 npx 미터치 |
136
138
 
137
139
  ---
@@ -80,6 +80,7 @@ LOCK_FILE="$HOME/.taskery/${PROJECT_ID}.merge.lock"
80
80
  - `$MAIN_WT/.project/rules/GIT_RULE.md` (프로젝트별 — 우선)
81
81
  - `~/.claude/rules/GIT_RULE.md` (글로벌)
82
82
  둘 다 없으면 종료 + *"GIT_RULE 누락"* 보고.
83
+ - **로컬 오버라이드** — `$MAIN_WT/.project/rules/GIT_RULE.local.md`가 있으면 함께 Read하고, 겹치는 항목은 **로컬 우선** 적용한다 (`npx update` 미터치 영역). 단 로컬 룰이 뒤집을 수 없는 것: 부모 브랜치 직접 커밋 금지 · `--no-ff` 강제 · 머지 락 직렬화 · destructive 명령 사용자 승인.
83
84
 
84
85
  ### Step 2 — 최종 검증 명령 재실행 (게이트)
85
86
 
@@ -101,18 +102,42 @@ FAIL 시:
101
102
  - *"고쳐"* → status `developing`으로 되돌림 + `/task-dev` 안내 + close 중단.
102
103
  - *"OK 마무리"* → 결함 명시한 채 진행 (Result 섹션에 *"검증 명령 X FAIL 알려진 결함 — 사유: ..."* 추가).
103
104
 
105
+ ### Step 2.5 — 문서 갱신 훑기 (게이트 통과 후 · `close` 호출 전)
106
+
107
+ 본 task로 갱신돼야 할 문서를 *목록으로* 훑는다. 이 단계가 없어 PLAN.md 체크리스트·수정이력·CHANGELOG가 연속으로 누락된 사례가 있다 (recordion FRICTION 2026-08-05, 5태스크 연속·26문서 무이력·6태스크 무CHANGELOG). 개별 항목이 아니라 *목록 훑기*가 본 단계의 요지다 — 하나씩 규칙을 더하는 방식으로는 습관 밖의 문서가 계속 빠진다.
108
+
109
+ **편집 위치** (`.gitignore` 케이스 — Step 1에서 판정한 값 사용):
110
+ - 등록 케이스 → `$MAIN_WT/.project/...` (gitignore라 git 추적 무관, 즉시 반영)
111
+ - 미등록 케이스 → `$WT_PATH/.project/...` (Step 3 `close`의 문서 커밋이 자동 수거 → 부모 머지로 반영)
112
+
113
+ **점검 목록** (해당 없으면 "해당 없음"으로 명시하고 넘어간다 — 침묵 통과 금지):
114
+
115
+ | # | 대상 | 갱신 내용 |
116
+ |---|------|----------|
117
+ | 1 | `.project/plans/<활성 plan>/PLAN.md` | 본 task에 해당하는 체크리스트 항목 `[x]` + TASK 번호 표기 |
118
+ | 2 | `.project/plans/<활성 plan>/ROADMAP.md` | Stage 상태·완료 노트 (출처가 `ST-N`이거나 Stage 진척이 생긴 경우) |
119
+ | 3 | `.project/changelog/<YYYY-MM>.md` | 본 task 항목 추가. **파일이 없으면 생성한다** (형식은 `.project/rules/CHANGELOG_RULE.md` — `.local.md` 있으면 그쪽 우선) |
120
+ | 4 | 본 task가 수정한 **제품 관통 문서·plan 문서**의 `## 수정 이력` | 해당 문서마다 행 추가, 표가 없으면 신설. 대상 목록의 출처 = `spec-diffs/` + Dev Plan Phase 0 기록 (등록 케이스는 `.project/`가 git 밖이라 `git diff`로 찾을 수 없다). **task 문서는 제외** — close가 잠그는 별개 계열이다 |
121
+ | 5 | 후속 task 후보 (있을 때만) | **손편집 금지** — `backlog_add` 도구(또는 `npx @angar2/taskery backlog-add ...`)로 등재한다. BL 채번·서식·`withMetaLock` 직렬화를 코드가 보장하며, 파일 위치는 등록/미등록과 무관하게 **항상 메인 워크트리**(`.project/tasks/<활성 plan>/BACKLOG.md`)다 — 위 "편집 위치" 분기가 적용되지 않는 유일한 항목 |
122
+ | 6 | `spec-diffs/` ↔ 실제 문서 변경 | 기재한 diff가 실제 반영됐는지 대조 |
123
+
124
+ - 갱신 결과는 Step 8 보고의 `- 문서 갱신:` 줄에 적는다.
125
+ - 본 단계에서 git 명령을 실행하지 않는다 (커밋은 Step 3 `close`와 Step 5 머지가 담당).
126
+
104
127
  ### Step 3 — 결정적 준비 (`task_close` 도구)
105
128
 
106
129
  `task_close` 도구 (또는 `npx @angar2/taskery close TASK-NNN`) 한 번으로 다음을 코드가 수행:
107
- - **Phase 기능 커밋** — uncommitted 코드 변경분(`.project/` 외)을 task.md `### Phase N`의 `- 파일:` 필드에 매핑해 Phase 순서대로 자동 커밋 (GIT_RULE 메시지 형식, 태그 자동: feature→`feat:` / bug→`fix:` / improve→`improve:` / refactor→`refactor:` / docs·chore→`docs:`).
130
+ - **Phase 기능 커밋** — uncommitted 코드 변경분(`.project/` 외)을 task.md `### Phase N`의 `- 파일:` 필드에 매핑해 Phase 순서대로 자동 커밋 (GIT_RULE 메시지 형식, 태그 자동: feature→`feat:` / bug→`fix:` / improve→`improve:` / refactor→`refactor:` / docs·chore→`docs:`). 매핑 폴백은 GIT_RULE §"Phase↔파일 매핑 규칙" — 한 파일이 여러 Phase에 걸치면 *가장 이른 Phase*, Dev Plan에 없는 *빌드 산출물*(`*.pbxproj`·잠금파일)은 *마지막 Phase*에 자동 편입(커밋 메시지에 사유 표기).
108
131
  - **status → `closed`** — task 문서 헤더 (`.gitignore` 케이스 자동 판정 — 등록=메인WT / 미등록=워크트리).
109
132
  - **flows/문서 커밋** (미등록 케이스) — 워크트리 안 `.project/flows/` + task 문서.
110
133
  - **추적 마커 빈커밋** — 부모 브랜치보다 앞선 커밋이 0개(코드 0·`.project` gitignore인 docs/분석 task)면 채번 보존용 1개.
111
134
 
112
- 반환 JSON `{ prepped, branch, parent, wtPath, registered, commits, aheadOfParent }`. **`parent`(= task 헤더에 기록된 부모 브랜치, 예 `dev` / `dev_feat_x`)가 이후 Step 4·5의 rebase·머지 대상이다** — 아래 명령의 `$PARENT`로 사용. (task-init이 fork 시점 현재 브랜치를 헤더에 기록해뒀다.)
135
+ 반환 JSON `{ prepped, branch, parent, wtPath, registered, commits, aheadOfParent, multiPhase?, autoAssigned? }`. **`parent`(= task 헤더에 기록된 부모 브랜치, 예 `dev` / `dev_feat_x`)가 이후 Step 4·5의 rebase·머지 대상이다** — 아래 명령의 `$PARENT`로 사용. (task-init이 fork 시점 현재 브랜치를 헤더에 기록해뒀다.)
136
+
137
+ - **`multiPhase` / `autoAssigned`가 있으면 Step 8 보고에 그대로 옮긴다** — 코드가 자동 편입한 파일과 그 근거(어느 Phase로 갔는지)를 사용자가 볼 수 있어야 한다. 재배치 목적의 수동 커밋 재작업은 하지 않는다(GIT_RULE: 중간 커밋 독립 빌드 비보장).
113
138
 
114
139
  **콜백 (반환 `blocked` 필드 / npx는 특수 exit — 메인이 LLM 판단 후 처리):**
115
- - **`blocked:'mapping'`** (npx exit 2 — `{files, phases, reason}`): Dev Plan `- 파일:` 필드가 변경 파일을 못 덮음(미매핑) 또는 한 파일이 여러 Phase에 걸침. → 메인이 Dev Plan + `git -C "$WT_PATH" diff --name-only` 정독 후 **코드 변경분을 Phase별로 수동 커밋**(GIT_RULE 형식, 위 태그) → `task_close` 도구(또는 `npx ... close`) **재호출**. 코드가 이미 커밋돼 있으므로 close가 status=closed + 문서 커밋 + 마커를 자동 완료한다.
140
+ - **`blocked:'mapping'`** (npx exit 2 — `{files, phases, reason}`): Dev Plan `- 파일:` 필드가 변경 파일을 못 덮음(**미매핑 — 빌드 산출물이 아닌 일반 파일 한정**. 다중 Phase 파일·산출물은 위 폴백으로 자동 처리돼 여기 오지 않는다). → 메인이 Dev Plan + `git -C "$WT_PATH" diff --name-only` 정독 후 **코드 변경분을 Phase별로 수동 커밋**(GIT_RULE 형식, 위 태그) → `task_close` 도구(또는 `npx ... close`) **재호출**. 코드가 이미 커밋돼 있으므로 close가 status=closed + 문서 커밋 + 마커를 자동 완료한다.
116
141
  - **`blocked:'status'`** (npx exit 3 — `{current}`): status가 `tested`가 아님. Step 1~2 통과했다면 발생 X. 발생 시 사용자 보고 + 중단.
117
142
 
118
143
  > **close 후 task 문서 수정 금지** — close가 status=`closed`로 잠갔다(`closed-immutable.sh` hook 차단). 이후 Step 4 충돌 해결 내역은 *task 문서가 아니라 rebase/머지 커밋 메시지*에 기록한다.
@@ -247,6 +272,8 @@ git -C "$MAIN_WT" worktree remove "$WT_PATH"
247
272
  - 작업 브랜치: <BRANCH> (삭제 / 보존)
248
273
  - 워크트리: <WT_PATH> (제거 / 보존)
249
274
  - 커밋: Phase <N>개 + 태스크 문서 + (CHANGELOG)
275
+ - 문서 갱신: <Step 2.5 점검 6항 중 실제 갱신한 것 — 예: PLAN.md 체크리스트 [x] / changelog 2026-08 신규 / ARCHITECTURE 수정이력 1행. 없으면 "해당 없음">
276
+ - 자동 편입: <multiPhase / autoAssigned 있을 때만 — 예: `src/ipc.ts` Phase 2·5 → Phase 2 / `project.pbxproj` → Phase 3(빌드 산출물)>
250
277
  - 부모 브랜치 병합: <PARENT> ← --no-ff 완료 ($MERGE_COMMIT)
251
278
  - 충돌 해결: <건수, 자료 한계 보고 있으면 포함>
252
279
  - 상태: tested → closed
@@ -59,6 +59,8 @@ self-check 명령은 워크트리(`$WT_PATH`)에서 실행 — 코드 변경이
59
59
  - **`## 검증 명령`** (코드 상태 — 빌드/린트/타입체크): self-check 게이트의 단일 진실 소스 (Step 6).
60
60
  - **`## 테스트 명령`** (테스트 실행 — 단위/통합/E2E): 구현 후 테스트 실행의 단일 진실 소스 (Step 6.5).
61
61
  - stash FRICTION_LOG #25 반영 — 두 섹션 분리. self-check 게이트는 *코드 상태*만, 테스트는 *별도 단계*.
62
+ 5. **프로젝트 로컬 룰 확인** — `$MAIN_WT/.project/rules/DEV_RULE.local.md`(구현 정책)와 `$MAIN_WT/.project/rules/TEST_RULE.local.md`(테스트 범위·방식 정책)를 각각 있으면 Read한다 (없으면 넘어간다. `npx update` 미터치 영역이라 프로젝트가 자유롭게 소유한다). 본 스킬 조항과 겹치는 항목은 **로컬 룰을 우선 적용**한다 — 프로젝트마다 구현·테스트 실행 방식이 다르므로 본 스킬 조항은 *안전한 기본값*이다. **Step 6.5의 테스트 실행 범위·신설 상한은 `TEST_RULE.local.md`가 있으면 그 규정을 따른다** (본 스킬이 테스트를 실제로 실행하는 주체이므로, 그 파일을 읽지 않으면 프로젝트 고유 규칙이 무력해진다).
63
+ - **로컬 룰이 뒤집을 수 없는 것** (충돌 시 코어 우선): 멀티세션 불변식(머지 락 · 메인 워크트리 테스트 금지 · git 명령은 `/task-close`만) · destructive 명령 사용자 승인 · 상태 전이 유효성.
62
64
 
63
65
  ### Step 2 — status 전환 (planned → developing)
64
66
 
@@ -118,7 +120,7 @@ Dev Plan에 Phase 0(제품 관통 문서 변경)이 있으면:
118
120
 
119
121
  stash FRICTION_LOG #14+19 / #25 반영 — 유닛 테스트는 본 단계에서 단일 시점 실행 (task-close / hook 영역 중복 제거).
120
122
 
121
- `CLAUDE.md` `## 테스트 명령` 섹션의 명령을 *전부* 실행. 모두 PASS여야 `developed`로 전이 가능.
123
+ `CLAUDE.md` `## 테스트 명령` 섹션의 명령을 *전부* 실행. 모두 PASS여야 `developed`로 전이 가능. **본 게이트는 *구현 완료 시점 1회*다** — 아래 "수정 루프 국면"에 해당하는 동안에는 매 사이클 전부 실행하지 않는다. 또한 수정 루프 국면을 거친 뒤 곧바로 `/task-test`로 이어가는 경우에는 국면 ⑥에 따라 본 게이트의 전체 실행을 생략하고 *격리 세션의 실행*으로 갈음한다 (그때의 `developed` 전이 근거 = 린트·타입체크 PASS + 변경이 닿는 테스트 PASS이며, 전체 스위트는 격리 세션이 책임진다).
122
124
 
123
125
  | 명령 | PASS 기준 |
124
126
  |------|---------|
@@ -132,6 +134,34 @@ stash FRICTION_LOG #14+19 / #25 반영 — 유닛 테스트는 본 단계에서
132
134
  - 신규 *테스트 식별자* (suite / class / function / describe 등) 가 테스트 runner 결과 로그에 *실제 등장*했는지 grep 직접 확인 (1 hit 이상).
133
135
  - 예: `grep -E "Suite \"<NewSuite>\"" test-output.log` → hit 1 이상.
134
136
  - 등장 안 하면 *Suite 미실행* (빌드 시스템 등록 누락 등) → FAIL 처리.
137
+ - **신규 테스트 실효성 확인 (음성대조)** — 본 task에서 테스트를 신설했으면, 가능한 범위에서 *구현을 일시적으로 되돌린 상태에서 그 테스트가 실제로 FAIL하는지* 확인한 뒤 통과를 근거로 삼는다 (Edit로 되돌렸다가 확인 후 즉시 원복 — git 명령 사용 금지). 통과만 보면 테스트가 무력해도 알 수 없다 (stash FRICTION 2026-07-28 — 흐름 테스트 2건이 이 확인으로 실효성을 입증했다). 되돌리기가 위험하거나 부작용이 큰 영역이면 생략하고 그 사유를 Result에 적는다.
138
+
139
+ **화면·입력 점유 스위트 자동 실행 금지 (stash FRICTION 2026-07-25 / recordion FRICTION 2026-08-05 반영)**:
140
+
141
+ 실제 앱을 띄워 UI를 자동 조작하는 스위트(XCUITest·headed 브라우저 E2E 등)는 사용자의 화면·마우스·키보드를 점유해 **작업을 중단시킨다.** `## 테스트 명령`이 단일 진실 소스라는 원칙은 유지하되, 실행 방식은 아래를 따른다.
142
+
143
+ | 상황 | 실행 방식 |
144
+ |------|----------|
145
+ | `TEST-GUIDE.md` *테스트 실행 환경* 절에 **격리 실행 경로**(창 숨김·헤드리스 플래그 / 컨테이너·가상 디스플레이 / VM / CI 러너) 선언됨 | 그 경로로 **자동 실행 OK** |
146
+ | 격리 경로 없음 + 비점유 부분만 한정 실행 가능 (번들·프로젝트·태그 지정 등) | **비점유 부분만 자동 실행**, 점유 스위트는 제외 |
147
+ | 격리 경로 없음 + 한정 수단도 없음 | **자동 실행 금지** — 사용자에게 *무엇을 왜 돌려야 하고 얼마나 걸리며 그동안 화면·입력을 못 쓴다*를 예고하고 승인을 받은 뒤에만 실행 |
148
+
149
+ - 헤드리스 모드가 존재하면 항상 그쪽을 쓴다.
150
+ - 본 task가 UI를 건드리지 않는데(순수 로직·CLI 등) 테스트 명령이 UI 스위트를 포함하면, 그 스위트는 실행 대상이 아니다.
151
+ - 격리 실행 경로를 새로 확인했으면 `TEST-GUIDE.md` *테스트 실행 환경* 절에 기록해 다음 task가 재사용하게 한다.
152
+
153
+ **수정 루프 국면 — 재실행 범위 비례 (recordion FRICTION 2026-08-08 반영)**:
154
+
155
+ 적용 범위 = ① `developed` 이후 사용자 검수 피드백으로 고치는 동안, ② `/task-test` FAIL 후 사용자 *"고쳐"*로 재진입해 고치는 동안. 이 두 국면은 *구현 완료 게이트*와 별개이며, 매 사이클 전체를 돌리지 않는다 (한 태스크에서 전체 E2E를 6회 이상 재실행해 사용자 시간을 수십 분 태운 사례가 있다).
156
+
157
+ 1. **매 사이클 실행** — 린트·타입체크(수 초) + *변경한 파일이 닿는 테스트* + 그 테스트가 쓰는 공유 헬퍼를 함께 쓰는 테스트. 대개 1~2개·수십 초다.
158
+ 2. **전체 게이트(Step 6 + Step 6.5)는 국면이 끝날 때 1회** — 국면 도중에는 돌리지 않는다. 풀빌드를 포함한 Step 6도 매 사이클 대상이 아니다. (국면이 끝나는 시점에 *실제로 돌릴지*는 아래 ⑥이 정한다 — 바로 `/task-test`로 이어가면 격리 세션의 실행으로 갈음한다.)
159
+ 3. **테스트 러너가 빌드를 포함하지 않으면 빌드를 선행**한다 — 옛 산출물로 돌면 이미 고친 결함이 남아 있는 것처럼 보여 없는 버그를 쫓게 된다 (recordion에서 이 때문에 30분을 소모했다). 어느 명령이 빌드를 포함하는지는 `TEST-GUIDE.md`에 기록해 재사용한다.
160
+ 4. **무관한 테스트가 깨질 때만 범위를 넓힌다** — 예상 밖 실패가 나오면 그때 상위 범위로 확대한다.
161
+ 5. **회귀 누락 위험은 2 + `/task-test` 격리 세션의 이중 게이트가 막는다.** 좁힌 범위 때문에 회귀를 놓친다는 우려로 매 사이클 전체를 도는 것은 비용 대비 효과가 없다.
162
+ 6. **`/task-test`로 바로 이어갈 때는 2의 전체 1회를 생략한다** — 격리 세션이 `## 검증 명령`·`## 테스트 명령`을 다시 전부 실행하므로 같은 스위트를 연달아 두 번 돌리는 셈이 된다. 세션이 `developed`에서 멈추고 사용자에게 제어를 넘길 때만 메인이 전체를 1회 실행한다. (결과: 전체 스위트는 태스크당 원칙 1회.)
163
+
164
+ **테스트 신설은 요구 범위에 비례** — 신설하는 테스트는 *본 task 요구사항을 덮는 데 필요한 최소*로 둔다. 기존 스펙이 이미 덮는 영역을 중복 검증하는 테스트를 새로 만들지 않는다. 스위트가 커질수록 이후 모든 task의 재실행 비용이 늘어난다. 프로젝트별 상한(예: *"E2E는 핵심 흐름만"*)은 `.project/rules/TEST_RULE.local.md`가 있으면 그쪽을 우선한다.
135
165
 
136
166
  **FAIL 시**: Step 6의 FAIL 룰 동일 적용 (추측 fix 반복 방지 / 진단 로그 / 동일 신고 재발 시 root cause).
137
167
 
@@ -156,7 +186,7 @@ stash FRICTION_LOG #14+19 / #25 반영 — 유닛 테스트는 본 단계에서
156
186
 
157
187
  ### Step 8 — status 전환 + 결과 보고
158
188
 
159
- 1. `set_status` 도구로 `developed` 전이 (또는 `npx @angar2/taskery set-status TASK-<NNN> developed`) — self-check + 테스트 PASS 후. (유효전이 코드 검증.)
189
+ 1. `set_status` 도구로 `developed` 전이 (또는 `npx @angar2/taskery set-status TASK-<NNN> developed`) — self-check + 테스트 PASS 후. (유효전이 코드 검증.) 수정 루프 국면을 거쳐 곧바로 `/task-test`로 이어가는 경우에는 Step 6.5 ⑥에 따라 *린트·타입체크 + 변경이 닿는 테스트 PASS*가 전이 근거이며, 전체 스위트는 격리 세션이 실행한다.
160
190
  2. 결과 보고:
161
191
 
162
192
  ```
@@ -175,6 +205,7 @@ stash FRICTION_LOG #14+19 / #25 반영 — 유닛 테스트는 본 단계에서
175
205
  - 두 조건 충족 시 — 그 명령으로 서버를 *백그라운드* 기동(포트 = 기준 포트 + TASK번호) + 접속 URL 보고. 모바일 검수가 필요하다고 판단되면 터널도 기동.
176
206
  - `/task-test`로 계속 진행하면 본 단계 생략 (마지막 단계에서만 기동 — 상세: GIT_RULE "멀티세션 검수 환경").
177
207
  - `## 검수 실행 명령` 미선언(CLI/라이브러리 등) 또는 사용자 *"안 띄워도 돼"* 발화 시 생략.
208
+ - **기동 전 확인 필수** — 새로 띄우기 전에 해당 포트·프로세스가 이미 살아 있는지 확인한다. `Port … is in use` 류 경고는 *이미 떠 있다*는 뜻이므로 무시하고 재기동하지 않는다. 종료할 때는 부모 프로세스만이 아니라 **자식 프로세스(브라우저·렌더러 창 등)까지** 확인해 정리한다 (recordion FRICTION 2026-08-05 — 중복 기동 + 창 잔존으로 두 번 지적, TASK-005 재발).
178
209
 
179
210
  ## 도구 가이드
180
211
 
@@ -13,22 +13,22 @@ description: task 시작 — 워크트리 분기 + 6 섹션 빈 골격 + 헤더
13
13
 
14
14
  ## 호출 시점 + 입력
15
15
 
16
- - 새 task 시작 (*"~~ 추가해줘"* / *"~~ 버그 고쳐"* / *"~~ 리팩터링"*), 백로그 진행(*"BL-003 진행하자"*), 로드맵 진행(*"RM-002 시작"*).
17
- - 인자 = (선택) 주제 / 유형 / 출처(BL-NNN, RM-NNN, DR) / 규모.
16
+ - 새 task 시작 (*"~~ 추가해줘"* / *"~~ 버그 고쳐"* / *"~~ 리팩터링"*), 백로그 진행(*"BL-003 진행하자"*), 로드맵 진행(*"Stage 2 진행하자"* / *"로드맵 다음 거 시작"*).
17
+ - 인자 = (선택) 주제 / 유형 / 출처(BL-NNN, ST-N, DR) / 규모.
18
18
  - **두 분기**: ① **제안** — 직전 맥락 명확 → *"이렇게 task 만들까요?"* + confirm. ② **인터뷰** — 맥락 부족 → 한 번에 한 질문씩.
19
19
  - **자동 추정 진행 X** — confirm 또는 답 받기 전 워크트리/파일 생성 금지.
20
20
 
21
21
  ## Step 1 — 메타 수집
22
22
 
23
23
  직전 대화 정독 → 추정 가능하면 분기 1(제안), 불가하면 분기 2(인터뷰):
24
- - Q1 *"무슨 작업? (한 줄로)"* / Q2 *"유형? (feature / bug / improvement / refactor / docs / chore)"* / Q3 *"출처? (백로그 BL-NNN / 로드맵 RM-NNN / 직접 요구사항 DR)"* / Q4 *"규모? (micro / small / medium / large)"* / (필요 시) Q5 *"plan `<NNN_slug>` 맞나요?"*
24
+ - Q1 *"무슨 작업? (한 줄로)"* / Q2 *"유형? (feature / bug / improvement / refactor / docs / chore)"* / Q3 *"출처? (백로그 BL-NNN / 로드맵 Stage ST-N / 직접 요구사항 DR)"* / Q4 *"규모? (micro / small / medium / large)"* / (필요 시) Q5 *"plan `<NNN_slug>` 맞나요?"*
25
25
 
26
26
  수집 항목(모두 확정 후 진행): 주제(한국어) · 유형 · 출처 · 규모 · plan(활성 plan — `status` 도구나 AGENT-GUIDE에서). 활성 plan 부재 시 *"`/plan-init` 먼저 실행할까요?"* 안내 + 중단.
27
27
 
28
28
  ## Step 2 — 출처별 메타 + 슬러그
29
29
 
30
30
  - **BL**: `backlog_get` 도구(또는 `npx @angar2/taskery backlog-get BL-NNN`) → `{ blId, status, type, title, slug, summary, target, taskNums }`. 항목 없으면 에러 → 보고 + 중단. **개요/대상 영역은 task.md §1 Requirements 초안 시작점**으로 기억(본문은 task-plan에서). 슬러그 = 반환 `slug` 그대로(재현성 — 별도 변환 X).
31
- - **RM**: `$MAIN_WT/.project/plans/<활성 plan>/ROADMAP.md`에서 `RM-NNN` 항목 메타 파싱. 슬러그 = ROADMAP에 박힌 그대로.
31
+ - **ST**: `$MAIN_WT/.project/plans/<활성 plan>/ROADMAP.md`에서 해당 Stage N의 작업 단위 확인 → 이번 task가 맡을 작업 단위를 사용자와 confirm. 슬러그 = 그 작업 단위 제목 → 영어 kebab-case(3 단어 이내) → confirm. (ROADMAP은 Stage 단위·별도 항목 채번 없음 — `ST-N` = Stage 번호 그대로.)
32
32
  - **DR**: 사용자 발화에서 주제 그대로. 슬러그 = 한국어 제목 → 영어 kebab-case(3 단어 이내) → confirm.
33
33
 
34
34
  **이미 `[x]` BL 재진행 케이스** — `backlog_get` 결과 `status==='checked'` & `taskNums` 비어있지 않으면: 사용자 호출 *"BL-NNN은 ${taskNums}로 이미 처리된 적 있어. 새 task로 다시 진행할까?"* → OK면 새 TASK 진행(§4 마킹은 콤마 누적) / X면 중단.
@@ -41,11 +41,11 @@ description: task 시작 — 워크트리 분기 + 6 섹션 빈 골격 + 헤더
41
41
 
42
42
  ## Step 4 — 분기 + 골격 생성 (`task_init`) + BL 마킹
43
43
 
44
- `task_init` 도구(또는 `npx @angar2/taskery fork <타입> <개발자> <출처> <슬러그> --size <규모> --title "<한국어 제목>" [--promote]`) 한 번으로 **채번 → 워크트리·브랜치 생성**(init 락 원자 — 병렬 task-init 번호 충돌·동일 출처 중복·동일 브랜치명을 코드가 차단)에 더해 **빈 골격 task.md 자동 생성**(생성일=오늘 · plan=활성 plan · 유형 자동매핑 `improve`→`improvement` · **부모 브랜치=fork 시점 현재 브랜치 자동 기록**(dev 고정 아님) · status=draft · `.gitignore` 케이스로 위치 자동 판정)까지 수행한다.
44
+ `task_init` 도구(또는 `npx @angar2/taskery fork <타입> <개발자> <출처> <슬러그> --size <규모> --title "<한국어 제목>" [--promote]`) 한 번으로 **채번 → 워크트리·브랜치 생성**(init 락 원자 — 병렬 task-init 번호 충돌·동일 출처 중복(**BL·구형 RM 한정** — `ST-N`은 Stage 하나에서 복수 task가 정상, `DR`은 ID 없음이라 둘 다 중복 검사 제외)·동일 브랜치명을 코드가 차단)에 더해 **빈 골격 task.md 자동 생성**(생성일=오늘 · plan=활성 plan · 유형 자동매핑 `improve`→`improvement` · **부모 브랜치=fork 시점 현재 브랜치 자동 기록**(dev 고정 아님) · status=draft · `.gitignore` 케이스로 위치 자동 판정)까지 수행한다.
45
45
 
46
- - 반환 `{ taskNum, nnn, branch, wtPath, projectId, docPath, registered, promoted }`. 폴더 승격은 `--promote`/`promote:true`.
46
+ - 반환 `{ taskNum, nnn, branch, wtPath, projectId, parent, docPath, registered, promoted }` (`parent` = fork 시점 현재 브랜치 = 이 task의 부모 브랜치. `/task-close`가 이 브랜치로 되병합한다). 폴더 승격은 `--promote`/`promote:true`.
47
47
  - 에러 시(*"… 이미 진행중"* / *"… detached HEAD"* — 부모 브랜치(현재 브랜치) 확인·SSoT를 fork가 강제) stderr 메시지 그대로 보고 + 중단.
48
- - **BL 출처면** 직후 `backlog_mark` 도구(또는 `npx @angar2/taskery backlog-mark BL-NNN TASK-NNN`)로 `[ ]→[x]` + `- TASK:` 마크(콤마 누적, withMetaLock). RM/DR은 skip.
48
+ - **BL 출처면** 직후 `backlog_mark` 도구(또는 `npx @angar2/taskery backlog-mark BL-NNN TASK-NNN`)로 `[ ]→[x]` + `- TASK:` 마크(콤마 누적, withMetaLock). ST/DR은 skip.
49
49
 
50
50
  ## Step 5 — 골격 확인 + 보고
51
51
 
@@ -57,17 +57,17 @@ description: task 시작 — 워크트리 분기 + 6 섹션 빈 골격 + 헤더
57
57
  ✅ TASK-<NNN> 생성 완료
58
58
  - 워크트리: <wtPath> / 브랜치: <branch> / task 문서: <docPath>
59
59
  - 헤더: <생성일> / <plan> / <유형> / <규모> / <부모 브랜치> / draft
60
- - BL 마킹: BL-<NNN> [x] (BL 출처 시. RM/DR 생략)
60
+ - BL 마킹: BL-<NNN> [x] (BL 출처 시. ST/DR 생략)
61
61
  - 다음: /task-plan TASK-<NNN> 으로 기획 채우기
62
62
  ```
63
63
 
64
64
  ## 주의사항
65
65
 
66
66
  - **워크트리 생성 + 골격 작성만** — 본문 채우기 금지. Requirements / Scope / Dev Plan / Test Plan은 *반드시* `/task-plan`.
67
- - **단계 경계** — 허용: ROADMAP §4 확인 / SSoT 조회 / `task_init` 호출 / 빈 골격 확인. 금지: ARCHITECTURE/API-SPEC/FEATURES 등 루트 제품 관통 문서 본문 Read · 도메인 코드 Read·Grep · 기존 task 본문 Read (그건 `/task-plan` 영역).
67
+ - **단계 경계** — 허용: ROADMAP의 해당 Stage 확인(출처가 `ST-N`일 때) / SSoT 조회 / `task_init` 호출 / 빈 골격 확인. 금지: ARCHITECTURE/API-SPEC/FEATURES 등 루트 제품 관통 문서 본문 Read · 도메인 코드 Read·Grep · 기존 task 본문 Read (그건 `/task-plan` 영역).
68
68
  - **자동 추정 진행 X** — 맥락 명확해도 *제안 + OK* 후 생성.
69
69
  - **상태는 `draft` 고정** · **slug 영어 kebab-case** · **헤더 6컬럼 모두 채움**(*"미정"* placeholder X — 코드가 채움. 부모 브랜치도 코드 자동 기록).
70
- - **fork 실패(같은 브랜치명 / 같은 출처 진행중) → 사용자 호출** + 중단(*"다른 세션이 같은 항목 진행 중"*).
70
+ - **fork 실패(같은 브랜치명 / 같은 출처 진행중 — BL·구형 RM 한정) → 사용자 호출** + 중단(*"다른 세션이 같은 항목 진행 중"*). `ST-N`·`DR`은 중복 게이트가 없으므로 같은 Stage로 두 번째 task를 만들 때 코드가 막아주지 않는다 — 중복 진행 여부는 메인이 `status`로 확인한다.
71
71
 
72
72
  ## 상태 전이
73
73
 
@@ -54,6 +54,7 @@ MAIN_WT=$(dirname "$(git rev-parse --path-format=absolute --git-common-dir)")
54
54
  - **`draft`** → 일반 기획 (Step 2~ 전체 진행).
55
55
  - **`testing` + Test Plan 보수 모드** → `/task-test`가 *시험문제 결함*으로 반려해 되돌아온 경우에 한해 허용. 이때는 **Test Plan 섹션만 보수**(Requirements / Scope / Dev Plan 보존), Step 2~4는 건너뛰고 Step 5(Test Plan)만 수행, status는 `testing` 그대로 둔다(전이 X — 보수 후 `/task-test` 재실행).
56
56
  - **그 외(`planned` / `developing` / `developed` / `tested` / `closed`)** → 종료 + 안내. (특히 `tested` / `closed`는 보수 모드로도 진입 거부 — 우회 방지.)
57
+ 4. **프로젝트 로컬 룰 확인** — `$MAIN_WT/.project/rules/TEST_RULE.local.md`가 있으면 Read한다 (없으면 넘어간다). Test Plan의 검증 범위·방식 선택에서 본 스킬 조항과 겹치면 **로컬 룰 우선** (멀티세션 불변식·안전 조항 제외).
57
58
 
58
59
  ### Step 2 — Requirements 인터뷰 + 증폭
59
60
 
@@ -169,11 +170,12 @@ Step 2 Requirements 인터뷰 결과로 본 task가 *UX/UI 구현 (페이지/컴
169
170
 
170
171
  #### 3. 목업 생성 + 사용자 승인
171
172
 
172
- 1. 메인이 HTML 목업 생성 — 정적 HTML/CSS (외부 라이브러리 X, 단일 파일 안 inline style + 필요 시 vanilla JS):
173
+ 1. 목업 형식·위치·네이밍은 `$MAIN_WT/.project/rules/MOCKUP_RULE.md`를 따른다 (`MOCKUP_RULE.local.md`가 있으면 그쪽 우선).
174
+ 2. 메인이 HTML 목업 생성 — 정적 HTML/CSS (외부 라이브러리 X, 단일 파일 안 inline style + 필요 시 vanilla JS):
173
175
  - 시각 영역 (레이아웃 / 색상 / 간격 / 타이포 / 호버 효과)
174
176
  - 인터랙션 (가능한 범위 — 클릭 / 호버 시뮬)
175
- 2. 사용자에게 *"브라우저로 `.project/tasks/<NNN_slug>/mockup/<task-doc-name>-mockup.html` 열어 확인 후 ✓/✗ 응답"*.
176
- 3. 사용자 ✓ → 승인 완료, 4 진행. ✗ → 메인이 수정 후 재승인 요청.
177
+ 3. 사용자에게 *"브라우저로 `.project/tasks/<NNN_slug>/mockup/<task-doc-name>-mockup.html` 열어 확인 후 ✓/✗ 응답"*.
178
+ 4. 사용자 ✓ → 승인 완료, 다음 절(4. 파일 위치 / 네이밍) 진행. ✗ → 메인이 수정 후 재승인 요청.
177
179
 
178
180
  #### 4. 파일 위치 / 네이밍
179
181
 
@@ -186,6 +188,7 @@ Step 2 Requirements 인터뷰 결과로 본 task가 *UX/UI 구현 (페이지/컴
186
188
  승인된 목업 = task-test의 **시각 *자동 대조* 정답지** (+ 미세 취향은 사용자 최종 사인). 사용자가 맘에 들 때까지 승인 안 하므로 목업 = 이미 사용자가 검증한 정답지 — task-test 격리 세션이 *실제 화면 캡처 → 이 목업과 자동 대조*해 객관적 깨짐을 잡는다. Step 5 Test Plan 작성 시:
187
189
  - **시각 *객관* 영역(레이아웃/요소 유무/색/배치) → `[AUTO]`** (캡처-목업 대조) + 목업 경로 참조 명시.
188
190
  - **시각 *미세 취향*(2px·색조·느낌) → `[USER]`** (사용자 최종 사인, 증거 캡처).
191
+ - **`[AUTO]` 승격 조건 — 실행 경로 확인 필수**: 시각 객관 영역의 `[AUTO]` 분류는 `$MAIN_WT/.project/TEST-GUIDE.md` *시각 실행* 절에 **이 프로젝트의 캡처 실행 경로가 적혀 있을 때만** 성립한다. 목업 존재는 *정답지 확보*일 뿐 *실행 경로 확보*가 아니다. 경로가 없거나, 앱 기동이 사용자 실데이터·전역 단축키·화면 입력을 건드리는 구조면 `[USER]` 폴백으로 둔다 (stash FRICTION 2026-07-27 — 경로 없는 `[AUTO]` 분류로 `/task-test` 2회 왕복). **다른 방식과 달리 시각은 경로가 없다고 사용자에게 도구 도입을 요구하며 멈추지 않는다** — 조용히 `[USER]`로 두고 진행한다. 자동화 도입 여부는 사용자가 원할 때 별도로 정한다.
189
192
  (이전의 "승인 목업 = USER 검수 기준 / 시각=전부 [USER]"는 폐기 — 사용자에게 흠 사냥을 떠넘기던 원인.)
190
193
 
191
194
  ---
@@ -230,14 +233,16 @@ Test Plan = *본 task에서 구현한 요구사항이 정상 동작하는지*
230
233
  | 사이드 이펙트 확인 | DB / 파일 / 로그 | 조회 결과(행/파일/카운트) | test 자동 | 시나리오 실행 후 상태 직접 조회 |
231
234
  | 시각 (캡처-목업 대조) | UI 레이아웃/색/배치 | 승인된 목업 | test 자동 (+ 미세는 사람) | 실제 화면 캡처 → 목업과 대조, 어긋남 목록 |
232
235
  | 회귀 시나리오 | 기존 기능 영향 | 이전과 동일 결과 | test 자동 | 직전 동작하던 시나리오 재실행 |
233
- | E2E 자동화 도구 | UI 동작 자동화 (도구 있을 시) | 액션 후 관측 변화 | 도구면 test, 아니면 사람 | Playwright / Cypress / XCTest UI Test 등 — 가능하면 자동화, 안 되면 수동 검수 |
236
+ | E2E 자동화 도구 | UI 동작 자동화 (도구 있을 시) | 액션 후 관측 변화 | 도구면 test, 아니면 사람 | Playwright / Cypress / XCTest UI Test 등 — 가능하면 자동화, 안 되면 수동 검수. **화면·입력 점유 여부를 먼저 확인** (아래) |
237
+
238
+ > **점유 스위트의 `[AUTO]` 조건** — UI를 실제 조작하는 자동화(XCUITest·headed 브라우저 E2E 등)는 실행 중 사용자의 화면·마우스·키보드를 점유한다. 이런 방식을 `[AUTO]`로 두려면 `TEST-GUIDE.md` *테스트 실행 환경* 절에 **격리 실행 경로**(창 숨김·헤드리스 플래그 / 컨테이너·가상 디스플레이 / VM / CI 러너)가 있어야 한다. 없으면 `[USER]`로 두거나 *사용자 승인 후 실행*을 시나리오에 명시한다 — 그냥 `[AUTO]`로 두면 격리 세션이 실행하지 못하고 *실행 보류*로 돌려보내 왕복이 생긴다 (stash FRICTION 2026-07-25 · recordion FRICTION 2026-08-05 — 자동 게이트가 사용자 작업을 중단시킨 마찰).
234
239
 
235
240
  #### UX/UI 영역 분리 + 검증 방식 매트릭스 (UX/UI 구현 task 한정)
236
241
 
237
242
  | 영역 | 분류 | 기준 |
238
243
  |------|------|------|
239
- | 동작 (클릭/호버/드래그/입력 → 결과) | 도구 있으면 `[AUTO]` E2E, 없으면 `[USER]` 체크리스트 | 액션 후 관측되는 변화 |
240
- | 시각 *객관* (레이아웃/요소 유무/색/배치 깨짐) | `[AUTO]` 캡처-목업 대조 | 승인된 목업 (격리 세션이 화면 캡처 → 대조 → 어긋남 목록) |
244
+ | 동작 (클릭/호버/드래그/입력 → 결과) | 도구 있고 *점유하지 않게 돌릴 경로*가 있으면 `[AUTO]` E2E, 없으면 `[USER]` 체크리스트 | 액션 후 관측되는 변화 |
245
+ | 시각 *객관* (레이아웃/요소 유무/색/배치 깨짐) | TEST-GUIDE에 캡처 실행 경로 **있으면** `[AUTO]` 캡처-목업 대조 (원칙), **없으면** `[USER]` 폴백 | 승인된 목업 (격리 세션이 화면 캡처 → 대조 → 어긋남 목록) |
241
246
  | 시각 *미세 취향* (2px·색조·호버 강도·느낌) | `[USER]` 체크리스트 + 목업 참조 | 사용자 최종 사인 (증거 캡처 첨부) |
242
247
 
243
248
  #### 시나리오 형식 — [명령/입력] + [구체적 기대값] 한 쌍 강제
@@ -277,6 +282,8 @@ Test Plan = *본 task에서 구현한 요구사항이 정상 동작하는지*
277
282
  - **grep/Read-only 존재 확인 시나리오 = 보조용, PASS 대체 영구 금지** (stash FRICTION_LOG 2026-06-01 반영) — 구현 코드의 존재 여부를 grep/Read로 재확인하는 시나리오는 *동어반복*. PASS 카운트에서 제외, 실동작 시나리오의 *보조*로만 사용. 단독 검증 가치 X — grep으로 실동작 검증을 *대체*할 수 없다(task-test 룰 10과 동일 강도).
278
283
  - **요구사항당 end-to-end 1개 이상 강제** — Requirements 각 항목이 *사용자 관점에서 실제 작동하는지* 검증하는 end-to-end 실동작 시나리오 최소 1개 보장. 산출물 실행 → 결과 확인 형태.
279
284
  - **무거운 검증 회피 영구 금지** — 부담(빌드 수 분+ / 외부 의존 / 환경 설정) 이유로 실동작 시나리오를 grep 동어반복으로 대체 X. 최소 1회는 실제 산출물로 확인. 부담은 *fix 사이클 1~2회 예상* 같은 사전 예고로 처리 (생략 사유 X).
285
+ - **어서션 정면성** — 각 시나리오의 PASS 기준이 *요구사항 자체를 관측*하는지 확인한다. "벗어날 수 없다"는 요구사항이면 실제로 벗어나 보는 것이 정면이고, 버튼 `disabled` 확인 같은 *우회 지표*는 통로가 열려 있어도 통과하므로 PASS 기준이 될 수 없다 (recordion FRICTION 2026-08-05 — 우회 지표로 스펙 113개가 전부 통과한 채 결함이 남았다).
286
+ - **흐름 계층도 시나리오 대상** — 상태 함수와 *그 함수들을 순서대로 엮는 흐름*(dispatch / 흐름 함수)이 함께 생기는 task는, 함수 단위 검증만이 아니라 **흐름을 타는 시나리오를 최소 1개** 둔다. 화면·이벤트에 묶여 있다는 이유로 흐름 계층을 빼면 *함수 사이 순서*에서 나는 결함이 검증에서 통째로 빠진다 (stash FRICTION 2026-07-28 — 검수 반려 4회 누적). 이벤트 객체가 필요한 자리는 판정을 호출 가능한 메서드로 분리해 시나리오가 닿게 한다.
280
287
 
281
288
  #### 요구사항 ↔ 시나리오 커버리지 점검 (필수 — stash FRICTION_LOG 2026-06-01 반영)
282
289
 
@@ -302,9 +309,9 @@ Test Plan 작성 완료 후 *반드시* 점검 단계 수행. *"누적/리셋
302
309
 
303
310
  방식을 골랐어도, **격리 세션이 *이 프로젝트의* DB/API/E2E에 실제로 어떻게 접근하는지 모르면 그 시나리오는 실행 불가능한 허울뿐인 명세다.** 스택마다 다르다(NestJS+TypeORM이면 그 DataSource/repository, 다른 프로젝트는 또 다름). 그래서 *모르면 묻고, 알면 재사용*:
304
311
 
305
- 1. 필요 방식마다 `$MAIN_WT/.project/TEST-GUIDE.md`를 Read → **이 프로젝트에 실행 경로가 적혀 있나** 확인.
312
+ 1. 필요 방식마다 `$MAIN_WT/.project/TEST-GUIDE.md`를 Read → **이 프로젝트에 실행 경로가 적혀 있나** 확인. **데이터·API·E2E뿐 아니라 *시각(앱 기동 + 화면 캡처)* 방식도 동일하게 확인 대상이다** — 목업이 있다는 사실은 실행 경로 확보가 아니다.
306
313
  2. **있으면** → 그 경로를 시나리오의 *방식* 칸에 반영(재사용). 앱은 기존 실행 명령으로 띄움.
307
- 3. **없으면** → *멈추고 사용자에게 요구*: 예) *"이 요구사항은 DB 검증이 필요한데 이 프로젝트에 실행 경로가 없다. 어떻게 검증할지 알려주거나 도구를 깔아줘."* → 사용자가 경로를 주거나 결정.
314
+ 3. **없으면** → *멈추고 사용자에게 요구*: 예) *"이 요구사항은 DB 검증이 필요한데 이 프로젝트에 실행 경로가 없다. 어떻게 검증할지 알려주거나 도구를 깔아줘."* → 사용자가 경로를 주거나 결정. **단 시각 방식은 예외** — 멈추지 않고 `[USER]` 폴백으로 두고 진행한다 (Step 4.5 §5 참조).
308
315
  4. **확인받은 즉시 `TEST-GUIDE.md`에 기록** → 다음 task/세션부터 안 묻고 재사용. (재사용 = 기억이 아니라 *파일* — 세션/컴팩트로 사라지지 않게.)
309
316
 
310
317
  > TEST-GUIDE는 init이 빈 골격으로 깔아둔다(`.project/TEST-GUIDE.md`). 방식별 섹션(데이터 검증 / API 호출 / UI·E2E / 시각 실행 / 기타)에 *이 프로젝트의 실제 실행 방법*을 채운다.
@@ -53,6 +53,7 @@ MAIN_WT=$(dirname "$(git rev-parse --path-format=absolute --git-common-dir)")
53
53
  - 인자 없음 → `developed` 상태 가장 최근 task. 발견 시 confirm. 없으면 *"검증할 task 없음."* + 종료.
54
54
  3. 상태 = `developed` 검증. `testing`이면 이어가기 (Step 2 skip). 그 외면 종료.
55
55
  4. `## Test Plan` 섹션 + `## Dev Plan`의 Phase별 *완료 기준* 정독. 자기완결성 점검 — 다른 문서 참조나 *"위에서 만든 X"* 같은 표현 있으면 사용자에게 보고 + Test Plan 보강 후 재시도.
56
+ 5. **프로젝트 로컬 룰 확인** — `$MAIN_WT/.project/rules/TEST_RULE.local.md`가 있으면 Read한다 (없으면 넘어간다). 검증 범위·방식에서 본 스킬 조항과 겹치면 **로컬 룰 우선**. 단 멀티세션 불변식·destructive 승인·상태 전이 유효성은 로컬이 뒤집지 못한다.
56
57
 
57
58
  ### Step 2 — status 전환 (developed → testing)
58
59
 
@@ -71,17 +72,19 @@ Task tool로 sub-agent spawn. prompt는 *자기완결적* — task.md 경로 +
71
72
 
72
73
  task 파일 경로: <ABSOLUTE_PATH_TO_TASK_MD>
73
74
  검증 방법 문서: <ABSOLUTE_PATH_TO_TEST_GUIDE_MD>
75
+ 프로젝트 검증 정책(있을 때만): <ABSOLUTE_PATH_TO_TEST_RULE_LOCAL_MD>
74
76
 
75
- task 파일을 직접 Read 해서 ## Test Plan 섹션 + ## Dev Plan의 Phase별 완료 기준을 정독하세요. 그리고 *검증 방법 문서(TEST-GUIDE.md)* 도 직접 Read — 각 테스트 방식을 *이 프로젝트에서 실제로 어떻게 실행하나*(DB 조회 경로 / API 호출법 / E2E 셋업 등)의 단일 소스입니다. 이 둘 + 목업 외 다른 문서 참조 X.
77
+ task 파일을 직접 Read 해서 ## Test Plan 섹션 + ## Dev Plan의 Phase별 완료 기준을 정독하세요. 그리고 *검증 방법 문서(TEST-GUIDE.md)* 도 직접 Read — 각 테스트 방식을 *이 프로젝트에서 실제로 어떻게 실행하나*(DB 조회 경로 / API 호출법 / E2E 셋업 등)의 단일 소스입니다. 위에 경로로 명시된 문서(task 파일 · 검증 방법 문서 · 프로젝트 검증 정책) + 목업 외 다른 문서 참조 X.
76
78
  (단 task.md가 `mockup/<task-doc-name>-mockup.html` 참조하면 해당 목업 파일도 직접 Read — *승인된 목업 = 시각 시나리오의 정답지*입니다. 시각 시나리오 = 실제 화면 캡처 → 이 목업과 자동 대조.)
79
+ 프로젝트 검증 정책 파일이 경로로 주어졌으면 그것도 Read한다 — 이 프로젝트 고유의 검증 범위·방식 규칙이며, 위 수행 룰과 겹치는 항목은 그쪽을 우선한다(멀티세션 불변식·안전 조항 제외).
77
80
 
78
81
  ## 수행 룰
79
82
 
80
83
  1. **본질 — Test Plan 시나리오 기반 실질 동작 검증**. 본 task에서 구현한 *요구사항이 정상 동작하는지* 검증이 목적. 단위 테스트 수치(카운트 +N / 모든 PASS) 자체로 task PASS 단정 절대 X — Test Plan에 명시된 *각 시나리오*가 PASS인지가 본질.
81
84
  2. ## Test Plan 시나리오를 *순서대로* 그대로 수행. 임의 추가 / 스킵 / 변형 금지. 각 시나리오의 `[AUTO]` / `[USER]` 분류 (task-plan에서 정의됨) 그대로 따름.
82
85
  3. `[AUTO]` 시나리오 — *실제 명령 실행*. 실행 없이 pass 처리 절대 금지. 검증 도구 (단위/통합/E2E 테스트 / API 호출 / 스크립트 실행 등) 직접 수행 + 결과 로그 확보.
83
- 4. `[USER]` 시나리오 — *정답지가 주관*인 영역만 (시각 미세 취향 / 색조·여백 2px 수준 / UX 사용감·매끄러움). UNCERTAIN(사람 검수)으로 분류 + *사용자 직접 검수 항목*으로 리턴 (단 증거=캡처 첨부). **시각 *객관* 영역(레이아웃 깨짐 / 요소 빠짐 / 빈 화면 / 색 완전히 틀림 / 배치 딴판)은 `[USER]`가 아니라 `[AUTO]`** — 실제 화면 캡처 → 승인 목업과 직접 대조해 격리 세션이 판정. (시각이라고 무조건 사람에게 넘기지 말 것 — 객관 깨짐은 네가 잡는다.)
84
- 5. CLAUDE.md `## 검증 명령` (빌드/린트/타입체크) + `## 테스트 명령` (단위/통합/E2E 테스트) 둘 다 직접 실행 + 결과 리턴.
86
+ 4. `[USER]` 시나리오 — *정답지가 주관*인 영역만 (시각 미세 취향 / 색조·여백 2px 수준 / UX 사용감·매끄러움). UNCERTAIN(사람 검수)으로 분류 + *사용자 직접 검수 항목*으로 리턴 (단 증거=캡처 첨부). **시각 *객관* 영역(레이아웃 깨짐 / 요소 빠짐 / 빈 화면 / 색 완전히 틀림 / 배치 딴판)은 `[USER]`가 아니라 `[AUTO]`** — 실제 화면 캡처 → 승인 목업과 직접 대조해 격리 세션이 판정. **단 이 승격은 TEST-GUIDE.md *시각 실행* 절에 이 프로젝트의 캡처 실행 경로가 있을 때 한정이다** — 경로가 없어 task.md가 시각 시나리오를 `[USER]`로 분류했다면 그 분류를 존중하고 `[AUTO]`로 뒤집지 않는다. (시각이라고 무조건 사람에게 넘기지 말 것 — 객관 깨짐은 네가 잡는다.)
87
+ 5. CLAUDE.md `## 검증 명령` (빌드/린트/타입체크) + `## 테스트 명령` (단위/통합/E2E 테스트) 둘 다 직접 실행 + 결과 리턴. **단 화면·입력을 점유하는 스위트는 룰 17의 제약을 먼저 따른다** (무단 실행 금지 — 룰 17이 본 룰보다 우선).
85
88
  6. **신규 테스트 식별자 등장 확인 (stash FRICTION_LOG #19 반영)** — 본 task에서 신규 테스트 추가되었으면, 단순 *카운트 +N / 모든 PASS* 자체로 PASS 단정 X. 신규 *테스트 식별자* (suite / class / function / describe 등) 가 테스트 runner 결과 로그에 *실제 등장*했는지 grep 직접 확인 (1 hit 이상). 등장 안 하면 *Suite 미실행* → FAIL.
86
89
  7. 코드와 동작만 신뢰. *"잘 될 거야"* / *"문제 없을 듯"* 같은 가정 일체 금지.
87
90
  8. 코드 파일 직접 수정 절대 금지. 임시 파일(테스트 스크립트 등) 생성 시 종료 후 삭제.
@@ -89,19 +92,26 @@ task 파일을 직접 Read 해서 ## Test Plan 섹션 + ## Dev Plan의 Phase별
89
92
  10. **grep/Read-only 존재 확인 시나리오 = 보조 검증** (stash FRICTION_LOG 2026-06-01 반영) — 시나리오가 *코드/파일에 X가 존재하나*를 grep / Read로 재확인하는 형태(동어반복)면 *보조 검증*으로 분류. 종합 판정의 PASS 카운트에서 제외하고, 같은 영역의 *실동작 시나리오*가 PASS인 경우에만 보조 자료로 첨부. 단독 PASS 가치 X. 실동작 시나리오 없이 grep-only 시나리오만으로 종합 PASS 단정 영구 금지 — 그 경우 *실동작 시나리오 부재*로 UNCERTAIN/FAIL 판정 + 메인 보고.
90
93
  11. **문 앞 검사 — 실행 전 시험문제 자격 확인 (먼저)**. 시나리오를 돌리기 *전에*, 각 `[AUTO]` 시나리오가 **[실행할 명령/입력] + [구체적 기대값(관측 가능한 결과 — 출력값 / 상태코드 / 어서션 / 조회 결과 / 화면 상태)]** 한 쌍을 갖췄는지 확인. *"정상 동작" / "잘 됨" / "확인한다"* 같은 *소원*은 기대값이 아님. 자격 미달 `[AUTO]` 시나리오가 있으면 → **그 시나리오는 검사하지 말고 "시험문제 결함(검증 불가)"으로 분류** + 무엇이 빠졌는지 지목 (아래 룰 13). 자격 미달을 PASS로 반올림 영구 금지.
91
94
  12. **증거 일치 시만 PASS**. 자격을 갖춘 `[AUTO]` 시나리오는 *실제로 실행*하여 — *관측한 결과가 기대값과 일치할 때만* PASS. 코드 정독 "그럴듯함" / 추정 / "될 듯"으로 PASS 영구 금지. 증거(명령 출력 · 응답 로그 · 캡처 · 조회 결과)를 결과 표 근거 칸에 첨부.
92
- 13. **UNCERTAIN 두 종류 — 섞지 말 것**.
95
+ 13. **UNCERTAIN 세 종류 — 섞지 말 것**.
93
96
  - **UNCERTAIN(사람 검수)** — 정답지가 *주관*인 영역(룰 4: 시각 미세 취향 / UX 느낌). 정상. 사용자 직접 검수 항목으로 리턴 (증거 캡처 첨부).
94
97
  - **UNCERTAIN(검증 불가)** — `[AUTO]`로 분류됐는데 *대조할 기대값을 구성하지 못한* 경우 = **시험문제 결함**. PASS로 반올림 금지, 사람 검수로도 넘기지 말 것(주관 아님). **근거 의무**: *무엇을 시도했고 왜 기대값을 구성할 수 없는지* 반드시 기록. (이건 네가 쉴 자리가 아니라 task-plan으로 되돌아가 Test Plan을 보수해야 하는 신호 — 도망쳐도 이득 없음.)
95
- 14. **시각 `[AUTO]` 시나리오 = 캡처-목업 대조**. 앱을 띄워 실제 화면을 캡처 → 승인된 목업(정답지)과 대조. 어긋나면 → *구체적 어긋남 목록*(예: "헤더 색 틀림 / 카드가 가로 아닌 세로로 쌓임 / 저장 버튼 없음")을 뽑아 **FAIL**로 보고. 합격선 = *픽셀 일치가 아니라* "사람이 보면 '이거네' 할 정도로 본질적으로 같다" (객관적 깨짐만 잡고 미세 취향은 룰 4로). 증거 = 스크린샷 + 어긋남 목록.
98
+ - **UNCERTAIN(실행 보류)** — 룰 17에 걸려 *실행하지 않은* 경우 = 시험문제도 코드도 아닌 **환경 제약**. 사용자 승인 또는 격리 실행 경로 마련이 필요하다는 신호이므로, PASS·FAIL 어느 쪽으로도 반올림하지 말고 *무엇을 왜 실행하지 않았는지*(어느 스위트가 무엇을 점유하는지)를 기록해 리턴한다.
99
+ 14. **시각 `[AUTO]` 시나리오 = 캡처-목업 대조**. 앱을 띄워 실제 화면을 캡처 → 승인된 목업(정답지)과 대조. (본 룰은 TEST-GUIDE에 캡처 실행 경로가 있는 경우에만 적용된다. 경로가 없으면 해당 시나리오는 `[USER]`이며 룰 4 단서에 따른다.) 어긋나면 → *구체적 어긋남 목록*(예: "헤더 색 틀림 / 카드가 가로 아닌 세로로 쌓임 / 저장 버튼 없음")을 뽑아 **FAIL**로 보고. 합격선 = *픽셀 일치가 아니라* "사람이 보면 '이거네' 할 정도로 본질적으로 같다" (객관적 깨짐만 잡고 미세 취향은 룰 4로). 증거 = 스크린샷 + 어긋남 목록.
100
+
101
+ 15. **픽스처 현실성 의심 + 재생성 판정**. 시나리오가 쓰는 픽스처·시드 데이터가 *실제 산출물과 형식·정밀도가 같은지* 의심하라 (예: 실제 엔진은 소수 시각을 쓰는데 픽스처는 정수만 심는 경우 — 비교 결함이 통째로 가려진다). 의심되면 **실제 코드(writer·생성기)로 픽스처를 재생성해 시나리오를 재실행하고, 그 결과로 판정**한다. 재생성 경로가 없어 대조를 구성할 수 없으면 그 시나리오는 *시험문제 결함(검증 불가)*으로 분류한다. 손으로 심은 픽스처는 구현자의 맹점을 그대로 복제하므로, 통과했다는 사실만으로 안전하다고 보지 않는다.
102
+ 16. **어서션 정면성**. 각 시나리오의 기대값이 요구사항을 *정면으로* 겨누는지 확인하라. "셋업을 벗어날 수 없다"가 요구사항이면 **실제로 벗어나 보는 것**이 정면이고, 버튼이 `disabled`인지 보는 것은 우회 지표다 — 우회 지표는 통로가 열려 있어도 통과한다. 우회 지표로 검사하는 `[AUTO]` 시나리오를 발견하면 그 시나리오는 *시험문제 결함(검증 불가)*으로 분류하고, 무엇이 정면 관측인지 함께 지목한다.
103
+
104
+ 17. **화면·입력 점유 스위트 자동 실행 금지**. 실제 앱을 띄워 UI를 자동 조작하는 스위트(XCUITest·headed 브라우저 E2E 등)는 사용자의 화면·마우스·키보드를 점유해 작업을 중단시킨다. TEST-GUIDE.md *테스트 실행 환경* 절에 **격리 실행 경로**(창 숨김·헤드리스 플래그 / 컨테이너·가상 디스플레이 / VM / CI 러너)가 선언돼 있으면 그 경로로 실행한다. 선언이 없으면 — 비점유 부분만 한정 실행할 수 있으면 그것만 실행하고, 한정 수단도 없으면 **실행하지 말고** 해당 시나리오를 *실행 보류(사용자 승인 필요)*로 표시해 메인에 리턴한다 (무단 실행 금지). 헤드리스 모드가 있으면 항상 그쪽을 쓴다.
96
105
 
97
106
  ## 결과 형식 (이 포맷 그대로 리턴)
98
107
 
99
108
  ### 종합 판정
100
109
  - **PASS** / **FAIL** / **UNCERTAIN** 중 하나
101
- - **UNCERTAIN** = 아래 둘 중 하나라도 1개 이상이면. *두 경우 모두 PASS 자동 전이 X* (공통). 그 다음 종류에 따라 라우팅이 갈림:
110
+ - **UNCERTAIN** = 아래 셋 중 하나라도 1개 이상이면. *세 경우 모두 PASS 자동 전이 X* (공통). 그 다음 종류에 따라 라우팅이 갈림:
102
111
  - **UNCERTAIN(사람 검수)** — 정답지가 주관인 `[USER]` 항목. → 사용자 직접 검수.
103
112
  - **UNCERTAIN(검증 불가)** — `[AUTO]`인데 기대값 구성 불가 = 시험문제 결함. → task-plan으로 Test Plan 보수 (사람 검수 아님). 어느 시나리오가 왜 검증 불가인지 명시.
104
- - 종합이 UNCERTAIN일 때 *두 종류 중 무엇인지* 반드시 표기 (둘 다면 둘 다).
113
+ - **UNCERTAIN(실행 보류)** — 룰 17에 걸려 실행하지 않음 = 환경 제약. → 메인이 사용자 승인을 받아 실행하거나, 격리 실행 경로를 마련한 뒤 재검증. 어느 스위트가 무엇을 점유하는지 명시.
114
+ - 종합이 UNCERTAIN일 때 *세 종류 중 무엇인지* 반드시 표기 (여럿이면 전부).
105
115
  - 한 줄 요약
106
116
 
107
117
  ### 시나리오별 결과
@@ -132,6 +142,10 @@ task 파일을 직접 Read 해서 ## Test Plan 섹션 + ## Dev Plan의 Phase별
132
142
  - <시나리오 N: 무엇이 [AUTO]인데 기대값을 못 만들었나 + 무엇을 시도했나 + 왜 불가한가>
133
143
  → task-plan으로 Test Plan 보수 필요 (이 시나리오를 [실행 명령 + 구체적 기대값]으로 다시 작성).
134
144
 
145
+ ### 실행 보류 (UNCERTAIN 실행 보류 — 해당 시)
146
+ - <어느 시나리오/명령을 실행하지 않았나 + 무엇을 점유하는가(화면·마우스·키보드·전역 단축키 등) + 비점유 부분만 실행했다면 그 범위>
147
+ → 메인이 사용자 승인을 받아 실행하거나, TEST-GUIDE *테스트 실행 환경* 절에 격리 실행 경로를 마련한 뒤 재검증 필요.
148
+
135
149
  ## 종료 조건
136
150
 
137
151
  위 형식 그대로 리턴 후 종료. 코드 수정 시도 금지 — 결과 리턴만.
@@ -140,6 +154,7 @@ task 파일을 직접 Read 해서 ## Test Plan 섹션 + ## Dev Plan의 Phase별
140
154
  **구현 디테일**:
141
155
  - `<ABSOLUTE_PATH_TO_TASK_MD>` 자리에 `.project/tasks/<NNN_slug>/<NNN>_<slug>.md` (또는 폴더 승격 시 `<...>/task.md`)의 절대 경로 삽입.
142
156
  - `<ABSOLUTE_PATH_TO_TEST_GUIDE_MD>` 자리에 `$MAIN_WT/.project/TEST-GUIDE.md`의 절대 경로 삽입 (위 "멀티세션 메타 위치"의 `MAIN_WT` 사용 — 워크트리에서 호출돼도 메인 워크트리의 단일 소스를 읽도록).
157
+ - `<ABSOLUTE_PATH_TO_TEST_RULE_LOCAL_MD>` 자리에 `$MAIN_WT/.project/rules/TEST_RULE.local.md`의 절대 경로 삽입. **파일이 없으면 그 줄 자체를 prompt에서 제외**한다 (없는 경로를 넘기면 격리 세션이 헤맨다).
143
158
  - Task tool의 `subagent_type`: `general-purpose` (도구 풀 다 필요).
144
159
  - Task tool의 `description`: `Isolated test for TASK-<NNN>`.
145
160
  - **코덱스 spawn (플랫폼 패리티)**: 코덱스에선 `.codex/agents/task-tester.toml`의 `task-tester` 서브에이전트로 spawn하되, *위 펜스 전체를 task prompt로 그대로 전달*한다 — 펜스가 양 플랫폼 공통 단일 룰 소스다(클로드 = `general-purpose` + 펜스 / 코덱스 = `task-tester` + 펜스, 동일 효과). `task-tester`의 `developer_instructions`는 같은 룰의 코덱스 기준선 미러 — 본 펜스의 수행 룰을 바꾸면 그 `developer_instructions`도 함께 갱신한다.
@@ -213,11 +228,35 @@ task 파일을 직접 Read 해서 ## Test Plan 섹션 + ## Dev Plan의 Phase별
213
228
  ```
214
229
  3. `/task-plan`은 *Test Plan 섹션만* 보수 (Requirements / Scope / Dev Plan / 코드 보존). 보수 후 `/task-test` 재실행 (코드 미변경이므로 `/task-dev` 재실행 없음). status는 `testing` 유지.
215
230
 
231
+ #### UNCERTAIN(실행 보류) 분기 — 환경 제약 → 승인 또는 격리 경로
232
+
233
+ 격리 세션이 화면·입력 점유(룰 17)를 이유로 실행하지 않은 시나리오를 보고한 경우. 코드 결함도 시험문제 결함도 아니므로 `/task-dev`·`/task-plan` 어느 쪽으로도 보내지 않는다.
234
+
235
+ 1. status는 `testing` 그대로 유지 (코드·시험문제 모두 멀쩡).
236
+ 2. 사용자에게 보고 + 선택지 제시:
237
+ ```
238
+ ⏸ TASK-<NNN> 격리 검증 — 실행 보류 (환경 제약)
239
+ - 보류 시나리오: <목록 + 어느 스위트가 무엇을 점유하는지 + 예상 소요>
240
+ - 비점유 부분 결과: <이미 실행한 범위의 PASS/FAIL>
241
+
242
+ 어떻게 할까?
243
+ 1. "지금 돌려" — 승인받아 실행 (그동안 화면·마우스·키보드를 사용할 수 없음)
244
+ 2. "나중에" — 자리를 비울 때 실행하도록 보류 유지
245
+ 3. "격리 경로 만들자" — 창 숨김·헤드리스 / 컨테이너 / VM / CI 중 하나를 마련 후 재검증 (다음 task부터 승인 없이 자동 실행됨)
246
+ 4. "보류인 채로 마무리해" — 미검증 영역으로 명시하고 tested 전이 (위험: 이 영역은 검증 안 된 채 closed)
247
+ ```
248
+ 3. 사용자 응답별 처리:
249
+ - **"지금 돌려"** → 승인받았으므로 점유 스위트를 실행하고, 그 결과로 PASS / FAIL 분기를 따른다.
250
+ - **"나중에"** → 보류 유지. status는 `testing` 그대로 두고 **여기서 멈춘다** (`/task-close`로 넘어가지 않는다 — close는 `tested`가 아니면 `blocked:'status'`로 차단된다). 사용자가 자리를 비운 뒤 *"이제 돌려"* 하면 위 "지금 돌려"로 재개.
251
+ - **"격리 경로 만들자"** → 경로(창 숨김·헤드리스 / 컨테이너 / VM / CI)를 마련하고 `TEST-GUIDE.md` *테스트 실행 환경* 절에 기록한 뒤 `/task-test` 재실행. 다음 task부터는 승인 없이 자동 실행된다.
252
+ - **"보류인 채로 마무리해"** (알려진 미검증 감수) → FAIL 분기의 *"OK 마무리"*와 동일 처리 — `set_status` 도구로 `tested` 전이(또는 `npx @angar2/taskery set-status TASK-<NNN> tested`) + Result 섹션에 *"미검증 영역: <보류 시나리오> — 사유: 화면·입력 점유, 사용자 판단으로 미실행"* 명시 후 `/task-close` 안내.
253
+ 4. **묵묵히 PASS 처리 금지** — 어느 경로로 가든 보류된 시나리오는 Result에 *미검증 영역*으로 남긴다. 실행하지 않은 것을 통과로 적지 않는다.
254
+
216
255
  #### UNCERTAIN(사람 검수) 분기 (USER 검수 흐름)
217
256
 
218
257
  격리 세션이 `[USER]` 시나리오(정답지가 *주관*인 영역 — 시각 미세 취향 / UX 느낌) 발견 시. **자동 PASS 전이 X — 사용자 직접 검수 PASS 받은 후에만 `tested` 전이**. (시각 *객관* 깨짐은 여기 아니라 FAIL — 격리 세션이 캡처-목업 대조로 이미 잡음.)
219
258
 
220
- > **검수 서버 먼저 기동** (`## 검수 실행 명령` 선언 + 시각/UI 검수 항목 포함 시): 체크리스트를 제시하기 *전에* 서버를 백그라운드 기동(포트 = 기준 포트 + TASK번호)하고 접속 URL을 함께 제공한다 — 사용자가 실제 화면을 보며 ✓/✗ 검수하도록. 격리 세션이 기능상 이미 띄웠으면 재사용. 사용자 *"안 띄워도 돼"* 시 생략. 상세: GIT_RULE "멀티세션 검수 환경".
259
+ > **검수 서버 먼저 기동** (`## 검수 실행 명령` 선언 + 시각/UI 검수 항목 포함 시): 체크리스트를 제시하기 *전에* 서버를 백그라운드 기동(포트 = 기준 포트 + TASK번호)하고 접속 URL을 함께 제공한다 — 사용자가 실제 화면을 보며 ✓/✗ 검수하도록. 격리 세션이 기능상 이미 띄웠으면 재사용. 사용자 *"안 띄워도 돼"* 시 생략. 기동 전 포트·프로세스 확인 + 종료 시 자식까지 정리. 상세: GIT_RULE "멀티세션 검수 환경".
221
260
 
222
261
  1. 메인이 사용자에게 *체크리스트 형식*으로 항목 보고 (stash FRICTION_LOG #14+19 반영):
223
262
 
@@ -231,8 +270,19 @@ USER 검수 — N 항목 (목업 기준: <mockup path — 있을 시>)
231
270
  각 항목 ✓/✗ 응답
232
271
  ```
233
272
 
273
+ **검수 항목 작성 규칙 (recordion FRICTION 2026-08-05 반영)**:
274
+
275
+ - 각 항목은 **① 왜 이 검수가 필요한지 → ② 화면 어디를 어떻게 조작하는지 → ③ 무엇이 보이면 정상이고 무엇이 문제인지** 세 요소를 모두 담는다. 하나라도 빠지면 사용자가 무엇을 봐야 할지 알 수 없다.
276
+ - **평이한 말로 쓴다** — 축약어·세션 내부 용어·task 진행 중에만 통하던 지칭(*"아까 그 화면"*, *"Phase 2 결과물"*) 금지. 이 프로젝트를 처음 보는 사람이 읽어도 따라 할 수 있어야 한다.
277
+ - **필요한 재료는 세션이 찾아서 제공한다** — 토큰·경로·테스트 계정·샘플 파일 등이 필요하면 *"네 것을 넣어라"* 대신 **리포에서 찾아 값 또는 정확한 위치를 함께 준다**. 사용자에게 찾게 만들지 않는다.
278
+
279
+ 예:
280
+ - ❌ `[ ] popover 토글 동작 확인`
281
+ - ✅ `[ ] 메뉴바 아이콘을 클릭하면 목록 창이 열리는지 — 창이 뜨고 항목이 세로로 나열되면 정상. 창이 안 뜨거나 항목이 겹쳐 보이면 문제.`
282
+
234
283
  2. **시각 영역 fix 사이클 사전 예고** — 시각 영역 항목 있으면 *"시각 영역은 한 사이클로 100% 일치 보장 X. fix 사이클 1~2회 예상. ✗ 발견 시 정상."* 안내. 사용자 기대치 사전 정렬, 부정 반응 누적 방지.
235
- 3. 사용자 응답 받음:
284
+ 3. **사용자가 스크린샷·산출물을 주면 체크리스트 전 항목과 1:1 대조** — 사용자가 물은 항목만 답하지 않는다. 받은 증거를 검수 항목 전체와 하나씩 맞춰 보고, 사용자가 언급하지 않은 어긋남도 먼저 지목한다 (stash FRICTION 2026-07-29 — 첫 스크린샷에 이미 찍혀 있던 결함을 세션이 짚지 못해 발견이 한 사이클 늦었다).
285
+ 4. 사용자 응답 받음:
236
286
  - 모두 ✓ → PASS 분기로 (`tested` 전이).
237
287
  - ✗ 1개 이상 → FAIL 분기로 (`developing` 회귀 + ✗ 항목별 fix).
238
288
 
@@ -246,6 +296,7 @@ USER 검수 — N 항목 (목업 기준: <mockup path — 있을 시>)
246
296
  - 격리 세션 검증이 *기능상* 이미 서버를 띄워 둔 경우 — 죽였다 다시 띄우지 말고 그대로 두고 URL만 보고.
247
297
  - **FAIL → 수정 흐름에서는 기동하지 않는다** (수정하러 가는 단계라 검수 무의미). **UNCERTAIN 검수는 Step 5에서 이미 기동**했으므로 여기서 중복 기동 X.
248
298
  - `## 검수 실행 명령` 미선언 또는 사용자 *"안 띄워도 돼"* 발화 시 생략. 상세: GIT_RULE "멀티세션 검수 환경".
299
+ - **기동 전 포트·프로세스 확인 + 종료 시 자식까지 정리** — `Port … is in use` 류 경고는 이미 떠 있다는 뜻이므로 재기동하지 않는다 (GIT_RULE "멀티세션 검수 환경" 참조).
249
300
 
250
301
  ## 도구 가이드
251
302
 
@@ -265,7 +316,7 @@ USER 검수 — N 항목 (목업 기준: <mockup path — 있을 시>)
265
316
  - **사용자검수 시나리오는 자동화 못 잡음** — UI 미세 취향 / UX 느낌 / 외부 결제 등 *정답지가 주관*인 것만 UNCERTAIN(사람 검수)으로 가야 정상. PASS 강제 X. **단 시각 *객관* 깨짐(요소 빠짐 / 빈 화면 / 배치 딴판 / 색 완전히 틀림)은 사람 검수가 아니라 격리 세션이 캡처-목업 대조로 직접 잡아 FAIL** — 사용자에게 흠 사냥을 떠넘기지 않는다.
266
317
  - **FAIL → developing 자동 되돌림 X** — 사용자 *"고쳐"* 답 받기 전 status 갱신 금지. *대화로 OK = 자동 전이* 룰의 핵심.
267
318
  - **테스트 후 plumbing fix 발견 시** — 격리 PASS 후에도 plumbing(빌드 산출물 정리, 설정 수정 등) fix 필요할 수 있음. 분기:
268
- - 검증 명령 *동작 자체에 영향 X* (예: 산출물 위치 정리, 주석 수정) → 메인 자체 재검증 OK (lint/typecheck/build/test 재실행)
319
+ - 검증 명령 *동작 자체에 영향 X* (예: 산출물 위치 정리, 주석 수정) → 메인 자체 재검증 OK. **재실행 범위는 변경에 비례** — 린트·타입체크 + *변경이 닿는 테스트*만. 전체 스위트 재실행은 불필요하다 (`/task-dev` Step 6.5 "수정 루프 국면" 참조)
269
320
  - 검증 명령 *동작 변경* (예: build 명령 자체 변경, 새 검증 명령 추가, 환경 변수 추가) → **격리 세션 재호출 필수** (환경 변경으로 격리 가정 무효화 — 재검증 가치 사라짐)
270
321
 
271
322
  ## 상태 전이
@@ -277,6 +328,8 @@ USER 검수 — N 항목 (목업 기준: <mockup path — 있을 시>)
277
328
  | `developed` | `tested` | FAIL + 사용자 *"OK 마무리"* (알려진 결함 명시) |
278
329
  | `developed` / `testing` | `testing` (유지) | UNCERTAIN(사람 검수) — 사용자 직접 검수 대기 |
279
330
  | `developed` / `testing` | `testing` (유지) | UNCERTAIN(검증 불가, 시험문제 결함) → `/task-plan`으로 Test Plan 보수 → `/task-test` 재실행 (코드·status 보존) |
331
+ | `developed` / `testing` | `testing` (유지) | UNCERTAIN(실행 보류, 환경 제약) → 사용자 승인 실행 / *"나중에"* 보류 / 격리 실행 경로 마련 후 재검증 (코드·시험문제 모두 보존) |
332
+ | `testing` | `tested` | UNCERTAIN(실행 보류) + 사용자 *"보류인 채로 마무리해"* — Result에 미검증 영역 명시 |
280
333
  | `testing` (이어가기) | 동일 분기 | — |
281
334
 
282
- > **세 갈래 요약**: PASS → `tested` / 코드 결함 → `developing`(사용자 *"고쳐"*) / 시험문제 결함 → Test Plan 보수(`task-plan`, 코드·status 보존) → 재검사. UNCERTAIN(사람 검수)은 검수 ✓ 받으면 `tested`, ✗면 `developing`.
335
+ > **네 갈래 요약**: PASS → `tested` / 코드 결함 → `developing`(사용자 *"고쳐"*) / 시험문제 결함 → Test Plan 보수(`task-plan`, 코드·status 보존) → 재검사 / 환경 제약(실행 보류) → 사용자 승인 실행 또는 격리 실행 경로 마련 후 재검증. UNCERTAIN(사람 검수)은 검수 ✓ 받으면 `tested`, ✗면 `developing`.