loadout-ai 0.5.1 → 0.5.2

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/CHANGELOG.md CHANGED
@@ -2,6 +2,17 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 0.5.2 - 2026-07-21
6
+
7
+ ### Fixed
8
+
9
+ - Revalidate and apply the exact skill-and-agent activation delta shown in a project
10
+ preview instead of broadening package selections across every requested agent.
11
+ - Reject browser-testing skills and Playwright MCP recommendations for CLI-only
12
+ TypeScript projects without a browser or frontend signal.
13
+ - Replace unexplained ranking numbers and a duplicated full activation delta with a
14
+ concise exact-transaction summary in the default project optimization output.
15
+
5
16
  ## 0.5.1 - 2026-07-21
6
17
 
7
18
  ### Fixed
package/MASTER_PLAN.md CHANGED
@@ -277,6 +277,14 @@ merged `codex/relatable-readme-hero` remote branch remains safe to delete after
277
277
  previews an unblocked removal of 29 packages/2,316 disabled records, and health
278
278
  reports `library ready (nothing active)` with explicit counts. Release as
279
279
  `0.5.1` before continuing the complete-uninstall acceptance step.
280
+ - [x] `P18-38 [TERRA]` Fix the founder-discovered asymmetric project-activation
281
+ scope bug. The `0.5.1` preview correctly budgeted 22 Codex and 18 Claude
282
+ additions, but apply-time revalidation broadened four Codex-only selectors
283
+ onto Claude. Revalidation now preserves the exact package, skill, and agent
284
+ tuple from the preview; a two-agent regression proves exact 30/18 applied
285
+ counts. CLI-only TypeScript projects also reject browser-testing and
286
+ Playwright false positives, and the default preview removes unexplained scores
287
+ and duplicated delta output. Release as `0.5.2` before resuming activation.
280
288
 
281
289
  ### Release 0.3 lifecycle hardening
282
290
 
package/README.md CHANGED
@@ -26,7 +26,7 @@
26
26
  </p>
27
27
 
28
28
  > [!IMPORTANT]
29
- > Installation is version-pinned so the code you test matches these docs. The commands below target `loadout-ai@0.5.1`; review the preview before every apply.
29
+ > Installation is version-pinned so the code you test matches these docs. The commands below target `loadout-ai@0.5.2`; review the preview before every apply.
30
30
 
31
31
  ## How it works
32
32
 
@@ -76,7 +76,7 @@ Skills, plugins, MCP servers, and agent settings tend to accumulate one experime
76
76
  You need Node.js 20 or newer and Git.
77
77
 
78
78
  ```bash
79
- npm install --global loadout-ai@0.5.1
79
+ npm install --global loadout-ai@0.5.2
80
80
  loadout --version
81
81
  loadout guide
82
82
  ```
package/dist/src/cli.js CHANGED
@@ -263,7 +263,7 @@ async function runSetup(options) {
263
263
  reader?.close();
264
264
  }
265
265
  }
