@massa-ai/cursor-plugin 1.29.0 → 1.30.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.
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "name": "massa-ai",
3
- "version": "1.29.0",
3
+ "version": "1.30.0",
4
4
  "description": "massa-ai — semantic code search, memory, and context compression for Cursor"
5
5
  }
package/README.md CHANGED
@@ -62,7 +62,7 @@ If you are building a VS Code extension for Cursor, you can register the plugin
62
62
  vscode.cursor.plugins.registerPath("/abs/path/to/apps/cursor-plugin");
63
63
  ```
64
64
 
65
- Cursor auto-discovers `skills/`, `hooks/hooks.json`, and `agents/` inside the registered directory. MCP is not bundled — Cursor reads `~/.cursor/mcp.json`, which `scripts/install-agents.sh` owns. The `.cursor-plugin/plugin.json` manifest is optional — Cursor discovers the subdirectories without it, but including one aids marketplace submission later.
65
+ Cursor 3.14 loads user-local plugins from `~/.cursor/plugins/local/<name>/` and also bridges Claude marketplace plugins from `~/.claude`, but subagents are discovered only from the flat `~/.cursor/agents/*.md` (or project `.cursor/agents/`) directory — never from a plugin's `agents/` subtree. `install.sh` therefore copies the specialists to `~/.cursor/agents/`; `hooks/hooks.json` entries are merged into `~/.cursor/hooks.json` pointing at the installed hook binary. Cursor reads no global rules file: the AGENTS.md bootstrap must be pasted into Cursor Settings → Rules or provided per project root. MCP is not bundled — Cursor reads `~/.cursor/mcp.json`, which `scripts/install-agents.sh` owns. The `.cursor-plugin/plugin.json` manifest is optional — Cursor discovers the subdirectories without it, but including one aids marketplace submission later.
66
66
 
67
67
  Use `unregisterPath` to remove:
68
68
 
package/install.sh CHANGED
@@ -11,8 +11,19 @@
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.
23
+ #
14
24
  # Idempotent: re-running is a no-op when owned entries already present.
15
- # Uninstall removes only ownership-marked entries + the plugin directory.
25
+ # Uninstall removes only ownership-marked entries + the plugin directory,
26
+ # regardless of which route (bridge or local) installed them.
16
27
  #
17
28
  # Usage:
18
29
  # apps/cursor-plugin/install.sh # install at user scope (~/.cursor)
@@ -92,20 +103,80 @@ if [[ "$SCOPE" == "project" ]]; then
92
103
  else
93
104
  CURSOR_DIR="$HOME/.cursor"
94
105
  fi
95
- # Cursor discovers locally-installed plugins under plugins/local/<name>/, not
96
- # plugins/<name>/. Installing to the latter is why massa-ai never appeared in
97
- # Cursor's plugin list even though every file was written correctly.
98
- #
99
- # UNVERIFIED against a running Cursor.app this path comes from Cursor's
100
- # plugin documentation, not from an observed load, because Cursor is not
101
- # installed on the machine this was developed on. Treat it as lower confidence
102
- # than the Claude/Codex routes, which were verified end-to-end.
106
+ # Two verified Cursor 3.14 load surfaces (observed live in "Cursor Plugins"
107
+ # exthost logs, 2026-08-05): user-local plugins ARE loaded from
108
+ # plugins/local/<name>/ ("loadUserLocalPlugin massa-ai loaded"), and Cursor
109
+ # additionally bridges Claude marketplace plugins from ~/.claude
110
+ # ("loadClaudePlugin massa-ai@massa-ai") so a machine with the Claude
111
+ # plugin installed loads massa-ai twice. Subagents, however, are discovered
112
+ # only from the flat .cursor/agents/*.md directory (cursor.com/docs/subagents
113
+ # no subdirectories), and there is no global rules file. So: PLUGIN_DIR
114
+ # carries the manifest, hook binary, and command skills; everything else
115
+ # Cursor must SEE goes to its dedicated read paths — $CURSOR_AGENTS_DIR
116
+ # (subagents), $CURSOR_DIR/skills/ (harness skills), $CURSOR_DIR/hooks.json,
117
+ # and $CURSOR_DIR/mcp.json (install-agents.sh).
103
118
  PLUGIN_DIR="$CURSOR_DIR/plugins/local/massa-ai"
119
+ # The only directory Cursor discovers subagents from (project scope:
120
+ # ./.cursor/agents/). Flat .md files, real copies, massa-ai- prefix owned.
121
+ CURSOR_AGENTS_DIR="$CURSOR_DIR/agents"
104
122
  # Pre-fix installs wrote here; removed on install so the two cannot both be
105
123
  # discovered and register duplicate hooks.
106
124
  LEGACY_PLUGIN_DIR="$CURSOR_DIR/plugins/massa-ai"
107
125
  HOOKS_JSON="$CURSOR_DIR/hooks.json"
108
126
 
127
+ # ── Claude-bridge detection (PAU-08/09/10) ──────────────────────────────────
128
+ # Probe contract pinned against a read-only capture of this machine's live
129
+ # ~/.claude registry files (2026-08-05) — never invented:
130
+ # - $CLAUDE_PLUGIN_REGISTRY must parse and list a non-empty
131
+ # plugins["massa-ai@massa-ai"] array
132
+ # - $CLAUDE_SETTINGS_JSON's enabledPlugins["massa-ai@massa-ai"] must not be
133
+ # literal false — an absent settings.json, or an absent key, is treated
134
+ # as enabled (matching Claude's own default)
135
+ # - any parse failure on either file → NOT detected (local fallback)
136
+ # User-scope only: ~/.claude is a per-user surface, so a --project plugin
137
+ # install always keeps the local branch regardless of what ~/.claude holds.
138
+ CLAUDE_PLUGIN_REGISTRY="$HOME/.claude/plugins/installed_plugins.json"
139
+ CLAUDE_SETTINGS_JSON="$HOME/.claude/settings.json"
140
+
141
+ claude_bridge_detected() {
142
+ [[ "$SCOPE" == "project" ]] && return 1
143
+ [[ -f "$CLAUDE_PLUGIN_REGISTRY" ]] || return 1
144
+
145
+ local runner=""
146
+ if command -v node &>/dev/null; then runner="node"
147
+ elif command -v bun &>/dev/null; then runner="bun"
148
+ else return 1
149
+ fi
150
+
151
+ "$runner" - "$CLAUDE_PLUGIN_REGISTRY" "$CLAUDE_SETTINGS_JSON" <<'NODE'
152
+ const fs = require("fs");
153
+ const [, , registryFile, settingsFile] = process.argv;
154
+
155
+ try {
156
+ const registry = JSON.parse(fs.readFileSync(registryFile, "utf8"));
157
+ const listed = registry && registry.plugins && registry.plugins["massa-ai@massa-ai"];
158
+ if (!Array.isArray(listed) || listed.length === 0) process.exit(1);
159
+ } catch {
160
+ process.exit(1);
161
+ }
162
+
163
+ try {
164
+ const raw = fs.readFileSync(settingsFile, "utf8");
165
+ if (raw.trim()) {
166
+ const settings = JSON.parse(raw);
167
+ const enabled = settings && settings.enabledPlugins && settings.enabledPlugins["massa-ai@massa-ai"];
168
+ if (enabled === false) process.exit(1);
169
+ }
170
+ } catch (e) {
171
+ // Absent settings.json is treated as enabled (Claude's own default); any
172
+ // other read/parse failure means the probe cannot trust this surface.
173
+ if (e.code !== "ENOENT") process.exit(1);
174
+ }
175
+
176
+ process.exit(0);
177
+ NODE
178
+ }
179
+
109
180
  # The 7 Cursor events → binary subcommands. The command path uses the
110
181
  # INSTALLED plugin dir (not the placeholder), so Cursor invokes the copy.
111
182
  # Cursor hooks.json shape: { "version": 1, "hooks": { "<event>": [...] } }
@@ -377,13 +448,15 @@ record_plugin_version() {
377
448
  local version installed_at route
378
449
  version="$(sed -n 's/.*"version"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' "$SCRIPT_DIR/package.json" | head -n 1)"
379
450
  installed_at="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
380
- # installRoute (T9, design F1): installer-owned, engine-read-only. Cursor
381
- # has one install shape (file copy) and no marketplace distinction, so this
382
- # is unconditionally "file" recorded for data-model consistency with the
383
- # other three hosts even though the switch engine never reads it for Cursor
384
- # (hosts.ts skips Cursor unconditionally: every tier resolves to inherit).
385
- # Written on EVERY install path.
386
- route="file"
451
+ # installRoute (T6, design Component 4): installer-owned, engine-read-only.
452
+ # "bridge" when ~/.claude's Claude marketplace plugin is what Cursor
453
+ # actually loads (this run skipped its own local copy); "local" otherwise.
454
+ # The switch engine still never reads it for Cursor (hosts.ts skips Cursor
455
+ # unconditionally: every tier resolves to inherit) — recorded for
456
+ # data-model consistency with the other three hosts, and now also read by
457
+ # the harness sentinel probe (T4/T5), which treats both routes alike (same
458
+ # flat-agents glob). Written on EVERY install path.
459
+ if [[ "$IS_BRIDGE" -eq 1 ]]; then route="bridge"; else route="local"; fi
387
460
 
388
461
  # Tolerant of a corrupt/missing state file (rewrites a minimal valid one —
389
462
  # AC-8). A record-write failure warns but never fails the install: the next
@@ -429,6 +502,19 @@ if [[ "$UNINSTALL" -eq 1 ]]; then
429
502
  merge_hooks_json "$HOOKS_JSON" "uninstall"
430
503
  echo " - removed massa-ai hook entries from $HOOKS_JSON"
431
504
  fi
505
+ # Remove the massa-ai-owned subagents from Cursor's discovery directory.
506
+ # Prefix glob only: user-authored agents in the same flat dir survive.
507
+ if [[ -d "$CURSOR_AGENTS_DIR" ]]; then
508
+ removed_agents=0
509
+ for agent in "$CURSOR_AGENTS_DIR/"massa-ai-*.md; do
510
+ [[ -f "$agent" ]] || continue
511
+ rm -f "$agent"
512
+ removed_agents=$((removed_agents + 1))
513
+ done
514
+ if [[ "$removed_agents" -gt 0 ]]; then
515
+ echo " - removed $removed_agents massa-ai subagents from $CURSOR_AGENTS_DIR"
516
+ fi
517
+ fi
432
518
  # Remove plugin directory
433
519
  if [[ -d "$LEGACY_PLUGIN_DIR" ]]; then
434
520
  rm -rf "$LEGACY_PLUGIN_DIR"
@@ -444,59 +530,101 @@ if [[ "$UNINSTALL" -eq 1 ]]; then
444
530
  fi
445
531
 
446
532
  # ── Install ──────────────────────────────────────────────────────────────────
447
- vecho "Installing massa-ai Cursor plugin to: $PLUGIN_DIR"
448
- # Migration: a pre-fix install left a copy at plugins/massa-ai. Leaving it in
449
- # place risks Cursor discovering both and firing every hook twice, so it goes
450
- # before the new location is written.
451
- if [[ -d "$LEGACY_PLUGIN_DIR" ]]; then
452
- rm -rf "$LEGACY_PLUGIN_DIR"
453
- echo -e " - removed pre-fix plugin copy at $LEGACY_PLUGIN_DIR"
533
+ # PAU-08/09: bridge-preferred, local-fallback. Computed once, used to decide
534
+ # both which artifacts this run writes and what installRoute gets recorded.
535
+ IS_BRIDGE=0
536
+ if claude_bridge_detected; then
537
+ IS_BRIDGE=1
454
538
  fi
455
- mkdir -p "$PLUGIN_DIR/.cursor-plugin" "$PLUGIN_DIR/skills" "$PLUGIN_DIR/hooks" "$PLUGIN_DIR/agents"
456
-
457
- # Copy manifest
458
- cp "$SCRIPT_DIR/.cursor-plugin/plugin.json" "$PLUGIN_DIR/.cursor-plugin/plugin.json"
459
- vecho " + .cursor-plugin/plugin.json"
460
539
 
461
- # Copy the 6 host-command skills (each in a subdirectory: skills/<name>/SKILL.md).
462
- # massa-ai/, persona-router/, and agents/ are the PDO-06 harness bundle, not a
463
- # Cursor command skill — they are installed separately, into the shared
464
- # harness skills directory (see "Skills bundling" below), not into this
465
- # plugin-cache skills/ tree.
466
540
  skill_count=0
467
- for src in "$SCRIPT_DIR/skills/"*/SKILL.md; do
468
- name="$(basename "$(dirname "$src")")"
469
- case "$name" in
470
- massa-ai|persona-router|agents) continue ;;
471
- esac
472
- mkdir -p "$PLUGIN_DIR/skills/$name"
473
- cp "$src" "$PLUGIN_DIR/skills/$name/SKILL.md"
474
- vecho " + skills/$name/SKILL.md"
475
- skill_count=$((skill_count + 1))
476
- done
541
+ specialist_count=0
477
542
 
