Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .claude-plugin/marketplace.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"name": "haku",
"source": "./",
"description": "Git-issue driven planning, orchestrator/worker development and verified merge-to-close for Claude Code (GitHub first)",
"version": "0.16.31",
"version": "0.16.32",
"category": "workflow"
}
]
Expand Down
2 changes: 1 addition & 1 deletion .claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "haku",
"version": "0.16.31",
"version": "0.16.32",
"description": "HAKU - Harness Assistant Keeping Users in the loop. Git-issue driven story planning, orchestrator/worker development and verified merge-to-close for Claude Code (GitHub first, GitLab next).",
"author": { "name": "dongqdev" },
"license": "MIT",
Expand Down
57 changes: 28 additions & 29 deletions docs/HANDOFF.md
Original file line number Diff line number Diff line change
@@ -1,35 +1,34 @@
# 인수인계 (2026-11-01)
# 인수인계 (2026-11-02)

다른 에이전트가 바로 이어받을 수 있도록 현재 상태를 적어 둡니다. 이어받은 뒤 이 파일은 지워도 됩니다.
다른 세션이 이 파일만 보고 이어받을 수 있게 적었습니다. 이어받은 뒤 이 파일은 지워도 됩니다(지우는 것이 기본).

## 프로젝트 원칙
- haku는 최신 트렌드 기반의 **가벼운 하네스**입니다. 핵심 목표는 **에이전트가 쉬지 않는 것**(감시 체계나 루프로 작업을 계속 이어감)입니다.
- 사용자 지시와 프로그램 방향에 항상 의문을 가지세요. 최신 트렌드에 맞지 않거나 더 좋은 길이 있으면 먼저 제안합니다.
- 답변은 한글, 사용자 결정·승인은 선택지로 받습니다.
## 원칙
- haku는 최신 트렌드 기반의 **가벼운 하네스**, 핵심 목표는 **에이전트가 쉬지 않는 것**입니다.
- 사용자 지시와 방향에 항상 의문을 갖고, 더 나은 길이 있으면 먼저 제안합니다.
- 답변은 한글, 사용자 결정·승인은 선택지로 받습니다. 병합은 사용자 승인 후에만 합니다.
- 이슈 하나당 작은 PR 하나. 공개 파일이 바뀌면 `node scripts/bump-version.js origin/main`(커밋 후 실행)으로 patch +1.
- 검증: `node --test tests/*.test.js`, `node scripts/check-eol.js --worktree`.

## 작업 규칙
- 이슈 하나당 작은 PR 하나. 버전은 `node scripts/bump-version.js origin/main`으로 올립니다(공개 파일이 바뀐 PR마다 patch +1).
- 병합 전: Claude 코드리뷰(`/code-review`), CI(3개 OS·version·paths) 통과, 사용자 승인 후 squash 병합. 본인 PR이라 GitHub 승인은 불가합니다.
- 전체 테스트: `node --test tests/*.test.js`, 줄바꿈: `node scripts/check-eol.js --worktree`.
## 현재 상태
- main은 0.16.31. PR #134(브랜치 `claude/project-thread-mawt75`, 0.16.32)가 열려 있고 CI는 통과했으며 사용자 승인 후 squash 병합 대기입니다.
- 내용: `hooks/worker-stop.js`의 `claimBlock`에서 `session_id` 폴백 제거(같은 세션의 다른 워커가 검사 없이 통과하던 문제), 테스트 추가, 이전 HANDOFF 정리.
- 열린 이슈 없음. 실세션 배너/ask 훅 검증은 사용자가 보류했습니다.

## 이번 세션 결과
| 이슈 | 상태 | PR |
|---|---|---|
| #117 워커 워크트리 의존성 준비 | 병합 (0.16.26, `linkFromMain`) | #128 |
| #91 retro-done --skip 문구 | 병합 (0.16.27) | #129 |
| #113 deferred 재개 제안 | 닫음(범위가 커서 가벼운 하네스 원칙에 안 맞음, 필요하면 status에 deferred 건수 표시 정도로 재제안) | - |
| #111 reconcile --apply --only/--except | PR 열림, 병합 대기 (0.16.28) | 이 PR |
## 결정 대기 (사용자 선택 필요)
"쉬지 않는 에이전트" 갭을 어떻게 메울지:
1. **문서+종료조건만 (권장)**: `skills/build/SKILL.md` 종료 조건을 `/goal` 형식(측정 가능한 종료 상태, 확인 방법, 턴 상한 문구)으로 쓰고, `docs/resume.md`를 공식 비교표로 갱신. 새 코드 없음.
2. 위 + 진전 없는 반복 감지 Stop 훅(같은 상태 반복 시 멈춤).
3. 자체 자동 재시작 루프 구현(가장 무겁고 공식 기능과 겹침).
4. 보류.
사용자가 아직 고르지 않았습니다. 답이 없으면 먼저 사용자에게 다시 묻습니다.

