@angar2/taskery 0.6.1 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/bin/plan.js CHANGED
@@ -1,16 +1,22 @@
1
1
  #!/usr/bin/env node
2
- // plan(기능 그룹) 생성 CLI — 채번+폴더+ROADMAP/PLAN/BACKLOG 골격+AGENT-GUIDE 갱신 원자 실행
2
+ // plan(기능 그룹) 생성·전환 CLI — 채번+폴더+ROADMAP/PLAN/BACKLOG 골격+활성plan 갱신 원자 실행
3
3
  /**
4
4
  * bin/plan.js
5
- * `npx @angar2/taskery plan-init <slug> [--force]` — plan 생성.
5
+ * `npx @angar2/taskery plan-init <slug> [--force]` — plan 생성.
6
+ * `npx @angar2/taskery plan-switch <NNN_slug>` — 활성 plan 전환.
6
7
  *
7
- * 채번(NNN) + legacy 게이트 + 폴더 mkdir(plans/<NNN_slug> + tasks/<NNN_slug>/{spec-diffs,screenshots,mockup})
8
- * + ROADMAP/PLAN/BACKLOG 골격 Write + AGENT-GUIDE 활성 plan 갱신을 코드가 수행.
9
- * 성공 시 JSON 한 줄 — { plan, nnn, planDir, tasksDir }. 이후 ROADMAP Stage 내용·PLAN 링크·
10
- * FEATURES/UX-UI delta는 LLM(plan-init 스킬)이 골격 placeholder를 채운다.
8
+ * dispatcher가 op('init' | 'switch')를 선행 인자로 주입한다 (backlog.js와 동일 패턴).
11
9
  *
12
- * legacy 폴더(NNN_ 아닌 plan 폴더) 잔존 + --force 없음 → exit 2 + { gated:true, legacyDirs }
13
- * (스킬이 사용자 confirm 후 --force로 재호출). 그 외 실패 시 stderr + exit 1.
10
+ * init: 채번(NNN) + legacy 게이트 + 폴더 mkdir(plans/<NNN_slug> + tasks/<NNN_slug>/{spec-diffs,screenshots,mockup})
11
+ * + ROADMAP/PLAN/BACKLOG 골격 Write + manifest.activePlan 갱신을 코드가 수행.
12
+ * 성공 시 JSON 한 줄 — { plan, nnn, planDir, tasksDir }. 이후 ROADMAP Stage 내용·PLAN 링크·
13
+ * FEATURES/UX-UI delta는 LLM(plan-init 스킬)이 골격 placeholder를 채운다.
14
+ * legacy 폴더(NNN_ 아닌 plan 폴더) 잔존 + --force 없음 → exit 2 + { gated:true, legacyDirs }
15
+ * (스킬이 사용자 confirm 후 --force로 재호출).
16
+ *
17
+ * switch: 대상 plan 폴더 실재 검증 후 manifest.activePlan 갱신. 성공 시 JSON — { plan, planDir }.
18
+ *
19
+ * 그 외 실패 시 stderr + exit 1.
14
20
  */
15
21
 
16
22
  const path = require('path');
@@ -20,10 +26,25 @@ const {
20
26
  readManifest,
21
27
  getMainWorktreePath,
22
28
  initPlan,
29
+ setActivePlan,
23
30
  } = require('./lib');
24
31
 