478
- # Copy hooks.json (the placeholder version — installer replaces paths)
479
- cp "$SCRIPT_DIR/hooks/hooks.json" "$PLUGIN_DIR/hooks/hooks.json"
480
- vecho " + hooks/hooks.json"
481
-
482
- # Older installs shipped a plugin-local mcp.json here. Cursor reads
483
- # ~/.cursor/mcp.json, not the plugin dir, and MCP is now owned by
484
- # scripts/install-agents.shdrop the residue so upgraders converge.
485
- if [[ -f "$PLUGIN_DIR/mcp.json" ]]; then
486
- rm -f "$PLUGIN_DIR/mcp.json"
487
- # Not gated by --quiet: this deletes a file in the user's home, so it is a
488
- # mutation notice rather than per-file chatter.
489
- echo -e " - removed stale mcp.json (MCP is now registered in ~/.cursor/mcp.json)"
543
+ if [[ "$IS_BRIDGE" -eq 1 ]]; then
544
+ 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."
545
+ # Converge (PAU-08, spec edge case): a pre-existing local install (current
546
+ # or pre-fix legacy location) must not double-load alongside the bridge, so
547
+ # one run here ends with exactly one load path. Owned hook entries are
548
+ # stripped with the SAME filter merge_hooks_json's "uninstall" mode already
549
+ # uses — the bridge delivers hooks instead.
550
+ if [[ -d "$LEGACY_PLUGIN_DIR" ]]; then
551
+ rm -rf "$LEGACY_PLUGIN_DIR"
552
+ echo -e " - removed pre-fix plugin copy at $LEGACY_PLUGIN_DIR"
553
+ fi
554
+ if [[ -d "$PLUGIN_DIR" ]]; then
555
+ rm -rf "$PLUGIN_DIR"
556
+ echo -e " - removed local plugin copy at $PLUGIN_DIR (Claude bridge already loads massa-ai)"
557
+ fi
558
+ if [[ -f "$HOOKS_JSON" ]]; then
559
+ merge_hooks_json "$HOOKS_JSON" "uninstall"
560
+ echo -e " - removed massa-ai hook entries from $HOOKS_JSON (the bridge delivers hooks instead)"
561
+ fi
562
+ else
563
+ vecho "Installing massa-ai Cursor plugin to: $PLUGIN_DIR"
564
+ # Migration: a pre-fix install left a copy at plugins/massa-ai. Leaving it in
565
+ # place risks Cursor discovering both and firing every hook twice, so it goes
566
+ # before the new location is written.
567
+ if [[ -d "$LEGACY_PLUGIN_DIR" ]]; then
568
+ rm -rf "$LEGACY_PLUGIN_DIR"
569
+ echo -e " - removed pre-fix plugin copy at $LEGACY_PLUGIN_DIR"
570
+ fi
571
+ mkdir -p "$PLUGIN_DIR/.cursor-plugin" "$PLUGIN_DIR/skills" "$PLUGIN_DIR/hooks"
572
+ # Migration: pre-fix installs bundled agents inside the plugin dir, which
573
+ # Cursor never reads. Drop that copy so upgraders converge on the one real
574
+ # discovery surface and a future Cursor plugin scan cannot double-register.
575
+ if [[ -d "$PLUGIN_DIR/agents" ]]; then
576
+ rm -rf "$PLUGIN_DIR/agents"
577
+ echo -e " - removed pre-fix agents copy at $PLUGIN_DIR/agents (Cursor reads $CURSOR_AGENTS_DIR)"
578
+ fi
579
+
580
+ # Copy manifest
581
+ cp "$SCRIPT_DIR/.cursor-plugin/plugin.json" "$PLUGIN_DIR/.cursor-plugin/plugin.json"
582
+ vecho " + .cursor-plugin/plugin.json"
583
+
584
+ # Copy the 6 host-command skills (each in a subdirectory: skills/<name>/SKILL.md).
585
+ # massa-ai/, persona-router/, and agents/ are the PDO-06 harness bundle, not a
586
+ # Cursor command skill — they are installed separately, into the shared
587
+ # harness skills directory (see "Skills bundling" below), not into this
588
+ # plugin-cache skills/ tree.
589
+ for src in "$SCRIPT_DIR/skills/"*/SKILL.md; do
590
+ name="$(basename "$(dirname "$src")")"
591
+ case "$name" in
592
+ massa-ai|persona-router|agents) continue ;;
593
+ esac
594
+ mkdir -p "$PLUGIN_DIR/skills/$name"
595
+ cp "$src" "$PLUGIN_DIR/skills/$name/SKILL.md"
596
+ vecho " + skills/$name/SKILL.md"
597
+ skill_count=$((skill_count + 1))
598
+ done
599
+
600
+ # Copy hooks.json (the placeholder version — installer replaces paths)
601
+ cp "$SCRIPT_DIR/hooks/hooks.json" "$PLUGIN_DIR/hooks/hooks.json"
602
+ vecho " + hooks/hooks.json"
603
+
604
+ # Older installs shipped a plugin-local mcp.json here. Cursor reads
605
+ # ~/.cursor/mcp.json, not the plugin dir, and MCP is now owned by
606
+ # scripts/install-agents.sh — drop the residue so upgraders converge.
607
+ if [[ -f "$PLUGIN_DIR/mcp.json" ]]; then
608
+ rm -f "$PLUGIN_DIR/mcp.json"
609
+ # Not gated by --quiet: this deletes a file in the user's home, so it is a
610
+ # mutation notice rather than per-file chatter.
611
+ echo -e " - removed stale mcp.json (MCP is now registered in ~/.cursor/mcp.json)"
612
+ fi
490
613
  fi
