heymark 1.0.2 → 1.1.1

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/README.md CHANGED
@@ -14,12 +14,12 @@ AI 코딩 도구의 컨벤션을 중앙에서 관리하고, 각 도구 형식으
14
14
 
15
15
  프로젝트마다 AI 도구별 규칙 파일을 따로 작성하면 관리가 파편화된다.
16
16
  이 시스템은 단일 진실 공급원(Single Source of Truth) 원칙에 따라, 한 곳에서 작성한 규칙을 여러 AI 도구 형식으로 자동 변환한다.
17
- 규칙을 한 번만 작성하면 Cursor, Claude Code, GitHub Copilot, OpenAI Codex 등에서 즉시 사용할 수 있다.
17
+ 규칙을 한 번만 작성하면 Cursor, Claude Code, GitHub Copilot, OpenAI Codex, Antigravity 등에서 즉시 사용할 수 있다.
18
18
 
19
19
  ## Features
20
20
 
21
21
  - **단일 소스 관리**: 마크다운 파일 하나로 모든 AI 도구의 규칙 통합 관리
22
- - **자동 형식 변환**: 4종 AI 도구의 네이티브 형식으로 자동 변환 (YAML frontmatter, AGENTS.md 등)
22
+ - **자동 형식 변환**: 5종 AI 도구의 네이티브 형식으로 자동 변환 (YAML frontmatter, AGENTS.md 등)
23
23
  - **선택적 변환**: 특정 도구만 선택하여 변환 가능
24
24
  - **NPM 패키지 배포**: NPM registry를 통한 public 배포로 간편한 설치 및 버전 관리
25
25
  - **플러그인 구조**: 변환 모듈 추가만으로 새 도구 지원 확장
@@ -73,16 +73,7 @@ npm publish
73
73
 
74
74
  ## Integration
75
75
 
76
- ### Installation
77
-
78
- ```bash
79
- # 패키지 설치
80
- npm install --save-dev heymark
81
- ```
82
-
83
- ### 초기 설정 (규칙 소스 = 원격 GitHub 저장소)
84
-
85
- 규칙은 **원격 GitHub 저장소(Public/Private)** 에서 읽습니다.
76
+ Rule/Skill은 **원격 GitHub 저장소(Public/Private)** 에서 읽습니다.
86
77
  최초 1회만 설정하면 됩니다.
87
78
 
88
79
  ```bash
@@ -90,8 +81,6 @@ npm install --save-dev heymark
90
81
  npx heymark init <GitHub-저장소-URL>
91
82
  ```
92
83
 
93
- 예시:
94
-
95
84
  ```bash
96
85
  # HTTPS (Private이면 Git credential/토큰 설정 필요)
97
86
  npx heymark init https://github.com/org/my-rules.git
@@ -103,17 +92,9 @@ npx heymark init git@github.com:org/my-rules.git
103
92
  npx heymark init https://github.com/org/my-rules.git --dir rules --branch main
104
93
  ```
105
94
 
106
- 설정은 **`.heymark/config.json`**에, 캐시(클론된 저장소)는 `.heymark/cache/`에 생성됩니다.
107
-
108
- ```gitignore
109
- .heymark/
110
- ```
111
-
112
- 이후 `heymark` 실행 시 해당 저장소를 clone/pull 한 뒤 `.md` 파일들을 변환합니다.
113
-
114
95
  ### Usage with npx
115
96
 
116
- 설치 없이 바로 실행하거나, 설치 후 npx로 실행할 수 있습니다.
97
+ 설치 없이 npx로 바로 실행할 수 있습니다.
117
98
  규칙은 **원격 GitHub 저장소**에서 가져오며, 해당 저장소 안의 마크다운 파일을 각 AI 도구 형식으로 변환해 현재 프로젝트에 생성합니다.
118
99
 
119
100
  ```bash
@@ -136,29 +117,8 @@ npx heymark --clean
136
117
  npx heymark --help
137
118
  ```
