@angar2/taskery 0.6.1 → 0.8.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 +11 -9
- package/bin/close.js +13 -1
- package/bin/init.js +7 -2
- package/bin/lib.js +238 -52
- package/bin/mcp.js +2 -2
- package/bin/migrate.js +344 -0
- package/bin/plan.js +61 -21
- package/bin/set-status.js +18 -3
- package/bin/status.js +12 -0
- package/bin/taskery.js +7 -2
- package/bin/update.js +75 -0
- package/package.json +1 -1
- package/template/.claude/skills/run-team/SKILL.md +4 -4
- package/template/.codex/agents/task-tester.toml +14 -8
- package/template/.codex/agents/test-plan-author.toml +34 -0
- package/template/.project/rules/CHANGELOG_RULE.md +27 -21
- package/template/.project/rules/GIT_RULE.md +7 -4
- package/template/.project/rules/MOCKUP_RULE.md +1 -1
- package/template/.project/rules/TASKERY_RULE.md +237 -0
- package/template/.project/rules/TASK_DOC_RULE.md +14 -3
- package/template/AGENTS.md +26 -179
- package/template/CLAUDE.md +1 -208
- package/template/shared/skills/log-friction/SKILL.md +21 -1
- package/template/shared/skills/plan-init/SKILL.md +8 -8
- package/template/shared/skills/project-init/SKILL.md +80 -43
- package/template/shared/skills/task-close/SKILL.md +24 -14
- package/template/shared/skills/task-dev/SKILL.md +17 -16
- package/template/shared/skills/task-init/SKILL.md +3 -3
- package/template/shared/skills/task-plan/SKILL.md +134 -19
- package/template/shared/skills/task-test/SKILL.md +39 -22
- package/template/.project/FRICTION_LOG.md +0 -16
- package/template/.project/TEST-GUIDE.md +0 -62
package/README.md
CHANGED
|
@@ -108,7 +108,8 @@ taskery prune
|
|
|
108
108
|
|
|
109
109
|
```
|
|
110
110
|
my-app/
|
|
111
|
-
├─
|
|
111
|
+
├─ AGENTS.md # AI 에이전트 진입 문서 (본문 단일 소스)
|
|
112
|
+
├─ CLAUDE.md # Claude Code용 — `@AGENTS.md` 임포트 한 줄
|
|
112
113
|
├─ .taskery-manifest.json # 패키지 업데이트 추적
|
|
113
114
|
├─ .gitignore
|
|
114
115
|
├─ .claude/
|
|
@@ -117,19 +118,19 @@ my-app/
|
|
|
117
118
|
│ └─ hooks/<hook-name>.sh # 2 hook
|
|
118
119
|
└─ .project/
|
|
119
120
|
├─ PROJECT.md # 프로젝트 개요
|
|
120
|
-
├─ AGENT-GUIDE.md # AI 에이전트 가이드
|
|
121
121
|
├─ LINKED-REPOS.md # 관계 리포지토리 정보
|
|
122
122
|
├─ GLOSSARY.md # 도메인 용어집 (영문/한글 표기 일관성)
|
|
123
123
|
├─ .env # 사용자 설정 환경변수 (관계 리포지토리 환경변수 등)
|
|
124
124
|
├─ SERVICE-POLICY.md / TECH-STACK.md / ARCHITECTURE.md # 제품 관통 문서 — 정적 (project-init 작성)
|
|
125
125
|
├─ DATA-MODEL.md / API-SPEC.md / FEATURES.md / UX-UI.md # 제품 관통 문서 — 성장 (골격 → plan·task가 채움)
|
|
126
126
|
├─ rules/
|
|
127
|
+
│ ├─ TASKERY_RULE.md # taskery 사용 설명서 (세션 필독)
|
|
127
128
|
│ ├─ TASK_DOC_RULE.md # task 문서 작성 규칙
|
|
128
129
|
│ ├─ GIT_RULE.md # 로컬 깃 운영 규칙
|
|
129
130
|
│ ├─ CHANGELOG_RULE.md # CHANGELOG 작성 규칙
|
|
130
131
|
│ ├─ MOCKUP_RULE.md # UX/UI HTML 목업 규칙
|
|
131
|
-
│ ├─ DEV_RULE.local.md #
|
|
132
|
-
│ ├─ TEST_RULE.local.md #
|
|
132
|
+
│ ├─ DEV_RULE.local.md # 이 프로젝트의 구현 정책 (모든 리포 필수)
|
|
133
|
+
│ ├─ TEST_RULE.local.md # 이 프로젝트의 검증 실행 경로·범위 (모든 리포 필수)
|
|
133
134
|
│ └─ *.local.md # 사용자 오버라이드 규칙 (패키지 업데이트 대상 제외)
|
|
134
135
|
├─ plans/ # plan(기능 그룹)별 PLAN.md / ROADMAP.md (<NNN_slug>/)
|
|
135
136
|
├─ tasks/ # task 문서 (<NNN_slug>/BACKLOG.md / spec-diffs / screenshots / mockup 포함)
|
|
@@ -138,17 +139,18 @@ my-app/
|
|
|
138
139
|
├─ shared/ # 관계 리포지토리 소통 메세지함
|
|
139
140
|
│ ├─ sent/completed/
|
|
140
141
|
│ └─ received/completed/
|
|
141
|
-
|
|
142
|
-
└─ TEST-GUIDE.md # 검증 방법 (방식별 이 프로젝트 실행 경로 — /task-plan이 채움)
|
|
142
|
+
└─ FRICTION_LOG.md # taskery 불편사항 누적 로그 (/log-friction 첫 기록 시 생성)
|
|
143
143
|
```
|
|
144
144
|
|
|
145
145
|
`.project/`는 플랫폼 무관 공통 자산이다. 진입 문서와 에이전트 자산 위치는 선택한 플랫폼에 따라 갈린다.
|
|
146
146
|
|
|
147
147
|
| 플랫폼 | 진입 문서 | 스킬 위치 | hook 위치 | hook 등록 |
|
|
148
148
|
|--------|----------|----------|----------|----------|
|
|
149
|
-
| Claude Code | `CLAUDE.md` | `.claude/skills/` | `.claude/hooks/` | `.claude/settings.json` |
|
|
149
|
+
| Claude Code | `CLAUDE.md` (→ `AGENTS.md` 임포트) | `.claude/skills/` | `.claude/hooks/` | `.claude/settings.json` |
|
|
150
150
|
| Codex | `AGENTS.md` | `.agents/skills/` | `.codex/hooks/` | `.codex/config.toml` |
|
|
151
151
|
|
|
152
|
+
진입 문서 본문은 `AGENTS.md` 한 벌이다. Claude Code는 `AGENTS.md`를 자동으로 읽지 않으므로 `CLAUDE.md`가 이를 임포트한다.
|
|
153
|
+
|
|
152
154
|
> 스킬 9종과 `git-guard.sh`는 양 플랫폼 동일 자산이다. 진입 문서, hook 등록 방식, 완료 보호 hook(`closed-immutable.sh`)의 구현만 플랫폼별로 다르다. `init`에서 둘 다 선택하면 폴더가 갈려 충돌 없이 공존한다.
|
|
153
155
|
|
|
154
156
|
---
|
|
@@ -207,7 +209,7 @@ my-app/
|
|
|
207
209
|
|
|
208
210
|
| 스킬 | 생성 위치 | 생성 문서 |
|
|
209
211
|
|------|---------|---------|
|
|
210
|
-
| `/project-init` | `.project/` | PROJECT.md /
|
|
212
|
+
| `/project-init` | `.project/` | PROJECT.md / LINKED-REPOS.md / GLOSSARY.md / .env + 제품 관통 문서(SERVICE-POLICY / TECH-STACK / ARCHITECTURE 작성, DATA-MODEL / API-SPEC / FEATURES / UX-UI 골격 — 타입 해당분) + 리포 로컬 룰 초안(TEST_RULE.local.md / DEV_RULE.local.md) |
|
|
211
213
|
| `/plan-init` | `.project/plans/<NNN_slug>/` | PLAN.md / ROADMAP.md + 제품 관통 문서 FEATURES/UX-UI에 기능 그룹 의도 추가 (제품 관통 문서 전체를 한꺼번에 만들지는 않음) |
|
|
212
214
|
| `/task-init` | `.project/tasks/<NNN_slug>/` | `<NNN>_<slug>.md` (단일 파일) 또는 `TASK-<NNN>_<slug>/task.md` (사용자 명시 시 폴더 승격) |
|
|
213
215
|
|
|
@@ -236,7 +238,7 @@ cd <project-name>
|
|
|
236
238
|
npx @angar2/taskery init
|
|
237
239
|
```
|
|
238
240
|
|
|
239
|
-
이후 메인 세션에 진입(진입 문서
|
|
241
|
+
이후 메인 세션에 진입(진입 문서 자동 정독 — 사용법 전체는 `.project/rules/TASKERY_RULE.md`)하여 다음 시퀀스로 호출한다. task 5 스킬은 호출과 동시에 상태를 전이시킨다.
|
|
240
242
|
|
|
241
243
|
```
|
|
242
244
|
/project-init # 진입 메타 문서와 디렉토리 골격 생성
|
package/bin/close.js
CHANGED
|
@@ -8,10 +8,11 @@
|
|
|
8
8
|
* 코드가 수행하고 멈춘다. 이후 비가역(사전 rebase+충돌해결 / 머지 락 / 부모 --no-ff 머지 /
|
|
9
9
|
* worktree remove / branch -d)은 스킬(task-close)이 git-guard hook + 복구출력 보호 하에 수행.
|
|
10
10
|
*
|
|
11
|
-
* stdout: { prepped, branch, parent, wtPath, registered, commits, aheadOfParent }. parent = 머지 대상. exit 0.
|
|
11
|
+
* stdout: { prepped, branch, parent, wtPath, registered, commits, aheadOfParent, changelog }. parent = 머지 대상. exit 0.
|
|
12
12
|
* 콜백(스킬이 LLM 판단 후 처리):
|
|
13
13
|
* - exit 3 + { blocked:'status', current } — status가 tested 아님 (게이트 차단)
|
|
14
14
|
* - exit 2 + { blocked:'mapping', files, phases, reason } — Phase↔파일 매핑 모호 (수동 커밋 필요)
|
|
15
|
+
* - exit 4 + { blocked:'docs', missing } — PLAN 체크·수정이력 누락 (문서 채운 후 그대로 재호출)
|
|
15
16
|
* 그 외 실패 시 stderr + exit 1.
|
|
16
17
|
*/
|
|
17
18
|
|
|
@@ -60,10 +61,21 @@ async function main() {
|
|
|
60
61
|
process.stdout.write(JSON.stringify(result) + '\n');
|
|
61
62
|
process.exit(3);
|
|
62
63
|
}
|
|
64
|
+
if (result.blocked === 'docs') {
|
|
65
|
+
console.error(`문서 게이트 차단 — 아래 문서에 TASK 표기 누락. 채운 후 그대로 재호출(변이 없음).`);
|
|
66
|
+
for (const miss of result.missing) {
|
|
67
|
+
console.error(` - [${miss.kind === 'plan' ? 'PLAN 체크리스트' : '수정 이력'}] ${miss.file}`);
|
|
68
|
+
}
|
|
69
|
+
process.stdout.write(JSON.stringify(result) + '\n');
|
|
70
|
+
process.exit(4);
|
|
71
|
+
}
|
|
63
72
|
if (result.blocked === 'mapping') {
|
|
64
73
|
console.error(
|
|
65
74
|
`Phase↔파일 매핑 모호 (${result.reason}) — Dev Plan '파일' 필드 확인 후 수동 Phase 커밋 필요.`,
|
|
66
75
|
);
|
|
76
|
+
console.error(
|
|
77
|
+
`미매핑 파일이 Dev Plan에 적혀 있다면 '- 파일:' 표기 형식(쉼표/가운뎃점/들여쓴 목록)을 확인 — 줄 전체가 경로 하나로 읽혔을 수 있습니다.`,
|
|
78
|
+
);
|
|
67
79
|
process.stdout.write(JSON.stringify(result) + '\n');
|
|
68
80
|
process.exit(2);
|
|
69
81
|
}
|
package/bin/init.js
CHANGED
|
@@ -31,6 +31,8 @@ const {
|
|
|
31
31
|
gitignorePatternsFor,
|
|
32
32
|
} = require('./lib');
|
|
33
33
|
|
|
34
|
+
const { ENTRY_DOCS_MARKER } = require('./migrate');
|
|
35
|
+
|
|
34
36
|
async function confirm(msg) {
|
|
35
37
|
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
36
38
|
return new Promise((resolve) => {
|
|
@@ -125,6 +127,8 @@ async function main() {
|
|
|
125
127
|
platforms,
|
|
126
128
|
stale_days: DEFAULT_STALE_DAYS,
|
|
127
129
|
lock_timeout_ms: DEFAULT_LOCK_TIMEOUT_MS,
|
|
130
|
+
activePlan: null, // /plan-init이 채운다 (0.7.0+ 활성 plan SSoT)
|
|
131
|
+
entryDocs: ENTRY_DOCS_MARKER, // 신규 설치는 이미 새 진입 문서 체계 — update가 이행을 시도하지 않도록
|
|
128
132
|
files: manifestFiles,
|
|
129
133
|
};
|
|
130
134
|
writeManifest(manifest, manifestPath);
|
|
@@ -189,10 +193,11 @@ async function main() {
|
|
|
189
193
|
console.log(` 카피: ${copied}개 / 스킵: ${skipped}개`);
|
|
190
194
|
console.log(` 플랫폼: ${platforms.join(', ')}`);
|
|
191
195
|
console.log(` manifest: ${MANIFEST_NAME}\n`);
|
|
192
|
-
const entryFile = platforms.includes('claude') ? 'CLAUDE.md' : 'AGENTS.md';
|
|
193
196
|
console.log(`다음 단계:`);
|
|
194
197
|
let step = 1;
|
|
195
|
-
|
|
198
|
+
// 진입 문서 본문은 AGENTS.md 한 벌 — CLAUDE.md는 `@AGENTS.md` 임포트 한 줄이라 채울 것이 없다.
|
|
199
|
+
console.log(` ${step++}. AGENTS.md 정독 + 프로젝트 메타 + 검증/테스트 명령 채우기`);
|
|
200
|
+
console.log(` (.project/rules/TASKERY_RULE.md = taskery 사용 설명서 — 세션이 필독)`);
|
|
196
201
|
if (platforms.includes('claude')) {
|
|
197
202
|
console.log(` ${step++}. Claude 첫 세션에서 taskery MCP 서버 승인 (.mcp.json — 구조화 도구 8종; 미승인 시 npx CLI 폴백)`);
|
|
198
203
|
}
|
package/bin/lib.js
CHANGED
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
* - getInitLockPath(projectId): ~/.taskery/<projectId>.init.lock (task 분기 직렬화용)
|
|
23
23
|
* - withMergeLock(projectId, fn, opts): proper-lockfile로 머지 락 + fn 실행 + 자동 release
|
|
24
24
|
* - withMetaLock(filePath, fn, opts): 메타 파일 쓰기 락 + fn 실행
|
|
25
|
+
* - withMetaLockSync(filePath, fn, opts): 위의 동기 변형 (sync 호출 체인용 — manifest 갱신)
|
|
25
26
|
* - getActiveTasks(mainWtPath): SSoT 조회 (git branch --list 'feature/*_TASK-*' ...)
|
|
26
27
|
* - getNextTaskNumber(mainWtPath): (진행중 ∪ 전 ref 머지 히스토리) 최대 + 1 (부모 무관 전역 채번)
|
|
27
28
|
* - forkTask(mainWtPath, {type,dev,src,slug}): init 락 안에서 부모(현재 브랜치) 캡처+채번+워크트리·브랜치 생성 원자 실행
|
|
@@ -29,7 +30,8 @@
|
|
|
29
30
|
* - assertMainWorktreeOn(mainWtPath, branch): 메인 워크트리가 지정 브랜치 체크아웃 상태인지 검증
|
|
30
31
|
*
|
|
31
32
|
* 백로그 (0.1.2+):
|
|
32
|
-
* - getActiveVersion(mainWtPath): .
|
|
33
|
+
* - getActiveVersion(mainWtPath): 활성 plan 식별자 — manifest.activePlan 우선, 구형 AGENT-GUIDE.md 폴백 (0.7.0+)
|
|
34
|
+
* - setActivePlan(mainWtPath, plan): manifest.activePlan 갱신 (plan 폴더 실재 검증 포함 — plan-switch)
|
|
33
35
|
* - getBacklogPath(mainWtPath, activeVersion?): .project/tasks/<활성 plan>/BACKLOG.md 절대 경로
|
|
34
36
|
* - parseBacklogItem(mainWtPath, blId): BL-NNN 항목 메타 파싱 ({ status, type, title, slug, summary, target, taskNums })
|
|
35
37
|
* - appendBacklogItem(mainWtPath, meta): withMetaLock + BL-NNN 채번 + 항목 append (placeholder 치환 우선)
|
|
@@ -63,8 +65,10 @@ const SHARED_DEST = {
|
|
|
63
65
|
function platformOf(relPath) {
|
|
64
66
|
if (relPath.startsWith('shared/')) return 'shared'; // 공통 — 설치 시 플랫폼별 매핑
|
|
65
67
|
if (relPath === 'CLAUDE.md' || relPath.startsWith('.claude/')) return 'claude';
|
|
66
|
-
if (relPath
|
|
67
|
-
|
|
68
|
+
if (relPath.startsWith('.codex/')) return 'codex';
|
|
69
|
+
// AGENTS.md는 0.7.0부터 agnostic — 진입 문서 본문의 단일 소스이고
|
|
70
|
+
// CLAUDE.md는 이를 `@AGENTS.md`로 임포트만 한다(Claude Code는 AGENTS.md를 자동 로드하지 않음).
|
|
71
|
+
return 'agnostic'; // AGENTS.md, .project/, .gitignore 등 — 항상 설치
|
|
68
72
|
}
|
|
69
73
|
|
|
70
74
|
// shared/ 상대경로를 특정 플랫폼의 설치 경로로 변환.
|
|
@@ -104,9 +108,9 @@ function resolveInstallPlan(templateFiles, platforms, { includeAgnostic = true }
|
|
|
104
108
|
|
|
105
109
|
// .gitignore 등록 패턴 (플랫폼별)
|
|
106
110
|
const GITIGNORE_PATTERNS = {
|
|
107
|
-
agnostic: ['.project/', '.taskery-manifest.json'],
|
|
111
|
+
agnostic: ['.project/', '.taskery-manifest.json', 'AGENTS.md'],
|
|
108
112
|
claude: ['.claude/', 'CLAUDE.md'],
|
|
109
|
-
codex: ['.codex/', '.agents/'
|
|
113
|
+
codex: ['.codex/', '.agents/'],
|
|
110
114
|
};
|
|
111
115
|
|
|
112
116
|
// 선택 플랫폼 + agnostic의 gitignore 패턴 합성 (중복 제거)
|
|
@@ -308,6 +312,27 @@ async function withMetaLock(filePath, fn, opts = {}) {
|
|
|
308
312
|
}
|
|
309
313
|
}
|
|
310
314
|
|
|
315
|
+
function withMetaLockSync(filePath, fn, opts = {}) {
|
|
316
|
+
// withMetaLock의 동기 변형. manifest 갱신 경로(initPlan → plan.js main)가 전부 sync라
|
|
317
|
+
// async 락을 쓰면 호출 사슬 전체를 async로 바꿔야 해 동기 락을 별도로 둔다.
|
|
318
|
+
const lockfile = require('proper-lockfile');
|
|
319
|
+
if (!fs.existsSync(filePath)) {
|
|
320
|
+
mkdirp(path.dirname(filePath));
|
|
321
|
+
fs.writeFileSync(filePath, '');
|
|
322
|
+
}
|
|
323
|
+
// sync API는 retries를 지원하지 않는다(proper-lockfile 제약). 경합 시 즉시 던지는데,
|
|
324
|
+
// manifest 쓰기는 사용자가 직접 부르는 명령들뿐이라 동시 실행이 실질적으로 없다.
|
|
325
|
+
const release = lockfile.lockSync(filePath, {
|
|
326
|
+
stale: opts.stale ?? DEFAULT_LOCK_TIMEOUT_MS,
|
|
327
|
+
realpath: false,
|
|
328
|
+
});
|
|
329
|
+
try {
|
|
330
|
+
return fn();
|
|
331
|
+
} finally {
|
|
332
|
+
release();
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
311
336
|
function currentBranch(mainWtPath) {
|
|
312
337
|
// 메인 워크트리의 현재 브랜치 = 태스크의 부모 브랜치(0.6.0). detached HEAD면 'HEAD' 반환.
|
|
313
338
|
return gitCapture(mainWtPath, ['rev-parse', '--abbrev-ref', 'HEAD']);
|
|
@@ -436,31 +461,51 @@ function computeNextPlanNumber(mainWtPath) {
|
|
|
436
461
|
const BACKLOG_PLACEHOLDER =
|
|
437
462
|
'(사용자 발화 또는 task-close 직후 메인 감지로 한 행씩 추가. 빈 상태 default.)';
|
|
438
463
|
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
464
|
+
// 구형(0.7.0 미만) 활성 plan 저장소 — .project/AGENT-GUIDE.md '## 활성 plan 버전' 섹션.
|
|
465
|
+
// 0.7.0에서 SSoT가 manifest.activePlan으로 이관됐으나, update 전 리포를 무중단으로 굴리기 위해
|
|
466
|
+
// 읽기 폴백으로만 남긴다. 쓰기는 하지 않는다(_setActivePlan은 manifest만 갱신).
|
|
467
|
+
// 헤딩 줄 끝 부가 텍스트(HTML 주석 등) 허용 — project-init 골격이 헤딩에 주석을 달므로 접두 일치로 판정.
|
|
468
|
+
// 완전 일치를 요구하면 자기 골격을 자기가 못 읽는 자기모순 (recordion FRICTION 2026-08-06, scaffoldError 6회).
|
|
469
|
+
function _readLegacyActivePlan(mainWtPath) {
|
|
445
470
|
const guidePath = path.join(mainWtPath, '.project', 'AGENT-GUIDE.md');
|
|
446
|
-
if (!fs.existsSync(guidePath))
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
const content = fs.readFileSync(guidePath, 'utf8');
|
|
450
|
-
const sec = content.match(/^##\s*활성 plan 버전[^\n]*\n+([^\n]+)/m);
|
|
451
|
-
if (!sec) {
|
|
452
|
-
throw new Error(`.project/AGENT-GUIDE.md '## 활성 plan 버전' 섹션 부재.`);
|
|
453
|
-
}
|
|
471
|
+
if (!fs.existsSync(guidePath)) return null;
|
|
472
|
+
const sec = fs.readFileSync(guidePath, 'utf8').match(/^##\s*활성 plan 버전[^\n]*\n+([^\n]+)/m);
|
|
473
|
+
if (!sec) return null;
|
|
454
474
|
// 첫 토큰 = plan 폴더 이름(' — 설명' 앞부분). '<예: ...>' 자리표시자는 미설정으로 간주.
|
|
455
475
|
const firstLine = sec[1].trim();
|
|
456
|
-
if (!firstLine || firstLine.startsWith('<'))
|
|
457
|
-
throw new Error(
|
|
458
|
-
`'## 활성 plan 버전' 값 미설정(${firstLine || '빈 줄'}) — /plan-init으로 활성 plan 갱신 필요.`,
|
|
459
|
-
);
|
|
460
|
-
}
|
|
476
|
+
if (!firstLine || firstLine.startsWith('<')) return null;
|
|
461
477
|
return firstLine.split(/\s+/)[0];
|
|
462
478
|
}
|
|
463
479
|
|
|
480
|
+
function getActiveVersion(mainWtPath) {
|
|
481
|
+
// 활성 plan 식별자 = manifest.activePlan (0.7.0+ SSoT). plan = 기능 그룹 단위라
|
|
482
|
+
// 식별자는 NNN_slug 폴더명(구형은 vX.X 버전명도 가능).
|
|
483
|
+
const manifest = readManifest(path.join(mainWtPath, MANIFEST_NAME));
|
|
484
|
+
if (manifest && manifest.activePlan) return manifest.activePlan;
|
|
485
|
+
|
|
486
|
+
// 구형 폴백 — update 전 리포. 읽기만 하고 승격은 update가 1회 수행한다.
|
|
487
|
+
const legacy = _readLegacyActivePlan(mainWtPath);
|
|
488
|
+
if (legacy) return legacy;
|
|
489
|
+
|
|
490
|
+
if (!manifest) {
|
|
491
|
+
throw new Error(`${MANIFEST_NAME} 부재. 'npx @angar2/taskery init' 먼저 실행.`);
|
|
492
|
+
}
|
|
493
|
+
throw new Error(
|
|
494
|
+
`활성 plan 미설정 (manifest.activePlan 없음) — /plan-init으로 plan을 생성하거나 ` +
|
|
495
|
+
`'npx @angar2/taskery plan-switch <NNN_slug>'로 지정 필요.`,
|
|
496
|
+
);
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
function setActivePlan(mainWtPath, plan) {
|
|
500
|
+
// plan-switch — 대상 plan 폴더 실재 검증 후 manifest.activePlan 갱신.
|
|
501
|
+
const planDir = path.join(mainWtPath, '.project', 'plans', plan);
|
|
502
|
+
if (!fs.existsSync(planDir)) {
|
|
503
|
+
throw new Error(`plan 폴더 부재: ${planDir} — 존재하는 plan만 활성으로 지정 가능.`);
|
|
504
|
+
}
|
|
505
|
+
_setActivePlan(mainWtPath, plan);
|
|
506
|
+
return { plan, planDir };
|
|
507
|
+
}
|
|
508
|
+
|
|
464
509
|
function getBacklogPath(mainWtPath, activeVersion) {
|
|
465
510
|
const v = activeVersion ?? getActiveVersion(mainWtPath);
|
|
466
511
|
return path.join(mainWtPath, '.project', 'tasks', v, 'BACKLOG.md');
|
|
@@ -817,7 +862,7 @@ function _renderTaskSkeleton({ taskNum, title, created, plan, docType, size, par
|
|
|
817
862
|
|
|
818
863
|
function scaffoldTaskDoc(mainWtPath, wtPath, meta) {
|
|
819
864
|
// meta: { taskNum, slug, title, type(브랜치 타입), size, parent, plan?, promoted?, created? }
|
|
820
|
-
// 위치: 등록(.gitignore) 케이스 → 메인WT, 미등록 → 워크트리. plan 미지정 시
|
|
865
|
+
// 위치: 등록(.gitignore) 케이스 → 메인WT, 미등록 → 워크트리. plan 미지정 시 활성 plan(manifest.activePlan)에서 검출.
|
|
821
866
|
// parent 미지정 시 현재 브랜치 캡처(단독 호출 안전망 — 통상 fork가 넘김).
|
|
822
867
|
// taskNum이 고유(fork init 락 채번)라 동일 문서 동시 쓰기 경합 없음 → 락 불요.
|
|
823
868
|
const registered = isProjectRegistered(mainWtPath);
|
|
@@ -851,7 +896,7 @@ function scaffoldTaskDoc(mainWtPath, wtPath, meta) {
|
|
|
851
896
|
}
|
|
852
897
|
|
|
853
898
|
// ─── plan 생성 (코드화, C3) ─────────────────────────────────────────
|
|
854
|
-
// 채번 + legacy 게이트 + 폴더 mkdir + ROADMAP/PLAN/BACKLOG 골격 +
|
|
899
|
+
// 채번 + legacy 게이트 + 폴더 mkdir + ROADMAP/PLAN/BACKLOG 골격 + manifest.activePlan 갱신을 코드로.
|
|
855
900
|
// LLM 몫(FEATURES/UX-UI delta · ROADMAP Stage 내용 · PLAN 링크)은 골격의 placeholder로 남긴다.
|
|
856
901
|
|
|
857
902
|
function _renderRoadmap(plan) {
|
|
@@ -906,29 +951,23 @@ function _renderPlanBacklog(plan) {
|
|
|
906
951
|
}
|
|
907
952
|
|
|
908
953
|
function _setActivePlan(mainWtPath, plan) {
|
|
909
|
-
//
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
const hIdx = lines.findIndex((l) => /^##\s*활성 plan 버전/.test(l));
|
|
916
|
-
if (hIdx === -1) {
|
|
917
|
-
throw new Error("AGENT-GUIDE.md '## 활성 plan 버전' 섹션 부재.");
|
|
954
|
+
// 활성 plan SSoT = manifest.activePlan (0.7.0+). 구형 AGENT-GUIDE.md는 갱신하지 않는다
|
|
955
|
+
// (읽기 폴백 전용 — 두 곳에 쓰면 다시 갈린다).
|
|
956
|
+
// manifest는 읽고-고쳐-쓰기라 다른 기록자(init/update/add)와 겹치면 필드가 유실될 수 있어 락으로 감싼다.
|
|
957
|
+
const manifestPath = path.join(mainWtPath, MANIFEST_NAME);
|
|
958
|
+
if (!fs.existsSync(manifestPath)) {
|
|
959
|
+
throw new Error(`${MANIFEST_NAME} 부재. 'npx @angar2/taskery init' 먼저 실행.`);
|
|
918
960
|
}
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
}
|
|
925
|
-
lines.splice(hIdx + 1, 0, '', valueLine); // 값 줄 부재 → 헤딩 뒤 삽입
|
|
926
|
-
}
|
|
927
|
-
fs.writeFileSync(guidePath, lines.join('\n'));
|
|
961
|
+
withMetaLockSync(manifestPath, () => {
|
|
962
|
+
const manifest = readManifest(manifestPath);
|
|
963
|
+
if (!manifest) throw new Error(`${MANIFEST_NAME} 읽기 실패.`);
|
|
964
|
+
manifest.activePlan = plan;
|
|
965
|
+
writeManifest(manifest, manifestPath);
|
|
966
|
+
});
|
|
928
967
|
}
|
|
929
968
|
|
|
930
969
|
function initPlan(mainWtPath, slug, opts = {}) {
|
|
931
|
-
// 채번 → legacy 게이트 → 폴더 + 골격 +
|
|
970
|
+
// 채번 → legacy 게이트 → 폴더 + 골격 + manifest.activePlan 갱신. slug 검증은 CLI 측.
|
|
932
971
|
// legacy 폴더(NNN_ 아닌 plan 폴더) 잔존 + !force → { gated:true } 반환(CLI가 사용자 confirm 후 force 재호출).
|
|
933
972
|
const { next, legacyDirs } = computeNextPlanNumber(mainWtPath);
|
|
934
973
|
if (legacyDirs.length && !opts.force) {
|
|
@@ -968,10 +1007,22 @@ const TAG_OF = {
|
|
|
968
1007
|
|
|
969
1008
|
function _parseDevPlanPhases(content) {
|
|
970
1009
|
// ## Dev Plan 섹션의 `### Phase N — 이름` + `- 파일:` 필드 추출.
|
|
1010
|
+
// 다중 파일 표기 지원 — 한 줄(쉼표·가운뎃점 `·` 구분) + `- 파일:` 다음의 들여쓴 하위 목록.
|
|
1011
|
+
// `/`는 경로 문자라 분할자로 쓰지 않는다. `(신규)`류 꼬리 괄호 주석은 제거한다.
|
|
971
1012
|
const lines = content.split('\n');
|
|
972
1013
|
let inDevPlan = false;
|
|
973
1014
|
const phases = [];
|
|
974
1015
|
let cur = null;
|
|
1016
|
+
let fileIndent = null; // `- 파일:` 줄의 들여쓰기 — 이보다 깊은 목록 항목을 파일로 수집
|
|
1017
|
+
const pushFiles = (raw) => {
|
|
1018
|
+
for (const piece of raw.split(/[,·]/)) {
|
|
1019
|
+
const f = piece
|
|
1020
|
+
.replace(/`/g, '')
|
|
1021
|
+
.trim()
|
|
1022
|
+
.replace(/\s*[((][^))]*[))]\s*$/, ''); // 꼬리 괄호 주석 제거
|
|
1023
|
+
if (f && !/^[(<]/.test(f)) cur.files.push(f); // placeholder(<...>, (...)) 제외
|
|
1024
|
+
}
|
|
1025
|
+
};
|
|
975
1026
|
for (const l of lines) {
|
|
976
1027
|
if (/^##\s+Dev Plan/.test(l)) {
|
|
977
1028
|
inDevPlan = true;
|
|
@@ -983,15 +1034,23 @@ function _parseDevPlanPhases(content) {
|
|
|
983
1034
|
if (ph) {
|
|
984
1035
|
cur = { num: parseInt(ph[1], 10), name: ph[2].trim(), files: [] };
|
|
985
1036
|
phases.push(cur);
|
|
1037
|
+
fileIndent = null;
|
|
986
1038
|
continue;
|
|
987
1039
|
}
|
|
988
1040
|
if (!cur) continue;
|
|
989
|
-
const fm = l.match(
|
|
1041
|
+
const fm = l.match(/^(\s*)-\s*파일\s*:\s*(.*)$/);
|
|
990
1042
|
if (fm) {
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
1043
|
+
fileIndent = fm[1].length;
|
|
1044
|
+
if (fm[2].trim()) pushFiles(fm[2]);
|
|
1045
|
+
continue;
|
|
1046
|
+
}
|
|
1047
|
+
if (fileIndent != null) {
|
|
1048
|
+
const sub = l.match(/^(\s+)-\s+(.+)$/);
|
|
1049
|
+
if (sub && sub[1].length > fileIndent) {
|
|
1050
|
+
pushFiles(sub[2]);
|
|
1051
|
+
continue;
|
|
1052
|
+
}
|
|
1053
|
+
fileIndent = null; // 다른 필드·비목록 줄에서 하위 목록 수집 종료
|
|
995
1054
|
}
|
|
996
1055
|
}
|
|
997
1056
|
return phases;
|
|
@@ -1110,10 +1169,116 @@ function splitUncommittedByPhase(wtPath, taskMdPath, meta) {
|
|
|
1110
1169
|
return result;
|
|
1111
1170
|
}
|
|
1112
1171
|
|
|
1172
|
+
// ─── close 문서 자동화 (recordion FRICTION 2026-08-09 — 문서 갱신 기계화) ─────────
|
|
1173
|
+
// CHANGELOG는 close가 직접 append(세션 준수 불요), PLAN 체크·수정이력은 grep 게이트로 차단.
|
|
1174
|
+
|
|
1175
|
+
function _parseTaskTitle(taskMdPath) {
|
|
1176
|
+
// task 문서 H1 → 제목 (`# TASK-NNN — 제목`의 제목부. 접두 없으면 전체).
|
|
1177
|
+
const m = fs.readFileSync(taskMdPath, 'utf8').match(/^#\s+(.+)$/m);
|
|
1178
|
+
if (!m) return null;
|
|
1179
|
+
return m[1].replace(/^TASK-\d+\s*[—-]\s*/, '').trim() || null;
|
|
1180
|
+
}
|
|
1181
|
+
|
|
1182
|
+
function appendChangelogEntry(base, { taskNum, title, type, phases }) {
|
|
1183
|
+
// CHANGELOG 항목을 close가 직접 append — CHANGELOG_RULE §1 두 형식(단일 CHANGELOG.md / 월별) 지원,
|
|
1184
|
+
// 없으면 월별 파일 생성. 최신 맨 위 삽입(§3). 재호출 가드: [TASK-NNN] 실재 시 skip
|
|
1185
|
+
// (mapping·docs 콜백 후 재호출 중복 차단). base = task 문서와 동일 분기(등록=메인WT / 미등록=워크트리).
|
|
1186
|
+
const nnn = String(taskNum).padStart(3, '0');
|
|
1187
|
+
const dir = path.join(base, '.project', 'changelog');
|
|
1188
|
+
const single = path.join(dir, 'CHANGELOG.md');
|
|
1189
|
+
const file = fs.existsSync(single) ? single : path.join(dir, `${_todayLocal().slice(0, 7)}.md`);
|
|
1190
|
+
const exists = fs.existsSync(file);
|
|
1191
|
+
const cur = exists ? fs.readFileSync(file, 'utf8') : '';
|
|
1192
|
+
if (cur.includes(`[TASK-${nnn}]`)) return { file, skipped: true };
|
|
1193
|
+
const named = (phases || []).filter((p) => p.num > 0 && p.name);
|
|
1194
|
+
const summary = named.length
|
|
1195
|
+
? named.map((p) => ` - Phase ${p.num} — ${p.name}`).join('\n')
|
|
1196
|
+
: ` - ${title || `TASK-${nnn}`}`;
|
|
1197
|
+
const entry = [
|
|
1198
|
+
`## [TASK-${nnn}] ${title || ''}`.trimEnd(),
|
|
1199
|
+
'',
|
|
1200
|
+
`- **날짜**: ${_todayLocal()}`,
|
|
1201
|
+
`- **타입**: ${type}`,
|
|
1202
|
+
`- **변경 요약**:`,
|
|
1203
|
+
summary,
|
|
1204
|
+
].join('\n');
|
|
1205
|
+
let next;
|
|
1206
|
+
if (!exists) {
|
|
1207
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
1208
|
+
next = entry + '\n';
|
|
1209
|
+
} else {
|
|
1210
|
+
// 첫 `## ` 항목 바로 위 삽입 (코드펜스 내부 제외). 항목이 없으면 헤더 뒤(끝) append.
|
|
1211
|
+
const lines = cur.split('\n');
|
|
1212
|
+
let fence = false;
|
|
1213
|
+
let idx = -1;
|
|
1214
|
+
for (let i = 0; i < lines.length; i++) {
|
|
1215
|
+
if (/^\s*(```|~~~)/.test(lines[i])) fence = !fence;
|
|
1216
|
+
else if (!fence && /^##\s+/.test(lines[i])) {
|
|
1217
|
+
idx = i;
|
|
1218
|
+
break;
|
|
1219
|
+
}
|
|
1220
|
+
}
|
|
1221
|
+
if (idx === -1) {
|
|
1222
|
+
next = cur + (cur.endsWith('\n') ? '' : '\n') + '\n' + entry + '\n';
|
|
1223
|
+
} else {
|
|
1224
|
+
lines.splice(idx, 0, entry, '');
|
|
1225
|
+
next = lines.join('\n');
|
|
1226
|
+
}
|
|
1227
|
+
}
|
|
1228
|
+
fs.writeFileSync(file, next);
|
|
1229
|
+
return { file, added: true };
|
|
1230
|
+
}
|
|
1231
|
+
|
|
1232
|
+
function checkDocsGate(base, { taskNum, src, plan }) {
|
|
1233
|
+
// 문서 게이트 — close의 모든 변이(mutation) 앞에서 실행. blocked여도 아무것도 안 바뀌어
|
|
1234
|
+
// 세션이 문서를 채운 후 그대로 재호출하면 된다. 반환 { ok:true } | { ok:false, missing:[...] }.
|
|
1235
|
+
// PLAN.md: 로드맵 출처(ST-/RM-)만 — 체크리스트에 TASK-NNN 표기 검사. BL·DR 출처는 항목
|
|
1236
|
+
// 자체가 없으므로 통과. 파일 부재(구형 plan)도 통과.
|
|
1237
|
+
// 수정이력: spec-diffs(항상 plan 폴더 공통 — 폴더 승격 무관, TASK_DOC_RULE §1.5)의 본 태스크
|
|
1238
|
+
// 파일이 가리키는 대상 문서(DEL 제외)에 TASK-NNN 표기 검사. 대상 부재 = 통과
|
|
1239
|
+
// (게이트 실패 방향은 '막는 쪽'이 아니라 '놓치는 쪽' — 정당한 close를 오차단하지 않는다).
|
|
1240
|
+
const nnn = String(taskNum).padStart(3, '0');
|
|
1241
|
+
const tag = `TASK-${nnn}`;
|
|
1242
|
+
const missing = [];
|
|
1243
|
+
if (/^(ST|RM)-/i.test(src || '')) {
|
|
1244
|
+
const planPath = path.join(base, '.project', 'plans', plan, 'PLAN.md');
|
|
1245
|
+
if (fs.existsSync(planPath) && !fs.readFileSync(planPath, 'utf8').includes(tag)) {
|
|
1246
|
+
missing.push({ kind: 'plan', file: planPath });
|
|
1247
|
+
}
|
|
1248
|
+
}
|
|
1249
|
+
const specDir = path.join(base, '.project', 'tasks', plan, 'spec-diffs');
|
|
1250
|
+
if (fs.existsSync(specDir)) {
|
|
1251
|
+
const seen = new Set();
|
|
1252
|
+
const mine = fs
|
|
1253
|
+
.readdirSync(specDir)
|
|
1254
|
+
.filter((f) => f.startsWith(`${nnn}_`) && f.endsWith('.md'));
|
|
1255
|
+
for (const f of mine) {
|
|
1256
|
+
const content = fs.readFileSync(path.join(specDir, f), 'utf8');
|
|
1257
|
+
// 헤딩 `## <.project/문서.md> [NEW|MOD|DEL]` — 꺾쇠 유무 관용 파싱
|
|
1258
|
+
for (const m of content.matchAll(/^##\s+<?([^\n>[\]]+?)>?\s*\[(NEW|MOD|DEL)\]/gm)) {
|
|
1259
|
+
if (m[2] === 'DEL') continue;
|
|
1260
|
+
const rel = m[1].trim();
|
|
1261
|
+
const target = fs.existsSync(path.join(base, rel))
|
|
1262
|
+
? path.join(base, rel)
|
|
1263
|
+
: path.join(base, '.project', rel);
|
|
1264
|
+
if (seen.has(target)) continue;
|
|
1265
|
+
seen.add(target);
|
|
1266
|
+
if (!fs.existsSync(target)) continue; // 대상 부재 → 통과
|
|
1267
|
+
if (!fs.readFileSync(target, 'utf8').includes(tag)) {
|
|
1268
|
+
missing.push({ kind: 'history', file: target });
|
|
1269
|
+
}
|
|
1270
|
+
}
|
|
1271
|
+
}
|
|
1272
|
+
}
|
|
1273
|
+
return missing.length ? { ok: false, missing } : { ok: true };
|
|
1274
|
+
}
|
|
1275
|
+
|
|
1113
1276
|
async function closeTask(mainWtPath, { taskNum }, opts = {}) {
|
|
1114
1277
|
// 결정적 준비만 — 비가역(머지·정리)·충돌 해결은 스킬이 후속 수행.
|
|
1115
|
-
//
|
|
1116
|
-
//
|
|
1278
|
+
// 순서: status 게이트 → docs 게이트 → Phase 커밋 → setStatus(closed) → CHANGELOG append
|
|
1279
|
+
// → (미등록) .project/ 커밋 → 추적 마커. 게이트 2종은 모든 변이 앞.
|
|
1280
|
+
// 반환: { blocked } (status/docs 게이트 미충족·매핑 모호) 또는
|
|
1281
|
+
// { prepped, branch, parent, wtPath, registered, commits, aheadOfParent, changelog,
|
|
1117
1282
|
// multiPhase?, autoAssigned? (매핑 폴백 적용 시 내역) }. parent = 스킬 머지 대상.
|
|
1118
1283
|
const active = getActiveTasks(mainWtPath).find((t) => t.taskNum === taskNum);
|
|
1119
1284
|
if (!active) {
|
|
@@ -1133,6 +1298,13 @@ async function closeTask(mainWtPath, { taskNum }, opts = {}) {
|
|
|
1133
1298
|
return { blocked: 'status', current: header.status, docPath: resolved.file };
|
|
1134
1299
|
}
|
|
1135
1300
|
|
|
1301
|
+
// 문서 게이트 — 모든 변이 앞. blocked 시 아무 것도 안 바뀌어 채우고 그대로 재호출.
|
|
1302
|
+
const docBase = resolved.registered ? mainWtPath : wtPath;
|
|
1303
|
+
const gate = checkDocsGate(docBase, { taskNum, src: active.src, plan: resolved.plan });
|
|
1304
|
+
if (!gate.ok) {
|
|
1305
|
+
return { blocked: 'docs', missing: gate.missing, docPath: resolved.file };
|
|
1306
|
+
}
|
|
1307
|
+
|
|
1136
1308
|
// Phase 커밋 (D2) — 코드 변경분만
|
|
1137
1309
|
const split = splitUncommittedByPhase(wtPath, resolved.file, { taskNum, type: active.type });
|
|
1138
1310
|
if (split.ambiguous) {
|
|
@@ -1149,6 +1321,15 @@ async function closeTask(mainWtPath, { taskNum }, opts = {}) {
|
|
|
1149
1321
|
// status=closed (문서 위치 무관 — setStatus가 .gitignore 케이스 따라 처리)
|
|
1150
1322
|
await setStatus(resolved.file, 'closed');
|
|
1151
1323
|
|
|
1324
|
+
// CHANGELOG 자동 append — 등록=메인WT 직접 쓰기(.project는 gitignore라 커밋 불요) /
|
|
1325
|
+
// 미등록=워크트리 쓰기(아래 .project/ 문서 커밋이 함께 수거).
|
|
1326
|
+
const changelog = appendChangelogEntry(docBase, {
|
|
1327
|
+
taskNum,
|
|
1328
|
+
title: _parseTaskTitle(resolved.file),
|
|
1329
|
+
type: header.type,
|
|
1330
|
+
phases: _parseDevPlanPhases(fs.readFileSync(resolved.file, 'utf8')),
|
|
1331
|
+
});
|
|
1332
|
+
|
|
1152
1333
|
const commits = [...split.committed];
|
|
1153
1334
|
// 미등록 케이스: .project/ 변경분(flows / 문서 / changelog)을 커밋
|
|
1154
1335
|
if (!resolved.registered) {
|
|
@@ -1215,6 +1396,7 @@ async function closeTask(mainWtPath, { taskNum }, opts = {}) {
|
|
|
1215
1396
|
docPath: resolved.file,
|
|
1216
1397
|
commits,
|
|
1217
1398
|
aheadOfParent: aheadFinal,
|
|
1399
|
+
changelog,
|
|
1218
1400
|
};
|
|
1219
1401
|
// 매핑 폴백 적용 내역 노출 — 코드가 자동 편입한 걸 스킬·사용자가 볼 수 있게
|
|
1220
1402
|
if (split.multiPhase) prepResult.multiPhase = split.multiPhase;
|
|
@@ -1258,6 +1440,7 @@ module.exports = {
|
|
|
1258
1440
|
getInitLockPath,
|
|
1259
1441
|
withMergeLock,
|
|
1260
1442
|
withMetaLock,
|
|
1443
|
+
withMetaLockSync,
|
|
1261
1444
|
currentBranch,
|
|
1262
1445
|
assertMainWorktreeOn,
|
|
1263
1446
|
parseBranchName,
|
|
@@ -1268,6 +1451,7 @@ module.exports = {
|
|
|
1268
1451
|
// 백로그 (0.1.2+)
|
|
1269
1452
|
BACKLOG_PLACEHOLDER,
|
|
1270
1453
|
getActiveVersion,
|
|
1454
|
+
setActivePlan,
|
|
1271
1455
|
getBacklogPath,
|
|
1272
1456
|
computeNextBLNumber,
|
|
1273
1457
|
formatBacklogBlock,
|
|
@@ -1288,5 +1472,7 @@ module.exports = {
|
|
|
1288
1472
|
// task close 결정적 준비 (D1·D2)
|
|
1289
1473
|
TAG_OF,
|
|
1290
1474
|
splitUncommittedByPhase,
|
|
1475
|
+
appendChangelogEntry,
|
|
1476
|
+
checkDocsGate,
|
|
1291
1477
|
closeTask,
|
|
1292
1478
|
};
|
package/bin/mcp.js
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* 도구 8종 = CLI 명령과 동일한 lib.js 함수의 다른 입구(네이티브 구조화 인자/반환).
|
|
8
8
|
* backlog_add / backlog_get / backlog_mark — 활성 plan BACKLOG.md 조작
|
|
9
9
|
* set_status — task 헤더 상태 전이 (7×7 유효전이 검증, closed 거부)
|
|
10
|
-
* plan_init — plan 생성 (채번+골격+
|
|
10
|
+
* plan_init — plan 생성 (채번+골격+manifest.activePlan 갱신)
|
|
11
11
|
* task_init — task 분기 (fork 원자 채번 + §1.3 골격 자동 생성)
|
|
12
12
|
* task_close — close 결정적 준비만 (Phase커밋·status=closed·마커). 비가역 머지/정리는 스킬.
|
|
13
13
|
* status — 멀티세션 현황 + 활성 plan
|
|
@@ -159,7 +159,7 @@ server.registerTool(
|
|
|
159
159
|
'plan_init',
|
|
160
160
|
{
|
|
161
161
|
description:
|
|
162
|
-
'plan(기능 그룹) 생성 — 채번 + 폴더 + ROADMAP/PLAN/BACKLOG 골격 +
|
|
162
|
+
'plan(기능 그룹) 생성 — 채번 + 폴더 + ROADMAP/PLAN/BACKLOG 골격 + 활성 plan 갱신. 이후 골격 내용은 LLM이 채움. legacy 폴더 잔존 시 gated 반환(force로 재호출).',
|
|
163
163
|
inputSchema: {
|
|
164
164
|
slug: z.string().regex(/^[a-z0-9]+(-[a-z0-9]+)*$/, '영어 kebab-case'),
|
|
165
165
|
force: z.boolean().optional().describe('legacy 게이트 무시'),
|