rulesync 7.19.0 → 7.21.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.md +1 -0
- package/dist/{chunk-J2ZF4SHC.js → chunk-UYWCICY6.js} +893 -701
- package/dist/cli/index.cjs +1313 -1044
- package/dist/cli/index.js +332 -257
- package/dist/index.cjs +750 -651
- package/dist/index.d.cts +3 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.js +7 -7
- package/package.json +2 -3
package/dist/cli/index.js
CHANGED
|
@@ -7,10 +7,12 @@ import {
|
|
|
7
7
|
CLIError,
|
|
8
8
|
CommandsProcessor,
|
|
9
9
|
ConfigResolver,
|
|
10
|
+
ConsoleLogger,
|
|
10
11
|
ErrorCodes,
|
|
11
12
|
FETCH_CONCURRENCY_LIMIT,
|
|
12
13
|
HooksProcessor,
|
|
13
14
|
IgnoreProcessor,
|
|
15
|
+
JsonLogger,
|
|
14
16
|
MAX_FILE_SIZE,
|
|
15
17
|
McpProcessor,
|
|
16
18
|
RULESYNC_AIIGNORE_FILE_NAME,
|
|
@@ -48,7 +50,6 @@ import {
|
|
|
48
50
|
SubagentsProcessor,
|
|
49
51
|
checkPathTraversal,
|
|
50
52
|
checkRulesyncDirExists,
|
|
51
|
-
createLogger,
|
|
52
53
|
createTempDirectory,
|
|
53
54
|
directoryExists,
|
|
54
55
|
ensureDir,
|
|
@@ -62,18 +63,20 @@ import {
|
|
|
62
63
|
importFromTool,
|
|
63
64
|
isSymlink,
|
|
64
65
|
listDirectoryFiles,
|
|
65
|
-
logger,
|
|
66
66
|
readFileContent,
|
|
67
67
|
removeDirectory,
|
|
68
68
|
removeFile,
|
|
69
69
|
removeTempDirectory,
|
|
70
70
|
stringifyFrontmatter,
|
|
71
71
|
writeFileContent
|
|
72
|
-
} from "../chunk-
|
|
72
|
+
} from "../chunk-UYWCICY6.js";
|
|
73
73
|
|
|
74
74
|
// src/cli/index.ts
|
|
75
75
|
import { Command } from "commander";
|
|
76
76
|
|
|
77
|
+
// src/utils/parse-comma-separated-list.ts
|
|
78
|
+
var parseCommaSeparatedList = (value) => value.split(",").map((s) => s.trim()).filter(Boolean);
|
|
79
|
+
|
|
77
80
|
// src/lib/fetch.ts
|
|
78
81
|
import { join } from "path";
|
|
79
82
|
import { Semaphore } from "es-toolkit/promise";
|
|
@@ -139,13 +142,14 @@ var GitHubClientError = class extends Error {
|
|
|
139
142
|
this.name = "GitHubClientError";
|
|
140
143
|
}
|
|
141
144
|
};
|
|
142
|
-
function logGitHubAuthHints(
|
|
143
|
-
|
|
145
|
+
function logGitHubAuthHints(params) {
|
|
146
|
+
const { error, logger: logger5 } = params;
|
|
147
|
+
logger5.error(`GitHub API Error: ${error.message}`);
|
|
144
148
|
if (error.statusCode === 401 || error.statusCode === 403) {
|
|
145
|
-
|
|
149
|
+
logger5.info(
|
|
146
150
|
"Tip: Set GITHUB_TOKEN or GH_TOKEN environment variable for private repositories or better rate limits."
|
|
147
151
|
);
|
|
148
|
-
|
|
152
|
+
logger5.info(
|
|
149
153
|
"Tip: If you use GitHub CLI, you can use `GITHUB_TOKEN=$(gh auth token) rulesync fetch ...`"
|
|
150
154
|
);
|
|
151
155
|
}
|
|
@@ -562,38 +566,38 @@ async function processFeatureConversion(params) {
|
|
|
562
566
|
return { paths };
|
|
563
567
|
}
|
|
564
568
|
async function convertFetchedFilesToRulesync(params) {
|
|
565
|
-
const { tempDir, outputDir, target, features } = params;
|
|
569
|
+
const { tempDir, outputDir, target, features, logger: logger5 } = params;
|
|
566
570
|
const convertedPaths = [];
|
|
567
571
|
const featureConfigs = [
|
|
568
572
|
{
|
|
569
573
|
feature: "rules",
|
|
570
574
|
getTargets: () => RulesProcessor.getToolTargets({ global: false }),
|
|
571
|
-
createProcessor: () => new RulesProcessor({ baseDir: tempDir, toolTarget: target, global: false })
|
|
575
|
+
createProcessor: () => new RulesProcessor({ baseDir: tempDir, toolTarget: target, global: false, logger: logger5 })
|
|
572
576
|
},
|
|
573
577
|
{
|
|
574
578
|
feature: "commands",
|
|
575
579
|
getTargets: () => CommandsProcessor.getToolTargets({ global: false, includeSimulated: false }),
|
|
576
|
-
createProcessor: () => new CommandsProcessor({ baseDir: tempDir, toolTarget: target, global: false })
|
|
580
|
+
createProcessor: () => new CommandsProcessor({ baseDir: tempDir, toolTarget: target, global: false, logger: logger5 })
|
|
577
581
|
},
|
|
578
582
|
{
|
|
579
583
|
feature: "subagents",
|
|
580
584
|
getTargets: () => SubagentsProcessor.getToolTargets({ global: false, includeSimulated: false }),
|
|
581
|
-
createProcessor: () => new SubagentsProcessor({ baseDir: tempDir, toolTarget: target, global: false })
|
|
585
|
+
createProcessor: () => new SubagentsProcessor({ baseDir: tempDir, toolTarget: target, global: false, logger: logger5 })
|
|
582
586
|
},
|
|
583
587
|
{
|
|
584
588
|
feature: "ignore",
|
|
585
589
|
getTargets: () => IgnoreProcessor.getToolTargets(),
|
|
586
|
-
createProcessor: () => new IgnoreProcessor({ baseDir: tempDir, toolTarget: target })
|
|
590
|
+
createProcessor: () => new IgnoreProcessor({ baseDir: tempDir, toolTarget: target, logger: logger5 })
|
|
587
591
|
},
|
|
588
592
|
{
|
|
589
593
|
feature: "mcp",
|
|
590
594
|
getTargets: () => McpProcessor.getToolTargets({ global: false }),
|
|
591
|
-
createProcessor: () => new McpProcessor({ baseDir: tempDir, toolTarget: target, global: false })
|
|
595
|
+
createProcessor: () => new McpProcessor({ baseDir: tempDir, toolTarget: target, global: false, logger: logger5 })
|
|
592
596
|
},
|
|
593
597
|
{
|
|
594
598
|
feature: "hooks",
|
|
595
599
|
getTargets: () => HooksProcessor.getToolTargets({ global: false }),
|
|
596
|
-
createProcessor: () => new HooksProcessor({ baseDir: tempDir, toolTarget: target, global: false })
|
|
600
|
+
createProcessor: () => new HooksProcessor({ baseDir: tempDir, toolTarget: target, global: false, logger: logger5 })
|
|
597
601
|
}
|
|
598
602
|
];
|
|
599
603
|
for (const config of featureConfigs) {
|
|
@@ -609,7 +613,7 @@ async function convertFetchedFilesToRulesync(params) {
|
|
|
609
613
|
convertedPaths.push(...result.paths);
|
|
610
614
|
}
|
|
611
615
|
if (features.includes("skills")) {
|
|
612
|
-
|
|
616
|
+
logger5.debug(
|
|
613
617
|
"Skills conversion is not yet supported in fetch command. Use import command instead."
|
|
614
618
|
);
|
|
615
619
|
}
|
|
@@ -638,7 +642,7 @@ function isNotFoundError(error) {
|
|
|
638
642
|
return false;
|
|
639
643
|
}
|
|
640
644
|
async function fetchFiles(params) {
|
|
641
|
-
const { source, options = {}, baseDir = process.cwd() } = params;
|
|
645
|
+
const { source, options = {}, baseDir = process.cwd(), logger: logger5 } = params;
|
|
642
646
|
const parsed = parseSource(source);
|
|
643
647
|
if (parsed.provider === "gitlab") {
|
|
644
648
|
throw new Error(
|
|
@@ -657,7 +661,7 @@ async function fetchFiles(params) {
|
|
|
657
661
|
});
|
|
658
662
|
const token = GitHubClient.resolveToken(options.token);
|
|
659
663
|
const client = new GitHubClient({ token });
|
|
660
|
-
|
|
664
|
+
logger5.debug(`Validating repository: ${parsed.owner}/${parsed.repo}`);
|
|
661
665
|
const isValid = await client.validateRepository(parsed.owner, parsed.repo);
|
|
662
666
|
if (!isValid) {
|
|
663
667
|
throw new GitHubClientError(
|
|
@@ -666,7 +670,7 @@ async function fetchFiles(params) {
|
|
|
666
670
|
);
|
|
667
671
|
}
|
|
668
672
|
const ref = resolvedRef ?? await client.getDefaultBranch(parsed.owner, parsed.repo);
|
|
669
|
-
|
|
673
|
+
logger5.debug(`Using ref: ${ref}`);
|
|
670
674
|
if (isToolTarget(target)) {
|
|
671
675
|
return fetchAndConvertToolFiles({
|
|
672
676
|
client,
|
|
@@ -677,7 +681,8 @@ async function fetchFiles(params) {
|
|
|
677
681
|
target,
|
|
678
682
|
outputDir,
|
|
679
683
|
baseDir,
|
|
680
|
-
conflictStrategy
|
|
684
|
+
conflictStrategy,
|
|
685
|
+
logger: logger5
|
|
681
686
|
});
|
|
682
687
|
}
|
|
683
688
|
const semaphore = new Semaphore(FETCH_CONCURRENCY_LIMIT);
|
|
@@ -688,10 +693,11 @@ async function fetchFiles(params) {
|
|
|
688
693
|
basePath: resolvedPath,
|
|
689
694
|
ref,
|
|
690
695
|
enabledFeatures,
|
|
691
|
-
semaphore
|
|
696
|
+
semaphore,
|
|
697
|
+
logger: logger5
|
|
692
698
|
});
|
|
693
699
|
if (filesToFetch.length === 0) {
|
|
694
|
-
|
|
700
|
+
logger5.warn(`No files found matching enabled features: ${enabledFeatures.join(", ")}`);
|
|
695
701
|
return {
|
|
696
702
|
source: `${parsed.owner}/${parsed.repo}`,
|
|
697
703
|
ref,
|
|
@@ -714,7 +720,7 @@ async function fetchFiles(params) {
|
|
|
714
720
|
const localPath = join(outputBasePath, relativePath);
|
|
715
721
|
const exists = await fileExists(localPath);
|
|
716
722
|
if (exists && conflictStrategy === "skip") {
|
|
717
|
-
|
|
723
|
+
logger5.debug(`Skipping existing file: ${relativePath}`);
|
|
718
724
|
return { relativePath, status: "skipped" };
|
|
719
725
|
}
|
|
720
726
|
const content = await withSemaphore(
|
|
@@ -723,7 +729,7 @@ async function fetchFiles(params) {
|
|
|
723
729
|
);
|
|
724
730
|
await writeFileContent(localPath, content);
|
|
725
731
|
const status = exists ? "overwritten" : "created";
|
|
726
|
-
|
|
732
|
+
logger5.debug(`Wrote: ${relativePath} (${status})`);
|
|
727
733
|
return { relativePath, status };
|
|
728
734
|
})
|
|
729
735
|
);
|
|
@@ -738,7 +744,7 @@ async function fetchFiles(params) {
|
|
|
738
744
|
return summary;
|
|
739
745
|
}
|
|
740
746
|
async function collectFeatureFiles(params) {
|
|
741
|
-
const { client, owner, repo, basePath, ref, enabledFeatures, semaphore } = params;
|
|
747
|
+
const { client, owner, repo, basePath, ref, enabledFeatures, semaphore, logger: logger5 } = params;
|
|
742
748
|
const dirCache = /* @__PURE__ */ new Map();
|
|
743
749
|
async function getCachedDirectory(path2) {
|
|
744
750
|
let promise = dirCache.get(path2);
|
|
@@ -771,7 +777,7 @@ async function collectFeatureFiles(params) {
|
|
|
771
777
|
}
|
|
772
778
|
} catch (error) {
|
|
773
779
|
if (isNotFoundError(error)) {
|
|
774
|
-
|
|
780
|
+
logger5.debug(`File not found: ${fullPath}`);
|
|
775
781
|
} else {
|
|
776
782
|
throw error;
|
|
777
783
|
}
|
|
@@ -796,7 +802,7 @@ async function collectFeatureFiles(params) {
|
|
|
796
802
|
}
|
|
797
803
|
} catch (error) {
|
|
798
804
|
if (isNotFoundError(error)) {
|
|
799
|
-
|
|
805
|
+
logger5.debug(`Feature not found: ${fullPath}`);
|
|
800
806
|
return collected;
|
|
801
807
|
}
|
|
802
808
|
throw error;
|
|
@@ -816,10 +822,11 @@ async function fetchAndConvertToolFiles(params) {
|
|
|
816
822
|
target,
|
|
817
823
|
outputDir,
|
|
818
824
|
baseDir,
|
|
819
|
-
conflictStrategy: _conflictStrategy
|
|
825
|
+
conflictStrategy: _conflictStrategy,
|
|
826
|
+
logger: logger5
|
|
820
827
|
} = params;
|
|
821
828
|
const tempDir = await createTempDirectory();
|
|
822
|
-
|
|
829
|
+
logger5.debug(`Created temp directory: ${tempDir}`);
|
|
823
830
|
const semaphore = new Semaphore(FETCH_CONCURRENCY_LIMIT);
|
|
824
831
|
try {
|
|
825
832
|
const filesToFetch = await collectFeatureFiles({
|
|
@@ -829,10 +836,11 @@ async function fetchAndConvertToolFiles(params) {
|
|
|
829
836
|
basePath: resolvedPath,
|
|
830
837
|
ref,
|
|
831
838
|
enabledFeatures,
|
|
832
|
-
semaphore
|
|
839
|
+
semaphore,
|
|
840
|
+
logger: logger5
|
|
833
841
|
});
|
|
834
842
|
if (filesToFetch.length === 0) {
|
|
835
|
-
|
|
843
|
+
logger5.warn(`No files found matching enabled features: ${enabledFeatures.join(", ")}`);
|
|
836
844
|
return {
|
|
837
845
|
source: `${parsed.owner}/${parsed.repo}`,
|
|
838
846
|
ref,
|
|
@@ -859,7 +867,7 @@ async function fetchAndConvertToolFiles(params) {
|
|
|
859
867
|
() => client.getFileContent(parsed.owner, parsed.repo, remotePath, ref)
|
|
860
868
|
);
|
|
861
869
|
await writeFileContent(localPath, content);
|
|
862
|
-
|
|
870
|
+
logger5.debug(`Fetched to temp: ${toolRelativePath}`);
|
|
863
871
|
})
|
|
864
872
|
);
|
|
865
873
|
const outputBasePath = join(baseDir, outputDir);
|
|
@@ -867,13 +875,14 @@ async function fetchAndConvertToolFiles(params) {
|
|
|
867
875
|
tempDir,
|
|
868
876
|
outputDir: outputBasePath,
|
|
869
877
|
target,
|
|
870
|
-
features: enabledFeatures
|
|
878
|
+
features: enabledFeatures,
|
|
879
|
+
logger: logger5
|
|
871
880
|
});
|
|
872
881
|
const results = convertedPaths.map((relativePath) => ({
|
|
873
882
|
relativePath,
|
|
874
883
|
status: "created"
|
|
875
884
|
}));
|
|
876
|
-
|
|
885
|
+
logger5.debug(`Converted ${converted} files from ${target} format to rulesync format`);
|
|
877
886
|
return {
|
|
878
887
|
source: `${parsed.owner}/${parsed.repo}`,
|
|
879
888
|
ref,
|
|
@@ -980,29 +989,30 @@ function formatFetchSummary(summary) {
|
|
|
980
989
|
}
|
|
981
990
|
|
|
982
991
|
// src/cli/commands/fetch.ts
|
|
983
|
-
async function fetchCommand(
|
|
992
|
+
async function fetchCommand(logger5, options) {
|
|
984
993
|
const { source, ...fetchOptions } = options;
|
|
985
|
-
|
|
994
|
+
logger5.debug(`Fetching files from ${source}...`);
|
|
986
995
|
try {
|
|
987
996
|
const summary = await fetchFiles({
|
|
988
997
|
source,
|
|
989
|
-
options: fetchOptions
|
|
998
|
+
options: fetchOptions,
|
|
999
|
+
logger: logger5
|
|
990
1000
|
});
|
|
991
|
-
if (
|
|
1001
|
+
if (logger5.jsonMode) {
|
|
992
1002
|
const createdFiles = summary.files.filter((f) => f.status === "created").map((f) => f.relativePath);
|
|
993
1003
|
const overwrittenFiles = summary.files.filter((f) => f.status === "overwritten").map((f) => f.relativePath);
|
|
994
1004
|
const skippedFiles = summary.files.filter((f) => f.status === "skipped").map((f) => f.relativePath);
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1005
|
+
logger5.captureData("source", source);
|
|
1006
|
+
logger5.captureData("path", fetchOptions.path);
|
|
1007
|
+
logger5.captureData("created", createdFiles);
|
|
1008
|
+
logger5.captureData("overwritten", overwrittenFiles);
|
|
1009
|
+
logger5.captureData("skipped", skippedFiles);
|
|
1010
|
+
logger5.captureData("totalFetched", summary.created + summary.overwritten + summary.skipped);
|
|
1001
1011
|
}
|
|
1002
1012
|
const output = formatFetchSummary(summary);
|
|
1003
|
-
|
|
1013
|
+
logger5.success(output);
|
|
1004
1014
|
if (summary.created + summary.overwritten === 0 && summary.skipped === 0) {
|
|
1005
|
-
|
|
1015
|
+
logger5.warn("No files were fetched.");
|
|
1006
1016
|
}
|
|
1007
1017
|
} catch (error) {
|
|
1008
1018
|
if (error instanceof GitHubClientError) {
|
|
@@ -1019,55 +1029,55 @@ function calculateTotalCount(result) {
|
|
|
1019
1029
|
}
|
|
1020
1030
|
|
|
1021
1031
|
// src/cli/commands/generate.ts
|
|
1022
|
-
function logFeatureResult(
|
|
1032
|
+
function logFeatureResult(logger5, params) {
|
|
1023
1033
|
const { count, paths, featureName, isPreview, modePrefix } = params;
|
|
1024
1034
|
if (count > 0) {
|
|
1025
1035
|
if (isPreview) {
|
|
1026
|
-
|
|
1036
|
+
logger5.info(`${modePrefix} Would write ${count} ${featureName}`);
|
|
1027
1037
|
} else {
|
|
1028
|
-
|
|
1038
|
+
logger5.success(`Written ${count} ${featureName}`);
|
|
1029
1039
|
}
|
|
1030
1040
|
for (const p of paths) {
|
|
1031
|
-
|
|
1041
|
+
logger5.info(` ${p}`);
|
|
1032
1042
|
}
|
|
1033
1043
|
}
|
|
1034
1044
|
}
|
|
1035
|
-
async function generateCommand(
|
|
1045
|
+
async function generateCommand(logger5, options) {
|
|
1036
1046
|
const config = await ConfigResolver.resolve(options);
|
|
1037
1047
|
const check = config.getCheck();
|
|
1038
1048
|
const isPreview = config.isPreviewMode();
|
|
1039
1049
|
const modePrefix = isPreview ? "[DRY RUN]" : "";
|
|
1040
|
-
|
|
1050
|
+
logger5.debug("Generating files...");
|
|
1041
1051
|
if (!await checkRulesyncDirExists({ baseDir: process.cwd() })) {
|
|
1042
1052
|
throw new CLIError(
|
|
1043
1053
|
".rulesync directory not found. Run 'rulesync init' first.",
|
|
1044
1054
|
ErrorCodes.RULESYNC_DIR_NOT_FOUND
|
|
1045
1055
|
);
|
|
1046
1056
|
}
|
|
1047
|
-
|
|
1057
|
+
logger5.debug(`Base directories: ${config.getBaseDirs().join(", ")}`);
|
|
1048
1058
|
const features = config.getFeatures();
|
|
1049
1059
|
if (features.includes("ignore")) {
|
|
1050
|
-
|
|
1060
|
+
logger5.debug("Generating ignore files...");
|
|
1051
1061
|
}
|
|
1052
1062
|
if (features.includes("mcp")) {
|
|
1053
|
-
|
|
1063
|
+
logger5.debug("Generating MCP files...");
|
|
1054
1064
|
}
|
|
1055
1065
|
if (features.includes("commands")) {
|
|
1056
|
-
|
|
1066
|
+
logger5.debug("Generating command files...");
|
|
1057
1067
|
}
|
|
1058
1068
|
if (features.includes("subagents")) {
|
|
1059
|
-
|
|
1069
|
+
logger5.debug("Generating subagent files...");
|
|
1060
1070
|
}
|
|
1061
1071
|
if (features.includes("skills")) {
|
|
1062
|
-
|
|
1072
|
+
logger5.debug("Generating skill files...");
|
|
1063
1073
|
}
|
|
1064
1074
|
if (features.includes("hooks")) {
|
|
1065
|
-
|
|
1075
|
+
logger5.debug("Generating hooks...");
|
|
1066
1076
|
}
|
|
1067
1077
|
if (features.includes("rules")) {
|
|
1068
|
-
|
|
1078
|
+
logger5.debug("Generating rule files...");
|
|
1069
1079
|
}
|
|
1070
|
-
const result = await generate({ config });
|
|
1080
|
+
const result = await generate({ config, logger: logger5 });
|
|
1071
1081
|
const totalGenerated = calculateTotalCount(result);
|
|
1072
1082
|
const featureResults = {
|
|
1073
1083
|
ignore: { count: result.ignoreCount, paths: result.ignorePaths },
|
|
@@ -1088,7 +1098,7 @@ async function generateCommand(logger2, options) {
|
|
|
1088
1098
|
hooks: (count) => `${count === 1 ? "hooks file" : "hooks files"}`
|
|
1089
1099
|
};
|
|
1090
1100
|
for (const [feature, data] of Object.entries(featureResults)) {
|
|
1091
|
-
logFeatureResult(
|
|
1101
|
+
logFeatureResult(logger5, {
|
|
1092
1102
|
count: data.count,
|
|
1093
1103
|
paths: data.paths,
|
|
1094
1104
|
featureName: featureLabels[feature]?.(data.count) ?? feature,
|
|
@@ -1096,15 +1106,15 @@ async function generateCommand(logger2, options) {
|
|
|
1096
1106
|
modePrefix
|
|
1097
1107
|
});
|
|
1098
1108
|
}
|
|
1099
|
-
if (
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1109
|
+
if (logger5.jsonMode) {
|
|
1110
|
+
logger5.captureData("features", featureResults);
|
|
1111
|
+
logger5.captureData("totalFiles", totalGenerated);
|
|
1112
|
+
logger5.captureData("hasDiff", result.hasDiff);
|
|
1113
|
+
logger5.captureData("skills", result.skills ?? []);
|
|
1104
1114
|
}
|
|
1105
1115
|
if (totalGenerated === 0) {
|
|
1106
1116
|
const enabledFeatures = features.join(", ");
|
|
1107
|
-
|
|
1117
|
+
logger5.info(`\u2713 All files are up to date (${enabledFeatures})`);
|
|
1108
1118
|
return;
|
|
1109
1119
|
}
|
|
1110
1120
|
const parts = [];
|
|
@@ -1116,9 +1126,9 @@ async function generateCommand(logger2, options) {
|
|
|
1116
1126
|
if (result.skillsCount > 0) parts.push(`${result.skillsCount} skills`);
|
|
1117
1127
|
if (result.hooksCount > 0) parts.push(`${result.hooksCount} hooks`);
|
|
1118
1128
|
if (isPreview) {
|
|
1119
|
-
|
|
1129
|
+
logger5.info(`${modePrefix} Would write ${totalGenerated} file(s) total (${parts.join(" + ")})`);
|
|
1120
1130
|
} else {
|
|
1121
|
-
|
|
1131
|
+
logger5.success(`\u{1F389} All done! Written ${totalGenerated} file(s) total (${parts.join(" + ")})`);
|
|
1122
1132
|
}
|
|
1123
1133
|
if (check) {
|
|
1124
1134
|
if (result.hasDiff) {
|
|
@@ -1127,7 +1137,7 @@ async function generateCommand(logger2, options) {
|
|
|
1127
1137
|
ErrorCodes.GENERATION_FAILED
|
|
1128
1138
|
);
|
|
1129
1139
|
} else {
|
|
1130
|
-
|
|
1140
|
+
logger5.success("\u2713 All files are up to date.");
|
|
1131
1141
|
}
|
|
1132
1142
|
}
|
|
1133
1143
|
}
|
|
@@ -1201,6 +1211,8 @@ var GITIGNORE_ENTRY_REGISTRY = [
|
|
|
1201
1211
|
{ target: "copilot", feature: "skills", entry: "**/.github/skills/" },
|
|
1202
1212
|
{ target: "copilot", feature: "hooks", entry: "**/.github/hooks/" },
|
|
1203
1213
|
{ target: "copilot", feature: "mcp", entry: "**/.vscode/mcp.json" },
|
|
1214
|
+
// GitHub Copilot CLI
|
|
1215
|
+
{ target: "copilotcli", feature: "mcp", entry: "**/.copilot/mcp-config.json" },
|
|
1204
1216
|
// Junie
|
|
1205
1217
|
{ target: "junie", feature: "rules", entry: "**/.junie/guidelines.md" },
|
|
1206
1218
|
{ target: "junie", feature: "mcp", entry: "**/.junie/mcp.json" },
|
|
@@ -1263,46 +1275,47 @@ var isFeatureSelected = (feature, target, features) => {
|
|
|
1263
1275
|
if (targetFeatures.includes("*")) return true;
|
|
1264
1276
|
return targetFeatures.includes(feature);
|
|
1265
1277
|
};
|
|
1266
|
-
var warnInvalidTargets = (targets) => {
|
|
1278
|
+
var warnInvalidTargets = (targets, logger5) => {
|
|
1267
1279
|
const validTargets = new Set(ALL_TOOL_TARGETS_WITH_WILDCARD);
|
|
1268
1280
|
for (const target of targets) {
|
|
1269
1281
|
if (!validTargets.has(target)) {
|
|
1270
|
-
|
|
1282
|
+
logger5?.warn(
|
|
1271
1283
|
`Unknown target '${target}'. Valid targets: ${ALL_TOOL_TARGETS_WITH_WILDCARD.join(", ")}`
|
|
1272
1284
|
);
|
|
1273
1285
|
}
|
|
1274
1286
|
}
|
|
1275
1287
|
};
|
|
1276
|
-
var warnInvalidFeatures = (features) => {
|
|
1288
|
+
var warnInvalidFeatures = (features, logger5) => {
|
|
1277
1289
|
const validFeatures = new Set(ALL_FEATURES_WITH_WILDCARD);
|
|
1290
|
+
const warned = /* @__PURE__ */ new Set();
|
|
1291
|
+
const warnOnce = (feature) => {
|
|
1292
|
+
if (!validFeatures.has(feature) && !warned.has(feature)) {
|
|
1293
|
+
warned.add(feature);
|
|
1294
|
+
logger5?.warn(
|
|
1295
|
+
`Unknown feature '${feature}'. Valid features: ${ALL_FEATURES_WITH_WILDCARD.join(", ")}`
|
|
1296
|
+
);
|
|
1297
|
+
}
|
|
1298
|
+
};
|
|
1278
1299
|
if (Array.isArray(features)) {
|
|
1279
1300
|
for (const feature of features) {
|
|
1280
|
-
|
|
1281
|
-
logger.warn(
|
|
1282
|
-
`Unknown feature '${feature}'. Valid features: ${ALL_FEATURES_WITH_WILDCARD.join(", ")}`
|
|
1283
|
-
);
|
|
1284
|
-
}
|
|
1301
|
+
warnOnce(feature);
|
|
1285
1302
|
}
|
|
1286
1303
|
} else {
|
|
1287
1304
|
for (const targetFeatures of Object.values(features)) {
|
|
1288
1305
|
if (!targetFeatures) continue;
|
|
1289
1306
|
for (const feature of targetFeatures) {
|
|
1290
|
-
|
|
1291
|
-
logger.warn(
|
|
1292
|
-
`Unknown feature '${feature}'. Valid features: ${ALL_FEATURES_WITH_WILDCARD.join(", ")}`
|
|
1293
|
-
);
|
|
1294
|
-
}
|
|
1307
|
+
warnOnce(feature);
|
|
1295
1308
|
}
|
|
1296
1309
|
}
|
|
1297
1310
|
}
|
|
1298
1311
|
};
|
|
1299
1312
|
var filterGitignoreEntries = (params) => {
|
|
1300
|
-
const { targets, features } = params ?? {};
|
|
1313
|
+
const { targets, features, logger: logger5 } = params ?? {};
|
|
1301
1314
|
if (targets && targets.length > 0) {
|
|
1302
|
-
warnInvalidTargets(targets);
|
|
1315
|
+
warnInvalidTargets(targets, logger5);
|
|
1303
1316
|
}
|
|
1304
1317
|
if (features) {
|
|
1305
|
-
warnInvalidFeatures(features);
|
|
1318
|
+
warnInvalidFeatures(features, logger5);
|
|
1306
1319
|
}
|
|
1307
1320
|
const seen = /* @__PURE__ */ new Set();
|
|
1308
1321
|
const result = [];
|
|
@@ -1368,7 +1381,7 @@ var removeExistingRulesyncEntries = (content) => {
|
|
|
1368
1381
|
}
|
|
1369
1382
|
return result;
|
|
1370
1383
|
};
|
|
1371
|
-
var gitignoreCommand = async (
|
|
1384
|
+
var gitignoreCommand = async (logger5, options) => {
|
|
1372
1385
|
const gitignorePath = join2(process.cwd(), ".gitignore");
|
|
1373
1386
|
let gitignoreContent = "";
|
|
1374
1387
|
if (await fileExists(gitignorePath)) {
|
|
@@ -1377,7 +1390,8 @@ var gitignoreCommand = async (logger2, options) => {
|
|
|
1377
1390
|
const cleanedContent = removeExistingRulesyncEntries(gitignoreContent);
|
|
1378
1391
|
const filteredEntries = filterGitignoreEntries({
|
|
1379
1392
|
targets: options?.targets,
|
|
1380
|
-
features: options?.features
|
|
1393
|
+
features: options?.features,
|
|
1394
|
+
logger: logger5
|
|
1381
1395
|
});
|
|
1382
1396
|
const existingEntries = new Set(
|
|
1383
1397
|
gitignoreContent.split("\n").map((line) => line.trim()).filter((line) => line !== "" && !isRulesyncHeader(line))
|
|
@@ -1391,37 +1405,37 @@ ${rulesyncBlock}
|
|
|
1391
1405
|
` : `${rulesyncBlock}
|
|
1392
1406
|
`;
|
|
1393
1407
|
if (gitignoreContent === newContent) {
|
|
1394
|
-
if (
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1408
|
+
if (logger5.jsonMode) {
|
|
1409
|
+
logger5.captureData("entriesAdded", []);
|
|
1410
|
+
logger5.captureData("gitignorePath", gitignorePath);
|
|
1411
|
+
logger5.captureData("alreadyExisted", filteredEntries);
|
|
1398
1412
|
}
|
|
1399
|
-
|
|
1413
|
+
logger5.success(".gitignore is already up to date");
|
|
1400
1414
|
return;
|
|
1401
1415
|
}
|
|
1402
1416
|
await writeFileContent(gitignorePath, newContent);
|
|
1403
|
-
if (
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1417
|
+
if (logger5.jsonMode) {
|
|
1418
|
+
logger5.captureData("entriesAdded", entriesToAdd);
|
|
1419
|
+
logger5.captureData("gitignorePath", gitignorePath);
|
|
1420
|
+
logger5.captureData("alreadyExisted", alreadyExistedEntries);
|
|
1407
1421
|
}
|
|
1408
|
-
|
|
1422
|
+
logger5.success("Updated .gitignore with rulesync entries:");
|
|
1409
1423
|
for (const entry of filteredEntries) {
|
|
1410
|
-
|
|
1424
|
+
logger5.info(` ${entry}`);
|
|
1411
1425
|
}
|
|
1412
|
-
|
|
1413
|
-
|
|
1426
|
+
logger5.info("");
|
|
1427
|
+
logger5.info(
|
|
1414
1428
|
"\u{1F4A1} If you're using Google Antigravity, note that rules, workflows, and skills won't load if they're gitignored."
|
|
1415
1429
|
);
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1430
|
+
logger5.info(" You can add the following to .git/info/exclude instead:");
|
|
1431
|
+
logger5.info(" **/.agent/rules/");
|
|
1432
|
+
logger5.info(" **/.agent/workflows/");
|
|
1433
|
+
logger5.info(" **/.agent/skills/");
|
|
1434
|
+
logger5.info(" For more details: https://github.com/dyoshikawa/rulesync/issues/981");
|
|
1421
1435
|
};
|
|
1422
1436
|
|
|
1423
1437
|
// src/cli/commands/import.ts
|
|
1424
|
-
async function importCommand(
|
|
1438
|
+
async function importCommand(logger5, options) {
|
|
1425
1439
|
if (!options.targets) {
|
|
1426
1440
|
throw new CLIError("No tools found in --targets", ErrorCodes.IMPORT_FAILED);
|
|
1427
1441
|
}
|
|
@@ -1430,17 +1444,17 @@ async function importCommand(logger2, options) {
|
|
|
1430
1444
|
}
|
|
1431
1445
|
const config = await ConfigResolver.resolve(options);
|
|
1432
1446
|
const tool = config.getTargets()[0];
|
|
1433
|
-
|
|
1434
|
-
const result = await importFromTool({ config, tool });
|
|
1447
|
+
logger5.debug(`Importing files from ${tool}...`);
|
|
1448
|
+
const result = await importFromTool({ config, tool, logger: logger5 });
|
|
1435
1449
|
const totalImported = calculateTotalCount(result);
|
|
1436
1450
|
if (totalImported === 0) {
|
|
1437
1451
|
const enabledFeatures = config.getFeatures().join(", ");
|
|
1438
|
-
|
|
1452
|
+
logger5.warn(`No files imported for enabled features: ${enabledFeatures}`);
|
|
1439
1453
|
return;
|
|
1440
1454
|
}
|
|
1441
|
-
if (
|
|
1442
|
-
|
|
1443
|
-
|
|
1455
|
+
if (logger5.jsonMode) {
|
|
1456
|
+
logger5.captureData("tool", tool);
|
|
1457
|
+
logger5.captureData("features", {
|
|
1444
1458
|
rules: { count: result.rulesCount },
|
|
1445
1459
|
ignore: { count: result.ignoreCount },
|
|
1446
1460
|
mcp: { count: result.mcpCount },
|
|
@@ -1449,7 +1463,7 @@ async function importCommand(logger2, options) {
|
|
|
1449
1463
|
skills: { count: result.skillsCount },
|
|
1450
1464
|
hooks: { count: result.hooksCount }
|
|
1451
1465
|
});
|
|
1452
|
-
|
|
1466
|
+
logger5.captureData("totalFiles", totalImported);
|
|
1453
1467
|
}
|
|
1454
1468
|
const parts = [];
|
|
1455
1469
|
if (result.rulesCount > 0) parts.push(`${result.rulesCount} rules`);
|
|
@@ -1459,7 +1473,7 @@ async function importCommand(logger2, options) {
|
|
|
1459
1473
|
if (result.subagentsCount > 0) parts.push(`${result.subagentsCount} subagents`);
|
|
1460
1474
|
if (result.skillsCount > 0) parts.push(`${result.skillsCount} skills`);
|
|
1461
1475
|
if (result.hooksCount > 0) parts.push(`${result.hooksCount} hooks`);
|
|
1462
|
-
|
|
1476
|
+
logger5.success(`Imported ${totalImported} file(s) total (${parts.join(" + ")})`);
|
|
1463
1477
|
}
|
|
1464
1478
|
|
|
1465
1479
|
// src/lib/init.ts
|
|
@@ -1688,8 +1702,8 @@ async function writeIfNotExists(path2, content) {
|
|
|
1688
1702
|
}
|
|
1689
1703
|
|
|
1690
1704
|
// src/cli/commands/init.ts
|
|
1691
|
-
async function initCommand(
|
|
1692
|
-
|
|
1705
|
+
async function initCommand(logger5) {
|
|
1706
|
+
logger5.debug("Initializing rulesync...");
|
|
1693
1707
|
await ensureDir(RULESYNC_RELATIVE_DIR_PATH);
|
|
1694
1708
|
const result = await init();
|
|
1695
1709
|
const createdFiles = [];
|
|
@@ -1697,29 +1711,29 @@ async function initCommand(logger2) {
|
|
|
1697
1711
|
for (const file of result.sampleFiles) {
|
|
1698
1712
|
if (file.created) {
|
|
1699
1713
|
createdFiles.push(file.path);
|
|
1700
|
-
|
|
1714
|
+
logger5.success(`Created ${file.path}`);
|
|
1701
1715
|
} else {
|
|
1702
1716
|
skippedFiles.push(file.path);
|
|
1703
|
-
|
|
1717
|
+
logger5.info(`Skipped ${file.path} (already exists)`);
|
|
1704
1718
|
}
|
|
1705
1719
|
}
|
|
1706
1720
|
if (result.configFile.created) {
|
|
1707
1721
|
createdFiles.push(result.configFile.path);
|
|
1708
|
-
|
|
1722
|
+
logger5.success(`Created ${result.configFile.path}`);
|
|
1709
1723
|
} else {
|
|
1710
1724
|
skippedFiles.push(result.configFile.path);
|
|
1711
|
-
|
|
1725
|
+
logger5.info(`Skipped ${result.configFile.path} (already exists)`);
|
|
1712
1726
|
}
|
|
1713
|
-
if (
|
|
1714
|
-
|
|
1715
|
-
|
|
1727
|
+
if (logger5.jsonMode) {
|
|
1728
|
+
logger5.captureData("created", createdFiles);
|
|
1729
|
+
logger5.captureData("skipped", skippedFiles);
|
|
1716
1730
|
}
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1731
|
+
logger5.success("rulesync initialized successfully!");
|
|
1732
|
+
logger5.info("Next steps:");
|
|
1733
|
+
logger5.info(
|
|
1720
1734
|
`1. Edit ${RULESYNC_RELATIVE_DIR_PATH}/**/*.md, ${RULESYNC_RELATIVE_DIR_PATH}/skills/*/${SKILL_FILE_NAME}, ${RULESYNC_MCP_RELATIVE_FILE_PATH}, ${RULESYNC_HOOKS_RELATIVE_FILE_PATH} and ${RULESYNC_AIIGNORE_RELATIVE_FILE_PATH}`
|
|
1721
1735
|
);
|
|
1722
|
-
|
|
1736
|
+
logger5.info("2. Run 'rulesync generate' to create configuration files");
|
|
1723
1737
|
}
|
|
1724
1738
|
|
|
1725
1739
|
// src/lib/sources.ts
|
|
@@ -1740,7 +1754,7 @@ var GitClientError = class extends Error {
|
|
|
1740
1754
|
this.name = "GitClientError";
|
|
1741
1755
|
}
|
|
1742
1756
|
};
|
|
1743
|
-
function validateGitUrl(url) {
|
|
1757
|
+
function validateGitUrl(url, options) {
|
|
1744
1758
|
const ctrl = findControlCharacter(url);
|
|
1745
1759
|
if (ctrl) {
|
|
1746
1760
|
throw new GitClientError(
|
|
@@ -1753,7 +1767,7 @@ function validateGitUrl(url) {
|
|
|
1753
1767
|
);
|
|
1754
1768
|
}
|
|
1755
1769
|
if (INSECURE_URL_SCHEMES.test(url)) {
|
|
1756
|
-
logger
|
|
1770
|
+
options?.logger?.warn(
|
|
1757
1771
|
`URL "${url}" uses an unencrypted protocol. Consider using https:// or ssh:// instead.`
|
|
1758
1772
|
);
|
|
1759
1773
|
}
|
|
@@ -1813,8 +1827,8 @@ async function resolveRefToSha(url, ref) {
|
|
|
1813
1827
|
}
|
|
1814
1828
|
}
|
|
1815
1829
|
async function fetchSkillFiles(params) {
|
|
1816
|
-
const { url, ref, skillsPath } = params;
|
|
1817
|
-
validateGitUrl(url);
|
|
1830
|
+
const { url, ref, skillsPath, logger: logger5 } = params;
|
|
1831
|
+
validateGitUrl(url, { logger: logger5 });
|
|
1818
1832
|
validateRef(ref);
|
|
1819
1833
|
if (skillsPath.split(/[/\\]/).includes("..") || isAbsolute(skillsPath)) {
|
|
1820
1834
|
throw new GitClientError(
|
|
@@ -1852,7 +1866,7 @@ async function fetchSkillFiles(params) {
|
|
|
1852
1866
|
await execFileAsync("git", ["-C", tmpDir, "checkout"], { timeout: GIT_TIMEOUT_MS });
|
|
1853
1867
|
const skillsDir = join4(tmpDir, skillsPath);
|
|
1854
1868
|
if (!await directoryExists(skillsDir)) return [];
|
|
1855
|
-
return await walkDirectory(skillsDir, skillsDir);
|
|
1869
|
+
return await walkDirectory(skillsDir, skillsDir, 0, { totalFiles: 0, totalSize: 0 }, logger5);
|
|
1856
1870
|
} catch (error) {
|
|
1857
1871
|
if (error instanceof GitClientError) throw error;
|
|
1858
1872
|
throw new GitClientError(`Failed to fetch skill files from ${url}`, error);
|
|
@@ -1863,7 +1877,7 @@ async function fetchSkillFiles(params) {
|
|
|
1863
1877
|
var MAX_WALK_DEPTH = 20;
|
|
1864
1878
|
var MAX_TOTAL_FILES = 1e4;
|
|
1865
1879
|
var MAX_TOTAL_SIZE = 100 * 1024 * 1024;
|
|
1866
|
-
async function walkDirectory(dir, baseDir, depth = 0, ctx = { totalFiles: 0, totalSize: 0 }) {
|
|
1880
|
+
async function walkDirectory(dir, baseDir, depth = 0, ctx = { totalFiles: 0, totalSize: 0 }, logger5) {
|
|
1867
1881
|
if (depth > MAX_WALK_DEPTH) {
|
|
1868
1882
|
throw new GitClientError(
|
|
1869
1883
|
`Directory tree exceeds max depth of ${MAX_WALK_DEPTH}: "${dir}". Aborting to prevent resource exhaustion.`
|
|
@@ -1874,15 +1888,15 @@ async function walkDirectory(dir, baseDir, depth = 0, ctx = { totalFiles: 0, tot
|
|
|
1874
1888
|
if (name === ".git") continue;
|
|
1875
1889
|
const fullPath = join4(dir, name);
|
|
1876
1890
|
if (await isSymlink(fullPath)) {
|
|
1877
|
-
|
|
1891
|
+
logger5?.warn(`Skipping symlink "${fullPath}".`);
|
|
1878
1892
|
continue;
|
|
1879
1893
|
}
|
|
1880
1894
|
if (await directoryExists(fullPath)) {
|
|
1881
|
-
results.push(...await walkDirectory(fullPath, baseDir, depth + 1, ctx));
|
|
1895
|
+
results.push(...await walkDirectory(fullPath, baseDir, depth + 1, ctx, logger5));
|
|
1882
1896
|
} else {
|
|
1883
1897
|
const size = await getFileSize(fullPath);
|
|
1884
1898
|
if (size > MAX_FILE_SIZE) {
|
|
1885
|
-
|
|
1899
|
+
logger5?.warn(
|
|
1886
1900
|
`Skipping file "${fullPath}" (${(size / 1024 / 1024).toFixed(2)}MB exceeds ${MAX_FILE_SIZE / 1024 / 1024}MB limit).`
|
|
1887
1901
|
);
|
|
1888
1902
|
continue;
|
|
@@ -1931,7 +1945,8 @@ var LegacyLockedSourceSchema = z4.object({
|
|
|
1931
1945
|
var LegacySourcesLockSchema = z4.object({
|
|
1932
1946
|
sources: z4.record(z4.string(), LegacyLockedSourceSchema)
|
|
1933
1947
|
});
|
|
1934
|
-
function migrateLegacyLock(
|
|
1948
|
+
function migrateLegacyLock(params) {
|
|
1949
|
+
const { legacy, logger: logger5 } = params;
|
|
1935
1950
|
const sources = {};
|
|
1936
1951
|
for (const [key, entry] of Object.entries(legacy.sources)) {
|
|
1937
1952
|
const skills = {};
|
|
@@ -1943,7 +1958,7 @@ function migrateLegacyLock(legacy) {
|
|
|
1943
1958
|
skills
|
|
1944
1959
|
};
|
|
1945
1960
|
}
|
|
1946
|
-
|
|
1961
|
+
logger5.info(
|
|
1947
1962
|
"Migrated legacy sources lockfile to version 1. Run 'rulesync install --update' to populate integrity hashes."
|
|
1948
1963
|
);
|
|
1949
1964
|
return { lockfileVersion: LOCKFILE_VERSION, sources };
|
|
@@ -1952,9 +1967,10 @@ function createEmptyLock() {
|
|
|
1952
1967
|
return { lockfileVersion: LOCKFILE_VERSION, sources: {} };
|
|
1953
1968
|
}
|
|
1954
1969
|
async function readLockFile(params) {
|
|
1970
|
+
const { logger: logger5 } = params;
|
|
1955
1971
|
const lockPath = join5(params.baseDir, RULESYNC_SOURCES_LOCK_RELATIVE_FILE_PATH);
|
|
1956
1972
|
if (!await fileExists(lockPath)) {
|
|
1957
|
-
|
|
1973
|
+
logger5.debug("No sources lockfile found, starting fresh.");
|
|
1958
1974
|
return createEmptyLock();
|
|
1959
1975
|
}
|
|
1960
1976
|
try {
|
|
@@ -1966,24 +1982,25 @@ async function readLockFile(params) {
|
|
|
1966
1982
|
}
|
|
1967
1983
|
const legacyResult = LegacySourcesLockSchema.safeParse(data);
|
|
1968
1984
|
if (legacyResult.success) {
|
|
1969
|
-
return migrateLegacyLock(legacyResult.data);
|
|
1985
|
+
return migrateLegacyLock({ legacy: legacyResult.data, logger: logger5 });
|
|
1970
1986
|
}
|
|
1971
|
-
|
|
1987
|
+
logger5.warn(
|
|
1972
1988
|
`Invalid sources lockfile format (${RULESYNC_SOURCES_LOCK_RELATIVE_FILE_PATH}). Starting fresh.`
|
|
1973
1989
|
);
|
|
1974
1990
|
return createEmptyLock();
|
|
1975
1991
|
} catch {
|
|
1976
|
-
|
|
1992
|
+
logger5.warn(
|
|
1977
1993
|
`Failed to read sources lockfile (${RULESYNC_SOURCES_LOCK_RELATIVE_FILE_PATH}). Starting fresh.`
|
|
1978
1994
|
);
|
|
1979
1995
|
return createEmptyLock();
|
|
1980
1996
|
}
|
|
1981
1997
|
}
|
|
1982
1998
|
async function writeLockFile(params) {
|
|
1999
|
+
const { logger: logger5 } = params;
|
|
1983
2000
|
const lockPath = join5(params.baseDir, RULESYNC_SOURCES_LOCK_RELATIVE_FILE_PATH);
|
|
1984
2001
|
const content = JSON.stringify(params.lock, null, 2) + "\n";
|
|
1985
2002
|
await writeFileContent(lockPath, content);
|
|
1986
|
-
|
|
2003
|
+
logger5.debug(`Wrote sources lockfile to ${lockPath}`);
|
|
1987
2004
|
}
|
|
1988
2005
|
function computeSkillIntegrity(files) {
|
|
1989
2006
|
const hash = createHash("sha256");
|
|
@@ -2055,15 +2072,15 @@ function getLockedSkillNames(entry) {
|
|
|
2055
2072
|
|
|
2056
2073
|
// src/lib/sources.ts
|
|
2057
2074
|
async function resolveAndFetchSources(params) {
|
|
2058
|
-
const { sources, baseDir, options = {} } = params;
|
|
2075
|
+
const { sources, baseDir, options = {}, logger: logger5 } = params;
|
|
2059
2076
|
if (sources.length === 0) {
|
|
2060
2077
|
return { fetchedSkillCount: 0, sourcesProcessed: 0 };
|
|
2061
2078
|
}
|
|
2062
2079
|
if (options.skipSources) {
|
|
2063
|
-
|
|
2080
|
+
logger5.info("Skipping source fetching.");
|
|
2064
2081
|
return { fetchedSkillCount: 0, sourcesProcessed: 0 };
|
|
2065
2082
|
}
|
|
2066
|
-
let lock = options.updateSources ? createEmptyLock() : await readLockFile({ baseDir });
|
|
2083
|
+
let lock = options.updateSources ? createEmptyLock() : await readLockFile({ baseDir, logger: logger5 });
|
|
2067
2084
|
if (options.frozen) {
|
|
2068
2085
|
const missingKeys = [];
|
|
2069
2086
|
for (const source of sources) {
|
|
@@ -2096,7 +2113,8 @@ async function resolveAndFetchSources(params) {
|
|
|
2096
2113
|
localSkillNames,
|
|
2097
2114
|
alreadyFetchedSkillNames: allFetchedSkillNames,
|
|
2098
2115
|
updateSources: options.updateSources ?? false,
|
|
2099
|
-
frozen: options.frozen ?? false
|
|
2116
|
+
frozen: options.frozen ?? false,
|
|
2117
|
+
logger: logger5
|
|
2100
2118
|
});
|
|
2101
2119
|
} else {
|
|
2102
2120
|
result = await fetchSource({
|
|
@@ -2106,7 +2124,8 @@ async function resolveAndFetchSources(params) {
|
|
|
2106
2124
|
lock,
|
|
2107
2125
|
localSkillNames,
|
|
2108
2126
|
alreadyFetchedSkillNames: allFetchedSkillNames,
|
|
2109
|
-
updateSources: options.updateSources ?? false
|
|
2127
|
+
updateSources: options.updateSources ?? false,
|
|
2128
|
+
logger: logger5
|
|
2110
2129
|
});
|
|
2111
2130
|
}
|
|
2112
2131
|
const { skillCount, fetchedSkillNames, updatedLock } = result;
|
|
@@ -2116,11 +2135,11 @@ async function resolveAndFetchSources(params) {
|
|
|
2116
2135
|
allFetchedSkillNames.add(name);
|
|
2117
2136
|
}
|
|
2118
2137
|
} catch (error) {
|
|
2119
|
-
|
|
2138
|
+
logger5.error(`Failed to fetch source "${sourceEntry.source}": ${formatError(error)}`);
|
|
2120
2139
|
if (error instanceof GitHubClientError) {
|
|
2121
|
-
logGitHubAuthHints(error);
|
|
2140
|
+
logGitHubAuthHints({ error, logger: logger5 });
|
|
2122
2141
|
} else if (error instanceof GitClientError) {
|
|
2123
|
-
logGitClientHints(error);
|
|
2142
|
+
logGitClientHints({ error, logger: logger5 });
|
|
2124
2143
|
}
|
|
2125
2144
|
}
|
|
2126
2145
|
}
|
|
@@ -2130,22 +2149,23 @@ async function resolveAndFetchSources(params) {
|
|
|
2130
2149
|
if (sourceKeys.has(normalizeSourceKey(key))) {
|
|
2131
2150
|
prunedSources[key] = value;
|
|
2132
2151
|
} else {
|
|
2133
|
-
|
|
2152
|
+
logger5.debug(`Pruned stale lockfile entry: ${key}`);
|
|
2134
2153
|
}
|
|
2135
2154
|
}
|
|
2136
2155
|
lock = { lockfileVersion: lock.lockfileVersion, sources: prunedSources };
|
|
2137
2156
|
if (!options.frozen && JSON.stringify(lock) !== originalLockJson) {
|
|
2138
|
-
await writeLockFile({ baseDir, lock });
|
|
2157
|
+
await writeLockFile({ baseDir, lock, logger: logger5 });
|
|
2139
2158
|
} else {
|
|
2140
|
-
|
|
2159
|
+
logger5.debug("Lockfile unchanged, skipping write.");
|
|
2141
2160
|
}
|
|
2142
2161
|
return { fetchedSkillCount: totalSkillCount, sourcesProcessed: sources.length };
|
|
2143
2162
|
}
|
|
2144
|
-
function logGitClientHints(
|
|
2163
|
+
function logGitClientHints(params) {
|
|
2164
|
+
const { error, logger: logger5 } = params;
|
|
2145
2165
|
if (error.message.includes("not installed")) {
|
|
2146
|
-
|
|
2166
|
+
logger5.info("Hint: Install git and ensure it is available on your PATH.");
|
|
2147
2167
|
} else {
|
|
2148
|
-
|
|
2168
|
+
logger5.info("Hint: Check your git credentials (SSH keys, credential helper, or access token).");
|
|
2149
2169
|
}
|
|
2150
2170
|
}
|
|
2151
2171
|
async function checkLockedSkillsExist(curatedDir, skillNames) {
|
|
@@ -2157,12 +2177,13 @@ async function checkLockedSkillsExist(curatedDir, skillNames) {
|
|
|
2157
2177
|
}
|
|
2158
2178
|
return true;
|
|
2159
2179
|
}
|
|
2160
|
-
async function cleanPreviousCuratedSkills(
|
|
2180
|
+
async function cleanPreviousCuratedSkills(params) {
|
|
2181
|
+
const { curatedDir, lockedSkillNames, logger: logger5 } = params;
|
|
2161
2182
|
const resolvedCuratedDir = resolve(curatedDir);
|
|
2162
2183
|
for (const prevSkill of lockedSkillNames) {
|
|
2163
2184
|
const prevDir = join6(curatedDir, prevSkill);
|
|
2164
2185
|
if (!resolve(prevDir).startsWith(resolvedCuratedDir + sep)) {
|
|
2165
|
-
|
|
2186
|
+
logger5.warn(
|
|
2166
2187
|
`Skipping removal of "${prevSkill}": resolved path is outside the curated directory.`
|
|
2167
2188
|
);
|
|
2168
2189
|
continue;
|
|
@@ -2173,21 +2194,21 @@ async function cleanPreviousCuratedSkills(curatedDir, lockedSkillNames) {
|
|
|
2173
2194
|
}
|
|
2174
2195
|
}
|
|
2175
2196
|
function shouldSkipSkill(params) {
|
|
2176
|
-
const { skillName, sourceKey, localSkillNames, alreadyFetchedSkillNames } = params;
|
|
2197
|
+
const { skillName, sourceKey, localSkillNames, alreadyFetchedSkillNames, logger: logger5 } = params;
|
|
2177
2198
|
if (skillName.includes("..") || skillName.includes("/") || skillName.includes("\\")) {
|
|
2178
|
-
|
|
2199
|
+
logger5.warn(
|
|
2179
2200
|
`Skipping skill with invalid name "${skillName}" from ${sourceKey}: contains path traversal characters.`
|
|
2180
2201
|
);
|
|
2181
2202
|
return true;
|
|
2182
2203
|
}
|
|
2183
2204
|
if (localSkillNames.has(skillName)) {
|
|
2184
|
-
|
|
2205
|
+
logger5.debug(
|
|
2185
2206
|
`Skipping remote skill "${skillName}" from ${sourceKey}: local skill takes precedence.`
|
|
2186
2207
|
);
|
|
2187
2208
|
return true;
|
|
2188
2209
|
}
|
|
2189
2210
|
if (alreadyFetchedSkillNames.has(skillName)) {
|
|
2190
|
-
|
|
2211
|
+
logger5.warn(
|
|
2191
2212
|
`Skipping duplicate skill "${skillName}" from ${sourceKey}: already fetched from another source.`
|
|
2192
2213
|
);
|
|
2193
2214
|
return true;
|
|
@@ -2195,7 +2216,7 @@ function shouldSkipSkill(params) {
|
|
|
2195
2216
|
return false;
|
|
2196
2217
|
}
|
|
2197
2218
|
async function writeSkillAndComputeIntegrity(params) {
|
|
2198
|
-
const { skillName, files, curatedDir, locked, resolvedSha, sourceKey } = params;
|
|
2219
|
+
const { skillName, files, curatedDir, locked, resolvedSha, sourceKey, logger: logger5 } = params;
|
|
2199
2220
|
const written = [];
|
|
2200
2221
|
for (const file of files) {
|
|
2201
2222
|
checkPathTraversal({
|
|
@@ -2208,14 +2229,23 @@ async function writeSkillAndComputeIntegrity(params) {
|
|
|
2208
2229
|
const integrity = computeSkillIntegrity(written);
|
|
2209
2230
|
const lockedSkillEntry = locked?.skills[skillName];
|
|
2210
2231
|
if (lockedSkillEntry?.integrity && lockedSkillEntry.integrity !== integrity && resolvedSha === locked?.resolvedRef) {
|
|
2211
|
-
|
|
2232
|
+
logger5.warn(
|
|
2212
2233
|
`Integrity mismatch for skill "${skillName}" from ${sourceKey}: expected "${lockedSkillEntry.integrity}", got "${integrity}". Content may have been tampered with.`
|
|
2213
2234
|
);
|
|
2214
2235
|
}
|
|
2215
2236
|
return { integrity };
|
|
2216
2237
|
}
|
|
2217
2238
|
function buildLockUpdate(params) {
|
|
2218
|
-
const {
|
|
2239
|
+
const {
|
|
2240
|
+
lock,
|
|
2241
|
+
sourceKey,
|
|
2242
|
+
fetchedSkills,
|
|
2243
|
+
locked,
|
|
2244
|
+
requestedRef,
|
|
2245
|
+
resolvedSha,
|
|
2246
|
+
remoteSkillNames,
|
|
2247
|
+
logger: logger5
|
|
2248
|
+
} = params;
|
|
2219
2249
|
const fetchedNames = Object.keys(fetchedSkills);
|
|
2220
2250
|
const remoteSet = new Set(remoteSkillNames);
|
|
2221
2251
|
const mergedSkills = { ...fetchedSkills };
|
|
@@ -2232,17 +2262,25 @@ function buildLockUpdate(params) {
|
|
|
2232
2262
|
resolvedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2233
2263
|
skills: mergedSkills
|
|
2234
2264
|
});
|
|
2235
|
-
|
|
2265
|
+
logger5.info(
|
|
2236
2266
|
`Fetched ${fetchedNames.length} skill(s) from ${sourceKey}: ${fetchedNames.join(", ") || "(none)"}`
|
|
2237
2267
|
);
|
|
2238
2268
|
return { updatedLock, fetchedNames };
|
|
2239
2269
|
}
|
|
2240
2270
|
async function fetchSource(params) {
|
|
2241
|
-
const {
|
|
2271
|
+
const {
|
|
2272
|
+
sourceEntry,
|
|
2273
|
+
client,
|
|
2274
|
+
baseDir,
|
|
2275
|
+
localSkillNames,
|
|
2276
|
+
alreadyFetchedSkillNames,
|
|
2277
|
+
updateSources,
|
|
2278
|
+
logger: logger5
|
|
2279
|
+
} = params;
|
|
2242
2280
|
const { lock } = params;
|
|
2243
2281
|
const parsed = parseSource(sourceEntry.source);
|
|
2244
2282
|
if (parsed.provider === "gitlab") {
|
|
2245
|
-
|
|
2283
|
+
logger5.warn(`GitLab sources are not yet supported. Skipping "${sourceEntry.source}".`);
|
|
2246
2284
|
return { skillCount: 0, fetchedSkillNames: [], updatedLock: lock };
|
|
2247
2285
|
}
|
|
2248
2286
|
const sourceKey = sourceEntry.source;
|
|
@@ -2255,18 +2293,18 @@ async function fetchSource(params) {
|
|
|
2255
2293
|
ref = locked.resolvedRef;
|
|
2256
2294
|
resolvedSha = locked.resolvedRef;
|
|
2257
2295
|
requestedRef = locked.requestedRef;
|
|
2258
|
-
|
|
2296
|
+
logger5.debug(`Using locked ref for ${sourceKey}: ${resolvedSha}`);
|
|
2259
2297
|
} else {
|
|
2260
2298
|
requestedRef = parsed.ref ?? await client.getDefaultBranch(parsed.owner, parsed.repo);
|
|
2261
2299
|
resolvedSha = await client.resolveRefToSha(parsed.owner, parsed.repo, requestedRef);
|
|
2262
2300
|
ref = resolvedSha;
|
|
2263
|
-
|
|
2301
|
+
logger5.debug(`Resolved ${sourceKey} ref "${requestedRef}" to SHA: ${resolvedSha}`);
|
|
2264
2302
|
}
|
|
2265
2303
|
const curatedDir = join6(baseDir, RULESYNC_CURATED_SKILLS_RELATIVE_DIR_PATH);
|
|
2266
2304
|
if (locked && resolvedSha === locked.resolvedRef && !updateSources) {
|
|
2267
2305
|
const allExist = await checkLockedSkillsExist(curatedDir, lockedSkillNames);
|
|
2268
2306
|
if (allExist) {
|
|
2269
|
-
|
|
2307
|
+
logger5.debug(`SHA unchanged for ${sourceKey}, skipping re-fetch.`);
|
|
2270
2308
|
return {
|
|
2271
2309
|
skillCount: 0,
|
|
2272
2310
|
fetchedSkillNames: lockedSkillNames,
|
|
@@ -2283,7 +2321,7 @@ async function fetchSource(params) {
|
|
|
2283
2321
|
remoteSkillDirs = entries.filter((e) => e.type === "dir").map((e) => ({ name: e.name, path: e.path }));
|
|
2284
2322
|
} catch (error) {
|
|
2285
2323
|
if (error instanceof GitHubClientError && error.statusCode === 404) {
|
|
2286
|
-
|
|
2324
|
+
logger5.warn(`No skills/ directory found in ${sourceKey}. Skipping.`);
|
|
2287
2325
|
return { skillCount: 0, fetchedSkillNames: [], updatedLock: lock };
|
|
2288
2326
|
}
|
|
2289
2327
|
throw error;
|
|
@@ -2292,14 +2330,15 @@ async function fetchSource(params) {
|
|
|
2292
2330
|
const semaphore = new Semaphore2(FETCH_CONCURRENCY_LIMIT);
|
|
2293
2331
|
const fetchedSkills = {};
|
|
2294
2332
|
if (locked) {
|
|
2295
|
-
await cleanPreviousCuratedSkills(curatedDir, lockedSkillNames);
|
|
2333
|
+
await cleanPreviousCuratedSkills({ curatedDir, lockedSkillNames, logger: logger5 });
|
|
2296
2334
|
}
|
|
2297
2335
|
for (const skillDir of filteredDirs) {
|
|
2298
2336
|
if (shouldSkipSkill({
|
|
2299
2337
|
skillName: skillDir.name,
|
|
2300
2338
|
sourceKey,
|
|
2301
2339
|
localSkillNames,
|
|
2302
|
-
alreadyFetchedSkillNames
|
|
2340
|
+
alreadyFetchedSkillNames,
|
|
2341
|
+
logger: logger5
|
|
2303
2342
|
})) {
|
|
2304
2343
|
continue;
|
|
2305
2344
|
}
|
|
@@ -2313,7 +2352,7 @@ async function fetchSource(params) {
|
|
|
2313
2352
|
});
|
|
2314
2353
|
const files = allFiles.filter((file) => {
|
|
2315
2354
|
if (file.size > MAX_FILE_SIZE) {
|
|
2316
|
-
|
|
2355
|
+
logger5.warn(
|
|
2317
2356
|
`Skipping file "${file.path}" (${(file.size / 1024 / 1024).toFixed(2)}MB exceeds ${MAX_FILE_SIZE / 1024 / 1024}MB limit).`
|
|
2318
2357
|
);
|
|
2319
2358
|
return false;
|
|
@@ -2335,9 +2374,10 @@ async function fetchSource(params) {
|
|
|
2335
2374
|
curatedDir,
|
|
2336
2375
|
locked,
|
|
2337
2376
|
resolvedSha,
|
|
2338
|
-
sourceKey
|
|
2377
|
+
sourceKey,
|
|
2378
|
+
logger: logger5
|
|
2339
2379
|
});
|
|
2340
|
-
|
|
2380
|
+
logger5.debug(`Fetched skill "${skillDir.name}" from ${sourceKey}`);
|
|
2341
2381
|
}
|
|
2342
2382
|
const result = buildLockUpdate({
|
|
2343
2383
|
lock,
|
|
@@ -2346,7 +2386,8 @@ async function fetchSource(params) {
|
|
|
2346
2386
|
locked,
|
|
2347
2387
|
requestedRef,
|
|
2348
2388
|
resolvedSha,
|
|
2349
|
-
remoteSkillNames: filteredDirs.map((d) => d.name)
|
|
2389
|
+
remoteSkillNames: filteredDirs.map((d) => d.name),
|
|
2390
|
+
logger: logger5
|
|
2350
2391
|
});
|
|
2351
2392
|
return {
|
|
2352
2393
|
skillCount: result.fetchedNames.length,
|
|
@@ -2355,7 +2396,15 @@ async function fetchSource(params) {
|
|
|
2355
2396
|
};
|
|
2356
2397
|
}
|
|
2357
2398
|
async function fetchSourceViaGit(params) {
|
|
2358
|
-
const {
|
|
2399
|
+
const {
|
|
2400
|
+
sourceEntry,
|
|
2401
|
+
baseDir,
|
|
2402
|
+
localSkillNames,
|
|
2403
|
+
alreadyFetchedSkillNames,
|
|
2404
|
+
updateSources,
|
|
2405
|
+
frozen,
|
|
2406
|
+
logger: logger5
|
|
2407
|
+
} = params;
|
|
2359
2408
|
const { lock } = params;
|
|
2360
2409
|
const url = sourceEntry.source;
|
|
2361
2410
|
const locked = getLockedSource(lock, url);
|
|
@@ -2412,11 +2461,17 @@ async function fetchSourceViaGit(params) {
|
|
|
2412
2461
|
const allNames = [...skillFileMap.keys()];
|
|
2413
2462
|
const filteredNames = isWildcard ? allNames : allNames.filter((n) => skillFilter.includes(n));
|
|
2414
2463
|
if (locked) {
|
|
2415
|
-
await cleanPreviousCuratedSkills(curatedDir, lockedSkillNames);
|
|
2464
|
+
await cleanPreviousCuratedSkills({ curatedDir, lockedSkillNames, logger: logger5 });
|
|
2416
2465
|
}
|
|
2417
2466
|
const fetchedSkills = {};
|
|
2418
2467
|
for (const skillName of filteredNames) {
|
|
2419
|
-
if (shouldSkipSkill({
|
|
2468
|
+
if (shouldSkipSkill({
|
|
2469
|
+
skillName,
|
|
2470
|
+
sourceKey: url,
|
|
2471
|
+
localSkillNames,
|
|
2472
|
+
alreadyFetchedSkillNames,
|
|
2473
|
+
logger: logger5
|
|
2474
|
+
})) {
|
|
2420
2475
|
continue;
|
|
2421
2476
|
}
|
|
2422
2477
|
fetchedSkills[skillName] = await writeSkillAndComputeIntegrity({
|
|
@@ -2425,7 +2480,8 @@ async function fetchSourceViaGit(params) {
|
|
|
2425
2480
|
curatedDir,
|
|
2426
2481
|
locked,
|
|
2427
2482
|
resolvedSha,
|
|
2428
|
-
sourceKey: url
|
|
2483
|
+
sourceKey: url,
|
|
2484
|
+
logger: logger5
|
|
2429
2485
|
});
|
|
2430
2486
|
}
|
|
2431
2487
|
const result = buildLockUpdate({
|
|
@@ -2435,7 +2491,8 @@ async function fetchSourceViaGit(params) {
|
|
|
2435
2491
|
locked,
|
|
2436
2492
|
requestedRef,
|
|
2437
2493
|
resolvedSha,
|
|
2438
|
-
remoteSkillNames: filteredNames
|
|
2494
|
+
remoteSkillNames: filteredNames,
|
|
2495
|
+
logger: logger5
|
|
2439
2496
|
});
|
|
2440
2497
|
return {
|
|
2441
2498
|
skillCount: result.fetchedNames.length,
|
|
@@ -2445,7 +2502,7 @@ async function fetchSourceViaGit(params) {
|
|
|
2445
2502
|
}
|
|
2446
2503
|
|
|
2447
2504
|
// src/cli/commands/install.ts
|
|
2448
|
-
async function installCommand(
|
|
2505
|
+
async function installCommand(logger5, options) {
|
|
2449
2506
|
const config = await ConfigResolver.resolve({
|
|
2450
2507
|
configPath: options.configPath,
|
|
2451
2508
|
verbose: options.verbose,
|
|
@@ -2453,10 +2510,10 @@ async function installCommand(logger2, options) {
|
|
|
2453
2510
|
});
|
|
2454
2511
|
const sources = config.getSources();
|
|
2455
2512
|
if (sources.length === 0) {
|
|
2456
|
-
|
|
2513
|
+
logger5.warn("No sources defined in configuration. Nothing to install.");
|
|
2457
2514
|
return;
|
|
2458
2515
|
}
|
|
2459
|
-
|
|
2516
|
+
logger5.debug(`Installing skills from ${sources.length} source(s)...`);
|
|
2460
2517
|
const result = await resolveAndFetchSources({
|
|
2461
2518
|
sources,
|
|
2462
2519
|
baseDir: process.cwd(),
|
|
@@ -2464,18 +2521,19 @@ async function installCommand(logger2, options) {
|
|
|
2464
2521
|
updateSources: options.update,
|
|
2465
2522
|
frozen: options.frozen,
|
|
2466
2523
|
token: options.token
|
|
2467
|
-
}
|
|
2524
|
+
},
|
|
2525
|
+
logger: logger5
|
|
2468
2526
|
});
|
|
2469
|
-
if (
|
|
2470
|
-
|
|
2471
|
-
|
|
2527
|
+
if (logger5.jsonMode) {
|
|
2528
|
+
logger5.captureData("sourcesProcessed", result.sourcesProcessed);
|
|
2529
|
+
logger5.captureData("skillsFetched", result.fetchedSkillCount);
|
|
2472
2530
|
}
|
|
2473
2531
|
if (result.fetchedSkillCount > 0) {
|
|
2474
|
-
|
|
2532
|
+
logger5.success(
|
|
2475
2533
|
`Installed ${result.fetchedSkillCount} skill(s) from ${result.sourcesProcessed} source(s).`
|
|
2476
2534
|
);
|
|
2477
2535
|
} else {
|
|
2478
|
-
|
|
2536
|
+
logger5.success(`All skills up to date (${result.sourcesProcessed} source(s) checked).`);
|
|
2479
2537
|
}
|
|
2480
2538
|
}
|
|
2481
2539
|
|
|
@@ -2488,6 +2546,7 @@ import { z as z13 } from "zod/mini";
|
|
|
2488
2546
|
// src/mcp/commands.ts
|
|
2489
2547
|
import { basename, join as join7 } from "path";
|
|
2490
2548
|
import { z as z5 } from "zod/mini";
|
|
2549
|
+
var logger = new ConsoleLogger({ verbose: false, silent: true });
|
|
2491
2550
|
var maxCommandSizeBytes = 1024 * 1024;
|
|
2492
2551
|
var maxCommandsCount = 1e3;
|
|
2493
2552
|
async function listCommands() {
|
|
@@ -2706,7 +2765,8 @@ async function executeGenerate(options = {}) {
|
|
|
2706
2765
|
verbose: false,
|
|
2707
2766
|
silent: true
|
|
2708
2767
|
});
|
|
2709
|
-
const
|
|
2768
|
+
const logger5 = new ConsoleLogger({ verbose: false, silent: true });
|
|
2769
|
+
const generateResult = await generate({ config, logger: logger5 });
|
|
2710
2770
|
return buildSuccessResponse({ generateResult, config });
|
|
2711
2771
|
} catch (error) {
|
|
2712
2772
|
return {
|
|
@@ -2884,7 +2944,8 @@ async function executeImport(options) {
|
|
|
2884
2944
|
silent: true
|
|
2885
2945
|
});
|
|
2886
2946
|
const tool = config.getTargets()[0];
|
|
2887
|
-
const
|
|
2947
|
+
const logger5 = new ConsoleLogger({ verbose: false, silent: true });
|
|
2948
|
+
const importResult = await importFromTool({ config, tool, logger: logger5 });
|
|
2888
2949
|
return buildSuccessResponse2({ importResult, config, tool });
|
|
2889
2950
|
} catch (error) {
|
|
2890
2951
|
return {
|
|
@@ -3069,6 +3130,7 @@ var mcpTools = {
|
|
|
3069
3130
|
// src/mcp/rules.ts
|
|
3070
3131
|
import { basename as basename2, join as join10 } from "path";
|
|
3071
3132
|
import { z as z10 } from "zod/mini";
|
|
3133
|
+
var logger2 = new ConsoleLogger({ verbose: false, silent: true });
|
|
3072
3134
|
var maxRuleSizeBytes = 1024 * 1024;
|
|
3073
3135
|
var maxRulesCount = 1e3;
|
|
3074
3136
|
async function listRules() {
|
|
@@ -3089,14 +3151,14 @@ async function listRules() {
|
|
|
3089
3151
|
frontmatter
|
|
3090
3152
|
};
|
|
3091
3153
|
} catch (error) {
|
|
3092
|
-
|
|
3154
|
+
logger2.error(`Failed to read rule file ${file}: ${formatError(error)}`);
|
|
3093
3155
|
return null;
|
|
3094
3156
|
}
|
|
3095
3157
|
})
|
|
3096
3158
|
);
|
|
3097
3159
|
return rules.filter((rule) => rule !== null);
|
|
3098
3160
|
} catch (error) {
|
|
3099
|
-
|
|
3161
|
+
logger2.error(
|
|
3100
3162
|
`Failed to read rules directory (${RULESYNC_RULES_RELATIVE_DIR_PATH}): ${formatError(error)}`
|
|
3101
3163
|
);
|
|
3102
3164
|
return [];
|
|
@@ -3251,6 +3313,7 @@ var ruleTools = {
|
|
|
3251
3313
|
// src/mcp/skills.ts
|
|
3252
3314
|
import { basename as basename3, dirname, join as join11 } from "path";
|
|
3253
3315
|
import { z as z11 } from "zod/mini";
|
|
3316
|
+
var logger3 = new ConsoleLogger({ verbose: false, silent: true });
|
|
3254
3317
|
var maxSkillSizeBytes = 1024 * 1024;
|
|
3255
3318
|
var maxSkillsCount = 1e3;
|
|
3256
3319
|
function aiDirFileToMcpSkillFile(file) {
|
|
@@ -3290,14 +3353,14 @@ async function listSkills() {
|
|
|
3290
3353
|
frontmatter
|
|
3291
3354
|
};
|
|
3292
3355
|
} catch (error) {
|
|
3293
|
-
|
|
3356
|
+
logger3.error(`Failed to read skill directory ${dirName}: ${formatError(error)}`);
|
|
3294
3357
|
return null;
|
|
3295
3358
|
}
|
|
3296
3359
|
})
|
|
3297
3360
|
);
|
|
3298
3361
|
return skills.filter((skill) => skill !== null);
|
|
3299
3362
|
} catch (error) {
|
|
3300
|
-
|
|
3363
|
+
logger3.error(
|
|
3301
3364
|
`Failed to read skills directory (${RULESYNC_SKILLS_RELATIVE_DIR_PATH}): ${formatError(error)}`
|
|
3302
3365
|
);
|
|
3303
3366
|
return [];
|
|
@@ -3489,6 +3552,7 @@ var skillTools = {
|
|
|
3489
3552
|
// src/mcp/subagents.ts
|
|
3490
3553
|
import { basename as basename4, join as join12 } from "path";
|
|
3491
3554
|
import { z as z12 } from "zod/mini";
|
|
3555
|
+
var logger4 = new ConsoleLogger({ verbose: false, silent: true });
|
|
3492
3556
|
var maxSubagentSizeBytes = 1024 * 1024;
|
|
3493
3557
|
var maxSubagentsCount = 1e3;
|
|
3494
3558
|
async function listSubagents() {
|
|
@@ -3509,7 +3573,7 @@ async function listSubagents() {
|
|
|
3509
3573
|
frontmatter
|
|
3510
3574
|
};
|
|
3511
3575
|
} catch (error) {
|
|
3512
|
-
|
|
3576
|
+
logger4.error(`Failed to read subagent file ${file}: ${formatError(error)}`);
|
|
3513
3577
|
return null;
|
|
3514
3578
|
}
|
|
3515
3579
|
})
|
|
@@ -3518,7 +3582,7 @@ async function listSubagents() {
|
|
|
3518
3582
|
(subagent) => subagent !== null
|
|
3519
3583
|
);
|
|
3520
3584
|
} catch (error) {
|
|
3521
|
-
|
|
3585
|
+
logger4.error(
|
|
3522
3586
|
`Failed to read subagents directory (${RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH}): ${formatError(error)}`
|
|
3523
3587
|
);
|
|
3524
3588
|
return [];
|
|
@@ -3890,7 +3954,7 @@ var rulesyncTool = {
|
|
|
3890
3954
|
};
|
|
3891
3955
|
|
|
3892
3956
|
// src/cli/commands/mcp.ts
|
|
3893
|
-
async function mcpCommand(
|
|
3957
|
+
async function mcpCommand(logger5, { version }) {
|
|
3894
3958
|
const server = new FastMCP({
|
|
3895
3959
|
name: "Rulesync MCP Server",
|
|
3896
3960
|
// eslint-disable-next-line no-type-assertion/no-type-assertion
|
|
@@ -3898,7 +3962,7 @@ async function mcpCommand(logger2, { version }) {
|
|
|
3898
3962
|
instructions: "This server handles Rulesync files including rules, commands, MCP, ignore files, subagents and skills for any AI agents. It should be used when you need those files."
|
|
3899
3963
|
});
|
|
3900
3964
|
server.addTool(rulesyncTool);
|
|
3901
|
-
|
|
3965
|
+
logger5.info("Rulesync MCP server started via stdio");
|
|
3902
3966
|
void server.start({
|
|
3903
3967
|
transportType: "stdio"
|
|
3904
3968
|
});
|
|
@@ -4214,43 +4278,43 @@ To upgrade, run:
|
|
|
4214
4278
|
}
|
|
4215
4279
|
|
|
4216
4280
|
// src/cli/commands/update.ts
|
|
4217
|
-
async function updateCommand(
|
|
4281
|
+
async function updateCommand(logger5, currentVersion, options) {
|
|
4218
4282
|
const { check = false, force = false, token } = options;
|
|
4219
4283
|
try {
|
|
4220
4284
|
const environment = detectExecutionEnvironment();
|
|
4221
|
-
|
|
4285
|
+
logger5.debug(`Detected environment: ${environment}`);
|
|
4222
4286
|
if (environment === "npm") {
|
|
4223
|
-
|
|
4287
|
+
logger5.info(getNpmUpgradeInstructions());
|
|
4224
4288
|
return;
|
|
4225
4289
|
}
|
|
4226
4290
|
if (environment === "homebrew") {
|
|
4227
|
-
|
|
4291
|
+
logger5.info(getHomebrewUpgradeInstructions());
|
|
4228
4292
|
return;
|
|
4229
4293
|
}
|
|
4230
4294
|
if (check) {
|
|
4231
|
-
|
|
4295
|
+
logger5.info("Checking for updates...");
|
|
4232
4296
|
const updateCheck = await checkForUpdate(currentVersion, token);
|
|
4233
|
-
if (
|
|
4234
|
-
|
|
4235
|
-
|
|
4236
|
-
|
|
4237
|
-
|
|
4297
|
+
if (logger5.jsonMode) {
|
|
4298
|
+
logger5.captureData("currentVersion", updateCheck.currentVersion);
|
|
4299
|
+
logger5.captureData("latestVersion", updateCheck.latestVersion);
|
|
4300
|
+
logger5.captureData("updateAvailable", updateCheck.hasUpdate);
|
|
4301
|
+
logger5.captureData(
|
|
4238
4302
|
"message",
|
|
4239
4303
|
updateCheck.hasUpdate ? `Update available: ${updateCheck.currentVersion} -> ${updateCheck.latestVersion}` : `Already at the latest version (${updateCheck.currentVersion})`
|
|
4240
4304
|
);
|
|
4241
4305
|
}
|
|
4242
4306
|
if (updateCheck.hasUpdate) {
|
|
4243
|
-
|
|
4307
|
+
logger5.success(
|
|
4244
4308
|
`Update available: ${updateCheck.currentVersion} -> ${updateCheck.latestVersion}`
|
|
4245
4309
|
);
|
|
4246
4310
|
} else {
|
|
4247
|
-
|
|
4311
|
+
logger5.info(`Already at the latest version (${updateCheck.currentVersion})`);
|
|
4248
4312
|
}
|
|
4249
4313
|
return;
|
|
4250
4314
|
}
|
|
4251
|
-
|
|
4315
|
+
logger5.info("Checking for updates...");
|
|
4252
4316
|
const message = await performBinaryUpdate(currentVersion, { force, token });
|
|
4253
|
-
|
|
4317
|
+
logger5.success(message);
|
|
4254
4318
|
} catch (error) {
|
|
4255
4319
|
if (error instanceof GitHubClientError) {
|
|
4256
4320
|
const authHint = error.statusCode === 401 || error.statusCode === 403 ? " Tip: Set GITHUB_TOKEN or GH_TOKEN environment variable, or use `GITHUB_TOKEN=$(gh auth token) rulesync update ...`" : "";
|
|
@@ -4268,57 +4332,68 @@ async function updateCommand(logger2, currentVersion, options) {
|
|
|
4268
4332
|
}
|
|
4269
4333
|
}
|
|
4270
4334
|
|
|
4271
|
-
// src/cli/
|
|
4272
|
-
|
|
4273
|
-
|
|
4335
|
+
// src/cli/wrap-command.ts
|
|
4336
|
+
function createLogger({
|
|
4337
|
+
name,
|
|
4338
|
+
globalOpts,
|
|
4339
|
+
getVersion: getVersion2
|
|
4340
|
+
}) {
|
|
4341
|
+
return globalOpts.json ? new JsonLogger({ command: name, version: getVersion2() }) : new ConsoleLogger();
|
|
4342
|
+
}
|
|
4343
|
+
function wrapCommand({
|
|
4344
|
+
name,
|
|
4345
|
+
errorCode,
|
|
4346
|
+
handler,
|
|
4347
|
+
getVersion: getVersion2,
|
|
4348
|
+
loggerFactory = createLogger
|
|
4349
|
+
}) {
|
|
4274
4350
|
return async (...args) => {
|
|
4275
4351
|
const command = args[args.length - 1];
|
|
4276
4352
|
const options = args[args.length - 2];
|
|
4277
4353
|
const positionalArgs = args.slice(0, -2);
|
|
4278
4354
|
const globalOpts = command.parent?.opts() ?? {};
|
|
4279
|
-
const
|
|
4280
|
-
|
|
4281
|
-
logger2.configure({
|
|
4355
|
+
const logger5 = loggerFactory({ name, globalOpts, getVersion: getVersion2 });
|
|
4356
|
+
logger5.configure({
|
|
4282
4357
|
verbose: Boolean(globalOpts.verbose) || Boolean(options.verbose),
|
|
4283
4358
|
silent: Boolean(globalOpts.silent) || Boolean(options.silent)
|
|
4284
4359
|
});
|
|
4285
4360
|
try {
|
|
4286
|
-
await handler(
|
|
4287
|
-
|
|
4288
|
-
logger2.outputJson(true);
|
|
4289
|
-
}
|
|
4361
|
+
await handler(logger5, options, globalOpts, positionalArgs);
|
|
4362
|
+
logger5.outputJson(true);
|
|
4290
4363
|
} catch (error) {
|
|
4291
4364
|
const code = error instanceof CLIError ? error.code : errorCode;
|
|
4292
4365
|
const errorArg = error instanceof Error ? error : formatError(error);
|
|
4293
|
-
|
|
4366
|
+
logger5.error(errorArg, code);
|
|
4294
4367
|
process.exit(error instanceof CLIError ? error.exitCode : 1);
|
|
4295
4368
|
}
|
|
4296
4369
|
};
|
|
4297
4370
|
}
|
|
4371
|
+
|
|
4372
|
+
// src/cli/index.ts
|
|
4373
|
+
var getVersion = () => "7.21.0";
|
|
4374
|
+
function wrapCommand2(name, errorCode, handler) {
|
|
4375
|
+
return wrapCommand({ name, errorCode, handler, getVersion });
|
|
4376
|
+
}
|
|
4298
4377
|
var main = async () => {
|
|
4299
4378
|
const program = new Command();
|
|
4300
4379
|
const version = getVersion();
|
|
4301
4380
|
program.name("rulesync").description("Unified AI rules management CLI tool").version(version, "-v, --version", "Show version").option("-j, --json", "Output results as JSON");
|
|
4302
4381
|
program.command("init").description("Initialize rulesync in current directory").option("-V, --verbose", "Verbose output").option("-s, --silent", "Suppress all output").action(
|
|
4303
|
-
|
|
4304
|
-
await initCommand(
|
|
4382
|
+
wrapCommand2("init", "INIT_FAILED", async (logger5) => {
|
|
4383
|
+
await initCommand(logger5);
|
|
4305
4384
|
})
|
|
4306
4385
|
);
|
|
4307
4386
|
program.command("gitignore").description("Add generated files to .gitignore").option(
|
|
4308
4387
|
"-t, --targets <tools>",
|
|
4309
4388
|
"Comma-separated list of tools to include (e.g., 'claudecode,copilot' or '*' for all)",
|
|
4310
|
-
|
|
4311
|
-
return value.split(",").map((t) => t.trim()).filter(Boolean);
|
|
4312
|
-
}
|
|
4389
|
+
parseCommaSeparatedList
|
|
4313
4390
|
).option(
|
|
4314
4391
|
"-f, --features <features>",
|
|
4315
4392
|
`Comma-separated list of features to include (${ALL_FEATURES.join(",")}) or '*' for all`,
|
|
4316
|
-
|
|
4317
|
-
return value.split(",").map((f) => f.trim()).filter(Boolean);
|
|
4318
|
-
}
|
|
4393
|
+
parseCommaSeparatedList
|
|
4319
4394
|
).option("-V, --verbose", "Verbose output").option("-s, --silent", "Suppress all output").action(
|
|
4320
|
-
|
|
4321
|
-
await gitignoreCommand(
|
|
4395
|
+
wrapCommand2("gitignore", "GITIGNORE_FAILED", async (logger5, options) => {
|
|
4396
|
+
await gitignoreCommand(logger5, {
|
|
4322
4397
|
// eslint-disable-next-line no-type-assertion/no-type-assertion
|
|
4323
4398
|
targets: options.targets,
|
|
4324
4399
|
// eslint-disable-next-line no-type-assertion/no-type-assertion
|
|
@@ -4332,40 +4407,40 @@ var main = async () => {
|
|
|
4332
4407
|
).option(
|
|
4333
4408
|
"-f, --features <features>",
|
|
4334
4409
|
`Comma-separated list of features to fetch (${ALL_FEATURES.join(",")}) or '*' for all`,
|
|
4335
|
-
|
|
4410
|
+
parseCommaSeparatedList
|
|
4336
4411
|
).option("-r, --ref <ref>", "Branch, tag, or commit SHA to fetch from").option("-p, --path <path>", "Subdirectory path within the repository").option("-o, --output <dir>", "Output directory (default: .rulesync)").option(
|
|
4337
4412
|
"-c, --conflict <strategy>",
|
|
4338
4413
|
"Conflict resolution strategy: skip, overwrite (default: overwrite)"
|
|
4339
4414
|
).option("--token <token>", "Git provider token for private repositories").option("-V, --verbose", "Verbose output").option("-s, --silent", "Suppress all output").action(
|
|
4340
|
-
|
|
4415
|
+
wrapCommand2("fetch", "FETCH_FAILED", async (logger5, options, _globalOpts, positionalArgs) => {
|
|
4341
4416
|
const source = positionalArgs[0];
|
|
4342
|
-
await fetchCommand(
|
|
4417
|
+
await fetchCommand(logger5, { ...options, source });
|
|
4343
4418
|
})
|
|
4344
4419
|
);
|
|
4345
4420
|
program.command("import").description("Import configurations from AI tools to rulesync format").option(
|
|
4346
4421
|
"-t, --targets <tool>",
|
|
4347
4422
|
"Tool to import from (e.g., 'copilot', 'cursor', 'cline')",
|
|
4348
|
-
|
|
4423
|
+
parseCommaSeparatedList
|
|
4349
4424
|
).option(
|
|
4350
4425
|
"-f, --features <features>",
|
|
4351
4426
|
`Comma-separated list of features to import (${ALL_FEATURES.join(",")}) or '*' for all`,
|
|
4352
|
-
|
|
4427
|
+
parseCommaSeparatedList
|
|
4353
4428
|
).option("-V, --verbose", "Verbose output").option("-s, --silent", "Suppress all output").option("-g, --global", "Import for global(user scope) configuration files").action(
|
|
4354
|
-
|
|
4355
|
-
await importCommand(
|
|
4429
|
+
wrapCommand2("import", "IMPORT_FAILED", async (logger5, options) => {
|
|
4430
|
+
await importCommand(logger5, options);
|
|
4356
4431
|
})
|
|
4357
4432
|
);
|
|
4358
4433
|
program.command("mcp").description("Start MCP server for rulesync").action(
|
|
4359
|
-
|
|
4360
|
-
await mcpCommand(
|
|
4434
|
+
wrapCommand2("mcp", "MCP_FAILED", async (logger5, _options) => {
|
|
4435
|
+
await mcpCommand(logger5, { version });
|
|
4361
4436
|
})
|
|
4362
4437
|
);
|
|
4363
4438
|
program.command("install").description("Install skills from declarative sources in rulesync.jsonc").option("--update", "Force re-resolve all source refs, ignoring lockfile").option(
|
|
4364
4439
|
"--frozen",
|
|
4365
4440
|
"Fail if lockfile is missing or out of sync (for CI); fetches missing skills using locked refs"
|
|
4366
4441
|
).option("--token <token>", "GitHub token for private repos").option("-c, --config <path>", "Path to configuration file").option("-V, --verbose", "Verbose output").option("-s, --silent", "Suppress all output").action(
|
|
4367
|
-
|
|
4368
|
-
await installCommand(
|
|
4442
|
+
wrapCommand2("install", "INSTALL_FAILED", async (logger5, options) => {
|
|
4443
|
+
await installCommand(logger5, {
|
|
4369
4444
|
// eslint-disable-next-line no-type-assertion/no-type-assertion
|
|
4370
4445
|
update: options.update,
|
|
4371
4446
|
// eslint-disable-next-line no-type-assertion/no-type-assertion
|
|
@@ -4384,15 +4459,15 @@ var main = async () => {
|
|
|
4384
4459
|
program.command("generate").description("Generate configuration files for AI tools").option(
|
|
4385
4460
|
"-t, --targets <tools>",
|
|
4386
4461
|
"Comma-separated list of tools to generate for (e.g., 'copilot,cursor,cline' or '*' for all)",
|
|
4387
|
-
|
|
4462
|
+
parseCommaSeparatedList
|
|
4388
4463
|
).option(
|
|
4389
4464
|
"-f, --features <features>",
|
|
4390
4465
|
`Comma-separated list of features to generate (${ALL_FEATURES.join(",")}) or '*' for all`,
|
|
4391
|
-
|
|
4466
|
+
parseCommaSeparatedList
|
|
4392
4467
|
).option("--delete", "Delete all existing files in output directories before generating").option(
|
|
4393
4468
|
"-b, --base-dir <paths>",
|
|
4394
4469
|
"Base directories to generate files (comma-separated for multiple paths)",
|
|
4395
|
-
|
|
4470
|
+
parseCommaSeparatedList
|
|
4396
4471
|
).option("-V, --verbose", "Verbose output").option("-s, --silent", "Suppress all output").option("-c, --config <path>", "Path to configuration file").option("-g, --global", "Generate for global(user scope) configuration files").option(
|
|
4397
4472
|
"--simulate-commands",
|
|
4398
4473
|
"Generate simulated commands. This feature is only available for copilot, cursor and codexcli."
|
|
@@ -4403,13 +4478,13 @@ var main = async () => {
|
|
|
4403
4478
|
"--simulate-skills",
|
|
4404
4479
|
"Generate simulated skills. This feature is only available for copilot, cursor and codexcli."
|
|
4405
4480
|
).option("--dry-run", "Dry run: show changes without writing files").option("--check", "Check if files are up to date (exits with code 1 if changes needed)").action(
|
|
4406
|
-
|
|
4407
|
-
await generateCommand(
|
|
4481
|
+
wrapCommand2("generate", "GENERATION_FAILED", async (logger5, options) => {
|
|
4482
|
+
await generateCommand(logger5, options);
|
|
4408
4483
|
})
|
|
4409
4484
|
);
|
|
4410
4485
|
program.command("update").description("Update rulesync to the latest version").option("--check", "Check for updates without installing").option("--force", "Force update even if already at latest version").option("--token <token>", "GitHub token for API access").option("-V, --verbose", "Verbose output").option("-s, --silent", "Suppress all output").action(
|
|
4411
|
-
|
|
4412
|
-
await updateCommand(
|
|
4486
|
+
wrapCommand2("update", "UPDATE_FAILED", async (logger5, options) => {
|
|
4487
|
+
await updateCommand(logger5, version, options);
|
|
4413
4488
|
})
|
|
4414
4489
|
);
|
|
4415
4490
|
program.parse();
|