@sdsrs/code-graph 0.8.4 → 0.9.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/bin/cli.js CHANGED
@@ -7,6 +7,17 @@ const path = require("path");
7
7
  // and detect dev mode from bin/ → repo root (one level up)
8
8
  process.env._FIND_BINARY_ROOT = path.resolve(__dirname, "..");
9
9
 
10
+ // Intercept adopt / unadopt before forwarding — they're node-only concerns
11
+ // (write to ~/.claude/projects/<slug>/memory/) and have no Rust counterpart.
12
+ // Lets `code-graph-mcp adopt` / `unadopt` work uniformly across plugin / npm / npx.
13
+ const sub = process.argv[2];
14
+ if (sub === "adopt" || sub === "unadopt") {
15
+ const { adopt, unadopt, formatResult } = require("../claude-plugin/scripts/adopt");
16
+ const result = sub === "unadopt" ? unadopt() : adopt();
17
+ process.stdout.write(formatResult(sub, result) + "\n");
18
+ process.exit(result.ok === false ? 1 : 0);
19
+ }
20
+
10
21
  const { findBinary } = require("../claude-plugin/scripts/find-binary");
11
22
 
12
23
  const binary = findBinary();
@@ -4,7 +4,7 @@
4
4
  "author": {
5
5
  "name": "sdsrs"
6
6
  },
