@hasna/skills 0.6.0 → 0.6.2

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/bin/index.js CHANGED
@@ -47,6 +47,14 @@ var __export = (target, all) => {
47
47
  var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
48
48
  var __require = import.meta.require;
49
49
 
50
+ // src/cli/output.ts
51
+ async function writeCliOutput(text, newline = true) {
52
+ await new Promise((resolve, reject) => {
53
+ process.stdout.write(newline ? `${text}
54
+ ` : text, (error) => error ? reject(error) : resolve());
55
+ });
56
+ }
57
+
50
58
  // ../../node_modules/.bun/react@18.3.1/node_modules/react/cjs/react.development.js
51
59
  var require_react_development = __commonJS((exports, module) => {
52
60
  if (true) {
@@ -36860,7 +36868,7 @@ var package_default;
36860
36868
  var init_package = __esm(() => {
36861
36869
  package_default = {
36862
36870
  name: "@hasna/skills",
36863
- version: "0.6.0",
36871
+ version: "0.6.2",
36864
36872
  description: "Skills library for AI coding agents",
36865
36873
  type: "module",
36866
36874
  bin: {
@@ -42998,7 +43006,7 @@ var require_cli_spinners = __commonJS((exports, module) => {
42998
43006
 
42999
43007
  // src/lib/agent-integration.ts
43000
43008
  import { createHash as createHash2, randomUUID as randomUUID3 } from "crypto";
43001
- import { existsSync as existsSync13, lstatSync as lstatSync3, mkdirSync as mkdirSync4, readFileSync as readFileSync8, readdirSync as readdirSync7, readlinkSync, realpathSync as realpathSync3, renameSync as renameSync2, writeFileSync as writeFileSync4, unlinkSync, chmodSync } from "fs";
43009
+ import { existsSync as existsSync13, lstatSync as lstatSync3, statSync as statSync7, mkdirSync as mkdirSync4, readFileSync as readFileSync8, readdirSync as readdirSync7, readlinkSync, realpathSync as realpathSync3, renameSync as renameSync2, writeFileSync as writeFileSync4, unlinkSync, chmodSync } from "fs";
43002
43010
  import { dirname as dirname4, join as join13, resolve as resolve4, relative as relative3, isAbsolute as isAbsolute3, sep as sep3 } from "path";
43003
43011
  import { homedir as homedir3 } from "os";
43004
43012
  function assertSafePath(path) {
@@ -43086,15 +43094,27 @@ function inventoryNativeSkills(home = homedir3(), options = {}) {
43086
43094
  for (const [agent, path] of ROOTS)
43087
43095
  roots.push([agent, join13(resolve4(options.projectDir), path)]);
43088
43096
  const entries = [], seen = new Set;
43089
- function visit(agent, path, vendor = false, depth = 0) {
43097
+ const cacheScans = new Map, maxAliasProofEntries = 1e4;
43098
+ function emptySiblingCacheAlias(path, parent) {
43099
+ const link2 = readlinkSync(path), target = resolve4(parent, link2);
43100
+ if (dirname4(target) !== parent || (isAbsolute3(link2) ? link2 !== target : dirname4(link2) !== "."))
43101
+ return false;
43102
+ if (!lstatSync3(target, { throwIfNoEntry: false })?.isDirectory())
43103
+ return false;
43104
+ const scan = cacheScans.get(target);
43105
+ return Boolean(scan?.complete && !scan.hasSkills && scan.entries <= maxAliasProofEntries);
43106
+ }
43107
+ function visit(agent, path, vendor = false, depth = 0, pluginCache = false) {
43108
+ const scan = { complete: true, hasSkills: false, entries: 1 };
43090
43109
  assertSafePath(path);
43091
43110
  if (!existsSync13(path))
43092
- return;
43111
+ return scan;
43093
43112
  if (!lstatSync3(path).isDirectory())
43094
- return;
43113
+ return scan;
43095
43114
  if (existsSync13(join13(path, "SKILL.md"))) {
43115
+ scan.hasSkills = true;
43096
43116
  if (seen.has(path))
43097
- return;
43117
+ return scan;
43098
43118
  seen.add(path);
43099
43119
  let managed = false;
43100
43120
  const marker = join13(path, ".hasna-skills.json");
@@ -43105,11 +43125,14 @@ function inventoryNativeSkills(home = homedir3(), options = {}) {
43105
43125
  }
43106
43126
  const rootAlias = aliases.find((item) => path === item.target || path.startsWith(item.target + sep3));
43107
43127
  entries.push({ agent, path, hash: treeHash(path), managed, vendor, ...rootAlias ? { rootAlias } : {} });
43108
- return;
43128
+ return scan;
43109
43129
  }
43110
43130
  if (depth > (vendor ? 8 : 3))
43111
- return;
43112
- for (const name of readdirSync7(path).sort()) {
43131
+ return { ...scan, complete: false };
43132
+ const children = readdirSync7(path, { withFileTypes: true }).sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0);
43133
+ if (pluginCache)
43134
+ children.sort((a, b) => Number(a.isSymbolicLink()) - Number(b.isSymbolicLink()));
43135
+ for (const { name } of children) {
43113
43136
  if (name === "node_modules")
43114
43137
  continue;
43115
43138
  if (name.startsWith(".") && name !== ".system")
@@ -43117,14 +43140,28 @@ function inventoryNativeSkills(home = homedir3(), options = {}) {
43117
43140
  const isVendor = vendor || name === ".system";
43118
43141
  if (isVendor && !options.includeVendor)
43119
43142
  continue;
43120
- visit(agent, join13(path, name), isVendor, depth + 1);
43143
+ const child = join13(path, name);
43144
+ if (isVendor && lstatSync3(child, { throwIfNoEntry: false })?.isSymbolicLink()) {
43145
+ const target = statSync7(child, { throwIfNoEntry: false });
43146
+ if (target?.isFile() || pluginCache && target?.isDirectory() && emptySiblingCacheAlias(child, path)) {
43147
+ scan.entries++;
43148
+ continue;
43149
+ }
43150
+ }
43151
+ const childScan = visit(agent, child, isVendor, depth + 1, pluginCache);
43152
+ scan.complete &&= childScan.complete;
43153
+ scan.hasSkills ||= childScan.hasSkills;
43154
+ scan.entries += childScan.entries;
43121
43155
  }
43156
+ if (pluginCache)
43157
+ cacheScans.set(path, scan);
43158
+ return scan;
43122
43159
  }
43123
43160
  for (const [agent, path] of roots)
43124
43161
  visit(agent, path);
43125
43162
  if (options.includeVendor) {
43126
43163
  for (const agent of ["codex", "claude"])
43127
- visit(agent, canonicalAgentPath(join13(home, `.${agent}`, "plugins", "cache"), aliases), true);
43164
+ visit(agent, canonicalAgentPath(join13(home, `.${agent}`, "plugins", "cache"), aliases), true, 0, true);
43128
43165
  }
43129
43166
  recheckRootAliases(aliases);
43130
43167
  return entries;
@@ -43355,7 +43392,7 @@ var init_agent_integration = __esm(() => {
43355
43392
  });
43356
43393
 
43357
43394
  // src/lib/home-migration.ts
43358
- import { existsSync as existsSync14, mkdirSync as mkdirSync5, readdirSync as readdirSync8, renameSync as renameSync3, rmSync as rmSync2, statSync as statSync7, writeFileSync as writeFileSync5 } from "fs";
43395
+ import { existsSync as existsSync14, mkdirSync as mkdirSync5, readdirSync as readdirSync8, renameSync as renameSync3, rmSync as rmSync2, statSync as statSync8, writeFileSync as writeFileSync5 } from "fs";
43359
43396
  import { join as join14 } from "path";
43360
43397
  function layoutMigrationRecordPath(appDir) {
43361
43398
  return join14(appDir, SKILLS_CACHE_DIRNAME, LAYOUT_MIGRATION_RECORD);
@@ -43368,7 +43405,7 @@ function migrationAppDir(homeDir) {
43368
43405
  }
43369
43406
  function looksLikeSkillDirectory2(path) {
43370
43407
  try {
43371
- if (!statSync7(path).isDirectory())
43408
+ if (!statSync8(path).isDirectory())
43372
43409
  return false;
43373
43410
  } catch {
43374
43411
  return false;
@@ -43500,7 +43537,7 @@ import {
43500
43537
  readdirSync as readdirSync9,
43501
43538
  renameSync as renameSync4,
43502
43539
  rmSync as rmSync3,
43503
- statSync as statSync8,
43540
+ statSync as statSync9,
43504
43541
  writeFileSync as writeFileSync6
43505
43542
  } from "fs";
43506
43543
  import { homedir as homedir4 } from "os";
@@ -43617,7 +43654,7 @@ function containsSkillDirectories(path) {
43617
43654
  }
43618
43655
  function isDirectory(path) {
43619
43656
  try {
43620
- return statSync8(path).isDirectory();
43657
+ return statSync9(path).isDirectory();
43621
43658
  } catch {
43622
43659
  return false;
43623
43660
  }
@@ -50115,7 +50152,7 @@ var init_remote_workspace_leave = __esm(() => {
50115
50152
  });
50116
50153
 
50117
50154
  // src/lib/auth-store.ts
50118
- import { chmodSync as chmodSync2, existsSync as existsSync18, mkdirSync as mkdirSync8, readFileSync as readFileSync13, renameSync as renameSync5, statSync as statSync9, unlinkSync as unlinkSync2, writeFileSync as writeFileSync8 } from "fs";
50155
+ import { chmodSync as chmodSync2, existsSync as existsSync18, mkdirSync as mkdirSync8, readFileSync as readFileSync13, renameSync as renameSync5, statSync as statSync10, unlinkSync as unlinkSync2, writeFileSync as writeFileSync8 } from "fs";
50119
50156
  import { basename as basename4, dirname as dirname7, join as join19 } from "path";
50120
50157
  function getAuthFilePath(env3 = process.env) {
50121
50158
  return skillsCredentialFilePath(env3);
@@ -50273,7 +50310,7 @@ function getApiUrl(action, env3 = process.env, options = {}) {
50273
50310
  }
50274
50311
  function credentialFileMode(env3 = process.env) {
50275
50312
  try {
50276
- return statSync9(skillsCredentialFilePath(env3)).mode & 511;
50313
+ return statSync10(skillsCredentialFilePath(env3)).mode & 511;
50277
50314
  } catch {
50278
50315
  return null;
50279
50316
  }
@@ -51422,7 +51459,7 @@ var init_revision = __esm(() => {
51422
51459
 
51423
51460
  // src/lib/skill-bundle.ts
51424
51461
  import { createHash as createHash5 } from "crypto";
51425
- import { readFileSync as readFileSync14, readdirSync as readdirSync10, statSync as statSync10 } from "fs";
51462
+ import { readFileSync as readFileSync14, readdirSync as readdirSync10, statSync as statSync11 } from "fs";
51426
51463
  import { join as join20, relative as relative4 } from "path";
51427
51464
  import { createGunzip } from "zlib";
51428
51465
  function isDotenvFile(lower) {
@@ -51483,7 +51520,7 @@ function walk(root, current, out) {
51483
51520
  continue;
51484
51521
  if (isCredentialFile(entry.name))
51485
51522
  continue;
51486
- const stats = statSync10(absolute);
51523
+ const stats = statSync11(absolute);
51487
51524
  out.push({
51488
51525
  path: rel,
51489
51526
  bytes: ownBytes(readFileSync14(absolute)),
@@ -53548,7 +53585,7 @@ function registerContextCommands(parent) {
53548
53585
  sessionId: options.session,
53549
53586
  file: options.file
53550
53587
  });
53551
- console.log(options.json ? JSON.stringify(result2) : result2.content);
53588
+ await writeCliOutput(options.json ? JSON.stringify(result2) : result2.content);
53552
53589
  } catch (error) {
53553
53590
  reportContextError(error, options.json);
53554
53591
  }
@@ -53565,7 +53602,7 @@ function registerContextCommands(parent) {
53565
53602
  maxChars: Number(options.maxChars),
53566
53603
  maxSkills: Number(options.maxSkills)
53567
53604
  });
53568
- console.log(options.json ? JSON.stringify(result2) : result2.context);
53605
+ await writeCliOutput(options.json ? JSON.stringify(result2) : result2.context);
53569
53606
  } catch (error) {
53570
53607
  reportContextError(error, options.json);
53571
53608
  }
@@ -53656,9 +53693,9 @@ async function handleProfileSync(options, dependencies = {}) {
53656
53693
  check: options.check ?? dependencies.check
53657
53694
  });
53658
53695
  if (options.json)
53659
- console.log(JSON.stringify(result2));
53696
+ await writeCliOutput(JSON.stringify(result2));
53660
53697
  else
53661
- console.log(options.check ? result2.changed ? "Skills profile or cached bundles need syncing." : "Skills profile and cached bundles are current." : `Synced Skills profile ${result2.profile.profileId} at ${result2.profile.profileRevision}; ${result2.downloaded} bundle(s) downloaded.`);
53698
+ await writeCliOutput(options.check ? result2.changed ? "Skills profile or cached bundles need syncing." : "Skills profile and cached bundles are current." : `Synced Skills profile ${result2.profile.profileId} at ${result2.profile.profileRevision}; ${result2.downloaded} bundle(s) downloaded.`);
53662
53699
  if (options.check && result2.changed)
53663
53700
  process.exitCode = 1;
53664
53701
  } catch (error) {
@@ -55427,7 +55464,7 @@ var init_init = __esm(() => {
55427
55464
  });
55428
55465
 
55429
55466
  // src/lib/home-adoption.ts
55430
- import { constants as constants4, closeSync as closeSync4, fstatSync as fstatSync4, lstatSync as lstatSync8, openSync as openSync4, readSync as readSync3, existsSync as existsSync24, mkdirSync as mkdirSync11, readdirSync as readdirSync11, readFileSync as readFileSync21, rmSync as rmSync6, statSync as statSync11, writeFileSync as writeFileSync12 } from "fs";
55467
+ import { constants as constants4, closeSync as closeSync4, fstatSync as fstatSync4, lstatSync as lstatSync8, openSync as openSync4, readSync as readSync3, existsSync as existsSync24, mkdirSync as mkdirSync11, readdirSync as readdirSync11, readFileSync as readFileSync21, rmSync as rmSync6, statSync as statSync12, writeFileSync as writeFileSync12 } from "fs";
55431
55468
  import { homedir as homedir6 } from "os";
55432
55469
  import { join as join27 } from "path";
55433
55470
  function indexCanonicalCorpus(corpusRoot) {
@@ -55463,7 +55500,7 @@ function selectedHomeNames(options, index, homeDir2, agents) {
55463
55500
  continue;
55464
55501
  const present = agents.some((agent) => {
55465
55502
  try {
55466
- return statSync11(join27(agentGlobalSkillsDir(agent, homeDir2), name)).isDirectory();
55503
+ return statSync12(join27(agentGlobalSkillsDir(agent, homeDir2), name)).isDirectory();
55467
55504
  } catch {
55468
55505
  return false;
55469
55506
  }
@@ -55497,7 +55534,7 @@ function scanUnmarkedHomes(options = {}) {
55497
55534
  continue;
55498
55535
  const dir = join27(home, skill);
55499
55536
  try {
55500
- if (!statSync11(dir).isDirectory())
55537
+ if (!statSync12(dir).isDirectory())
55501
55538
  continue;
55502
55539
  } catch {
55503
55540
  continue;
@@ -55510,7 +55547,7 @@ function scanUnmarkedHomes(options = {}) {
55510
55547
  if (!existsSync24(skillMdPath))
55511
55548
  continue;
55512
55549
  const hash2 = hashSkillMarkdownFile(skillMdPath);
55513
- const mtime = statSync11(skillMdPath).mtime.toISOString();
55550
+ const mtime = statSync12(skillMdPath).mtime.toISOString();
55514
55551
  const entry = { agent, skill, home, path: dir, hash: hash2, mtime };
55515
55552
  const canonicalHash = index.get(skill);
55516
55553
  if (canonicalHash === undefined) {
@@ -55639,7 +55676,7 @@ function pruneStrayHomes(options = {}) {
55639
55676
  continue;
55640
55677
  const dir = join27(home, skill);
55641
55678
  try {
55642
- if (!statSync11(dir).isDirectory())
55679
+ if (!statSync12(dir).isDirectory())
55643
55680
  continue;
55644
55681
  } catch {
55645
55682
  continue;
@@ -55680,7 +55717,7 @@ var init_home_adoption = __esm(() => {
55680
55717
  });
55681
55718
 
55682
55719
  // src/lib/home-census.ts
55683
- import { existsSync as existsSync25, readFileSync as readFileSync22, readdirSync as readdirSync12, statSync as statSync12 } from "fs";
55720
+ import { existsSync as existsSync25, readFileSync as readFileSync22, readdirSync as readdirSync12, statSync as statSync13 } from "fs";
55684
55721
  import { homedir as homedir7 } from "os";
55685
55722
  import { join as join28 } from "path";
55686
55723
  function sortEntries(entries) {
@@ -55725,7 +55762,7 @@ function censusHomeDrift(options = {}) {
55725
55762
  continue;
55726
55763
  const dir = join28(home, skill);
55727
55764
  try {
55728
- if (!statSync12(dir).isDirectory())
55765
+ if (!statSync13(dir).isDirectory())
55729
55766
  continue;
55730
55767
  } catch {
55731
55768
  continue;
@@ -55978,7 +56015,7 @@ var exports_diagnostic = {};
55978
56015
  __export(exports_diagnostic, {
55979
56016
  registerDiagnostic: () => registerDiagnostic
55980
56017
  });
55981
- import { existsSync as existsSync26, readFileSync as readFileSync24, readdirSync as readdirSync13, statSync as statSync13 } from "fs";
56018
+ import { existsSync as existsSync26, readFileSync as readFileSync24, readdirSync as readdirSync13, statSync as statSync14 } from "fs";
55982
56019
  import { join as join29 } from "path";
55983
56020
  import { execSync as execSync2 } from "child_process";
55984
56021
  function registerDiagnostic(parent) {
@@ -56171,7 +56208,7 @@ function handleWhoami(options) {
56171
56208
  let skillCount = 0;
56172
56209
  if (exists)
56173
56210
  try {
56174
- skillCount = readdirSync13(agentSkillsPath).filter((f) => !f.startsWith(".") && statSync13(join29(agentSkillsPath, f)).isDirectory()).length;
56211
+ skillCount = readdirSync13(agentSkillsPath).filter((f) => !f.startsWith(".") && statSync14(join29(agentSkillsPath, f)).isDirectory()).length;
56175
56212
  } catch {}
56176
56213
  agentConfigs.push({ agent, label: AGENT_LABELS[agent], path: agentSkillsPath, exists, skillCount });
56177
56214
  }
@@ -56758,7 +56795,7 @@ var init_runs = __esm(() => {
56758
56795
 
56759
56796
  // src/lib/run-state.ts
56760
56797
  import { createHash as createHash9, randomBytes } from "crypto";
56761
- import { existsSync as existsSync27, mkdirSync as mkdirSync13, readFileSync as readFileSync25, readdirSync as readdirSync14, statSync as statSync14, writeFileSync as writeFileSync14 } from "fs";
56798
+ import { existsSync as existsSync27, mkdirSync as mkdirSync13, readFileSync as readFileSync25, readdirSync as readdirSync14, statSync as statSync15, writeFileSync as writeFileSync14 } from "fs";
56762
56799
  import { extname, join as join31, relative as relative5 } from "path";
56763
56800
  function createSkillRun(params, targetDir = process.cwd()) {
56764
56801
  const now3 = new Date;
@@ -56840,7 +56877,7 @@ function listSkillRuns(targetDir = process.cwd(), limit = 50) {
56840
56877
  const records = [];
56841
56878
  for (const day of readdirSync14(runsRoot).sort().reverse()) {
56842
56879
  const dayDir = join31(runsRoot, day);
56843
- if (!statSync14(dayDir).isDirectory())
56880
+ if (!statSync15(dayDir).isDirectory())
56844
56881
  continue;
56845
56882
  for (const runId2 of readdirSync14(dayDir).sort().reverse()) {
56846
56883
  const record4 = readRunRecord(join31(dayDir, runId2));
@@ -56889,7 +56926,7 @@ function collectRunArtifacts(context) {
56889
56926
  return [];
56890
56927
  const artifacts = [];
56891
56928
  for (const path of walkFiles(context.exportDir)) {
56892
- const stat = statSync14(path);
56929
+ const stat = statSync15(path);
56893
56930
  const bytes = readFileSync25(path);
56894
56931
  artifacts.push({
56895
56932
  path: toProjectRelative(context.targetDir, path),
@@ -56914,7 +56951,7 @@ function walkFiles(dir) {
56914
56951
  const files = [];
56915
56952
  for (const entry of readdirSync14(dir)) {
56916
56953
  const full = join31(dir, entry);
56917
- if (statSync14(full).isDirectory())
56954
+ if (statSync15(full).isDirectory())
56918
56955
  files.push(...walkFiles(full));
56919
56956
  else
56920
56957
  files.push(full);
@@ -74823,7 +74860,7 @@ var init_run_routing = __esm(() => {
74823
74860
  });
74824
74861
 
74825
74862
  // src/mcp/operation-tools.ts
74826
- import { existsSync as existsSync28, readdirSync as readdirSync15, statSync as statSync15 } from "fs";
74863
+ import { existsSync as existsSync28, readdirSync as readdirSync15, statSync as statSync16 } from "fs";
74827
74864
  import { join as join32 } from "path";
74828
74865
  function registerOperationTools(server) {
74829
74866
  server.registerTool("scaffold_skill", {
@@ -75282,7 +75319,7 @@ function registerOperationTools(server) {
75282
75319
  try {
75283
75320
  skillCount = readdirSync15(agentSkillsPath).filter((f) => {
75284
75321
  const full = join32(agentSkillsPath, f);
75285
- return !f.startsWith(".") && statSync15(full).isDirectory();
75322
+ return !f.startsWith(".") && statSync16(full).isDirectory();
75286
75323
  }).length;
75287
75324
  } catch {}
75288
75325
  }
@@ -75869,7 +75906,7 @@ import {
75869
75906
  mkdirSync as mkdirSync16,
75870
75907
  readFileSync as readFileSync27,
75871
75908
  readdirSync as readdirSync16,
75872
- statSync as statSync16,
75909
+ statSync as statSync17,
75873
75910
  writeFileSync as writeFileSync16
75874
75911
  } from "fs";
75875
75912
  import { dirname as dirname12, join as join35, normalize as normalize3, relative as relative6, sep as sep4 } from "path";
@@ -75995,7 +76032,7 @@ function walkFiles2(dir) {
75995
76032
  const files = [];
75996
76033
  for (const entry of readdirSync16(dir)) {
75997
76034
  const full = join35(dir, entry);
75998
- const stats = statSync16(full);
76035
+ const stats = statSync17(full);
75999
76036
  if (stats.isDirectory())
76000
76037
  files.push(...walkFiles2(full));
76001
76038
  else
@@ -77924,7 +77961,7 @@ function registerRuntime(parent) {
77924
77961
  try {
77925
77962
  const client = await CloudExecutionClient.configured();
77926
77963
  const value = operation === "show" ? await client.get(id) : await client[operation](id);
77927
- console.log(JSON.stringify(value, null, 2));
77964
+ await writeCliOutput(JSON.stringify(value, null, 2));
77928
77965
  } catch (error2) {
77929
77966
  console.error(JSON.stringify({ error: error2.message }));
77930
77967
  process.exitCode = 1;
@@ -78260,9 +78297,9 @@ async function handleRun(name, args2, options) {
78260
78297
  throw new Error("Selected local execution accepts arguments and structured --input only");
78261
78298
  const result4 = await executeSelectedLocal(selected, { args: args2, input, cwd: process.cwd() });
78262
78299
  if (options.json)
78263
- console.log(JSON.stringify(result4, null, 2));
78300
+ await writeCliOutput(JSON.stringify(result4, null, 2));
78264
78301
  else {
78265
- process.stdout.write(result4.stdout);
78302
+ await writeCliOutput(result4.stdout, false);
78266
78303
  process.stderr.write(result4.stderr);
78267
78304
  console.error(JSON.stringify({ selection: result4.selection, target: result4.target, exitCode: result4.exitCode, runDirectory: result4.runDirectory }));
78268
78305
  }
@@ -78281,7 +78318,7 @@ async function handleRun(name, args2, options) {
78281
78318
  if (options.wait)
78282
78319
  console.error(JSON.stringify({ executionId: admitted.id, status: admitted.status }));
78283
78320
  const result3 = options.wait ? await client2.wait(admitted.id, { timeoutMs, intervalMs }) : admitted;
78284
- console.log(JSON.stringify(result3, null, 2));
78321
+ await writeCliOutput(JSON.stringify(result3, null, 2));
78285
78322
  if (["failed", "cancelled"].includes(result3.status))
78286
78323
  process.exitCode = 1;
78287
78324
  } catch (error2) {
@@ -78484,7 +78521,7 @@ async function handleRun(name, args2, options) {
78484
78521
  console.log(JSON.stringify({ skill: skill.name, args: args2, ...result2, run: completed }, null, 2));
78485
78522
  else {
78486
78523
  if (result2.stdout)
78487
- process.stdout.write(result2.stdout);
78524
+ await writeCliOutput(result2.stdout, false);
78488
78525
  if (result2.stderr)
78489
78526
  process.stderr.write(result2.stderr);
78490
78527
  if (result2.error)
@@ -79110,7 +79147,7 @@ var init_completion = __esm(() => {
79110
79147
  });
79111
79148
 
79112
79149
  // src/lib/portable-snapshot-filter.ts
79113
- import { readdirSync as readdirSync17, statSync as statSync17 } from "fs";
79150
+ import { readdirSync as readdirSync17, statSync as statSync18 } from "fs";
79114
79151
  import { homedir as homedir9 } from "os";
79115
79152
  import { join as join40, sep as sep5 } from "path";
79116
79153
  function isExcludedSkillFileName(fileName) {
@@ -79178,7 +79215,7 @@ function walkEntries(absoluteRoot) {
79178
79215
  }
79179
79216
  function isRegularFile(filePath) {
79180
79217
  try {
79181
- return statSync17(filePath).isFile();
79218
+ return statSync18(filePath).isFile();
79182
79219
  } catch {
79183
79220
  return false;
79184
79221
  }
@@ -79264,7 +79301,7 @@ import {
79264
79301
  copyFileSync as copyFileSync2,
79265
79302
  mkdirSync as mkdirSync21,
79266
79303
  readFileSync as readFileSync31,
79267
- statSync as statSync18,
79304
+ statSync as statSync19,
79268
79305
  writeFileSync as writeFileSync21
79269
79306
  } from "fs";
79270
79307
  import { dirname as dirname17, isAbsolute as isAbsolute8, relative as relative7, resolve as resolve9, sep as sep6 } from "path";
@@ -79304,7 +79341,7 @@ function scanHome(definition, homesRoot) {
79304
79341
  skipped.push({ relativePath: entry.relativePath, reason: "not-regular-file" });
79305
79342
  continue;
79306
79343
  }
79307
- const info = statSync18(entry.fullPath);
79344
+ const info = statSync19(entry.fullPath);
79308
79345
  portable.push({
79309
79346
  relativePath: entry.relativePath,
79310
79347
  fullPath: entry.fullPath,
@@ -79856,15 +79893,15 @@ function agents(value) {
79856
79893
  }
79857
79894
  function registerAgentIntegration(parent) {
79858
79895
  const hook = parent.command("hook").description("Load selected Skills context through agent lifecycle hooks");
79859
- hook.command("install").option("--agent <agent>", "Agent to configure: claude, codex, all", "all").option("--command <path>", "Skills executable used by the hook", "skills").option("--selection-profile <id>", "Shared selection profile", "default").option("--include-vendor", "Also disable discovered vendor system skills in Codex", false).option("--allow-root-aliases", "Allow home .claude/.codex aliases to existing directories within this home", false).option("--apply", "Apply the plan, preserving prior configuration in private backups", false).option("--json", "Output a receipt as JSON", false).description("Plan or install prompt hooks and disable native skill invocation").action((options) => {
79896
+ hook.command("install").option("--agent <agent>", "Agent to configure: claude, codex, all", "all").option("--command <path>", "Skills executable used by the hook", "skills").option("--selection-profile <id>", "Shared selection profile", "default").option("--include-vendor", "Also disable discovered vendor system skills in Codex", false).option("--allow-root-aliases", "Allow home .claude/.codex aliases to existing directories within this home", false).option("--apply", "Apply the plan, preserving prior configuration in private backups", false).option("--json", "Output a receipt as JSON", false).description("Plan or install prompt hooks and disable native skill invocation").action(async (options) => {
79860
79897
  try {
79861
79898
  const plan = planAgentIntegration({ agents: agents(options.agent), command: options.command, profileId: options.selectionProfile, includeVendor: options.includeVendor, allowRootAliases: options.allowRootAliases });
79862
79899
  const result2 = options.apply ? applyAgentIntegration(plan) : { changed: [], backups: [] };
79863
79900
  const receipt = { applied: options.apply, planned: plan.changes.map((change) => change.path), ...result2, rootAliases: plan.rootAliases ?? [], nativeSkills: plan.nativeSkills.map((entry) => ({ agent: entry.agent, path: entry.path, managed: entry.managed, vendor: entry.vendor })) };
79864
79901
  if (options.json)
79865
- console.log(JSON.stringify(receipt));
79902
+ await writeCliOutput(JSON.stringify(receipt));
79866
79903
  else
79867
- console.log(`${options.apply ? "Configured" : "Planned"} ${plan.changes.length} agent configuration change(s).${options.apply ? " Restart the agent and trust the installed hook configuration." : " Use --apply to install."}`);
79904
+ await writeCliOutput(`${options.apply ? "Configured" : "Planned"} ${plan.changes.length} agent configuration change(s).${options.apply ? " Restart the agent and trust the installed hook configuration." : " Use --apply to install."}`);
79868
79905
  } catch (error2) {
79869
79906
  console.error(error2.message);
79870
79907
  process.exitCode = 1;
@@ -79906,29 +79943,29 @@ function registerAgentIntegration(parent) {
79906
79943
  const result2 = JSON.parse(stdout);
79907
79944
  if (typeof result2.context !== "string")
79908
79945
  throw new Error("Invalid Skills context response");
79909
- console.log(JSON.stringify(hookContextOutput(event, result2)));
79946
+ await writeCliOutput(JSON.stringify(hookContextOutput(event, result2)));
79910
79947
  } finally {
79911
79948
  clearTimeout(timer);
79912
79949
  }
79913
79950
  } catch {
79914
79951
  const reason = "Skills context is unavailable. Run skills sync --selection-profile <id> and skills context --stdin --json to diagnose the selected profile.";
79915
79952
  if (event === "UserPromptSubmit")
79916
- console.log(JSON.stringify({ decision: "block", reason }));
79953
+ await writeCliOutput(JSON.stringify({ decision: "block", reason }));
79917
79954
  else if (event === "SessionStart")
79918
- console.log(JSON.stringify({ continue: false, stopReason: reason, systemMessage: reason }));
79955
+ await writeCliOutput(JSON.stringify({ continue: false, stopReason: reason, systemMessage: reason }));
79919
79956
  else
79920
- console.log(JSON.stringify({ systemMessage: reason, hookSpecificOutput: { hookEventName: "SubagentStart", additionalContext: "Required Skills context was unavailable. Report this to the parent before performing task actions." } }));
79957
+ await writeCliOutput(JSON.stringify({ systemMessage: reason, hookSpecificOutput: { hookEventName: "SubagentStart", additionalContext: "Required Skills context was unavailable. Report this to the parent before performing task actions." } }));
79921
79958
  }
79922
79959
  });
79923
79960
  const migrate = parent.command("migrate").description("Preserve and retire native agent skill copies");
79924
- migrate.command("native").option("--project <directory>", "Also inventory native skills in a project directory").option("--include-unmanaged", "Archive user-authored skills as well as Skills-managed copies", false).option("--allow-root-aliases", "Allow home .claude/.codex aliases to existing directories within this home", false).option("--apply", "Move selected skills to private archives outside agent discovery roots", false).option("--json", "Output inventory and archive receipt as JSON", false).description("Inventory native skill copies; preserve complete directories before retiring them").action((options) => {
79961
+ migrate.command("native").option("--project <directory>", "Also inventory native skills in a project directory").option("--include-unmanaged", "Archive user-authored skills as well as Skills-managed copies", false).option("--allow-root-aliases", "Allow home .claude/.codex aliases to existing directories within this home", false).option("--apply", "Move selected skills to private archives outside agent discovery roots", false).option("--json", "Output inventory and archive receipt as JSON", false).description("Inventory native skill copies; preserve complete directories before retiring them").action(async (options) => {
79925
79962
  try {
79926
79963
  const inventory = inventoryNativeSkills(undefined, { projectDir: options.project, allowRootAliases: options.allowRootAliases });
79927
79964
  const result2 = options.apply ? archiveNativeSkills(inventory, { includeUnmanaged: options.includeUnmanaged, allowRootAliases: options.allowRootAliases }) : { entries: [] };
79928
79965
  if (options.json)
79929
- console.log(JSON.stringify({ applied: options.apply, inventory, ...result2 }));
79966
+ await writeCliOutput(JSON.stringify({ applied: options.apply, inventory, ...result2 }));
79930
79967
  else
79931
- console.log(`${inventory.length} native skill(s) found; ${result2.entries.length} archived with recovery receipts.${options.apply ? "" : " Use --apply to archive managed copies."}`);
79968
+ await writeCliOutput(`${inventory.length} native skill(s) found; ${result2.entries.length} archived with recovery receipts.${options.apply ? "" : " Use --apply to archive managed copies."}`);
79932
79969
  } catch (error2) {
79933
79970
  console.error(error2.message);
79934
79971
  process.exitCode = 1;
@@ -80008,7 +80045,7 @@ function registerProfiles(parent) {
80008
80045
  if (options.save)
80009
80046
  writeFileSync22(options.save, `${text2}
80010
80047
  `, { mode: 384, flag: "wx" });
80011
- console.log(options.json ? text2 : `${profile.id} at ${profile.revision}: ${profile.selections.map((skill) => `${skill.slug}@${skill.version}`).join(", ")}`);
80048
+ await writeCliOutput(options.json ? text2 : `${profile.id} at ${profile.revision}: ${profile.selections.map((skill) => `${skill.slug}@${skill.version}`).join(", ")}`);
80012
80049
  } catch (error2) {
80013
80050
  console.error(error2.message);
80014
80051
  process.exitCode = 1;
@@ -80021,7 +80058,7 @@ function registerProfiles(parent) {
80021
80058
  throw new Error("Profile input exceeds the size limit");
80022
80059
  const input = JSON.parse(text2);
80023
80060
  const profile = await saveSkillProfile(id2, input.selections, options.ifMatch);
80024
- console.log(options.json ? JSON.stringify(profile) : `Saved ${profile.id} at ${profile.revision}`);
80061
+ await writeCliOutput(options.json ? JSON.stringify(profile) : `Saved ${profile.id} at ${profile.revision}`);
80025
80062
  } catch (error2) {
80026
80063
  console.error(error2.message);
80027
80064
  process.exitCode = 1;
@@ -80029,7 +80066,7 @@ function registerProfiles(parent) {
80029
80066
  });
80030
80067
  parent.command("station-state <id>").description("Read this actor's last applied skill selection on a station").option("--json", "Output the station receipt as JSON", false).action(async (id2) => {
80031
80068
  try {
80032
- console.log(JSON.stringify(await readStationSkillState(id2), null, 2));
80069
+ await writeCliOutput(JSON.stringify(await readStationSkillState(id2), null, 2));
80033
80070
  } catch (error2) {
80034
80071
  console.error(error2.message);
80035
80072
  process.exitCode = 1;
@@ -80047,7 +80084,7 @@ import {
80047
80084
  mkdirSync as mkdirSync22,
80048
80085
  readdirSync as readdirSync18,
80049
80086
  readFileSync as readFileSync34,
80050
- statSync as statSync19,
80087
+ statSync as statSync20,
80051
80088
  writeFileSync as writeFileSync23
80052
80089
  } from "fs";
80053
80090
  import { dirname as dirname18, join as join42, resolve as resolve10, sep as sep7 } from "path";
@@ -80144,7 +80181,7 @@ function planStationHydration(stationId, repoRoot) {
80144
80181
  });
80145
80182
  continue;
80146
80183
  }
80147
- const info = statSync19(entry.fullPath);
80184
+ const info = statSync20(entry.fullPath);
80148
80185
  const manifestHash = manifestHashes.get(`${agent}${MANIFEST_HASH_KEY_SEP}${homeRelative}`) ?? null;
80149
80186
  let verified = false;
80150
80187
  if (manifestHash !== null) {
@@ -82707,11 +82744,11 @@ var init_storage = __esm(() => {
82707
82744
  });
82708
82745
 
82709
82746
  // src/lib/registry-reconcile.ts
82710
- import { existsSync as existsSync35, readFileSync as readFileSync36, statSync as statSync20, writeFileSync as writeFileSync25 } from "fs";
82747
+ import { existsSync as existsSync35, readFileSync as readFileSync36, statSync as statSync21, writeFileSync as writeFileSync25 } from "fs";
82711
82748
  import { join as join44 } from "path";
82712
82749
  function isDirectory2(path) {
82713
82750
  try {
82714
- return statSync20(path).isDirectory();
82751
+ return statSync21(path).isDirectory();
82715
82752
  } catch {
82716
82753
  return false;
82717
82754
  }
@@ -83147,7 +83184,6 @@ var init_registry_reconcile2 = __esm(() => {
83147
83184
  init_selection_cache();
83148
83185
  init_registry_reconcile();
83149
83186
  });
83150
-
83151
83187
  // ../../node_modules/.bun/ink@5.2.1+6e93904c7bfe2418/node_modules/ink/build/render.js
83152
83188
  import { Stream } from "stream";
83153
83189
  import process12 from "process";
@@ -90963,8 +90999,8 @@ program2.command("interactive", { isDefault: true }).alias("i").allowExcessArgum
90963
90999
  process.exit(1);
90964
91000
  }
90965
91001
  if (!isTTY2) {
90966
- console.log(JSON.stringify(loadBasicRegistry().map(getCompactSkillDiscovery)));
90967
- process.exit(0);
91002
+ await writeCliOutput(JSON.stringify(loadBasicRegistry().map(getCompactSkillDiscovery)));
91003
+ return;
90968
91004
  }
90969
91005
  render_default(/* @__PURE__ */ jsx_dev_runtime7.jsxDEV(App2, {}, undefined, false, undefined, this));
90970
91006
  });
package/bin/mcp.js CHANGED
@@ -5586,7 +5586,7 @@ var package_default;
5586
5586
  var init_package = __esm(() => {
5587
5587
  package_default = {
5588
5588
  name: "@hasna/skills",
5589
- version: "0.6.0",
5589
+ version: "0.6.2",
5590
5590
  description: "Skills library for AI coding agents",
5591
5591
  type: "module",
5592
5592
  bin: {
@@ -26973,7 +26973,6 @@ var init_selection_resolver = __esm(() => {
26973
26973
  init_selected_document();
26974
26974
  init_selection_cache();
26975
26975
  });
26976
-
26977
26976
  // src/lib/skill-context.ts
26978
26977
  var init_skill_context = __esm(() => {
26979
26978
  init_selection_resolver();
package/bin/migrate.js CHANGED
@@ -7,7 +7,7 @@ import { join as join6 } from "path";
7
7
  // package.json
8
8
  var package_default = {
9
9
  name: "@hasna/skills",
10
- version: "0.6.0",
10
+ version: "0.6.2",
11
11
  description: "Skills library for AI coding agents",
12
12
  type: "module",
13
13
  bin: {
package/bin/server.js CHANGED
@@ -23100,7 +23100,7 @@ var init_dist_es9 = __esm(() => {
23100
23100
  // package.json
23101
23101
  var package_default = {
23102
23102
  name: "@hasna/skills",
23103
- version: "0.6.0",
23103
+ version: "0.6.2",
23104
23104
  description: "Skills library for AI coding agents",
23105
23105
  type: "module",
23106
23106
  bin: {
package/bin/worker.js CHANGED
@@ -23103,7 +23103,7 @@ import { randomUUID as randomUUID5 } from "crypto";
23103
23103
  // package.json
23104
23104
  var package_default = {
23105
23105
  name: "@hasna/skills",
23106
- version: "0.6.0",
23106
+ version: "0.6.2",
23107
23107
  description: "Skills library for AI coding agents",
23108
23108
  type: "module",
23109
23109
  bin: {
@@ -1,5 +1,2 @@
1
- /**
2
- * run / mcp / self-update — runtime commands
3
- */
4
1
  import type { Command } from "commander";
5
2
  export declare function registerRuntime(parent: Command): void;
@@ -0,0 +1,2 @@
1
+ /** Finish pipe writes before an async command returns, including under backpressure. */
2
+ export declare function writeCliOutput(text: string, newline?: boolean): Promise<void>;
package/dist/index.js CHANGED
@@ -3493,7 +3493,7 @@ import {
3493
3493
  readdirSync as readdirSync8,
3494
3494
  renameSync as renameSync3,
3495
3495
  rmSync as rmSync2,
3496
- statSync as statSync7,
3496
+ statSync as statSync8,
3497
3497
  writeFileSync as writeFileSync5
3498
3498
  } from "fs";
3499
3499
  import { homedir as homedir3 } from "os";
@@ -3501,7 +3501,7 @@ import { basename as basename3, dirname as dirname5, join as join10 } from "path
3501
3501
 
3502
3502
  // src/lib/agent-integration.ts
3503
3503
  import { createHash as createHash2, randomUUID } from "crypto";
3504
- import { existsSync as existsSync9, lstatSync as lstatSync3, mkdirSync as mkdirSync4, readFileSync as readFileSync7, readdirSync as readdirSync7, readlinkSync, realpathSync as realpathSync3, renameSync as renameSync2, writeFileSync as writeFileSync4, unlinkSync, chmodSync } from "fs";
3504
+ import { existsSync as existsSync9, lstatSync as lstatSync3, statSync as statSync7, mkdirSync as mkdirSync4, readFileSync as readFileSync7, readdirSync as readdirSync7, readlinkSync, realpathSync as realpathSync3, renameSync as renameSync2, writeFileSync as writeFileSync4, unlinkSync, chmodSync } from "fs";
3505
3505
  import { dirname as dirname4, join as join9, resolve as resolve3, relative as relative3, isAbsolute as isAbsolute3, sep as sep3 } from "path";
3506
3506
  import { homedir as homedir2 } from "os";
3507
3507
  var sha = (data) => createHash2("sha256").update(data).digest("hex");
@@ -3600,15 +3600,27 @@ function inventoryNativeSkills(home = homedir2(), options = {}) {
3600
3600
  for (const [agent, path] of ROOTS)
3601
3601
  roots.push([agent, join9(resolve3(options.projectDir), path)]);
3602
3602
  const entries = [], seen = new Set;
3603
- function visit(agent, path, vendor = false, depth = 0) {
3603
+ const cacheScans = new Map, maxAliasProofEntries = 1e4;
3604
+ function emptySiblingCacheAlias(path, parent) {
3605
+ const link = readlinkSync(path), target = resolve3(parent, link);
3606
+ if (dirname4(target) !== parent || (isAbsolute3(link) ? link !== target : dirname4(link) !== "."))
3607
+ return false;
3608
+ if (!lstatSync3(target, { throwIfNoEntry: false })?.isDirectory())
3609
+ return false;
3610
+ const scan = cacheScans.get(target);
3611
+ return Boolean(scan?.complete && !scan.hasSkills && scan.entries <= maxAliasProofEntries);
3612
+ }
3613
+ function visit(agent, path, vendor = false, depth = 0, pluginCache = false) {
3614
+ const scan = { complete: true, hasSkills: false, entries: 1 };
3604
3615
  assertSafePath(path);
3605
3616
  if (!existsSync9(path))
3606
- return;
3617
+ return scan;
3607
3618
  if (!lstatSync3(path).isDirectory())
3608
- return;
3619
+ return scan;
3609
3620
  if (existsSync9(join9(path, "SKILL.md"))) {
3621
+ scan.hasSkills = true;
3610
3622
  if (seen.has(path))
3611
- return;
3623
+ return scan;
3612
3624
  seen.add(path);
3613
3625
  let managed = false;
3614
3626
  const marker = join9(path, ".hasna-skills.json");
@@ -3619,11 +3631,14 @@ function inventoryNativeSkills(home = homedir2(), options = {}) {
3619
3631
  }
3620
3632
  const rootAlias = aliases.find((item) => path === item.target || path.startsWith(item.target + sep3));
3621
3633
  entries.push({ agent, path, hash: treeHash(path), managed, vendor, ...rootAlias ? { rootAlias } : {} });
3622
- return;
3634
+ return scan;
3623
3635
  }
3624
3636
  if (depth > (vendor ? 8 : 3))
3625
- return;
3626
- for (const name of readdirSync7(path).sort()) {
3637
+ return { ...scan, complete: false };
3638
+ const children = readdirSync7(path, { withFileTypes: true }).sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0);
3639
+ if (pluginCache)
3640
+ children.sort((a, b) => Number(a.isSymbolicLink()) - Number(b.isSymbolicLink()));
3641
+ for (const { name } of children) {
3627
3642
  if (name === "node_modules")
3628
3643
  continue;
3629
3644
  if (name.startsWith(".") && name !== ".system")
@@ -3631,14 +3646,28 @@ function inventoryNativeSkills(home = homedir2(), options = {}) {
3631
3646
  const isVendor = vendor || name === ".system";
3632
3647
  if (isVendor && !options.includeVendor)
3633
3648
  continue;
3634
- visit(agent, join9(path, name), isVendor, depth + 1);
3649
+ const child = join9(path, name);
3650
+ if (isVendor && lstatSync3(child, { throwIfNoEntry: false })?.isSymbolicLink()) {
3651
+ const target = statSync7(child, { throwIfNoEntry: false });
3652
+ if (target?.isFile() || pluginCache && target?.isDirectory() && emptySiblingCacheAlias(child, path)) {
3653
+ scan.entries++;
3654
+ continue;
3655
+ }
3656
+ }
3657
+ const childScan = visit(agent, child, isVendor, depth + 1, pluginCache);
3658
+ scan.complete &&= childScan.complete;
3659
+ scan.hasSkills ||= childScan.hasSkills;
3660
+ scan.entries += childScan.entries;
3635
3661
  }
3662
+ if (pluginCache)
3663
+ cacheScans.set(path, scan);
3664
+ return scan;
3636
3665
  }
3637
3666
  for (const [agent, path] of roots)
3638
3667
  visit(agent, path);
3639
3668
  if (options.includeVendor) {
3640
3669
  for (const agent of ["codex", "claude"])
3641
- visit(agent, canonicalAgentPath(join9(home, `.${agent}`, "plugins", "cache"), aliases), true);
3670
+ visit(agent, canonicalAgentPath(join9(home, `.${agent}`, "plugins", "cache"), aliases), true, 0, true);
3642
3671
  }
3643
3672
  recheckRootAliases(aliases);
3644
3673
  return entries;
@@ -3963,7 +3992,7 @@ function containsSkillDirectories(path) {
3963
3992
  }
3964
3993
  function isDirectory(path) {
3965
3994
  try {
3966
- return statSync7(path).isDirectory();
3995
+ return statSync8(path).isDirectory();
3967
3996
  } catch {
3968
3997
  return false;
3969
3998
  }
@@ -4594,7 +4623,7 @@ function loadProjectConfigCompat(targetDir) {
4594
4623
  }
4595
4624
  // src/lib/run-state.ts
4596
4625
  import { createHash as createHash3, randomBytes } from "crypto";
4597
- import { existsSync as existsSync13, mkdirSync as mkdirSync7, readFileSync as readFileSync11, readdirSync as readdirSync9, statSync as statSync8, writeFileSync as writeFileSync7 } from "fs";
4626
+ import { existsSync as existsSync13, mkdirSync as mkdirSync7, readFileSync as readFileSync11, readdirSync as readdirSync9, statSync as statSync9, writeFileSync as writeFileSync7 } from "fs";
4598
4627
  import { extname, join as join13, relative as relative4 } from "path";
4599
4628
  function createSkillRun(params, targetDir = process.cwd()) {
4600
4629
  const now = new Date;
@@ -4676,7 +4705,7 @@ function listSkillRuns(targetDir = process.cwd(), limit = 50) {
4676
4705
  const records = [];
4677
4706
  for (const day of readdirSync9(runsRoot).sort().reverse()) {
4678
4707
  const dayDir = join13(runsRoot, day);
4679
- if (!statSync8(dayDir).isDirectory())
4708
+ if (!statSync9(dayDir).isDirectory())
4680
4709
  continue;
4681
4710
  for (const runId of readdirSync9(dayDir).sort().reverse()) {
4682
4711
  const record = readRunRecord(join13(dayDir, runId));
@@ -4715,7 +4744,7 @@ function collectRunArtifacts(context) {
4715
4744
  return [];
4716
4745
  const artifacts = [];
4717
4746
  for (const path of walkFiles(context.exportDir)) {
4718
- const stat = statSync8(path);
4747
+ const stat = statSync9(path);
4719
4748
  const bytes = readFileSync11(path);
4720
4749
  artifacts.push({
4721
4750
  path: toProjectRelative(context.targetDir, path),
@@ -4740,7 +4769,7 @@ function walkFiles(dir) {
4740
4769
  const files = [];
4741
4770
  for (const entry of readdirSync9(dir)) {
4742
4771
  const full = join13(dir, entry);
4743
- if (statSync8(full).isDirectory())
4772
+ if (statSync9(full).isDirectory())
4744
4773
  files.push(...walkFiles(full));
4745
4774
  else
4746
4775
  files.push(full);
@@ -12614,7 +12643,7 @@ import { dirname as dirname7, join as join18 } from "path";
12614
12643
 
12615
12644
  // src/lib/skill-bundle.ts
12616
12645
  import { createHash as createHash6 } from "crypto";
12617
- import { readFileSync as readFileSync15, readdirSync as readdirSync10, statSync as statSync9 } from "fs";
12646
+ import { readFileSync as readFileSync15, readdirSync as readdirSync10, statSync as statSync10 } from "fs";
12618
12647
  import { join as join17, relative as relative5 } from "path";
12619
12648
  import { createGunzip } from "zlib";
12620
12649
  var BLOCK = 512;
@@ -12798,7 +12827,7 @@ function walk(root, current, out) {
12798
12827
  continue;
12799
12828
  if (isCredentialFile(entry.name))
12800
12829
  continue;
12801
- const stats = statSync9(absolute);
12830
+ const stats = statSync10(absolute);
12802
12831
  out.push({
12803
12832
  path: rel,
12804
12833
  bytes: ownBytes(readFileSync15(absolute)),
@@ -13710,7 +13739,7 @@ import { dirname as dirname8, relative as relative6 } from "path";
13710
13739
  // package.json
13711
13740
  var package_default = {
13712
13741
  name: "@hasna/skills",
13713
- version: "0.6.0",
13742
+ version: "0.6.2",
13714
13743
  description: "Skills library for AI coding agents",
13715
13744
  type: "module",
13716
13745
  bin: {
@@ -15031,7 +15060,7 @@ import {
15031
15060
  mkdirSync as mkdirSync12,
15032
15061
  readFileSync as readFileSync17,
15033
15062
  readdirSync as readdirSync11,
15034
- statSync as statSync10,
15063
+ statSync as statSync11,
15035
15064
  writeFileSync as writeFileSync11
15036
15065
  } from "fs";
15037
15066
  import { dirname as dirname10, join as join20, normalize as normalize3, relative as relative7, sep as sep4 } from "path";
@@ -15529,7 +15558,7 @@ function walkFiles2(dir) {
15529
15558
  const files = [];
15530
15559
  for (const entry of readdirSync11(dir)) {
15531
15560
  const full = join20(dir, entry);
15532
- const stats = statSync10(full);
15561
+ const stats = statSync11(full);
15533
15562
  if (stats.isDirectory())
15534
15563
  files.push(...walkFiles2(full));
15535
15564
  else
@@ -15615,13 +15644,13 @@ import {
15615
15644
  copyFileSync as copyFileSync2,
15616
15645
  mkdirSync as mkdirSync13,
15617
15646
  readFileSync as readFileSync18,
15618
- statSync as statSync12,
15647
+ statSync as statSync13,
15619
15648
  writeFileSync as writeFileSync12
15620
15649
  } from "fs";
15621
15650
  import { dirname as dirname11, isAbsolute as isAbsolute5, relative as relative8, resolve as resolve4, sep as sep6 } from "path";
15622
15651
 
15623
15652
  // src/lib/portable-snapshot-filter.ts
15624
- import { readdirSync as readdirSync12, statSync as statSync11 } from "fs";
15653
+ import { readdirSync as readdirSync12, statSync as statSync12 } from "fs";
15625
15654
  import { homedir as homedir5 } from "os";
15626
15655
  import { join as join21, sep as sep5 } from "path";
15627
15656
  var SYNC_HOMES = [
@@ -15759,7 +15788,7 @@ function walkEntries(absoluteRoot) {
15759
15788
  }
15760
15789
  function isRegularFile(filePath) {
15761
15790
  try {
15762
- return statSync11(filePath).isFile();
15791
+ return statSync12(filePath).isFile();
15763
15792
  } catch {
15764
15793
  return false;
15765
15794
  }
@@ -15815,7 +15844,7 @@ function scanHome(definition, homesRoot) {
15815
15844
  skipped.push({ relativePath: entry.relativePath, reason: "not-regular-file" });
15816
15845
  continue;
15817
15846
  }
15818
- const info = statSync12(entry.fullPath);
15847
+ const info = statSync13(entry.fullPath);
15819
15848
  portable.push({
15820
15849
  relativePath: entry.relativePath,
15821
15850
  fullPath: entry.fullPath,
@@ -15942,7 +15971,7 @@ import {
15942
15971
  mkdirSync as mkdirSync14,
15943
15972
  readdirSync as readdirSync13,
15944
15973
  readFileSync as readFileSync19,
15945
- statSync as statSync13,
15974
+ statSync as statSync14,
15946
15975
  writeFileSync as writeFileSync13
15947
15976
  } from "fs";
15948
15977
  import { dirname as dirname12, join as join22, resolve as resolve5, sep as sep7 } from "path";
@@ -16042,7 +16071,7 @@ function planStationHydration(stationId, repoRoot) {
16042
16071
  });
16043
16072
  continue;
16044
16073
  }
16045
- const info = statSync13(entry.fullPath);
16074
+ const info = statSync14(entry.fullPath);
16046
16075
  const manifestHash = manifestHashes.get(`${agent}${MANIFEST_HASH_KEY_SEP}${homeRelative}`) ?? null;
16047
16076
  let verified = false;
16048
16077
  if (manifestHash !== null) {
package/dist/sdk/index.js CHANGED
@@ -24427,7 +24427,7 @@ var init_event_sink = __esm(() => {
24427
24427
  // package.json
24428
24428
  var package_default = {
24429
24429
  name: "@hasna/skills",
24430
- version: "0.6.0",
24430
+ version: "0.6.2",
24431
24431
  description: "Skills library for AI coding agents",
24432
24432
  type: "module",
24433
24433
  bin: {
@@ -38174,7 +38174,7 @@ import {
38174
38174
  readdirSync as readdirSync9,
38175
38175
  renameSync as renameSync3,
38176
38176
  rmSync as rmSync2,
38177
- statSync as statSync8,
38177
+ statSync as statSync9,
38178
38178
  writeFileSync as writeFileSync5
38179
38179
  } from "fs";
38180
38180
  import { homedir as homedir4 } from "os";
@@ -38182,7 +38182,7 @@ import { basename as basename3, dirname as dirname9, join as join15 } from "path
38182
38182
 
38183
38183
  // src/lib/agent-integration.ts
38184
38184
  import { createHash as createHash7, randomUUID as randomUUID5 } from "crypto";
38185
- import { existsSync as existsSync4, lstatSync as lstatSync2, mkdirSync as mkdirSync4, readFileSync as readFileSync6, readdirSync as readdirSync4, readlinkSync, realpathSync, renameSync, writeFileSync as writeFileSync2, unlinkSync, chmodSync } from "fs";
38185
+ import { existsSync as existsSync4, lstatSync as lstatSync2, statSync as statSync3, mkdirSync as mkdirSync4, readFileSync as readFileSync6, readdirSync as readdirSync4, readlinkSync, realpathSync, renameSync, writeFileSync as writeFileSync2, unlinkSync, chmodSync } from "fs";
38186
38186
  import { dirname as dirname6, join as join9, resolve as resolve2, relative as relative2, isAbsolute as isAbsolute3, sep } from "path";
38187
38187
  import { homedir as homedir3 } from "os";
38188
38188
  var sha = (data) => createHash7("sha256").update(data).digest("hex");
@@ -38281,15 +38281,27 @@ function inventoryNativeSkills(home = homedir3(), options = {}) {
38281
38281
  for (const [agent, path] of ROOTS)
38282
38282
  roots.push([agent, join9(resolve2(options.projectDir), path)]);
38283
38283
  const entries = [], seen = new Set;
38284
- function visit(agent, path, vendor = false, depth = 0) {
38284
+ const cacheScans = new Map, maxAliasProofEntries = 1e4;
38285
+ function emptySiblingCacheAlias(path, parent) {
38286
+ const link = readlinkSync(path), target = resolve2(parent, link);
38287
+ if (dirname6(target) !== parent || (isAbsolute3(link) ? link !== target : dirname6(link) !== "."))
38288
+ return false;
38289
+ if (!lstatSync2(target, { throwIfNoEntry: false })?.isDirectory())
38290
+ return false;
38291
+ const scan = cacheScans.get(target);
38292
+ return Boolean(scan?.complete && !scan.hasSkills && scan.entries <= maxAliasProofEntries);
38293
+ }
38294
+ function visit(agent, path, vendor = false, depth = 0, pluginCache = false) {
38295
+ const scan = { complete: true, hasSkills: false, entries: 1 };
38285
38296
  assertSafePath(path);
38286
38297
  if (!existsSync4(path))
38287
- return;
38298
+ return scan;
38288
38299
  if (!lstatSync2(path).isDirectory())
38289
- return;
38300
+ return scan;
38290
38301
  if (existsSync4(join9(path, "SKILL.md"))) {
38302
+ scan.hasSkills = true;
38291
38303
  if (seen.has(path))
38292
- return;
38304
+ return scan;
38293
38305
  seen.add(path);
38294
38306
  let managed = false;
38295
38307
  const marker = join9(path, ".hasna-skills.json");
@@ -38300,11 +38312,14 @@ function inventoryNativeSkills(home = homedir3(), options = {}) {
38300
38312
  }
38301
38313
  const rootAlias = aliases.find((item) => path === item.target || path.startsWith(item.target + sep));
38302
38314
  entries.push({ agent, path, hash: treeHash(path), managed, vendor, ...rootAlias ? { rootAlias } : {} });
38303
- return;
38315
+ return scan;
38304
38316
  }
38305
38317
  if (depth > (vendor ? 8 : 3))
38306
- return;
38307
- for (const name of readdirSync4(path).sort()) {
38318
+ return { ...scan, complete: false };
38319
+ const children = readdirSync4(path, { withFileTypes: true }).sort((a3, b3) => a3.name < b3.name ? -1 : a3.name > b3.name ? 1 : 0);
38320
+ if (pluginCache)
38321
+ children.sort((a3, b3) => Number(a3.isSymbolicLink()) - Number(b3.isSymbolicLink()));
38322
+ for (const { name } of children) {
38308
38323
  if (name === "node_modules")
38309
38324
  continue;
38310
38325
  if (name.startsWith(".") && name !== ".system")
@@ -38312,14 +38327,28 @@ function inventoryNativeSkills(home = homedir3(), options = {}) {
38312
38327
  const isVendor = vendor || name === ".system";
38313
38328
  if (isVendor && !options.includeVendor)
38314
38329
  continue;
38315
- visit(agent, join9(path, name), isVendor, depth + 1);
38330
+ const child = join9(path, name);
38331
+ if (isVendor && lstatSync2(child, { throwIfNoEntry: false })?.isSymbolicLink()) {
38332
+ const target = statSync3(child, { throwIfNoEntry: false });
38333
+ if (target?.isFile() || pluginCache && target?.isDirectory() && emptySiblingCacheAlias(child, path)) {
38334
+ scan.entries++;
38335
+ continue;
38336
+ }
38337
+ }
38338
+ const childScan = visit(agent, child, isVendor, depth + 1, pluginCache);
38339
+ scan.complete &&= childScan.complete;
38340
+ scan.hasSkills ||= childScan.hasSkills;
38341
+ scan.entries += childScan.entries;
38316
38342
  }
38343
+ if (pluginCache)
38344
+ cacheScans.set(path, scan);
38345
+ return scan;
38317
38346
  }
38318
38347
  for (const [agent, path] of roots)
38319
38348
  visit(agent, path);
38320
38349
  if (options.includeVendor) {
38321
38350
  for (const agent of ["codex", "claude"])
38322
- visit(agent, canonicalAgentPath(join9(home, `.${agent}`, "plugins", "cache"), aliases), true);
38351
+ visit(agent, canonicalAgentPath(join9(home, `.${agent}`, "plugins", "cache"), aliases), true, 0, true);
38323
38352
  }
38324
38353
  recheckRootAliases(aliases);
38325
38354
  return entries;
@@ -38532,7 +38561,7 @@ import {
38532
38561
  realpathSync as realpathSync3,
38533
38562
  renameSync as renameSync2,
38534
38563
  rmSync,
38535
- statSync as statSync7,
38564
+ statSync as statSync8,
38536
38565
  writeFileSync as writeFileSync4
38537
38566
  } from "fs";
38538
38567
  import { basename as basename2, dirname as dirname8, isAbsolute as isAbsolute5, join as join14, normalize as normalize2, relative as relative4, resolve as resolve3, sep as sep3 } from "path";
@@ -39247,7 +39276,7 @@ var SKILLS = [
39247
39276
  ];
39248
39277
 
39249
39278
  // src/lib/hosted-skill-set.ts
39250
- import { existsSync as existsSync5, readFileSync as readFileSync7, readdirSync as readdirSync5, statSync as statSync3 } from "fs";
39279
+ import { existsSync as existsSync5, readFileSync as readFileSync7, readdirSync as readdirSync5, statSync as statSync4 } from "fs";
39251
39280
  import { join as join10 } from "path";
39252
39281
  var HOSTED_RUNTIMES = new Set(["hosted"]);
39253
39282
  var HOSTED_SOURCES = new Set(["remote", "private-hosted"]);
@@ -39286,7 +39315,7 @@ var BRACE_SOURCE_EXCLUSION = new RegExp(`^!skills/\\{(${SLUG}(?:,${SLUG})+)\\}/s
39286
39315
  var SINGLE_SOURCE_EXCLUSION = new RegExp(`^!skills/(${SLUG})/src$`);
39287
39316
 
39288
39317
  // src/lib/skill-validation.ts
39289
- import { existsSync as existsSync6, lstatSync as lstatSync3, readFileSync as readFileSync8, readdirSync as readdirSync6, statSync as statSync4 } from "fs";
39318
+ import { existsSync as existsSync6, lstatSync as lstatSync3, readFileSync as readFileSync8, readdirSync as readdirSync6, statSync as statSync5 } from "fs";
39290
39319
  import { isAbsolute as isAbsolute4, join as join11, normalize } from "path";
39291
39320
  var VALID_SKILL_KINDS = ["executable", "instruction"];
39292
39321
  var DOC_FILES = ["SKILL.md", "README.md", "CLAUDE.md"];
@@ -39574,7 +39603,7 @@ function validateSkillDirectory(name, skillPath, registryMeta) {
39574
39603
  const targetPath = join11(skillPath, target);
39575
39604
  if (!existsSync6(targetPath)) {
39576
39605
  add(warnings, "package.bin_target_missing", `package.json bin '${command3}' target '${target}' is not present before build`);
39577
- } else if (statSync4(targetPath).isDirectory()) {
39606
+ } else if (statSync5(targetPath).isDirectory()) {
39578
39607
  add(issues, "package.bin_target_directory", `package.json bin '${command3}' target '${target}' must point to a file, not a directory`);
39579
39608
  }
39580
39609
  }
@@ -39600,7 +39629,7 @@ function validateSkillDirectory(name, skillPath, registryMeta) {
39600
39629
  add(issues, "skill.src_index_missing", "Missing src/index.ts or src/index.js");
39601
39630
  } else {
39602
39631
  const indexPath = existsSync6(join11(srcDir, "index.ts")) ? join11(srcDir, "index.ts") : join11(srcDir, "index.js");
39603
- const size = statSync4(indexPath).size;
39632
+ const size = statSync5(indexPath).size;
39604
39633
  if (size < 50)
39605
39634
  add(warnings, "skill.src_index_minimal", `Source entry point is very small (${size}B)`);
39606
39635
  }
@@ -39625,7 +39654,7 @@ function validateRegistryConsistency(registry, skillsDir) {
39625
39654
  })));
39626
39655
  const skillDirs = existsSync6(skillsDir) ? readdirSync6(skillsDir).filter((entry) => {
39627
39656
  const fullPath = join11(skillsDir, entry);
39628
- return !entry.startsWith(".") && entry !== "_common" && statSync4(fullPath).isDirectory();
39657
+ return !entry.startsWith(".") && entry !== "_common" && statSync5(fullPath).isDirectory();
39629
39658
  }) : [];
39630
39659
  const directoryNames = new Set(skillDirs);
39631
39660
  const registryNameSet = new Set(registryNames);
@@ -39641,7 +39670,7 @@ function validateRegistryConsistency(registry, skillsDir) {
39641
39670
 
39642
39671
  // src/lib/skill-hash.ts
39643
39672
  import { createHash as createHash8 } from "crypto";
39644
- import { existsSync as existsSync7, readdirSync as readdirSync7, readFileSync as readFileSync9, statSync as statSync5 } from "fs";
39673
+ import { existsSync as existsSync7, readdirSync as readdirSync7, readFileSync as readFileSync9, statSync as statSync6 } from "fs";
39645
39674
  import { join as join12, sep as sep2 } from "path";
39646
39675
  var CONTENT_HASH_ALGORITHM = "sha256";
39647
39676
  var HASH_EXCLUDE_DIRS = new Set([".git", "node_modules", "dist", "build", ".turbo"]);
@@ -39707,7 +39736,7 @@ function collectBundleFiles(skillPath) {
39707
39736
  const absolute = join12(skillPath, entry);
39708
39737
  if (!existsSync7(absolute))
39709
39738
  continue;
39710
- if (statSync5(absolute).isDirectory())
39739
+ if (statSync6(absolute).isDirectory())
39711
39740
  collectDirectory(files, absolute, entry);
39712
39741
  else
39713
39742
  collectFile(files, absolute, entry);
@@ -39722,7 +39751,7 @@ function collectDirectory(files, dir, rel) {
39722
39751
  const childRel = `${rel}/${entry}`;
39723
39752
  let stats;
39724
39753
  try {
39725
- stats = statSync5(absolute);
39754
+ stats = statSync6(absolute);
39726
39755
  } catch {
39727
39756
  continue;
39728
39757
  }
@@ -40871,7 +40900,7 @@ function findPortableSkill(name, options = {}) {
40871
40900
  return null;
40872
40901
  }
40873
40902
  const path = getPortableSkillPath(normalized, options);
40874
- if (!existsSync9(path) || !statSync7(path).isDirectory())
40903
+ if (!existsSync9(path) || !statSync8(path).isDirectory())
40875
40904
  return null;
40876
40905
  try {
40877
40906
  return summarizePortableSkill(path, normalized);
@@ -40940,7 +40969,7 @@ function scaffoldPortableSkill(name, options = {}) {
40940
40969
  }
40941
40970
  function portPortableSkillDirectory(sourceDir, options = {}) {
40942
40971
  const absoluteSource = normalize2(sourceDir);
40943
- if (!existsSync9(absoluteSource) || !statSync7(absoluteSource).isDirectory()) {
40972
+ if (!existsSync9(absoluteSource) || !statSync8(absoluteSource).isDirectory()) {
40944
40973
  throw new Error(`Import directory not found: ${sourceDir}`);
40945
40974
  }
40946
40975
  const continueOnError = options.continueOnError ?? true;
@@ -41006,7 +41035,7 @@ function assertDisjointImportPaths(source, destination) {
41006
41035
  }
41007
41036
  function portPortableSkill(sourcePath, options = {}) {
41008
41037
  const absoluteSource = normalize2(sourcePath);
41009
- if (!existsSync9(absoluteSource) || !statSync7(absoluteSource).isDirectory()) {
41038
+ if (!existsSync9(absoluteSource) || !statSync8(absoluteSource).isDirectory()) {
41010
41039
  throw new Error(`Skill source directory not found: ${sourcePath}`);
41011
41040
  }
41012
41041
  const inferred = readPortableSkillManifestForImport(absoluteSource);
@@ -41170,7 +41199,7 @@ function validatePortableSkillDirectory(name, skillPath) {
41170
41199
  const entryPath = join14(skillPath, command3.entry);
41171
41200
  if (!existsSync9(entryPath))
41172
41201
  add3(issues, "portable.command_entry_missing", `Command '${command3.name}' entry '${command3.entry}' is missing`);
41173
- else if (statSync7(entryPath).isDirectory())
41202
+ else if (statSync8(entryPath).isDirectory())
41174
41203
  add3(issues, "portable.command_entry_directory", `Command '${command3.name}' entry '${command3.entry}' must be a file`);
41175
41204
  }
41176
41205
  }
@@ -41260,7 +41289,7 @@ function summarizePortableSkill(skillPath, fallbackName) {
41260
41289
  }
41261
41290
  function safeIsDirectory(path) {
41262
41291
  try {
41263
- return statSync7(path).isDirectory();
41292
+ return statSync8(path).isDirectory();
41264
41293
  } catch {
41265
41294
  return false;
41266
41295
  }
@@ -41400,7 +41429,7 @@ function containsSkillDirectories(path) {
41400
41429
  }
41401
41430
  function isDirectory(path) {
41402
41431
  try {
41403
- return statSync8(path).isDirectory();
41432
+ return statSync9(path).isDirectory();
41404
41433
  } catch {
41405
41434
  return false;
41406
41435
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hasna/skills",
3
- "version": "0.6.0",
3
+ "version": "0.6.2",
4
4
  "description": "Skills library for AI coding agents",
5
5
  "type": "module",
6
6
  "bin": {