@angar2/taskery 0.4.0 → 0.5.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/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+충돌해결 / 머지 락 / dev --no-ff 머지 /
9
+ * worktree remove / branch -d)은 스킬(task-close)이 git-guard hook + 복구출력 보호 하에 수행.
10
+ *
11
+ * stdout: { prepped, branch, wtPath, registered, commits, aheadOfDev }. 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,32 @@ 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
+ promoted: !!opts.promote,
100
+ });
101
+ out = { ...result, docPath: doc.file, registered: doc.registered, promoted: doc.promoted };
102
+ } catch (e) {
103
+ out = { ...result, scaffoldError: e.message };
104
+ }
105
+ }
106
+ process.stdout.write(JSON.stringify(out) + '\n');
64
107
  }
65
108
 
66
109
  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) => {