jutell 1.0.0 → 1.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,6 +1,6 @@
1
1
  # jutell
2
2
 
3
- JuTell by Ju0의 로컬 설치·상태·진단·관리자 실행 CLI입니다. `jutell@0.3.0`은 npm에 공개되었습니다.
3
+ JuTell by Ju0의 로컬 설치·상태·진단·관리자 실행 CLI입니다. `jutell@1.0.0`은 npm에 공개되었습니다.
4
4
 
5
5
  ```powershell
6
6
  npm install -g jutell
@@ -11,7 +11,7 @@ jutell status
11
11
  개발자·기여자용 로컬 설치 (일반 사용자는 위 registry 설치 사용):
12
12
 
13
13
  ```bash
14
- npm install -g ./jutell-1.0.0.tgz
14
+ npm install -g ./jutell-1.0.1.tgz
15
15
  ```
16
16
 
17
17
  기존 `beginner-bridge` 명령은 같은 기능의 호환 별칭이며 실행 시 새 명령 사용을 안내합니다. CLI는 프로젝트 코드, Prompt, AI 답변, Git diff와 비밀정보를 수집하거나 외부로 전송하지 않습니다.
@@ -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.0.0',
8
+ version: '1.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.',
11
11
  });
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: beginner-bridge
3
- jutellSkillVersion: "1.0.0"
3
+ jutellSkillVersion: "1.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
  ---
@@ -1,5 +1,5 @@
1
1
  {
2
- "cli": "1.0.0",
2
+ "cli": "1.0.1",
3
3
  "skill": "확인 필요",
4
4
  "mcp": "0.1.0",
5
5
  "admin": "0.1.0"
@@ -149,17 +149,43 @@ function withoutCanonicalBlock(content) {
149
149
  function hasMcpKey(content, key) {
150
150
  return new RegExp(`^\\s*\\[mcp_servers\\.${key}\\]\\s*$`, 'm').test(content);
151
151
  }
152
- function hasJuTellMcpEvidence(content, key) {
152
+ function keySlice(content, key) {
153
153
  const keyPattern = new RegExp(`^\\s*\\[mcp_servers\\.${key}\\]\\s*$`, 'm');
154
154
  const match = content.match(keyPattern);
155
155
  if (!match || match.index === undefined)
156
+ return undefined;
157
+ return content.slice(match.index, match.index + 1200);
158
+ }
159
+ function hasJuTellMcpEvidence(content, key) {
160
+ const slice = keySlice(content, key);
161
+ if (!slice)
156
162
  return false;
157
- const slice = content.slice(match.index, match.index + 1200);
158
163
  // JuTell's server always contains `assets/mcp-server` or `apps/mcp-server` in args (see buildMcpBlock)
159
164
  // Unrelated custom entries (e.g. jira, other, even a custom `jutell` pointing to `not-mcp-server.js`) do not –
160
165
  // this prevents broadly adopting arbitrary unmarked MCP entries.
161
166
  return /(?:assets|apps)[\\/]mcp-server/i.test(slice);
162
167
  }
168
+ function extractField(block, field) {
169
+ if (!block)
170
+ return undefined;
171
+ const match = block.match(new RegExp(`^\\s*${field}\\s*=\\s*(.+?)\\s*$`, 'm'));
172
+ return match?.[1];
173
+ }
174
+ // Removes a bare (unmarked) `[mcp_servers.<key>]` table — header line through its own
175
+ // key/value lines, up to the next table header or end of file. Used when repairing a stale
176
+ // heuristic-recognized entry that predates JUTELL_CLI_MCP markers, so a rewrite replaces it
177
+ // instead of leaving a duplicate `[mcp_servers.<key>]` table behind.
178
+ function withoutKeySection(content, key) {
179
+ const headerPattern = new RegExp(`^\\s*\\[mcp_servers\\.${key}\\]\\s*$`, 'm');
180
+ const match = content.match(headerPattern);
181
+ if (!match || match.index === undefined)
182
+ return content;
183
+ const start = match.index;
184
+ const afterHeader = start + match[0].length;
185
+ const nextHeaderMatch = content.slice(afterHeader).match(/^\s*\[/m);
186
+ const end = nextHeaderMatch?.index !== undefined ? afterHeader + nextHeaderMatch.index : content.length;
187
+ return content.slice(0, start) + content.slice(end);
188
+ }
163
189
  function tomlString(value) {
164
190
  return JSON.stringify(value.replaceAll('\\', '/'));
165
191
  }
@@ -207,6 +233,15 @@ export async function readCodexRegistration(paths, packageRoot, enabled) {
207
233
  }
208
234
  return false;
209
235
  })();
236
+ const preview = buildMcpBlock(paths, packageRoot, enabled);
237
+ // The command/args a currently-recognized JuTell entry (managed-block or heuristic-evidence)
238
+ // actually has, vs. what the current installation would write today. A mismatch means the
239
+ // entry is stale — e.g. left over from a different machine/OS or an older JuTell install —
240
+ // and should be repaired even though the key is already registered and `enabled` already matches.
241
+ const canonicalEntryText = canonicalManaged ?? (canonicalHeuristic ? keySlice(content, CANONICAL_MCP_KEY) : undefined);
242
+ const canonicalUpToDate = Boolean(canonicalEntryText
243
+ && extractField(canonicalEntryText, 'command') === extractField(preview, 'command')
244
+ && extractField(canonicalEntryText, 'args') === extractField(preview, 'args'));
210
245
  return {
211
246
  content,
212
247
  exists: content.length > 0,
@@ -216,17 +251,23 @@ export async function readCodexRegistration(paths, packageRoot, enabled) {
216
251
  canonicalRegistered,
217
252
  legacyRegistered,
218
253
  bothRegistered: canonicalRegistered && legacyRegistered,
219
- preview: buildMcpBlock(paths, packageRoot, enabled),
254
+ canonicalUpToDate,
255
+ preview,
220
256
  };
221
257
  }
222
258
  export async function registerMcp(paths, packageRoot, enabled) {
223
259
  const current = await readCodexRegistration(paths, packageRoot, enabled);
224
260
  if (current.conflict)
225
261
  throw new Error('Codex 설정에 같은 이름의 관리되지 않는 MCP 항목이 있어 자동 변경하지 않았습니다.');
226
- if (current.canonicalRegistered && current.enabled === enabled)
262
+ if (current.canonicalRegistered && current.enabled === enabled && current.canonicalUpToDate)
227
263
  return current;
228
264
  await backupFile(paths.codexConfigFile);
229
- const withoutManaged = withoutCanonicalBlock(current.content).replace(/\n{3,}/g, '\n\n').trimEnd();
265
+ // withoutCanonicalBlock only removes a marker-wrapped block. A stale entry recognized by
266
+ // heuristic evidence alone (no markers, e.g. from an older JuTell install) would otherwise
267
+ // survive as a leftover bare `[mcp_servers.jutell]` table alongside the freshly-appended one.
268
+ // Safe to strip unconditionally here: reaching this point already ruled out (via `conflict`
269
+ // above) any bare `[mcp_servers.jutell]` that isn't JuTell's own.
270
+ const withoutManaged = withoutKeySection(withoutCanonicalBlock(current.content), CANONICAL_MCP_KEY).replace(/\n{3,}/g, '\n\n').trimEnd();
230
271
  const next = `${withoutManaged}${withoutManaged ? '\n\n' : ''}${current.preview}\n`;
231
272
  await writeTextSafely(paths.codexConfigFile, next);
232
273
  return readCodexRegistration(paths, packageRoot, enabled);
@@ -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.0.0
143
+ io.write(`JuTell CLI 1.0.1
144
144
 
145
145
  시작할 때는 jutell만 입력하면 됩니다.
146
146
  처음 연결하면 안내에 따라 AI Agent와 보고 방식을 고르고
@@ -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.0.0' } },
9
+ params: { protocolVersion: PROTOCOL_VERSION, capabilities: {}, clientInfo: { name: 'jutell-doctor', version: '1.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,6 +1,6 @@
1
1
  {
2
2
  "name": "jutell",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "JuTell by Ju0 — non-developer harness for AI coding agents (Skill, MCP, local dashboard)",
5
5
  "license": "MIT",
6
6
  "repository": {