@massa-ai/claude-plugin 1.52.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.
package/install.sh
CHANGED
|
@@ -185,8 +185,54 @@ try {
|
|
|
185
185
|
}
|
|
186
186
|
}
|
|
187
187
|
|
|
188
|
+
// Ownership test. The `_massaAiOwned` marker is the precise signal, but it is
|
|
189
|
+
// NOT the only way our hook entries reach settings.json: this plugin also ships
|
|
190
|
+
// settings.json.template, a hand-merge guide whose blocks carry no marker and
|
|
191
|
+
// whose command the reader is told to rewrite to an absolute path. Entries that
|
|
192
|
+
// arrived that way were invisible to both directions of this merge — never
|
|
193
|
+
// deduped on install, never removed when the marketplace route took over — so
|
|
194
|
+
// they fired alongside the bundle's own hooks/hooks.json. Measured live
|
|
195
|
+
// 2026-08-17: 5 unmarked massa-ai blocks in ~/.claude/settings.json beside a
|
|
196
|
+
// registered marketplace plugin, and every event ingested twice.
|
|
197
|
+
//
|
|
198
|
+
// So recognise our entries by WHAT THEY ARE as well. Referencing the
|
|
199
|
+
// massa-ai-hook binary is unambiguous — it is this project's own file name, in
|
|
200
|
+
// every shape it is documented in (`bun run "<abs>/massa-ai-hook.ts" <sub>`,
|
|
201
|
+
// `bun run "${CLAUDE_PLUGIN_ROOT}/hooks/massa-ai-hook.ts" <sub>`). A user hook
|
|
202
|
+
// that merely mentions massa-ai elsewhere (`rtk hook claude`, an MCP tool name)
|
|
203
|
+
// does not match and is preserved.
|
|
204
|
+
const OWNED_COMMAND = /massa-ai-hook/;
|
|
205
|
+
|
|
206
|
+
function commandIsOwned(h) {
|
|
207
|
+
return Boolean(h) && typeof h.command === "string" && OWNED_COMMAND.test(h.command);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// A matcher-group block is ours when it is marked, or when every command it
|
|
211
|
+
// carries is ours. A block MIXING user and massa-ai commands is not owned
|
|
212
|
+
// wholesale — stripOwnedHooks below removes only our commands from it.
|
|
213
|
+
function blockIsOwned(b) {
|
|
214
|
+
if (!b || typeof b !== "object") return false;
|
|
215
|
+
if (b._massaAiOwned === true) return true;
|
|
216
|
+
return Array.isArray(b.hooks) && b.hooks.length > 0 && b.hooks.every(commandIsOwned);
|
|
217
|
+
}
|
|
218
|
+
|
|
188
219
|
function hasOwned(arr) {
|
|
189
|
-
return Array.isArray(arr) && arr.some(
|
|
220
|
+
return Array.isArray(arr) && arr.some(blockIsOwned);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// Returns the event array with our entries removed: whole blocks that are ours,
|
|
224
|
+
// and our individual commands out of blocks we share with the user.
|
|
225
|
+
function stripOwnedHooks(arr) {
|
|
226
|
+
const kept = [];
|
|
227
|
+
for (const b of arr) {
|
|
228
|
+
if (blockIsOwned(b)) continue;
|
|
229
|
+
if (b && Array.isArray(b.hooks) && b.hooks.some(commandIsOwned)) {
|
|
230
|
+
kept.push({ ...b, hooks: b.hooks.filter((h) => !commandIsOwned(h)) });
|
|
231
|
+
continue;
|
|
232
|
+
}
|
|
233
|
+
kept.push(b);
|
|
234
|
+
}
|
|
235
|
+
return kept;
|
|
190
236
|
}
|
|
191
237
|
|
|
192
238
|
// Double-fire guard: true when massa-ai is already installed as a Claude Code
|
|
@@ -223,20 +269,54 @@ function pluginAlreadyInstalled() {
|
|
|
223
269
|
if (mode === "uninstall") {
|
|
224
270
|
const hooks = cfg.hooks;
|
|
225
271
|
if (hooks && typeof hooks === "object" && !Array.isArray(hooks)) {
|
|
226
|
-
|
|
272
|
+
// Back up before removing. The install path has always done this; removal
|
|
273
|
+
// did not, and now that it deletes unmarked entries it did not necessarily
|
|
274
|
+
// write, the asymmetry is not defensible.
|
|
275
|
+
if (existed) {
|
|
276
|
+
fs.copyFileSync(file, `${file}.massa-ai.bak-${ts}`);
|
|
277
|
+
}
|
|
278
|
+
// Every event, not just the 5 we write. The predicate identifies our
|
|
279
|
+
// commands rather than a location, so an entry parked under an event this
|
|
280
|
+
// release does not use is still ours, and a user's is still not.
|
|
281
|
+
for (const evt of Object.keys(hooks)) {
|
|
227
282
|
if (Array.isArray(hooks[evt])) {
|
|
228
|
-
hooks[evt] = hooks[evt]
|
|
283
|
+
hooks[evt] = stripOwnedHooks(hooks[evt]);
|
|
229
284
|
if (hooks[evt].length === 0) delete hooks[evt];
|
|
230
285
|
}
|
|
231
286
|
}
|
|
232
287
|
if (Object.keys(hooks).length === 0) delete cfg.hooks;
|
|
233
288
|
}
|
|
234
289
|
} else if (pluginAlreadyInstalled()) {
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
290
|
+
// Not writing is only half the guard. If settings.json ALREADY carries our
|
|
291
|
+
// entries — from an older release, or from a hand-merge of
|
|
292
|
+
// settings.json.template — declining to add more still leaves both sources
|
|
293
|
+
// live. This branch is reached with the plugin registered but no usable
|
|
294
|
+
// `claude` CLI, so the marketplace route (which removes them) never runs.
|
|
295
|
+
// Strip them here and fall through to the write.
|
|
296
|
+
const hooks = cfg.hooks;
|
|
297
|
+
let removed = 0;
|
|
298
|
+
if (hooks && typeof hooks === "object" && !Array.isArray(hooks)) {
|
|
299
|
+
for (const evt of Object.keys(hooks)) {
|
|
300
|
+
if (!Array.isArray(hooks[evt])) continue;
|
|
301
|
+
const before = JSON.stringify(hooks[evt]);
|
|
302
|
+
hooks[evt] = stripOwnedHooks(hooks[evt]);
|
|
303
|
+
if (JSON.stringify(hooks[evt]) !== before) removed++;
|
|
304
|
+
if (hooks[evt].length === 0) delete hooks[evt];
|
|
305
|
+
}
|
|
306
|
+
if (Object.keys(hooks).length === 0) delete cfg.hooks;
|
|
307
|
+
}
|
|
308
|
+
if (removed > 0) {
|
|
309
|
+
if (existed) fs.copyFileSync(file, `${file}.massa-ai.bak-${ts}`);
|
|
310
|
+
console.error(
|
|
311
|
+
` ↷ massa-ai is installed as a Claude Code plugin — removed ${removed} stale` +
|
|
312
|
+
" settings.json hook event(s) that would have fired alongside the plugin's own",
|
|
313
|
+
);
|
|
314
|
+
} else {
|
|
315
|
+
console.error(
|
|
316
|
+
" ↷ massa-ai is installed as a Claude Code plugin — skipping settings.json" +
|
|
317
|
+
" hooks (the plugin already provides them; wiring both would double-fire)",
|
|
318
|
+
);
|
|
319
|
+
}
|
|
240
320
|
} else {
|
|
241
321
|
// install: backup before first write if file existed
|
|
242
322
|
if (existed) {
|
|
@@ -309,7 +389,7 @@ install_bundled_skills() {
|
|
|
309
389
|
fi
|
|
310
390
|
|
|
311
391
|
local installed=0 name src dest
|
|
312
|
-
for name in massa-ai persona-router; do
|
|
392
|
+
for name in massa-ai persona-router profile; do
|
|
313
393
|
src="$SCRIPT_DIR/skills/$name"
|
|
314
394
|
[[ -d "$src" ]] || continue
|
|
315
395
|
dest="$HARNESS_SKILLS_DIR/$name"
|
|
@@ -338,7 +418,7 @@ if (typeof data.platforms !== "object" || data.platforms === null || Array.isArr
|
|
|
338
418
|
}
|
|
339
419
|
data.version = 2;
|
|
340
420
|
const prev = data.platforms[host];
|
|
341
|
-
data.platforms[host] = { root, skillsOwner: "plugin", skills: ["massa-ai", "persona-router"] };
|
|
421
|
+
data.platforms[host] = { root, skillsOwner: "plugin", skills: ["massa-ai", "persona-router", "profile"] };
|
|
342
422
|
// The whole-record replace must not drop fields a previous successful install
|
|
343
423
|
// wrote (R2) — re-attach them. modelProfile (T10, MPS-03 round-trip
|
|
344
424
|
// obligation) is engine-owned; installRoute is installer-owned but written by
|
|
@@ -385,7 +465,7 @@ NODE
|
|
|
385
465
|
)"
|
|
386
466
|
[[ "$raw_owner" == "plugin" ]] && {
|
|
387
467
|
local name
|
|
388
|
-
for name in massa-ai persona-router; do
|
|
468
|
+
for name in massa-ai persona-router profile; do
|
|
389
469
|
rm -rf "$HARNESS_SKILLS_DIR/$name"
|
|
390
470
|
done
|
|
391
471
|
rmdir "$HARNESS_SKILLS_DIR" 2>/dev/null || true
|
|
@@ -734,10 +814,16 @@ remove_file_route_artifacts() {
|
|
|
734
814
|
if [[ -f "$SETTINGS_JSON" ]]; then
|
|
735
815
|
merge_settings_hooks "$SETTINGS_JSON" "uninstall"
|
|
736
816
|
fi
|
|
817
|
+
# Prefix-glob against the INSTALLED directory, not the source bundle (IPT-03
|
|
818
|
+
# site 6, D2): deriving removals from $SCRIPT_DIR/commands/*.md misses
|
|
819
|
+
# installed copies whenever the bundle is absent or stale (normal under
|
|
820
|
+
# AD-016). Mirrors the agents loop directly below, which already does this
|
|
821
|
+
# correctly. The keep-list here is empty by design — the marketplace bundle
|
|
822
|
+
# serves its own commands, so every loose file-route copy is removed.
|
|
737
823
|
if [[ -d "$TARGET/commands" ]]; then
|
|
738
|
-
for
|
|
739
|
-
[[ -f "$
|
|
740
|
-
rm -f "$
|
|
824
|
+
for f in "$TARGET/commands/"massa-ai-*.md; do
|
|
825
|
+
[[ -f "$f" ]] || continue
|
|
826
|
+
rm -f "$f"
|
|
741
827
|
done
|
|
742
828
|
fi
|
|
743
829
|
if [[ -d "$TARGET/agents" ]]; then
|
|
@@ -824,6 +910,21 @@ else
|
|
|
824
910
|
command_count=$((command_count + 1))
|
|
825
911
|
done
|
|
826
912
|
|
|
913
|
+
# Shed commands retired from the bundle (IPT-02 site 2, D1 copy-then-prune):
|
|
914
|
+
# copy the current set first, then remove owned destination entries the
|
|
915
|
+
# bundle no longer ships — never the reverse, so an interrupted run never
|
|
916
|
+
# leaves the user with fewer commands than before. Removal population is the
|
|
917
|
+
# destination directory (D2); ownership test is the massa-ai- name prefix
|
|
918
|
+
# (install.sh:765).
|
|
919
|
+
for f in "$TARGET/commands/"massa-ai-*.md; do
|
|
920
|
+
[[ -f "$f" ]] || continue
|
|
921
|
+
prune_name="$(basename "$f" .md)"
|
|
922
|
+
prune_name="${prune_name#massa-ai-}"
|
|
923
|
+
[[ -f "$SCRIPT_DIR/commands/${prune_name}.md" ]] && continue
|
|
924
|
+
rm -f "$f"
|
|
925
|
+
vecho " - removed retired command $(basename "$f")"
|
|
926
|
+
done
|
|
927
|
+
|
|
827
928
|
# Subagent specialists (generated from skills/agents/*/SKILL.md, navigator
|
|
828
929
|
# included). The massa-ai- name prefix is the ownership marker used by uninstall.
|
|
829
930
|
#
|
|
@@ -859,6 +960,20 @@ else
|
|
|
859
960
|
done
|
|
860
961
|
vecho " + ${specialist_count} subagent specialists (generated from skills/agents/*/SKILL.md)"
|
|
861
962
|
|
|
963
|
+
# Shed specialists retired from the bundle (IPT-02 site 1, D1
|
|
964
|
+
# copy-then-prune): copy the current set first, then remove owned
|
|
965
|
+
# destination entries ACTIVE_AGENTS_SRC (the bundle, or the active
|
|
966
|
+
# model-profile variant) no longer ships. Never prune-then-copy — an
|
|
967
|
+
# interrupted run must never leave the user with fewer agents than before.
|
|
968
|
+
# Removal population is the destination directory (D2); ownership test is
|
|
969
|
+
# the massa-ai- name prefix (install.sh:775-777).
|
|
970
|
+
for f in "$TARGET/agents/"massa-ai-*.md; do
|
|
971
|
+
[[ -f "$f" ]] || continue
|
|
972
|
+
[[ -f "$ACTIVE_AGENTS_SRC/$(basename "$f")" ]] && continue
|
|
973
|
+
rm -f "$f"
|
|
974
|
+
vecho " - removed retired specialist $(basename "$f")"
|
|
975
|
+
done
|
|
976
|
+
|
|
862
977
|
install_variant_tree
|
|
863
978
|
fi
|
|
864
979
|
|
package/package.json
CHANGED
package/skills/massa-ai/SKILL.md
CHANGED
|
@@ -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
|
|
49
|
+
server or Synapse is unavailable. The full tool surface includes 59 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
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# massa-ai Tool Contracts (
|
|
1
|
+
# massa-ai Tool Contracts (59 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
|
|
@@ -103,6 +103,8 @@ depends on exact content.
|
|
|
103
103
|
| `handoff_accept` | Accept an open handoff (open→accepted) | Req: `id`. Opt: `projectId`. |
|
|
104
104
|
| `handoff_cancel` | Cancel/expire an open handoff | Req: `id`. Opt: `projectId`. |
|
|
105
105
|
| `handoff_list_pending` | List open handoffs, oldest-first | Req: `projectId`. Opt: `targetAgent`. |
|
|
106
|
+
| `handoff_update` | Edit targetAgent/summary/openQuestions/nextSteps/files by id | Req: `id`. Opt: `projectId`, `targetAgent`, `summary`, `openQuestions`, `nextSteps`, `files` — at least one editable field required. Unknown/restricted fields (`status`, `acceptedAt`, etc.) are rejected by name. |
|
|
107
|
+
| `handoff_delete` | Hard-delete a handoff, any status | Req: `id`. Opt: `projectId`. |
|
|
106
108
|
|
|
107
109
|
## MCP Capability Matrix — Auto-improvement (Proposals)
|
|
108
110
|
|
|
@@ -111,6 +113,9 @@ depends on exact content.
|
|
|
111
113
|
| `list_proposals` | List pending auto-improvement proposals, newest-first | Req: `projectId`. |
|
|
112
114
|
| `approve_proposal` | Approve a proposal; applies the memory edit | Req: `id`. Opt: `projectId`, `source`. |
|
|
113
115
|
| `reject_proposal` | Reject a proposal (no edit applied) | Req: `id`. Opt: `projectId`, `reason`. |
|
|
116
|
+
| `create_proposal` | Create a pending proposal by hand | Req: `projectId`, `kind` (`memory.create`\|`memory.update`\|`memory.tag`), `payload`. Opt: `rationale`, `targetMemoryId`. `payload` is validated against the same per-kind rules the store enforces on read. |
|
|
117
|
+
| `update_proposal` | Edit rationale/payload by id, any status | Req: `id`. Opt: `projectId`, `rationale`, `payload` — at least one editable field required. `kind`/`targetMemoryId`/`status`/`decidedAt` are immutable. A `payload` edit re-validates against the row's existing `kind`. |
|
|
118
|
+
| `delete_proposal` | Hard-delete a proposal, any status | Req: `id`. Opt: `projectId`. Deleting an approved proposal does not reverse its applied memory edit. |
|
|
114
119
|
|
|
115
120
|
## MCP Capability Matrix — Passive Capture
|
|
116
121
|
|