@massa-ai/cursor-plugin 1.53.0 → 1.54.1

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.
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "name": "massa-ai",
3
- "version": "1.53.0",
3
+ "version": "1.54.1",
4
4
  "description": "massa-ai — semantic code search, memory, and context compression for Cursor"
5
5
  }
package/install.sh CHANGED
@@ -11,15 +11,38 @@
11
11
  # MCP registration is delegated to scripts/install-agents.sh, the single writer
12
12
  # of host MCP config. This installer no longer ships a plugin-local mcp.json.
13
13
  #
14
- # Cursor 3.14 also bridges the Claude marketplace registry from ~/.claude and
15
- # loads any plugin listed there IN ADDITION to a local install — a machine
16
- # with both would load massa-ai twice and fire every hook twice (AD-017). At
17
- # USER scope, this installer prefers the bridge: when ~/.claude lists massa-ai
18
- # as installed and enabled, it skips its own local plugin copy and hook
19
- # wiring (removing a pre-existing local copy so one run converges), while
20
- # still writing the flat subagents, harness skills, and MCP registration this
21
- # installer always owns. Project-scope installs are unaffected — ~/.claude is
22
- # a user surface, never a project one.
14
+ # LOCAL IS THE DEFAULT ROUTE. The Claude bridge is opt-in (--prefer-bridge).
15
+ #
16
+ # Cursor can bridge the Claude marketplace registry from ~/.claude observed
17
+ # live in Cursor 3.14's "Cursor Plugins" exthost log on 2026-08-05
18
+ # ("loadClaudePlugin massa-ai@massa-ai"). This installer used to PREFER that
19
+ # bridge at user scope: on detection it deleted its own plugin directory and
20
+ # stripped its hook wiring, on the premise that the bridge loads the whole
21
+ # plugin.
22
+ #
23
+ # Measured on Cursor 3.16.17 (2026-08-17), with massa-ai listed and
24
+ # `enabledPlugins` true in ~/.claude, that premise is at best half true:
25
+ #
26
+ # hooks → fire in Cursor
27
+ # plugin → absent from the Cursor UI
28
+ # commands → absent (all 46 workflow commands)
29
+ #
30
+ # So bridge-preference silently withheld a working local install, and the user
31
+ # lost every workflow command with nothing replacing them. Detection reads
32
+ # ~/.claude, which is a fact about CLAUDE's files — never proof that Cursor
33
+ # loaded anything from them — and nothing here can observe the difference.
34
+ #
35
+ # The default therefore installs everything locally. --prefer-bridge (or
36
+ # MASSA_AI_CURSOR_PREFER_BRIDGE=1) restores the old behaviour for anyone whose
37
+ # bridge does deliver hooks.
38
+ #
39
+ # AD-017 TRADE-OFF, STATED PLAINLY: if the bridge IS delivering hooks and you
40
+ # take the default, both sources are live and every hook fires TWICE. The
41
+ # install warns when it detects that combination. Choose --prefer-bridge, or
42
+ # disable massa-ai in ~/.claude/settings.json, to keep exactly one source.
43
+ #
44
+ # Project-scope installs never consult the bridge — ~/.claude is a user
45
+ # surface, never a project one.
23
46
  #
24
47
  # Idempotent: re-running is a no-op when owned entries already present.
25
48
  # Uninstall removes only ownership-marked entries + the plugin directory,
@@ -29,6 +52,13 @@
29
52
  # apps/cursor-plugin/install.sh # install at user scope (~/.cursor)
30
53
  # apps/cursor-plugin/install.sh --user # (same)
31
54
  # apps/cursor-plugin/install.sh --project # install at project scope (./.cursor)
55
+ # apps/cursor-plugin/install.sh --prefer-bridge # leave hook wiring to the
56
+ # # Claude bridge instead of
57
+ # # installing it locally; use when
58
+ # # bridge-delivered hooks DO fire
59
+ # # in your Cursor, to avoid every
60
+ # # hook firing twice. Same as
61
+ # # MASSA_AI_CURSOR_PREFER_BRIDGE=1
32
62
  # apps/cursor-plugin/install.sh --uninstall # remove owned entries + plugin dir
33
63
  # apps/cursor-plugin/install.sh -h|--help # show this help
34
64
 
@@ -41,11 +71,26 @@ CLAUDE_PLUGIN_BIN="$REPO_ROOT/apps/claude-plugin/hooks/massa-ai-hook.ts"
41
71
  SCOPE="user"
