mnemos-capture 1.6.1 → 1.8.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/README.md +157 -51
- package/dist/cli/{kos.js → agent.js} +64 -25
- package/dist/cli/code-map.js +546 -0
- package/dist/cli/config.js +1 -1
- package/dist/cli/git-hook.js +170 -0
- package/dist/cli/hooks.js +78 -35
- package/dist/cli/index.js +118 -10
- package/dist/cli/mcp-server.js +45 -20
- package/dist/cli/session-start.js +73 -0
- package/package.json +3 -2
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { execFileSync } from "child_process";
|
|
2
|
+
import { callMcp, initializeMcp } from "./hooks.js";
|
|
3
|
+
import { repoFromRemote } from "./code-map.js";
|
|
4
|
+
/** The name this command's MCP session carries, so the server can tell where a session began. */
|
|
5
|
+
export const SESSION_START_CLIENT = "xkg-session-start";
|
|
6
|
+
/**
|
|
7
|
+
* How each agent's SessionStart hook reads added context. Agents not listed
|
|
8
|
+
* here get plain text, which Claude Code and Codex also accept.
|
|
9
|
+
*/
|
|
10
|
+
const FORMATS = {
|
|
11
|
+
claude: hookSpecificOutput,
|
|
12
|
+
codex: hookSpecificOutput,
|
|
13
|
+
gemini: hookSpecificOutput,
|
|
14
|
+
copilot: (text) => ({ additionalContext: text }),
|
|
15
|
+
cursor: (text) => ({ additional_context: text }),
|
|
16
|
+
};
|
|
17
|
+
function hookSpecificOutput(text) {
|
|
18
|
+
return { hookSpecificOutput: { hookEventName: "SessionStart", additionalContext: text } };
|
|
19
|
+
}
|
|
20
|
+
function message(count) {
|
|
21
|
+
const ask = count === 1
|
|
22
|
+
? "You have 1 new xkg capture. Want to see it and apply it here?"
|
|
23
|
+
: `You have ${count} new xkg captures. Want to see them and apply one here?`;
|
|
24
|
+
return (`xkg: ${count} new capture${count === 1 ? "" : "s"} in the user's inbox.\n` +
|
|
25
|
+
`Before starting the user's task, ask them: "${ask}"\n` +
|
|
26
|
+
"If they say yes, call the xkg `briefing` tool with this repo's name, branch and last few commit messages, " +
|
|
27
|
+
"then check each candidate against the code before suggesting where it applies.");
|
|
28
|
+
}
|
|
29
|
+
/** `owner/name` of the repo at `cwd` from its origin remote, or null outside a repo or without an origin. */
|
|
30
|
+
export function currentRepo(cwd = process.cwd()) {
|
|
31
|
+
try {
|
|
32
|
+
const url = execFileSync("git", ["remote", "get-url", "origin"], { cwd, encoding: "utf-8", timeout: 2000, stdio: ["ignore", "pipe", "ignore"] });
|
|
33
|
+
return repoFromRemote(url);
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
/** What the server's briefing starts with when captures wait for the repo. */
|
|
40
|
+
const OFFER_PREFIX = "xkg: ";
|
|
41
|
+
/**
|
|
42
|
+
* Runs from any agent's SessionStart hook. In a repo with captures waiting for
|
|
43
|
+
* it (HANDOFFS.md), prints the server's offer as is, so the hook and a briefing
|
|
44
|
+
* call give the same words. Otherwise counts new captures and, when there are
|
|
45
|
+
* some, prints the question the agent should ask. Either way shaped for that
|
|
46
|
+
* agent. Never fails or delays a session for long: on any error it prints nothing.
|
|
47
|
+
*/
|
|
48
|
+
export async function sessionStart(apiKey, opts = {}) {
|
|
49
|
+
// One limit for the whole command, so a hung server can't hold the session twice as long.
|
|
50
|
+
const deadline = Date.now() + (opts.timeoutMs ?? 3000);
|
|
51
|
+
const remaining = () => Math.max(1, deadline - Date.now());
|
|
52
|
+
try {
|
|
53
|
+
const session = await initializeMcp(apiKey, SESSION_START_CLIENT, remaining())
|
|
54
|
+
.then((res) => (res.ok ? res.headers.get("mcp-session-id") : null))
|
|
55
|
+
.catch(() => null);
|
|
56
|
+
const format = opts.agent ? FORMATS[opts.agent] : undefined;
|
|
57
|
+
const print = (body) => process.stdout.write((format ? JSON.stringify(format(body)) : body) + "\n");
|
|
58
|
+
const repo = opts.repo === undefined ? currentRepo() : opts.repo;
|
|
59
|
+
if (repo) {
|
|
60
|
+
const offer = await callMcp(apiKey, "briefing", { repo }, remaining(), session).catch(() => null);
|
|
61
|
+
if (offer?.startsWith(OFFER_PREFIX))
|
|
62
|
+
return void print(offer);
|
|
63
|
+
}
|
|
64
|
+
const text = await callMcp(apiKey, "list_inbox", {}, remaining(), session);
|
|
65
|
+
const count = parseInt(text?.match(/^(\d+)\s+capture/)?.[1] ?? "0", 10);
|
|
66
|
+
if (count <= 0)
|
|
67
|
+
return;
|
|
68
|
+
print(message(count));
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
// Silent fail: never interrupt a session start
|
|
72
|
+
}
|
|
73
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mnemos-capture",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.8.0",
|
|
4
4
|
"description": "xkg — Turn what you discover into context your AI workers can use. Capture once, then brief, plan, and execute with any MCP-compatible tool.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -16,7 +16,8 @@
|
|
|
16
16
|
"prepublishOnly": "npm run build:cli",
|
|
17
17
|
"test": "vitest run",
|
|
18
18
|
"test:watch": "vitest",
|
|
19
|
-
"test:e2e": "playwright test",
|
|
19
|
+
"test:e2e": "playwright test --project=network",
|
|
20
|
+
"test:ui": "playwright test --project=ui",
|
|
20
21
|
"lint": "eslint .",
|
|
21
22
|
"typecheck": "tsc --noEmit"
|
|
22
23
|
},
|