@massa-ai/cursor-plugin 1.60.0 → 1.60.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.60.0",
3
+ "version": "1.60.1",
4
4
  "description": "massa-ai — semantic code search, memory, and context compression for Cursor"
5
5
  }
@@ -187,6 +187,93 @@ export function resolveHookApiKey(): string {
187
187
  return "";
188
188
  }
189
189
 
190
+ // ── Session-start drift line (agent-runtime-drift T06) ─────────────────────
191
+
192
+ /** Same XDG resolution as getHookConfigPath — duplicated for the same
193
+ * dependency-freeze reason, pinned by tests on both sides. */
194
+ export function getInstallStatePath(): string {
195
+ const xdg = process.env.XDG_CONFIG_HOME;
196
+ const base = xdg && xdg.trim() ? xdg : path.join(homedir(), ".config");
197
+ return path.join(base, "massa-ai", "install-state.json");
198
+ }
199
+
200
+ function readJsonAt(filePath: string): Record<string, unknown> | null {
201
+ try {
202
+ const parsed = JSON.parse(readFileSync(filePath, "utf8"));
203
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed)
204
+ ? (parsed as Record<string, unknown>)
205
+ : null;
206
+ } catch {
207
+ return null;
208
+ }
209
+ }
210
+
211
+ /**
212
+ * Pinned one-key read (`^model:`) — deliberately NOT the shared frontmatter
213
+ * parser: this binary's dependency freeze (see getHookConfigPath) forbids
214
+ * importing @massa-ai/shared. The cross-side test pins this regex against the
215
+ * shared parser's output (spec INV B3), so the writer and this reader cannot
216
+ * silently disagree about what `model:` means.
217
+ */
218
+ function readModelLine(filePath: string): string | null {
219
+ try {
220
+ const match = /^model:\s*(.+)\r?$/m.exec(readFileSync(filePath, "utf8"));
221
+ const value = match?.[1]?.trim();
222
+ return value ? value.replace(/^["']|["']$/g, "") : null;
223
+ } catch {
224
+ return null;
225
+ }
226
+ }
227
+
228
+ /**
229
+ * The ≤2-line session-start drift report (agent-runtime-drift): version
230
+ * drift (live bundle vs recorded state), agent drift (active investigator
231
+ * model vs the recorded profile's variant), and the host env override that
232
+ * nullifies every per-agent model at runtime. Null when every recording
233
+ * agrees — silent when healthy. Read-only (INV5); every failure degrades to
234
+ * fewer lines, never a throw. The fix path is the existing profile switch,
235
+ * never the hook.
236
+ */
237
+ export function buildSessionStartDoctorLine(
238
+ pluginRoot: string,
239
+ env: Readonly<Record<string, string | undefined>>,
240
+ ): string | null {
241
+ const lines: string[] = [];
242
+
243
+ const sourceVersion = readJsonAt(path.join(pluginRoot, ".claude-plugin", "plugin.json"))?.version;
244
+ const state = readJsonAt(getInstallStatePath());
245
+ const platforms = state?.platforms as Record<string, Record<string, unknown>> | undefined;
246
+ const claude = platforms?.claude;
247
+ const stateVersion = claude?.plugin?.version;
248
+ if (typeof sourceVersion === "string" && typeof stateVersion === "string" && sourceVersion !== stateVersion) {
249
+ lines.push(
250
+ `[massa-ai] version drift: live bundle ${sourceVersion} vs recorded ${stateVersion} — update the plugin or re-run the installer`,
251
+ );
252
+ }
253
+
254
+ const profile = claude?.modelProfile?.profile;
255
+ if (typeof profile === "string" && profile) {
256
+ const activeModel = readModelLine(path.join(pluginRoot, "agents", "massa-ai-investigator.md"));
257
+ const variantModel = readModelLine(
258
+ path.join(pluginRoot, "agent-profiles", profile, "massa-ai-investigator.md"),
259
+ );
260
+ if (activeModel && variantModel && activeModel !== variantModel) {
261
+ lines.push(
262
+ `[massa-ai] agent drift: investigator ${activeModel} vs ${profile} variant ${variantModel} — re-run the profile switch`,
263
+ );
264
+ }
265
+ }
266
+
267
+ const override = env.CLAUDE_CODE_SUBAGENT_MODEL;
268
+ if (typeof override === "string" && override.trim()) {
269
+ lines.push(
270
+ `[massa-ai] CLAUDE_CODE_SUBAGENT_MODEL=${override.trim()} overrides every per-agent model at runtime`,
271
+ );
272
+ }
273
+
274
+ return lines.length ? lines.slice(0, 2).join("\n") : null;
275
+ }
276
+
190
277
  // ── POST helper ─────────────────────────────────────────────────────────────
191
278
 
192
279
  export function postObservation(
@@ -299,6 +386,17 @@ export async function main(stdinInput?: string): Promise<void> {
299
386
 
300
387
  const projectId = resolveProjectId(sessionId, cwd);
301
388
 
389
+ // agent-runtime-drift (T06): session-start prints a bounded drift line when
390
+ // the recordings disagree (read-only, silent when healthy — INV5).
391
+ if (subcommand === "session-start") {
392
+ try {
393
+ const doctorLine = buildSessionStartDoctorLine(path.resolve(import.meta.dirname, ".."), process.env);
394
+ if (doctorLine) process.stdout.write(doctorLine + "\n");
395
+ } catch {
396
+ // silent-degrade: drift reporting must never block the agent
397
+ }
398
+ }
399
+
302
400
  const baseUrl = process.env.MASSA_AI_API_BASE || "http://localhost:3333";
303
401
  const hookUrl = `${baseUrl}/api/v1/hook`;
304
402
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@massa-ai/cursor-plugin",
3
- "version": "1.60.0",
3
+ "version": "1.60.1",
4
4
  "description": "massa-ai plugin for Cursor — semantic code search, memory, and context compression",
5
5
  "files": [
6
6
  "agents",
@@ -303,7 +303,7 @@ like every other dispatch.
303
303
  ```md
304
304
  🤖 [Agent Started] Investigator — model opus, effort high. Scope: the four emitters.
305
305
  🤖 [Agent Started] Designer — model/effort unknown (no installed agent file at
306
- ~/.claude/plugins/cache/massa-ai/massa-ai/1.48.0/agents/massa-ai-designer.md). Dispatching anyway.
306
+ <liveRoot>/agents/massa-ai-designer.md). Dispatching anyway.
307
307
  ```
308
308
 
309
309
  That second line is a measured case, not a hypothetical: on a machine with plugin
@@ -316,7 +316,7 @@ this table so the two cannot drift silently:
316
316
 
317
317
  | Host | Installed agents directory | Glob | Model / effort keys |
318
318
  | --- | --- | --- | --- |
319
- | Claude — marketplace route | `<marketplaceRoot>/agents` a *versioned* bundle root, e.g. `~/.claude/plugins/cache/massa-ai/massa-ai/1.48.0/agents` | `massa-ai-*.md` | `model:` / `effort:` |
319
+ | Claude — marketplace route | `<marketplaceRoot>/agents`, where `<marketplaceRoot>` is `resolveClaudeMarketplaceInstall`'s live root: for a **directory-source** marketplace the host loads the plugin LIVE from the source bundle — e.g. `<repo>/apps/claude-plugin/agents`; for any other kind it is the *versioned* cache snapshot, e.g. `~/.claude/plugins/cache/massa-ai/massa-ai/1.48.0/agents` (a stale-able snapshot — never hardcode it; read `profile_list`'s `liveRoot`) | `massa-ai-*.md` | `model:` / `effort:` |
320
320
  | Claude — file route | `~/.claude/agents` | `massa-ai-*.md` | `model:` / `effort:` |
321
321
  | Codex | `~/.codex/agents` | `massa-ai-*.toml` | `model` / `model_reasoning_effort` |
322
322
  | OpenCode | `~/.config/opencode/agents` | `massa-ai-*.md` | `model:` / `reasoningEffort:` |