handoff-mcp-server 0.26.0 → 0.28.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 (72) 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/Cargo.lock +0 -686
  6. package/Cargo.toml +0 -30
  7. package/scripts/cargo-env.sh +0 -29
  8. package/scripts/handoff-memory-hook.py +0 -208
  9. package/scripts/install-local.sh +0 -109
  10. package/scripts/postinstall.js +0 -50
  11. package/scripts/sync-plugin-skills.sh +0 -35
  12. package/scripts/sync-plugin-version.sh +0 -85
  13. package/scripts/sync-workflow-inline.sh +0 -138
  14. package/src/cli.rs +0 -551
  15. package/src/context/injection.rs +0 -276
  16. package/src/context/mod.rs +0 -129
  17. package/src/lib.rs +0 -5
  18. package/src/main.rs +0 -157
  19. package/src/mcp/handlers/assignees.rs +0 -254
  20. package/src/mcp/handlers/auto_schedule.rs +0 -489
  21. package/src/mcp/handlers/bulk_update.rs +0 -155
  22. package/src/mcp/handlers/calendar.rs +0 -196
  23. package/src/mcp/handlers/capacity.rs +0 -318
  24. package/src/mcp/handlers/check_criterion.rs +0 -70
  25. package/src/mcp/handlers/config.rs +0 -402
  26. package/src/mcp/handlers/config_crud.rs +0 -183
  27. package/src/mcp/handlers/dashboard.rs +0 -214
  28. package/src/mcp/handlers/docs.rs +0 -2288
  29. package/src/mcp/handlers/docs_query.rs +0 -1335
  30. package/src/mcp/handlers/fork_session.rs +0 -91
  31. package/src/mcp/handlers/get_session.rs +0 -48
  32. package/src/mcp/handlers/get_task.rs +0 -53
  33. package/src/mcp/handlers/import_context.rs +0 -470
  34. package/src/mcp/handlers/init.rs +0 -28
  35. package/src/mcp/handlers/list_sessions.rs +0 -187
  36. package/src/mcp/handlers/list_tasks.rs +0 -308
  37. package/src/mcp/handlers/load_context.rs +0 -361
  38. package/src/mcp/handlers/log_time.rs +0 -67
  39. package/src/mcp/handlers/memory.rs +0 -961
  40. package/src/mcp/handlers/merge_sessions.rs +0 -103
  41. package/src/mcp/handlers/metrics.rs +0 -196
  42. package/src/mcp/handlers/milestones.rs +0 -102
  43. package/src/mcp/handlers/mod.rs +0 -140
  44. package/src/mcp/handlers/refer.rs +0 -307
  45. package/src/mcp/handlers/referrals.rs +0 -74
  46. package/src/mcp/handlers/save_context.rs +0 -354
  47. package/src/mcp/handlers/task_checklist.rs +0 -507
  48. package/src/mcp/handlers/timer.rs +0 -529
  49. package/src/mcp/handlers/update_session.rs +0 -197
  50. package/src/mcp/handlers/update_task.rs +0 -452
  51. package/src/mcp/mod.rs +0 -6
  52. package/src/mcp/protocol.rs +0 -41
  53. package/src/mcp/resources.rs +0 -57
  54. package/src/mcp/router.rs +0 -154
  55. package/src/mcp/tools.rs +0 -1522
  56. package/src/mcp/types.rs +0 -108
  57. package/src/setup.rs +0 -1212
  58. package/src/storage/config.rs +0 -578
  59. package/src/storage/docs/frontmatter.rs +0 -509
  60. package/src/storage/docs/mod.rs +0 -835
  61. package/src/storage/docs/model.rs +0 -708
  62. package/src/storage/docs/reassemble.rs +0 -167
  63. package/src/storage/docs/split.rs +0 -377
  64. package/src/storage/git.rs +0 -47
  65. package/src/storage/memory/injected.rs +0 -340
  66. package/src/storage/memory/mod.rs +0 -236
  67. package/src/storage/memory/model.rs +0 -127
  68. package/src/storage/mod.rs +0 -96
  69. package/src/storage/referrals.rs +0 -248
  70. package/src/storage/sessions.rs +0 -859
  71. package/src/storage/tasks.rs +0 -957
  72. 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.26.0",
3
+ "version": "0.28.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.28.0",
31
+ "handoff-mcp-server-darwin-x64": "0.28.0",
32
+ "handoff-mcp-server-linux-arm64": "0.28.0",
33
+ "handoff-mcp-server-linux-x64": "0.28.0",
34
+ "handoff-mcp-server-win32-arm64": "0.28.0",
35
+ "handoff-mcp-server-win32-x64": "0.28.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"