138
119
 
139
- **설치 없이 바로 사용:**
140
-
141
- ```bash
142
- # 설치 없이 최신 버전으로 실행
143
- npx heymark@latest
144
- ```
145
-
146
- **패키지 업데이트:**
147
-
148
- ```bash
149
- npm update heymark
150
- npx heymark
151
- ```
152
-
153
- **동작 방식:**
154
-
155
- - 기본적으로 기존 생성된 파일을 삭제한 후 새로 생성합니다
156
- - 이를 통해 이전 버전의 파일이 남지 않고 깔끔하게 동기화됩니다
157
-
158
120
  ## Getting Started
159
121
 
160
- 규칙을 작성하고 로컬에서 테스트하는 방법.
161
-
162
122
  ### Writing Rules
163
123
 
164
124
  규칙 소스 디렉터리(외부 레포)에 마크다운 파일 작성. YAML frontmatter로 메타데이터 정의:
@@ -199,15 +159,6 @@ node scripts/sync.js --clean
199
159
 
200
160
  ## Tool Support
201
161
 
202
- ### Supported Tools
203
-
204
- | Tool | Output Format | Key Features |
205
- | :------------- | :-------------------------- | :------------------------------ |
206
- | Cursor | `.cursor/rules/*.mdc` | YAML frontmatter, glob 매칭 |
207
- | Claude Code | `.claude/skills/*/SKILL.md` | 스킬 디렉토리 구조 |
208
- | GitHub Copilot | `.github/instructions/*.md` | applyTo 패턴 매칭 |
209
- | OpenAI Codex | `AGENTS.md` | 단일 파일 병합 (Agent Rules v1) |
210
-
211
162
  ### Adding New Tools
212
163
 
213
164
  변환 모듈을 추가하면 자동 인식된다. 필수 export 인터페이스:
@@ -224,3 +175,15 @@ module.exports = {
224
175
  },
225
176
  };
