predicate-skill 1.2.0 → 1.6.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.
@@ -0,0 +1,30 @@
1
+ # Codex CLI adapter
2
+
3
+ ## Install MCP server
4
+
5
+ Merge `config.toml.template` into `~/.codex/config.toml`, replacing
6
+ `__PLUGIN_DIR__` with the absolute path to this package. The 8 `kg_*`
7
+ tools will be available the next time you launch `codex`.
8
+
9
+ ## Hooks
10
+
11
+ Codex CLI does not expose SessionStart, PreCompact, or Stop lifecycle
12
+ events as of writing. The three scripts in this directory are provided
13
+ so you can:
14
+
15
+ 1. Run `session-start.sh` manually and paste output into your initial
16
+ Codex prompt. Or alias:
17
+
18
+ ```sh
19
+ # in ~/.zshrc or ~/.bashrc
20
+ codex() { command codex --context "$(predicate sessionstart 2>/dev/null)" "$@"; }
21
+ ```
22
+
23
+ 2. Wire `pre-compact.sh` and `stop.sh` to cron for periodic maintenance:
24
+
25
+ ```cron
26
+ */30 * * * * /absolute/path/hooks/codex-cli/pre-compact.sh >/dev/null 2>&1
27
+ ```
28
+
29
+ If Codex CLI adds lifecycle hooks in the future, this adapter is ready
30
+ to wire them — script logic is unchanged.
@@ -0,0 +1,10 @@
1
+ # Merge into ~/.codex/config.toml — replace __PLUGIN_DIR__ with the
2
+ # absolute path to packages/predicate-skill.
3
+
4
+ [mcp_servers.predicate]
5
+ command = "node"
6
+ args = ["__PLUGIN_DIR__/server.bundle.mjs"]
7
+
8
+ [mcp_servers.predicate.env]
9
+ FUSEKI_URL = "http://localhost:3030"
10
+ PREDICATE_DATASET = "predicate"
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env bash
2
+ # Codex CLI pre-compact adapter: run via cron to keep the KG tidy.
3
+ set -euo pipefail
4
+ predicate maintain
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env bash
2
+ # Codex CLI session-start adapter. Codex has no native SessionStart event;
3
+ # run this manually before a session and paste the output as initial context,
4
+ # or alias it to `codex` in your shell rc.
5
+ set -euo pipefail
6
+ predicate sessionstart 2>/dev/null || \
7
+ echo "Predicate: Fuseki not reachable; run \`predicate up\` first."
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env bash
2
+ # Codex CLI stop adapter: run manually after long sessions.
3
+ set -euo pipefail
4
+ predicate maintain
@@ -0,0 +1,46 @@
1
+ # Cursor adapter
2
+
3
+ Predicate exposes its 8 `kg_*` tools to Cursor over MCP, plus three optional
4
+ maintenance scripts you can wire into cron.
5
+
6
+ ## 1. MCP server
7
+
8
+ Copy `mcp.json.template` to `.cursor/mcp.json` (project-local) or
9
+ `~/.cursor/mcp.json` (global), replacing `__PLUGIN_DIR__` with the absolute
10
+ path to your local clone, e.g. `/Users/you/code/predicate/packages/predicate-skill`.
11
+
12
+ Then in Cursor restart MCP (Cmd-Shift-P → "Reload MCP servers") and the 8
13
+ `kg_*` tools will be available.
14
+
15
+ ## 2. Optional: SessionStart context
16
+
17
+ Cursor has no native SessionStart event. Two options:
18
+
19
+ **a. Manual:** Run `bash session-start.sh` in your terminal; paste the
20
+ output into `.cursor/rules/predicate.md`.
21
+
22
+ **b. Cron:** Refresh the rule file periodically:
23
+
24
+ ```cron
25
+ */10 * * * * bash /absolute/path/hooks/cursor/session-start.sh > /project/.cursor/rules/predicate.md
26
+ ```
27
+
28
+ ## 3. Optional: PreCompact maintenance
29
+
30
+ Cursor has no native PreCompact event. Wire `pre-compact.sh` to cron so the
31
+ KG stays tidy between sessions:
32
+
33
+ ```cron
34
+ */30 * * * * /absolute/path/hooks/cursor/pre-compact.sh >/dev/null 2>&1
35
+ ```
36
+
37
+ ## 4. Optional: Stop maintenance
38
+
39
+ Run `bash stop.sh` manually after a long session, or wire it into a shell
40
+ shutdown alias.
41
+
42
+ ## Notes
43
+
44
+ All scripts require `predicate` on `$PATH`. Install with
45
+ `npm install -g predicate-skill`, or use the absolute path:
46
+ `/abs/path/to/predicate/packages/predicate-skill/cli.bundle.mjs`.
@@ -0,0 +1,12 @@
1
+ {
2
+ "mcpServers": {
3
+ "predicate": {
4
+ "command": "node",
5
+ "args": ["__PLUGIN_DIR__/server.bundle.mjs"],
6
+ "env": {
7
+ "FUSEKI_URL": "http://localhost:3030",
8
+ "PREDICATE_DATASET": "predicate"
9
+ }
10
+ }
11
+ }
12
+ }
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env bash
2
+ # Cursor pre-compact adapter: trims low-confidence stale facts and
3
+ # promotes any matured staged TBox proposals before context compaction.
4
+ # Cursor has no native PreCompact event — run manually or via cron, e.g.:
5
+ # */30 * * * * /path/to/hooks/cursor/pre-compact.sh
6
+ set -euo pipefail
7
+ predicate maintain
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env bash
2
+ # Cursor session-start adapter: emits a plain text status line.
3
+ # Cursor reads stdout when invoked from a custom rule script;
4
+ # can also be run manually and pasted into .cursor/rules/predicate.md.
5
+ set -euo pipefail
6
+ predicate sessionstart 2>/dev/null || \
7
+ echo "Predicate: Fuseki not reachable; run \`predicate up\` first."
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env bash
2
+ # Cursor session-end adapter: runs maintenance on session close.
3
+ # Cursor has no native Stop event — run manually after each session.
4
+ set -euo pipefail
5
+ predicate maintain
@@ -0,0 +1,29 @@
1
+ # Gemini CLI adapter
2
+
3
+ ## Install
4
+
5
+ Merge `settings.json.template` into `~/.gemini/settings.json`, replacing
6
+ `__PLUGIN_DIR__` with the absolute path to this package
7
+ (e.g. `/Users/you/code/predicate/packages/predicate-skill`).
8
+
9
+ Restart Gemini CLI. The 8 `kg_*` tools will be available; the three hook
10
+ scripts will fire on `sessionStart`, `preCompress`, and `stop`.
11
+
12
+ ## Hooks reference
13
+
14
+ | Event | Script | What it does |
15
+ |---|---|---|
16
+ | `sessionStart` | `session-start.sh` | Prints KG status line; Gemini reads stdout as context. |
17
+ | `preCompress` | `pre-compact.sh` | Runs `predicate maintain` before context compression. |
18
+ | `stop` | `stop.sh` | Runs `predicate maintain` on session close. |
19
+
20
+ ## If your Gemini version doesn't expose hooks
21
+
22
+ The `hooks` block is harmless if unsupported. You can still run each script
23
+ manually or via cron — see `../cursor/README.md` for cron examples; the
24
+ syntax is identical.
25
+
26
+ ## Verify wiring
27
+
28
+ Run `gemini --debug` and start a fresh session; you should see Predicate's
29
+ KG status line printed in the debug output before your first prompt.
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env bash
2
+ # Gemini CLI pre-compress adapter: runs maintenance before Gemini compacts
3
+ # the chat context. Wire to the `preCompress` event in settings.json.
4
+ set -euo pipefail
5
+ predicate maintain
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env bash
2
+ # Gemini CLI session-start adapter. Gemini reads stdout as additional context
3
+ # when wired via the `hooks` block in ~/.gemini/settings.json (event: "sessionStart").
4
+ set -euo pipefail
5
+ predicate sessionstart 2>/dev/null || \
6
+ echo "Predicate: Fuseki not reachable; run \`predicate up\` first."
@@ -0,0 +1,17 @@
1
+ {
2
+ "mcpServers": {
3
+ "predicate": {
4
+ "command": "node",
5
+ "args": ["__PLUGIN_DIR__/server.bundle.mjs"],
6
+ "env": {
7
+ "FUSEKI_URL": "http://localhost:3030",
8
+ "PREDICATE_DATASET": "predicate"
9
+ }
10
+ }
11
+ },
12
+ "hooks": [
13
+ { "event": "sessionStart", "command": "bash __PLUGIN_DIR__/hooks/gemini-cli/session-start.sh" },
14
+ { "event": "preCompress", "command": "bash __PLUGIN_DIR__/hooks/gemini-cli/pre-compact.sh" },
15
+ { "event": "stop", "command": "bash __PLUGIN_DIR__/hooks/gemini-cli/stop.sh" }
16
+ ]
17
+ }
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env bash
2
+ # Gemini CLI stop adapter: runs maintenance on session close.
3
+ # Wire to the `stop` event in settings.json.
4
+ set -euo pipefail
5
+ predicate maintain
package/hooks/hooks.json CHANGED
@@ -4,6 +4,21 @@
4
4
  "event": "SessionStart",