491
614
 
492
- # Copy agents every generated subagent specialist (auto-discovered by Cursor
493
- # from the plugin's agents/ dir). All of them, navigator included, are generated
494
- # from skills/agents/*/SKILL.md and owned by the massa-ai- name prefix (CRS-04).
495
- specialist_count=0
615
+ # Flat agents and harness skills are written in BOTH branches (PAU-08/09):
616
+ # Cursor discovers subagents only from the flat directory regardless of which
617
+ # plugin-load path is active, and MCP/harness-skills ownership is independent
618
+ # of it too. Prune-then-copy so a specialist deleted from the bundle cannot
619
+ # linger installed. All of them, navigator included, are generated from
620
+ # skills/agents/*/SKILL.md and owned by the massa-ai- name prefix (CRS-04) —
621
+ # user-authored agents are untouched.
622
+ mkdir -p "$CURSOR_AGENTS_DIR"
623
+ rm -f "$CURSOR_AGENTS_DIR/"massa-ai-*.md
496
624
  for src in "$SCRIPT_DIR/agents/"massa-ai-*.md; do
497
625
  [[ -f "$src" ]] || continue
498
626
  name="$(basename "$src")"
499
- cp "$src" "$PLUGIN_DIR/agents/$name"
627
+ cp "$src" "$CURSOR_AGENTS_DIR/$name"
500
628
  vecho " + $name"
