handoff-mcp-server 0.25.1 → 0.27.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.
Files changed (78) hide show
  1. package/README.md +31 -4
  2. package/bin/handoff-mcp.js +56 -13
  3. package/bin/resolve-binary.js +122 -0
  4. package/package.json +14 -9
  5. package/skills/handoff/agents/openai.yaml +12 -0
  6. package/skills/handoff-docs/agents/openai.yaml +12 -0
  7. package/skills/handoff-import/agents/openai.yaml +12 -0
  8. package/skills/handoff-load/agents/openai.yaml +12 -0
  9. package/skills/handoff-memory/agents/openai.yaml +12 -0
  10. package/skills/handoff-refer/agents/openai.yaml +12 -0
  11. package/Cargo.lock +0 -686
  12. package/Cargo.toml +0 -29
  13. package/scripts/cargo-env.sh +0 -29
  14. package/scripts/handoff-memory-hook.py +0 -208
  15. package/scripts/install-local.sh +0 -109
  16. package/scripts/postinstall.js +0 -50
  17. package/scripts/sync-plugin-skills.sh +0 -35
  18. package/scripts/sync-plugin-version.sh +0 -85
  19. package/scripts/sync-workflow-inline.sh +0 -138
  20. package/src/cli.rs +0 -551
  21. package/src/context/injection.rs +0 -276
  22. package/src/context/mod.rs +0 -129
  23. package/src/lib.rs +0 -5
  24. package/src/main.rs +0 -157
  25. package/src/mcp/handlers/assignees.rs +0 -254
  26. package/src/mcp/handlers/auto_schedule.rs +0 -489
  27. package/src/mcp/handlers/bulk_update.rs +0 -155
  28. package/src/mcp/handlers/calendar.rs +0 -196
  29. package/src/mcp/handlers/capacity.rs +0 -318
  30. package/src/mcp/handlers/check_criterion.rs +0 -70
  31. package/src/mcp/handlers/config.rs +0 -402
  32. package/src/mcp/handlers/config_crud.rs +0 -183
  33. package/src/mcp/handlers/dashboard.rs +0 -214
  34. package/src/mcp/handlers/docs.rs +0 -2288
  35. package/src/mcp/handlers/docs_query.rs +0 -1335
  36. package/src/mcp/handlers/fork_session.rs +0 -91
  37. package/src/mcp/handlers/get_session.rs +0 -48
  38. package/src/mcp/handlers/get_task.rs +0 -53
  39. package/src/mcp/handlers/import_context.rs +0 -470
  40. package/src/mcp/handlers/init.rs +0 -28
  41. package/src/mcp/handlers/list_sessions.rs +0 -187
  42. package/src/mcp/handlers/list_tasks.rs +0 -308
  43. package/src/mcp/handlers/load_context.rs +0 -361
  44. package/src/mcp/handlers/log_time.rs +0 -67
  45. package/src/mcp/handlers/memory.rs +0 -961
  46. package/src/mcp/handlers/merge_sessions.rs +0 -103
  47. package/src/mcp/handlers/metrics.rs +0 -196
  48. package/src/mcp/handlers/milestones.rs +0 -102
  49. package/src/mcp/handlers/mod.rs +0 -140
  50. package/src/mcp/handlers/refer.rs +0 -307
  51. package/src/mcp/handlers/referrals.rs +0 -74
  52. package/src/mcp/handlers/save_context.rs +0 -354
  53. package/src/mcp/handlers/task_checklist.rs +0 -507
  54. package/src/mcp/handlers/timer.rs +0 -529
  55. package/src/mcp/handlers/update_session.rs +0 -197
  56. package/src/mcp/handlers/update_task.rs +0 -452
  57. package/src/mcp/mod.rs +0 -6
  58. package/src/mcp/protocol.rs +0 -41
  59. package/src/mcp/resources.rs +0 -57
  60. package/src/mcp/router.rs +0 -154
  61. package/src/mcp/tools.rs +0 -1522
  62. package/src/mcp/types.rs +0 -108
  63. package/src/setup.rs +0 -1212
  64. package/src/storage/config.rs +0 -578
  65. package/src/storage/docs/frontmatter.rs +0 -509
  66. package/src/storage/docs/mod.rs +0 -835
  67. package/src/storage/docs/model.rs +0 -708
  68. package/src/storage/docs/reassemble.rs +0 -167
  69. package/src/storage/docs/split.rs +0 -377
  70. package/src/storage/git.rs +0 -47
  71. package/src/storage/memory/injected.rs +0 -340
  72. package/src/storage/memory/mod.rs +0 -236
  73. package/src/storage/memory/model.rs +0 -127
  74. package/src/storage/mod.rs +0 -96
  75. package/src/storage/referrals.rs +0 -248
  76. package/src/storage/sessions.rs +0 -859
  77. package/src/storage/tasks.rs +0 -957
  78. package/templates/claude-md-section.md +0 -12
