@angar2/taskery 0.1.3 → 0.2.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
@@ -57,10 +57,13 @@ draft → planned → developing → developed → testing → tested → closed
57
57
  # 새 프로젝트
58
58
  npx -p @angar2/taskery create-taskery <project-name>
59
59
 
60
- # 기존 프로젝트에 도입
60
+ # 기존 프로젝트에 도입 (init 시 에이전트 플랫폼 선택)
61
61
  cd <project-name>
62
62
  npx @angar2/taskery init
63
63
 
64
+ # 다른 플랫폼 자산 추가 (예: 기존 설치에 Codex 추가)
65
+ npx @angar2/taskery add codex
66
+
64
67
  # 최신 버전 머지 갱신
65
68
  npx @angar2/taskery update
66
69
 
@@ -79,10 +82,13 @@ npm install -g @angar2/taskery
79
82
  # 새 프로젝트
80
83
  create-taskery <project-name>
81
84
 
82
- # 기존 프로젝트에 도입
85
+ # 기존 프로젝트에 도입 (init 시 에이전트 플랫폼 선택)
83
86
  cd <project-name>
84
87
  taskery init
85
88
 
89
+ # 다른 플랫폼 자산 추가 (예: 기존 설치에 Codex 추가)
90
+ taskery add codex
91
+
86
92
  # 최신 버전 머지 갱신
87
93
  taskery update
88
94
 
@@ -95,7 +101,7 @@ taskery prune
95
101
 
96
102
  ## 패키지 디렉토리 구조
97
103
 
98
- `taskery init` 직후 골격:
104
+ `taskery init` 직후 골격 (아래는 Claude Code 선택 시 예시):
99
105
 
100
106
  ```
101
107
  my-app/
@@ -128,6 +134,15 @@ my-app/
128
134
  └─ FRICTION_LOG.md # taskery 불편사항 누적 로그
129
135
  ```
130
136
 
137
+ `.project/`는 플랫폼 무관 공통 자산이다. 진입 문서와 에이전트 자산 위치는 선택한 플랫폼에 따라 갈린다.
138
+
139
+ | 플랫폼 | 진입 문서 | 스킬 위치 | hook 위치 | hook 등록 |
140
+ |--------|----------|----------|----------|----------|
141
+ | Claude Code | `CLAUDE.md` | `.claude/skills/` | `.claude/hooks/` | `.claude/settings.json` |
142
+ | Codex | `AGENTS.md` | `.agents/skills/` | `.codex/hooks/` | `.codex/config.toml` |
143
+
144
+ > 스킬 9종과 `git-guard.sh`는 양 플랫폼 동일 자산이다. 진입 문서, hook 등록 방식, 완료 보호 hook(`closed-immutable.sh`)의 구현만 플랫폼별로 다르다. `init`에서 둘 다 선택하면 폴더가 갈려 충돌 없이 공존한다.
145
+
131
146
  ---
132
147
 
133
148
  ## 멀티세션 (병렬 작업)
@@ -196,7 +211,7 @@ my-app/
196
211
  | `git-guard.sh` | git 명령 실행 직전 | 주력 브랜치 직접 커밋 / `--force` / `--no-verify` / 강제 브랜치 삭제 / `reset --hard` / `clean -fd` |
197
212
  | `closed-immutable.sh` | 파일 수정 직전 | 완료(`closed`)된 task.md 본 파일 재수정 (관련 spec-diff·스크린샷은 자유) |
198
213
 
199
- hook은 catastrophic 사고만 차단한다. 정상 흐름에는 간섭하지 않는다.
214
+ hook은 catastrophic 사고만 차단한다. 정상 흐름에는 간섭하지 않는다. 등록 방식은 플랫폼별로 다르다 — Claude Code는 `.claude/settings.json`, Codex는 `.codex/config.toml`(최초 1회 `/hooks` 신뢰 승인 필요).
200
215
 
201
216
  > 각 hook의 영역 분리 정신, 등록 매칭, 차단 정책, 예외 처리 절차는 [HOOKS.md](https://github.com/angar2/taskery/blob/main/plan/HOOKS.md)에서 확인 가능
202
217
 
@@ -210,7 +225,7 @@ cd <project-name>
210
225
  npx @angar2/taskery init
211
226
  ```
212
227
 
213
- 이후 메인 세션에 진입(`CLAUDE.md` 자동 정독)하여 다음 시퀀스로 호출한다. task 5 스킬은 호출과 동시에 상태를 전이시킨다.
228
+ 이후 메인 세션에 진입(진입 문서 `CLAUDE.md` 또는 `AGENTS.md` 자동 정독)하여 다음 시퀀스로 호출한다. task 5 스킬은 호출과 동시에 상태를 전이시킨다.
214
229
 
215
230
  ```
216
231
  /project-init # 진입 메타 문서와 디렉토리 골격 생성
package/bin/add.js ADDED
@@ -0,0 +1,113 @@
1
+ #!/usr/bin/env node
2
+ // bin/add.js
3
+ // `npx @angar2/taskery add <platform>` — 기존 설치 리포에 다른 플랫폼 자산 추가.
4
+ //
5
+ // 동작:
6
+ // 1. <platform> 인자 검증 (claude | codex)
7
+ // 2. .taskery-manifest.json 필수 — 없으면 'init' 안내
8
+ // 3. 이미 manifest.platforms에 있으면 변경 없이 종료
9
+ // 4. 해당 플랫폼 전용 자산만 카피 (공통 .project/는 init이 이미 설치)
10
+ // 5. manifest.platforms push + files 갱신
11
+
12
+ const fs = require('fs');
13
+ const path = require('path');
14
+ const readline = require('readline');
15
+
16
+ const {
17
+ MANIFEST_NAME,
18
+ findTemplateDir,
19
+ walkTemplate,
20
+ copyFile,
21
+ writeManifest,
22
+ readManifest,
23
+ getPackageVersion,
24
+ resolveInstallPlan,
25
+ PLATFORMS,
26
+ } = require('./lib');
27
+
28
+ async function confirm(msg) {
29
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
30
+ return new Promise((resolve) => {
31
+ rl.question(`${msg} (y/N) `, (ans) => {
32
+ rl.close();
33
+ resolve(ans.trim().toLowerCase() === 'y');
34
+ });
35
+ });
36
+ }
37
+
38
+ async function main() {
39
+ const cwd = process.cwd();
40
+ const platform = (process.argv[2] || '').trim();
41
+
42
+ // 1. 플랫폼 인자 검증
43
+ if (!PLATFORMS.includes(platform)) {
44
+ console.error(
45
+ `taskery add: 플랫폼 인자 필요 (${PLATFORMS.join(' | ')}). 예: npx @angar2/taskery add codex`,
46
+ );
47
+ process.exit(1);
48
+ }
49
+
50
+ // 2. manifest 필수
51
+ const manifestPath = path.join(cwd, MANIFEST_NAME);
52
+ const manifest = readManifest(manifestPath);
53
+ if (!manifest) {
54
+ console.error(
55
+ `taskery add: ${MANIFEST_NAME} 없음 — 'npx @angar2/taskery init' 먼저 실행 필요.`,
56
+ );
57
+ process.exit(1);
58
+ }
59
+
60
+ // 3. 이미 설치된 플랫폼?
61
+ const platforms = Array.isArray(manifest.platforms) ? manifest.platforms : ['claude'];
62
+ if (platforms.includes(platform)) {
63
+ console.log(`taskery add: '${platform}' 이미 설치됨 — 변경 없음.`);
64
+ process.exit(0);
65
+ }
66
+
67
+ // 4. 해당 플랫폼 전용 + 공통(shared/) 자산만 카피 (agnostic은 init이 이미 설치)
68
+ const templateDir = findTemplateDir();
69
+ const templateFiles = walkTemplate(templateDir);
70
+ const plan = resolveInstallPlan(templateFiles, [platform], { includeAgnostic: false });
71
+
72
+ console.log(`\ntaskery v${getPackageVersion()} add ${platform}`);
73
+ console.log(`카피 대상: ${plan.length}개 파일\n`);
74
+
75
+ const files = { ...(manifest.files || {}) };
76
+ let copied = 0;
77
+ let skipped = 0;
78
+
79
+ for (const { templateRel, installRel, hash } of plan) {
80
+ const src = path.join(templateDir, templateRel);
81
+ const dst = path.join(cwd, installRel);
82
+ if (fs.existsSync(dst)) {
83
+ const ok = await confirm(` '${installRel}' 이미 존재 — 덮어쓸까?`);
84
+ if (!ok) {
85
+ console.log(` skip: ${installRel}`);
86
+ skipped++;
87
+ continue;
88
+ }
89
+ }
90
+ copyFile(src, dst);
91
+ files[installRel] = { hash, core: true, managed: true };
92
+ copied++;
93
+ console.log(` copy: ${installRel}`);
94
+ }
95
+
96
+ // 5. manifest 갱신
97
+ manifest.platforms = [...platforms, platform];
98
+ manifest.files = files;
99
+ manifest.updated_at = new Date().toISOString();
100
+ writeManifest(manifest, manifestPath);
101
+
102
+ console.log(`\n✅ taskery add ${platform} 완료 (카피 ${copied} / 스킵 ${skipped})`);
103
+ console.log(` platforms: ${manifest.platforms.join(', ')}`);
104
+ if (platform === 'codex') {
105
+ console.log(` 다음: Codex 최초 1회 '/hooks'로 hook trust 승인 (.codex/config.toml)`);
106
+ console.log(` AGENTS.md 정독 (코덱스 진입점)`);
107
+ }
108
+ }
109
+
110
+ main().catch((e) => {
111
+ console.error(`taskery add 실패: ${e.message}`);
112
+ process.exit(1);
113
+ });
package/bin/init.js CHANGED
@@ -27,6 +27,8 @@ const {
27
27
  generateProjectId,
28
28
  DEFAULT_STALE_DAYS,
29
29
  DEFAULT_LOCK_TIMEOUT_MS,
30
+ resolveInstallPlan,
31
+ gitignorePatternsFor,
30
32
  } = require('./lib');
31
33
 
32
34
  async function confirm(msg) {
@@ -39,6 +41,23 @@ async function confirm(msg) {
39
41
  });
40
42
  }
41
43
 
44
+ // 에이전트 플랫폼 선택 — 고른 것만 독립 설치 (공통 .project/는 항상)
45
+ async function selectPlatforms() {
46
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
47
+ return new Promise((resolve) => {
48
+ rl.question(
49
+ `\n에이전트 플랫폼 선택:\n 1) Claude Code\n 2) Codex\n 3) 둘 다\n선택 (1/2/3, 기본 1): `,
50
+ (ans) => {
51
+ rl.close();
52
+ const c = ans.trim() || '1';
53
+ if (c === '2') resolve(['codex']);
54
+ else if (c === '3') resolve(['claude', 'codex']);
55
+ else resolve(['claude']);
56
+ },
57
+ );
58
+ });
59
+ }
60
+
42
61
  async function main() {
43
62
  const cwd = process.cwd();
44
63
  const templateDir = findTemplateDir();
@@ -55,26 +74,28 @@ async function main() {
55
74
  }
56
75
  }
57
76
 
58
- // 2. template/ 정독 + 해시 맵
77
+ // 2. 플랫폼 선택 + template/ 정독 → 설치 계획 전개 (공통 shared/는 플랫폼 경로로 매핑)
78
+ const platforms = await selectPlatforms();
59
79
  const templateFiles = walkTemplate(templateDir);
60
- const fileEntries = Object.entries(templateFiles);
80
+ const plan = resolveInstallPlan(templateFiles, platforms);
61
81
 
62
82
  console.log(`\ntaskery v${getPackageVersion()} init`);
63
83
  console.log(`대상: ${cwd}`);
64
- console.log(`카피 대상: ${fileEntries.length}개 파일\n`);
84
+ console.log(`플랫폼: ${platforms.join(', ')}`);
85
+ console.log(`카피 대상: ${plan.length}개 파일\n`);
65
86
 
66
- // 3. 카피 + manifest 빌드
87
+ // 3. 카피 + manifest 빌드 (manifest 키 = 설치 경로 installRel)
67
88
  const manifestFiles = {};
68
89
  let copied = 0;
69
90
  let skipped = 0;
70
91
 
