@angar2/taskery 0.8.1 → 1.0.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 +437 -188
- package/bin/add.js +27 -99
- package/bin/backlog.js +240 -113
- package/bin/check.js +182 -0
- package/bin/commands.js +151 -0
- package/bin/finish.js +365 -0
- package/bin/init.js +99 -190
- package/bin/install.js +143 -0
- package/bin/lib.js +591 -1336
- package/bin/mcp.js +31 -263
- package/bin/orche.js +240 -0
- package/bin/plan.js +58 -120
- package/bin/prepare.js +329 -0
- package/bin/prune.js +46 -176
- package/bin/status.js +57 -151
- package/bin/taskery.js +76 -120
- package/bin/update.js +66 -269
- package/package.json +5 -5
- package/template/.taskery/BACKLOG.md +38 -0
- package/template/.taskery/rules/CHANGELOG_RULE.md +31 -0
- package/template/.taskery/rules/DEV_RULE.local.md +8 -0
- package/template/.taskery/rules/GIT_RULE.md +82 -0
- package/template/.taskery/rules/MOCKUP_RULE.md +19 -0
- package/template/.taskery/rules/TASKERY_RULE.md +112 -0
- package/template/.taskery/rules/TASK_DOC_RULE.md +104 -0
- package/template/.taskery/rules/TEST_RULE.local.md +16 -0
- package/template/AGENTS.md +15 -53
- package/template/shared/skills/add-backlog/SKILL.md +8 -38
- package/template/shared/skills/log-friction/SKILL.md +10 -88
- package/template/shared/skills/plan-init/SKILL.md +10 -98
- package/template/shared/skills/project-init/SKILL.md +13 -342
- package/template/shared/skills/task-close/SKILL.md +16 -319
- package/template/shared/skills/task-dev/SKILL.md +19 -233
- package/template/shared/skills/task-init/SKILL.md +11 -68
- package/template/shared/skills/task-orche/SKILL.md +35 -0
- package/template/shared/skills/task-plan/SKILL.md +19 -498
- package/template/shared/skills/task-test/SKILL.md +23 -342
- package/bin/close.js +0 -86
- package/bin/fork.js +0 -112
- package/bin/legacy-quotes.js +0 -60
- package/bin/migrate.js +0 -481
- package/bin/set-status.js +0 -98
- package/template/.claude/hooks/closed-immutable.sh +0 -77
- package/template/.claude/settings.json +0 -18
- package/template/.claude/skills/run-team/SKILL.md +0 -145
- package/template/.codex/agents/task-tester.toml +0 -41
- package/template/.codex/agents/test-plan-author.toml +0 -34
- package/template/.codex/config.toml +0 -17
- package/template/.codex/hooks/closed-immutable.sh +0 -66
- package/template/.project/changelog/.gitkeep +0 -0
- package/template/.project/flows/.gitkeep +0 -0
- package/template/.project/plans/.gitkeep +0 -0
- package/template/.project/rules/CHANGELOG_RULE.md +0 -97
- package/template/.project/rules/GIT_RULE.md +0 -325
- package/template/.project/rules/MOCKUP_RULE.md +0 -86
- package/template/.project/rules/TASKERY_RULE.md +0 -237
- package/template/.project/rules/TASK_DOC_RULE.md +0 -668
- package/template/.project/shared/received/completed/.gitkeep +0 -0
- package/template/.project/shared/sent/completed/.gitkeep +0 -0
- package/template/.project/tasks/.gitkeep +0 -0
- package/template/shared/hooks/git-guard.sh +0 -108
package/bin/check.js
ADDED
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
// approve-plan · test-code · test-scenario — 계획 끝 기록, 코드 테스트, 실사용 시나리오 결과(§5-3)
|
|
2
|
+
const L = require('./lib');
|
|
3
|
+
|
|
4
|
+
async function approvePlan(ctx, a) {
|
|
5
|
+
const main = ctx.main;
|
|
6
|
+
const num = L.parseTaskNum(a.task);
|
|
7
|
+
const st = L.readState(main, num);
|
|
8
|
+
if (st.closed) L.fail(`${L.taskLabel(num)}은 이미 닫혔다.`);
|
|
9
|
+
const text = L.readDoc(main, st);
|
|
10
|
+
const problems = [];
|
|
11
|
+
const newDoc = L.isNewDoc(text);
|
|
12
|
+
const goal = L.goalProblem(text);
|
|
13
|
+
if (goal) problems.push(goal);
|
|
14
|
+
const files = L.plannedFiles(text);
|
|
15
|
+
if (L.sw(st, 'dev') && !files.length) {
|
|
16
|
+
problems.push(
|
|
17
|
+
newDoc
|
|
18
|
+
? '`## 개발 계획`에 파일이 없다 — small은 한 줄에 `- 경로` 하나, medium·large는 Phase마다 `- 파일:`'
|
|
19
|
+
: '`## 만질 파일`(medium·large는 `## Phase`의 `- 파일:` 포함)에 파일이 없다 — 한 줄에 `- 경로` 하나씩',
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
if (L.sw(st, 'test')) {
|
|
23
|
+
const n = L.parseCriteria(text).length;
|
|
24
|
+
if (n < 1 || n > 5) {
|
|
25
|
+
problems.push(`\`## ${newDoc ? '테스트 계획' : '완료 기준'}\` 완료 기준이 ${n}개다 — 테스트 켜짐이면 1~5개를 \`1. [AUTO] 시작 → 행동 → 기대하는 끝 상태\` 형식으로 적는다`);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
if (problems.length) L.fail(`approve-plan: ${L.taskLabel(num)} 태스크 문서를 고친 뒤 다시 부른다.\n${problems.map((p) => `- ${p}`).join('\n')}\n문서: ${L.docShown(main, st)}`);
|
|
29
|
+
st.approve = { at: L.nowIso(), files };
|
|
30
|
+
L.writeState(main, st);
|
|
31
|
+
L.syncDoc(main, st);
|
|
32
|
+
const d = L.stageDurations(st);
|
|
33
|
+
const next = L.sw(st, 'dev') ? '개발(task-dev)' : '테스트(task-test)';
|
|
34
|
+
const fileNote = L.sw(st, 'dev') ? `, 계획 파일 ${files.length}개` : '';
|
|
35
|
+
return `${L.taskLabel(num)} 계획을 기록했다 — 시작·기획 ${L.minutes(d.plan)}${fileNote}.\n다음: ${next}.`;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// 등록된 코드 테스트를 dir에서 차례로 실행한다. 실패하면 멈춤 오류를 던진다
|
|
39
|
+
async function runCodeTests(main, dir) {
|
|
40
|
+
const manifest = L.requireInstalled(main);
|
|
41
|
+
const cmds = manifest.codeTest;
|
|
42
|
+
if (cmds === 'none') return { none: true, ms: 0, outputs: [] };
|
|
43
|
+
if (!Array.isArray(cmds) || cmds.length === 0) {
|
|
44
|
+
L.fail(
|
|
45
|
+
'test-code: 등록된 코드 테스트 명령이 없다. 리포를 보고 화면을 켜지 않는 코드 테스트 명령(린트·타입·빌드·단위 테스트)을 정해 ' +
|
|
46
|
+
"`test-code --register \"<명령>\" [\"<명령>\" …]`으로 등록한 뒤 다시 부른다. 코드 테스트가 없는 리포면 `test-code --register none`.",
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
let total = 0;
|
|
50
|
+
const outputs = [];
|
|
51
|
+
for (const cmd of cmds) {
|
|
52
|
+
const r = await L.runShell(cmd, dir);
|
|
53
|
+
total += r.ms;
|
|
54
|
+
if (r.code !== 0) {
|
|
55
|
+
L.fail(`test-code: 실패 — \`${cmd}\` (종료 코드 ${r.code})\n--- 출력 끝부분 ---\n${L.tail(r.out, 60)}\n---\n원인을 고친 뒤 다시 부른다.${overNotice(total, cmds)}`);
|
|
56
|
+
}
|
|
57
|
+
outputs.push({ cmd, out: r.out });
|
|
58
|
+
}
|
|
59
|
+
return { none: false, ms: total, outputs };
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function overNotice(totalMs, cmds) {
|
|
63
|
+
if (totalMs <= L.CODE_TEST_NOTICE_MS) return '';
|
|
64
|
+
return (
|
|
65
|
+
`\n알림: 코드 테스트가 3분을 넘었다(${L.minutes(totalMs)}) — 화면을 켜는 테스트가 섞였는지 확인한다.\n` +
|
|
66
|
+
`지금 등록된 명령: ${cmds.map((c) => `\`${c}\``).join(', ')}\n` +
|
|
67
|
+
'바꾸려면 `test-code --register "<명령>" …`으로 목록 전체를 다시 등록한다.'
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
async function testCode(ctx, a) {
|
|
72
|
+
const main = ctx.main;
|
|
73
|
+
if (a.register) {
|
|
74
|
+
const list = a.register.map((s) => String(s).trim()).filter(Boolean);
|
|
75
|
+
if (!list.length) L.fail('test-code --register: 등록할 명령을 하나 이상 넣는다. 코드 테스트가 없는 리포면 `--register none`.');
|
|
76
|
+
const manifest = L.requireInstalled(main);
|
|
77
|
+
manifest.codeTest = list.length === 1 && list[0] === 'none' ? 'none' : list;
|
|
78
|
+
L.writeManifest(main, manifest);
|
|
79
|
+
return manifest.codeTest === 'none'
|
|
80
|
+
? '코드 테스트 없음으로 등록했다 — test-code는 코드 지문만 남기고 통과한다.'
|
|
81
|
+
: `코드 테스트 명령 ${list.length}개를 등록했다(목록 전체를 바꿨다):\n${list.map((c) => `- ${c}`).join('\n')}`;
|
|
82
|
+
}
|
|
83
|
+
let st = null;
|
|
84
|
+
let dir;
|
|
85
|
+
if (a.task) {
|
|
86
|
+
st = L.readState(main, L.parseTaskNum(a.task));
|
|
87
|
+
if (st.closed) L.fail(`${L.taskLabel(st.num)}은 이미 닫혔다.`);
|
|
88
|
+
dir = L.workDir(main, st);
|
|
89
|
+
} else {
|
|
90
|
+
dir = L.git(ctx.cwd, ['rev-parse', '--show-toplevel']);
|
|
91
|
+
}
|
|
92
|
+
const result = await runCodeTests(main, dir);
|
|
93
|
+
const cmds = L.requireInstalled(main).codeTest;
|
|
94
|
+
const lines = [];
|
|
95
|
+
if (result.none) lines.push('코드 테스트 없음으로 등록된 리포 — 실행 없이 통과.');
|
|
96
|
+
else {
|
|
97
|
+
lines.push(`통과 — ${result.outputs.length}개 명령, ${L.minutes(result.ms)}.`);
|
|
98
|
+
for (const o of result.outputs) lines.push(`--- \`${o.cmd}\` 출력 끝부분 ---\n${L.tail(o.out, 15) || '(출력 없음)'}`);
|
|
99
|
+
}
|
|
100
|
+
if (st) {
|
|
101
|
+
st.testCode = { at: L.nowIso(), fingerprint: L.fingerprint(dir) };
|
|
102
|
+
L.writeState(main, st);
|
|
103
|
+
L.syncDoc(main, st);
|
|
104
|
+
L.appendResult(
|
|
105
|
+
main,
|
|
106
|
+
st,
|
|
107
|
+
result.none
|
|
108
|
+
? `- 코드 테스트 · 등록된 명령 없음 · ${L.clock(st.testCode.at)}`
|
|
109
|
+
: `- 코드 테스트 · 통과 · 명령 ${result.outputs.length}개 · ${L.minutes(result.ms)} · ${L.clock(st.testCode.at)}`,
|
|
110
|
+
);
|
|
111
|
+
lines.push(`${L.taskLabel(st.num)} 개발 칸을 기록했다 — 개발 ${L.minutes(L.stageDurations(st).dev)}.`);
|
|
112
|
+
}
|
|
113
|
+
if (!result.none) lines.push(overNotice(result.ms, cmds).trim());
|
|
114
|
+
return lines.filter(Boolean).join('\n');
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const RESULTS = ['pass', 'fail', 'accept'];
|
|
118
|
+
|
|
119
|
+
function lastEntry(st, n) {
|
|
120
|
+
const list = (st.scenarios && st.scenarios[n]) || [];
|
|
121
|
+
return list[list.length - 1] || null;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
async function testScenario(ctx, a) {
|
|
125
|
+
const main = ctx.main;
|
|
126
|
+
const num = L.parseTaskNum(a.task);
|
|
127
|
+
const st = L.readState(main, num);
|
|
128
|
+
if (st.closed) L.fail(`${L.taskLabel(num)}은 이미 닫혔다.`);
|
|
129
|
+
const criteria = L.parseCriteria(L.readDoc(main, st));
|
|
130
|
+
const n = parseInt(a.number, 10);
|
|
131
|
+
if (!criteria.length) L.fail('test-scenario: 태스크 문서 `## 테스트 계획`에 완료 기준이 없다.');
|
|
132
|
+
if (!(n >= 1 && n <= criteria.length)) L.fail(`test-scenario: 시나리오 번호는 1~${criteria.length} 중 하나다 (받음: '${a.number}').`);
|
|
133
|
+
const result = String(a.result || '').toLowerCase();
|
|
134
|
+
if (!RESULTS.includes(result)) L.fail("test-scenario: 결과는 pass · fail · accept 중 하나다.");
|
|
135
|
+
const text = String(a.text || '').trim();
|
|
136
|
+
if (!text) {
|
|
137
|
+
L.fail(
|
|
138
|
+
result === 'accept'
|
|
139
|
+
? 'test-scenario accept: FAIL을 알고 넘어가자고 한 사용자의 말을 그대로 넣는다.'
|
|
140
|
+
: 'test-scenario: 증거(실행 출력, 화면 캡처 경로, 또는 사용자 확인 시각)를 넣는다. 증거 없는 PASS는 적지 않는다.',
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
if (result === 'accept') {
|
|
144
|
+
const last = lastEntry(st, n);
|
|
145
|
+
if (!last || last.result !== 'fail') L.fail(`test-scenario accept: 시나리오 ${n}의 마지막 결과가 FAIL일 때만 받는다.`);
|
|
146
|
+
}
|
|
147
|
+
const at = L.nowIso();
|
|
148
|
+
st.scenarios = st.scenarios || {};
|
|
149
|
+
st.scenarios[n] = st.scenarios[n] || [];
|
|
150
|
+
st.scenarios[n].push(result === 'accept' ? { result, quote: text, at } : { result, evidence: text, at });
|
|
151
|
+
const label = result === 'pass' ? 'PASS' : result === 'fail' ? 'FAIL' : 'FAIL 수락';
|
|
152
|
+
const body = result === 'accept' ? `사용자: "${text}"` : text;
|
|
153
|
+
|
|
154
|
+
// 모든 시나리오가 PASS거나 FAIL 뒤 accept면 테스트 칸을 채운다
|
|
155
|
+
let complete = true;
|
|
156
|
+
let accepted = false;
|
|
157
|
+
for (let i = 1; i <= criteria.length; i++) {
|
|
158
|
+
const last = lastEntry(st, i);
|
|
159
|
+
if (!last || last.result === 'fail') complete = false;
|
|
160
|
+
else if (last.result === 'accept') accepted = true;
|
|
161
|
+
}
|
|
162
|
+
if (complete) st.testDone = { at, accepted };
|
|
163
|
+
else delete st.testDone;
|
|
164
|
+
L.writeState(main, st);
|
|
165
|
+
L.appendResult(main, st, `- 시나리오 ${n} · ${label} · ${body} · ${L.clock(at)}`);
|
|
166
|
+
L.syncDoc(main, st);
|
|
167
|
+
|
|
168
|
+
const out = [`${L.taskLabel(num)} 시나리오 ${n}: ${label} 기록.`];
|
|
169
|
+
if (complete) out.push(`모든 시나리오가 끝났다 — 테스트 칸을 채웠다(${L.minutes(L.stageDurations(st).test)}${accepted ? ', FAIL 수락' : ''}). 다음: 마무리(task-close).`);
|
|
170
|
+
else {
|
|
171
|
+
const left = [];
|
|
172
|
+
for (let i = 1; i <= criteria.length; i++) {
|
|
173
|
+
const last = lastEntry(st, i);
|
|
174
|
+
if (!last) left.push(`${i}(결과 없음)`);
|
|
175
|
+
else if (last.result === 'fail') left.push(`${i}(FAIL)`);
|
|
176
|
+
}
|
|
177
|
+
out.push(`남은 시나리오: ${left.join(', ')}`);
|
|
178
|
+
}
|
|
179
|
+
return out.join('\n');
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
module.exports = { approvePlan, testCode, testScenario, runCodeTests, lastEntry };
|
package/bin/commands.js
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
// 명령 정의표 — 이름·받는 값·할 일을 한 번만 적고, CLI 입구(taskery.js)와 MCP 도구(mcp.js)를 여기서 만든다(§5-3)
|
|
2
|
+
const L = require('./lib');
|
|
3
|
+
const { prepareTask } = require('./prepare');
|
|
4
|
+
const { approvePlan, testCode, testScenario } = require('./check');
|
|
5
|
+
const { verifyClose, commitTask, mergeTask, closeTask } = require('./finish');
|
|
6
|
+
const { status } = require('./status');
|
|
7
|
+
const { planInit } = require('./plan');
|
|
8
|
+
const { backlogAdd, backlogGet, backlogMark } = require('./backlog');
|
|
9
|
+
const { prune } = require('./prune');
|
|
10
|
+
const { orcaDispatchTask, reportTask, waitReports } = require('./orche');
|
|
11
|
+
|
|
12
|
+
// 인자 종류: positional(순서대로) · string(--이름 값) · bool(--이름) · list(--이름 값 값 …)
|
|
13
|
+
const TASK = { name: 'task', positional: true, desc: '태스크 번호 (TASK-012 또는 12)' };
|
|
14
|
+
const RANGE = { name: 'range', desc: '사용자가 새로 말한 진행 범위 — 태스크 문서 헤더 표의 범위 칸만 바꾼다' };
|
|
15
|
+
|
|
16
|
+
const COMMANDS = [
|
|
17
|
+
{
|
|
18
|
+
name: 'status',
|
|
19
|
+
summary: '열린 태스크·단계 표·단계별 시간·문서 경로·워크트리 경로, 플랜별 시작할 수 있는 태스크를 보여 준다',
|
|
20
|
+
args: [],
|
|
21
|
+
run: status,
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
name: 'plan-init',
|
|
25
|
+
summary: '다음 플랜 번호로 .taskery/plans/<NNN>_<slug>/PLAN.md 틀을 만든다',
|
|
26
|
+
args: [
|
|
27
|
+
{ name: 'slug', positional: true, desc: '플랜 이름 — 영문 kebab-case (필수)' },
|
|
28
|
+
{ name: 'title', desc: '플랜 제목 (없으면 slug)' },
|
|
29
|
+
],
|
|
30
|
+
run: planInit,
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
name: 'prepare-task',
|
|
34
|
+
summary: '태스크를 연다 — 번호·브랜치·워크트리·태스크 문서·기록',
|
|
35
|
+
args: [
|
|
36
|
+
{ name: 'name', positional: true, desc: '태스크 제목 한 줄 (필수)' },
|
|
37
|
+
{ name: 'slug', desc: '영문 kebab-case — 브랜치·문서 파일 이름 (필수)' },
|
|
38
|
+
{ name: 'type', desc: 'feature · bug · improve · refactor · docs · chore (필수)' },
|
|
39
|
+
{ name: 'size', desc: 'small · medium · large (필수)' },
|
|
40
|
+
{ name: 'switch', desc: '켜진 스위치 목록, 예 plan,dev,test (기본 모두 켜짐)' },
|
|
41
|
+
{ name: 'range', desc: '사용자가 말한 진행 범위 그대로 (기본 한 단계)' },
|
|
42
|
+
{ name: 'plan', desc: '플랜 폴더 이름 (플랜이 하나면 생략)' },
|
|
43
|
+
{ name: 'item', desc: 'PLAN.md 태스크 목록의 항목 번호' },
|
|
44
|
+
{ name: 'from', desc: '옮겨 오는 백로그 번호 목록, 예 BL-3,BL-7' },
|
|
45
|
+
{ name: 'dev', desc: '작업자 식별자 (GIT_RULE.md 브랜치 이름에 작업자 칸이 있을 때 필수)' },
|
|
46
|
+
{ name: 'no-worktree', type: 'bool', desc: '워크트리 생략 — 사용자가 명시했을 때만' },
|
|
47
|
+
{ name: 'no-branch', type: 'bool', desc: '브랜치·워크트리 생략 — 사용자가 명시했을 때만' },
|
|
48
|
+
],
|
|
49
|
+
run: prepareTask,
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
name: 'approve-plan',
|
|
53
|
+
summary: '태스크 문서(요구사항·개발 계획·완료 기준)를 검사하고 계획 끝을 기록한다',
|
|
54
|
+
args: [TASK, RANGE],
|
|
55
|
+
run: approvePlan,
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
name: 'test-code',
|
|
59
|
+
summary: '등록된 코드 테스트를 실행한다(태스크를 주면 개발 칸 기록). --register로 명령 목록을 등록·교체한다',
|
|
60
|
+
args: [
|
|
61
|
+
{ ...TASK, desc: '태스크 번호 (없으면 기록 없이 결과만)' },
|
|
62
|
+
{ name: 'register', type: 'list', desc: '코드 테스트 명령 목록 — 부를 때마다 목록 전체를 바꾼다. 없으면 none' },
|
|
63
|
+
RANGE,
|
|
64
|
+
],
|
|
65
|
+
run: testCode,
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
name: 'test-scenario',
|
|
69
|
+
summary: '완료 기준 시나리오 결과를 기록한다 — pass|fail "증거" 또는 accept "<사용자가 한 말>"',
|
|
70
|
+
args: [
|
|
71
|
+
TASK,
|
|
72
|
+
{ name: 'number', positional: true, desc: '시나리오 번호' },
|
|
73
|
+
{ name: 'result', positional: true, desc: 'pass · fail · accept' },
|
|
74
|
+
{ name: 'text', positional: true, desc: 'pass·fail은 증거, accept는 사용자가 한 말' },
|
|
75
|
+
RANGE,
|
|
76
|
+
],
|
|
77
|
+
run: testScenario,
|
|
78
|
+
},
|
|
79
|
+
{ name: 'verify-close', summary: '마무리 검사만 한다', args: [TASK, RANGE], run: verifyClose },
|
|
80
|
+
{ name: 'commit-task', summary: 'Phase별 코드 커밋만 하고 병합 확인 요약을 낸다', args: [TASK, RANGE], run: commitTask },
|
|
81
|
+
{ name: 'merge-task', summary: '병합 잠금 안에서 부모 최신으로 rebase한 뒤 병합한다', args: [TASK, RANGE], run: mergeTask },
|
|
82
|
+
{ name: 'close-task', summary: '워크트리·브랜치를 정리하고 태스크를 닫는다(주인이 본진에서)', args: [TASK], run: closeTask },
|
|
83
|
+
{
|
|
84
|
+
name: 'prune',
|
|
85
|
+
summary: '닫힌 태스크에 남은 워크트리·브랜치를 정리한다 — 기본은 항목마다 묻는다',
|
|
86
|
+
args: [{ name: 'yes', type: 'bool', desc: '묻지 않고 정리 — 사용자가 명시했을 때만. 커밋 안 된 변경이 있는 곳은 건너뛴다' }],
|
|
87
|
+
interactive: true,
|
|
88
|
+
run: prune,
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
name: 'backlog-add',
|
|
92
|
+
summary: '백로그 번호를 발급해 BACKLOG.md 열린 항목 맨 위에 빈 양식을 넣는다',
|
|
93
|
+
args: [
|
|
94
|
+
{ name: 'title', positional: true, desc: '항목 제목 한 줄 (필수)' },
|
|
95
|
+
{ name: 'type', desc: 'bug · improve · feature · refactor · docs · chore' },
|
|
96
|
+
],
|
|
97
|
+
run: backlogAdd,
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
name: 'backlog-get',
|
|
101
|
+
summary: '번호를 주면 그 항목 전문, 없으면 열린 항목 목록(번호·종류·제목·상태)',
|
|
102
|
+
args: [{ name: 'id', positional: true, desc: '백로그 번호 (BL-3 또는 3)' }],
|
|
103
|
+
run: backlogGet,
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
name: 'backlog-mark',
|
|
107
|
+
summary: '--from 없이 연 태스크를 백로그 항목에 연결한다(연결 태스크 칸·상태 진행)',
|
|
108
|
+
args: [
|
|
109
|
+
{ name: 'id', positional: true, desc: '백로그 번호 (BL-3 또는 3)' },
|
|
110
|
+
{ name: 'task', positional: true, desc: '태스크 번호 (TASK-012 또는 12)' },
|
|
111
|
+
],
|
|
112
|
+
run: backlogMark,
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
name: 'orca-dispatch-task',
|
|
116
|
+
summary: 'Orca 새 탭에 태스크 세션을 띄우고 첫 지시문을 보낸다(Orca 전용). 탭 handle은 .state에 기록한다',
|
|
117
|
+
args: [
|
|
118
|
+
TASK,
|
|
119
|
+
{ name: 'agent', desc: 'claude · codex (필수)' },
|
|
120
|
+
{ name: 'model', desc: '에이전트 모델 — 사용자가 정한 것 (필수)' },
|
|
121
|
+
{ name: 'note', desc: '첫 지시문 끝에 붙일 오케스트레이션의 말 한 줄' },
|
|
122
|
+
],
|
|
123
|
+
run: orcaDispatchTask,
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
name: 'report-task',
|
|
127
|
+
summary: '태스크 세션이 오케스트레이션에 보고 한 줄을 남긴다(.taskery/reports.log)',
|
|
128
|
+
args: [TASK, { name: 'text', positional: true, desc: '보고 한 줄 (필수)' }],
|
|
129
|
+
run: reportTask,
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
name: 'wait-reports',
|
|
133
|
+
summary: '안 읽은 보고가 생기면 그 줄들을 출력하고 끝난다. 보고 없이 10분이면 오래 조용한 태스크 탭 목록과 함께 끝난다(백그라운드 셸로 건다)',
|
|
134
|
+
args: [],
|
|
135
|
+
cliOnly: true,
|
|
136
|
+
run: waitReports,
|
|
137
|
+
},
|
|
138
|
+
];
|
|
139
|
+
|
|
140
|
+
// 명령 실행 공통 — 본진을 찾고, --range가 있으면 범위를 먼저 갱신한다
|
|
141
|
+
// ask는 묻는 명령(prune)에 CLI가 넘기는 질문 함수다. MCP에는 없다
|
|
142
|
+
async function execute(cmd, args, { cwd = process.cwd(), main = null, ask = null } = {}) {
|
|
143
|
+
const ctx = { cwd, main: main || L.findMain(cwd), ask };
|
|
144
|
+
if (args.range && args.task && cmd.args.includes(RANGE)) {
|
|
145
|
+
const st = L.readState(ctx.main, L.parseTaskNum(args.task));
|
|
146
|
+
L.updateRange(ctx.main, st, String(args.range));
|
|
147
|
+
}
|
|
148
|
+
return cmd.run(ctx, args);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
module.exports = { COMMANDS, execute };
|