package/README.md CHANGED
@@ -61,6 +61,32 @@ to pick up the change mid-session (a Claude Code restart also applies it).
61
61
  > is the *marketplace* name (the `name` field in `.claude-plugin/marketplace.json`).
62
62
  > Install commands always use `<plugin>@<marketplace>`.
63
63
 
64
+ #### Platform support
65
+
66
+ The npm package ships **prebuilt binaries** — no Rust toolchain, no compiler,
67
+ and no install scripts are needed. npm downloads only the binary matching your
68
+ platform.
69
+
70
+ | Platform | x64 | arm64 |
71
+ |---|---|---|
72
+ | Linux (glibc 2.35+) | ✅ | ✅ |
73
+ | macOS | ✅ | ✅ |
74
+ | Windows | ✅ | ✅ |
75
+
76
+ WSL works as a plain Linux install. For anything else — musl/Alpine, FreeBSD,
77
+ 32-bit, or glibc older than 2.35 — install with `cargo install handoff-mcp`
78
+ instead, which builds from source.
79
+
80
+ If you already have a binary you built yourself, point the npm wrapper at it
81
+ with `HANDOFF_MCP_BINARY_PATH=/path/to/handoff-mcp`.
82
+
83
+ > **Note**: installing with `--omit=optional` skips the prebuilt binary and
84
+ > leaves the CLI unable to start. Reinstall without that flag.
85
+ >
86
+ > **Alpine / musl**: the Linux binaries are glibc-linked. npm may install one
87
+ > anyway, and it fails to exec with a confusing "not found" error even though
88
+ > the file is present. Use `cargo install handoff-mcp` on musl systems.
89
+
64
90
  **Optional: task loop (automated TDD + research workflows)**
65
91
 
66
92
  ```bash
@@ -263,10 +289,11 @@ cargo install handoff-mcp
263
289
  npm install -g handoff-mcp-server
264
290
  ```
265
291
 
