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
10 changes: 5 additions & 5 deletions apps/mcp-server/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,11 +97,11 @@ Claude Code hooks ──stdin──▶ ~/.claude/haribo/hook.mjs ──POST /hoo

T1(결정 사항과 다르면 먼저 묻기)을 이 저장소 밖에서도 띄우려고 터미널 알림과 같은 길(`hook.mjs` → `/hooks/<이벤트>`)로 옮기는 중이에요. 화면 글은 `@sil/t1`이 만들어요.

| Claude Code 이벤트 | 서버 주소 | 하는 일 |
| ----------------------------------------------------------------- | ------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `PostToolUse` (`mcp__haribo__show_standard_check`) | `POST /hooks/after-t1` | `screen`에 따라 ② 다른 결정 사항 / ③ 등록될 모습(받는 이슈는 서버가 DB에서 읽어요) / 예외 안내를 띄우고, Claude에게 다음에 물을 질문을 글자 그대로 넘겨요 |
| `PreToolUse` (`mcp__haribo__save_standard`) | `POST /hooks/before-save` | ③에서 보여 준 값과 다르면 막고(`deny`), 같으면 사람에게 허락을 물어요(`ask`) |
| `PostToolUse`·`PostToolUseFailure` (`mcp__haribo__save_standard`) | `POST /hooks/after-save` | ④ 등록 완료. 그사이 다른 사람이 먼저 바꿨으면(`VERSION_CONFLICT`) 예외 7 |
| Claude Code 이벤트 | 서버 주소 | 하는 일 |
| ----------------------------------------------------------------- | ------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `PostToolUse` (`mcp__haribo__show_standard_check`) | `POST /hooks/after-t1` | `screen`에 따라 ② 다른 결정 사항 / ③ 등록될 모습(받는 이슈는 서버가 DB에서 읽어요) / 예외 안내를 띄우고, Claude에게 다음에 물을 질문을 글자 그대로 넘겨요. 작업 중에 알아챈 T1에서 아니요면 요청 값이 코드에 이미 있을 때 기존 값으로 되돌리라고 넘겨요 (BDT-95) |
| `PreToolUse` (`mcp__haribo__save_standard`) | `POST /hooks/before-save` | ③에서 보여 준 값과 다르면 막고(`deny`), 같으면 사람에게 허락을 물어요(`ask`) |
| `PostToolUse`·`PostToolUseFailure` (`mcp__haribo__save_standard`) | `POST /hooks/after-save` | ④ 등록 완료. 그사이 다른 사람이 먼저 바꿨으면(`VERSION_CONFLICT`) 예외 7 |

- **③ 값은 팀원 PC에 둬요:** 서버는 쉬었다 깨어나면 메모리가 비어서 ③ 값을 기억하지 않아요. `hook.mjs`가 ③ 직후 `~/.claude/haribo/t1-<세션>-<결정 사항 ID>.json`을 남기고, 저장 직전·직후에 그 값을 `t1_preview`로 붙여 보내요. 저장이 끝나면 지워요. ③에서 취소·Esc로 끝나 남은 파일은 새 세션을 켤 때 하루 넘은 것부터 지워요.
- **③을 거치지 않은 저장은 막아요:** ③ 파일이 없으면 저장 직전 hook이 막고 `show_standard_check`로 ③부터 하라고 알려요. 안내문 2·3·10번도 저장 전에 ③을 보여 주게 했어요. ③과 다른 ID로 저장할 때도 파일이 없어 막혀요. 서버에 못 붙으면 막지 않아요.
Expand Down
20 changes: 20 additions & 0 deletions apps/mcp-server/src/hooks/t1-hooks.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,23 @@ describe('after-t1: ② 다른 결정 사항', () => {
expect(result.context).toContain('설명하는 글을 쓰지 마세요');
});

it('작업 중에 알아챘으면 아니요 뒤 요청 값이 코드에 있을 때 되돌리라고 넘겨요 (detect.ask.rollback-if-applied)', async () => {
const result = await afterT1({
screen: 'conflicts',
conflicts: {
trigger: 'agent',
request: '앱 알림 제목을 32자로 자르기',
conflicts: [CHANGE],
},
});
expect(result.context).toContain(T1_COPY.rolledBack);
expect(result.context).toContain('코드의 지금 값을 직접 읽어');
// 아직 요청 값이 아니면 고치지 않아요
expect(result.context).toContain(
`아무것도 고치지 않고 "${T1_COPY.notChanged}"`,
);
});

