@angar2/taskery 0.4.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -39,7 +39,7 @@ draft → planned → developing → developed → testing → tested → closed
39
39
  | `developed` | 구현 완료 (자가 검증 통과) |
40
40
  | `testing` | 독립 검증 진행 중 |
41
41
  | `tested` | 독립 검증 완료 |
42
- | `closed` | git 커밋 + dev 병합 완료 |
42
+ | `closed` | git 커밋 + 부모 브랜치 병합 완료 |
43
43
 
44
44
  > 상태별 전이 조건과 task 문서 양식은 [TASK-DOC.md](https://github.com/angar2/taskery/blob/main/plan/TASK-DOC.md)에서 확인 가능
45
45
 
@@ -153,8 +153,8 @@ my-app/
153
153
  같은 프로젝트에서 여러 메인 세션을 동시에 운영해 독립 task를 병렬로 진행한다.
154
154
 
155
155
  - 새 task를 시작하면(`/task-init`) 작업 폴더(`~/.taskery/worktrees/<projectId>/TASK-NNN_<출처>_<슬러그>/`)와 작업 브랜치를 함께 분기한다.
156
- - 작업 폴더에서 새 메인 세션을 열어 진행하거나, 메인 세션 1개로 모든 작업을 지휘하거나, 메인이 서브 세션을 호출해 병렬로 진행한다 — 운영 방식은 자유. 메인 워크트리(원본 폴더)는 `dev` 전용 상태로 유지된다.
157
- - task 완료(`/task-close`) 시 머지 락으로 직렬화한 후 dev에 `--no-ff` 병합한다. 다른 세션이 먼저 머지해 충돌이 발생하면 본 세션이 단순/의미적/판단 불가 3단계로 해결을 시도한다.
156
+ - 작업 폴더에서 새 메인 세션을 열어 진행하거나, 메인 세션 1개로 모든 작업을 지휘하거나, 메인이 서브 세션을 호출해 병렬로 진행한다 — 운영 방식은 자유. 메인 워크트리(원본 폴더)는 task를 시작한 브랜치(*부모 브랜치*, 기본은 `dev`)를 그대로 유지한다.
157
+ - task 완료(`/task-close`) 시 머지 락으로 직렬화한 후 **그 부모 브랜치**에 `--no-ff` 병합한다(개인은 `dev`, 회사/로드맵은 서 있던 브랜치). 다른 세션이 먼저 머지해 충돌이 발생하면 본 세션이 단순/의미적/판단 불가 3단계로 해결을 시도한다.
158
158
  - 머지 직후 작업 폴더와 작업 브랜치는 자동 정리된다(보존 키워드 사용 시 양쪽 유지). 안전망으로 복구 명령이 함께 출력된다.
159
159
 
160
160
  요건: git ≥ 2.31. 보조 명령 `npx @angar2/taskery status` / `prune`로 진행 현황과 stale 정리를 확인한다.
@@ -189,7 +189,7 @@ my-app/
189
189
  | `/task-plan` | task | task의 요구사항·범위·개발 계획·테스트 계획 작성 |
190
190
  | `/task-dev` | task | 계획에 따른 단계별 구현 + 자가 검증 |
191
191
  | `/task-test` | task | 별도 격리 세션에서 독립 검증 (메인 가정 차단) |
192
- | `/task-close` | task | 최종 검증 후 git 커밋 + dev 브랜치 `--no-ff` 병합 |
192
+ | `/task-close` | task | 최종 검증 후 git 커밋 + 부모 브랜치 `--no-ff` 병합 |
193
193
  | `/add-backlog` | meta | 사용자 발화로 plan(기능 그룹)별 `tasks/<NNN_slug>/BACKLOG.md`에 task 후보 1건씩 추가 (0.1.2+) |
194
194
  | `/log-friction` | meta | 사용자 불편을 `.project/FRICTION_LOG.md`에 한 행 기록 |
195
195
  | `/run-team` | meta | 여러 작업을 묶을 수 있는 단위로 task로 나눠 자동 병렬 처리 — 리더 세션이 팀원(독립 세션)에게 task를 1건씩 분배하고 흐름을 관리 (Claude 전용 · 실험 기능 전제 · 0.3.x+) |
@@ -240,7 +240,7 @@ npx @angar2/taskery init
240
240
  /task-plan TASK-001 # draft → planned : 요구사항·범위·개발 계획·테스트 계획 작성
241
241
  /task-dev TASK-001 # planned → developed : 단계별 구현과 자체 검증
242
242
  /task-test TASK-001 # developed → tested : 별도 격리 세션으로 독립 검증
243
- /task-close TASK-001 # tested → closed : 최종 검증 후 git 커밋과 dev 병합
243
+ /task-close TASK-001 # tested → closed : 최종 검증 후 git 커밋과 부모 브랜치 병합
244
244
 
245
245
  # 불편 발생 시 등록
246
246
  /log-friction # 사용자 불편 한 행 기록
package/bin/backlog.js ADDED
@@ -0,0 +1,131 @@
1
+ #!/usr/bin/env node
2
+ // 활성 plan별 BACKLOG.md 조작 CLI — add(추가) / get(조회) / mark(확인 마킹) 묶음
3
+ /**
4
+ * bin/backlog.js
5
+ * `npx @angar2/taskery backlog-add|backlog-get|backlog-mark ...` — 백로그 조작.
6
+ *
7
+ * dispatcher(taskery.js)가 서브op(add|get|mark)를 첫 인자로 주입한다.
8
+ * add --type <t> --title <제목> --slug <slug> --summary <개요> --target <대상영역>
9
+ * → withMetaLock + BL-NNN 채번 + append. stdout: { blNum, blId }
10
+ * get <BL-NNN> → 항목 메타 JSON ({ blId, status, type, title, slug, summary, target, taskNums }). 없으면 exit 1
11
+ * mark <BL-NNN> <TASK-NNN> → withMetaLock + [ ]→[x] + TASK 마크. stdout: { blId, taskNum }
12
+ *
13
+ * 채번·서식·락은 전부 코드(lib.js)가 보장 — LLM 손 파싱/Edit 제거.
14
+ * 실패(인자 미달 / 활성 plan 부재 / 항목 없음 등) 시 stderr + exit 1.
15
+ */
16
+
17
+ const path = require('path');
18
+
19
+ const {
20
+ MANIFEST_NAME,
21
+ readManifest,
22
+ getMainWorktreePath,
23
+ appendBacklogItem,
24
+ parseBacklogItem,
25
+ markBacklogChecked,
26
+ } = require('./lib');
27
+
28
+ function parseFlags(argv) {
29
+ // --key value | --key=value 둘 다 지원. 정의된 키만 수집.
30
+ const KEYS = ['type', 'title', 'slug', 'summary', 'target'];
31
+ const out = {};
32
+ for (let i = 0; i < argv.length; i++) {
33
+ const a = argv[i];
34
+ if (!a.startsWith('--')) continue;
35
+ const eq = a.indexOf('=');
36
+ let key;
37
+ let val;
38
+ if (eq !== -1) {
39
+ key = a.slice(2, eq);
40
+ val = a.slice(eq + 1);
41
+ } else {
42
+ key = a.slice(2);
43
+ val = argv[++i];
44
+ }
45
+ if (KEYS.includes(key)) out[key] = val;
46
+ }
47
+ return out;
48
+ }
49
+
50
+ function parseTaskNum(token) {
51
+ // 'TASK-007' | 'TASK-7' | '7' → 7
52
+ const m = String(token).match(/(\d+)/);
53
+ return m ? parseInt(m[1], 10) : NaN;
54
+ }
55
+
56
+ function resolveMainWt() {
57
+ let mainWt;
58
+ try {
59
+ mainWt = getMainWorktreePath();
60
+ } catch (e) {
61
+ console.error(`taskery backlog 실패: ${e.message}`);
62
+ process.exit(1);
63
+ }
64
+ if (!readManifest(path.join(mainWt, MANIFEST_NAME))) {
65
+ console.error(`${MANIFEST_NAME} 없음 — 'npx @angar2/taskery init' 먼저 실행 필요.`);
66
+ process.exit(1);
67
+ }
68
+ return mainWt;
69
+ }
70
+
71
+ async function doAdd(rest) {
72
+ const meta = parseFlags(rest);
73
+ const missing = ['type', 'title', 'slug', 'summary', 'target'].filter((k) => !meta[k]);
74
+ if (missing.length) {
75
+ console.error(
76
+ `사용법: npx @angar2/taskery backlog-add --type <t> --title <제목> --slug <slug> --summary <개요> --target <대상영역>`,
77
+ );
78
+ console.error(`누락: ${missing.map((k) => '--' + k).join(', ')}`);
79
+ process.exit(1);
80
+ }
81
+ const mainWt = resolveMainWt();
82
+ const blNum = await appendBacklogItem(mainWt, meta);
83
+ const blId = `BL-${String(blNum).padStart(3, '0')}`;
84
+ process.stdout.write(JSON.stringify({ blNum, blId }) + '\n');
85
+ }
86
+
87
+ function doGet(rest) {
88
+ const blId = rest[0];
89
+ if (!blId || !/^BL-\d+$/.test(blId)) {
90
+ console.error(`사용법: npx @angar2/taskery backlog-get <BL-NNN>`);
91
+ process.exit(1);
92
+ }
93
+ const mainWt = resolveMainWt();
94
+ const item = parseBacklogItem(mainWt, blId);
95
+ if (!item) {
96
+ console.error(`${blId} 항목 없음.`);
97
+ process.exit(1);
98
+ }
99
+ process.stdout.write(JSON.stringify(item) + '\n');
100
+ }
101
+
102
+ async function doMark(rest) {
103
+ const blId = rest[0];
104
+ const taskNum = parseTaskNum(rest[1]);
105
+ if (!blId || !/^BL-\d+$/.test(blId) || !Number.isInteger(taskNum)) {
106
+ console.error(`사용법: npx @angar2/taskery backlog-mark <BL-NNN> <TASK-NNN>`);
107
+ process.exit(1);
108
+ }
109
+ const mainWt = resolveMainWt();
110
+ await markBacklogChecked(mainWt, blId, taskNum);
111
+ process.stdout.write(JSON.stringify({ blId, taskNum }) + '\n');
112
+ }
113
+
114
+ async function main() {
115
+ const op = process.argv[2];
116
+ const rest = process.argv.slice(3);
117
+ try {
118
+ if (op === 'add') await doAdd(rest);
119
+ else if (op === 'get') doGet(rest);
120
+ else if (op === 'mark') await doMark(rest);
121
+ else {
122
+ console.error(`backlog: 알 수 없는 op '${op}' (add | get | mark)`);
123
+ process.exit(1);
124
+ }
125
+ } catch (e) {
126
+ console.error(`taskery backlog-${op} 실패: ${e.message}`);
127
+ process.exit(1);
128
+ }
129
+ }
130
+
131
+ main();
package/bin/close.js ADDED
@@ -0,0 +1,74 @@
1
+ #!/usr/bin/env node
2
+ // task close 결정적 준비 CLI — Phase 커밋·status=closed·flows/문서 커밋·추적마커 (비가역 머지/정리는 스킬)
3
+ /**
4
+ * bin/close.js
5
+ * `npx @angar2/taskery close <TASK-NNN>` — close 결정적 준비.
6
+ *
7
+ * 게이트(status=tested) → Phase 커밋(D2 자동 매핑) → status=closed → flows/문서 커밋 → 추적마커까지
8
+ * 코드가 수행하고 멈춘다. 이후 비가역(사전 rebase+충돌해결 / 머지 락 / 부모 --no-ff 머지 /
9
+ * worktree remove / branch -d)은 스킬(task-close)이 git-guard hook + 복구출력 보호 하에 수행.
10
+ *
11
+ * stdout: { prepped, branch, parent, wtPath, registered, commits, aheadOfParent }. parent = 머지 대상. exit 0.
12
+ * 콜백(스킬이 LLM 판단 후 처리):
13
+ * - exit 3 + { blocked:'status', current } — status가 tested 아님 (게이트 차단)
14
+ * - exit 2 + { blocked:'mapping', files, phases, reason } — Phase↔파일 매핑 모호 (수동 커밋 필요)
15
+ * 그 외 실패 시 stderr + exit 1.
16
+ */
17
+
18
+ const path = require('path');
19
+
20
+ const {
21
+ MANIFEST_NAME,
22
+ readManifest,
23
+ getMainWorktreePath,
24
+ closeTask,
25
+ } = require('./lib');
26
+
27
+ async function main() {
28
+ const arg = process.argv[2];
29
+ const m = String(arg || '').match(/(\d+)/);
30
+ if (!m) {
31
+ console.error('사용법: npx @angar2/taskery close <TASK-NNN>');
32
+ process.exit(1);
33
+ }
34
+ const taskNum = parseInt(m[1], 10);
35
+
36
+ let mainWt;
37
+ try {
38
+ mainWt = getMainWorktreePath();
39
+ } catch (e) {
40
+ console.error(`taskery close 실패: ${e.message}`);
41
+ process.exit(1);
42
+ }
43
+ if (!readManifest(path.join(mainWt, MANIFEST_NAME))) {
44
+ console.error(`${MANIFEST_NAME} 없음 — 'npx @angar2/taskery init' 먼저 실행 필요.`);
45
+ process.exit(1);
46
+ }
47
+
48
+ let result;
49
+ try {
50
+ result = await closeTask(mainWt, { taskNum });
51
+ } catch (e) {
52
+ console.error(`taskery close 실패: ${e.message}`);
53
+ process.exit(1);
54
+ }
55
+
56
+ if (result.blocked === 'status') {
57
+ console.error(
58
+ `close 게이트 차단 — status=${result.current} (tested 아님). /task-test 통과 후 재호출.`,
59
+ );
60
+ process.stdout.write(JSON.stringify(result) + '\n');
61
+ process.exit(3);
62
+ }
63
+ if (result.blocked === 'mapping') {
64
+ console.error(
65
+ `Phase↔파일 매핑 모호 (${result.reason}) — Dev Plan '파일' 필드 확인 후 수동 Phase 커밋 필요.`,
66
+ );
67
+ process.stdout.write(JSON.stringify(result) + '\n');
68
+ process.exit(2);
69
+ }
70
+
71
+ process.stdout.write(JSON.stringify(result) + '\n');
72
+ }
73
+
74
+ main();
package/bin/fork.js CHANGED
@@ -1,11 +1,13 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
3
  * bin/fork.js
4
- * `npx @angar2/taskery fork <type> <dev> <src> <slug>` — task 분기.
4
+ * `npx @angar2/taskery fork <type> <dev> <src> <slug> [--size <s> --title "<t>" --promote]` — task 분기.
5
5
  *
6
6
  * [채번 → 워크트리·브랜치 생성]을 init 락으로 원자 실행 (병렬 task-init 번호 충돌 차단).
7
- * 성공 시 결과 JSON 한 줄을 stdout으로 출력 ({ taskNum, nnn, branch, wtPath, projectId }).
7
+ * --size·--title 주면 fork 직후 §1.3 빈 골격 task.md도 코드가 자동 생성(C1, 생성일·status=draft 자동).
8
+ * 성공 시 결과 JSON 한 줄을 stdout으로 출력 ({ taskNum, nnn, branch, wtPath, projectId, [docPath, registered, promoted | scaffoldError] }).
8
9
  * 실패(정책 위배 / 동일 항목 진행중 / 동일 브랜치명 등) 시 stderr + exit 1.
10
+ * 골격 생성 실패는 워크트리 생성을 무효화하지 않음 — exit 0 + scaffoldError로 보고(스킬이 폴백 Write).
9
11
  *
10
12
  * 호출 주체: `/task-init` Step 6 (사용자 직접 호출도 가능하나 통상 스킬 경유).
11
13
  */
@@ -17,16 +19,38 @@ const {
17
19
  readManifest,
18
20
  getMainWorktreePath,
19
21
  forkTask,
22
+ scaffoldTaskDoc,
20
23
  } = require('./lib');
21
24
 
22
25
  const VALID_TYPES = ['feature', 'bug', 'improve', 'refactor', 'docs', 'chore'];
26
+ const VALID_SIZES = ['micro', 'small', 'medium', 'large'];
23
27
 
24
28
  async function main() {
25
- const [type, dev, src, slug] = process.argv.slice(2);
29
+ // 위치 인자 4개 + 플래그(--size <s> / --title "<t>" / --promote).
30
+ const argv = process.argv.slice(2);
31
+ const positional = [];
32
+ const opts = {};
33
+ for (let i = 0; i < argv.length; i++) {
34
+ const a = argv[i];
35
+ if (a === '--promote') {
36
+ opts.promote = true;
37
+ } else if (a.startsWith('--')) {
38
+ opts[a.slice(2)] = argv[++i];
39
+ } else {
40
+ positional.push(a);
41
+ }
42
+ }
43
+ const [type, dev, src, slug] = positional;
26
44
 
27
45
  if (!type || !dev || !src || !slug) {
28
- console.error('사용법: npx @angar2/taskery fork <type> <dev> <src> <slug>');
29
- console.error('예: npx @angar2/taskery fork feature claude BL-003 login-feature');
46
+ console.error(
47
+ '사용법: npx @angar2/taskery fork <type> <dev> <src> <slug> [--size <s> --title "<제목>" --promote]',
48
+ );
49
+ console.error('예: npx @angar2/taskery fork feature claude BL-003 login --size medium --title "로그인 기능"');
50
+ process.exit(1);
51
+ }
52
+ if (opts.size && !VALID_SIZES.includes(opts.size)) {
53
+ console.error(`--size는 ${VALID_SIZES.join(' | ')} 중 하나 (받음: ${opts.size})`);
30
54
  process.exit(1);
31
55
  }
32
56
  if (!VALID_TYPES.includes(type)) {
@@ -54,13 +78,33 @@ async function main() {
54
78
  process.exit(1);
55
79
  }
56
80
 
81
+ let result;
57
82
  try {
58
- const result = await forkTask(mainWt, { type, dev, src, slug });
59
- process.stdout.write(JSON.stringify(result) + '\n');
83
+ result = await forkTask(mainWt, { type, dev, src, slug });
60
84
  } catch (e) {
61
85
  console.error(`taskery fork 실패: ${e.message}`);
62
86
  process.exit(1);
63
87
  }
88
+
89
+ // 골격 자동 생성 (--size·--title 제공 시). 워크트리는 이미 생성됐으므로 실패해도 exit 1 X.
90
+ let out = result;
91
+ if (opts.size && opts.title) {
92
+ try {
93
+ const doc = scaffoldTaskDoc(mainWt, result.wtPath, {
94
+ taskNum: result.taskNum,
95
+ slug,
96
+ title: opts.title,
97
+ type,
98
+ size: opts.size,
99
+ parent: result.parent,
100
+ promoted: !!opts.promote,
101
+ });
102
+ out = { ...result, docPath: doc.file, registered: doc.registered, promoted: doc.promoted };
103
+ } catch (e) {
104
+ out = { ...result, scaffoldError: e.message };
105
+ }
106
+ }
107
+ process.stdout.write(JSON.stringify(out) + '\n');
64
108
  }
65
109
 
66
110
  main();
package/bin/init.js CHANGED
@@ -161,6 +161,29 @@ async function main() {
161
161
  console.log(` .gitignore — 사용자 선택으로 패스`);
162
162
  }
163
163
 
164
+ // 4.7. MCP 서버 등록 — Claude는 .mcp.json 생성(첫 세션 승인 게이트), Codex는 명령 안내.
165
+ // MCP 도구 8종 = lib core의 구조화 입구(CLI와 동일 함수). 미등록 환경은 npx CLI가 폴백.
166
+ if (platforms.includes('claude')) {
167
+ const mcpJsonPath = path.join(cwd, '.mcp.json');
168
+ let mcpConfig = { mcpServers: {} };
169
+ if (fs.existsSync(mcpJsonPath)) {
170
+ // 기존 .mcp.json의 다른 서버 보존 — taskery 항목만 머지.
171
+ try {
172
+ mcpConfig = JSON.parse(fs.readFileSync(mcpJsonPath, 'utf8'));
173
+ } catch (e) {
174
+ mcpConfig = { mcpServers: {} };
175
+ }
176
+ if (!mcpConfig.mcpServers) mcpConfig.mcpServers = {};
177
+ }
178
+ if (!mcpConfig.mcpServers.taskery) {
179
+ mcpConfig.mcpServers.taskery = { command: 'npx', args: ['-y', '@angar2/taskery', 'mcp'] };
180
+ fs.writeFileSync(mcpJsonPath, JSON.stringify(mcpConfig, null, 2) + '\n');
181
+ console.log(` .mcp.json — taskery MCP 서버 등록 (첫 세션에서 승인 필요)`);
182
+ } else {
183
+ console.log(` .mcp.json — taskery 서버 이미 등록됨, 스킵`);
184
+ }
185
+ }
186
+
164
187
  // 5. 결과 보고
165
188
  console.log(`\n✅ taskery init 완료`);
166
189
  console.log(` 카피: ${copied}개 / 스킵: ${skipped}개`);
@@ -168,11 +191,16 @@ async function main() {
168
191
  console.log(` manifest: ${MANIFEST_NAME}\n`);
169
192
  const entryFile = platforms.includes('claude') ? 'CLAUDE.md' : 'AGENTS.md';
170
193
  console.log(`다음 단계:`);
171
- console.log(` 1. ${entryFile} 정독 + 프로젝트 메타 + 검증 명령 채우기`);
194
+ let step = 1;
195
+ console.log(` ${step++}. ${entryFile} 정독 + 프로젝트 메타 + 검증 명령 채우기`);
196
+ if (platforms.includes('claude')) {
197
+ console.log(` ${step++}. Claude 첫 세션에서 taskery MCP 서버 승인 (.mcp.json — 구조화 도구 8종; 미승인 시 npx CLI 폴백)`);
198
+ }
172
199
  if (platforms.includes('codex')) {
173
- console.log(` 2. Codex 최초 1회 '/hooks'로 hook trust 승인 (.codex/config.toml — git-guard / closed-immutable)`);
200
+ console.log(` ${step++}. Codex 최초 1회 '/hooks'로 hook trust 승인 (.codex/config.toml — git-guard / closed-immutable)`);
201
+ console.log(` + MCP 등록: codex mcp add taskery -- npx -y @angar2/taskery mcp`);
174
202
  }
175
- console.log(` 3. 에이전트 진입 → '/project-init' → '/plan-init <기능그룹>' → '/task-init'\n`);
203
+ console.log(` ${step++}. 에이전트 진입 → '/project-init' → '/plan-init <기능그룹>' → '/task-init'\n`);
176
204
  }
177
205
 
178
206
  main().catch((e) => {