5
5
  "matcher": "startup|clear|compact",
6
6
  "command": "bash ${PLUGIN_DIR}/hooks/session-start.sh"
7
+ },
8
+ {
9
+ "event": "PreToolUse",
10
+ "matcher": "*",
11
+ "command": "bash ${PLUGIN_DIR}/hooks/pre-tool-use.sh"
12
+ },
13
+ {
14
+ "event": "PostToolUse",
15
+ "matcher": "*",
16
+ "command": "bash ${PLUGIN_DIR}/hooks/post-tool-use.sh"
17
+ },
18
+ {
19
+ "event": "Stop",
20
+ "matcher": "*",
21
+ "command": "bash ${PLUGIN_DIR}/hooks/stop.sh"
7
22
  }
8
23
  ]
9
24
  }
@@ -0,0 +1,31 @@
1
+ # OpenCode adapter
2
+
3
+ ## Install
4
+
5
+ Merge `opencode.json.template` into `~/.config/opencode/opencode.json`
6
+ (or your project-local `opencode.json`), replacing `__PLUGIN_DIR__` with
7
+ the absolute path to this package.
8
+
9
+ Restart OpenCode. The 8 `kg_*` tools will be available, and the three
10
+ hook scripts will fire on `session.started`, `session.compacted`, and
11
+ `session.stopped`.
12
+
13
+ ## Hooks reference
14
+
15
+ | Event | Script | What it does |
16
+ |---|---|---|
17
+ | `session.started` | `session-start.sh` | Prints KG status line; OpenCode reads stdout as context. |
18
+ | `session.compacted` | `pre-compact.sh` | Runs `predicate maintain` before context compression. |
19
+ | `session.stopped` | `stop.sh` | Runs `predicate maintain` on session close. |
20
+
21
+ ## Verify wiring
22
+
23
+ Start an OpenCode session and check the debug log; you should see
24
+ Predicate's KG status line in the initial context, and `predicate maintain`
25
+ output when the session compacts or stops.
26
+
27
+ ## If event names changed in your OpenCode version
28
+
29
+ Consult `opencode --help events` (or the OpenCode docs) for the current
30
+ event names. The scripts are event-agnostic — only the template's `on:`
31
+ keys need to match.
@@ -0,0 +1,18 @@
1
+ {
2
+ "$schema": "https://opencode.ai/config.json",
3
+ "mcp": {
4
+ "predicate": {
5
+ "type": "local",
6
+ "command": ["node", "__PLUGIN_DIR__/server.bundle.mjs"],
7
+ "environment": {
8
+ "FUSEKI_URL": "http://localhost:3030",
9
+ "PREDICATE_DATASET": "predicate"
10
+ }
11
+ }
12
+ },
13
+ "events": [
14
+ { "on": "session.started", "run": "bash __PLUGIN_DIR__/hooks/opencode/session-start.sh" },
15
+ { "on": "session.compacted", "run": "bash __PLUGIN_DIR__/hooks/opencode/pre-compact.sh" },
16
+ { "on": "session.stopped", "run": "bash __PLUGIN_DIR__/hooks/opencode/stop.sh" }
17
+ ]
18
+ }
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env bash
2
+ # OpenCode pre-compact adapter. Wire to the session.compacted event
3
+ # (fires immediately before OpenCode compresses chat history).
4
+ set -euo pipefail
5
+ predicate maintain
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env bash
2
+ # OpenCode session-start adapter. OpenCode reads stdout as additional context
3
+ # when wired to the session.started event.
4
+ set -euo pipefail
5
+ predicate sessionstart 2>/dev/null || \
6
+ echo "Predicate: Fuseki not reachable; run \`predicate up\` first."
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env bash
2
+ # OpenCode stop adapter. Wire to the session.stopped event.
3
+ set -euo pipefail
4
+ predicate maintain
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env bash
2
+ # Claude Code PostToolUse hook: records {toolName, input, output, sessionId,
3
+ # phase:"post"} in kg:usage. Reads Claude Code's hook payload JSON from stdin
4
+ # and delegates to `predicate capture --from-stdin`. Fails open.
5
+ set -uo pipefail
6
+
7
+ if command -v predicate >/dev/null 2>&1; then
8
+ predicate capture --from-stdin --phase post >/dev/null 2>&1 || true
9
+ fi
10
+ exit 0
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env bash
2
+ # Claude Code PreToolUse hook: records {toolName, input, sessionId, phase:"pre"}
3
+ # in kg:usage. Reads Claude Code's hook payload JSON from stdin and delegates
4
+ # to `predicate capture --from-stdin`. Fails open: any error returns exit 0
5
+ # so the user's tool invocation is never blocked by capture logic.
6
+ set -uo pipefail
7
+
8
+ if command -v predicate >/dev/null 2>&1; then
9
+ predicate capture --from-stdin --phase pre >/dev/null 2>&1 || true
10
+ fi
11
+ exit 0
@@ -1,25 +1,13 @@
1
1
  #!/usr/bin/env bash