7
- "version": "0.8.4",
7
+ "version": "0.9.1",
8
8
  "keywords": [
9
9
  "code-graph",
10
10
  "ast",
@@ -9,7 +9,11 @@ const os = require('os');
9
9
 
10
10
  const SENTINEL_BEGIN = '<!-- code-graph-mcp:begin v1 -->';
11
11
  const SENTINEL_END = '<!-- code-graph-mcp:end -->';
12
- const INDEX_LINE = '- [code-graph-mcp](plugin_code_graph_mcp.md) — "谁调 X / 改 X 炸啥 / 模块结构" → `get_call_graph` / `impact_analysis` / `module_overview`,替代多步 Grep/Read';
12
+ const INDEX_LINE = [
13
+ '- [code-graph-mcp](plugin_code_graph_mcp.md) — 代码理解 12 工具(替代 Grep/Read 多步):',
14
+ ' - 问"谁调/改动/模块/概念/HTTP/相似" → `get_call_graph`/`impact_analysis`/`module_overview`/`semantic_code_search`/`trace_http_chain`/`find_similar_code`',
15
+ ' - 问"返回型/引用/死码/依赖/架构/看签名" → `ast_search`/`find_references`/`find_dead_code`/`dependency_graph`/`project_map`/`get_ast_node`',
16
+ ].join('\n');
13
17
  const TEMPLATE_PATH = path.resolve(__dirname, '..', 'templates', 'plugin_code_graph_mcp.md');
14
18
  const TARGET_NAME = 'plugin_code_graph_mcp.md';
15
19
 
@@ -87,6 +91,43 @@ function adopt({ cwd, home, templatePath } = {}) {
87
91
  return { ok: true, target, indexPath, indexed: true, healed };
88
92
  }
89
93
 
94
+ // v0.9.0 — "已 adopt" 判定:template 文件在 + MEMORY.md 内有我们的 sentinel 块。
95
+ // 用在 maybeAutoAdopt 里做幂等门,也用在 session-init 里推导 quietHooks。
96
+ function isAdopted({ cwd, home } = {}) {
97
+ const dir = memoryDir(cwd, home);
98
+ const target = path.join(dir, TARGET_NAME);
99
+ const indexPath = path.join(dir, 'MEMORY.md');
100
+ if (!fs.existsSync(target) || !fs.existsSync(indexPath)) return false;
101
+ const index = fs.readFileSync(indexPath, 'utf8');
102
+ return index.includes(SENTINEL_BEGIN) && index.includes(SENTINEL_END);
103
+ }
104
+
105
+ // 检测脚本是否从 Claude Code 插件 cache 运行。
106
+ // 走 __dirname 而非 CLAUDE_PLUGIN_ROOT — 后者在多插件共存时会互相污染
107
+ // (见 feedback_plugin_env_isolation.md)。
108
+ function isPluginModeInstall(scriptPath = __dirname) {
109
+ const sep = path.sep;
110
+ return scriptPath.includes(`${sep}.claude${sep}plugins${sep}`);
111
+ }
112
+
113
+ // C' 上下文感知默认(v0.9.0):插件模式下首次 SessionStart 静默 adopt。
114
+ // /plugin install 本身已构成知情同意;npm / npx / 裸 checkout 保持 opt-in。
115
+ // 退出:CODE_GRAPH_NO_AUTO_ADOPT=1。
116
+ function maybeAutoAdopt({ cwd, home, env, scriptPath } = {}) {
117
+ env = env || process.env;
118
+ if (env.CODE_GRAPH_NO_AUTO_ADOPT === '1') {
119
+ return { attempted: false, reason: 'opted-out' };
120
+ }
121
+ if (!isPluginModeInstall(scriptPath || __dirname)) {
122
+ return { attempted: false, reason: 'not-plugin-mode' };
123
+ }
124
+ if (isAdopted({ cwd, home })) {
125
+ return { attempted: false, reason: 'already-adopted' };
126
+ }
127
+ const result = adopt({ cwd, home });
128
+ return { attempted: true, reason: 'adopted', result };
129
+ }
130
+
90
131
  function unadopt({ cwd, home } = {}) {
91
132
  const blocked = platformGuard();
92
133
  if (blocked) return blocked;
@@ -132,7 +173,9 @@ function formatResult(action, result) {
132
173
  if (result.healed) lines.push(`[code-graph] Healed malformed sentinel block → ${result.indexPath}`);
133
174
  else if (result.indexed) lines.push(`[code-graph] Indexed → ${result.indexPath}`);
134
175
  else lines.push(`[code-graph] Index already up-to-date — no write`);
135
- lines.push('[code-graph] Activate: set CODE_GRAPH_QUIET_HOOKS=1 in ~/.claude/settings.json env');
176
+ // v0.9.0: adoption auto-implies quietHooks; no env var needed for the common case.
177
+ lines.push('[code-graph] Active — quietHooks auto-enabled via adopted state.');
178
+ lines.push('[code-graph] Force inject: CODE_GRAPH_QUIET_HOOKS=0 Force silent: =1');
136
179
  return lines.join('\n');
137
180
  }
138
181
  if (action === 'unadopt') {
@@ -154,5 +197,6 @@ if (require.main === module) {
154
197
 
155
198
  module.exports = {
156
199
  adopt, unadopt, memoryDir, formatResult, stripSentinelBlock,
200
+ isAdopted, isPluginModeInstall, maybeAutoAdopt,
157
201
  SENTINEL_BEGIN, SENTINEL_END, INDEX_LINE, TEMPLATE_PATH, TARGET_NAME,
158
202
  };
@@ -6,6 +6,7 @@ const path = require('path');
6
6
  const os = require('os');
7
7
  const {
8
8
  adopt, unadopt, memoryDir, stripSentinelBlock,
9
+ isAdopted, isPluginModeInstall, maybeAutoAdopt,
9
10
  SENTINEL_BEGIN, SENTINEL_END, INDEX_LINE, TEMPLATE_PATH,
10
11
  } = require('./adopt');
11
12
 
@@ -213,6 +214,140 @@ test('unadopt heals malformed sentinel (orphan BEGIN)', () => {
213
214
  } finally { sb.cleanup(); }
214
215
  });
215
216
 
217
+ // ──────────────────────────────────────────────────────────────────────────
218
+ // v0.9.0 — C' context-aware auto-adopt
219
+ // ──────────────────────────────────────────────────────────────────────────
220
+
221
+ test('isAdopted returns false on fresh project (no files)', () => {
222
+ const sb = makeSandbox();
223
+ try {
224
+ assert.strictEqual(isAdopted({ cwd: sb.cwd, home: sb.home }), false);
225
+ } finally { sb.cleanup(); }
226
+ });
227
+
228
+ test('isAdopted returns true after adopt()', () => {
229
+ const sb = makeSandbox();
230
+ try {
231
+ adopt({ cwd: sb.cwd, home: sb.home });
232
+ assert.strictEqual(isAdopted({ cwd: sb.cwd, home: sb.home }), true);
233
+ } finally { sb.cleanup(); }
234
+ });
235
+
236
+ test('isAdopted returns false after unadopt()', () => {
237
+ const sb = makeSandbox();
238
+ try {
239
+ adopt({ cwd: sb.cwd, home: sb.home });
240
+ unadopt({ cwd: sb.cwd, home: sb.home });
241
+ assert.strictEqual(isAdopted({ cwd: sb.cwd, home: sb.home }), false);
242
+ } finally { sb.cleanup(); }
243
+ });
244
+
245
+ test('isAdopted returns false when target file exists but index has no sentinel', () => {
246
+ const sb = makeSandbox();
247
+ try {
248
+ const indexPath = path.join(sb.dir, 'MEMORY.md');
249
+ fs.writeFileSync(indexPath, '# Memory Index\n- [foo.md](foo.md) — unrelated\n');
250
+ fs.writeFileSync(path.join(sb.dir, 'plugin_code_graph_mcp.md'), 'stale copy');
251
+ assert.strictEqual(isAdopted({ cwd: sb.cwd, home: sb.home }), false);
252
+ } finally { sb.cleanup(); }
253
+ });
254
+
255
+ test('isPluginModeInstall recognizes ~/.claude/plugins/... paths', () => {
256
+ const pluginPath = '/home/user/.claude/plugins/cache/code-graph-mcp@0.9.0/scripts';
257
+ assert.strictEqual(isPluginModeInstall(pluginPath), true);
258
+ });
259
+
260
+ test('isPluginModeInstall rejects npm global install paths', () => {
261
+ const npmPath = '/usr/local/lib/node_modules/@sdsrs/code-graph/claude-plugin/scripts';
262
+ assert.strictEqual(isPluginModeInstall(npmPath), false);
263
+ });
264
+
265
+ test('isPluginModeInstall rejects dev-checkout paths', () => {
266
+ const devPath = '/mnt/data_ssd/dev/projects/code-graph-mcp/claude-plugin/scripts';
267
+ assert.strictEqual(isPluginModeInstall(devPath), false);
268
+ });
269
+
270
+ test('isPluginModeInstall rejects npx cache paths', () => {
271
+ const npxPath = '/tmp/npx-abc123/node_modules/@sdsrs/code-graph/claude-plugin/scripts';
272
+ assert.strictEqual(isPluginModeInstall(npxPath), false);
273
+ });
274
+
275
+ test('maybeAutoAdopt skips when CODE_GRAPH_NO_AUTO_ADOPT=1', () => {
276
+ const sb = makeSandbox();
277
+ try {
278
+ const res = maybeAutoAdopt({
279
+ cwd: sb.cwd, home: sb.home,
280
+ scriptPath: '/home/u/.claude/plugins/cache/code-graph-mcp/scripts',
281
+ env: { CODE_GRAPH_NO_AUTO_ADOPT: '1' },
282
+ });
283
+ assert.strictEqual(res.attempted, false);
284
+ assert.strictEqual(res.reason, 'opted-out');
285
+ assert.strictEqual(isAdopted({ cwd: sb.cwd, home: sb.home }), false);
286
+ } finally { sb.cleanup(); }
287
+ });
288
+
289
+ test('maybeAutoAdopt skips when not plugin-mode (npm install)', () => {
290
+ const sb = makeSandbox();
291
+ try {
292
+ const res = maybeAutoAdopt({
293
+ cwd: sb.cwd, home: sb.home,
294
+ scriptPath: '/usr/local/lib/node_modules/@sdsrs/code-graph/claude-plugin/scripts',
295
+ env: {},
296
+ });
297
+ assert.strictEqual(res.attempted, false);
298
+ assert.strictEqual(res.reason, 'not-plugin-mode');
299
+ assert.strictEqual(isAdopted({ cwd: sb.cwd, home: sb.home }), false);
300
+ } finally { sb.cleanup(); }
301
+ });
302
+
303
+ test('maybeAutoAdopt skips when already adopted (idempotent)', () => {
304
+ const sb = makeSandbox();
305
+ try {
306
+ adopt({ cwd: sb.cwd, home: sb.home });
307
+ const res = maybeAutoAdopt({
308
+ cwd: sb.cwd, home: sb.home,
309
+ scriptPath: '/home/u/.claude/plugins/cache/code-graph-mcp/scripts',
310
+ env: {},
311
+ });
312
+ assert.strictEqual(res.attempted, false);
313
+ assert.strictEqual(res.reason, 'already-adopted');
314
+ } finally { sb.cleanup(); }
315
+ });
316
+
317
+ test('maybeAutoAdopt runs adopt when plugin-mode + unadopted + no opt-out', () => {
318
+ const sb = makeSandbox();
319
+ try {
320
+ const res = maybeAutoAdopt({
321
+ cwd: sb.cwd, home: sb.home,
322
+ scriptPath: '/home/u/.claude/plugins/cache/code-graph-mcp/scripts',
323
+ env: {},
324
+ });
325
+ assert.strictEqual(res.attempted, true);
326
+ assert.strictEqual(res.result.ok, true);
327
+ assert.strictEqual(res.result.indexed, true);
328
+ assert.strictEqual(isAdopted({ cwd: sb.cwd, home: sb.home }), true);
329
+ } finally { sb.cleanup(); }
330
+ });
331
+
332
+ test('maybeAutoAdopt returns no-memory-dir when project memory missing', () => {
333
+ const home = fs.mkdtempSync(path.join(os.tmpdir(), 'cg-adopt-home-'));
334
+ const cwd = fs.mkdtempSync(path.join(os.tmpdir(), 'cg-adopt-cwd-'));
335
+ try {
336
+ const res = maybeAutoAdopt({
337
+ cwd, home,
338
+ scriptPath: '/home/u/.claude/plugins/cache/code-graph-mcp/scripts',
339
+ env: {},
340
+ });
341
+ // Plugin-mode + not adopted + no opt-out → attempt runs, but adopt() fails gracefully
342
+ assert.strictEqual(res.attempted, true);
343
+ assert.strictEqual(res.result.ok, false);
344
+ assert.strictEqual(res.result.reason, 'no-memory-dir');
345
+ } finally {
346
+ fs.rmSync(home, { recursive: true, force: true });
347
+ fs.rmSync(cwd, { recursive: true, force: true });
348
+ }
349
+ });
350
+
216
351
  test('Windows platform is rejected with clear reason', { skip: process.platform === 'win32' }, () => {
217
352
  const orig = process.platform;
218
353
  Object.defineProperty(process, 'platform', { value: 'win32', configurable: true });
@@ -9,6 +9,16 @@ const {
9
9
  cleanupDisabledStatusline, isPluginInactive, readJson, CACHE_DIR,
10
10
  } = require('./lifecycle');
11
11
  const { readBinaryVersion, isDevMode, getNewestMtime } = require('./version-utils');
12
+ const { maybeAutoAdopt, isAdopted } = require('./adopt');
13
+
14
+ // v0.9.0 — quietHooks 推导:显式 env override > adopted 状态。
15
+ // CODE_GRAPH_QUIET_HOOKS='0' 强制 noisy;'1' 强制 quiet;未设 → 跟随 adopted。
16
+ function computeQuietHooks({ adopted, env = {} } = {}) {
17
+ const envQuiet = env.CODE_GRAPH_QUIET_HOOKS;
18
+ if (envQuiet === '0') return false;
19
+ if (envQuiet === '1') return true;
20
+ return !!adopted;
21
+ }
12
22
 
13
23
  function launchBackgroundAutoUpdate(spawnFn = spawn, env = process.env) {
14
24
  try {
@@ -247,9 +257,23 @@ function runSessionInit() {
247
257
 
248
258
  const autoUpdateLaunched = launchBackgroundAutoUpdate();
249
259
  const indexFreshness = binaryCheck.available ? ensureIndexFresh() : 'skipped';
250
- // CODE_GRAPH_QUIET_HOOKS=1 → skip the 60-line project-map injection; rely
251
- // on MEMORY.md pointer + on-demand `project_map` tool call instead.
252
- const quietHooks = process.env.CODE_GRAPH_QUIET_HOOKS === '1';
260
+
261
+ // v0.9.0 C' 上下文感知默认:插件模式下首次 SessionStart 自动 adopt。
262
+ // 仅一次 stderr 提示(采纳成功时),让用户知道发生了什么 + 如何回退。
263
+ const autoAdopt = maybeAutoAdopt({ scriptPath: __dirname });
264
+ if (autoAdopt.attempted && autoAdopt.result && autoAdopt.result.ok) {
265
+ process.stderr.write(
266
+ '[code-graph] Auto-adopted into project MEMORY.md (plugin install → knowing consent).\n' +
267
+ ' Opt out: CODE_GRAPH_NO_AUTO_ADOPT=1 in ~/.claude/settings.json env\n' +
268
+ ' Reverse: code-graph-mcp unadopt\n'
269
+ );
270
+ }
271
+
272
+ // quietHooks: adopted → quiet by default (rely on MEMORY.md pointer + on-demand
273
+ // project_map tool); env '1'/'0' overrides for explicit control.
274
+ const adopted = isAdopted();
275
+ const quietHooks = computeQuietHooks({ adopted, env: process.env });
276
+
253
277
  const mapInjected = binaryCheck.available && !quietHooks ? injectProjectMap() : false;
254
278
  const consistencyIssues = binaryCheck.available
255
279
  ? consistencyCheck(binaryCheck.binary)
@@ -257,7 +281,7 @@ function runSessionInit() {
257
281
  return {
258
282
  inactive: false, lifecycle,
259
283
  autoUpdateLaunched, indexFreshness, mapInjected, binaryCheck, consistencyIssues,
260
- quietHooks,
284
+ quietHooks, adopted, autoAdopted: autoAdopt.attempted,
261
285
  };
262
286
  }
263
287
 
@@ -298,6 +322,7 @@ module.exports = {
298
322
  verifyBinary,
299
323
  consistencyCheck,
300
324
  runSessionInit,
325
+ computeQuietHooks,
301
326
  };
302
327
 
303
328
  if (require.main === module) {
@@ -4,7 +4,7 @@ const assert = require('node:assert/strict');
4
4
  const fs = require('fs');
5
5
  const path = require('path');
6
6
 
7
- const { launchBackgroundAutoUpdate, syncLifecycleConfig, ensureIndexFresh, verifyBinary } = require('./session-init');
7
+ const { launchBackgroundAutoUpdate, syncLifecycleConfig, ensureIndexFresh, verifyBinary, computeQuietHooks } = require('./session-init');
8
8
 
9
9
  test('syncLifecycleConfig is exported as a callable helper', () => {
10
10
  assert.equal(typeof syncLifecycleConfig, 'function');
@@ -83,6 +83,30 @@ test('consistencyCheck returns empty array when binary version matches plugin',
83
83
  assert.ok(Array.isArray(result));
84
84
  });
85
85
 
86
+ // ──────────────────────────────────────────────────────────────────────────
87
+ // v0.9.0 — quietHooks inference from adopted state
88
+ // ──────────────────────────────────────────────────────────────────────────
89
+
90
+ test('computeQuietHooks: env "0" forces noisy regardless of adoption', () => {
91
+ assert.equal(computeQuietHooks({ adopted: true, env: { CODE_GRAPH_QUIET_HOOKS: '0' } }), false);
92
+ assert.equal(computeQuietHooks({ adopted: false, env: { CODE_GRAPH_QUIET_HOOKS: '0' } }), false);
93
+ });
94
+
95
+ test('computeQuietHooks: env "1" forces quiet regardless of adoption', () => {
96
+ assert.equal(computeQuietHooks({ adopted: true, env: { CODE_GRAPH_QUIET_HOOKS: '1' } }), true);
97
+ assert.equal(computeQuietHooks({ adopted: false, env: { CODE_GRAPH_QUIET_HOOKS: '1' } }), true);
98
+ });
99
+
100
+ test('computeQuietHooks: env unset → follows adopted state', () => {
101
+ assert.equal(computeQuietHooks({ adopted: true, env: {} }), true);
102
+ assert.equal(computeQuietHooks({ adopted: false, env: {} }), false);
103
+ });
104
+
105
+ test('computeQuietHooks: env unset, no env arg → follows adopted state', () => {
106
+ assert.equal(computeQuietHooks({ adopted: true }), true);
107
+ assert.equal(computeQuietHooks({ adopted: false }), false);
108
+ });
109
+
86
110
  test('consistencyCheck returns version-mismatch when versions differ', (t) => {
87
111
  const os = require('os');
88
112
  const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'cc-'));
@@ -6,7 +6,11 @@ type: reference
6
6
  # code-graph-mcp 插件契约
7
7
 
8
8
  > Invited-memory 模式:MCP `instructions` 仅留指针,决策细则集中在此。
9
- > 启用条件:`CODE_GRAPH_QUIET_HOOKS=1`(在 `~/.claude/settings.json` 的 `env` 中设置)。
9
+ >
10
+ > **v0.9.0 起**:插件(`/plugin install`)模式下首次 SessionStart 自动 adopt,
11
+ > 本文件自动写入,自动切换 quietHooks(跳过每次 project_map 注入)。
12
+ > 退出:`CODE_GRAPH_NO_AUTO_ADOPT=1` 阻止,`code-graph-mcp unadopt` 回退。
13
+ > 手动强控:`CODE_GRAPH_QUIET_HOOKS=0` 强制注入 / `=1` 强制静默(覆盖 adoption 推导)。
10
14
 
11
15
  ## 何时调用 MCP/CLI(替代多步 Grep/Read)
12
16
 
@@ -27,9 +31,9 @@ type: reference
27
31
 
28
32
  ## 不要替代
29
33
 
30
- - 精确字符串 / 常量 / 正则 仍用 `Grep`
31
- - 非代码文件(README/JSON/log) 仍用 `Grep`
32
- - 即将编辑的具体文件 → 仍用 `Read`
34
+ - 非代码文件(README/JSON/log)用内置 `Grep`
35
+ - 代码里查常量/函数名/字符串首选 `code-graph-mcp grep "pattern" [path]`(每个命中带 containing function/module 上下文,结构化);只做纯文本匹配且不关心上下文时用内置 `Grep`
36
+ - 即将编辑的具体文件 → `Read`(`overview <file>` 看概览,`show SYMBOL` 看某符号)
33
37
 
34
38
  ## 工作流惯例
35
39
 
@@ -65,6 +69,8 @@ code-graph-mcp health-check # 索引健康
65
69
  - `impact` 在 `--change-type signature` 时返回最严格的破坏面
66
70
  - 索引陈旧 → SessionStart 自带 `ensureIndexFresh`;手动跑 `incremental-index`
67
71
 
68
- ## 卸载
72
+ ## 卸载 / 回退
69
73
 
70
- `code-graph-mcp unadopt` 精确移除 sentinel 段 + 本文件;或取消 `CODE_GRAPH_QUIET_HOOKS` 即恢复原注入。
74
+ - `code-graph-mcp unadopt` 精确移除 sentinel 段 + 本文件,quietHooks 自动回到 false(下次 SessionStart 恢复 project_map 注入)。
75
+ - `CODE_GRAPH_NO_AUTO_ADOPT=1`(`~/.claude/settings.json` env) — 阻止未来自动 adopt,不影响已 adopted 状态。
76
+ - `CODE_GRAPH_QUIET_HOOKS=0` — 强制恢复 project_map 注入(即使已 adopted)。
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sdsrs/code-graph",
3
- "version": "0.8.4",
3
+ "version": "0.9.1",
4
4
  "description": "MCP server that indexes codebases into an AST knowledge graph with semantic search, call graph traversal, and HTTP route tracing",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -34,10 +34,10 @@
34
34
  "node": ">=16"
35
35
  },
36
36
  "optionalDependencies": {
37
- "@sdsrs/code-graph-linux-x64": "0.8.4",
38
- "@sdsrs/code-graph-linux-arm64": "0.8.4",
39
- "@sdsrs/code-graph-darwin-x64": "0.8.4",
40
- "@sdsrs/code-graph-darwin-arm64": "0.8.4",
41
- "@sdsrs/code-graph-win32-x64": "0.8.4"
37
+ "@sdsrs/code-graph-linux-x64": "0.9.1",
38
+ "@sdsrs/code-graph-linux-arm64": "0.9.1",
39
+ "@sdsrs/code-graph-darwin-x64": "0.9.1",
40
+ "@sdsrs/code-graph-darwin-arm64": "0.9.1",
41
+ "@sdsrs/code-graph-win32-x64": "0.9.1"
42
42
  }
43
43
  }