feat(core): Antigravity 등 .agents/skills 규약을 읽는 하네스를 지원한다
Antigravity 에서 스킬로 인식되지 않는다는 제보를 받고 확인했다. 설치 경로가 없었던 것이 원인이다. 공식 문서 기준 경로: 워크스페이스 <root>/.agents/skills/<이름>/ 전역 ~/.gemini/config/skills/<이름>/ .agents/skills 는 특정 제품 경로가 아니라 중립 경로라, 같은 규약을 쓰는 다른 하네스도 함께 읽는다. SKILL.md 구조(프론트매터 + references/)가 Claude Code 와 같아서 변환 없이 그대로 복사하면 된다. 프론트매터 요구사항도 맞는 것을 확인했다 — description 필수, name 선택. 우리 SKILL.md 는 둘 다 갖고 있다. - targets/antigravity.ts 추가, 설치 대상 5 → 6종 - 하위호환 경로(.agent/skills, 단수)는 감지에만 쓰고 설치는 복수형에만 한다. 두 곳에 깔면 스킬이 두 번 잡힌다 - 테스트 2개 추가: 범위별 경로, 변환 없이 싣는지 (테스트 25 → 27개) - 소개 페이지 설치 대상 목록에 반영
This commit is contained in:
parent
4e1e5f0073
commit
e4ac43f0b3
8 changed files with 96 additions and 6 deletions
|
|
@ -380,6 +380,7 @@ export const ko: Content = {
|
||||||
targets: [
|
targets: [
|
||||||
{ name: "Claude Code", path: "~/.claude/skills/designpaca/" },
|
{ name: "Claude Code", path: "~/.claude/skills/designpaca/" },
|
||||||
{ name: "Codex CLI", path: "~/.codex/skills/designpaca/" },
|
{ name: "Codex CLI", path: "~/.codex/skills/designpaca/" },
|
||||||
|
{ name: "Antigravity", path: ".agents/skills/designpaca/" },
|
||||||
{ name: "Cursor", path: ".cursor/rules/designpaca.mdc" },
|
{ name: "Cursor", path: ".cursor/rules/designpaca.mdc" },
|
||||||
{ name: "Windsurf", path: ".windsurf/rules/designpaca.md" },
|
{ name: "Windsurf", path: ".windsurf/rules/designpaca.md" },
|
||||||
{ name: "AGENTS.md", path: "블록 주입. 에이전트 무관" },
|
{ name: "AGENTS.md", path: "블록 주입. 에이전트 무관" },
|
||||||
|
|
@ -646,6 +647,7 @@ export const en: Content = {
|
||||||
targets: [
|
targets: [
|
||||||
{ name: "Claude Code", path: "~/.claude/skills/designpaca/" },
|
{ name: "Claude Code", path: "~/.claude/skills/designpaca/" },
|
||||||
{ name: "Codex CLI", path: "~/.codex/skills/designpaca/" },
|
{ name: "Codex CLI", path: "~/.codex/skills/designpaca/" },
|
||||||
|
{ name: "Antigravity", path: ".agents/skills/designpaca/" },
|
||||||
{ name: "Cursor", path: ".cursor/rules/designpaca.mdc" },
|
{ name: "Cursor", path: ".cursor/rules/designpaca.mdc" },
|
||||||
{ name: "Windsurf", path: ".windsurf/rules/designpaca.md" },
|
{ name: "Windsurf", path: ".windsurf/rules/designpaca.md" },
|
||||||
{ name: "AGENTS.md", path: "block injection, agent agnostic" },
|
{ name: "AGENTS.md", path: "block injection, agent agnostic" },
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "designpaca",
|
"name": "designpaca",
|
||||||
"version": "0.2.0",
|
"version": "0.3.0",
|
||||||
"description": "웹 디자인 파이프라인 스킬 — Claude Code · Codex · Cursor 에 한 줄로 설치한다",
|
"description": "웹 디자인 파이프라인 스킬 — Claude Code · Codex · Cursor 에 한 줄로 설치한다",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"design",
|
"design",
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@designpaca/core",
|
"name": "@designpaca/core",
|
||||||
"version": "0.2.0",
|
"version": "0.3.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"description": "designpaca 설치 엔진 — 타깃 어댑터, 매니페스트, 드리프트 감지",
|
"description": "designpaca 설치 엔진 — 타깃 어댑터, 매니페스트, 드리프트 감지",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
|
|
||||||
55
packages/core/src/targets/antigravity.ts
Normal file
55
packages/core/src/targets/antigravity.ts
Normal file
|
|
@ -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 와 같아서
|
||||||
|
* 파일을 그대로 복사하면 된다 — 변환이 필요 없다.
|
||||||
|
*
|
||||||
|
* 경로(공식 문서 기준):
|
||||||
|
* 워크스페이스 <root>/.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")),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
@ -1,11 +1,12 @@
|
||||||
import type { TargetAdapter, TargetId } from "../types.ts";
|
import type { TargetAdapter, TargetId } from "../types.ts";
|
||||||
import { claudeCode } from "./claude-code.ts";
|
import { claudeCode } from "./claude-code.ts";
|
||||||
import { codex } from "./codex.ts";
|
import { codex } from "./codex.ts";
|
||||||
|
import { antigravity } from "./antigravity.ts";
|
||||||
import { cursor } from "./cursor.ts";
|
import { cursor } from "./cursor.ts";
|
||||||
import { windsurf } from "./windsurf.ts";
|
import { windsurf } from "./windsurf.ts";
|
||||||
import { agentsMd } from "./agents-md.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 {
|
export function getAdapter(id: TargetId): TargetAdapter {
|
||||||
const a = ADAPTERS.find((x) => x.id === id);
|
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 { MARKER, rewriteRefPaths } from "./common.ts";
|
||||||
export { claudeCode, codex, cursor, windsurf, agentsMd };
|
export { claudeCode, codex, antigravity, cursor, windsurf, agentsMd };
|
||||||
|
|
|
||||||
|
|
@ -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: 현재 프로젝트 */
|
/** 설치 범위 — user: 홈 디렉터리 전역, project: 현재 프로젝트 */
|
||||||
export type Scope = "user" | "project";
|
export type Scope = "user" | "project";
|
||||||
|
|
|
||||||
|
|
@ -161,3 +161,29 @@ test("windsurf: 12,000자 상한을 넘으면 설치를 막는다", async () =>
|
||||||
assert.ok(plan.blocked, "상한 초과인데 막지 않았다");
|
assert.ok(plan.blocked, "상한 초과인데 막지 않았다");
|
||||||
assert.match(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 });
|
||||||
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@designpaca/skill",
|
"name": "@designpaca/skill",
|
||||||
"version": "0.2.0",
|
"version": "0.3.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"description": "designpaca 스킬 원본 — SKILL.md 와 참조 문서",
|
"description": "designpaca 스킬 원본 — SKILL.md 와 참조 문서",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue