@tryaura/aura-cli 0.2.1 → 0.3.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/dist/bin/aura.js +3 -3
- package/dist/index.js +1 -1
- package/dist/plugins/index.js +1 -1
- package/dist/{plugins-Dbasvr1u.js → plugins-BEuoEUo_.js} +58 -580
- package/dist/{run.boundary-CF5kZTH3.js → run.boundary-NVWIwGgg.js} +7294 -4828
- package/dist/{shared-link-plan-D8fxdFYv.js → shared-link-plan-AcSkw9bO.js} +170 -850
- package/package.json +5 -5
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
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-AcSkw9bO.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: "
|
|
247
|
-
lineTemplate: `@${SHARED_INSTRUCTIONS_TEMPLATE_TOKEN}`
|
|
246
|
+
kind: "symlink"
|
|
248
247
|
},
|
|
249
248
|
supportedRange: ">=2.1.0 <3.0.0"
|
|
250
249
|
});
|
|
@@ -1521,12 +1520,12 @@ var src_default$2 = definePlugin({
|
|
|
1521
1520
|
* silently stop finding anything, so the contract is exported and imported rather than retyped.
|
|
1522
1521
|
*
|
|
1523
1522
|
* `rulesProject` names the `.cursor/rules` directory slot; each discovered child file gets a
|
|
1524
|
-
* derived id of the form `cursor.rules.project/<encoded-name
|
|
1525
|
-
*
|
|
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.
|
|
1526
1526
|
*/
|
|
1527
1527
|
const CURSOR_SOURCE_IDS = Object.freeze({
|
|
1528
1528
|
agents: "cursor.rules.project.agents",
|
|
1529
|
-
aura: "cursor.rules.project/aura-owned",
|
|
1530
1529
|
legacyRules: "cursor.rules.project.legacy",
|
|
1531
1530
|
mcpGlobal: "cursor.mcp.global",
|
|
1532
1531
|
mcpProject: "cursor.mcp.project",
|
|
@@ -1812,6 +1811,7 @@ function normalizeReference(reference) {
|
|
|
1812
1811
|
//#region ../../plugins/adapter-cursor/src/adapter.ts
|
|
1813
1812
|
const cursorAdapter = defineAdapter({
|
|
1814
1813
|
capabilities: { instructions: {
|
|
1814
|
+
globalSharedLink: "not-applicable",
|
|
1815
1815
|
importStyle: "at-import",
|
|
1816
1816
|
loading: "import-graph"
|
|
1817
1817
|
} },
|
|
@@ -1840,18 +1840,6 @@ const cursorAdapter = defineAdapter({
|
|
|
1840
1840
|
unusableMcpServers: mcpFiles.flatMap(({ config }) => config.unusable)
|
|
1841
1841
|
};
|
|
1842
1842
|
},
|
|
1843
|
-
sharedLink: {
|
|
1844
|
-
entryPath: "./.cursor/rules/aura.mdc",
|
|
1845
|
-
kind: "native-copy",
|
|
1846
|
-
lineTemplate: [
|
|
1847
|
-
"---",
|
|
1848
|
-
"alwaysApply: true",
|
|
1849
|
-
"---",
|
|
1850
|
-
"",
|
|
1851
|
-
`@file ${SHARED_INSTRUCTIONS_TEMPLATE_TOKEN}`,
|
|
1852
|
-
""
|
|
1853
|
-
].join("\n")
|
|
1854
|
-
},
|
|
1855
1843
|
supportedRange: ">=0.45.0 <4.0.0"
|
|
1856
1844
|
});
|
|
1857
1845
|
/**
|
|
@@ -1903,13 +1891,6 @@ function cursorFiles({ environment, files }) {
|
|
|
1903
1891
|
path: join(environment.cwd, "AGENTS.md"),
|
|
1904
1892
|
scope: "project"
|
|
1905
1893
|
},
|
|
1906
|
-
{
|
|
1907
|
-
id: CURSOR_SOURCE_IDS.aura,
|
|
1908
|
-
kind: "instructions",
|
|
1909
|
-
optional: true,
|
|
1910
|
-
path: join(environment.cwd, ".cursor", "rules", "aura.mdc"),
|
|
1911
|
-
scope: "project"
|
|
1912
|
-
},
|
|
1913
1894
|
{
|
|
1914
1895
|
id: CURSOR_SOURCE_IDS.mcpGlobal,
|
|
1915
1896
|
kind: "mcp",
|
|
@@ -1931,16 +1912,13 @@ function discoveredRuleSpecs(files) {
|
|
|
1931
1912
|
const specs = [];
|
|
1932
1913
|
for (const file of files.values()) {
|
|
1933
1914
|
if (!isRuleSourceId(file.spec.id) || file.entries === void 0) continue;
|
|
1934
|
-
for (const entry of file.entries) {
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
scope: "project"
|
|
1942
|
-
});
|
|
1943
|
-
}
|
|
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
|
+
});
|
|
1944
1922
|
}
|
|
1945
1923
|
return specs;
|
|
1946
1924
|
}
|
|
@@ -2183,7 +2161,7 @@ function detectConflicts(model) {
|
|
|
2183
2161
|
function claudeFindings(app) {
|
|
2184
2162
|
const effective = readClaudePermissionMode(app);
|
|
2185
2163
|
if (effective === void 0 || !RESTRICTIVE_MODES.has(effective.mode)) return [];
|
|
2186
|
-
const path = sourcePath$
|
|
2164
|
+
const path = sourcePath$1(app, effective.sourceId);
|
|
2187
2165
|
return [{
|
|
2188
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.",
|
|
2189
2167
|
id: `claude-permission-mode:${effective.mode}`,
|
|
@@ -2199,7 +2177,7 @@ function claudeFindings(app) {
|
|
|
2199
2177
|
function codexFindings(app) {
|
|
2200
2178
|
const trust = readCodexProjectTrust(app) ?? "unknown";
|
|
2201
2179
|
if (trust === "trusted" || trust === "unreadable") return [];
|
|
2202
|
-
const path = sourcePath$
|
|
2180
|
+
const path = sourcePath$1(app, CODEX_SOURCE_IDS.mcp);
|
|
2203
2181
|
return [{
|
|
2204
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.",
|
|
2205
2183
|
id: `codex-project-trust:${trust}`,
|
|
@@ -2212,7 +2190,7 @@ function codexFindings(app) {
|
|
|
2212
2190
|
}
|
|
2213
2191
|
}];
|
|
2214
2192
|
}
|
|
2215
|
-
function sourcePath$
|
|
2193
|
+
function sourcePath$1(app, sourceId) {
|
|
2216
2194
|
return app.sourceFiles.find((file) => file.spec.id === sourceId)?.spec.path;
|
|
2217
2195
|
}
|
|
2218
2196
|
function guidedFix$2(finding, model) {
|
|
@@ -4072,9 +4050,9 @@ const sharedInstructionLinksCheck = defineCheck({
|
|
|
4072
4050
|
detect(model) {
|
|
4073
4051
|
return model.apps.flatMap((app) => detectMissingLink(app, model));
|
|
4074
4052
|
},
|
|
4075
|
-
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.
|
|
4076
4054
|
|
|
4077
|
-
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.`,
|
|
4078
4056
|
findingGroup: SHARED_INSTRUCTION_GROUP,
|
|
4079
4057
|
fix(finding, model) {
|
|
4080
4058
|
const app = model.apps.find((candidate) => candidate.adapterId === findingAppId(finding));
|
|
@@ -4089,6 +4067,10 @@ Re-run the check with \`--fix\` to add the adapter-supported link without replac
|
|
|
4089
4067
|
});
|
|
4090
4068
|
function detectMissingLink(app, model) {
|
|
4091
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) {
|
|
4092
4074
|
const shared = app.sharedLink;
|
|
4093
4075
|
const sharedPath = resolve(model.sharedInstructions.path);
|
|
4094
4076
|
if (shared !== void 0 && linksToShared(app, shared, sharedPath)) return [];
|
|
@@ -4247,431 +4229,47 @@ function isScope$1(value) {
|
|
|
4247
4229
|
return SCOPES.some((scope) => scope === value);
|
|
4248
4230
|
}
|
|
4249
4231
|
//#endregion
|
|
4250
|
-
//#region ../../plugins/checks-core/src/mgd-001-documents.ts
|
|
4251
|
-
const SHARED_SOURCE_ID = "aura.shared-instructions";
|
|
4252
|
-
/**
|
|
4253
|
-
* Every document whose managed block this check owns, parsed.
|
|
4254
|
-
*
|
|
4255
|
-
* Memoized per model because detection and remediation ask the same question: `detect` walks the
|
|
4256
|
-
* set once, then each finding's guided fixes look its document back up. Rebuilding the set per
|
|
4257
|
-
* finding re-resolves every instruction path and re-parses every block to reach an answer that
|
|
4258
|
-
* cannot have changed — the model is frozen for the length of a run.
|
|
4259
|
-
*/
|
|
4260
|
-
const CACHE = /* @__PURE__ */ new WeakMap();
|
|
4261
|
-
function managedDocuments(model) {
|
|
4262
|
-
const cached = CACHE.get(model);
|
|
4263
|
-
if (cached !== void 0) return cached;
|
|
4264
|
-
const documents = /* @__PURE__ */ new Map();
|
|
4265
|
-
if (model.sharedInstructions.content !== void 0) documents.set(resolve(model.sharedInstructions.path), managedDocument(model.sharedInstructions.content, model.sharedInstructions.path, SHARED_SOURCE_ID));
|
|
4266
|
-
for (const document of canonicalInstructionDocuments(model.instructionFiles)) {
|
|
4267
|
-
const path = resolve(document.path);
|
|
4268
|
-
if (!documents.has(path) && !isSymlinkDocument(document, model)) documents.set(path, managedDocument(document.content, document.path, document.sourceId));
|
|
4269
|
-
}
|
|
4270
|
-
const resolved = Object.freeze([...documents.values()]);
|
|
4271
|
-
CACHE.set(model, resolved);
|
|
4272
|
-
return resolved;
|
|
4273
|
-
}
|
|
4274
|
-
/** Finds the document one finding was raised against, without rebuilding the set. */
|
|
4275
|
-
function findManagedDocument(model, sourceId, path) {
|
|
4276
|
-
const target = resolve(path);
|
|
4277
|
-
return managedDocuments(model).find((document) => document.sourceId === sourceId && resolve(document.path) === target);
|
|
4278
|
-
}
|
|
4279
|
-
function managedDocument(content, path, sourceId) {
|
|
4280
|
-
return Object.freeze({
|
|
4281
|
-
content,
|
|
4282
|
-
parsed: readManagedBlock(content),
|
|
4283
|
-
path,
|
|
4284
|
-
sourceId
|
|
4285
|
-
});
|
|
4286
|
-
}
|
|
4287
|
-
function isSymlinkDocument(document, model) {
|
|
4288
|
-
const path = resolve(document.path);
|
|
4289
|
-
return model.apps.some((app) => app.sourceFiles.some((file) => file.spec.id === document.sourceId && resolve(file.spec.path) === path && file.pathKind === "symlink"));
|
|
4290
|
-
}
|
|
4291
|
-
//#endregion
|
|
4292
|
-
//#region ../../plugins/checks-core/src/mgd-001-fixes.ts
|
|
4293
|
-
const CHECK_ID$4 = "MGD-001";
|
|
4294
|
-
function guidedFixes$2(finding, model) {
|
|
4295
|
-
if (finding.checkId !== CHECK_ID$4) return [];
|
|
4296
|
-
const kind = finding.metadata?.["kind"];
|
|
4297
|
-
if (kind === "malformed") return [malformedMergeChoice(finding)];
|
|
4298
|
-
if (kind === "unterminated-fence") return [unterminatedFenceChoice(finding)];
|
|
4299
|
-
return kind === "hash-mismatch" ? mismatchChoices(finding, model) : [];
|
|
4300
|
-
}
|
|
4301
|
-
function mismatchChoices(finding, model) {
|
|
4302
|
-
const context = mismatchContext(finding, model);
|
|
4303
|
-
if (context === void 0) throw new Error(`The managed snippet for finding "${finding.id}" is no longer available.`);
|
|
4304
|
-
const choices = [];
|
|
4305
|
-
const keep = keepChoice(context, model);
|
|
4306
|
-
choices.push(keep);
|
|
4307
|
-
const canonical = model.availableSnippets.find((candidate) => candidate.id === context.snippet.id);
|
|
4308
|
-
const restore = restoreChoice(context, canonical, model);
|
|
4309
|
-
if (restore !== void 0) choices.push(restore);
|
|
4310
|
-
choices.push(mergeChoice(context.document, context.snippet, canonical?.content));
|
|
4311
|
-
return Object.freeze(choices);
|
|
4312
|
-
}
|
|
4313
|
-
function mismatchContext(finding, model) {
|
|
4314
|
-
const document = findingDocument(finding, model);
|
|
4315
|
-
const snippetId = finding.metadata?.["snippetId"];
|
|
4316
|
-
if (document === void 0 || typeof snippetId !== "string") return;
|
|
4317
|
-
if (document.parsed.status !== "present") return;
|
|
4318
|
-
const snippet = document.parsed.block.snippets.find((candidate) => candidate.id === snippetId);
|
|
4319
|
-
return snippet === void 0 ? void 0 : {
|
|
4320
|
-
document,
|
|
4321
|
-
snippet
|
|
4322
|
-
};
|
|
4323
|
-
}
|
|
4324
|
-
function keepChoice(context, model) {
|
|
4325
|
-
const { document, snippet } = context;
|
|
4326
|
-
const keep = reconcileManagedSnippet(document.content, snippet.id, { kind: "keep" });
|
|
4327
|
-
if (keep.status === "invalid") throw new Error(`Keep yours could not reconcile ${snippet.id}: ${keep.problems.map((problem) => problem.message).join(" ")}`);
|
|
4328
|
-
const applied = operationsWithManifest({
|
|
4329
|
-
content: keep.content,
|
|
4330
|
-
hash: snippet.computedHash,
|
|
4331
|
-
model,
|
|
4332
|
-
path: document.path,
|
|
4333
|
-
pinned: true,
|
|
4334
|
-
snippetId: snippet.id
|
|
4335
|
-
});
|
|
4336
|
-
return {
|
|
4337
|
-
id: "keep",
|
|
4338
|
-
label: "Keep yours",
|
|
4339
|
-
plan: {
|
|
4340
|
-
...applied.manualSteps.length === 0 ? {} : { manualSteps: [...applied.manualSteps] },
|
|
4341
|
-
operations: applied.operations,
|
|
4342
|
-
summary: `Adopt the edited ${snippet.id} snippet.`
|
|
4343
|
-
}
|
|
4344
|
-
};
|
|
4345
|
-
}
|
|
4346
|
-
function restoreChoice(context, canonical, model) {
|
|
4347
|
-
if (canonical === void 0) return;
|
|
4348
|
-
const restored = reconcileManagedSnippet(context.document.content, context.snippet.id, {
|
|
4349
|
-
content: canonical.content,
|
|
4350
|
-
kind: "restore"
|
|
4351
|
-
});
|
|
4352
|
-
if (restored.status === "invalid") throw new Error(`Restore could not reconcile ${context.snippet.id}: ${restored.problems.map((problem) => problem.message).join(" ")}`);
|
|
4353
|
-
const applied = operationsWithManifest({
|
|
4354
|
-
content: restored.content,
|
|
4355
|
-
hash: canonical.hash,
|
|
4356
|
-
model,
|
|
4357
|
-
path: context.document.path,
|
|
4358
|
-
pinned: false,
|
|
4359
|
-
snippetId: context.snippet.id,
|
|
4360
|
-
version: canonical.version
|
|
4361
|
-
});
|
|
4362
|
-
return {
|
|
4363
|
-
id: "restore",
|
|
4364
|
-
label: "Restore",
|
|
4365
|
-
plan: {
|
|
4366
|
-
...applied.manualSteps.length === 0 ? {} : { manualSteps: [...applied.manualSteps] },
|
|
4367
|
-
operations: applied.operations,
|
|
4368
|
-
summary: `Restore ${context.snippet.id} from the registered snippet.`
|
|
4369
|
-
}
|
|
4370
|
-
};
|
|
4371
|
-
}
|
|
4372
|
-
/**
|
|
4373
|
-
* Pairs the instruction-file write with the manifest update that records it.
|
|
4374
|
-
*
|
|
4375
|
-
* The write can succeed where the manifest update cannot: an unreadable manifest, or an id that
|
|
4376
|
-
* several selections claim, leaves nothing unambiguous to edit. Applying only the file half is
|
|
4377
|
-
* still the right outcome — the marker hash is what stops the next run overwriting the user — but
|
|
4378
|
-
* it leaves the manifest disagreeing with disk, which is the drift the manifest exists to catch.
|
|
4379
|
-
* So the shortfall becomes a manual step rather than a silence.
|
|
4380
|
-
*/
|
|
4381
|
-
function operationsWithManifest(options) {
|
|
4382
|
-
const operations = [{
|
|
4383
|
-
content: options.content,
|
|
4384
|
-
path: options.path,
|
|
4385
|
-
type: "write"
|
|
4386
|
-
}];
|
|
4387
|
-
if (options.model.manifest.status === "missing") return {
|
|
4388
|
-
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}.`],
|
|
4389
|
-
operations
|
|
4390
|
-
};
|
|
4391
|
-
if (options.model.manifest.status === "read-only") return {
|
|
4392
|
-
manualSteps: [`Record ${options.snippetId} at hash ${options.hash} after repairing the Aura manifest: ${options.model.manifest.problem.message}`],
|
|
4393
|
-
operations
|
|
4394
|
-
};
|
|
4395
|
-
const matches = options.model.manifest.value.snippets.filter((snippet) => snippet.id === options.snippetId);
|
|
4396
|
-
const selected = matches[0];
|
|
4397
|
-
if (matches.length !== 1 || selected === void 0) return {
|
|
4398
|
-
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}.`],
|
|
4399
|
-
operations
|
|
4400
|
-
};
|
|
4401
|
-
const manifest = {
|
|
4402
|
-
...options.model.manifest.value,
|
|
4403
|
-
snippets: options.model.manifest.value.snippets.map((snippet) => snippet === selected ? updatedSelection(snippet, options) : snippet)
|
|
4404
|
-
};
|
|
4405
|
-
operations.push(createAuraManifestWriteOperation(options.model.manifest, manifest));
|
|
4406
|
-
return {
|
|
4407
|
-
manualSteps: [],
|
|
4408
|
-
operations
|
|
4409
|
-
};
|
|
4410
|
-
}
|
|
4411
|
-
function updatedSelection(snippet, options) {
|
|
4412
|
-
return {
|
|
4413
|
-
...snippet,
|
|
4414
|
-
hash: options.hash,
|
|
4415
|
-
pinned: options.pinned,
|
|
4416
|
-
version: options.version ?? snippet.version
|
|
4417
|
-
};
|
|
4418
|
-
}
|
|
4419
|
-
function mergeChoice(document, snippet, canonicalContent) {
|
|
4420
|
-
const lines = `${String(snippet.startLine)}-${String(snippet.endLine)}`;
|
|
4421
|
-
return {
|
|
4422
|
-
...canonicalContent === void 0 ? {} : { details: () => diffManagedSnippet(document.path, snippet.content, canonicalContent) },
|
|
4423
|
-
id: "merge",
|
|
4424
|
-
label: "Merge",
|
|
4425
|
-
plan: {
|
|
4426
|
-
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."],
|
|
4427
|
-
operations: [],
|
|
4428
|
-
summary: `Manually merge the edited ${snippet.id} snippet.`
|
|
4429
|
-
}
|
|
4430
|
-
};
|
|
4431
|
-
}
|
|
4432
|
-
function malformedMergeChoice(finding) {
|
|
4433
|
-
const path = finding.locations?.[0]?.path;
|
|
4434
|
-
return {
|
|
4435
|
-
id: "merge",
|
|
4436
|
-
label: "Repair manually",
|
|
4437
|
-
plan: {
|
|
4438
|
-
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."],
|
|
4439
|
-
operations: [],
|
|
4440
|
-
summary: "Repair the malformed Aura-managed block manually."
|
|
4441
|
-
}
|
|
4442
|
-
};
|
|
4443
|
-
}
|
|
4444
|
-
function unterminatedFenceChoice(finding) {
|
|
4445
|
-
const path = finding.locations?.[0]?.path;
|
|
4446
|
-
return {
|
|
4447
|
-
id: "close-fence",
|
|
4448
|
-
label: "Close the fence",
|
|
4449
|
-
plan: {
|
|
4450
|
-
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."],
|
|
4451
|
-
operations: [],
|
|
4452
|
-
summary: "Close the unterminated Markdown fence manually."
|
|
4453
|
-
}
|
|
4454
|
-
};
|
|
4455
|
-
}
|
|
4456
|
-
function findingDocument(finding, model) {
|
|
4457
|
-
const sourceId = finding.metadata?.["sourceId"];
|
|
4458
|
-
const path = finding.locations?.[0]?.path;
|
|
4459
|
-
return typeof sourceId === "string" && path !== void 0 ? findManagedDocument(model, sourceId, path) : void 0;
|
|
4460
|
-
}
|
|
4461
|
-
const managedBlockHashCheck = defineCheck({
|
|
4462
|
-
defaultSeverity: "warn",
|
|
4463
|
-
detect: detectManagedBlockDrift,
|
|
4464
|
-
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.
|
|
4465
|
-
|
|
4466
|
-
Re-run the check with \`--fix\` 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.`,
|
|
4467
|
-
fix: () => void 0,
|
|
4468
|
-
fixability: "guided",
|
|
4469
|
-
guidedFixes: guidedFixes$2,
|
|
4470
|
-
id: "MGD-001",
|
|
4471
|
-
scope: "global",
|
|
4472
|
-
title: "Aura-managed instruction blocks have not changed by hand"
|
|
4473
|
-
});
|
|
4474
|
-
function detectManagedBlockDrift(model) {
|
|
4475
|
-
return managedDocuments(model).flatMap((document) => findingsForDocument(document, model));
|
|
4476
|
-
}
|
|
4477
|
-
function findingsForDocument(document, model) {
|
|
4478
|
-
const parsed = document.parsed;
|
|
4479
|
-
const hiddenMarker = parsed.notes.find((note) => note.code === "unterminated-fence");
|
|
4480
|
-
if (hiddenMarker !== void 0) return [unterminatedFenceFinding(document, hiddenMarker.line, model)];
|
|
4481
|
-
if (parsed.status === "invalid") return [malformedFinding(document, parsed.problems, model)];
|
|
4482
|
-
if (parsed.status === "absent") return [];
|
|
4483
|
-
const manifestHashes = manifestSnippetHashes(model);
|
|
4484
|
-
return parsed.block.snippets.filter((snippet) => {
|
|
4485
|
-
const manifestHash = manifestHashes.get(snippet.id);
|
|
4486
|
-
return !snippet.hashMatches || manifestHash !== void 0 && manifestHash !== snippet.computedHash;
|
|
4487
|
-
}).map((snippet) => ({
|
|
4488
|
-
details: "Run `aura check --fix` and choose Keep yours, Restore, or Merge before running setup again.",
|
|
4489
|
-
id: `${document.sourceId}:${snippet.id}`,
|
|
4490
|
-
locations: [{
|
|
4491
|
-
line: snippet.startLine,
|
|
4492
|
-
path: document.path
|
|
4493
|
-
}],
|
|
4494
|
-
message: `Managed snippet ${snippet.id} in ${displayInstructionPath(document.path, model)} was edited by hand.`,
|
|
4495
|
-
metadata: {
|
|
4496
|
-
kind: "hash-mismatch",
|
|
4497
|
-
snippetId: snippet.id,
|
|
4498
|
-
sourceId: document.sourceId
|
|
4499
|
-
}
|
|
4500
|
-
}));
|
|
4501
|
-
}
|
|
4502
|
-
function unterminatedFenceFinding(document, line, model) {
|
|
4503
|
-
return {
|
|
4504
|
-
details: "Close the Markdown fence, then run `aura check --fix` again. Aura will not reconcile this document while its markers are hidden.",
|
|
4505
|
-
id: `${document.sourceId}:unterminated-fence`,
|
|
4506
|
-
locations: [{
|
|
4507
|
-
...line === void 0 ? {} : { line },
|
|
4508
|
-
path: document.path
|
|
4509
|
-
}],
|
|
4510
|
-
message: `An unclosed Markdown fence hides Aura-managed markers in ${displayInstructionPath(document.path, model)}.`,
|
|
4511
|
-
metadata: {
|
|
4512
|
-
kind: "unterminated-fence",
|
|
4513
|
-
sourceId: document.sourceId
|
|
4514
|
-
}
|
|
4515
|
-
};
|
|
4516
|
-
}
|
|
4517
|
-
function malformedFinding(document, problems, model) {
|
|
4518
|
-
return {
|
|
4519
|
-
details: `${problems.map((problem) => problem.message).join(" ")} Aura will not write to a block it cannot parse; repair the markers by hand.`,
|
|
4520
|
-
id: `${document.sourceId}:malformed-managed-block`,
|
|
4521
|
-
locations: problems.map((problem) => ({
|
|
4522
|
-
...problem.line === void 0 ? {} : { line: problem.line },
|
|
4523
|
-
path: document.path
|
|
4524
|
-
})),
|
|
4525
|
-
message: `The Aura-managed block in ${displayInstructionPath(document.path, model)} is malformed.`,
|
|
4526
|
-
metadata: {
|
|
4527
|
-
kind: "malformed",
|
|
4528
|
-
problems: problems.map((problem) => ({
|
|
4529
|
-
code: problem.code,
|
|
4530
|
-
...problem.line === void 0 ? {} : { line: problem.line }
|
|
4531
|
-
})),
|
|
4532
|
-
sourceId: document.sourceId
|
|
4533
|
-
}
|
|
4534
|
-
};
|
|
4535
|
-
}
|
|
4536
|
-
function manifestSnippetHashes(model) {
|
|
4537
|
-
if (model.manifest.status !== "ready") return /* @__PURE__ */ new Map();
|
|
4538
|
-
const hashes = /* @__PURE__ */ new Map();
|
|
4539
|
-
const ambiguous = /* @__PURE__ */ new Set();
|
|
4540
|
-
for (const snippet of model.manifest.value.snippets) if (hashes.has(snippet.id)) ambiguous.add(snippet.id);
|
|
4541
|
-
else hashes.set(snippet.id, snippet.hash);
|
|
4542
|
-
for (const id of ambiguous) hashes.delete(id);
|
|
4543
|
-
return hashes;
|
|
4544
|
-
}
|
|
4545
|
-
//#endregion
|
|
4546
4232
|
//#region ../../plugins/checks-core/src/mgd-002-fixes.ts
|
|
4547
|
-
function
|
|
4548
|
-
if (finding.checkId !== "MGD-002") return [];
|
|
4233
|
+
function managedSkillUpdateChoices(finding, model) {
|
|
4549
4234
|
const kind = finding.metadata?.["kind"];
|
|
4550
|
-
|
|
4551
|
-
if (kind === "snippet-missing") return missingSnippetChoices(finding, model);
|
|
4552
|
-
return kind === "skill-update" || kind === "skill-diverged" ? skillUpdateChoices(finding, model) : [];
|
|
4235
|
+
return finding.checkId === "MGD-002" && (kind === "skill-update" || kind === "skill-diverged") ? skillUpdateChoices(finding, model) : [];
|
|
4553
4236
|
}
|
|
4554
|
-
|
|
4555
|
-
|
|
4556
|
-
*
|
|
4557
|
-
* A diverged revision is a deliberate move to content that is not newer, so calling it an update
|
|
4558
|
-
* would misdescribe what the user is accepting.
|
|
4559
|
-
*/
|
|
4560
|
-
function applyLabel(finding, version) {
|
|
4561
|
-
return `${finding.metadata?.["kind"] === "snippet-update" || finding.metadata?.["kind"] === "skill-update" ? "Update to" : "Switch to"} ${version}`;
|
|
4562
|
-
}
|
|
4563
|
-
function snippetUpdateChoices(finding, model) {
|
|
4564
|
-
const context = readyFindingContext(finding, model);
|
|
4565
|
-
if (context === void 0) return [];
|
|
4566
|
-
const { contentId, manifest: manifestState } = context;
|
|
4567
|
-
const installed = manifestState.value.snippets.find((snippet) => snippet.id === contentId);
|
|
4568
|
-
const available = model.availableSnippets.find((snippet) => snippet.id === contentId);
|
|
4569
|
-
const located = locateManagedSnippet(model, contentId);
|
|
4570
|
-
if (installed === void 0 || available === void 0 || located === void 0) return [];
|
|
4571
|
-
if (!located.snippet.hashMatches || located.snippet.computedHash !== installed.hash) return [resolveDriftFirst(contentId)];
|
|
4572
|
-
const reconciled = reconcileManagedSnippet(located.document.content, contentId, {
|
|
4573
|
-
content: available.content,
|
|
4574
|
-
kind: "restore"
|
|
4575
|
-
});
|
|
4576
|
-
if (reconciled.status === "invalid") return [resolveDriftFirst(contentId)];
|
|
4577
|
-
const manifest = {
|
|
4578
|
-
...manifestState.value,
|
|
4579
|
-
snippets: manifestState.value.snippets.map((snippet) => snippet === installed ? {
|
|
4580
|
-
...snippet,
|
|
4581
|
-
hash: available.hash,
|
|
4582
|
-
pinned: false,
|
|
4583
|
-
version: available.version
|
|
4584
|
-
} : snippet)
|
|
4585
|
-
};
|
|
4586
|
-
return [{
|
|
4587
|
-
details: () => diffManagedSnippet(located.document.path, located.snippet.content, available.content),
|
|
4588
|
-
id: "update",
|
|
4589
|
-
label: applyLabel(finding, available.version),
|
|
4590
|
-
plan: {
|
|
4591
|
-
operations: [{
|
|
4592
|
-
content: reconciled.content,
|
|
4593
|
-
path: located.document.path,
|
|
4594
|
-
type: "write"
|
|
4595
|
-
}, createAuraManifestWriteOperation(manifestState, manifest)],
|
|
4596
|
-
summary: `Set managed snippet ${contentId} to ${available.version}.`
|
|
4597
|
-
}
|
|
4598
|
-
}];
|
|
4599
|
-
}
|
|
4600
|
-
function missingSnippetChoices(finding, model) {
|
|
4601
|
-
const context = readyFindingContext(finding, model);
|
|
4237
|
+
function skillUpdateChoices(finding, model) {
|
|
4238
|
+
const context = skillUpdateContext(finding, model);
|
|
4602
4239
|
if (context === void 0) return [];
|
|
4603
|
-
|
|
4604
|
-
|
|
4605
|
-
if (installed === void 0) return [];
|
|
4606
|
-
const pinnedManifest = {
|
|
4607
|
-
...manifest.value,
|
|
4608
|
-
snippets: manifest.value.snippets.map((snippet) => snippet === installed ? {
|
|
4609
|
-
...snippet,
|
|
4610
|
-
pinned: true
|
|
4611
|
-
} : snippet)
|
|
4612
|
-
};
|
|
4613
|
-
const choices = [{
|
|
4614
|
-
id: "pin",
|
|
4615
|
-
label: "Keep as pinned",
|
|
4616
|
-
plan: {
|
|
4617
|
-
operations: [createAuraManifestWriteOperation(manifest, pinnedManifest)],
|
|
4618
|
-
summary: `Keep unavailable snippet ${contentId} at its installed revision.`
|
|
4619
|
-
}
|
|
4620
|
-
}];
|
|
4621
|
-
const removal = removeSnippetPlan(model, contentId, installed);
|
|
4622
|
-
if (removal !== void 0) choices.push({
|
|
4623
|
-
id: "remove",
|
|
4624
|
-
label: "Remove",
|
|
4625
|
-
plan: removal
|
|
4626
|
-
});
|
|
4627
|
-
return choices;
|
|
4628
|
-
}
|
|
4629
|
-
function removeSnippetPlan(model, contentId, installed) {
|
|
4630
|
-
if (model.manifest.status !== "ready") return;
|
|
4631
|
-
const located = locateManagedSnippet(model, contentId);
|
|
4632
|
-
if (located === void 0) return;
|
|
4633
|
-
const preserve = located.document.parsed.status === "present" ? located.document.parsed.block.snippets.map((snippet) => snippet.id).filter((id) => id !== contentId) : [];
|
|
4634
|
-
const reconciled = reconcileParsedManagedBlock(located.document.content, located.document.parsed, [], {
|
|
4635
|
-
ownedSnippetIds: model.manifest.value.snippets.map((snippet) => snippet.id),
|
|
4636
|
-
preserveSnippetIds: preserve,
|
|
4637
|
-
previousSnippetHashes: new Map(model.manifest.value.snippets.map((snippet) => [snippet.id, snippet.hash]))
|
|
4638
|
-
});
|
|
4639
|
-
if (reconciled.status === "invalid") return;
|
|
4640
|
-
const manifest = {
|
|
4641
|
-
...model.manifest.value,
|
|
4642
|
-
snippets: model.manifest.value.snippets.filter((snippet) => snippet !== installed)
|
|
4643
|
-
};
|
|
4644
|
-
return {
|
|
4645
|
-
operations: [{
|
|
4646
|
-
content: reconciled.content,
|
|
4647
|
-
path: located.document.path,
|
|
4648
|
-
type: "write"
|
|
4649
|
-
}, createAuraManifestWriteOperation(model.manifest, manifest)],
|
|
4650
|
-
summary: `Remove unavailable managed snippet ${contentId}.`
|
|
4651
|
-
};
|
|
4240
|
+
if (context.shared.problem !== void 0 || context.shared.treeHash !== context.installed.treeHash) return [resolveSkillDriftFirst(context.contentId)];
|
|
4241
|
+
return [skillUpdateChoice(finding, context)];
|
|
4652
4242
|
}
|
|
4653
|
-
function
|
|
4243
|
+
function skillUpdateContext(finding, model) {
|
|
4654
4244
|
const context = readyFindingContext(finding, model);
|
|
4655
4245
|
const sourceId = finding.metadata?.["sourceId"];
|
|
4656
|
-
if (context === void 0 || typeof sourceId !== "string") return
|
|
4657
|
-
const { contentId, manifest
|
|
4658
|
-
const installed =
|
|
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);
|
|
4659
4249
|
const available = model.availableSkills?.find((skill) => skill.id === contentId && skill.source.id === sourceId);
|
|
4660
4250
|
const shared = model.sharedSkills?.find((skill) => skill.id === contentId);
|
|
4661
|
-
|
|
4662
|
-
|
|
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;
|
|
4663
4261
|
const manifest = {
|
|
4664
4262
|
...manifestState.value,
|
|
4665
4263
|
skills: manifestState.value.skills.map((skill) => skill === installed ? updatedSkill(skill, available) : skill)
|
|
4666
4264
|
};
|
|
4667
|
-
return
|
|
4265
|
+
return {
|
|
4668
4266
|
id: "update",
|
|
4669
|
-
label:
|
|
4267
|
+
label: `${finding.metadata?.["kind"] === "skill-update" ? "Update to" : "Switch to"} ${available.version}`,
|
|
4670
4268
|
plan: {
|
|
4671
4269
|
operations: [...planSharedSkillTreeUpdate(shared.path, shared.entries, available), createAuraManifestWriteOperation(manifestState, manifest)],
|
|
4672
4270
|
summary: `Set managed skill ${contentId} to ${available.version}.`
|
|
4673
4271
|
}
|
|
4674
|
-
}
|
|
4272
|
+
};
|
|
4675
4273
|
}
|
|
4676
4274
|
function readyFindingContext(finding, model) {
|
|
4677
4275
|
const contentId = finding.metadata?.["contentId"];
|
|
@@ -4688,27 +4286,6 @@ function updatedSkill(installed, available) {
|
|
|
4688
4286
|
version: available.version
|
|
4689
4287
|
};
|
|
4690
4288
|
}
|
|
4691
|
-
function locateManagedSnippet(model, contentId) {
|
|
4692
|
-
for (const document of managedDocuments(model)) {
|
|
4693
|
-
if (document.parsed.status !== "present") continue;
|
|
4694
|
-
const snippet = document.parsed.block.snippets.find((candidate) => candidate.id === contentId);
|
|
4695
|
-
if (snippet !== void 0) return {
|
|
4696
|
-
document,
|
|
4697
|
-
snippet
|
|
4698
|
-
};
|
|
4699
|
-
}
|
|
4700
|
-
}
|
|
4701
|
-
function resolveDriftFirst(contentId) {
|
|
4702
|
-
return {
|
|
4703
|
-
id: "resolve-drift",
|
|
4704
|
-
label: "Resolve local edits first",
|
|
4705
|
-
plan: {
|
|
4706
|
-
manualSteps: [`Run \`aura check --only MGD-001 --fix\` and resolve edits to ${contentId}, then review this update again.`],
|
|
4707
|
-
operations: [],
|
|
4708
|
-
summary: `Resolve local edits to ${contentId} before updating it.`
|
|
4709
|
-
}
|
|
4710
|
-
};
|
|
4711
|
-
}
|
|
4712
4289
|
function resolveSkillDriftFirst(contentId) {
|
|
4713
4290
|
return {
|
|
4714
4291
|
id: "resolve-drift",
|
|
@@ -4720,55 +4297,21 @@ function resolveSkillDriftFirst(contentId) {
|
|
|
4720
4297
|
}
|
|
4721
4298
|
};
|
|
4722
4299
|
}
|
|
4300
|
+
//#endregion
|
|
4301
|
+
//#region ../../plugins/checks-core/src/mgd-002.ts
|
|
4723
4302
|
const managedContentUpdateCheck = defineCheck({
|
|
4724
4303
|
defaultSeverity: "info",
|
|
4725
|
-
detect:
|
|
4726
|
-
explain: "Aura records the exact
|
|
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.",
|
|
4727
4306
|
fix: () => void 0,
|
|
4728
4307
|
fixability: "guided",
|
|
4729
|
-
guidedFixes:
|
|
4308
|
+
guidedFixes: managedSkillUpdateChoices,
|
|
4730
4309
|
id: "MGD-002",
|
|
4731
4310
|
scope: "global",
|
|
4732
|
-
title: "Managed
|
|
4311
|
+
title: "Managed skills are at their reviewed source revisions"
|
|
4733
4312
|
});
|
|
4734
|
-
function
|
|
4735
|
-
|
|
4736
|
-
return [...model.manifest.value.snippets.flatMap((snippet) => snippetFinding(snippet, model)), ...model.manifest.value.skills.flatMap((skill) => skillFinding(skill, model))];
|
|
4737
|
-
}
|
|
4738
|
-
function snippetFinding(installed, model) {
|
|
4739
|
-
if (installed.pinned) return [];
|
|
4740
|
-
const available = model.availableSnippets.find((snippet) => snippet.id === installed.id);
|
|
4741
|
-
if (available === void 0) return [{
|
|
4742
|
-
id: `snippet:${installed.id}:missing`,
|
|
4743
|
-
message: `Managed snippet ${installed.id} at version ${installed.version} is no longer provided by an installed plugin.`,
|
|
4744
|
-
metadata: {
|
|
4745
|
-
contentId: installed.id,
|
|
4746
|
-
kind: "snippet-missing"
|
|
4747
|
-
}
|
|
4748
|
-
}];
|
|
4749
|
-
const status = managedContentRevisionStatus(installed.version, installed.hash, available.version, available.hash);
|
|
4750
|
-
if (status === "current") return [];
|
|
4751
|
-
if (status === "diverged") return [{
|
|
4752
|
-
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.",
|
|
4753
|
-
id: `snippet:${installed.id}:diverged`,
|
|
4754
|
-
message: `Managed snippet ${installed.id} is recorded at version ${installed.version}, but the installed plugin offers ${available.version}, which is not newer.`,
|
|
4755
|
-
metadata: {
|
|
4756
|
-
availableVersion: available.version,
|
|
4757
|
-
contentId: installed.id,
|
|
4758
|
-
installedVersion: installed.version,
|
|
4759
|
-
kind: "snippet-diverged"
|
|
4760
|
-
}
|
|
4761
|
-
}];
|
|
4762
|
-
return [{
|
|
4763
|
-
id: `snippet:${installed.id}:update`,
|
|
4764
|
-
message: `Managed snippet ${installed.id} can update from version ${installed.version} to ${available.version}.`,
|
|
4765
|
-
metadata: {
|
|
4766
|
-
availableVersion: available.version,
|
|
4767
|
-
contentId: installed.id,
|
|
4768
|
-
installedVersion: installed.version,
|
|
4769
|
-
kind: "snippet-update"
|
|
4770
|
-
}
|
|
4771
|
-
}];
|
|
4313
|
+
function detectManagedSkillUpdates(model) {
|
|
4314
|
+
return model.manifest.status === "ready" ? model.manifest.value.skills.flatMap((skill) => skillFinding(skill, model)) : [];
|
|
4772
4315
|
}
|
|
4773
4316
|
function skillFinding(installed, model) {
|
|
4774
4317
|
if (installed.pinned || !installed.source.startsWith("plugin:")) return [];
|
|
@@ -5188,7 +4731,7 @@ const mcp003 = defineCheck({
|
|
|
5188
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.`,
|
|
5189
4732
|
fix: () => void 0,
|
|
5190
4733
|
fixability: "guided",
|
|
5191
|
-
guidedFixes
|
|
4734
|
+
guidedFixes,
|
|
5192
4735
|
id: CHECK_ID,
|
|
5193
4736
|
scope: "global",
|
|
5194
4737
|
title: "Configured MCP servers can be reached"
|
|
@@ -5258,14 +4801,14 @@ function findingFor(server, probe, model) {
|
|
|
5258
4801
|
...probe.transient === true ? { severity: "warn" } : {}
|
|
5259
4802
|
};
|
|
5260
4803
|
}
|
|
5261
|
-
function guidedFixes
|
|
4804
|
+
function guidedFixes(finding, model) {
|
|
5262
4805
|
const server = findingServer(finding, model);
|
|
5263
4806
|
const kind = finding.metadata?.["kind"];
|
|
5264
4807
|
if (server === void 0 || kind !== "command" && kind !== "url") return [];
|
|
5265
4808
|
return [repairChoice(server, kind, model), removeChoice(server, model)];
|
|
5266
4809
|
}
|
|
5267
4810
|
function repairChoice(server, kind, model) {
|
|
5268
|
-
const location = sourcePath
|
|
4811
|
+
const location = sourcePath(server, model) ?? "the application's MCP configuration";
|
|
5269
4812
|
return {
|
|
5270
4813
|
id: "repair",
|
|
5271
4814
|
label: "Repair server",
|
|
@@ -5293,7 +4836,7 @@ function removeChoice(server, model) {
|
|
|
5293
4836
|
};
|
|
5294
4837
|
}
|
|
5295
4838
|
function manualRemoval(server, model, blockers) {
|
|
5296
|
-
const path = sourcePath
|
|
4839
|
+
const path = sourcePath(server, model) ?? "the application's MCP configuration file";
|
|
5297
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));
|
|
5298
4841
|
return {
|
|
5299
4842
|
manualSteps: [
|
|
@@ -5315,72 +4858,9 @@ function findingServer(finding, model) {
|
|
|
5315
4858
|
if (typeof appId !== "string" || typeof name !== "string" || scope !== "global" && scope !== "project" || typeof sourceId !== "string") return;
|
|
5316
4859
|
return model.mcpServers.find((server) => server.appId === appId && server.name === name && server.scope === scope && server.sourceId === sourceId);
|
|
5317
4860
|
}
|
|
5318
|
-
function sourcePath
|
|
4861
|
+
function sourcePath(server, model) {
|
|
5319
4862
|
return model.apps.find((app) => app.adapterId === server.appId)?.sourceFiles.find((file) => file.spec.id === server.sourceId)?.spec.path;
|
|
5320
4863
|
}
|
|
5321
|
-
const mcp004 = defineCheck({
|
|
5322
|
-
defaultSeverity: "error",
|
|
5323
|
-
detect: detectInlineSecrets,
|
|
5324
|
-
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.
|
|
5325
|
-
|
|
5326
|
-
Re-run with \`--fix\` 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.`,
|
|
5327
|
-
findingGroup: {
|
|
5328
|
-
description: "Replace inline credentials with environment-variable references.",
|
|
5329
|
-
id: "checks-core/mcp-credentials",
|
|
5330
|
-
title: "Protect credentials stored in MCP configuration"
|
|
5331
|
-
},
|
|
5332
|
-
fix: remediation,
|
|
5333
|
-
fixability: "guided",
|
|
5334
|
-
guidedFixes,
|
|
5335
|
-
id: "MCP-004",
|
|
5336
|
-
scope: "global",
|
|
5337
|
-
title: "MCP credentials use environment references"
|
|
5338
|
-
});
|
|
5339
|
-
function detectInlineSecrets(model) {
|
|
5340
|
-
return model.mcpSecretSightings.map((sighting) => {
|
|
5341
|
-
const supported = canPlanMcpSecretRemediation(model, sighting);
|
|
5342
|
-
const path = sourcePath(model, sighting);
|
|
5343
|
-
return {
|
|
5344
|
-
details: supported ? `Aura can replace this field with ${sighting.suggestedEnvName} as part of a whole-file guided rewrite.` : manualDetails(sighting),
|
|
5345
|
-
...supported ? {} : { fixability: "manual" },
|
|
5346
|
-
id: `${sighting.appId}:${sighting.sourceId}:${sighting.serverName}:${sighting.field}`,
|
|
5347
|
-
...path === void 0 ? {} : { locations: [{ path }] },
|
|
5348
|
-
message: `MCP server ${sighting.serverName} in ${sighting.appId} stores a credential inline at ${sighting.field}.`,
|
|
5349
|
-
metadata: {
|
|
5350
|
-
appId: sighting.appId,
|
|
5351
|
-
field: sighting.field,
|
|
5352
|
-
serverName: sighting.serverName,
|
|
5353
|
-
sourceId: sighting.sourceId,
|
|
5354
|
-
suggestedEnvName: sighting.suggestedEnvName
|
|
5355
|
-
}
|
|
5356
|
-
};
|
|
5357
|
-
});
|
|
5358
|
-
}
|
|
5359
|
-
function remediation(finding, model) {
|
|
5360
|
-
const sighting = findingSighting(finding, model);
|
|
5361
|
-
return sighting === void 0 ? void 0 : planMcpSecretRemediation(model, sighting);
|
|
5362
|
-
}
|
|
5363
|
-
function guidedFixes(finding, model) {
|
|
5364
|
-
const plan = remediation(finding, model);
|
|
5365
|
-
return plan === void 0 ? [] : [{
|
|
5366
|
-
id: "replace-file-secrets",
|
|
5367
|
-
label: "Replace inline credentials in this file",
|
|
5368
|
-
plan
|
|
5369
|
-
}];
|
|
5370
|
-
}
|
|
5371
|
-
function findingSighting(finding, model) {
|
|
5372
|
-
const appId = finding.metadata?.["appId"];
|
|
5373
|
-
const sourceId = finding.metadata?.["sourceId"];
|
|
5374
|
-
const serverName = finding.metadata?.["serverName"];
|
|
5375
|
-
const field = finding.metadata?.["field"];
|
|
5376
|
-
return model.mcpSecretSightings.find((sighting) => sighting.appId === appId && sighting.sourceId === sourceId && sighting.serverName === serverName && sighting.field === field);
|
|
5377
|
-
}
|
|
5378
|
-
function sourcePath(model, sighting) {
|
|
5379
|
-
return model.apps.find((app) => app.adapterId === sighting.appId)?.sourceFiles.find((file) => file.spec.id === sighting.sourceId)?.spec.path;
|
|
5380
|
-
}
|
|
5381
|
-
function manualDetails(sighting) {
|
|
5382
|
-
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.`;
|
|
5383
|
-
}
|
|
5384
4864
|
//#endregion
|
|
5385
4865
|
//#region ../../plugins/checks-core/src/mcp-005-placement.ts
|
|
5386
4866
|
/** Identifier every MCP-005 module stamps and matches on. */
|
|
@@ -5901,13 +5381,11 @@ const checksCorePlugin = definePlugin({
|
|
|
5901
5381
|
instructionLinkIntegrityCheck,
|
|
5902
5382
|
instructionContextBudgetCheck,
|
|
5903
5383
|
instructionPrecedenceCheck,
|
|
5904
|
-
managedBlockHashCheck,
|
|
5905
5384
|
managedContentUpdateCheck,
|
|
5906
5385
|
unmanagedDetectedAppCheck,
|
|
5907
5386
|
mcp001,
|
|
5908
5387
|
mcp002,
|
|
5909
5388
|
mcp003,
|
|
5910
|
-
mcp004,
|
|
5911
5389
|
mcp005,
|
|
5912
5390
|
skl001,
|
|
5913
5391
|
skl002,
|