@massa-ai/claude-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.
- package/.claude-plugin/plugin.json +1 -1
- package/install.sh +88 -8
- package/package.json +1 -1
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) {
|
package/package.json
CHANGED