skiller 0.9.12 → 0.9.14

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.
@@ -42,6 +42,7 @@ const AgentSourceCompatibility_1 = require("./AgentSourceCompatibility");
42
42
  const ConfigLoader_1 = require("./ConfigLoader");
43
43
  const FileSystemUtils_1 = require("./FileSystemUtils");
44
44
  const project_paths_1 = require("./project-paths");
45
+ const PRESET_CONFIG_FILENAME = 'preset.toml';
45
46
  const PRESET_MANIFEST_RELATIVE_PATH = path
46
47
  .join(project_paths_1.CANONICAL_SKILLER_DIR, '.skiller-preset-manifest.json')
47
48
  .replace(/\\/g, '/');
@@ -129,6 +130,13 @@ async function collectPresetFiles(rootDir) {
129
130
  await walk(rootDir);
130
131
  return results.sort((a, b) => a.localeCompare(b));
131
132
  }
133
+ async function collectFileFromPath(targetPath) {
134
+ const stats = await fs_1.promises.stat(targetPath);
135
+ if (stats.isFile()) {
136
+ return targetPath;
137
+ }
138
+ throw new Error(`[skiller] Included path '${targetPath}' must resolve to a file, not a directory.`);
139
+ }
132
140
  function isHardDenied(relativePath) {
133
141
  return HARD_DENY_PATTERNS.some((pattern) => (0, FileSystemUtils_1.matchesPattern)(relativePath, pattern));
134
142
  }
@@ -152,6 +160,40 @@ async function readRawTomlFile(filePath) {
152
160
  return {};
153
161
  }
154
162
  }
