@tryaura/aura-cli 0.1.0 → 0.2.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.
- package/README.md +3 -2
- package/dist/bin/aura.js +4 -3
- package/dist/index.d.ts +12 -2
- package/dist/index.js +2 -2
- package/dist/plugins/index.d.ts +1 -1
- package/dist/plugins/index.js +1 -1
- package/dist/{plugins-n3DS8XXi.js → plugins-SZegBVXF.js} +354 -108
- package/dist/{run-pMPxJqFQ.js → run.boundary-BtMqQAEQ.js} +4004 -1351
- package/dist/{skill-deployment-plan-C4GTrVSy.js → shared-link-plan-DVgOyqP9.js} +280 -92
- package/dist/{types-DmBu6g0d.d.ts → types--E7zqKfw.d.ts} +4 -2
- package/package.json +6 -6
- package/schema/check-output-v1.schema.json +18 -1
|
@@ -1,12 +1,11 @@
|
|
|
1
|
-
import {
|
|
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";
|
|
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
|
-
import { dirname, extname, isAbsolute, join,
|
|
3
|
+
import { dirname, extname, isAbsolute, join, resolve, sep } from "node:path";
|
|
4
4
|
import { parse, stringify } from "smol-toml";
|
|
5
5
|
import { Buffer } from "node:buffer";
|
|
6
6
|
import { parseTOML } from "toml-eslint-parser";
|
|
7
7
|
import ignore from "ignore";
|
|
8
8
|
import { createHash } from "node:crypto";
|
|
9
|
-
import { createTwoFilesPatch } from "diff";
|
|
10
9
|
import { isDeepStrictEqual } from "node:util";
|
|
11
10
|
//#region ../../plugins/adapter-claude-code/src/contract.ts
|
|
12
11
|
/**
|
|
@@ -2455,19 +2454,15 @@ function compareParagraphs$1(left, right) {
|
|
|
2455
2454
|
//#region ../../plugins/checks-core/src/instruction-paths.ts
|
|
2456
2455
|
/** Names an instruction file the way a user can address it from the current workspace. */
|
|
2457
2456
|
function displayInstructionPath(path, model) {
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
2457
|
+
return displayPath(path, {
|
|
2458
|
+
cwd: model.cwd,
|
|
2459
|
+
homeDir: model.homeDir,
|
|
2460
|
+
...model.projectRoot === void 0 ? {} : { projectRoot: model.projectRoot }
|
|
2461
|
+
});
|
|
2462
2462
|
}
|
|
2463
2463
|
function instructionLineRange(startLine, endLine) {
|
|
2464
2464
|
return startLine === endLine ? String(startLine) : `${String(startLine)}-${String(endLine)}`;
|
|
2465
2465
|
}
|
|
2466
|
-
function pathInside(root, path) {
|
|
2467
|
-
const difference = relative(root, path);
|
|
2468
|
-
if (difference.length === 0 || difference === ".." || difference.startsWith(`..${sep}`) || isAbsolute(difference)) return;
|
|
2469
|
-
return difference.split(sep).join("/");
|
|
2470
|
-
}
|
|
2471
2466
|
//#endregion
|
|
2472
2467
|
//#region ../../plugins/checks-core/src/ins-003/clusters.ts
|
|
2473
2468
|
/** Groups duplicate-pair edges into transitive connected components. */
|
|
@@ -2642,6 +2637,11 @@ const duplicateInstructionsCheck = defineCheck({
|
|
|
2642
2637
|
explain: `Repeated guidance across agent instruction files is easy to update in one place and forget in another. Similar copies also consume context repeatedly and can diverge into subtly different rules.
|
|
2643
2638
|
|
|
2644
2639
|
Review each reported cluster and keep its clearest version in the shared instruction source. Remove the redundant copies only after confirming their scopes and conditions are preserved.`,
|
|
2640
|
+
findingGroup: {
|
|
2641
|
+
description: "Keep each piece of guidance in one canonical source.",
|
|
2642
|
+
id: "checks-core/duplicate-instructions",
|
|
2643
|
+
title: "Consolidate duplicated instruction guidance"
|
|
2644
|
+
},
|
|
2645
2645
|
fixability: "manual",
|
|
2646
2646
|
id: "INS-003",
|
|
2647
2647
|
scope: "global",
|
|
@@ -4015,61 +4015,13 @@ function findingForProjectSignals(document, report, model) {
|
|
|
4015
4015
|
};
|
|
4016
4016
|
}
|
|
4017
4017
|
//#endregion
|
|
4018
|
-
//#region ../core/src/managed-block/reconcile-snippet.ts
|
|
4019
|
-
/** Reconciles exactly one snippet while preserving every other source byte. */
|
|
4020
|
-
function reconcileManagedSnippet(source, snippetId, resolution) {
|
|
4021
|
-
const current = readManagedBlock(source);
|
|
4022
|
-
if (current.status === "invalid") return invalidResult(source, current.notes, current.problems);
|
|
4023
|
-
if (current.status === "absent") return missingSnippet(source, current.notes, snippetId, "an Aura-managed block");
|
|
4024
|
-
const snippet = current.block.snippets.find((candidate) => candidate.id === snippetId);
|
|
4025
|
-
if (snippet === void 0) return missingSnippet(source, current.notes, snippetId, "the Aura-managed block");
|
|
4026
|
-
const lineEnding = markerLineEnding(source, snippet.startOffset, snippet.contentStartOffset);
|
|
4027
|
-
const canonical = resolution.kind === "restore" ? canonicalizeManagedSnippet(resolution.content) : void 0;
|
|
4028
|
-
if (canonical !== void 0) {
|
|
4029
|
-
const problems = managedSnippetContentProblems(snippet.id, canonical);
|
|
4030
|
-
if (problems.length > 0) return invalidResult(source, current.notes, problems);
|
|
4031
|
-
}
|
|
4032
|
-
const content = canonical === void 0 ? snippet.content : withLineEnding(canonical, lineEnding);
|
|
4033
|
-
const hash = canonical === void 0 ? snippet.computedHash : hashCanonicalManagedSnippet(canonical);
|
|
4034
|
-
const opening = `${AURA_MANAGED_SNIPPET_BEGIN_PREFIX}${snippet.id} sha256=${hash} -->${lineEnding}`;
|
|
4035
|
-
const updated = source.slice(0, snippet.startOffset) + opening + content + source.slice(snippet.contentEndOffset);
|
|
4036
|
-
return updated === source ? Object.freeze({
|
|
4037
|
-
content: source,
|
|
4038
|
-
notes: current.notes,
|
|
4039
|
-
status: "unchanged"
|
|
4040
|
-
}) : Object.freeze({
|
|
4041
|
-
content: updated,
|
|
4042
|
-
notes: current.notes,
|
|
4043
|
-
status: "updated"
|
|
4044
|
-
});
|
|
4045
|
-
}
|
|
4046
|
-
/** Creates the opt-in edited-versus-canonical detail used by the merge choice. */
|
|
4047
|
-
function diffManagedSnippet(path, editedContent, canonicalContent) {
|
|
4048
|
-
return createTwoFilesPatch(`${path} (edited)`, `${path} (canonical)`, canonicalizeManagedSnippet(editedContent), canonicalizeManagedSnippet(canonicalContent), "edited", "canonical", { context: 3 });
|
|
4049
|
-
}
|
|
4050
|
-
function missingSnippet(source, notes, snippetId, location) {
|
|
4051
|
-
return invalidResult(source, notes, [Object.freeze({
|
|
4052
|
-
code: "missing-snippet",
|
|
4053
|
-
message: `Snippet "${snippetId}" does not exist in ${location}.`
|
|
4054
|
-
})]);
|
|
4055
|
-
}
|
|
4056
|
-
function invalidResult(source, notes, problems) {
|
|
4057
|
-
return Object.freeze({
|
|
4058
|
-
content: source,
|
|
4059
|
-
notes,
|
|
4060
|
-
problems,
|
|
4061
|
-
status: "invalid"
|
|
4062
|
-
});
|
|
4063
|
-
}
|
|
4064
|
-
function markerLineEnding(source, startOffset, contentStartOffset) {
|
|
4065
|
-
return source.slice(startOffset, contentStartOffset).endsWith("\r\n") ? "\r\n" : "\n";
|
|
4066
|
-
}
|
|
4067
|
-
function withLineEnding(content, lineEnding) {
|
|
4068
|
-
return lineEnding === "\n" ? content : content.replaceAll("\n", lineEnding);
|
|
4069
|
-
}
|
|
4070
|
-
//#endregion
|
|
4071
4018
|
//#region ../../plugins/checks-core/src/instructions.ts
|
|
4072
4019
|
const READY = "Aura can add the missing link with check --fix.";
|
|
4020
|
+
const SHARED_INSTRUCTION_GROUP = Object.freeze({
|
|
4021
|
+
description: "Create the shared source and connect supported agent applications.",
|
|
4022
|
+
id: "checks-core/shared-instructions",
|
|
4023
|
+
title: "Complete the shared instruction setup"
|
|
4024
|
+
});
|
|
4073
4025
|
const sharedInstructionsCheck = defineCheck({
|
|
4074
4026
|
defaultSeverity: "error",
|
|
4075
4027
|
detect(model) {
|
|
@@ -4090,6 +4042,7 @@ const sharedInstructionsCheck = defineCheck({
|
|
|
4090
4042
|
explain: `A non-empty shared instruction source gives every configured agent application one canonical set of guidance. Without it, application-specific files become independent copies that drift as preferences change.
|
|
4091
4043
|
|
|
4092
4044
|
Re-run the check with \`--fix\` to create the shared source from Aura's starter template. Review the generated ~/agents/AGENTS.md, add personal guidance there, and check again.`,
|
|
4045
|
+
findingGroup: SHARED_INSTRUCTION_GROUP,
|
|
4093
4046
|
fix(_finding, model) {
|
|
4094
4047
|
const shared = model.sharedInstructions;
|
|
4095
4048
|
if (shared.problem !== void 0 || (shared.content?.trim().length ?? 0) > 0) return;
|
|
@@ -4116,6 +4069,7 @@ const sharedInstructionLinksCheck = defineCheck({
|
|
|
4116
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.
|
|
4117
4070
|
|
|
4118
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.`,
|
|
4072
|
+
findingGroup: SHARED_INSTRUCTION_GROUP,
|
|
4119
4073
|
fix(finding, model) {
|
|
4120
4074
|
const app = model.apps.find((candidate) => candidate.adapterId === findingAppId(finding));
|
|
4121
4075
|
if (app === void 0) return;
|
|
@@ -4133,6 +4087,7 @@ function detectMissingLink(app, model) {
|
|
|
4133
4087
|
const sharedPath = resolve(model.sharedInstructions.path);
|
|
4134
4088
|
if (shared !== void 0 && linksToShared(app, shared, sharedPath)) return [];
|
|
4135
4089
|
const outcome = planSharedInstructionLink(app, model);
|
|
4090
|
+
if ("plan" in outcome && outcome.plan.operations.length === 0) return [];
|
|
4136
4091
|
const blocked = "blocked" in outcome;
|
|
4137
4092
|
return [{
|
|
4138
4093
|
details: blocked ? outcome.blocked : READY,
|
|
@@ -4143,10 +4098,16 @@ function detectMissingLink(app, model) {
|
|
|
4143
4098
|
metadata: { appId: app.adapterId }
|
|
4144
4099
|
}];
|
|
4145
4100
|
}
|
|
4146
|
-
/**
|
|
4101
|
+
/**
|
|
4102
|
+
* Whether the declared entry already carries a link the declared mechanism would have written.
|
|
4103
|
+
*
|
|
4104
|
+
* Target validity belongs to INS-001 and INS-006. INS-002 only owns the link's mechanism and
|
|
4105
|
+
* destination; treating a missing shared source as a missing link produces a zero-operation fix
|
|
4106
|
+
* because the planner correctly sees that the link itself is already converged.
|
|
4107
|
+
*/
|
|
4147
4108
|
function linksToShared(app, shared, sharedPath) {
|
|
4148
4109
|
const entryPath = resolve(shared.entryPath);
|
|
4149
|
-
return app.instructionFiles.some((document) => resolve(document.path) === entryPath && document.links.some((link) =>
|
|
4110
|
+
return app.instructionFiles.some((document) => resolve(document.path) === entryPath && document.links.some((link) => compatibleLink(shared.kind, link.kind) && resolve(link.targetPath) === sharedPath));
|
|
4150
4111
|
}
|
|
4151
4112
|
/** Which parsed link kinds each declared mechanism can be satisfied by. */
|
|
4152
4113
|
function compatibleLink(declared, found) {
|
|
@@ -4323,9 +4284,9 @@ function isSymlinkDocument(document, model) {
|
|
|
4323
4284
|
}
|
|
4324
4285
|
//#endregion
|
|
4325
4286
|
//#region ../../plugins/checks-core/src/mgd-001-fixes.ts
|
|
4326
|
-
const CHECK_ID$
|
|
4287
|
+
const CHECK_ID$4 = "MGD-001";
|
|
4327
4288
|
function guidedFixes$2(finding, model) {
|
|
4328
|
-
if (finding.checkId !== CHECK_ID$
|
|
4289
|
+
if (finding.checkId !== CHECK_ID$4) return [];
|
|
4329
4290
|
const kind = finding.metadata?.["kind"];
|
|
4330
4291
|
if (kind === "malformed") return [malformedMergeChoice(finding)];
|
|
4331
4292
|
if (kind === "unterminated-fence") return [unterminatedFenceChoice(finding)];
|
|
@@ -4576,6 +4537,302 @@ function manifestSnippetHashes(model) {
|
|
|
4576
4537
|
return hashes;
|
|
4577
4538
|
}
|
|
4578
4539
|
//#endregion
|
|
4540
|
+
//#region ../../plugins/checks-core/src/mgd-002-fixes.ts
|
|
4541
|
+
function managedContentUpdateChoices(finding, model) {
|
|
4542
|
+
if (finding.checkId !== "MGD-002") return [];
|
|
4543
|
+
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
|
+
}];
|
|
4593
|
+
}
|
|
4594
|
+
function missingSnippetChoices(finding, model) {
|
|
4595
|
+
const context = readyFindingContext(finding, model);
|
|
4596
|
+
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
|
+
};
|
|
4646
|
+
}
|
|
4647
|
+
function skillUpdateChoices(finding, model) {
|
|
4648
|
+
const context = readyFindingContext(finding, model);
|
|
4649
|
+
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);
|
|
4653
|
+
const available = model.availableSkills?.find((skill) => skill.id === contentId && skill.source.id === sourceId);
|
|
4654
|
+
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)];
|
|
4657
|
+
const manifest = {
|
|
4658
|
+
...manifestState.value,
|
|
4659
|
+
skills: manifestState.value.skills.map((skill) => skill === installed ? updatedSkill(skill, available) : skill)
|
|
4660
|
+
};
|
|
4661
|
+
return [{
|
|
4662
|
+
id: "update",
|
|
4663
|
+
label: applyLabel(finding, available.version),
|
|
4664
|
+
plan: {
|
|
4665
|
+
operations: [...planSharedSkillTreeUpdate(shared.path, shared.entries, available), createAuraManifestWriteOperation(manifestState, manifest)],
|
|
4666
|
+
summary: `Set managed skill ${contentId} to ${available.version}.`
|
|
4667
|
+
}
|
|
4668
|
+
}];
|
|
4669
|
+
}
|
|
4670
|
+
function readyFindingContext(finding, model) {
|
|
4671
|
+
const contentId = finding.metadata?.["contentId"];
|
|
4672
|
+
return typeof contentId === "string" && model.manifest.status === "ready" ? {
|
|
4673
|
+
contentId,
|
|
4674
|
+
manifest: model.manifest
|
|
4675
|
+
} : void 0;
|
|
4676
|
+
}
|
|
4677
|
+
function updatedSkill(installed, available) {
|
|
4678
|
+
return {
|
|
4679
|
+
...installed,
|
|
4680
|
+
pinned: false,
|
|
4681
|
+
treeHash: available.treeHash,
|
|
4682
|
+
version: available.version
|
|
4683
|
+
};
|
|
4684
|
+
}
|
|
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
|
+
function resolveSkillDriftFirst(contentId) {
|
|
4707
|
+
return {
|
|
4708
|
+
id: "resolve-drift",
|
|
4709
|
+
label: "Review local edits first",
|
|
4710
|
+
plan: {
|
|
4711
|
+
manualSteps: [`Review the locally changed shared skill ${contentId}; pin or reconcile it before applying the source update.`],
|
|
4712
|
+
operations: [],
|
|
4713
|
+
summary: `Review local edits to skill ${contentId} before updating it.`
|
|
4714
|
+
}
|
|
4715
|
+
};
|
|
4716
|
+
}
|
|
4717
|
+
const managedContentUpdateCheck = defineCheck({
|
|
4718
|
+
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.",
|
|
4721
|
+
fix: () => void 0,
|
|
4722
|
+
fixability: "guided",
|
|
4723
|
+
guidedFixes: managedContentUpdateChoices,
|
|
4724
|
+
id: "MGD-002",
|
|
4725
|
+
scope: "global",
|
|
4726
|
+
title: "Managed snippets and skills are at their reviewed source revisions"
|
|
4727
|
+
});
|
|
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
|
+
}];
|
|
4766
|
+
}
|
|
4767
|
+
function skillFinding(installed, model) {
|
|
4768
|
+
if (installed.pinned || !installed.source.startsWith("plugin:")) return [];
|
|
4769
|
+
const available = model.availableSkills?.find((skill) => skill.id === installed.id && skill.source.id === installed.source);
|
|
4770
|
+
if (available === void 0) return [];
|
|
4771
|
+
const status = managedContentRevisionStatus(installed.version, installed.treeHash, available.version, available.treeHash);
|
|
4772
|
+
if (status === "current") return [];
|
|
4773
|
+
const location = { path: join(sharedSkillsRoot(model.homeDir), installed.id) };
|
|
4774
|
+
if (status === "diverged") return [{
|
|
4775
|
+
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 skill to stop the comparison.",
|
|
4776
|
+
id: `skill:${installed.source}:${installed.id}:diverged`,
|
|
4777
|
+
locations: [location],
|
|
4778
|
+
message: `Managed skill ${installed.id} is recorded at version ${installed.version}, but the installed plugin offers ${available.version}, which is not newer.`,
|
|
4779
|
+
metadata: {
|
|
4780
|
+
availableVersion: available.version,
|
|
4781
|
+
contentId: installed.id,
|
|
4782
|
+
installedVersion: installed.version,
|
|
4783
|
+
kind: "skill-diverged",
|
|
4784
|
+
sourceId: installed.source
|
|
4785
|
+
}
|
|
4786
|
+
}];
|
|
4787
|
+
return [{
|
|
4788
|
+
id: `skill:${installed.source}:${installed.id}:update`,
|
|
4789
|
+
locations: [location],
|
|
4790
|
+
message: `Managed skill ${installed.id} can update from version ${installed.version} to ${available.version}.`,
|
|
4791
|
+
metadata: {
|
|
4792
|
+
availableVersion: available.version,
|
|
4793
|
+
contentId: installed.id,
|
|
4794
|
+
installedVersion: installed.version,
|
|
4795
|
+
kind: "skill-update",
|
|
4796
|
+
sourceId: installed.source
|
|
4797
|
+
}
|
|
4798
|
+
}];
|
|
4799
|
+
}
|
|
4800
|
+
/**
|
|
4801
|
+
* Deliberately manual: the only resolution is a full `setup` run.
|
|
4802
|
+
*
|
|
4803
|
+
* Recording the decision means asking the apps picker, and this check must never start a nested
|
|
4804
|
+
* command. Declaring it fixable would open an interactive fix on a single choice that changes
|
|
4805
|
+
* nothing — a prompt whose only answer leaves the finding exactly where it was — so the next step
|
|
4806
|
+
* is carried on the finding itself instead.
|
|
4807
|
+
*/
|
|
4808
|
+
const unmanagedDetectedAppCheck = defineCheck({
|
|
4809
|
+
defaultSeverity: "info",
|
|
4810
|
+
detect: detectUnmanagedApps,
|
|
4811
|
+
explain: "Aura can inspect an installed agent application without managing it. This informational check highlights detected applications that have never been accepted or ignored in the Aura manifest.\n\nRun `aura setup` to add the application, or leave it unchecked once to record that Aura should ignore it. There is no automatic fix: the decision belongs to the apps step, and this check never starts a nested command.",
|
|
4812
|
+
findingGroup: {
|
|
4813
|
+
description: "Run `aura setup` to manage them or record an ignore decision.",
|
|
4814
|
+
id: "checks-core/application-management",
|
|
4815
|
+
title: "Decide which detected applications Aura should manage"
|
|
4816
|
+
},
|
|
4817
|
+
fixability: "manual",
|
|
4818
|
+
id: "MGD-003",
|
|
4819
|
+
scope: "global",
|
|
4820
|
+
title: "Detected applications have an explicit Aura management decision"
|
|
4821
|
+
});
|
|
4822
|
+
function detectUnmanagedApps(model) {
|
|
4823
|
+
const apps = model.manifest.status === "ready" ? model.manifest.value.apps : {};
|
|
4824
|
+
const ignored = new Set(model.manifest.status === "ready" ? model.manifest.value.ignoredApps ?? [] : []);
|
|
4825
|
+
return model.apps.flatMap((app) => {
|
|
4826
|
+
if (app.synthetic === true || Object.hasOwn(apps, app.adapterId) || ignored.has(app.adapterId)) return [];
|
|
4827
|
+
return [{
|
|
4828
|
+
details: `Run \`aura setup\` and select ${app.displayName} to manage it, or leave it unchecked to record an ignored-app decision.`,
|
|
4829
|
+
id: app.adapterId,
|
|
4830
|
+
message: `${app.displayName} detected but not managed — run \`aura setup\` to add it.`,
|
|
4831
|
+
metadata: { appId: app.adapterId }
|
|
4832
|
+
}];
|
|
4833
|
+
});
|
|
4834
|
+
}
|
|
4835
|
+
//#endregion
|
|
4579
4836
|
//#region ../../plugins/checks-core/src/mcp-common.ts
|
|
4580
4837
|
/**
|
|
4581
4838
|
* The servers the manifest selects for applications this scan can actually converge.
|
|
@@ -4681,7 +4938,17 @@ function detectParity(model) {
|
|
|
4681
4938
|
if (model.manifest.status !== "ready") return [];
|
|
4682
4939
|
const desired = desiredMcpTargets(model);
|
|
4683
4940
|
const desiredByApp = /* @__PURE__ */ new Map();
|
|
4684
|
-
const findings = []
|
|
4941
|
+
const findings = (model.overriddenRequiredMcpServers ?? []).map((override) => ({
|
|
4942
|
+
details: "The omission was explicitly confirmed during setup and remains recorded in the Aura manifest. Run `aura setup` to add the server or keep the documented deviation.",
|
|
4943
|
+
id: `override:${override.catalogId}`,
|
|
4944
|
+
message: `MCP server ${override.catalogId} required by ${override.requiredBy} is explicitly omitted; this machine deviates from the team preset.`,
|
|
4945
|
+
metadata: {
|
|
4946
|
+
catalogId: override.catalogId,
|
|
4947
|
+
kind: "preset-override",
|
|
4948
|
+
requiredBy: override.requiredBy
|
|
4949
|
+
},
|
|
4950
|
+
severity: "info"
|
|
4951
|
+
}));
|
|
4685
4952
|
const affected = /* @__PURE__ */ new Set();
|
|
4686
4953
|
for (const target of desired) {
|
|
4687
4954
|
const names = desiredByApp.get(target.appId) ?? /* @__PURE__ */ new Set();
|
|
@@ -4806,36 +5073,26 @@ function inlineCredential(transport) {
|
|
|
4806
5073
|
/**
|
|
4807
5074
|
* One row per state, rather than the same three-way branch spelled out for each string.
|
|
4808
5075
|
*
|
|
4809
|
-
* The
|
|
4810
|
-
*
|
|
4811
|
-
*
|
|
5076
|
+
* The strings a state needs were previously parallel ternaries in two functions, so adding a state
|
|
5077
|
+
* meant finding all of them and nothing made them agree. Keyed by state, the compiler requires the
|
|
5078
|
+
* row and the lookups cannot drift.
|
|
4812
5079
|
*/
|
|
4813
5080
|
const CURSOR_STATE_ACTIONS = Object.freeze({
|
|
4814
5081
|
disabled: {
|
|
4815
|
-
choiceId: "enable-in-cursor",
|
|
4816
|
-
choiceLabel: "Enable in Cursor",
|
|
4817
5082
|
guidance: (name) => `Open Cursor, open MCP settings, and enable server ${name}.`,
|
|
4818
|
-
message: (name, displayName) => `MCP server ${name} is disabled in ${displayName}
|
|
4819
|
-
summary: (name) => `Enable MCP server ${name} in Cursor.`
|
|
5083
|
+
message: (name, displayName) => `MCP server ${name} is disabled in ${displayName}.`
|
|
4820
5084
|
},
|
|
4821
5085
|
error: {
|
|
4822
|
-
choiceId: "repair-in-cursor",
|
|
4823
|
-
choiceLabel: "Repair in Cursor",
|
|
4824
5086
|
guidance: (name) => `Open Cursor's MCP settings, inspect the connection error for server ${name}, and repair its configuration or service.`,
|
|
4825
|
-
message: (name, displayName) => `MCP server ${name} in ${displayName} cannot be reached because Cursor reported a connection error
|
|
4826
|
-
summary: (name) => `Repair MCP server ${name} in Cursor.`
|
|
5087
|
+
message: (name, displayName) => `MCP server ${name} in ${displayName} cannot be reached because Cursor reported a connection error.`
|
|
4827
5088
|
},
|
|
4828
5089
|
"needs-approval": {
|
|
4829
|
-
choiceId: "approve-in-cursor",
|
|
4830
|
-
choiceLabel: "Approve in Cursor",
|
|
4831
5090
|
guidance: (name) => `Open Cursor, open MCP settings, and approve server ${name}.`,
|
|
4832
|
-
message: (name, displayName) => `MCP server ${name} in ${displayName} needs approval
|
|
4833
|
-
summary: (name) => `Approve MCP server ${name} in Cursor.`
|
|
5091
|
+
message: (name, displayName) => `MCP server ${name} in ${displayName} needs approval.`
|
|
4834
5092
|
}
|
|
4835
5093
|
});
|
|
4836
5094
|
//#endregion
|
|
4837
5095
|
//#region ../../plugins/checks-core/src/mcp-003-cursor.ts
|
|
4838
|
-
const CHECK_ID$1 = "MCP-003";
|
|
4839
5096
|
/**
|
|
4840
5097
|
* Cursor runtime-state findings that are not superseded by a concrete transport failure.
|
|
4841
5098
|
*
|
|
@@ -4857,23 +5114,6 @@ function cursorMcpStateFindings(app, concretelyFailed) {
|
|
|
4857
5114
|
return state === "error" && concretelyFailed.has(mcpServerNameKey(app.adapterId, name)) ? [] : [cursorStateFinding(app, name, state)];
|
|
4858
5115
|
});
|
|
4859
5116
|
}
|
|
4860
|
-
/** Zero-operation guided action for one Cursor-owned runtime-state finding. */
|
|
4861
|
-
function cursorMcpStateChoice(finding) {
|
|
4862
|
-
if (finding.checkId !== CHECK_ID$1 || finding.metadata?.["appId"] !== "cursor" || finding.metadata?.["kind"] !== "cursor-state") return;
|
|
4863
|
-
const name = finding.metadata["serverName"];
|
|
4864
|
-
const state = finding.metadata["state"];
|
|
4865
|
-
if (typeof name !== "string" || !isActionableState(state)) return;
|
|
4866
|
-
const action = CURSOR_STATE_ACTIONS[state];
|
|
4867
|
-
return {
|
|
4868
|
-
id: action.choiceId,
|
|
4869
|
-
label: action.choiceLabel,
|
|
4870
|
-
plan: {
|
|
4871
|
-
manualSteps: [action.guidance(name), "Run `aura check` again."],
|
|
4872
|
-
operations: [],
|
|
4873
|
-
summary: action.summary(name)
|
|
4874
|
-
}
|
|
4875
|
-
};
|
|
4876
|
-
}
|
|
4877
5117
|
/** Stable identity for the name-level state Cursor reports. */
|
|
4878
5118
|
function mcpServerNameKey(appId, name) {
|
|
4879
5119
|
return `${appId}\0${name}`;
|
|
@@ -4882,6 +5122,7 @@ function cursorStateFinding(app, name, state) {
|
|
|
4882
5122
|
const action = CURSOR_STATE_ACTIONS[state];
|
|
4883
5123
|
return {
|
|
4884
5124
|
details: action.guidance(name),
|
|
5125
|
+
fixability: "manual",
|
|
4885
5126
|
id: `${app.adapterId}:${name}:cursor-${state}`,
|
|
4886
5127
|
message: action.message(name, app.displayName),
|
|
4887
5128
|
metadata: {
|
|
@@ -4903,6 +5144,7 @@ function cursorStateFinding(app, name, state) {
|
|
|
4903
5144
|
function unknownStateFinding(app, name) {
|
|
4904
5145
|
return {
|
|
4905
5146
|
details: `Run \`cursor-agent mcp list\` to see what ${app.displayName} reports for server ${name}, and check whether server ${name} is approved and enabled in its MCP settings.`,
|
|
5147
|
+
fixability: "manual",
|
|
4906
5148
|
id: `${app.adapterId}:${name}:cursor-unknown`,
|
|
4907
5149
|
message: `MCP server ${name}'s state in ${app.displayName} could not be understood.`,
|
|
4908
5150
|
metadata: {
|
|
@@ -4918,6 +5160,7 @@ function unavailableFinding(app, reason) {
|
|
|
4918
5160
|
const cause = reason === "timeout" ? "did not finish in time" : "exited unsuccessfully";
|
|
4919
5161
|
return {
|
|
4920
5162
|
details: `Run \`cursor-agent mcp list\` to see why, then re-run \`aura check\`. Approval and enablement of ${app.displayName}'s MCP servers were not checked; every other MCP check still ran.`,
|
|
5163
|
+
fixability: "manual",
|
|
4921
5164
|
id: `${app.adapterId}:cursor-state-unavailable`,
|
|
4922
5165
|
message: `${app.displayName}'s MCP server states were not read because \`cursor-agent mcp list\` ${cause}.`,
|
|
4923
5166
|
metadata: {
|
|
@@ -4928,9 +5171,6 @@ function unavailableFinding(app, reason) {
|
|
|
4928
5171
|
severity: "info"
|
|
4929
5172
|
};
|
|
4930
5173
|
}
|
|
4931
|
-
function isActionableState(value) {
|
|
4932
|
-
return value === "needs-approval" || value === "disabled" || value === "error";
|
|
4933
|
-
}
|
|
4934
5174
|
//#endregion
|
|
4935
5175
|
//#region ../../plugins/checks-core/src/mcp-003.ts
|
|
4936
5176
|
const CHECK_ID = "MCP-003";
|
|
@@ -4971,6 +5211,7 @@ function unsetEnvironmentFindings(model) {
|
|
|
4971
5211
|
const credential = catalog?.manifest.credentialEnv.find((candidate) => candidate.name === variableName);
|
|
4972
5212
|
return [{
|
|
4973
5213
|
details: credentialDetails(server.name, variableName, credential, catalog?.manifest.docsUrl),
|
|
5214
|
+
fixability: "manual",
|
|
4974
5215
|
id: `environment:${String(serverIndex)}:${variableName}`,
|
|
4975
5216
|
locations: [{ path: model.manifest.path }],
|
|
4976
5217
|
message: `MCP server ${server.name} needs environment variable ${variableName}.`,
|
|
@@ -5012,8 +5253,6 @@ function findingFor(server, probe, model) {
|
|
|
5012
5253
|
};
|
|
5013
5254
|
}
|
|
5014
5255
|
function guidedFixes$1(finding, model) {
|
|
5015
|
-
const cursorChoice = cursorMcpStateChoice(finding);
|
|
5016
|
-
if (cursorChoice !== void 0) return [cursorChoice];
|
|
5017
5256
|
const server = findingServer(finding, model);
|
|
5018
5257
|
const kind = finding.metadata?.["kind"];
|
|
5019
5258
|
if (server === void 0 || kind !== "command" && kind !== "url") return [];
|
|
@@ -5079,6 +5318,11 @@ const mcp004 = defineCheck({
|
|
|
5079
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.
|
|
5080
5319
|
|
|
5081
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
|
+
},
|
|
5082
5326
|
fix: remediation,
|
|
5083
5327
|
fixability: "guided",
|
|
5084
5328
|
guidedFixes,
|
|
@@ -5652,6 +5896,8 @@ const checksCorePlugin = definePlugin({
|
|
|
5652
5896
|
instructionContextBudgetCheck,
|
|
5653
5897
|
instructionPrecedenceCheck,
|
|
5654
5898
|
managedBlockHashCheck,
|
|
5899
|
+
managedContentUpdateCheck,
|
|
5900
|
+
unmanagedDetectedAppCheck,
|
|
5655
5901
|
mcp001,
|
|
5656
5902
|
mcp002,
|
|
5657
5903
|
mcp003,
|