266
- Both install the same binary. `cargo install` fetches the crate from
267
- [crates.io](https://crates.io/crates/handoff-mcp) and compiles it directly;
268
- `npm install` downloads the source and runs `cargo build --release` via
269
- postinstall, so either way you need a Rust toolchain.
292
+ Both install the same binary, by different routes. `cargo install` fetches the
293
+ crate from [crates.io](https://crates.io/crates/handoff-mcp) and compiles it,
294
+ so it needs a Rust toolchain and works on any target Rust supports.
295
+ `npm install` downloads a prebuilt binary for your platform and compiles
296
+ nothing — see [Platform support](#platform-support) for the list.
270
297
 
271
298
  ### Build from source
272
299
 
@@ -1,22 +1,65 @@
1
1
  #!/usr/bin/env node
2
2
  "use strict";
3
3
 
4
- const { execFileSync } = require("child_process");
5
- const path = require("path");
6
- const fs = require("fs");
4
+ // Thin wrapper around the prebuilt binary shipped by the per-platform
5
+ // optionalDependencies. Deliberately does no work at install time: npm v12
6
+ // disables install scripts by default, so anything that needs to happen must
7
+ // happen here, at run time.
7
8
 
8
- const binary = path.join(__dirname, "handoff-mcp-bin");
9
+ const { spawn } = require("child_process");
10
+ const { resolveBinary } = require("./resolve-binary.js");
9
11
 
10
- if (!fs.existsSync(binary)) {
11
- console.error(
12
- "handoff-mcp binary not found. Try reinstalling: npm install -g handoff-mcp-server"
13
- );
12
+ const result = resolveBinary();
13
+
14
+ if (!result.ok) {
15
+ console.error(result.message);
14
16
  process.exit(1);
15
17
  }
16
18
 
17
- try {
18
- execFileSync(binary, process.argv.slice(2), { stdio: "inherit" });
19
- } catch (e) {
20
- if (e.status != null) process.exit(e.status);
21
- process.exit(1);
19
+ // Async spawn, not spawnSync. spawnSync blocks the event loop for the entire
20
+ // life of the child, which means no signal handler can ever run: an MCP client
21
+ // that stops the server signals the wrapper, the wrapper dies instantly, and
22
+ // the real server is reparented to init — a leaked process per session, still
23
+ // holding .handoff/ file handles. Async spawn keeps the wrapper alive to
24
+ // forward the signal.
25
+ //
26
+ // stdio "inherit" is required, not incidental: this is an MCP stdio server, so
27
+ // the child must own the real stdin/stdout and the wrapper must not sit in the
28
+ // middle of the JSON-RPC stream.
29
+ const child = spawn(result.binary, process.argv.slice(2), { stdio: "inherit" });
30
+
31
+ // Forwarded rather than handled: the binary owns shutdown (flushing state to
32
+ // .handoff/), so the wrapper's only job is to relay the request and wait.
33
+ // Registering a handler also stops Node from applying its default terminate
34
+ // behavior, which is what kept the wrapper from dying before the child.
35
+ const FORWARDED = ["SIGINT", "SIGTERM", "SIGHUP", "SIGQUIT"];
36
+ for (const sig of FORWARDED) {
37
+ process.on(sig, () => {
38
+ if (!child.killed) child.kill(sig);
39
+ });
22
40
  }
41
+
42
+ child.on("error", (err) => {
43
+ const hint =
44
+ result.source === "env"
45
+ ? `HANDOFF_MCP_BINARY_PATH points at "${result.binary}", which could not be executed.`
46
+ : `The prebuilt binary at "${result.binary}" could not be executed.` +
47
+ "\nIf this is a musl-based system (Alpine), the published binaries are" +
48
+ "\nglibc-linked and will not run; build from source with" +
49
+ "\n`cargo install handoff-mcp` and set HANDOFF_MCP_BINARY_PATH.";
50
+ console.error(`handoff-mcp: ${err.message}\n\n${hint}`);
51
+ process.exit(1);
52
+ });
53
+
54
+ child.on("close", (code, signal) => {
55
+ // Re-raising the signal is what makes `kill` behave as though the wrapper
56
+ // were not there: the parent shell sees the process die from the signal
57
+ // rather than from a plain exit code. The default handler must be restored
58
+ // first, or the forwarding handler above would swallow it.
59
+ if (signal) {
60
+ process.removeAllListeners(signal);
61
+ process.kill(process.pid, signal);
62
+ return;
63
+ }
64
+ process.exit(code == null ? 1 : code);
65
+ });
@@ -0,0 +1,122 @@
1
+ "use strict";
2
+
3
+ // Resolution logic for the prebuilt binary shipped by the per-platform
4
+ // optionalDependencies (handoff-mcp-server-<platform>-<arch>).
5
+ //
6
+ // Kept separate from handoff-mcp.js so the mapping and the error messages can
7
+ // be unit-tested without spawning a real binary.
8
+
9
+ const PACKAGE_NAME = "handoff-mcp-server";
10
+
11
+ // Platform/arch pairs we publish a prebuilt binary for. Keys are
12
+ // `${process.platform}-${process.arch}`; see .github/workflows/release.yml for
13
+ // the matching build matrix.
14
+ const SUPPORTED = {
15
+ "linux-x64": "linux-x64",
16
+ "linux-arm64": "linux-arm64",
17
+ "darwin-x64": "darwin-x64",
18
+ "darwin-arm64": "darwin-arm64",
19
+ "win32-x64": "win32-x64",
20
+ "win32-arm64": "win32-arm64",
21
+ };
22
+
23
+ /** Name of the platform package for a given platform/arch, or null if unsupported. */
24
+ function platformPackage(platform, arch) {
25
+ const suffix = SUPPORTED[`${platform}-${arch}`];
26
+ return suffix ? `${PACKAGE_NAME}-${suffix}` : null;
27
+ }
28
+
29
+ /** Path of the binary *inside* a platform package. Windows needs the .exe suffix. */
30
+ function binaryEntry(platform) {
31
+ return platform === "win32" ? "handoff-mcp.exe" : "handoff-mcp";
32
+ }
33
+
34
+ /**
35
+ * Locate the handoff-mcp binary.
36
+ *
37
+ * Order:
38
+ * 1. HANDOFF_MCP_BINARY_PATH override (esbuild's ESBUILD_BINARY_PATH
39
+ * equivalent) — lets users point at a self-built binary on platforms we
40
+ * do not publish, and lets tests run without installing anything.
41
+ * 2. require.resolve() into the platform package. Never path arithmetic:
42
+ * npm hoisting and pnpm's symlinked store both move node_modules around,
43
+ * and require.resolve is the only thing that follows those layouts.
44
+ *
45
+ * @param {object} [opts]
46
+ * @param {string} [opts.platform] defaults to process.platform
47
+ * @param {string} [opts.arch] defaults to process.arch
48
+ * @param {object} [opts.env] defaults to process.env
49
+ * @param {(id: string) => string} [opts.resolve] defaults to require.resolve
50
+ * @returns {{ ok: true, binary: string, source: "env" | "package" }
51
+ * | { ok: false, message: string }}
52
+ */
53
+ function resolveBinary(opts = {}) {
54
+ const platform = opts.platform || process.platform;
55
+ const arch = opts.arch || process.arch;
56
+ const env = opts.env || process.env;
57
+ const resolve = opts.resolve || require.resolve;
58
+
59
+ const override = env.HANDOFF_MCP_BINARY_PATH;
60
+ if (override) {
61
+ // Trusted as-is: an explicit override that does not exist should surface
62
+ // as a spawn error naming the path the user asked for, not as a fallback
63
+ // to some other binary they did not ask for.
64
+ return { ok: true, binary: override, source: "env" };
65
+ }
66
+
67
+ const pkg = platformPackage(platform, arch);
68
+ if (!pkg) {
69
+ return {
70
+ ok: false,
71
+ message: unsupportedMessage(platform, arch),
72
+ };
73
+ }
74
+
75
+ try {
76
+ return {
77
+ ok: true,
78
+ binary: resolve(`${pkg}/bin/${binaryEntry(platform)}`),
79
+ source: "package",
80
+ };
81
+ } catch (e) {
82
+ return { ok: false, message: missingPackageMessage(pkg, e) };
83
+ }
84
+ }
85
+
86
+ function unsupportedMessage(platform, arch) {
87
+ return [
88
+ `handoff-mcp: no prebuilt binary for ${platform}-${arch}.`,
89
+ "",
90
+ `Prebuilt binaries are published for: ${Object.keys(SUPPORTED).join(", ")}.`,
91
+ "",
92
+ "To run on this platform, build from source and point the wrapper at it:",
93
+ " cargo install handoff-mcp",
94
+ " export HANDOFF_MCP_BINARY_PATH=$(command -v handoff-mcp)",
95
+ ].join("\n");
96
+ }
97
+
98
+ function missingPackageMessage(pkg, cause) {
99
+ return [
100
+ `handoff-mcp: the platform package "${pkg}" is not installed.`,
101
+ "",
102
+ "This usually means one of:",
103
+ " - installed with --omit=optional / --no-optional (the prebuilt binary",
104
+ " ships as an optional dependency; reinstall without that flag)",
105
+ " - a lockfile generated on a different platform was installed with",
106
+ " `npm ci` (see https://github.com/npm/cli/issues/8320); delete",
107
+ " node_modules and package-lock.json, then reinstall",
108
+ "",
109
+ `Reinstall with: npm install -g ${PACKAGE_NAME}`,
110
+ `Or set HANDOFF_MCP_BINARY_PATH to a binary you built yourself.`,
111
+ "",
112
+ `(resolution error: ${cause && cause.message ? cause.message : cause})`,
113
+ ].join("\n");
114
+ }
115
+
116
+ module.exports = {
117
+ PACKAGE_NAME,
118
+ SUPPORTED,
119
+ platformPackage,
120
+ binaryEntry,
121
+ resolveBinary,
122
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "handoff-mcp-server",
3
- "version": "0.25.1",
3
+ "version": "0.27.0",
4
4
  "description": "MCP server that gives AI coding agents persistent memory across sessions",
5
5
  "license": "MIT",
6
6
  "author": "AlphaElements <66808803+alphaelements@users.noreply.github.com>",
@@ -21,25 +21,30 @@
21
21
  "handoff-mcp": "./bin/handoff-mcp.js"
22
22
  },
23
23
  "scripts": {
24
- "postinstall": "node scripts/postinstall.js",
25
- "test:js": "node --test \"plugin-task-loop/workflows/lib/*.test.js\"",
24
+ "test:js": "node --test \"bin/*.test.js\" \"plugin-task-loop/workflows/lib/*.test.js\"",
26
25
  "sync:workflow": "./scripts/sync-workflow-inline.sh",
27
26
  "check:workflow": "./scripts/sync-workflow-inline.sh --check"
28
27
  },
28
+ "//optionalDependencies": "Prebuilt binaries, one per platform. Pinned to an exact version (never ^ or ~): a wrapper must never pair with a platform package built from different sources. npm skips the entries whose os/cpu do not match instead of failing, which is what makes this work.",
29
+ "optionalDependencies": {
30
+ "handoff-mcp-server-darwin-arm64": "0.27.0",
31
+ "handoff-mcp-server-darwin-x64": "0.27.0",
32
+ "handoff-mcp-server-linux-arm64": "0.27.0",
33
+ "handoff-mcp-server-linux-x64": "0.27.0",
34
+ "handoff-mcp-server-win32-arm64": "0.27.0",
35
+ "handoff-mcp-server-win32-x64": "0.27.0"
36
+ },
29
37
  "files": [
30
38
  "bin/handoff-mcp.js",
31
- "scripts/",
39
+ "bin/resolve-binary.js",
32
40
  "skills/",
33
- "src/",
34
- "templates/",
35
- "Cargo.toml",
36
- "Cargo.lock",
37
41
  "LICENSE",
38
42
  "README.md"
39
43
  ],
40
44
  "os": [
41
45
  "linux",
42
- "darwin"
46
+ "darwin",
47
+ "win32"
43
48
  ],
44
49
  "engines": {
45
50
  "node": ">=16"
@@ -0,0 +1,12 @@
1
+ interface:
2
+ display_name: "Handoff"
3
+ short_description: "Load and save session context, track tasks, and manage project state across sessions"
4
+ default_prompt: "Use $handoff to run the session handoff workflow"
5
+
6
+ dependencies:
7
+ tools:
8
+ - type: "mcp"
9
+ value: "handoff"
10
+ description: "Handoff MCP server for session management"
11
+ transport: "stdio"
12
+ command: "handoff-mcp"
@@ -0,0 +1,12 @@
1
+ interface:
2
+ display_name: "Handoff Docs"
3
+ short_description: "Save, read, search, and traverse structured project documents (specs, designs, ADRs)"
4
+ default_prompt: "Use $handoff-docs to manage project documents"
5
+
6
+ dependencies:
7
+ tools:
8
+ - type: "mcp"
9
+ value: "handoff"
10
+ description: "Handoff MCP server for session management"
11
+ transport: "stdio"
12
+ command: "handoff-mcp"
@@ -0,0 +1,12 @@
1
+ interface:
2
+ display_name: "Handoff Import"
3
+ short_description: "Import existing documents into structured .handoff/ task and session management"
4
+ default_prompt: "Use $handoff-import to import a document into handoff"
5
+
6
+ dependencies:
7
+ tools:
8
+ - type: "mcp"
9
+ value: "handoff"
10
+ description: "Handoff MCP server for session management"
11
+ transport: "stdio"
12
+ command: "handoff-mcp"
@@ -0,0 +1,12 @@
1
+ interface:
2
+ display_name: "Handoff Load"
3
+ short_description: "Load the previous session's handoff context and show what to work on next"
4
+ default_prompt: "Use $handoff-load to load the handoff context for this project"
5
+
6
+ dependencies:
7
+ tools:
8
+ - type: "mcp"
9
+ value: "handoff"
10
+ description: "Handoff MCP server for session management"
11
+ transport: "stdio"
12
+ command: "handoff-mcp"
@@ -0,0 +1,12 @@
1
+ interface:
2
+ display_name: "Handoff Memory"
3
+ short_description: "Save, query, merge, and clean up durable project knowledge across sessions"
4
+ default_prompt: "Use $handoff-memory to query project memories for relevant knowledge"
5
+
6
+ dependencies:
7
+ tools:
8
+ - type: "mcp"
9
+ value: "handoff"
10
+ description: "Handoff MCP server for session management"
11
+ transport: "stdio"
12
+ command: "handoff-mcp"
@@ -0,0 +1,12 @@
1
+ interface:
2
+ display_name: "Handoff Refer"
3
+ short_description: "Send a cross-project referral to another project's handoff store"
4
+ default_prompt: "Use $handoff-refer to send a referral to another project"
5
+
6
+ dependencies:
7
+ tools:
8
+ - type: "mcp"
9
+ value: "handoff"
10
+ description: "Handoff MCP server for session management"
11
+ transport: "stdio"
12
+ command: "handoff-mcp"