@tryaura/aura-cli 0.2.0 → 0.3.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,4 +1,4 @@
1
- import { G as src_default$4, I as createAuraManifestWriteOperation, K as pluralize, S as readManagedBlock, W as SHARED_INSTRUCTIONS_TEMPLATE, _ as planSharedSkillTreeUpdate, b as reconcileManagedSnippet, c as planManifestMcpConvergence, f as isAuraOwnedSkillTarget, g as managedContentRevisionStatus, h as skillDeploymentStatus, i as planMcpSecretRemediation, l as planMcpServerRemoval, m as sharedSkillsRoot, n as canPlanMcpSecretRemediation, o as mcpConvergenceBlockers, p as planSkillDeployment, q as displayPath, t as planSharedInstructionLink, v as reconcileParsedManagedBlock, y as diffManagedSnippet } from "./shared-link-plan-DVgOyqP9.js";
1
+ import { A as displayPath, D as SHARED_INSTRUCTIONS_TEMPLATE, O as src_default$4, d as sharedSkillsRoot, f as skillDeploymentStatus, i as mcpConvergenceBlockers, k as pluralize, l as isAuraOwnedSkillTarget, m as planSharedSkillTreeUpdate, o as planManifestMcpConvergence, p as managedContentRevisionStatus, s as planMcpServerRemoval, t as planSharedInstructionLink, u as planSkillDeployment, v as createAuraManifestWriteOperation } from "./shared-link-plan-C57dSoid.js";
2
2
  import { EMPTY_COLLECTION, McpWriteError, SHARED_INSTRUCTIONS_TEMPLATE_TOKEN, TIMEOUT_EXIT_CODE, collectJsonMcpServers, collectMcpServers, configStringArray, configStringRecord, defineAdapter, defineCheck, definePlugin, detectExecutable, detectLineEnding, inspectJsonMcpSecrets, isConfigRecord, isFileReference, jsonMcpEntry, jsonMcpSecretTransform, maskMarkdownCode, mcpCommandEntry, mcpEnvironmentVariableNames, mcpWriteResult, normalizeMcpServerDefinition, parseAtImports, parseConfigObject, parseInstalledSkills, parseJsonMcpConfig, redactMcpArguments, referenceTargetPath, sanitizeMcpUrl, skillDirectorySpecs, splitSourceLines, stdioTransport, writeJsonMcpServers } from "@tryaura/aura-sdk";
3
3
  import { dirname, extname, isAbsolute, join, resolve, sep } from "node:path";
4
4
  import { parse, stringify } from "smol-toml";
@@ -243,8 +243,7 @@ const claudeCodeAdapter = defineAdapter({
243
243
  },
244
244
  sharedLink: {
245
245
  entryPath: "~/.claude/CLAUDE.md",
246
- kind: "import-line",
247
- lineTemplate: `@${SHARED_INSTRUCTIONS_TEMPLATE_TOKEN}`
246
+ kind: "symlink"
248
247
  },
249
248
  supportedRange: ">=2.1.0 <3.0.0"
250
249
  });
