diff --git a/apps/site/src/i18n/content.ts b/apps/site/src/i18n/content.ts index a9908fc..2466100 100644 --- a/apps/site/src/i18n/content.ts +++ b/apps/site/src/i18n/content.ts @@ -380,6 +380,7 @@ export const ko: Content = { targets: [ { name: "Claude Code", path: "~/.claude/skills/designpaca/" }, { name: "Codex CLI", path: "~/.codex/skills/designpaca/" }, + { name: "Antigravity", path: ".agents/skills/designpaca/" }, { name: "Cursor", path: ".cursor/rules/designpaca.mdc" }, { name: "Windsurf", path: ".windsurf/rules/designpaca.md" }, { name: "AGENTS.md", path: "블록 주입. 에이전트 무관" }, @@ -646,6 +647,7 @@ export const en: Content = { targets: [ { name: "Claude Code", path: "~/.claude/skills/designpaca/" }, { name: "Codex CLI", path: "~/.codex/skills/designpaca/" }, + { name: "Antigravity", path: ".agents/skills/designpaca/" }, { name: "Cursor", path: ".cursor/rules/designpaca.mdc" }, { name: "Windsurf", path: ".windsurf/rules/designpaca.md" }, { name: "AGENTS.md", path: "block injection, agent agnostic" }, diff --git a/packages/cli/package.json b/packages/cli/package.json index 6dfc998..25ae466 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "designpaca", - "version": "0.2.0", + "version": "0.3.0", "description": "웹 디자인 파이프라인 스킬 — Claude Code · Codex · Cursor 에 한 줄로 설치한다", "keywords": [ "design", diff --git a/packages/core/package.json b/packages/core/package.json index 02721d5..42263a0 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@designpaca/core", - "version": "0.2.0", + "version": "0.3.0", "private": true, "description": "designpaca 설치 엔진 — 타깃 어댑터, 매니페스트, 드리프트 감지", "type": "module", diff --git a/packages/core/src/targets/antigravity.ts b/packages/core/src/targets/antigravity.ts new file mode 100644 index 0000000..f7388dd --- /dev/null +++ b/packages/core/src/targets/antigravity.ts @@ -0,0 +1,55 @@ +import path from "node:path"; +import { exists } from "../fsx.ts"; +import type { TargetAdapter, TargetContext } from "../types.ts"; +import { anyExists, scopeRoot, skillDirActions } from "./common.ts"; + +/** + * Antigravity 와 `.agents/skills` 를 읽는 도구들. + * + * `.agents/skills/` 는 특정 제품의 경로가 아니라 **중립 경로**다. + * Antigravity 가 여기서 스킬을 읽고, 같은 규약을 쓰는 다른 하네스도 늘고 있다. + * SKILL.md 구조(프론트매터 + references/)가 Claude Code 와 같아서 + * 파일을 그대로 복사하면 된다 — 변환이 필요 없다. + * + * 경로(공식 문서 기준): + * 워크스페이스 /.agents/skills/<이름>/ + * 전역 ~/.gemini/config/skills/<이름>/ + * + * 구버전은 `.agent/skills`(단수)를 읽었고 지금도 하위호환으로 지원된다. + * 새로 설치하는 쪽은 복수형이 맞으므로 복수형에만 쓴다 — 두 곳에 같은 파일을 + * 깔면 스킬이 두 번 잡힌다. + * + * 프론트매터 요구사항도 확인했다: `description` 필수, `name` 선택(없으면 폴더명). + * 우리 SKILL.md 는 둘 다 갖고 있다. + */ +export const antigravity: TargetAdapter = { + id: "antigravity", + label: "Antigravity", + hint: ".agents/skills/designpaca — 중립 경로, 같은 규약을 쓰는 도구가 함께 읽는다", + scopes: ["user", "project"], + + async detect(ctx: TargetContext) { + return anyExists([ + path.join(ctx.home, ".gemini", "config", "skills"), + path.join(ctx.home, ".gemini"), + path.join(ctx.cwd, ".agents"), + // 폴더 이름이 바뀌기 전에 만든 프로젝트 + path.join(ctx.cwd, ".agent"), + ]); + }, + + async plan(ctx: TargetContext) { + const root = scopeRoot( + ctx, + [".gemini", "config", "skills", "designpaca"], + [".agents", "skills", "designpaca"], + ); + return { + target: this.id, + scope: ctx.scope, + root, + actions: skillDirActions(root, ctx.skill), + alreadyInstalled: await exists(path.join(root, "SKILL.md")), + }; + }, +}; diff --git a/packages/core/src/targets/index.ts b/packages/core/src/targets/index.ts index a70adfd..b0531f0 100644 --- a/packages/core/src/targets/index.ts +++ b/packages/core/src/targets/index.ts @@ -1,11 +1,12 @@ import type { TargetAdapter, TargetId } from "../types.ts"; import { claudeCode } from "./claude-code.ts"; import { codex } from "./codex.ts"; +import { antigravity } from "./antigravity.ts"; import { cursor } from "./cursor.ts"; import { windsurf } from "./windsurf.ts"; import { agentsMd } from "./agents-md.ts"; -export const ADAPTERS: TargetAdapter[] = [claudeCode, codex, cursor, windsurf, agentsMd]; +export const ADAPTERS: TargetAdapter[] = [claudeCode, codex, antigravity, cursor, windsurf, agentsMd]; export function getAdapter(id: TargetId): TargetAdapter { const a = ADAPTERS.find((x) => x.id === id); @@ -14,4 +15,4 @@ export function getAdapter(id: TargetId): TargetAdapter { } export { MARKER, rewriteRefPaths } from "./common.ts"; -export { claudeCode, codex, cursor, windsurf, agentsMd }; +export { claudeCode, codex, antigravity, cursor, windsurf, agentsMd }; diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index f11c1c1..ee87fd3 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -1,5 +1,11 @@ /** 설치 대상 식별자 */ -export type TargetId = "claude-code" | "codex" | "cursor" | "windsurf" | "agents-md"; +export type TargetId = + | "claude-code" + | "codex" + | "antigravity" + | "cursor" + | "windsurf" + | "agents-md"; /** 설치 범위 — user: 홈 디렉터리 전역, project: 현재 프로젝트 */ export type Scope = "user" | "project"; diff --git a/packages/core/test/installer.test.ts b/packages/core/test/installer.test.ts index e070f6e..1c7d74c 100644 --- a/packages/core/test/installer.test.ts +++ b/packages/core/test/installer.test.ts @@ -161,3 +161,29 @@ test("windsurf: 12,000자 상한을 넘으면 설치를 막는다", async () => assert.ok(plan.blocked, "상한 초과인데 막지 않았다"); assert.match(plan.blocked, /상한/); }); + +test("antigravity: 프로젝트는 .agents/skills, 전역은 ~/.gemini/config/skills", async () => { + // 두 경로가 다른 제품 규약이라 하나만 맞으면 다른 쪽에서 스킬이 안 잡힌다. + const proj = await planInstall("antigravity", "project", skill, env); + assert.equal(proj.root, path.join(cwd, ".agents", "skills", "designpaca")); + + const user = await planInstall("antigravity", "user", skill, env); + assert.equal(user.root, path.join(home, ".gemini", "config", "skills", "designpaca")); +}); + +test("antigravity: SKILL.md 를 변환 없이 그대로 싣는다", async () => { + // Claude Code 와 같은 규약이라 프론트매터도 참조 경로도 손대지 않아야 한다. + // 여기서 경로를 재작성하면 references 가 존재하지 않는 곳을 가리킨다. + const plan = await planInstall("antigravity", "project", skill, env); + const res = await applyPlan(plan, skill.version, { env }); + + const md = await fs.readFile(path.join(plan.root, "SKILL.md"), "utf8"); + assert.match(md, /^---\n/, "프론트매터가 있어야 한다"); + assert.match(md, /description:/, "Antigravity 는 description 을 필수로 요구한다"); + assert.match(md, /`references\/tokens\.md`/, "참조 경로를 재작성하면 안 된다"); + + const ref = await fs.readFile(path.join(plan.root, "references", "tokens.md"), "utf8"); + assert.match(ref, /# 토큰/); + + await removeInstall(res.record, { env }); +}); diff --git a/packages/skill/package.json b/packages/skill/package.json index aa145ab..cd4caa0 100644 --- a/packages/skill/package.json +++ b/packages/skill/package.json @@ -1,6 +1,6 @@ { "name": "@designpaca/skill", - "version": "0.2.0", + "version": "0.3.0", "private": true, "description": "designpaca 스킬 원본 — SKILL.md 와 참조 문서", "scripts": {