mindgraph-mcp 0.14.7 → 0.14.9

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
@@ -3,7 +3,7 @@
3
3
  [![npm](https://img.shields.io/npm/v/mindgraph-mcp)](https://www.npmjs.com/package/mindgraph-mcp)
4
4
  [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
5
5
 
6
- Model Context Protocol server for [MindGraph](https://mindgraph.cloud) — plug your persistent, structured knowledge graph into Claude Desktop, Claude Code, and any MCP-compatible client.
6
+ Model Context Protocol server for [MindGraph](https://mindgraph.cloud) — plug your persistent, structured knowledge graph into Claude Desktop, Claude Code, Codex, and any MCP-compatible client.
7
7
 
8
8
  ## Install
9
9
 
@@ -77,7 +77,10 @@ which state you're in).
77
77
 
78
78
  The hooks open/rebind a Session at start and inject the current work brief,
79
79
  stamp every MindGraph tool call with verified session/repo/commit provenance,
80
- and prompt one reflection checkpoint before a substantial session ends. They
80
+ and prompt one reflection checkpoint before a substantial session ends. A
81
+ session automatically re-claims only *this* agent's own prior work; other
82
+ tasks appear in the brief as context, and the agent claims one deliberately
83
+ when it starts that work. They
81
84
  **fail open** — if MindGraph is unreachable, your coding session is never
82
85
  blocked. `--scope project` (with `--project-dir`) installs into a single
83
86
  repository's `.claude/settings.json` instead; `uninstall-hooks` reverses either
@@ -87,18 +90,36 @@ project settings.
87
90
 
88
91
  ### Coding agents (Codex)
89
92
 
90
- The server is harness-neutral — Codex gets the full coding tool surface today:
93
+ Codex gets the same durable work substrate through its native command hooks.
94
+ Register the MCP server with a distinct agent identity, then install the hooks:
91
95
 
92
96
  ```bash
93
97
  codex mcp add mindgraph --env MINDGRAPH_API_KEY=mg_your_key_here \
94
98
  --env MINDGRAPH_PROFILE=coding --env MINDGRAPH_HARNESS=codex \
99
+ --env MINDGRAPH_AGENT_ID=codex:your-name \
95
100
  -- npx -y mindgraph-mcp@latest
101
+
102
+ npx -y mindgraph-mcp install-hooks --harness codex --scope user \
103
+ --api-key mg_your_key_here --agent-id codex:your-name
96
104
  ```
97
105
 
98
106
  (or the equivalent `[mcp_servers.mindgraph]` block in Codex's `config.toml`).
99
- The hooks adapter for Codex — automatic session lifecycle and brief injection —
100
- is planned; until then, ask the agent to call `mindgraph_plan resume_work` at
101
- the start of a session.
107
+ The installer copies the pinned self-contained runner to
108
+ `~/.mindgraph/bin/mindgraph-hook.cjs`, merge-safely upserts only entries marked
109
+ `--owner mindgraph`, and writes user hooks to `$CODEX_HOME/hooks.json`
110
+ (`~/.codex/hooks.json` by default). Use `--scope project --project-dir …` for
111
+ `<project>/.codex/hooks.json`. Re-run the install command to refresh owned
112
+ entries; foreign hooks are never changed. Codex requires newly installed or
113
+ changed command hooks to be reviewed with `/hooks` before they run.
114
+
115
+ At SessionStart the adapter opens/rebinds the graph Session and injects one
116
+ bounded work brief, re-claiming only this agent's own prior work.
117
+ PreToolUse replaces forged/absent invocation provenance
118
+ while preserving model-selected work targets, PostToolUse updates only the
119
+ disposable runtime ledger, Stop runs the once-per-session reflection
120
+ checkpoint, and SessionEnd performs best-effort cleanup. All paths fail open:
121
+ missing credentials or an unreachable MindGraph return Codex's no-op response
122
+ and never block the coding session.
102
123
 
103
124
  ## What You Get
104
125
 
@@ -146,7 +167,7 @@ mindgraph-mcp install Install into Claude Desktop config
146
167
  mindgraph-mcp install-code Install into Claude Code (--hooks: also install session hooks)
147
168
  mindgraph-mcp uninstall Remove from Claude Desktop config
148
169
  mindgraph-mcp uninstall-code Remove from Claude Code
149
- mindgraph-mcp install-hooks Install Claude Code hooks (--harness claude-code)
170
+ mindgraph-mcp install-hooks Install coding hooks (--harness claude-code|codex)
150
171
  mindgraph-mcp uninstall-hooks Remove MindGraph-owned hook entries
151
172
  mindgraph-mcp status Show installation status
152
173
  ```
package/dist/cli.js CHANGED
@@ -2651,6 +2651,38 @@ async function handleCommit(client2, args) {
2651
2651
  return err3(`Unknown commit action: ${action}`);
2652
2652
  }
2653
2653
  }
2654
+ async function repositoryScopeUidsForTask(client2, options) {
2655
+ const scopeUids = [...new Set(options.scopeUids ?? [])];
2656
+ const cwd = options.invocationContext?.cwd;
2657
+ const repoId = options.invocationContext?.repoId;
2658
+ if (!options.repo && !cwd && !repoId) {
2659
+ return options.scopeUids;
2660
+ }
2661
+ const agentId = options.agentId ?? process.env.MINDGRAPH_AGENT_ID ?? "mcp";
2662
+ const fallbackSpaceUid = options.repoSpaceUid ?? `space:agent:${agentId}`;
2663
+ const env = { ...process.env };
2664
+ if (!options.repo && repoId) {
2665
+ env.MINDGRAPH_REPO_ID = repoId;
2666
+ }
2667
+ const adapter = new CodegraphAdapter({
2668
+ cwd: cwd ?? process.cwd(),
2669
+ env
2670
+ });
2671
+ const repository = await adapter.resolveRepository(options.repo, fallbackSpaceUid);
2672
+ const anchored = await anchorRepository(
2673
+ client2,
2674
+ repository,
2675
+ repository.spaceUid ?? fallbackSpaceUid,
2676
+ agentId
2677
+ );
2678
+ if (!anchored.uid) {
2679
+ throw new Error(
2680
+ `repository scope ${repository.repoId} could not be materialized (${anchored.status ?? "missing uid"})`
2681
+ );
2682
+ }
2683
+ scopeUids.push(anchored.uid);
2684
+ return [...new Set(scopeUids)];
2685
+ }
2654
2686
  async function handlePlan(client2, args) {
2655
2687
  const {
2656
2688
  action,
@@ -2681,6 +2713,9 @@ async function handlePlan(client2, args) {
2681
2713
  release_lease,
2682
2714
  override_reason,
2683
2715
  scope_uids,
2716
+ repo,
2717
+ repo_space_uid,
2718
+ invocation_context,
2684
2719
  limit,
2685
2720
  confidence,
2686
2721
  salience,
@@ -2710,7 +2745,14 @@ async function handlePlan(client2, args) {
2710
2745
  case "checkpoint_iteration":
2711
2746
  case "block_task":
2712
2747
  case "complete_task":
2713
- case "abandon_iteration":
2748
+ case "abandon_iteration": {
2749
+ const resolvedScopeUids = action === "create_task" ? await repositoryScopeUidsForTask(client2, {
2750
+ scopeUids: scope_uids,
2751
+ repo,
2752
+ repoSpaceUid: repo_space_uid,
2753
+ invocationContext: invocation_context,
2754
+ agentId: agent_id
2755
+ }) : scope_uids;
2714
2756
  return ok3(
2715
2757
  await client2.plan({
2716
2758
  action,
@@ -2738,7 +2780,7 @@ async function handlePlan(client2, args) {
2738
2780
  allow_takeover,
2739
2781
  release_lease,
2740
2782
  override_reason,
2741
- scope_uids,
2783
+ scope_uids: resolvedScopeUids,
2742
2784
  limit,
2743
2785
  input_snapshot,
2744
2786
  output_snapshot,
@@ -2753,6 +2795,7 @@ async function handlePlan(client2, args) {
2753
2795
  agent_id
2754
2796
  })
2755
2797
  );
2798
+ }
2756
2799
  // Execution tracking
2757
2800
  case "start_execution":
2758
2801
  return ok3(
@@ -3310,6 +3353,7 @@ var init_tools = __esm({
3310
3353
  "use strict";
3311
3354
  init_error_detail();
3312
3355
  init_code_tool();
3356
+ init_codegraph();
3313
3357
  init_sync_tool();
3314
3358
  TOOLS = [
3315
3359
  {
@@ -3774,7 +3818,7 @@ var init_tools = __esm({
3774
3818
  scope_uids: {
3775
3819
  type: "array",
3776
3820
  items: { type: "string" },
3777
- description: "Ordinary target UIDs that bound deterministic resume selection"
3821
+ description: "Required target UIDs for create_task and ordinary target UIDs that bound deterministic resume selection"
3778
3822
  },
3779
3823
  limit: {
3780
3824
  type: "number",
@@ -4917,7 +4961,7 @@ Capture knowledge when the user shares something worth remembering:
4917
4961
  server = new import_server.Server(
4918
4962
  {
4919
4963
  name: "mindgraph",
4920
- version: "0.14.7"
4964
+ version: "0.14.9"
4921
4965
  },
4922
4966
  {
4923
4967
  capabilities: {
@@ -5182,7 +5226,7 @@ function saveHookEnv(env, dir) {
5182
5226
  // src/cli-args.ts
5183
5227
  function parseArgs(argv) {
5184
5228
  const args = argv.slice(2);
5185
- let command = "serve";
5229
+ let command2 = "serve";
5186
5230
  let apiKey = process.env.MINDGRAPH_API_KEY;
5187
5231
  let baseUrl = process.env.MINDGRAPH_BASE_URL;
5188
5232
  let scope = "project";
@@ -5224,16 +5268,16 @@ function parseArgs(argv) {
5224
5268
  break;
5225
5269
  case "--help":
5226
5270
  case "-h":
5227
- command = "help";
5271
+ command2 = "help";
5228
5272
  break;
5229
5273
  default:
5230
5274
  if (!arg.startsWith("-")) {
5231
- command = arg;
5275
+ command2 = arg;
5232
5276
  }
5233
5277
  break;
5234
5278
  }
5235
5279
  }
5236
- return { command, apiKey, baseUrl, scope, projectDir, harness, hooks, agentId };
5280
+ return { command: command2, apiKey, baseUrl, scope, projectDir, harness, hooks, agentId };
5237
5281
  }
5238
5282
  function mcpAddFailureOutput(e) {
5239
5283
  const err5 = e;
@@ -5247,16 +5291,17 @@ function classifyMcpAddFailure(e) {
5247
5291
  return "other";
5248
5292
  }
5249
5293
 
5250
- // src/claude-hooks.ts
5294
+ // src/hook-core.ts
5251
5295
  var import_node_crypto2 = __toESM(require("crypto"));
5252
5296
  var import_node_fs3 = __toESM(require("fs"));
5253
5297
  var import_node_os3 = __toESM(require("os"));
5254
5298
  var import_node_path3 = __toESM(require("path"));
5255
5299
  var import_node_child_process2 = require("child_process");
5256
5300
  init_codegraph();
5257
- function defaultLedger(sessionId) {
5301
+ var NOOP = { kind: "noop" };
5302
+ function defaultLedger(sessionId, options) {
5258
5303
  return {
5259
- harness: "claude-code",
5304
+ harness: options.harness,
5260
5305
  harnessSessionId: sessionId,
5261
5306
  materialToolCalls: 0,
5262
5307
  mutatingOperations: 0,
@@ -5273,7 +5318,8 @@ function runtimeDir(options) {
5273
5318
  return options.runtimeDir || process.env.MINDGRAPH_RUNTIME_DIR || import_node_path3.default.join(import_node_os3.default.homedir(), ".mindgraph", "runtime");
5274
5319
  }
5275
5320
  function ledgerPath(sessionId, options) {
5276
- const key = import_node_crypto2.default.createHash("sha256").update(`claude-code\0${sessionId}`).digest("hex");
5321
+ const harness = "harness" in options ? options.harness : "claude-code";
5322
+ const key = import_node_crypto2.default.createHash("sha256").update(`${harness}\0${sessionId}`).digest("hex");
5277
5323
  return import_node_path3.default.join(runtimeDir(options), `${key}.json`);
5278
5324
  }
5279
5325
  function waitBriefly(ms) {
@@ -5302,10 +5348,10 @@ function withLedger(sessionId, options, update) {
5302
5348
  }
5303
5349
  }
5304
5350
  if (lockFd === void 0) {
5305
- return update(defaultLedger(sessionId));
5351
+ return update(defaultLedger(sessionId, options));
5306
5352
  }
5307
5353
  try {
5308
- let ledger = defaultLedger(sessionId);
5354
+ let ledger = defaultLedger(sessionId, options);
5309
5355
  try {
5310
5356
  ledger = {
5311
5357
  ...ledger,
@@ -5356,8 +5402,9 @@ async function invocationContext(input, options) {
5356
5402
  workspaceFile: options.workspaceFile
5357
5403
  }).resolveRepository();
5358
5404
  return {
5359
- harness: "claude-code",
5405
+ harness: options.harness,
5360
5406
  harnessSessionId: input.session_id,
5407
+ harnessTurnId: input.turn_id,
5361
5408
  cwd: input.cwd,
5362
5409
  repoId: repository.repoId,
5363
5410
  branch: gitValue(input.cwd, ["branch", "--show-current"]),
@@ -5434,13 +5481,21 @@ async function repositoryScopeUids(input, client2, options, agentId) {
5434
5481
  )
5435
5482
  ];
5436
5483
  }
5484
+ function isOwnPriorWork(brief, agentId) {
5485
+ const reason = string(brief?.selection_reason);
5486
+ if (reason === "owned_live_lease" || reason === "same_agent_session_rebind") {
5487
+ return true;
5488
+ }
5489
+ return string(object(brief?.lease)?.lease_owner_agent_id) === agentId;
5490
+ }
5437
5491
  async function sessionStart(input, client2, options) {
5438
- const agentId = options.agentId || process.env.MINDGRAPH_AGENT_ID || "claude-code";
5492
+ const agentId = options.agentId || process.env.MINDGRAPH_AGENT_ID || options.harness;
5493
+ const harnessLabel = options.harness === "claude-code" ? "Claude Code" : "Codex";
5439
5494
  const opened = object(
5440
5495
  await client2.session({
5441
5496
  action: "open",
5442
- label: `Claude Code ${input.session_id}`,
5443
- harness: "claude-code",
5497
+ label: `${harnessLabel} ${input.session_id}`,
5498
+ harness: options.harness,
5444
5499
  harness_session_id: input.session_id,
5445
5500
  model: input.model,
5446
5501
  agent_id: agentId
@@ -5458,7 +5513,7 @@ async function sessionStart(input, client2, options) {
5458
5513
  );
5459
5514
  const task = object(brief?.task);
5460
5515
  const lease = object(brief?.lease);
5461
- if (sessionUid && string(task?.uid) && number(task?.version)) {
5516
+ if (sessionUid && string(task?.uid) && number(task?.version) && isOwnPriorWork(brief, agentId)) {
5462
5517
  try {
5463
5518
  const claimed = object(
5464
5519
  await client2.plan({
@@ -5483,7 +5538,11 @@ async function sessionStart(input, client2, options) {
5483
5538
  } catch {
5484
5539
  }
5485
5540
  }
5486
- withLedger(input.session_id, options, (ledger) => {
5541
+ const briefText = boundedBrief(brief || { status: "no_eligible_work" });
5542
+ const briefHash = import_node_crypto2.default.createHash("sha256").update(briefText).digest("hex");
5543
+ const shouldInject = withLedger(input.session_id, options, (ledger) => {
5544
+ const changed = ledger.lastBriefHash !== briefHash;
5545
+ const reinjectCompact = options.reinjectUnchangedCompact && input.source === "compact";
5487
5546
  ledger.sessionUid = sessionUid;
5488
5547
  ledger.baselineWorktree = worktreeState(input.cwd);
5489
5548
  const selectedTask = object(brief?.task);
@@ -5495,14 +5554,10 @@ async function sessionStart(input, client2, options) {
5495
5554
  ledger.leaseEpoch = number(selectedLease?.lease_epoch);
5496
5555
  ledger.leaseExpiresAt = number(selectedLease?.lease_expires_at);
5497
5556
  ledger.executionUid = string(object(brief?.active_execution)?.uid) || string(latest?.uid);
5498
- return void 0;
5557
+ ledger.lastBriefHash = briefHash;
5558
+ return changed || reinjectCompact;
5499
5559
  });
5500
- return {
5501
- hookSpecificOutput: {
5502
- hookEventName: "SessionStart",
5503
- additionalContext: boundedBrief(brief || { status: "no_eligible_work" })
5504
- }
5505
- };
5560
+ return shouldInject ? { kind: "context", context: briefText } : NOOP;
5506
5561
  }
5507
5562
  async function preTool(input, options) {
5508
5563
  const original = { ...input.tool_input || {} };
@@ -5527,12 +5582,7 @@ async function preTool(input, options) {
5527
5582
  if (ledger.taskVersion !== void 0 && updatedInput.expected_version === void 0) {
5528
5583
  updatedInput.expected_version = ledger.taskVersion;
5529
5584
  }
5530
- return {
5531
- hookSpecificOutput: {
5532
- hookEventName: "PreToolUse",
5533
- updatedInput
5534
- }
5535
- };
5585
+ return { kind: "rewrite", updatedInput };
5536
5586
  }
5537
5587
  async function postTool(input, client2, options) {
5538
5588
  const name = input.tool_name || "";
@@ -5543,7 +5593,7 @@ async function postTool(input, client2, options) {
5543
5593
  let heartbeat;
5544
5594
  withLedger(input.session_id, options, (ledger) => {
5545
5595
  ledger.materialToolCalls += 1;
5546
- if (["Edit", "Write", "NotebookEdit"].includes(name) || name === "Bash" && action !== "read") {
5596
+ if (["Edit", "Write", "NotebookEdit", "apply_patch"].includes(name) || name === "Bash" && action !== "read") {
5547
5597
  ledger.mutatingOperations += 1;
5548
5598
  }
5549
5599
  if (name.includes("mindgraph_capture") && !payload?.error) {
@@ -5583,7 +5633,7 @@ async function postTool(input, client2, options) {
5583
5633
  expected_version: heartbeat.taskVersion,
5584
5634
  lease_epoch: heartbeat.leaseEpoch,
5585
5635
  idempotency_key: `hook-heartbeat:${input.session_id}:${heartbeat.taskUid}:${Math.floor(nowSeconds / 60)}`,
5586
- agent_id: options.agentId || "claude-code"
5636
+ agent_id: options.agentId || options.harness
5587
5637
  })
5588
5638
  );
5589
5639
  withLedger(input.session_id, options, (ledger) => {
@@ -5593,24 +5643,25 @@ async function postTool(input, client2, options) {
5593
5643
  } catch {
5594
5644
  }
5595
5645
  }
5596
- return {};
5646
+ return NOOP;
5597
5647
  }
5598
5648
  function stop(input, options) {
5599
- if (input.stop_hook_active) return {};
5649
+ if (input.stop_hook_active) return NOOP;
5600
5650
  return withLedger(input.session_id, options, (ledger) => {
5601
5651
  const changed = ledger.mutatingOperations >= 2 || ledger.baselineWorktree !== "unknown" && worktreeState(input.cwd) !== ledger.baselineWorktree;
5602
5652
  const substantial = Boolean(ledger.executionUid) || changed;
5603
5653
  if (!substantial || ledger.iterationCheckpointed || ledger.memoryWritten || ledger.stopNudged) {
5604
- return {};
5654
+ return NOOP;
5605
5655
  }
5606
5656
  ledger.stopNudged = true;
5607
5657
  return {
5608
- decision: "block",
5658
+ kind: "block",
5609
5659
  reason: "Substantial work is still uncheckpointed. Checkpoint the active iteration, capture one durable lesson/decision/risk, or explicitly state that nothing durable changed. If a local harness task should become authoritative outside this list, promote it once with a stable idempotency key; otherwise do not copy it."
5610
5660
  };
5611
5661
  });
5612
5662
  }
5613
5663
  async function sessionEnd(input, client2, options) {
5664
+ const harnessLabel = options.harness === "claude-code" ? "Claude Code" : "Codex";
5614
5665
  const ledger = readLedger(input.session_id, options);
5615
5666
  if (ledger.executionUid && ledger.taskUid && ledger.sessionUid && ledger.taskVersion !== void 0 && ledger.leaseEpoch !== void 0) {
5616
5667
  try {
@@ -5622,9 +5673,9 @@ async function sessionEnd(input, client2, options) {
5622
5673
  expected_version: ledger.taskVersion,
5623
5674
  lease_epoch: ledger.leaseEpoch,
5624
5675
  release_lease: true,
5625
- summary: `Claude Code SessionEnd: ${input.reason || "interrupted"}`,
5676
+ summary: `${harnessLabel} SessionEnd: ${input.reason || "interrupted"}`,
5626
5677
  idempotency_key: `session-end:${input.session_id}:${ledger.executionUid}`,
5627
- agent_id: options.agentId || "claude-code"
5678
+ agent_id: options.agentId || options.harness
5628
5679
  });
5629
5680
  } catch {
5630
5681
  }
@@ -5634,19 +5685,19 @@ async function sessionEnd(input, client2, options) {
5634
5685
  await client2.session({
5635
5686
  action: "close",
5636
5687
  session_uid: ledger.sessionUid,
5637
- agent_id: options.agentId || "claude-code"
5688
+ agent_id: options.agentId || options.harness
5638
5689
  });
5639
5690
  } catch {
5640
5691
  }
5641
5692
  }
5642
- return {};
5693
+ return NOOP;
5643
5694
  }
5644
- async function runClaudeHook(input, client2, options = {}) {
5695
+ async function runHookCore(input, client2, options) {
5645
5696
  switch (input.hook_event_name) {
5646
5697
  case "SessionStart":
5647
5698
  return sessionStart(input, client2, options);
5648
5699
  case "PreToolUse":
5649
- if (!input.tool_name?.startsWith("mcp__mindgraph__")) return {};
5700
+ if (!input.tool_name?.startsWith("mcp__mindgraph__")) return NOOP;
5650
5701
  return preTool(input, options);
5651
5702
  case "PostToolUse":
5652
5703
  return postTool(input, client2, options);
@@ -5655,19 +5706,19 @@ async function runClaudeHook(input, client2, options = {}) {
5655
5706
  ledger.nativeTasksCreated += 1;
5656
5707
  return void 0;
5657
5708
  });
5658
- return {};
5709
+ return NOOP;
5659
5710
  case "TaskCompleted":
5660
5711
  withLedger(input.session_id, options, (ledger) => {
5661
5712
  ledger.nativeTasksTerminal += 1;
5662
5713
  return void 0;
5663
5714
  });
5664
- return {};
5715
+ return NOOP;
5665
5716
  case "Stop":
5666
5717
  return stop(input, options);
5667
5718
  case "SessionEnd":
5668
5719
  return sessionEnd(input, client2, options);
5669
5720
  default:
5670
- return {};
5721
+ return NOOP;
5671
5722
  }
5672
5723
  }
5673
5724
  async function readHookInput(stream = process.stdin) {
@@ -5678,6 +5729,68 @@ async function readHookInput(stream = process.stdin) {
5678
5729
  return JSON.parse(Buffer.concat(chunks).toString("utf8"));
5679
5730
  }
5680
5731
 
5732
+ // src/claude-hooks.ts
5733
+ async function runClaudeHook(input, client2, options = {}) {
5734
+ const output = await runHookCore(input, client2, {
5735
+ ...options,
5736
+ harness: "claude-code",
5737
+ // D10: compaction is a new Claude context boundary, so the same brief must
5738
+ // be re-injected even when its content hash is unchanged.
5739
+ reinjectUnchangedCompact: true
5740
+ });
5741
+ switch (output.kind) {
5742
+ case "context":
5743
+ return {
5744
+ hookSpecificOutput: {
5745
+ hookEventName: "SessionStart",
5746
+ additionalContext: output.context
5747
+ }
5748
+ };
5749
+ case "rewrite":
5750
+ return {
5751
+ hookSpecificOutput: {
5752
+ hookEventName: "PreToolUse",
5753
+ updatedInput: output.updatedInput
5754
+ }
5755
+ };
5756
+ case "block":
5757
+ return { decision: "block", reason: output.reason };
5758
+ case "noop":
5759
+ return {};
5760
+ }
5761
+ }
5762
+
5763
+ // src/codex-hooks.ts
5764
+ async function runCodexHook(input, client2, options = {}) {
5765
+ const output = await runHookCore(input, client2, {
5766
+ ...options,
5767
+ harness: "codex",
5768
+ // B7's native contract suppresses repeated unchanged brief injections.
5769
+ reinjectUnchangedCompact: false
5770
+ });
5771
+ switch (output.kind) {
5772
+ case "context":
5773
+ return {
5774
+ hookSpecificOutput: {
5775
+ hookEventName: "SessionStart",
5776
+ additionalContext: output.context
5777
+ }
5778
+ };
5779
+ case "rewrite":
5780
+ return {
5781
+ hookSpecificOutput: {
5782
+ hookEventName: "PreToolUse",
5783
+ permissionDecision: "allow",
5784
+ updatedInput: output.updatedInput
5785
+ }
5786
+ };
5787
+ case "block":
5788
+ return { decision: "block", reason: output.reason };
5789
+ case "noop":
5790
+ return {};
5791
+ }
5792
+ }
5793
+
5681
5794
  // src/hook-installer.ts
5682
5795
  var import_node_fs4 = __toESM(require("fs"));
5683
5796
  var import_node_os4 = __toESM(require("os"));
@@ -5685,59 +5798,78 @@ var import_node_path4 = __toESM(require("path"));
5685
5798
  var import_node_crypto3 = __toESM(require("crypto"));
5686
5799
  var OWNER_MARKER = "--owner mindgraph";
5687
5800
  var RUNNER_RELATIVE = ".mindgraph/bin/mindgraph-hook.cjs";
5688
- var COMMAND = `node "$HOME/${RUNNER_RELATIVE}" hook --harness claude-code --owner mindgraph`;
5801
+ function command(harness) {
5802
+ return `node "$HOME/${RUNNER_RELATIVE}" hook --harness ${harness} --owner mindgraph`;
5803
+ }
5804
+ function windowsCommand(harness) {
5805
+ return `node "%USERPROFILE%\\.mindgraph\\bin\\mindgraph-hook.cjs" hook --harness ${harness} --owner mindgraph`;
5806
+ }
5689
5807
  function installHookRunner(sourceFile, homeDir) {
5690
5808
  const target = import_node_path4.default.join(homeDir || import_node_os4.default.homedir(), RUNNER_RELATIVE);
5691
5809
  import_node_fs4.default.mkdirSync(import_node_path4.default.dirname(target), { recursive: true });
5692
5810
  import_node_fs4.default.copyFileSync(sourceFile, target);
5693
5811
  return target;
5694
5812
  }
5695
- function uninstallHookRunner(homeDir) {
5696
- try {
5697
- import_node_fs4.default.rmSync(import_node_path4.default.join(homeDir || import_node_os4.default.homedir(), RUNNER_RELATIVE));
5698
- } catch {
5813
+ function settingsPath(harness, scope, projectDir = process.cwd(), codexHomeDir = process.env.CODEX_HOME || import_node_path4.default.join(import_node_os4.default.homedir(), ".codex")) {
5814
+ if (harness === "codex") {
5815
+ return scope === "user" ? import_node_path4.default.join(codexHomeDir, "hooks.json") : import_node_path4.default.join(projectDir, ".codex", "hooks.json");
5699
5816
  }
5700
- }
5701
- function settingsPath(scope, projectDir = process.cwd()) {
5702
5817
  return scope === "user" ? import_node_path4.default.join(import_node_os4.default.homedir(), ".claude", "settings.json") : import_node_path4.default.join(projectDir, ".claude", "settings.json");
5703
5818
  }
5704
5819
  function readSettings(file) {
5705
5820
  if (!import_node_fs4.default.existsSync(file)) return {};
5706
5821
  return JSON.parse(import_node_fs4.default.readFileSync(file, "utf8"));
5707
5822
  }
5708
- function ownedHook(timeout) {
5823
+ function ownedHook(harness, timeout, additionalContextLimit) {
5709
5824
  return {
5710
5825
  type: "command",
5711
- command: COMMAND,
5826
+ command: command(harness),
5827
+ ...harness === "codex" ? { commandWindows: windowsCommand(harness) } : {},
5712
5828
  timeout,
5713
- statusMessage: "Syncing MindGraph session context"
5829
+ statusMessage: "Syncing MindGraph session context",
5830
+ ...additionalContextLimit === void 0 ? {} : { additionalContextLimit }
5714
5831
  };
5715
5832
  }
5716
- function desiredEntries() {
5717
- return {
5833
+ function desiredEntries(harness) {
5834
+ const common = {
5718
5835
  SessionStart: [
5719
5836
  {
5720
- matcher: "startup|resume|clear|compact|fork",
5721
- hooks: [ownedHook(20)]
5837
+ matcher: harness === "claude-code" ? "startup|resume|clear|compact|fork" : "startup|resume|clear|compact",
5838
+ // B7 live acceptance caught cold SessionStart calls crossing the
5839
+ // inherited 20s edge in both harnesses: the hooks correctly failed
5840
+ // open, but no brief reached the model. The same installed runner
5841
+ // completed in 16.84s once warm. Keep a 30s cold-tenant margin.
5842
+ hooks: [
5843
+ ownedHook(
5844
+ harness,
5845
+ 30,
5846
+ harness === "codex" ? 3e3 : void 0
5847
+ )
5848
+ ]
5722
5849
  }
5723
5850
  ],
5724
5851
  PreToolUse: [
5725
5852
  {
5726
5853
  matcher: "mcp__mindgraph__.*",
5727
- hooks: [ownedHook(3)]
5854
+ hooks: [ownedHook(harness, 3)]
5728
5855
  }
5729
5856
  ],
5730
- PostToolUse: [{ matcher: ".*", hooks: [ownedHook(5)] }],
5731
- TaskCreated: [{ hooks: [ownedHook(3)] }],
5732
- TaskCompleted: [{ hooks: [ownedHook(3)] }],
5733
- Stop: [{ hooks: [ownedHook(5)] }],
5734
- SessionEnd: [{ hooks: [ownedHook(8)] }]
5857
+ PostToolUse: [{ matcher: ".*", hooks: [ownedHook(harness, 5)] }],
5858
+ Stop: [{ hooks: [ownedHook(harness, 5)] }],
5859
+ SessionEnd: [
5860
+ { hooks: [ownedHook(harness, harness === "codex" ? 3 : 8)] }
5861
+ ]
5735
5862
  };
5863
+ return harness === "claude-code" ? {
5864
+ ...common,
5865
+ TaskCreated: [{ hooks: [ownedHook(harness, 3)] }],
5866
+ TaskCompleted: [{ hooks: [ownedHook(harness, 3)] }]
5867
+ } : common;
5736
5868
  }
5737
5869
  function hookCommand(value) {
5738
5870
  if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
5739
- const command = value.command;
5740
- return typeof command === "string" ? command : void 0;
5871
+ const command2 = value.command;
5872
+ return typeof command2 === "string" ? command2 : void 0;
5741
5873
  }
5742
5874
  function entryHasOwnedHook(value) {
5743
5875
  if (!value || typeof value !== "object" || Array.isArray(value)) return false;
@@ -5753,13 +5885,13 @@ function writeSettings(file, settings) {
5753
5885
  });
5754
5886
  import_node_fs4.default.renameSync(temporary, file);
5755
5887
  }
5756
- function installClaudeHooks(scope, projectDir = process.cwd()) {
5757
- const file = settingsPath(scope, projectDir);
5888
+ function installHooks(harness, scope, projectDir = process.cwd(), codexHomeDir) {
5889
+ const file = settingsPath(harness, scope, projectDir, codexHomeDir);
5758
5890
  const settings = readSettings(file);
5759
5891
  const hooks = settings.hooks && typeof settings.hooks === "object" && !Array.isArray(settings.hooks) ? settings.hooks : {};
5760
5892
  let added = 0;
5761
5893
  let updated = 0;
5762
- for (const [event, wanted] of Object.entries(desiredEntries())) {
5894
+ for (const [event, wanted] of Object.entries(desiredEntries(harness))) {
5763
5895
  const current = Array.isArray(hooks[event]) ? [...hooks[event]] : [];
5764
5896
  const kept = current.filter((entry) => !entryHasOwnedHook(entry));
5765
5897
  const hadOwned = kept.length !== current.length;
@@ -5774,8 +5906,8 @@ function installClaudeHooks(scope, projectDir = process.cwd()) {
5774
5906
  writeSettings(file, settings);
5775
5907
  return { path: file, added, updated };
5776
5908
  }
5777
- function uninstallClaudeHooks(scope, projectDir = process.cwd()) {
5778
- const file = settingsPath(scope, projectDir);
5909
+ function uninstallHooks(harness, scope, projectDir = process.cwd(), codexHomeDir) {
5910
+ const file = settingsPath(harness, scope, projectDir, codexHomeDir);
5779
5911
  if (!import_node_fs4.default.existsSync(file)) return { path: file, removed: 0 };
5780
5912
  const settings = readSettings(file);
5781
5913
  const hooks = settings.hooks && typeof settings.hooks === "object" && !Array.isArray(settings.hooks) ? settings.hooks : {};
@@ -5810,6 +5942,18 @@ function uninstallClaudeHooks(scope, projectDir = process.cwd()) {
5810
5942
  writeSettings(file, settings);
5811
5943
  return { path: file, removed };
5812
5944
  }
5945
+ function installClaudeHooks(scope, projectDir = process.cwd()) {
5946
+ return installHooks("claude-code", scope, projectDir);
5947
+ }
5948
+ function uninstallClaudeHooks(scope, projectDir = process.cwd()) {
5949
+ return uninstallHooks("claude-code", scope, projectDir);
5950
+ }
5951
+ function installCodexHooks(scope, projectDir = process.cwd(), codexHomeDir) {
5952
+ return installHooks("codex", scope, projectDir, codexHomeDir);
5953
+ }
5954
+ function uninstallCodexHooks(scope, projectDir = process.cwd(), codexHomeDir) {
5955
+ return uninstallHooks("codex", scope, projectDir, codexHomeDir);
5956
+ }
5813
5957
 
5814
5958
  // src/cli.ts
5815
5959
  function getClaudeDesktopConfigPath() {
@@ -6019,8 +6163,8 @@ USAGE:
6019
6163
  mindgraph-mcp install-code Install into Claude Code (--hooks: + session hooks)
6020
6164
  mindgraph-mcp uninstall Remove from Claude Desktop config
6021
6165
  mindgraph-mcp uninstall-code Remove from Claude Code
6022
- mindgraph-mcp install-hooks --harness claude-code [--scope user|project]
6023
- mindgraph-mcp uninstall-hooks --harness claude-code [--scope user|project]
6166
+ mindgraph-mcp install-hooks --harness claude-code|codex [--scope user|project]
6167
+ mindgraph-mcp uninstall-hooks --harness claude-code|codex [--scope user|project]
6024
6168
  mindgraph-mcp status Show installation status
6025
6169
 
6026
6170
  OPTIONS:
@@ -6028,7 +6172,7 @@ OPTIONS:
6028
6172
  --base-url <url> Custom API base URL (default: https://api.mindgraph.cloud)
6029
6173
  --scope <scope> Hook settings scope: project (default) or user
6030
6174
  --project-dir <dir> Project root for project-scoped hooks
6031
- --harness <name> Hook harness (currently claude-code)
6175
+ --harness <name> Hook harness (claude-code or codex)
6032
6176
  --help, -h Show this help message
6033
6177
 
6034
6178
  EXAMPLES:
@@ -6166,8 +6310,8 @@ async function interactiveInit(baseUrl) {
6166
6310
  console.log("\nDone! Your knowledge graph is now connected.");
6167
6311
  }
6168
6312
  async function main2() {
6169
- const { command, apiKey, baseUrl, scope, projectDir, harness, hooks, agentId } = parseArgs(process.argv);
6170
- switch (command) {
6313
+ const { command: command2, apiKey, baseUrl, scope, projectDir, harness, hooks, agentId } = parseArgs(process.argv);
6314
+ switch (command2) {
6171
6315
  case "help":
6172
6316
  printUsage();
6173
6317
  break;
@@ -6200,14 +6344,15 @@ async function main2() {
6200
6344
  uninstallClaudeCode();
6201
6345
  break;
6202
6346
  case "install-hooks": {
6203
- if ((harness || "claude-code") !== "claude-code") {
6204
- throw new Error("only --harness claude-code is available in this release");
6347
+ const hookHarness = harness || "claude-code";
6348
+ if (hookHarness !== "claude-code" && hookHarness !== "codex") {
6349
+ throw new Error("--harness must be claude-code or codex");
6205
6350
  }
6206
6351
  const runner = installHookRunner(__filename);
6207
6352
  console.log(`Installed pinned hook runner at ${runner}`);
6208
- const result = installClaudeHooks(scope, projectDir);
6353
+ const result = hookHarness === "codex" ? installCodexHooks(scope, projectDir) : installClaudeHooks(scope, projectDir);
6209
6354
  console.log(
6210
- `Installed ${result.added} and refreshed ${result.updated} MindGraph Claude Code hook entries in ${result.path}`
6355
+ `Installed ${result.added} and refreshed ${result.updated} MindGraph ${hookHarness === "codex" ? "Codex" : "Claude Code"} hook entries in ${result.path}`
6211
6356
  );
6212
6357
  printCodegraphStatus();
6213
6358
  if (apiKey || baseUrl || agentId) {
@@ -6221,18 +6366,22 @@ async function main2() {
6221
6366
  break;
6222
6367
  }
6223
6368
  case "uninstall-hooks": {
6224
- if ((harness || "claude-code") !== "claude-code") {
6225
- throw new Error("only --harness claude-code is available in this release");
6369
+ const hookHarness = harness || "claude-code";
6370
+ if (hookHarness !== "claude-code" && hookHarness !== "codex") {
6371
+ throw new Error("--harness must be claude-code or codex");
6226
6372
  }
6227
- uninstallHookRunner();
6228
- const result = uninstallClaudeHooks(scope, projectDir);
6373
+ const result = hookHarness === "codex" ? uninstallCodexHooks(scope, projectDir) : uninstallClaudeHooks(scope, projectDir);
6229
6374
  console.log(
6230
- `Removed ${result.removed} MindGraph Claude Code hooks from ${result.path}`
6375
+ `Removed ${result.removed} MindGraph ${hookHarness === "codex" ? "Codex" : "Claude Code"} hooks from ${result.path}`
6231
6376
  );
6232
6377
  break;
6233
6378
  }
6234
6379
  case "hook": {
6235
6380
  const stored = loadHookEnv();
6381
+ const hookHarness = harness || "claude-code";
6382
+ if (hookHarness !== "claude-code" && hookHarness !== "codex") {
6383
+ throw new Error("--harness must be claude-code or codex");
6384
+ }
6236
6385
  const hookApiKey = apiKey ?? stored.apiKey;
6237
6386
  const hookBaseUrl = baseUrl ?? stored.baseUrl;
6238
6387
  if (!hookApiKey) {
@@ -6248,9 +6397,18 @@ async function main2() {
6248
6397
  maxRetries: 0,
6249
6398
  telemetrySurface: "mcp"
6250
6399
  });
6251
- const output = await runClaudeHook(input, client2, {
6252
- agentId: process.env.MINDGRAPH_AGENT_ID || stored.agentId || "claude-code"
6253
- });
6400
+ const hookOptions = {
6401
+ agentId: process.env.MINDGRAPH_AGENT_ID || stored.agentId || hookHarness
6402
+ };
6403
+ const output = hookHarness === "codex" ? await runCodexHook(
6404
+ input,
6405
+ client2,
6406
+ hookOptions
6407
+ ) : await runClaudeHook(
6408
+ input,
6409
+ client2,
6410
+ hookOptions
6411
+ );
6254
6412
  process.stdout.write(`${JSON.stringify(output)}
6255
6413
  `);
6256
6414
  } catch {
@@ -6266,7 +6424,7 @@ async function main2() {
6266
6424
  break;
6267
6425
  }
6268
6426
  default:
6269
- console.error(`Unknown command: ${command}`);
6427
+ console.error(`Unknown command: ${command2}`);
6270
6428
  printUsage();
6271
6429
  process.exit(1);
6272
6430
  }
package/dist/index.js CHANGED
@@ -1889,7 +1889,7 @@ var TOOLS = [
1889
1889
  scope_uids: {
1890
1890
  type: "array",
1891
1891
  items: { type: "string" },
1892
- description: "Ordinary target UIDs that bound deterministic resume selection"
1892
+ description: "Required target UIDs for create_task and ordinary target UIDs that bound deterministic resume selection"
1893
1893
  },
1894
1894
  limit: {
1895
1895
  type: "number",
@@ -2687,6 +2687,38 @@ async function handleCommit(client2, args) {
2687
2687
  return err3(`Unknown commit action: ${action}`);
2688
2688
  }
2689
2689
  }
2690
+ async function repositoryScopeUidsForTask(client2, options) {
2691
+ const scopeUids = [...new Set(options.scopeUids ?? [])];
2692
+ const cwd = options.invocationContext?.cwd;
2693
+ const repoId = options.invocationContext?.repoId;
2694
+ if (!options.repo && !cwd && !repoId) {
2695
+ return options.scopeUids;
2696
+ }
2697
+ const agentId = options.agentId ?? process.env.MINDGRAPH_AGENT_ID ?? "mcp";
2698
+ const fallbackSpaceUid = options.repoSpaceUid ?? `space:agent:${agentId}`;
2699
+ const env = { ...process.env };
2700
+ if (!options.repo && repoId) {
2701
+ env.MINDGRAPH_REPO_ID = repoId;
2702
+ }
2703
+ const adapter = new CodegraphAdapter({
2704
+ cwd: cwd ?? process.cwd(),
2705
+ env
2706
+ });
2707
+ const repository = await adapter.resolveRepository(options.repo, fallbackSpaceUid);
2708
+ const anchored = await anchorRepository(
2709
+ client2,
2710
+ repository,
2711
+ repository.spaceUid ?? fallbackSpaceUid,
2712
+ agentId
2713
+ );
2714
+ if (!anchored.uid) {
2715
+ throw new Error(
2716
+ `repository scope ${repository.repoId} could not be materialized (${anchored.status ?? "missing uid"})`
2717
+ );
2718
+ }
2719
+ scopeUids.push(anchored.uid);
2720
+ return [...new Set(scopeUids)];
2721
+ }
2690
2722
  async function handlePlan(client2, args) {
2691
2723
  const {
2692
2724
  action,
@@ -2717,6 +2749,9 @@ async function handlePlan(client2, args) {
2717
2749
  release_lease,
2718
2750
  override_reason,
2719
2751
  scope_uids,
2752
+ repo,
2753
+ repo_space_uid,
2754
+ invocation_context,
2720
2755
  limit,
2721
2756
  confidence,
2722
2757
  salience,
@@ -2746,7 +2781,14 @@ async function handlePlan(client2, args) {
2746
2781
  case "checkpoint_iteration":
2747
2782
  case "block_task":
2748
2783
  case "complete_task":
2749
- case "abandon_iteration":
2784
+ case "abandon_iteration": {
2785
+ const resolvedScopeUids = action === "create_task" ? await repositoryScopeUidsForTask(client2, {
2786
+ scopeUids: scope_uids,
2787
+ repo,
2788
+ repoSpaceUid: repo_space_uid,
2789
+ invocationContext: invocation_context,
2790
+ agentId: agent_id
2791
+ }) : scope_uids;
2750
2792
  return ok3(
2751
2793
  await client2.plan({
2752
2794
  action,
@@ -2774,7 +2816,7 @@ async function handlePlan(client2, args) {
2774
2816
  allow_takeover,
2775
2817
  release_lease,
2776
2818
  override_reason,
2777
- scope_uids,
2819
+ scope_uids: resolvedScopeUids,
2778
2820
  limit,
2779
2821
  input_snapshot,
2780
2822
  output_snapshot,
@@ -2789,6 +2831,7 @@ async function handlePlan(client2, args) {
2789
2831
  agent_id
2790
2832
  })
2791
2833
  );
2834
+ }
2792
2835
  // Execution tracking
2793
2836
  case "start_execution":
2794
2837
  return ok3(
@@ -3955,7 +3998,7 @@ if (PROFILE === "coding") {
3955
3998
  var server = new import_server.Server(
3956
3999
  {
3957
4000
  name: "mindgraph",
3958
- version: "0.14.7"
4001
+ version: "0.14.9"
3959
4002
  },
3960
4003
  {
3961
4004
  capabilities: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mindgraph-mcp",
3
- "version": "0.14.7",
3
+ "version": "0.14.9",
4
4
  "description": "MCP server for MindGraph — connect Claude Desktop, Claude Code, and any MCP client to your knowledge graph",
5
5
  "repository": {
6
6
  "type": "git",