501
629
  specialist_count=$((specialist_count + 1))
502
630
  done
@@ -504,31 +632,33 @@ vecho " + ${specialist_count} subagent specialists (generated from skills/agent
504
632
 
505
633
  # Skills bundling (PDO-08, 09): install massa-ai/persona-router into the
506
634
  # shared harness skills directory, unless scripts/install-skills.sh already
507
- # owns it for this platform.
635
+ # owns it for this platform. Runs in both branches (same reasoning as above).
508
636
  vecho ""
509
637
  install_bundled_skills
510
638
 
511
- # Copy the real hooks/massa-ai-hook this plugin already ships (T14/PDO-14: a
512
- # generated real file, no longer a symlink into apps/claude-plugin/ — that
513
- # path does not exist in a registry tarball install, where $REPO_ROOT is not
514
- # this monorepo. $CLAUDE_PLUGIN_BIN is kept only as a repo-checkout fallback.
515
- if [[ -f "$SCRIPT_DIR/hooks/massa-ai-hook" ]]; then
516
- cp "$SCRIPT_DIR/hooks/massa-ai-hook" "$PLUGIN_DIR/hooks/massa-ai-hook"
517
- chmod +x "$PLUGIN_DIR/hooks/massa-ai-hook"
518
- vecho " + hooks/massa-ai-hook"
519
- elif [[ -f "$CLAUDE_PLUGIN_BIN" ]]; then
520
- ln -sfn "$CLAUDE_PLUGIN_BIN" "$PLUGIN_DIR/hooks/massa-ai-hook"
521
- vecho " + hooks/massa-ai-hook → $CLAUDE_PLUGIN_BIN"
522
- else
523
- echo " ⚠ Warning: no massa-ai-hook binary found" >&2
524
- echo " Hooks will not fire until the binary is available." >&2
525
- fi
639
+ if [[ "$IS_BRIDGE" -ne 1 ]]; then
640
+ # Copy the real hooks/massa-ai-hook this plugin already ships (T14/PDO-14: a
641
+ # generated real file, no longer a symlink into apps/claude-plugin/ that
642
+ # path does not exist in a registry tarball install, where $REPO_ROOT is not
643
+ # this monorepo. $CLAUDE_PLUGIN_BIN is kept only as a repo-checkout fallback.
644
+ if [[ -f "$SCRIPT_DIR/hooks/massa-ai-hook" ]]; then
645
+ cp "$SCRIPT_DIR/hooks/massa-ai-hook" "$PLUGIN_DIR/hooks/massa-ai-hook"
646
+ chmod +x "$PLUGIN_DIR/hooks/massa-ai-hook"
647
+ vecho " + hooks/massa-ai-hook"
648
+ elif [[ -f "$CLAUDE_PLUGIN_BIN" ]]; then
649
+ ln -sfn "$CLAUDE_PLUGIN_BIN" "$PLUGIN_DIR/hooks/massa-ai-hook"
650
+ vecho " + hooks/massa-ai-hook → $CLAUDE_PLUGIN_BIN"
651
+ else
652
+ echo " Warning: no massa-ai-hook binary found" >&2
653
+ echo " Hooks will not fire until the binary is available." >&2
654
+ fi
526
655
 
527
- # Merge hooks.json (array-append, backup, idempotent)
528
- vecho ""
529
- vecho "Merging hooks into $HOOKS_JSON..."
530
- merge_hooks_json "$HOOKS_JSON" "install"
531
- vecho " + 7 massa-ai hook events wired (array-append, user hooks preserved)"
656
+ # Merge hooks.json (array-append, backup, idempotent)
657
+ vecho ""
658
+ vecho "Merging hooks into $HOOKS_JSON..."
659
+ merge_hooks_json "$HOOKS_JSON" "install"
660
+ vecho " + 7 massa-ai hook events wired (array-append, user hooks preserved)"
661
+ fi
532
662
 
533
663
  # ── MCP registration (delegated) ─────────────────────────────────────────────
534
664
  # scripts/install-agents.sh is the single writer of host MCP config. It writes
@@ -552,10 +682,17 @@ fi
552
682
 
553
683
  # Summary line in quiet mode
554
684
  if [ "${MASSA_AI_VERBOSE:-0}" != "1" ]; then
555
- ok "cursor plugin installed (${skill_count} skills, ${specialist_count} specialists, 7 hooks)"
685
+ if [[ "$IS_BRIDGE" -eq 1 ]]; then
686
+ ok "cursor plugin: using the Claude bridge (${specialist_count} specialists) — hooks delivered by the bridge, not installed locally"
687
+ else
688
+ ok "cursor plugin installed (${skill_count} skills, ${specialist_count} specialists, 7 hooks)"
689
+ fi
556
690
  else
557
691
  vecho ""
558
692
  vecho "Done. Restart Cursor to pick up the plugin."
693
+ if [[ "$IS_BRIDGE" -eq 1 ]]; then
694
+ vecho "💡 massa-ai loads via the Claude bridge (~/.claude) — local plugin/hook install skipped."
695
+ fi
559
696
  vecho "💡 MCP is registered in ~/.cursor/mcp.json by scripts/install-agents.sh (single writer)."
560
697
  fi
561
698
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@massa-ai/cursor-plugin",
3
- "version": "1.29.0",
3
+ "version": "1.30.0",
4
4
  "description": "massa-ai plugin for Cursor — semantic code search, memory, and context compression",