it('직접 요청이면 묻지 않고 이유로 가요. 되돌림이면 직접 요청이어도 먼저 물어요', async () => {
const conflicts = {
trigger: 'direct',
Expand Down Expand Up @@ -151,6 +168,9 @@ describe('after-t1: ② 다른 결정 사항', () => {
expect(revert.context).toContain(
`"question":"${T1_COPY.direct.revertAsk}"`,
);
// 직접 요청의 아니요는 별도 이슈라 되돌리라고 넘기지 않아요
expect(revert.context).not.toContain(T1_COPY.rolledBack);
expect(revert.context).toContain(T1_COPY.notChanged);
});

it('입력이 도구 모양과 다르면 아무것도 하지 않아요 (도구가 이미 거절해요)', async () => {
Expand Down
11 changes: 10 additions & 1 deletion apps/mcp-server/src/hooks/t1-hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,12 @@ function askJson(

const reasonStep = `예면 이유를 정해요. 사람이 대화에서 한 말로 알 수 있으면 한 문장으로 정리하고, 알 수 없으면 "${T1_COPY.reasonPrompt}"를 글로 물어요(지어내지 않아요). 그다음 ${toolName('show_standard_check')}(screen: "preview")로 ③을 보여 줘요.`;

/**
* 작업 중에 알아챘을 때 아니요 뒤 할 일. 요청 값이 이미 코드에 있으면 기존 값으로 되돌려요
* (detect.ask.rollback-if-applied). 비교는 도구가 아니라 에이전트가 코드를 읽어 직접 해요 (SIL-86)
*/
const agentDeclinedStep = `아니요면 저장하지 않아요. 요청 값이 코드에 이미 들어갔는지 코드의 지금 값을 직접 읽어 확인해요. 요청 값과 같으면 그곳을 기존 결정 사항 값으로 되돌리고 "${T1_COPY.rolledBack}"를 {requested}·{current}만 채워 글자 그대로 보여 준 뒤 멈추고 기다려요. 아직 요청 값이 아니면 아무것도 고치지 않고 "${T1_COPY.notChanged}"를 보여 준 뒤 멈추고 기다려요. 되돌리는 것 말고는 지금 값대로도 코드를 쓰지 않아요.`;

/** ② 다른 결정 사항 뒤 할 일 (예전 CLAUDE.md T1 2~4, 8) */
function conflictsContext(input: T1ConflictsInput): string {
const { ask, direct, notChanged } = T1_COPY;
Expand All @@ -110,7 +116,10 @@ function conflictsContext(input: T1ConflictsInput): string {
SHOWN,
`AskUserQuestion으로 이 질문을 글자 그대로 물어요(질문에 화면 내용을 다시 적지 않아요): ${askJson(question, ask.header, [ask.yes, ask.no])}`,
`선택지 대신 글로 답하면 "${ask.typedReply}"를 쓰고 같은 질문을 다시 물어요.`,
`아니요면 저장하지 않고 "${notChanged}"를 보여 준 뒤 멈추고 기다려요. 지금 값대로도 코드를 쓰지 않아요.`,
// 직접 요청(되돌림)의 아니요는 별도 이슈라 그대로 둬요 (BDT-95 하지 않는 것)
input.trigger === 'agent'
? agentDeclinedStep
: `아니요면 저장하지 않고 "${notChanged}"를 보여 준 뒤 멈추고 기다려요. 지금 값대로도 코드를 쓰지 않아요.`,
reasonStep,
].join('\n');
}
Expand Down
14 changes: 7 additions & 7 deletions packages/t1/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@ T1(결정 사항과 다르면 먼저 묻기)의 화면 모양, 문구, 저장

MCP 서버(`show_standard_check` 도구와 T1 hooks)가 가져다 써서 `apps/detect/src/t1/`에서 옮겼어요 (BDT-108, D49). 화면은 저장하지 않고 글자만 만들어요. ③ 받는 이슈만 팀 DB를 읽어요.

| 파일 | 내용 |
| ------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `src/copy.ts` | T1 문구 (착수 안내 3절 → FigJam "T1 결정 사항 등록" 10/13). 다른 결정 사항 화면(`conflicts`, 경우 1·2), 묻기, 아니요, 이유 묻기, ③ 등록될 모습, 취소, ④ 등록 완료, 직접 요청(`direct`), 되돌림 화면(`conflicts.revert`, 부건님 시안 10/16). 문구는 여기에만 둬요 |
| `src/save-input.ts` | 등록하기를 골랐을 때 저장 값. `source: ai_chat`, `aiDetected`는 에이전트가 알아챘으면 `true`·직접 요청이면 `false`(D61), `baseVersion`은 `get_standard`의 `currentVersion`, `alwaysOn`·`scope`는 지금 값. 새 결정 사항은 `buildT1NewSaveInput`(`baseVersion: 0`) |
| `src/screens.ts` | hooks가 띄우는 화면 모양(③ 등록될 모습, 다른 결정 사항 화면). 부건님 프로토타입 "T1 결정 사항 등록"(10/14)을 옮겼어요. 칸 계산은 `text.ts` |
| `src/issues.ts` | ③ 이슈 표에 넣을 받는 이슈를 core `listIssues()`로 팀 DB에서 읽어요(팀원 `.env`의 Supabase 주소·공용 키). 알리기와 같은 규칙(담당자 있음, 바꾼 본인 뺌). 못 읽으면 표 칸 이름만 보여요 |
| `src/text.ts` | 화면 칸 계산(한글·이모지는 두 칸), 줄 나누기와 자르기 |
| 파일 | 내용 |
| ------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `src/copy.ts` | T1 문구 (착수 안내 3절 → FigJam "T1 결정 사항 등록" 10/13). 다른 결정 사항 화면(`conflicts`, 경우 1·2), 묻기, 아니요, 아니요 뒤 되돌림(`rolledBack`, BDT-95), 이유 묻기, ③ 등록될 모습, 취소, ④ 등록 완료, 직접 요청(`direct`), 되돌림 화면(`conflicts.revert`, 부건님 시안 10/16). 문구는 여기에만 둬요 |
| `src/save-input.ts` | 등록하기를 골랐을 때 저장 값. `source: ai_chat`, `aiDetected`는 에이전트가 알아챘으면 `true`·직접 요청이면 `false`(D61), `baseVersion`은 `get_standard`의 `currentVersion`, `alwaysOn`·`scope`는 지금 값. 새 결정 사항은 `buildT1NewSaveInput`(`baseVersion: 0`) |
| `src/screens.ts` | hooks가 띄우는 화면 모양(③ 등록될 모습, 다른 결정 사항 화면). 부건님 프로토타입 "T1 결정 사항 등록"(10/14)을 옮겼어요. 칸 계산은 `text.ts` |
| `src/issues.ts` | ③ 이슈 표에 넣을 받는 이슈를 core `listIssues()`로 팀 DB에서 읽어요(팀원 `.env`의 Supabase 주소·공용 키). 알리기와 같은 규칙(담당자 있음, 바꾼 본인 뺌). 못 읽으면 표 칸 이름만 보여요 |
| `src/text.ts` | 화면 칸 계산(한글·이모지는 두 칸), 줄 나누기와 자르기 |

테스트는 `src/t1.test.ts`예요 (`pnpm test`). hooks 테스트는 `apps/mcp-server/src/hooks/t1-hooks.test.ts`예요.
7 changes: 7 additions & 0 deletions packages/t1/src/copy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,13 @@ export const T1_COPY = {
* 사람이 "40자로 작업해 줘"처럼 어떻게 할지 다시 말할 때만 진행해요 (10/13 경하님)
*/
notChanged: '기존 결정 사항을 그대로 유지할게요. 다른 작업을 시작해 주세요.',
/**
* 아니요를 골랐는데 요청 값이 이미 코드에 들어가 있을 때. 기존 값으로 되돌린 뒤 notChanged 대신 보여 주고 멈춰요
* (detect.ask.rollback-if-applied, BDT-95). {requested}는 요청 값, {current}는 기존 결정 사항 값이에요.
* 값마다 받침이 달라 조사는 괄호 밖 "값"에 붙여요 (데모 10/26: "6500를")
*/
rolledBack:
'코드에 이미 들어간 값({requested})을 기존 결정 사항 값({current})으로 되돌렸어요. 다른 작업을 시작해 주세요.',
/** 대화에서 이유를 유추할 수 없을 때 글로 묻기. 답을 한 문장으로 정리해 ③에 넣어요 */
reasonPrompt:
'이 결정 사항을 바꾸는 이유를 작성해 주세요. 작성해 주신 내용을 기반으로 정리해 드릴게요.',
Expand Down
1 change: 1 addition & 0 deletions packages/t1/src/t1.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ describe('T1 문구', () => {
}
const messages = [
T1_COPY.notChanged,
T1_COPY.rolledBack,
T1_COPY.reasonPrompt,
T1_COPY.cancelled.withTask,
T1_COPY.cancelled.noTask,
Expand Down
Loading