Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion ai_docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,8 @@ Every remaining host combines its `targets/<host>/` overlay with the same
canonical skills. Generated host trees are output, never authoring source.
The Cursor generated host also includes `README.md`, `rules/`, and `commands/`.
Those extras are Cursor-only. The generated Devin archive stays lean
(`.devin-plugin`, `.mcp.json`, `skills/`) even though a `git-subdir` clone of
(`.devin-plugin`, `.mcp.json`, `assets/icon.svg` because the manifest `logo`
points at it, and `skills/`) even though a `git-subdir` clone of
`plugins/ask-gina/` contains the rest of the package. Grok Bot still loads
skills and MCP.

Expand Down
11 changes: 10 additions & 1 deletion plugins/ask-gina/__tests__/sync-plugin-skills.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,13 @@ describe("sync-plugin-skills", () => {
assert.deepStrictEqual((yield* fs.readDirectory(generatedTargets.targets.devin)).sort(), [
".devin-plugin",
".mcp.json",
"assets",
"skills",
]);
assert.deepStrictEqual(
yield* fs.readDirectory(paths.join(generatedTargets.targets.devin, "assets")),
["icon.svg"],
);

for (const relative of [
[".codex-plugin", "plugin.json"],
Expand Down Expand Up @@ -74,7 +79,11 @@ describe("sync-plugin-skills", () => {
);
}

for (const relative of [[".devin-plugin", "plugin.json"], [".mcp.json"]] as const) {
for (const relative of [
[".devin-plugin", "plugin.json"],
[".mcp.json"],
["assets", "icon.svg"],
] as const) {
assert.strictEqual(
yield* fs.readFileString(paths.join(generatedTargets.targets.devin, ...relative)),
yield* fs.readFileString(paths.join(packageRoot, ...relative)),
Expand Down
10 changes: 9 additions & 1 deletion tools/__tests__/pack-artifacts.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -572,9 +572,17 @@ describe("plugin target packing", () => {
assert.deepStrictEqual((yield* fs.readDirectory(fixture.stage)).sort(), [
".devin-plugin",
".mcp.json",
"assets",
"skills",
]);
for (const relative of [[".devin-plugin", "plugin.json"], [".mcp.json"]] as const) {
assert.deepStrictEqual(yield* fs.readDirectory(path.join(fixture.stage, "assets")), [
"icon.svg",
]);
for (const relative of [
[".devin-plugin", "plugin.json"],
[".mcp.json"],
["assets", "icon.svg"],
] as const) {
assert.strictEqual(
yield* fs.readFileString(path.join(fixture.stage, ...relative)),
yield* fs.readFileString(path.join(fixture.plugin, ...relative)),
Expand Down
10 changes: 9 additions & 1 deletion tools/check-target-conformance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -284,7 +284,6 @@ const foreignArtifacts: Readonly<Record<TargetName, readonly string[]>> = {
"plugin.json",
"mcp.json",
"gemini-extension.json",
"assets",
"rules",
"commands",
],
Expand Down Expand Up @@ -527,6 +526,15 @@ export const checkGeneratedTargetConformance: {
);
}

if (target === "devin") {
const iconPath = paths.join(generatedTargetRoot, "assets", "icon.svg");
addCheck(
"devin.assets.icon_exists",
"Devin manifest logo asset exists",
yield* withFileSystemError(iconPath, "cannot be inspected", fs.exists(iconPath)),
);
}

if (target === "cursor") {
const iconPath = paths.join(generatedTargetRoot, "assets", "icon.svg");
const readmePath = paths.join(generatedTargetRoot, "README.md");
Expand Down
7 changes: 7 additions & 0 deletions tools/pack-artifacts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -917,6 +917,13 @@ export const stagePluginTarget: {
path.join(plugin, ".mcp.json"),
path.join(stage, ".mcp.json"),
).pipe(Effect.mapError((cause) => fail("cannot stage devin .mcp.json", cause)));
yield* fs
.makeDirectory(path.join(stage, "assets"), { recursive: true })
.pipe(Effect.mapError((cause) => fail("cannot create devin assets", cause)));
yield* copyCheckedRegularFile(
path.join(plugin, "assets", "icon.svg"),
path.join(stage, "assets", "icon.svg"),
).pipe(Effect.mapError((cause) => fail("cannot stage devin icon", cause)));
} else {
const sourceOverlay = path.join(plugin, "targets", host);
yield* filesBelow(sourceOverlay);
Expand Down
2 changes: 1 addition & 1 deletion tools/sync-plugin-skills.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ const CURSOR_SOURCE_ENTRIES = [
"rules",
"commands",
] as const;
const DEVIN_SOURCE_ENTRIES = [".devin-plugin", ".mcp.json"] as const;
const DEVIN_SOURCE_ENTRIES = [".devin-plugin", ".mcp.json", "assets/icon.svg"] as const;
const here = fileURLToPath(new URL(".", import.meta.url));

type SyncEnvironment = FileSystem.FileSystem | Path.Path;
Expand Down
Loading