skilld 0.11.2 → 0.12.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.
@@ -17,9 +17,14 @@ function getCacheDir(name, version) {
17
17
  }
18
18
  const CACHE_DIR = join(homedir(), ".skilld");
19
19
  const REFERENCES_DIR = join(CACHE_DIR, "references");
20
+ const REPOS_DIR = join(CACHE_DIR, "repos");
21
+ function getRepoCacheDir(owner, repo) {
22
+ if (owner.includes("..") || repo.includes("..") || owner.includes("/") || repo.includes("/")) throw new Error(`Invalid repo path: ${owner}/${repo}`);
23
+ return join(REPOS_DIR, owner, repo);
24
+ }
20
25
  function getPackageDbPath(name, version) {
21
26
  return join(REFERENCES_DIR, getCacheKey(name, version), "search.db");
22
27
  }
23
- export { getCacheKey as a, getCacheDir as i, REFERENCES_DIR as n, getVersionKey as o, getPackageDbPath as r, CACHE_DIR as t };
28
+ export { getRepoCacheDir as a, getVersionKey as c, getPackageDbPath as i, REFERENCES_DIR as n, getCacheDir as o, REPOS_DIR as r, getCacheKey as s, CACHE_DIR as t };
24
29
 
25
30
  //# sourceMappingURL=config.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"config.mjs","names":[],"sources":["../../src/cache/version.ts","../../src/cache/config.ts"],"sourcesContent":["/**\n * Version utilities\n */\n\nimport { resolve } from 'pathe'\nimport { REFERENCES_DIR } from './config.ts'\n\n/** Validate npm package name (scoped or unscoped) */\nconst VALID_PKG_NAME = /^(?:@[a-z0-9][-a-z0-9._]*\\/)?[a-z0-9][-a-z0-9._]*$/\n\n/** Validate version string (semver-ish, no path separators) */\nconst VALID_VERSION = /^[a-z0-9][-\\w.+]*$/i\n\n/**\n * Get exact version key for cache keying\n */\nexport function getVersionKey(version: string): string {\n return version\n}\n\n/**\n * Get cache key for a package: name@version\n */\nexport function getCacheKey(name: string, version: string): string {\n return `${name}@${getVersionKey(version)}`\n}\n\n/**\n * Get path to cached package references.\n * Validates name/version to prevent path traversal.\n */\nexport function getCacheDir(name: string, version: string): string {\n if (!VALID_PKG_NAME.test(name))\n throw new Error(`Invalid package name: ${name}`)\n if (!VALID_VERSION.test(version))\n throw new Error(`Invalid version: ${version}`)\n\n const dir = resolve(REFERENCES_DIR, getCacheKey(name, version))\n if (!dir.startsWith(REFERENCES_DIR))\n throw new Error(`Path traversal detected: ${dir}`)\n return dir\n}\n","/**\n * Cache configuration\n */\n\nimport { homedir } from 'node:os'\nimport { join } from 'pathe'\nimport { getCacheKey } from './version.ts'\n\n/** Global cache directory */\nexport const CACHE_DIR = join(homedir(), '.skilld')\n\n/** References subdirectory */\nexport const REFERENCES_DIR = join(CACHE_DIR, 'references')\n\n/** Get search DB path for a specific package@version */\nexport function getPackageDbPath(name: string, version: string): string {\n return join(REFERENCES_DIR, getCacheKey(name, version), 'search.db')\n}\n"],"mappings":";;AAQA,MAAM,iBAAiB;AAGvB,MAAM,gBAAgB;AAKtB,SAAgB,cAAc,SAAyB;AACrD,QAAO;;AAMT,SAAgB,YAAY,MAAc,SAAyB;AACjE,QAAO,GAAG,KAAK,GAAG,cAAc,QAAQ;;AAO1C,SAAgB,YAAY,MAAc,SAAyB;AACjE,KAAI,CAAC,eAAe,KAAK,KAAK,CAC5B,OAAM,IAAI,MAAM,yBAAyB,OAAO;AAClD,KAAI,CAAC,cAAc,KAAK,QAAQ,CAC9B,OAAM,IAAI,MAAM,oBAAoB,UAAU;CAEhD,MAAM,MAAM,QAAQ,gBAAgB,YAAY,MAAM,QAAQ,CAAC;AAC/D,KAAI,CAAC,IAAI,WAAW,eAAe,CACjC,OAAM,IAAI,MAAM,4BAA4B,MAAM;AACpD,QAAO;;AC/BT,MAAa,YAAY,KAAK,SAAS,EAAE,UAAU;AAGnD,MAAa,iBAAiB,KAAK,WAAW,aAAa;AAG3D,SAAgB,iBAAiB,MAAc,SAAyB;AACtE,QAAO,KAAK,gBAAgB,YAAY,MAAM,QAAQ,EAAE,YAAY"}
