@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.
- package/dist/bin/aura.js +3 -3
- package/dist/index.js +1 -1
- package/dist/plugins/index.js +1 -1
- package/dist/{plugins-SZegBVXF.js → plugins-CL4yWDig.js} +88 -604
- package/dist/{run.boundary-BtMqQAEQ.js → run.boundary-DtkI7ZeG.js} +5815 -3586
- package/dist/{shared-link-plan-DVgOyqP9.js → shared-link-plan-C57dSoid.js} +188 -853
- 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-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: "
|
|
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
|
|
1196
|
+
* Every directory Codex searches for project instructions, from `cwd` to the repository root.
|
|
1198
1197
|
*
|
|
1199
|
-
*
|
|
1200
|
-
*
|
|
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
|
|
1355
|
-
*
|
|
1356
|
-
*
|
|
1357
|
-
*
|
|
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
|
|
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,
|
|
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
|
-
|
|
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
|
|
1522
|
-
*
|
|
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
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
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
|
|
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$
|
|
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$
|
|
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
|
|
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$
|
|
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)
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
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
|
|
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
|
|
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
|
|
4542
|
-
if (finding.checkId !== "MGD-002") return [];
|
|
4233
|
+
function managedSkillUpdateChoices(finding, model) {
|
|
4543
4234
|
const kind = finding.metadata?.["kind"];
|
|
4544
|
-
|
|
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
|
|
4595
|
-
const context =
|
|
4237
|
+
function skillUpdateChoices(finding, model) {
|
|
4238
|
+
const context = skillUpdateContext(finding, model);
|
|
4596
4239
|
if (context === void 0) return [];
|
|
4597
|
-
|
|
4598
|
-
|
|
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
|
|
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
|
|
4652
|
-
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);
|
|
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
|
-
|
|
4656
|
-
|
|
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:
|
|
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:
|
|
4720
|
-
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.",
|
|
4721
4306
|
fix: () => void 0,
|
|
4722
4307
|
fixability: "guided",
|
|
4723
|
-
guidedFixes:
|
|
4308
|
+
guidedFixes: managedSkillUpdateChoices,
|
|
4724
4309
|
id: "MGD-002",
|
|
4725
4310
|
scope: "global",
|
|
4726
|
-
title: "Managed
|
|
4311
|
+
title: "Managed skills are at their reviewed source revisions"
|
|
4727
4312
|
});
|
|
4728
|
-
function
|
|
4729
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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,
|