jutell 1.1.0 → 2.0.1

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
@@ -1,39 +1,186 @@
1
- # jutell
1
+ # JuTell
2
2
 
3
- **Your coding agent writes the code. JuTell helps you understand what happened.**
3
+ **Tell your agent what you mean. Understand what it did.**
4
4
 
5
- JuTell sits beside Codex, Claude Code, or OpenCode and turns their work into a plain-language report: what changed, what's actually verified, what's still unknown, and what to do next.
5
+ [![npm version](https://img.shields.io/npm/v/jutell.svg)](https://www.npmjs.com/package/jutell)
6
+ [![license: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/ju0o/jutell/blob/main/LICENSE)
7
+ [![node: >=18](https://img.shields.io/badge/node-%3E%3D18-brightgreen.svg)](https://nodejs.org)
8
+ ![Codex: Supported](https://img.shields.io/badge/Codex-Supported-brightgreen)
9
+ ![Claude Code: Beta](https://img.shields.io/badge/Claude%20Code-Beta-yellow)
10
+ ![OpenCode: Beta](https://img.shields.io/badge/OpenCode-Beta-yellow)
11
+
12
+ For people who can tell a coding agent what they want, but don't want to read code or logs just to
13
+ know whether it actually worked.
6
14
 
7
15
  ```bash
8
16
  npm install -g jutell
9
17
  jutell
10
18
  ```
11
19
 
12
- `jutell` finds the coding agents you already have installed, asks for one approval, connects them, and hands you straight back to your normal session — no per-agent setup command needed for a normal first run.
20
+ That's the whole setup. `jutell` finds the coding agents already installed on your machine, asks
21
+ for one approval, connects them, and hands you straight back to your normal session.
13
22
 
14
- | Agent | Status |
15
- |---|---|
16
- | Codex | Supported |
17
- | Claude Code | Beta |
18
- | OpenCode | Beta |
23
+ JuTell sits **beside** a coding agent you already use — Codex, Claude Code, or OpenCode. It checks
24
+ project facts before work and separates verified from assumed after it. It is not an AI model, and
25
+ it does not provide or replace your agent.
19
26
 
20
- To connect (or reconnect) one specific agent by hand, use `jutell use codex` / `jutell use claude` / `jutell use opencode` — this is the manual/repair path, not the normal first run.
27
+ ## The problem it solves
21
28
 
22
- **한국어 사용자라면:** 전체 문서와 한국어 안내는 [GitHub 저장소의 README.ko.md](https://github.com/ju0o/jutell/blob/main/README.ko.md)에 있습니다.
29
+ AI coding agents made writing code faster. They didn't fix two older problems:
23
30
 
24
- Full docs, images, and the complete feature walkthrough live on [GitHub](https://github.com/ju0o/jutell#readme).
31
+ - **Before work** — it's hard to explain exactly what you mean, and an agent that guesses wrong can
32
+ quietly do the wrong thing.
33
+ - **After work** — a wall of diffs and "I tested it" don't tell you what's real and what's assumed.
25
34
 
26
- <details>
27
- <summary>Build from source instead (contributors / verifying the repo directly)</summary>
35
+ JuTell doesn't write code. It makes the conversation around the code honest, on both ends.
28
36
 
29
- Most people should use the npm install above.
37
+ ## What it actually looks like
30
38
 
31
- ```bash
32
- cd packages/cli
33
- npm install
34
- npm pack
35
- npm install -g ./jutell-1.1.0.tgz
39
+ A real first run, on a project with OpenCode installed:
40
+
41
+ ```console
42
+ $ jutell
43
+ JuTell
44
+
45
+ Found coding agents:
46
+
47
+ Codex not detected
48
+ OpenCode found
49
+ Claude Code not detected
50
+
51
+ Connecting JuTell...
52
+ OpenCode connected.
53
+
54
+ Open a new conversation in OpenCode and JuTell applies automatically.
55
+ ```
56
+
57
+ Then check it honestly — note that it does **not** claim the agent session picked it up, because it
58
+ cannot see that from here:
59
+
60
+ ```console
61
+ $ jutell status
62
+ JuTell status
63
+
64
+ CLI: 2.0.1
65
+ Skill: installed
66
+ AGENTS.md: JuTell block present
67
+ OpenCode MCP: enabled (auto-start on new session)
68
+ Codex MCP: not registered
69
+ Current agent session applied: needs direct confirmation
70
+ Profile: balanced
71
+ Telemetry: disabled
72
+ External transmission: none
36
73
  ```
37
- </details>
38
74
 
39
- The legacy `beginner-bridge` command is a compatibility alias for the same functionality and tells you to switch to `jutell` when you run it. The CLI does not collect or transmit your project code, prompts, AI answers, Git diffs, or secrets.
75
+ And when something looks wrong:
76
+
77
+ ```console
78
+ $ jutell doctor
79
+ OK Node version: Node 22.22.1
80
+ OK Skill file: verified
81
+ OK Skill version: 2.0.1 (installed copy matches)
82
+ OK MCP server live connection (Stdio): JuTell server responded with 5 tools
83
+ OK External transmission code: no outbound patterns in the MCP build
84
+ Check Current agent session applied: must be confirmed in that agent's session
85
+ Warning Codex MCP: not registered
86
+ ```
87
+
88
+ `doctor` marks every line OK / warning / needs-a-closer-look, and never prints full paths or secret
89
+ values.
90
+
91
+ ## The rule it follows
92
+
93
+ JuTell does not call something "verified" unless it actually checked it. On a real run that changed
94
+ an empty-search message's styling, it reported:
95
+
96
+ | | |
97
+ |---|---|
98
+ | Code checked | the style values actually changed |
99
+ | Test checked | the existing test still passed |
100
+ | Real browser view | **not checked** — no browser was available |
101
+ | Your action | Open the screen once and look. |
102
+
103
+ It does not turn "looks right in code" into "confirmed on screen."
104
+
105
+ If you are verifying the source instead of installing from npm, `npm pack` in
106
+ `packages/cli` creates `jutell-2.0.1.tgz`; ordinary users do not need this path.
107
+
108
+ ## Supported agents & platforms
109
+
110
+ | What you need | Status |
111
+ |---|---|
112
+ | **Codex** | Supported |
113
+ | **Claude Code** | Beta |
114
+ | **OpenCode** | Beta |
115
+ | Windows | Tested |
116
+ | Ubuntu | Limited testing |
117
+ | macOS | Available / unverified |
118
+
119
+ You need a coding agent first — JuTell connects to one you already installed.
120
+
121
+ ## Commands
122
+
123
+ | Command | What it does |
124
+ |---|---|
125
+ | `jutell` | Finds installed agents and offers to connect them. |
126
+ | `jutell status` | Installation, connection, profile, and feature status. |
127
+ | `jutell doctor` | Checks for setup problems. |
128
+ | `jutell use codex` / `claude` / `opencode` | Connect one agent by hand (repair path). |
129
+ | `jutell on` / `jutell off` | Turn the connection on or off. |
130
+ | `jutell upgrade` | Refresh the installed Skill/config/MCP. |
131
+ | `jutell uninstall` | Remove JuTell's managed setup. |
132
+
133
+ Reports can be tuned with a project `.jutell.json` — profiles `minimal`, `balanced`, `learning`,
134
+ `detailed` change explanation length, never the underlying facts or risk.
135
+
136
+ ## First things to try
137
+
138
+ 1. "Change the login button to blue."
139
+ 2. "Please make signup simpler."
140
+ 3. "Make the empty search box show a helpful message."
141
+
142
+ For 1 and 3, JuTell should let your agent get on with it. For "make signup simpler," it should read
143
+ the project first and ask **one** grounded question only if a real choice remains — not turn your
144
+ request into a questionnaire.
145
+
146
+ ## What JuTell is — and is not
147
+
148
+ **Is:** a communication layer beside your coding agent · clarifies material intent before work ·
149
+ explains actual work and evidence after it.
150
+
151
+ **Is not:** an AI model · a replacement coding agent · a correctness guarantee · a full autonomous
152
+ debugger · a multi-agent orchestrator.
153
+
154
+ ## Trust and privacy
155
+
156
+ JuTell explains work using files, Git, and command output already on your computer. It does not
157
+ collect or send your project code, prompts, raw agent answers, diffs, or secrets. Telemetry is off
158
+ by default and its storage/transmission is not implemented at this stage.
159
+
160
+ ## Install trouble on Ubuntu/Linux
161
+
162
+ If `npm install -g jutell` fails with `EACCES` / permission denied, that's a common npm global
163
+ folder ownership issue, not something specific to JuTell. **Avoid `sudo npm install -g jutell`** —
164
+ it leaves root-owned files that cause the same problem again. Two safe fixes:
165
+ [Ubuntu/Linux install permission error](https://github.com/ju0o/jutell/blob/main/docs/CLI_INSTALLATION.md).
166
+
167
+ ## 한국어
168
+
169
+ 전체 한국어 문서는 [README.ko.md](https://github.com/ju0o/jutell/blob/main/README.ko.md)에 있습니다.
170
+ 비개발자도 "승인해도 되는가", "무엇을 직접 확인해야 하는가"를 판단할 수 있게 하는 것이 목적입니다.
171
+
172
+ ## More
173
+
174
+ Full walkthrough with images, a real report, a real failure, and an honest efficiency snapshot:
175
+ [GitHub README](https://github.com/ju0o/jutell#readme) ·
176
+ [2.0.0 release showcase](https://github.com/ju0o/jutell/blob/main/docs/releases/2.0.0-showcase.md) ·
177
+ [Changelog](https://github.com/ju0o/jutell/blob/main/CHANGELOG.md) ·
178
+ [MCP integration](https://github.com/ju0o/jutell/blob/main/docs/MCP_INTEGRATION.md)
179
+
180
+ ## Support
181
+
182
+ JuTell is free and MIT-licensed, and it stays that way. If it saved you time:
183
+
184
+ [![Support on Ko-fi](https://img.shields.io/badge/Ko--fi-support-FF5E5B?logo=ko-fi&logoColor=white)](https://ko-fi.com/ju0o___)
185
+
186
+ Not supporting changes nothing — every feature stays available to everyone.
@@ -5,7 +5,7 @@ import { activeFeatures, beginnerReportRules, bridgeStatus, reportPreferences, s
5
5
  import { recordToolCall } from './tools/usage-counters.js';
6
6
  const server = new McpServer({
7
7
  name: 'JuTell',
8
- version: '1.1.0',
8
+ version: '2.0.1',
9
9
  }, {
10
10
  instructions: 'JuTell by Ju0 is a local read-only report helper. Read only project configuration and approved report rules. Never access project code, Git diff, prompts, AI answers, secrets, or external networks. Skill mode remains available if this MCP server is disabled or unavailable. When both jutell and beginner_bridge servers are visible, prefer the canonical jutell server; use beginner_bridge only for compatibility. For owner-facing reports, apply the JuTell reporting guidance before composing the final answer. Prefer these tools over re-reading the JuTell Skill reference files when both are available, since a tool call returns the same project-specific rules in one step. Call get_beginner_report_rules once, at task completion, right before writing the final report — not after every file read, shell command, or edit, and not to verify work that is already done. If these tools are unavailable or blocked, fall back to the JuTell Skill files without interrupting the task, and never tell the user JuTell MCP was used unless a JuTell tool call actually returned a result in this task.',
11
11
  });
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: beginner-bridge
3
- jutellSkillVersion: "1.1.0"
3
+ jutellSkillVersion: "2.0.1"
4
4
  schemaVersion: 1
5
5
  description: JuTell by Ju0 creates concise, evidence-based work reports for non-developers, separating observed facts, code-based expectations, verification results, risks, and user actions. The legacy Skill ID is retained for compatibility.
6
6
  ---
@@ -54,45 +54,240 @@ JuTell MCP가 보이면 canonical `jutell` 서버를 사용한다. `jutell`과 `
54
54
 
55
55
  JuTell MCP를 사용할 수 있고 이미 확보한 근거를 재사용해 보고·검증·핸드오프 단계의 모호함을 줄여줄 때는 MCP 도구를 우선한다. MCP가 보이지 않거나 Provider 정책으로 막혀 있거나 불필요한 추가 작업이 될 때는 방해 없이 이 Skill의 참고 문서로 계속 진행한다. 두 경로 모두 최종 결과물의 품질은 같아야 한다. 실제로 JuTell MCP 도구를 호출해 응답을 받은 경우에만 "JuTell MCP를 사용했다"고 표현하고, 호출하지 않았다면 이 Skill의 지침만 따랐다고 표현한다.
56
56
 
57
+ ## Intent Bridge
58
+
59
+ 실제로 코드나 문서를 바꾸기 전에, 사용자 요청에 결과를 크게 바꿀 수 있는 불확실함이 있는지 확인하고, 있으면 이해한 내용을 짧게 보여준 뒤 승인이나 수정을 받는 절차다. `requestClarificationGuide` Feature가 꺼져 있으면 이 절차 전체를 적용하지 않고 평소처럼 바로 진행한다.
60
+
61
+ 사용자에게는 실제 결정만 묻고, 확인 가능한 사실은 Agent가 직접 확인한다. 질문이 필요하다면 결과를 가장 크게 바꾸는 것 하나만 묻는다.
62
+
63
+ ### 언제 보여주나
64
+
65
+ 요청이 짧다는 이유만으로 보여주지 않는다. 판단 기준은 하나뿐이다: **Agent가 불확실한 부분을 스스로 추측해서 진행하면, 결과가 사용자가 원한 것과 크게 달라질 수 있는가.**
66
+
67
+ 보여줘야 하는 예 (무엇을 어떻게 바꿀지가 정해지지 않음):
68
+ - "로그인 화면 좀 더 깔끔하게 해줘."
69
+ - "이 버튼 좀 더 좋게 바꿔줘."
70
+ - "회원가입 좀 간단하게 만들어줘."
71
+
72
+ 보여주지 않는 예 (짧아도 이미 명확함 — 무엇을, 어디를, 어떻게 바꿀지가 요청 자체에 이미 있다):
73
+ - "README에서 'teh'를 'the'로 고쳐줘."
74
+ - "list.js의 remove 함수 테스트 하나 추가해줘."
75
+ - "버튼 텍스트를 '로그인'에서 '시작하기'로 바꿔줘."
76
+
77
+ ### 질문하기 전에 다섯 가지로 나눈다
78
+
79
+ 사용자에게 물을지 말지 정하기 전에, 남은 불확실함을 다음 다섯 가지로 나눈다. 이 다섯 가지는 Agent 내부 판단 기준이다 — `ASK_USER`, `AGENT_CHECK` 같은 이름표를 사용자에게 그대로 보여주는 기술 UI로 만들지 않는다. 쉬운 말로 바꿔 쓰는 것이 실제로 도움이 될 때만 자연스럽게 반영한다.
80
+
81
+ - **AGENT_CHECK** — 사용자 결정이 아니라 저장소·코드·환경에서 Agent가 직접 확인할 수 있는 사실.
82
+ - **SAFE_INFERENCE** — 되돌릴 수 있고 결과를 크게 제한하지 않는, 위험이 낮은 추론.
83
+ - **NON_BLOCKING_UNKNOWN** — 아직 모르지만 지금 진행해도 안전한 것.
84
+ - **BLOCKING_UNKNOWN** — 잘못 추측하면 결과가 사용자 의도와 크게 달라질 수 있는 것.
85
+ - **ASK_USER** — 실제로 사용자에게 물어야 하는 것. 아래 판단 기준을 그대로 적용해서 정한다.
86
+
87
+ #### ASK_USER 판단 기준
88
+
89
+ 다음 두 가지가 **모두** 사실일 때만 ASK_USER로 분류한다.
90
+
91
+ 1. Agent가 프로젝트·저장소·환경의 근거만으로 답을 신뢰성 있게 확정할 수 없다.
92
+ 2. 그럴듯한 답이 여러 개 있고, 그 답에 따라 결과가 크게 달라지거나 사용자 의도를 어길 위험이 있다.
93
+
94
+ 둘 중 하나라도 아니면 사용자에게 묻지 않는다. 점수판, confidence 퍼센트, 질문 순위를 매기는 엔진을 따로 만들지 않는다.
95
+
96
+ #### AGENT_CHECK — 먼저 저장소에서 확인한다
97
+
98
+ 질문을 만들기 전에, Agent가 스스로 확인할 수 있는 사실은 먼저 확인한다. 예: 현재 사용 중인 framework, 파일·컴포넌트 위치, 현재 화면 구조, 기존 스타일, 기존 입력 필드, 현재 동작, 테스트 framework, 기존 반응형 처리 방식.
99
+
100
+ 저장소에서 확인할 수 있는 기술 사실을 사용자에게 묻지 않는다.
101
+
102
+ 나쁜 예:
103
+ - "어떤 CSS 프레임워크를 쓰고 계신가요?" — Agent가 확인할 수 있으면 묻지 않는다.
104
+ - "로그인 버튼이 있는 컴포넌트가 어디인가요?" — Agent가 찾을 수 있으면 묻지 않는다.
105
+
106
+ 사용자는 저장소 조회 도구가 아니다.
107
+
108
+ AGENT_CHECK에는 필수 관련 수정 범위를 정하는 데 필요한 기술적 제약 확인도 포함된다. 예: 로그인 처리 코드가 특정 요소 ID나 필드 이름을 그대로 참조하는지 확인하는 것. 이런 제약은 사용자의 의도가 아니라 Agent가 직접 확인한 구현 사실이며, 새로운 분류를 따로 만들지 않고 이 AGENT_CHECK 안에서 다룬다.
109
+
110
+ #### SAFE_INFERENCE — 낮은 위험의 추론
111
+
112
+ 다음을 모두 만족하면 낮은 위험의 추론으로 진행할 수 있다.
113
+
114
+ - 되돌릴 수 있다.
115
+ - 결과를 크게 제한하지 않는다.
116
+ - 다른 그럴듯한 답을 골랐어도 사용자 의도를 크게 어기지 않는다.
117
+
118
+ 이때도 필요한 곳에는 추론이라는 표시를 남긴다. SAFE_INFERENCE를 조용히 사용자 의도로 승격하지 않는다.
119
+
120
+ #### NON_BLOCKING_UNKNOWN — 몰라도 진행할 수 있다
121
+
122
+ 아직 정해지지 않았지만 작업을 안전하게 진행할 수 있으면, 억지로 질문을 만들지 않는다. 모르는 채로 남긴다. 필요하면 Intent Bridge의 "아직 정하지 않은 것" 항목이나 최종 보고서에 남긴다.
123
+
124
+ 예: 나중에 조정 가능한 세밀한 시각적 디테일.
125
+
126
+ #### BLOCKING_UNKNOWN — 추측하면 결과가 크게 달라진다
127
+
128
+ 추측이 실제로 잘못된 제품 결과로 이어질 수 있으면 BLOCKING_UNKNOWN이 되고, ASK_USER 대상이 될 수 있다.
129
+
130
+ 예:
131
+ - 기존 동작이 실제로 바뀔 수 있는지
132
+ - 기존 필드가 실제로 삭제될 수 있는지
133
+ - 결제 흐름 요구사항이 실제로 바뀔 수 있는지
134
+ - 사용자가 원하는 것이 가벼운 정리인지, 전체적인 느낌을 바꾸는 재설계인지
135
+
136
+ 이 예시를 다른 상황까지 과도하게 일반화하지 않는다.
137
+
138
+ ### 질문 없이 진행하는 경로
139
+
140
+ Intent Bridge가 항상 사용자 질문을 요구하지는 않는다. 다음 흐름도 정상이다.
141
+
142
+ 1. 사용자 요청을 받는다.
143
+ 2. 결과를 크게 바꿀 수 있는 불확실함이 있는지 본다.
144
+ 3. Agent가 확인 가능한 사실(AGENT_CHECK)을 먼저 확인한다.
145
+ 4. 남은 것이 SAFE_INFERENCE나 NON_BLOCKING_UNKNOWN뿐이고 실제 BLOCKING_UNKNOWN이 남지 않으면
146
+ 5. 이해한 내용을 짧게 보여줄 수는 있지만, 실제 사용자 결정을 물을 필요는 없다.
147
+ 6. Agent가 바로 작업을 진행한다.
148
+
149
+ 이럴 때는 판단할 실제 사용자 결정이 남아 있지 않으므로 "이대로 진행해도 될까요?" 같은 형식적인 확인 질문도 만들지 않는다. 이 동작은 V2.1에서 의도한 것이다.
150
+
151
+ ### 다섯 가지 정보 구분
152
+
153
+ - **사용자가 직접 말한 것 (USER_SAID)** — 사용자의 표현을 그대로 옮긴다. 바꿔 쓰지 않는다.
154
+ - **JuTell이 이해한 것 (UNDERSTOOD)** — 쉬운 말로 다시 표현할 수 있지만, 사용자가 말하지 않은 새 요구사항을 추가하지 않는다.
155
+ - **JuTell이 추론한 것 (INFERRED)** — 반드시 추론이라고 표시한다. 절대 사용자가 직접 말한 것으로 바꿔 쓰지 않는다.
156
+ - **아직 정하지 않은 것 (UNKNOWN)** — 모르는 채로 남긴다. 추측으로 채우거나 요구사항으로 바꾸지 않는다.
157
+ - **Agent가 먼저 확인할 것 (AGENT_SHOULD_CHECK)** — 사용자가 결정할 일이 아니라, Agent가 저장소·코드에서 직접 확인해야 할 사실만 적는다.
158
+
159
+ 위 AGENT_SHOULD_CHECK는 앞서 정리한 AGENT_CHECK 판단과 같은 항목이다 — 보고서에 남길 때의 이름일 뿐, 별도 체계가 아니다.
160
+
161
+ ### 질문은 최대 하나
162
+
163
+ ASK_USER가 필요하면 질문은 최대 하나다.
164
+
165
+ 질문을 고르기 전에 다음을 모두 제외한다.
166
+ - AGENT_CHECK 항목
167
+ - SAFE_INFERENCE 항목
168
+ - NON_BLOCKING_UNKNOWN 항목
169
+
170
+ 그러고도 BLOCKING_UNKNOWN이 여러 개 남으면, 잘못 추측했을 때 사용자가 실제로 원했을 것과 가장 크게 달라지는 항목 하나만 고른다.
171
+
172
+ 다음 기준으로 고르지 않는다.
173
+ - 가장 기술적인 항목
174
+ - 구현이 가장 어려운 항목
175
+ - 목록에서 첫 번째로 나온 항목
176
+
177
+ 질문의 가치가 구현 편의보다 우선한다.
178
+
179
+ ### 형식
180
+
181
+ 짧게 유지한다. 값이 있는 항목만 보여주고, 빈 항목은 만들지 않는다. 모든 필드를 강제로 채우지 않는다. `templates/request-builder/`의 어휘(WHY·IMPORTANT·DO NOT CHANGE·DONE WHEN 등)를 참고해도 되지만, 8단계 템플릿 전체를 대화에 그대로 옮기지 않는다. 사용자의 평소 말투를 개발자 용어로 바꿔 쓰지 않는다.
182
+
183
+ ```
184
+ 제가 이렇게 이해했어요.
185
+
186
+ 원하는 것
187
+ (이해한 내용, 한 문장)
188
+
189
+ 건드리지 말 것 (있을 때만)
190
+ (사용자가 말했거나 요청 자체로 명백히 손대면 안 되는 부분)
191
+
192
+ 유지할 것 (있을 때만)
193
+ (파일은 바뀌어도 계속 그대로 동작해야 하는 것)
194
+
195
+ 제가 추론한 것 (있을 때만)
196
+ (추론이라고 분명히 표시)
197
+
198
+ 아직 정하지 않은 것 (있을 때만)
199
+ (모르는 채로)
200
+
201
+ Agent가 먼저 확인할 것 (있을 때만)
202
+ (저장소·코드 사실 확인 항목)
203
+ ```
204
+
205
+ 건드리지 말 것과 유지할 것은 다르다. 건드리지 말 것은 손대면 안 되는 부분(예: 색상, 인증 로직) 자체이고, 유지할 것은 구현 파일이 바뀌어도 계속 그대로 동작해야 하는 기능(예: 로그인 제출 동작)이다. 필수 관련 수정으로 어떤 파일을 열어보거나 살짝 손보는 것과, 건드리지 말 것의 값·동작을 실제로 바꾸는 것을 같은 것으로 보지 않는다 — 후자만 금지한다. 값이 있을 때만 보여주는 다른 필드와 마찬가지로, 사용자가 말했거나 요청 자체로 명백할 때만 이 두 필드를 보여주고, 모든 요청에 습관적으로 채우지 않는다.
206
+
207
+ 실제 사용자 결정(BLOCKING_UNKNOWN)이 남아 있으면, 위 내용 뒤에 그 결정을 묻는 문장 하나만 덧붙인다. 이 문장 자체가 질문이다 — 쉬운 말을 쓰고, 실제 제품·사용자 결정을 묻고, 한 문장으로 답할 수 있게 하고, 기술 용어를 피하고, 필요할 때만 2~3개의 구체적인 선택지를 보여주고, 구현 방법이 아니라 결과로 설명한다.
208
+
209
+ 예시 형태(그대로 베끼지 않는다): "현재 분위기는 유지하면서 정리할까요, 아니면 전체적인 느낌까지 바꿔도 될까요?"
210
+
211
+ 나쁜 예: "CSS 아키텍처를 유지할까요?", "Grid와 Flex 중 어떤 방식이 좋으세요?" — 기술 용어이거나 구현 방식을 묻고 있다.
212
+
213
+ 실제 사용자 결정이 남아 있지 않으면 이 문장을 만들지 않는다. "이대로 진행해도 될까요?" 같은 일반적인 확인 문구를 결정 질문 대신, 또는 결정 질문에 이어 덧붙이지 않는다.
214
+
215
+ 질문 하나를 물은 뒤에는 같은 내용을 다른 말로 다시 확인하지 않는다. "결정 질문" 다음에 "이대로 진행해도 될까요?"를 잇는 것처럼, 같은 선택을 다른 표현으로 두 번 묻는 형태를 만들지 않는다.
216
+
217
+ ### 위험이 있는 요청
218
+
219
+ `references/risk-level-guide.md`의 위험 어휘를 그대로 재사용한다. 결제, 로그인과 인증·권한, 데이터베이스 구조나 데이터 손실처럼 위험이 높은 영역과 관련된 요청은, 남은 불확실함이 실제 사용자·비즈니스 동작을 크게 바꿀 수 있을 때 ASK_USER 쪽으로 더 기울여 판단한다.
220
+
221
+ 새로운 위험 분류 체계를 따로 만들지 않는다. 결제·인증·데이터 관련 요청이라는 이유만으로 모든 요청에 자동으로 질문하지 않는다 — 위 두 가지 ASK_USER 조건은 그대로 적용되고, 위험은 경계선에서 Agent가 얼마나 신중해야 하는지에만 영향을 준다.
222
+
223
+ ### 한 번만 확인한다
224
+
225
+ 이 확인은 요청당 최대 한 번이다. 사용자가 승인하거나 고쳐 말하면 그 내용으로 바로 진행한다. 사용자의 답변에도 여전히 불확실함이 남으면 JuTell이 Intent Bridge를 다시 반복하지 않는다 — 그 지점부터는 평소 Agent의 판단과 질문 방식을 따른다. JuTell은 자체적으로 여러 번 되묻는 질문 엔진을 만들지 않는다.
226
+
227
+ 작업을 진행하는 도중 완료를 막는 새로운 사용자 결정이 나타났는데 이번 요청에서 이미 질문을 한 번 사용했다면, 같은 내용을 다른 말로 다시 묻지 않는다. 이때는 `확인 완료`로 마무리하지 않는다 — 보고서 상태를 `작업 보류`로 두고, 남은 결정 하나를 `사용자 결정 필요`로 짧게 설명한 뒤 사용자의 다음 답을 기다린다. 이것도 별도의 질문 엔진이 아니라 이 절의 한 번만 확인한다 규칙과 기존 보고서 상태를 그대로 적용한 것이다.
228
+
57
229
  ## 실행 절차
58
230
 
59
231
  1. 소유자 대상 구현·보고 작업이면 최종 답변을 작성하기 전에 JuTell 보고 규칙(`get_beginner_report_rules` 등)을 먼저 확인해 적용한다. 이 확인은 작업이 끝나갈 때, 최종 보고를 쓰기 직전 한 번만 한다. 파일을 읽거나 도구를 쓸 때마다, 또는 이미 끝난 작업을 다시 검증하려고 반복 확인하지 않는다. JuTell MCP를 사용할 수 있으면 이 확인을 MCP 도구 호출 한 번으로 처리하고 참고 문서를 여러 개 다시 읽지 않는다. MCP를 사용할 수 없으면 `references/report-format.md` 등 이 Skill의 참고 문서로 대신한다.
60
- 2. 사용자 요청, 작업 유형, 허용 범위와 금지 범위를 확인한다.
61
- 3. 코드 변경 작업이면 가능한 범위에서 작업 시작 기준 상태를 기록한다.
232
+ 2. 실제로 코드나 문서를 바꾸기 전에, 위 Intent Bridge 기준(AGENT_CHECK·SAFE_INFERENCE·NON_BLOCKING_UNKNOWN·BLOCKING_UNKNOWN·ASK_USER 다섯 가지 분류 포함)으로 이번 요청에 남은 불확실함을 정리한다. AGENT_CHECK 항목은 먼저 저장소·코드에서 직접 확인한다. `requestClarificationGuide`가 켜져 있고 그런 불확실함이 있으면 Intent Bridge 형식대로 이해한 내용을 짧게 보여준다. 이때 실제 사용자 결정(BLOCKING_UNKNOWN)이 남아 있으면 그 결정을 묻는 질문 하나만 받은 뒤 진행하고, SAFE_INFERENCE나 NON_BLOCKING_UNKNOWN만 남아 있으면 질문 없이 바로 진행한다. 불확실함이 전혀 없거나 `requestClarificationGuide`가 꺼져 있으면 평소처럼 바로 진행한다.
233
+ 3. 사용자 요청, 작업 유형, 허용 범위와 금지 범위를 확인한다. 이 구분은 `requestClarificationGuide` 설정이나 Intent Bridge 표시 여부와 관계없이 항상 적용한다 — 정밀한 요청처럼 Intent Bridge를 보여줄 필요가 없을 때도 조용히 적용되는 내부 판단이다.
234
+ * 직접 범위: 사용자가 실제로 요청한 변경 대상 그 자체. Intent Bridge의 "원하는 것"이 곧 직접 범위이며, 이를 별도의 "무엇을 바꿔도 되는지" 필드로 다시 만들지 않는다.
235
+ * 필수 관련 수정: 직접 범위를 올바르고 안전하고 일관되게 완료하기 위해 함께 손봐야 하는 부분. 예: 필드 하나를 없애면 그 필드를 참조하던 검증·테스트·안내 문구도 함께 정리한다. 같은 기능 영역 안에 머물고, 요청하지 않은 새 기능을 더하지 않고, 사용자가 이미 정한 것을 뒤집지 않는 한 필수 관련 수정은 따로 허락을 구하지 않고 진행한다. "한 줄만 고친다"로 좁게 해석해 안전한 완료에 필요한 관련 수정까지 막지 않는다.
236
+ * 건드리면 안 되는 부분: 사용자가 직접 말했거나("색상은 건드리지 마", "기능은 건드리지 마") 요청 자체로 명백히 손대면 안 되는 부분. 이 부분을 열어보거나 필수 관련 수정으로 훑어보는 것과, 실제로 그 값이나 동작을 바꾸는 것을 같은 것으로 보지 않는다 — 후자만 금지한다.
237
+ * 유지해야 할 동작: 구현 파일은 바뀌어도 계속 그대로 동작해야 하는 기능. 예: 화면을 다시 꾸며도 로그인 제출·검증 동작은 그대로 동작해야 한다. "파일을 건드렸다"를 "동작이 바뀌었다"와 같은 것으로 보지 않는다.
238
+ * 범위 밖: 이번 요청이 요구하지 않는 부분. Agent가 보기에 더 나은 개선이라도 사용자 허락 없이는 범위를 넓히지 않는다. 이번 요청 없이는 직접 범위를 안전하고 올바르게 완료할 수 없고, 그 답에 따라 결과가 크게 달라지거나 사용자 의도를 어길 위험이 있을 때만 위 Intent Bridge의 ASK_USER 판단 기준을 그대로 적용해 최대 한 번 묻는다 — 범위 전용 질문 체계를 새로 만들지 않는다. 그렇지 않으면 조용히 수행하지 않는다. 유용하면 최종 보고서에서 범위 밖 관찰이나 기존 다음 행동 제안(최대 3개)으로만 짧게 알리고, 건드리지 않았다는 사실 자체를 숨기지 않는다. 지금 요청을 그 개선 작업으로 바꾸지 않는다.
239
+ 4. 코드 변경 작업이면 가능한 범위에서 작업 시작 기준 상태를 기록한다.
62
240
  * Git 저장소와 브랜치
63
241
  * 기존 수정 파일과 추적되지 않은 파일
64
242
  * 실행 가능한 테스트·빌드·검사 명령
65
243
  * 브라우저 또는 실제 실행 가능 여부
66
- 4. 기준 상태를 기록하지 못하면 기존 변경과 이번 변경을 임의로 섞지 않는다. Codex가 직접 수정한 사실이 명확한 파일만 이번 변경으로 표시하고 나머지는 출처 구분 불가로 표시한다.
67
- 5. 실제 변경 파일과 내용을 확인한다. 파일명만으로 기능 역할이나 변경 의미를 확정하지 않는다.
68
- 6. 주요 파일을 작업 규모에 맞게 선택한다. 단순 작업은 최대 3개, 일반 작업은 최대 5개를 우선 설명한다.
69
- 7. 공식 문서나 프로젝트 설정에서 확인 가능한 검증 명령을 찾는다. 명령을 임의로 만들어 실행하지 않는다.
70
- 8. 안전한 검증만 실행한다.
244
+ 5. 기준 상태를 기록하지 못하면 기존 변경과 이번 변경을 임의로 섞지 않는다. Codex가 직접 수정한 사실이 명확한 파일만 이번 변경으로 표시하고 나머지는 출처 구분 불가로 표시한다.
245
+ 6. 실제 변경 파일과 내용을 확인한다. 파일명만으로 기능 역할이나 변경 의미를 확정하지 않는다.
246
+ 7. 주요 파일을 작업 규모에 맞게 선택한다. 단순 작업은 최대 3개, 일반 작업은 최대 5개를 우선 설명한다.
247
+ 8. 공식 문서나 프로젝트 설정에서 확인 가능한 검증 명령을 찾는다. 명령을 임의로 만들어 실행하지 않는다.
248
+ 9. 안전한 검증만 실행한다.
71
249
  * 사용자가 실행을 금지한 명령은 실행하지 않는다.
72
250
  * 비밀정보를 출력할 가능성이 있는 명령은 실행하지 않는다.
73
251
  * 전체 환경변수, `.env`, 인증 헤더, 쿠키, 토큰, 연결 문자열을 그대로 출력하지 않는다.
74
252
  * 도구 출력과 오류 로그를 사용자에게 보여주기 전에 민감한 값을 제거한다.
75
253
  * 브라우저는 현재 Codex 환경에 이미 제공되고 사용자가 허용한 경우에만 선택적으로 사용한다.
76
- 9. 검증 결과를 실제 실행 범위와 함께 기록한다. 실행하지 못한 검증을 통과했다고 쓰지 않는다.
77
- 10. 네 가지 정보 체계를 분리한다.
254
+ 10. 무엇을 검증할지는 이번 요청 모양에 맞춘다. 확인 순서는 다음과 같다.
255
+ 1. 원하는 것 — 사용자가 실제로 요청한 결과가 실제로 일어났는지. Intent Bridge의 "원하는 것"과 같다.
256
+ 2. 건드리지 말 것·유지할 것 — 사용자가 말했거나 요청 자체로 명백한 보존 조건이 실제로 유지됐는지. 사용자가 이번 요청에서 완료 조건으로 직접 말한 것(예: "테스트까지 통과하게 해줘", "모바일에서 안 깨지는 것까지 확인해줘")도 여기 포함한다.
257
+ 3. 위 변경과 직접 관련된 회귀 증거.
258
+ 4. 위 세 가지로 설명되지 않는 추가 근거가 실제로 필요할 때만 그 이상을 확인한다.
259
+
260
+ 프로젝트에 전체 테스트나 검증 수단이 있다는 이유만으로 이번 요청과 관계없는 검증까지 실행하지 않는다. README 오타 하나처럼 작은 작업은 diff 확인만으로 충분하고, 전화번호 입력 필드 제거처럼 검증·테스트와 실제로 연결된 작업은 관련 테스트까지 실행한다. 위 1·2번과 직접 관련된 테스트는 그 테스트가 실제로 그 결과나 보존 조건을 확인해줄 때 실행한다 — 프로젝트에 전체 테스트가 있는지 여부와 관계없다.
261
+
262
+ 실제 화면 확인이 이번 요청의 결과나 완료 조건을 뒷받침하는 데 실제로 필요한 경우가 아니면 이 확인만을 위해 브라우저를 새로 켜지 않는다. 필요해서 확인하려는데 쓸 수 없으면 확인했다고 쓰지 않고 그 사실 그대로 기록한다. 이런 보조 확인 수단이 없다는 사실만으로 바로 `작업 보류`가 되지는 않는다 — 그 확인이 이번 요청의 완료 조건 자체였는지에 따라 달라진다.
263
+
264
+ 사용자가 완료 조건을 따로 말하지 않았다면, Agent가 이번 요청 모양에 맞는 최소 검증을 스스로 정한다. 이는 추론(INFERRED)이며, 사용자가 말한 것(USER_SAID)으로 바꿔 쓰지 않는다. 이 구분이 사용자 이해에 실제로 중요할 때만 보고서에 드러내고, 매번 체크리스트로 보여주지 않는다.
265
+
266
+ 이미 작업 중에 확보한 근거로 위 조건이 증명됐다면, 형식을 맞추려고 같은 파일을 다시 읽거나 같은 검증을 다시 실행하지 않는다.
267
+ 11. 검증 결과를 실제 실행 범위와 함께 기록한다. 실행하지 못한 검증을 통과했다고 쓰지 않는다. 확인하지 못한 것은 확인했다고 쓰지 않는다.
268
+ 12. 네 가지 정보 체계를 분리한다.
78
269
  * 근거 출처: 파일, Git, 명령, 브라우저 또는 실제 실행, 코드 예상, 사용자 제공 정보
79
270
  * 확인 상태: 확인됨, 일부 확인, 확인하지 못함
80
271
  * 사용자 행동: 사용자 확인 필요, 추가 테스트 권장, 설정 필요, 사용자 결정 필요
81
272
  * 보고서 상태: 확인 완료, 추가 확인 필요, 일부 확인, 작업 보류, 범위 밖
82
- 11. 위험도는 변경 영향도를 기준으로 판단한다. 검증 도구가 없다는 이유만으로 위험도를 판정 불가로 만들지 않는다. 여러 조건이 겹치면 가장 높은 위험도를 적용한다.
83
- 12. 필요한 용어만 처음 등장할 때 설명한다. 기술 전용 용어는 해당 기술이 사용되는 것을 확인한 경우에만 설명한다. 기본 사전에 없는 용어는 추측하지 않는다.
84
- 13. 활성 Feature와 `references/report-format.md`에 따라 하나의 비개발자용 최종 보고를 작성한다. `explainedDiff`가 활성이면 의미 있는 변경에 같은 근거로 설명형 변경 요약을 덧붙인다. 중요한 코드 블록이 이미 작업 과정에서 확인되었고 사용자 이해에 실제 도움이 될 때만 1~2개까지 보여준다. 이 섹션만을 위해 파일을 다시 읽거나 git diff를 다시 실행하지 않는다. `explainedDiff`가 꺼져 있으면 Readable Code도 생략한다(사용자가 코드 설명을 명시한 경우만 예외).
85
- 14. `requestBuilder`가 활성이고 사용자가 다음 AI에게 넘기기·세션 마무리·이어서 할 일 정리를 요청하면, 현재 작업에서 이미 아는 근거만으로 `다음 AI에게 전달하기` 블록을 만든다. Request Builder의 `NEXT_AGENT_HANDOFF` 템플릿 또는 세션 `SESSION_SUMMARY`를 재사용해도 된다. 저장소 재탐색, 테스트 재실행, 자동 전송, HTML/클라우드 산출물은 만들지 않는다.
86
- 15. `nextActionSuggestions`가 활성일 때 보고서 끝에 다음 행동 제안을 **최대 3개**만 추가한다. 다음 경우에만 제안한다.
273
+ 13. `확인 완료`는 원하는 것이 실제로 일어났고, 사용자가 말한 건드리지 말 것·유지할 것·완료 조건이 실제로 유지됐다고 확인했을 때만 쓴다. 이 중 하나라도 확인하지 못했다면 `확인 완료` 대신 `추가 확인 필요`·`일부 확인`·`작업 보류` 중 이번 요청과 실제로 맞는 상태를 쓴다. 이 항목들은 `docs/BEGINNER_REPORT_SPEC.md`의 중요한 미확인 사항에 포함된다. 이번 요청과 직접 관련 없는 다른 미확인 사항 때문에 `확인 완료`를 막지는 않는다. 새로운 보고서 상태를 만들지 않고 위 다섯 가지 기존 상태만 사용한다.
274
+
275
+ 관련 검증이 통과했다는 사실이 이 판단을 뒤집지 않는다 — 테스트는 실제로 다룬 범위만 증명한다. Agent가 이번 작업 중 이미 알게 된, 원하는 것의 실제 성공이나 보존·완료 조건의 실제 성립에 필수적인 미확인·실패가 있으면(예: 필드 제거로 외부 서비스 연결처럼 이미 중요한 미확인 사항으로 보는 항목에 새로 의존하게 됐는데 그 연결이 확인되지 않은 경우), 관련 테스트가 통과했더라도 `확인 완료`를 쓰지 않고 `위험과 사용자 확인`에 그 사실을 짧게 밝힌다. 완료에 필수적이지 않은 미확인 때문에 곧장 `작업 보류`로 가지 않는다 — 그런 경우는 기존 상태 우선순위를 따른다. 이것은 확인하지 않은 것을 확인했다고 말하는 허위 검증과는 다른 문제다 — 사실을 정직하게 보고했더라도 그 사실을 알면서 `확인 완료`를 고르면 그 자체가 성급한 완료다. JuTell은 이 판단이 실제로 맞는지 독립적으로 검증하거나 보장하지 않는다 — Agent가 실제로 아는 것을 근거로 정직하게 상태를 고르는지가 전부다.
276
+ 14. 위험도는 변경 영향도를 기준으로 판단한다. 검증 도구가 없다는 이유만으로 위험도를 판정 불가로 만들지 않는다. 여러 조건이 겹치면 가장 높은 위험도를 적용한다.
277
+ 15. 필요한 용어만 처음 등장할 때 설명한다. 기술 전용 용어는 해당 기술이 사용되는 것을 확인한 경우에만 설명한다. 기본 사전에 없는 용어는 추측하지 않는다.
278
+ 16. 활성 Feature와 `references/report-format.md`에 따라 하나의 비개발자용 최종 보고를 작성한다. `explainedDiff`가 활성이면 의미 있는 변경에 같은 근거로 설명형 변경 요약을 덧붙인다. 중요한 코드 블록이 이미 작업 과정에서 확인되었고 사용자 이해에 실제 도움이 될 때만 1~2개까지 보여준다. 이 섹션만을 위해 파일을 다시 읽거나 git diff를 다시 실행하지 않는다. `explainedDiff`가 꺼져 있으면 Readable Code도 생략한다(사용자가 코드 설명을 명시한 경우만 예외).
279
+ 17. `requestBuilder`가 활성이고 사용자가 다음 AI에게 넘기기·세션 마무리·이어서 할 일 정리를 요청하면, 현재 작업에서 이미 아는 근거만으로 `다음 AI에게 전달하기` 블록을 만든다. Request Builder의 `NEXT_AGENT_HANDOFF` 템플릿 또는 세션 `SESSION_SUMMARY`를 재사용해도 된다. 저장소 재탐색, 테스트 재실행, 자동 전송, HTML/클라우드 산출물은 만들지 않는다. 이번 작업에서 Intent Bridge로 승인받은 내용이 있으면 "지금 하던 일"에 그대로 재사용하고, 다시 확인하지 않는다.
280
+ 18. `nextActionSuggestions`가 활성일 때 보고서 끝에 다음 행동 제안을 **최대 3개**만 추가한다. 다음 경우에만 제안한다.
87
281
  * 사용자가 직접 확인해야 할 항목이 남은 경우
88
282
  * 검증되지 않아 보류된 항목이 있는 경우
89
283
  * 설정이 필요한 경우
90
284
  * 데이터 손실·보안 위험이 확인된 경우
91
285
  제안은 보고서에서 이미 확인된 항목 중에서만 고르고, 추측이나 새 작업을 제안하지 않는다. 해당하지 않으면 생략한다.
92
- 16. 제출 전 다음을 점검한다.
286
+ 19. 제출 전 다음을 점검한다.
93
287
  * 화면 변화와 내부 변화를 분리했는가
94
288
  * 예상과 실제 확인을 구분했는가
95
289
  * 근거가 중요한 주장과 일치하는가
290
+ * 원하는 것과 사용자가 말한 건드리지 말 것·유지할 것·완료 조건을 실제로 확인했는가
96
291
  * 검증 결과와 보고서 상태가 일치하는가
97
292
  * 위험도 근거가 실제 변경과 일치하는가
98
293
  * 주요 파일 수를 지켰는가
@@ -11,6 +11,8 @@
11
11
  * 사용자 행동: 사용자 확인 필요, 추가 테스트 권장, 설정 필요, 사용자 결정 필요
12
12
  * 보고서 상태: 확인 완료, 추가 확인 필요, 일부 확인, 작업 보류, 범위 밖
13
13
 
14
+ 이번 요청이 실제로 원하는 결과를 만들어냈는지, 사용자가 말한 건드리지 말 것·유지할 것·완료 조건이 실제로 유지됐는지 확인하지 못했다면 `확인 완료`를 쓰지 않는다. 관련 검증이 통과했다는 사실만으로 이 판단을 대신하지 않는다 — 검증은 실제로 다룬 범위만 증명한다. 완료에 필수적인 미확인·실패를 이미 알고 있다면 `위험과 사용자 확인`에 짧게 밝히고 `확인 완료`가 아닌 실제로 맞는 상태를 쓴다.
15
+
14
16
  검증 결과는 별도로 표시한다.
15
17
 
16
18
  * 통과
@@ -22,6 +24,8 @@
22
24
 
23
25
  중요한 주장에는 가능한 경우 근거와 확인 상태를 함께 적는다. 코드만 보고 예상한 내용은 `근거: 코드 예상`과 `확인 상태: 확인하지 못함`으로 표시한다.
24
26
 
27
+ 이번 요청이 요구하지 않는 더 나은 개선을 발견해도 조용히 수행하지 않는다. 유용하면 수행하지 않고 다음 행동 제안(6.6)이나 범위 밖 관찰로 짧게만 언급하며, 건드리지 않았다는 사실 자체를 숨기지 않는다.
28
+
25
29
  ## 2. 단순 작업 보고
26
30
 
27
31
  문구, 색상, 작은 화면 배치처럼 위험이 낮은 작업에 사용한다.
@@ -53,6 +57,8 @@
53
57
 
54
58
  단순 작업은 기본 항목마다 1~2문장, 주요 파일 최대 3개, 전체 기본 12문장 또는 약 25줄을 우선한다. 안전 문제나 중요한 실패가 있으면 정확한 경고를 우선한다.
55
59
 
60
+ 완료에 필수적인 미확인·실패를 이미 알고 있으면, 형식을 위한 별도 항목을 만들지 않고 `위험과 사용자 확인`에 그 사실을 짧게 밝힌다(예: "외부 신원 인증 서비스 실제 연결은 확인하지 못했습니다"). 그런 사실이 있으면 `보고서 상태`에 `확인 완료`를 쓰지 않는다.
61
+
56
62
  ## 3. 일반 작업 보고
57
63
 
58
64
  기능 동작이 바뀌거나 여러 파일이 수정된 경우 사용한다.
@@ -1,5 +1,5 @@
1
1
  {
2
- "cli": "1.1.0",
2
+ "cli": "2.0.1",
3
3
  "skill": "확인 필요",
4
4
  "mcp": "0.1.0",
5
5
  "admin": "0.1.0"
package/dist/cli.js CHANGED
@@ -11,6 +11,7 @@ import { useCommand, connectCommand, disconnectCommand, switchCommand } from './
11
11
  import { sessionCommand } from './commands/session/index.js';
12
12
  import { upgradeCommand } from './commands/upgrade.js';
13
13
  import { migrateCommand } from './commands/migrate.js';
14
+ import { maybeShowFundingNotice } from './output/funding.js';
14
15
  function safeError(message, verbose) {
15
16
  if (verbose)
16
17
  return message;
@@ -21,6 +22,9 @@ function safeError(message, verbose) {
21
22
  export async function run(argv = process.argv.slice(2), io = createIo(), legacyAlias = false) {
22
23
  if (legacyAlias)
23
24
  io.write('`beginner-bridge`는 이전 명령입니다. 앞으로는 `jutell` 사용을 권장합니다.');
25
+ const fundingSuppressed = argv.includes('--no-funding');
26
+ if (fundingSuppressed)
27
+ argv = argv.filter((a) => a !== '--no-funding');
24
28
  if (argv.includes('--version')) {
25
29
  io.write((await readVersionInfo()).cli);
26
30
  return 0;
@@ -72,6 +76,7 @@ export async function run(argv = process.argv.slice(2), io = createIo(), legacyA
72
76
  await sessionCommand(paths, options, io, extraArgs);
73
77
  else
74
78
  throw new Error(`알 수 없는 명령입니다: ${command}`);
79
+ maybeShowFundingNotice(paths, io, fundingSuppressed);
75
80
  return 0;
76
81
  }
77
82
  catch (error) {
@@ -102,11 +102,27 @@ export async function migrateCommand(paths, options, io) {
102
102
  const legacyPattern = /# BEGINNER_BRIDGE_CLI_MCP_BEGIN[\s\S]*?# BEGINNER_BRIDGE_CLI_MCP_END\n?/m;
103
103
  const legacyPattern2 = /# BEGINNER_BRIDGE_MCP_BEGIN[\s\S]*?# BEGINNER_BRIDGE_MCP_END\n?/m;
104
104
  let next = text.replace(legacyPattern, '').replace(legacyPattern2, '');
105
- // Also remove unmarked legacy with heuristic: if beginner_bridge still present but not in managed block, check evidence
106
- if (/^\s*\[mcp_servers\.beginner_bridge\]/m.test(next) && /(?:assets|apps)[\\/]mcp-server/i.test(next.slice(next.search(/^\s*\[mcp_servers\.beginner_bridge\]/m), next.search(/^\s*\[mcp_servers\.beginner_bridge\]/m) + 1200))) {
107
- // Remove that section (from header until next header/marker or end)
108
- const idx = next.search(/^\s*\[mcp_servers\.beginner_bridge\]/m);
109
- const after = next.slice(idx);
105
+ // Also remove unmarked legacy with heuristic: if beginner_bridge still present but not in managed block, check evidence.
106
+ // Use indexOf (not a `\s*`-prefixed regex .search()) to find the header: `^\s*\[...\]` lets
107
+ // `\s*` swallow a preceding blank line, so .search() can return an index *before* the literal
108
+ // `[` - e.g. when `use codex` leaves a blank-line separator above this block (its normal
109
+ // output shape). `after.slice(1)` below then only strips 1 of those whitespace chars, lands
110
+ // back inside the same header, and "the next header" it finds is this one again, so nothing
111
+ // ever gets cut. indexOf always anchors exactly at `[`, where slice(1) is meant to start from.
112
+ const beginnerHeader = '[mcp_servers.beginner_bridge]';
113
+ const headerIdx = next.indexOf(beginnerHeader);
114
+ if (headerIdx >= 0) {
115
+ // Bound the section to *this table only* (up to the next `[section]` header,
116
+ // the canonical marker, or EOF) before doing anything else with it. The
117
+ // evidence check below must only ever look inside that bound - a flat N-char
118
+ // lookahead from the header (the previous approach) reads past this table's
119
+ // own end into whatever comes next in the file, and a JuTell-managed config
120
+ // almost always has the real `assets/mcp-server` canonical entry sitting
121
+ // right after the legacy one - so that flat window would find canonical's
122
+ // path and treat it as evidence for the *unrelated* entry above it, deleting
123
+ // a genuinely unrelated user-owned `beginner_bridge` server that merely
124
+ // happens to sit next to JuTell's own block in the same file.
125
+ const after = next.slice(headerIdx);
110
126
  const nextHeader = after.slice(1).search(/^\s*\[mcp_servers\./m);
111
127
  const nextMarker = after.search(/#\s*JUTELL_CLI_MCP_BEGIN/m);
112
128
  let cut = after.length;
@@ -114,7 +130,10 @@ export async function migrateCommand(paths, options, io) {
114
130
  cut = Math.min(cut, nextHeader + 1);
115
131
  if (nextMarker >= 0)
116
132
  cut = Math.min(cut, nextMarker);
117
- next = next.slice(0, idx) + after.slice(cut);
133
+ const ownSection = after.slice(0, cut);
134
+ if (/(?:assets|apps)[\\/]mcp-server/i.test(ownSection)) {
135
+ next = next.slice(0, headerIdx) + after.slice(cut);
136
+ }
118
137
  }
119
138
  next = next.replace(/\n{3,}/g, '\n\n').trim();
120
139
  await writeTextSafely(file, next ? `${next}\n` : '');
@@ -55,14 +55,16 @@ export async function getStatus(paths) {
55
55
  const warnings = [];
56
56
  if (!config.valid)
57
57
  warnings.push('설정 파일을 읽지 못해 balanced 기본값을 사용 중입니다.');
58
+ if (config.invalidLimitsFields.length)
59
+ warnings.push(`.jutell.json의 limits 값 중 숫자가 아닌 항목이 있어 기본값을 대신 사용했습니다: ${config.invalidLimitsFields.join(', ')}. 실제 파일 값은 바뀌지 않았으니 직접 고쳐주세요.`);
58
60
  if (registration.conflict)
59
61
  warnings.push('같은 이름의 관리되지 않는 Codex MCP 설정이 있어 자동 변경하지 않았습니다.');
60
62
  if (opencode.conflict)
61
63
  warnings.push('OpenCode 설정에 같은 이름의 관리되지 않는 MCP 항목이 있어 자동 변경하지 않았습니다.');
62
64
  if (registration.bothRegistered)
63
- warnings.push('Codex에 canonical jutell과 legacy beginner_bridge MCP가 모두 있습니다. 자동 정리하지 않았습니다. 이전 항목 제거는 추후 안전한 마이그레이션에서 안내합니다.');
65
+ warnings.push('Codex에 canonical jutell과 legacy beginner_bridge MCP가 모두 있습니다. 자동 정리하지 않았습니다. 이전 항목을 정리하려면 jutell migrate --clean 을 실행하세요.');
64
66
  if (opencode.bothRegistered)
65
- warnings.push('OpenCode에 canonical jutell과 legacy beginner_bridge MCP가 모두 있습니다. 자동 정리하지 않았습니다. 이전 항목 제거는 추후 안전한 마이그레이션에서 안내합니다.');
67
+ warnings.push('OpenCode에 canonical jutell과 legacy beginner_bridge MCP가 모두 있습니다. 자동 정리하지 않았습니다. 이전 항목을 정리하려면 jutell migrate --clean 을 실행하세요.');
66
68
  if (registration.legacyRegistered && !registration.canonicalRegistered)
67
69
  warnings.push('Codex에 이전 beginner_bridge 항목만 있습니다. jutell use codex 를 실행하면 보존하면서 새 jutell 항목을 추가합니다.');
68
70
  if (opencode.legacyRegistered && !opencode.canonicalRegistered)
@@ -176,9 +178,14 @@ export async function getDoctorResults(paths) {
176
178
  checks.push({ name: 'Claude Code MCP', status: claude.registered ? '정상' : '주의', detail: claude.registered ? `${claude.claudeScope} 범위(${claude.claudeScope === 'user' ? '사용자 전역' : '현재 프로젝트'})에 등록되어 있습니다.` : 'Claude Code MCP가 등록되지 않았습니다.' });
177
179
  checks.push({ name: config.source === 'legacy' ? '.beginner-bridge.json' : '.jutell.json', status: config.valid ? '정상' : '오류', detail: config.exists ? (config.valid ? (config.source === 'legacy' ? '이전 설정 파일을 읽었습니다. 새 .jutell.json이 없으면 사용합니다.' : '설정 형식을 확인했습니다.') : '설정이 올바르지 않아 기본값을 사용합니다.') : '없으면 기본 설정을 사용합니다.' });
178
180
  const featuresValid = Object.keys(config.config.features).every((id) => FEATURE_IDS.includes(id));
179
- const limitsValid = config.config.limits.maxMainFiles >= 1 && config.config.limits.maxMainFiles <= 10 && config.config.limits.maxGlossaryTerms >= 0 && config.config.limits.maxGlossaryTerms <= 10 && config.config.limits.compactReportMaxSentences >= 4 && config.config.limits.compactReportMaxSentences <= 30;
181
+ // Range check runs on the already-normalized (defaulted) values, so it can never itself
182
+ // fail - normalizeConfig() only ever produces values inside these ranges. Fold in
183
+ // invalidLimitsFields (computed from the raw, pre-normalization file) so a malformed
184
+ // value that got silently replaced with its default is still reported as unhealthy,
185
+ // not "정상" for a file whose actual on-disk content doesn't match what's checked.
186
+ const limitsValid = config.config.limits.maxMainFiles >= 1 && config.config.limits.maxMainFiles <= 10 && config.config.limits.maxGlossaryTerms >= 0 && config.config.limits.maxGlossaryTerms <= 10 && config.config.limits.compactReportMaxSentences >= 4 && config.config.limits.compactReportMaxSentences <= 30 && config.invalidLimitsFields.length === 0;
180
187
  checks.push({ name: '공식 Feature ID', status: featuresValid ? '정상' : '오류', detail: featuresValid ? '현재 공식 ID만 확인했습니다.' : '지원하지 않는 Feature ID가 있습니다.' });
181
- checks.push({ name: 'limits', status: limitsValid ? '정상' : '오류', detail: limitsValid ? '허용 범위를 확인했습니다.' : '허용 범위를 벗어난 값이 있습니다.' });
188
+ checks.push({ name: 'limits', status: limitsValid ? '정상' : '오류', detail: config.invalidLimitsFields.length ? `숫자가 아닌 값이 있어 기본값을 대신 사용했습니다: ${config.invalidLimitsFields.join(', ')}.` : limitsValid ? '허용 범위를 확인했습니다.' : '허용 범위를 벗어난 값이 있습니다.' });
182
189
  checks.push({ name: '로컬 관리자 빌드', status: await exists(adminEntry) ? '정상' : '오류', detail: await exists(adminEntry) ? '관리자 화면 파일을 확인했습니다.' : '관리자 화면 파일이 없습니다.' });
183
190
  checks.push({ name: '포트 사용 가능 여부', status: await portAvailable() ? '정상' : '주의', detail: '127.0.0.1의 임시 포트를 확인했습니다.' });
184
191
  checks.push({ name: '쓰기 권한', status: await writeCheck(paths) ? '정상' : '오류', detail: '로컬 상태 폴더에 임시 파일을 만들고 삭제했습니다.' });
@@ -2,7 +2,7 @@ import { assets, codexScopedPaths, packageRoot } from '../config/paths.js';
2
2
  import { readCodexRegistration, registerMcp, snapshot, restore } from '../config/managed.js';
3
3
  import { ensureBridgeConfig, setMcpEnabled } from '../installer/config.js';
4
4
  import { installSkill, recordSkillFiles, removeAddedSkillFiles } from '../installer/skill.js';
5
- import { agentsFile, ensureJuTellAgentsBlock } from '../installer/agents.js';
5
+ import { agentsFile, claudeMdFile, ensureJuTellAgentsBlock } from '../installer/agents.js';
6
6
  import { opencodeDetected, readOpenCodeRegistration, registerOpenCodeMcp, setOpenCodeEnabled } from '../installer/opencode.js';
7
7
  import { readClaudeRegistration, registerClaudeMcp, removeClaudeMcp } from '../installer/claude.js';
8
8
  import { findProvider, supportedProviderNames } from '../installer/providers.js';
@@ -54,8 +54,12 @@ async function resolveTarget(args, io) {
54
54
  async function registrationSnapshots(paths) {
55
55
  const opencode = await readOpenCodeRegistration(paths, packageRoot(), false);
56
56
  const files = [paths.configFile, paths.codexConfigFile, codexScopedPaths(paths).codexConfigFile, opencode.file, paths.claudeConfigFile];
57
+ // registerClaudeMcp writes CLAUDE.md before touching the MCP entry (see its
58
+ // comment) - snapshot it too so a failure partway through `use claude`
59
+ // rolls it back along with AGENTS.md instead of leaving it added while the
60
+ // MCP registration itself got rolled back.
57
61
  if (paths.scope === 'project')
58
- files.push(agentsFile(paths.targetRoot));
62
+ files.push(agentsFile(paths.targetRoot), claudeMdFile(paths.targetRoot));
59
63
  return Promise.all(files.map((file) => snapshot(file)));
60
64
  }
61
65
  async function registerProviderEnabled(paths, provider, io) {
@@ -63,7 +67,7 @@ async function registerProviderEnabled(paths, provider, io) {
63
67
  await adapter.register(paths, true);
64
68
  const current = await adapter.read(paths, true);
65
69
  if (current.canonicalRegistered && current.legacyRegistered) {
66
- io.write('\n이전 beginner_bridge 항목을 그대로 두고 새 jutell 항목을 추가했습니다.\n이전 항목은 자동으로 삭제하지 않습니다. 제거는 추후 안전한 마이그레이션에서 안내합니다.');
70
+ io.write('\n이전 beginner_bridge 항목을 그대로 두고 새 jutell 항목을 추가했습니다.\n이전 항목은 자동으로 삭제하지 않습니다. 정리하려면 jutell migrate --clean 을 실행하세요.');
67
71
  }
68
72
  if (provider.id === 'codex') {
69
73
  io.write('\nCodex는 MCP 서버 목록을 사용자 전역 설정에서만 읽습니다.\nJuTell 프로젝트 규칙(AGENTS.md, Skill, 설정)은 이 프로젝트에 그대로 두고,\nCodex MCP 연결만 사용자 전역 설정(Codex 홈)에 등록했습니다.');
@@ -109,19 +109,40 @@ export function normalizeConfig(value) {
109
109
  voice: { preset: voicePreset },
110
110
  };
111
111
  }
112
+ const LIMITS_KEYS = ['maxMainFiles', 'maxGlossaryTerms', 'compactReportMaxSentences'];
113
+ // normalizeConfig() silently substitutes the schema default for any limits field that
114
+ // isn't a valid integer - correct for making the CLI keep working, but it never signals
115
+ // that the substitution happened, so a user's own (invalid) edit is silently overridden
116
+ // with no way to notice. Computed separately here (readBridgeConfig has the raw parsed
117
+ // object; normalizeConfig only returns the already-normalized result) so status/doctor
118
+ // can warn about it without changing normalizeConfig's own return shape or call sites.
119
+ function invalidLimitsFields(parsed) {
120
+ if (!('limits' in parsed))
121
+ return []; // never set - nothing of the user's is being overridden
122
+ const limits = parsed.limits;
123
+ // `limits` present but not a plain object (a string, array, number, boolean, or null)
124
+ // is the same silent-override problem one level up: normalizeConfig()'s own
125
+ // `input.limits && typeof === 'object' && !Array.isArray` guard treats any of these
126
+ // as if `limits` were `{}` and defaults every field - so report all three as invalid
127
+ // rather than picking apart a shape that was never a fields-object to begin with.
128
+ if (!limits || typeof limits !== 'object' || Array.isArray(limits))
129
+ return [...LIMITS_KEYS];
130
+ const record = limits;
131
+ return LIMITS_KEYS.filter((key) => key in record && !(typeof record[key] === 'number' && Number.isInteger(record[key])));
132
+ }
112
133
  export async function readBridgeConfig(paths) {
113
134
  const preferred = await readText(paths.configFile);
114
135
  const raw = preferred ?? await readText(paths.legacyConfigFile);
115
136
  const source = preferred !== undefined ? 'new' : raw !== undefined ? 'legacy' : 'default';
116
137
  if (!raw)
117
- return { config: await defaultConfig(), exists: false, valid: true, source };
138
+ return { config: await defaultConfig(), exists: false, valid: true, source, invalidLimitsFields: [] };
118
139
  try {
119
140
  const parsed = JSON.parse(raw);
120
141
  const valid = parsed.version === 1 && typeof parsed.profile === 'string' && PROFILES.includes(parsed.profile);
121
- return { config: normalizeConfig(parsed), exists: true, valid, source };
142
+ return { config: normalizeConfig(parsed), exists: true, valid, source, invalidLimitsFields: invalidLimitsFields(parsed) };
122
143
  }
123
144
  catch {
124
- return { config: await defaultConfig(), exists: true, valid: false, source };
145
+ return { config: await defaultConfig(), exists: true, valid: false, source, invalidLimitsFields: [] };
125
146
  }
126
147
  }
127
148
  export async function writeBridgeConfig(paths, config) {
@@ -6,6 +6,7 @@ const managedBlock = `${JUTELL_AGENTS_BEGIN}
6
6
  ## JuTell
7
7
 
8
8
  - 코드 또는 문서 변경 후 \`.agents/skills/beginner-bridge/SKILL.md\`와 \`.jutell.json\`을 먼저 읽습니다. 소유자 대상 구현/보고 전에는 JuTell 보고 규칙을 먼저 적용해 최종 답변을 작성합니다.
9
+ - 실제로 코드나 문서를 바꾸기 전에는, 요청에 결과를 크게 바꿀 수 있는 불확실함이 있는지 SKILL.md의 Intent Bridge 규칙으로 판단합니다. 불확실함이 없거나 \`requestClarificationGuide\`가 꺼져 있으면 평소처럼 바로 진행합니다.
9
10
  - JuTell MCP가 보이면 canonical \`jutell\` 서버를 사용합니다. \`jutell\`과 legacy \`beginner_bridge\`가 모두 보이면 \`jutell\`을 우선하고 \`beginner_bridge\`는 호환용으로만 사용합니다.
10
11
  - JuTell MCP를 사용할 수 있고 이미 확보한 근거로 보고·검증·핸드오프의 모호함을 줄여줄 때는 그 도구를 우선합니다. 사용할 수 없거나 Provider 정책으로 막혀 있으면 작업을 멈추지 않고 JuTell Skill로 계속합니다. 실제로 호출해 응답을 받은 경우에만 JuTell MCP를 사용했다고 말합니다.
11
12
  - 확인하지 않은 결과를 사실처럼 표현하지 않습니다.
@@ -18,12 +19,22 @@ function markerPattern() {
18
19
  export function agentsFile(projectRoot) {
19
20
  return path.join(projectRoot, 'AGENTS.md');
20
21
  }
21
- export async function hasJuTellAgentsBlock(projectRoot) {
22
- const content = await readText(agentsFile(projectRoot));
22
+ // Claude Code does not auto-discover `AGENTS.md` the way Codex/OpenCode do - it
23
+ // auto-loads `CLAUDE.md` instead (verified empirically: a project with only an
24
+ // AGENTS.md canary instruction was never followed, the same canary in CLAUDE.md
25
+ // always was). Without this, `jutell use claude` reports a healthy MCP
26
+ // connection while the agent never learns to read SKILL.md or call jutell_*
27
+ // tools, because the one file telling it to do that sits somewhere Claude Code
28
+ // doesn't automatically read. Same managed block, same markers, second file -
29
+ // see `ensureJuTellClaudeMdBlock` below, wired in from installer/claude.ts.
30
+ export function claudeMdFile(projectRoot) {
31
+ return path.join(projectRoot, 'CLAUDE.md');
32
+ }
33
+ async function hasManagedBlock(file) {
34
+ const content = await readText(file);
23
35
  return Boolean(content && markerPattern().test(content));
24
36
  }
25
- export async function ensureJuTellAgentsBlock(projectRoot) {
26
- const file = agentsFile(projectRoot);
37
+ async function ensureManagedBlock(file) {
27
38
  const current = await readText(file) ?? '';
28
39
  const next = markerPattern().test(current)
29
40
  ? current.replace(markerPattern(), managedBlock).replace(/\n{3,}/g, '\n\n').trimEnd() + '\n'
@@ -32,8 +43,7 @@ export async function ensureJuTellAgentsBlock(projectRoot) {
32
43
  await writeTextSafely(file, next);
33
44
  return { changed: next !== current };
34
45
  }
35
- export async function removeJuTellAgentsBlock(projectRoot) {
36
- const file = agentsFile(projectRoot);
46
+ async function removeManagedBlock(file) {
37
47
  const current = await readText(file);
38
48
  if (!current || !markerPattern().test(current))
39
49
  return { changed: false };
@@ -41,3 +51,21 @@ export async function removeJuTellAgentsBlock(projectRoot) {
41
51
  await writeTextSafely(file, next ? `${next}\n` : '');
42
52
  return { changed: true };
43
53
  }
54
+ export async function hasJuTellAgentsBlock(projectRoot) {
55
+ return hasManagedBlock(agentsFile(projectRoot));
56
+ }
57
+ export async function ensureJuTellAgentsBlock(projectRoot) {
58
+ return ensureManagedBlock(agentsFile(projectRoot));
59
+ }
60
+ export async function removeJuTellAgentsBlock(projectRoot) {
61
+ return removeManagedBlock(agentsFile(projectRoot));
62
+ }
63
+ export async function hasJuTellClaudeMdBlock(projectRoot) {
64
+ return hasManagedBlock(claudeMdFile(projectRoot));
65
+ }
66
+ export async function ensureJuTellClaudeMdBlock(projectRoot) {
67
+ return ensureManagedBlock(claudeMdFile(projectRoot));
68
+ }
69
+ export async function removeJuTellClaudeMdBlock(projectRoot) {
70
+ return removeManagedBlock(claudeMdFile(projectRoot));
71
+ }
@@ -2,6 +2,7 @@ import { execFileSync } from 'node:child_process';
2
2
  import path from 'node:path';
3
3
  import { readText } from '../config/managed.js';
4
4
  import { claudeHome } from '../config/paths.js';
5
+ import { ensureJuTellClaudeMdBlock, removeJuTellClaudeMdBlock } from './agents.js';
5
6
  export const CLAUDE_MCP_KEY = 'jutell';
6
7
  function normalizeForCompare(value) {
7
8
  return value.replace(/\\/g, '/').toLowerCase();
@@ -119,6 +120,14 @@ function runClaude(args, paths) {
119
120
  });
120
121
  }
121
122
  export async function registerClaudeMcp(paths, packageRoot, enabled) {
123
+ // Claude Code auto-loads `CLAUDE.md`, not `AGENTS.md` (verified empirically -
124
+ // see the comment on ensureJuTellClaudeMdBlock). Without a managed CLAUDE.md,
125
+ // the MCP server below connects fine but the agent never learns to read
126
+ // SKILL.md or call jutell_* tools, since AGENTS.md alone isn't guaranteed to
127
+ // be discovered. Ensured unconditionally (before the idempotent early-return
128
+ // below) so a repeat `use claude` still restores it if a user deleted it.
129
+ if (paths.scope === 'project')
130
+ await ensureJuTellClaudeMdBlock(paths.targetRoot);
122
131
  const current = await readClaudeRegistration(paths, packageRoot, enabled);
123
132
  const config = await readClaudeConfig(paths);
124
133
  if (config === undefined)
@@ -147,6 +156,13 @@ export async function registerClaudeMcp(paths, packageRoot, enabled) {
147
156
  }
148
157
  export async function removeClaudeMcp(paths, packageRoot) {
149
158
  const current = await readClaudeRegistration(paths, packageRoot, false);
159
+ // CLAUDE.md is Claude-specific (no other provider reads it, same way only
160
+ // OpenCode reads its own opencode.json mcp block) - unlike AGENTS.md, which
161
+ // stays shared across providers and is only ever touched by disable/uninstall.
162
+ // Removed here unconditionally so disconnect/switch/uninstall - every caller
163
+ // of removeClaudeMcp - clean it up too, not just the MCP entry.
164
+ if (paths.scope === 'project')
165
+ await removeJuTellClaudeMdBlock(paths.targetRoot);
150
166
  if (!current.registered)
151
167
  return current;
152
168
  try {
@@ -140,7 +140,7 @@ export function parseOptions(args) {
140
140
  }
141
141
  export function scopeLabel(scope) { return scope === 'global' ? '사용자 전역' : '현재 프로젝트'; }
142
142
  export function printHelp(io) {
143
- io.write(`JuTell CLI 1.1.0
143
+ io.write(`JuTell CLI 2.0.1
144
144
 
145
145
  시작할 때는 jutell만 입력하면 됩니다.
146
146
  설치된 Coding Agent(Codex, OpenCode, Claude Code)를 찾아 연결하고,
@@ -0,0 +1,38 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ export const FUNDING_URL = 'https://ko-fi.com/ju0o___';
4
+ const MARKER_FILE = 'funding-notice-shown';
5
+ function truthy(value) {
6
+ if (!value)
7
+ return false;
8
+ const normalized = value.trim().toLowerCase();
9
+ return normalized !== '' && normalized !== '0' && normalized !== 'false';
10
+ }
11
+ /**
12
+ * Shows the support line at most once per machine, and only in a real
13
+ * terminal. Never runs in CI, never blocks, and never changes the exit code:
14
+ * any failure here is swallowed so a funding message can't break a command.
15
+ */
16
+ export function maybeShowFundingNotice(paths, io, suppressed) {
17
+ try {
18
+ if (suppressed)
19
+ return;
20
+ if (truthy(process.env.JUTELL_NO_FUNDING))
21
+ return;
22
+ if (truthy(process.env.CI))
23
+ return;
24
+ if (!process.stdout.isTTY)
25
+ return;
26
+ const marker = path.join(paths.dataRoot, MARKER_FILE);
27
+ if (fs.existsSync(marker))
28
+ return;
29
+ fs.mkdirSync(paths.dataRoot, { recursive: true });
30
+ fs.writeFileSync(marker, new Date().toISOString());
31
+ io.write('');
32
+ io.write(`JuTell은 무료이고 앞으로도 무료입니다. 도움이 되셨다면: ${FUNDING_URL}`);
33
+ io.write('이 안내는 다시 표시되지 않습니다. (끄기: JUTELL_NO_FUNDING=1 또는 --no-funding)');
34
+ }
35
+ catch {
36
+ // A support message must never affect the command result.
37
+ }
38
+ }
@@ -6,7 +6,7 @@ const INITIALIZE = JSON.stringify({
6
6
  jsonrpc: '2.0',
7
7
  id: 1,
8
8
  method: 'initialize',
9
- params: { protocolVersion: PROTOCOL_VERSION, capabilities: {}, clientInfo: { name: 'jutell-doctor', version: '1.1.0' } },
9
+ params: { protocolVersion: PROTOCOL_VERSION, capabilities: {}, clientInfo: { name: 'jutell-doctor', version: '2.0.1' } },
10
10
  });
11
11
  const INITIALIZED = JSON.stringify({ jsonrpc: '2.0', method: 'notifications/initialized' });
12
12
  const TOOLS_LIST = JSON.stringify({ jsonrpc: '2.0', id: 2, method: 'tools/list', params: {} });
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "jutell",
3
- "version": "1.1.0",
4
- "description": "JuTell by Ju0 — non-developer harness for AI coding agents (Skill, MCP, local dashboard)",
3
+ "version": "2.0.1",
4
+ "description": "Understand what your AI coding agent actually did - a clarify-before / verify-after layer for Codex, Claude Code and OpenCode",
5
5
  "license": "MIT",
6
6
  "repository": {
7
7
  "type": "git",
@@ -11,16 +11,31 @@
11
11
  "bugs": {
12
12
  "url": "https://github.com/ju0o/jutell/issues"
13
13
  },
14
+ "funding": {
15
+ "type": "ko_fi",
16
+ "url": "https://ko-fi.com/ju0o___"
17
+ },
14
18
  "keywords": [
15
19
  "jutell",
16
20
  "ai",
21
+ "ai-agent",
22
+ "coding-agent",
17
23
  "agent",
18
24
  "harness",
19
- "mcp",
20
- "skill",
21
25
  "codex",
26
+ "claude-code",
27
+ "claude",
22
28
  "opencode",
23
- "claude-code"
29
+ "mcp",
30
+ "mcp-server",
31
+ "skill",
32
+ "cli",
33
+ "developer-tools",
34
+ "ai-tools",
35
+ "llm",
36
+ "code-review",
37
+ "vibe-coding",
38
+ "korean"
24
39
  ],
25
40
  "author": "Ju0",
26
41
  "engines": {