@timekast/factory 2.6.0 → 2.7.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.
@@ -261,6 +261,7 @@ export function computeDoctor(deps = {}) {
261
261
  if (!hasLockfile(rootDir)) {
262
262
  return {
263
263
  orphans: [],
264
+ externalSkills: [],
264
265
  conflicts: [],
265
266
  formatterDrift: [],
266
267
  repoOwned: [],
@@ -285,7 +286,7 @@ export function computeDoctor(deps = {}) {
285
286
  const manifestPaths = new Set(lock.files.map((f) => f.path));
286
287
  // Orphans: files under `.claude/` on disk that the manifest does not track.
287
288
  const claudePaths = collectClaudePaths(rootDir);
288
- const orphans = claudePaths.filter((p) => !manifestPaths.has(p)).sort();
289
+ const { orphans, externalSkills } = splitExternalSkills(claudePaths.filter((p) => !manifestPaths.has(p)).sort(), manifestPaths);
289
290
  // Pending conflicts: tracked file whose normalized on-disk hash drifted from
290
291
  // the recorded hash (a local modification not yet resolved by `update`).
291
292
  const conflicts = [];
@@ -335,6 +336,7 @@ export function computeDoctor(deps = {}) {
335
336
  }
336
337
  return {
337
338
  orphans,
339
+ externalSkills,
338
340
  conflicts,
339
341
  formatterDrift,
340
342
  repoOwned,
@@ -350,6 +352,37 @@ export function computeDoctor(deps = {}) {
350
352
  neonRevokePending,
351
353
  };
352
354
  }
355
+ const SKILLS_DIR_PREFIX = '.claude/skills/';
356
+ /**
357
+ * Move out of `orphans` every file that lives in a skill directory the manifest knows nothing
358
+ * about, and name those directories. A loose file inside a KIT skill directory stays an orphan:
359
+ * that one is a retired or stray kit file, not someone else's skill.
360
+ */
361
+ export function splitExternalSkills(allOrphans, manifestPaths) {
362
+ const skillDirOf = (p) => {
363
+ if (!p.startsWith(SKILLS_DIR_PREFIX))
364
+ return null;
365
+ const rest = p.slice(SKILLS_DIR_PREFIX.length);
366
+ const slash = rest.indexOf('/');
367
+ return slash > 0 ? rest.slice(0, slash) : null;
368
+ };
369
+ const kitSkillDirs = new Set();
370
+ for (const p of manifestPaths) {
371
+ const dir = skillDirOf(p);
372
+ if (dir)
373
+ kitSkillDirs.add(dir);
374
+ }
375
+ const orphans = [];
376
+ const external = new Set();
377
+ for (const p of allOrphans) {
378
+ const dir = skillDirOf(p);
379
+ if (dir && !kitSkillDirs.has(dir))
380
+ external.add(dir);
381
+ else
382
+ orphans.push(p);
383
+ }
384
+ return { orphans, externalSkills: [...external].sort() };
385
+ }
353
386
  /** Render the doctor result to the console. The A1 notice is ALWAYS last. */
354
387
  function renderDoctor(result, repoVault) {
355
388
  const vaultLines = renderRepoVault(repoVault, { suggestAdopt: result.suggestAdopt });
@@ -410,6 +443,13 @@ function renderDoctor(result, repoVault) {
410
443
  }
411
444
  }
412
445
  }
446
+ // Skills the kit does not manage (#110): informative, never a problem. Shown whatever the rest says.
447
+ if (result.externalSkills.length > 0) {
448
+ console.log('\nSkills que no gestiona el kit (propias del proyecto o add-ons con su instalador):');
449
+ for (const name of result.externalSkills) {
450
+ console.log(` • .claude/skills/${name}/ — no es huérfana: no la borres; \`factory update\` no la toca`);
451
+ }
452
+ }
413
453
  // Repo-owned kit files (#76): informative, never a conflict. Shown whatever the rest says.
414
454
  if (result.repoOwned.length > 0) {
415
455
  console.log('\nArchivos propios del repo (el kit dejó su versión al lado, en `.kit-example`):');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@timekast/factory",
3
- "version": "2.6.0",
3
+ "version": "2.7.0",
4
4
  "description": "Public, thin CLI to bootstrap and maintain TimeKast Factory derived projects.",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",