## 남은 이슈 (우선순위 순)
1. **#111** 이 PR을 코드리뷰·CI 확인 후 사용자 승인 받아 병합.
2. **#119** plan 검증: Scope의 구체 파일 경로가 기본 브랜치에 있는지 경고. 힌트: `scripts/lib/publish.js`(검증 모드)와 `tests/publish-scope-warning.test.js`. 와일드카드는 건너뛰고, 확장자만 다른 파일을 제안하며, 새 파일 표시(`"new": true` 등)는 경고에서 뺍니다. plan 스킬 승인 화면에 한 줄 추가.
3. **#112** build 스킬(`skills/build/SKILL.md`)에 차단된 사이트의 사용자 제공 링크 처리 절차를 문서로 추가(코드 변경 없음, 인수 기준은 이슈 본문).
4. **#92** 미해결 표식 검사 빈틈(`scripts/lib/publish.js`, `tests/unresolved-marker.test.js`): 변형 표기, id 검사, CLI 종료 코드 시험, 우회 안내.
## 트렌드 조사 요약 (2026-11-02, 웹 조사; 일부는 2차 자료)
- 방향 적합: 얇은 하네스, 상태를 이슈/PR/라벨/워크트리에 두기(Ralph loop의 git 호스팅 변형), 서로 대화하지 않는 격리 워커, 훅 게이트는 업계 수렴 방향과 일치. agent teams는 아직 실험 단계라 도입 불필요.
- 공식 기능: `/goal`(Stop 훅 기반, 평가자는 대화 증거만 봄, 세션 재개 시 복원), `/loop`(세션 범위, 7일 만료, 재개 시 self-paced 미복원), 클라우드 scheduled tasks(구 routines: 스케줄/API/GitHub 이벤트 트리거, 최소 1시간, 이벤트마다 새 세션).
- 갭: (1) 쉬지 않기가 기본 흐름에 없고 `docs/resume.md` 수동 설정뿐, (2) build 종료 조건이 `/goal` 형식이 아님, (3) resume.md가 낡았을 수 있음, (4) 무인 실행 안전장치(턴/예산 상한, 진전 없음 감지) 부족.
- 출처: code.claude.com/docs/en/goal, /scheduled-tasks, /routines, /agent-teams, /hooks-guide; anthropic.com/engineering/effective-harnesses-for-long-running-agents; addyosmani.com/blog/long-running-agents; arxiv.org/pdf/2609.00006

## 이 세션에서 알게 된 점
- `linkFromMain`은 심볼릭 링크 이름을 저장소 로컬 `info/exclude`에 추가합니다(제거하지 않음).
- 지정 브랜치 `claude/project-thread-g9b6id`를 병합 후 재사용할 때는 `git checkout -B <branch> origin/main` 뒤 `git merge -s ours origin/<branch>`로 강제 푸시 없이 올렸습니다.

## 추가 개선 후보 (사용자 확인 전 제안)
- 실제 세션에서 배너/ask 훅 동작 검증(자동 테스트는 있으나 실세션 확인 기록 없음).
- "쉬지 않는 에이전트" 목표 갭: 워커가 멈췄을 때 감시·재시작하는 루프(예: `/loop` 또는 `Stop` 훅 기반 자동 이어가기)가 문서에만 있고 기본 흐름에 묶여 있지 않음.
## 다음 순서
1. 사용자에게 PR #134 병합 승인과 위 결정을 받는다.
2. 선택에 따라 새 이슈/PR 하나로 진행(브랜치는 병합 후 `git checkout -B <branch> origin/main`).
3. 인계 완료 후 이 파일 삭제.
2 changes: 1 addition & 1 deletion hooks/worker-stop.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ function decide(input) {
// Marker for "this agent was already sent back once". Returns true when it was created now (first block), false when it
// already existed or cannot be told (then the worker passes). No key in the input means no block.
function claimBlock(input, dir) {
const id = input.agent_id || input.agent_transcript_path || input.session_id;
const id = input.agent_id || input.agent_transcript_path;
if (typeof id !== 'string' || !id) return false;
try {
const name = 'haku-worker-stop-' + crypto.createHash('sha256').update(id).digest('hex').slice(0, 32);
Expand Down
7 changes: 7 additions & 0 deletions tests/hook-worker-stop.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,10 @@ test('without stop_hook_active in the input it blocks an agent once, then passes
assert.strictEqual(run(one).status, 0);
assert.strictEqual(run(JSON.stringify({ agent_type: 'worker', last_assistant_message: 'done.' })).status, 0);
});

test('the session id alone is never a key: other workers of the same session are not skipped', () => {
const s = o => JSON.stringify({ session_id: 'sess', agent_type: 'worker', last_assistant_message: 'done.', ...o });
assert.strictEqual(run(s({})).status, 0);
assert.strictEqual(run(s({ agent_id: 'w-one' })).status, 2);
assert.strictEqual(run(s({ agent_id: 'w-two' })).status, 2);
});
Loading