226
177
  ```
178
+
179
+ ### Supported Tools
180
+
181
+ | Tool | Output Format | Key Features |
182
+ | :------------- | :--------------------------------------- | :------------------------------------------------------- |
183
+ | Cursor | `.cursor/rules/*.mdc` | YAML frontmatter (`description`, `globs`, `alwaysApply`) |
184
+ | Claude Code | `.claude/skills/*/SKILL.md` | 스킬 디렉토리 구조 + YAML frontmatter |
185
+ | GitHub Copilot | `.github/instructions/*.instructions.md` | `applyTo` 다중 패턴 매핑 |
186
+ | OpenAI Codex | `.agents/skills/*/SKILL.md` | 스킬 디렉토리 구조 + YAML frontmatter |
187
+ | Antigravity | `.agent/skills/*/SKILL.md` | 스킬 디렉토리 구조 + YAML frontmatter |
188
+
189
+ Antigravity 스킬 파일 경로: `/.agent/skills/<skill-folder>/SKILL.md`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "heymark",
3
- "version": "1.0.2",
3
+ "version": "1.1.1",
4
4
  "description": "Centralized AI coding tool conventions with auto-conversion to multiple formats",
5
5
  "main": "scripts/sync.js",
6
6
  "bin": {
@@ -32,8 +32,10 @@ function loadConfig(projectRoot) {
32
32
  }
33
33
  return {
34
34
  rulesSource: data.rulesSource.trim(),
35
- branch: typeof data.branch === "string" && data.branch.trim() ? data.branch.trim() : "main",
36
- rulesSourceDir: typeof data.rulesSourceDir === "string" ? data.rulesSourceDir.trim() : "",
35
+ branch:
36
+ typeof data.branch === "string" && data.branch.trim() ? data.branch.trim() : "main",
37
+ rulesSourceDir:
38
+ typeof data.rulesSourceDir === "string" ? data.rulesSourceDir.trim() : "",
37
39
  };
38
40
  } catch {
39
41
  return null;
@@ -14,7 +14,8 @@ const CACHE_DIR_NAME = path.join(".heymark", "cache");
14
14
  function sanitizeRepoName(url) {
15
15
  let s = url.trim();
16
16
  if (s.endsWith(".git")) s = s.slice(0, -4);
17
- const match = s.match(/github\.com[:/]([^/]+\/[^/]+?)(?:\/|$)/) || s.match(/([^/]+\/[^/]+?)(?:\/|$)/);
17
+ const match =
18
+ s.match(/github\.com[:/]([^/]+\/[^/]+?)(?:\/|$)/) || s.match(/([^/]+\/[^/]+?)(?:\/|$)/);
18
19
  if (match) {
19
20
  return match[1].replace(/\//g, "-");
20
21
  }
@@ -52,10 +53,13 @@ function getRulesDirFromRepo(projectRoot, config) {
52
53
  }
53
54
  } else {
54
55
  try {
55
- execSync("git fetch origin && git checkout --quiet . && git pull --quiet origin " + branch, {
56
- stdio: "pipe",
57
- cwd: clonePath,
58
- });
56
+ execSync(
57
+ "git fetch origin && git checkout --quiet . && git pull --quiet origin " + branch,
58
+ {
59
+ stdio: "pipe",
60
+ cwd: clonePath,
61
+ }
62
+ );
59
63
  } catch (err) {
60
64
  // pull 실패 시(네트워크 등) 기존 클론 내용으로 진행
61
65
  }
package/scripts/sync.js CHANGED
@@ -138,7 +138,9 @@ function runInit(initArgs) {
138
138
  }
139
139
  const config = { rulesSource: repoUrl.trim(), branch, rulesSourceDir };
140
140
  const configPath = writeConfig(PROJECT_ROOT, config);
141
- console.log(`[Init] Rules source saved to ${path.relative(PROJECT_ROOT, configPath) || configPath}`);
141
+ console.log(
142
+ `[Init] Rules source saved to ${path.relative(PROJECT_ROOT, configPath) || configPath}`
143
+ );
142
144
  console.log(` rulesSource: ${config.rulesSource}`);
143
145
  if (branch !== "main") console.log(` branch: ${branch}`);
144
146
  if (rulesSourceDir) console.log(` rulesSourceDir: ${rulesSourceDir}`);
@@ -0,0 +1,41 @@
1
+ "use strict";
2
+
3
+ const fs = require("fs");
4
+ const path = require("path");
5
+
6
+ module.exports = {
7
+ name: "Antigravity",
8
+ output: ".agent/skills/*/SKILL.md",
9
+
10
+ generate(rules, projectRoot) {
11
+ for (const rule of rules) {
12
+ const skillDir = path.join(projectRoot, ".agent", "skills", rule.name);
13
+ fs.mkdirSync(skillDir, { recursive: true });
14
+
15
+ const lines = [
16
+ "---",
17
+ `name: ${rule.name}`,
18
+ `description: "${rule.description}"`,
19
+ "---",
20
+ ];
21
+ const content = lines.join("\n") + "\n\n" + rule.body + "\n";
22
+ fs.writeFileSync(path.join(skillDir, "SKILL.md"), content);
23
+ }
24
+
25
+ return rules.length;
26
+ },
27
+
28
+ clean(ruleNames, projectRoot) {
29
+ const cleaned = [];
30
+
31
+ for (const name of ruleNames) {
32
+ const skillDir = path.join(projectRoot, ".agent", "skills", name);
33
+ if (fs.existsSync(skillDir)) {
34
+ fs.rmSync(skillDir, { recursive: true });
35
+ cleaned.push(path.join(".agent", "skills", name));
36
+ }
37
+ }
38
+
39
+ return cleaned;
40
+ },
41
+ };
@@ -2,23 +2,40 @@
2
2
 
3
3
  const fs = require("fs");
4
4
  const path = require("path");
5
- const { writeMergedFile } = require("../lib/parser");
6
5
 