42
72
  UNINSTALL=0
43
73
  DRY_RUN=0
74
+ # Local is the DEFAULT route; the Claude bridge is opt-in.
75
+ #
76
+ # Bridge detection reads ~/.claude — a fact about CLAUDE's files — and infers
77
+ # from it that Cursor is loading massa-ai. That inference cannot be verified
78
+ # from here, and it was measured wrong on Cursor 3.16.17: the plugin and its
79
+ # 46 workflow commands were absent from the Cursor UI while ~/.claude said the
80
+ # plugin was installed and enabled. Preferring the bridge by default therefore
81
+ # meant silently withholding a working local install on the strength of a
82
+ # claim about a different product.
83
+ #
84
+ # So the default installs everything locally, and --prefer-bridge (or
85
+ # MASSA_AI_CURSOR_PREFER_BRIDGE=1) opts into leaving hook wiring to the
86
+ # bridge. See the AD-017 double-fire note where PREFER_BRIDGE is consumed.
87
+ PREFER_BRIDGE="${MASSA_AI_CURSOR_PREFER_BRIDGE:-0}"
44
88
 
45
89
  for arg in "$@"; do
46
90
  case "$arg" in
47
91
  --user) SCOPE="user" ;;
48
92
  --project) SCOPE="project" ;;
93
+ --prefer-bridge) PREFER_BRIDGE=1 ;;
49
94
  --uninstall) UNINSTALL=1 ;;
50
95
  --quiet) MASSA_AI_VERBOSE=0 ;;
51
96
  --verbose) MASSA_AI_VERBOSE=1 ;;
@@ -249,14 +294,37 @@ if (typeof cfg.hooks !== "object" || cfg.hooks === null) cfg.hooks = {};
249
294
 
250
295
  const hooks = cfg.hooks;
251
296
 
297
+ // Ownership test — marker first, command shape as the fallback. The marker is
298
+ // precise but not the only way our entries reach this file: an entry written by
299
+ // a release before the marker existed, or copied by hand from documentation,
300
+ // carries none, and was invisible to BOTH directions here — never deduped on
301
+ // install, never removed on uninstall. Claude's copy of this merge had exactly
302
+ // that gap and it double-fired every lifecycle event (measured live
303
+ // 2026-08-17). Referencing the massa-ai-hook binary is unambiguous; a user hook
304
+ // that merely mentions massa-ai elsewhere does not match.
305
+ const OWNED_COMMAND = /massa-ai-hook/;
306
+
307
+ function entryIsOwned(e) {
308
+ if (!e || typeof e !== "object") return false;
309
+ if (e._massaAiOwned === true) return true;
310
+ return typeof e.command === "string" && OWNED_COMMAND.test(e.command);
311
+ }
312
+
252
313
  function hasOwned(arr) {
253
- return Array.isArray(arr) && arr.some((e) => e && e._massaAiOwned === true);
314
+ return Array.isArray(arr) && arr.some(entryIsOwned);
254
315
  }
255
316
 
256
317
  if (mode === "uninstall") {
257
- for (const [evt] of EVENTS) {
318
+ // Back up before removing: this now deletes unmarked entries it did not
319
+ // necessarily write, so the install path's backup discipline applies here too.
320
+ if (existed) {
321
+ fs.copyFileSync(file, `${file}.massa-ai.bak-${ts}`);
322
+ }
323
+ // Every event, not just the ones this release writes — the predicate
324
+ // identifies our commands rather than a location.
325
+ for (const evt of Object.keys(hooks)) {
258
326
  if (Array.isArray(hooks[evt])) {
259
- hooks[evt] = hooks[evt].filter((e) => !(e && e._massaAiOwned === true));
327
+ hooks[evt] = hooks[evt].filter((e) => !entryIsOwned(e));
260
328
  if (hooks[evt].length === 0) delete hooks[evt];
261
329
  }
262
330
  }
@@ -536,86 +604,110 @@ fi
536
604
  # ── Install ──────────────────────────────────────────────────────────────────
537
605
  # PAU-08/09: bridge-preferred, local-fallback. Computed once, used to decide
538
606
  # both which artifacts this run writes and what installRoute gets recorded.
607
+ # Local by default. The probe still runs when the bridge is NOT opted into,
608
+ # purely so the AD-017 double-fire warning below can be accurate — its result
609
+ # no longer decides what gets installed.
539
610
  IS_BRIDGE=0
611
+ BRIDGE_AVAILABLE=0
540
612
  if claude_bridge_detected; then
541
- IS_BRIDGE=1
613
+ BRIDGE_AVAILABLE=1
614
+ fi
615
+ if [[ "$PREFER_BRIDGE" == "1" ]]; then
616
+ if [[ "$BRIDGE_AVAILABLE" == "1" ]]; then
617
+ IS_BRIDGE=1
618
+ vecho "--prefer-bridge: leaving hook wiring to the Claude bridge."
619
+ else
620
+ # Opting into a bridge that is not there would leave the user with no hooks
621
+ # at all, which is strictly worse than the default. Fall back loudly.
622
+ echo " ⚠ --prefer-bridge requested, but ~/.claude does not list massa-ai as installed and enabled." >&2
623
+ echo " Falling back to a local hook install so hooks are not silently absent." >&2
624
+ fi
542
625
  fi
543
626
 
544
627
  skill_count=0
545
628
  specialist_count=0
546
629
 
547
630
  if [[ "$IS_BRIDGE" -eq 1 ]]; then
548
- vecho "Installing massa-ai Cursor plugin (Claude-bridge route): ~/.claude already lists massa-ai as installed and enabled, so Cursor loads it via the bridge."
549
- # Converge (PAU-08, spec edge case): a pre-existing local install (current
550
- # or pre-fix legacy location) must not double-load alongside the bridge, so
551
- # one run here ends with exactly one load path. Owned hook entries are
552
- # stripped with the SAME filter merge_hooks_json's "uninstall" mode already
553
- # uses — the bridge delivers hooks instead.
554
- if [[ -d "$LEGACY_PLUGIN_DIR" ]]; then
555
- rm -rf "$LEGACY_PLUGIN_DIR"
556
- echo -e " - removed pre-fix plugin copy at $LEGACY_PLUGIN_DIR"
557
- fi
558
- if [[ -d "$PLUGIN_DIR" ]]; then
559
- rm -rf "$PLUGIN_DIR"
560
- echo -e " - removed local plugin copy at $PLUGIN_DIR (Claude bridge already loads massa-ai)"
561
- fi
631
+ vecho "Claude-bridge route: ~/.claude lists massa-ai as installed and enabled, so Cursor delivers its HOOKS via the bridge. The plugin bundle is still installed locally — see below."
632
+ # Hook wiring, and only hook wiring, is what the bridge actually delivers.
633
+ # Owned entries are stripped with the SAME filter merge_hooks_json's
634
+ # "uninstall" mode uses, so the bridge's copy is the only one that fires.
562
635
  if [[ -f "$HOOKS_JSON" ]]; then
563
636
  merge_hooks_json "$HOOKS_JSON" "uninstall"
564
637
  echo -e " - removed massa-ai hook entries from $HOOKS_JSON (the bridge delivers hooks instead)"
565
638
  fi
566
- else
567
- vecho "Installing massa-ai Cursor plugin to: $PLUGIN_DIR"
568
- # Migration: a pre-fix install left a copy at plugins/massa-ai. Leaving it in
569
- # place risks Cursor discovering both and firing every hook twice, so it goes
570
- # before the new location is written.
571
- if [[ -d "$LEGACY_PLUGIN_DIR" ]]; then
572
- rm -rf "$LEGACY_PLUGIN_DIR"
573
- echo -e " - removed pre-fix plugin copy at $LEGACY_PLUGIN_DIR"
574
- fi
575
- mkdir -p "$PLUGIN_DIR/.cursor-plugin" "$PLUGIN_DIR/skills" "$PLUGIN_DIR/hooks"
576
- # Migration: pre-fix installs bundled agents inside the plugin dir, which
577
- # Cursor never reads. Drop that copy so upgraders converge on the one real
578
- # discovery surface and a future Cursor plugin scan cannot double-register.
579
- if [[ -d "$PLUGIN_DIR/agents" ]]; then
580
- rm -rf "$PLUGIN_DIR/agents"
581
- echo -e " - removed pre-fix agents copy at $PLUGIN_DIR/agents (Cursor reads $CURSOR_AGENTS_DIR)"
582
- fi
639
+ fi
583
640
 
584
- # Copy manifest
585
- cp "$SCRIPT_DIR/.cursor-plugin/plugin.json" "$PLUGIN_DIR/.cursor-plugin/plugin.json"
586
- vecho " + .cursor-plugin/plugin.json"
587
-
588
- # Copy the host-command skills (each in a subdirectory: skills/<name>/SKILL.md),
589
- # quick + generated workflow commands alike. massa-ai/, persona-router/,
590
- # agents/, and profile/ are the PDO-06 harness bundle, not a Cursor command
591
- # skill they are installed separately, into the shared harness skills
592
- # directory (see "Skills bundling" below), not into this plugin-cache
593
- # skills/ tree. `profile` was missing from this exclusion pre-fix, which
594
- # leaked it into the command-skill cache mislabeled as `/profile`.
595
- for src in "$SCRIPT_DIR/skills/"*/SKILL.md; do
596
- name="$(basename "$(dirname "$src")")"
597
- case "$name" in
598
- massa-ai|persona-router|agents|profile) continue ;;
599
- esac
600
- mkdir -p "$PLUGIN_DIR/skills/$name"
601
- cp "$src" "$PLUGIN_DIR/skills/$name/SKILL.md"
602
- vecho " + skills/$name/SKILL.md"
603
- skill_count=$((skill_count + 1))
604
- done
641
+ # The plugin bundle (manifest + workflow-command skills) installs on BOTH
642
+ # routes. It used to be skipped whenever the bridge was detected, on the
643
+ # premise that the bridge loads the whole plugin.
644
+ #
645
+ # Measured against Cursor 3.16.17 on 2026-08-17, that premise is half true.
646
+ # With the bridge active massa-ai listed and `enabledPlugins` true in
647
+ # ~/.claude hooks DO fire in Cursor, while the plugin and its 46 workflow
648
+ # commands are absent from the UI entirely. The old branch therefore deleted a
649
+ # working local bundle and nothing replaced it: the user kept the hooks and
650
+ # silently lost every workflow command. The header's "Cursor 3.14 also bridges
651
+ # the Claude marketplace registry" was pinned against a 2026-08-05 capture and
652
+ # was never re-verified against a later Cursor.
653
+ #
654
+ # So the two deliveries are gated independently: hooks by $IS_BRIDGE above,
655
+ # the bundle never. Reinstating the bundle on the bridge route cannot
656
+ # double-fire hooks — .cursor-plugin/plugin.json declares only
657
+ # name/version/description, so the bundle carries no hook wiring of its own;
658
+ # $HOOKS_JSON is the only surface that does, and on the bridge route it is
659
+ # left stripped.
660
+ vecho "Installing the massa-ai Cursor plugin bundle to: $PLUGIN_DIR"
661
+ # Migration: a pre-fix install left a copy at plugins/massa-ai. Leaving it in
662
+ # place risks Cursor discovering both and firing every hook twice, so it goes
663
+ # before the new location is written. Runs on both routes.
664
+ if [[ -d "$LEGACY_PLUGIN_DIR" ]]; then
665
+ rm -rf "$LEGACY_PLUGIN_DIR"
666
+ echo -e " - removed pre-fix plugin copy at $LEGACY_PLUGIN_DIR"
667
+ fi
668
+ mkdir -p "$PLUGIN_DIR/.cursor-plugin" "$PLUGIN_DIR/skills" "$PLUGIN_DIR/hooks"
669
+ # Migration: pre-fix installs bundled agents inside the plugin dir, which
670
+ # Cursor never reads. Drop that copy so upgraders converge on the one real
671
+ # discovery surface and a future Cursor plugin scan cannot double-register.
672
+ if [[ -d "$PLUGIN_DIR/agents" ]]; then
673
+ rm -rf "$PLUGIN_DIR/agents"
674
+ echo -e " - removed pre-fix agents copy at $PLUGIN_DIR/agents (Cursor reads $CURSOR_AGENTS_DIR)"
675
+ fi
605
676
 
606
- # Copy hooks.json (the placeholder version — installer replaces paths)
607
- cp "$SCRIPT_DIR/hooks/hooks.json" "$PLUGIN_DIR/hooks/hooks.json"
608
- vecho " + hooks/hooks.json"
609
-
610
- # Older installs shipped a plugin-local mcp.json here. Cursor reads
611
- # ~/.cursor/mcp.json, not the plugin dir, and MCP is now owned by
612
- # scripts/install-agents.sh drop the residue so upgraders converge.
613
- if [[ -f "$PLUGIN_DIR/mcp.json" ]]; then
614
- rm -f "$PLUGIN_DIR/mcp.json"
615
- # Not gated by --quiet: this deletes a file in the user's home, so it is a
616
- # mutation notice rather than per-file chatter.
617
- echo -e " - removed stale mcp.json (MCP is now registered in ~/.cursor/mcp.json)"
618
- fi
677
+ # Copy manifest
678
+ cp "$SCRIPT_DIR/.cursor-plugin/plugin.json" "$PLUGIN_DIR/.cursor-plugin/plugin.json"
679
+ vecho " + .cursor-plugin/plugin.json"
680
+
681
+ # Copy the host-command skills (each in a subdirectory: skills/<name>/SKILL.md),
682
+ # quick + generated workflow commands alike. massa-ai/, persona-router/,
683
+ # agents/, and profile/ are the PDO-06 harness bundle, not a Cursor command
684
+ # skill they are installed separately, into the shared harness skills
685
+ # directory (see "Skills bundling" below), not into this plugin-cache
686
+ # skills/ tree. `profile` was missing from this exclusion pre-fix, which
687
+ # leaked it into the command-skill cache mislabeled as `/profile`.
688
+ for src in "$SCRIPT_DIR/skills/"*/SKILL.md; do
689
+ name="$(basename "$(dirname "$src")")"
690
+ case "$name" in
691
+ massa-ai|persona-router|agents|profile) continue ;;
692
+ esac
693
+ mkdir -p "$PLUGIN_DIR/skills/$name"
694
+ cp "$src" "$PLUGIN_DIR/skills/$name/SKILL.md"
695
+ vecho " + skills/$name/SKILL.md"
696
+ skill_count=$((skill_count + 1))
697
+ done
698
+
699
+ # Copy hooks.json (the placeholder version — installer replaces paths)
700
+ cp "$SCRIPT_DIR/hooks/hooks.json" "$PLUGIN_DIR/hooks/hooks.json"
701
+ vecho " + hooks/hooks.json"
702
+
703
+ # Older installs shipped a plugin-local mcp.json here. Cursor reads
704
+ # ~/.cursor/mcp.json, not the plugin dir, and MCP is now owned by
705
+ # scripts/install-agents.sh — drop the residue so upgraders converge.
706
+ if [[ -f "$PLUGIN_DIR/mcp.json" ]]; then
707
+ rm -f "$PLUGIN_DIR/mcp.json"
708
+ # Not gated by --quiet: this deletes a file in the user's home, so it is a
709
+ # mutation notice rather than per-file chatter.
710
+ echo -e " - removed stale mcp.json (MCP is now registered in ~/.cursor/mcp.json)"
619
711
  fi
