skiller 0.9.12 → 0.9.13

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