7
6
  module.exports = {
8
- name: "OpenAI Codex",
9
- output: "AGENTS.md",
10
-
11
- generate(rules, projectRoot) {
12
- writeMergedFile(path.join(projectRoot, "AGENTS.md"), rules);
13
- return rules.length;
14
- },
15
-
16
- clean(_ruleNames, projectRoot) {
17
- const filePath = path.join(projectRoot, "AGENTS.md");
18
- if (fs.existsSync(filePath)) {
19
- fs.unlinkSync(filePath);
20
- return ["AGENTS.md"];
21
- }
22
- return [];
23
- },
7
+ name: "OpenAI Codex",
8
+ output: ".agents/skills/*/SKILL.md",
9
+
10
+ generate(rules, projectRoot) {
11
+ for (const rule of rules) {
12
+ const skillDir = path.join(projectRoot, ".agents", "skills", rule.name);
13
+ fs.mkdirSync(skillDir, { recursive: true });
14
+
15
+ const lines = [
16
+ "---",
17
+ `name: ${rule.name}`,
18
+ `description: "${rule.description}"`,
19
+ "---",
20
+ ];
21
+ const content = lines.join("\n") + "\n\n" + rule.body + "\n";
22
+ fs.writeFileSync(path.join(skillDir, "SKILL.md"), content);
23
+ }
24
+
25
+ return rules.length;
26
+ },
27
+
28
+ clean(ruleNames, projectRoot) {
29
+ const cleaned = [];
30
+
31
+ for (const name of ruleNames) {
32
+ const skillDir = path.join(projectRoot, ".agents", "skills", name);
33
+ if (fs.existsSync(skillDir)) {
34
+ fs.rmSync(skillDir, { recursive: true });
35
+ cleaned.push(path.join(".agents", "skills", name));
36
+ }
37
+ }
38
+
39
+ return cleaned;
40
+ },
24
41
  };
@@ -4,45 +4,38 @@ const fs = require("fs");
4
4
  const path = require("path");
5
5
 
6
6
  module.exports = {
7
- name: "GitHub Copilot",
8
- output: ".github/instructions/*.instructions.md",
9
-
10
- generate(rules, projectRoot) {
11
- const destDir = path.join(projectRoot, ".github", "instructions");
12
- fs.mkdirSync(destDir, { recursive: true });
13
-
14
- for (const rule of rules) {
15
- const globs = rule.globs
16
- ? rule.globs.split(",").map((g) => g.trim())
17
- : ["**"];
18
-
19
- const applyToLines = globs.map((g) => ` - "${g}"`).join("\n");
20
- const header = `applyTo:\n${applyToLines}\n---`;
21
-
22
- const content = header + "\n\n" + rule.body + "\n";
23
- fs.writeFileSync(
24
- path.join(destDir, `${rule.name}.instructions.md`),
25
- content
26
- );
27
- }
28
-
29
- return rules.length;
30
- },
31
-
32
- clean(ruleNames, projectRoot) {
33
- const cleaned = [];
34
- const destDir = path.join(projectRoot, ".github", "instructions");
35
-
36
- for (const name of ruleNames) {
37
- const filePath = path.join(destDir, `${name}.instructions.md`);
38
- if (fs.existsSync(filePath)) {
39
- fs.unlinkSync(filePath);
40
- cleaned.push(
41
- path.join(".github", "instructions", `${name}.instructions.md`)
42
- );
43
- }
44
- }
45
-
46
- return cleaned;
47
- },
7
+ name: "GitHub Copilot",
8
+ output: ".github/instructions/*.instructions.md",
9
+
10
+ generate(rules, projectRoot) {
11
+ const destDir = path.join(projectRoot, ".github", "instructions");
12
+ fs.mkdirSync(destDir, { recursive: true });
13
+
14
+ for (const rule of rules) {
15
+ const globs = rule.globs ? rule.globs.split(",").map((g) => g.trim()) : ["**"];
16
+
17
+ const applyToLines = globs.map((g) => ` - "${g}"`).join("\n");
18
+ const header = `applyTo:\n${applyToLines}\n---`;
19
+
20
+ const content = header + "\n\n" + rule.body + "\n";
21
+ fs.writeFileSync(path.join(destDir, `${rule.name}.instructions.md`), content);
22
+ }
23
+
24
+ return rules.length;
25
+ },
26
+
27
+ clean(ruleNames, projectRoot) {
28
+ const cleaned = [];
29
+ const destDir = path.join(projectRoot, ".github", "instructions");
30
+
31
+ for (const name of ruleNames) {
32
+ const filePath = path.join(destDir, `${name}.instructions.md`);
33
+ if (fs.existsSync(filePath)) {
34
+ fs.unlinkSync(filePath);
35
+ cleaned.push(path.join(".github", "instructions", `${name}.instructions.md`));
36
+ }
37
+ }
38
+
39
+ return cleaned;
40
+ },
48
41
  };