@@ -1194,12 +1193,10 @@ function validateToml(content, message) {
1194
1193
  //#endregion
1195
1194
  //#region ../../plugins/adapter-codex/src/project-directories.ts
1196
1195
  /**
1197
- * Every directory of the current project Codex consults, from `cwd` outward to the repository root.
1196
+ * Every directory Codex searches for project instructions, from `cwd` to the repository root.
1198
1197
  *
1199
- * Two features need exactly this list. Codex keys `[projects."..."]` by the directory it was
1200
- * launched in, which is frequently a subdirectory of the repository rather than its root, and it
1201
- * reads one `AGENTS.md` per directory from the root down to `cwd`. Both are the same walk read in
1202
- * opposite directions, so they share it rather than each keeping a copy.
1198
+ * Codex reads one `AGENTS.md` per directory from the root down to `cwd`. Project trust deliberately
1199
+ * does not share this walk: Codex checks only the exact cwd and the primary Git checkout for trust.
1203
1200
  *
1204
1201
  * Without a repository root there is nothing to walk to: every ancestor of the working directory is
1205
1202
  * a different project as far as Codex is concerned, so only `cwd` is considered. The same answer
@@ -1351,11 +1348,10 @@ function remainingBytes(selected, files, remaining) {
1351
1348
  /**
1352
1349
  * Reads the current project's trust marker from Codex's shared configuration.
1353
1350
  *
1354
- * Codex keys `[projects."..."]` by the directory it was launched in, which is frequently a
1355
- * subdirectory of the repository rather than its root. Looking only at the root would report a
1356
- * project as untrusted whenever the developer started Codex from somewhere inside it, so every
1357
- * directory from `cwd` up to and including the repository root is considered and the narrowest
1358
- * recorded answer wins.
1351
+ * Codex first checks the exact directory it was launched in, then the primary Git checkout. The
1352
+ * second identity matters for linked worktrees: Codex resolves their `.git` pointer back to the
1353
+ * main checkout, so trusting that checkout covers every associated worktree unless an exact cwd
1354
+ * entry overrides it.
1359
1355
  */
1360
1356
  function parseProjectTrust(file, lookup) {
1361
1357
  const root = parseConfigObject(file.content, parse);
@@ -1363,7 +1359,7 @@ function parseProjectTrust(file, lookup) {
1363
1359
  const projects = root["projects"];
1364
1360
  if (!isConfigRecord(projects)) return "unknown";
1365
1361
  const entries = new Map(Object.entries(projects).map(([key, value]) => [normalize(key), value]));
1366
- for (const directory of projectDirectories(lookup)) {
1362
+ for (const directory of trustDirectories(lookup)) {
1367
1363
  const project = entries.get(directory);
1368
1364
  if (!isConfigRecord(project)) continue;
1369
1365
  const trust = project["trust_level"];
@@ -1371,6 +1367,12 @@ function parseProjectTrust(file, lookup) {
1371
1367
  }
1372
1368
  return "unknown";
1373
1369
  }
1370
+ function trustDirectories(lookup) {
1371
+ const cwd = normalize(lookup.cwd);
1372
+ if (lookup.gitMainWorktreeRoot === void 0) return [cwd];
1373
+ const mainWorktreeRoot = normalize(lookup.gitMainWorktreeRoot);
1374
+ return cwd === mainWorktreeRoot ? [cwd] : [cwd, mainWorktreeRoot];
1375
+ }
1374
1376
  /** Collapses separators and trailing slashes so a config key and a scanned path compare equal. */
1375
1377
  function normalize(directory) {
1376
1378
  return resolve(directory);
@@ -1401,7 +1403,7 @@ const codexAdapter = defineAdapter({
1401
1403
  mcpWrite: writeMcpServers$1,
1402
1404
  mcpSecrets: transformMcpSecrets$1,
1403
1405
  parse: (input) => {
1404
- const { cwd, files, homeDir, projectRoot } = input;
1406
+ const { cwd, files, gitMainWorktreeRoot, homeDir } = input;
1405
1407
  const mcp = files.get(CODEX_SOURCE_IDS.mcp);
1406
1408
  const mcpConfig = mcp === void 0 ? EMPTY_MCP : parseMcpServers$1(mcp);
1407
1409
  const instructions = selectInstructionFiles(files, {
@@ -1414,7 +1416,7 @@ const codexAdapter = defineAdapter({
1414
1416
  mcpSecretSightings: mcpConfig.secretSightings ?? [],
1415
1417
  metadata: { [CODEX_PROJECT_TRUST_KEY]: mcp === void 0 ? "unknown" : parseProjectTrust(mcp, {
1416
1418
  cwd,
1417
- projectRoot
1419
+ gitMainWorktreeRoot
1418
1420
  }) },
1419
1421
  problems: [...shadowedEntryProblems(instructions.shadowed), ...unusableConfig(mcp, mcpConfig.malformed)],
1420
1422
  skills: parseInstalledSkills(CODEX_ADAPTER_ID, input, SKILL_DIRECTORIES),
@@ -1518,12 +1520,12 @@ var src_default$2 = definePlugin({
1518
1520
  * silently stop finding anything, so the contract is exported and imported rather than retyped.
1519
1521
  *
1520
1522
  * `rulesProject` names the `.cursor/rules` directory slot; each discovered child file gets a
1521
- * derived id of the form `cursor.rules.project/<encoded-name>`, and `aura` is the derived id
1522
- * reserved for the rule Aura itself maintains there.
1523
+ * derived id of the form `cursor.rules.project/<encoded-name>`. No id is reserved for a rule Aura
1524
+ * writes, because Aura writes none: a rule left behind by an older version is read as the ordinary
1525
+ * project rule it is.
1523
1526
  */
1524
1527
  const CURSOR_SOURCE_IDS = Object.freeze({
1525
1528
  agents: "cursor.rules.project.agents",
1526
- aura: "cursor.rules.project/aura-owned",
1527
1529
  legacyRules: "cursor.rules.project.legacy",
1528
1530
  mcpGlobal: "cursor.mcp.global",
1529
1531
  mcpProject: "cursor.mcp.project",
@@ -1809,6 +1811,7 @@ function normalizeReference(reference) {
1809
1811
  //#region ../../plugins/adapter-cursor/src/adapter.ts
1810
1812
  const cursorAdapter = defineAdapter({
1811
1813
  capabilities: { instructions: {
1814
+ globalSharedLink: "not-applicable",
1812
1815
  importStyle: "at-import",
1813
1816
  loading: "import-graph"
1814
1817
  } },
@@ -1837,18 +1840,6 @@ const cursorAdapter = defineAdapter({
1837
1840
  unusableMcpServers: mcpFiles.flatMap(({ config }) => config.unusable)
1838
1841
  };
1839
1842
  },
1840
- sharedLink: {
1841
- entryPath: "./.cursor/rules/aura.mdc",
1842
- kind: "native-copy",
1843
- lineTemplate: [
1844
- "---",
1845
- "alwaysApply: true",
1846
- "---",
1847
- "",
1848
- `@file ${SHARED_INSTRUCTIONS_TEMPLATE_TOKEN}`,
1849
- ""
1850
- ].join("\n")
1851
- },
1852
1843
  supportedRange: ">=0.45.0 <4.0.0"
1853
1844
  });
1854
1845
  /**
@@ -1900,13 +1891,6 @@ function cursorFiles({ environment, files }) {
1900
1891
  path: join(environment.cwd, "AGENTS.md"),
1901
1892
  scope: "project"
1902
1893
  },
1903
- {
1904
- id: CURSOR_SOURCE_IDS.aura,
1905
- kind: "instructions",
1906
- optional: true,
1907
- path: join(environment.cwd, ".cursor", "rules", "aura.mdc"),
1908
- scope: "project"
1909
- },
1910
1894
  {
1911
1895
  id: CURSOR_SOURCE_IDS.mcpGlobal,
1912
1896
  kind: "mcp",
@@ -1928,16 +1912,13 @@ function discoveredRuleSpecs(files) {
1928
1912
  const specs = [];
1929
1913
  for (const file of files.values()) {
1930
1914
  if (!isRuleSourceId(file.spec.id) || file.entries === void 0) continue;
1931
- for (const entry of file.entries) {
1932
- if (file.spec.id === CURSOR_SOURCE_IDS.rulesProject && entry === "aura.mdc") continue;
1933
- specs.push({
1934
- id: `${file.spec.id}/${encodeURIComponent(entry)}`,
1935
- kind: "instructions",
1936
- optional: true,
1937
- path: join(file.spec.path, entry),
1938
- scope: "project"
1939
- });
1940
- }
1915
+ for (const entry of file.entries) specs.push({
1916
+ id: `${file.spec.id}/${encodeURIComponent(entry)}`,
1917
+ kind: "instructions",
1918
+ optional: true,
1919
+ path: join(file.spec.path, entry),
1920
+ scope: "project"
1921
+ });
1941
1922
  }
1942
1923
  return specs;
1943
1924
  }
@@ -2162,7 +2143,7 @@ const env004 = defineCheck({
2162
2143
  detect: (model) => detectConflicts(model),
2163
2144
  explain: `Claude Code can appear unable to work when its effective default permission mode is plan or dontAsk. Codex skips project-scoped configuration when a project is untrusted, so Aura also reports projects that are not explicitly trusted.
2164
2145
 
2165
- Claude Code: edit the settings file named by the finding and choose default or acceptEdits as appropriate. Codex: edit ~/.codex/config.toml and add a \`[projects."<project-root>"]\` section with \`trust_level = "trusted"\` after reviewing the repository.`,
2146
+ Claude Code: edit the settings file named by the finding and choose default or acceptEdits as appropriate. Codex: after reviewing the repository, edit ~/.codex/config.toml and trust the exact working directory or the primary Git checkout. Codex resolves linked worktrees back to that primary checkout.`,
2166
2147
  fix: (finding, model) => guidedFix$2(finding, model),
2167
2148
  fixability: "guided",
2168
2149
  id: "ENV-004",
@@ -2180,7 +2161,7 @@ function detectConflicts(model) {
2180
2161
  function claudeFindings(app) {
2181
2162
  const effective = readClaudePermissionMode(app);
2182
2163
  if (effective === void 0 || !RESTRICTIVE_MODES.has(effective.mode)) return [];
2183
- const path = sourcePath$2(app, effective.sourceId);
2164
+ const path = sourcePath$1(app, effective.sourceId);
2184
2165
  return [{
2185
2166
  details: effective.mode === "plan" ? "Plan mode prevents source edits and most commands. Choose a normal working mode if this was not intentional." : "dontAsk denies tools that were not pre-approved, which can make requests stop without a permission prompt.",
2186
2167
  id: `claude-permission-mode:${effective.mode}`,
@@ -2196,9 +2177,9 @@ function claudeFindings(app) {
2196
2177
  function codexFindings(app) {
2197
2178
  const trust = readCodexProjectTrust(app) ?? "unknown";
2198
2179
  if (trust === "trusted" || trust === "unreadable") return [];
2199
- const path = sourcePath$2(app, CODEX_SOURCE_IDS.mcp);
2180
+ const path = sourcePath$1(app, CODEX_SOURCE_IDS.mcp);
2200
2181
  return [{
2201
- details: trust === "untrusted" ? "Codex explicitly marks this project untrusted and skips its project-scoped configuration." : "Codex has no trusted entry for this project or any directory inside it, so project-scoped configuration may be unavailable until trust is confirmed.",
2182
+ details: trust === "untrusted" ? "Codex explicitly marks this project untrusted and skips its project-scoped configuration." : "Codex has no trusted entry for this working directory or its primary Git checkout, so project-scoped configuration may be unavailable until trust is confirmed.",
2202
2183
  id: `codex-project-trust:${trust}`,
2203
2184
  ...path === void 0 ? {} : { locations: [{ path }] },
2204
2185
  message: trust === "untrusted" ? "Codex marks this project as untrusted." : "Codex does not mark this project as trusted.",
@@ -2209,7 +2190,7 @@ function codexFindings(app) {
2209
2190
  }
2210
2191
  }];
2211
2192
  }
2212
- function sourcePath$2(app, sourceId) {
2193
+ function sourcePath$1(app, sourceId) {
2213
2194
  return app.sourceFiles.find((file) => file.spec.id === sourceId)?.spec.path;
2214
2195
  }
2215
2196
  function guidedFix$2(finding, model) {
@@ -2223,11 +2204,14 @@ function guidedFix$2(finding, model) {
2223
2204
  summary: "Change Claude Code's restrictive default permission mode."
2224
2205
  };
2225
2206
  }
2226
- if (appId === "codex" && model.projectRoot !== void 0) return {
2227
- manualSteps: [`After reviewing the repository, add [projects.${JSON.stringify(model.projectRoot)}] with trust_level = "trusted" in ~/.codex/config.toml.`, "Restart Codex in the project and run `aura check` again."],
2228
- operations: [],
2229
- summary: "Trust the current project in Codex."
2230
- };
2207
+ if (appId === "codex" && model.projectRoot !== void 0) {
2208
+ const trustRoot = model.gitMainWorktreeRoot ?? model.cwd;
2209
+ return {
2210
+ manualSteps: [`After reviewing the repository, add [projects.${JSON.stringify(trustRoot)}] with trust_level = "trusted" in ~/.codex/config.toml.`, "Restart Codex in the project and run `aura check` again."],
2211
+ operations: [],
2212
+ summary: "Trust the current project in Codex."
2213
+ };
2214
+ }
2231
2215
  }
2232
2216
  //#endregion
2233
2217
  //#region ../../plugins/checks-core/src/hashing.ts
@@ -2756,7 +2740,7 @@ const legacyInstructionsCheck = defineCheck({
2756
2740
  detect: (model) => legacyDocuments(model.instructionFiles).map((legacy) => toFinding(legacy, model)),
2757
2741
  explain: `Legacy instruction files are easy to forget after changing agent applications or configuration formats. They may still be loaded unexpectedly, or hold useful guidance that newer application files never received.
2758
2742
 
2759
- Re-run the check with \`--fix --interactive\` to review the suggested migration steps, then use the \`setup\` command to consolidate selected content. Aura archives originals through its reversible backup history instead of deleting them silently.`,
2743
+ Re-run the check with \`--fix\` to review the suggested migration steps, then use the \`setup\` command to consolidate selected content. Aura archives originals through its reversible backup history instead of deleting them silently.`,
2760
2744
  fix: guidedFix$1,
2761
2745
  fixability: "guided",
2762
2746
  id: "INS-004",
@@ -3466,7 +3450,7 @@ const instructionLinkIntegrityCheck = defineCheck({
3466
3450
  detect: detectLinkProblems,
3467
3451
  explain: `Instruction imports must point to files the application can load without cycles or excessive nesting. Broken or unsupported chains silently discard guidance and can leave an agent running with only part of the intended instructions.
3468
3452
 
3469
- Re-run the check with \`--fix --interactive\` to review the affected source and target paths. Create or correct missing targets, remove one edge from cycles, and flatten chains that exceed the named application's supported depth.`,
3453
+ Re-run the check with \`--fix\` to review the affected source and target paths. Create or correct missing targets, remove one edge from cycles, and flatten chains that exceed the named application's supported depth.`,
3470
3454
  fix: guidedFix,
3471
3455
  fixability: "guided",
3472
3456
  id: "INS-006",
@@ -4066,9 +4050,9 @@ const sharedInstructionLinksCheck = defineCheck({
4066
4050
  detect(model) {
4067
4051
  return model.apps.flatMap((app) => detectMissingLink(app, model));
4068
4052
  },
4069
- explain: `Each detected agent application must load the canonical shared instructions through a mechanism its adapter understands. A missing import, native copy, or symlink leaves that application working from different rules even when the shared source is correct.
4053
+ explain: `Each detected agent application must load the canonical shared instructions through a mechanism its adapter understands. A missing import, native copy, or symlink leaves that application working from different rules even when the shared source is correct. Applications with no instruction file of their own in the home directory are out of scope: Cursor, for one, keeps its global User Rules inside its own settings, where Aura cannot write.
4070
4054
 
4071
- Re-run the check with \`--fix\` to add the adapter-supported link without replacing unrelated instructions. Inspect the preview first, apply it, then restart the affected application if it caches instruction files.`,
4055
+ Re-run the check with \`--fix\` to add the adapter-supported link without replacing unrelated instructions. Inspect the preview first, apply it, then restart the affected application if it caches instruction files. An application whose instruction file already holds guidance of its own is a manual finding instead, because \`--fix\` never overwrites one: run \`aura setup\` and choose instruction consolidation to merge that content into the shared source, which archives the original before the link takes its place.`,
4072
4056
  findingGroup: SHARED_INSTRUCTION_GROUP,
4073
4057
  fix(finding, model) {
4074
4058
  const app = model.apps.find((candidate) => candidate.adapterId === findingAppId(finding));
@@ -4083,6 +4067,10 @@ Re-run the check with \`--fix\` to add the adapter-supported link without replac
4083
4067
  });
4084
4068
  function detectMissingLink(app, model) {
4085
4069
  if (app.synthetic === true) return [];
4070
+ if (app.capabilities?.instructions?.globalSharedLink === "not-applicable") return [];
4071
+ return detectRequiredLink(app, model);
4072
+ }
4073
+ function detectRequiredLink(app, model) {
4086
4074
  const shared = app.sharedLink;
4087
4075
  const sharedPath = resolve(model.sharedInstructions.path);
4088
4076
  if (shared !== void 0 && linksToShared(app, shared, sharedPath)) return [];
@@ -4241,431 +4229,47 @@ function isScope$1(value) {
4241
4229
  return SCOPES.some((scope) => scope === value);
4242
4230
  }
4243
4231
  //#endregion
4244
- //#region ../../plugins/checks-core/src/mgd-001-documents.ts
4245
- const SHARED_SOURCE_ID = "aura.shared-instructions";
4246
- /**
4247
- * Every document whose managed block this check owns, parsed.
4248
- *
4249
- * Memoized per model because detection and remediation ask the same question: `detect` walks the
4250
- * set once, then each finding's guided fixes look its document back up. Rebuilding the set per
4251
- * finding re-resolves every instruction path and re-parses every block to reach an answer that
4252
- * cannot have changed — the model is frozen for the length of a run.
4253
- */
4254
- const CACHE = /* @__PURE__ */ new WeakMap();
4255
- function managedDocuments(model) {
4256
- const cached = CACHE.get(model);
4257
- if (cached !== void 0) return cached;
4258
- const documents = /* @__PURE__ */ new Map();
4259
- if (model.sharedInstructions.content !== void 0) documents.set(resolve(model.sharedInstructions.path), managedDocument(model.sharedInstructions.content, model.sharedInstructions.path, SHARED_SOURCE_ID));
4260
- for (const document of canonicalInstructionDocuments(model.instructionFiles)) {
4261
- const path = resolve(document.path);
4262
- if (!documents.has(path) && !isSymlinkDocument(document, model)) documents.set(path, managedDocument(document.content, document.path, document.sourceId));
4263
- }
4264
- const resolved = Object.freeze([...documents.values()]);
4265
- CACHE.set(model, resolved);
4266
- return resolved;
4267
- }
4268
- /** Finds the document one finding was raised against, without rebuilding the set. */
4269
- function findManagedDocument(model, sourceId, path) {
4270
- const target = resolve(path);
4271
- return managedDocuments(model).find((document) => document.sourceId === sourceId && resolve(document.path) === target);
4272
- }
4273
- function managedDocument(content, path, sourceId) {
4274
- return Object.freeze({
4275
- content,
4276
- parsed: readManagedBlock(content),
4277
- path,
4278
- sourceId
4279
- });
4280
- }
4281
- function isSymlinkDocument(document, model) {
4282
- const path = resolve(document.path);
4283
- return model.apps.some((app) => app.sourceFiles.some((file) => file.spec.id === document.sourceId && resolve(file.spec.path) === path && file.pathKind === "symlink"));
4284
- }
4285
- //#endregion
4286
- //#region ../../plugins/checks-core/src/mgd-001-fixes.ts
4287
- const CHECK_ID$4 = "MGD-001";
4288
- function guidedFixes$2(finding, model) {
4289
- if (finding.checkId !== CHECK_ID$4) return [];
4290
- const kind = finding.metadata?.["kind"];
4291
- if (kind === "malformed") return [malformedMergeChoice(finding)];
4292
- if (kind === "unterminated-fence") return [unterminatedFenceChoice(finding)];
4293
- return kind === "hash-mismatch" ? mismatchChoices(finding, model) : [];
4294
- }
4295
- function mismatchChoices(finding, model) {
4296
- const context = mismatchContext(finding, model);
4297
- if (context === void 0) throw new Error(`The managed snippet for finding "${finding.id}" is no longer available.`);
4298
- const choices = [];
4299
- const keep = keepChoice(context, model);
4300
- choices.push(keep);
4301
- const canonical = model.availableSnippets.find((candidate) => candidate.id === context.snippet.id);
4302
- const restore = restoreChoice(context, canonical, model);
4303
- if (restore !== void 0) choices.push(restore);
4304
- choices.push(mergeChoice(context.document, context.snippet, canonical?.content));
4305
- return Object.freeze(choices);
4306
- }
4307
- function mismatchContext(finding, model) {
4308
- const document = findingDocument(finding, model);
4309
- const snippetId = finding.metadata?.["snippetId"];
4310
- if (document === void 0 || typeof snippetId !== "string") return;
4311
- if (document.parsed.status !== "present") return;
4312
- const snippet = document.parsed.block.snippets.find((candidate) => candidate.id === snippetId);
4313
- return snippet === void 0 ? void 0 : {
4314
- document,
4315
- snippet
4316
- };
4317
- }
4318
- function keepChoice(context, model) {
4319
- const { document, snippet } = context;
4320
- const keep = reconcileManagedSnippet(document.content, snippet.id, { kind: "keep" });
4321
- if (keep.status === "invalid") throw new Error(`Keep yours could not reconcile ${snippet.id}: ${keep.problems.map((problem) => problem.message).join(" ")}`);
4322
- const applied = operationsWithManifest({
4323
- content: keep.content,
4324
- hash: snippet.computedHash,
4325
- model,
4326
- path: document.path,
4327
- pinned: true,
4328
- snippetId: snippet.id
4329
- });
4330
- return {
4331
- id: "keep",
4332
- label: "Keep yours",
4333
- plan: {
4334
- ...applied.manualSteps.length === 0 ? {} : { manualSteps: [...applied.manualSteps] },
4335
- operations: applied.operations,
4336
- summary: `Adopt the edited ${snippet.id} snippet.`
4337
- }
4338
- };
4339
- }
4340
- function restoreChoice(context, canonical, model) {
4341
- if (canonical === void 0) return;
4342
- const restored = reconcileManagedSnippet(context.document.content, context.snippet.id, {
4343
- content: canonical.content,
4344
- kind: "restore"
4345
- });
4346
- if (restored.status === "invalid") throw new Error(`Restore could not reconcile ${context.snippet.id}: ${restored.problems.map((problem) => problem.message).join(" ")}`);
4347
- const applied = operationsWithManifest({
4348
- content: restored.content,
4349
- hash: canonical.hash,
4350
- model,
4351
- path: context.document.path,
4352
- pinned: false,
4353
- snippetId: context.snippet.id,
4354
- version: canonical.version
4355
- });
4356
- return {
4357
- id: "restore",
4358
- label: "Restore",
4359
- plan: {
4360
- ...applied.manualSteps.length === 0 ? {} : { manualSteps: [...applied.manualSteps] },
4361
- operations: applied.operations,
4362
- summary: `Restore ${context.snippet.id} from the registered snippet.`
4363
- }
4364
- };
4365
- }
4366
- /**
4367
- * Pairs the instruction-file write with the manifest update that records it.
4368
- *
4369
- * The write can succeed where the manifest update cannot: an unreadable manifest, or an id that
4370
- * several selections claim, leaves nothing unambiguous to edit. Applying only the file half is
4371
- * still the right outcome — the marker hash is what stops the next run overwriting the user — but
4372
- * it leaves the manifest disagreeing with disk, which is the drift the manifest exists to catch.
4373
- * So the shortfall becomes a manual step rather than a silence.
4374
- */
4375
- function operationsWithManifest(options) {
4376
- const operations = [{
4377
- content: options.content,
4378
- path: options.path,
4379
- type: "write"
4380
- }];
4381
- if (options.model.manifest.status === "missing") return {
4382
- manualSteps: [`Create ${options.model.manifest.path} and record ${options.snippetId} at hash ${options.hash}; the manifest does not exist, so this fix updates only ${options.path}.`],
4383
- operations
4384
- };
4385
- if (options.model.manifest.status === "read-only") return {
4386
- manualSteps: [`Record ${options.snippetId} at hash ${options.hash} after repairing the Aura manifest: ${options.model.manifest.problem.message}`],
4387
- operations
4388
- };
4389
- const matches = options.model.manifest.value.snippets.filter((snippet) => snippet.id === options.snippetId);
4390
- const selected = matches[0];
4391
- if (matches.length !== 1 || selected === void 0) return {
4392
- manualSteps: [`Record ${options.snippetId} at hash ${options.hash} in ${options.model.manifest.path} by hand; ${String(matches.length)} manifest entries claim that id, so this fix updates only ${options.path}.`],
4393
- operations
4394
- };
4395
- const manifest = {
4396
- ...options.model.manifest.value,
4397
- snippets: options.model.manifest.value.snippets.map((snippet) => snippet === selected ? updatedSelection(snippet, options) : snippet)
4398
- };
4399
- operations.push(createAuraManifestWriteOperation(options.model.manifest, manifest));
4400
- return {
4401
- manualSteps: [],
4402
- operations
4403
- };
4404
- }
4405
- function updatedSelection(snippet, options) {
4406
- return {
4407
- ...snippet,
4408
- hash: options.hash,
4409
- pinned: options.pinned,
4410
- version: options.version ?? snippet.version
4411
- };
4412
- }
4413
- function mergeChoice(document, snippet, canonicalContent) {
4414
- const lines = `${String(snippet.startLine)}-${String(snippet.endLine)}`;
4415
- return {
4416
- ...canonicalContent === void 0 ? {} : { details: () => diffManagedSnippet(document.path, snippet.content, canonicalContent) },
4417
- id: "merge",
4418
- label: "Merge",
4419
- plan: {
4420
- manualSteps: [canonicalContent === void 0 ? `Edit ${document.path} at lines ${lines}; the registry no longer provides ${snippet.id}, so no canonical diff is available.` : `Review the diff and edit ${document.path} at lines ${lines} to the combined content you want.`, "Run `aura check` again and choose Keep yours to adopt the result."],
4421
- operations: [],
4422
- summary: `Manually merge the edited ${snippet.id} snippet.`
4423
- }
4424
- };
4425
- }
4426
- function malformedMergeChoice(finding) {
4427
- const path = finding.locations?.[0]?.path;
4428
- return {
4429
- id: "merge",
4430
- label: "Repair manually",
4431
- plan: {
4432
- manualSteps: [path === void 0 ? "Repair the malformed Aura marker structure without changing surrounding content." : `Repair the malformed Aura marker structure in ${path} without changing surrounding content.`, "Run `aura check` again after the block parses cleanly."],
4433
- operations: [],
4434
- summary: "Repair the malformed Aura-managed block manually."
4435
- }
4436
- };
4437
- }
4438
- function unterminatedFenceChoice(finding) {
4439
- const path = finding.locations?.[0]?.path;
4440
- return {
4441
- id: "close-fence",
4442
- label: "Close the fence",
4443
- plan: {
4444
- manualSteps: [path === void 0 ? "Close the Markdown fence that hides the Aura-managed markers." : `Close the Markdown fence that hides the Aura-managed markers in ${path}.`, "Run `aura check` again after the markers are visible."],
4445
- operations: [],
4446
- summary: "Close the unterminated Markdown fence manually."
4447
- }
4448
- };
4449
- }
4450
- function findingDocument(finding, model) {
4451
- const sourceId = finding.metadata?.["sourceId"];
4452
- const path = finding.locations?.[0]?.path;
4453
- return typeof sourceId === "string" && path !== void 0 ? findManagedDocument(model, sourceId, path) : void 0;
4454
- }
4455
- const managedBlockHashCheck = defineCheck({
4456
- defaultSeverity: "warn",
4457
- detect: detectManagedBlockDrift,
4458
- explain: `Aura hashes each managed snippet so hand edits cannot be overwritten silently. A mismatch means the installed text no longer matches the registered version, so a later setup run must not guess which side should win.
4459
-
4460
- Re-run the check with \`--fix --interactive\` and choose Keep yours to adopt the edit, Restore to return to registered content, or Merge to review both versions and reconcile them manually. Every executable choice is previewed and backed up before Aura writes.`,
4461
- fix: () => void 0,
4462
- fixability: "guided",
4463
- guidedFixes: guidedFixes$2,
4464
- id: "MGD-001",
4465
- scope: "global",
4466
- title: "Aura-managed instruction blocks have not changed by hand"
4467
- });
4468
- function detectManagedBlockDrift(model) {
4469
- return managedDocuments(model).flatMap((document) => findingsForDocument(document, model));
4470
- }
4471
- function findingsForDocument(document, model) {
4472
- const parsed = document.parsed;
4473
- const hiddenMarker = parsed.notes.find((note) => note.code === "unterminated-fence");
4474
- if (hiddenMarker !== void 0) return [unterminatedFenceFinding(document, hiddenMarker.line, model)];
4475
- if (parsed.status === "invalid") return [malformedFinding(document, parsed.problems, model)];
4476
- if (parsed.status === "absent") return [];
4477
- const manifestHashes = manifestSnippetHashes(model);
4478
- return parsed.block.snippets.filter((snippet) => {
4479
- const manifestHash = manifestHashes.get(snippet.id);
4480
- return !snippet.hashMatches || manifestHash !== void 0 && manifestHash !== snippet.computedHash;
4481
- }).map((snippet) => ({
4482
- details: "Run `aura check --fix --interactive` and choose Keep yours, Restore, or Merge before running setup again.",
4483
- id: `${document.sourceId}:${snippet.id}`,
4484
- locations: [{
4485
- line: snippet.startLine,
4486
- path: document.path
4487
- }],
4488
- message: `Managed snippet ${snippet.id} in ${displayInstructionPath(document.path, model)} was edited by hand.`,
4489
- metadata: {
4490
- kind: "hash-mismatch",
4491
- snippetId: snippet.id,
4492
- sourceId: document.sourceId
4493
- }
4494
- }));
4495
- }
4496
- function unterminatedFenceFinding(document, line, model) {
4497
- return {
4498
- details: "Close the Markdown fence, then run `aura check --fix --interactive` again. Aura will not reconcile this document while its markers are hidden.",
4499
- id: `${document.sourceId}:unterminated-fence`,
4500
- locations: [{
4501
- ...line === void 0 ? {} : { line },
4502
- path: document.path
4503
- }],
4504
- message: `An unclosed Markdown fence hides Aura-managed markers in ${displayInstructionPath(document.path, model)}.`,
4505
- metadata: {
4506
- kind: "unterminated-fence",
4507
- sourceId: document.sourceId
4508
- }
4509
- };
4510
- }
4511
- function malformedFinding(document, problems, model) {
4512
- return {
4513
- details: `${problems.map((problem) => problem.message).join(" ")} Aura will not write to a block it cannot parse; repair the markers by hand.`,
4514
- id: `${document.sourceId}:malformed-managed-block`,
4515
- locations: problems.map((problem) => ({
4516
- ...problem.line === void 0 ? {} : { line: problem.line },
4517
- path: document.path
4518
- })),
4519
- message: `The Aura-managed block in ${displayInstructionPath(document.path, model)} is malformed.`,
4520
- metadata: {
4521
- kind: "malformed",
4522
- problems: problems.map((problem) => ({
4523
- code: problem.code,
4524
- ...problem.line === void 0 ? {} : { line: problem.line }
4525
- })),
4526
- sourceId: document.sourceId
4527
- }
4528
- };
4529
- }
4530
- function manifestSnippetHashes(model) {
4531
- if (model.manifest.status !== "ready") return /* @__PURE__ */ new Map();
4532
- const hashes = /* @__PURE__ */ new Map();
4533
- const ambiguous = /* @__PURE__ */ new Set();
4534
- for (const snippet of model.manifest.value.snippets) if (hashes.has(snippet.id)) ambiguous.add(snippet.id);
4535
- else hashes.set(snippet.id, snippet.hash);
4536
- for (const id of ambiguous) hashes.delete(id);
4537
- return hashes;
4538
- }
4539
- //#endregion
4540
4232
  //#region ../../plugins/checks-core/src/mgd-002-fixes.ts
4541
- function managedContentUpdateChoices(finding, model) {
4542
- if (finding.checkId !== "MGD-002") return [];
4233
+ function managedSkillUpdateChoices(finding, model) {
4543
4234
  const kind = finding.metadata?.["kind"];
4544
- if (kind === "snippet-update" || kind === "snippet-diverged") return snippetUpdateChoices(finding, model);
4545
- if (kind === "snippet-missing") return missingSnippetChoices(finding, model);
4546
- return kind === "skill-update" || kind === "skill-diverged" ? skillUpdateChoices(finding, model) : [];
4547
- }
4548
- /**
4549
- * How a choice names the revision it would apply.
4550
- *
4551
- * A diverged revision is a deliberate move to content that is not newer, so calling it an update
4552
- * would misdescribe what the user is accepting.
4553
- */
4554
- function applyLabel(finding, version) {
4555
- return `${finding.metadata?.["kind"] === "snippet-update" || finding.metadata?.["kind"] === "skill-update" ? "Update to" : "Switch to"} ${version}`;
4556
- }
4557
- function snippetUpdateChoices(finding, model) {
4558
- const context = readyFindingContext(finding, model);
4559
- if (context === void 0) return [];
4560
- const { contentId, manifest: manifestState } = context;
4561
- const installed = manifestState.value.snippets.find((snippet) => snippet.id === contentId);
4562
- const available = model.availableSnippets.find((snippet) => snippet.id === contentId);
4563
- const located = locateManagedSnippet(model, contentId);
4564
- if (installed === void 0 || available === void 0 || located === void 0) return [];
4565
- if (!located.snippet.hashMatches || located.snippet.computedHash !== installed.hash) return [resolveDriftFirst(contentId)];
4566
- const reconciled = reconcileManagedSnippet(located.document.content, contentId, {
4567
- content: available.content,
4568
- kind: "restore"
4569
- });
4570
- if (reconciled.status === "invalid") return [resolveDriftFirst(contentId)];
4571
- const manifest = {
4572
- ...manifestState.value,
4573
- snippets: manifestState.value.snippets.map((snippet) => snippet === installed ? {
4574
- ...snippet,
4575
- hash: available.hash,
4576
- pinned: false,
4577
- version: available.version
4578
- } : snippet)
4579
- };
4580
- return [{
4581
- details: () => diffManagedSnippet(located.document.path, located.snippet.content, available.content),
4582
- id: "update",
4583
- label: applyLabel(finding, available.version),
4584
- plan: {
4585
- operations: [{
4586
- content: reconciled.content,
4587
- path: located.document.path,
4588
- type: "write"
4589
- }, createAuraManifestWriteOperation(manifestState, manifest)],
4590
- summary: `Set managed snippet ${contentId} to ${available.version}.`
4591
- }
4592
- }];
4235
+ return finding.checkId === "MGD-002" && (kind === "skill-update" || kind === "skill-diverged") ? skillUpdateChoices(finding, model) : [];
4593
4236
  }
4594
- function missingSnippetChoices(finding, model) {
4595
- const context = readyFindingContext(finding, model);
4237
+ function skillUpdateChoices(finding, model) {
4238
+ const context = skillUpdateContext(finding, model);
4596
4239
  if (context === void 0) return [];
4597
- const { contentId, manifest } = context;
4598
- const installed = manifest.value.snippets.find((snippet) => snippet.id === contentId);
4599
- if (installed === void 0) return [];
4600
- const pinnedManifest = {
4601
- ...manifest.value,
4602
- snippets: manifest.value.snippets.map((snippet) => snippet === installed ? {
4603
- ...snippet,
4604
- pinned: true
4605
- } : snippet)
4606
- };
4607
- const choices = [{
4608
- id: "pin",
4609
- label: "Keep as pinned",
4610
- plan: {
4611
- operations: [createAuraManifestWriteOperation(manifest, pinnedManifest)],
4612
- summary: `Keep unavailable snippet ${contentId} at its installed revision.`
4613
- }
4614
- }];
4615
- const removal = removeSnippetPlan(model, contentId, installed);
4616
- if (removal !== void 0) choices.push({
4617
- id: "remove",
4618
- label: "Remove",
4619
- plan: removal
4620
- });
4621
- return choices;
4622
- }
4623
- function removeSnippetPlan(model, contentId, installed) {
4624
- if (model.manifest.status !== "ready") return;
4625
- const located = locateManagedSnippet(model, contentId);
4626
- if (located === void 0) return;
4627
- const preserve = located.document.parsed.status === "present" ? located.document.parsed.block.snippets.map((snippet) => snippet.id).filter((id) => id !== contentId) : [];
4628
- const reconciled = reconcileParsedManagedBlock(located.document.content, located.document.parsed, [], {
4629
- ownedSnippetIds: model.manifest.value.snippets.map((snippet) => snippet.id),
4630
- preserveSnippetIds: preserve,
4631
- previousSnippetHashes: new Map(model.manifest.value.snippets.map((snippet) => [snippet.id, snippet.hash]))
4632
- });
4633
- if (reconciled.status === "invalid") return;
4634
- const manifest = {
4635
- ...model.manifest.value,
4636
- snippets: model.manifest.value.snippets.filter((snippet) => snippet !== installed)
4637
- };
4638
- return {
4639
- operations: [{
4640
- content: reconciled.content,
4641
- path: located.document.path,
4642
- type: "write"
4643
- }, createAuraManifestWriteOperation(model.manifest, manifest)],
4644
- summary: `Remove unavailable managed snippet ${contentId}.`
4645
- };
4240
+ if (context.shared.problem !== void 0 || context.shared.treeHash !== context.installed.treeHash) return [resolveSkillDriftFirst(context.contentId)];
4241
+ return [skillUpdateChoice(finding, context)];
4646
4242
  }
4647
- function skillUpdateChoices(finding, model) {
4243
+ function skillUpdateContext(finding, model) {
4648
4244
  const context = readyFindingContext(finding, model);
4649
4245
  const sourceId = finding.metadata?.["sourceId"];
4650
- if (context === void 0 || typeof sourceId !== "string") return [];
4651
- const { contentId, manifest: manifestState } = context;
4652
- const installed = manifestState.value.skills.find((skill) => skill.id === contentId && skill.source === sourceId);
4246
+ if (context === void 0 || typeof sourceId !== "string") return;
4247
+ const { contentId, manifest } = context;
4248
+ const installed = manifest.value.skills.find((skill) => skill.id === contentId && skill.source === sourceId);
4653
4249
  const available = model.availableSkills?.find((skill) => skill.id === contentId && skill.source.id === sourceId);
4654
4250
  const shared = model.sharedSkills?.find((skill) => skill.id === contentId);
4655
- if (installed === void 0 || available === void 0 || shared === void 0) return [];
4656
- if (shared.problem !== void 0 || shared.treeHash !== installed.treeHash) return [resolveSkillDriftFirst(contentId)];
4251
+ return installed === void 0 || available === void 0 || shared === void 0 ? void 0 : {
4252
+ available,
4253
+ contentId,
4254
+ installed,
4255
+ manifest,
4256
+ shared
4257
+ };
4258
+ }
4259
+ function skillUpdateChoice(finding, context) {
4260
+ const { available, contentId, installed, manifest: manifestState, shared } = context;
4657
4261
  const manifest = {
4658
4262
  ...manifestState.value,
4659
4263
  skills: manifestState.value.skills.map((skill) => skill === installed ? updatedSkill(skill, available) : skill)
4660
4264
  };
4661
- return [{
4265
+ return {
4662
4266
  id: "update",
4663
- label: applyLabel(finding, available.version),
4267
+ label: `${finding.metadata?.["kind"] === "skill-update" ? "Update to" : "Switch to"} ${available.version}`,
4664
4268
  plan: {
4665
4269
  operations: [...planSharedSkillTreeUpdate(shared.path, shared.entries, available), createAuraManifestWriteOperation(manifestState, manifest)],
4666
4270
  summary: `Set managed skill ${contentId} to ${available.version}.`
4667
4271
  }
4668
- }];
4272
+ };
4669
4273
  }
4670
4274
  function readyFindingContext(finding, model) {
4671
4275
  const contentId = finding.metadata?.["contentId"];
@@ -4682,27 +4286,6 @@ function updatedSkill(installed, available) {
4682
4286
  version: available.version
4683
4287
  };
4684
4288
  }
4685
- function locateManagedSnippet(model, contentId) {
4686
- for (const document of managedDocuments(model)) {
4687
- if (document.parsed.status !== "present") continue;
4688
- const snippet = document.parsed.block.snippets.find((candidate) => candidate.id === contentId);
4689
- if (snippet !== void 0) return {
4690
- document,
4691
- snippet
4692
- };
4693
- }
4694
- }
4695
- function resolveDriftFirst(contentId) {
4696
- return {
4697
- id: "resolve-drift",
4698
- label: "Resolve local edits first",
4699
- plan: {
4700
- manualSteps: [`Run \`aura check --only MGD-001 --fix --interactive\` and resolve edits to ${contentId}, then review this update again.`],
4701
- operations: [],
4702
- summary: `Resolve local edits to ${contentId} before updating it.`
4703
- }
4704
- };
4705
- }
4706
4289
  function resolveSkillDriftFirst(contentId) {
4707
4290
  return {
4708
4291
  id: "resolve-drift",
@@ -4714,55 +4297,21 @@ function resolveSkillDriftFirst(contentId) {
4714
4297
  }
4715
4298
  };
4716
4299
  }
4300
+ //#endregion
4301
+ //#region ../../plugins/checks-core/src/mgd-002.ts
4717
4302
  const managedContentUpdateCheck = defineCheck({
4718
4303
  defaultSeverity: "info",
4719
- detect: detectManagedContentUpdates,
4720
- explain: "Aura records the exact version and content hash of each managed snippet and skill. When an installed plugin offers a different revision, Aura reports it and waits for a reviewed change instead of silently replacing managed content. A revision that is not newer — a rollback, or one this build cannot order — is reported too, because Aura keeps the recorded revision either way and an unreported hold would look like nothing to do. Pinned selections remain quiet until they are unpinned.\n\nRun `aura check --fix --interactive` to review bundled revisions and vanished snippets. Locally edited content must be resolved first; directory-sourced skill updates stay in `aura setup`, where network and credential approval are explicit.",
4304
+ detect: detectManagedSkillUpdates,
4305
+ explain: "Aura records the exact source revision of each managed skill. When an installed plugin offers a different revision, Aura reports it and waits for a reviewed change instead of silently replacing the skill. Pinned skills remain quiet until they are unpinned.\n\nRun `aura check --fix` to review bundled skill revisions. Directory-sourced skill updates stay in `aura setup`, where network and credential approval are explicit.",
4721
4306
  fix: () => void 0,
4722
4307
  fixability: "guided",
4723
- guidedFixes: managedContentUpdateChoices,
4308
+ guidedFixes: managedSkillUpdateChoices,
4724
4309
  id: "MGD-002",
4725
4310
  scope: "global",
4726
- title: "Managed snippets and skills are at their reviewed source revisions"
4311
+ title: "Managed skills are at their reviewed source revisions"
4727
4312
  });
4728
- function detectManagedContentUpdates(model) {
4729
- if (model.manifest.status !== "ready") return [];
4730
- return [...model.manifest.value.snippets.flatMap((snippet) => snippetFinding(snippet, model)), ...model.manifest.value.skills.flatMap((skill) => skillFinding(skill, model))];
4731
- }
4732
- function snippetFinding(installed, model) {
4733
- if (installed.pinned) return [];
4734
- const available = model.availableSnippets.find((snippet) => snippet.id === installed.id);
4735
- if (available === void 0) return [{
4736
- id: `snippet:${installed.id}:missing`,
4737
- message: `Managed snippet ${installed.id} at version ${installed.version} is no longer provided by an installed plugin.`,
4738
- metadata: {
4739
- contentId: installed.id,
4740
- kind: "snippet-missing"
4741
- }
4742
- }];
4743
- const status = managedContentRevisionStatus(installed.version, installed.hash, available.version, available.hash);
4744
- if (status === "current") return [];
4745
- if (status === "diverged") return [{
4746
- details: "Aura keeps the recorded revision because the offered one is not newer. Re-run `aura setup` interactively to switch to it deliberately, or pin this snippet to stop the comparison.",
4747
- id: `snippet:${installed.id}:diverged`,
4748
- message: `Managed snippet ${installed.id} is recorded at version ${installed.version}, but the installed plugin offers ${available.version}, which is not newer.`,
4749
- metadata: {
4750
- availableVersion: available.version,
4751
- contentId: installed.id,
4752
- installedVersion: installed.version,
4753
- kind: "snippet-diverged"
4754
- }
4755
- }];
4756
- return [{
4757
- id: `snippet:${installed.id}:update`,
4758
- message: `Managed snippet ${installed.id} can update from version ${installed.version} to ${available.version}.`,
4759
- metadata: {
4760
- availableVersion: available.version,
4761
- contentId: installed.id,
4762
- installedVersion: installed.version,
4763
- kind: "snippet-update"
4764
- }
4765
- }];
4313
+ function detectManagedSkillUpdates(model) {
4314
+ return model.manifest.status === "ready" ? model.manifest.value.skills.flatMap((skill) => skillFinding(skill, model)) : [];
4766
4315
  }
4767
4316
  function skillFinding(installed, model) {
4768
4317
  if (installed.pinned || !installed.source.startsWith("plugin:")) return [];
@@ -5179,10 +4728,10 @@ const mcp003 = defineCheck({
5179
4728
  detect: detectDeadServers,
5180
4729
  explain: `Aura checks stdio commands with filesystem-only PATH resolution, and starts no MCP server of its own. Package launchers such as npx, uvx, and bunx are checked, but Aura does not guess whether the requested package is installed or cached.
5181
4730
 
5182
- Remote URLs are silent and generate no network traffic unless \`check --online\` is supplied. Online probes use short, bounded requests without configured authentication headers, and they skip an endpoint whose credentials Aura stripped, because the remaining URL no longer addresses the configured server. A redirect to a private address is reported rather than followed. A timeout or a server-side fault is a warning, not an error: it may not repeat. Re-run with \`--fix --interactive\` to choose whether to repair or remove a server that definitely failed its probe. Cursor separately requires its MCP servers to be approved and enabled in its own settings, which no file records; Aura reads those states by running \`cursor-agent mcp list\` when that CLI is installed and identifies itself. The command is Cursor's, and it connects to servers to answer, so Aura runs it from your home directory: only your own global configuration takes part, never a \`.cursor/mcp.json\` committed to the repository you are in. Aura never approves, enables, or retries a server itself, and every state it reports this way is a warning. Aura also reports an informational reminder when a desired MCP transport references an unset environment variable. It retains only the variable name and whether it is set; credential values never enter the workspace model or report.`,
4731
+ Remote URLs are silent and generate no network traffic unless \`check --online\` is supplied. Online probes use short, bounded requests without configured authentication headers, and they skip an endpoint whose credentials Aura stripped, because the remaining URL no longer addresses the configured server. A redirect to a private address is reported rather than followed. A timeout or a server-side fault is a warning, not an error: it may not repeat. Re-run with \`--fix\` to choose whether to repair or remove a server that definitely failed its probe. Cursor separately requires its MCP servers to be approved and enabled in its own settings, which no file records; Aura reads those states by running \`cursor-agent mcp list\` when that CLI is installed and identifies itself. The command is Cursor's, and it connects to servers to answer, so Aura runs it from your home directory: only your own global configuration takes part, never a \`.cursor/mcp.json\` committed to the repository you are in. Aura never approves, enables, or retries a server itself, and every state it reports this way is a warning. Aura also reports an informational reminder when a desired MCP transport references an unset environment variable. It retains only the variable name and whether it is set; credential values never enter the workspace model or report.`,
5183
4732
  fix: () => void 0,
5184
4733
  fixability: "guided",
5185
- guidedFixes: guidedFixes$1,
4734
+ guidedFixes,
5186
4735
  id: CHECK_ID,
5187
4736
  scope: "global",
5188
4737
  title: "Configured MCP servers can be reached"
@@ -5252,14 +4801,14 @@ function findingFor(server, probe, model) {
5252
4801
  ...probe.transient === true ? { severity: "warn" } : {}
5253
4802
  };
5254
4803
  }
5255
- function guidedFixes$1(finding, model) {
4804
+ function guidedFixes(finding, model) {
5256
4805
  const server = findingServer(finding, model);
5257
4806
  const kind = finding.metadata?.["kind"];
5258
4807
  if (server === void 0 || kind !== "command" && kind !== "url") return [];
5259
4808
  return [repairChoice(server, kind, model), removeChoice(server, model)];
5260
4809
  }
5261
4810
  function repairChoice(server, kind, model) {
5262
- const location = sourcePath$1(server, model) ?? "the application's MCP configuration";
4811
+ const location = sourcePath(server, model) ?? "the application's MCP configuration";
5263
4812
  return {
5264
4813
  id: "repair",
5265
4814
  label: "Repair server",
@@ -5287,7 +4836,7 @@ function removeChoice(server, model) {
5287
4836
  };
5288
4837
  }
5289
4838
  function manualRemoval(server, model, blockers) {
5290
- const path = sourcePath$1(server, model) ?? "the application's MCP configuration file";
4839
+ const path = sourcePath(server, model) ?? "the application's MCP configuration file";
5291
4840
  const selected = model.manifest.status === "ready" && model.manifest.value.mcpServers.some((entry) => entry.name === server.name && entry.scope === server.scope && entry.apps.includes(server.appId));
5292
4841
  return {
5293
4842
  manualSteps: [
@@ -5309,72 +4858,9 @@ function findingServer(finding, model) {
5309
4858
  if (typeof appId !== "string" || typeof name !== "string" || scope !== "global" && scope !== "project" || typeof sourceId !== "string") return;
5310
4859
  return model.mcpServers.find((server) => server.appId === appId && server.name === name && server.scope === scope && server.sourceId === sourceId);
5311
4860
  }
5312
- function sourcePath$1(server, model) {
4861
+ function sourcePath(server, model) {
5313
4862
  return model.apps.find((app) => app.adapterId === server.appId)?.sourceFiles.find((file) => file.spec.id === server.sourceId)?.spec.path;
5314
4863
  }
5315
- const mcp004 = defineCheck({
5316
- defaultSeverity: "error",
5317
- detect: detectInlineSecrets,
5318
- explain: `Inline credentials in MCP configuration can leak through source control, logs, diagnostics, and copied configuration. Aura identifies credential-bearing fields without retaining their values, prefixes, or lengths.
5319
-
5320
- Re-run with \`--fix --interactive\` to replace every behavior-preserving sighting in the same source file with an environment reference. Copy the current values directly from the named file before applying the rewrite; Aura never displays or stores those values outside the protected undo payload, which stays owner-only under \`~/agents/.backups\` until twenty later fixes have pushed it out of the journal. Then rotate each credential at its issuer: moving a value behind an environment reference stops it leaking again, not from wherever it has already reached.`,
5321
- findingGroup: {
5322
- description: "Replace inline credentials with environment-variable references.",
5323
- id: "checks-core/mcp-credentials",
5324
- title: "Protect credentials stored in MCP configuration"
5325
- },
5326
- fix: remediation,
5327
- fixability: "guided",
5328
- guidedFixes,
5329
- id: "MCP-004",
5330
- scope: "global",
5331
- title: "MCP credentials use environment references"
5332
- });
5333
- function detectInlineSecrets(model) {
5334
- return model.mcpSecretSightings.map((sighting) => {
5335
- const supported = canPlanMcpSecretRemediation(model, sighting);
5336
- const path = sourcePath(model, sighting);
5337
- return {
5338
- details: supported ? `Aura can replace this field with ${sighting.suggestedEnvName} as part of a whole-file guided rewrite.` : manualDetails(sighting),
5339
- ...supported ? {} : { fixability: "manual" },
5340
- id: `${sighting.appId}:${sighting.sourceId}:${sighting.serverName}:${sighting.field}`,
5341
- ...path === void 0 ? {} : { locations: [{ path }] },
5342
- message: `MCP server ${sighting.serverName} in ${sighting.appId} stores a credential inline at ${sighting.field}.`,
5343
- metadata: {
5344
- appId: sighting.appId,
5345
- field: sighting.field,
5346
- serverName: sighting.serverName,
5347
- sourceId: sighting.sourceId,
5348
- suggestedEnvName: sighting.suggestedEnvName
5349
- }
5350
- };
5351
- });
5352
- }
5353
- function remediation(finding, model) {
5354
- const sighting = findingSighting(finding, model);
5355
- return sighting === void 0 ? void 0 : planMcpSecretRemediation(model, sighting);
5356
- }
5357
- function guidedFixes(finding, model) {
5358
- const plan = remediation(finding, model);
5359
- return plan === void 0 ? [] : [{
5360
- id: "replace-file-secrets",
5361
- label: "Replace inline credentials in this file",
5362
- plan
5363
- }];
5364
- }
5365
- function findingSighting(finding, model) {
5366
- const appId = finding.metadata?.["appId"];
5367
- const sourceId = finding.metadata?.["sourceId"];
5368
- const serverName = finding.metadata?.["serverName"];
5369
- const field = finding.metadata?.["field"];
5370
- return model.mcpSecretSightings.find((sighting) => sighting.appId === appId && sighting.sourceId === sourceId && sighting.serverName === serverName && sighting.field === field);
5371
- }
5372
- function sourcePath(model, sighting) {
5373
- return model.apps.find((app) => app.adapterId === sighting.appId)?.sourceFiles.find((file) => file.spec.id === sighting.sourceId)?.spec.path;
5374
- }
5375
- function manualDetails(sighting) {
5376
- return sighting.appId === "codex" ? `Aura cannot rewrite ${sighting.field} without changing how Codex reads it — either the field has no environment-reference form, or the entry is not written as a \`[mcp_servers.${sighting.serverName}]\` table. Move the credential manually to an environment-aware field, then run \`aura check\` again.` : `This adapter cannot safely rewrite ${sighting.field}. Replace it manually with ${sighting.suggestedEnvName}, then run \`aura check\` again.`;
5377
- }
5378
4864
  //#endregion
5379
4865
  //#region ../../plugins/checks-core/src/mcp-005-placement.ts
5380
4866
  /** Identifier every MCP-005 module stamps and matches on. */
@@ -5557,7 +5043,7 @@ const mcp005 = defineCheck({
5557
5043
  detect: detectScopePlacement,
5558
5044
  explain: `The Aura manifest distinguishes personal MCP servers from team servers. Personal servers belong in global configuration; putting one in repository configuration can publish it to teammates through source control. Team servers belong in project configuration; keeping one only in personal configuration means teammates do not receive it and their setup drifts. Placement is judged by the file an entry is written in, not by the scope label alone, because an application can keep both kinds in one document.
5559
5045
 
5560
- Re-run with \`--fix --interactive\` to move an Aura-owned entry through the same manifest convergence and undo machinery as other MCP fixes. Aura never removes a name outside its ownership ledger automatically.`,
5046
+ Re-run with \`--fix\` to move an Aura-owned entry through the same manifest convergence and undo machinery as other MCP fixes. Aura never removes a name outside its ownership ledger automatically.`,
5561
5047
  fix: placementFix,
5562
5048
  fixability: "guided",
5563
5049
  id: MCP_005_ID,
@@ -5895,13 +5381,11 @@ const checksCorePlugin = definePlugin({
5895
5381
  instructionLinkIntegrityCheck,
5896
5382
  instructionContextBudgetCheck,
5897
5383
  instructionPrecedenceCheck,
5898
- managedBlockHashCheck,
5899
5384
  managedContentUpdateCheck,
5900
5385
  unmanagedDetectedAppCheck,
5901
5386
  mcp001,
5902
5387
  mcp002,
5903
5388
  mcp003,
5904
- mcp004,
5905
5389
  mcp005,
5906
5390
  skl001,
5907
5391
  skl002,