@angar2/taskery 1.0.2 → 1.0.3

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/finish.js CHANGED
@@ -286,6 +286,15 @@ function writeChangelog(main, st, goal) {
286
286
  return file;
287
287
  }
288
288
 
289
+ // orca worktree rm 뒤 폴더가 사라지기를 기다리는 최대 시간
290
+ const ORCA_RM_WAIT_MS = 30 * 1000;
291
+
292
+ // 경로가 사라질 때까지 기다린다(최대 ms)
293
+ function waitGone(p, ms) {
294
+ const until = Date.now() + ms;
295
+ while (fs.existsSync(p) && Date.now() < until) Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 200);
296
+ }
297
+
289
298
  // 워크트리·브랜치 정리 — 커밋 안 된 변경이 있으면 남기고, 브랜치는 병합된 것만 지운다.
290
299
  // force는 prune에서 사용자가 확인했을 때만 — 병합 안 된 브랜치도 지운다(복구 명령 출력)
291
300
  function cleanup(main, st, { force = false } = {}) {
@@ -307,6 +316,8 @@ function cleanup(main, st, { force = false } = {}) {
307
316
  try {
308
317
  execFileSync('orca', ['worktree', 'rm', '--worktree', `path:${st.worktree}`, '--json'], { stdio: ['ignore', 'pipe', 'pipe'] });
309
318
  notes.push(`Orca 워크트리를 지웠다: ${st.worktree}`);
319
+ // Orca는 답한 뒤에도 뒤에서 폴더를 지우고 병합된 브랜치도 함께 지운다 — 폴더가 사라질 때까지 기다린 뒤 브랜치를 본다
320
+ waitGone(st.worktree, ORCA_RM_WAIT_MS);
310
321
  } catch (e) {
311
322
  notes.push(`Orca 워크트리를 지우지 못해 남겼다: ${((e.stderr || '') + '').trim() || e.message}`);
312
323
  branchFree = false;
@@ -325,7 +336,10 @@ function cleanup(main, st, { force = false } = {}) {
325
336
  }
326
337
  if (!merged && !force) notes.push(`브랜치 ${st.branch}는 부모에 병합되지 않아 남겼다.`);
327
338
  else if (!branchFree || L.currentBranch(main) === st.branch) notes.push(`브랜치 ${st.branch}를 지우지 못해 남겼다(사용 중).`);
328
- else if (L.git(main, ['branch', merged ? '-d' : '-D', st.branch], { allowFail: true }) === null) notes.push(`브랜치 ${st.branch}를 지우지 못해 남겼다.`);
339
+ else if (L.git(main, ['branch', merged ? '-d' : '-D', st.branch], { allowFail: true }) === null) {
340
+ // 그 사이 Orca가 지웠으면 실패가 아니다
341
+ notes.push(L.branchExists(main, st.branch) ? `브랜치 ${st.branch}를 지우지 못해 남겼다.` : `브랜치 ${st.branch}가 지워졌다. 복구: git branch ${st.branch} ${tip}`);
342
+ }
329
343
  else notes.push(`브랜치 ${st.branch}를 지웠다. 복구: git branch ${st.branch} ${tip}`);
330
344
  return notes;
331
345
  }
package/bin/lib.js CHANGED
@@ -716,6 +716,7 @@ module.exports = {
716
716
  docAbs,
717
717
  docShown,
718
718
  readDoc,
719
+ fenceMask,
719
720
  section,
720
721
  isNewDoc,
721
722
  meaningfulLines,
package/bin/orche.js CHANGED
@@ -10,6 +10,8 @@ const WAIT_MS = Number(process.env.TASKERY_WAIT_REPORTS_MS) || 60 * 60 * 1000;
10
10
  const POLL_MS = Math.min(1000, WAIT_MS);
11
11
  // 첫 지시문을 보낸 뒤 턴 시작을 지켜보는 시간(orca terminal send --wait-submit)
12
12
  const SUBMIT_WAIT_SEC = 30;
13
+ // 턴 시작 미관측 뒤 Enter를 보내고 화면을 읽기 전 기다리는 시간
14
+ const SCREEN_SETTLE_MS = 2000;
13
15
 
14
16
  function reportsFile(main) {
15
17
  return path.join(main, '.taskery', 'reports.log');
@@ -197,13 +199,23 @@ async function orcaDispatchTask(ctx, a) {
197
199
  if (r.refused) L.fail(`orca-dispatch-task: ${label} 탭(${handle})이 첫 지시문을 받지 않았다 — ${r.refused}\n${manual}`);
198
200
  return r.observed;
199
201
  };
200
- // 턴 시작이 관측되지 않으면 화면을 보고, 지시문이 보이지 않을 때만 한 번 더 보낸다(준비 전 입력이 사라진 경우)
202
+ // Orca가 턴 시작을 관측했다고 답해도 지시문이 입력창에 제출되지 않은 채 남은 적이 있다(stash TASK-105).
203
+ // 그래서 관측 여부와 무관하게 Enter만 한 번 더 보낸다 — 입력창이 비었거나 이미 진행 중이면 영향이 없다
204
+ const observed = send();
201
205
  let sendNote = null;
202
- if (!send()) {
206
+ try {
207
+ orca(['terminal', 'send', '--terminal', handle, '--enter']);
208
+ } catch (e) {
209
+ if (observed) sendNote = `확인용 Enter를 보내지 못했다(${e.message}) — 탭에서 진행을 확인한다.`;
210
+ else L.fail(`orca-dispatch-task: ${label} 탭(${handle})에서 첫 지시문의 턴 시작이 관측되지 않았고, Enter도 보내지 못했다 — ${e.message}\n'orca terminal read --terminal ${handle}'로 화면을 읽어, 지시문이 입력창에 남아 있으면 Enter를, 없으면 아래 첫 지시문을 보낸다.\n--- 첫 지시문 ---\n${text}`);
211
+ }
212
+ // 관측되지 않았으면 화면을 본다 — 지시문이 보이면 Enter로 들어간 것이고, 없으면 준비 전에 사라진 것이라 한 번 더 보낸다
213
+ if (!observed) {
214
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, SCREEN_SETTLE_MS);
203
215
  if (onScreen(handle, text.split('\n')[0].split(' ').slice(0, 4).join(' '))) {
204
- sendNote = '턴 시작은 관측되지 않았지만 탭 화면에 첫 지시문이 보인다 — 탭에서 진행을 확인한다.';
216
+ sendNote = '턴 시작이 관측되지 않아 Enter를 한 번 더 보냈고, 탭 화면에 첫 지시문이 보인다 — 탭에서 진행을 확인한다.';
205
217
  } else if (send()) {
206
- sendNote = '첫 전송은 턴 시작이 관측되지 않고 화면에도 없어 한 번 더 보냈다.';
218
+ sendNote = '첫 전송은 턴 시작이 관측되지 않고 Enter 뒤 화면에도 없어 한 번 더 보냈다.';
207
219
  } else {
208
220
  L.fail(`orca-dispatch-task: ${label} 탭(${handle})에 첫 지시문을 두 번 보냈지만 턴 시작이 관측되지 않았다.\n${manual}`);
209
221
  }
package/bin/prepare.js CHANGED
@@ -218,12 +218,26 @@ async function installPackages(dir) {
218
218
  return [`npm ci 실패(${took}) — 워크트리에서 직접 설치한다(${dir}):\n${L.tail(r.out, 15)}`];
219
219
  }
220
220
 
221
- // PLAN.md 목록 항목 줄 끝에 태스크 번호를 붙인다(`--item`)
222
- function linkItem(main, plan, item, label) {
221
+ // PLAN.md `## 태스크 목록` 절에서 항목 줄을 찾는다. 다른 절의 번호 목록(목표 등)은 보지 않는다. 없으면 멈춘다
222
+ function itemLine(main, plan, item) {
223
223
  const file = path.join(main, '.taskery', 'plans', plan, 'PLAN.md');
224
224
  const lines = fs.readFileSync(file, 'utf8').split('\n');
225
- const idx = lines.findIndex((l) => new RegExp(`^\\s*${item}\\.\\s`).test(l));
226
- if (idx === -1) L.fail(`prepare-task: ${plan}/PLAN.md에 항목 ${item}이 없다. 목록 형식은 '${item}. <한 줄 설명> — 선행: <항목 번호들 또는 없음>'이다.`);
225
+ const fenced = L.fenceMask(lines);
226
+ const start = lines.findIndex((l, i) => !fenced[i] && l.trim() === '## 태스크 목록');
227
+ let idx = -1;
228
+ for (let i = start + 1; start !== -1 && i < lines.length && !(!fenced[i] && /^## /.test(lines[i])); i++) {
229
+ if (!fenced[i] && new RegExp(`^\\s*${item}\\.\\s`).test(lines[i])) {
230
+ idx = i;
231
+ break;
232
+ }
233
+ }
234
+ if (idx === -1) L.fail(`prepare-task: ${plan}/PLAN.md \`## 태스크 목록\`에 항목 ${item}이 없다. 목록 형식은 '${item}. <한 줄 설명> — 선행: <항목 번호들 또는 없음>'이다.`);
235
+ return { file, lines, idx };
236
+ }
237
+
238
+ // 항목 줄 끝에 태스크 번호를 붙인다(`--item`)
239
+ function linkItem(main, plan, item, label) {
240
+ const { file, lines, idx } = itemLine(main, plan, item);
227
241
  const linked = lines[idx].match(/TASK-\d+/g) || [];
228
242
  lines[idx] = `${lines[idx].replace(/\s+$/, '')} (${label})`;
229
243
  fs.writeFileSync(file, lines.join('\n'));
@@ -236,6 +250,7 @@ async function prepareTask(ctx, a) {
236
250
  const rule = L.readGitRule(main);
237
251
  const switches = validate(a, rule);
238
252
  const plan = resolvePlan(main, a.plan);
253
+ if (a.item != null) itemLine(main, plan, a.item); // 브랜치·워크트리를 만들기 전에 확인한다
239
254
  const noBranch = !!a['no-branch'];
240
255
  const noWorktree = noBranch || !!a['no-worktree'];
241
256
  const parent = checkMain(main);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@angar2/taskery",
3
- "version": "1.0.2",
3
+ "version": "1.0.3",
4
4
  "description": "AI 코딩 에이전트(Claude Code·Codex)에게 일을 태스크 단위로 맡겨 기획·개발·테스트·병합까지 진행하게 하는 작업 체계",
5
5
  "bin": {
6
6
  "taskery": "bin/taskery.js",