1
+ {"version":3,"file":"config.mjs","names":[],"sources":["../../src/cache/version.ts","../../src/cache/config.ts"],"sourcesContent":["/**\n * Version utilities\n */\n\nimport { resolve } from 'pathe'\nimport { REFERENCES_DIR } from './config.ts'\n\n/** Validate npm package name (scoped or unscoped) */\nconst VALID_PKG_NAME = /^(?:@[a-z0-9][-a-z0-9._]*\\/)?[a-z0-9][-a-z0-9._]*$/\n\n/** Validate version string (semver-ish, no path separators) */\nconst VALID_VERSION = /^[a-z0-9][-\\w.+]*$/i\n\n/**\n * Get exact version key for cache keying\n */\nexport function getVersionKey(version: string): string {\n return version\n}\n\n/**\n * Get cache key for a package: name@version\n */\nexport function getCacheKey(name: string, version: string): string {\n return `${name}@${getVersionKey(version)}`\n}\n\n/**\n * Get path to cached package references.\n * Validates name/version to prevent path traversal.\n */\nexport function getCacheDir(name: string, version: string): string {\n if (!VALID_PKG_NAME.test(name))\n throw new Error(`Invalid package name: ${name}`)\n if (!VALID_VERSION.test(version))\n throw new Error(`Invalid version: ${version}`)\n\n const dir = resolve(REFERENCES_DIR, getCacheKey(name, version))\n if (!dir.startsWith(REFERENCES_DIR))\n throw new Error(`Path traversal detected: ${dir}`)\n return dir\n}\n","/**\n * Cache configuration\n */\n\nimport { homedir } from 'node:os'\nimport { join } from 'pathe'\nimport { getCacheKey } from './version.ts'\n\n/** Global cache directory */\nexport const CACHE_DIR = join(homedir(), '.skilld')\n\n/** References subdirectory */\nexport const REFERENCES_DIR = join(CACHE_DIR, 'references')\n\n/** Repo-level cache (issues, discussions, releases shared across monorepo packages) */\nexport const REPOS_DIR = join(CACHE_DIR, 'repos')\n\n/** Get repo cache dir for owner/repo with path traversal validation */\nexport function getRepoCacheDir(owner: string, repo: string): string {\n if (owner.includes('..') || repo.includes('..') || owner.includes('/') || repo.includes('/'))\n throw new Error(`Invalid repo path: ${owner}/${repo}`)\n return join(REPOS_DIR, owner, repo)\n}\n\n/** Get search DB path for a specific package@version */\nexport function getPackageDbPath(name: string, version: string): string {\n return join(REFERENCES_DIR, getCacheKey(name, version), 'search.db')\n}\n"],"mappings":";;AAQA,MAAM,iBAAiB;AAGvB,MAAM,gBAAgB;AAKtB,SAAgB,cAAc,SAAyB;AACrD,QAAO;;AAMT,SAAgB,YAAY,MAAc,SAAyB;AACjE,QAAO,GAAG,KAAK,GAAG,cAAc,QAAQ;;AAO1C,SAAgB,YAAY,MAAc,SAAyB;AACjE,KAAI,CAAC,eAAe,KAAK,KAAK,CAC5B,OAAM,IAAI,MAAM,yBAAyB,OAAO;AAClD,KAAI,CAAC,cAAc,KAAK,QAAQ,CAC9B,OAAM,IAAI,MAAM,oBAAoB,UAAU;CAEhD,MAAM,MAAM,QAAQ,gBAAgB,YAAY,MAAM,QAAQ,CAAC;AAC/D,KAAI,CAAC,IAAI,WAAW,eAAe,CACjC,OAAM,IAAI,MAAM,4BAA4B,MAAM;AACpD,QAAO;;AC/BT,MAAa,YAAY,KAAK,SAAS,EAAE,UAAU;AAGnD,MAAa,iBAAiB,KAAK,WAAW,aAAa;AAG3D,MAAa,YAAY,KAAK,WAAW,QAAQ;AAGjD,SAAgB,gBAAgB,OAAe,MAAsB;AACnE,KAAI,MAAM,SAAS,KAAK,IAAI,KAAK,SAAS,KAAK,IAAI,MAAM,SAAS,IAAI,IAAI,KAAK,SAAS,IAAI,CAC1F,OAAM,IAAI,MAAM,sBAAsB,MAAM,GAAG,OAAO;AACxD,QAAO,KAAK,WAAW,OAAO,KAAK;;AAIrC,SAAgB,iBAAiB,MAAc,SAAyB;AACtE,QAAO,KAAK,gBAAgB,YAAY,MAAM,QAAQ,EAAE,YAAY"}
@@ -1,7 +1,7 @@
1
1
  import { t as __exportAll } from "./chunk.mjs";