163
+ async function readPresetConfig(presetRoot) {
164
+ const configPath = path.join(presetRoot, PRESET_CONFIG_FILENAME);
165
+ const raw = await readRawTomlFile(configPath);
166
+ if (Object.keys(raw).length === 0) {
167
+ return { include: [] };
168
+ }
169
+ if (raw.version !== undefined && raw.version !== 1) {
170
+ throw new Error(`[skiller] ${PRESET_CONFIG_FILENAME} in '${presetRoot}' must use version = 1.`);
171
+ }
172
+ if (raw.include !== undefined && !Array.isArray(raw.include)) {
173
+ throw new Error(`[skiller] ${PRESET_CONFIG_FILENAME} in '${presetRoot}' must set include = ["path", ...].`);
174
+ }
175
+ return {
176
+ include: Array.isArray(raw.include)
177
+ ? raw.include.map((entry) => String(entry))
178
+ : [],
179
+ };
180
+ }
181
+ function deriveTargetRelativePath(sourcePath) {
182
+ const segments = normalizeRelativePath(path.resolve(sourcePath)).split('/');
183
+ const specialFileNames = new Set(['skills-lock.json', 'skiller-lock.json']);
184
+ for (let index = 0; index < segments.length; index += 1) {
185
+ const segment = segments[index];
186
+ if (segment === '.agents' ||
187
+ segment === '.claude' ||
188
+ segment === '.codex') {
189
+ return segments.slice(index).join('/');
190
+ }
191
+ if (specialFileNames.has(segment) && index === segments.length - 1) {
192
+ return segment;
193
+ }
194
+ }
195
+ return null;
196
+ }
155
197
  async function looksLikePresetRoot(dirPath) {
156
198
  try {
157
199
  const stats = await fs_1.promises.stat(path.join(dirPath, '.agents'));
@@ -225,11 +267,10 @@ async function resolvePresetSourceInput(projectRoot, rawSource) {
225
267
  }
226
268
  return cwdCandidate;
227
269
  }
228
- async function writeMergedConfig(projectRoot, presetRoot) {
229
- const baseConfigPath = path.join(presetRoot, project_paths_1.CANONICAL_SKILLER_DIR, project_paths_1.SKILLER_CONFIG_FILE);
270
+ async function writeMergedConfig(projectRoot, baseConfigPath) {
230
271
  const localConfigPath = path.join(projectRoot, project_paths_1.CANONICAL_SKILLER_DIR, project_paths_1.SKILLER_CONFIG_FILE);
231
272
  const [baseRaw, localRaw] = await Promise.all([
232
- readRawTomlFile(baseConfigPath),
273
+ baseConfigPath ? readRawTomlFile(baseConfigPath) : Promise.resolve({}),
233
274
  readRawTomlFile(localConfigPath),
234
275
  ]);
235
276
  const merged = (0, ConfigLoader_1.deepMergeConfig)((0, ConfigLoader_1.withoutSync)(baseRaw), (0, ConfigLoader_1.withoutSync)(localRaw));
@@ -244,21 +285,61 @@ async function installPresetIntoProject(projectRoot, options) {
244
285
  try {
245
286
  const { preset, presetRoot } = await resolvePresetRoot(workspace, options.preset);
246
287
  const previousManifest = await readPresetManifest(projectRoot);
247
- const selectedFiles = (await collectPresetFiles(presetRoot)).filter((relativePath) => !isHardDenied(relativePath) &&
248
- !COPY_EXCEPTIONS.has(relativePath) &&
288
+ const presetConfig = await readPresetConfig(presetRoot);
289
+ const nextSources = new Map();
290
+ let mergedConfigSourcePath;
291
+ for (const includePath of presetConfig.include) {
292
+ const resolvedIncludePath = path.resolve(presetRoot, includePath);
293
+ const sourcePath = await collectFileFromPath(resolvedIncludePath);
294
+ const targetRelativePath = deriveTargetRelativePath(sourcePath);
295
+ if (!targetRelativePath) {
296
+ throw new Error(`[skiller] Included path '${includePath}' in '${preset}/${PRESET_CONFIG_FILENAME}' must resolve under .agents, .claude, .codex, skills-lock.json, or skiller-lock.json.`);
297
+ }
298
+ if (isHardDenied(targetRelativePath)) {
299
+ throw new Error(`[skiller] Included path '${includePath}' resolves to denied target '${targetRelativePath}'.`);
300
+ }
301
+ if (COPY_EXCEPTIONS.has(targetRelativePath)) {
302
+ mergedConfigSourcePath = sourcePath;
303
+ continue;
304
+ }
305
+ if (!isPresetAllowlisted(targetRelativePath)) {
306
+ throw new Error(`[skiller] Included path '${includePath}' resolves to unsupported target '${targetRelativePath}'.`);
307
+ }
308
+ nextSources.set(targetRelativePath, sourcePath);
309
+ }
310
+ const selectedFiles = (await collectPresetFiles(presetRoot)).filter((relativePath) => relativePath !== PRESET_CONFIG_FILENAME &&
311
+ !isHardDenied(relativePath) &&
249
312
  isPresetAllowlisted(relativePath));
313
+ if (selectedFiles.includes(path
314
+ .join(project_paths_1.CANONICAL_SKILLER_DIR, project_paths_1.SKILLER_CONFIG_FILE)
315
+ .replace(/\\/g, '/'))) {
316
+ mergedConfigSourcePath = path.join(presetRoot, project_paths_1.CANONICAL_SKILLER_DIR, project_paths_1.SKILLER_CONFIG_FILE);
317
+ }
250
318
  const nextFiles = {};
251
319
  const synced = [];
320
+ for (const [targetRelativePath, sourcePath] of [
321
+ ...nextSources.entries(),
322
+ ].sort(([left], [right]) => left.localeCompare(right))) {
323
+ const content = await fs_1.promises.readFile(sourcePath);
324
+ const targetPath = path.join(projectRoot, targetRelativePath);
325
+ await fs_1.promises.mkdir(path.dirname(targetPath), { recursive: true });
326
+ await fs_1.promises.writeFile(targetPath, content);
327
+ nextFiles[targetRelativePath] = hashBuffer(content);
328
+ synced.push(targetRelativePath);
329
+ }
252
330
  for (const relativePath of selectedFiles) {
253
331
  const sourcePath = path.join(presetRoot, relativePath);
254
332
  const targetPath = path.join(projectRoot, relativePath);
333
+ if (COPY_EXCEPTIONS.has(relativePath)) {
334
+ continue;
335
+ }
255
336
  const content = await fs_1.promises.readFile(sourcePath);
256
337
  await fs_1.promises.mkdir(path.dirname(targetPath), { recursive: true });
257
338
  await fs_1.promises.writeFile(targetPath, content);
258
339
  nextFiles[relativePath] = hashBuffer(content);
259
340
  synced.push(relativePath);
260
341
  }
261
- const mergedConfigHash = await writeMergedConfig(projectRoot, presetRoot);
342
+ const mergedConfigHash = await writeMergedConfig(projectRoot, mergedConfigSourcePath);
262
343
  nextFiles['.agents/skiller.toml'] = mergedConfigHash;
263
344
  synced.push('.agents/skiller.toml');
264
345
  const removed = [];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "skiller",
3
- "version": "0.9.12",
3
+ "version": "0.9.14",
4
4
  "description": "Skiller — apply the same rules to all coding agents",
5
5
  "main": "dist/lib.js",
6
6
  "publishConfig": {
@@ -10,6 +10,7 @@
10
10
  "lint": "eslint \"src/**/*.{ts,tsx}\"",
11
11
  "lint:fix": "eslint \"src/**/*.{ts,tsx}\" --fix",
12
12
  "format": "prettier --write \"src/**/*.{ts,tsx,json,md}\"",
13
+ "prepack": "pnpm build",
13
14
  "refresh:skills-catalog": "node scripts/refresh-skills-agent-catalog.mjs",
14
15
  "test": "jest",
15
16
  "test:watch": "jest --watch",