@@ -4,38 +4,38 @@ const fs = require("fs");
4
4
  const path = require("path");
5
5
 
6
6
  module.exports = {
7
- name: "Cursor",
8
- output: ".cursor/rules/*.mdc",
9
-
10
- generate(rules, projectRoot) {
11
- const destDir = path.join(projectRoot, ".cursor", "rules");
12
- fs.mkdirSync(destDir, { recursive: true });
13
-
14
- for (const rule of rules) {
15
- const lines = ["---", `description: "${rule.description}"`];
16
- if (rule.globs) lines.push(`globs: "${rule.globs}"`);
17
- lines.push(`alwaysApply: ${rule.alwaysApply}`);
18
- lines.push("---");
19
-
20
- const content = lines.join("\n") + "\n\n" + rule.body + "\n";
21
- fs.writeFileSync(path.join(destDir, `${rule.name}.mdc`), content);
22
- }
23
-
24
- return rules.length;
25
- },
26
-
27
- clean(ruleNames, projectRoot) {
28
- const cleaned = [];
29
- const destDir = path.join(projectRoot, ".cursor", "rules");
30
-
31
- for (const name of ruleNames) {
32
- const filePath = path.join(destDir, `${name}.mdc`);
33
- if (fs.existsSync(filePath)) {
34
- fs.unlinkSync(filePath);
35
- cleaned.push(path.join(".cursor", "rules", `${name}.mdc`));
36
- }
37
- }
38
-
39
- return cleaned;
40
- },
7
+ name: "Cursor",
8
+ output: ".cursor/rules/*.mdc",
9
+
10
+ generate(rules, projectRoot) {
11
+ const destDir = path.join(projectRoot, ".cursor", "rules");
12
+ fs.mkdirSync(destDir, { recursive: true });
13
+
14
+ for (const rule of rules) {
15
+ const lines = ["---", `description: "${rule.description}"`];
16
+ if (rule.globs) lines.push(`globs: "${rule.globs}"`);
17
+ lines.push(`alwaysApply: ${rule.alwaysApply}`);
18
+ lines.push("---");
19
+
20
+ const content = lines.join("\n") + "\n\n" + rule.body + "\n";
21
+ fs.writeFileSync(path.join(destDir, `${rule.name}.mdc`), content);
22
+ }
23
+
24
+ return rules.length;
25
+ },
26
+
27
+ clean(ruleNames, projectRoot) {
28
+ const cleaned = [];
29
+ const destDir = path.join(projectRoot, ".cursor", "rules");
30
+
31
+ for (const name of ruleNames) {
32
+ const filePath = path.join(destDir, `${name}.mdc`);
33
+ if (fs.existsSync(filePath)) {
34
+ fs.unlinkSync(filePath);
35
+ cleaned.push(path.join(".cursor", "rules", `${name}.mdc`));
36
+ }
37
+ }
38
+
39
+ return cleaned;
40
+ },
41
41
  };