25
- function main() {
26
- const argv = process.argv.slice(2);
32
+ function resolveMainWt() {
33
+ let mainWt;
34
+ try {
35
+ mainWt = getMainWorktreePath();
36
+ } catch (e) {
37
+ console.error(`taskery plan 실패: ${e.message}`);
38
+ process.exit(1);
39
+ }
40
+ if (!readManifest(path.join(mainWt, MANIFEST_NAME))) {
41
+ console.error(`${MANIFEST_NAME} 없음 — 'npx @angar2/taskery init' 먼저 실행 필요.`);
42
+ process.exit(1);
43
+ }
44
+ return mainWt;
45
+ }
46
+
47
+ function runInit(argv) {
27
48
  const positional = [];
28
49
  let force = false;
29
50
  for (const a of argv) {
@@ -42,17 +63,7 @@ function main() {
42
63
  process.exit(1);
43
64
  }
44
65
 
45
- let mainWt;
46
- try {
47
- mainWt = getMainWorktreePath();
48
- } catch (e) {
49
- console.error(`taskery plan-init 실패: ${e.message}`);
50
- process.exit(1);
51
- }
52
- if (!readManifest(path.join(mainWt, MANIFEST_NAME))) {
53
- console.error(`${MANIFEST_NAME} 없음 — 'npx @angar2/taskery init' 먼저 실행 필요.`);
54
- process.exit(1);
55
- }
66
+ const mainWt = resolveMainWt();
56
67
 
57
68
  let result;
58
69
  try {
@@ -82,4 +93,33 @@ function main() {
82
93
  );
83
94
  }
84
95
 
96
+ function runSwitch(argv) {
97
+ const plan = argv.filter((a) => !a.startsWith('--'))[0];
98
+ if (!plan) {
99
+ console.error('사용법: npx @angar2/taskery plan-switch <NNN_slug>');
100
+ console.error('예: npx @angar2/taskery plan-switch 002_compare-products');
101
+ process.exit(1);
102
+ }
103
+
104
+ const mainWt = resolveMainWt();
105
+
106
+ let result;
107
+ try {
108
+ result = setActivePlan(mainWt, plan);
109
+ } catch (e) {
110
+ console.error(`taskery plan-switch 실패: ${e.message}`);
111
+ process.exit(1);
112
+ }
113
+
114
+ process.stdout.write(JSON.stringify({ plan: result.plan, planDir: result.planDir }) + '\n');
115
+ }
116
+
117
+ function main() {
118
+ const [op, ...rest] = process.argv.slice(2);
119
+ if (op === 'init') return runInit(rest);
120
+ if (op === 'switch') return runSwitch(rest);
121
+ console.error(`taskery plan: 알 수 없는 op '${op}' (init | switch)`);
122
+ process.exit(1);
123
+ }
124
+
85
125
  main();
package/bin/set-status.js CHANGED
@@ -22,6 +22,9 @@ const {
22
22
  resolveTaskDocPath,
23
23
  setStatus,
24
24
  TRANSITIONS,
25
+ getActiveTasks,
26
+ getWorktreePath,
27
+ getProjectId,
25
28
  } = require('./lib');
26
29
 
27
30
  async function main() {
@@ -60,13 +63,25 @@ async function main() {
60
63
  process.exit(1);
61
64
  }
62
65
 
63
- // 현재 작업트리 toplevel = 미등록 케이스의 문서 base (호출 위치 = 해당 워크트리).
66
+ // 현재 작업트리 toplevel = 미등록 케이스의 문서 base.
64
67
  const wtPath = gitCapture(process.cwd(), ['rev-parse', '--show-toplevel'], { allowFail: true }) || mainWt;
65
68
 
66
- const resolved = resolveTaskDocPath(mainWt, wtPath, { taskNum });
69
+ let resolved = resolveTaskDocPath(mainWt, wtPath, { taskNum });
70
+
71
+ // 미등록 케이스에서 메인 워크트리(또는 다른 task의 워크트리)에서 호출하면 문서가 그 트리에 없다.
72
+ // 호출 위치를 강요하는 대신 SSoT에서 해당 task의 워크트리를 역조회해 한 번 더 찾는다
73
+ // — 스킬이 "호출 위치 자유"를 전제하므로 CLI가 그 약속을 지켜야 한다.
74
+ if (!resolved) {
75
+ const hit = getActiveTasks(mainWt).find((t) => t.taskNum === taskNum);
76
+ if (hit) {
77
+ const owner = getWorktreePath(getProjectId(mainWt), hit);
78
+ if (owner !== wtPath) resolved = resolveTaskDocPath(mainWt, owner, { taskNum });
79
+ }
80
+ }
81
+
67
82
  if (!resolved) {
68
83
  console.error(
69
- `TASK-${String(taskNum).padStart(3, '0')} 문서를 찾지 못함 (.project/tasks/*/ 아래 단일 파일/폴더 승격 모두 탐색).`,
84
+ `TASK-${String(taskNum).padStart(3, '0')} 문서를 찾지 못함 (.project/tasks/*/ 아래 단일 파일/폴더 승격 모두 탐색 + 진행중 task 워크트리 역조회).`,
70
85
  );
71
86
  process.exit(1);
72
87
  }
package/bin/status.js CHANGED
@@ -23,6 +23,7 @@ const {
23
23
  gitCapture,
24
24
  getMainWorktreePath,
25
25
  getActiveTasks,
26
+ getActiveVersion,
26
27
  getWorktreesRoot,
27
28
  getMergeLockPath,
28
29
  DEFAULT_STALE_DAYS,
@@ -65,6 +66,17 @@ function main() {
65
66
  `현재 브랜치(= 새 태스크 부모): ${currentBranch}${detached ? ' ⚠ (detached HEAD — 브랜치 체크아웃 필요)' : ''}`,
66
67
  );
67
68
  console.log(`projectId: ${projectId}`);
69
+ // 활성 plan(0.7.0+ manifest SSoT) — 스킬·진입 문서가 "활성 plan은 status로 확인"이라
70
+ // 지시하므로 여기서 반드시 출력한다. 미설정이면 다음 행동까지 안내한다.
71
+ let activePlan = null;
72
+ try {
73
+ activePlan = getActiveVersion(mainWt);
74
+ } catch (e) {
75
+ activePlan = null;
76
+ }
77
+ console.log(
78
+ `활성 plan: ${activePlan ?? '미설정 — /plan-init으로 생성하거나 plan-switch <NNN_slug>로 지정'}`,
79
+ );
68
80
  console.log(`stale_days: ${staleDays} / lock_timeout_ms: ${lockTimeoutMs}\n`);
69
81
 
70
82
  // 2. 머지 락 상태
package/bin/taskery.js CHANGED
@@ -12,7 +12,8 @@
12
12
  * fork — task 분기 (채번+워크트리+브랜치 생성 init 락 원자 실행, 0.3.2+) + 골격 task.md 자동 생성(--size/--title)
13
13
  * backlog-add/get/mark — 활성 plan BACKLOG.md 조작 (채번/조회/확인 마킹, 코드화)
14
14
  * set-status — task 헤더 상태 전이 (7×7 유효전이 검증, 코드화)
15
- * plan-init — plan 생성 (채번+폴더+ROADMAP/PLAN/BACKLOG 골격+AGENT-GUIDE 갱신, 코드화)
15
+ * plan-init — plan 생성 (채번+폴더+ROADMAP/PLAN/BACKLOG 골격+manifest.activePlan 갱신, 코드화)
16
+ * plan-switch — 활성 plan 전환 (manifest.activePlan 갱신, 0.7.0+)
16
17
  * close — close 결정적 준비 (Phase커밋+status=closed+문서커밋+추적마커; 비가역 머지/정리는 스킬, 코드화)
17
18
  * mcp — stdio MCP 서버 기동 (lib core를 도구 8종으로 노출; Claude/Codex 등록)
18
19
  * help — 사용법 출력
@@ -47,6 +48,7 @@ function help() {
47
48
  npx @angar2/taskery backlog-mark <BL-NNN> <TASK-NNN> 백로그 확인 마킹
48
49
  npx @angar2/taskery set-status <TASK-NNN> <state> task 상태 전이 (유효전이 검증)
49
50
  npx @angar2/taskery plan-init <slug> [--force] plan 생성 (채번+폴더+골격+활성plan 갱신)
51
+ npx @angar2/taskery plan-switch <NNN_slug> 활성 plan 전환
50
52
  npx @angar2/taskery close <TASK-NNN> close 결정적 준비 (Phase커밋+status=closed+추적마커)
51
53
  npx @angar2/taskery mcp MCP stdio 서버 기동 (Claude .mcp.json / Codex codex mcp add 등록)
52
54
  npx @angar2/taskery help 도움말
@@ -105,7 +107,10 @@ switch (sub) {
105
107
  runScript('set-status.js');
106
108
  break;
107
109
  case 'plan-init':
108
- runScript('plan.js');
110
+ runScript('plan.js', ['init']);
111
+ break;
112
+ case 'plan-switch':
113
+ runScript('plan.js', ['switch']);
109
114
  break;
110
115
  case 'close':
111
116
  runScript('close.js');
package/bin/update.js CHANGED
@@ -38,6 +38,15 @@ const {
38
38
  resolveInstallPlan,
39
39
  } = require('./lib');
40
40
 
41
+ const {
42
+ ENTRY_DOCS_MARKER,
43
+ mergeGitignore,
44
+ ingestActivePlan,
45
+ migrateEntryDocs,
46
+ planGitignore,
47
+ planTestGuide,
48
+ } = require('./migrate');
49
+
41
50
  async function confirm(msg) {
42
51
  const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
43
52
  return new Promise((resolve) => {
@@ -82,8 +91,22 @@ async function main() {
82
91
  const newFiles = {};
83
92
  const summary = { unchanged: 0, new: 0, autoUpdated: 0, customizedReplaced: 0, skipped: 0, removed: 0 };
84
93
 
94
+ // 2.5. 0.7.0 진입 문서 재편 — **본 루프보다 먼저.**
95
+ // CLAUDE.md/AGENTS.md를 본 루프에 맡기면 '사용자 customize 검출' 프롬프트가 뜨는데,
96
+ // 덮으면 리포 값이 .bak에만 남고 안 덮으면 구판 전문이 새 판과 함께 남아 규칙이 충돌한다.
97
+ // 이행이 최종 상태까지 기록하고, 처리한 경로는 아래 루프에서 제외한다.
98
+ const entryMig = migrateEntryDocs(cwd, templateDir, platforms, oldManifest);
99
+ const entryHandled = new Set(entryMig.handled);
100
+ for (const line of entryMig.notes) console.log(` ${line}`);
101
+ Object.assign(newFiles, entryMig.entries);
102
+
85
103
  // 3. 새 template 파일 분기 (rel = 설치 경로 installRel)
86
104
  for (const [rel, { hash: newHash, templateRel }] of Object.entries(newTemplateFiles)) {
105
+ if (entryHandled.has(rel)) {
106
+ // 2.5에서 이행 완료(또는 보류) — 본 루프가 다시 손대면 이행 결과를 덮는다.
107
+ if (entryMig.held && oldFiles[rel]) newFiles[rel] = oldFiles[rel];
108
+ continue;
109
+ }
87
110
  if (isLocalOverride(rel)) {
88
111
  // template에 .local.md 들어가면 안 되지만 방어적 스킵
89
112
  console.log(` skip (.local override 영역): ${rel}`);
@@ -95,6 +118,17 @@ async function main() {
95
118
  const dst = path.join(cwd, rel);
96
119
  const oldEntry = oldFiles[rel];
97
120
 
121
+ // .gitignore는 *절대 덮어쓰지 않는다* — 사용자 무시 규칙이 통째로 날아간다.
122
+ // 없으면 복사, 있으면 빠진 줄만 덧붙인다(migrate.mergeGitignore).
123
+ if (rel === '.gitignore') {
124
+ const r = mergeGitignore(src, dst);
125
+ newFiles[rel] = { hash: newHash, core: true, managed: true };
126
+ if (!r.merged) { summary.new++; console.log(` new: ${rel}`); }
127
+ else if (r.added.length) { summary.autoUpdated++; console.log(` merge: ${rel} — ${r.added.length}줄 추가 (기존 줄 보존)`); }
128
+ else { summary.unchanged++; }
129
+ continue;
130
+ }
131
+
98
132
  // a. 신규 — manifest에 없음
99
133
  if (!oldEntry) {
100
134
  copyFile(src, dst);
@@ -173,6 +207,12 @@ async function main() {
173
207
  : (migration.lock_timeout_ms = true, DEFAULT_LOCK_TIMEOUT_MS);
174
208
  if (!Array.isArray(oldManifest.platforms)) migration.platforms = true;
175
209
 
210
+ // 활성 plan(0.7.0+) — 기존 값이 있으면 **반드시 그대로 보존**한다. newManifest는 아래에서
211
+ // 명시 필드 목록으로 새로 조립되므로, 여기서 넘기지 않으면 update 한 번에 활성 plan이
212
+ // 소리 없이 사라지고 구형 AGENT-GUIDE 폴백의 낡은 값으로 되돌아간다.
213
+ const { activePlan, ingested: activePlanIngested } = ingestActivePlan(cwd, oldManifest);
214
+ if (activePlanIngested) migration.activePlan = true;
215
+
176
216
  const newManifest = {
177
217
  version: getPackageVersion(),
178
218
  installed_at: oldManifest.installed_at,
@@ -181,10 +221,42 @@ async function main() {
181
221
  platforms,
182
222
  stale_days,
183
223
  lock_timeout_ms,
224
+ activePlan: activePlan ?? null,
225
+ // 진입 문서 이행 완료 마커 — 있으면 다음 update가 이행을 재실행하지 않는다.
226
+ entryDocs: entryMig.done ? ENTRY_DOCS_MARKER : (oldManifest.entryDocs ?? null),
184
227
  files: newFiles,
185
228
  };
186
229
  writeManifest(newManifest, manifestPath);
187
230
 
231
+ // 5.5. 부수 이행 — .gitignore 누락 패턴 / TEST-GUIDE 이관. 둘 다 사용자 승인 후에만.
232
+ const gi = planGitignore(cwd, platforms);
233
+ if (gi.missing.length > 0) {
234
+ console.log(`\n taskery 자산 중 .gitignore에 없는 항목: ${gi.missing.join(', ')}`);
235
+ console.log(` (등록하지 않으면 이 파일들이 커밋에 섞여 들어갑니다.)`);
236
+ if (await confirm(` .gitignore에 추가할까?`)) {
237
+ gi.apply();
238
+ console.log(` .gitignore 갱신 — ${gi.missing.length}개 패턴 추가`);
239
+ }
240
+ }
241
+
242
+ const tg = planTestGuide(cwd);
243
+ if (tg.conflict) {
244
+ // 자동 병합은 사용자 파일을 훼손할 수 있어 하지 않는다. 다만 침묵하면 TEST-GUIDE에 쌓인
245
+ // 내용이 아무도 안 읽는 채로 남으므로 반드시 알린다.
246
+ console.log(
247
+ `\n ⚠ .project/TEST-GUIDE.md(폐지)와 .project/rules/TEST_RULE.local.md가 모두 있습니다.`,
248
+ );
249
+ console.log(` TEST-GUIDE 쪽 내용은 이제 아무 스킬도 읽지 않습니다 — 필요한 항목을 직접 옮겨주세요.`);
250
+ }
251
+ if (tg.available) {
252
+ console.log(`\n .project/TEST-GUIDE.md는 0.7.0에서 폐지되고 역할이 TEST_RULE.local.md로 옮겨졌습니다.`);
253
+ console.log(` 지금 옮기지 않으면 /task-plan·/task-dev가 기존 내용을 읽지 않습니다. (원본은 보존)`);
254
+ if (await confirm(` 내용을 .project/rules/TEST_RULE.local.md로 이관할까?`)) {
255
+ tg.apply();
256
+ console.log(` 이관 완료 — .project/rules/TEST_RULE.local.md (원본 TEST-GUIDE.md 보존)`);
257
+ }
258
+ }
259
+
188
260
  const migratedFields = Object.entries(migration)
189
261
  .filter(([, added]) => added)
190
262
  .map(([k]) => k);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@angar2/taskery",
3
- "version": "0.6.1",
3
+ "version": "0.7.0",
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,7 +24,7 @@ description: agent teams로 다건 태스크를 독립 워크트리에서 자동
24
24
  | 통신 | 결과만 리더에 반환 | 팀원↔리더 자동 통지 + 팀원끼리 직접 통신 (리더가 다 봄) |
25
25
  | 스킬 호출 | 제한적 | 프로젝트 `.claude/skills/` 그대로 로드 → taskery 5스킬 직접 호출 |
26
26
 
27
- 팀원은 각자 새 세션으로 시작하며 프로젝트 `CLAUDE.md`를 자동 로드하므로 taskery 룰을 그대로 따른다.
27
+ 팀원은 각자 새 세션으로 시작하며 진입 문서(`CLAUDE.md` → `@AGENTS.md`)를 자동 로드하므로 taskery 룰을 그대로 따른다.
28
28
 
29
29
  ## 호출 시점
30
30
 
@@ -62,7 +62,7 @@ description: agent teams로 다건 태스크를 독립 워크트리에서 자동
62
62
  MAIN_WT=$(dirname "$(git rev-parse --path-format=absolute --git-common-dir)")
63
63
  ```
64
64
  - 대상이 명시 작업 목록이면 그 발화를 그대로.
65
- - 활성 plan은 `$MAIN_WT/.project/AGENT-GUIDE.md`에서 검출.
65
+ - 활성 plan은 `status` 도구(또는 `npx @angar2/taskery status`)로 검출.
66
66
 
67
67
  ### Step 2 — 태스크 묶기 판단
68
68
 
@@ -112,8 +112,8 @@ description: agent teams로 다건 태스크를 독립 워크트리에서 자동
112
112
 
113
113
  ## 도구 가이드
114
114
 
115
- - **Bash**: 메인 워크트리 검출 / 활성화 가드 확인 / BACKLOG·AGENT-GUIDE 정독용 경로 산출
116
- - **Read**: `$MAIN_WT/.project/tasks/<활성 plan>/BACKLOG.md` / `$MAIN_WT/.project/AGENT-GUIDE.md`
115
+ - **Bash**: 메인 워크트리 검출 / 활성화 가드 확인 / `status`로 활성 plan 검출 / BACKLOG 정독용 경로 산출
116
+ - **Read**: `$MAIN_WT/.project/tasks/<활성 plan>/BACKLOG.md` / `$MAIN_WT/.project/rules/TASKERY_RULE.md`
117
117
  - **agent teams (자연어)**: 팀원 생성 + 태스크별 지시 + 모니터링. (전용 슬래시 명령은 없음 — 리더가 자연어로 팀을 가동)
118
118
  - **AskUserQuestion**: 태스크 묶음 / 팀원 수 / 중단점 모드 확인
119
119
 
@@ -1,7 +1,7 @@
1
1
  # taskery 격리 검증 서브에이전트 — /task-test가 spawn하는 독립 tester
2
2
  #
3
3
  # 클로드의 "Task tool 격리 세션"에 대응하는 코덱스 서브에이전트 정의.
4
- # 메인 세션의 plan/dev 컨텍스트(확증편향) 없이 task.md + TEST-GUIDE.md를 보고 독립 검증한다.
4
+ # 메인 세션의 plan/dev 컨텍스트(확증편향) 없이 task.md + TEST_RULE.local.md를 보고 독립 검증한다.
5
5
  #
6
6
  # ⚠ 미러 — 아래 developer_instructions는 `.agents/skills/task-test/SKILL.md`의 격리 펜스(수행 룰)와
7
7
  # *같은 효과*를 내야 한다. /task-test는 그 펜스 전체를 spawn prompt(task)로 전달하므로 펜스가 단일 룰
@@ -12,7 +12,7 @@
12
12
  # 환경의 사용 가능 모델에 맞게 조정한다(기본 gpt-5.5).
13
13
 
14
14
  name = "task-tester"
15
- description = "격리 검증 에이전트. 메인의 확증편향 없이 task.md + TEST-GUIDE를 보고 Test Plan을 증거 기반으로 독립 수행한다. /task-test가 spawn한다."
15
+ description = "격리 검증 에이전트. 메인의 확증편향 없이 task.md + TEST_RULE.local.md를 보고 Test Plan을 증거 기반으로 독립 수행한다. /task-test가 spawn한다."
16
16
  model = "gpt-5.5"
17
17
  sandbox_mode = "read-only"
18
18
  developer_instructions = """
@@ -20,16 +20,16 @@ developer_instructions = """
20
20
 
21
21
  검사 룰의 단일 소스 = spawn 시 넘겨받은 검사 지시(prompt)다. 그것을 그대로 따르며, 아래는 그 핵심을 코덱스에서 동일하게 보장하는 기준선이다(넘겨받은 지시와 충돌 시 넘겨받은 지시 우선):
22
22
 
23
- - 넘겨받은 task.md와 TEST-GUIDE.md를 직접 읽는다(task.md가 목업을 참조하면 목업도). "task.md 하나만"이 아니다 — TEST-GUIDE는 각 검증 방식을 *이 프로젝트에서 실제로 어떻게 실행하나*(DB 조회 경로 / API 호출법 / E2E 셋업 / 시각 실행)의 단일 소스다.
23
+ - 넘겨받은 task.md와 TEST_RULE.local.md를 직접 읽는다(task.md가 목업을 참조하면 목업도). "task.md 하나만"이 아니다 — TEST_RULE.local.md는 각 검증 방식을 *이 프로젝트에서 실제로 어떻게 실행하나*(DB 조회 경로 / API 호출법 / E2E 셋업 / 시각 실행)의 단일 소스다.
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). 시각이라고 무조건 사람에게 넘기지 마라. 단 시각 객관의 [AUTO] 승격은 TEST-GUIDE의 *시각 실행* 절에 캡처 실행 경로가 있을 때 한정이다 — 경로가 없어 task.md가 [USER]로 분류했으면 그 분류를 존중한다.
27
+ - [AUTO]/[USER] 분류 — [USER]는 정답지가 *주관*인 것만(시각 미세 취향 / 색조·2px / UX 느낌) → UNCERTAIN(사람 검수) + 증거 캡처로 리턴. 시각 *객관* 깨짐(요소 빠짐/빈 화면/배치 딴판/색 완전히 틀림)은 [USER]가 아니라 [AUTO] — 실제 화면 캡처 → 승인 목업과 대조해 네가 직접 판정한다(어긋나면 *구체적 어긋남 목록* + FAIL). 시각이라고 무조건 사람에게 넘기지 마라. 단 시각 객관의 [AUTO] 승격은 TEST_RULE.local.md의 *시각 실행* 절에 캡처 실행 경로가 있을 때 한정이다 — 경로가 없어 task.md가 [USER]로 분류했으면 그 분류를 존중한다.
28
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
30
  - 픽스처 현실성 — 픽스처·시드가 실제 산출물과 형식·정밀도가 같은지 의심하고, 의심되면 실제 코드(writer·생성기)로 재생성해 재실행한 결과로 판정한다. 재생성 경로가 없어 대조를 구성할 수 없으면 시험문제 결함(검증 불가)으로 분류한다.
31
31
  - 어서션 정면성 — 기대값이 요구사항을 정면으로 겨누는지 확인한다("벗어날 수 없다"면 실제로 벗어나 볼 것. 버튼 disabled 확인은 우회 지표다). 우회 지표로 검사하는 [AUTO] 시나리오는 시험문제 결함으로 분류하고 무엇이 정면 관측인지 지목한다.
32
- - 화면·입력 점유 금지 — 실제 앱을 띄워 UI를 자동 조작하는 스위트는 사용자의 화면·입력을 점유한다. TEST-GUIDE의 *테스트 실행 환경* 절에 격리 실행 경로(창 숨김·헤드리스 / 컨테이너 / VM / CI)가 있으면 그 경로로 실행하고, 없으면 비점유 부분만 실행하거나 실행 보류(사용자 승인 필요)로 표시해 리턴한다. 헤드리스 모드가 있으면 항상 그쪽을 쓴다.
32
+ - 화면·입력 점유 금지 — 실제 앱을 띄워 UI를 자동 조작하는 스위트는 사용자의 화면·입력을 점유한다. TEST_RULE.local.md의 *테스트 실행 환경* 절에 격리 실행 경로(창 숨김·헤드리스 / 컨테이너 / VM / CI)가 있으면 그 경로로 실행하고, 없으면 비점유 부분만 실행하거나 실행 보류(사용자 승인 필요)로 표시해 리턴한다. 헤드리스 모드가 있으면 항상 그쪽을 쓴다.
33
33
  - 코드와 동작만 신뢰한다. 코드 파일 수정 금지. 임시 파일(테스트 스크립트 등)은 종료 후 삭제한다.
34
34
  - 검증을 마치면 넘겨받은 검사 지시의 결과 형식(종합 판정 PASS/FAIL/UNCERTAIN / 시나리오별 결과 / 검증·테스트 명령 결과 / UNCERTAIN 3종 / 시험문제 결함 / 실행 보류)을 그대로 리턴한다.
35
35
  """
@@ -2,7 +2,7 @@
2
2
 
3
3
  > 본 파일은 taskery 시스템 권장 프로젝트별 CHANGELOG 정책이다.
4
4
  > 우선순위: **이 파일** (`.project/rules/CHANGELOG_RULE.md`) → 메인 판단.
5
- > 프로젝트별 변형이 필요하면 `.project/rules/CHANGELOG_RULE.local.md` 생성 (npx update 미터치).
5
+ > 프로젝트별 변형이 필요하면 `.project/rules/CHANGELOG_RULE.local.md` 생성 (npx update 미터치, 구속력은 코어와 동등 — `TASKERY_RULE` §7).
6
6
 
7
7
  ---
8
8
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  > 이 파일은 taskery 시스템 권장 프로젝트별 git 정책이다.
4
4
  > 우선순위: **이 파일** (`.project/rules/GIT_RULE.md`) → 글로벌 (`~/.claude/rules/GIT_RULE.md`) → 메인 판단.
5
- > 프로젝트별 변형이 필요하면 `.project/rules/GIT_RULE.local.md` 생성 (npx update 미터치).
5
+ > 프로젝트별 변형이 필요하면 `.project/rules/GIT_RULE.local.md` 생성 (npx update 미터치, 구속력은 코어와 동등 — `TASKERY_RULE` §7).
6
6
 
7
7
  ---
8
8
 
@@ -233,7 +233,7 @@ docs: [TASK-{NNN}] CHANGELOG 업데이트
233
233
 
234
234
  ### 메인 워크트리 절대 경로 검출
235
235
 
236
- 워크트리 안에서 메인 워크트리 메타(`.project/`, `CLAUDE.md`)에 접근할 때 사용:
236
+ 워크트리 안에서 메인 워크트리 메타(`.project/`, `AGENTS.md`)에 접근할 때 사용:
237
237
 
238
238
  ```sh
239
239
  MAIN_WT=$(dirname "$(git rev-parse --path-format=absolute --git-common-dir)")
@@ -262,12 +262,12 @@ git -C "$MAIN_WT" branch --list \
262
262
 
263
263
  여러 세션이 병렬로 검수용 dev 서버·터널을 띄울 때 포트·프로세스가 충돌하지 않도록 task별로 자원을 독립 소유한다. CLI·라이브러리 등 검수 서버가 없는 프로젝트는 무관.
264
264
 
265
- - **포트 격리 (결정적)**: 메인 세션(부모 브랜치) = 기준 포트, task 워크트리 = 기준 포트 + TASK번호. 기준 포트는 프로젝트가 `CLAUDE.md`(코덱스는 `AGENTS.md`) `## 검수 실행 명령`에 선언한다 (멀티 프로젝트 동시 운영 시 프로젝트마다 다른 기준 포트). TASK번호는 1부터라 offset 0(기준 포트)이 메인 몫으로 항상 비어 충돌하지 않는다.
265
+ - **포트 격리 (결정적)**: 메인 세션(부모 브랜치) = 기준 포트, task 워크트리 = 기준 포트 + TASK번호. 기준 포트는 프로젝트가 `AGENTS.md` `## 검수 실행 명령`에 선언한다 (멀티 프로젝트 동시 운영 시 프로젝트마다 다른 기준 포트). TASK번호는 1부터라 offset 0(기준 포트)이 메인 몫으로 항상 비어 충돌하지 않는다.
266
266
  - **터널 (필수 아님)**: 모바일 검수 등 필요한 task만, 자기 포트를 가리키는 별도 프로세스로 판다 → task별 독립 URL.
267
267
  - **자기 것만 정리**: `/task-close` 시 본 task가 띄운 서버·터널(자기 포트 것)만 종료한다. **`pkill -f` 같은 광역 종료 금지** — 다른 세션의 서버까지 죽이는 원인.
268
268
  - **검수 기동 (자동)**: 사용자가 결과를 *눈으로 검수*하는 시점에 위 포트로 서버를 백그라운드 기동하고 접속 URL을 보고한다 (사용자가 매번 실행을 지시하지 않게). 정확한 발동 지점은 각 스킬 참조 — `/task-dev`는 세션이 거기서 끝날 때(Step 9), `/task-test`는 UNCERTAIN 검수 직전(Step 5)·PASS 후 종료 시(Step 7). FAIL→수정 흐름에서는 기동하지 않는다. 단계가 *기능상* 서버를 요구하면(E2E 등) 검수 시점과 무관하게 띄운다.
269
269
  - **중복 기동 방지**: 새로 띄우기 전에 해당 포트·프로세스가 이미 살아 있는지 확인한다. `Port … is in use` 류 경고는 *이미 떠 있다*는 신호이므로 무시하고 재기동하지 않는다. 종료 시에는 부모 프로세스만이 아니라 자식 프로세스(브라우저·렌더러 창 등)까지 확인해 정리한다.
270
- - **화면·입력 점유 회피**: 실제 앱을 띄워 UI를 자동 조작하는 테스트 스위트는 사용자의 화면·마우스·키보드를 점유해 작업을 중단시킨다. 자동 게이트·격리 세션은 `.project/TEST-GUIDE.md` *테스트 실행 환경* 절에 선언된 격리 실행 경로(창 숨김·헤드리스 / 컨테이너 / VM / CI)로만 자동 실행하고, 선언이 없으면 비점유 부분만 실행하거나 사용자 승인을 받는다.
270
+ - **화면·입력 점유 회피**: 실제 앱을 띄워 UI를 자동 조작하는 테스트 스위트는 사용자의 화면·마우스·키보드를 점유해 작업을 중단시킨다. 자동 게이트·격리 세션은 `.project/rules/TEST_RULE.local.md` *테스트 실행 환경* 절에 선언된 격리 실행 경로(창 숨김·헤드리스 / 컨테이너 / VM / CI)로만 자동 실행하고, 선언이 없으면 비점유 부분만 실행하거나 사용자 승인을 받는다.
271
271
  - **효과 (정직)**: 포트 충돌은 결정적으로 제거된다. 다른 세션의 종료·턴 경계 프로세스 소실은 위 규율·백그라운드 실행으로 완화되나 문서 규율이라 강제는 아니다(best-effort).
272
272
 
273
273
  ---
@@ -320,3 +320,4 @@ git -C "$MAIN_WT" branch --list \
320
320
  | 2026-06-25 | §멀티세션 워크트리 정책에 "멀티세션 검수 환경" 추가 — task별 dev 서버 포트 격리(메인=기준 포트, task=기준+TASK번호) + 터널 독립 + close 시 자기 것만 종료(광역 종료 금지) + 사용자 검수 시점 서버 자동 기동(task-test UNCERTAIN/PASS·task-dev 종료, FAIL 제외). FRICTION 검토 F7 반영 |
321
321
  | 2026-08-08 | 출처 표기 개편 — `RM-NNN`(로드맵 항목 채번, 미구현) → `ST-N`(ROADMAP Stage 번호 그대로, 채번 불요·동일 출처 중복 게이트 미적용). 구형 RM-NNN은 호환 수용만. + §커밋 순서에 "Phase↔파일 매핑 규칙" 신설 — 다중 Phase 파일=이른 Phase 편입+표기 / 빌드 산출물=마지막 Phase 자동 편입 / 일반 미매핑만 blocked / 중간 커밋 독립 빌드 비보장 명문화. stash·recordion FRICTION_LOG 반영 |
322
322
  | 2026-08-08 | §멀티세션 검수 환경에 "중복 기동 방지"·"화면·입력 점유 회피" 2항 추가 — 검수 앱 중복 기동(부모만 종료해 창 잔존) 및 UI 자동조작 스위트의 화면·입력 강탈 마찰 반영. stash·recordion FRICTION_LOG |
323
+ | 2026-08-08 | 0.7.0 문서 체계 재편 정합 — 진입 문서 단일화(`AGENTS.md` 본문 + `CLAUDE.md`는 임포트 한 줄)에 따라 검증 명령 참조 경로 변경, 폐지된 `TEST-GUIDE.md` 참조를 `TEST_RULE.local.md`로 교체. 머리말에 `.local.md` 구속력 안내 추가(코어와 동등 — `TASKERY_RULE` §7) |
@@ -2,7 +2,7 @@
2
2
 
3
3
  > 본 파일은 taskery 시스템 권장 UX/UI 구현 task의 HTML 목업 정책이다.
4
4
  > 우선순위: **이 파일** (`.project/rules/MOCKUP_RULE.md`) → 메인 판단.
5
- > 프로젝트별 변형이 필요하면 `.project/rules/MOCKUP_RULE.local.md` 생성 (npx update 미터치).
5
+ > 프로젝트별 변형이 필요하면 `.project/rules/MOCKUP_RULE.local.md` 생성 (npx update 미터치, 구속력은 코어와 동등 — `TASKERY_RULE` §7).
6
6
 
7
7
  ---
8
8