620
712
 
621
713
  # Flat agents and harness skills are written in BOTH branches (PAU-08/09):
@@ -708,9 +800,20 @@ fi
708
800
  # Summary line in quiet mode
709
801
  if [ "${MASSA_AI_VERBOSE:-0}" != "1" ]; then
710
802
  if [[ "$IS_BRIDGE" -eq 1 ]]; then
711
- ok "cursor plugin: using the Claude bridge (${specialist_count} specialists) — hooks delivered by the bridge, not installed locally"
803
+ ok "cursor plugin installed (${skill_count} skills, ${specialist_count} specialists) — hooks left to the Claude bridge (--prefer-bridge)"
804
+ ok " if massa-ai hooks do NOT fire in Cursor, the bridge is not delivering them: re-run without --prefer-bridge"
712
805
  else
713
806
  ok "cursor plugin installed (${skill_count} skills, ${specialist_count} specialists, 7 hooks)"
807
+ if [[ "$BRIDGE_AVAILABLE" -eq 1 ]]; then
808
+ # AD-017: both sources live. Not an error — the default is local by
809
+ # design — but the user must be told, because a doubled hook is silent
810
+ # and looks like the tool misbehaving rather than being installed twice.
811
+ echo " ⚠ ~/.claude also lists massa-ai as installed and enabled." >&2
812
+ echo " If Cursor's Claude bridge delivers those hooks, they now fire TWICE" >&2
813
+ echo " (once from the bridge, once from ~/.cursor/hooks.json)." >&2
814
+ echo " Keep one source: re-run with --prefer-bridge, or disable massa-ai" >&2
815
+ echo " in ~/.claude/settings.json (enabledPlugins)." >&2
816
+ fi
714
817
  fi
715
818
  else
716
819
  vecho ""
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@massa-ai/cursor-plugin",
3
- "version": "1.53.0",
3
+ "version": "1.54.1",
4
4
  "description": "massa-ai plugin for Cursor — semantic code search, memory, and context compression",
5
5
  "files": [
6
6
  "agents",