@tryaura/aura-cli 0.3.0 → 0.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin/aura.js +3 -3
- package/dist/index.js +1 -1
- package/dist/plugins/index.js +1 -1
- package/dist/{plugins-CL4yWDig.js → plugins-C-b2qvpr.js} +94 -258
- package/dist/{run.boundary-DtkI7ZeG.js → run.boundary-fZhCH0bP.js} +988 -474
- package/dist/{shared-link-plan-C57dSoid.js → shared-link-plan-DMijyUdG.js} +28 -18
- package/package.json +5 -5
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { A as displayPath, C as isRecord$4, D as SHARED_INSTRUCTIONS_TEMPLATE, E as AuraManifestError, S as errorMessage$1, T as resolveAuraManifestPath, _ as assertAuraManifestWritable, a as planDesiredMcpConvergence, b as parseAuraManifest, c as rememberMcpConvergence, d as sharedSkillsRoot, g as hashContent, h as stripLegacyManagedBlock, k as pluralize, m as planSharedSkillTreeUpdate, n as appendInstructionFragments, p as managedContentRevisionStatus, r as forgetMcpPlans, t as planSharedInstructionLink, u as planSkillDeployment, v as createAuraManifestWriteOperation, w as AURA_MANIFEST_PATH, x as errorCode, y as createEmptyAuraManifest } from "./shared-link-plan-
|
|
2
|
-
import { COMMAND_NOT_FOUND_EXIT_CODE, DEFAULT_EXEC_TIMEOUT_MS, DEFAULT_HTTP_TIMEOUT_MS, MAX_EXEC_OUTPUT_CHARACTERS, MAX_EXEC_TIMEOUT_MS, MAX_HTTP_RESPONSE_BYTES, MAX_HTTP_TIMEOUT_MS, McpWriteError, NOT_EXECUTABLE_EXIT_CODE, OUTPUT_LIMIT_EXIT_CODE, SHARED_INSTRUCTIONS_TEMPLATE_TOKEN, TIMEOUT_EXIT_CODE, defineOwnProperty, detectExecutable, hasMcpRedaction, jsonPropertyPath, mcpEnvironmentVariableNames, mcpServerNameProblem, normalizeMcpServerDefinition, parseMcpServerDefinition, parseMcpServerManifest, parseSkillFrontmatter, parseSkillReferences, resolveMcpSecretNameCollisions, resolveSkillDirectory, splitSourceLines } from "@tryaura/aura-sdk";
|
|
1
|
+
import { A as displayPath, C as isRecord$4, D as SHARED_INSTRUCTIONS_TEMPLATE, E as AuraManifestError, S as errorMessage$1, T as resolveAuraManifestPath, _ as assertAuraManifestWritable, a as planDesiredMcpConvergence, b as parseAuraManifest, c as rememberMcpConvergence, d as sharedSkillsRoot, g as hashContent, h as stripLegacyManagedBlock, k as pluralize, m as planSharedSkillTreeUpdate, n as appendInstructionFragments, p as managedContentRevisionStatus, r as forgetMcpPlans, t as planSharedInstructionLink, u as planSkillDeployment, v as createAuraManifestWriteOperation, w as AURA_MANIFEST_PATH, x as errorCode, y as createEmptyAuraManifest } from "./shared-link-plan-DMijyUdG.js";
|
|
2
|
+
import { COMMAND_NOT_FOUND_EXIT_CODE, DEFAULT_EXEC_TIMEOUT_MS, DEFAULT_HTTP_TIMEOUT_MS, MAX_EXEC_OUTPUT_CHARACTERS, MAX_EXEC_TIMEOUT_MS, MAX_HTTP_RESPONSE_BYTES, MAX_HTTP_TIMEOUT_MS, McpWriteError, NOT_EXECUTABLE_EXIT_CODE, OUTPUT_LIMIT_EXIT_CODE, SHARED_INSTRUCTIONS_TEMPLATE_TOKEN, TIMEOUT_EXIT_CODE, defineOwnProperty, detectExecutable, hasMcpRedaction, jsonPropertyPath, mcpEnvironmentVariableNames, mcpServerNameProblem, normalizeMcpServerDefinition, parseMcpServerDefinition, parseMcpServerManifest, parseMcpServerManifestValue, parseSkillFrontmatter, parseSkillReferences, resolveMcpSecretNameCollisions, resolveSkillDirectory, splitSourceLines } from "@tryaura/aura-sdk";
|
|
3
3
|
import { basename, delimiter, dirname, isAbsolute, join, posix, relative, resolve, sep, win32 } from "node:path";
|
|
4
4
|
import { Buffer as Buffer$1, isUtf8 } from "node:buffer";
|
|
5
5
|
import { createHash, randomUUID, timingSafeEqual } from "node:crypto";
|
|
@@ -10,7 +10,7 @@ import { access, chmod, link, lstat, mkdir, open, opendir, readFile, readdir, re
|
|
|
10
10
|
import { applyPatch, createTwoFilesPatch, structuredPatch } from "diff";
|
|
11
11
|
import { coerce, parse, satisfies, valid, validRange } from "semver";
|
|
12
12
|
import { isDeepStrictEqual } from "node:util";
|
|
13
|
-
import { fileURLToPath } from "node:url";
|
|
13
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
14
14
|
import { EnvHttpProxyAgent, fetch as fetch$1 } from "undici";
|
|
15
15
|
import { gunzipSync } from "node:zlib";
|
|
16
16
|
import process$1 from "node:process";
|
|
@@ -931,25 +931,21 @@ function backupRoot(homeDir) {
|
|
|
931
931
|
/**
|
|
932
932
|
* Determines where a plan may write.
|
|
933
933
|
*
|
|
934
|
-
*
|
|
935
|
-
*
|
|
934
|
+
* Roots are derived only from Aura's shared home directory and what detected adapters declared as
|
|
935
|
+
* global-scope configuration, so the writable surface of the home directory is exactly the
|
|
936
936
|
* applications Aura found rather than a hardcoded guess. A declared file contributes its own
|
|
937
937
|
* directory — `~/.claude/CLAUDE.md` yields `~/.claude`, not `~` — which keeps an adapter that reads
|
|
938
938
|
* something under a shared directory such as `~/.config/<app>` from opening `~/.config` wholesale.
|
|
939
939
|
*/
|
|
940
|
-
function resolveAllowedRoots(model
|
|
940
|
+
function resolveAllowedRoots(model) {
|
|
941
941
|
const homeDir = resolve(model.homeDir);
|
|
942
942
|
const roots = /* @__PURE__ */ new Map();
|
|
943
|
-
add(roots, {
|
|
944
|
-
exact: false,
|
|
945
|
-
path: resolve(model.projectRoot ?? model.cwd)
|
|
946
|
-
});
|
|
947
943
|
const sharedDirectory = dirname(resolve(model.sharedInstructions.path));
|
|
948
944
|
if (isStrictDescendant(homeDir, sharedDirectory)) add(roots, {
|
|
949
945
|
exact: false,
|
|
950
946
|
path: sharedDirectory
|
|
951
947
|
});
|
|
952
|
-
for (const root of
|
|
948
|
+
for (const root of deriveManagedHomeRoots(model, homeDir)) add(roots, root);
|
|
953
949
|
return Object.freeze([...roots.values()]);
|
|
954
950
|
}
|
|
955
951
|
/**
|
|
@@ -1002,17 +998,6 @@ function rootsForSpec(spec, homeDir) {
|
|
|
1002
998
|
path
|
|
1003
999
|
}];
|
|
1004
1000
|
}
|
|
1005
|
-
function validateManagedHomeRoots(managedHomeRoots, homeDir) {
|
|
1006
|
-
return managedHomeRoots.map((candidate) => {
|
|
1007
|
-
if (!isAbsolute(candidate)) throw new FixPlanError("invalid-options", `managedHomeRoots must contain absolute paths: ${candidate}`, { path: candidate });
|
|
1008
|
-
const path = resolve(candidate);
|
|
1009
|
-
if (!isStrictDescendant(homeDir, path)) throw new FixPlanError("invalid-options", `managedHomeRoots must sit strictly inside ${homeDir}: ${candidate}`, { path: candidate });
|
|
1010
|
-
return {
|
|
1011
|
-
exact: false,
|
|
1012
|
-
path
|
|
1013
|
-
};
|
|
1014
|
-
});
|
|
1015
|
-
}
|
|
1016
1001
|
function add(roots, root) {
|
|
1017
1002
|
const existing = roots.get(root.path);
|
|
1018
1003
|
if (existing === void 0 || existing.exact && !root.exact) roots.set(root.path, root);
|
|
@@ -1109,12 +1094,12 @@ async function mapWithConcurrency(items, limit, run) {
|
|
|
1109
1094
|
//#endregion
|
|
1110
1095
|
//#region ../core/src/fix-plan/path-policy.ts
|
|
1111
1096
|
/** Resolves the roots and filesystem traits a plan is validated against. */
|
|
1112
|
-
async function createPathPolicy(model
|
|
1097
|
+
async function createPathPolicy(model) {
|
|
1113
1098
|
const sensitivity = await detectCaseSensitivity(resolve(model.projectRoot ?? model.cwd));
|
|
1114
1099
|
return {
|
|
1115
1100
|
caseInsensitive: sensitivity !== "sensitive",
|
|
1116
1101
|
reservedRoots: Object.freeze([backupRoot(model.homeDir)]),
|
|
1117
|
-
roots: resolveAllowedRoots(model
|
|
1102
|
+
roots: resolveAllowedRoots(model),
|
|
1118
1103
|
rootsCaseInsensitive: sensitivity === "insensitive"
|
|
1119
1104
|
};
|
|
1120
1105
|
}
|
|
@@ -2878,7 +2863,7 @@ function decodeUtf8(content) {
|
|
|
2878
2863
|
//#endregion
|
|
2879
2864
|
//#region ../core/src/fix-plan/prepare.ts
|
|
2880
2865
|
async function prepareOperations(options) {
|
|
2881
|
-
const policy = await createPathPolicy(options.model
|
|
2866
|
+
const policy = await createPathPolicy(options.model);
|
|
2882
2867
|
const validated = await validatePlanPaths(options.plan, policy);
|
|
2883
2868
|
const budget = { remaining: MAX_RETAINED_PLAN_BYTES };
|
|
2884
2869
|
const operations = [];
|
|
@@ -3397,7 +3382,7 @@ async function undoFixPlan(options) {
|
|
|
3397
3382
|
if (options.backupId !== void 0) throw new FixPlanError("backup-error", `no undo journal entry named ${options.backupId}`);
|
|
3398
3383
|
return Object.freeze({ status: "nothing-to-undo" });
|
|
3399
3384
|
}
|
|
3400
|
-
const policy = await createPathPolicy(options.model
|
|
3385
|
+
const policy = await createPathPolicy(options.model);
|
|
3401
3386
|
return withJournalLock(root, options.now, async () => {
|
|
3402
3387
|
const selection = await select(root, options);
|
|
3403
3388
|
if (selection === void 0) return Object.freeze({ status: "nothing-to-undo" });
|
|
@@ -3748,50 +3733,32 @@ function portableSkillFilePathKey(path) {
|
|
|
3748
3733
|
//#endregion
|
|
3749
3734
|
//#region ../core/src/workspace/shared-links.ts
|
|
3750
3735
|
const GLOBAL_PREFIX = "~/";
|
|
3751
|
-
const PROJECT_PREFIX = "./";
|
|
3752
3736
|
const SHARED_INSTRUCTIONS_SEGMENTS = Object.freeze(["agents", "AGENTS.md"]);
|
|
3753
3737
|
const SHARED_INSTRUCTIONS_HOME_REFERENCE = "~/agents/AGENTS.md";
|
|
3754
|
-
const PROJECT_SHARED_INSTRUCTIONS_FILE = "AGENTS.md";
|
|
3755
3738
|
const SHARED_LINK_KINDS = /* @__PURE__ */ new Set([
|
|
3756
3739
|
"import-line",
|
|
3757
3740
|
"native-copy",
|
|
3758
3741
|
"symlink"
|
|
3759
3742
|
]);
|
|
3760
|
-
|
|
3761
|
-
|
|
3762
|
-
scope: "global"
|
|
3763
|
-
});
|
|
3764
|
-
const PROJECT_SLOT = Object.freeze({
|
|
3765
|
-
name: "projectSharedLink",
|
|
3766
|
-
scope: "project"
|
|
3767
|
-
});
|
|
3743
|
+
/** The one adapter field a shared-link declaration can come from. */
|
|
3744
|
+
const SLOT_NAME = "sharedLink";
|
|
3768
3745
|
/** Canonical shared-instruction path for one captured environment. */
|
|
3769
3746
|
function sharedInstructionsPath(environment) {
|
|
3770
3747
|
return join(environment.homeDir, ...SHARED_INSTRUCTIONS_SEGMENTS);
|
|
3771
3748
|
}
|
|
3772
3749
|
/**
|
|
3773
|
-
*
|
|
3774
|
-
*
|
|
3775
|
-
* Exported because the setup wizard has to name the same file core links adapters to. Two packages
|
|
3776
|
-
* each spelling out `AGENTS.md` is how a link ends up pointing at a file setup never wrote.
|
|
3777
|
-
*/
|
|
3778
|
-
function projectSharedInstructionsPath(projectRoot, cwd) {
|
|
3779
|
-
return join(projectRoot ?? cwd, PROJECT_SHARED_INSTRUCTIONS_FILE);
|
|
3780
|
-
}
|
|
3781
|
-
/**
|
|
3782
|
-
* Returns every declarative problem in a shared-link contribution to the given scope.
|
|
3750
|
+
* Returns every declarative problem in a shared-link contribution.
|
|
3783
3751
|
*
|
|
3784
|
-
* The prefix is
|
|
3785
|
-
*
|
|
3786
|
-
*
|
|
3787
|
-
*
|
|
3752
|
+
* The `~/` prefix is required rather than merely accepted. A declaration naming `./something` is
|
|
3753
|
+
* what let a home-scoped check demand a file inside whichever repository the user was standing in,
|
|
3754
|
+
* written with their home directory spelled out absolutely; Aura links only the home entry now, so
|
|
3755
|
+
* the prefix is the whole statement of where the file lives.
|
|
3788
3756
|
*/
|
|
3789
|
-
function sharedLinkViolations(link
|
|
3757
|
+
function sharedLinkViolations(link) {
|
|
3790
3758
|
const violations = [];
|
|
3791
3759
|
if (!SHARED_LINK_KINDS.has(link.kind)) violations.push(`kind "${String(link.kind)}" is not supported`);
|
|
3792
|
-
const
|
|
3793
|
-
|
|
3794
|
-
if (relative === void 0) violations.push(`entryPath must begin with "${prefix}" at ${scope} scope`);
|
|
3760
|
+
const relative = typeof link.entryPath === "string" && link.entryPath.startsWith(GLOBAL_PREFIX) ? link.entryPath.slice(2) : void 0;
|
|
3761
|
+
if (relative === void 0) violations.push(`entryPath must begin with "${GLOBAL_PREFIX}"`);
|
|
3795
3762
|
else if (relative.length === 0 || relative.includes("\\") || relative.includes("\0") || relative.split("/").some((segment) => segment.length === 0 || segment === "." || segment === "..")) violations.push("entryPath must be a normalized portable path without traversal");
|
|
3796
3763
|
if (link.kind === "symlink") {
|
|
3797
3764
|
if (link.lineTemplate !== void 0) violations.push("symlink declarations must not provide lineTemplate");
|
|
@@ -3802,51 +3769,36 @@ function sharedLinkViolations(link, scope) {
|
|
|
3802
3769
|
}
|
|
3803
3770
|
/** Resolves and verifies one adapter's shared-link declaration against its read-side files. */
|
|
3804
3771
|
function resolveAdapterSharedLink(adapter, environment, files) {
|
|
3805
|
-
return resolveSharedLink(adapter.sharedLink,
|
|
3806
|
-
}
|
|
3807
|
-
function resolveAdapterProjectSharedLink(adapter, environment, files, projectRoot) {
|
|
3808
|
-
const target = projectSharedInstructionsPath(projectRoot, environment.cwd);
|
|
3809
|
-
return resolveSharedLink(adapter.projectSharedLink, PROJECT_SLOT, environment, files, (entryPath) => portableRelativePath(entryPath, target));
|
|
3772
|
+
return resolveSharedLink(adapter.sharedLink, environment, files);
|
|
3810
3773
|
}
|
|
3811
3774
|
/**
|
|
3812
|
-
* Resolves one declaration, naming the shared source the way
|
|
3775
|
+
* Resolves one declaration, naming the shared source the way a home entry can survive.
|
|
3813
3776
|
*
|
|
3814
|
-
*
|
|
3815
|
-
* owns. A project entry names it relative to itself, so the file it produces is one the whole team
|
|
3816
|
-
* can commit. Nothing here can write a machine-specific absolute path into a repository, because
|
|
3817
|
-
* the slot decides both halves at once.
|
|
3777
|
+
* The entry names the source through `~`, which is the same string on every machine the user owns.
|
|
3818
3778
|
*/
|
|
3819
|
-
function resolveSharedLink(declaration,
|
|
3779
|
+
function resolveSharedLink(declaration, environment, files) {
|
|
3820
3780
|
if (declaration === void 0) return;
|
|
3821
|
-
const entryPath = resolveSharedEntry(declaration,
|
|
3781
|
+
const entryPath = resolveSharedEntry(declaration, environment, files);
|
|
3822
3782
|
if (declaration.kind === "symlink") return {
|
|
3823
3783
|
entryPath,
|
|
3824
3784
|
kind: declaration.kind,
|
|
3825
|
-
scope:
|
|
3785
|
+
scope: "global"
|
|
3826
3786
|
};
|
|
3827
3787
|
return {
|
|
3828
|
-
content: declaration.lineTemplate?.replace(SHARED_INSTRUCTIONS_TEMPLATE_TOKEN,
|
|
3788
|
+
content: declaration.lineTemplate?.replace(SHARED_INSTRUCTIONS_TEMPLATE_TOKEN, SHARED_INSTRUCTIONS_HOME_REFERENCE),
|
|
3829
3789
|
entryPath,
|
|
3830
3790
|
kind: declaration.kind,
|
|
3831
|
-
scope:
|
|
3791
|
+
scope: "global"
|
|
3832
3792
|
};
|
|
3833
3793
|
}
|
|
3834
|
-
function resolveSharedEntry(declaration,
|
|
3835
|
-
const violations = sharedLinkViolations(declaration
|
|
3836
|
-
if (violations.length > 0) throw new Error(`declares an invalid ${
|
|
3837
|
-
const
|
|
3838
|
-
const
|
|
3839
|
-
|
|
3840
|
-
if (![...files.values()].some((file) => resolve(file.spec.path) === resolve(entryPath) && file.spec.scope === slot.scope)) throw new Error(`declares ${slot.name} entry ${entryPath}, but its files() result did not declare that path at ${slot.scope} scope`);
|
|
3794
|
+
function resolveSharedEntry(declaration, environment, files) {
|
|
3795
|
+
const violations = sharedLinkViolations(declaration);
|
|
3796
|
+
if (violations.length > 0) throw new Error(`declares an invalid ${SLOT_NAME}: ${violations.join("; ")}`);
|
|
3797
|
+
const relative = declaration.entryPath.slice(2);
|
|
3798
|
+
const entryPath = join(environment.homeDir, ...relative.split("/"));
|
|
3799
|
+
if (![...files.values()].some((file) => resolve(file.spec.path) === resolve(entryPath) && file.spec.scope === "global")) throw new Error(`declares ${SLOT_NAME} entry ${entryPath}, but its files() result did not declare that path at global scope`);
|
|
3841
3800
|
return entryPath;
|
|
3842
3801
|
}
|
|
3843
|
-
function portableRelativePath(entryPath, targetPath) {
|
|
3844
|
-
const path = relative(dirname(entryPath), targetPath).replaceAll("\\", "/");
|
|
3845
|
-
return path.startsWith(".") ? path : `./${path}`;
|
|
3846
|
-
}
|
|
3847
|
-
function entryPrefix(scope) {
|
|
3848
|
-
return scope === "global" ? GLOBAL_PREFIX : PROJECT_PREFIX;
|
|
3849
|
-
}
|
|
3850
3802
|
function countToken(template) {
|
|
3851
3803
|
return template.split(SHARED_INSTRUCTIONS_TEMPLATE_TOKEN).length - 1;
|
|
3852
3804
|
}
|
|
@@ -3866,6 +3818,41 @@ function toSharedInstructions(path, contents) {
|
|
|
3866
3818
|
problem
|
|
3867
3819
|
};
|
|
3868
3820
|
}
|
|
3821
|
+
//#endregion
|
|
3822
|
+
//#region ../core/src/plugin-validation-links.ts
|
|
3823
|
+
/**
|
|
3824
|
+
* Validates an adapter's shared-instruction link declaration.
|
|
3825
|
+
*
|
|
3826
|
+
* Two rules, because the contract removed one field and constrained the other. A plugin built
|
|
3827
|
+
* against the previous contract still carries `projectSharedLink`; `apiVersion` already refuses
|
|
3828
|
+
* that plugin, and naming the property is what tells its author which one to delete.
|
|
3829
|
+
*/
|
|
3830
|
+
function collectSharedLinkDeclarationViolations(state, adapter, pluginLabel) {
|
|
3831
|
+
if ("projectSharedLink" in adapter) state.violations.push(`${pluginLabel} adapter "${adapter.id}" declares removed projectSharedLink; Aura manages only global shared-instruction links. Remove this property.`);
|
|
3832
|
+
if (adapter.sharedLink === void 0) return;
|
|
3833
|
+
for (const violation of sharedLinkViolations(adapter.sharedLink)) state.violations.push(`${pluginLabel} adapter "${adapter.id}" declares invalid sharedLink: ${violation}.`);
|
|
3834
|
+
}
|
|
3835
|
+
//#endregion
|
|
3836
|
+
//#region ../core/src/plugin-validation-skills.ts
|
|
3837
|
+
/** Rejects skill destinations that are empty, duplicated, or outside the user's home directory. */
|
|
3838
|
+
function collectSkillDirectoryViolations(state, adapter, pluginLabel) {
|
|
3839
|
+
const directories = adapter.capabilities?.skills?.directories;
|
|
3840
|
+
if (directories === void 0) return;
|
|
3841
|
+
if (directories.length === 0) {
|
|
3842
|
+
state.violations.push(`${pluginLabel} adapter "${adapter.id}" declares Agent Skills support without a global skills directory.`);
|
|
3843
|
+
return;
|
|
3844
|
+
}
|
|
3845
|
+
const ids = /* @__PURE__ */ new Set();
|
|
3846
|
+
for (const directory of directories) {
|
|
3847
|
+
if (ids.has(directory.id)) state.violations.push(`${pluginLabel} adapter "${adapter.id}" declares duplicate skills directory ID "${directory.id}".`);
|
|
3848
|
+
ids.add(directory.id);
|
|
3849
|
+
if (!isGlobalSkillPath(directory.entryPath)) state.violations.push(`${pluginLabel} adapter "${adapter.id}" declares invalid global skills directory ${directory.entryPath}.`);
|
|
3850
|
+
}
|
|
3851
|
+
}
|
|
3852
|
+
function isGlobalSkillPath(entryPath) {
|
|
3853
|
+
const relative = entryPath.startsWith("~/") ? entryPath.slice(2) : void 0;
|
|
3854
|
+
return relative !== void 0 && relative.length > 0 && !relative.includes("\\") && !relative.includes("\0") && relative.split("/").every((segment) => segment.length > 0 && segment !== "." && segment !== "..");
|
|
3855
|
+
}
|
|
3869
3856
|
/** Plugin ids become id namespaces, so `/` and anything shell- or path-surprising is refused. */
|
|
3870
3857
|
const PLUGIN_ID_PATTERN = /^[a-z0-9][a-z0-9._-]*$/;
|
|
3871
3858
|
function createRegistryState() {
|
|
@@ -3875,7 +3862,7 @@ function createRegistryState() {
|
|
|
3875
3862
|
};
|
|
3876
3863
|
}
|
|
3877
3864
|
function isSupportedPlugin(candidate) {
|
|
3878
|
-
return candidate.apiVersion ===
|
|
3865
|
+
return candidate.apiVersion === 2;
|
|
3879
3866
|
}
|
|
3880
3867
|
/** Validates each contribution's namespace and collects the ones that claimed their id. */
|
|
3881
3868
|
function collectNamespaced(state, kind, contributions, plugin, collected) {
|
|
@@ -3903,6 +3890,7 @@ function collectUnknownBareCheckIdPlugins(state, bareCheckIdPlugins, candidates)
|
|
|
3903
3890
|
function collectIdentityViolations(state, plugin) {
|
|
3904
3891
|
const violationCount = state.violations.length;
|
|
3905
3892
|
if (!PLUGIN_ID_PATTERN.test(plugin.id)) state.violations.push(`Plugin "${plugin.name}" declares ID "${plugin.id}", which cannot be used as an ID namespace; expected lowercase letters, digits, ".", "-", or "_", starting with a letter or digit.`);
|
|
3893
|
+
if (plugin.id === "repo") state.violations.push(`Plugin "${plugin.name}" declares ID "repo", which is reserved.`);
|
|
3906
3894
|
if (plugin.name.trim().length === 0) state.violations.push(`Plugin "${plugin.id}" declares an empty name; reports identify plugins by name.`);
|
|
3907
3895
|
if (canonicalSemver(plugin.version) !== plugin.version) state.violations.push(`${formatPlugin(plugin)} declares version "${plugin.version}"; expected a semver version such as "1.0.0".`);
|
|
3908
3896
|
return state.violations.length === violationCount;
|
|
@@ -3912,15 +3900,8 @@ function collectAdapterViolations(state, adapters, plugin) {
|
|
|
3912
3900
|
for (const adapter of adapters ?? []) {
|
|
3913
3901
|
if (!PLUGIN_ID_PATTERN.test(adapter.id)) state.violations.push(`${formatPlugin(plugin)} contributes adapter ID "${adapter.id}", which is not a usable adapter ID; expected lowercase letters, digits, ".", "-", or "_", starting with a letter or digit.`);
|
|
3914
3902
|
collectSharedLinkCapabilityViolations(state, adapter, plugin);
|
|
3915
|
-
|
|
3916
|
-
|
|
3917
|
-
"global",
|
|
3918
|
-
adapter.sharedLink
|
|
3919
|
-
], [
|
|
3920
|
-
"projectSharedLink",
|
|
3921
|
-
"project",
|
|
3922
|
-
adapter.projectSharedLink
|
|
3923
|
-
]]) if (link !== void 0) for (const violation of sharedLinkViolations(link, scope)) state.violations.push(`${formatPlugin(plugin)} adapter "${adapter.id}" declares invalid ${name}: ${violation}.`);
|
|
3903
|
+
collectSkillDirectoryViolations(state, adapter, formatPlugin(plugin));
|
|
3904
|
+
collectSharedLinkDeclarationViolations(state, adapter, formatPlugin(plugin));
|
|
3924
3905
|
}
|
|
3925
3906
|
}
|
|
3926
3907
|
function collectSharedLinkCapabilityViolations(state, adapter, plugin) {
|
|
@@ -3953,8 +3934,8 @@ function claimId(state, kind, id, plugin) {
|
|
|
3953
3934
|
return true;
|
|
3954
3935
|
}
|
|
3955
3936
|
function formatApiVersionViolation(candidate) {
|
|
3956
|
-
const remedy = candidate.apiVersion >
|
|
3957
|
-
return `${formatPlugin(candidate)} uses unsupported apiVersion ${candidate.apiVersion}; this Aura build supports apiVersion
|
|
3937
|
+
const remedy = candidate.apiVersion > 2 ? "Upgrade Aura, or install a plugin release built against" : "Upgrade the plugin to a release built against";
|
|
3938
|
+
return `${formatPlugin(candidate)} uses unsupported apiVersion ${candidate.apiVersion}; this Aura build supports apiVersion 2. ${remedy} apiVersion 2.`;
|
|
3958
3939
|
}
|
|
3959
3940
|
function formatViolations(violations) {
|
|
3960
3941
|
return [violations.length === 1 ? "Aura cannot build the plugin registry:" : `Aura cannot build the plugin registry (${violations.length} problems):`, ...violations.map((violation) => ` - ${violation}`)].join("\n");
|
|
@@ -5163,9 +5144,8 @@ function normalizeVersion(version) {
|
|
|
5163
5144
|
/**
|
|
5164
5145
|
* Splits desired servers into the ones Aura may write and the collisions a person has to settle.
|
|
5165
5146
|
*
|
|
5166
|
-
*
|
|
5167
|
-
*
|
|
5168
|
-
* that would not have collided or skips one that never happened.
|
|
5147
|
+
* Desired servers are global-only. A server named `docs` in a project `.mcp.json` remains a
|
|
5148
|
+
* read-only observation and cannot collide with the global entry Aura manages.
|
|
5169
5149
|
*/
|
|
5170
5150
|
function classifyDesired(desired, ledgerNames, state) {
|
|
5171
5151
|
const ledger = new Set(ledgerNames);
|
|
@@ -5184,11 +5164,11 @@ function classifyDesired(desired, ledgerNames, state) {
|
|
|
5184
5164
|
/** `owned` to write it, a blocker to refuse, `undefined` when the config already satisfies it. */
|
|
5185
5165
|
function collisionBlocker(entry, ledger, state) {
|
|
5186
5166
|
if (ledger.has(entry.name)) return "owned";
|
|
5187
|
-
const sameName = (candidate) => candidate.name === entry.name && candidate.scope ===
|
|
5167
|
+
const sameName = (candidate) => candidate.name === entry.name && candidate.scope === "global";
|
|
5188
5168
|
const unusable = state.unusable.find(sameName);
|
|
5189
5169
|
if (unusable !== void 0) return {
|
|
5190
|
-
message: unusable.reason === "disabled" ? `MCP server ${entry.name} is already declared in this application's
|
|
5191
|
-
scope:
|
|
5170
|
+
message: unusable.reason === "disabled" ? `MCP server ${entry.name} is already declared in this application's global configuration but is turned off there. Remove or enable it, then run the fix again.` : `MCP server ${entry.name} is already declared in this application's global configuration in a form Aura does not recognize, so Aura left it unchanged.`,
|
|
5171
|
+
scope: "global",
|
|
5192
5172
|
sourceId: unusable.sourceId
|
|
5193
5173
|
};
|
|
5194
5174
|
const existing = state.servers.filter(sameName);
|
|
@@ -5197,7 +5177,7 @@ function collisionBlocker(entry, ledger, state) {
|
|
|
5197
5177
|
if ("message" in normalized) return normalized;
|
|
5198
5178
|
return existing.every((server) => isDeepStrictEqual(server.transport, normalized.transport)) ? void 0 : {
|
|
5199
5179
|
message: `MCP server ${entry.name} already exists outside Aura's ownership ledger and differs from the manifest.`,
|
|
5200
|
-
scope:
|
|
5180
|
+
scope: "global"
|
|
5201
5181
|
};
|
|
5202
5182
|
}
|
|
5203
5183
|
/**
|
|
@@ -5212,7 +5192,7 @@ function normalizeDesired(entry) {
|
|
|
5212
5192
|
} catch (error) {
|
|
5213
5193
|
return {
|
|
5214
5194
|
message: error instanceof McpWriteError ? `MCP server ${entry.name} cannot be written as the manifest defines it: ${error.message}` : `MCP server ${entry.name} has a manifest definition Aura cannot represent.`,
|
|
5215
|
-
scope:
|
|
5195
|
+
scope: "global"
|
|
5216
5196
|
};
|
|
5217
5197
|
}
|
|
5218
5198
|
}
|
|
@@ -5226,28 +5206,25 @@ function createAppMcpConvergence(adapter, files, state) {
|
|
|
5226
5206
|
const targets = [...files.values()].filter((file) => file.spec.kind === "mcp");
|
|
5227
5207
|
const classified = classifyDesired(desired, ledgerNames, state);
|
|
5228
5208
|
if (classified.blockers.length > 0) return blocked(classified.blockers);
|
|
5229
|
-
const
|
|
5230
|
-
|
|
5231
|
-
const operations = scopes.flatMap((result) => result.operations);
|
|
5232
|
-
return blockers.length > 0 ? blocked(blockers) : {
|
|
5209
|
+
const planned = planGlobalTarget(adapter, writer, targets, classified.owned, ledgerNames, state.secretSightings);
|
|
5210
|
+
return planned.blockers.length > 0 ? blocked(planned.blockers) : {
|
|
5233
5211
|
blockers: [],
|
|
5234
|
-
operations,
|
|
5212
|
+
operations: planned.operations,
|
|
5235
5213
|
ownedNames: [...new Set(classified.owned.map((entry) => entry.name))].sort()
|
|
5236
5214
|
};
|
|
5237
5215
|
};
|
|
5238
5216
|
}
|
|
5239
|
-
function
|
|
5240
|
-
const
|
|
5241
|
-
|
|
5242
|
-
if (
|
|
5243
|
-
|
|
5244
|
-
const target = scopedTargets[0];
|
|
5217
|
+
function planGlobalTarget(adapter, writer, targets, desired, ledgerNames, sightings) {
|
|
5218
|
+
const globalTargets = targets.filter((target) => target.spec.scope === "global");
|
|
5219
|
+
if (desired.length > 0 && globalTargets.length === 0) return blockedScope(`${adapter.displayName} has no global MCP configuration target.`, "global");
|
|
5220
|
+
if (globalTargets.length > 1) return blockedScope(`${adapter.displayName} declares more than one global MCP write target.`, "global");
|
|
5221
|
+
const target = globalTargets[0];
|
|
5245
5222
|
return target === void 0 ? {
|
|
5246
5223
|
blockers: [],
|
|
5247
5224
|
operations: []
|
|
5248
|
-
} :
|
|
5225
|
+
} : writeGlobalTarget(adapter, writer, target, desired, ledgerNames, sightings);
|
|
5249
5226
|
}
|
|
5250
|
-
function
|
|
5227
|
+
function writeGlobalTarget(adapter, writer, target, desired, ledgerNames, sightings) {
|
|
5251
5228
|
if (!needsTargetWrite(target, desired, ledgerNames)) return {
|
|
5252
5229
|
blockers: [],
|
|
5253
5230
|
operations: []
|
|
@@ -5256,7 +5233,7 @@ function writeScopeTarget(adapter, writer, target, desired, ledgerNames, scope,
|
|
|
5256
5233
|
if (unwritable !== void 0) return {
|
|
5257
5234
|
blockers: [{
|
|
5258
5235
|
...unwritable,
|
|
5259
|
-
scope,
|
|
5236
|
+
scope: "global",
|
|
5260
5237
|
sourceId: target.spec.id
|
|
5261
5238
|
}],
|
|
5262
5239
|
operations: []
|
|
@@ -5266,7 +5243,7 @@ function writeScopeTarget(adapter, writer, target, desired, ledgerNames, scope,
|
|
|
5266
5243
|
blockers: [{
|
|
5267
5244
|
message: written.refusal,
|
|
5268
5245
|
path: target.spec.path,
|
|
5269
|
-
scope,
|
|
5246
|
+
scope: "global",
|
|
5270
5247
|
sourceId: target.spec.id
|
|
5271
5248
|
}],
|
|
5272
5249
|
operations: []
|
|
@@ -5275,14 +5252,14 @@ function writeScopeTarget(adapter, writer, target, desired, ledgerNames, scope,
|
|
|
5275
5252
|
blockers: [{
|
|
5276
5253
|
message: `${adapter.displayName}'s MCP configuration at ${target.spec.path} is larger than Aura will rewrite in one operation, so Aura left it unchanged.`,
|
|
5277
5254
|
path: target.spec.path,
|
|
5278
|
-
scope,
|
|
5255
|
+
scope: "global",
|
|
5279
5256
|
sourceId: target.spec.id
|
|
5280
5257
|
}],
|
|
5281
5258
|
operations: []
|
|
5282
5259
|
};
|
|
5283
5260
|
const operation = {
|
|
5284
5261
|
content: written.content,
|
|
5285
|
-
mode:
|
|
5262
|
+
mode: 384,
|
|
5286
5263
|
path: target.spec.path,
|
|
5287
5264
|
precondition: targetPrecondition(target),
|
|
5288
5265
|
type: "write"
|
|
@@ -5525,11 +5502,9 @@ async function scanAdapter(adapter, context) {
|
|
|
5525
5502
|
return { diagnostics: [failure$2(adapter, "files", error)] };
|
|
5526
5503
|
}
|
|
5527
5504
|
diagnostics.push(...discovery.diagnostics);
|
|
5528
|
-
let projectSharedLink;
|
|
5529
5505
|
let sharedLink;
|
|
5530
5506
|
try {
|
|
5531
5507
|
sharedLink = resolveAdapterSharedLink(adapter, context.environment, discovery.files);
|
|
5532
|
-
projectSharedLink = resolveAdapterProjectSharedLink(adapter, context.environment, discovery.files, await context.projectRoot);
|
|
5533
5508
|
} catch (error) {
|
|
5534
5509
|
diagnostics.push(failure$2(adapter, "files", error));
|
|
5535
5510
|
}
|
|
@@ -5553,9 +5528,8 @@ async function scanAdapter(adapter, context) {
|
|
|
5553
5528
|
mcpServers,
|
|
5554
5529
|
metadata: snapshot.metadata,
|
|
5555
5530
|
skills: snapshot.skills,
|
|
5556
|
-
skillDirectories: (adapter.capabilities?.skills?.directories ?? []).map((directory) => resolveSkillDirectory(directory, context.environment.homeDir
|
|
5531
|
+
skillDirectories: (adapter.capabilities?.skills?.directories ?? []).map((directory) => resolveSkillDirectory(directory, context.environment.homeDir)),
|
|
5557
5532
|
sourceFiles: [...discovery.files.values()].filter((file) => file.spec.kind !== "probe").map(toStatus),
|
|
5558
|
-
...projectSharedLink === void 0 ? {} : { projectSharedLink },
|
|
5559
5533
|
...sharedLink === void 0 ? {} : { sharedLink },
|
|
5560
5534
|
support: evaluateSupport(adapter.supportedRange, detection.version),
|
|
5561
5535
|
...adapter.synthetic === void 0 ? {} : { synthetic: adapter.synthetic },
|
|
@@ -6315,12 +6289,13 @@ const MAX_SKILL_DRIVER_CALL_MS = 3e4;
|
|
|
6315
6289
|
*
|
|
6316
6290
|
* Bundled and already-installed trees are local state Aura itself wrote, so they walk unbounded.
|
|
6317
6291
|
* A driver-materialized tree is whatever a plugin's driver put on disk — the reviewed origin is a
|
|
6318
|
-
* claim, not a guarantee — so it walks under {@link DRIVER_WALK_POLICY}, which caps
|
|
6319
|
-
* file count, and total bytes, refuses paths that will not survive a portable
|
|
6320
|
-
* refuses two paths that would alias on a case-insensitive one.
|
|
6292
|
+
* claim, not a guarantee — so it walks under {@link DRIVER_WALK_POLICY}, which caps directory
|
|
6293
|
+
* count, file size, file count, and total bytes, refuses paths that will not survive a portable
|
|
6294
|
+
* filesystem, and refuses two paths that would alias on a case-insensitive one.
|
|
6321
6295
|
*/
|
|
6322
6296
|
/** The bounds an untrusted, driver-supplied tree is read under. */
|
|
6323
6297
|
const DRIVER_WALK_POLICY = Object.freeze({
|
|
6298
|
+
maxDirectories: 200,
|
|
6324
6299
|
maxFileBytes: MAX_SKILL_FILE_BYTES,
|
|
6325
6300
|
maxFiles: 200,
|
|
6326
6301
|
maxTotalBytes: MAX_SKILL_RESPONSE_BYTES
|
|
@@ -6334,19 +6309,22 @@ function treeHash(files) {
|
|
|
6334
6309
|
return createHash("sha256").update(`${signature}\n`, "utf8").digest("hex");
|
|
6335
6310
|
}
|
|
6336
6311
|
/** Reads one skill directory, stopping at the first entry it cannot safely resolve. */
|
|
6337
|
-
async function walkTree(root, reader, policy) {
|
|
6312
|
+
async function walkTree(root, reader, policy, outerBoundary) {
|
|
6338
6313
|
const files = [];
|
|
6339
6314
|
const entries = [];
|
|
6340
6315
|
const state = {
|
|
6316
|
+
bytesRead: 0,
|
|
6317
|
+
directoryCount: 0,
|
|
6341
6318
|
portablePaths: /* @__PURE__ */ new Set(),
|
|
6342
6319
|
totalBytes: 0
|
|
6343
6320
|
};
|
|
6344
6321
|
const path = resolve(root);
|
|
6345
6322
|
const problem = await walkPath({
|
|
6346
|
-
boundary: await reader.realPath(path) ?? path,
|
|
6323
|
+
boundary: outerBoundary ?? await reader.realPath(path) ?? path,
|
|
6347
6324
|
path
|
|
6348
6325
|
}, path, reader, files, entries, state, policy);
|
|
6349
6326
|
return {
|
|
6327
|
+
bytesRead: state.bytesRead,
|
|
6350
6328
|
entries: Object.freeze(entries),
|
|
6351
6329
|
files: Object.freeze(files.sort((left, right) => comparePortablePaths(left.path, right.path))),
|
|
6352
6330
|
...problem === void 0 ? {} : { problem }
|
|
@@ -6368,6 +6346,11 @@ async function walkPath(root, path, reader, files, entries, state, policy) {
|
|
|
6368
6346
|
message: `${relativePath || "."} could not be read (${contents.problem})`
|
|
6369
6347
|
};
|
|
6370
6348
|
if (contents.entries !== void 0) {
|
|
6349
|
+
if (policy !== void 0 && state.directoryCount >= policy.maxDirectories) return {
|
|
6350
|
+
kind: "too-large",
|
|
6351
|
+
message: `${relativePath || "."} exceeds the skill directory limit`
|
|
6352
|
+
};
|
|
6353
|
+
state.directoryCount += 1;
|
|
6371
6354
|
entries.push({
|
|
6372
6355
|
kind: "directory",
|
|
6373
6356
|
path
|
|
@@ -6385,6 +6368,8 @@ function addFile(path, relativePath, contents, files, entries, state, policy) {
|
|
|
6385
6368
|
kind: "unsupported",
|
|
6386
6369
|
message: `${relativePath} is not a readable regular file`
|
|
6387
6370
|
};
|
|
6371
|
+
const bytes = Buffer$1.byteLength(contents.content, "utf8");
|
|
6372
|
+
state.bytesRead += bytes;
|
|
6388
6373
|
if (contents.utf8Valid === false) return {
|
|
6389
6374
|
kind: "unsupported",
|
|
6390
6375
|
message: `${relativePath} is not valid UTF-8`
|
|
@@ -6399,7 +6384,6 @@ function addFile(path, relativePath, contents, files, entries, state, policy) {
|
|
|
6399
6384
|
kind: "unsupported",
|
|
6400
6385
|
message: `${relativePath} aliases another skill file`
|
|
6401
6386
|
};
|
|
6402
|
-
const bytes = Buffer$1.byteLength(contents.content, "utf8");
|
|
6403
6387
|
if (state.totalBytes + bytes > policy.maxTotalBytes) return {
|
|
6404
6388
|
kind: "too-large",
|
|
6405
6389
|
message: `${relativePath} exceeds the skill size limit`
|
|
@@ -6425,7 +6409,7 @@ function comparePortablePaths(left, right) {
|
|
|
6425
6409
|
//#endregion
|
|
6426
6410
|
//#region ../core/src/workspace/skills.ts
|
|
6427
6411
|
const SKILLS_DIAGNOSTIC_ID = "core/skills";
|
|
6428
|
-
const SKILL_FILE$
|
|
6412
|
+
const SKILL_FILE$2 = "SKILL.md";
|
|
6429
6413
|
/** Canonical shared skill root for one captured environment. */
|
|
6430
6414
|
function sharedSkillsPath(environment) {
|
|
6431
6415
|
return sharedSkillsRoot(environment.homeDir);
|
|
@@ -6448,8 +6432,8 @@ async function resolveBundledSkills(registrations, reader) {
|
|
|
6448
6432
|
path,
|
|
6449
6433
|
skill
|
|
6450
6434
|
};
|
|
6451
|
-
if (!tree.files.some((file) => file.path === SKILL_FILE$
|
|
6452
|
-
message: `does not contain ${SKILL_FILE$
|
|
6435
|
+
if (!tree.files.some((file) => file.path === SKILL_FILE$2)) return {
|
|
6436
|
+
message: `does not contain ${SKILL_FILE$2}`,
|
|
6453
6437
|
path,
|
|
6454
6438
|
skill
|
|
6455
6439
|
};
|
|
@@ -6485,7 +6469,7 @@ async function resolveDriverSkillPack(skill, source, reader) {
|
|
|
6485
6469
|
return { kind: "invalid" };
|
|
6486
6470
|
}
|
|
6487
6471
|
const tree = await walkTree(path, reader, DRIVER_WALK_POLICY);
|
|
6488
|
-
if (tree.problem !== void 0 || !tree.files.some((file) => file.path === SKILL_FILE$
|
|
6472
|
+
if (tree.problem !== void 0 || !tree.files.some((file) => file.path === SKILL_FILE$2)) return { kind: "invalid" };
|
|
6489
6473
|
return {
|
|
6490
6474
|
kind: "resolved",
|
|
6491
6475
|
value: Object.freeze({
|
|
@@ -6529,8 +6513,8 @@ async function scanSharedSkills(environment, reader) {
|
|
|
6529
6513
|
* reports `unreadable` and leaves the real cause to travel in `problemDetail`.
|
|
6530
6514
|
*/
|
|
6531
6515
|
function sharedSkillDefinition(path, tree, homeDir) {
|
|
6532
|
-
const skillFilePath = join(path, SKILL_FILE$
|
|
6533
|
-
const file = tree.files.find((entry) => entry.path === SKILL_FILE$
|
|
6516
|
+
const skillFilePath = join(path, SKILL_FILE$2);
|
|
6517
|
+
const file = tree.files.find((entry) => entry.path === SKILL_FILE$2);
|
|
6534
6518
|
if (file === void 0) return {
|
|
6535
6519
|
definitionStatus: tree.problem === void 0 ? "missing-file" : "unreadable",
|
|
6536
6520
|
skillFilePath
|
|
@@ -7264,7 +7248,7 @@ function parseContents(body) {
|
|
|
7264
7248
|
}
|
|
7265
7249
|
//#endregion
|
|
7266
7250
|
//#region ../core/src/skills/pack-schema.ts
|
|
7267
|
-
const SKILL_FILE = "SKILL.md";
|
|
7251
|
+
const SKILL_FILE$1 = "SKILL.md";
|
|
7268
7252
|
/**
|
|
7269
7253
|
* Parses a directory's `skills/<id>` body.
|
|
7270
7254
|
*
|
|
@@ -7307,9 +7291,9 @@ function parseDirectorySkillPack(body, expectedId) {
|
|
|
7307
7291
|
kind: "invalid",
|
|
7308
7292
|
problem: files
|
|
7309
7293
|
};
|
|
7310
|
-
if (!files.some((file) => file.path === SKILL_FILE)) return {
|
|
7294
|
+
if (!files.some((file) => file.path === SKILL_FILE$1)) return {
|
|
7311
7295
|
kind: "invalid",
|
|
7312
|
-
problem: `response does not contain a root ${SKILL_FILE}`
|
|
7296
|
+
problem: `response does not contain a root ${SKILL_FILE$1}`
|
|
7313
7297
|
};
|
|
7314
7298
|
return {
|
|
7315
7299
|
files,
|
|
@@ -7483,7 +7467,7 @@ async function downloadFiles(environment, location, remoteFiles) {
|
|
|
7483
7467
|
async function listAgenticSkills(environment, source, options = {}) {
|
|
7484
7468
|
const catalog = await loadAgenticCatalog(environment, source, options.noCache === true);
|
|
7485
7469
|
if (catalog.kind === "failure") return {
|
|
7486
|
-
diagnostics: [diagnostic(`Skill source "${source.id}" ${catalog.reason}, so it is unavailable.`)],
|
|
7470
|
+
diagnostics: [diagnostic$1(`Skill source "${source.id}" ${catalog.reason}, so it is unavailable.`)],
|
|
7487
7471
|
listings: [],
|
|
7488
7472
|
status: {
|
|
7489
7473
|
hint: "unreachable",
|
|
@@ -7492,7 +7476,7 @@ async function listAgenticSkills(environment, source, options = {}) {
|
|
|
7492
7476
|
};
|
|
7493
7477
|
return {
|
|
7494
7478
|
collections: catalog.collections,
|
|
7495
|
-
diagnostics: [...cacheDiagnostics(source, catalog), ...catalog.problems.map((problem) => diagnostic(`Skill source "${source.id}" catalog ${problem}, so some of it is unavailable.`))],
|
|
7479
|
+
diagnostics: [...cacheDiagnostics(source, catalog), ...catalog.problems.map((problem) => diagnostic$1(`Skill source "${source.id}" catalog ${problem}, so some of it is unavailable.`))],
|
|
7496
7480
|
listings: Object.freeze(catalog.entries.map(({ listing }) => Object.freeze({
|
|
7497
7481
|
...listing,
|
|
7498
7482
|
source
|
|
@@ -7506,7 +7490,7 @@ async function listAgenticSkills(environment, source, options = {}) {
|
|
|
7506
7490
|
function cacheDiagnostics(source, catalog) {
|
|
7507
7491
|
if (catalog.cacheAgeMs === void 0) return [];
|
|
7508
7492
|
const age = describeCacheAge(catalog.cacheAgeMs);
|
|
7509
|
-
return [diagnostic(catalog.staleAfterFailure === true ? `Skill source "${source.id}" could not be reached, so its listing is served from the local cache (${age}).` : `Skill source "${source.id}" listing served from the local cache (${age}); pass --no-cache to refetch it now.`)];
|
|
7493
|
+
return [diagnostic$1(catalog.staleAfterFailure === true ? `Skill source "${source.id}" could not be reached, so its listing is served from the local cache (${age}).` : `Skill source "${source.id}" listing served from the local cache (${age}); pass --no-cache to refetch it now.`)];
|
|
7510
7494
|
}
|
|
7511
7495
|
/** Resolves AgenticSkills entries from their exact GitHub directories into reviewed Aura packs. */
|
|
7512
7496
|
async function resolveAgenticSkills(environment, source, skillIds) {
|
|
@@ -7529,7 +7513,7 @@ async function resolveAgenticSkills(environment, source, skillIds) {
|
|
|
7529
7513
|
function catalogEntries(catalog) {
|
|
7530
7514
|
return catalog.kind === "catalog" ? new Map(catalog.entries.map((entry) => [entry.listing.id, entry])) : /* @__PURE__ */ new Map();
|
|
7531
7515
|
}
|
|
7532
|
-
function diagnostic(message) {
|
|
7516
|
+
function diagnostic$1(message) {
|
|
7533
7517
|
return {
|
|
7534
7518
|
adapterId: AGENTICSKILLS_DIAGNOSTIC_ID,
|
|
7535
7519
|
message,
|
|
@@ -7537,7 +7521,7 @@ function diagnostic(message) {
|
|
|
7537
7521
|
};
|
|
7538
7522
|
}
|
|
7539
7523
|
function skillDiagnostic(source, id, reason) {
|
|
7540
|
-
return diagnostic(`Skill "${id}" from "${source.id}" ${reason}, so it is unavailable.`);
|
|
7524
|
+
return diagnostic$1(`Skill "${id}" from "${source.id}" ${reason}, so it is unavailable.`);
|
|
7541
7525
|
}
|
|
7542
7526
|
function isDiagnostic(value) {
|
|
7543
7527
|
return "adapterId" in value;
|
|
@@ -7851,7 +7835,7 @@ const DIRECTORY_PREFIX = "directory:";
|
|
|
7851
7835
|
/** Namespaced MCP catalog id, such as `official/github`. */
|
|
7852
7836
|
const MCP_CATALOG_ID_PATTERN = /^[a-z0-9][a-z0-9._-]*\/[a-zA-Z0-9][a-zA-Z0-9._-]*$/u;
|
|
7853
7837
|
const SKILL_ID_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/u;
|
|
7854
|
-
const SKILL_SOURCE_ID_PATTERN = /^(?:directory|driver|plugin):[^\s:]+$/u;
|
|
7838
|
+
const SKILL_SOURCE_ID_PATTERN = /^(?:directory|driver|plugin|repo):[^\s:]+$/u;
|
|
7855
7839
|
/**
|
|
7856
7840
|
* Picks the first collected problem out of a batch of independently validated fields.
|
|
7857
7841
|
*
|
|
@@ -7984,6 +7968,52 @@ function collectJsonValue(value, path, depth) {
|
|
|
7984
7968
|
return collectJsonObject(value, path, depth);
|
|
7985
7969
|
}
|
|
7986
7970
|
//#endregion
|
|
7971
|
+
//#region ../core/src/preset/repo-content-limits.ts
|
|
7972
|
+
/**
|
|
7973
|
+
* The largest combined byte size of a repository's content set.
|
|
7974
|
+
*
|
|
7975
|
+
* The snapshot lives in memory for the whole run so planners apply consented bytes rather than a
|
|
7976
|
+
* re-read; a repository must not be able to balloon that residency.
|
|
7977
|
+
*/
|
|
7978
|
+
const MAX_REPO_CONTENT_TOTAL_BYTES = 16e6;
|
|
7979
|
+
//#endregion
|
|
7980
|
+
//#region ../core/src/preset/schema-provides.ts
|
|
7981
|
+
const REPO_MCP_ID_PATTERN = /^repo\/[a-zA-Z0-9][a-zA-Z0-9._-]*$/u;
|
|
7982
|
+
/**
|
|
7983
|
+
* Collects the `provides` envelope of a repository preset.
|
|
7984
|
+
*
|
|
7985
|
+
* Content a preset authors reaches command lines and app configuration, so it may only arrive
|
|
7986
|
+
* from the file the user hash-trusted for this repository. Callers validating a downloaded or
|
|
7987
|
+
* bundled preset reject the field wholesale before this collector runs.
|
|
7988
|
+
*/
|
|
7989
|
+
function collectProvides(value) {
|
|
7990
|
+
if (value === void 0) return;
|
|
7991
|
+
if (!isRecord$4(value)) return "$.provides: must be an object";
|
|
7992
|
+
const servers = collectProvidedMcpServers(value["mcpServers"]);
|
|
7993
|
+
if (typeof servers === "string") return servers;
|
|
7994
|
+
return servers === void 0 ? Object.freeze({}) : Object.freeze({ mcpServers: servers });
|
|
7995
|
+
}
|
|
7996
|
+
function collectProvidedMcpServers(value) {
|
|
7997
|
+
if (value === void 0) return;
|
|
7998
|
+
if (!Array.isArray(value)) return "$.provides.mcpServers: must be an array of MCP server definitions";
|
|
7999
|
+
if (value.length > 16) return `$.provides.mcpServers: must contain at most ${String(16)} definitions`;
|
|
8000
|
+
const result = [];
|
|
8001
|
+
const ids = /* @__PURE__ */ new Set();
|
|
8002
|
+
const serverNames = /* @__PURE__ */ new Set();
|
|
8003
|
+
for (const [index, candidate] of value.entries()) {
|
|
8004
|
+
const path = `$.provides.mcpServers[${String(index)}]`;
|
|
8005
|
+
const parsed = parseMcpServerManifestValue(candidate);
|
|
8006
|
+
if ("error" in parsed) return `${path}${parsed.error.path.slice(1)}: ${parsed.error.message}`;
|
|
8007
|
+
if (!REPO_MCP_ID_PATTERN.test(parsed.value.id)) return `${path}.id: must be namespaced "repo/<name>"`;
|
|
8008
|
+
if (ids.has(parsed.value.id)) return `${path}.id: must not duplicate another provided server id`;
|
|
8009
|
+
if (serverNames.has(parsed.value.serverName)) return `${path}.serverName: must not duplicate another provided server name`;
|
|
8010
|
+
ids.add(parsed.value.id);
|
|
8011
|
+
serverNames.add(parsed.value.serverName);
|
|
8012
|
+
result.push(parsed.value);
|
|
8013
|
+
}
|
|
8014
|
+
return Object.freeze(result);
|
|
8015
|
+
}
|
|
8016
|
+
//#endregion
|
|
7987
8017
|
//#region ../core/src/preset/schema-sources.ts
|
|
7988
8018
|
function collectSkills(value) {
|
|
7989
8019
|
if (value === void 0) return;
|
|
@@ -7997,7 +8027,7 @@ function collectSkills(value) {
|
|
|
7997
8027
|
const id = candidate["id"];
|
|
7998
8028
|
const source = candidate["source"];
|
|
7999
8029
|
if (typeof id !== "string" || !SKILL_ID_PATTERN.test(id)) return `${path}.id: must be a kebab-case skill id`;
|
|
8000
|
-
if (typeof source !== "string" || !SKILL_SOURCE_ID_PATTERN.test(source)) return `${path}.source: must be a plugin:, directory:, or
|
|
8030
|
+
if (typeof source !== "string" || !SKILL_SOURCE_ID_PATTERN.test(source)) return `${path}.source: must be a plugin:, directory:, driver:, or repo: source id`;
|
|
8001
8031
|
const identity = `${source}/${id}`;
|
|
8002
8032
|
if (seen.has(identity)) return `${path}: must not duplicate another skill selection`;
|
|
8003
8033
|
seen.add(identity);
|
|
@@ -8071,31 +8101,32 @@ function toDirectoryId(id) {
|
|
|
8071
8101
|
function toSourceId(id) {
|
|
8072
8102
|
if (id.startsWith("directory:")) return toDirectoryId(id);
|
|
8073
8103
|
if (id.startsWith("driver:")) return `driver:${id.slice(7)}`;
|
|
8104
|
+
if (id.startsWith("repo:")) return `repo:${id.slice(5)}`;
|
|
8074
8105
|
return `plugin:${id.slice(id.indexOf(":") + 1)}`;
|
|
8075
8106
|
}
|
|
8076
8107
|
//#endregion
|
|
8077
8108
|
//#region ../core/src/preset/schema.ts
|
|
8078
8109
|
/** Validates and freezes one parsed JSON document as a data-only team preset. */
|
|
8079
|
-
function validateTeamPreset(value) {
|
|
8110
|
+
function validateTeamPreset(value, options = {}) {
|
|
8080
8111
|
if (!isRecord$4(value)) return invalid$2("$: must be an object");
|
|
8081
|
-
const
|
|
8082
|
-
if (
|
|
8083
|
-
if (value["schemaVersion"] !== 1) return invalid$2("$.schemaVersion: must be 1");
|
|
8112
|
+
const problemBeforeFields = envelopeProblem(value, options);
|
|
8113
|
+
if (problemBeforeFields !== void 0) return invalid$2(problemBeforeFields);
|
|
8084
8114
|
const name = value["name"];
|
|
8085
|
-
if (name !== void 0 && (typeof name !== "string" || name.trim().length === 0 || name.length > 128)) return invalid$2("$.name: must be a non-empty string of at most 128 characters");
|
|
8086
8115
|
const checks = collectChecks(value["checks"]);
|
|
8087
8116
|
const allowed = collectAllowedSources(value["allowedSkillSources"]);
|
|
8088
8117
|
const directories = collectDirectories(value["skillDirectories"]);
|
|
8089
8118
|
const requiredMcpServers = collectIds(value["requiredMcpServers"], "$.requiredMcpServers", MCP_CATALOG_ID_PATTERN);
|
|
8090
8119
|
const snippets = collectIds(value["snippets"], "$.snippets", CONTENT_ID_PATTERN);
|
|
8091
8120
|
const skills = collectSkills(value["skills"]);
|
|
8121
|
+
const provides = collectProvides(value["provides"]);
|
|
8092
8122
|
const problem = firstProblem([
|
|
8093
8123
|
checks,
|
|
8094
8124
|
allowed,
|
|
8095
8125
|
directories,
|
|
8096
8126
|
requiredMcpServers,
|
|
8097
8127
|
snippets,
|
|
8098
|
-
skills
|
|
8128
|
+
skills,
|
|
8129
|
+
provides
|
|
8099
8130
|
]);
|
|
8100
8131
|
if (problem !== void 0) return invalid$2(problem);
|
|
8101
8132
|
return {
|
|
@@ -8103,6 +8134,7 @@ function validateTeamPreset(value) {
|
|
|
8103
8134
|
preset: Object.freeze({
|
|
8104
8135
|
...Array.isArray(allowed) ? { allowedSkillSources: allowed } : {},
|
|
8105
8136
|
...isChecks(checks) ? { checks } : {},
|
|
8137
|
+
...typeof provides === "object" ? { provides } : {},
|
|
8106
8138
|
...Array.isArray(requiredMcpServers) ? { requiredMcpServers } : {},
|
|
8107
8139
|
...typeof name === "string" ? { name } : {},
|
|
8108
8140
|
schemaVersion: 1,
|
|
@@ -8118,6 +8150,15 @@ function invalid$2(problem) {
|
|
|
8118
8150
|
problem
|
|
8119
8151
|
};
|
|
8120
8152
|
}
|
|
8153
|
+
/** Checks the document envelope — depth, version, origin gate, name — before any field walks. */
|
|
8154
|
+
function envelopeProblem(value, options) {
|
|
8155
|
+
const depthProblem = documentDepthProblem(value, "$", 0);
|
|
8156
|
+
if (depthProblem !== void 0) return depthProblem;
|
|
8157
|
+
if (value["schemaVersion"] !== 1) return "$.schemaVersion: must be 1";
|
|
8158
|
+
if (options.allowProvides !== true && value["provides"] !== void 0) return "$.provides: only the repository preset may provide content";
|
|
8159
|
+
const name = value["name"];
|
|
8160
|
+
if (name !== void 0 && (typeof name !== "string" || name.trim().length === 0 || name.length > 128)) return "$.name: must be a non-empty string of at most 128 characters";
|
|
8161
|
+
}
|
|
8121
8162
|
/**
|
|
8122
8163
|
* Rejects a document nested past the limit before any field is read.
|
|
8123
8164
|
*
|
|
@@ -8967,7 +9008,7 @@ async function readTeamPreset(cwd, reader) {
|
|
|
8967
9008
|
} catch {
|
|
8968
9009
|
return failure(path, "is not valid JSON");
|
|
8969
9010
|
}
|
|
8970
|
-
const result = validateTeamPreset(parsed);
|
|
9011
|
+
const result = validateTeamPreset(parsed, { allowProvides: true });
|
|
8971
9012
|
if (result.kind === "invalid") return failure(path, `is not a valid team preset (${result.problem})`);
|
|
8972
9013
|
return {
|
|
8973
9014
|
content: contents.content,
|
|
@@ -8989,18 +9030,275 @@ function failure(path, reason) {
|
|
|
8989
9030
|
};
|
|
8990
9031
|
}
|
|
8991
9032
|
//#endregion
|
|
8992
|
-
//#region ../core/src/
|
|
9033
|
+
//#region ../core/src/skills/repo-source.ts
|
|
9034
|
+
const REPO_SKILLS_DIAGNOSTIC_ID = "core/repo-skills";
|
|
9035
|
+
const SKILL_FILE = "SKILL.md";
|
|
9036
|
+
/** The one source id repository skills are offered under; one repository, one source. */
|
|
9037
|
+
const REPO_SKILL_SOURCE_ID = "repo:workspace";
|
|
9038
|
+
/** The repository skills directory as a picker-facing source. */
|
|
9039
|
+
function repoSkillSource(path) {
|
|
9040
|
+
return Object.freeze({
|
|
9041
|
+
id: REPO_SKILL_SOURCE_ID,
|
|
9042
|
+
kind: "repo",
|
|
9043
|
+
name: "This repository",
|
|
9044
|
+
path
|
|
9045
|
+
});
|
|
9046
|
+
}
|
|
9047
|
+
/**
|
|
9048
|
+
* Resolves every skill tree below `.aura/skills` into an installable pack.
|
|
9049
|
+
*
|
|
9050
|
+
* A broken entry earns a diagnostic and drops out rather than failing the run: unlike snippets,
|
|
9051
|
+
* these trees are outside the trust hash — they are offers the per-skill review gates, so an
|
|
9052
|
+
* unreadable one is a smaller catalog, not a broken consent record. Each tree walks under the
|
|
9053
|
+
* driver policy: a cloned tree deserves exactly the suspicion a driver-materialized one gets.
|
|
9054
|
+
*/
|
|
9055
|
+
async function resolveRepoSkills(root, boundary, reader, maxTotalBytes) {
|
|
9056
|
+
const rootRead = await reader.readWithin(root, [boundary]);
|
|
9057
|
+
const { contents } = rootRead;
|
|
9058
|
+
if (!contents.exists) return {
|
|
9059
|
+
diagnostics: [],
|
|
9060
|
+
skills: []
|
|
9061
|
+
};
|
|
9062
|
+
if (rootRead.kind !== "read" || contents.pathKind === "symlink" || contents.problem !== void 0 || contents.entries === void 0) return {
|
|
9063
|
+
diagnostics: [{
|
|
9064
|
+
adapterId: REPO_SKILLS_DIAGNOSTIC_ID,
|
|
9065
|
+
message: "Repository skills path .aura/skills is not a directory, so none are offered.",
|
|
9066
|
+
path: root,
|
|
9067
|
+
phase: "read"
|
|
9068
|
+
}],
|
|
9069
|
+
skills: []
|
|
9070
|
+
};
|
|
9071
|
+
const diagnostics = [];
|
|
9072
|
+
const named = contents.entries.filter((entry) => !entry.startsWith("."));
|
|
9073
|
+
if (named.length > 32) diagnostics.push({
|
|
9074
|
+
adapterId: REPO_SKILLS_DIAGNOSTIC_ID,
|
|
9075
|
+
message: `Repository skills directory holds more than the ${String(32)} entries Aura offers; the rest are ignored.`,
|
|
9076
|
+
path: root,
|
|
9077
|
+
phase: "read"
|
|
9078
|
+
});
|
|
9079
|
+
const skills = [];
|
|
9080
|
+
const source = repoSkillSource(root);
|
|
9081
|
+
let bytesRead = 0;
|
|
9082
|
+
for (const id of named.slice(0, 32)) {
|
|
9083
|
+
const remainingBytes = maxTotalBytes - bytesRead;
|
|
9084
|
+
if (remainingBytes <= 0) {
|
|
9085
|
+
diagnostics.push(diagnostic(join(root, id), "exceeds the repository content size budget"));
|
|
9086
|
+
continue;
|
|
9087
|
+
}
|
|
9088
|
+
const outcome = await resolveOneSkill(root, id, source, reader, boundary, remainingWalkPolicy(remainingBytes));
|
|
9089
|
+
bytesRead += outcome.bytesRead;
|
|
9090
|
+
if (outcome.problem !== void 0) {
|
|
9091
|
+
diagnostics.push(diagnostic(join(root, id), outcome.bytesRead >= remainingBytes ? "exceeds the repository content size budget" : outcome.problem));
|
|
9092
|
+
continue;
|
|
9093
|
+
}
|
|
9094
|
+
skills.push(outcome.skill);
|
|
9095
|
+
}
|
|
9096
|
+
return {
|
|
9097
|
+
diagnostics: Object.freeze(diagnostics),
|
|
9098
|
+
skills: Object.freeze(skills)
|
|
9099
|
+
};
|
|
9100
|
+
}
|
|
9101
|
+
async function resolveOneSkill(root, id, source, reader, boundary, policy) {
|
|
9102
|
+
const idProblem = skillIdProblem(id);
|
|
9103
|
+
if (idProblem !== void 0) return {
|
|
9104
|
+
bytesRead: 0,
|
|
9105
|
+
problem: idProblem
|
|
9106
|
+
};
|
|
9107
|
+
const tree = await walkTree(join(root, id), reader, policy, boundary);
|
|
9108
|
+
if (tree.problem !== void 0) return {
|
|
9109
|
+
bytesRead: tree.bytesRead,
|
|
9110
|
+
problem: tree.problem.message
|
|
9111
|
+
};
|
|
9112
|
+
const definition = tree.files.find((file) => file.path === SKILL_FILE);
|
|
9113
|
+
if (definition === void 0) return {
|
|
9114
|
+
bytesRead: tree.bytesRead,
|
|
9115
|
+
problem: `does not contain ${SKILL_FILE}`
|
|
9116
|
+
};
|
|
9117
|
+
const frontmatter = parseSkillFrontmatter(definition.content);
|
|
9118
|
+
return {
|
|
9119
|
+
bytesRead: tree.bytesRead,
|
|
9120
|
+
skill: Object.freeze({
|
|
9121
|
+
description: frontmatter.description ?? "Repository skill.",
|
|
9122
|
+
files: tree.files,
|
|
9123
|
+
id,
|
|
9124
|
+
name: frontmatter.name ?? id,
|
|
9125
|
+
source,
|
|
9126
|
+
treeHash: treeHash(tree.files),
|
|
9127
|
+
version: frontmatter.version ?? "0.0.0"
|
|
9128
|
+
})
|
|
9129
|
+
};
|
|
9130
|
+
}
|
|
9131
|
+
/** Caps each attempt by what remains of the repository-wide read budget. */
|
|
9132
|
+
function remainingWalkPolicy(remainingBytes) {
|
|
9133
|
+
return Object.freeze({
|
|
9134
|
+
...DRIVER_WALK_POLICY,
|
|
9135
|
+
maxFileBytes: Math.min(DRIVER_WALK_POLICY.maxFileBytes, remainingBytes),
|
|
9136
|
+
maxTotalBytes: Math.min(DRIVER_WALK_POLICY.maxTotalBytes, remainingBytes)
|
|
9137
|
+
});
|
|
9138
|
+
}
|
|
9139
|
+
function diagnostic(path, message) {
|
|
9140
|
+
return {
|
|
9141
|
+
adapterId: REPO_SKILLS_DIAGNOSTIC_ID,
|
|
9142
|
+
message: `Repository skill ${message}, so it is not offered.`,
|
|
9143
|
+
path,
|
|
9144
|
+
phase: "read"
|
|
9145
|
+
};
|
|
9146
|
+
}
|
|
9147
|
+
//#endregion
|
|
9148
|
+
//#region ../core/src/preset/repo-content-files.ts
|
|
9149
|
+
/** Matches the optional frontmatter block a snippet file may open with. */
|
|
9150
|
+
const FRONTMATTER_PATTERN = /^---\r?\n[\s\S]*?\r?\n---(?:\r?\n|$)/u;
|
|
9151
|
+
/** Reads every Markdown snippet below `.aura/snippets` under the trust-set rules. */
|
|
9152
|
+
async function readRepoSnippets(root, boundary, reader) {
|
|
9153
|
+
const contents = await reader.read(root);
|
|
9154
|
+
if (!contents.exists) return {
|
|
9155
|
+
files: [],
|
|
9156
|
+
status: "ready"
|
|
9157
|
+
};
|
|
9158
|
+
if (contents.entries === void 0) return {
|
|
9159
|
+
problem: "the snippets path is not a directory",
|
|
9160
|
+
status: "invalid"
|
|
9161
|
+
};
|
|
9162
|
+
const names = contents.entries.filter((entry) => !entry.startsWith(".") && entry.endsWith(".md"));
|
|
9163
|
+
if (names.length > 64) return {
|
|
9164
|
+
problem: `holds more than the ${String(64)} snippet limit`,
|
|
9165
|
+
status: "invalid"
|
|
9166
|
+
};
|
|
9167
|
+
const files = [];
|
|
9168
|
+
for (const name of names) {
|
|
9169
|
+
const result = await readSnippetFile(root, name, boundary, reader);
|
|
9170
|
+
if (typeof result === "string") return {
|
|
9171
|
+
problem: result,
|
|
9172
|
+
status: "invalid"
|
|
9173
|
+
};
|
|
9174
|
+
files.push(result);
|
|
9175
|
+
}
|
|
9176
|
+
files.sort((left, right) => left.entry.id < right.entry.id ? -1 : 1);
|
|
9177
|
+
return {
|
|
9178
|
+
files: Object.freeze(files),
|
|
9179
|
+
status: "ready"
|
|
9180
|
+
};
|
|
9181
|
+
}
|
|
9182
|
+
async function readSnippetFile(root, name, boundary, reader) {
|
|
9183
|
+
const stem = name.slice(0, -3);
|
|
9184
|
+
if (skillIdProblem(stem) !== void 0) return "contains an entry that is not a kebab-case Markdown file name";
|
|
9185
|
+
const read = await reader.readWithin(join(root, name), [boundary], { maxBytes: MAX_SNIPPET_BYTES });
|
|
9186
|
+
const problem = snippetReadProblem(read);
|
|
9187
|
+
if (problem !== void 0) return problem;
|
|
9188
|
+
const text = read.contents.content ?? "";
|
|
9189
|
+
const frontmatter = parseSkillFrontmatter(text);
|
|
9190
|
+
const body = text.replace(FRONTMATTER_PATTERN, "");
|
|
9191
|
+
return {
|
|
9192
|
+
entry: Object.freeze({
|
|
9193
|
+
body,
|
|
9194
|
+
...frontmatter.description === void 0 ? {} : { description: frontmatter.description },
|
|
9195
|
+
id: `repo/${stem}`,
|
|
9196
|
+
name: frontmatter.name ?? stem
|
|
9197
|
+
}),
|
|
9198
|
+
text
|
|
9199
|
+
};
|
|
9200
|
+
}
|
|
9201
|
+
/** Why a bounded, boundary-checked read did not yield a plain snippet file. */
|
|
9202
|
+
function snippetReadProblem(read) {
|
|
9203
|
+
if (read.kind === "outside") return "contains an entry that leads outside the repository's .aura directory";
|
|
9204
|
+
if (read.kind === "unverified" || read.contents.pathKind === "symlink") return "contains an entry that is not a regular file";
|
|
9205
|
+
const { contents } = read;
|
|
9206
|
+
if (contents.problem !== void 0 || contents.isDirectory || contents.content === void 0 || contents.utf8Valid === false) return "contains an entry that is not a readable UTF-8 file";
|
|
9207
|
+
if ((contents.size ?? 0) > 256e3) return `contains an entry larger than the ${String(MAX_SNIPPET_BYTES)} byte snippet limit`;
|
|
9208
|
+
}
|
|
9209
|
+
//#endregion
|
|
9210
|
+
//#region ../core/src/preset/repo-content.ts
|
|
9211
|
+
const REPO_CONTENT_DIAGNOSTIC_ID = "core/repo-content";
|
|
9212
|
+
/**
|
|
9213
|
+
* Reads the repository's trust-hashed content and any requested installable content in one snapshot.
|
|
9214
|
+
*
|
|
9215
|
+
* The result is what catalogs and planners consume for the whole run: applying from this snapshot
|
|
9216
|
+
* rather than from a re-read is what closes the window between the bytes the user consented to
|
|
9217
|
+
* and the bytes a later write could put on disk. A broken snippet set fails the run — those bytes
|
|
9218
|
+
* are inside the trust hash — while a broken skill tree only shrinks the offers, because skills
|
|
9219
|
+
* stay behind the per-skill review.
|
|
9220
|
+
*/
|
|
9221
|
+
async function readRepoContent(auraDir, preset, presetText, reader, options = {}) {
|
|
9222
|
+
const boundary = await reader.realPath(auraDir) ?? auraDir;
|
|
9223
|
+
const snippets = await readRepoSnippets(join(auraDir, "snippets"), boundary, reader);
|
|
9224
|
+
if (snippets.status === "invalid") return {
|
|
9225
|
+
diagnostics: [{
|
|
9226
|
+
adapterId: REPO_CONTENT_DIAGNOSTIC_ID,
|
|
9227
|
+
message: `Repository snippets directory ".aura/snippets" ${snippets.problem}.`,
|
|
9228
|
+
path: join(auraDir, "snippets"),
|
|
9229
|
+
phase: "read"
|
|
9230
|
+
}],
|
|
9231
|
+
status: "invalid"
|
|
9232
|
+
};
|
|
9233
|
+
const snippetBytes = snippets.files.reduce((sum, file) => sum + Buffer$1.byteLength(file.text, "utf8"), 0);
|
|
9234
|
+
if (snippetBytes > 16e6) return {
|
|
9235
|
+
diagnostics: [{
|
|
9236
|
+
adapterId: REPO_CONTENT_DIAGNOSTIC_ID,
|
|
9237
|
+
message: `Repository snippets exceed the ${String(MAX_REPO_CONTENT_TOTAL_BYTES)} byte repository content budget.`,
|
|
9238
|
+
path: join(auraDir, "snippets"),
|
|
9239
|
+
phase: "read"
|
|
9240
|
+
}],
|
|
9241
|
+
status: "invalid"
|
|
9242
|
+
};
|
|
9243
|
+
const skills = options.includeSkills === false ? {
|
|
9244
|
+
diagnostics: [],
|
|
9245
|
+
skills: []
|
|
9246
|
+
} : await resolveRepoSkills(join(auraDir, "skills"), boundary, reader, MAX_REPO_CONTENT_TOTAL_BYTES - snippetBytes);
|
|
9247
|
+
return {
|
|
9248
|
+
contentSet: Object.freeze({
|
|
9249
|
+
mcpServers: preset.provides?.mcpServers ?? [],
|
|
9250
|
+
skills: skills.skills,
|
|
9251
|
+
snippets: Object.freeze(snippets.files.map((file) => file.entry))
|
|
9252
|
+
}),
|
|
9253
|
+
diagnostics: skills.diagnostics,
|
|
9254
|
+
hash: hashRepoContentSet(presetText, snippets.files.map((file) => ({
|
|
9255
|
+
id: file.entry.id,
|
|
9256
|
+
text: file.text
|
|
9257
|
+
}))),
|
|
9258
|
+
status: "ready"
|
|
9259
|
+
};
|
|
9260
|
+
}
|
|
9261
|
+
/**
|
|
9262
|
+
* Lifts the preset's inline MCP definitions into catalog entries.
|
|
9263
|
+
*
|
|
9264
|
+
* The `repo/` namespace is reserved against plugins at registry build, so these can never collide
|
|
9265
|
+
* with — or be shadowed by — a plugin catalog entry. The source URL names the preset file itself:
|
|
9266
|
+
* that is the file whose bytes the user trusted.
|
|
9267
|
+
*/
|
|
9268
|
+
function repoMcpServerDefs(servers, presetPath) {
|
|
9269
|
+
return Object.freeze(servers.map((manifest) => Object.freeze({
|
|
9270
|
+
description: manifest.description,
|
|
9271
|
+
id: manifest.id,
|
|
9272
|
+
kind: "mcp-server",
|
|
9273
|
+
manifest,
|
|
9274
|
+
name: manifest.name,
|
|
9275
|
+
source: Object.freeze({
|
|
9276
|
+
type: "file",
|
|
9277
|
+
url: pathToFileURL(presetPath).href
|
|
9278
|
+
}),
|
|
9279
|
+
version: "0.0.0"
|
|
9280
|
+
})));
|
|
9281
|
+
}
|
|
8993
9282
|
/**
|
|
8994
|
-
* Hashes repository
|
|
9283
|
+
* Hashes the repository content set for the trust record.
|
|
8995
9284
|
*
|
|
8996
|
-
*
|
|
8997
|
-
*
|
|
9285
|
+
* With no snippet files the hash is exactly the preset file's own content hash, so a repository
|
|
9286
|
+
* that never adds `.aura/snippets` keeps every trust its users already recorded. The first
|
|
9287
|
+
* snippet changes the descriptor shape — and consent is re-asked, which is the point.
|
|
8998
9288
|
*/
|
|
8999
|
-
function
|
|
9000
|
-
return hashContent(
|
|
9289
|
+
function hashRepoContentSet(presetText, snippets) {
|
|
9290
|
+
if (snippets.length === 0) return hashContent(presetText);
|
|
9291
|
+
const lines = [
|
|
9292
|
+
"aura-repo-content-v1",
|
|
9293
|
+
`preset ${hashContent(presetText)}`,
|
|
9294
|
+
...[...snippets].sort((left, right) => left.id < right.id ? -1 : 1).map((file) => `snippet ${file.id} ${hashContent(file.text)}`)
|
|
9295
|
+
];
|
|
9296
|
+
return hashContent(lines.join("\n"));
|
|
9001
9297
|
}
|
|
9298
|
+
//#endregion
|
|
9299
|
+
//#region ../core/src/preset/repo-trust.ts
|
|
9002
9300
|
/** Reads and validates the repository preset below the invoking directory. */
|
|
9003
|
-
async function readRepoPreset(environment, reader = createFileReader()) {
|
|
9301
|
+
async function readRepoPreset(environment, reader = createFileReader(), options = {}) {
|
|
9004
9302
|
const cwd = await reader.realPath(environment.cwd) ?? environment.cwd;
|
|
9005
9303
|
const path = resolveTeamPresetPath(cwd);
|
|
9006
9304
|
const state = await readTeamPreset(cwd, reader);
|
|
@@ -9009,10 +9307,17 @@ async function readRepoPreset(environment, reader = createFileReader()) {
|
|
|
9009
9307
|
path,
|
|
9010
9308
|
status: state.status
|
|
9011
9309
|
};
|
|
9310
|
+
const content = await readRepoContent(dirname(path), state.preset, state.content, reader, options);
|
|
9311
|
+
if (content.status === "invalid") return {
|
|
9312
|
+
diagnostics: [...state.diagnostics, ...content.diagnostics],
|
|
9313
|
+
path,
|
|
9314
|
+
status: "invalid"
|
|
9315
|
+
};
|
|
9012
9316
|
const mainWorktreePath = await resolveMainWorktreePresetPath(cwd, path, reader);
|
|
9013
9317
|
return {
|
|
9014
|
-
|
|
9015
|
-
|
|
9318
|
+
contentSet: content.contentSet,
|
|
9319
|
+
diagnostics: [...state.diagnostics, ...content.diagnostics],
|
|
9320
|
+
hash: content.hash,
|
|
9016
9321
|
...mainWorktreePath === void 0 ? {} : { mainWorktreePath },
|
|
9017
9322
|
path,
|
|
9018
9323
|
preset: state.preset,
|
|
@@ -9057,7 +9362,7 @@ function applyRequiredMcpServers(model, config) {
|
|
|
9057
9362
|
model
|
|
9058
9363
|
};
|
|
9059
9364
|
const manifest = model.manifest.value;
|
|
9060
|
-
const configured = new Set(manifest.mcpServers.map((server) =>
|
|
9365
|
+
const configured = new Set(manifest.mcpServers.map((server) => server.name));
|
|
9061
9366
|
const configuredCatalogIds = new Set(manifest.mcpServers.flatMap((server) => server.catalogId === void 0 ? [] : [server.catalogId]));
|
|
9062
9367
|
const diagnostics = [];
|
|
9063
9368
|
const required = [];
|
|
@@ -9078,7 +9383,7 @@ function applyRequiredMcpServers(model, config) {
|
|
|
9078
9383
|
continue;
|
|
9079
9384
|
}
|
|
9080
9385
|
const name = catalog.manifest.serverName;
|
|
9081
|
-
if (configured.has(
|
|
9386
|
+
if (configured.has(name)) {
|
|
9082
9387
|
diagnostics.push(note(`is already configured as "${name}", so your own settings were kept`, catalog.id, selection));
|
|
9083
9388
|
continue;
|
|
9084
9389
|
}
|
|
@@ -9092,7 +9397,6 @@ function applyRequiredMcpServers(model, config) {
|
|
|
9092
9397
|
catalogId: catalog.id,
|
|
9093
9398
|
name,
|
|
9094
9399
|
requiredBy: selection.provenance.label,
|
|
9095
|
-
scope: "global",
|
|
9096
9400
|
transport: catalog.manifest.transportTemplate
|
|
9097
9401
|
}));
|
|
9098
9402
|
}
|
|
@@ -9117,6 +9421,91 @@ function note(reason, id, selection) {
|
|
|
9117
9421
|
};
|
|
9118
9422
|
}
|
|
9119
9423
|
//#endregion
|
|
9424
|
+
//#region src/runtime-config.ts
|
|
9425
|
+
/** Loads the selected preset and resolves all runtime layers once for a command. */
|
|
9426
|
+
async function resolveRuntimeConfig(input) {
|
|
9427
|
+
if (input.manifest.status === "read-only") return {
|
|
9428
|
+
message: input.manifest.problem.message,
|
|
9429
|
+
status: "invalid"
|
|
9430
|
+
};
|
|
9431
|
+
const manifest = input.manifest.status === "ready" ? input.manifest.value : void 0;
|
|
9432
|
+
const loaded = await loadTeamPreset({
|
|
9433
|
+
cliReference: input.cliReference,
|
|
9434
|
+
defaultReference: input.defaultPreset,
|
|
9435
|
+
environment: input.environment,
|
|
9436
|
+
manifestReference: manifest?.preset,
|
|
9437
|
+
noCache: input.noCache,
|
|
9438
|
+
offline: input.online !== true,
|
|
9439
|
+
presets: input.registry.presets
|
|
9440
|
+
});
|
|
9441
|
+
if (loaded.status === "invalid") return loaded;
|
|
9442
|
+
const repo = input.repoPresetState ?? await readRepoPreset(input.environment, void 0, { includeSkills: false });
|
|
9443
|
+
if (repo.status === "invalid") return {
|
|
9444
|
+
message: `${repo.diagnostics[0]?.message ?? `Repository preset ${repo.path} cannot be read.`} Fix or remove the file to continue.`,
|
|
9445
|
+
status: "invalid"
|
|
9446
|
+
};
|
|
9447
|
+
const repoTrusted = repo.status === "ready" && repo.hash !== void 0 && (repo.hash === input.acceptedRepoPresetHash || isRepoPresetTrusted(manifest, repo, repo.hash));
|
|
9448
|
+
const resolved = resolveEffectiveConfig({
|
|
9449
|
+
checks: input.registry.checks,
|
|
9450
|
+
cli: input.cliLayer,
|
|
9451
|
+
distro: input.defaults,
|
|
9452
|
+
knownMcpServers: /* @__PURE__ */ new Set([...input.registry.mcpServers.map((server) => server.id), ...repoTrusted ? (repo.preset?.provides?.mcpServers ?? []).map((server) => server.id) : []]),
|
|
9453
|
+
...manifest === void 0 ? {} : { manifest: {
|
|
9454
|
+
...manifest.checks === void 0 ? {} : { checks: manifest.checks },
|
|
9455
|
+
skills: manifest.skills.map(({ id, source }) => ({
|
|
9456
|
+
id,
|
|
9457
|
+
source
|
|
9458
|
+
})),
|
|
9459
|
+
snippets: manifest.snippets.map(({ id }) => id)
|
|
9460
|
+
} },
|
|
9461
|
+
...loaded.status === "ready" ? {
|
|
9462
|
+
preset: loaded.preset,
|
|
9463
|
+
selectedPreset: loaded.selected
|
|
9464
|
+
} : {},
|
|
9465
|
+
...repoTrusted ? { repo: repo.preset } : {}
|
|
9466
|
+
});
|
|
9467
|
+
if (resolved.status === "invalid") return {
|
|
9468
|
+
message: resolved.problems.join("\n"),
|
|
9469
|
+
status: "invalid"
|
|
9470
|
+
};
|
|
9471
|
+
const policyPreset = loaded.status === "ready" || resolved.config.allowedSkillSources !== void 0 || resolved.config.skillDirectories.length > 0 ? Object.freeze({
|
|
9472
|
+
...loaded.status === "ready" ? loaded.preset : {},
|
|
9473
|
+
...resolved.config.allowedSkillSources === void 0 ? {} : { allowedSkillSources: resolved.config.allowedSkillSources.value },
|
|
9474
|
+
name: resolved.config.preset?.name ?? resolved.config.allowedSkillSources?.provenance.label ?? resolved.config.skillDirectories[0]?.provenance.label ?? "runtime policy",
|
|
9475
|
+
schemaVersion: 1,
|
|
9476
|
+
skillDirectories: resolved.config.skillDirectories.map(({ value }) => value)
|
|
9477
|
+
}) : void 0;
|
|
9478
|
+
return {
|
|
9479
|
+
config: resolved.config,
|
|
9480
|
+
notes: [...loaded.status === "ready" ? loaded.notes : [], ...repoTrusted ? repo.diagnostics.map((diagnostic) => diagnostic.message) : []],
|
|
9481
|
+
...policyPreset === void 0 ? {} : { preset: policyPreset },
|
|
9482
|
+
presetOrigin: loaded.status === "ready" ? loaded.origin : policyPreset?.name ?? ".aura/preset.json",
|
|
9483
|
+
...repo.status === "ready" && repo.hash !== void 0 ? { repoPreset: {
|
|
9484
|
+
...repoTrusted && repo.contentSet !== void 0 ? { contentSet: repo.contentSet } : {},
|
|
9485
|
+
hash: repo.hash,
|
|
9486
|
+
...repo.mainWorktreePath === void 0 ? {} : { mainWorktreePath: repo.mainWorktreePath },
|
|
9487
|
+
path: repo.path,
|
|
9488
|
+
...repoTrusted && repo.preset !== void 0 ? { preset: repo.preset } : {},
|
|
9489
|
+
status: repoTrusted ? "applied" : "held"
|
|
9490
|
+
} } : {},
|
|
9491
|
+
status: "ready"
|
|
9492
|
+
};
|
|
9493
|
+
}
|
|
9494
|
+
/**
|
|
9495
|
+
* Adds a trusted repository's provided MCP definitions to the model's catalog.
|
|
9496
|
+
*
|
|
9497
|
+
* Run before {@link applyRequiredMcpServers} projection wherever a model is projected, so a
|
|
9498
|
+
* repository can require a server it defines itself. A held repository changes nothing.
|
|
9499
|
+
*/
|
|
9500
|
+
function withRepoMcpCatalog(model, repoPreset) {
|
|
9501
|
+
const servers = repoPreset?.status === "applied" ? repoPreset.contentSet?.mcpServers ?? [] : [];
|
|
9502
|
+
if (servers.length === 0 || repoPreset === void 0) return model;
|
|
9503
|
+
return Object.freeze({
|
|
9504
|
+
...model,
|
|
9505
|
+
availableMcpServers: Object.freeze([...model.availableMcpServers, ...repoMcpServerDefs(servers, repoPreset.path)])
|
|
9506
|
+
});
|
|
9507
|
+
}
|
|
9508
|
+
//#endregion
|
|
9120
9509
|
//#region src/safe-text.ts
|
|
9121
9510
|
const MAX_FINDING_TEXT_CHARACTERS = 500;
|
|
9122
9511
|
const UNICODE_FORMAT_CHARACTER = /\p{Cf}/u;
|
|
@@ -9287,10 +9676,7 @@ function isBundledSkillSelection(skill) {
|
|
|
9287
9676
|
/** Final instruction handling only; paths, sources, and duplicate choices stop here. */
|
|
9288
9677
|
function instructionActions(selections) {
|
|
9289
9678
|
if (selections === void 0) return [];
|
|
9290
|
-
return [
|
|
9291
|
-
action,
|
|
9292
|
-
scope
|
|
9293
|
-
}));
|
|
9679
|
+
return [{ action: selections.global.action }];
|
|
9294
9680
|
}
|
|
9295
9681
|
/**
|
|
9296
9682
|
* Per-check state and severity counts for every executed check, ordered by check ID.
|
|
@@ -9339,6 +9725,15 @@ function countsByCheck(findings) {
|
|
|
9339
9725
|
}
|
|
9340
9726
|
//#endregion
|
|
9341
9727
|
//#region src/command-support.ts
|
|
9728
|
+
/**
|
|
9729
|
+
* Projects preset requirements onto a check scan's model.
|
|
9730
|
+
*
|
|
9731
|
+
* A trusted repository's provided MCP definitions join the catalog first, so a repository can
|
|
9732
|
+
* require a server it defines itself and `check` reports that requirement the way setup would.
|
|
9733
|
+
*/
|
|
9734
|
+
function projectConfiguredModel(model, configured) {
|
|
9735
|
+
return applyRequiredMcpServers(withRepoMcpCatalog(model, configured.repoPreset), configured.config);
|
|
9736
|
+
}
|
|
9342
9737
|
/** The `--home` override every scanning command shares. */
|
|
9343
9738
|
function homeOption() {
|
|
9344
9739
|
return Option.String("--home", { description: "Override the home directory." });
|
|
@@ -9404,75 +9799,6 @@ function reportUnexpectedFailure(error, subject, branding, withDetail, stderr, t
|
|
|
9404
9799
|
return 3;
|
|
9405
9800
|
}
|
|
9406
9801
|
//#endregion
|
|
9407
|
-
//#region src/runtime-config.ts
|
|
9408
|
-
/** Loads the selected preset and resolves all runtime layers once for a command. */
|
|
9409
|
-
async function resolveRuntimeConfig(input) {
|
|
9410
|
-
if (input.manifest.status === "read-only") return {
|
|
9411
|
-
message: input.manifest.problem.message,
|
|
9412
|
-
status: "invalid"
|
|
9413
|
-
};
|
|
9414
|
-
const manifest = input.manifest.status === "ready" ? input.manifest.value : void 0;
|
|
9415
|
-
const loaded = await loadTeamPreset({
|
|
9416
|
-
cliReference: input.cliReference,
|
|
9417
|
-
defaultReference: input.defaultPreset,
|
|
9418
|
-
environment: input.environment,
|
|
9419
|
-
manifestReference: manifest?.preset,
|
|
9420
|
-
noCache: input.noCache,
|
|
9421
|
-
offline: input.online !== true,
|
|
9422
|
-
presets: input.registry.presets
|
|
9423
|
-
});
|
|
9424
|
-
if (loaded.status === "invalid") return loaded;
|
|
9425
|
-
const repo = await readRepoPreset(input.environment);
|
|
9426
|
-
if (repo.status === "invalid") return {
|
|
9427
|
-
message: `${repo.diagnostics[0]?.message ?? `Repository preset ${repo.path} cannot be read.`} Fix or remove the file to continue.`,
|
|
9428
|
-
status: "invalid"
|
|
9429
|
-
};
|
|
9430
|
-
const repoTrusted = repo.status === "ready" && repo.hash !== void 0 && (repo.hash === input.acceptedRepoPresetHash || isRepoPresetTrusted(manifest, repo, repo.hash));
|
|
9431
|
-
const resolved = resolveEffectiveConfig({
|
|
9432
|
-
checks: input.registry.checks,
|
|
9433
|
-
cli: input.cliLayer,
|
|
9434
|
-
distro: input.defaults,
|
|
9435
|
-
knownMcpServers: new Set(input.registry.mcpServers.map((server) => server.id)),
|
|
9436
|
-
...manifest === void 0 ? {} : { manifest: {
|
|
9437
|
-
...manifest.checks === void 0 ? {} : { checks: manifest.checks },
|
|
9438
|
-
skills: manifest.skills.map(({ id, source }) => ({
|
|
9439
|
-
id,
|
|
9440
|
-
source
|
|
9441
|
-
})),
|
|
9442
|
-
snippets: manifest.snippets.map(({ id }) => id)
|
|
9443
|
-
} },
|
|
9444
|
-
...loaded.status === "ready" ? {
|
|
9445
|
-
preset: loaded.preset,
|
|
9446
|
-
selectedPreset: loaded.selected
|
|
9447
|
-
} : {},
|
|
9448
|
-
...repoTrusted ? { repo: repo.preset } : {}
|
|
9449
|
-
});
|
|
9450
|
-
if (resolved.status === "invalid") return {
|
|
9451
|
-
message: resolved.problems.join("\n"),
|
|
9452
|
-
status: "invalid"
|
|
9453
|
-
};
|
|
9454
|
-
const policyPreset = loaded.status === "ready" || resolved.config.allowedSkillSources !== void 0 || resolved.config.skillDirectories.length > 0 ? Object.freeze({
|
|
9455
|
-
...loaded.status === "ready" ? loaded.preset : {},
|
|
9456
|
-
...resolved.config.allowedSkillSources === void 0 ? {} : { allowedSkillSources: resolved.config.allowedSkillSources.value },
|
|
9457
|
-
name: resolved.config.preset?.name ?? resolved.config.allowedSkillSources?.provenance.label ?? resolved.config.skillDirectories[0]?.provenance.label ?? "runtime policy",
|
|
9458
|
-
schemaVersion: 1,
|
|
9459
|
-
skillDirectories: resolved.config.skillDirectories.map(({ value }) => value)
|
|
9460
|
-
}) : void 0;
|
|
9461
|
-
return {
|
|
9462
|
-
config: resolved.config,
|
|
9463
|
-
notes: loaded.status === "ready" ? loaded.notes : [],
|
|
9464
|
-
...policyPreset === void 0 ? {} : { preset: policyPreset },
|
|
9465
|
-
presetOrigin: loaded.status === "ready" ? loaded.origin : policyPreset?.name ?? ".aura/preset.json",
|
|
9466
|
-
...repo.status === "ready" && repo.hash !== void 0 ? { repoPreset: {
|
|
9467
|
-
hash: repo.hash,
|
|
9468
|
-
...repo.mainWorktreePath === void 0 ? {} : { mainWorktreePath: repo.mainWorktreePath },
|
|
9469
|
-
path: repo.path,
|
|
9470
|
-
status: repoTrusted ? "applied" : "held"
|
|
9471
|
-
} } : {},
|
|
9472
|
-
status: "ready"
|
|
9473
|
-
};
|
|
9474
|
-
}
|
|
9475
|
-
//#endregion
|
|
9476
9802
|
//#region src/check-config.ts
|
|
9477
9803
|
/** Resolves the effective configuration for one `check` run. */
|
|
9478
9804
|
function resolveCheckConfiguration(options) {
|
|
@@ -12917,7 +13243,7 @@ var CheckCommand = class extends Command {
|
|
|
12917
13243
|
try {
|
|
12918
13244
|
const startedAt = environment.now();
|
|
12919
13245
|
let scan = await scanner.run();
|
|
12920
|
-
let projected =
|
|
13246
|
+
let projected = projectConfiguredModel(scan.model, configured);
|
|
12921
13247
|
let model = projected.model;
|
|
12922
13248
|
let run = runChecks(activeChecks, model, configured.config);
|
|
12923
13249
|
let fixRunDiagnostics = [];
|
|
@@ -12945,7 +13271,7 @@ var CheckCommand = class extends Command {
|
|
|
12945
13271
|
fixes = outcome.fixes;
|
|
12946
13272
|
if (outcome.applied) {
|
|
12947
13273
|
scan = await scanner.run();
|
|
12948
|
-
projected =
|
|
13274
|
+
projected = projectConfiguredModel(scan.model, configured);
|
|
12949
13275
|
model = projected.model;
|
|
12950
13276
|
run = runChecks(activeChecks, model, configured.config);
|
|
12951
13277
|
}
|
|
@@ -13029,7 +13355,7 @@ function renderRootHelp(branding) {
|
|
|
13029
13355
|
rows: [NO_COLOR_ROW],
|
|
13030
13356
|
title: "Advanced"
|
|
13031
13357
|
}
|
|
13032
|
-
], branding.docsUrl === void 0 ? [] : [`Docs: ${branding.docsUrl}`]);
|
|
13358
|
+
], branding.docsUrl === void 0 ? [] : [`Docs: ${branding.docsUrl}`], ROOT_INTRO);
|
|
13033
13359
|
}
|
|
13034
13360
|
function renderCheckHelp(branding) {
|
|
13035
13361
|
const bin = branding.command;
|
|
@@ -13216,6 +13542,17 @@ const NO_COLOR_ROW = {
|
|
|
13216
13542
|
term: "--no-color",
|
|
13217
13543
|
text: "Disable terminal colors"
|
|
13218
13544
|
};
|
|
13545
|
+
/**
|
|
13546
|
+
* The root screen's one paragraph: what Aura is for, in the words of someone who has not read the
|
|
13547
|
+
* docs yet. It sits above "Get started" because a first-time reader needs the problem before the
|
|
13548
|
+
* command. Hand-wrapped at 80 columns — the renderer aligns columns, it never reflows prose — and
|
|
13549
|
+
* carried only by the root screen, since every other screen is reached by someone who already knows.
|
|
13550
|
+
*/
|
|
13551
|
+
const ROOT_INTRO = [
|
|
13552
|
+
"Every AI coding agent keeps its own rules and its own tool settings, in its own",
|
|
13553
|
+
"files. Aura reads all of them, tells you where they disagree or are broken, and",
|
|
13554
|
+
"fixes them after you say yes - so every agent works from the same instructions."
|
|
13555
|
+
];
|
|
13219
13556
|
/** Test and CI plumbing, present on every command and interesting to almost nobody. */
|
|
13220
13557
|
function advancedRows() {
|
|
13221
13558
|
return [
|
|
@@ -13263,9 +13600,10 @@ function headline(branding) {
|
|
|
13263
13600
|
return branding.description === void 0 ? name : `${name} — ${branding.description}`;
|
|
13264
13601
|
}
|
|
13265
13602
|
/** Terms align to one shared column so the eye scans a single list, not per-section islands. */
|
|
13266
|
-
function renderHelpScreen(header, sections, footers) {
|
|
13603
|
+
function renderHelpScreen(header, sections, footers, intro = []) {
|
|
13267
13604
|
const width = Math.max(...sections.flatMap((section) => section.rows.map((row) => row.term.length)));
|
|
13268
13605
|
const lines = [header];
|
|
13606
|
+
if (intro.length > 0) lines.push("", ...intro.map((line) => ` ${line}`));
|
|
13269
13607
|
for (const section of sections) {
|
|
13270
13608
|
lines.push("", ` ${section.title}`);
|
|
13271
13609
|
for (const row of section.rows) lines.push(` ${row.term.padEnd(width)} ${row.text}`);
|
|
@@ -13507,7 +13845,7 @@ function planLinks$1(context, scopeSelections, archived, ownership, manualSteps)
|
|
|
13507
13845
|
}
|
|
13508
13846
|
function planAppLinks(context, app, scopeSelections, archived, ownership, manualSteps) {
|
|
13509
13847
|
return scopeSelections.flatMap((selection) => {
|
|
13510
|
-
const link =
|
|
13848
|
+
const link = app.sharedLink;
|
|
13511
13849
|
if (link === void 0) return [];
|
|
13512
13850
|
const outcome = planSharedInstructionLink(app, context.model, {
|
|
13513
13851
|
link,
|
|
@@ -13534,12 +13872,12 @@ function planAppLinks(context, app, scopeSelections, archived, ownership, manual
|
|
|
13534
13872
|
* only once that entry is merged here and archived. The wizard does not offer an entry already
|
|
13535
13873
|
* pointing at the target, so leaving this to the selection strands the app on every run.
|
|
13536
13874
|
*/
|
|
13537
|
-
function mandatoryEntryPaths(context
|
|
13875
|
+
function mandatoryEntryPaths(context) {
|
|
13538
13876
|
const managedIds = new Set(managedAppIdList(context));
|
|
13539
13877
|
const paths = /* @__PURE__ */ new Set();
|
|
13540
13878
|
for (const app of context.model.apps) {
|
|
13541
13879
|
if (app.synthetic === true || !managedIds.has(app.adapterId)) continue;
|
|
13542
|
-
const link =
|
|
13880
|
+
const link = app.sharedLink;
|
|
13543
13881
|
if (link?.kind === "symlink") paths.add(resolve(link.entryPath));
|
|
13544
13882
|
}
|
|
13545
13883
|
return paths;
|
|
@@ -13551,7 +13889,7 @@ function mandatoryEntryPaths(context, scope) {
|
|
|
13551
13889
|
* archives them.
|
|
13552
13890
|
*/
|
|
13553
13891
|
function requiredEntries(context, selection, inventory) {
|
|
13554
|
-
const required = mandatoryEntryPaths(context
|
|
13892
|
+
const required = mandatoryEntryPaths(context);
|
|
13555
13893
|
return inventory.filter((source) => source.scope === selection.scope && required.has(resolve(source.path)));
|
|
13556
13894
|
}
|
|
13557
13895
|
/** Folds the required entries into the selection, so the merge and the archive both see them. */
|
|
@@ -13586,7 +13924,7 @@ function canonicalSourcePath(document) {
|
|
|
13586
13924
|
*/
|
|
13587
13925
|
function isAuraArtifact(document, model) {
|
|
13588
13926
|
const path = resolve(document.path);
|
|
13589
|
-
return model.apps.some((app) =>
|
|
13927
|
+
return model.apps.some((app) => app.sharedLink !== void 0 && resolve(app.sharedLink.entryPath) === path && matchesLink(document, app.sharedLink));
|
|
13590
13928
|
}
|
|
13591
13929
|
function matchesLink(document, link) {
|
|
13592
13930
|
if (link.kind === "native-copy") return document.content === link.content;
|
|
@@ -13600,7 +13938,7 @@ function matchesLink(document, link) {
|
|
|
13600
13938
|
* would otherwise re-walk every application to rediscover the same handful of links.
|
|
13601
13939
|
*/
|
|
13602
13940
|
function importLineLinks(model) {
|
|
13603
|
-
return model.apps.
|
|
13941
|
+
return model.apps.map((app) => app.sharedLink).filter((link) => link?.kind === "import-line" && link.content !== void 0);
|
|
13604
13942
|
}
|
|
13605
13943
|
/** Removes legacy marked links and a plain import Aura appended at the declared entry path. */
|
|
13606
13944
|
function stripAuraInstructionArtifacts(content, path, links) {
|
|
@@ -13736,17 +14074,14 @@ function removeRanges(content, ranges) {
|
|
|
13736
14074
|
//#endregion
|
|
13737
14075
|
//#region src/setup/instructions.ts
|
|
13738
14076
|
function instructionTargets(model) {
|
|
13739
|
-
return {
|
|
13740
|
-
global: resolve(model.sharedInstructions.path),
|
|
13741
|
-
project: resolve(projectSharedInstructionsPath(model.projectRoot, model.cwd))
|
|
13742
|
-
};
|
|
14077
|
+
return { global: resolve(model.sharedInstructions.path) };
|
|
13743
14078
|
}
|
|
13744
14079
|
function instructionInventory(model) {
|
|
13745
14080
|
const targets = instructionTargets(model);
|
|
13746
14081
|
const owned = new Set(model.manifest.status === "ready" ? Object.values(model.manifest.value.ownership).flatMap((entry) => entry.files.map((path) => resolve(path))) : []);
|
|
13747
14082
|
const documents = /* @__PURE__ */ new Map();
|
|
13748
14083
|
for (const document of model.instructionFiles) {
|
|
13749
|
-
if ([resolve(document.path), canonicalSourcePath(document)].some((candidate) => candidate === targets.global ||
|
|
14084
|
+
if ([resolve(document.path), canonicalSourcePath(document)].some((candidate) => candidate === targets.global || owned.has(candidate)) || isAuraArtifact(document, model)) continue;
|
|
13750
14085
|
const key = canonicalSourcePath(document);
|
|
13751
14086
|
const current = documents.get(key);
|
|
13752
14087
|
if (current === void 0 || document.content.length > current.content.length) documents.set(key, document);
|
|
@@ -13847,10 +14182,8 @@ function planInstructions(context) {
|
|
|
13847
14182
|
const ownership = /* @__PURE__ */ new Map();
|
|
13848
14183
|
const inventory = instructionInventory(context.model);
|
|
13849
14184
|
const clusters = duplicateClusters(context.findings ?? []);
|
|
13850
|
-
const
|
|
13851
|
-
const
|
|
13852
|
-
for (const selection of scopeSelections) if (planScope(context, selection, inventory, clusters, state)) linked.push(selection);
|
|
13853
|
-
const linkOperations = planLinks$1(context, linked, state.archived, ownership, state.manualSteps);
|
|
14185
|
+
const selection = selections.global;
|
|
14186
|
+
const linkOperations = planLinks$1(context, selection.action !== "blocked" && planScope(context, selection, inventory, clusters, state) ? [selection] : [], state.archived, ownership, state.manualSteps);
|
|
13854
14187
|
for (const [path, archive] of state.archived) {
|
|
13855
14188
|
const linkIndex = linkOperations.findIndex((operation) => resolve(primaryPath(operation)) === path);
|
|
13856
14189
|
const link = linkIndex === -1 ? void 0 : linkOperations.splice(linkIndex, 1)[0];
|
|
@@ -13892,14 +14225,10 @@ function planScope(context, requested, inventory, clusters, state) {
|
|
|
13892
14225
|
if (selection.action !== "consolidate") return true;
|
|
13893
14226
|
const content = composeConsolidatedInstructions(chosen, selection, clusters, context.model, existing);
|
|
13894
14227
|
if (content.trim().length === 0) {
|
|
13895
|
-
|
|
13896
|
-
|
|
13897
|
-
|
|
13898
|
-
|
|
13899
|
-
});
|
|
13900
|
-
return false;
|
|
13901
|
-
}
|
|
13902
|
-
state.manualSteps.push(`Configure ${selection.targetPath} on the next run: select at least one project source to consolidate, or choose the starter template. Aura wrote nothing there.`);
|
|
14228
|
+
state.blockers.push({
|
|
14229
|
+
path: selection.targetPath,
|
|
14230
|
+
reason: "No selected instruction content is available to consolidate. Select a source, or choose the starter template."
|
|
14231
|
+
});
|
|
13903
14232
|
return false;
|
|
13904
14233
|
}
|
|
13905
14234
|
planConsolidatedTarget(context, selection, existing, content, state);
|
|
@@ -14083,7 +14412,7 @@ function withRequiredOverrides(manifest, requiredIds, overriddenIds) {
|
|
|
14083
14412
|
* was available to receive it.
|
|
14084
14413
|
*/
|
|
14085
14414
|
function unconfiguredRequirement(context, id) {
|
|
14086
|
-
return context.interactive ? `Required MCP catalog entry ${id} could not be selected for a managed compatible application.` : `Required MCP catalog entry ${id} is not configured yet. Run setup interactively to choose its name
|
|
14415
|
+
return context.interactive ? `Required MCP catalog entry ${id} could not be selected for a managed compatible application.` : `Required MCP catalog entry ${id} is not configured yet. Run setup interactively to choose its name and applications; later non-interactive runs re-apply what the manifest records globally.`;
|
|
14087
14416
|
}
|
|
14088
14417
|
function withMcpOwnership(manifest, appId, names) {
|
|
14089
14418
|
const previous = manifest.ownership[appId];
|
|
@@ -14160,6 +14489,131 @@ function formatThresholds(thresholds) {
|
|
|
14160
14489
|
return pairs.length === 0 ? "no thresholds" : pairs.join(", ");
|
|
14161
14490
|
}
|
|
14162
14491
|
//#endregion
|
|
14492
|
+
//#region src/setup/repo-trust-preview.ts
|
|
14493
|
+
/** Printed instead of the row block when the preset carries nothing worth reviewing. */
|
|
14494
|
+
const NO_SETTINGS = "No check, MCP, skill, or snippet settings.";
|
|
14495
|
+
/**
|
|
14496
|
+
* Security-relevant capabilities shown before repository-controlled settings are accepted.
|
|
14497
|
+
*
|
|
14498
|
+
* One row per thing, never one row per field: a snippet the preset both provides and selects is
|
|
14499
|
+
* one snippet, and naming it twice made the screen long enough to skim past. Everything
|
|
14500
|
+
* executable-adjacent is spelled out verbatim (escaped) — the MCP command line or endpoint is
|
|
14501
|
+
* exactly what a later selection would configure, and trust time is the one moment the user is
|
|
14502
|
+
* looking. Snippet bodies are deliberately not echoed — pasting untrusted Markdown into a consent
|
|
14503
|
+
* prompt is its own injection surface — the picker's preview is where they are read, and the
|
|
14504
|
+
* prompt itself carries the explicit-selection boundary.
|
|
14505
|
+
*/
|
|
14506
|
+
function repoPresetTrustPreview(preset, contentSet) {
|
|
14507
|
+
const rows = [
|
|
14508
|
+
...mcpRows(preset, contentSet),
|
|
14509
|
+
...skillRows(preset, contentSet),
|
|
14510
|
+
...snippetRows(preset, contentSet),
|
|
14511
|
+
...(preset.skillDirectories ?? []).map(directoryRow),
|
|
14512
|
+
...listRow("sources", preset.allowedSkillSources),
|
|
14513
|
+
...checksRow(preset.checks)
|
|
14514
|
+
];
|
|
14515
|
+
return [
|
|
14516
|
+
"",
|
|
14517
|
+
...rows.length === 0 ? [` ${NO_SETTINGS}`] : alignRows(rows),
|
|
14518
|
+
""
|
|
14519
|
+
].join("\n");
|
|
14520
|
+
}
|
|
14521
|
+
/** Whether the prompt should promise that admitted content still needs a deliberate tick. */
|
|
14522
|
+
function hasRepoContent(contentSet) {
|
|
14523
|
+
return contentSet !== void 0 && contentSet.mcpServers.length + contentSet.skills.length + contentSet.snippets.length > 0;
|
|
14524
|
+
}
|
|
14525
|
+
function alignRows(rows) {
|
|
14526
|
+
const width = Math.max(...rows.map((row) => row.term.length));
|
|
14527
|
+
return rows.map((row) => ` ${row.term.padEnd(width)} ${row.detail}`);
|
|
14528
|
+
}
|
|
14529
|
+
/**
|
|
14530
|
+
* Every MCP server the repository would configure, provided ones with their full command line.
|
|
14531
|
+
*
|
|
14532
|
+
* A required id the repository does not define itself resolves from the catalog, so it gets a row
|
|
14533
|
+
* saying that rather than an executable surface this file cannot promise.
|
|
14534
|
+
*/
|
|
14535
|
+
function mcpRows(preset, contentSet) {
|
|
14536
|
+
const provided = contentSet?.mcpServers ?? [];
|
|
14537
|
+
const providedIds = new Set(provided.map((server) => server.id));
|
|
14538
|
+
const required = new Set(preset.requiredMcpServers ?? []);
|
|
14539
|
+
return [...provided.map((server) => ({
|
|
14540
|
+
detail: `${mcpServerPreview(server)}${required.has(server.id) ? " · required" : ""}`,
|
|
14541
|
+
term: "mcp"
|
|
14542
|
+
})), ...[...required].filter((id) => !providedIds.has(id)).map((id) => ({
|
|
14543
|
+
detail: `${safe(id)} · required, from the catalog`,
|
|
14544
|
+
term: "mcp"
|
|
14545
|
+
}))];
|
|
14546
|
+
}
|
|
14547
|
+
/** Skill trees the repository ships, then selections that point somewhere else. */
|
|
14548
|
+
function skillRows(preset, contentSet) {
|
|
14549
|
+
const provided = contentSet?.skills ?? [];
|
|
14550
|
+
const providedKeys = new Set(provided.map((skill) => `${skill.source.id}/${skill.id}`));
|
|
14551
|
+
return [...provided.map((skill) => ({
|
|
14552
|
+
detail: `${safe(skill.id)} (${count(skill.files.length, "file")})`,
|
|
14553
|
+
term: "skill"
|
|
14554
|
+
})), ...(preset.skills ?? []).map((selection) => `${selection.source}/${selection.id}`).filter((key) => !providedKeys.has(key)).map((key) => ({
|
|
14555
|
+
detail: `${safe(key)} · selected`,
|
|
14556
|
+
term: "skill"
|
|
14557
|
+
}))];
|
|
14558
|
+
}
|
|
14559
|
+
/** Snippets the repository ships, then selections from the catalog. */
|
|
14560
|
+
function snippetRows(preset, contentSet) {
|
|
14561
|
+
const provided = contentSet?.snippets ?? [];
|
|
14562
|
+
const providedIds = new Set(provided.map((snippet) => snippet.id));
|
|
14563
|
+
return [...provided.map((snippet) => ({
|
|
14564
|
+
detail: `${safe(snippet.id)} (${String(Buffer$1.byteLength(snippet.body, "utf8"))} B)`,
|
|
14565
|
+
term: "snippet"
|
|
14566
|
+
})), ...(preset.snippets ?? []).filter((id) => !providedIds.has(id)).map((id) => ({
|
|
14567
|
+
detail: `${safe(id)} · selected`,
|
|
14568
|
+
term: "snippet"
|
|
14569
|
+
}))];
|
|
14570
|
+
}
|
|
14571
|
+
/** The full executable surface of one provided MCP server, escaped but verbatim. */
|
|
14572
|
+
function mcpServerPreview(server) {
|
|
14573
|
+
return `${safe(server.id)} "${safe(server.serverName)}" → ${transportPreview(server.transportTemplate)}`;
|
|
14574
|
+
}
|
|
14575
|
+
function transportPreview(transport) {
|
|
14576
|
+
if (transport.type === "http") return `http ${safe(transport.url)}`;
|
|
14577
|
+
const args = (transport.args ?? []).map((argument) => `"${safe(argument)}"`).join(", ");
|
|
14578
|
+
const env = (transport.env ?? []).map(safe).join(", ");
|
|
14579
|
+
return [
|
|
14580
|
+
`stdio "${safe(transport.command)}"`,
|
|
14581
|
+
...args === "" ? [] : [`args [${args}]`],
|
|
14582
|
+
...env === "" ? [] : [`env ${env}`]
|
|
14583
|
+
].join(", ");
|
|
14584
|
+
}
|
|
14585
|
+
/** The exact check settings the repository preset asks the user to trust. */
|
|
14586
|
+
function checksRow(checks) {
|
|
14587
|
+
if (checks === void 0) return [];
|
|
14588
|
+
const settings = [
|
|
14589
|
+
...(checks.disabled ?? []).map((id) => `${safe(id)}: disabled`),
|
|
14590
|
+
...(checks.enabled ?? []).map((id) => `${safe(id)}: enabled`),
|
|
14591
|
+
...Object.entries(checks.severity ?? {}).map(([id, severity]) => `${safe(id)}: severity ${safe(severity)}`),
|
|
14592
|
+
...Object.entries(checks.thresholds ?? {}).map(([id, thresholds]) => `${safe(id)}: thresholds ${safe(JSON.stringify(thresholds))}`)
|
|
14593
|
+
].sort();
|
|
14594
|
+
return [{
|
|
14595
|
+
detail: settings.length === 0 ? "(none)" : settings.join("; "),
|
|
14596
|
+
term: "checks"
|
|
14597
|
+
}];
|
|
14598
|
+
}
|
|
14599
|
+
function directoryRow(source) {
|
|
14600
|
+
const token = source.kind === "private-directory" ? ` · token ${safe(source.tokenEnv)}` : "";
|
|
14601
|
+
return {
|
|
14602
|
+
detail: `${safe(source.name)} → ${safe(source.url)}${token}`,
|
|
14603
|
+
term: "directory"
|
|
14604
|
+
};
|
|
14605
|
+
}
|
|
14606
|
+
function listRow(term, values) {
|
|
14607
|
+
if (values === void 0) return [];
|
|
14608
|
+
return [{
|
|
14609
|
+
detail: values.length === 0 ? "(none)" : values.map(safe).join(", "),
|
|
14610
|
+
term
|
|
14611
|
+
}];
|
|
14612
|
+
}
|
|
14613
|
+
function count(total, noun) {
|
|
14614
|
+
return `${String(total)} ${noun}${total === 1 ? "" : "s"}`;
|
|
14615
|
+
}
|
|
14616
|
+
//#endregion
|
|
14163
14617
|
//#region src/setup/repo-trust.ts
|
|
14164
14618
|
/**
|
|
14165
14619
|
* Asks whether an untrusted repository preset may be applied, once per repo and contents.
|
|
@@ -14169,61 +14623,39 @@ function formatThresholds(thresholds) {
|
|
|
14169
14623
|
* answer. A non-interactive run resolves without the layer, and the plan summary says so.
|
|
14170
14624
|
*/
|
|
14171
14625
|
async function establishRepoPresetTrust(options) {
|
|
14172
|
-
const repo = await readRepoPreset(options.environment);
|
|
14626
|
+
const repo = options.repoPresetState ?? await readRepoPreset(options.environment);
|
|
14173
14627
|
if (repo.status !== "ready" || repo.hash === void 0) return { kind: "resolved" };
|
|
14174
14628
|
const manifest = options.manifest.status === "ready" ? options.manifest.value : void 0;
|
|
14175
14629
|
if (isRepoPresetTrusted(manifest, repo, repo.hash) || !options.interactive) return { kind: "resolved" };
|
|
14176
14630
|
const name = repo.preset?.name;
|
|
14177
|
-
options.io.note(name === void 0 ? `
|
|
14178
|
-
if (repo.preset !== void 0) options.io.note(repoPresetTrustPreview(repo.preset));
|
|
14631
|
+
options.io.note(name === void 0 ? `Repository preset — ${AURA_TEAM_PRESET_PATH}` : `Repository preset "${safe(name)}" — ${AURA_TEAM_PRESET_PATH}`);
|
|
14632
|
+
if (repo.preset !== void 0) options.io.note(repoPresetTrustPreview(repo.preset, repo.contentSet));
|
|
14179
14633
|
if (repo.mainWorktreePath !== void 0) options.io.note("This directory is a linked worktree, so trusting these contents also applies them in every other worktree of the same checkout.");
|
|
14180
14634
|
const changed = (manifest?.trustedRepoPresets ?? []).some((entry) => entry.path === repo.path);
|
|
14181
|
-
const confirmation = await options.io.confirm(changed
|
|
14635
|
+
const confirmation = await options.io.confirm(trustPrompt(changed, repo.contentSet));
|
|
14182
14636
|
if (confirmation === "aborted") return { kind: "aborted" };
|
|
14183
14637
|
return confirmation === "accepted" ? {
|
|
14184
14638
|
acceptedHash: repo.hash,
|
|
14185
14639
|
kind: "resolved"
|
|
14186
14640
|
} : { kind: "resolved" };
|
|
14187
14641
|
}
|
|
14188
|
-
/**
|
|
14189
|
-
|
|
14190
|
-
|
|
14191
|
-
|
|
14192
|
-
|
|
14193
|
-
|
|
14194
|
-
|
|
14195
|
-
|
|
14196
|
-
|
|
14197
|
-
|
|
14198
|
-
return ["Review these repository-controlled settings before trusting:", ...settings.length === 0 ? ["No check, MCP, skill, or snippet settings."] : settings].join("\n");
|
|
14199
|
-
}
|
|
14200
|
-
/** The exact check settings the repository preset asks the user to trust. */
|
|
14201
|
-
function checksPreview(checks) {
|
|
14202
|
-
if (checks === void 0) return;
|
|
14203
|
-
const settings = [
|
|
14204
|
-
...(checks.disabled ?? []).map((id) => `${safe(id)}: disabled`),
|
|
14205
|
-
...(checks.enabled ?? []).map((id) => `${safe(id)}: enabled`),
|
|
14206
|
-
...Object.entries(checks.severity ?? {}).map(([id, severity]) => `${safe(id)}: severity ${safe(severity)}`),
|
|
14207
|
-
...Object.entries(checks.thresholds ?? {}).map(([id, thresholds]) => `${safe(id)}: thresholds ${safe(JSON.stringify(thresholds))}`)
|
|
14208
|
-
].sort();
|
|
14209
|
-
return `Checks: ${settings.length === 0 ? "(none)" : settings.join("; ")}`;
|
|
14210
|
-
}
|
|
14211
|
-
function directoryPreview(source) {
|
|
14212
|
-
const token = source.kind === "private-directory" ? `; token ${safe(source.tokenEnv)}` : "";
|
|
14213
|
-
return `Skill directory: ${safe(source.name)} — ${safe(source.url)}${token}`;
|
|
14214
|
-
}
|
|
14215
|
-
function listPreview(label, values) {
|
|
14216
|
-
return values === void 0 ? void 0 : `${label}: ${listValues(values)}`;
|
|
14217
|
-
}
|
|
14218
|
-
function listValues(values) {
|
|
14219
|
-
return values.length === 0 ? "(none)" : values.map(safe).join(", ");
|
|
14642
|
+
/**
|
|
14643
|
+
* The question itself, short because the note above it already named the file and its contents.
|
|
14644
|
+
*
|
|
14645
|
+
* The two-tier contract rides here rather than in the summary: a preset that only carries policy
|
|
14646
|
+
* installs nothing either way, so promising a later tick would be noise in the one line the user
|
|
14647
|
+
* is certain to read.
|
|
14648
|
+
*/
|
|
14649
|
+
function trustPrompt(changed, contentSet) {
|
|
14650
|
+
if (changed) return "The preset changed since you trusted it. Trust the new contents?";
|
|
14651
|
+
return hasRepoContent(contentSet) ? "Trust it? Nothing installs until you pick it; applies to every run here until the file changes." : "Trust it? Applies to every run here until the file changes.";
|
|
14220
14652
|
}
|
|
14221
14653
|
/**
|
|
14222
14654
|
* Whether this run's prompt accepted exactly the contents the resolved layer applied.
|
|
14223
14655
|
*
|
|
14224
|
-
*
|
|
14225
|
-
*
|
|
14226
|
-
*
|
|
14656
|
+
* Boot passes one immutable snapshot through the prompt and configuration resolution. Comparing
|
|
14657
|
+
* hashes here keeps this helper correct for direct callers too: trust is recorded only for the
|
|
14658
|
+
* layer this run actually applied.
|
|
14227
14659
|
*/
|
|
14228
14660
|
function acceptedRepoPreset(configured, acceptedHash) {
|
|
14229
14661
|
const repo = configured.repoPreset;
|
|
@@ -14236,9 +14668,11 @@ function setupRepoPresetContext(configured, acceptedHash, recorded) {
|
|
|
14236
14668
|
return Object.freeze({
|
|
14237
14669
|
accepted: acceptedRepoPreset(configured, acceptedHash),
|
|
14238
14670
|
checkSummary: repo.status === "applied" ? presetCheckSummary(configured.config, "repo") : Object.freeze([]),
|
|
14671
|
+
...repo.contentSet === void 0 ? {} : { contentSet: repo.contentSet },
|
|
14239
14672
|
hash: repo.hash,
|
|
14240
14673
|
...repo.mainWorktreePath === void 0 ? {} : { mainWorktreePath: repo.mainWorktreePath },
|
|
14241
14674
|
path: repo.path,
|
|
14675
|
+
...repo.preset === void 0 ? {} : { preset: repo.preset },
|
|
14242
14676
|
recorded,
|
|
14243
14677
|
status: repo.status
|
|
14244
14678
|
});
|
|
@@ -14856,11 +15290,15 @@ async function bootSetup(request, environment) {
|
|
|
14856
15290
|
message: manifest.problem.message,
|
|
14857
15291
|
status: "invalid"
|
|
14858
15292
|
};
|
|
15293
|
+
const initialRepoPreset = await readRepoPreset(environment, void 0, { includeSkills: request.interactive });
|
|
15294
|
+
const manifestValue = manifest.status === "ready" ? manifest.value : void 0;
|
|
15295
|
+
const repoPresetState = !request.interactive && initialRepoPreset.status === "ready" && initialRepoPreset.hash !== void 0 && isRepoPresetTrusted(manifestValue, initialRepoPreset, initialRepoPreset.hash) ? await readRepoPreset(environment) : initialRepoPreset;
|
|
14859
15296
|
const trust = await establishRepoPresetTrust({
|
|
14860
15297
|
environment,
|
|
14861
15298
|
interactive: request.interactive,
|
|
14862
15299
|
io: request.io,
|
|
14863
|
-
manifest
|
|
15300
|
+
manifest,
|
|
15301
|
+
repoPresetState
|
|
14864
15302
|
});
|
|
14865
15303
|
if (trust.kind === "aborted") return { status: "aborted" };
|
|
14866
15304
|
const scanCancellation = new AbortController();
|
|
@@ -14882,7 +15320,8 @@ async function bootSetup(request, environment) {
|
|
|
14882
15320
|
manifest,
|
|
14883
15321
|
noCache: request.noCache,
|
|
14884
15322
|
online: true,
|
|
14885
|
-
registry: request.registry
|
|
15323
|
+
registry: request.registry,
|
|
15324
|
+
repoPresetState
|
|
14886
15325
|
});
|
|
14887
15326
|
if (configured.status === "invalid") {
|
|
14888
15327
|
scanCancellation.abort();
|
|
@@ -14890,7 +15329,7 @@ async function bootSetup(request, environment) {
|
|
|
14890
15329
|
}
|
|
14891
15330
|
const scan = await settleScan(request.io);
|
|
14892
15331
|
const trusted = await bootRepoPresetTrust(request, configured, trust.acceptedHash, scan);
|
|
14893
|
-
const projected = applyRequiredMcpServers(trusted.scan.model, configured.config);
|
|
15332
|
+
const projected = applyRequiredMcpServers(withRepoMcpCatalog(trusted.scan.model, configured.repoPreset), configured.config);
|
|
14894
15333
|
return {
|
|
14895
15334
|
activeChecks: enabledChecks(request.registry.checks, configured.config),
|
|
14896
15335
|
configured,
|
|
@@ -14907,8 +15346,8 @@ async function bootSetup(request, environment) {
|
|
|
14907
15346
|
};
|
|
14908
15347
|
}
|
|
14909
15348
|
/** Re-projects a rescan so the closing checklist reads the same virtual state the run planned. */
|
|
14910
|
-
function projectRescan(rescanned, config) {
|
|
14911
|
-
const projection = applyRequiredMcpServers(rescanned.model, config);
|
|
15349
|
+
function projectRescan(rescanned, config, repoPreset) {
|
|
15350
|
+
const projection = applyRequiredMcpServers(withRepoMcpCatalog(rescanned.model, repoPreset), config);
|
|
14912
15351
|
return {
|
|
14913
15352
|
...rescanned,
|
|
14914
15353
|
diagnostics: [...rescanned.diagnostics, ...projection.diagnostics],
|
|
@@ -15242,6 +15681,7 @@ function createMcpSetupCatalog(inputs) {
|
|
|
15242
15681
|
entries.push({
|
|
15243
15682
|
catalog,
|
|
15244
15683
|
key: `catalog:${catalog.id}`,
|
|
15684
|
+
...isRepoCatalogId(catalog.id) ? { repo: true } : {},
|
|
15245
15685
|
required: requiredIds.has(catalog.id),
|
|
15246
15686
|
sourceName: owner?.name
|
|
15247
15687
|
});
|
|
@@ -15253,6 +15693,7 @@ function createMcpSetupCatalog(inputs) {
|
|
|
15253
15693
|
catalog,
|
|
15254
15694
|
existing: match.server,
|
|
15255
15695
|
key: `manifest:${String(match.index)}`,
|
|
15696
|
+
...isRepoCatalogId(catalog.id) ? { repo: true } : {},
|
|
15256
15697
|
required: requiredIds.has(catalog.id),
|
|
15257
15698
|
sourceName: owner?.name
|
|
15258
15699
|
});
|
|
@@ -15265,6 +15706,7 @@ function createMcpSetupCatalog(inputs) {
|
|
|
15265
15706
|
...catalog === void 0 ? {} : { catalog },
|
|
15266
15707
|
existing: server,
|
|
15267
15708
|
key: `manifest:${String(index)}`,
|
|
15709
|
+
...server.catalogId !== void 0 && isRepoCatalogId(server.catalogId) ? { repo: true } : {},
|
|
15268
15710
|
required: server.catalogId !== void 0 && requiredIds.has(server.catalogId),
|
|
15269
15711
|
sourceName: server.catalogId === void 0 ? void 0 : inputs.registry.ownerOf("mcp-server", server.catalogId)?.name
|
|
15270
15712
|
});
|
|
@@ -15277,12 +15719,24 @@ function createMcpSetupCatalog(inputs) {
|
|
|
15277
15719
|
requiredIds
|
|
15278
15720
|
});
|
|
15279
15721
|
}
|
|
15722
|
+
/**
|
|
15723
|
+
* Required rows first (they carry blocker semantics whoever asked for them), then this
|
|
15724
|
+
* repository's own definitions, then configured rows, then everything else by name. Repo rows
|
|
15725
|
+
* lead the optional block — they are what a person running setup inside the repository came for —
|
|
15726
|
+
* without outranking a requirement they did not make.
|
|
15727
|
+
*/
|
|
15280
15728
|
function compareEntries(left, right) {
|
|
15281
15729
|
if (left.required !== right.required) return left.required ? -1 : 1;
|
|
15730
|
+
const leftRepo = left.repo === true;
|
|
15731
|
+
if (leftRepo !== (right.repo === true)) return leftRepo ? -1 : 1;
|
|
15282
15732
|
const leftConfigured = left.existing !== void 0;
|
|
15283
15733
|
if (leftConfigured !== (right.existing !== void 0)) return leftConfigured ? -1 : 1;
|
|
15284
15734
|
return entryName(left).localeCompare(entryName(right));
|
|
15285
15735
|
}
|
|
15736
|
+
/** The `repo/` namespace is reserved against plugins, so the prefix alone is provenance. */
|
|
15737
|
+
function isRepoCatalogId(id) {
|
|
15738
|
+
return id.startsWith("repo/");
|
|
15739
|
+
}
|
|
15286
15740
|
/** User-facing name used by the picker and deterministic sorting. */
|
|
15287
15741
|
function mcpCatalogEntryName(entry) {
|
|
15288
15742
|
return entryName(entry);
|
|
@@ -15365,7 +15819,7 @@ const MAX_CONCURRENT_SOURCE_LISTINGS = 4;
|
|
|
15365
15819
|
* of both. Only the draining is ordered, which is what keeps the picker's rows stable.
|
|
15366
15820
|
*/
|
|
15367
15821
|
async function loadListing(request) {
|
|
15368
|
-
const entries = [...bundledEntries(request.inputs)];
|
|
15822
|
+
const entries = [...repoEntries(request.inputs), ...bundledEntries(request.inputs)];
|
|
15369
15823
|
const notes = [...request.notes];
|
|
15370
15824
|
const unavailableSources = request.unapproved.map((source) => ({
|
|
15371
15825
|
hint: `connection not approved; token ${source.tokenEnv}`,
|
|
@@ -15485,6 +15939,30 @@ function memoizedDriverListing(request, source) {
|
|
|
15485
15939
|
request.driverListings.set(source.id, listing);
|
|
15486
15940
|
return listing;
|
|
15487
15941
|
}
|
|
15942
|
+
/** Repository skills the active allowlist admits; the source id is `repo:workspace`. */
|
|
15943
|
+
function allowedRepoSkills(inputs) {
|
|
15944
|
+
return (inputs.repoSkills ?? []).filter((skill) => isSkillSourceAllowed(inputs.preset, skill.source.id));
|
|
15945
|
+
}
|
|
15946
|
+
/**
|
|
15947
|
+
* This repository's own skills, resolved from the trusted snapshot rather than any fetch.
|
|
15948
|
+
*
|
|
15949
|
+
* `remote` stays false — no fetch is needed — but the review stage still gates their first
|
|
15950
|
+
* install by identity: trees that arrive by cloning get the same on-screen reading a directory
|
|
15951
|
+
* skill does.
|
|
15952
|
+
*/
|
|
15953
|
+
function repoEntries(inputs) {
|
|
15954
|
+
return allowedRepoSkills(inputs).map((skill) => ({
|
|
15955
|
+
description: skill.description,
|
|
15956
|
+
id: skill.id,
|
|
15957
|
+
identity: skillIdentity(skill.source.id, skill.id),
|
|
15958
|
+
name: skill.name,
|
|
15959
|
+
preview: skill.files.find((file) => file.path === "SKILL.md")?.content,
|
|
15960
|
+
remote: false,
|
|
15961
|
+
sourceId: skill.source.id,
|
|
15962
|
+
sourceName: skill.source.name,
|
|
15963
|
+
version: skill.version
|
|
15964
|
+
}));
|
|
15965
|
+
}
|
|
15488
15966
|
function bundledEntries(inputs) {
|
|
15489
15967
|
return (inputs.model.availableSkills ?? []).filter((skill) => isSkillSourceAllowed(inputs.preset, skill.source.id)).map((skill) => ({
|
|
15490
15968
|
description: skill.description,
|
|
@@ -15525,6 +16003,7 @@ function remoteEntries(listings, source) {
|
|
|
15525
16003
|
function createSkillCatalog(inputs) {
|
|
15526
16004
|
const collected = collectSkillDirectorySources(inputs.registryDirectories, inputs.preset);
|
|
15527
16005
|
const packs = /* @__PURE__ */ new Map();
|
|
16006
|
+
for (const skill of allowedRepoSkills(inputs)) packs.set(skillIdentity(skill.source.id, skill.id), skill);
|
|
15528
16007
|
const failures = /* @__PURE__ */ new Map();
|
|
15529
16008
|
const pending = /* @__PURE__ */ new Map();
|
|
15530
16009
|
const driverListings = /* @__PURE__ */ new Map();
|
|
@@ -15601,7 +16080,8 @@ function createSetupCatalogs(inputs) {
|
|
|
15601
16080
|
presetOrigin: inputs.presetOrigin,
|
|
15602
16081
|
registryDirectories: inputs.registry.skillDirectories,
|
|
15603
16082
|
registryDrivers: inputs.registry.skillSources,
|
|
15604
|
-
registryPresets: inputs.registry.presets
|
|
16083
|
+
registryPresets: inputs.registry.presets,
|
|
16084
|
+
repoSkills: inputs.repoSkills
|
|
15605
16085
|
})
|
|
15606
16086
|
};
|
|
15607
16087
|
}
|
|
@@ -15632,15 +16112,17 @@ function requiringPreset(preset, config) {
|
|
|
15632
16112
|
}
|
|
15633
16113
|
//#endregion
|
|
15634
16114
|
//#region src/setup/snippets.ts
|
|
16115
|
+
/** The picker group every repository-defined snippet renders under. */
|
|
16116
|
+
const REPO_SNIPPET_CATEGORY = "From this repository";
|
|
15635
16117
|
/** Bounds simultaneous snippet handles independently of how many contributions a distro ships. */
|
|
15636
16118
|
const MAX_CONCURRENT_SNIPPET_READS = 24;
|
|
15637
|
-
function createSnippetCatalog(snippets, manifest, presetIds = []) {
|
|
16119
|
+
function createSnippetCatalog(snippets, manifest, presetIds = [], repoSnippets = []) {
|
|
15638
16120
|
let resolved = [];
|
|
15639
16121
|
let pending;
|
|
15640
16122
|
return {
|
|
15641
16123
|
entries: () => resolved,
|
|
15642
16124
|
load: () => {
|
|
15643
|
-
pending ??= resolveSnippetCatalog(snippets, manifest, presetIds).then((entries) => {
|
|
16125
|
+
pending ??= resolveSnippetCatalog(snippets, manifest, presetIds, repoSnippets).then((entries) => {
|
|
15644
16126
|
resolved = entries;
|
|
15645
16127
|
return entries;
|
|
15646
16128
|
});
|
|
@@ -15649,13 +16131,23 @@ function createSnippetCatalog(snippets, manifest, presetIds = []) {
|
|
|
15649
16131
|
};
|
|
15650
16132
|
}
|
|
15651
16133
|
/** Resolves every registry source once and retains manifest-only selections as unavailable rows. */
|
|
15652
|
-
async function resolveSnippetCatalog(snippets, manifest, presetIds = []) {
|
|
15653
|
-
const
|
|
16134
|
+
async function resolveSnippetCatalog(snippets, manifest, presetIds = [], repoSnippets = []) {
|
|
16135
|
+
const repoEntries = repoSnippets.map((snippet) => Object.freeze({
|
|
16136
|
+
category: REPO_SNIPPET_CATEGORY,
|
|
16137
|
+
content: snippet.body,
|
|
16138
|
+
description: snippet.description ?? "Defined by this repository.",
|
|
16139
|
+
id: snippet.id,
|
|
16140
|
+
name: snippet.name,
|
|
16141
|
+
origin: "repo",
|
|
16142
|
+
status: "available"
|
|
16143
|
+
}));
|
|
16144
|
+
const registered = /* @__PURE__ */ new Set([...snippets.map((snippet) => snippet.id), ...repoEntries.map((entry) => entry.id)]);
|
|
15654
16145
|
const limit = createLimiter(MAX_CONCURRENT_SNIPPET_READS);
|
|
15655
16146
|
const resolved = await Promise.all(snippets.map((snippet) => limit(() => resolveSnippet(snippet))));
|
|
15656
16147
|
const previous = manifest.status === "ready" ? manifest.value.snippets : [];
|
|
15657
16148
|
const previousIds = new Set(previous.map((entry) => entry.id));
|
|
15658
16149
|
return Object.freeze([
|
|
16150
|
+
...repoEntries,
|
|
15659
16151
|
...resolved,
|
|
15660
16152
|
...previous.map((entry) => entry.id).filter((id) => !registered.has(id)).map((id) => Object.freeze({
|
|
15661
16153
|
category: "general",
|
|
@@ -16071,7 +16563,6 @@ function excerpt(sources, path, startLine, endLine) {
|
|
|
16071
16563
|
//#region src/setup/steps/instruction-stages.ts
|
|
16072
16564
|
const TEMPLATE_VALUE = "template";
|
|
16073
16565
|
const CONSOLIDATE_VALUE = "consolidate";
|
|
16074
|
-
const SKIP_VALUE = "skip";
|
|
16075
16566
|
/**
|
|
16076
16567
|
* The target's current text when this scope has nothing left to decide, otherwise undefined.
|
|
16077
16568
|
*
|
|
@@ -16102,13 +16593,13 @@ function scopeStages(input) {
|
|
|
16102
16593
|
return [
|
|
16103
16594
|
{
|
|
16104
16595
|
isApplicable: () => !input.blocked,
|
|
16105
|
-
label:
|
|
16596
|
+
label: "Personal",
|
|
16106
16597
|
apply: (state, answers) => update(state, { action: settled(answers[actionId], offered, fallback) }),
|
|
16107
16598
|
questions: (state) => input.blocked ? void 0 : [{
|
|
16108
16599
|
id: actionId,
|
|
16109
16600
|
initial: [draft(state).action ?? fallback],
|
|
16110
16601
|
kind: "select",
|
|
16111
|
-
label:
|
|
16602
|
+
label: "Personal",
|
|
16112
16603
|
options,
|
|
16113
16604
|
prompt: `How should Aura configure ${input.targetPath}?`
|
|
16114
16605
|
}]
|
|
@@ -16127,7 +16618,7 @@ function scopeStages(input) {
|
|
|
16127
16618
|
label: source.path,
|
|
16128
16619
|
value: source.path
|
|
16129
16620
|
})),
|
|
16130
|
-
prompt:
|
|
16621
|
+
prompt: "Which personal instruction files should Aura consolidate?"
|
|
16131
16622
|
}] : void 0
|
|
16132
16623
|
},
|
|
16133
16624
|
{
|
|
@@ -16164,10 +16655,8 @@ function settled(answer, offered, fallback) {
|
|
|
16164
16655
|
* advice, the mark and the cursor on row `1.` together, and leaves the rest of the menu in the
|
|
16165
16656
|
* order it was built. A scope with nothing to combine recommends nothing and keeps that order.
|
|
16166
16657
|
*
|
|
16167
|
-
*
|
|
16168
|
-
*
|
|
16169
|
-
* offers should not be one the closing checklist then fails the run over. The project tier has no
|
|
16170
|
-
* error-severity counterpart, so a declined project scope still ends green.
|
|
16658
|
+
* Personal instructions cannot be skipped because setup must leave applications connected to the
|
|
16659
|
+
* global shared source.
|
|
16171
16660
|
*/
|
|
16172
16661
|
function actionOptions(input) {
|
|
16173
16662
|
const options = [];
|
|
@@ -16187,11 +16676,6 @@ function actionOptions(input) {
|
|
|
16187
16676
|
label: "Use starter template",
|
|
16188
16677
|
value: TEMPLATE_VALUE
|
|
16189
16678
|
});
|
|
16190
|
-
if (input.scope === "project") options.push({
|
|
16191
|
-
description: hasTargetContent(input) ? `Aura writes nothing here and adds no project-level link; ${basename(input.targetPath)} and anything already linking to it stay as they are.` : "Aura writes nothing here and adds no project-level link.",
|
|
16192
|
-
label: "Skip project instructions",
|
|
16193
|
-
value: SKIP_VALUE
|
|
16194
|
-
});
|
|
16195
16679
|
return recommendedFirst(options);
|
|
16196
16680
|
}
|
|
16197
16681
|
function hasTargetContent(input) {
|
|
@@ -16200,7 +16684,7 @@ function hasTargetContent(input) {
|
|
|
16200
16684
|
//#endregion
|
|
16201
16685
|
//#region src/setup/steps/instructions.ts
|
|
16202
16686
|
/**
|
|
16203
|
-
* The instructions step: one back-navigable chain of forms
|
|
16687
|
+
* The instructions step: one back-navigable chain of forms for personal instructions.
|
|
16204
16688
|
*
|
|
16205
16689
|
* Each scope contributes action → sources → duplicate review stages; a stage whose precondition no
|
|
16206
16690
|
* longer holds (a non-consolidate action, no duplicated paragraphs left) simply disappears from
|
|
@@ -16210,9 +16694,8 @@ function hasTargetContent(input) {
|
|
|
16210
16694
|
const instructionsStep = {
|
|
16211
16695
|
gather: async (context, io) => {
|
|
16212
16696
|
const inputs = scopeInputs(context);
|
|
16213
|
-
|
|
16214
|
-
|
|
16215
|
-
const stages = scopes.flatMap(scopeStages);
|
|
16697
|
+
if (context.revisited !== true) emitScopeNotes(inputs.global, io);
|
|
16698
|
+
const stages = scopeStages(inputs.global);
|
|
16216
16699
|
const result = await runFormChain(stages, initialState(context), io, {
|
|
16217
16700
|
entry: context.enteredBackward === true ? "end" : "start",
|
|
16218
16701
|
flow: context.flow
|
|
@@ -16226,55 +16709,34 @@ const instructionsStep = {
|
|
|
16226
16709
|
telemetryCategory: "instructions",
|
|
16227
16710
|
title: "Instructions"
|
|
16228
16711
|
};
|
|
16229
|
-
/**
|
|
16230
|
-
function scopeList(inputs) {
|
|
16231
|
-
return inputs.project === void 0 ? [inputs.global] : [inputs.global, inputs.project];
|
|
16232
|
-
}
|
|
16233
|
-
/** Folds both scopes' drafts into the step's selections, unoffered when it asked nothing to do it. */
|
|
16712
|
+
/** Folds the personal draft into the step's selections, unoffered when it asked nothing to do it. */
|
|
16234
16713
|
function settle(context, inputs, result, unoffered) {
|
|
16235
|
-
const project = inputs.project === void 0 ? void 0 : scopeSelection(inputs.project, result.project);
|
|
16236
16714
|
const selections = {
|
|
16237
16715
|
...context.selections,
|
|
16238
|
-
instructions: {
|
|
16239
|
-
global: scopeSelection(inputs.global, result.global),
|
|
16240
|
-
...project === void 0 ? {} : { project }
|
|
16241
|
-
}
|
|
16716
|
+
instructions: { global: scopeSelection(inputs.global, result.global) }
|
|
16242
16717
|
};
|
|
16243
16718
|
return unoffered ? {
|
|
16244
16719
|
selections,
|
|
16245
16720
|
unoffered: true
|
|
16246
16721
|
} : selections;
|
|
16247
16722
|
}
|
|
16248
|
-
/** Everything
|
|
16723
|
+
/** Everything personal instruction setup needs from the workspace. */
|
|
16249
16724
|
function scopeInputs(context) {
|
|
16250
16725
|
const inventory = instructionInventory(context.model);
|
|
16251
16726
|
const targets = instructionTargets(context.model);
|
|
16252
16727
|
const clusters = duplicateClusters(context.findings ?? []);
|
|
16253
|
-
|
|
16728
|
+
return { global: {
|
|
16254
16729
|
blocked: context.model.sharedInstructions.problem !== void 0,
|
|
16255
16730
|
clusters,
|
|
16256
16731
|
scope: "global",
|
|
16257
16732
|
sources: inventory.filter((source) => source.scope === "global"),
|
|
16258
16733
|
targetContentValue: instructionTargetContent(context.model, "global", targets.global),
|
|
16259
16734
|
targetPath: targets.global
|
|
16260
|
-
};
|
|
16261
|
-
const projectSources = inventory.filter((source) => source.scope === "project");
|
|
16262
|
-
const projectContent = instructionTargetContent(context.model, "project", targets.project);
|
|
16263
|
-
return {
|
|
16264
|
-
global,
|
|
16265
|
-
project: projectSources.length === 0 && projectContent === void 0 ? void 0 : {
|
|
16266
|
-
blocked: false,
|
|
16267
|
-
clusters,
|
|
16268
|
-
scope: "project",
|
|
16269
|
-
sources: projectSources,
|
|
16270
|
-
targetContentValue: projectContent,
|
|
16271
|
-
targetPath: targets.project
|
|
16272
|
-
}
|
|
16273
|
-
};
|
|
16735
|
+
} };
|
|
16274
16736
|
}
|
|
16275
16737
|
function scopeSelection(input, draft) {
|
|
16276
16738
|
if (input.blocked) return inactiveSelection(input, "blocked");
|
|
16277
|
-
if (draft.action !== "consolidate") return inactiveSelection(input, inactiveAction(draft.action
|
|
16739
|
+
if (draft.action !== "consolidate") return inactiveSelection(input, inactiveAction(draft.action));
|
|
16278
16740
|
const selectedSources = draft.selectedSources ?? [];
|
|
16279
16741
|
const relevant = relevantDuplicateClusters(selectedSources, input.clusters);
|
|
16280
16742
|
return {
|
|
@@ -16292,9 +16754,9 @@ function scopeSelection(input, draft) {
|
|
|
16292
16754
|
* INS-002 firing at error severity, so the action menu never offers it there; this mapping is the
|
|
16293
16755
|
* second half of that rule, keeping an action off the menu from becoming one the planner obeys.
|
|
16294
16756
|
*/
|
|
16295
|
-
function inactiveAction(action
|
|
16757
|
+
function inactiveAction(action) {
|
|
16296
16758
|
if (action === "template") return "template";
|
|
16297
|
-
return
|
|
16759
|
+
return "keep";
|
|
16298
16760
|
}
|
|
16299
16761
|
function inactiveSelection(input, action) {
|
|
16300
16762
|
return {
|
|
@@ -16308,10 +16770,7 @@ function inactiveSelection(input, action) {
|
|
|
16308
16770
|
/** A re-entered step resumes from what this run already decided, not from cold-start defaults. */
|
|
16309
16771
|
function initialState(context) {
|
|
16310
16772
|
const existing = context.selections.instructions;
|
|
16311
|
-
return {
|
|
16312
|
-
global: toDraft(existing?.global),
|
|
16313
|
-
project: toDraft(existing?.project)
|
|
16314
|
-
};
|
|
16773
|
+
return { global: toDraft(existing?.global) };
|
|
16315
16774
|
}
|
|
16316
16775
|
function toDraft(selection) {
|
|
16317
16776
|
if (selection === void 0 || selection.action === "blocked") return {};
|
|
@@ -16333,10 +16792,10 @@ function emitScopeNotes(input, io) {
|
|
|
16333
16792
|
path: input.targetPath,
|
|
16334
16793
|
scope: input.scope
|
|
16335
16794
|
});
|
|
16336
|
-
io.note(
|
|
16795
|
+
io.note(`Personal instructions already live in ${input.targetPath} (${size}); Aura found nothing else to consolidate and leaves the file as it is.`);
|
|
16337
16796
|
return;
|
|
16338
16797
|
}
|
|
16339
|
-
if (input.sources.length > 0) io.note(`Found ${describeSources(input.sources)} for
|
|
16798
|
+
if (input.sources.length > 0) io.note(`Found ${describeSources(input.sources)} for personal consolidation.`);
|
|
16340
16799
|
}
|
|
16341
16800
|
function describeSources(sources) {
|
|
16342
16801
|
return sources.map((source) => `${basename(source.path)} (${describeInstructionSource(source)})`).join(" and ");
|
|
@@ -16525,47 +16984,27 @@ async function configureEntry(context, entry, taken, io) {
|
|
|
16525
16984
|
return SETUP_BACK;
|
|
16526
16985
|
}
|
|
16527
16986
|
const defaultName = seed?.name ?? catalog?.serverName ?? "custom";
|
|
16528
|
-
const defaultScope = seed?.scope ?? "global";
|
|
16529
16987
|
const apps = appChoices(context, catalog?.supportedApps, seed?.apps ?? []);
|
|
16530
16988
|
if (apps.eligible.length === 0 && (seed?.apps.length ?? 0) === 0) {
|
|
16531
16989
|
io.note("No detected, managed, compatible application can receive this MCP server.");
|
|
16532
16990
|
return;
|
|
16533
16991
|
}
|
|
16534
16992
|
let name = defaultName;
|
|
16535
|
-
let scope = defaultScope;
|
|
16536
16993
|
let selectedApps = seed === void 0 || seed.apps.length === 0 ? apps.eligible : seed.apps;
|
|
16537
16994
|
for (;;) {
|
|
16538
|
-
const result = await io.ask([
|
|
16539
|
-
|
|
16540
|
-
|
|
16541
|
-
|
|
16542
|
-
|
|
16543
|
-
|
|
16544
|
-
|
|
16545
|
-
|
|
16546
|
-
label: "Global",
|
|
16547
|
-
value: "global"
|
|
16548
|
-
}, {
|
|
16549
|
-
label: "Project",
|
|
16550
|
-
value: "project"
|
|
16551
|
-
}],
|
|
16552
|
-
prompt: "Where should applications configure this server?"
|
|
16553
|
-
},
|
|
16554
|
-
{
|
|
16555
|
-
id: "mcp-apps",
|
|
16556
|
-
initial: selectedApps,
|
|
16557
|
-
kind: "multiselect",
|
|
16558
|
-
label: "Applications",
|
|
16559
|
-
options: apps.options,
|
|
16560
|
-
prompt: "Which applications should enable this server?"
|
|
16561
|
-
}
|
|
16562
|
-
]);
|
|
16995
|
+
const result = await io.ask([textQuestion("mcp-name", "Name", "Configuration name", name), {
|
|
16996
|
+
id: "mcp-apps",
|
|
16997
|
+
initial: selectedApps,
|
|
16998
|
+
kind: "multiselect",
|
|
16999
|
+
label: "Applications",
|
|
17000
|
+
options: apps.options,
|
|
17001
|
+
prompt: "Which applications should enable this server?"
|
|
17002
|
+
}]);
|
|
16563
17003
|
if (result === "aborted") return SETUP_ABORTED;
|
|
16564
17004
|
if (result === "back") return SETUP_BACK;
|
|
16565
17005
|
name = answerText(result["mcp-name"]);
|
|
16566
|
-
scope = selectedValues(result["mcp-scope"])[0] === "project" ? "project" : "global";
|
|
16567
17006
|
selectedApps = selectedValues(result["mcp-apps"]);
|
|
16568
|
-
const problem = entryProblem(name,
|
|
17007
|
+
const problem = entryProblem(name, selectedApps, taken);
|
|
16569
17008
|
if (problem !== void 0) {
|
|
16570
17009
|
io.note(problem);
|
|
16571
17010
|
if (!context.interactive) return SETUP_ABORTED;
|
|
@@ -16575,7 +17014,6 @@ async function configureEntry(context, entry, taken, io) {
|
|
|
16575
17014
|
apps: Object.freeze([...new Set(selectedApps)]),
|
|
16576
17015
|
...catalog === void 0 ? {} : { catalogId: catalog.id },
|
|
16577
17016
|
name,
|
|
16578
|
-
scope,
|
|
16579
17017
|
transport
|
|
16580
17018
|
});
|
|
16581
17019
|
}
|
|
@@ -16588,12 +17026,12 @@ async function configureEntry(context, entry, taken, io) {
|
|
|
16588
17026
|
* the form that is one line to re-type; caught at serialization it would end the run after every
|
|
16589
17027
|
* question had been answered.
|
|
16590
17028
|
*/
|
|
16591
|
-
function entryProblem(name,
|
|
17029
|
+
function entryProblem(name, apps, taken) {
|
|
16592
17030
|
const nameProblem = mcpServerNameProblem(name);
|
|
16593
17031
|
if (nameProblem !== void 0) return `MCP server $.name ${nameProblem}.`;
|
|
16594
17032
|
if (apps.length === 0) return "MCP server $.apps must select at least one application.";
|
|
16595
17033
|
const selected = new Set(apps);
|
|
16596
|
-
return taken.some((server) => server.name === name && server.
|
|
17034
|
+
return taken.some((server) => server.name === name && server.apps.some((app) => selected.has(app))) ? `MCP server $.name ${name} is already configured globally for an application selected here. Choose another name.` : void 0;
|
|
16597
17035
|
}
|
|
16598
17036
|
function appChoices(context, supportedApps, existingApps) {
|
|
16599
17037
|
const managed = new Set(managedAppIdList(context));
|
|
@@ -16654,7 +17092,7 @@ function workingEntries(context) {
|
|
|
16654
17092
|
}
|
|
16655
17093
|
function matchesEntry(server, entry) {
|
|
16656
17094
|
const existing = entry.existing;
|
|
16657
|
-
if (existing !== void 0) return existing.catalogId === server.catalogId && existing.name === server.name
|
|
17095
|
+
if (existing !== void 0) return existing.catalogId === server.catalogId && existing.name === server.name;
|
|
16658
17096
|
return entry.catalog?.id === server.catalogId;
|
|
16659
17097
|
}
|
|
16660
17098
|
function nextCustomKey(entries) {
|
|
@@ -16665,7 +17103,7 @@ function nextCustomKey(entries) {
|
|
|
16665
17103
|
/**
|
|
16666
17104
|
* A default name no other row has taken.
|
|
16667
17105
|
*
|
|
16668
|
-
* Two servers cannot share one name in one application
|
|
17106
|
+
* Two servers cannot share one name in one application — the manifest refuses to record
|
|
16669
17107
|
* it — so seeding every custom server with `custom` would turn "add two, accept the defaults" into
|
|
16670
17108
|
* a plan the manifest declines to hold.
|
|
16671
17109
|
*/
|
|
@@ -16727,7 +17165,6 @@ async function gatherMcp(context, io) {
|
|
|
16727
17165
|
const server = {
|
|
16728
17166
|
apps: [],
|
|
16729
17167
|
name: nextCustomName(entries),
|
|
16730
|
-
scope: "global",
|
|
16731
17168
|
transport
|
|
16732
17169
|
};
|
|
16733
17170
|
entries.push({
|
|
@@ -16802,15 +17239,24 @@ function requiredCatalogIds(entries) {
|
|
|
16802
17239
|
return [...new Set(entries.flatMap((entry) => entry.required && entry.catalog !== void 0 ? [entry.catalog.id] : []))];
|
|
16803
17240
|
}
|
|
16804
17241
|
function serverOption(entry) {
|
|
16805
|
-
const
|
|
16806
|
-
const tags = [...entry.required ? ["from preset", "required"] : [], ...configured ? [entry.catalog === void 0 ? "custom" : "configured"] : []];
|
|
17242
|
+
const tags = serverTags(entry);
|
|
16807
17243
|
const suffix = tags.length === 0 ? "" : ` (${tags.join(", ")})`;
|
|
16808
17244
|
return {
|
|
16809
|
-
description: entry
|
|
17245
|
+
description: serverProvenance(entry),
|
|
16810
17246
|
label: `${safe(mcpCatalogEntryName(entry))}${suffix}`,
|
|
16811
17247
|
value: entry.key
|
|
16812
17248
|
};
|
|
16813
17249
|
}
|
|
17250
|
+
/** What the run knows about a row: who selected it, whether it is required or configured. */
|
|
17251
|
+
function serverTags(entry) {
|
|
17252
|
+
const configured = entry.selectedServer !== void 0 || entry.existing !== void 0;
|
|
17253
|
+
const origin = entry.repo === true ? "from repo" : "from preset";
|
|
17254
|
+
return [...entry.required ? [origin, "required"] : entry.repo === true ? [origin] : [], ...configured ? [entry.catalog === void 0 ? "custom" : "configured"] : []];
|
|
17255
|
+
}
|
|
17256
|
+
function serverProvenance(entry) {
|
|
17257
|
+
if (entry.repo === true) return "Repository: .aura/preset.json";
|
|
17258
|
+
return entry.sourceName === void 0 ? "Custom server" : `Plugin: ${entry.sourceName}`;
|
|
17259
|
+
}
|
|
16814
17260
|
function hasManagedMcpApp(context) {
|
|
16815
17261
|
const managed = new Set(managedAppIdList(context));
|
|
16816
17262
|
return context.appCatalog.some((entry) => entry.kind === "detected" && entry.supportsMcp && managed.has(catalogEntryId(entry)));
|
|
@@ -16927,11 +17373,7 @@ function pickerOptions(inputs) {
|
|
|
16927
17373
|
const policy = inputs.catalog.policy;
|
|
16928
17374
|
const unsupported = !hasSkillsHome(inputs.managedApps);
|
|
16929
17375
|
const preset = new Set(inputs.presetSkills.map((skill) => skillIdentity(skill.source, skill.id)));
|
|
16930
|
-
const
|
|
16931
|
-
entry.sourceName,
|
|
16932
|
-
entry.name,
|
|
16933
|
-
entry.id
|
|
16934
|
-
]).map((entry) => ({
|
|
17376
|
+
const entryRow = (entry) => ({
|
|
16935
17377
|
description: `${entry.description} · v${entry.version}`,
|
|
16936
17378
|
get disabled() {
|
|
16937
17379
|
return unsupported || inputs.listing.verification?.isMissing(entry.identity) === true;
|
|
@@ -16941,10 +17383,18 @@ function pickerOptions(inputs) {
|
|
|
16941
17383
|
return inputs.listing.verification?.isMissing(entry.identity) === true ? "source no longer publishes this skill" : void 0;
|
|
16942
17384
|
},
|
|
16943
17385
|
group: entry.sourceName,
|
|
16944
|
-
label: `${entry.name}${
|
|
17386
|
+
label: `${entry.name}${selectedBySuffix(entry.identity)}`,
|
|
16945
17387
|
...previewField(inputs, entry),
|
|
16946
17388
|
value: entry.identity
|
|
16947
|
-
})
|
|
17389
|
+
});
|
|
17390
|
+
const selectedBySuffix = (identity) => inputs.repoSelectedIdentities.has(identity) ? " (from repo)" : preset.has(identity) ? " (from preset)" : "";
|
|
17391
|
+
const sortedEntries = sortForDisplay(inputs.listing.entries, (entry) => [
|
|
17392
|
+
entry.sourceName,
|
|
17393
|
+
entry.name,
|
|
17394
|
+
entry.id
|
|
17395
|
+
]);
|
|
17396
|
+
const repoEntryRows = sortedEntries.filter((entry) => entry.sourceId.startsWith("repo:")).map(entryRow);
|
|
17397
|
+
const entryRows = sortedEntries.filter((entry) => !entry.sourceId.startsWith("repo:")).map(entryRow);
|
|
16948
17398
|
const manifestRows = inputs.manifestSkills.flatMap((skill) => {
|
|
16949
17399
|
const identity = skillIdentity(skill.source, skill.id);
|
|
16950
17400
|
if (covered.has(identity)) return [];
|
|
@@ -16999,18 +17449,23 @@ function pickerOptions(inputs) {
|
|
|
16999
17449
|
value: `truncated:${source.id}`
|
|
17000
17450
|
}));
|
|
17001
17451
|
const represented = /* @__PURE__ */ new Set([...inputs.listing.entries.map((entry) => entry.identity), ...inputs.manifestSkills.map((skill) => skillIdentity(skill.source, skill.id))]);
|
|
17002
|
-
const presetRows = inputs.presetSkills.filter((skill) => !represented.has(skillIdentity(skill.source, skill.id))).map((skill) =>
|
|
17003
|
-
|
|
17004
|
-
|
|
17005
|
-
|
|
17006
|
-
|
|
17007
|
-
|
|
17008
|
-
|
|
17009
|
-
|
|
17452
|
+
const presetRows = inputs.presetSkills.filter((skill) => !represented.has(skillIdentity(skill.source, skill.id))).map((skill) => {
|
|
17453
|
+
const identity = skillIdentity(skill.source, skill.id);
|
|
17454
|
+
const fromRepo = inputs.repoSelectedIdentities.has(identity);
|
|
17455
|
+
return {
|
|
17456
|
+
description: fromRepo ? "Selected by the repository preset, but unavailable in this run." : "Selected by the active team preset, but unavailable in this run.",
|
|
17457
|
+
disabled: true,
|
|
17458
|
+
disabledNote: fromRepo ? "repo selection unavailable" : "preset selection unavailable",
|
|
17459
|
+
group: skill.source,
|
|
17460
|
+
label: `${skill.id} ${fromRepo ? "(from repo)" : "(from preset)"}`,
|
|
17461
|
+
value: identity
|
|
17462
|
+
};
|
|
17463
|
+
});
|
|
17010
17464
|
return [
|
|
17011
17465
|
...sourceRows,
|
|
17012
17466
|
...truncatedRows,
|
|
17013
17467
|
...packRows,
|
|
17468
|
+
...repoEntryRows,
|
|
17014
17469
|
...entryRows,
|
|
17015
17470
|
...manifestRows,
|
|
17016
17471
|
...presetRows
|
|
@@ -17093,6 +17548,16 @@ function skillStages(inputs) {
|
|
|
17093
17548
|
function isRemoteIdentity(identity) {
|
|
17094
17549
|
return identity.startsWith("directory:") || identity.startsWith("driver:");
|
|
17095
17550
|
}
|
|
17551
|
+
/**
|
|
17552
|
+
* A repository identity's install needs no fetch, but the review boundary all the same.
|
|
17553
|
+
*
|
|
17554
|
+
* The trees arrive by cloning, not by anything the user selected — "local files" is the bundled
|
|
17555
|
+
* exemption's mechanism, but distribution vouching was its justification, and a checkout has
|
|
17556
|
+
* neither.
|
|
17557
|
+
*/
|
|
17558
|
+
function isRepoIdentity(identity) {
|
|
17559
|
+
return identity.startsWith("repo:");
|
|
17560
|
+
}
|
|
17096
17561
|
/** The typed selection behind each offered identity: catalog entries plus manifest rows. */
|
|
17097
17562
|
function selectionsByIdentity(inputs) {
|
|
17098
17563
|
const selections = /* @__PURE__ */ new Map();
|
|
@@ -17152,7 +17617,8 @@ function reviewStage(inputs) {
|
|
|
17152
17617
|
const offered = selectionsByIdentity(inputs);
|
|
17153
17618
|
const entries = new Map(inputs.listing.entries.map((entry) => [entry.identity, entry]));
|
|
17154
17619
|
const bundled = bundledByIdentity(inputs.availableSkills);
|
|
17155
|
-
const
|
|
17620
|
+
const repo = bundledByIdentity(inputs.repoSkills);
|
|
17621
|
+
const reviewable = (identity) => needsReview(identity, recorded, bundled, repo);
|
|
17156
17622
|
return {
|
|
17157
17623
|
apply: (state, answers) => ({
|
|
17158
17624
|
...state,
|
|
@@ -17169,7 +17635,11 @@ function reviewStage(inputs) {
|
|
|
17169
17635
|
}), inputs.approvedPrivateSourceIds);
|
|
17170
17636
|
const resolutionWithBundled = {
|
|
17171
17637
|
problems: resolution.problems,
|
|
17172
|
-
resolved: new Map([
|
|
17638
|
+
resolved: new Map([
|
|
17639
|
+
...resolution.resolved,
|
|
17640
|
+
...bundled,
|
|
17641
|
+
...repo
|
|
17642
|
+
])
|
|
17173
17643
|
};
|
|
17174
17644
|
const questions = state.selected.filter(reviewable).flatMap((identity) => reviewQuestion(identity, state.decisions, resolutionWithBundled, recorded, entries.get(identity), offered.get(identity)));
|
|
17175
17645
|
return questions.length === 0 ? void 0 : questions;
|
|
@@ -17179,8 +17649,13 @@ function reviewStage(inputs) {
|
|
|
17179
17649
|
function bundledByIdentity(packs) {
|
|
17180
17650
|
return new Map(packs.map((pack) => [skillIdentity(pack.source.id, pack.id), pack]));
|
|
17181
17651
|
}
|
|
17182
|
-
function needsReview(identity, recorded, bundled) {
|
|
17652
|
+
function needsReview(identity, recorded, bundled, repo) {
|
|
17183
17653
|
if (isRemoteIdentity(identity)) return true;
|
|
17654
|
+
if (isRepoIdentity(identity)) {
|
|
17655
|
+
const previous = recorded.get(identity);
|
|
17656
|
+
const pack = repo.get(identity);
|
|
17657
|
+
return previous === void 0 || pack !== void 0 && movedFrom(previous, pack);
|
|
17658
|
+
}
|
|
17184
17659
|
const previous = recorded.get(identity);
|
|
17185
17660
|
const available = bundled.get(identity);
|
|
17186
17661
|
return previous !== void 0 && available !== void 0 && movedFrom(previous, available);
|
|
@@ -17206,11 +17681,19 @@ async function finalizeSkills(state, inputs, manifestSkills) {
|
|
|
17206
17681
|
const selection = offered.get(identity);
|
|
17207
17682
|
return selection === void 0 ? [] : [selection];
|
|
17208
17683
|
}), inputs.approvedPrivateSourceIds);
|
|
17684
|
+
const repoPacks = new Map(inputs.repoSkills.map((pack) => [skillIdentity(pack.source.id, pack.id), pack]));
|
|
17209
17685
|
const selected = [];
|
|
17210
17686
|
const resolved = [];
|
|
17211
17687
|
for (const identity of state.selected) {
|
|
17212
17688
|
const selection = offered.get(identity);
|
|
17213
17689
|
if (selection === void 0) continue;
|
|
17690
|
+
if (isRepoIdentity(identity)) {
|
|
17691
|
+
finalizeRemote(identity, selection, state, repoPacks.get(identity), recorded.get(identity), {
|
|
17692
|
+
resolved,
|
|
17693
|
+
selected
|
|
17694
|
+
});
|
|
17695
|
+
continue;
|
|
17696
|
+
}
|
|
17214
17697
|
if (!isRemoteIdentity(identity)) {
|
|
17215
17698
|
selected.push(selection);
|
|
17216
17699
|
continue;
|
|
@@ -17283,15 +17766,7 @@ async function gatherApprovedSkills(context, io, approval) {
|
|
|
17283
17766
|
const manifestSkills = recordedSkills(context);
|
|
17284
17767
|
emitNotes(context, io, listing, manifestSkills);
|
|
17285
17768
|
if (isEmptyCatalog(listing, manifestSkills)) return emptyCatalogOutcome$1(context);
|
|
17286
|
-
const inputs =
|
|
17287
|
-
approvedPrivateSourceIds,
|
|
17288
|
-
availableSkills: context.model.availableSkills ?? [],
|
|
17289
|
-
catalog: context.skillCatalog,
|
|
17290
|
-
listing,
|
|
17291
|
-
managedApps: managedSkillApps(context),
|
|
17292
|
-
manifestSkills,
|
|
17293
|
-
presetSkills: context.preset?.skills ?? []
|
|
17294
|
-
};
|
|
17769
|
+
const inputs = stageInputs(context, approvedPrivateSourceIds, listing, manifestSkills);
|
|
17295
17770
|
const opening = openingSelection(context, inputs, manifestSkills);
|
|
17296
17771
|
noteHeldBack(inputs, io, opening.heldBack);
|
|
17297
17772
|
const result = await runFormChain(skillStages(inputs), opening.state, io, {
|
|
@@ -17316,6 +17791,21 @@ function noteHeldBack(inputs, io, heldBack) {
|
|
|
17316
17791
|
function recordedSkills(context) {
|
|
17317
17792
|
return context.manifest.status === "ready" ? context.manifest.value.skills : [];
|
|
17318
17793
|
}
|
|
17794
|
+
/** Everything the picker/review chain reads, with the repository selections folded in. */
|
|
17795
|
+
function stageInputs(context, approvedPrivateSourceIds, listing, manifestSkills) {
|
|
17796
|
+
const repoSelections = context.repoPreset?.preset?.skills ?? [];
|
|
17797
|
+
return {
|
|
17798
|
+
approvedPrivateSourceIds,
|
|
17799
|
+
availableSkills: context.model.availableSkills ?? [],
|
|
17800
|
+
catalog: context.skillCatalog,
|
|
17801
|
+
listing,
|
|
17802
|
+
managedApps: managedSkillApps(context),
|
|
17803
|
+
manifestSkills,
|
|
17804
|
+
presetSkills: [...context.preset?.skills ?? [], ...repoSelections],
|
|
17805
|
+
repoSelectedIdentities: new Set(repoSelections.map((skill) => skillIdentity(skill.source, skill.id))),
|
|
17806
|
+
repoSkills: context.repoPreset?.contentSet?.skills ?? []
|
|
17807
|
+
};
|
|
17808
|
+
}
|
|
17319
17809
|
function emptyCatalogOutcome$1(context) {
|
|
17320
17810
|
return context.enteredBackward === true ? SETUP_BACK : {
|
|
17321
17811
|
selections: { ...context.selections },
|
|
@@ -17439,7 +17929,7 @@ const snippetsStep = {
|
|
|
17439
17929
|
const installed = installedSnippetIds(context);
|
|
17440
17930
|
if (context.revisited !== true) emitCatalogNotes(context, catalog, installed, io.note);
|
|
17441
17931
|
if (catalog.length === 0) return emptyCatalogOutcome(context, io);
|
|
17442
|
-
const options = snippetOptions(catalog, installed, new Set(context.preset?.snippets ?? []));
|
|
17932
|
+
const options = snippetOptions(catalog, installed, new Set(context.preset?.snippets ?? []), new Set(context.repoPreset?.preset?.snippets ?? []));
|
|
17443
17933
|
const selectable = new Set(options.filter((option) => option.disabled !== true).map((option) => option.value));
|
|
17444
17934
|
const question = {
|
|
17445
17935
|
id: "snippets",
|
|
@@ -17479,14 +17969,23 @@ const RECORD_NOTE = "Aura keeps the record; the text stays where it is.";
|
|
|
17479
17969
|
* checkbox has anything to do — the row is there to report the record, not to be answered with.
|
|
17480
17970
|
* Gathering those rows under one heading is what lets the numbers below start at 1 and count only
|
|
17481
17971
|
* what a tick would change, and it empties out any category whose every snippet is installed.
|
|
17972
|
+
*
|
|
17973
|
+
* Repository rows lead the offered rows: they are this repository's own guidance, which is what a
|
|
17974
|
+
* person running setup inside it most likely came for. Plugin categories keep their sorted order
|
|
17975
|
+
* underneath.
|
|
17482
17976
|
*/
|
|
17483
|
-
function snippetOptions(catalog, installed, preset) {
|
|
17977
|
+
function snippetOptions(catalog, installed, preset, repoSelected) {
|
|
17484
17978
|
const sorted = sortForDisplay(catalog, (entry) => [
|
|
17485
17979
|
entry.category,
|
|
17486
17980
|
entry.name,
|
|
17487
17981
|
entry.id
|
|
17488
17982
|
]);
|
|
17489
|
-
|
|
17983
|
+
const offered = sorted.filter((entry) => !installed.has(entry.id));
|
|
17984
|
+
return [
|
|
17985
|
+
...recordBlock(sorted.filter((entry) => installed.has(entry.id))),
|
|
17986
|
+
...offered.filter((entry) => entry.origin === "repo").map((entry) => offeredOption(entry, preset, repoSelected)),
|
|
17987
|
+
...offered.filter((entry) => entry.origin !== "repo").map((entry) => offeredOption(entry, preset, repoSelected))
|
|
17988
|
+
];
|
|
17490
17989
|
}
|
|
17491
17990
|
/**
|
|
17492
17991
|
* The installed rows, with the shared note appended to the last of them.
|
|
@@ -17506,10 +18005,11 @@ function recordBlock(entries) {
|
|
|
17506
18005
|
}));
|
|
17507
18006
|
}
|
|
17508
18007
|
/** A row a tick would act on, or one disabled because no installed plugin publishes its text. */
|
|
17509
|
-
function offeredOption(entry, preset) {
|
|
18008
|
+
function offeredOption(entry, preset, repoSelected) {
|
|
18009
|
+
const selectedBy = repoSelected.has(entry.id) ? " (from repo)" : preset.has(entry.id) ? " (from preset)" : "";
|
|
17510
18010
|
const base = {
|
|
17511
18011
|
group: entry.category,
|
|
17512
|
-
label: `${entry.name}${
|
|
18012
|
+
label: `${entry.name}${selectedBy}`,
|
|
17513
18013
|
value: entry.id,
|
|
17514
18014
|
...entry.status === "available" ? { preview: entry.content } : {}
|
|
17515
18015
|
};
|
|
@@ -17535,19 +18035,20 @@ function snippetsPrompt(options) {
|
|
|
17535
18035
|
return options.every((option) => option.locked === true) ? "Every snippet the installed plugins provide is already in your instructions." : "Nothing here can be added: every snippet is either installed already or unavailable.";
|
|
17536
18036
|
}
|
|
17537
18037
|
/**
|
|
17538
|
-
* What the picker opens with: every installed id, plus this run's answer or
|
|
18038
|
+
* What the picker opens with: every installed id, plus this run's answer or team-preset defaults.
|
|
17539
18039
|
*
|
|
17540
18040
|
* Installed rows are locked, so they are seeded on every visit — there is no earlier answer that
|
|
17541
18041
|
* could have dropped one. Their `✔` comes from the lock rather than from this seeding, so what it
|
|
17542
|
-
* settles is the answer the form reports back, not the checkbox the picker draws.
|
|
17543
|
-
* only what
|
|
17544
|
-
*
|
|
18042
|
+
* settles is the answer the form reports back, not the checkbox the picker draws. Team-preset
|
|
18043
|
+
* defaults add only what their installed plugins can contribute. Repository rows deliberately
|
|
18044
|
+
* open unticked even when the repository preset selects them: their bodies are arbitrary agent
|
|
18045
|
+
* instructions, so a first install requires a person to select the previewable row explicitly.
|
|
17545
18046
|
*/
|
|
17546
18047
|
function initialSelection(context, options, installed) {
|
|
17547
|
-
const
|
|
18048
|
+
const selected = new Set(context.preset?.snippets ?? []);
|
|
17548
18049
|
const chosen = context.selections.snippets;
|
|
17549
18050
|
const kept = [...installed];
|
|
17550
|
-
const added = chosen?.selected ?? options.filter((option) => option.disabled !== true && !installed.has(option.value) &&
|
|
18051
|
+
const added = chosen?.selected ?? options.filter((option) => option.disabled !== true && !installed.has(option.value) && selected.has(option.value)).map((option) => option.value);
|
|
17551
18052
|
return [...kept, ...added];
|
|
17552
18053
|
}
|
|
17553
18054
|
function emptyCatalogOutcome(context, io) {
|
|
@@ -17642,36 +18143,9 @@ async function runSetup(request) {
|
|
|
17642
18143
|
stdout.write("\nLeft everything as it was.\n");
|
|
17643
18144
|
return finish(1, "aborted");
|
|
17644
18145
|
}
|
|
17645
|
-
const { activeChecks, configured, effectiveModel, effectiveScan
|
|
17646
|
-
const model = scan.model;
|
|
18146
|
+
const { activeChecks, configured, effectiveModel, effectiveScan } = booted;
|
|
17647
18147
|
const steps = request.steps ?? SETUP_STEPS;
|
|
17648
|
-
const
|
|
17649
|
-
const catalogs = createSetupCatalogs({
|
|
17650
|
-
config: configured.config,
|
|
17651
|
-
environment,
|
|
17652
|
-
interactive: request.interactive,
|
|
17653
|
-
model: effectiveModel,
|
|
17654
|
-
noCache: request.noCache,
|
|
17655
|
-
preset: configured.preset,
|
|
17656
|
-
presetNotes: [...configured.notes, ...projected.diagnostics.map((diagnostic) => diagnostic.message)],
|
|
17657
|
-
presetOrigin: configured.presetOrigin,
|
|
17658
|
-
registry: request.registry
|
|
17659
|
-
});
|
|
17660
|
-
const preset = setupPresetContext(request, configured.config, configured.preset);
|
|
17661
|
-
const repoPreset = booted.repoPreset;
|
|
17662
|
-
const stepContext = {
|
|
17663
|
-
appCatalog: buildAppCatalog(request.registry.adapters, model, scan.skipped),
|
|
17664
|
-
...initialFindings === void 0 ? {} : { findings: initialFindings },
|
|
17665
|
-
interactive: request.interactive,
|
|
17666
|
-
isEnvironmentVariableSet: (name) => environment.readVariable(name) !== void 0,
|
|
17667
|
-
manifest: model.manifest,
|
|
17668
|
-
mcpCatalog: catalogs.mcpCatalog,
|
|
17669
|
-
model: effectiveModel,
|
|
17670
|
-
skillCatalog: catalogs.skillCatalog,
|
|
17671
|
-
snippetCatalog: createSnippetCatalog(request.registry.snippets, model.manifest, preset?.snippets ?? []),
|
|
17672
|
-
...preset === void 0 ? {} : { preset },
|
|
17673
|
-
...repoPreset === void 0 ? {} : { repoPreset }
|
|
17674
|
-
};
|
|
18148
|
+
const stepContext = buildStepContext(request, booted, environment, steps.some((step) => step.needsFindings === true) ? gatherFindings(activeChecks, effectiveModel, io, configured.config) : void 0);
|
|
17675
18149
|
let start = {
|
|
17676
18150
|
index: 0,
|
|
17677
18151
|
offered: /* @__PURE__ */ new Set(),
|
|
@@ -17701,7 +18175,7 @@ async function runSetup(request) {
|
|
|
17701
18175
|
environment,
|
|
17702
18176
|
mcpCatalog: request.registry.mcpServers,
|
|
17703
18177
|
skills: request.registry.skills
|
|
17704
|
-
}), configured.config), activeChecks, configured.config), "applied", {
|
|
18178
|
+
}), configured.config, configured.repoPreset), activeChecks, configured.config), "applied", {
|
|
17705
18179
|
actions: setupActions(ready.gathered),
|
|
17706
18180
|
appliedOperationCount: result.appliedOperationCount
|
|
17707
18181
|
});
|
|
@@ -17710,6 +18184,46 @@ async function runSetup(request) {
|
|
|
17710
18184
|
function plannedActions(gathered) {
|
|
17711
18185
|
return gathered === void 0 ? void 0 : setupActions(gathered);
|
|
17712
18186
|
}
|
|
18187
|
+
/** Assembles the catalogs and the immutable context every wizard step reads. */
|
|
18188
|
+
function buildStepContext(request, booted, environment, initialFindings) {
|
|
18189
|
+
const { configured, effectiveModel, repoPreset, scan } = booted;
|
|
18190
|
+
const model = scan.model;
|
|
18191
|
+
const catalogs = buildCatalogs(request, booted, environment);
|
|
18192
|
+
const preset = setupPresetContext(request, configured.config, configured.preset);
|
|
18193
|
+
return {
|
|
18194
|
+
appCatalog: buildAppCatalog(request.registry.adapters, model, scan.skipped),
|
|
18195
|
+
...initialFindings === void 0 ? {} : { findings: initialFindings },
|
|
18196
|
+
interactive: request.interactive,
|
|
18197
|
+
isEnvironmentVariableSet: (name) => environment.readVariable(name) !== void 0,
|
|
18198
|
+
manifest: model.manifest,
|
|
18199
|
+
mcpCatalog: catalogs.mcpCatalog,
|
|
18200
|
+
model: effectiveModel,
|
|
18201
|
+
skillCatalog: catalogs.skillCatalog,
|
|
18202
|
+
snippetCatalog: buildSnippetCatalog(request, booted, preset),
|
|
18203
|
+
...preset === void 0 ? {} : { preset },
|
|
18204
|
+
...repoPreset === void 0 ? {} : { repoPreset }
|
|
18205
|
+
};
|
|
18206
|
+
}
|
|
18207
|
+
function buildCatalogs(request, booted, environment) {
|
|
18208
|
+
const { configured, effectiveModel, projected, repoPreset } = booted;
|
|
18209
|
+
return createSetupCatalogs({
|
|
18210
|
+
config: configured.config,
|
|
18211
|
+
environment,
|
|
18212
|
+
interactive: request.interactive,
|
|
18213
|
+
model: effectiveModel,
|
|
18214
|
+
noCache: request.noCache,
|
|
18215
|
+
preset: configured.preset,
|
|
18216
|
+
presetNotes: [...configured.notes, ...projected.diagnostics.map((diagnostic) => diagnostic.message)],
|
|
18217
|
+
presetOrigin: configured.presetOrigin,
|
|
18218
|
+
registry: request.registry,
|
|
18219
|
+
repoSkills: repoPreset?.contentSet?.skills ?? []
|
|
18220
|
+
});
|
|
18221
|
+
}
|
|
18222
|
+
/** The snippet catalog, with the trusted repository's bodies and default selections folded in. */
|
|
18223
|
+
function buildSnippetCatalog(request, booted, preset) {
|
|
18224
|
+
const repoPreset = booted.repoPreset;
|
|
18225
|
+
return createSnippetCatalog(request.registry.snippets, booted.scan.model.manifest, [...preset?.snippets ?? [], ...repoPreset?.preset?.snippets ?? []], repoPreset?.contentSet?.snippets ?? []);
|
|
18226
|
+
}
|
|
17713
18227
|
function setupPresetContext(request, config, preset) {
|
|
17714
18228
|
const selected = config.preset;
|
|
17715
18229
|
if (selected === void 0 || preset === void 0) return;
|