heymark 1.1.3 → 2.0.0
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.ko.md +63 -110
- package/README.md +65 -110
- package/package.json +3 -3
- package/scripts/cli.js +375 -0
- package/scripts/lib/config.js +30 -14
- package/scripts/lib/repo.js +14 -14
- package/scripts/tools/antigravity.js +5 -9
- package/scripts/tools/claude.js +5 -9
- package/scripts/tools/codex.js +5 -9
- package/scripts/tools/copilot.js +5 -9
- package/scripts/tools/cursor.js +5 -9
- package/scripts/sync.js +0 -312
package/scripts/cli.js
ADDED
|
@@ -0,0 +1,375 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
|
|
4
|
+
const fs = require("fs");
|
|
5
|
+
const path = require("path");
|
|
6
|
+
const { CONFIG_RELATIVE, DEFAULT_BRANCH, loadConfig, writeConfig } = require("./lib/config");
|
|
7
|
+
const { loadRules } = require("./lib/parser");
|
|
8
|
+
const { CACHE_DIR_NAME, getLinkedRulesDir, sanitizeRepoName } = require("./lib/repo");
|
|
9
|
+
|
|
10
|
+
const SCRIPT_DIR = __dirname;
|
|
11
|
+
const PROJECT_ROOT = process.cwd();
|
|
12
|
+
|
|
13
|
+
const COMMAND_LINK = "link";
|
|
14
|
+
const COMMAND_SYNC = "sync";
|
|
15
|
+
const COMMAND_CLEAN = "clean";
|
|
16
|
+
const COMMAND_STATUS = "status";
|
|
17
|
+
const COMMAND_HELP = "help";
|
|
18
|
+
|
|
19
|
+
const OPTION_BRANCH = "--branch";
|
|
20
|
+
const OPTION_FOLDER = "--folder";
|
|
21
|
+
const OPTION_SAMPLES = "--samples";
|
|
22
|
+
const SHORT_BRANCH = "-b";
|
|
23
|
+
const SHORT_FOLDER = "-f";
|
|
24
|
+
const TARGET_ALL = "all";
|
|
25
|
+
const TARGET_DOT = ".";
|
|
26
|
+
const LATEST_VERSION_COMMAND = "npx heymark@latest";
|
|
27
|
+
const SAMPLES_REPO_URL = "https://github.com/MosslandOpenDevs/heymark.git";
|
|
28
|
+
const SAMPLES_FOLDER = "samples";
|
|
29
|
+
|
|
30
|
+
function exitWithError(message, details = []) {
|
|
31
|
+
console.error(`[Error] ${message}`);
|
|
32
|
+
details.forEach((detail) => console.error(` ${detail}`));
|
|
33
|
+
process.exit(1);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function discoverTools() {
|
|
37
|
+
const toolsDir = path.join(SCRIPT_DIR, "tools");
|
|
38
|
+
const registry = {};
|
|
39
|
+
|
|
40
|
+
fs.readdirSync(toolsDir)
|
|
41
|
+
.filter((fileName) => fileName.endsWith(".js"))
|
|
42
|
+
.sort()
|
|
43
|
+
.forEach((file) => {
|
|
44
|
+
const key = path.basename(file, ".js");
|
|
45
|
+
registry[key] = require(path.join(toolsDir, file));
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
return registry;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function showHelp(tools) {
|
|
52
|
+
const toolLines = Object.entries(tools)
|
|
53
|
+
.map(([toolKey, toolDefinition]) => {
|
|
54
|
+
const paddedKey = toolKey.padEnd(10);
|
|
55
|
+
const paddedName = toolDefinition.name.padEnd(16);
|
|
56
|
+
return ` ${paddedKey} ${paddedName} -> ${toolDefinition.output}`;
|
|
57
|
+
})
|
|
58
|
+
.join("\n");
|
|
59
|
+
|
|
60
|
+
console.log(`
|
|
61
|
+
Heymark CLI
|
|
62
|
+
|
|
63
|
+
Reads *.md from a GitHub repository (public or private) and generates
|
|
64
|
+
tool-specific configuration files for various AI coding assistants.
|
|
65
|
+
Same rules everywhere: A computer, B computer, same remote repo.
|
|
66
|
+
|
|
67
|
+
Usage:
|
|
68
|
+
heymark help
|
|
69
|
+
heymark link <repo-url> [--branch <name>] [--folder <path>]
|
|
70
|
+
heymark link --samples
|
|
71
|
+
heymark sync [.|<tool>...]
|
|
72
|
+
heymark clean [.|<tool>...]
|
|
73
|
+
heymark status
|
|
74
|
+
heymark
|
|
75
|
+
|
|
76
|
+
Options:
|
|
77
|
+
--branch, -b <name> Branch name (used with 'link')
|
|
78
|
+
--folder, -f <path> Subdirectory path in repository (used with 'link')
|
|
79
|
+
--samples Use built-in sample Skill repository
|
|
80
|
+
|
|
81
|
+
Targets:
|
|
82
|
+
. All available tools
|
|
83
|
+
<tool>... Space-separated tool names (no commas)
|
|
84
|
+
|
|
85
|
+
Available tools:
|
|
86
|
+
${toolLines}
|
|
87
|
+
|
|
88
|
+
Examples:
|
|
89
|
+
heymark help
|
|
90
|
+
heymark link --samples
|
|
91
|
+
heymark link https://github.com/org/my-rules.git
|
|
92
|
+
heymark link https://github.com/org/my-rules.git --folder rules --branch main
|
|
93
|
+
heymark sync .
|
|
94
|
+
heymark sync cursor claude
|
|
95
|
+
heymark clean .
|
|
96
|
+
heymark status
|
|
97
|
+
heymark
|
|
98
|
+
|
|
99
|
+
Latest version:
|
|
100
|
+
${LATEST_VERSION_COMMAND}
|
|
101
|
+
`);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function parseLinkArgs(args) {
|
|
105
|
+
if (args.length === 1 && args[0] === OPTION_SAMPLES) {
|
|
106
|
+
return {
|
|
107
|
+
repoUrl: SAMPLES_REPO_URL,
|
|
108
|
+
branch: DEFAULT_BRANCH,
|
|
109
|
+
folder: SAMPLES_FOLDER,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (args.includes(OPTION_SAMPLES)) {
|
|
114
|
+
exitWithError("--samples cannot be combined with other link arguments.", [
|
|
115
|
+
"Use: heymark link --samples",
|
|
116
|
+
]);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const repoUrl = args[0];
|
|
120
|
+
if (!repoUrl || repoUrl.startsWith("--")) {
|
|
121
|
+
exitWithError("link requires a GitHub repository URL.", [
|
|
122
|
+
"Example: heymark link https://github.com/org/my-rules.git",
|
|
123
|
+
"Example: heymark link git@github.com:org/my-rules.git",
|
|
124
|
+
"Optional: --branch <branch> --folder <subdir> (e.g. --folder rules)",
|
|
125
|
+
]);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
let branch = DEFAULT_BRANCH;
|
|
129
|
+
let folder = "";
|
|
130
|
+
|
|
131
|
+
for (let i = 1; i < args.length; i++) {
|
|
132
|
+
const arg = args[i];
|
|
133
|
+
|
|
134
|
+
if (arg === OPTION_BRANCH || arg === SHORT_BRANCH) {
|
|
135
|
+
const value = args[++i];
|
|
136
|
+
if (!value) {
|
|
137
|
+
exitWithError("--branch requires a branch name.");
|
|
138
|
+
}
|
|
139
|
+
branch = value.trim();
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (arg === OPTION_FOLDER || arg === SHORT_FOLDER) {
|
|
144
|
+
const value = args[++i];
|
|
145
|
+
if (!value) {
|
|
146
|
+
exitWithError("--folder requires a folder path.");
|
|
147
|
+
}
|
|
148
|
+
folder = value.trim();
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
exitWithError(`Unknown option for link: ${arg}`);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return { repoUrl: repoUrl.trim(), branch, folder };
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function runLink(args) {
|
|
159
|
+
const config = parseLinkArgs(args);
|
|
160
|
+
const configPath = writeConfig(PROJECT_ROOT, config);
|
|
161
|
+
|
|
162
|
+
console.log(
|
|
163
|
+
`[Link] Linked repository saved to ${path.relative(PROJECT_ROOT, configPath) || configPath}`
|
|
164
|
+
);
|
|
165
|
+
console.log(` repoUrl: ${config.repoUrl}`);
|
|
166
|
+
if (config.branch !== DEFAULT_BRANCH) {
|
|
167
|
+
console.log(` branch: ${config.branch}`);
|
|
168
|
+
}
|
|
169
|
+
if (config.folder) {
|
|
170
|
+
console.log(` folder: ${config.folder}`);
|
|
171
|
+
}
|
|
172
|
+
console.log("");
|
|
173
|
+
console.log("Run 'heymark sync .' to fetch rules and generate tool configs.");
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function loadLinkedConfigOrExit() {
|
|
177
|
+
const linkedConfig = loadConfig(PROJECT_ROOT);
|
|
178
|
+
if (linkedConfig) {
|
|
179
|
+
return linkedConfig;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
exitWithError("No linked repository found.", [
|
|
183
|
+
`Run: heymark ${COMMAND_LINK} <github-repo-url>`,
|
|
184
|
+
`Config: ${CONFIG_RELATIVE}`,
|
|
185
|
+
]);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function resolveSelectedTools(toolArgs, availableTools) {
|
|
189
|
+
const availableToolKeys = Object.keys(availableTools);
|
|
190
|
+
if (toolArgs.length === 0) {
|
|
191
|
+
return availableToolKeys;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const selectedTools = toolArgs.map((tool) => tool.trim().toLowerCase()).filter(Boolean);
|
|
195
|
+
if (selectedTools.length === 0) {
|
|
196
|
+
return availableToolKeys;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
if (selectedTools.some((tool) => tool.includes(","))) {
|
|
200
|
+
exitWithError("Tool names must be space-separated (no commas).", [
|
|
201
|
+
"Example: heymark sync cursor claude",
|
|
202
|
+
]);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const hasDotToken = selectedTools.includes(TARGET_DOT);
|
|
206
|
+
const hasAllToken = selectedTools.includes(TARGET_ALL);
|
|
207
|
+
|
|
208
|
+
if (hasAllToken) {
|
|
209
|
+
exitWithError("'all' is not supported. Use '.' for all tools.");
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
if (hasDotToken) {
|
|
213
|
+
if (selectedTools.length > 1) {
|
|
214
|
+
exitWithError(`'${TARGET_DOT}' cannot be combined with tool names.`);
|
|
215
|
+
}
|
|
216
|
+
return availableToolKeys;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const invalidTools = selectedTools.filter((tool) => !availableTools[tool]);
|
|
220
|
+
if (invalidTools.length > 0) {
|
|
221
|
+
exitWithError(`Unknown tool(s): ${invalidTools.join(", ")}`, [
|
|
222
|
+
`Available: ${availableToolKeys.join(", ")}`,
|
|
223
|
+
]);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
return Array.from(new Set(selectedTools));
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function cleanGeneratedFiles(tools, selectedTools, ruleNames, onlyPrintWhenDeleted) {
|
|
230
|
+
for (const toolKey of selectedTools) {
|
|
231
|
+
const cleanedPaths = tools[toolKey].clean(ruleNames, PROJECT_ROOT);
|
|
232
|
+
if (onlyPrintWhenDeleted && cleanedPaths.length === 0) {
|
|
233
|
+
continue;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
cleanedPaths.forEach((filePath) => {
|
|
237
|
+
console.log(` Deleted: ${filePath}`);
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function loadRulesFromLinkedRepo() {
|
|
243
|
+
const linkedConfig = loadLinkedConfigOrExit();
|
|
244
|
+
const rulesDir = getLinkedRulesDir(PROJECT_ROOT, linkedConfig);
|
|
245
|
+
const rules = loadRules(rulesDir);
|
|
246
|
+
return { linkedConfig, rulesDir, rules };
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function printSyncContext(selectedTools, rulesDir) {
|
|
250
|
+
const rulesRelPath = path.relative(PROJECT_ROOT, rulesDir) || ".";
|
|
251
|
+
console.log("[Sync] Starting convention sync...");
|
|
252
|
+
console.log(` Source: ${rulesRelPath} (from linked repo)`);
|
|
253
|
+
console.log(` Target: ${PROJECT_ROOT}`);
|
|
254
|
+
console.log(` Tools: ${selectedTools.join(", ")}`);
|
|
255
|
+
console.log("");
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
function runSync(toolArgs, tools) {
|
|
259
|
+
const selectedTools = resolveSelectedTools(toolArgs, tools);
|
|
260
|
+
const { rulesDir, rules } = loadRulesFromLinkedRepo();
|
|
261
|
+
printSyncContext(selectedTools, rulesDir);
|
|
262
|
+
|
|
263
|
+
console.log(`[Load] ${rules.length} rule(s): ${rules.map((rule) => rule.name).join(", ")}`);
|
|
264
|
+
console.log("");
|
|
265
|
+
|
|
266
|
+
const ruleNames = rules.map((rule) => rule.name);
|
|
267
|
+
|
|
268
|
+
// Ensure regenerated output is always fresh.
|
|
269
|
+
console.log("[Clean] Removing existing generated files...");
|
|
270
|
+
cleanGeneratedFiles(tools, selectedTools, ruleNames, true);
|
|
271
|
+
console.log("");
|
|
272
|
+
|
|
273
|
+
console.log("[Generate]");
|
|
274
|
+
for (const toolKey of selectedTools) {
|
|
275
|
+
const toolDefinition = tools[toolKey];
|
|
276
|
+
const count = toolDefinition.generate(rules, PROJECT_ROOT);
|
|
277
|
+
const summary = `${toolDefinition.name.padEnd(16)} -> ${toolDefinition.output}`;
|
|
278
|
+
console.log(` ${summary} (${count} rules)`);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
console.log("");
|
|
282
|
+
console.log(`[Done] ${selectedTools.length} tool(s) synced successfully.`);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
function runClean(toolArgs, tools) {
|
|
286
|
+
const selectedTools = resolveSelectedTools(toolArgs, tools);
|
|
287
|
+
const { rulesDir, rules } = loadRulesFromLinkedRepo();
|
|
288
|
+
const rulesRelPath = path.relative(PROJECT_ROOT, rulesDir) || ".";
|
|
289
|
+
|
|
290
|
+
console.log("[Clean] Removing generated files...");
|
|
291
|
+
console.log(` Source: ${rulesRelPath} (from linked repo)`);
|
|
292
|
+
console.log(` Tools: ${selectedTools.join(", ")}`);
|
|
293
|
+
console.log("");
|
|
294
|
+
|
|
295
|
+
const ruleNames = rules.map((rule) => rule.name);
|
|
296
|
+
cleanGeneratedFiles(tools, selectedTools, ruleNames, false);
|
|
297
|
+
|
|
298
|
+
console.log("");
|
|
299
|
+
console.log(`[Done] ${selectedTools.length} tool(s) cleaned successfully.`);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
function runStatus(tools) {
|
|
303
|
+
const linkedConfig = loadConfig(PROJECT_ROOT);
|
|
304
|
+
const toolKeys = Object.keys(tools);
|
|
305
|
+
|
|
306
|
+
console.log("[Status] Heymark");
|
|
307
|
+
console.log(` Project: ${PROJECT_ROOT}`);
|
|
308
|
+
console.log(` Config: ${CONFIG_RELATIVE}`);
|
|
309
|
+
console.log(` Tools: ${toolKeys.join(", ")}`);
|
|
310
|
+
console.log(` Latest: ${LATEST_VERSION_COMMAND}`);
|
|
311
|
+
console.log("");
|
|
312
|
+
|
|
313
|
+
if (!linkedConfig) {
|
|
314
|
+
console.log("No repository is linked yet.");
|
|
315
|
+
console.log(`Run: heymark ${COMMAND_LINK} <github-repo-url>`);
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
const cachePath = path.join(
|
|
320
|
+
PROJECT_ROOT,
|
|
321
|
+
CACHE_DIR_NAME,
|
|
322
|
+
sanitizeRepoName(linkedConfig.repoUrl)
|
|
323
|
+
);
|
|
324
|
+
const cacheState = fs.existsSync(cachePath) ? "ready" : "not-fetched";
|
|
325
|
+
|
|
326
|
+
console.log("Linked repository:");
|
|
327
|
+
console.log(` repoUrl: ${linkedConfig.repoUrl}`);
|
|
328
|
+
console.log(` branch: ${linkedConfig.branch || DEFAULT_BRANCH}`);
|
|
329
|
+
console.log(` folder: ${linkedConfig.folder || "(root)"}`);
|
|
330
|
+
console.log(` cache: ${cacheState}`);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
function main() {
|
|
334
|
+
const tools = discoverTools();
|
|
335
|
+
const args = process.argv.slice(2);
|
|
336
|
+
|
|
337
|
+
if (args.length === 0) {
|
|
338
|
+
runStatus(tools);
|
|
339
|
+
return;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
const command = args[0];
|
|
343
|
+
const commandArgs = args.slice(1);
|
|
344
|
+
|
|
345
|
+
if (command === COMMAND_LINK) {
|
|
346
|
+
runLink(commandArgs);
|
|
347
|
+
return;
|
|
348
|
+
}
|
|
349
|
+
if (command === COMMAND_SYNC) {
|
|
350
|
+
runSync(commandArgs, tools);
|
|
351
|
+
return;
|
|
352
|
+
}
|
|
353
|
+
if (command === COMMAND_CLEAN) {
|
|
354
|
+
runClean(commandArgs, tools);
|
|
355
|
+
return;
|
|
356
|
+
}
|
|
357
|
+
if (command === COMMAND_STATUS) {
|
|
358
|
+
if (commandArgs.length > 0) {
|
|
359
|
+
exitWithError("status does not accept arguments.");
|
|
360
|
+
}
|
|
361
|
+
runStatus(tools);
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
364
|
+
if (command === COMMAND_HELP) {
|
|
365
|
+
if (commandArgs.length > 0) {
|
|
366
|
+
exitWithError("help does not accept arguments.");
|
|
367
|
+
}
|
|
368
|
+
showHelp(tools);
|
|
369
|
+
return;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
exitWithError(`Unknown command: ${command}`, ["Use 'heymark help' for usage information."]);
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
main();
|
package/scripts/lib/config.js
CHANGED
|
@@ -9,7 +9,7 @@ const DEFAULT_BRANCH = "main";
|
|
|
9
9
|
const CONFIG_RELATIVE = path.join(CONFIG_DIR, CONFIG_FILENAME);
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
|
-
* @typedef {{
|
|
12
|
+
* @typedef {{ repoUrl: string, branch?: string, folder?: string }} THeymarkConfig
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
15
|
/**
|
|
@@ -24,35 +24,51 @@ function getConfigPath(projectRoot) {
|
|
|
24
24
|
/**
|
|
25
25
|
* Normalize and validate config payload.
|
|
26
26
|
* @param {unknown} value
|
|
27
|
-
* @returns {
|
|
27
|
+
* @returns {THeymarkConfig | null}
|
|
28
28
|
*/
|
|
29
29
|
function normalizeConfig(value) {
|
|
30
30
|
if (!value || typeof value !== "object") {
|
|
31
31
|
return null;
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
-
const raw =
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
34
|
+
const raw = /** @type {{
|
|
35
|
+
* repoUrl?: unknown,
|
|
36
|
+
* branch?: unknown,
|
|
37
|
+
* folder?: unknown,
|
|
38
|
+
* rulesSource?: unknown,
|
|
39
|
+
* rulesSourceDir?: unknown
|
|
40
|
+
* }} */ (value);
|
|
41
|
+
|
|
42
|
+
const repoUrl =
|
|
43
|
+
typeof raw.repoUrl === "string" && raw.repoUrl.trim()
|
|
44
|
+
? raw.repoUrl.trim()
|
|
45
|
+
: typeof raw.rulesSource === "string" && raw.rulesSource.trim()
|
|
46
|
+
? raw.rulesSource.trim()
|
|
47
|
+
: "";
|
|
48
|
+
|
|
49
|
+
if (!repoUrl) {
|
|
39
50
|
return null;
|
|
40
51
|
}
|
|
41
52
|
|
|
42
53
|
return {
|
|
43
|
-
|
|
54
|
+
repoUrl,
|
|
44
55
|
branch:
|
|
45
56
|
typeof raw.branch === "string" && raw.branch.trim()
|
|
46
57
|
? raw.branch.trim()
|
|
47
58
|
: DEFAULT_BRANCH,
|
|
48
|
-
|
|
59
|
+
folder:
|
|
60
|
+
typeof raw.folder === "string"
|
|
61
|
+
? raw.folder.trim()
|
|
62
|
+
: typeof raw.rulesSourceDir === "string"
|
|
63
|
+
? raw.rulesSourceDir.trim()
|
|
64
|
+
: "",
|
|
49
65
|
};
|
|
50
66
|
}
|
|
51
67
|
|
|
52
68
|
/**
|
|
53
69
|
* Read config from project root.
|
|
54
70
|
* @param {string} projectRoot
|
|
55
|
-
* @returns {
|
|
71
|
+
* @returns {THeymarkConfig | null}
|
|
56
72
|
*/
|
|
57
73
|
function loadConfig(projectRoot) {
|
|
58
74
|
const configPath = getConfigPath(projectRoot);
|
|
@@ -71,7 +87,7 @@ function loadConfig(projectRoot) {
|
|
|
71
87
|
/**
|
|
72
88
|
* Create initial config file in .heymark/config.json.
|
|
73
89
|
* @param {string} projectRoot
|
|
74
|
-
* @param {
|
|
90
|
+
* @param {THeymarkConfig} config
|
|
75
91
|
* @returns {string}
|
|
76
92
|
*/
|
|
77
93
|
function writeConfig(projectRoot, config) {
|
|
@@ -87,11 +103,11 @@ function writeConfig(projectRoot, config) {
|
|
|
87
103
|
|
|
88
104
|
const configPath = path.join(configDir, CONFIG_FILENAME);
|
|
89
105
|
const toWrite = {
|
|
90
|
-
|
|
106
|
+
repoUrl: normalized.repoUrl,
|
|
91
107
|
branch: normalized.branch || DEFAULT_BRANCH,
|
|
92
108
|
};
|
|
93
|
-
if (normalized.
|
|
94
|
-
toWrite.
|
|
109
|
+
if (normalized.folder) {
|
|
110
|
+
toWrite.folder = normalized.folder;
|
|
95
111
|
}
|
|
96
112
|
|
|
97
113
|
fs.writeFileSync(configPath, JSON.stringify(toWrite, null, 2), "utf8");
|
package/scripts/lib/repo.js
CHANGED
|
@@ -30,24 +30,24 @@ function sanitizeRepoName(repoUrl) {
|
|
|
30
30
|
return normalized.replace(/[^a-zA-Z0-9._-]/g, "-") || "repo";
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
-
function
|
|
33
|
+
function cloneLinkedRepo(projectRoot, clonePath, branch, repoUrl) {
|
|
34
34
|
execSync(`git clone --depth 1 --branch "${branch}" "${repoUrl}" "${clonePath}"`, {
|
|
35
35
|
stdio: "inherit",
|
|
36
36
|
cwd: projectRoot,
|
|
37
37
|
});
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
-
function
|
|
40
|
+
function updateLinkedRepo(clonePath, branch) {
|
|
41
41
|
execSync(`git fetch origin && git checkout --quiet . && git pull --quiet origin "${branch}"`, {
|
|
42
42
|
stdio: "pipe",
|
|
43
43
|
cwd: clonePath,
|
|
44
44
|
});
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
-
function
|
|
48
|
-
const rulesDir =
|
|
47
|
+
function resolveLinkedFolder(clonePath, folder) {
|
|
48
|
+
const rulesDir = folder ? path.join(clonePath, folder) : clonePath;
|
|
49
49
|
if (!fs.existsSync(rulesDir) || !fs.statSync(rulesDir).isDirectory()) {
|
|
50
|
-
console.error(`[Error] Rules directory not found in repo: ${
|
|
50
|
+
console.error(`[Error] Rules directory not found in repo: ${folder || "(root)"}`);
|
|
51
51
|
process.exit(1);
|
|
52
52
|
}
|
|
53
53
|
return rulesDir;
|
|
@@ -57,13 +57,13 @@ function resolveRulesDirectory(clonePath, rulesSourceDir) {
|
|
|
57
57
|
* Clone or update remote repository and return local rules directory.
|
|
58
58
|
* Private repositories require user git credentials (SSH key or token).
|
|
59
59
|
* @param {string} projectRoot
|
|
60
|
-
* @param {{
|
|
60
|
+
* @param {{ repoUrl: string, branch?: string, folder?: string }} config
|
|
61
61
|
* @returns {string}
|
|
62
62
|
*/
|
|
63
|
-
function
|
|
64
|
-
const repoUrl = config.
|
|
63
|
+
function getLinkedRulesDir(projectRoot, config) {
|
|
64
|
+
const repoUrl = config.repoUrl;
|
|
65
65
|
const branch = config.branch || DEFAULT_BRANCH;
|
|
66
|
-
const
|
|
66
|
+
const folder = config.folder || "";
|
|
67
67
|
|
|
68
68
|
const cacheBase = path.join(projectRoot, CACHE_DIR_NAME);
|
|
69
69
|
const repoName = sanitizeRepoName(repoUrl);
|
|
@@ -72,26 +72,26 @@ function getRulesDirFromRepo(projectRoot, config) {
|
|
|
72
72
|
if (!fs.existsSync(clonePath)) {
|
|
73
73
|
fs.mkdirSync(cacheBase, { recursive: true });
|
|
74
74
|
try {
|
|
75
|
-
|
|
75
|
+
cloneLinkedRepo(projectRoot, clonePath, branch, repoUrl);
|
|
76
76
|
} catch {
|
|
77
77
|
console.error("[Error] Failed to clone rules repository.");
|
|
78
78
|
console.error(" For private repos, ensure you have access (SSH key or HTTPS token).");
|
|
79
|
-
console.error(" Example: heymark
|
|
79
|
+
console.error(" Example: heymark link https://github.com/org/repo.git");
|
|
80
80
|
process.exit(1);
|
|
81
81
|
}
|
|
82
82
|
} else {
|
|
83
83
|
try {
|
|
84
|
-
|
|
84
|
+
updateLinkedRepo(clonePath, branch);
|
|
85
85
|
} catch {
|
|
86
86
|
// Continue with cached clone when fetch or pull fails.
|
|
87
87
|
}
|
|
88
88
|
}
|
|
89
89
|
|
|
90
|
-
return
|
|
90
|
+
return resolveLinkedFolder(clonePath, folder);
|
|
91
91
|
}
|
|
92
92
|
|
|
93
93
|
module.exports = {
|
|
94
94
|
CACHE_DIR_NAME,
|
|
95
|
-
|
|
95
|
+
getLinkedRulesDir,
|
|
96
96
|
sanitizeRepoName,
|
|
97
97
|
};
|
|
@@ -38,16 +38,12 @@ module.exports = {
|
|
|
38
38
|
},
|
|
39
39
|
|
|
40
40
|
clean(ruleNames, projectRoot) {
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
const skillDir = getSkillDir(projectRoot, ruleName);
|
|
45
|
-
if (fs.existsSync(skillDir)) {
|
|
46
|
-
fs.rmSync(skillDir, { recursive: true });
|
|
47
|
-
cleaned.push(path.join(SKILLS_DIR, ruleName));
|
|
48
|
-
}
|
|
41
|
+
const skillsDirPath = path.join(projectRoot, SKILLS_DIR);
|
|
42
|
+
if (!fs.existsSync(skillsDirPath)) {
|
|
43
|
+
return [];
|
|
49
44
|
}
|
|
50
45
|
|
|
51
|
-
|
|
46
|
+
fs.rmSync(skillsDirPath, { recursive: true, force: true });
|
|
47
|
+
return [SKILLS_DIR];
|
|
52
48
|
},
|
|
53
49
|
};
|
package/scripts/tools/claude.js
CHANGED
|
@@ -38,16 +38,12 @@ module.exports = {
|
|
|
38
38
|
},
|
|
39
39
|
|
|
40
40
|
clean(ruleNames, projectRoot) {
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
const skillDir = getSkillDir(projectRoot, ruleName);
|
|
45
|
-
if (fs.existsSync(skillDir)) {
|
|
46
|
-
fs.rmSync(skillDir, { recursive: true });
|
|
47
|
-
cleaned.push(path.join(SKILLS_DIR, ruleName));
|
|
48
|
-
}
|
|
41
|
+
const skillsDirPath = path.join(projectRoot, SKILLS_DIR);
|
|
42
|
+
if (!fs.existsSync(skillsDirPath)) {
|
|
43
|
+
return [];
|
|
49
44
|
}
|
|
50
45
|
|
|
51
|
-
|
|
46
|
+
fs.rmSync(skillsDirPath, { recursive: true, force: true });
|
|
47
|
+
return [SKILLS_DIR];
|
|
52
48
|
},
|
|
53
49
|
};
|
package/scripts/tools/codex.js
CHANGED
|
@@ -38,16 +38,12 @@ module.exports = {
|
|
|
38
38
|
},
|
|
39
39
|
|
|
40
40
|
clean(ruleNames, projectRoot) {
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
const skillDir = getSkillDir(projectRoot, ruleName);
|
|
45
|
-
if (fs.existsSync(skillDir)) {
|
|
46
|
-
fs.rmSync(skillDir, { recursive: true });
|
|
47
|
-
cleaned.push(path.join(SKILLS_DIR, ruleName));
|
|
48
|
-
}
|
|
41
|
+
const skillsDirPath = path.join(projectRoot, SKILLS_DIR);
|
|
42
|
+
if (!fs.existsSync(skillsDirPath)) {
|
|
43
|
+
return [];
|
|
49
44
|
}
|
|
50
45
|
|
|
51
|
-
|
|
46
|
+
fs.rmSync(skillsDirPath, { recursive: true, force: true });
|
|
47
|
+
return [SKILLS_DIR];
|
|
52
48
|
},
|
|
53
49
|
};
|
package/scripts/tools/copilot.js
CHANGED
|
@@ -50,16 +50,12 @@ module.exports = {
|
|
|
50
50
|
},
|
|
51
51
|
|
|
52
52
|
clean(ruleNames, projectRoot) {
|
|
53
|
-
const
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
const filePath = getInstructionPath(projectRoot, ruleName);
|
|
57
|
-
if (fs.existsSync(filePath)) {
|
|
58
|
-
fs.unlinkSync(filePath);
|
|
59
|
-
cleaned.push(path.join(INSTRUCTIONS_DIR, `${ruleName}${FILE_SUFFIX}`));
|
|
60
|
-
}
|
|
53
|
+
const instructionsDirPath = path.join(projectRoot, INSTRUCTIONS_DIR);
|
|
54
|
+
if (!fs.existsSync(instructionsDirPath)) {
|
|
55
|
+
return [];
|
|
61
56
|
}
|
|
62
57
|
|
|
63
|
-
|
|
58
|
+
fs.rmSync(instructionsDirPath, { recursive: true, force: true });
|
|
59
|
+
return [INSTRUCTIONS_DIR];
|
|
64
60
|
},
|
|
65
61
|
};
|
package/scripts/tools/cursor.js
CHANGED
|
@@ -37,16 +37,12 @@ module.exports = {
|
|
|
37
37
|
},
|
|
38
38
|
|
|
39
39
|
clean(ruleNames, projectRoot) {
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
const filePath = getRulePath(projectRoot, ruleName);
|
|
44
|
-
if (fs.existsSync(filePath)) {
|
|
45
|
-
fs.unlinkSync(filePath);
|
|
46
|
-
cleaned.push(path.join(RULES_DIR, `${ruleName}${FILE_SUFFIX}`));
|
|
47
|
-
}
|
|
40
|
+
const rulesDirPath = path.join(projectRoot, RULES_DIR);
|
|
41
|
+
if (!fs.existsSync(rulesDirPath)) {
|
|
42
|
+
return [];
|
|
48
43
|
}
|
|
49
44
|
|
|
50
|
-
|
|
45
|
+
fs.rmSync(rulesDirPath, { recursive: true, force: true });
|
|
46
|
+
return [RULES_DIR];
|
|
51
47
|
},
|
|
52
48
|
};
|