2
- # SessionStart hook: emits a short context block telling the agent
3
- # how many open goals and active concepts Predicate is tracking.
2
+ # SessionStart hook for Claude Code: emits a short context block telling
3
+ # the agent what's in the KG. Delegates to `predicate sessionstart` so the
4
+ # message format stays in one place.
4
5
  set -euo pipefail
5
- FUSEKI="${FUSEKI_URL:-http://localhost:3030}"
6
- DS="${PREDICATE_DATASET:-predicate}"
7
6
 
8
- if ! curl -fsS "$FUSEKI/$/ping" >/dev/null 2>&1; then
9
- jq -n '{ additional_context: "Predicate: Fuseki not reachable; KG tools may fail. Start it with `pnpm fuseki:up`." }'
10
- exit 0
7
+ if MSG="$(predicate sessionstart 2>/dev/null)"; then
8
+ :
9
+ else
10
+ MSG="Predicate: Fuseki not reachable; KG tools may fail. Start it with \`predicate up\`."
11
11
  fi
12
12
 
13
- GOALS=$(curl -fsS "$FUSEKI/$DS/query" \
14
- --data-urlencode "query=PREFIX pred: <https://predicate.dev/meta#>
15
- SELECT (COUNT(*) AS ?n) WHERE { GRAPH <kg:goals> { ?g pred:status \"active\" } }" \
16
- --header "Accept: application/sparql-results+json" \
17
- | jq -r '.results.bindings[0].n.value // "0"')
18
-
19
- CONCEPTS=$(curl -fsS "$FUSEKI/$DS/query" \
20
- --data-urlencode "query=SELECT (COUNT(DISTINCT ?c) AS ?n) WHERE { GRAPH <kg:tbox> { ?c a <http://www.w3.org/2002/07/owl#Class> } }" \
21
- --header "Accept: application/sparql-results+json" \
22
- | jq -r '.results.bindings[0].n.value // "0"')
23
-
24
- MSG="Predicate ready: ${GOALS} active goals, ${CONCEPTS} TBox classes. Use kg_explore_schema before drafting SPARQL."
25
13
  jq -n --arg m "$MSG" '{ additional_context: $m }'
package/hooks/stop.sh ADDED
@@ -0,0 +1,20 @@
1
+ #!/usr/bin/env bash
2
+ # Claude Code Stop hook: reads the Stop-hook JSON payload from stdin,
3
+ # runs structured turn extraction (predicate extract), then a
4
+ # maintenance sweep. Fail-open: any error returns exit 0 so capture
5
+ # never blocks the user's next prompt.
6
+ set -uo pipefail
7
+
8
+ if ! command -v predicate >/dev/null 2>&1; then
9
+ exit 0
10
+ fi
11
+
12
+ # Buffer stdin so we can tee it into extract.
13
+ payload="$(cat || true)"
14
+
15
+ if [ -n "$payload" ]; then
16
+ printf '%s' "$payload" | predicate extract --from-stdin >/dev/null 2>&1 || true
17
+ fi
18
+
19
+ predicate maintain >/dev/null 2>&1 || true
20
+ exit 0
@@ -0,0 +1,43 @@
1
+ # VS Code Copilot adapter
2
+
3
+ ## Install MCP server
4
+
5
+ Merge `settings.json.template` into your VS Code `settings.json`
6
+ (User or Workspace), replacing `__PLUGIN_DIR__` with the absolute path
7
+ to this package. Restart VS Code. The 8 `kg_*` tools will be available
8
+ to Copilot Chat.
9
+
10
+ ## Hooks
11
+
12
+ VS Code Copilot does not expose SessionStart, PreCompact, or Stop
13
+ lifecycle events as of writing. The three scripts in this directory
14
+ are provided so you can:
15
+
16
+ 1. Run `session-start.sh` manually before opening Copilot Chat and
17
+ paste the output into a prompt as initial context.
18
+
19
+ 2. Wire `pre-compact.sh` and `stop.sh` to cron for periodic KG
20
+ maintenance — see `../cursor/README.md` for cron examples.
21
+
22
+ 3. Use them in VS Code tasks (`.vscode/tasks.json`):
23
+
24
+ ```json
25
+ {
26
+ "version": "2.0.0",
27
+ "tasks": [
28
+ {
29
+ "label": "predicate: session start",
30
+ "type": "shell",
31
+ "command": "bash ${workspaceFolder}/packages/predicate-skill/hooks/vscode-copilot/session-start.sh"
32
+ },
33
+ {
34
+ "label": "predicate: maintain",
35
+ "type": "shell",
36
+ "command": "bash ${workspaceFolder}/packages/predicate-skill/hooks/vscode-copilot/pre-compact.sh"
37
+ }
38
+ ]
39
+ }
40
+ ```
41
+
42
+ If VS Code adds lifecycle hooks for Copilot Chat in the future, this
43
+ adapter is ready to wire them — script logic is unchanged.
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env bash
2
+ # VS Code Copilot pre-compact adapter. Run via cron or a VS Code task.
3
+ set -euo pipefail
4
+ predicate maintain
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env bash
2
+ # VS Code Copilot session-start adapter. VS Code has no native SessionStart
3
+ # hook today — run this manually before invoking Copilot Chat, or wire it
4
+ # to a VS Code task in tasks.json.
5
+ set -euo pipefail
6
+ predicate sessionstart 2>/dev/null || \
7
+ echo "Predicate: Fuseki not reachable; run \`predicate up\` first."
@@ -0,0 +1,12 @@
1
+ {
2
+ "github.copilot.chat.mcp.servers": {
3
+ "predicate": {
4
+ "command": "node",
5
+ "args": ["__PLUGIN_DIR__/server.bundle.mjs"],
6
+ "env": {
7
+ "FUSEKI_URL": "http://localhost:3030",
8
+ "PREDICATE_DATASET": "predicate"
9
+ }
10
+ }
11
+ }
12
+ }
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env bash
2
+ # VS Code Copilot stop adapter. Run manually after a long chat session
3
+ # or wire to a VS Code task that runs on workspace close.
4
+ set -euo pipefail
5
+ predicate maintain
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "predicate-skill",
3
- "version": "1.2.0",
3
+ "version": "1.6.0",
4
4
  "description": "Local reasoning knowledge graph (RDF/OWL) for AI agents — Claude Code plugin + MCP server + predicate CLI.",
5
5
  "author": {
6
6
  "name": "Nordic Agents Research",
package/server.bundle.mjs CHANGED
@@ -3643,7 +3643,7 @@ var require_fast_uri = __commonJS({
3643
3643
  normalizeString(uri, options);
3644
3644
  } else if (typeof uri === "object") {
3645
3645
  uri = /** @type {T} */
3646
- parse3(serialize(uri, options), options);
3646
+ parse3(serialize2(uri, options), options);
3647
3647
  }
3648
3648
  return uri;
3649
3649
  }
@@ -3651,13 +3651,13 @@ var require_fast_uri = __commonJS({
3651
3651
  const schemelessOptions = options ? Object.assign({ scheme: "null" }, options) : { scheme: "null" };
3652
3652
  const resolved = resolveComponent(parse3(baseURI, schemelessOptions), parse3(relativeURI, schemelessOptions), schemelessOptions, true);
3653
3653
  schemelessOptions.skipEscape = true;
3654
- return serialize(resolved, schemelessOptions);
3654
+ return serialize2(resolved, schemelessOptions);
3655
3655
  }
3656
3656
  function resolveComponent(base, relative, options, skipNormalization) {
3657
3657
  const target = {};
3658
3658
  if (!skipNormalization) {
3659
- base = parse3(serialize(base, options), options);
3660
- relative = parse3(serialize(relative, options), options);
3659
+ base = parse3(serialize2(base, options), options);
3660
+ relative = parse3(serialize2(relative, options), options);
3661
3661
  }
3662
3662
  options = options || {};
3663
3663
  if (!options.tolerant && relative.scheme) {
@@ -3711,7 +3711,7 @@ var require_fast_uri = __commonJS({
3711
3711
  const normalizedB = normalizeComparableURI(uriB, options);
3712
3712
  return normalizedA !== void 0 && normalizedB !== void 0 && normalizedA.toLowerCase() === normalizedB.toLowerCase();
3713
3713
  }
3714
- function serialize(cmpts, opts) {
3714
+ function serialize2(cmpts, opts) {
3715
3715
  const component = {
3716
3716
  host: cmpts.host,
3717
3717
  scheme: cmpts.scheme,
@@ -3889,7 +3889,7 @@ var require_fast_uri = __commonJS({
3889
3889
  function normalizeStringWithStatus(uri, opts) {
3890
3890
  const { parsed, malformedAuthorityOrPort } = parseWithStatus(uri, opts);
3891
3891
  return {
3892
- normalized: malformedAuthorityOrPort ? uri : serialize(parsed, opts),
3892
+ normalized: malformedAuthorityOrPort ? uri : serialize2(parsed, opts),
3893
3893
  malformedAuthorityOrPort
3894
3894
  };
3895
3895
  }
@@ -3899,7 +3899,7 @@ var require_fast_uri = __commonJS({
3899
3899
  return malformedAuthorityOrPort ? void 0 : normalized;
3900
3900
  }
3901
3901
  if (typeof uri === "object") {
3902
- return serialize(uri, opts);
3902
+ return serialize2(uri, opts);
3903
3903
  }
3904
3904
  }
3905
3905
  var fastUri = {
@@ -3908,7 +3908,7 @@ var require_fast_uri = __commonJS({
3908
3908
  resolve: resolve2,
3909
3909
  resolveComponent,
3910
3910
  equal,
3911
- serialize,
3911
+ serialize: serialize2,
3912
3912
  parse: parse3
3913
3913
  };
3914
3914
  module.exports = fastUri;
@@ -30990,9 +30990,9 @@ async function kgAsk(client, input) {
30990
30990
  async function logUsage(client, question, sparql, rowCount, elapsedMs) {
30991
30991
  const usage = escapeIRI(GRAPH.usage);
30992
30992
  const id = `urn:predicate:usage:${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
30993
- const META6 = "https://predicate.dev/meta#";
30993
+ const META7 = "https://predicate.dev/meta#";
30994
30994
  await client.update(`
30995
- PREFIX pred: <${META6}>
30995
+ PREFIX pred: <${META7}>
30996
30996
  PREFIX xsd: <http://www.w3.org/2001/XMLSchema#>
30997
30997
  INSERT DATA { GRAPH ${usage} {
30998
30998
  <${id}> a pred:Query ;
@@ -37832,6 +37832,51 @@ async function kgStats(client) {
37832
37832
  };
37833
37833
  }
37834
37834
 
37835
+ // ../predicate-mcp/src/tools/kg-capture.ts
37836
+ var META6 = "https://predicate.dev/meta#";
37837
+ function truncate(s, max) {
37838
+ if (s.length <= max) return s;
37839
+ const extra = s.length - max;
37840
+ return `${s.slice(0, max)} \u2026 [truncated, ${extra} more chars]`;
37841
+ }
37842
+ function serialize(value, max) {
37843
+ let s;
37844
+ if (value === void 0 || value === null) s = "";
37845
+ else if (typeof value === "string") s = value;
37846
+ else {
37847
+ try {
37848
+ s = JSON.stringify(value);
37849
+ } catch {
37850
+ s = String(value);
37851
+ }
37852
+ }
37853
+ return truncate(s, max);
37854
+ }
37855
+ async function kgCapture(client, input) {
37856
+ const t0 = Date.now();
37857
+ const maxChars = parseInt(process.env["PREDICATE_CAPTURE_TRUNCATE"] ?? "500", 10);
37858
+ const captureId = `urn:predicate:capture:${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
37859
+ const inputStr = serialize(input.input, maxChars);
37860
+ const hasOutput = input.output !== void 0 && input.output !== null;
37861
+ const outputStr = hasOutput ? serialize(input.output, maxChars) : "";
37862
+ const lines = [
37863
+ `${escapeIRI(captureId)} a <${META6}ToolCall> ;`,
37864
+ ` <${META6}toolName> ${escapeLiteral(input.toolName)} ;`,
37865
+ ` <${META6}phase> ${escapeLiteral(input.phase)} ;`,
37866
+ ` <${META6}at> "${(/* @__PURE__ */ new Date()).toISOString()}"^^<http://www.w3.org/2001/XMLSchema#dateTime>`
37867
+ ];
37868
+ if (inputStr.length > 0) lines.push(` ; <${META6}toolInput> ${escapeLiteral(inputStr)}`);
37869
+ if (hasOutput) lines.push(` ; <${META6}toolOutput> ${escapeLiteral(outputStr)}`);
37870
+ if (input.sessionId) lines.push(` ; <${META6}sessionId> ${escapeLiteral(input.sessionId)}`);
37871
+ lines.push(" .");
37872
+ await client.update(`
37873
+ INSERT DATA { GRAPH ${escapeIRI(GRAPH.usage)} {
37874
+ ${lines.join("\n ")}
37875
+ } }
37876
+ `);
37877
+ return { captureId, elapsedMs: Date.now() - t0 };
37878
+ }
37879
+
37835
37880
  // ../predicate-mcp/src/tools/registry.ts
37836
37881
  var deltaQuadSchema = external_exports.object({
37837
37882
  s: external_exports.string(),
@@ -37996,6 +38041,27 @@ function buildTools(client) {
37996
38041
  inputSchema: external_exports.object({}),
37997
38042
  handler: async () => kgStats(client)
37998
38043
  },
38044
+ {
38045
+ name: "kg_capture",
38046
+ description: "Record a tool invocation (toolName, input, output, sessionId, phase) into kg:usage. Used by per-platform PreToolUse/PostToolUse hooks; safe to call directly. Returns {captureId, elapsedMs}.",
38047
+ inputSchema: external_exports.object({
38048
+ toolName: external_exports.string().min(1),
38049
+ input: external_exports.unknown().optional(),
38050
+ output: external_exports.unknown().optional(),
38051
+ sessionId: external_exports.string().optional(),
38052
+ phase: external_exports.enum(["pre", "post"])
38053
+ }),
38054
+ handler: async (raw) => {
38055
+ const args = external_exports.object({
38056
+ toolName: external_exports.string().min(1),
38057
+ input: external_exports.unknown().optional(),
38058
+ output: external_exports.unknown().optional(),
38059
+ sessionId: external_exports.string().optional(),
38060
+ phase: external_exports.enum(["pre", "post"])
38061
+ }).parse(raw);
38062
+ return kgCapture(client, args);
38063
+ }
38064
+ },
37999
38065
  ...stubs()
38000
38066
  ];
38001
38067
  }