heymark 1.1.1 → 1.1.3

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/package.json CHANGED
@@ -1,57 +1,56 @@
1
- {
2
- "name": "heymark",
3
- "version": "1.1.1",
4
- "description": "Centralized AI coding tool conventions with auto-conversion to multiple formats",
5
- "main": "scripts/sync.js",
6
- "bin": {
7
- "heymark": "scripts/sync.js"
8
- },
9
- "scripts": {
10
- "sync": "node scripts/sync.js",
11
- "init": "node scripts/sync.js init",
12
- "test": "echo \"Error: no test specified\" && exit 1"
13
- },
14
- "keywords": [
15
- "ai",
16
- "coding",
17
- "conventions",
18
- "cursor",
19
- "claude",
20
- "copilot",
21
- "codex",
22
- "rules"
23
- ],
24
- "author": {
25
- "name": "i2na",
26
- "email": "yena.e121@gmail.com",
27
- "url": "https://github.com/i2na"
28
- },
29
- "contributors": [
30
- {
31
- "name": "i2na",
32
- "email": "yena.e121@gmail.com",
33
- "url": "https://github.com/i2na"
34
- },
35
- {
36
- "name": "yezzero",
37
- "email": "yeyeonggim06@gmail.com",
38
- "url": "https://github.com/yezzero"
39
- }
40
- ],
41
- "license": "MIT",
42
- "engines": {
43
- "node": ">=14.0.0"
44
- },
45
- "files": [
46
- "scripts/",
47
- "rules/",
48
- "README.md"
49
- ],
50
- "repository": {
51
- "type": "git",
52
- "url": "git+https://github.com/MosslandOpenDevs/heymark.git"
53
- },
54
- "publishConfig": {
55
- "access": "public"
56
- }
57
- }
1
+ {
2
+ "name": "heymark",
3
+ "version": "1.1.3",
4
+ "description": "Rules & Skills Hub for AI Coding Assistants",
5
+ "type": "commonjs",
6
+ "main": "scripts/sync.js",
7
+ "bin": "scripts/sync.js",
8
+ "keywords": [
9
+ "ai-coding-assistant",
10
+ "ai-agent",
11
+ "ai-skill",
12
+ "ai-rule",
13
+ "skill-management",
14
+ "cursor",
15
+ "claude-code",
16
+ "copilot",
17
+ "codex",
18
+ "antigravity"
19
+ ],
20
+ "homepage": "https://github.com/MosslandOpenDevs/heymark#readme",
21
+ "bugs": {
22
+ "url": "https://github.com/MosslandOpenDevs/heymark/issues",
23
+ "email": "yena@moss.land"
24
+ },
25
+ "license": "MIT",
26
+ "author": {
27
+ "name": "i-2na",
28
+ "email": "yena@moss.land",
29
+ "url": "https://github.com/i2na"
30
+ },
31
+ "contributors": [
32
+ {
33
+ "name": "i-2na",
34
+ "email": "yena@moss.land",
35
+ "url": "https://github.com/i2na"
36
+ },
37
+ {
38
+ "name": "yezzero",
39
+ "email": "yeyeonggim06@gmail.com",
40
+ "url": "https://github.com/yezzero"
41
+ }
42
+ ],
43
+ "engines": {
44
+ "node": ">=14.0.0"
45
+ },
46
+ "files": [
47
+ "scripts/"
48
+ ],
49
+ "repository": {
50
+ "type": "git",
51
+ "url": "git+https://github.com/MosslandOpenDevs/heymark.git"
52
+ },
53
+ "publishConfig": {
54
+ "access": "public"
55
+ }
56
+ }
@@ -1,73 +1,110 @@
1
- "use strict";
2
-
3
- const fs = require("fs");
4
- const path = require("path");
5
-
6
- const CONFIG_DIR = ".heymark";
7
- const CONFIG_FILENAME = "config.json";
8
- /** 프로젝트 루트 기준 설정 파일 경로 (표시용) */
9
- const CONFIG_RELATIVE = path.join(CONFIG_DIR, CONFIG_FILENAME);
10
-
11
- /**
12
- * rulesSource: GitHub 저장소 URL (https://github.com/org/repo 또는 git@github.com:org/repo.git)
13
- * branch: 브랜치 (기본 main)
14
- * rulesSourceDir: 저장소 내부에서 .md가 있는 하위 디렉터리 (기본 "" = 루트)
15
- * @typedef {{ rulesSource: string, branch?: string, rulesSourceDir?: string }} RuleBookConfig
16
- */
17
-
18
- /**
19
- * 프로젝트 루트에서 .heymark/config.json을 읽습니다.
20
- * @param {string} projectRoot - 프로젝트 루트 (보통 process.cwd())
21
- * @returns {RuleBookConfig | null}
22
- */
23
- function loadConfig(projectRoot) {
24
- const configPath = path.join(projectRoot, CONFIG_DIR, CONFIG_FILENAME);
25
- if (!fs.existsSync(configPath)) return null;
26
-
27
- try {
28
- const raw = fs.readFileSync(configPath, "utf8");
29
- const data = JSON.parse(raw);
30
- if (!data || typeof data.rulesSource !== "string" || !data.rulesSource.trim()) {
31
- return null;
32
- }
33
- return {
34
- rulesSource: data.rulesSource.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() : "",
39
- };
40
- } catch {
41
- return null;
42
- }
43
- }
44
-
45
- /**
46
- * 초기 설정 파일을 생성합니다. (원격 GitHub 저장소 URL 사용) .heymark/config.json에 저장합니다.
47
- * @param {string} projectRoot
48
- * @param {RuleBookConfig} config - { rulesSource: repoUrl, branch?, rulesSourceDir? }
49
- */
50
- function writeConfig(projectRoot, config) {
51
- const configDir = path.join(projectRoot, CONFIG_DIR);
52
- if (!fs.existsSync(configDir)) {
53
- fs.mkdirSync(configDir, { recursive: true });
54
- }
55
- const configPath = path.join(configDir, CONFIG_FILENAME);
56
- const toWrite = {
57
- rulesSource: config.rulesSource,
58
- branch: config.branch || "main",
59
- };
60
- if (config.rulesSourceDir) {
61
- toWrite.rulesSourceDir = config.rulesSourceDir;
62
- }
63
- fs.writeFileSync(configPath, JSON.stringify(toWrite, null, 2), "utf8");
64
- return configPath;
65
- }
66
-
67
- module.exports = {
68
- CONFIG_DIR,
69
- CONFIG_FILENAME,
70
- CONFIG_RELATIVE,
71
- loadConfig,
72
- writeConfig,
73
- };
1
+ "use strict";
2
+
3
+ const fs = require("fs");
4
+ const path = require("path");
5
+
6
+ const CONFIG_DIR = ".heymark";
7
+ const CONFIG_FILENAME = "config.json";
8
+ const DEFAULT_BRANCH = "main";
9
+ const CONFIG_RELATIVE = path.join(CONFIG_DIR, CONFIG_FILENAME);
10
+
11
+ /**
12
+ * @typedef {{ rulesSource: string, branch?: string, rulesSourceDir?: string }} RuleBookConfig
13
+ */
14
+
15
+ /**
16
+ * Resolve config file path from project root.
17
+ * @param {string} projectRoot
18
+ * @returns {string}
19
+ */
20
+ function getConfigPath(projectRoot) {
21
+ return path.join(projectRoot, CONFIG_DIR, CONFIG_FILENAME);
22
+ }
23
+
24
+ /**
25
+ * Normalize and validate config payload.
26
+ * @param {unknown} value
27
+ * @returns {RuleBookConfig | null}
28
+ */
29
+ function normalizeConfig(value) {
30
+ if (!value || typeof value !== "object") {
31
+ return null;
32
+ }
33
+
34
+ const raw =
35
+ /** @type {{ rulesSource?: unknown, branch?: unknown, rulesSourceDir?: unknown }} */ (
36
+ value
37
+ );
38
+ if (typeof raw.rulesSource !== "string" || !raw.rulesSource.trim()) {
39
+ return null;
40
+ }
41
+
42
+ return {
43
+ rulesSource: raw.rulesSource.trim(),
44
+ branch:
45
+ typeof raw.branch === "string" && raw.branch.trim()
46
+ ? raw.branch.trim()
47
+ : DEFAULT_BRANCH,
48
+ rulesSourceDir: typeof raw.rulesSourceDir === "string" ? raw.rulesSourceDir.trim() : "",
49
+ };
50
+ }
51
+
52
+ /**
53
+ * Read config from project root.
54
+ * @param {string} projectRoot
55
+ * @returns {RuleBookConfig | null}
56
+ */
57
+ function loadConfig(projectRoot) {
58
+ const configPath = getConfigPath(projectRoot);
59
+ if (!fs.existsSync(configPath)) {
60
+ return null;
61
+ }
62
+
63
+ try {
64
+ const raw = fs.readFileSync(configPath, "utf8");
65
+ return normalizeConfig(JSON.parse(raw));
66
+ } catch {
67
+ return null;
68
+ }
69
+ }
70
+
71
+ /**
72
+ * Create initial config file in .heymark/config.json.
73
+ * @param {string} projectRoot
74
+ * @param {RuleBookConfig} config
75
+ * @returns {string}
76
+ */
77
+ function writeConfig(projectRoot, config) {
78
+ const configDir = path.join(projectRoot, CONFIG_DIR);
79
+ if (!fs.existsSync(configDir)) {
80
+ fs.mkdirSync(configDir, { recursive: true });
81
+ }
82
+
83
+ const normalized = normalizeConfig(config);
84
+ if (!normalized) {
85
+ throw new Error("Invalid config payload");
86
+ }
87
+
88
+ const configPath = path.join(configDir, CONFIG_FILENAME);
89
+ const toWrite = {
90
+ rulesSource: normalized.rulesSource,
91
+ branch: normalized.branch || DEFAULT_BRANCH,
92
+ };
93
+ if (normalized.rulesSourceDir) {
94
+ toWrite.rulesSourceDir = normalized.rulesSourceDir;
95
+ }
96
+
97
+ fs.writeFileSync(configPath, JSON.stringify(toWrite, null, 2), "utf8");
98
+ return configPath;
99
+ }
100
+
101
+ module.exports = {
102
+ CONFIG_DIR,
103
+ CONFIG_FILENAME,
104
+ CONFIG_RELATIVE,
105
+ DEFAULT_BRANCH,
106
+ getConfigPath,
107
+ loadConfig,
108
+ normalizeConfig,
109
+ writeConfig,
110
+ };
@@ -1,82 +1,125 @@
1
- "use strict";
2
-
3
- const fs = require("fs");
4
- const path = require("path");
5
-
6
- const GENERATED_MARKER = "<!-- @generated by sync-rules - DO NOT EDIT -->";
7
-
8
- function parseFrontmatter(content) {
9
- const match = content.match(/^---\r?\n([\s\S]+?)\r?\n---\r?\n?([\s\S]*)$/);
10
- if (!match) return { metadata: {}, body: content.trim() };
11
-
12
- const metadata = {};
13
- match[1].split(/\r?\n/).forEach((line) => {
14
- const idx = line.indexOf(":");
15
- if (idx === -1) return;
16
- const key = line.slice(0, idx).trim();
17
- let value = line.slice(idx + 1).trim();
18
- if (
19
- (value.startsWith('"') && value.endsWith('"')) ||
20
- (value.startsWith("'") && value.endsWith("'"))
21
- ) {
22
- value = value.slice(1, -1);
23
- }
24
- if (value === "true") value = true;
25
- else if (value === "false") value = false;
26
- metadata[key] = value;
27
- });
28
-
29
- return { metadata, body: match[2].trim() };
30
- }
31
-
32
- function loadRules(rulesDir) {
33
- if (!fs.existsSync(rulesDir)) {
34
- console.error(`[Error] Rules directory not found: ${rulesDir}`);
35
- console.error(" Ensure 'rules/' exists relative to the script location.");
36
- process.exit(1);
37
- }
38
-
39
- const files = fs
40
- .readdirSync(rulesDir)
41
- .filter((f) => f.endsWith(".md"))
42
- .sort();
43
-
44
- if (files.length === 0) {
45
- console.error(`[Error] No .md files found in: ${rulesDir}`);
46
- process.exit(1);
47
- }
48
-
49
- return files.map((file) => {
50
- const raw = fs.readFileSync(path.join(rulesDir, file), "utf8");
51
- const { metadata, body } = parseFrontmatter(raw);
52
- const baseName = path.basename(file, ".md");
53
-
54
- return {
55
- fileName: file,
56
- name: metadata.name || baseName,
57
- description: metadata.description || baseName,
58
- globs: metadata.globs || "",
59
- alwaysApply: metadata.alwaysApply === true,
60
- metadata,
61
- body,
62
- };
63
- });
64
- }
65
-
66
- function mergeRuleBodies(rules) {
67
- return rules.map((r) => r.body).join("\n\n---\n\n");
68
- }
69
-
70
- function writeMergedFile(filePath, rules) {
71
- fs.mkdirSync(path.dirname(filePath), { recursive: true });
72
- const content = `${GENERATED_MARKER}\n\n${mergeRuleBodies(rules)}\n`;
73
- fs.writeFileSync(filePath, content);
74
- }
75
-
76
- module.exports = {
77
- GENERATED_MARKER,
78
- parseFrontmatter,
79
- loadRules,
80
- mergeRuleBodies,
81
- writeMergedFile,
82
- };
1
+ "use strict";
2
+
3
+ const fs = require("fs");
4
+ const path = require("path");
5
+
6
+ const GENERATED_MARKER = "<!-- @generated by sync-rules - DO NOT EDIT -->";
7
+ const FRONTMATTER_REGEX = /^---\r?\n([\s\S]+?)\r?\n---\r?\n?([\s\S]*)$/;
8
+ const MARKDOWN_EXTENSION = ".md";
9
+ const RULE_SEPARATOR = "\n\n---\n\n";
10
+
11
+ /**
12
+ * @param {string} rawValue
13
+ * @returns {string | boolean}
14
+ */
15
+ function parseMetadataValue(rawValue) {
16
+ let value = rawValue.trim();
17
+ if (
18
+ (value.startsWith('"') && value.endsWith('"')) ||
19
+ (value.startsWith("'") && value.endsWith("'"))
20
+ ) {
21
+ value = value.slice(1, -1);
22
+ }
23
+
24
+ if (value === "true") {
25
+ return true;
26
+ }
27
+ if (value === "false") {
28
+ return false;
29
+ }
30
+
31
+ return value;
32
+ }
33
+
34
+ /**
35
+ * @param {string} metadataBlock
36
+ * @returns {Record<string, string | boolean>}
37
+ */
38
+ function parseMetadataBlock(metadataBlock) {
39
+ const metadata = {};
40
+
41
+ metadataBlock.split(/\r?\n/).forEach((line) => {
42
+ const separatorIndex = line.indexOf(":");
43
+ if (separatorIndex === -1) {
44
+ return;
45
+ }
46
+
47
+ const key = line.slice(0, separatorIndex).trim();
48
+ const rawValue = line.slice(separatorIndex + 1);
49
+ metadata[key] = parseMetadataValue(rawValue);
50
+ });
51
+
52
+ return metadata;
53
+ }
54
+
55
+ function parseFrontmatter(content) {
56
+ const match = content.match(FRONTMATTER_REGEX);
57
+ if (!match) {
58
+ return { metadata: {}, body: content.trim() };
59
+ }
60
+
61
+ const [, metadataBlock, body] = match;
62
+ return {
63
+ metadata: parseMetadataBlock(metadataBlock),
64
+ body: body.trim(),
65
+ };
66
+ }
67
+
68
+ function loadMarkdownFiles(rulesDir) {
69
+ return fs
70
+ .readdirSync(rulesDir)
71
+ .filter((fileName) => fileName.endsWith(MARKDOWN_EXTENSION))
72
+ .sort();
73
+ }
74
+
75
+ function createRuleFromFile(rulesDir, fileName) {
76
+ const filePath = path.join(rulesDir, fileName);
77
+ const raw = fs.readFileSync(filePath, "utf8");
78
+ const { metadata, body } = parseFrontmatter(raw);
79
+ const baseName = path.basename(fileName, MARKDOWN_EXTENSION);
80
+
81
+ return {
82
+ fileName,
83
+ name: metadata.name || baseName,
84
+ description: metadata.description || baseName,
85
+ globs: metadata.globs || "",
86
+ alwaysApply: metadata.alwaysApply === true,
87
+ metadata,
88
+ body,
89
+ };
90
+ }
91
+
92
+ function loadRules(rulesDir) {
93
+ if (!fs.existsSync(rulesDir)) {
94
+ console.error(`[Error] Rules directory not found: ${rulesDir}`);
95
+ console.error(" Ensure 'rules/' exists relative to the script location.");
96
+ process.exit(1);
97
+ }
98
+
99
+ const files = loadMarkdownFiles(rulesDir);
100
+
101
+ if (files.length === 0) {
102
+ console.error(`[Error] No .md files found in: ${rulesDir}`);
103
+ process.exit(1);
104
+ }
105
+
106
+ return files.map((fileName) => createRuleFromFile(rulesDir, fileName));
107
+ }
108
+
109
+ function mergeRuleBodies(rules) {
110
+ return rules.map((rule) => rule.body).join(RULE_SEPARATOR);
111
+ }
112
+
113
+ function writeMergedFile(filePath, rules) {
114
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
115
+ const content = `${GENERATED_MARKER}\n\n${mergeRuleBodies(rules)}\n`;
116
+ fs.writeFileSync(filePath, content, "utf8");
117
+ }
118
+
119
+ module.exports = {
120
+ GENERATED_MARKER,
121
+ parseFrontmatter,
122
+ loadRules,
123
+ mergeRuleBodies,
124
+ writeMergedFile,
125
+ };