71
- for (const [rel, hash] of fileEntries) {
72
- const src = path.join(templateDir, rel);
73
- const dst = path.join(cwd, rel);
92
+ for (const { templateRel, installRel, hash } of plan) {
93
+ const src = path.join(templateDir, templateRel);
94
+ const dst = path.join(cwd, installRel);
74
95
 
75
96
  // .local.md suffix는 사용자 영역 — taskery template에는 없어야 정상이지만 방어적으로 스킵
76
- if (isLocalOverride(rel)) {
77
- console.log(` skip (.local override 영역): ${rel}`);
97
+ if (isLocalOverride(installRel)) {
98
+ console.log(` skip (.local override 영역): ${installRel}`);
78
99
  skipped++;
79
100
  continue;
80
101
  }
@@ -82,18 +103,18 @@ async function main() {
82
103
  // 사용자 .local.md 충돌 검사 — 사용자가 직접 만든 *.local.md 보호
83
104
  // 본 init은 신규 설치 default, 충돌 거의 없지만 update 흐름과 일관성 유지
84
105
  if (fs.existsSync(dst)) {
85
- const ok = await confirm(` '${rel}' 이미 존재 — 덮어쓸까?`);
106
+ const ok = await confirm(` '${installRel}' 이미 존재 — 덮어쓸까?`);
86
107
  if (!ok) {
87
- console.log(` skip: ${rel}`);
108
+ console.log(` skip: ${installRel}`);
88
109
  skipped++;
89
110
  continue;
90
111
  }
91
112
  }
92
113
 
93
114
  copyFile(src, dst);
94
- manifestFiles[rel] = { hash, core: true, managed: true };
115
+ manifestFiles[installRel] = { hash, core: true, managed: true };
95
116
  copied++;
96
- console.log(` copy: ${rel}`);
117
+ console.log(` copy: ${installRel}`);
97
118
  }
98
119
 
99
120
  // 4. manifest 작성 — 멀티세션(0.1.2+): projectId / stale_days / lock_timeout_ms 포함
@@ -101,6 +122,7 @@ async function main() {
101
122
  version: getPackageVersion(),
102
123
  installed_at: new Date().toISOString(),
103
124
  projectId: generateProjectId(),
125
+ platforms,
104
126
  stale_days: DEFAULT_STALE_DAYS,
105
127
  lock_timeout_ms: DEFAULT_LOCK_TIMEOUT_MS,
106
128
  files: manifestFiles,
@@ -111,14 +133,9 @@ async function main() {
111
133
  // 공개 repo면 내부 워크플로 파일 노출 회피 — taskery 내부 영역 .gitignore 등록 제안.
112
134
  // 사용자 NO면 패스 (그대로 둠). 이미 등록되어 있으면 스킵.
113
135
  const gitignorePath = path.join(cwd, '.gitignore');
114
- const taskeryPatterns = [
115
- '.project/',
116
- '.claude/',
117
- 'CLAUDE.md',
118
- '.taskery-manifest.json',
119
- ];
136
+ const taskeryPatterns = gitignorePatternsFor(platforms);
120
137
  const addGitignore = await confirm(
121
- `\ntaskery 내부 파일(.project/, .claude/, CLAUDE.md, .taskery-manifest.json)을 .gitignore에 등록할까?\n (공개 repo면 권장 — 내부 워크플로 파일 노출 회피)`,
138
+ `\ntaskery 내부 파일(${taskeryPatterns.join(', ')})을 .gitignore에 등록할까?\n (공개 repo면 권장 — 내부 워크플로 파일 노출 회피)`,
122
139
  );
123
140
  if (addGitignore) {
124
141
  const existing = fs.existsSync(gitignorePath)
@@ -147,12 +164,15 @@ async function main() {
147
164
  // 5. 결과 보고
148
165
  console.log(`\n✅ taskery init 완료`);
149
166
  console.log(` 카피: ${copied}개 / 스킵: ${skipped}개`);
167
+ console.log(` 플랫폼: ${platforms.join(', ')}`);
150
168
  console.log(` manifest: ${MANIFEST_NAME}\n`);
169
+ const entryFile = platforms.includes('claude') ? 'CLAUDE.md' : 'AGENTS.md';
151
170
  console.log(`다음 단계:`);
152
- console.log(` 1. CLAUDE.md 정독 + 프로젝트 메타 + 검증 명령 채우기`);
153
- console.log(` 2. Claude Code 진입 → '/project-init' 호출 → '.project/' 진입 문서 골격 생성`);
154
- console.log(` 3. '/plan-init <vX.X>' 호출 → 9 기획 문서 작성`);
155
- console.log(` 4. '/task-init' 호출 → 첫 task 시작\n`);
171
+ console.log(` 1. ${entryFile} 정독 + 프로젝트 메타 + 검증 명령 채우기`);
172
+ if (platforms.includes('codex')) {
173
+ console.log(` 2. Codex 최초 1회 '/hooks'로 hook trust 승인 (.codex/config.toml — git-guard / closed-immutable)`);
174
+ }
175
+ console.log(` 3. 에이전트 진입 → '/project-init' → '/plan-init <vX.X>' → '/task-init'\n`);
156
176
  }
157
177
 
158
178
  main().catch((e) => {
package/bin/lib.js CHANGED
@@ -43,6 +43,81 @@ const { execFileSync } = require('child_process');
43
43
  const LOCAL_SUFFIX = '.local.md';
44
44
  const MANIFEST_NAME = '.taskery-manifest.json';
45
45
 
46
+ // === 멀티 플랫폼 (0.2.0+) ===
47
+ // 공통 소스 + 플랫폼별 조립 설치: 공통 자산(shared/)은 단일 소스로 보관하고
48
+ // 설치 시 고른 플랫폼의 실제 경로로 매핑 복사한다. 플랫폼 고유 자산은 그대로 복사.
49
+ const PLATFORMS = ['claude', 'codex'];
50
+ const DEFAULT_PLATFORMS = ['claude']; // 기존(0.1.x) 설치 마이그레이션 기본값
51
+
52
+ // shared/ 공통 자산의 플랫폼별 설치 경로 매핑.
53
+ // shared 하위 첫 세그먼트(skills|hooks) → 각 플랫폼의 실제 설치 루트.
54
+ // 스킬·git-guard 본문은 양 플랫폼 동일(검증 완료) — 단일 소스로 보관, 설치 때 갈라 깐다.
55
+ const SHARED_DEST = {
56
+ claude: { skills: '.claude/skills', hooks: '.claude/hooks' },
57
+ codex: { skills: '.agents/skills', hooks: '.codex/hooks' },
58
+ };
59
+
60
+ // template 상대경로 → 소속 ('agnostic' | 'shared' | 'claude' | 'codex')
61
+ function platformOf(relPath) {
62
+ if (relPath.startsWith('shared/')) return 'shared'; // 공통 — 설치 시 플랫폼별 매핑
63
+ if (relPath === 'CLAUDE.md' || relPath.startsWith('.claude/')) return 'claude';
64
+ if (relPath === 'AGENTS.md' || relPath.startsWith('.codex/')) return 'codex';
65
+ return 'agnostic'; // .project/, .gitignore 등 — 항상 설치
66
+ }
67
+
68
+ // shared/ 상대경로를 특정 플랫폼의 설치 경로로 변환.
69
+ // 예: 'shared/skills/task-init/SKILL.md' + 'codex' → '.agents/skills/task-init/SKILL.md'
70
+ function mapSharedDest(relPath, platform) {
71
+ const rest = relPath.slice('shared/'.length); // 'skills/task-init/SKILL.md'
72
+ const sep = rest.indexOf('/');
73
+ const top = sep === -1 ? rest : rest.slice(0, sep); // 'skills'
74
+ const tail = sep === -1 ? '' : rest.slice(sep); // '/task-init/SKILL.md'
75
+ const root = SHARED_DEST[platform] && SHARED_DEST[platform][top];
76
+ if (!root) {
77
+ throw new Error(`shared 자산 매핑 미정의: ${relPath} (platform=${platform})`);
78
+ }
79
+ return root + tail;
80
+ }
81
+
82
+ // template 파일맵을 설치 계획으로 전개 — [{ templateRel, installRel, hash }].
83
+ // - agnostic: 그대로 (includeAgnostic=false면 제외 — add 용)
84
+ // - shared: 선택 플랫폼마다 매핑 경로로 1건씩 전개 (1:N)
85
+ // - 플랫폼 고유: 선택 플랫폼이면 그대로
86
+ function resolveInstallPlan(templateFiles, platforms, { includeAgnostic = true } = {}) {
87
+ const plan = [];
88
+ for (const [rel, hash] of Object.entries(templateFiles)) {
89
+ const p = platformOf(rel);
90
+ if (p === 'agnostic') {
91
+ if (includeAgnostic) plan.push({ templateRel: rel, installRel: rel, hash });
92
+ } else if (p === 'shared') {
93
+ for (const platform of platforms) {
94
+ plan.push({ templateRel: rel, installRel: mapSharedDest(rel, platform), hash });
95
+ }
96
+ } else if (platforms.includes(p)) {
97
+ plan.push({ templateRel: rel, installRel: rel, hash });
98
+ }
99
+ }
100
+ return plan;
101
+ }
102
+
103
+ // .gitignore 등록 패턴 (플랫폼별)
104
+ const GITIGNORE_PATTERNS = {
105
+ agnostic: ['.project/', '.taskery-manifest.json'],
106
+ claude: ['.claude/', 'CLAUDE.md'],
107
+ codex: ['.codex/', '.agents/', 'AGENTS.md'],
108
+ };
109
+
110
+ // 선택 플랫폼 + agnostic의 gitignore 패턴 합성 (중복 제거)
111
+ function gitignorePatternsFor(platforms) {
112
+ const out = [...GITIGNORE_PATTERNS.agnostic];
113
+ for (const p of platforms) {
114
+ for (const pat of GITIGNORE_PATTERNS[p] || []) {
115
+ if (!out.includes(pat)) out.push(pat);
116
+ }
117
+ }
118
+ return out;
119
+ }
120
+
46
121
  function findTemplateDir() {
47
122
  // 패키지 루트는 이 lib.js 파일의 부모(bin/)의 부모
48
123
  const pkgRoot = path.resolve(__dirname, '..');
@@ -461,6 +536,15 @@ async function markBacklogChecked(mainWtPath, blId, taskNum) {
461
536
  module.exports = {
462
537
  LOCAL_SUFFIX,
463
538
  MANIFEST_NAME,
539
+ // 멀티 플랫폼 (0.2.0+)
540
+ PLATFORMS,
541
+ DEFAULT_PLATFORMS,
542
+ SHARED_DEST,
543
+ platformOf,
544
+ mapSharedDest,
545
+ resolveInstallPlan,
546
+ GITIGNORE_PATTERNS,
547
+ gitignorePatternsFor,
464
548
  findTemplateDir,
465
549
  sha256,
466
550
  walkTemplate,
package/bin/taskery.js CHANGED
@@ -4,7 +4,8 @@
4
4
  * `npx @angar2/taskery <subcommand>` — 진입점 dispatcher.
5
5
  *
6
6
  * 서브커맨드:
7
- * init — 현재 디렉토리에 taskery 자산 카피 + manifest 생성
7
+ * init — 현재 디렉토리에 taskery 자산 카피 + manifest 생성 (플랫폼 선택)
8
+ * add — 기존 설치 리포에 플랫폼 자산 추가 (claude | codex)
8
9
  * update — 최신 버전 fetch + manifest 비교 + 머지 갱신
9
10
  * status — 멀티세션 현황 요약 (진행중 태스크 / 워크트리 / 머지 락, 0.1.2+)
10
11
  * prune — stale 워크트리 / 브랜치 대화형 정리 (0.1.2+)
@@ -28,7 +29,8 @@ function help() {
28
29
  console.log(`taskery v${require('./lib').getPackageVersion()}
29
30
 
30
31
  사용법:
31
- npx @angar2/taskery init 현재 디렉토리에 taskery 자산 설치
32
+ npx @angar2/taskery init 현재 디렉토리에 taskery 자산 설치 (플랫폼 선택)
33
+ npx @angar2/taskery add <p> 기존 설치에 플랫폼 추가 (claude | codex)
32
34
  npx @angar2/taskery update 최신 버전 fetch + 머지 갱신
33
35
  npx @angar2/taskery status 멀티세션 현황 (진행중 태스크 / 워크트리 / 머지 락)
34
36
  npx @angar2/taskery prune stale 워크트리 / 브랜치 대화형 정리
@@ -45,6 +47,9 @@ switch (sub) {
45
47
  case 'init':
46
48
  runScript('init.js');
47
49
  break;
50
+ case 'add':
51
+ runScript('add.js');
52
+ break;
48
53
  case 'update':
49
54
  runScript('update.js');
50
55
  break;
package/bin/update.js CHANGED
@@ -34,6 +34,8 @@ const {
34
34
  generateProjectId,
35
35
  DEFAULT_STALE_DAYS,
36
36
  DEFAULT_LOCK_TIMEOUT_MS,
37
+ DEFAULT_PLATFORMS,
38
+ resolveInstallPlan,
37
39
  } = require('./lib');
38
40
 
39
41
  async function confirm(msg) {
@@ -60,8 +62,17 @@ async function main() {
60
62
  process.exit(1);
61
63
  }
62
64
 
63
- // 2. template/ 새 해시 맵
64
- const newTemplateFiles = walkTemplate(templateDir);
65
+ // 2. 설치된 플랫폼 자산만 갱신 대상 (platforms 누락 시 마이그레이션 기본값)
66
+ // 설치 계획(installRel 기준)으로 전개 — shared/는 플랫폼 경로로 매핑된 상태.
67
+ // newTemplateFiles: installRel → { hash(소스), templateRel(복사 원본) }
68
+ const platforms = Array.isArray(oldManifest.platforms)
69
+ ? oldManifest.platforms
70
+ : DEFAULT_PLATFORMS;
71
+ const plan = resolveInstallPlan(walkTemplate(templateDir), platforms);
72
+ const newTemplateFiles = {};
73
+ for (const it of plan) {
74
+ newTemplateFiles[it.installRel] = { hash: it.hash, templateRel: it.templateRel };
75
+ }
65
76
  const oldFiles = oldManifest.files || {};
66
77
 
67
78
  console.log(`\ntaskery update`);
@@ -71,8 +82,8 @@ async function main() {
71
82
  const newFiles = {};
72
83
  const summary = { unchanged: 0, new: 0, autoUpdated: 0, customizedReplaced: 0, skipped: 0, removed: 0 };
73
84
 
74
- // 3. 새 template 파일 분기
75
- for (const [rel, newHash] of Object.entries(newTemplateFiles)) {
85
+ // 3. 새 template 파일 분기 (rel = 설치 경로 installRel)
86
+ for (const [rel, { hash: newHash, templateRel }] of Object.entries(newTemplateFiles)) {
76
87
  if (isLocalOverride(rel)) {
77
88
  // template에 .local.md 들어가면 안 되지만 방어적 스킵
78
89
  console.log(` skip (.local override 영역): ${rel}`);
@@ -80,7 +91,7 @@ async function main() {
80
91
  continue;
81
92
  }
82
93
 
83
- const src = path.join(templateDir, rel);
94
+ const src = path.join(templateDir, templateRel);
84
95
  const dst = path.join(cwd, rel);
85
96
  const oldEntry = oldFiles[rel];
86
97
 
@@ -149,8 +160,8 @@ async function main() {
149
160
  }
150
161
  }
151
162
 
152
- // 5. manifest 갱신 — 멀티세션(0.1.2+) 필드 누락 시 자동 마이그레이션
153
- const migration = { projectId: false, stale_days: false, lock_timeout_ms: false };
163
+ // 5. manifest 갱신 — 멀티세션(0.1.2+) / 플랫폼(0.2.0+) 필드 누락 시 자동 마이그레이션
164
+ const migration = { projectId: false, stale_days: false, lock_timeout_ms: false, platforms: false };
154
165
  const projectId = oldManifest.projectId || (migration.projectId = true, generateProjectId());
155
166
  const stale_days =
156
167
  typeof oldManifest.stale_days === 'number'
@@ -160,12 +171,14 @@ async function main() {
160
171
  typeof oldManifest.lock_timeout_ms === 'number'
161
172
  ? oldManifest.lock_timeout_ms
162
173
  : (migration.lock_timeout_ms = true, DEFAULT_LOCK_TIMEOUT_MS);
174
+ if (!Array.isArray(oldManifest.platforms)) migration.platforms = true;
163
175
 
164
176
  const newManifest = {
165
177
  version: getPackageVersion(),
166
178
  installed_at: oldManifest.installed_at,
167
179
  updated_at: new Date().toISOString(),
168
180
  projectId,
181
+ platforms,
169
182
  stale_days,
170
183
  lock_timeout_ms,
171
184
  files: newFiles,
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@angar2/taskery",
3
- "version": "0.1.3",
4
- "description": "Lightweight task system for Claude Code — 1 메인 세션(또는 멀티세션) + 스킬 9종 + catastrophic hook 안전망 2종",
3
+ "version": "0.2.0",
4
+ "description": "Lightweight task system for Claude Code & Codex — 1 메인 세션(또는 멀티세션) + 스킬 9종 + catastrophic hook 안전망 2종",
5
5
  "bin": {
6
6
  "taskery": "bin/taskery.js",
7
7
  "create-taskery": "bin/create.js"
@@ -18,6 +18,7 @@
18
18
  },
19
19
  "keywords": [
20
20
  "claude-code",
21
+ "codex",
21
22
  "task-management",
22
23
  "ai-coding-workflow",
23
24
  "skills",
@@ -0,0 +1,20 @@
1
+ # taskery 격리 검증 서브에이전트 — /task-test가 spawn하는 독립 tester
2
+ #
3
+ # 클로드의 "Task tool 격리 세션"에 대응하는 코덱스 서브에이전트 정의.
4
+ # 메인 세션의 plan/dev 컨텍스트(확증편향) 없이 task.md만 보고 독립 검증한다.
5
+ #
6
+ # ⚠ model 명시 필수 — 생략하면 자식 모델 resolve가 service tier validation에서 실패한다.
7
+ # 환경의 사용 가능 모델에 맞게 조정한다(기본 gpt-5.5).
8
+
9
+ name = "task-tester"
10
+ description = "격리 검증 에이전트. 메인의 확증편향 없이 task.md만 보고 Test Plan을 독립 수행한다. /task-test가 spawn한다."
11
+ model = "gpt-5.5"
12
+ sandbox_mode = "read-only"
13
+ developer_instructions = """
14
+ 너는 격리된 tester다. 메인 세션의 가정을 일체 신뢰하지 말고, 넘겨받은 task.md 하나만 보고 자기완결적으로 검증하라.
15
+ - task.md의 ## Test Plan 시나리오를 순서대로 그대로 수행한다(임의 추가/스킵/변형 금지).
16
+ - [AUTO] 시나리오는 실제 명령을 실행하고 로그를 확보한다. 실행 없이 pass 처리 금지.
17
+ - [USER] 시나리오는 UNCERTAIN으로 분류하고 사용자 직접 검수 항목으로 리턴한다.
18
+ - 코드와 동작만 신뢰한다. "잘 될 것"이라는 가정 금지. 코드 파일 수정 금지.
19
+ - 검증을 마치면 task-test SKILL.md의 결과 형식(종합 판정 / 시나리오별 결과 / 검증·테스트 명령 결과)을 그대로 리턴한다.
20
+ """
@@ -0,0 +1,17 @@
1
+ # taskery Codex 프로젝트 설정 — PreToolUse hook 2종 등록 (git 안전망 + closed 보호)
2
+ #
3
+ # hook 스크립트 경로는 git 리포 루트 기준으로 해석한다($(git rev-parse --show-toplevel)).
4
+ # 설치 위치와 무관하게 동작하도록 절대경로 하드코딩을 피한다.
5
+ # 최초 1회 코덱스 `/hooks`로 trust 승인이 필요하다(승인 전 hook은 자동 skip).
6
+
7
+ [[hooks.PreToolUse]]
8
+ matcher = "^Bash$"
9
+ [[hooks.PreToolUse.hooks]]
10
+ type = "command"
11
+ command = 'bash "$(git rev-parse --show-toplevel)/.codex/hooks/git-guard.sh"'
12
+
13
+ [[hooks.PreToolUse]]
14
+ matcher = "apply_patch"
15
+ [[hooks.PreToolUse.hooks]]
16
+ type = "command"
17
+ command = 'bash "$(git rev-parse --show-toplevel)/.codex/hooks/closed-immutable.sh"'
@@ -0,0 +1,66 @@
1
+ #!/bin/bash
2
+ # .codex/hooks/closed-immutable.sh
3
+ # Codex PreToolUse(apply_patch) 훅 — closed 상태 task.md 재수정 차단 (코덱스 전용판)
4
+ #
5
+ # 클로드판(.claude/hooks/closed-immutable.sh)과의 차이:
6
+ # - 클로드: tool_input.file_path 1개를 읽음 (Write|Edit 도구)
7
+ # - 코덱스: 파일 편집이 apply_patch 도구 1개로 처리되며 tool_input.command 안에
8
+ # patch envelope 전체(`*** Begin Patch` ... `*** Update File: <경로>` ...)가 들어옴.
9
+ # file_path 키가 없으므로 command에서 `*** Update File:` / `*** Add File:` 헤더의
10
+ # 경로를 정규식 추출해 각각 검사한다. matcher = "apply_patch".
11
+ #
12
+ # 잡는 것:
13
+ # 1. .project/tasks/<vX.X>/<NNN>_<slug>.md (단일 파일)
14
+ # 2. .project/tasks/<vX.X>/TASK-<NNN>_<slug>/task.md (폴더 승격)
15
+ # → 헤더 status가 'closed'면 차단 (exit 2)
16
+ #
17
+ # 차단 안 함 (의도적): spec-diffs / screenshots / mockup — 역사적 자료 자유 수정.
18
+ # Add File은 대상 파일이 존재하지 않으므로 자연 통과(새 파일은 closed일 수 없음).
19
+ #
20
+ # 우회: 헤더 status를 closed → developing 등으로 직접 변경 후 편집.
21
+ # Catastrophic 영역 (완료 보호). 잘 지키면 작동 0회 (무해).
22
+
23
+ INPUT=$(cat)
24
+
25
+ # tool_input.command (apply_patch envelope 전체) 추출
26
+ CMD=$(echo "$INPUT" | python3 -c "
27
+ import sys, json
28
+ try:
29
+ print(json.load(sys.stdin).get('tool_input', {}).get('command', ''))
30
+ except Exception:
31
+ print('')
32
+ " 2>/dev/null)
33
+
34
+ # Update/Add File 헤더에서 대상 경로 추출 (한 patch에 여러 파일 가능)
35
+ PATHS=$(echo "$CMD" | grep -oE '^\*\*\* (Update|Add) File: .+$' | sed -E 's/^\*\*\* (Update|Add) File: //')
36
+
37
+ [ -z "$PATHS" ] && exit 0
38
+
39
+ while IFS= read -r FILE_PATH; do
40
+ # task.md 본 파일 패턴만 검사 (spec-diffs/screenshots/mockup 직속은 보호 대상 X)
41
+ echo "$FILE_PATH" | grep -qE '\.project/tasks/v[^/]+/([0-9]+_[^/]+\.md|TASK-[^/]+/task\.md)$' || continue
42
+ # 존재하는 파일만 (Add File 신규 생성은 통과)
43
+ [ -f "$FILE_PATH" ] || continue
44
+
45
+ # 헤더 status 추출 (5컬럼 표 — 생성일 / 플랜 / 유형 / 규모 / 상태)
46
+ STATUS=$(TM="$FILE_PATH" python3 -c "
47
+ import os, re
48
+ try:
49
+ content = open(os.environ['TM']).read()
50
+ except Exception:
51
+ print('')
52
+ raise SystemExit(0)
53
+ WL = r'(draft|planned|developing|developed|testing|tested|closed)'
54
+ m = re.search(rf'^\|[^|]*\|[^|]*\|[^|]*\|[^|]*\|\s*{WL}\s*\|\s*\$', content, re.MULTILINE)
55
+ print(m.group(1) if m else '')
56
+ " 2>/dev/null)
57
+
58
+ if [ "$STATUS" = "closed" ]; then
59
+ echo "closed-immutable: '$FILE_PATH' 차단. task 상태가 'closed' — 재수정 금지." >&2
60
+ echo " 의도적 재진입 시: sed로 헤더 status를 'developing' 등으로 변경 후 편집 가능." >&2
61
+ echo " closed task 코드 / spec-diffs / screenshots / mockup은 보호 영역 외 — 자유 수정. 코드 변경은 새 task로 처리가 정상 흐름." >&2
62
+ exit 2
63
+ fi
64
+ done <<< "$PATHS"
65
+
66
+ exit 0
@@ -0,0 +1,178 @@
1
+ # AGENTS.md
2
+
3
+ > 이 파일은 Codex 세션이 *매 세션 시작 시 자동 로드*하는 단일 진입점이다.
4
+ > 프로젝트 메타 + 검증 명령 + 룰 참조 + 스킬 9종 + 동기화 룰만 포함한다.
5
+ > 사람용 도메인 설명은 `.project/PROJECT.md`, 진입 가이드는 `.project/AGENT-GUIDE.md` 참조.
6
+ >
7
+ > Codex 환경 차이 (Claude와 다른 점만):
8
+ > - 스킬 본문: `.agents/skills/<스킬>/SKILL.md` (Claude의 `.claude/skills/`가 아님)
9
+ > - Hook: `.codex/config.toml`의 `[[hooks.PreToolUse]]`로 등록 — 최초 1회 `/hooks` trust 승인 필요
10
+ > - 격리 검증(/task-test): `.codex/agents/task-tester.toml` 서브에이전트로 spawn
11
+
12
+ ---
13
+
14
+ ## 프로젝트 메타
15
+
16
+ - **이름**: <프로젝트명>
17
+ - **타입**: <frontend / backend / fullstack / cli / library / other>
18
+ - **소개 (한 줄)**: <도메인 / 비전>
19
+ - **상세**: `.project/PROJECT.md` 참조
20
+
21
+ > `/project-init` 호출 시 위 4 항목 채워짐. 변경 필요하면 직접 편집.
22
+
23
+ ---
24
+
25
+ ## 메인 세션 최상위 룰
26
+
27
+ > 본 룰은 *모든 스킬 호출 / 사용자 발화 처리 / 작업 진행*에 적용. 위반 시 사용자 마찰 직결.
28
+
29
+ 1. **범위 준수** — 사용자가 명시한 범위/행동만 수행. 명시 외 자체 진입 영구 금지. 예: 사용자가 *"X부터 Y까지"* 지시 → Y 끝에서 정지 + 상태 보고 + 다음 단계 명시 호출 대기.
30
+ 2. **Skill 정식 발동** — task 단계는 반드시 해당 스킬(`.agents/skills/<스킬>/SKILL.md`)을 정식 호출. 가이드 본문을 머릿속 절차로 대체 금지 (컴팩트 세션 / 시스템 리마인더 인지 상태에서도 동일).
31
+ 3. **워크트리 자가 진단** — 세션 시작 시 현재 cwd가 *메인 워크트리*인지 *태스크 워크트리*인지 자가 판단 (`git rev-parse --show-toplevel` + `~/.taskery/worktrees/` 경로 비교). 태스크 워크트리면 *해당 진행중 태스크 컨텍스트*로 진입, 메인 워크트리면 *새 태스크 / 진행중 목록 인터뷰* 흐름.
32
+ 4. **모호 발화 자의 해석 금지** — *"워크트리 없이"* / *"메인에서"* / *"이 자리에서 그냥"* 류로 읽히는 발화 = 코어 규칙(메인 워크트리 = dev 전용) 충돌 신호. 즉시 정지 + 규칙 한 줄 명시 + 1줄 confirm 요청. 자의 해석 후 진행 영구 금지. 사용자 의도가 *오타/모호*일 가능성을 *워크트리 사용*으로 읽는 게 자연스러움.
33
+
34
+ ---
35
+
36
+ ## 멀티세션 워크트리 (0.1.2+)
37
+
38
+ > 멀티세션 = 같은 프로젝트에서 *여러 메인 세션이 독립 태스크를 병렬*로 진행. git worktree로 작업 폴더 격리, dev 머지 시 직렬화.
39
+
40
+ - **메인 워크트리 = dev 전용**: 메인 워크트리는 항상 `dev` 체크아웃 상태. 모든 태스크 작업은 *예외 없이* 별도 워크트리에서 수행. 메인 워크트리 HEAD를 dev에서 떼는 어떤 작업(`git checkout <task-branch>` / `git switch <task-branch>` / `git reset` HEAD 이동 / `git rebase` HEAD 이동 등)도 영구 금지. *"잠깐만 메인에서"* / *"테스트 한 번만 메인에서"* 같은 예외 발화도 거부 — 별도 워크트리에서 처리.
41
+ - **워크트리 위치**: `~/.taskery/worktrees/<projectId>/TASK-NNN_<출처>_<슬러그>/`
42
+ - `<projectId>`: `.taskery-manifest.json` `projectId` (8자 hex)
43
+ - `<출처>`: `BL-NNN` / `RM-NNN` / `DR`
44
+ - **`/task-init`이 워크트리 생성** + **`/task-close`가 dev 머지 + 워크트리 자동 제거** (자동 흐름. 보존 키워드 `keep` / `브랜치 남겨` 등 발화 시 둘 다 보존).
45
+ - **메인 메타 접근**: 워크트리에서 `.project/`, `AGENTS.md` 등 접근 시 *메인 워크트리 절대 경로* 사용:
46
+ ```sh
47
+ MAIN_WT=$(dirname "$(git rev-parse --path-format=absolute --git-common-dir)")
48
+ ```
49
+ - **수동 git 작업 시 정합성 보장 X** — taskery 명령(스킬 / `npx @angar2/taskery <서브>`)으로만 운영.
50
+ - **CLI 보조 명령**:
51
+ - `npx @angar2/taskery status` — 진행중 태스크 + 워크트리 + dev 머지 상태 요약
52
+ - `npx @angar2/taskery prune` — stale 워크트리 대화형 정리
53
+
54
+ 상세: `.project/rules/GIT_RULE.md` "멀티세션 워크트리 정책" 섹션.
55
+
56
+ 요건: git ≥ 2.31 (`--path-format=absolute` 옵션 필요).
57
+
58
+ ---
59
+
60
+ ## 백로그 (0.1.2+)
61
+
62
+ > `.project/tasks/<vX.X>/BACKLOG.md` = *버전별 task 후보 누적*. 사용자 발화로 1건씩 얕은 분석(개요 / 대상 영역) 곁들여 추가.
63
+
64
+ - **`/add-backlog`**: 사용자 *"~ 백로그에 추가"* 발화 → 얕은 분석(코드 탐색 X, 추정 수준) → BL-NNN 채번 → BACKLOG.md append (`[ ]` 대기).
65
+ - **`/task-init` 연동**: 사용자 *"백로그의 BL-NNN 진행"* 발화 → 해당 항목 *확인 마킹*. `[ ]` → `[x]` + `- TASK: TASK-NNN` 추가 (다회 진행 시 콤마).
66
+ - **체크박스 의미**: `[ ]` = 미확인 (task로 옮기지 않은 메모) / `[x]` = 확인 완료 (task로 옮김). **dev 머지 완료 의미 X** — 완료 여부는 `git log dev --grep 'BL-NNN'` + 브랜치명 추적 + `taskery status`.
67
+
68
+ 상세: `.agents/skills/add-backlog/SKILL.md`.
69
+
70
+ ---
71
+
72
+ ## 검증 명령
73
+
74
+ > *코드 상태 검증* (빌드 / 린트 / 타입체크). 테스트 실행은 본 섹션 X — `## 테스트 명령` 참조.
75
+ > `/task-dev` self-check / `/task-close` 최종 게이트가 *이 섹션을 단일 진실 소스로 참조*.
76
+ > 백틱(`...`) 안 명령 그대로 실행됨. 언어/프레임워크 따라 변경.
77
+
78
+ - 린트: `<예: npm run lint>`
79
+ - 타입체크: `<예: npm run typecheck>` (TypeScript / Flow / mypy 등 — 해당 시)
80
+ - 빌드: `<예: npm run build>`
81
+
82
+ > 위 항목 중 *프로젝트에 해당 없는 것*은 행 자체 삭제. 빈 백틱(``)은 사용 금지.
83
+
84
+ ---
85
+
86
+ ## 테스트 명령
87
+
88
+ > *테스트 실행* (단위 / 통합 / E2E 등). 코드 상태 검증은 `## 검증 명령` 참조.
89
+ > `/task-dev` 구현 후 테스트 + `/task-test` 격리 세션이 *이 섹션을 단일 진실 소스로 참조*.
90
+ > 백틱(`...`) 안 명령 그대로 실행됨. 언어/프레임워크 따라 변경.
91
+
92
+ - 단위 테스트: `<예: npm test>`
93
+ - 통합 테스트: `<예: npm run test:integration>` (있을 시)
94
+ - E2E 테스트: `<예: npx playwright test>` (있을 시)
95
+
96
+ > 위 항목 중 *프로젝트에 해당 없는 것*은 행 자체 삭제. 빈 백틱(``)은 사용 금지.
97
+
98
+ ---
99
+
100
+ ## 룰 문서 참조
101
+
102
+ | 룰 | 위치 | 역할 |
103
+ |----|------|------|
104
+ | TASK_DOC_RULE | `.project/rules/TASK_DOC_RULE.md` | task 문서 양식 (헤더 5컬럼 / 6 섹션 / 7 상태) |
105
+ | GIT_RULE | `.project/rules/GIT_RULE.md` (있으면) → 글로벌 `~/.claude/rules/GIT_RULE.md` (fallback) | git 정책 (브랜치 / 커밋 / 머지) |
106
+ | CHANGELOG_RULE | `.project/rules/CHANGELOG_RULE.md` | CHANGELOG 작성 정책 (위치 / 형식 / 필수 필드) |
107
+ | MOCKUP_RULE | `.project/rules/MOCKUP_RULE.md` | UX/UI task의 HTML 목업 위치 / 형식 / 네이밍 |
108
+ | `*.local.md` | `.project/rules/` | (옵션) 사용자 오버라이드 — `*.local.md` suffix는 npx 미터치 |
109
+
110
+ ---
111
+
112
+ ## 스킬 9종 — project > plan > task 위계 + 회고
113
+
114
+ | 스킬 | 레벨 | 역할 |
115
+ |------|------|------|
116
+ | `/project-init` | project | PROJECT/AGENT-GUIDE/LINKED-REPOS/.env 골격 생성 (1회성) |
117
+ | `/plan-init` | plan | `.project/plans/<vX.X>/` 안 기획 문서 작성 |
118
+ | `/task-init` | task | task.md 빈 골격 + status=draft |
119
+ | `/task-plan` | task | Requirements/Scope/Dev Plan/Test Plan 작성 (draft → planned) |
120
+ | `/task-dev` | task | Phase 순서 구현 + self-check (planned → developed) |
121
+ | `/task-test` | task | task-tester 서브에이전트 격리 검증 (developed → tested) |
122
+ | `/task-close` | task | 검증 명령 게이트 + 커밋 + dev 병합 (tested → closed) |
123
+ | `/add-backlog` | meta | 사용자 발화로 버전별 BACKLOG.md에 항목 1건 추가 (얕은 분석 + BL-NNN 채번 — 0.1.2+) |
124
+ | `/log-friction` | meta | FRICTION_LOG.md에 사용자 불편 한 행 기록 |
125
+
126
+ 본문은 `.agents/skills/<스킬>/SKILL.md` 참조.
127
+
128
+ ---
129
+
130
+ ## Hook 안전망 — Catastrophic 2종
131
+
132
+ > `.codex/config.toml`의 `[[hooks.PreToolUse]]`로 등록. 최초 1회 `/hooks` trust 승인 후 자동 작동.
133
+
134
+ | Hook | 영역 | 잡는 것 |
135
+ |------|------|--------|
136
+ | `git-guard.sh` | PreToolUse(Bash) | main/dev 직접 커밋 / `--force` / `--no-verify` / `branch -D` / `reset --hard` / `clean -fd` |
137
+ | `closed-immutable.sh` | PreToolUse(apply_patch) | `closed` 상태 task.md 본 파일 재수정 차단 (정상 흐름은 새 task로). spec-diffs / screenshots / mockup은 자유 수정 (역사적 자료) |
138
+
139
+ **잘 지키면 hook 작동 0회 (무해). catastrophic만 차단.**
140
+
141
+ > closed-immutable.sh는 코덱스 파일편집 도구(apply_patch)의 patch envelope에서 `*** Update File:` 경로를 파싱해 검사한다(Claude판은 `file_path` 키를 읽음 — 메커니즘만 다르고 효과 동일).
142
+
143
+ ---
144
+
145
+ ## 불편 데이터
146
+
147
+ - **누적**: `.project/FRICTION_LOG.md` — 메인이 한 줄씩 추가 (사용자 불만 발화 캐치 또는 사용자 명시)
148
+ - **등록**: `/log-friction` 스킬 — 사용자 명시 호출 / 불만 발화 캐치 / task-close 직후 마찰 신호 자체 감지 시 한 행 기록
149
+
150
+ ---
151
+
152
+ ## 동기화 룰 (사용자 의무)
153
+
154
+ > taskery는 *plan/ ↔ template/ 자동 빌드 X*. 사용자가 직접 정합 유지.
155
+
156
+ - **`.project/plans/<vX.X>/` 변경 시** → 관련 task의 `spec-diffs/` 갱신 (`/task-plan` Phase 0 흐름 — 진행 중 task 있으면)
157
+ - **`AGENTS.md` 검증 명령 변경 시** → 모든 스킬 + hook이 그대로 따름. 별도 동기화 불필요
158
+ - **룰(`*.md` in `.project/rules/`) 변경 시** → 스킬 instruction이 다음 호출부터 변경 반영
159
+ - **`*.local.md` 사용자 오버라이드** → `npx @angar2/taskery update`가 미터치. 코어 룰 갱신 시 `*.bak` 백업 후 사용자 confirm
160
+
161
+ ---
162
+
163
+ ## 멀티리포 통신 (해당 시)
164
+
165
+ - 송신: `.project/shared/sent/<filename>.md` 작성
166
+ - 수신: 연결 리포에서 `received/`로 카피 후 정독
167
+ - 처리 완료: `sent/completed/` 또는 `received/completed/`로 이동
168
+ - 연결 리포 경로: `.project/.env` (gitignore)
169
+ - 리포 목록: `.project/LINKED-REPOS.md`
170
+
171
+ ---
172
+
173
+ ## 메인 세션 진입 시
174
+
175
+ 1. 본 파일(`AGENTS.md`) 정독 — 검증 명령 + 룰 위치 + 스킬 목록 파악
176
+ 2. `.project/AGENT-GUIDE.md` 정독 — 활성 plan 버전 + 폴더 구조 + 작업 흐름
177
+ 3. `.project/plans/<활성버전>/PLAN.md` 정독 — 기획 문서 인덱스 + 진행 상태
178
+ 4. 사용자 발화 받기 → 적절한 스킬 호출 또는 직접 작업
File without changes