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 +16 -53
- package/package.json +1 -1
- package/scripts/lib/config.js +4 -2
- package/scripts/lib/repo.js +9 -5
- package/scripts/sync.js +3 -1
- package/scripts/tools/antigravity.js +41 -0
- package/scripts/tools/codex.js +34 -17
- package/scripts/tools/copilot.js +34 -41
- package/scripts/tools/cursor.js +34 -34
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
|
-
- **자동 형식 변환**:
|
|
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
|
-
|
|
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
|
-
설치 없이
|
|
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
package/scripts/lib/config.js
CHANGED
|
@@ -32,8 +32,10 @@ function loadConfig(projectRoot) {
|
|
|
32
32
|
}
|
|
33
33
|
return {
|
|
34
34
|
rulesSource: data.rulesSource.trim(),
|
|
35
|
-
branch:
|
|
36
|
-
|
|
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;
|
package/scripts/lib/repo.js
CHANGED
|
@@ -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 =
|
|
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(
|
|
56
|
-
|
|
57
|
-
|
|
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(
|
|
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
|
+
};
|
package/scripts/tools/codex.js
CHANGED
|
@@ -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
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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
|
};
|
package/scripts/tools/copilot.js
CHANGED
|
@@ -4,45 +4,38 @@ const fs = require("fs");
|
|
|
4
4
|
const path = require("path");
|
|
5
5
|
|
|
6
6
|
module.exports = {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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
|
};
|
package/scripts/tools/cursor.js
CHANGED
|
@@ -4,38 +4,38 @@ const fs = require("fs");
|
|
|
4
4
|
const path = require("path");
|
|
5
5
|
|
|
6
6
|
module.exports = {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
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
|
};
|