mindgraph-mcp 0.18.0 → 0.19.0

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/dist/cli.js CHANGED
@@ -65,8 +65,8 @@ var init_dist = __esm({
65
65
  this.retryBackoffMs = config.retryBackoffMs ?? 1e3;
66
66
  }
67
67
  // ---- HTTP helpers ----
68
- async request(method, path7, body) {
69
- const url = `${this.baseUrl}${path7}`;
68
+ async request(method, path8, body) {
69
+ const url = `${this.baseUrl}${path8}`;
70
70
  const requestId = createRequestId();
71
71
  const init = {
72
72
  method,
@@ -91,13 +91,14 @@ var init_dist = __esm({
91
91
  parsed = text2;
92
92
  }
93
93
  const err5 = new MindGraphError(
94
- `${method} ${path7} failed: ${res.status}`,
94
+ `${method} ${path8} failed: ${res.status}`,
95
95
  res.status,
96
96
  parsed
97
97
  );
98
98
  if (res.status === 503 && attempt < this.maxRetries) {
99
99
  lastError = err5;
100
- const delay = this.retryBackoffMs * 2 ** attempt;
100
+ const retryAfterSecs = Number(res.headers.get("retry-after"));
101
+ const delay = Number.isFinite(retryAfterSecs) && retryAfterSecs > 0 ? Math.min(retryAfterSecs * 1e3, 1e4) : this.retryBackoffMs * 2 ** attempt;
101
102
  await new Promise((r) => setTimeout(r, delay));
102
103
  continue;
103
104
  }
@@ -109,17 +110,17 @@ var init_dist = __esm({
109
110
  }
110
111
  throw lastError;
111
112
  }
112
- get(path7) {
113
- return this.request("GET", path7);
113
+ get(path8) {
114
+ return this.request("GET", path8);
114
115
  }
115
- post(path7, body) {
116
- return this.request("POST", path7, body);
116
+ post(path8, body) {
117
+ return this.request("POST", path8, body);
117
118
  }
118
- patch(path7, body) {
119
- return this.request("PATCH", path7, body);
119
+ patch(path8, body) {
120
+ return this.request("PATCH", path8, body);
120
121
  }
121
- del(path7) {
122
- return this.request("DELETE", path7);
122
+ del(path8) {
123
+ return this.request("DELETE", path8);
123
124
  }
124
125
  // ---- Health ----
125
126
  async health() {
@@ -148,6 +149,52 @@ var init_dist = __esm({
148
149
  async entity(req) {
149
150
  return this.post("/reality/entity", req);
150
151
  }
152
+ async series(req) {
153
+ return this.post("/reality/series", req);
154
+ }
155
+ async createSeries(req) {
156
+ return this.post("/reality/series", { action: "create", ...req });
157
+ }
158
+ async appendSeries(req) {
159
+ return this.post("/reality/series", { action: "append", ...req });
160
+ }
161
+ async seriesWindow(req) {
162
+ return this.post("/reality/series", { action: "window", ...req });
163
+ }
164
+ async aggregateSeries(req) {
165
+ return this.post("/reality/series", { action: "aggregate", ...req });
166
+ }
167
+ async latestSeries(seriesUid, options) {
168
+ return this.post("/reality/series", {
169
+ action: "latest",
170
+ series_uid: seriesUid,
171
+ ...options
172
+ });
173
+ }
174
+ async listSeriesForEntity(entityUid, options) {
175
+ return this.post("/reality/series", {
176
+ action: "list_for_entity",
177
+ entity_uid: entityUid,
178
+ ...options
179
+ });
180
+ }
181
+ async batchLatestSeries(entityUids, options) {
182
+ return this.post("/reality/series", {
183
+ action: "batch_latest",
184
+ entity_uids: entityUids,
185
+ ...options
186
+ });
187
+ }
188
+ async batchAggregateSeries(req) {
189
+ return this.post("/reality/series", { action: "batch_aggregate", ...req });
190
+ }
191
+ async deleteSeries(seriesUid, options) {
192
+ return this.post("/reality/series", {
193
+ action: "delete_series",
194
+ series_uid: seriesUid,
195
+ ...options
196
+ });
197
+ }
151
198
  /** Convenience: create or find an entity by label. */
152
199
  async findOrCreateEntity(label, props, agentId) {
153
200
  return this.post("/reality/entity", {
@@ -717,6 +764,20 @@ var init_dist = __esm({
717
764
  async getOntologySchema(id) {
718
765
  return this.get(`/v1/ontology/schemas/${seg(id)}`);
719
766
  }
767
+ async createOntologySeriesBinding(schemaId, request) {
768
+ return this.post(`/v1/ontology/schemas/${seg(schemaId)}/series-bindings`, request);
769
+ }
770
+ async syncOntologySeriesBinding(bindingId, mode) {
771
+ return this.post(
772
+ `/v1/ontology/series-bindings/${seg(bindingId)}/sync`,
773
+ mode ? { mode } : {}
774
+ );
775
+ }
776
+ async archiveOntologySeriesBinding(schemaId, bindingId) {
777
+ return this.del(
778
+ `/v1/ontology/schemas/${seg(schemaId)}/series-bindings/${seg(bindingId)}`
779
+ );
780
+ }
720
781
  async createOntologySchema(req) {
721
782
  return this.post("/v1/ontology/schemas", req);
722
783
  }
@@ -988,14 +1049,14 @@ function unavailabilityCaveats(reason, message) {
988
1049
  }
989
1050
  return caveats;
990
1051
  }
991
- function normalizePath(path7) {
992
- return path7.replaceAll("\\", "/").replace(/^\.\//, "");
1052
+ function normalizePath(path8) {
1053
+ return path8.replaceAll("\\", "/").replace(/^\.\//, "");
993
1054
  }
994
1055
  function normalizeSignature(signature) {
995
1056
  return signature.replace(/\s+/g, " ").trim();
996
1057
  }
997
- function languageFromPath(path7) {
998
- const extension = (0, import_node_path2.extname)(path7).toLowerCase();
1058
+ function languageFromPath(path8) {
1059
+ const extension = (0, import_node_path2.extname)(path8).toLowerCase();
999
1060
  return {
1000
1061
  ".c": "c",
1001
1062
  ".cc": "cpp",
@@ -1158,7 +1219,7 @@ function codeRefIdentityKey(ref) {
1158
1219
  }
1159
1220
  };
1160
1221
  }
1161
- var import_node_child_process, import_node_fs2, import_promises, import_node_os2, import_node_path2, import_node_crypto2, UnknownRepositoryError, DEFAULT_TIMEOUT_MS, DEFAULT_STATUS_TTL_MS, MAX_OUTPUT_BYTES, CodegraphAdapter;
1222
+ var import_node_child_process, import_node_fs2, import_promises, import_node_os2, import_node_path2, import_node_crypto3, UnknownRepositoryError, DEFAULT_TIMEOUT_MS, DEFAULT_STATUS_TTL_MS, MAX_OUTPUT_BYTES, CodegraphAdapter;
1162
1223
  var init_codegraph = __esm({
1163
1224
  "src/codegraph.ts"() {
1164
1225
  "use strict";
@@ -1167,7 +1228,7 @@ var init_codegraph = __esm({
1167
1228
  import_promises = require("fs/promises");
1168
1229
  import_node_os2 = require("os");
1169
1230
  import_node_path2 = require("path");
1170
- import_node_crypto2 = require("crypto");
1231
+ import_node_crypto3 = require("crypto");
1171
1232
  UnknownRepositoryError = class extends Error {
1172
1233
  constructor(repo) {
1173
1234
  super(
@@ -1296,7 +1357,7 @@ var init_codegraph = __esm({
1296
1357
  } catch {
1297
1358
  }
1298
1359
  (0, import_node_fs2.mkdirSync)(identityDir, { recursive: true, mode: 448 });
1299
- const generated = (0, import_node_crypto2.randomUUID)();
1360
+ const generated = (0, import_node_crypto3.randomUUID)();
1300
1361
  try {
1301
1362
  (0, import_node_fs2.writeFileSync)(identityFile, `${generated}
1302
1363
  `, {
@@ -2308,7 +2369,7 @@ function windowsCommand(harness) {
2308
2369
  function installHookRunner(sourceFile, homeDir, version) {
2309
2370
  const target = import_node_path4.default.join(homeDir || import_node_os4.default.homedir(), RUNNER_RELATIVE);
2310
2371
  import_node_fs4.default.mkdirSync(import_node_path4.default.dirname(target), { recursive: true });
2311
- const temporary = `${target}.${process.pid}.${import_node_crypto4.default.randomUUID()}.tmp`;
2372
+ const temporary = `${target}.${process.pid}.${import_node_crypto5.default.randomUUID()}.tmp`;
2312
2373
  import_node_fs4.default.copyFileSync(sourceFile, temporary);
2313
2374
  import_node_fs4.default.renameSync(temporary, target);
2314
2375
  const versionPath = import_node_path4.default.join(homeDir || import_node_os4.default.homedir(), RUNNER_VERSION_RELATIVE);
@@ -2478,7 +2539,7 @@ function entryHasOwnedHook(value) {
2478
2539
  }
2479
2540
  function writeSettings(file, settings) {
2480
2541
  import_node_fs4.default.mkdirSync(import_node_path4.default.dirname(file), { recursive: true });
2481
- const temporary = `${file}.${process.pid}.${import_node_crypto4.default.randomUUID()}.tmp`;
2542
+ const temporary = `${file}.${process.pid}.${import_node_crypto5.default.randomUUID()}.tmp`;
2482
2543
  import_node_fs4.default.writeFileSync(temporary, `${JSON.stringify(settings, null, 2)}
2483
2544
  `, {
2484
2545
  mode: 384
@@ -2554,14 +2615,14 @@ function installCodexHooks(scope, projectDir = process.cwd(), codexHomeDir) {
2554
2615
  function uninstallCodexHooks(scope, projectDir = process.cwd(), codexHomeDir) {
2555
2616
  return uninstallHooks("codex", scope, projectDir, codexHomeDir);
2556
2617
  }
2557
- var import_node_fs4, import_node_os4, import_node_path4, import_node_crypto4, OWNER_MARKER, RUNNER_RELATIVE, RUNNER_VERSION_RELATIVE;
2618
+ var import_node_fs4, import_node_os4, import_node_path4, import_node_crypto5, OWNER_MARKER, RUNNER_RELATIVE, RUNNER_VERSION_RELATIVE;
2558
2619
  var init_hook_installer = __esm({
2559
2620
  "src/hook-installer.ts"() {
2560
2621
  "use strict";
2561
2622
  import_node_fs4 = __toESM(require("fs"));
2562
2623
  import_node_os4 = __toESM(require("os"));
2563
2624
  import_node_path4 = __toESM(require("path"));
2564
- import_node_crypto4 = __toESM(require("crypto"));
2625
+ import_node_crypto5 = __toESM(require("crypto"));
2565
2626
  OWNER_MARKER = "--owner mindgraph";
2566
2627
  RUNNER_RELATIVE = ".mindgraph/bin/mindgraph-hook.cjs";
2567
2628
  RUNNER_VERSION_RELATIVE = `${RUNNER_RELATIVE}.version`;
@@ -2603,7 +2664,7 @@ function fileDescriptor(root, absolute) {
2603
2664
  const content = import_node_fs5.default.readFileSync(absolute, "utf8");
2604
2665
  return {
2605
2666
  logical_path: import_node_path5.default.relative(root, absolute).split(import_node_path5.default.sep).join("/"),
2606
- content_hash: import_node_crypto5.default.createHash("sha256").update(content).digest("hex"),
2667
+ content_hash: import_node_crypto6.default.createHash("sha256").update(content).digest("hex"),
2607
2668
  bytes: Buffer.byteLength(content),
2608
2669
  content
2609
2670
  };
@@ -2716,11 +2777,11 @@ async function scanRepository(remote, repository, requested, provider, agentId)
2716
2777
  files: results
2717
2778
  };
2718
2779
  }
2719
- var import_node_crypto5, import_node_fs5, import_node_path5, ok2, err2, SYNC_TOOL;
2780
+ var import_node_crypto6, import_node_fs5, import_node_path5, ok2, err2, SYNC_TOOL;
2720
2781
  var init_sync_tool = __esm({
2721
2782
  "src/sync-tool.ts"() {
2722
2783
  "use strict";
2723
- import_node_crypto5 = __toESM(require("crypto"));
2784
+ import_node_crypto6 = __toESM(require("crypto"));
2724
2785
  import_node_fs5 = __toESM(require("fs"));
2725
2786
  import_node_path5 = __toESM(require("path"));
2726
2787
  init_codegraph();
@@ -3121,15 +3182,15 @@ function renderTraversal(response) {
3121
3182
  const labelByUid = /* @__PURE__ */ new Map();
3122
3183
  for (const node of nodes) labelByUid.set(str(node.uid), str(node.label) || "?");
3123
3184
  if (paths.length > 0) {
3124
- const items = paths.map((raw) => obj(raw)).filter((path7) => Boolean(path7)).map((path7, index) => {
3125
- const uids = Array.isArray(path7.node_uids) ? path7.node_uids.map(String) : [];
3126
- const labels = Array.isArray(path7.labels) ? path7.labels.map(String) : Array.isArray(path7.nodes) ? path7.nodes.map((hop) => {
3185
+ const items = paths.map((raw) => obj(raw)).filter((path8) => Boolean(path8)).map((path8, index) => {
3186
+ const uids = Array.isArray(path8.node_uids) ? path8.node_uids.map(String) : [];
3187
+ const labels = Array.isArray(path8.labels) ? path8.labels.map(String) : Array.isArray(path8.nodes) ? path8.nodes.map((hop) => {
3127
3188
  const hopNode = obj(hop);
3128
3189
  return str(hopNode?.label) || labelByUid.get(str(hopNode?.uid) || "") || str(hop) || "?";
3129
3190
  }) : [];
3130
3191
  const route = labels.length > 0 ? labels.join(" \u2192 ") : uids.join(" \u2192 ") || "(path)";
3131
- const cost = num(path7.cost) ?? num(path7.path_cost);
3132
- const confidence = num(path7.path_confidence);
3192
+ const cost = num(path8.cost) ?? num(path8.path_cost);
3193
+ const confidence = num(path8.path_confidence);
3133
3194
  const meta = [
3134
3195
  cost !== void 0 ? `cost ${cost}` : void 0,
3135
3196
  confidence !== void 0 ? `confidence ${confidence}` : void 0
@@ -3362,6 +3423,9 @@ async function handleTool(client2, name, args) {
3362
3423
  case "mindgraph_retrieve":
3363
3424
  result = await handleRetrieve(client2, args);
3364
3425
  break;
3426
+ case "mindgraph_series_query":
3427
+ result = await handleSeriesQuery(client2, args);
3428
+ break;
3365
3429
  case "mindgraph_ingest":
3366
3430
  result = await handleIngest(client2, args);
3367
3431
  break;
@@ -3406,12 +3470,36 @@ async function handleTool(client2, name, args) {
3406
3470
  return err3(errorDetail(e));
3407
3471
  }
3408
3472
  }
3473
+ async function handleSeriesQuery(client2, args) {
3474
+ const action = typeof args.action === "string" ? args.action : "";
3475
+ if (!["list_for_entity", "latest", "window", "aggregate"].includes(action)) {
3476
+ return err3(`Unknown series query action: ${action}`);
3477
+ }
3478
+ if (action === "list_for_entity" && typeof args.entity_uid !== "string") {
3479
+ return err3("entity_uid is required for action='list_for_entity'");
3480
+ }
3481
+ if (action !== "list_for_entity" && typeof args.series_uid !== "string") {
3482
+ return err3(`series_uid is required for action='${action}'`);
3483
+ }
3484
+ if ((action === "window" || action === "aggregate") && (typeof args.from !== "number" || typeof args.to !== "number")) {
3485
+ return err3(`from and to are required for action='${action}'`);
3486
+ }
3487
+ if (action === "aggregate" && (typeof args.bucket !== "string" || typeof args.agg !== "string")) {
3488
+ return err3("bucket and agg are required for action='aggregate'");
3489
+ }
3490
+ return ok3(
3491
+ await client2.series(args)
3492
+ );
3493
+ }
3409
3494
  async function handleCapture(client2, args) {
3410
3495
  const {
3411
3496
  action,
3412
3497
  label,
3413
3498
  summary,
3414
3499
  content,
3500
+ name,
3501
+ description,
3502
+ license,
3415
3503
  session_uid,
3416
3504
  work_uid,
3417
3505
  execution_uid,
@@ -3446,6 +3534,41 @@ async function handleCapture(client2, args) {
3446
3534
  agent_id
3447
3535
  })
3448
3536
  );
3537
+ case "skill": {
3538
+ if (!name) return err3("name is required for action=skill");
3539
+ if (!description) return err3("description is required for action=skill");
3540
+ if (content === void 0) return err3("content is required for action=skill");
3541
+ const hasProvenance = [session_uid, work_uid, execution_uid].some(
3542
+ (uid) => typeof uid === "string" && uid.length > 0
3543
+ ) || Array.isArray(summarizes_uids) && summarizes_uids.some((uid) => typeof uid === "string" && uid.length > 0);
3544
+ if (!hasProvenance) {
3545
+ return err3(
3546
+ "at least one of session_uid, work_uid, execution_uid, or summarizes_uids is required for action=skill"
3547
+ );
3548
+ }
3549
+ return ok3(
3550
+ await client2.distill({
3551
+ label,
3552
+ output_type: "skill",
3553
+ summary,
3554
+ confidence,
3555
+ salience,
3556
+ session_uid,
3557
+ work_uid,
3558
+ execution_uid,
3559
+ idempotency_key,
3560
+ supersedes_uid,
3561
+ summarizes_uids,
3562
+ props: {
3563
+ name,
3564
+ description,
3565
+ content,
3566
+ ...license ? { license } : {}
3567
+ },
3568
+ agent_id
3569
+ })
3570
+ );
3571
+ }
3449
3572
  case "journal": {
3450
3573
  if (!content) return err3("content is required for action=journal");
3451
3574
  const journalProps = { content };
@@ -4433,7 +4556,7 @@ var init_tools = __esm({
4433
4556
  TOOLS = [
4434
4557
  {
4435
4558
  name: "mindgraph_capture",
4436
- description: "Capture knowledge into the graph. Use 'entity' for people/orgs/places/events/nations/concepts (auto-deduplicates \u2014 safe to call even if already exists), 'observation' for factual statements, 'source' for documents/URLs, 'snippet' for quotes from a source, and 'concept' for abstract ideas. Use 'journal' for quick personal notes and 'lesson' for a durable learning attributable to a session or source nodes. Prefer 'entity'/'observation' for objective facts, 'journal' for informal notes, and 'lesson' only for reusable knowledge.",
4559
+ description: "Capture knowledge into the graph. Use 'entity' for people/orgs/places/events/nations/concepts (auto-deduplicates \u2014 safe to call even if already exists), 'observation' for factual statements, 'source' for documents/URLs, 'snippet' for quotes from a source, and 'concept' for abstract ideas. Use 'journal' for quick personal notes, 'lesson' for a durable learning, and 'skill' for a reusable procedure authored from completed work. Skill capture always creates a candidate requiring admin review; it never publishes. Skill names must be 1-64 lowercase a-z, digits, or hyphens, with no leading/trailing/consecutive hyphens and no 'anthropic' or 'claude'. Provide at least one provenance field and write instructions, not a transcript dump.",
4437
4560
  inputSchema: {
4438
4561
  type: "object",
4439
4562
  properties: {
@@ -4446,7 +4569,8 @@ var init_tools = __esm({
4446
4569
  "snippet",
4447
4570
  "concept",
4448
4571
  "journal",
4449
- "lesson"
4572
+ "lesson",
4573
+ "skill"
4450
4574
  ],
4451
4575
  description: "Type of knowledge to capture"
4452
4576
  },
@@ -4460,11 +4584,30 @@ var init_tools = __esm({
4460
4584
  },
4461
4585
  content: {
4462
4586
  type: "string",
4463
- description: "Journal content/body text (for action=journal)"
4587
+ maxLength: 65536,
4588
+ description: "Journal body (action=journal) or complete SKILL.md instructions, at most 64 KiB UTF-8 (action=skill)"
4589
+ },
4590
+ name: {
4591
+ type: "string",
4592
+ minLength: 1,
4593
+ maxLength: 64,
4594
+ pattern: "^(?!.*(?:anthropic|claude))(?!-)(?!.*--)[a-z0-9](?:[a-z0-9-]{0,62}[a-z0-9])?$",
4595
+ description: "Stable skill identifier using lowercase a-z, digits, and hyphens (for action=skill)"
4596
+ },
4597
+ description: {
4598
+ type: "string",
4599
+ minLength: 1,
4600
+ maxLength: 1024,
4601
+ description: "When the skill should be used; XML tags are rejected (for action=skill)"
4602
+ },
4603
+ license: {
4604
+ type: "string",
4605
+ description: "Optional skill license (for action=skill)"
4464
4606
  },
4465
4607
  session_uid: {
4466
4608
  type: "string",
4467
- description: "Session UID that produced a lesson (for action=lesson)"
4609
+ minLength: 1,
4610
+ description: "Session UID that produced a lesson or skill"
4468
4611
  },
4469
4612
  work_uid: {
4470
4613
  type: "string",
@@ -4485,7 +4628,8 @@ var init_tools = __esm({
4485
4628
  summarizes_uids: {
4486
4629
  type: "array",
4487
4630
  items: { type: "string" },
4488
- description: "Source node UIDs the lesson was learned from (for action=lesson)"
4631
+ minItems: 1,
4632
+ description: "Source node UIDs the lesson or skill was learned from"
4489
4633
  },
4490
4634
  entity_type: {
4491
4635
  type: "string",
@@ -4552,7 +4696,24 @@ var init_tools = __esm({
4552
4696
  description: "Agent identity"
4553
4697
  }
4554
4698
  },
4555
- required: ["action", "label"]
4699
+ required: ["action", "label"],
4700
+ allOf: [
4701
+ {
4702
+ if: {
4703
+ properties: { action: { const: "skill" } },
4704
+ required: ["action"]
4705
+ },
4706
+ then: {
4707
+ required: ["name", "description", "content"],
4708
+ anyOf: [
4709
+ { required: ["session_uid"] },
4710
+ { required: ["work_uid"] },
4711
+ { required: ["execution_uid"] },
4712
+ { required: ["summarizes_uids"] }
4713
+ ]
4714
+ }
4715
+ }
4716
+ ]
4556
4717
  }
4557
4718
  },
4558
4719
  {
@@ -5138,6 +5299,42 @@ var init_tools = __esm({
5138
5299
  required: ["action"]
5139
5300
  }
5140
5301
  },
5302
+ {
5303
+ name: "mindgraph_series_query",
5304
+ description: "Read dense numeric time series without placing raw points in general retrieval context. Use list_for_entity to discover series, latest for cached statistics, window for [from,to) keyset pages, and aggregate for bounded calendar buckets.",
5305
+ inputSchema: {
5306
+ type: "object",
5307
+ additionalProperties: false,
5308
+ properties: {
5309
+ action: {
5310
+ type: "string",
5311
+ enum: ["list_for_entity", "latest", "window", "aggregate"]
5312
+ },
5313
+ entity_uid: { type: "string" },
5314
+ series_uid: { type: "string" },
5315
+ from: { type: "number" },
5316
+ to: { type: "number" },
5317
+ cursor: { type: "number" },
5318
+ limit: { type: "number", minimum: 1, maximum: 1e4 },
5319
+ bucket: {
5320
+ type: "string",
5321
+ enum: ["year", "quarter", "month", "week", "day", "hour"]
5322
+ },
5323
+ agg: {
5324
+ type: "string",
5325
+ enum: ["sum", "mean", "min", "max", "count", "std_dev", "last"]
5326
+ },
5327
+ fill: { type: "string", enum: ["none", "null", "prev"] }
5328
+ },
5329
+ required: ["action"]
5330
+ },
5331
+ annotations: {
5332
+ readOnlyHint: true,
5333
+ destructiveHint: false,
5334
+ idempotentHint: true,
5335
+ openWorldHint: false
5336
+ }
5337
+ },
5141
5338
  {
5142
5339
  name: "mindgraph_ingest",
5143
5340
  description: "Ingest long-form content (articles, transcripts, documents, meeting notes) into the knowledge graph. The pipeline automatically chunks the text, extracts entities/claims/relationships via LLM, and deduplicates against existing nodes. Use 'chunk' for a single passage (synchronous), 'document' for full documents (async \u2014 returns a job ID), 'session' for conversation transcripts. Use 'job_status' to check progress. For short individual facts, use the specific cognitive tools instead.",
@@ -5624,7 +5821,7 @@ function isTransientStatus(status) {
5624
5821
  return status === 408 || status === 425 || status === 429 || status >= 500;
5625
5822
  }
5626
5823
  function sleep(ms) {
5627
- return new Promise((resolve2) => setTimeout(resolve2, ms));
5824
+ return new Promise((resolve3) => setTimeout(resolve3, ms));
5628
5825
  }
5629
5826
  function headers(apiKey, orgId) {
5630
5827
  const value = {
@@ -5642,6 +5839,7 @@ function unsupportedResponse(status, body) {
5642
5839
  }
5643
5840
  function toolMutability(toolName, action) {
5644
5841
  if (toolName === "mindgraph_retrieve") return "read";
5842
+ if (toolName === "mindgraph_series_query") return "read";
5645
5843
  if (toolName === "mindgraph_plan") {
5646
5844
  return action && PLAN_READ_ACTIONS.has(action) ? "read" : "write";
5647
5845
  }
@@ -5826,6 +6024,8 @@ var init_governance = __esm({
5826
6024
  UID_FIELDS = [
5827
6025
  "uid",
5828
6026
  "source_uid",
6027
+ "entity_uid",
6028
+ "series_uid",
5829
6029
  "session_uid",
5830
6030
  "work_uid",
5831
6031
  "goal_uid",
@@ -5978,7 +6178,7 @@ async function main() {
5978
6178
  const transport = new import_stdio.StdioServerTransport();
5979
6179
  await server.connect(transport);
5980
6180
  console.error(
5981
- `MindGraph MCP server v${"0.18.0"} running on stdio (${BASE_URL})`
6181
+ `MindGraph MCP server v${"0.19.0"} running on stdio (${BASE_URL})`
5982
6182
  );
5983
6183
  }
5984
6184
  var import_server, import_stdio, import_types, import_node_path6, API_KEY, BASE_URL, AGENT_ID, ORG_ID, PROFILE, HARNESS, client, INSTRUCTIONS, RUNNER_REFRESH, HOOK_SKEW_NOTE, server, GOVERNANCE_CONFIG, PROMPTS;
@@ -6119,7 +6319,7 @@ Capture knowledge when the user shares something worth remembering:
6119
6319
  }
6120
6320
  RUNNER_REFRESH = (() => {
6121
6321
  try {
6122
- return refreshHookRunner("0.18.0", import_node_path6.default.join(__dirname, "cli.js"));
6322
+ return refreshHookRunner("0.19.0", import_node_path6.default.join(__dirname, "cli.js"));
6123
6323
  } catch {
6124
6324
  return void 0;
6125
6325
  }
@@ -6133,12 +6333,12 @@ Capture knowledge when the user shares something worth remembering:
6133
6333
  }
6134
6334
  })();
6135
6335
  console.error(
6136
- `[mindgraph-mcp] refreshed pinned hook runner ${RUNNER_REFRESH.runnerVersion ? `v${RUNNER_REFRESH.runnerVersion}` : "(pre-sidecar)"} \u2192 v${"0.18.0"}` + (refreshedSettings.length > 0 ? `; refreshed owned hook entries in ${refreshedSettings.join(", ")}` : "")
6336
+ `[mindgraph-mcp] refreshed pinned hook runner ${RUNNER_REFRESH.runnerVersion ? `v${RUNNER_REFRESH.runnerVersion}` : "(pre-sidecar)"} \u2192 v${"0.19.0"}` + (refreshedSettings.length > 0 ? `; refreshed owned hook entries in ${refreshedSettings.join(", ")}` : "")
6137
6337
  );
6138
6338
  }
6139
6339
  HOOK_SKEW_NOTE = (() => {
6140
6340
  try {
6141
- return versionSkewNote("0.18.0");
6341
+ return versionSkewNote("0.19.0");
6142
6342
  } catch {
6143
6343
  return void 0;
6144
6344
  }
@@ -6152,7 +6352,7 @@ ${HOOK_SKEW_NOTE}`;
6152
6352
  server = new import_server.Server(
6153
6353
  {
6154
6354
  name: "mindgraph",
6155
- version: "0.18.0"
6355
+ version: "0.19.0"
6156
6356
  },
6157
6357
  {
6158
6358
  capabilities: {
@@ -6370,8 +6570,8 @@ ${conversation}`
6370
6570
  });
6371
6571
 
6372
6572
  // src/cli.ts
6373
- var fs5 = __toESM(require("fs"));
6374
- var path6 = __toESM(require("path"));
6573
+ var fs6 = __toESM(require("fs"));
6574
+ var path7 = __toESM(require("path"));
6375
6575
  var os4 = __toESM(require("os"));
6376
6576
  var crypto5 = __toESM(require("crypto"));
6377
6577
  var import_child_process = require("child_process");
@@ -6389,6 +6589,8 @@ function parseArgs(argv) {
6389
6589
  let harness;
6390
6590
  let hooks = false;
6391
6591
  let agentId = process.env.MINDGRAPH_AGENT_ID;
6592
+ const positionals = [];
6593
+ let dir;
6392
6594
  for (let i = 0; i < args.length; i++) {
6393
6595
  const arg = args[i];
6394
6596
  switch (arg) {
@@ -6409,6 +6611,9 @@ function parseArgs(argv) {
6409
6611
  case "--project-dir":
6410
6612
  projectDir = args[++i];
6411
6613
  break;
6614
+ case "--dir":
6615
+ dir = args[++i];
6616
+ break;
6412
6617
  case "--harness":
6413
6618
  harness = args[++i];
6414
6619
  break;
@@ -6427,12 +6632,27 @@ function parseArgs(argv) {
6427
6632
  break;
6428
6633
  default:
6429
6634
  if (!arg.startsWith("-")) {
6430
- command2 = arg;
6635
+ if (command2 === "serve") {
6636
+ command2 = arg;
6637
+ } else {
6638
+ positionals.push(arg);
6639
+ }
6431
6640
  }
6432
6641
  break;
6433
6642
  }
6434
6643
  }
6435
- return { command: command2, apiKey, baseUrl, scope, projectDir, harness, hooks, agentId };
6644
+ return {
6645
+ command: command2,
6646
+ apiKey,
6647
+ baseUrl,
6648
+ scope,
6649
+ projectDir,
6650
+ harness,
6651
+ hooks,
6652
+ agentId,
6653
+ positionals,
6654
+ dir
6655
+ };
6436
6656
  }
6437
6657
  function mcpAddFailureOutput(e) {
6438
6658
  const err5 = e;
@@ -6446,8 +6666,375 @@ function classifyMcpAddFailure(e) {
6446
6666
  return "other";
6447
6667
  }
6448
6668
 
6669
+ // src/skills-cli.ts
6670
+ var import_node_crypto2 = require("crypto");
6671
+ var fs2 = __toESM(require("fs"));
6672
+ var path2 = __toESM(require("path"));
6673
+ function httpSkillsApi(baseUrl, apiKey, agentId) {
6674
+ const endpoint = `${baseUrl.replace(/\/$/, "")}/action/skill`;
6675
+ const call = async (body) => {
6676
+ const response = await fetch(endpoint, {
6677
+ method: "POST",
6678
+ headers: {
6679
+ "content-type": "application/json",
6680
+ authorization: `Bearer ${apiKey}`
6681
+ },
6682
+ body: JSON.stringify(
6683
+ agentId ? { agent_id: agentId, ...body } : body
6684
+ )
6685
+ });
6686
+ let parsed = {};
6687
+ try {
6688
+ parsed = await response.json();
6689
+ } catch {
6690
+ parsed = { error: `non-JSON response (HTTP ${response.status})` };
6691
+ }
6692
+ return { status: response.status, body: parsed };
6693
+ };
6694
+ return {
6695
+ async list(grantedTo) {
6696
+ const { status, body } = await call({
6697
+ action: "list",
6698
+ ...grantedTo ? { granted_to: grantedTo } : {}
6699
+ });
6700
+ if (status !== 200) {
6701
+ throw new Error(
6702
+ `skill list failed (HTTP ${status}): ${String(body.error ?? "")}`
6703
+ );
6704
+ }
6705
+ return body.skills ?? [];
6706
+ },
6707
+ async get(uid) {
6708
+ const { status, body } = await call({ action: "get", uid });
6709
+ if (status === 404) return null;
6710
+ if (status !== 200) {
6711
+ throw new Error(
6712
+ `skill get failed (HTTP ${status}): ${String(body.error ?? "")}`
6713
+ );
6714
+ }
6715
+ const props = body.props ?? {};
6716
+ return {
6717
+ uid: String(body.uid ?? uid),
6718
+ name: String(props.name ?? ""),
6719
+ description: String(props.description ?? ""),
6720
+ content: String(props.content ?? ""),
6721
+ license: props.license ?? null,
6722
+ status: String(props.status ?? "candidate"),
6723
+ status_reason: props.status_reason ?? null,
6724
+ content_hash: String(props.content_hash ?? ""),
6725
+ version: Number(body.version ?? 0)
6726
+ };
6727
+ },
6728
+ send: call
6729
+ };
6730
+ }
6731
+ function isValidSkillName(name) {
6732
+ return name.length >= 1 && name.length <= 64 && /^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(name);
6733
+ }
6734
+ function renderSkillMd(skill) {
6735
+ const lines = [
6736
+ "---",
6737
+ `name: ${JSON.stringify(skill.name)}`,
6738
+ `description: ${JSON.stringify(skill.description)}`
6739
+ ];
6740
+ if (skill.license) {
6741
+ lines.push(`license: ${JSON.stringify(skill.license)}`);
6742
+ }
6743
+ lines.push(
6744
+ "metadata:",
6745
+ ` mindgraph-uid: ${JSON.stringify(skill.uid)}`,
6746
+ ` mindgraph-version: ${JSON.stringify(String(skill.version))}`,
6747
+ ` mindgraph-content-hash: ${JSON.stringify(skill.content_hash)}`,
6748
+ "---",
6749
+ "",
6750
+ skill.content
6751
+ );
6752
+ return `${lines.join("\n")}
6753
+ `;
6754
+ }
6755
+ function scalar(raw) {
6756
+ const trimmed = raw.trim();
6757
+ try {
6758
+ const parsed = JSON.parse(trimmed);
6759
+ if (typeof parsed === "string") return parsed;
6760
+ } catch {
6761
+ }
6762
+ return trimmed;
6763
+ }
6764
+ function parseSkillMd(text) {
6765
+ if (!text.startsWith("---\n")) return null;
6766
+ const end = text.indexOf("\n---\n", 4);
6767
+ if (end < 0) return null;
6768
+ const frontmatter = text.slice(4, end + 1);
6769
+ const body = text.slice(end + 5).replace(/^\n/, "").replace(/\n$/, "");
6770
+ const entries = [];
6771
+ for (const line of frontmatter.split("\n")) {
6772
+ const top = /^([A-Za-z0-9_-]+):(.*)$/.exec(line);
6773
+ if (top) {
6774
+ entries.push({ key: top[1], raw: top[2] });
6775
+ } else if (entries.length > 0) {
6776
+ entries[entries.length - 1].raw += `
6777
+ ${line}`;
6778
+ }
6779
+ }
6780
+ const result = { unknown: {}, body };
6781
+ for (const { key, raw } of entries) {
6782
+ switch (key) {
6783
+ case "name":
6784
+ result.name = scalar(raw);
6785
+ break;
6786
+ case "description":
6787
+ result.description = scalar(raw);
6788
+ break;
6789
+ case "license":
6790
+ result.license = scalar(raw);
6791
+ break;
6792
+ case "metadata": {
6793
+ const uid = /mindgraph-uid:\s*(.+)/.exec(raw);
6794
+ const version = /mindgraph-version:\s*(.+)/.exec(raw);
6795
+ const hash = /mindgraph-content-hash:\s*(.+)/.exec(raw);
6796
+ if (uid) {
6797
+ result.pin = {
6798
+ uid: scalar(uid[1]),
6799
+ version: version ? scalar(version[1]) : void 0,
6800
+ contentHash: hash ? scalar(hash[1]) : void 0
6801
+ };
6802
+ } else if (raw.trim().length > 0) {
6803
+ result.unknown[key] = raw;
6804
+ }
6805
+ break;
6806
+ }
6807
+ default:
6808
+ result.unknown[key] = raw;
6809
+ break;
6810
+ }
6811
+ }
6812
+ return result;
6813
+ }
6814
+ var STATE_FILE = ".mindgraph-skills-state.json";
6815
+ function sha256(text) {
6816
+ return (0, import_node_crypto2.createHash)("sha256").update(text).digest("hex");
6817
+ }
6818
+ function loadState(dir) {
6819
+ try {
6820
+ return JSON.parse(
6821
+ fs2.readFileSync(path2.join(dir, STATE_FILE), "utf8")
6822
+ );
6823
+ } catch {
6824
+ return {};
6825
+ }
6826
+ }
6827
+ function saveState(dir, state) {
6828
+ fs2.writeFileSync(
6829
+ path2.join(dir, STATE_FILE),
6830
+ `${JSON.stringify(state, null, 2)}
6831
+ `
6832
+ );
6833
+ }
6834
+ function isSymlink(target) {
6835
+ try {
6836
+ return fs2.lstatSync(target).isSymbolicLink();
6837
+ } catch {
6838
+ return false;
6839
+ }
6840
+ }
6841
+ function safeSkillPaths(dir, name) {
6842
+ if (!isValidSkillName(name)) {
6843
+ return `invalid skill name ${JSON.stringify(name)}`;
6844
+ }
6845
+ const root = path2.resolve(dir);
6846
+ const skillDir = path2.resolve(root, name);
6847
+ if (skillDir !== path2.join(root, name) || !skillDir.startsWith(root + path2.sep)) {
6848
+ return "resolved path escapes the target directory";
6849
+ }
6850
+ if (isSymlink(skillDir)) {
6851
+ return "target directory is a symlink";
6852
+ }
6853
+ const skillFile = path2.join(skillDir, "SKILL.md");
6854
+ if (isSymlink(skillFile)) {
6855
+ return "target SKILL.md is a symlink";
6856
+ }
6857
+ return { skillDir, skillFile };
6858
+ }
6859
+ async function skillsPull(api, dir, grantedTo) {
6860
+ const report = {
6861
+ written: [],
6862
+ unchanged: [],
6863
+ locallyEdited: [],
6864
+ conflicts: [],
6865
+ removed: [],
6866
+ refused: []
6867
+ };
6868
+ fs2.mkdirSync(dir, { recursive: true });
6869
+ const state = loadState(dir);
6870
+ const summaries = await api.list(grantedTo);
6871
+ const fetched = /* @__PURE__ */ new Set();
6872
+ for (const summary of summaries) {
6873
+ const detail = await api.get(summary.uid);
6874
+ if (!detail || detail.status !== "published") continue;
6875
+ fetched.add(detail.name);
6876
+ const paths = safeSkillPaths(dir, detail.name);
6877
+ if (typeof paths === "string") {
6878
+ report.refused.push({ name: detail.name, reason: paths });
6879
+ continue;
6880
+ }
6881
+ const { skillDir, skillFile } = paths;
6882
+ const rendered2 = renderSkillMd(detail);
6883
+ let existing = null;
6884
+ try {
6885
+ existing = fs2.readFileSync(skillFile, "utf8");
6886
+ } catch {
6887
+ existing = null;
6888
+ }
6889
+ if (existing === rendered2) {
6890
+ state[detail.name] = {
6891
+ uid: detail.uid,
6892
+ content_hash: detail.content_hash,
6893
+ file_sha256: sha256(rendered2)
6894
+ };
6895
+ report.unchanged.push(detail.name);
6896
+ continue;
6897
+ }
6898
+ const recorded = state[detail.name];
6899
+ const locallyModified = existing !== null && (!recorded || sha256(existing) !== recorded.file_sha256);
6900
+ if (locallyModified) {
6901
+ if (recorded && recorded.content_hash === detail.content_hash) {
6902
+ report.locallyEdited.push(detail.name);
6903
+ continue;
6904
+ }
6905
+ fs2.writeFileSync(`${skillFile}.conflict`, rendered2);
6906
+ report.conflicts.push(detail.name);
6907
+ continue;
6908
+ }
6909
+ fs2.mkdirSync(skillDir, { recursive: true });
6910
+ fs2.writeFileSync(skillFile, rendered2);
6911
+ state[detail.name] = {
6912
+ uid: detail.uid,
6913
+ content_hash: detail.content_hash,
6914
+ file_sha256: sha256(rendered2)
6915
+ };
6916
+ report.written.push(detail.name);
6917
+ }
6918
+ for (const [name, recorded] of Object.entries(state)) {
6919
+ if (fetched.has(name)) continue;
6920
+ const paths = safeSkillPaths(dir, name);
6921
+ if (typeof paths === "string") {
6922
+ delete state[name];
6923
+ continue;
6924
+ }
6925
+ const { skillDir, skillFile } = paths;
6926
+ let statusReason = null;
6927
+ try {
6928
+ const gone = await api.get(recorded.uid);
6929
+ statusReason = gone?.status_reason ?? (gone ? `status: ${gone.status}` : null);
6930
+ } catch {
6931
+ statusReason = null;
6932
+ }
6933
+ let existing = null;
6934
+ try {
6935
+ existing = fs2.readFileSync(skillFile, "utf8");
6936
+ } catch {
6937
+ existing = null;
6938
+ }
6939
+ if (existing === null) {
6940
+ delete state[name];
6941
+ continue;
6942
+ }
6943
+ const locallyModified = sha256(existing) !== recorded.file_sha256;
6944
+ if (locallyModified) {
6945
+ fs2.renameSync(skillFile, `${skillFile}.conflict`);
6946
+ report.removed.push({ name, statusReason, kept: true });
6947
+ } else {
6948
+ fs2.rmSync(skillDir, { recursive: true, force: true });
6949
+ report.removed.push({ name, statusReason, kept: false });
6950
+ }
6951
+ delete state[name];
6952
+ }
6953
+ saveState(dir, state);
6954
+ return report;
6955
+ }
6956
+ async function skillsPush(api, skillPath) {
6957
+ const resolved = path2.resolve(skillPath);
6958
+ const file = resolved.endsWith("SKILL.md") ? resolved : path2.join(resolved, "SKILL.md");
6959
+ if (isSymlink(file)) {
6960
+ return { outcome: "error", message: `${file} is a symlink; refusing` };
6961
+ }
6962
+ let text;
6963
+ try {
6964
+ text = fs2.readFileSync(file, "utf8");
6965
+ } catch {
6966
+ return { outcome: "error", message: `could not read ${file}` };
6967
+ }
6968
+ const parsed = parseSkillMd(text);
6969
+ if (!parsed) {
6970
+ return {
6971
+ outcome: "error",
6972
+ message: "SKILL.md must start with a --- frontmatter block"
6973
+ };
6974
+ }
6975
+ if (!parsed.name || !parsed.description) {
6976
+ return {
6977
+ outcome: "error",
6978
+ message: "SKILL.md frontmatter requires name and description"
6979
+ };
6980
+ }
6981
+ if (parsed.pin?.uid) {
6982
+ const { status: status2, body: body2 } = await api.send({
6983
+ action: "update",
6984
+ uid: parsed.pin.uid,
6985
+ name: parsed.name,
6986
+ description: parsed.description,
6987
+ content: parsed.body,
6988
+ ...parsed.license ? { license: parsed.license } : {}
6989
+ });
6990
+ if (status2 === 200) {
6991
+ return {
6992
+ outcome: "updated",
6993
+ message: `updated candidate ${parsed.name} (${parsed.pin.uid})`,
6994
+ uid: parsed.pin.uid
6995
+ };
6996
+ }
6997
+ return {
6998
+ outcome: status2 === 409 ? "conflict" : "error",
6999
+ message: `update failed (HTTP ${status2}): ${String(body2.error ?? "")}`,
7000
+ existingUid: body2.existing_uid ?? void 0,
7001
+ existingStatus: body2.existing_status ?? void 0
7002
+ };
7003
+ }
7004
+ const { status, body } = await api.send({
7005
+ action: "create",
7006
+ skill_source: "imported",
7007
+ name: parsed.name,
7008
+ description: parsed.description,
7009
+ content: parsed.body,
7010
+ ...parsed.license ? { license: parsed.license } : {},
7011
+ ...Object.keys(parsed.unknown).length > 0 ? { imported_frontmatter: parsed.unknown } : {},
7012
+ import_origin: `skills-push:${path2.basename(resolved)}`
7013
+ });
7014
+ if (status === 200 && body.replayed === true) {
7015
+ return {
7016
+ outcome: "replayed",
7017
+ message: `unchanged: ${parsed.name} already imported (${String(body.uid)})`,
7018
+ uid: String(body.uid)
7019
+ };
7020
+ }
7021
+ if (status === 200) {
7022
+ return {
7023
+ outcome: "created",
7024
+ message: `imported ${parsed.name} as a candidate (${String(body.uid)}) \u2014 review and publish in the dashboard`,
7025
+ uid: String(body.uid)
7026
+ };
7027
+ }
7028
+ return {
7029
+ outcome: status === 409 ? "conflict" : "error",
7030
+ message: `import failed (HTTP ${status}): ${String(body.error ?? "")}`,
7031
+ existingUid: body.existing_uid ?? void 0,
7032
+ existingStatus: body.existing_status ?? void 0
7033
+ };
7034
+ }
7035
+
6449
7036
  // src/hook-core.ts
6450
- var import_node_crypto3 = __toESM(require("crypto"));
7037
+ var import_node_crypto4 = __toESM(require("crypto"));
6451
7038
  var import_node_fs3 = __toESM(require("fs"));
6452
7039
  var import_node_os3 = __toESM(require("os"));
6453
7040
  var import_node_path3 = __toESM(require("path"));
@@ -6477,7 +7064,7 @@ function runtimeDir(options) {
6477
7064
  }
6478
7065
  function ledgerPath(sessionId, options) {
6479
7066
  const harness = "harness" in options ? options.harness : "claude-code";
6480
- const key = import_node_crypto3.default.createHash("sha256").update(`${harness}\0${sessionId}`).digest("hex");
7067
+ const key = import_node_crypto4.default.createHash("sha256").update(`${harness}\0${sessionId}`).digest("hex");
6481
7068
  return import_node_path3.default.join(runtimeDir(options), `${key}.json`);
6482
7069
  }
6483
7070
  function waitBriefly(ms) {
@@ -6519,7 +7106,7 @@ function withLedger(sessionId, options, update) {
6519
7106
  }
6520
7107
  const result = update(ledger);
6521
7108
  ledger.updatedAt = Date.now();
6522
- const temporary = `${file}.${process.pid}.${import_node_crypto3.default.randomUUID()}.tmp`;
7109
+ const temporary = `${file}.${process.pid}.${import_node_crypto4.default.randomUUID()}.tmp`;
6523
7110
  import_node_fs3.default.writeFileSync(temporary, `${JSON.stringify(ledger)}
6524
7111
  `, { mode: 384 });
6525
7112
  import_node_fs3.default.renameSync(temporary, file);
@@ -6927,7 +7514,7 @@ async function sessionStart(input, client2, options) {
6927
7514
  const contextText = registrationNote ? `${registrationNote}
6928
7515
 
6929
7516
  ${briefText}` : briefText;
6930
- const briefHash = import_node_crypto3.default.createHash("sha256").update(contextText).digest("hex");
7517
+ const briefHash = import_node_crypto4.default.createHash("sha256").update(contextText).digest("hex");
6931
7518
  const shouldInject = withLedger(input.session_id, options, (ledger) => {
6932
7519
  const changed = ledger.lastBriefHash !== briefHash;
6933
7520
  const reinjectCompact = options.reinjectUnchangedCompact && input.source === "compact";
@@ -7026,7 +7613,7 @@ async function postTool(input, client2, options) {
7026
7613
  ledger.mutatingOperations += 1;
7027
7614
  }
7028
7615
  if (!payload?.error) {
7029
- if (name.includes("mindgraph_capture") && ["lesson", "journal"].includes(action || "") || name.includes("mindgraph_commit") && action === "resolve_decision" || name.includes("mindgraph_plan") && action === "assess_risk") {
7616
+ if (name.includes("mindgraph_capture") && ["lesson", "journal", "skill"].includes(action || "") || name.includes("mindgraph_commit") && action === "resolve_decision" || name.includes("mindgraph_plan") && action === "assess_risk") {
7030
7617
  ledger.memoryWritten = true;
7031
7618
  }
7032
7619
  }
@@ -7309,15 +7896,15 @@ async function runCodexHook(input, client2, options = {}) {
7309
7896
  init_hook_installer();
7310
7897
  function recordHookHealth(harness, error) {
7311
7898
  try {
7312
- const dir = process.env.MINDGRAPH_RUNTIME_DIR || path6.join(os4.homedir(), ".mindgraph", "runtime");
7313
- fs5.mkdirSync(dir, { recursive: true, mode: 448 });
7314
- const file = path6.join(dir, "hook-health.json");
7899
+ const dir = process.env.MINDGRAPH_RUNTIME_DIR || path7.join(os4.homedir(), ".mindgraph", "runtime");
7900
+ fs6.mkdirSync(dir, { recursive: true, mode: 448 });
7901
+ const file = path7.join(dir, "hook-health.json");
7315
7902
  let failures = 0;
7316
7903
  try {
7317
- failures = Number(JSON.parse(fs5.readFileSync(file, "utf8")).consecutiveFailures) || 0;
7904
+ failures = Number(JSON.parse(fs6.readFileSync(file, "utf8")).consecutiveFailures) || 0;
7318
7905
  } catch {
7319
7906
  }
7320
- fs5.writeFileSync(
7907
+ fs6.writeFileSync(
7321
7908
  file,
7322
7909
  `${JSON.stringify({
7323
7910
  harness,
@@ -7333,14 +7920,14 @@ function recordHookHealth(harness, error) {
7333
7920
  }
7334
7921
  function clearHookHealth(harness) {
7335
7922
  try {
7336
- const file = path6.join(
7337
- process.env.MINDGRAPH_RUNTIME_DIR || path6.join(os4.homedir(), ".mindgraph", "runtime"),
7923
+ const file = path7.join(
7924
+ process.env.MINDGRAPH_RUNTIME_DIR || path7.join(os4.homedir(), ".mindgraph", "runtime"),
7338
7925
  "hook-health.json"
7339
7926
  );
7340
- if (!fs5.existsSync(file)) return;
7341
- const current = JSON.parse(fs5.readFileSync(file, "utf8"));
7927
+ if (!fs6.existsSync(file)) return;
7928
+ const current = JSON.parse(fs6.readFileSync(file, "utf8"));
7342
7929
  if (!Number(current.consecutiveFailures)) return;
7343
- fs5.writeFileSync(
7930
+ fs6.writeFileSync(
7344
7931
  file,
7345
7932
  `${JSON.stringify({
7346
7933
  harness,
@@ -7356,7 +7943,7 @@ function clearHookHealth(harness) {
7356
7943
  function getClaudeDesktopConfigPath() {
7357
7944
  switch (process.platform) {
7358
7945
  case "darwin":
7359
- return path6.join(
7946
+ return path7.join(
7360
7947
  os4.homedir(),
7361
7948
  "Library",
7362
7949
  "Application Support",
@@ -7364,13 +7951,13 @@ function getClaudeDesktopConfigPath() {
7364
7951
  "claude_desktop_config.json"
7365
7952
  );
7366
7953
  case "win32":
7367
- return path6.join(
7368
- process.env.APPDATA || path6.join(os4.homedir(), "AppData", "Roaming"),
7954
+ return path7.join(
7955
+ process.env.APPDATA || path7.join(os4.homedir(), "AppData", "Roaming"),
7369
7956
  "Claude",
7370
7957
  "claude_desktop_config.json"
7371
7958
  );
7372
7959
  case "linux":
7373
- return path6.join(
7960
+ return path7.join(
7374
7961
  os4.homedir(),
7375
7962
  ".config",
7376
7963
  "Claude",
@@ -7381,16 +7968,16 @@ function getClaudeDesktopConfigPath() {
7381
7968
  }
7382
7969
  }
7383
7970
  function readJsonFile(filePath) {
7384
- if (!fs5.existsSync(filePath)) return {};
7385
- const content = fs5.readFileSync(filePath, "utf-8");
7971
+ if (!fs6.existsSync(filePath)) return {};
7972
+ const content = fs6.readFileSync(filePath, "utf-8");
7386
7973
  return JSON.parse(content);
7387
7974
  }
7388
7975
  function writeJsonFile(filePath, data) {
7389
- const dir = path6.dirname(filePath);
7390
- if (!fs5.existsSync(dir)) {
7391
- fs5.mkdirSync(dir, { recursive: true });
7976
+ const dir = path7.dirname(filePath);
7977
+ if (!fs6.existsSync(dir)) {
7978
+ fs6.mkdirSync(dir, { recursive: true });
7392
7979
  }
7393
- fs5.writeFileSync(filePath, JSON.stringify(data, null, 2) + "\n");
7980
+ fs6.writeFileSync(filePath, JSON.stringify(data, null, 2) + "\n");
7394
7981
  }
7395
7982
  function getMcpServerEntry(apiKey, baseUrl) {
7396
7983
  const env = { MINDGRAPH_API_KEY: apiKey };
@@ -7477,7 +8064,7 @@ function installClaudeCode(apiKey, baseUrl, agentId) {
7477
8064
  }
7478
8065
  function uninstallClaudeDesktop() {
7479
8066
  const configPath = getClaudeDesktopConfigPath();
7480
- if (!fs5.existsSync(configPath)) {
8067
+ if (!fs6.existsSync(configPath)) {
7481
8068
  console.log("Claude Desktop config not found. Nothing to remove.");
7482
8069
  return;
7483
8070
  }
@@ -7504,7 +8091,7 @@ function uninstallClaudeCode() {
7504
8091
  }
7505
8092
  function printStatus() {
7506
8093
  const desktopPath = getClaudeDesktopConfigPath();
7507
- if (fs5.existsSync(desktopPath)) {
8094
+ if (fs6.existsSync(desktopPath)) {
7508
8095
  const config = readJsonFile(desktopPath);
7509
8096
  const servers = config.mcpServers;
7510
8097
  if (servers && "mindgraph" in servers) {
@@ -7522,12 +8109,12 @@ function printStatus() {
7522
8109
  `MINDGRAPH_BASE_URL: ${process.env.MINDGRAPH_BASE_URL || "https://api.mindgraph.cloud (default)"}`
7523
8110
  );
7524
8111
  try {
7525
- const healthFile = path6.join(
7526
- process.env.MINDGRAPH_RUNTIME_DIR || path6.join(os4.homedir(), ".mindgraph", "runtime"),
8112
+ const healthFile = path7.join(
8113
+ process.env.MINDGRAPH_RUNTIME_DIR || path7.join(os4.homedir(), ".mindgraph", "runtime"),
7527
8114
  "hook-health.json"
7528
8115
  );
7529
- if (fs5.existsSync(healthFile)) {
7530
- const health = JSON.parse(fs5.readFileSync(healthFile, "utf8"));
8116
+ if (fs6.existsSync(healthFile)) {
8117
+ const health = JSON.parse(fs6.readFileSync(healthFile, "utf8"));
7531
8118
  const failures = Number(health.consecutiveFailures) || 0;
7532
8119
  console.log(
7533
8120
  failures > 0 ? `Hooks: FAILING (${failures} consecutive; last: ${health.lastError} at ${health.lastFailureAt})` : `Hooks: healthy (last recovery ${health.lastOkAt || "unknown"})`
@@ -7544,7 +8131,7 @@ function withHooksFlag() {
7544
8131
  }
7545
8132
  function finishCodeInstall(apiKey, baseUrl, withHooks, agentId) {
7546
8133
  if (withHooks) {
7547
- const runner = installHookRunner(__filename, void 0, "0.18.0");
8134
+ const runner = installHookRunner(__filename, void 0, "0.19.0");
7548
8135
  console.log(`Installed pinned hook runner at ${runner}`);
7549
8136
  const result = installClaudeHooks("user");
7550
8137
  console.log(
@@ -7592,6 +8179,20 @@ USAGE:
7592
8179
  mindgraph-mcp install-hooks --harness claude-code|codex [--scope user|project]
7593
8180
  mindgraph-mcp uninstall-hooks --harness claude-code|codex [--scope user|project]
7594
8181
  mindgraph-mcp status Show installation status
8182
+ mindgraph-mcp skills pull [--dir .claude/skills]
8183
+ Write your org's published+granted skills as
8184
+ SKILL.md directories (allowlisted frontmatter
8185
+ only). Revoked/archived skills are withdrawn;
8186
+ local edits are never overwritten (.conflict
8187
+ sidecars). NOTE: coding harnesses hot-reload
8188
+ skill files \u2014 running sessions adopt pulled
8189
+ changes immediately; session pinning exists
8190
+ on the managed-agent leg only.
8191
+ mindgraph-mcp skills push <path>
8192
+ Import a SKILL.md directory as a review
8193
+ candidate (nothing auto-publishes). A
8194
+ previously pulled directory routes to the
8195
+ candidate-only update path.
7595
8196
 
7596
8197
  OPTIONS:
7597
8198
  --api-key <key> MindGraph API key (or set MINDGRAPH_API_KEY env var)
@@ -7655,7 +8256,7 @@ async function pollForKey(code) {
7655
8256
  } catch (e) {
7656
8257
  if (e instanceof Error && e.message.includes("Session")) throw e;
7657
8258
  }
7658
- await new Promise((resolve2) => setTimeout(resolve2, POLL_INTERVAL));
8259
+ await new Promise((resolve3) => setTimeout(resolve3, POLL_INTERVAL));
7659
8260
  }
7660
8261
  throw new Error("Timed out waiting for authorization.");
7661
8262
  }
@@ -7665,7 +8266,7 @@ async function interactiveInit(baseUrl) {
7665
8266
  input: process.stdin,
7666
8267
  output: process.stdout
7667
8268
  });
7668
- const ask = (q) => new Promise((resolve2) => rl.question(q, resolve2));
8269
+ const ask = (q) => new Promise((resolve3) => rl.question(q, resolve3));
7669
8270
  console.log("\nMindGraph MCP Server Setup\n");
7670
8271
  console.log(" 1. Sign in via browser (recommended)");
7671
8272
  console.log(" 2. Enter API key manually");
@@ -7736,8 +8337,79 @@ async function interactiveInit(baseUrl) {
7736
8337
  console.log("\nDone! Your knowledge graph is now connected.");
7737
8338
  }
7738
8339
  async function main2() {
7739
- const { command: command2, apiKey, baseUrl, scope, projectDir, harness, hooks, agentId } = parseArgs(process.argv);
8340
+ const {
8341
+ command: command2,
8342
+ apiKey,
8343
+ baseUrl,
8344
+ scope,
8345
+ projectDir,
8346
+ harness,
8347
+ hooks,
8348
+ agentId,
8349
+ positionals,
8350
+ dir
8351
+ } = parseArgs(process.argv);
7740
8352
  switch (command2) {
8353
+ case "skills": {
8354
+ if (!apiKey) {
8355
+ console.error(
8356
+ "API key required. Use --api-key or set MINDGRAPH_API_KEY."
8357
+ );
8358
+ process.exit(1);
8359
+ }
8360
+ const sub = positionals[0];
8361
+ const api = httpSkillsApi(
8362
+ baseUrl ?? "https://api.mindgraph.cloud",
8363
+ apiKey,
8364
+ agentId
8365
+ );
8366
+ if (sub === "pull") {
8367
+ if (!agentId) {
8368
+ console.error(
8369
+ "note: no --agent-id/MINDGRAPH_AGENT_ID \u2014 pulling ALL published skills; bind an agent identity to scope the set by its grants."
8370
+ );
8371
+ }
8372
+ const report = await skillsPull(api, dir ?? ".claude/skills", agentId);
8373
+ for (const name of report.written) console.log(` written ${name}`);
8374
+ for (const name of report.unchanged)
8375
+ console.log(` unchanged ${name}`);
8376
+ for (const name of report.locallyEdited)
8377
+ console.log(` local-edit ${name} \u2014 upstream unchanged; left alone`);
8378
+ for (const name of report.conflicts)
8379
+ console.log(
8380
+ ` CONFLICT ${name} \u2014 local edits kept; new upstream saved as SKILL.md.conflict`
8381
+ );
8382
+ for (const removal of report.removed)
8383
+ console.log(
8384
+ ` withdrawn ${removal.name}${removal.kept ? " (local edits kept as SKILL.md.conflict)" : ""}${removal.statusReason ? ` \u2014 ${removal.statusReason}` : ""}`
8385
+ );
8386
+ for (const refusal of report.refused)
8387
+ console.log(` REFUSED ${refusal.name} \u2014 ${refusal.reason}`);
8388
+ const total = report.written.length + report.unchanged.length + report.locallyEdited.length + report.conflicts.length;
8389
+ console.log(`
8390
+ ${total} skill(s) synced.`);
8391
+ } else if (sub === "push") {
8392
+ const target = positionals[1];
8393
+ if (!target) {
8394
+ console.error("Usage: mindgraph-mcp skills push <path>");
8395
+ process.exit(1);
8396
+ }
8397
+ const report = await skillsPush(api, target);
8398
+ console.log(report.message);
8399
+ if (report.existingUid) {
8400
+ console.log(
8401
+ ` existing skill: ${report.existingUid}${report.existingStatus ? ` (${report.existingStatus})` : ""}`
8402
+ );
8403
+ }
8404
+ if (report.outcome === "error" || report.outcome === "conflict") {
8405
+ process.exit(1);
8406
+ }
8407
+ } else {
8408
+ console.error("Usage: mindgraph-mcp skills pull|push [...]");
8409
+ process.exit(1);
8410
+ }
8411
+ break;
8412
+ }
7741
8413
  case "help":
7742
8414
  printUsage();
7743
8415
  break;
@@ -7774,7 +8446,7 @@ async function main2() {
7774
8446
  if (hookHarness !== "claude-code" && hookHarness !== "codex") {
7775
8447
  throw new Error("--harness must be claude-code or codex");
7776
8448
  }
7777
- const runner = installHookRunner(__filename, void 0, "0.18.0");
8449
+ const runner = installHookRunner(__filename, void 0, "0.19.0");
7778
8450
  console.log(`Installed pinned hook runner at ${runner}`);
7779
8451
  const result = hookHarness === "codex" ? installCodexHooks(scope, projectDir) : installClaudeHooks(scope, projectDir);
7780
8452
  console.log(