rulesync 7.15.2 → 7.17.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 -1
- package/dist/{chunk-WY325EI7.js → chunk-ZQUEAGJI.js} +164 -60
- package/dist/cli/index.cjs +331 -194
- package/dist/cli/index.js +177 -138
- package/dist/index.cjs +160 -61
- package/dist/index.js +1 -1
- package/package.json +1 -1
package/dist/cli/index.cjs
CHANGED
|
@@ -155,6 +155,8 @@ var RULESYNC_CURATED_SKILLS_RELATIVE_DIR_PATH = (0, import_node_path.join)(
|
|
|
155
155
|
var RULESYNC_SOURCES_LOCK_RELATIVE_FILE_PATH = "rulesync.lock";
|
|
156
156
|
var RULESYNC_MCP_FILE_NAME = "mcp.json";
|
|
157
157
|
var RULESYNC_HOOKS_FILE_NAME = "hooks.json";
|
|
158
|
+
var RULESYNC_CONFIG_SCHEMA_URL = "https://github.com/dyoshikawa/rulesync/releases/latest/download/config-schema.json";
|
|
159
|
+
var RULESYNC_MCP_SCHEMA_URL = "https://github.com/dyoshikawa/rulesync/releases/latest/download/mcp-schema.json";
|
|
158
160
|
var MAX_FILE_SIZE = 10 * 1024 * 1024;
|
|
159
161
|
var FETCH_CONCURRENCY_LIMIT = 10;
|
|
160
162
|
|
|
@@ -408,6 +410,7 @@ var import_node_path5 = require("path");
|
|
|
408
410
|
|
|
409
411
|
// src/utils/frontmatter.ts
|
|
410
412
|
var import_gray_matter = __toESM(require("gray-matter"), 1);
|
|
413
|
+
var import_js_yaml = require("js-yaml");
|
|
411
414
|
function isPlainObject(value) {
|
|
412
415
|
if (value === null || typeof value !== "object") return false;
|
|
413
416
|
const prototype = Object.getPrototypeOf(value);
|
|
@@ -446,8 +449,55 @@ function deepRemoveNullishObject(obj) {
|
|
|
446
449
|
}
|
|
447
450
|
return result;
|
|
448
451
|
}
|
|
449
|
-
function
|
|
450
|
-
|
|
452
|
+
function deepFlattenStringsValue(value) {
|
|
453
|
+
if (value === null || value === void 0) {
|
|
454
|
+
return void 0;
|
|
455
|
+
}
|
|
456
|
+
if (typeof value === "string") {
|
|
457
|
+
return value.replace(/\n+/g, " ").trim();
|
|
458
|
+
}
|
|
459
|
+
if (Array.isArray(value)) {
|
|
460
|
+
const cleanedArray = value.map((item) => deepFlattenStringsValue(item)).filter((item) => item !== void 0);
|
|
461
|
+
return cleanedArray;
|
|
462
|
+
}
|
|
463
|
+
if (isPlainObject(value)) {
|
|
464
|
+
const result = {};
|
|
465
|
+
for (const [key, val] of Object.entries(value)) {
|
|
466
|
+
const cleaned = deepFlattenStringsValue(val);
|
|
467
|
+
if (cleaned !== void 0) {
|
|
468
|
+
result[key] = cleaned;
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
return result;
|
|
472
|
+
}
|
|
473
|
+
return value;
|
|
474
|
+
}
|
|
475
|
+
function deepFlattenStringsObject(obj) {
|
|
476
|
+
if (!obj || typeof obj !== "object") {
|
|
477
|
+
return {};
|
|
478
|
+
}
|
|
479
|
+
const result = {};
|
|
480
|
+
for (const [key, val] of Object.entries(obj)) {
|
|
481
|
+
const cleaned = deepFlattenStringsValue(val);
|
|
482
|
+
if (cleaned !== void 0) {
|
|
483
|
+
result[key] = cleaned;
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
return result;
|
|
487
|
+
}
|
|
488
|
+
function stringifyFrontmatter(body, frontmatter, options) {
|
|
489
|
+
const { avoidBlockScalars = false } = options ?? {};
|
|
490
|
+
const cleanFrontmatter = avoidBlockScalars ? deepFlattenStringsObject(frontmatter) : deepRemoveNullishObject(frontmatter);
|
|
491
|
+
if (avoidBlockScalars) {
|
|
492
|
+
return import_gray_matter.default.stringify(body, cleanFrontmatter, {
|
|
493
|
+
engines: {
|
|
494
|
+
yaml: {
|
|
495
|
+
parse: (input) => (0, import_js_yaml.load)(input) ?? {},
|
|
496
|
+
stringify: (data) => (0, import_js_yaml.dump)(data, { lineWidth: -1 })
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
});
|
|
500
|
+
}
|
|
451
501
|
return import_gray_matter.default.stringify(body, cleanFrontmatter);
|
|
452
502
|
}
|
|
453
503
|
function parseFrontmatter(content, filePath) {
|
|
@@ -1599,7 +1649,7 @@ var CursorCommand = class _CursorCommand extends ToolCommand {
|
|
|
1599
1649
|
}
|
|
1600
1650
|
super({
|
|
1601
1651
|
...rest,
|
|
1602
|
-
fileContent: stringifyFrontmatter(body, frontmatter)
|
|
1652
|
+
fileContent: stringifyFrontmatter(body, frontmatter, { avoidBlockScalars: true })
|
|
1603
1653
|
});
|
|
1604
1654
|
this.frontmatter = frontmatter;
|
|
1605
1655
|
this.body = body;
|
|
@@ -5397,7 +5447,7 @@ var import_mini19 = require("zod/mini");
|
|
|
5397
5447
|
|
|
5398
5448
|
// src/types/mcp.ts
|
|
5399
5449
|
var import_mini18 = require("zod/mini");
|
|
5400
|
-
var McpServerSchema = import_mini18.z.
|
|
5450
|
+
var McpServerSchema = import_mini18.z.looseObject({
|
|
5401
5451
|
type: import_mini18.z.optional(import_mini18.z.enum(["stdio", "sse", "http"])),
|
|
5402
5452
|
command: import_mini18.z.optional(import_mini18.z.union([import_mini18.z.string(), import_mini18.z.array(import_mini18.z.string())])),
|
|
5403
5453
|
args: import_mini18.z.optional(import_mini18.z.array(import_mini18.z.string())),
|
|
@@ -5429,6 +5479,10 @@ var RulesyncMcpServerSchema = import_mini19.z.extend(McpServerSchema, {
|
|
|
5429
5479
|
var RulesyncMcpConfigSchema = import_mini19.z.object({
|
|
5430
5480
|
mcpServers: import_mini19.z.record(import_mini19.z.string(), RulesyncMcpServerSchema)
|
|
5431
5481
|
});
|
|
5482
|
+
var RulesyncMcpFileSchema = import_mini19.z.looseObject({
|
|
5483
|
+
$schema: import_mini19.z.optional(import_mini19.z.string()),
|
|
5484
|
+
...RulesyncMcpConfigSchema.shape
|
|
5485
|
+
});
|
|
5432
5486
|
var RulesyncMcp = class _RulesyncMcp extends RulesyncFile {
|
|
5433
5487
|
json;
|
|
5434
5488
|
constructor(params) {
|
|
@@ -5454,7 +5508,7 @@ var RulesyncMcp = class _RulesyncMcp extends RulesyncFile {
|
|
|
5454
5508
|
};
|
|
5455
5509
|
}
|
|
5456
5510
|
validate() {
|
|
5457
|
-
const result =
|
|
5511
|
+
const result = RulesyncMcpFileSchema.safeParse(this.json);
|
|
5458
5512
|
if (!result.success) {
|
|
5459
5513
|
return { success: false, error: result.error };
|
|
5460
5514
|
}
|
|
@@ -5557,11 +5611,17 @@ var ToolMcp = class extends ToolFile {
|
|
|
5557
5611
|
toRulesyncMcpDefault({
|
|
5558
5612
|
fileContent = void 0
|
|
5559
5613
|
} = {}) {
|
|
5614
|
+
const content = fileContent ?? this.fileContent;
|
|
5615
|
+
const { $schema: _, ...json } = JSON.parse(content);
|
|
5616
|
+
const withSchema = {
|
|
5617
|
+
$schema: RULESYNC_MCP_SCHEMA_URL,
|
|
5618
|
+
...json
|
|
5619
|
+
};
|
|
5560
5620
|
return new RulesyncMcp({
|
|
5561
5621
|
baseDir: this.baseDir,
|
|
5562
5622
|
relativeDirPath: RULESYNC_RELATIVE_DIR_PATH,
|
|
5563
|
-
relativeFilePath:
|
|
5564
|
-
fileContent:
|
|
5623
|
+
relativeFilePath: RULESYNC_MCP_FILE_NAME,
|
|
5624
|
+
fileContent: JSON.stringify(withSchema, null, 2)
|
|
5565
5625
|
});
|
|
5566
5626
|
}
|
|
5567
5627
|
static async fromFile(_params) {
|
|
@@ -5864,10 +5924,7 @@ var CodexcliMcp = class _CodexcliMcp extends ToolMcp {
|
|
|
5864
5924
|
toRulesyncMcp() {
|
|
5865
5925
|
const mcpServers = this.toml.mcp_servers ?? {};
|
|
5866
5926
|
const converted = convertFromCodexFormat(mcpServers);
|
|
5867
|
-
return
|
|
5868
|
-
baseDir: this.baseDir,
|
|
5869
|
-
relativeDirPath: RULESYNC_RELATIVE_DIR_PATH,
|
|
5870
|
-
relativeFilePath: ".mcp.json",
|
|
5927
|
+
return this.toRulesyncMcpDefault({
|
|
5871
5928
|
fileContent: JSON.stringify({ mcpServers: converted }, null, 2)
|
|
5872
5929
|
});
|
|
5873
5930
|
}
|
|
@@ -6025,12 +6082,26 @@ var CursorMcp = class _CursorMcp extends ToolMcp {
|
|
|
6025
6082
|
json;
|
|
6026
6083
|
constructor(params) {
|
|
6027
6084
|
super(params);
|
|
6028
|
-
|
|
6085
|
+
if (this.fileContent !== void 0) {
|
|
6086
|
+
try {
|
|
6087
|
+
this.json = JSON.parse(this.fileContent);
|
|
6088
|
+
} catch (error) {
|
|
6089
|
+
throw new Error(
|
|
6090
|
+
`Failed to parse Cursor MCP config at ${(0, import_node_path46.join)(this.relativeDirPath, this.relativeFilePath)}: ${formatError(error)}`,
|
|
6091
|
+
{ cause: error }
|
|
6092
|
+
);
|
|
6093
|
+
}
|
|
6094
|
+
} else {
|
|
6095
|
+
this.json = {};
|
|
6096
|
+
}
|
|
6029
6097
|
}
|
|
6030
6098
|
getJson() {
|
|
6031
6099
|
return this.json;
|
|
6032
6100
|
}
|
|
6033
|
-
|
|
6101
|
+
isDeletable() {
|
|
6102
|
+
return !this.global;
|
|
6103
|
+
}
|
|
6104
|
+
static getSettablePaths(_options) {
|
|
6034
6105
|
return {
|
|
6035
6106
|
relativeDirPath: ".cursor",
|
|
6036
6107
|
relativeFilePath: "mcp.json"
|
|
@@ -6038,41 +6109,62 @@ var CursorMcp = class _CursorMcp extends ToolMcp {
|
|
|
6038
6109
|
}
|
|
6039
6110
|
static async fromFile({
|
|
6040
6111
|
baseDir = process.cwd(),
|
|
6041
|
-
validate = true
|
|
6112
|
+
validate = true,
|
|
6113
|
+
global = false
|
|
6042
6114
|
}) {
|
|
6043
|
-
const
|
|
6044
|
-
|
|
6045
|
-
|
|
6046
|
-
|
|
6047
|
-
|
|
6048
|
-
)
|
|
6049
|
-
)
|
|
6115
|
+
const paths = this.getSettablePaths({ global });
|
|
6116
|
+
const filePath = (0, import_node_path46.join)(baseDir, paths.relativeDirPath, paths.relativeFilePath);
|
|
6117
|
+
const fileContent = await readFileContentOrNull(filePath) ?? '{"mcpServers":{}}';
|
|
6118
|
+
let json;
|
|
6119
|
+
try {
|
|
6120
|
+
json = JSON.parse(fileContent);
|
|
6121
|
+
} catch (error) {
|
|
6122
|
+
throw new Error(
|
|
6123
|
+
`Failed to parse Cursor MCP config at ${(0, import_node_path46.join)(paths.relativeDirPath, paths.relativeFilePath)}: ${formatError(error)}`,
|
|
6124
|
+
{ cause: error }
|
|
6125
|
+
);
|
|
6126
|
+
}
|
|
6127
|
+
const newJson = { ...json, mcpServers: json.mcpServers ?? {} };
|
|
6050
6128
|
return new _CursorMcp({
|
|
6051
6129
|
baseDir,
|
|
6052
|
-
relativeDirPath:
|
|
6053
|
-
relativeFilePath:
|
|
6054
|
-
fileContent,
|
|
6055
|
-
validate
|
|
6130
|
+
relativeDirPath: paths.relativeDirPath,
|
|
6131
|
+
relativeFilePath: paths.relativeFilePath,
|
|
6132
|
+
fileContent: JSON.stringify(newJson, null, 2),
|
|
6133
|
+
validate,
|
|
6134
|
+
global
|
|
6056
6135
|
});
|
|
6057
6136
|
}
|
|
6058
|
-
static fromRulesyncMcp({
|
|
6137
|
+
static async fromRulesyncMcp({
|
|
6059
6138
|
baseDir = process.cwd(),
|
|
6060
6139
|
rulesyncMcp,
|
|
6061
|
-
validate = true
|
|
6140
|
+
validate = true,
|
|
6141
|
+
global = false
|
|
6062
6142
|
}) {
|
|
6063
|
-
const
|
|
6064
|
-
const
|
|
6143
|
+
const paths = this.getSettablePaths({ global });
|
|
6144
|
+
const fileContent = await readOrInitializeFileContent(
|
|
6145
|
+
(0, import_node_path46.join)(baseDir, paths.relativeDirPath, paths.relativeFilePath),
|
|
6146
|
+
JSON.stringify({ mcpServers: {} }, null, 2)
|
|
6147
|
+
);
|
|
6148
|
+
let json;
|
|
6149
|
+
try {
|
|
6150
|
+
json = JSON.parse(fileContent);
|
|
6151
|
+
} catch (error) {
|
|
6152
|
+
throw new Error(
|
|
6153
|
+
`Failed to parse Cursor MCP config at ${(0, import_node_path46.join)(paths.relativeDirPath, paths.relativeFilePath)}: ${formatError(error)}`,
|
|
6154
|
+
{ cause: error }
|
|
6155
|
+
);
|
|
6156
|
+
}
|
|
6157
|
+
const rulesyncJson = rulesyncMcp.getJson();
|
|
6158
|
+
const mcpServers = isMcpServers(rulesyncJson.mcpServers) ? rulesyncJson.mcpServers : {};
|
|
6065
6159
|
const transformedServers = convertEnvToCursorFormat(mcpServers);
|
|
6066
|
-
const cursorConfig = {
|
|
6067
|
-
mcpServers: transformedServers
|
|
6068
|
-
};
|
|
6069
|
-
const fileContent = JSON.stringify(cursorConfig, null, 2);
|
|
6160
|
+
const cursorConfig = { ...json, mcpServers: transformedServers };
|
|
6070
6161
|
return new _CursorMcp({
|
|
6071
6162
|
baseDir,
|
|
6072
|
-
relativeDirPath:
|
|
6073
|
-
relativeFilePath:
|
|
6074
|
-
fileContent,
|
|
6075
|
-
validate
|
|
6163
|
+
relativeDirPath: paths.relativeDirPath,
|
|
6164
|
+
relativeFilePath: paths.relativeFilePath,
|
|
6165
|
+
fileContent: JSON.stringify(cursorConfig, null, 2),
|
|
6166
|
+
validate,
|
|
6167
|
+
global
|
|
6076
6168
|
});
|
|
6077
6169
|
}
|
|
6078
6170
|
toRulesyncMcp() {
|
|
@@ -6082,12 +6174,8 @@ var CursorMcp = class _CursorMcp extends ToolMcp {
|
|
|
6082
6174
|
...this.json,
|
|
6083
6175
|
mcpServers: transformedServers
|
|
6084
6176
|
};
|
|
6085
|
-
return
|
|
6086
|
-
|
|
6087
|
-
relativeDirPath: this.relativeDirPath,
|
|
6088
|
-
relativeFilePath: "rulesync.mcp.json",
|
|
6089
|
-
fileContent: JSON.stringify(transformedJson),
|
|
6090
|
-
validate: true
|
|
6177
|
+
return this.toRulesyncMcpDefault({
|
|
6178
|
+
fileContent: JSON.stringify(transformedJson, null, 2)
|
|
6091
6179
|
});
|
|
6092
6180
|
}
|
|
6093
6181
|
validate() {
|
|
@@ -6096,14 +6184,16 @@ var CursorMcp = class _CursorMcp extends ToolMcp {
|
|
|
6096
6184
|
static forDeletion({
|
|
6097
6185
|
baseDir = process.cwd(),
|
|
6098
6186
|
relativeDirPath,
|
|
6099
|
-
relativeFilePath
|
|
6187
|
+
relativeFilePath,
|
|
6188
|
+
global = false
|
|
6100
6189
|
}) {
|
|
6101
6190
|
return new _CursorMcp({
|
|
6102
6191
|
baseDir,
|
|
6103
6192
|
relativeDirPath,
|
|
6104
6193
|
relativeFilePath,
|
|
6105
6194
|
fileContent: "{}",
|
|
6106
|
-
validate: false
|
|
6195
|
+
validate: false,
|
|
6196
|
+
global
|
|
6107
6197
|
});
|
|
6108
6198
|
}
|
|
6109
6199
|
};
|
|
@@ -6163,13 +6253,7 @@ var FactorydroidMcp = class _FactorydroidMcp extends ToolMcp {
|
|
|
6163
6253
|
});
|
|
6164
6254
|
}
|
|
6165
6255
|
toRulesyncMcp() {
|
|
6166
|
-
return
|
|
6167
|
-
baseDir: this.baseDir,
|
|
6168
|
-
relativeDirPath: this.relativeDirPath,
|
|
6169
|
-
relativeFilePath: "rulesync.mcp.json",
|
|
6170
|
-
fileContent: JSON.stringify(this.json),
|
|
6171
|
-
validate: true
|
|
6172
|
-
});
|
|
6256
|
+
return this.toRulesyncMcpDefault();
|
|
6173
6257
|
}
|
|
6174
6258
|
validate() {
|
|
6175
6259
|
return { success: true, error: null };
|
|
@@ -6932,7 +7016,7 @@ var toolMcpFactories = /* @__PURE__ */ new Map([
|
|
|
6932
7016
|
class: CursorMcp,
|
|
6933
7017
|
meta: {
|
|
6934
7018
|
supportsProject: true,
|
|
6935
|
-
supportsGlobal:
|
|
7019
|
+
supportsGlobal: true,
|
|
6936
7020
|
supportsEnabledTools: false,
|
|
6937
7021
|
supportsDisabledTools: false
|
|
6938
7022
|
}
|
|
@@ -7645,9 +7729,15 @@ var import_node_path59 = require("path");
|
|
|
7645
7729
|
var DirFeatureProcessor = class {
|
|
7646
7730
|
baseDir;
|
|
7647
7731
|
dryRun;
|
|
7648
|
-
|
|
7732
|
+
avoidBlockScalars;
|
|
7733
|
+
constructor({
|
|
7734
|
+
baseDir = process.cwd(),
|
|
7735
|
+
dryRun = false,
|
|
7736
|
+
avoidBlockScalars = false
|
|
7737
|
+
}) {
|
|
7649
7738
|
this.baseDir = baseDir;
|
|
7650
7739
|
this.dryRun = dryRun;
|
|
7740
|
+
this.avoidBlockScalars = avoidBlockScalars;
|
|
7651
7741
|
}
|
|
7652
7742
|
/**
|
|
7653
7743
|
* Return tool targets that this feature supports.
|
|
@@ -7673,7 +7763,9 @@ var DirFeatureProcessor = class {
|
|
|
7673
7763
|
let mainFileContent;
|
|
7674
7764
|
if (mainFile) {
|
|
7675
7765
|
const mainFilePath = (0, import_node_path59.join)(dirPath, mainFile.name);
|
|
7676
|
-
const content = stringifyFrontmatter(mainFile.body, mainFile.frontmatter
|
|
7766
|
+
const content = stringifyFrontmatter(mainFile.body, mainFile.frontmatter, {
|
|
7767
|
+
avoidBlockScalars: this.avoidBlockScalars
|
|
7768
|
+
});
|
|
7677
7769
|
mainFileContent = addTrailingNewline(content);
|
|
7678
7770
|
const existingContent = await readFileContentOrNull(mainFilePath);
|
|
7679
7771
|
if (existingContent !== mainFileContent) {
|
|
@@ -10412,7 +10504,7 @@ var SkillsProcessor = class extends DirFeatureProcessor {
|
|
|
10412
10504
|
getFactory = defaultGetFactory4,
|
|
10413
10505
|
dryRun = false
|
|
10414
10506
|
}) {
|
|
10415
|
-
super({ baseDir, dryRun });
|
|
10507
|
+
super({ baseDir, dryRun, avoidBlockScalars: toolTarget === "cursor" });
|
|
10416
10508
|
const result = SkillsProcessorToolTargetSchema.safeParse(toolTarget);
|
|
10417
10509
|
if (!result.success) {
|
|
10418
10510
|
throw new Error(
|
|
@@ -11493,7 +11585,7 @@ var CursorSubagent = class _CursorSubagent extends ToolSubagent {
|
|
|
11493
11585
|
...cursorSection
|
|
11494
11586
|
};
|
|
11495
11587
|
const body = rulesyncSubagent.getBody();
|
|
11496
|
-
const fileContent = stringifyFrontmatter(body, cursorFrontmatter);
|
|
11588
|
+
const fileContent = stringifyFrontmatter(body, cursorFrontmatter, { avoidBlockScalars: true });
|
|
11497
11589
|
const paths = this.getSettablePaths({ global });
|
|
11498
11590
|
return new _CursorSubagent({
|
|
11499
11591
|
baseDir,
|
|
@@ -16932,13 +17024,22 @@ var import_jsonc_parser2 = require("jsonc-parser");
|
|
|
16932
17024
|
// src/config/config.ts
|
|
16933
17025
|
var import_node_path117 = require("path");
|
|
16934
17026
|
var import_mini59 = require("zod/mini");
|
|
16935
|
-
|
|
17027
|
+
|
|
17028
|
+
// src/utils/validation.ts
|
|
17029
|
+
function findControlCharacter(value) {
|
|
16936
17030
|
for (let i = 0; i < value.length; i++) {
|
|
16937
17031
|
const code = value.charCodeAt(i);
|
|
16938
|
-
if (code >= 0 && code <= 31 || code === 127)
|
|
17032
|
+
if (code >= 0 && code <= 31 || code === 127) {
|
|
17033
|
+
return { position: i, hex: `0x${code.toString(16).padStart(2, "0")}` };
|
|
17034
|
+
}
|
|
16939
17035
|
}
|
|
16940
|
-
return
|
|
17036
|
+
return null;
|
|
17037
|
+
}
|
|
17038
|
+
function hasControlCharacters(value) {
|
|
17039
|
+
return findControlCharacter(value) !== null;
|
|
16941
17040
|
}
|
|
17041
|
+
|
|
17042
|
+
// src/config/config.ts
|
|
16942
17043
|
var SourceEntrySchema = import_mini59.z.object({
|
|
16943
17044
|
source: import_mini59.z.string().check((0, import_mini59.minLength)(1, "source must be a non-empty string")),
|
|
16944
17045
|
skills: (0, import_mini59.optional)(import_mini59.z.array(import_mini59.z.string())),
|
|
@@ -18219,6 +18320,7 @@ async function createConfigFile() {
|
|
|
18219
18320
|
path4,
|
|
18220
18321
|
JSON.stringify(
|
|
18221
18322
|
{
|
|
18323
|
+
$schema: RULESYNC_CONFIG_SCHEMA_URL,
|
|
18222
18324
|
targets: ["copilot", "cursor", "claudecode", "codexcli"],
|
|
18223
18325
|
features: ["rules", "ignore", "mcp", "commands", "subagents", "skills", "hooks"],
|
|
18224
18326
|
baseDirs: ["."],
|
|
@@ -18276,6 +18378,7 @@ globs: ["**/*"]
|
|
|
18276
18378
|
const sampleMcpFile = {
|
|
18277
18379
|
filename: "mcp.json",
|
|
18278
18380
|
content: `{
|
|
18381
|
+
"$schema": "${RULESYNC_MCP_SCHEMA_URL}",
|
|
18279
18382
|
"mcpServers": {
|
|
18280
18383
|
"serena": {
|
|
18281
18384
|
"type": "stdio",
|
|
@@ -18458,17 +18561,8 @@ var import_node_path122 = require("path");
|
|
|
18458
18561
|
var import_node_util = require("util");
|
|
18459
18562
|
var execFileAsync = (0, import_node_util.promisify)(import_node_child_process.execFile);
|
|
18460
18563
|
var GIT_TIMEOUT_MS = 6e4;
|
|
18461
|
-
var ALLOWED_URL_SCHEMES = /^(https?:\/\/|ssh:\/\/|git:\/\/|file
|
|
18564
|
+
var ALLOWED_URL_SCHEMES = /^(https?:\/\/|ssh:\/\/|git:\/\/|file:\/\/\/).+$|^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9.-]+:[a-zA-Z0-9_.+/~-]+$/;
|
|
18462
18565
|
var INSECURE_URL_SCHEMES = /^(git:\/\/|http:\/\/)/;
|
|
18463
|
-
function findControlCharacter(value) {
|
|
18464
|
-
for (let i = 0; i < value.length; i++) {
|
|
18465
|
-
const code = value.charCodeAt(i);
|
|
18466
|
-
if (code >= 0 && code <= 31 || code === 127) {
|
|
18467
|
-
return { position: i, hex: `0x${code.toString(16).padStart(2, "0")}` };
|
|
18468
|
-
}
|
|
18469
|
-
}
|
|
18470
|
-
return null;
|
|
18471
|
-
}
|
|
18472
18566
|
var GitClientError = class extends Error {
|
|
18473
18567
|
constructor(message, cause) {
|
|
18474
18568
|
super(message, { cause });
|
|
@@ -18524,6 +18618,7 @@ async function resolveDefaultRef(url) {
|
|
|
18524
18618
|
const ref = stdout.match(/^ref: refs\/heads\/(.+)\tHEAD$/m)?.[1];
|
|
18525
18619
|
const sha = stdout.match(/^([0-9a-f]{40})\tHEAD$/m)?.[1];
|
|
18526
18620
|
if (!ref || !sha) throw new GitClientError(`Could not parse default branch from: ${url}`);
|
|
18621
|
+
validateRef(ref);
|
|
18527
18622
|
return { ref, sha };
|
|
18528
18623
|
} catch (error) {
|
|
18529
18624
|
if (error instanceof GitClientError) throw error;
|
|
@@ -18550,6 +18645,17 @@ async function fetchSkillFiles(params) {
|
|
|
18550
18645
|
const { url, ref, skillsPath } = params;
|
|
18551
18646
|
validateGitUrl(url);
|
|
18552
18647
|
validateRef(ref);
|
|
18648
|
+
if (skillsPath.split(/[/\\]/).includes("..") || (0, import_node_path122.isAbsolute)(skillsPath)) {
|
|
18649
|
+
throw new GitClientError(
|
|
18650
|
+
`Invalid skillsPath "${skillsPath}": must be a relative path without ".."`
|
|
18651
|
+
);
|
|
18652
|
+
}
|
|
18653
|
+
const ctrl = findControlCharacter(skillsPath);
|
|
18654
|
+
if (ctrl) {
|
|
18655
|
+
throw new GitClientError(
|
|
18656
|
+
`skillsPath contains control character ${ctrl.hex} at position ${ctrl.position}`
|
|
18657
|
+
);
|
|
18658
|
+
}
|
|
18553
18659
|
await checkGitAvailable();
|
|
18554
18660
|
const tmpDir = await createTempDirectory("rulesync-git-");
|
|
18555
18661
|
try {
|
|
@@ -18584,7 +18690,9 @@ async function fetchSkillFiles(params) {
|
|
|
18584
18690
|
}
|
|
18585
18691
|
}
|
|
18586
18692
|
var MAX_WALK_DEPTH = 20;
|
|
18587
|
-
|
|
18693
|
+
var MAX_TOTAL_FILES = 1e4;
|
|
18694
|
+
var MAX_TOTAL_SIZE = 100 * 1024 * 1024;
|
|
18695
|
+
async function walkDirectory(dir, baseDir, depth = 0, ctx = { totalFiles: 0, totalSize: 0 }) {
|
|
18588
18696
|
if (depth > MAX_WALK_DEPTH) {
|
|
18589
18697
|
throw new GitClientError(
|
|
18590
18698
|
`Directory tree exceeds max depth of ${MAX_WALK_DEPTH}: "${dir}". Aborting to prevent resource exhaustion.`
|
|
@@ -18599,7 +18707,7 @@ async function walkDirectory(dir, baseDir, depth = 0) {
|
|
|
18599
18707
|
continue;
|
|
18600
18708
|
}
|
|
18601
18709
|
if (await directoryExists(fullPath)) {
|
|
18602
|
-
results.push(...await walkDirectory(fullPath, baseDir, depth + 1));
|
|
18710
|
+
results.push(...await walkDirectory(fullPath, baseDir, depth + 1, ctx));
|
|
18603
18711
|
} else {
|
|
18604
18712
|
const size = await getFileSize(fullPath);
|
|
18605
18713
|
if (size > MAX_FILE_SIZE) {
|
|
@@ -18608,8 +18716,20 @@ async function walkDirectory(dir, baseDir, depth = 0) {
|
|
|
18608
18716
|
);
|
|
18609
18717
|
continue;
|
|
18610
18718
|
}
|
|
18719
|
+
ctx.totalFiles++;
|
|
18720
|
+
ctx.totalSize += size;
|
|
18721
|
+
if (ctx.totalFiles >= MAX_TOTAL_FILES) {
|
|
18722
|
+
throw new GitClientError(
|
|
18723
|
+
`Repository exceeds max file count of ${MAX_TOTAL_FILES}. Aborting to prevent resource exhaustion.`
|
|
18724
|
+
);
|
|
18725
|
+
}
|
|
18726
|
+
if (ctx.totalSize >= MAX_TOTAL_SIZE) {
|
|
18727
|
+
throw new GitClientError(
|
|
18728
|
+
`Repository exceeds max total size of ${MAX_TOTAL_SIZE / 1024 / 1024}MB. Aborting to prevent resource exhaustion.`
|
|
18729
|
+
);
|
|
18730
|
+
}
|
|
18611
18731
|
const content = await readFileContent(fullPath);
|
|
18612
|
-
results.push({ relativePath:
|
|
18732
|
+
results.push({ relativePath: (0, import_node_path122.relative)(baseDir, fullPath), content, size });
|
|
18613
18733
|
}
|
|
18614
18734
|
}
|
|
18615
18735
|
return results;
|
|
@@ -18625,7 +18745,7 @@ var LockedSkillSchema = import_mini60.z.object({
|
|
|
18625
18745
|
});
|
|
18626
18746
|
var LockedSourceSchema = import_mini60.z.object({
|
|
18627
18747
|
requestedRef: (0, import_mini60.optional)(import_mini60.z.string()),
|
|
18628
|
-
resolvedRef: import_mini60.z.string(),
|
|
18748
|
+
resolvedRef: import_mini60.z.string().check((0, import_mini60.refine)((v) => /^[0-9a-f]{40}$/.test(v), "resolvedRef must be a 40-character hex SHA")),
|
|
18629
18749
|
resolvedAt: (0, import_mini60.optional)(import_mini60.z.string()),
|
|
18630
18750
|
skills: import_mini60.z.record(import_mini60.z.string(), LockedSkillSchema)
|
|
18631
18751
|
});
|
|
@@ -18828,6 +18948,8 @@ async function resolveAndFetchSources(params) {
|
|
|
18828
18948
|
logger.error(`Failed to fetch source "${sourceEntry.source}": ${formatError(error)}`);
|
|
18829
18949
|
if (error instanceof GitHubClientError) {
|
|
18830
18950
|
logGitHubAuthHints(error);
|
|
18951
|
+
} else if (error instanceof GitClientError) {
|
|
18952
|
+
logGitClientHints(error);
|
|
18831
18953
|
}
|
|
18832
18954
|
}
|
|
18833
18955
|
}
|
|
@@ -18848,6 +18970,13 @@ async function resolveAndFetchSources(params) {
|
|
|
18848
18970
|
}
|
|
18849
18971
|
return { fetchedSkillCount: totalSkillCount, sourcesProcessed: sources.length };
|
|
18850
18972
|
}
|
|
18973
|
+
function logGitClientHints(error) {
|
|
18974
|
+
if (error.message.includes("not installed")) {
|
|
18975
|
+
logger.info("Hint: Install git and ensure it is available on your PATH.");
|
|
18976
|
+
} else {
|
|
18977
|
+
logger.info("Hint: Check your git credentials (SSH keys, credential helper, or access token).");
|
|
18978
|
+
}
|
|
18979
|
+
}
|
|
18851
18980
|
async function checkLockedSkillsExist(curatedDir, skillNames) {
|
|
18852
18981
|
if (skillNames.length === 0) return true;
|
|
18853
18982
|
for (const name of skillNames) {
|
|
@@ -18857,9 +18986,88 @@ async function checkLockedSkillsExist(curatedDir, skillNames) {
|
|
|
18857
18986
|
}
|
|
18858
18987
|
return true;
|
|
18859
18988
|
}
|
|
18989
|
+
async function cleanPreviousCuratedSkills(curatedDir, lockedSkillNames) {
|
|
18990
|
+
const resolvedCuratedDir = (0, import_node_path124.resolve)(curatedDir);
|
|
18991
|
+
for (const prevSkill of lockedSkillNames) {
|
|
18992
|
+
const prevDir = (0, import_node_path124.join)(curatedDir, prevSkill);
|
|
18993
|
+
if (!(0, import_node_path124.resolve)(prevDir).startsWith(resolvedCuratedDir + import_node_path124.sep)) {
|
|
18994
|
+
logger.warn(
|
|
18995
|
+
`Skipping removal of "${prevSkill}": resolved path is outside the curated directory.`
|
|
18996
|
+
);
|
|
18997
|
+
continue;
|
|
18998
|
+
}
|
|
18999
|
+
if (await directoryExists(prevDir)) {
|
|
19000
|
+
await removeDirectory(prevDir);
|
|
19001
|
+
}
|
|
19002
|
+
}
|
|
19003
|
+
}
|
|
19004
|
+
function shouldSkipSkill(params) {
|
|
19005
|
+
const { skillName, sourceKey, localSkillNames, alreadyFetchedSkillNames } = params;
|
|
19006
|
+
if (skillName.includes("..") || skillName.includes("/") || skillName.includes("\\")) {
|
|
19007
|
+
logger.warn(
|
|
19008
|
+
`Skipping skill with invalid name "${skillName}" from ${sourceKey}: contains path traversal characters.`
|
|
19009
|
+
);
|
|
19010
|
+
return true;
|
|
19011
|
+
}
|
|
19012
|
+
if (localSkillNames.has(skillName)) {
|
|
19013
|
+
logger.debug(
|
|
19014
|
+
`Skipping remote skill "${skillName}" from ${sourceKey}: local skill takes precedence.`
|
|
19015
|
+
);
|
|
19016
|
+
return true;
|
|
19017
|
+
}
|
|
19018
|
+
if (alreadyFetchedSkillNames.has(skillName)) {
|
|
19019
|
+
logger.warn(
|
|
19020
|
+
`Skipping duplicate skill "${skillName}" from ${sourceKey}: already fetched from another source.`
|
|
19021
|
+
);
|
|
19022
|
+
return true;
|
|
19023
|
+
}
|
|
19024
|
+
return false;
|
|
19025
|
+
}
|
|
19026
|
+
async function writeSkillAndComputeIntegrity(params) {
|
|
19027
|
+
const { skillName, files, curatedDir, locked, resolvedSha, sourceKey } = params;
|
|
19028
|
+
const written = [];
|
|
19029
|
+
for (const file of files) {
|
|
19030
|
+
checkPathTraversal({
|
|
19031
|
+
relativePath: file.relativePath,
|
|
19032
|
+
intendedRootDir: (0, import_node_path124.join)(curatedDir, skillName)
|
|
19033
|
+
});
|
|
19034
|
+
await writeFileContent((0, import_node_path124.join)(curatedDir, skillName, file.relativePath), file.content);
|
|
19035
|
+
written.push({ path: file.relativePath, content: file.content });
|
|
19036
|
+
}
|
|
19037
|
+
const integrity = computeSkillIntegrity(written);
|
|
19038
|
+
const lockedSkillEntry = locked?.skills[skillName];
|
|
19039
|
+
if (lockedSkillEntry?.integrity && lockedSkillEntry.integrity !== integrity && resolvedSha === locked?.resolvedRef) {
|
|
19040
|
+
logger.warn(
|
|
19041
|
+
`Integrity mismatch for skill "${skillName}" from ${sourceKey}: expected "${lockedSkillEntry.integrity}", got "${integrity}". Content may have been tampered with.`
|
|
19042
|
+
);
|
|
19043
|
+
}
|
|
19044
|
+
return { integrity };
|
|
19045
|
+
}
|
|
19046
|
+
function buildLockUpdate(params) {
|
|
19047
|
+
const { lock, sourceKey, fetchedSkills, locked, requestedRef, resolvedSha } = params;
|
|
19048
|
+
const fetchedNames = Object.keys(fetchedSkills);
|
|
19049
|
+
const mergedSkills = { ...fetchedSkills };
|
|
19050
|
+
if (locked) {
|
|
19051
|
+
for (const [skillName, skillEntry] of Object.entries(locked.skills)) {
|
|
19052
|
+
if (!(skillName in mergedSkills)) {
|
|
19053
|
+
mergedSkills[skillName] = skillEntry;
|
|
19054
|
+
}
|
|
19055
|
+
}
|
|
19056
|
+
}
|
|
19057
|
+
const updatedLock = setLockedSource(lock, sourceKey, {
|
|
19058
|
+
requestedRef,
|
|
19059
|
+
resolvedRef: resolvedSha,
|
|
19060
|
+
resolvedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
19061
|
+
skills: mergedSkills
|
|
19062
|
+
});
|
|
19063
|
+
logger.info(
|
|
19064
|
+
`Fetched ${fetchedNames.length} skill(s) from ${sourceKey}: ${fetchedNames.join(", ") || "(none)"}`
|
|
19065
|
+
);
|
|
19066
|
+
return { updatedLock, fetchedNames };
|
|
19067
|
+
}
|
|
18860
19068
|
async function fetchSource(params) {
|
|
18861
19069
|
const { sourceEntry, client, baseDir, localSkillNames, alreadyFetchedSkillNames, updateSources } = params;
|
|
18862
|
-
|
|
19070
|
+
const { lock } = params;
|
|
18863
19071
|
const parsed = parseSource(sourceEntry.source);
|
|
18864
19072
|
if (parsed.provider === "gitlab") {
|
|
18865
19073
|
logger.warn(`GitLab sources are not yet supported. Skipping "${sourceEntry.source}".`);
|
|
@@ -18912,37 +19120,15 @@ async function fetchSource(params) {
|
|
|
18912
19120
|
const semaphore = new import_promise2.Semaphore(FETCH_CONCURRENCY_LIMIT);
|
|
18913
19121
|
const fetchedSkills = {};
|
|
18914
19122
|
if (locked) {
|
|
18915
|
-
|
|
18916
|
-
for (const prevSkill of lockedSkillNames) {
|
|
18917
|
-
const prevDir = (0, import_node_path124.join)(curatedDir, prevSkill);
|
|
18918
|
-
if (!(0, import_node_path124.resolve)(prevDir).startsWith(resolvedCuratedDir + import_node_path124.sep)) {
|
|
18919
|
-
logger.warn(
|
|
18920
|
-
`Skipping removal of "${prevSkill}": resolved path is outside the curated directory.`
|
|
18921
|
-
);
|
|
18922
|
-
continue;
|
|
18923
|
-
}
|
|
18924
|
-
if (await directoryExists(prevDir)) {
|
|
18925
|
-
await removeDirectory(prevDir);
|
|
18926
|
-
}
|
|
18927
|
-
}
|
|
19123
|
+
await cleanPreviousCuratedSkills(curatedDir, lockedSkillNames);
|
|
18928
19124
|
}
|
|
18929
19125
|
for (const skillDir of filteredDirs) {
|
|
18930
|
-
if (
|
|
18931
|
-
|
|
18932
|
-
|
|
18933
|
-
|
|
18934
|
-
|
|
18935
|
-
}
|
|
18936
|
-
if (localSkillNames.has(skillDir.name)) {
|
|
18937
|
-
logger.debug(
|
|
18938
|
-
`Skipping remote skill "${skillDir.name}" from ${sourceKey}: local skill takes precedence.`
|
|
18939
|
-
);
|
|
18940
|
-
continue;
|
|
18941
|
-
}
|
|
18942
|
-
if (alreadyFetchedSkillNames.has(skillDir.name)) {
|
|
18943
|
-
logger.warn(
|
|
18944
|
-
`Skipping duplicate skill "${skillDir.name}" from ${sourceKey}: already fetched from another source.`
|
|
18945
|
-
);
|
|
19126
|
+
if (shouldSkipSkill({
|
|
19127
|
+
skillName: skillDir.name,
|
|
19128
|
+
sourceKey,
|
|
19129
|
+
localSkillNames,
|
|
19130
|
+
alreadyFetchedSkillNames
|
|
19131
|
+
})) {
|
|
18946
19132
|
continue;
|
|
18947
19133
|
}
|
|
18948
19134
|
const allFiles = await listDirectoryRecursive({
|
|
@@ -18965,55 +19151,39 @@ async function fetchSource(params) {
|
|
|
18965
19151
|
const skillFiles = [];
|
|
18966
19152
|
for (const file of files) {
|
|
18967
19153
|
const relativeToSkill = file.path.substring(skillDir.path.length + 1);
|
|
18968
|
-
const localFilePath = (0, import_node_path124.join)(curatedDir, skillDir.name, relativeToSkill);
|
|
18969
|
-
checkPathTraversal({
|
|
18970
|
-
relativePath: relativeToSkill,
|
|
18971
|
-
intendedRootDir: (0, import_node_path124.join)(curatedDir, skillDir.name)
|
|
18972
|
-
});
|
|
18973
19154
|
const content = await withSemaphore(
|
|
18974
19155
|
semaphore,
|
|
18975
19156
|
() => client.getFileContent(parsed.owner, parsed.repo, file.path, ref)
|
|
18976
19157
|
);
|
|
18977
|
-
|
|
18978
|
-
skillFiles.push({ path: relativeToSkill, content });
|
|
19158
|
+
skillFiles.push({ relativePath: relativeToSkill, content });
|
|
18979
19159
|
}
|
|
18980
|
-
|
|
18981
|
-
|
|
18982
|
-
|
|
18983
|
-
|
|
18984
|
-
|
|
18985
|
-
|
|
18986
|
-
|
|
18987
|
-
|
|
19160
|
+
fetchedSkills[skillDir.name] = await writeSkillAndComputeIntegrity({
|
|
19161
|
+
skillName: skillDir.name,
|
|
19162
|
+
files: skillFiles,
|
|
19163
|
+
curatedDir,
|
|
19164
|
+
locked,
|
|
19165
|
+
resolvedSha,
|
|
19166
|
+
sourceKey
|
|
19167
|
+
});
|
|
18988
19168
|
logger.debug(`Fetched skill "${skillDir.name}" from ${sourceKey}`);
|
|
18989
19169
|
}
|
|
18990
|
-
const
|
|
18991
|
-
|
|
18992
|
-
|
|
18993
|
-
|
|
18994
|
-
|
|
18995
|
-
mergedSkills[skillName] = skillEntry;
|
|
18996
|
-
}
|
|
18997
|
-
}
|
|
18998
|
-
}
|
|
18999
|
-
lock = setLockedSource(lock, sourceKey, {
|
|
19170
|
+
const result = buildLockUpdate({
|
|
19171
|
+
lock,
|
|
19172
|
+
sourceKey,
|
|
19173
|
+
fetchedSkills,
|
|
19174
|
+
locked,
|
|
19000
19175
|
requestedRef,
|
|
19001
|
-
|
|
19002
|
-
resolvedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
19003
|
-
skills: mergedSkills
|
|
19176
|
+
resolvedSha
|
|
19004
19177
|
});
|
|
19005
|
-
logger.info(
|
|
19006
|
-
`Fetched ${fetchedNames.length} skill(s) from ${sourceKey}: ${fetchedNames.join(", ") || "(none)"}`
|
|
19007
|
-
);
|
|
19008
19178
|
return {
|
|
19009
|
-
skillCount: fetchedNames.length,
|
|
19010
|
-
fetchedSkillNames: fetchedNames,
|
|
19011
|
-
updatedLock:
|
|
19179
|
+
skillCount: result.fetchedNames.length,
|
|
19180
|
+
fetchedSkillNames: result.fetchedNames,
|
|
19181
|
+
updatedLock: result.updatedLock
|
|
19012
19182
|
};
|
|
19013
19183
|
}
|
|
19014
19184
|
async function fetchSourceViaGit(params) {
|
|
19015
19185
|
const { sourceEntry, baseDir, localSkillNames, alreadyFetchedSkillNames, updateSources, frozen } = params;
|
|
19016
|
-
|
|
19186
|
+
const { lock } = params;
|
|
19017
19187
|
const url = sourceEntry.source;
|
|
19018
19188
|
const locked = getLockedSource(lock, url);
|
|
19019
19189
|
const lockedSkillNames = locked ? getLockedSkillNames(locked) : [];
|
|
@@ -19069,68 +19239,35 @@ async function fetchSourceViaGit(params) {
|
|
|
19069
19239
|
const allNames = [...skillFileMap.keys()];
|
|
19070
19240
|
const filteredNames = isWildcard ? allNames : allNames.filter((n) => skillFilter.includes(n));
|
|
19071
19241
|
if (locked) {
|
|
19072
|
-
|
|
19073
|
-
for (const prev of lockedSkillNames) {
|
|
19074
|
-
const dir = (0, import_node_path124.join)(curatedDir, prev);
|
|
19075
|
-
if ((0, import_node_path124.resolve)(dir).startsWith(base + import_node_path124.sep) && await directoryExists(dir)) {
|
|
19076
|
-
await removeDirectory(dir);
|
|
19077
|
-
}
|
|
19078
|
-
}
|
|
19242
|
+
await cleanPreviousCuratedSkills(curatedDir, lockedSkillNames);
|
|
19079
19243
|
}
|
|
19080
19244
|
const fetchedSkills = {};
|
|
19081
19245
|
for (const skillName of filteredNames) {
|
|
19082
|
-
if (
|
|
19083
|
-
logger.warn(
|
|
19084
|
-
`Skipping skill with invalid name "${skillName}" from ${url}: contains path traversal characters.`
|
|
19085
|
-
);
|
|
19246
|
+
if (shouldSkipSkill({ skillName, sourceKey: url, localSkillNames, alreadyFetchedSkillNames })) {
|
|
19086
19247
|
continue;
|
|
19087
19248
|
}
|
|
19088
|
-
|
|
19089
|
-
|
|
19090
|
-
|
|
19091
|
-
|
|
19092
|
-
|
|
19093
|
-
|
|
19094
|
-
|
|
19095
|
-
|
|
19096
|
-
`Skipping duplicate skill "${skillName}" from ${url}: already fetched from another source.`
|
|
19097
|
-
);
|
|
19098
|
-
continue;
|
|
19099
|
-
}
|
|
19100
|
-
const files = skillFileMap.get(skillName) ?? [];
|
|
19101
|
-
const written = [];
|
|
19102
|
-
for (const file of files) {
|
|
19103
|
-
checkPathTraversal({
|
|
19104
|
-
relativePath: file.relativePath,
|
|
19105
|
-
intendedRootDir: (0, import_node_path124.join)(curatedDir, skillName)
|
|
19106
|
-
});
|
|
19107
|
-
await writeFileContent((0, import_node_path124.join)(curatedDir, skillName, file.relativePath), file.content);
|
|
19108
|
-
written.push({ path: file.relativePath, content: file.content });
|
|
19109
|
-
}
|
|
19110
|
-
const integrity = computeSkillIntegrity(written);
|
|
19111
|
-
const lockedSkillEntry = locked?.skills[skillName];
|
|
19112
|
-
if (lockedSkillEntry?.integrity && lockedSkillEntry.integrity !== integrity && resolvedSha === locked?.resolvedRef) {
|
|
19113
|
-
logger.warn(`Integrity mismatch for skill "${skillName}" from ${url}.`);
|
|
19114
|
-
}
|
|
19115
|
-
fetchedSkills[skillName] = { integrity };
|
|
19116
|
-
}
|
|
19117
|
-
const fetchedNames = Object.keys(fetchedSkills);
|
|
19118
|
-
const mergedSkills = { ...fetchedSkills };
|
|
19119
|
-
if (locked) {
|
|
19120
|
-
for (const [k, v] of Object.entries(locked.skills)) {
|
|
19121
|
-
if (!(k in mergedSkills)) mergedSkills[k] = v;
|
|
19122
|
-
}
|
|
19249
|
+
fetchedSkills[skillName] = await writeSkillAndComputeIntegrity({
|
|
19250
|
+
skillName,
|
|
19251
|
+
files: skillFileMap.get(skillName) ?? [],
|
|
19252
|
+
curatedDir,
|
|
19253
|
+
locked,
|
|
19254
|
+
resolvedSha,
|
|
19255
|
+
sourceKey: url
|
|
19256
|
+
});
|
|
19123
19257
|
}
|
|
19124
|
-
|
|
19258
|
+
const result = buildLockUpdate({
|
|
19259
|
+
lock,
|
|
19260
|
+
sourceKey: url,
|
|
19261
|
+
fetchedSkills,
|
|
19262
|
+
locked,
|
|
19125
19263
|
requestedRef,
|
|
19126
|
-
|
|
19127
|
-
resolvedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
19128
|
-
skills: mergedSkills
|
|
19264
|
+
resolvedSha
|
|
19129
19265
|
});
|
|
19130
|
-
|
|
19131
|
-
|
|
19132
|
-
|
|
19133
|
-
|
|
19266
|
+
return {
|
|
19267
|
+
skillCount: result.fetchedNames.length,
|
|
19268
|
+
fetchedSkillNames: result.fetchedNames,
|
|
19269
|
+
updatedLock: result.updatedLock
|
|
19270
|
+
};
|
|
19134
19271
|
}
|
|
19135
19272
|
|
|
19136
19273
|
// src/cli/commands/install.ts
|
|
@@ -20946,7 +21083,7 @@ async function updateCommand(currentVersion, options) {
|
|
|
20946
21083
|
}
|
|
20947
21084
|
|
|
20948
21085
|
// src/cli/index.ts
|
|
20949
|
-
var getVersion = () => "7.
|
|
21086
|
+
var getVersion = () => "7.17.0";
|
|
20950
21087
|
var main = async () => {
|
|
20951
21088
|
const program = new import_commander.Command();
|
|
20952
21089
|
const version = getVersion();
|