5
5
  "files": [
6
6
  "agents",
@@ -46,7 +46,7 @@ Before reading any massa-ai file:
46
46
  Never pass `workflowSessionId` in that field. Use `synapse_task_begin`/`synapse_task_end`
47
47
  for task envelopes and `synapse_prefetch` to warm the buffer on file open.
48
48
  - Prefer the shared v2 retrieval order; fall back gracefully if the massa-ai
49
- server or Synapse is unavailable. The full tool surface includes 52 tools
49
+ server or Synapse is unavailable. The full tool surface includes 54 tools
50
50
  (see `references/mcp-tools.md`): indexing, search, symbol graph
51
51
  (`trace_path`, `impact_analysis`, `get_architecture`), memory CRUD
52
52
  (`remember`, `recall`, `memory_update`, `memory_delete`), checkpoints
@@ -166,6 +166,7 @@ current context already contains it.
166
166
  | `commit` | draft or create safe Conventional Commits with Jira branch prefixes and audit report exclusions | `workflows/commit.md` |
167
167
  | `the-fool` | direct challenge, red-team, pre-mortem, evidence audit | `workflows/the-fool.md` |
168
168
  | `judge-with-debate` | standalone multi-judge debate evaluation of user-supplied artifacts | `workflows/judge-with-debate.md` |
169
+ | `pr-review` | review a hosted GitHub PR / GitLab MR and post findings via `gh`/`glab` | `workflows/pr-review.md` |
169
170
  | `to-prd` | turn the current conversation into a PRD without a new interview | `workflows/to-prd.md` |
170
171
  | `skill-architect` | design and build a new skill through structured conversation | `workflows/skill-architect.md` |
171
172
  | `furps-refinement` | FURPS+ refinement of a PRD and/or ADR before implementation, with The Fool pre-validation and DoR coverage | `workflows/refinement/furps-refinement.md` |
@@ -181,7 +182,7 @@ Deterministic routing precedence, first match wins:
181
182
 
182
183
  1. **Explicit route:** user names a massa-ai workflow, report family, saved finding type, or asks for a direct challenge.
183
184
  2. **Requested artifact:** ADR, RFC, TDD, Jira ticket, commit, session guide, audit report, implementation audit report, mobile Figma report, FURPS refinement report, PRD synthesized from the current conversation -> `to-prd` (explicit request only; refining an existing PRD stays `furps-refinement`), or new SKILL.md / skill design -> `skill-architect`.
184
- 3. **Target type:** broken behavior/error -> `debug`; saved audit finding -> matching `*-fix`; implementation scope review -> `implementation-audit`; Maestro E2E/device automation target -> `maestro`, `maestro-audit`, or child-only `maestro-fix` before generic tests workflows; security/privacy/auth finding -> security workflow; tests/flakes/coverage finding -> tests workflow; supplied Figma/screenshot mobile UI design -> `design`; mobile Figma compare/audit -> `mobile-figma-audit`; saved `MFM-*` findings -> `mobile-figma-fix`.
185
+ 3. **Target type:** broken behavior/error -> `debug`; hosted PR/MR reference (number or URL) to review with posted findings -> `pr-review` (local working diff stays with audit routes); saved audit finding -> matching `*-fix`; implementation scope review -> `implementation-audit`; Maestro E2E/device automation target -> `maestro`, `maestro-audit`, or child-only `maestro-fix` before generic tests workflows; security/privacy/auth finding -> security workflow; tests/flakes/coverage finding -> tests workflow; supplied Figma/screenshot mobile UI design -> `design`; mobile Figma compare/audit -> `mobile-figma-audit`; saved `MFM-*` findings -> `mobile-figma-fix`.
185
186
  4. **Primary verb:** create/add/implement -> `feature` unless the concrete target is new Maestro flow work, which routes to `maestro`; restructure without behavior change -> `refactor`; inspect/understand only -> `exploration`; record selected decision -> `adr`; compare open options -> `rfc`; design settled implementation -> `tdd`; refine/quality-check an existing PRD or ADR document (not implementation auditing) -> `furps-refinement`.
186
187
  5. **Risk domain escalation:** migrations, irreversible operations, auth/privacy, cross-service contracts, public compatibility, or work over 10 files routes to `spec-driven` unless the user explicitly requests a narrower workflow and accepts the containment.
187
188
  6. **General fallback:** use `general` only after a one-line General fallback preflight names the specialized workflow considered, rejected reason, and why fallback does not change verification or mutation behavior.
@@ -100,9 +100,9 @@ behavior through MCP declarations and non-destructive runtime probes.
100
100
  - Preferred: `bash scripts/install-agents.sh --agent <host>` — the single writer
101
101
  of host MCP config (Claude Code, Claude Desktop, Codex, Cursor, OpenCode).
102
102
  Plugin installers call it; nothing else writes an MCP entry.
103
- - OpenCode: local MCP package via `bunx @massa-ai/mcp-client`, or
104
- `@massa-ai/opencode-plugin` (which registers tools in-process, so
105
- `install-agents.sh` skips the MCP entry when that plugin is present).
103
+ - OpenCode: `@massa-ai/opencode-plugin` is hooks-only (AD-017) and delegates
104
+ MCP registration to `install-agents.sh --agent opencode` on every install,
105
+ same as every other host no separate MCP step needed.
106
106
  - VS Code/Antigravity: `.vscode/mcp.json` or `./scripts/setup-vscode.sh`.
107
107
  - Docker: run the `mcp` service through `docker compose run --rm -i mcp`.
108
108
 
@@ -1,4 +1,4 @@
1
- # massa-ai Tool Contracts (52 Tools)
1
+ # massa-ai Tool Contracts (54 Tools)
2
2
 
3
3
  Load when exact MCP schemas, REST fallbacks, response modes, or
4
4
  polling rules are needed. Prefer the active tool declaration over copied
@@ -138,6 +138,8 @@ depends on exact content.
138
138
  |---|---|---|
139
139
  | `rename_project` | Rename a project identity transactionally | Req: `sourceProjectId`, `targetProjectId`. Default `dryRun=true`; apply with `dryRun=false` + `operationId` + `expectedPlanHash`. Administrative, not workflow-recurring. |
140
140
  | `merge_projects` | Merge one project identity into another | Req: `sourceProjectId`, `targetProjectId`. Same dryRun/planHash contract as `rename_project`. Administrative, not workflow-recurring. |
141
+ | `profile_list` | List shipped model profiles and each detected host's current profile + bundle version | No required args; optional `host` to scope to one. Offline — reads on-disk variant directories only, never the registry. |
142
+ | `profile_set` | Switch the installed massa-ai agents to a registry model profile | Req: `profile`. Optional `host` (default: every detected, supported host), `dryRun` (default `false`) to preview without writing. Local trust model — mutates installed agent files on the machine the MCP server runs on; a host session restart is required to take effect. |
141
143
 
142
144
  ## Retrieval Order
143
145
 
@@ -0,0 +1,246 @@
1
+ ---
2
+ name: pr-review
3
+ description: "Explicit-route workflow to review a hosted GitHub Pull Request or GitLab Merge Request across six dimensions — security, requirements, test coverage, architecture, regression, performance — using massa-ai roster subagents, then post inline comments plus one consolidated summary through the host CLI (gh or glab). Use when the user says review PR 128, review this MR, or code review this pull request. Do NOT use for local working-diff review (audit workflows), creating PRs, replying to review comments, or fixing CI."
4
+ license: CC-BY-4.0
5
+ metadata:
6
+ version: "1.0.0"
7
+ ---
8
+
9
+ Attribution: adapted from the `pr-review` skill by github.com/augusto-dmh
10
+ (TLC skills catalog), licensed CC-BY-4.0. Host abstraction (GitLab support),
11
+ massa-ai roster dispatches, memory/index/`.specs/` integration, and channel
12
+ discipline are this repository's additions; repository contracts win on any
13
+ conflict with the base.
14
+
15
+ ### PR Review
16
+
17
+ Use when the user explicitly asks to review a hosted PR (Pull Request, GitHub) or
18
+ MR (Merge Request, GitLab) — "review PR 128", "review this MR", "check pull request
19
+ 42". Explicit route only: never auto-trigger during coding. Local working-tree diff
20
+ review stays with the audit workflows and `massa-ai-reviewer`; this workflow exists
21
+ to **post findings back to the host**.
22
+
23
+ Load `references/project-context.md` (intake sweep) before the first substantive
24
+ read. Resolve `projectId` and `workflowSessionId` = `pr-review-<number>` per the
25
+ Core Contract, and run a budgeted `recall` (limit ≤ 3, minImportance ≥ 0.7) for
26
+ prior review conventions and known regression patterns.
27
+
28
+ ## Execution Contract (non-negotiable)
29
+
30
+ 1. **Orchestration-only.** The main agent never authors a review finding. It
31
+ gathers context, dispatches the review subagents, dedupes their returned
32
+ findings, and posts. Doing the review inline — even for a small diff — is a
33
+ failure of this workflow.
34
+ 2. **Comment-only, never destructive.** Forbidden in every circumstance:
35
+ `gh pr review --approve`, `gh pr review --request-changes`, `gh pr merge`,
36
+ `glab mr approve`, `glab mr revoke`, `glab mr merge`, and the raw
37
+ `POST …/approve` / `POST …/unapprove` endpoints. Posting notes or discussions
38
+ never approves — keep it that way. Never modify repository files.
39
+ 3. **Subagents never touch the host.** Review subagents are read-only and
40
+ host-agnostic: they receive the diff and context in their packet and return
41
+ findings in their reply block. Only the orchestrator executes `gh`/`glab`.
42
+ 4. **File-body posting.** Every multiline body is written to a temp file and
43
+ posted with the host's file-body mechanism (`--body-file` / `-F body=@file`).
44
+ Inlining a multiline `--body` string is the protocol's most common failure.
45
+ 5. **Ask, never guess.** No PR/MR reference in the request → ask for it. Host CLI
46
+ cannot resolve the reference → stop and surface the CLI error output.
47
+
48
+ ## Step 1 — Initialize
49
+
50
+ ### 1a. Resolve the host
51
+
52
+ Order: explicit user statement > CLI probe > git remote host. Probe with
53
+ `gh repo view` / `glab repo view` **exit status** (glab's no-remote error text is
54
+ not a documented stable string — never match on the message). Both probes fail →
55
+ stop and report which CLI is missing or unauthenticated (`gh auth status` /
56
+ `glab auth status`). Both succeed (mirrored repo) → ask the user which host to
57
+ review on; a posted comment is outward-facing. Record `HOST ∈ {github, gitlab}`.
58
+
59
+ ### 1b. PR/MR context (via the command map below)
60
+
61
+ Resolve repository identity, then fetch: title + body/description + source
62
+ branch, the head anchor (`{SHA}` on GitHub; the full `diff_refs`
63
+ `{base_sha, head_sha, start_sha}` triple on GitLab), the full diff, and the
64
+ changed-file list. Then load the existing inline-comment inventory as
65
+ `{id, path, line, body}` records — **page to completion** (GitLab discussions
66
+ default to 20 per page; pin `per_page=100` and loop) — used for dedupe,
67
+ `[RESOLVED]` replies, and threading.
68
+
69
+ ### 1c. Project discovery (the adaptive spine)
70
+
71
+ Probe the repository once and record a DISCOVERY MAP passed verbatim to every
72
+ subagent. Prefer evidence the project states over guesses; mark absences `none`.
73
+
74
+ ```
75
+ TEST: <command CI actually runs> | globs: <...> | unit vs e2e: <split | none>
76
+ REQS: tracker=<GH #42 | Jira KEY-123 | GitLab #42 | none> ; specs=<paths | none>
77
+ CONVENTIONS: <doc/skill paths that state rules | none-found>
78
+ REVIEW_SKILLS: <project-local review skill paths | none>
79
+ INDEX: <massa-ai retrieval state: fresh | stale | unavailable — CLI fallback>
80
+ ```
81
+
82
+ - **TEST**: the CI workflow config is authoritative; manifests are fallback.
83
+ - **REQS Track A (tracker)**: ticket key from branch name or PR/MR body —
84
+ `gh issue view {N} --json title,body` / `glab issue view {N} --output json`;
85
+ Jira only through an already-configured Atlassian MCP (never invent a host).
86
+ - **REQS Track B (in-repo)**: `.specs/project/FEATURES.json` and
87
+ `.specs/features/<slug>/{spec,tasks}.md` acceptance criteria matched by branch,
88
+ ticket, or feature stem; then `docs/`, ADR/RFC directories, `*-spec.md`.
89
+ - **CONVENTIONS/REVIEW_SKILLS**: `CONTRIBUTING*`, `ARCHITECTURE*`, `AGENTS.md`,
90
+ `CLAUDE.md`, `docs/**` convention files, `.claude/skills/`, `.cursor/skills/`.
91
+ - **INDEX**: `list_projects` freshness first; when fresh, `project_map` or
92
+ `get_architecture` for orientation and `impact_analysis` over the PR/MR diff
93
+ for centrality-ranked hotspots; `search` under `references/synapse-policy.md`
94
+ when two or more related searches are planned. Index results are leads until
95
+ confirmed against the diff — never evidence on their own. Server or index
96
+ unavailable → record it and continue per `references/graceful-degradation.md`.
97
+
98
+ ## Host Command Map
99
+
100
+ The orchestrator reads every host operation from this table. `{REPO}`/`{PR}` are
101
+ GitHub coordinates; `{MR}` is the GitLab IID; `:id` is glab's project placeholder
102
+ (resolved from the current repo's remote — 8 placeholders are documented:
103
+ `:branch :fullpath :group :id :namespace :repo :user :username`).
104
+
105
+ | Operation | GitHub (`gh`) | GitLab (`glab`) |
106
+ | --- | --- | --- |
107
+ | Identity | `gh repo view --json nameWithOwner -q .nameWithOwner` → `{REPO}` | `glab repo view --output json --jq .path_with_namespace` (project id: `--jq .id`) |
108
+ | Metadata | `gh pr view {PR} --json title,body,headRefName,headRefOid` → `{SHA}` | `glab mr view {MR} --output json` → `title`, `description`, `source_branch`, `sha`, `diff_refs.{base_sha,head_sha,start_sha}` |
109
+ | Full diff | `gh pr diff {PR}` | `glab mr diff {MR} --raw` |
110
+ | Changed files | `gh pr diff {PR} --name-only` | `glab api "projects/:id/merge_requests/{MR}/diffs?per_page=100&page={N}"` → `new_path`/`old_path`, page to completion |
111
+ | Existing comments | `gh api repos/{REPO}/pulls/{PR}/comments` | `glab api "projects/:id/merge_requests/{MR}/discussions?per_page=100&page={N}"` → note `id`, `position.new_path`, `position.new_line`, `body` |
112
+ | Inline comment | `gh api repos/{REPO}/pulls/{PR}/comments -F body=@body.md -f commit_id={SHA} -f path={path} -F line={N} -f side=RIGHT` | `glab api --method POST "projects/:id/merge_requests/{MR}/discussions" -F body=@body.md -f "position[position_type]=text" -f "position[base_sha]={base}" -f "position[head_sha]={head}" -f "position[start_sha]={start}" -f "position[new_path]={path}" -f "position[old_path]={old}" -F "position[new_line]={N}"` |
113
+ | Thread reply | `gh api repos/{REPO}/pulls/{PR}/comments/{COMMENT_ID}/replies -F body=@body.md` | `glab api --method POST "projects/:id/merge_requests/{MR}/discussions/{DISCUSSION_ID}/notes" -F body=@body.md` |
114
+ | Summary | `gh pr review {PR} --comment --body-file summary.md` | `glab api --method POST "projects/:id/merge_requests/{MR}/notes" -F body=@summary.md` |
115
+
116
+ Anchoring and flag semantics (load-bearing, verified against official docs):
117
+
118
+ - **GitHub `line={N}`** is the 1-based line number in the **head file** on side
119
+ `RIGHT` — count from the hunk header across added and context lines. A
120
+ diff-relative offset returns 422 or lands on the wrong line.
121
+ - **GitLab added line** ⇒ send `position[new_line]` and **omit** `old_line`
122
+ (removed line: the reverse; context line: both). `new_path` **and** `old_path`
123
+ are both required for `position_type=text` — take `old_path` from the `/diffs`
124
+ inventory, never assume it equals `new_path` (renames break that).
125
+ - **`-F`/`--field` expands `@file` and infers types on both CLIs; `-f`/
126
+ `--raw-field` does neither** — `-f body=@body.md` posts the literal string
127
+ `@body.md`. Use `-F` for bodies and line numbers, `-f` for plain strings.
128
+ glab's `-F` also switches the default method to POST.
129
+ - `glab mr note create` has experimental inline flags (`--file`, `--line`,
130
+ `--reply`) — GitLab marks them "might be unstable or removed at any time"; the
131
+ stable `glab api` paths above are the contract. A plain summary may also use
132
+ `glab mr note create {MR} < summary.md` (body from stdin).
133
+
134
+ ## Step 2 — Dispatch the review (two waves)
135
+
136
+ Six dimensions run as read-only roster dispatches under
137
+ `references/agent-orchestration.md` (wave cap 4 → wave 1 = rows 1–4, wave 2 =
138
+ rows 5–6). Each packet carries: the dimension row below, the DISCOVERY MAP, the
139
+ PR/MR intent (title/body/branch), the existing-comment inventory, the diff
140
+ trimmed to hunks relevant to the dimension per `references/context-firewall.md`,
141
+ the severity labels, and the reply contract.
142
+
143
+ | # | Dimension | Agent | Packet delta (lens / scope) | Marker `{type}` |
144
+ | --- | --- | --- | --- | --- |
145
+ | 1 | Security | `massa-ai-audit-specialist` | `lens: security` — secrets, authn/authz on new endpoints, injection, unsafe deserialization, PII in logs, permissive CORS, leaking payload fields | `security` |
146
+ | 2 | Requirements & DoD (Definition of Done) | `massa-ai-audit-specialist` | `lens: requirements` — score merged Track A + Track B criteria against the diff, evidence-or-zero: ✅ implemented (`path:line`) / 🟡 partial / ❌ missing; no source ⇒ report "requirements verification skipped" | `requirements` |
147
+ | 3 | Architecture & conventions | `massa-ai-audit-specialist` | `lens: architecture` — extract every explicit rule from the profile's CONVENTIONS/REVIEW_SKILLS docs into a numbered matrix, grade each changed file PASS/VIOLATION/N/A; no docs ⇒ minimal generic boundary sweep, stated | `architecture` |
148
+ | 4 | Performance | `massa-ai-audit-specialist` | `lens: performance` — only issues clearly visible in the diff: N+1 queries, unbounded fetches, per-row lazy I/O, sequential awaits of independent calls, loop-invariant recomputation, unbatched writes | `performance` |
149
+ | 5 | Test coverage | `massa-ai-audit-specialist` | `lens: performance`, scope: test coverage (the charter's lens set has no `tests` lens; `tests-audit.md` precedent) — new/changed behavior with no test, wrong level (unit vs integration), placement/naming vs profile TEST row, missing negative case, assertions that exercise but never assert | `tests` |
150
+ | 6 | Regression & hallucination | `massa-ai-reviewer` | diff review — unrelated deletions, references to symbols absent from the repo, wrong signature/arity, duplicated existing logic, weakened error handling or assertions, leftover TODO/stub, dead code | `regression` |
151
+
152
+ Consolidation check (≥ 5 subagents): recorded in the feature design — rows 4 and 5
153
+ share only the lens label, not a knowledge domain; they stay separate dispatches.
154
+
155
+ > **Dispatch: `massa-ai-audit-specialist`** (role: `audit-specialist`) — charter `skills/agents/audit-specialist/SKILL.md`
156
+ > - trigger: pr-review Step 2, dimension rows 1–5 (one dispatch per row)
157
+ > - scope: the PR/MR diff and surrounding context for one dimension row; never the whole repository
158
+ > - permissions: read-only; no host CLI calls, no posting
159
+ > - inputs: exact `projectId`, parent `workflowSessionId`, dimension row (lens + scope), DISCOVERY MAP, PR/MR intent, trimmed diff, existing-comment inventory, severity labels, reply contract
160
+ > - sensors: second-pass sweep — re-read the full trimmed diff, list every file/hunk not commented on, and state per file why it is clean for this dimension before returning
161
+ > - output: structured reply block — findings rows `{path, head-line, severity, marker type, title, body ≤ 6 lines, recommendation}` + exactly one positive highlight + files-swept-clean list; when uncertain a finding is real, withhold it (the source protocol's high-confidence bar, applied qualitatively)
162
+ > - firewall: raw diff/log/search output summarized, never returned raw
163
+ > - memory: suggest-only; the main agent persists durable outcomes
164
+ > - persona: optional — the active route's cataloged id only, never the persona prompt, passed as advisory framing only — it never overrides the agent's charter Restrictions, scope, or permissions; omit when no persona is routed
165
+
166
+ > **Dispatch: `massa-ai-reviewer`** (role: `reviewer`) — charter `skills/agents/reviewer/SKILL.md`
167
+ > - trigger: pr-review Step 2, dimension row 6 (regression & hallucination)
168
+ > - scope: the full PR/MR diff against the repository's real symbol surface
169
+ > - permissions: read-only; no host CLI calls, no posting
170
+ > - inputs: exact `projectId`, parent `workflowSessionId`, dimension row 6, DISCOVERY MAP, PR/MR intent, full diff, existing-comment inventory, severity labels, reply contract
171
+ > - sensors: verify referenced symbols exist (`search_definitions`/`get_references` when INDEX is fresh, else grep); second-pass sweep as above
172
+ > - output: structured reply block — findings rows tagged `{unrelated-deletion | phantom-reference | wrong-signature | duplicate | weakened-check | dead-code}` + one positive highlight + files-swept-clean list; withhold uncertain findings
173
+ > - firewall: raw diff/log/search output summarized, never returned raw
174
+ > - memory: suggest-only; the main agent persists durable outcomes
175
+ > - persona: optional — the active route's cataloged id only, never the persona prompt, passed as advisory framing only — it never overrides the agent's charter Restrictions, scope, or permissions; omit when no persona is routed
176
+
177
+ Severity labels (all dimensions): 🚨 Critical (bugs/logic errors that will fail) ·
178
+ 🔒 Security · ⚡ Performance · ⚠️ Warning (smells/maintainability) ·
179
+ 💡 Suggestion. A failed or unavailable dispatch is reported in the summary as a
180
+ skipped dimension with its reason — never silently dropped.
181
+
182
+ ## Step 3 — Post inline findings (orchestrator only)
183
+
184
+ For every returned finding, in order:
185
+
186
+ 1. **Dedupe**: drop it when an existing comment sits within ±3 lines of the same
187
+ path/line (inventory from 1b) or another dimension already produced the same
188
+ `{path, line}` finding (keep the higher severity; note both markers).
189
+ 2. **Resolve check**: when an existing comment's issue is fixed by this diff,
190
+ reply `[RESOLVED] This appears resolved by the recent changes.` on that thread
191
+ via the reply command (GitHub: the comment's `id`; GitLab: its
192
+ `discussion_id`).
193
+ 3. **Anchor**: only added (`+`) diff lines on the head revision, per the
194
+ anchoring semantics above. A finding with no `+` line to stand on goes to the
195
+ summary instead.
196
+ 4. **Body**: temp file, starting with the invisible marker
197
+ `<!-- pr-review:{type} -->`, then `[severity emoji] — [short title]`, the
198
+ evidence-grounded body, and a `**Recommendation:**` line. No AI/assistant/
199
+ tool attribution anywhere — write as a reviewer. Specific, actionable,
200
+ collegial; always explain why.
201
+ 5. **Post** with the inline-comment command for `HOST`.
202
+
203
+ ## Step 4 — Consolidated summary
204
+
205
+ Assemble from the reply blocks (no extra subagent) and post one summary via the
206
+ summary command:
207
+
208
+ ```markdown
209
+ ## 📋 PR Review Summary
210
+
211
+ | | |
212
+ |---|---|
213
+ | **Host / target** | {github PR #N | gitlab MR !N} @ {head sha} |
214
+ | **Dimensions** | 6 (Security · Requirements & DoD · Tests · Architecture · Regression · Performance) |
215
+ | **Detected runner** | {TEST row | none found} |
216
+ | **Requirements source** | {tracker / spec paths / none} |
217
+ | **Project refs loaded** | {CONVENTIONS + REVIEW_SKILLS rows} |
218
+ | **Findings** | {N} across {M} files |
219
+
220
+ ### 🔒 Security ({N}) / 🚨 Critical ({N}) / ⚡ Performance ({N}) / ⚠️ Warnings ({N}) / 💡 Suggestions ({N})
221
+ - [`path/file:L42`] Finding title — one line each, grouped by severity
222
+
223
+ ### 📋 Requirements
224
+ {✅/🟡/❌ rows from dimension 2, with `path:line` evidence}
225
+
226
+ ### 🔍 Files with no findings
227
+ - `path` — swept clean by {dimensions} (omit section when every logic file got a comment; config/lock/declaration files excluded)
228
+
229
+ ### ✅ Highlights
230
+ - one per dimension
231
+
232
+ > See inline comments for details. {Skipped dimensions/sensors with reasons, if any.}
233
+ ```
234
+
235
+ Zero findings overall → post "✅ No issues found across all review dimensions."
236
+ with the metadata table intact.
237
+
238
+ ## Completion
239
+
240
+ - Emit Conversation Feedback status updates at wave boundaries when that policy
241
+ is active; expand every abbreviation on first use in user-facing output.
242
+ - Persist durable outcomes only (recurring review pattern, confirmed project
243
+ convention) with the required memory tags; do not fabricate memories.
244
+ - Close with `references/evidence-gate.md`: counts posted vs deduped vs withheld,
245
+ skipped dimensions/sensors with reasons, and the summary URL/reference.
246
+ <!-- validator anchors: comment-only | added (+) diff lines | page to completion | two waves -->