2
- import { _ as writeSections, b as sanitizeMarkdown, h as readCachedSection, y as repairMarkdown } from "./storage.mjs";
2
+ import { S as sanitizeMarkdown, g as readCachedSection, v as writeSections, x as repairMarkdown } from "./storage.mjs";
3
3
  import { t as yamlEscape } from "./yaml.mjs";
4
- import { i as getPackageRules, r as getFilePatterns } from "./package-registry.mjs";
4
+ import { a as getPackageRules, i as getFilePatterns } from "./package-registry.mjs";
5
5
  import { homedir } from "node:os";
6
6
  import { dirname, join, relative } from "pathe";
7
7
  import { existsSync, lstatSync, mkdirSync, readFileSync, readdirSync, realpathSync, symlinkSync, unlinkSync, writeFileSync } from "node:fs";
@@ -554,14 +554,21 @@ function maxItems(min, max, sectionCount) {
554
554
  const scale = budgetScale(sectionCount);
555
555
  return Math.max(min, Math.round(max * scale));
556
556
  }
557
+ function releaseBoost(significantReleases, minorVersion) {
558
+ const combined = (!significantReleases ? 0 : significantReleases <= 5 ? 0 : significantReleases <= 15 ? 1 : 2) + (!minorVersion ? 0 : minorVersion <= 3 ? 0 : minorVersion <= 10 ? 1 : 2);
559
+ if (combined <= 0) return 1;
560
+ if (combined <= 2) return 1.3;
561
+ return 1.6;
562
+ }
557
563
  function budgetScale(sectionCount) {
558
564
  if (!sectionCount || sectionCount <= 1) return 1;
559
565
  if (sectionCount === 2) return .85;
560
566
  if (sectionCount === 3) return .7;
561
567
  return .6;
562
568
  }