266
- const LOADOUT_VERSION = "0.5.1";
266
+ const LOADOUT_VERSION = "0.5.2";
267
267
  function durableSchedulerLauncher() {
268
268
  return [
269
269
  join(dirname(process.execPath), process.platform === "win32" ? "npx.cmd" : "npx"),
@@ -57,6 +57,10 @@ function hasProjectCompatibility(name, project) {
57
57
  if (/(?:publish-to-pages|github-pages|cloudflare-pages)/.test(name) &&
58
58
  !project.frameworks.some((framework) => ["react", "next.js", "vue", "svelte"].includes(framework)))
59
59
  return false;
60
+ if (/playwright|e2e|webapp-testing|browser-testing/.test(name) &&
61
+ !project.frameworks.includes("playwright") &&
62
+ !project.frameworks.some((framework) => ["react", "next.js", "vue", "svelte"].includes(framework)))
63
+ return false;
60
64
  return true;
61
65
  }
62
66
  const SOURCE_PRIORITY = {
@@ -396,11 +400,20 @@ export function formatProjectActivation(plan) {
396
400
  ...plan.agentPlans.flatMap((agentPlan) => [
397
401
  `${agentPlan.displayName}: ${agentPlan.activeBefore} active (${agentPlan.managedBefore} managed, ${agentPlan.unmanagedBefore} unmanaged); ${agentPlan.capacity}/${plan.limit} slots available`,
398
402
  `Proposed additions for ${agentPlan.displayName}: ${agentPlan.selected.length}`,
399
- ...agentPlan.selected.map((item) => ` + ${item.selector} [${item.score}] — ${item.reasons.join(", ")}`),
403
+ ...agentPlan.selected.map((item) => ` + ${item.selector} — ${item.reasons.join(", ")}`),
400
404
  ...agentPlan.alternatives.map((item) => ` = ${item.unitId}: selected ${item.selected}; deferred equivalent source(s): ${item.deferred.join(", ")}`),
401
405
  ]),
402
406
  ...(plan.activation
403
- ? ["", "Exact activation delta:", formatActivationPlan(plan.activation)]
407
+ ? plan.activation.blocked
408
+ ? [
409
+ "",
410
+ "Blocked activation details:",
411
+ formatActivationPlan(plan.activation),
412
+ ]
413
+ : [
414
+ "",
415
+ `Activation transaction: ${plan.activation.changes.length} exact skill change(s), applied together with one rollback snapshot.`,
416
+ ]
404
417
  : []),
405
418
  ...plan.warnings.map((warning) => `Warning: ${warning}`),
406
419
  ].join("\n");
@@ -287,12 +287,39 @@ export async function planActivationChange(action, packageIds, options = {}) {
287
287
  function activationKey(record) {
288
288
  return `${record.packageId}\0${record.agent}\0${record.unitId ?? ""}`;
289
289
  }
290
+ async function revalidateExactActivationPlan(plan) {
291
+ const selectorsByAgent = new Map();
292
+ for (const change of plan.changes) {
293
+ const selectors = selectorsByAgent.get(change.agent) ?? [];
294
+ selectors.push(change.unitId ? `${change.packageId}/${change.unitId}` : change.packageId);
295
+ selectorsByAgent.set(change.agent, selectors);
296
+ }
297
+ const fragments = await Promise.all([...selectorsByAgent].map(([agent, selectors]) => planActivationChange(plan.action, selectors, { agents: [agent] })));
298
+ const changes = fragments.flatMap((fragment) => fragment.changes);
299
+ const plannedKeys = plan.changes.map(activationKey);
300
+ const freshKeys = changes.map(activationKey);
301
+ if (new Set(plannedKeys).size !== plannedKeys.length ||
302
+ new Set(freshKeys).size !== freshKeys.length ||
303
+ plannedKeys.length !== freshKeys.length ||
304
+ plannedKeys.some((key) => !freshKeys.includes(key)))
305
+ throw new Error("Activation selection changed after preview; generate a new plan before applying.");
306
+ const order = new Map(plannedKeys.map((key, index) => [key, index]));
307
+ changes.sort((left, right) => order.get(activationKey(left)) - order.get(activationKey(right)));
308
+ const warnings = fragments.flatMap((fragment) => fragment.warnings);
309
+ return {
310
+ action: plan.action,
311
+ packages: plan.packages,
312
+ ...(plan.requestedAgents ? { requestedAgents: plan.requestedAgents } : {}),
313
+ changes,
314
+ skipped: fragments.flatMap((fragment) => fragment.skipped),
315
+ blocked: warnings.length > 0,
316
+ warnings,
317
+ };
318
+ }
290
319
  export async function applyActivationChange(plan, options = {}) {
291
320
  const applied = await runMutationTransaction(async () => {
292
321
  await options.preflight?.();
293
- const fresh = await planActivationChange(plan.action, plan.packages, {
294
- ...(plan.requestedAgents ? { agents: plan.requestedAgents } : {}),
295
- });
322
+ const fresh = await revalidateExactActivationPlan(plan);
296
323
  if (fresh.blocked)
297
324
  throw new Error(fresh.warnings.join("; "));
298
325
  if (!fresh.changes.length)
@@ -179,8 +179,10 @@ export function recommendPackages(signals, catalog) {
179
179
  if (signals.frameworks.some((item) => ["react", "next.js", "vue", "svelte"].includes(item)))
180
180
  add("ui-ux-pro-max", `Frontend framework detected: ${signals.frameworks.join(", ")}.`, "high");
181
181
  if (signals.frameworks.includes("playwright") ||
182
- signals.languages.includes("javascript/typescript"))
183
- add("playwright-mcp", "Browser verification is useful for this web-capable project.", signals.frameworks.includes("playwright") ? "high" : "medium");
182
+ signals.frameworks.some((item) => ["react", "next.js", "vue", "svelte"].includes(item)))
183
+ add("playwright-mcp", signals.frameworks.includes("playwright")
184
+ ? "Playwright is already configured in this project."
185
+ : "Browser verification may help test the detected frontend framework.", signals.frameworks.includes("playwright") ? "high" : "medium");
184
186
  if (signals.files.includes(".git"))
185
187
  add("github-mcp-server", "A Git repository was detected; GitHub tools may help with issues and pull requests.", "medium");
186
188
  if (signals.roles.includes("obsidian-vault"))
@@ -183,7 +183,7 @@ cleanup deliberately deletes Loadout's snapshots, so it is the last lifecycle te
183
183
  ## Troubleshooting and recovery
184
184
 
185
185
  - **`loadout` is not found after installation:** confirm `npm install --global
186
- loadout-ai@0.5.1` completed, run `hash -r`, and confirm npm's global binary
186
+ loadout-ai@0.5.2` completed, run `hash -r`, and confirm npm's global binary
187
187
  directory is on `PATH`. For a source checkout, run `npm run build` and `npm link`.
188
188
  - **A preview asks for `--approve-risk`:** read the reported scripts, domains,
189
189
  credentials, binaries, or instruction findings. If you accept that specific plan,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "loadout-ai",
3
- "version": "0.5.1",
3
+ "version": "0.5.2",
4
4
  "private": false,
5
5
  "license": "MIT",
6
6
  "description": "Universal upgrade manager for AI coding agents",