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