563
- function apiChangesSection({ packageName, version, hasReleases, hasChangelog, hasDocs, hasIssues, hasDiscussions, features, enabledSectionCount }) {
569
+ function apiChangesSection({ packageName, version, hasReleases, hasChangelog, hasDocs, hasIssues, hasDiscussions, pkgFiles, features, enabledSectionCount, releaseCount }) {
564
570
  const [, major, minor] = version?.match(/^(\d+)\.(\d+)/) ?? [];
571
+ const boost = releaseBoost(releaseCount, minor ? Number(minor) : void 0);
565
572
  const searchHints = [];
566
573
  if (features?.search !== false) {
567
574
  searchHints.push(`\`npx -y skilld search "deprecated" -p ${packageName}\``, `\`npx -y skilld search "breaking" -p ${packageName}\``);
@@ -643,21 +650,29 @@ This section documents version-specific API changes — prioritize recent major/
643
650
  - NEW: \`useTemplateRef()\` — new in v3.5, replaces \`$refs\` pattern [source](./.skilld/releases/v3.5.0.md)
644
651
 
645
652
  - BREAKING: \`db.query()\` — returns \`{ rows }\` not raw array since v4 [source](./.skilld/docs/migration.md)
653
+
654
+ **Also changed:** \`defineModel()\` stable v3.4 · \`onWatcherCleanup()\` new v3.5 · \`Suspense\` stable v3.5
646
655
  </format-example>
647
656
 
648
- Each item: BREAKING/DEPRECATED/NEW label + API name + what changed + source link. All source links MUST use \`./.skilld/\` prefix (e.g., \`[source](./.skilld/releases/v2.0.0.md)\`). Do NOT use emoji — use plain text markers only.`,
657
+ Each item: BREAKING/DEPRECATED/NEW label + API name + what changed + source link. All source links MUST use \`./.skilld/\` prefix (e.g., \`[source](./.skilld/releases/v2.0.0.md)\`). Do NOT use emoji — use plain text markers only.
658
+
659
+ **Tiered format:** Top-scoring items get full detailed entries. Remaining relevant items go in a compact "**Also changed:**" line at the end — API name + brief label, separated by \` · \`. This surfaces more changes without bloating the section.`,
649
660
  rules: [
650
- `- **API Changes:** ${maxItems(6, 12, enabledSectionCount)} items from version history, MAX ${maxLines(50, 80, enabledSectionCount)} lines`,
661
+ `- **API Changes:** ${maxItems(6, Math.round(12 * boost), enabledSectionCount)} detailed items + compact "Also changed" line for remaining, MAX ${maxLines(50, Math.round(80 * boost), enabledSectionCount)} lines`,
651
662
  "- **Recency:** Only include changes from the current major version and the previous→current migration. Exclude changes from older major versions entirely — users already migrated past them",
652
663
  "- Focus on APIs that CHANGED, not general conventions or gotchas",
653
664
  "- New APIs get NEW: prefix, deprecated/breaking get BREAKING: or DEPRECATED: prefix",
654
- "- **Experimental APIs:** Append `(experimental)` to any API behind an experimental/unstable import path or flag. MAX 2 experimental items",
665
+ "- **Experimental APIs:** Append `(experimental)` to ALL items for unstable/experimental APIs every mention, not just the first. MAX 2 experimental items",
666
+ pkgFiles?.some((f) => f.endsWith(".d.ts")) ? "- **Verify before including:** Search for API names in `.d.ts` type definitions or source exports. If you searched and cannot find the export, do NOT include the item — you may be confusing it with a similar API from a different package or version" : "- **Verify before including:** Cross-reference API names against release notes, changelogs, or docs. Do NOT include APIs you infer from similar packages — only include APIs explicitly named in the references",
667
+ "- **Framework-specific sourcing:** When docs have framework-specific subdirectories (e.g., `vue/`, `react/`), always cite the framework-specific version. Never cite React migration guides as sources in a Vue skill when equivalent Vue docs exist",
655
668
  hasReleases ? "- Start with `./.skilld/releases/_INDEX.md` to identify recent major/minor releases, then read specific release files" : "",
656
669
  hasChangelog ? "- Scan CHANGELOG.md for version headings, focus on Features/Breaking Changes sections" : ""
657
670
  ].filter(Boolean)
658
671
  };
659
672
  }
660
- function bestPracticesSection({ packageName, hasIssues, hasDiscussions, hasReleases, hasChangelog, hasDocs, features, enabledSectionCount }) {
673
+ function bestPracticesSection({ packageName, hasIssues, hasDiscussions, hasReleases, hasChangelog, hasDocs, pkgFiles, features, enabledSectionCount, releaseCount, version }) {
674
+ const [, , minor] = version?.match(/^(\d+)\.(\d+)/) ?? [];
675
+ const boost = 1 + (releaseBoost(releaseCount, minor ? Number(minor) : void 0) - 1) * .5;
661
676
  const searchHints = [];
662
677
  if (features?.search !== false) searchHints.push(`\`npx -y skilld search "recommended" -p ${packageName}\``, `\`npx -y skilld search "avoid" -p ${packageName}\``);
663
678
  const referenceWeights = [];
@@ -720,10 +735,12 @@ instance.init({ ... })
720
735
 
721
736
  Each item: markdown list item (-) + ${packageName}-specific pattern + why it's preferred + source link. Code block only when the pattern isn't obvious from the title. Use the most relevant language tag (ts, vue, css, json, etc). Every example must be specific to ${packageName} — never generic TypeScript/JS advice. All source links MUST use \`./.skilld/\` prefix (e.g., \`[source](./.skilld/docs/guide.md)\`). Do NOT use emoji — use plain text markers only.`,
722
737
  rules: [
723
- `- **${maxItems(4, 10, enabledSectionCount)} best practice items**`,
724
- `- **MAX ${maxLines(80, 150, enabledSectionCount)} lines** for best practices section`,
725
- "- **Verify before including:** Confirm file paths exist via Glob/Read before linking. Confirm functions/composables are real exports in `./.skilld/pkg/` `.d.ts` files before documenting",
726
- "- **Diversity:** Cover at least 3 distinct areas of the library. No single feature should have more than 40% of items",
738
+ `- **${maxItems(4, Math.round(10 * boost), enabledSectionCount)} best practice items**`,
739
+ `- **MAX ${maxLines(80, Math.round(150 * boost), enabledSectionCount)} lines** for best practices section`,
740
+ pkgFiles?.some((f) => f.endsWith(".d.ts")) ? "- **Verify before including:** Confirm file paths exist via Glob/Read before linking. Confirm functions/composables are real exports in `./.skilld/pkg/` `.d.ts` files before documenting. If you cannot find an export, do NOT include it" : "- **Verify before including:** Confirm file paths exist via Glob/Read before linking. Only document APIs explicitly named in docs, release notes, or changelogs — do NOT infer API names from similar packages",
741
+ "- **Source quality:** Issues and discussions are only valid sources if they contain a maintainer response, accepted answer, or confirmed workaround. Do NOT cite bare issue titles, one-line feature requests, or unresolved questions as sources",
742
+ "- **Framework-specific sourcing:** When docs have framework-specific subdirectories (e.g., `vue/`, `react/`), always prefer the framework-specific version over shared or other-framework docs. Never cite React examples in a Vue skill",
743
+ "- **Diversity:** Cover at least 3 distinct areas of the library. Count items per feature — if any single feature exceeds 40% of items, replace the excess with items from underrepresented areas",
727
744
  "- **Experimental APIs:** Mark unstable/experimental features with `(experimental)` in the description. **MAX 1 experimental item** — prioritize stable, production-ready patterns that most users need"
728
745
  ]
729
746
  };
@@ -824,6 +841,12 @@ function buildSectionPrompt(opts) {
824
841
  ...opts,
825
842
  versionContext
826
843
  });
844
+ const hasDocs = !!opts.docFiles?.some((f) => f.includes("/docs/"));
845
+ const releaseCount = opts.docFiles?.filter((f) => {
846
+ if (!f.includes("/releases/")) return false;
847
+ const m = f.match(/v\d+\.(\d+)\.(\d+)\.md$/);
848
+ return m && (m[1] === "0" || m[2] === "0");
849
+ }).length;
827
850
  const sectionDef = getSectionDef(section, {
828
851
  packageName,
829
852
  version,
@@ -831,9 +854,11 @@ function buildSectionPrompt(opts) {
831
854
  hasDiscussions,
832
855
  hasReleases,
833
856
  hasChangelog,
834
- hasDocs: !!opts.docFiles?.some((f) => f.includes("/docs/")),
857
+ hasDocs,
858
+ pkgFiles: opts.pkgFiles,
835
859
  features: opts.features,
836
- enabledSectionCount: opts.enabledSectionCount
860
+ enabledSectionCount: opts.enabledSectionCount,
861
+ releaseCount
837
862
  }, customPrompt);
838
863
  if (!sectionDef) return "";
839
864
  const outputFile = SECTION_OUTPUT_FILES[section];
@@ -844,11 +869,11 @@ function buildSectionPrompt(opts) {
844
869
  `- **NEVER fetch external URLs.** All information is in the local \`./.skilld/\` directory. Use Read, Glob${opts.features?.search !== false ? ", and `skilld search`" : ""} only.`,
845
870
  "- **Do NOT use Task tool or spawn subagents.** Work directly.",
846
871
  "- **Do NOT re-read files** you have already read in this session.",
847
- "- **Read `_INDEX.md` first** in issues/releases/discussions — only drill into files that look relevant. Skip stub/placeholder files.",
872
+ "- **Read `_INDEX.md` first** in docs/issues/releases/discussions — only drill into files that look relevant. Skip stub/placeholder files.",
848
873
  "- **Skip files starting with `PROMPT_`** — these are generation prompts, not reference material.",
849
874
  "- **Stop exploring once you have enough high-quality items** to fill the budget. Do not read additional files just to be thorough.",
850
- "- **To verify API exports:** Read the `.d.ts` file directly (see Types row in references). Package directories are often gitignored — if you search `pkg/`, pass `no_ignore: true` to avoid silent empty results."
851
- ];
875
+ opts.pkgFiles?.some((f) => f.endsWith(".d.ts")) ? "- **To verify API exports:** Read the `.d.ts` file directly (see Types row in references). Package directories are often gitignored — if you search `pkg/`, pass `no_ignore: true` to avoid silent empty results." : ""
876
+ ].filter(Boolean);
852
877
  return `${preamble}${sectionDef.referenceWeights?.length ? `\n\n## Reference Priority\n\n| Reference | Path | Score | Use For |\n|-----------|------|:-----:|--------|\n${sectionDef.referenceWeights.map((w) => `| ${w.name} | [\`${w.path.split("/").pop()}\`](${w.path}) | ${w.score}/10 | ${w.useFor} |`).join("\n")}` : ""}
853
878
 
854
879
  ## Task
@@ -883,12 +908,8 @@ function buildAllSectionPrompts(opts) {
883
908
  function sanitizeName(name) {
884
909
  return name.toLowerCase().replace(/[^a-z0-9._]+/g, "-").replace(/^[.\-]+|[.\-]+$/g, "").slice(0, 255) || "unnamed-skill";
885
910
  }
886
- function computeSkillDirName(packageName, repoUrl) {
887
- if (repoUrl) {
888
- const match = repoUrl.match(/github\.com\/([^/]+)\/([^/]+?)(?:\.git)?(?:[/#]|$)/);
889
- if (match) return sanitizeName(`${match[1]}-${match[2]}`);
890
- }
891
- return sanitizeName(packageName);
911
+ function computeSkillDirName(packageName) {
912
+ return `${sanitizeName(packageName)}-skilld`;
892
913
  }
893
914
  function installSkillForAgents(skillName, skillContent, options = {}) {
894
915
  const isGlobal = options.global ?? false;
@@ -1056,7 +1077,7 @@ function generateFrontmatter({ name, version, description: pkgDescription, globs
1056
1077
  if (desc.length > 1024) desc = `${desc.slice(0, 1021)}...`;
1057
1078
  const lines = [
1058
1079
  "---",
1059
- `name: ${dirName ?? sanitizeName(name)}`,
1080
+ `name: ${dirName ?? computeSkillDirName(name)}`,
1060
1081
  `description: ${yamlEscape(desc)}`
1061
1082
  ];
1062
1083
  const metaEntries = [];
@@ -1810,7 +1831,7 @@ function shortenPath(p) {
1810
1831
  return parts.length > 2 ? `.../${parts.slice(-2).join("/")}` : p;
1811
1832
  }
1812
1833
  const SECTION_MAX_LINES = {
1813
- "api-changes": 160,
1834
+ "api-changes": 210,
1814
1835
  "best-practices": 300,
1815
1836
  "custom": 160
1816
1837
  };