pnpm-plugin-skills 0.2.0 → 0.3.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.
package/dist/index.js CHANGED
@@ -6432,18 +6432,18 @@ var __webpack_exports__ = {};
6432
6432
  __webpack_require__.d(__webpack_exports__, {
6433
6433
  preResolution: ()=>preResolution
6434
6434
  });
6435
- const promises_namespaceObject = require("node:fs/promises");
6436
- const external_node_path_namespaceObject = require("node:path");
6437
- var dist = __webpack_require__("../../node_modules/.pnpm/yaml@2.8.3/node_modules/yaml/dist/index.js");
6438
- const external_node_crypto_namespaceObject = require("node:crypto");
6439
- const external_node_os_namespaceObject = require("node:os");
6440
- const external_node_child_process_namespaceObject = require("node:child_process");
6441
- const external_node_util_namespaceObject = require("node:util");
6442
6435
  if ("u" > typeof process) {
6443
6436
  "u" > typeof Deno && "string" == typeof Deno.version?.deno || "u" > typeof Bun && Bun.version;
6444
6437
  process.platform, process.arch, process.version;
6445
6438
  process.argv;
6446
6439
  } else "u" < typeof navigator || (navigator.platform, navigator.userAgent);
6440
+ const promises_namespaceObject = require("node:fs/promises");
6441
+ const external_node_path_namespaceObject = require("node:path");
6442
+ var dist = __webpack_require__("../../node_modules/.pnpm/yaml@2.8.3/node_modules/yaml/dist/index.js");
6443
+ const external_node_child_process_namespaceObject = require("node:child_process");
6444
+ const external_node_os_namespaceObject = require("node:os");
6445
+ const external_node_util_namespaceObject = require("node:util");
6446
+ const external_node_crypto_namespaceObject = require("node:crypto");
6447
6447
  const external_node_process_namespaceObject = require("node:process");
6448
6448
  require("node:readline");
6449
6449
  __webpack_require__("../../node_modules/.pnpm/sisteransi@1.0.5/node_modules/sisteransi/src/index.js");
@@ -6574,39 +6574,267 @@ var __webpack_exports__ = {};
6574
6574
  new RegExp(`(?:\\${ke}(?<code>\\d+)m|\\${Se}(?<uri>.*)${Ce})`, "y");
6575
6575
  dist_I("\u2500", "-"), dist_I("\u2501", "="), dist_I("\u2588", "#");
6576
6576
  (0, external_node_util_namespaceObject.styleText)("gray", dist_h);
6577
+ var package_namespaceObject = {
6578
+ rE: "0.3.0"
6579
+ };
6580
+ var codes_ErrorCode = /*#__PURE__*/ function(ErrorCode) {
6581
+ ErrorCode["FILE_NOT_FOUND"] = "ENOENT";
6582
+ ErrorCode["PERMISSION_DENIED"] = "EACCES";
6583
+ ErrorCode["FILE_EXISTS"] = "EEXIST";
6584
+ ErrorCode["FS_ERROR"] = "EFS";
6585
+ ErrorCode["GIT_CLONE_FAILED"] = "EGITCLONE";
6586
+ ErrorCode["GIT_FETCH_FAILED"] = "EGITFETCH";
6587
+ ErrorCode["GIT_CHECKOUT_FAILED"] = "EGITCHECKOUT";
6588
+ ErrorCode["GIT_REF_NOT_FOUND"] = "EGITREF";
6589
+ ErrorCode["GIT_NOT_INSTALLED"] = "EGITNOTFOUND";
6590
+ ErrorCode["PARSE_ERROR"] = "EPARSE";
6591
+ ErrorCode["JSON_PARSE_ERROR"] = "EJSONPARSE";
6592
+ ErrorCode["YAML_PARSE_ERROR"] = "EYAMLPARSE";
6593
+ ErrorCode["INVALID_SPECIFIER"] = "EINVALIDSPEC";
6594
+ ErrorCode["MANIFEST_NOT_FOUND"] = "EMANIFEST";
6595
+ ErrorCode["LOCKFILE_NOT_FOUND"] = "ELOCKFILE";
6596
+ ErrorCode["LOCKFILE_OUTDATED"] = "ELOCKOUTDATED";
6597
+ ErrorCode["MANIFEST_EXISTS"] = "EMANIFESTEXISTS";
6598
+ ErrorCode["NETWORK_ERROR"] = "ENETWORK";
6599
+ ErrorCode["REPO_NOT_FOUND"] = "EREPONOTFOUND";
6600
+ ErrorCode["UNKNOWN_ERROR"] = "EUNKNOWN";
6601
+ ErrorCode["NOT_IMPLEMENTED"] = "ENOTIMPL";
6602
+ ErrorCode["VALIDATION_ERROR"] = "EVALIDATION";
6603
+ ErrorCode["SKILL_NOT_FOUND"] = "ESKILLNOTFOUND";
6604
+ ErrorCode["SKILL_EXISTS"] = "ESKILLEXISTS";
6605
+ return ErrorCode;
6606
+ }({});
6607
+ class SpmError extends Error {
6608
+ code;
6609
+ cause;
6610
+ context;
6611
+ constructor(options){
6612
+ super(options.message);
6613
+ this.code = options.code;
6614
+ this.cause = options.cause;
6615
+ this.context = options.context ?? {};
6616
+ this.name = 'SpmError';
6617
+ if (Error.captureStackTrace) Error.captureStackTrace(this, SpmError);
6618
+ }
6619
+ toString() {
6620
+ let result = `${this.code}: ${this.message}`;
6621
+ if (this.cause) result += `\n Caused by: ${this.cause.message}`;
6622
+ return result;
6623
+ }
6624
+ toJSON() {
6625
+ return {
6626
+ name: this.name,
6627
+ code: this.code,
6628
+ message: this.message,
6629
+ context: this.context,
6630
+ cause: this.cause ? {
6631
+ name: this.cause.name,
6632
+ message: this.cause.message
6633
+ } : void 0,
6634
+ stack: this.stack
6635
+ };
6636
+ }
6637
+ }
6638
+ class FileSystemError extends SpmError {
6639
+ operation;
6640
+ path;
6641
+ constructor(options){
6642
+ const message = options.message ?? `${options.operation} failed for ${options.path}`;
6643
+ super({
6644
+ code: options.code,
6645
+ message,
6646
+ cause: options.cause,
6647
+ context: {
6648
+ operation: options.operation,
6649
+ path: options.path
6650
+ }
6651
+ });
6652
+ this.operation = options.operation;
6653
+ this.path = options.path;
6654
+ this.name = 'FileSystemError';
6655
+ }
6656
+ }
6657
+ class GitError extends SpmError {
6658
+ operation;
6659
+ repoUrl;
6660
+ ref;
6661
+ constructor(options){
6662
+ const message = options.message ?? `git ${options.operation} failed${options.repoUrl ? ` for ${options.repoUrl}` : ''}`;
6663
+ super({
6664
+ code: options.code,
6665
+ message,
6666
+ cause: options.cause,
6667
+ context: {
6668
+ operation: options.operation,
6669
+ repoUrl: options.repoUrl,
6670
+ ref: options.ref
6671
+ }
6672
+ });
6673
+ this.operation = options.operation;
6674
+ this.repoUrl = options.repoUrl;
6675
+ this.ref = options.ref;
6676
+ this.name = 'GitError';
6677
+ }
6678
+ }
6679
+ class ParseError extends SpmError {
6680
+ filePath;
6681
+ content;
6682
+ constructor(options){
6683
+ super({
6684
+ code: options.code,
6685
+ message: options.message,
6686
+ cause: options.cause,
6687
+ context: {
6688
+ filePath: options.filePath,
6689
+ contentSnippet: options.content?.slice(0, 200)
6690
+ }
6691
+ });
6692
+ this.filePath = options.filePath;
6693
+ this.content = options.content;
6694
+ this.name = 'ParseError';
6695
+ }
6696
+ }
6697
+ class ManifestError extends SpmError {
6698
+ filePath;
6699
+ constructor(options){
6700
+ const defaultMessages = {
6701
+ [codes_ErrorCode.MANIFEST_NOT_FOUND]: `Manifest not found: ${options.filePath}`,
6702
+ [codes_ErrorCode.LOCKFILE_NOT_FOUND]: `Lockfile not found: ${options.filePath}`,
6703
+ [codes_ErrorCode.LOCKFILE_OUTDATED]: `Lockfile is out of date: ${options.filePath}`,
6704
+ [codes_ErrorCode.MANIFEST_EXISTS]: `Manifest already exists: ${options.filePath}`
6705
+ };
6706
+ const message = options.message ?? defaultMessages[options.code] ?? `Manifest error: ${options.filePath}`;
6707
+ super({
6708
+ code: options.code,
6709
+ message,
6710
+ cause: options.cause,
6711
+ context: {
6712
+ filePath: options.filePath
6713
+ }
6714
+ });
6715
+ this.filePath = options.filePath;
6716
+ this.name = 'ManifestError';
6717
+ }
6718
+ }
6719
+ function convertNodeError(error, context) {
6720
+ switch(error.code){
6721
+ case 'ENOENT':
6722
+ return new FileSystemError({
6723
+ code: codes_ErrorCode.FILE_NOT_FOUND,
6724
+ operation: context.operation,
6725
+ path: context.path,
6726
+ cause: error
6727
+ });
6728
+ case 'EACCES':
6729
+ case 'EPERM':
6730
+ return new FileSystemError({
6731
+ code: codes_ErrorCode.PERMISSION_DENIED,
6732
+ operation: context.operation,
6733
+ path: context.path,
6734
+ cause: error
6735
+ });
6736
+ case 'EEXIST':
6737
+ return new FileSystemError({
6738
+ code: codes_ErrorCode.FILE_EXISTS,
6739
+ operation: context.operation,
6740
+ path: context.path,
6741
+ cause: error
6742
+ });
6743
+ case 'ENOTDIR':
6744
+ return new FileSystemError({
6745
+ code: codes_ErrorCode.FS_ERROR,
6746
+ operation: context.operation,
6747
+ path: context.path,
6748
+ message: `Not a directory: ${context.path}`,
6749
+ cause: error
6750
+ });
6751
+ case 'EISDIR':
6752
+ return new FileSystemError({
6753
+ code: codes_ErrorCode.FS_ERROR,
6754
+ operation: context.operation,
6755
+ path: context.path,
6756
+ message: `Is a directory: ${context.path}`,
6757
+ cause: error
6758
+ });
6759
+ default:
6760
+ return new FileSystemError({
6761
+ code: codes_ErrorCode.FS_ERROR,
6762
+ operation: context.operation,
6763
+ path: context.path,
6764
+ message: error.message,
6765
+ cause: error
6766
+ });
6767
+ }
6768
+ }
6577
6769
  async function readSkillsLock(rootDir) {
6578
6770
  const filePath = external_node_path_namespaceObject.join(rootDir, 'skills-lock.yaml');
6579
6771
  try {
6580
6772
  const raw = await (0, promises_namespaceObject.readFile)(filePath, 'utf8');
6581
- return dist.parse(raw);
6773
+ try {
6774
+ return dist.parse(raw);
6775
+ } catch (parseError) {
6776
+ throw new ParseError({
6777
+ code: codes_ErrorCode.YAML_PARSE_ERROR,
6778
+ filePath,
6779
+ content: raw,
6780
+ message: `Failed to parse skills-lock.yaml: ${parseError.message}`,
6781
+ cause: parseError
6782
+ });
6783
+ }
6582
6784
  } catch (error) {
6583
6785
  if ('ENOENT' === error.code) return null;
6584
- throw error;
6786
+ if (error instanceof ParseError) throw error;
6787
+ throw convertNodeError(error, {
6788
+ operation: 'read',
6789
+ path: filePath
6790
+ });
6585
6791
  }
6586
6792
  }
6587
6793
  async function readSkillsManifest(rootDir) {
6588
6794
  const filePath = external_node_path_namespaceObject.join(rootDir, 'skills.json');
6589
6795
  try {
6590
6796
  const raw = await (0, promises_namespaceObject.readFile)(filePath, 'utf8');
6591
- const json = JSON.parse(raw);
6592
- return {
6593
- installDir: json.installDir ?? '.agents/skills',
6594
- linkTargets: json.linkTargets ?? [],
6595
- skills: json.skills ?? {}
6596
- };
6797
+ try {
6798
+ const json = JSON.parse(raw);
6799
+ return {
6800
+ installDir: json.installDir ?? '.agents/skills',
6801
+ linkTargets: json.linkTargets ?? [],
6802
+ skills: json.skills ?? {}
6803
+ };
6804
+ } catch (parseError) {
6805
+ throw new ParseError({
6806
+ code: codes_ErrorCode.JSON_PARSE_ERROR,
6807
+ filePath,
6808
+ content: raw,
6809
+ message: `Failed to parse skills.json: ${parseError.message}`,
6810
+ cause: parseError
6811
+ });
6812
+ }
6597
6813
  } catch (error) {
6598
6814
  if ('ENOENT' === error.code) return null;
6599
- throw error;
6815
+ if (error instanceof ParseError) throw error;
6816
+ throw convertNodeError(error, {
6817
+ operation: 'read',
6818
+ path: filePath
6819
+ });
6600
6820
  }
6601
6821
  }
6602
6822
  function parseSpecifier(specifier) {
6603
6823
  const firstHashIndex = specifier.indexOf('#');
6604
6824
  const secondHashIndex = firstHashIndex >= 0 ? specifier.indexOf('#', firstHashIndex + 1) : -1;
6605
- if (secondHashIndex >= 0) throw new Error('Invalid specifier: multiple # fragments are not supported');
6825
+ if (secondHashIndex >= 0) throw new ParseError({
6826
+ code: codes_ErrorCode.INVALID_SPECIFIER,
6827
+ message: 'Invalid specifier: multiple # fragments are not supported',
6828
+ content: specifier
6829
+ });
6606
6830
  const hashIndex = firstHashIndex;
6607
6831
  const sourcePart = hashIndex >= 0 ? specifier.slice(0, hashIndex) : specifier;
6608
6832
  const fragment = hashIndex >= 0 ? specifier.slice(hashIndex + 1) : '';
6609
- if (!sourcePart) throw new Error('Specifier source is required');
6833
+ if (!sourcePart) throw new ParseError({
6834
+ code: codes_ErrorCode.INVALID_SPECIFIER,
6835
+ message: 'Specifier source is required',
6836
+ content: specifier
6837
+ });
6610
6838
  if (!fragment) return {
6611
6839
  sourcePart,
6612
6840
  ref: null,
@@ -6629,7 +6857,18 @@ var __webpack_exports__ = {};
6629
6857
  };
6630
6858
  }
6631
6859
  function normalizeSpecifier(specifier) {
6632
- const parsed = parseSpecifier(specifier);
6860
+ let parsed;
6861
+ try {
6862
+ parsed = parseSpecifier(specifier);
6863
+ } catch (error) {
6864
+ if (error instanceof ParseError) throw error;
6865
+ throw new ParseError({
6866
+ code: codes_ErrorCode.INVALID_SPECIFIER,
6867
+ message: `Invalid specifier: ${error.message}`,
6868
+ content: specifier,
6869
+ cause: error
6870
+ });
6871
+ }
6633
6872
  const type = parsed.sourcePart.startsWith('file:') ? 'file' : parsed.sourcePart.startsWith('npm:') ? 'npm' : 'git';
6634
6873
  const skillPath = parsed.path || '/';
6635
6874
  const skillName = external_node_path_namespaceObject.posix.basename(skillPath);
@@ -6696,14 +6935,32 @@ var __webpack_exports__ = {};
6696
6935
  if (commit) return commit;
6697
6936
  const clonedCommit = await resolveGitCommitByClone(url, target);
6698
6937
  if (clonedCommit) return clonedCommit;
6699
- throw new Error(`Unable to resolve git ref ${target} for ${url}`);
6938
+ throw new GitError({
6939
+ code: codes_ErrorCode.GIT_REF_NOT_FOUND,
6940
+ operation: 'resolve-ref',
6941
+ repoUrl: url,
6942
+ ref: target,
6943
+ message: `Unable to resolve git ref "${target}" for ${url}`
6944
+ });
6700
6945
  }
6701
- async function resolveLockEntry(cwd, specifier) {
6702
- const normalized = normalizeSpecifier(specifier);
6946
+ async function resolveLockEntry(cwd, specifier, skillName) {
6947
+ let normalized;
6948
+ try {
6949
+ normalized = normalizeSpecifier(specifier);
6950
+ } catch (error) {
6951
+ if (error instanceof ParseError) throw error;
6952
+ throw new ParseError({
6953
+ code: codes_ErrorCode.INVALID_SPECIFIER,
6954
+ message: `Failed to parse specifier "${specifier}": ${error.message}`,
6955
+ content: specifier,
6956
+ cause: error
6957
+ });
6958
+ }
6959
+ const finalSkillName = skillName || normalized.skillName;
6703
6960
  if ('file' === normalized.type) {
6704
6961
  const sourceRoot = external_node_path_namespaceObject.resolve(cwd, normalized.source.slice(5));
6705
6962
  return {
6706
- skillName: normalized.skillName,
6963
+ skillName: finalSkillName,
6707
6964
  entry: {
6708
6965
  specifier: normalized.normalized,
6709
6966
  resolution: {
@@ -6717,7 +6974,7 @@ var __webpack_exports__ = {};
6717
6974
  if ('git' === normalized.type) {
6718
6975
  const commit = await resolveGitCommit(normalized.source, normalized.ref);
6719
6976
  return {
6720
- skillName: normalized.skillName,
6977
+ skillName: finalSkillName,
6721
6978
  entry: {
6722
6979
  specifier: normalized.normalized,
6723
6980
  resolution: {
@@ -6730,14 +6987,21 @@ var __webpack_exports__ = {};
6730
6987
  }
6731
6988
  };
6732
6989
  }
6733
- throw new Error(`Unsupported specifier type in 0.1.0 core flow: ${normalized.type}`);
6990
+ throw new ParseError({
6991
+ code: codes_ErrorCode.INVALID_SPECIFIER,
6992
+ message: `Unsupported specifier type in 0.1.0 core flow: ${normalized.type}`,
6993
+ content: specifier
6994
+ });
6734
6995
  }
6735
- async function syncSkillsLock(cwd, manifest, existingLock) {
6736
- const nextSkills = {};
6737
- for (const specifier of Object.values(manifest.skills)){
6738
- const { skillName, entry } = await resolveLockEntry(cwd, specifier);
6739
- nextSkills[skillName] = entry;
6740
- }
6996
+ async function syncSkillsLock(cwd, manifest, _existingLock) {
6997
+ const entries = await Promise.all(Object.entries(manifest.skills).map(async ([skillName, specifier])=>{
6998
+ const { skillName: resolvedName, entry } = await resolveLockEntry(cwd, specifier, skillName);
6999
+ return [
7000
+ resolvedName,
7001
+ entry
7002
+ ];
7003
+ }));
7004
+ const nextSkills = Object.fromEntries(entries);
6741
7005
  return {
6742
7006
  lockfileVersion: '0.1',
6743
7007
  installDir: manifest.installDir ?? '.agents/skills',
@@ -6747,7 +7011,14 @@ var __webpack_exports__ = {};
6747
7011
  }
6748
7012
  async function writeSkillsLock(rootDir, lockfile) {
6749
7013
  const filePath = external_node_path_namespaceObject.join(rootDir, 'skills-lock.yaml');
6750
- await (0, promises_namespaceObject.writeFile)(filePath, dist.stringify(lockfile), 'utf8');
7014
+ try {
7015
+ await (0, promises_namespaceObject.writeFile)(filePath, dist.stringify(lockfile), 'utf8');
7016
+ } catch (error) {
7017
+ throw convertNodeError(error, {
7018
+ operation: 'write',
7019
+ path: filePath
7020
+ });
7021
+ }
6751
7022
  }
6752
7023
  (0, external_node_util_namespaceObject.promisify)(external_node_child_process_namespaceObject.execFile);
6753
7024
  new Set([
@@ -6757,6 +7028,46 @@ var __webpack_exports__ = {};
6757
7028
  'build',
6758
7029
  '__pycache__'
6759
7030
  ]);
7031
+ function parseForComparison(specifier) {
7032
+ const parsed = parseSpecifier(specifier);
7033
+ return {
7034
+ sourcePart: parsed.sourcePart,
7035
+ ref: parsed.ref,
7036
+ path: parsed.path || '/'
7037
+ };
7038
+ }
7039
+ function isSpecifierCompatible(manifestSpecifier, lockSpecifier) {
7040
+ const manifest = parseForComparison(manifestSpecifier);
7041
+ const lock = parseForComparison(lockSpecifier);
7042
+ if (manifest.sourcePart !== lock.sourcePart) return false;
7043
+ if (manifest.path !== lock.path) return false;
7044
+ if (null === manifest.ref) return true;
7045
+ return manifest.ref === lock.ref;
7046
+ }
7047
+ function normalizeInstallDir(dir) {
7048
+ return dir ?? '.agents/skills';
7049
+ }
7050
+ function normalizeLinkTargets(targets) {
7051
+ return targets ?? [];
7052
+ }
7053
+ function arraysEqual(a, b) {
7054
+ if (a.length !== b.length) return false;
7055
+ return a.every((val, i)=>val === b[i]);
7056
+ }
7057
+ function isLockInSync(manifest, lock) {
7058
+ if (!lock) return false;
7059
+ if (normalizeInstallDir(manifest.installDir) !== normalizeInstallDir(lock.installDir)) return false;
7060
+ if (!arraysEqual(normalizeLinkTargets(manifest.linkTargets), normalizeLinkTargets(lock.linkTargets))) return false;
7061
+ const manifestSkills = Object.entries(manifest.skills);
7062
+ const lockSkillNames = Object.keys(lock.skills);
7063
+ if (manifestSkills.length !== lockSkillNames.length) return false;
7064
+ for (const [name, specifier] of manifestSkills){
7065
+ const lockEntry = lock.skills[name];
7066
+ if (!lockEntry) return false;
7067
+ if (!isSpecifierCompatible(specifier, lockEntry.specifier)) return false;
7068
+ }
7069
+ return true;
7070
+ }
6760
7071
  async function ensureDir(dirPath) {
6761
7072
  await (0, promises_namespaceObject.mkdir)(dirPath, {
6762
7073
  recursive: true
@@ -6772,12 +7083,6 @@ var __webpack_exports__ = {};
6772
7083
  async function writeJson(filePath, value) {
6773
7084
  await (0, promises_namespaceObject.writeFile)(filePath, `${JSON.stringify(value, null, 2)}\n`, 'utf8');
6774
7085
  }
6775
- async function linkSkill(rootDir, installDir, linkTarget, skillName) {
6776
- const absoluteTarget = external_node_path_namespaceObject.join(rootDir, installDir, skillName);
6777
- const absoluteLink = external_node_path_namespaceObject.join(rootDir, linkTarget, skillName);
6778
- await ensureDir(external_node_path_namespaceObject.dirname(absoluteLink));
6779
- await replaceSymlink(absoluteTarget, absoluteLink);
6780
- }
6781
7086
  async function readInstallState(rootDir) {
6782
7087
  const filePath = external_node_path_namespaceObject.join(rootDir, '.agents/skills/.skills-pm-install-state.json');
6783
7088
  try {
@@ -6792,6 +7097,12 @@ var __webpack_exports__ = {};
6792
7097
  const filePath = external_node_path_namespaceObject.join(dirPath, '.skills-pm-install-state.json');
6793
7098
  await writeJson(filePath, value);
6794
7099
  }
7100
+ async function linkSkill(rootDir, installDir, linkTarget, skillName) {
7101
+ const absoluteTarget = external_node_path_namespaceObject.join(rootDir, installDir, skillName);
7102
+ const absoluteLink = external_node_path_namespaceObject.join(rootDir, linkTarget, skillName);
7103
+ await ensureDir(external_node_path_namespaceObject.dirname(absoluteLink));
7104
+ await replaceSymlink(absoluteTarget, absoluteLink);
7105
+ }
6795
7106
  async function materializeLocalSkill(rootDir, skillName, sourceRoot, sourcePath, installDir) {
6796
7107
  const relativeSkillPath = sourcePath.replace(/^\//, '');
6797
7108
  const absoluteSkillPath = external_node_path_namespaceObject.join(sourceRoot, relativeSkillPath);
@@ -6817,14 +7128,24 @@ var __webpack_exports__ = {};
6817
7128
  }
6818
7129
  const materializeGitSkill_execFileAsync = (0, external_node_util_namespaceObject.promisify)(external_node_child_process_namespaceObject.execFile);
6819
7130
  async function checkoutCommit(checkoutRoot, commit) {
6820
- await materializeGitSkill_execFileAsync('git', [
6821
- 'checkout',
6822
- commit
6823
- ], {
6824
- cwd: checkoutRoot
6825
- });
7131
+ try {
7132
+ await materializeGitSkill_execFileAsync('git', [
7133
+ 'checkout',
7134
+ commit
7135
+ ], {
7136
+ cwd: checkoutRoot
7137
+ });
7138
+ } catch (error) {
7139
+ throw new GitError({
7140
+ code: codes_ErrorCode.GIT_CHECKOUT_FAILED,
7141
+ operation: 'checkout',
7142
+ ref: commit,
7143
+ message: `Failed to checkout commit ${commit}`,
7144
+ cause: error
7145
+ });
7146
+ }
6826
7147
  }
6827
- async function fetchCommitFallback(checkoutRoot, commit) {
7148
+ async function fetchCommitFallback(checkoutRoot, commit, _repoUrl) {
6828
7149
  try {
6829
7150
  await materializeGitSkill_execFileAsync('git', [
6830
7151
  'fetch',
@@ -6867,18 +7188,33 @@ var __webpack_exports__ = {};
6867
7188
  async function materializeGitSkill(rootDir, skillName, repoUrl, commit, sourcePath, installDir) {
6868
7189
  const checkoutRoot = await (0, promises_namespaceObject.mkdtemp)(external_node_path_namespaceObject.join((0, external_node_os_namespaceObject.tmpdir)(), 'skills-pm-git-checkout-'));
6869
7190
  try {
6870
- await materializeGitSkill_execFileAsync('git', [
6871
- 'clone',
6872
- '--depth',
6873
- '1',
6874
- repoUrl,
6875
- checkoutRoot
6876
- ]);
7191
+ try {
7192
+ await materializeGitSkill_execFileAsync('git', [
7193
+ 'clone',
7194
+ '--depth',
7195
+ '1',
7196
+ repoUrl,
7197
+ checkoutRoot
7198
+ ]);
7199
+ } catch (error) {
7200
+ throw new GitError({
7201
+ code: codes_ErrorCode.GIT_CLONE_FAILED,
7202
+ operation: 'clone',
7203
+ repoUrl,
7204
+ message: `Failed to clone repository ${repoUrl}`,
7205
+ cause: error
7206
+ });
7207
+ }
6877
7208
  if (commit && 'HEAD' !== commit) try {
6878
7209
  await checkoutCommit(checkoutRoot, commit);
6879
- } catch {
6880
- await fetchCommitFallback(checkoutRoot, commit);
6881
- await checkoutCommit(checkoutRoot, commit);
7210
+ } catch (checkoutError) {
7211
+ if (checkoutError instanceof GitError) try {
7212
+ await fetchCommitFallback(checkoutRoot, commit, repoUrl);
7213
+ await checkoutCommit(checkoutRoot, commit);
7214
+ } catch {
7215
+ throw checkoutError;
7216
+ }
7217
+ else throw checkoutError;
6882
7218
  }
6883
7219
  const skillDocPath = external_node_path_namespaceObject.join(checkoutRoot, sourcePath.replace(/^\//, ''), 'SKILL.md');
6884
7220
  await (0, promises_namespaceObject.readFile)(skillDocPath, 'utf8');
@@ -6979,28 +7315,41 @@ var __webpack_exports__ = {};
6979
7315
  linked: Object.keys(lockfile.skills)
6980
7316
  };
6981
7317
  }
6982
- async function installSkills(rootDir) {
6983
- const manifest = await readSkillsManifest(rootDir);
6984
- if (!manifest) return {
6985
- status: 'skipped',
6986
- reason: 'manifest-missing'
6987
- };
6988
- const currentLock = await readSkillsLock(rootDir);
6989
- const lockfile = await syncSkillsLock(rootDir, manifest, currentLock);
6990
- await fetchSkillsFromLock(rootDir, manifest, lockfile);
6991
- await linkSkillsFromLock(rootDir, manifest, lockfile);
6992
- await writeSkillsLock(rootDir, lockfile);
7318
+ async function installCommand(options) {
7319
+ const manifest = await readSkillsManifest(options.cwd);
7320
+ if (!manifest) throw new ManifestError({
7321
+ code: codes_ErrorCode.MANIFEST_NOT_FOUND,
7322
+ filePath: `${options.cwd}/skills.json`,
7323
+ message: 'No skills.json found in the current directory. Run "spm init" to create one.'
7324
+ });
7325
+ const currentLock = await readSkillsLock(options.cwd);
7326
+ if (options.frozenLockfile) {
7327
+ if (!currentLock) throw new ManifestError({
7328
+ code: codes_ErrorCode.LOCKFILE_NOT_FOUND,
7329
+ filePath: `${options.cwd}/skills-lock.yaml`,
7330
+ message: 'Lockfile is required in frozen mode but none was found. Run "spm install" first.'
7331
+ });
7332
+ if (!isLockInSync(manifest, currentLock)) throw new ManifestError({
7333
+ code: codes_ErrorCode.LOCKFILE_OUTDATED,
7334
+ filePath: `${options.cwd}/skills-lock.yaml`,
7335
+ message: 'Lockfile is out of sync with manifest. Run install without --frozen-lockfile to update.'
7336
+ });
7337
+ await fetchSkillsFromLock(options.cwd, manifest, currentLock);
7338
+ await linkSkillsFromLock(options.cwd, manifest, currentLock);
7339
+ return {
7340
+ status: 'installed',
7341
+ installed: Object.keys(currentLock.skills)
7342
+ };
7343
+ }
7344
+ const lockfile = await syncSkillsLock(options.cwd, manifest, currentLock);
7345
+ await fetchSkillsFromLock(options.cwd, manifest, lockfile);
7346
+ await linkSkillsFromLock(options.cwd, manifest, lockfile);
7347
+ await writeSkillsLock(options.cwd, lockfile);
6993
7348
  return {
6994
7349
  status: 'installed',
6995
7350
  installed: Object.keys(lockfile.skills)
6996
7351
  };
6997
7352
  }
6998
- async function installCommand(options) {
6999
- return installSkills(options.cwd);
7000
- }
7001
- var package_namespaceObject = {
7002
- rE: "0.2.0"
7003
- };
7004
7353
  package_namespaceObject.rE;
7005
7354
  async function preResolution(options = {}) {
7006
7355
  const lockfileDir = options.lockfileDir;
package/dist/index.mjs CHANGED
@@ -1,9 +1,9 @@
1
1
  import { cp, lstat, mkdir, mkdtemp as promises_mkdtemp, readFile as promises_readFile, readdir as promises_readdir, rm, symlink, writeFile as promises_writeFile } from "node:fs/promises";
2
2
  import node_path_0 from "node:path";
3
- import { createHash } from "node:crypto";
4
- import { tmpdir as external_node_os_tmpdir } from "node:os";
5
3
  import { execFile } from "node:child_process";
4
+ import { tmpdir as external_node_os_tmpdir } from "node:os";
6
5
  import { promisify, styleText } from "node:util";
6
+ import { createHash } from "node:crypto";
7
7
  import node_process from "node:process";
8
8
  import "node:readline";
9
9
  import "node:tty";
@@ -6555,39 +6555,267 @@ new RegExp(`(?:\\${ke}(?<code>\\d+)m|\\${Se}(?<uri>.*)${Ce})`, "y");
6555
6555
  dist_I("\u2500", "-"), dist_I("\u2501", "="), dist_I("\u2588", "#");
6556
6556
  styleText("gray", dist_h);
6557
6557
  const dist = __webpack_require__("../../node_modules/.pnpm/yaml@2.8.3/node_modules/yaml/dist/index.js");
6558
+ var package_namespaceObject = {
6559
+ rE: "0.3.0"
6560
+ };
6561
+ var codes_ErrorCode = /*#__PURE__*/ function(ErrorCode) {
6562
+ ErrorCode["FILE_NOT_FOUND"] = "ENOENT";
6563
+ ErrorCode["PERMISSION_DENIED"] = "EACCES";
6564
+ ErrorCode["FILE_EXISTS"] = "EEXIST";
6565
+ ErrorCode["FS_ERROR"] = "EFS";
6566
+ ErrorCode["GIT_CLONE_FAILED"] = "EGITCLONE";
6567
+ ErrorCode["GIT_FETCH_FAILED"] = "EGITFETCH";
6568
+ ErrorCode["GIT_CHECKOUT_FAILED"] = "EGITCHECKOUT";
6569
+ ErrorCode["GIT_REF_NOT_FOUND"] = "EGITREF";
6570
+ ErrorCode["GIT_NOT_INSTALLED"] = "EGITNOTFOUND";
6571
+ ErrorCode["PARSE_ERROR"] = "EPARSE";
6572
+ ErrorCode["JSON_PARSE_ERROR"] = "EJSONPARSE";
6573
+ ErrorCode["YAML_PARSE_ERROR"] = "EYAMLPARSE";
6574
+ ErrorCode["INVALID_SPECIFIER"] = "EINVALIDSPEC";
6575
+ ErrorCode["MANIFEST_NOT_FOUND"] = "EMANIFEST";
6576
+ ErrorCode["LOCKFILE_NOT_FOUND"] = "ELOCKFILE";
6577
+ ErrorCode["LOCKFILE_OUTDATED"] = "ELOCKOUTDATED";
6578
+ ErrorCode["MANIFEST_EXISTS"] = "EMANIFESTEXISTS";
6579
+ ErrorCode["NETWORK_ERROR"] = "ENETWORK";
6580
+ ErrorCode["REPO_NOT_FOUND"] = "EREPONOTFOUND";
6581
+ ErrorCode["UNKNOWN_ERROR"] = "EUNKNOWN";
6582
+ ErrorCode["NOT_IMPLEMENTED"] = "ENOTIMPL";
6583
+ ErrorCode["VALIDATION_ERROR"] = "EVALIDATION";
6584
+ ErrorCode["SKILL_NOT_FOUND"] = "ESKILLNOTFOUND";
6585
+ ErrorCode["SKILL_EXISTS"] = "ESKILLEXISTS";
6586
+ return ErrorCode;
6587
+ }({});
6588
+ class SpmError extends Error {
6589
+ code;
6590
+ cause;
6591
+ context;
6592
+ constructor(options){
6593
+ super(options.message);
6594
+ this.code = options.code;
6595
+ this.cause = options.cause;
6596
+ this.context = options.context ?? {};
6597
+ this.name = 'SpmError';
6598
+ if (Error.captureStackTrace) Error.captureStackTrace(this, SpmError);
6599
+ }
6600
+ toString() {
6601
+ let result = `${this.code}: ${this.message}`;
6602
+ if (this.cause) result += `\n Caused by: ${this.cause.message}`;
6603
+ return result;
6604
+ }
6605
+ toJSON() {
6606
+ return {
6607
+ name: this.name,
6608
+ code: this.code,
6609
+ message: this.message,
6610
+ context: this.context,
6611
+ cause: this.cause ? {
6612
+ name: this.cause.name,
6613
+ message: this.cause.message
6614
+ } : void 0,
6615
+ stack: this.stack
6616
+ };
6617
+ }
6618
+ }
6619
+ class FileSystemError extends SpmError {
6620
+ operation;
6621
+ path;
6622
+ constructor(options){
6623
+ const message = options.message ?? `${options.operation} failed for ${options.path}`;
6624
+ super({
6625
+ code: options.code,
6626
+ message,
6627
+ cause: options.cause,
6628
+ context: {
6629
+ operation: options.operation,
6630
+ path: options.path
6631
+ }
6632
+ });
6633
+ this.operation = options.operation;
6634
+ this.path = options.path;
6635
+ this.name = 'FileSystemError';
6636
+ }
6637
+ }
6638
+ class GitError extends SpmError {
6639
+ operation;
6640
+ repoUrl;
6641
+ ref;
6642
+ constructor(options){
6643
+ const message = options.message ?? `git ${options.operation} failed${options.repoUrl ? ` for ${options.repoUrl}` : ''}`;
6644
+ super({
6645
+ code: options.code,
6646
+ message,
6647
+ cause: options.cause,
6648
+ context: {
6649
+ operation: options.operation,
6650
+ repoUrl: options.repoUrl,
6651
+ ref: options.ref
6652
+ }
6653
+ });
6654
+ this.operation = options.operation;
6655
+ this.repoUrl = options.repoUrl;
6656
+ this.ref = options.ref;
6657
+ this.name = 'GitError';
6658
+ }
6659
+ }
6660
+ class ParseError extends SpmError {
6661
+ filePath;
6662
+ content;
6663
+ constructor(options){
6664
+ super({
6665
+ code: options.code,
6666
+ message: options.message,
6667
+ cause: options.cause,
6668
+ context: {
6669
+ filePath: options.filePath,
6670
+ contentSnippet: options.content?.slice(0, 200)
6671
+ }
6672
+ });
6673
+ this.filePath = options.filePath;
6674
+ this.content = options.content;
6675
+ this.name = 'ParseError';
6676
+ }
6677
+ }
6678
+ class ManifestError extends SpmError {
6679
+ filePath;
6680
+ constructor(options){
6681
+ const defaultMessages = {
6682
+ [codes_ErrorCode.MANIFEST_NOT_FOUND]: `Manifest not found: ${options.filePath}`,
6683
+ [codes_ErrorCode.LOCKFILE_NOT_FOUND]: `Lockfile not found: ${options.filePath}`,
6684
+ [codes_ErrorCode.LOCKFILE_OUTDATED]: `Lockfile is out of date: ${options.filePath}`,
6685
+ [codes_ErrorCode.MANIFEST_EXISTS]: `Manifest already exists: ${options.filePath}`
6686
+ };
6687
+ const message = options.message ?? defaultMessages[options.code] ?? `Manifest error: ${options.filePath}`;
6688
+ super({
6689
+ code: options.code,
6690
+ message,
6691
+ cause: options.cause,
6692
+ context: {
6693
+ filePath: options.filePath
6694
+ }
6695
+ });
6696
+ this.filePath = options.filePath;
6697
+ this.name = 'ManifestError';
6698
+ }
6699
+ }
6700
+ function convertNodeError(error, context) {
6701
+ switch(error.code){
6702
+ case 'ENOENT':
6703
+ return new FileSystemError({
6704
+ code: codes_ErrorCode.FILE_NOT_FOUND,
6705
+ operation: context.operation,
6706
+ path: context.path,
6707
+ cause: error
6708
+ });
6709
+ case 'EACCES':
6710
+ case 'EPERM':
6711
+ return new FileSystemError({
6712
+ code: codes_ErrorCode.PERMISSION_DENIED,
6713
+ operation: context.operation,
6714
+ path: context.path,
6715
+ cause: error
6716
+ });
6717
+ case 'EEXIST':
6718
+ return new FileSystemError({
6719
+ code: codes_ErrorCode.FILE_EXISTS,
6720
+ operation: context.operation,
6721
+ path: context.path,
6722
+ cause: error
6723
+ });
6724
+ case 'ENOTDIR':
6725
+ return new FileSystemError({
6726
+ code: codes_ErrorCode.FS_ERROR,
6727
+ operation: context.operation,
6728
+ path: context.path,
6729
+ message: `Not a directory: ${context.path}`,
6730
+ cause: error
6731
+ });
6732
+ case 'EISDIR':
6733
+ return new FileSystemError({
6734
+ code: codes_ErrorCode.FS_ERROR,
6735
+ operation: context.operation,
6736
+ path: context.path,
6737
+ message: `Is a directory: ${context.path}`,
6738
+ cause: error
6739
+ });
6740
+ default:
6741
+ return new FileSystemError({
6742
+ code: codes_ErrorCode.FS_ERROR,
6743
+ operation: context.operation,
6744
+ path: context.path,
6745
+ message: error.message,
6746
+ cause: error
6747
+ });
6748
+ }
6749
+ }
6558
6750
  async function readSkillsLock(rootDir) {
6559
6751
  const filePath = node_path_0.join(rootDir, 'skills-lock.yaml');
6560
6752
  try {
6561
6753
  const raw = await promises_readFile(filePath, 'utf8');
6562
- return dist.parse(raw);
6754
+ try {
6755
+ return dist.parse(raw);
6756
+ } catch (parseError) {
6757
+ throw new ParseError({
6758
+ code: codes_ErrorCode.YAML_PARSE_ERROR,
6759
+ filePath,
6760
+ content: raw,
6761
+ message: `Failed to parse skills-lock.yaml: ${parseError.message}`,
6762
+ cause: parseError
6763
+ });
6764
+ }
6563
6765
  } catch (error) {
6564
6766
  if ('ENOENT' === error.code) return null;
6565
- throw error;
6767
+ if (error instanceof ParseError) throw error;
6768
+ throw convertNodeError(error, {
6769
+ operation: 'read',
6770
+ path: filePath
6771
+ });
6566
6772
  }
6567
6773
  }
6568
6774
  async function readSkillsManifest(rootDir) {
6569
6775
  const filePath = node_path_0.join(rootDir, 'skills.json');
6570
6776
  try {
6571
6777
  const raw = await promises_readFile(filePath, 'utf8');
6572
- const json = JSON.parse(raw);
6573
- return {
6574
- installDir: json.installDir ?? '.agents/skills',
6575
- linkTargets: json.linkTargets ?? [],
6576
- skills: json.skills ?? {}
6577
- };
6778
+ try {
6779
+ const json = JSON.parse(raw);
6780
+ return {
6781
+ installDir: json.installDir ?? '.agents/skills',
6782
+ linkTargets: json.linkTargets ?? [],
6783
+ skills: json.skills ?? {}
6784
+ };
6785
+ } catch (parseError) {
6786
+ throw new ParseError({
6787
+ code: codes_ErrorCode.JSON_PARSE_ERROR,
6788
+ filePath,
6789
+ content: raw,
6790
+ message: `Failed to parse skills.json: ${parseError.message}`,
6791
+ cause: parseError
6792
+ });
6793
+ }
6578
6794
  } catch (error) {
6579
6795
  if ('ENOENT' === error.code) return null;
6580
- throw error;
6796
+ if (error instanceof ParseError) throw error;
6797
+ throw convertNodeError(error, {
6798
+ operation: 'read',
6799
+ path: filePath
6800
+ });
6581
6801
  }
6582
6802
  }
6583
6803
  function parseSpecifier(specifier) {
6584
6804
  const firstHashIndex = specifier.indexOf('#');
6585
6805
  const secondHashIndex = firstHashIndex >= 0 ? specifier.indexOf('#', firstHashIndex + 1) : -1;
6586
- if (secondHashIndex >= 0) throw new Error('Invalid specifier: multiple # fragments are not supported');
6806
+ if (secondHashIndex >= 0) throw new ParseError({
6807
+ code: codes_ErrorCode.INVALID_SPECIFIER,
6808
+ message: 'Invalid specifier: multiple # fragments are not supported',
6809
+ content: specifier
6810
+ });
6587
6811
  const hashIndex = firstHashIndex;
6588
6812
  const sourcePart = hashIndex >= 0 ? specifier.slice(0, hashIndex) : specifier;
6589
6813
  const fragment = hashIndex >= 0 ? specifier.slice(hashIndex + 1) : '';
6590
- if (!sourcePart) throw new Error('Specifier source is required');
6814
+ if (!sourcePart) throw new ParseError({
6815
+ code: codes_ErrorCode.INVALID_SPECIFIER,
6816
+ message: 'Specifier source is required',
6817
+ content: specifier
6818
+ });
6591
6819
  if (!fragment) return {
6592
6820
  sourcePart,
6593
6821
  ref: null,
@@ -6610,7 +6838,18 @@ function parseSpecifier(specifier) {
6610
6838
  };
6611
6839
  }
6612
6840
  function normalizeSpecifier(specifier) {
6613
- const parsed = parseSpecifier(specifier);
6841
+ let parsed;
6842
+ try {
6843
+ parsed = parseSpecifier(specifier);
6844
+ } catch (error) {
6845
+ if (error instanceof ParseError) throw error;
6846
+ throw new ParseError({
6847
+ code: codes_ErrorCode.INVALID_SPECIFIER,
6848
+ message: `Invalid specifier: ${error.message}`,
6849
+ content: specifier,
6850
+ cause: error
6851
+ });
6852
+ }
6614
6853
  const type = parsed.sourcePart.startsWith('file:') ? 'file' : parsed.sourcePart.startsWith('npm:') ? 'npm' : 'git';
6615
6854
  const skillPath = parsed.path || '/';
6616
6855
  const skillName = node_path_0.posix.basename(skillPath);
@@ -6677,14 +6916,32 @@ async function resolveGitCommit(url, ref) {
6677
6916
  if (commit) return commit;
6678
6917
  const clonedCommit = await resolveGitCommitByClone(url, target);
6679
6918
  if (clonedCommit) return clonedCommit;
6680
- throw new Error(`Unable to resolve git ref ${target} for ${url}`);
6919
+ throw new GitError({
6920
+ code: codes_ErrorCode.GIT_REF_NOT_FOUND,
6921
+ operation: 'resolve-ref',
6922
+ repoUrl: url,
6923
+ ref: target,
6924
+ message: `Unable to resolve git ref "${target}" for ${url}`
6925
+ });
6681
6926
  }
6682
- async function resolveLockEntry(cwd, specifier) {
6683
- const normalized = normalizeSpecifier(specifier);
6927
+ async function resolveLockEntry(cwd, specifier, skillName) {
6928
+ let normalized;
6929
+ try {
6930
+ normalized = normalizeSpecifier(specifier);
6931
+ } catch (error) {
6932
+ if (error instanceof ParseError) throw error;
6933
+ throw new ParseError({
6934
+ code: codes_ErrorCode.INVALID_SPECIFIER,
6935
+ message: `Failed to parse specifier "${specifier}": ${error.message}`,
6936
+ content: specifier,
6937
+ cause: error
6938
+ });
6939
+ }
6940
+ const finalSkillName = skillName || normalized.skillName;
6684
6941
  if ('file' === normalized.type) {
6685
6942
  const sourceRoot = node_path_0.resolve(cwd, normalized.source.slice(5));
6686
6943
  return {
6687
- skillName: normalized.skillName,
6944
+ skillName: finalSkillName,
6688
6945
  entry: {
6689
6946
  specifier: normalized.normalized,
6690
6947
  resolution: {
@@ -6698,7 +6955,7 @@ async function resolveLockEntry(cwd, specifier) {
6698
6955
  if ('git' === normalized.type) {
6699
6956
  const commit = await resolveGitCommit(normalized.source, normalized.ref);
6700
6957
  return {
6701
- skillName: normalized.skillName,
6958
+ skillName: finalSkillName,
6702
6959
  entry: {
6703
6960
  specifier: normalized.normalized,
6704
6961
  resolution: {
@@ -6711,14 +6968,21 @@ async function resolveLockEntry(cwd, specifier) {
6711
6968
  }
6712
6969
  };
6713
6970
  }
6714
- throw new Error(`Unsupported specifier type in 0.1.0 core flow: ${normalized.type}`);
6971
+ throw new ParseError({
6972
+ code: codes_ErrorCode.INVALID_SPECIFIER,
6973
+ message: `Unsupported specifier type in 0.1.0 core flow: ${normalized.type}`,
6974
+ content: specifier
6975
+ });
6715
6976
  }
6716
- async function syncSkillsLock(cwd, manifest, existingLock) {
6717
- const nextSkills = {};
6718
- for (const specifier of Object.values(manifest.skills)){
6719
- const { skillName, entry } = await resolveLockEntry(cwd, specifier);
6720
- nextSkills[skillName] = entry;
6721
- }
6977
+ async function syncSkillsLock(cwd, manifest, _existingLock) {
6978
+ const entries = await Promise.all(Object.entries(manifest.skills).map(async ([skillName, specifier])=>{
6979
+ const { skillName: resolvedName, entry } = await resolveLockEntry(cwd, specifier, skillName);
6980
+ return [
6981
+ resolvedName,
6982
+ entry
6983
+ ];
6984
+ }));
6985
+ const nextSkills = Object.fromEntries(entries);
6722
6986
  return {
6723
6987
  lockfileVersion: '0.1',
6724
6988
  installDir: manifest.installDir ?? '.agents/skills',
@@ -6728,7 +6992,14 @@ async function syncSkillsLock(cwd, manifest, existingLock) {
6728
6992
  }
6729
6993
  async function writeSkillsLock(rootDir, lockfile) {
6730
6994
  const filePath = node_path_0.join(rootDir, 'skills-lock.yaml');
6731
- await promises_writeFile(filePath, dist.stringify(lockfile), 'utf8');
6995
+ try {
6996
+ await promises_writeFile(filePath, dist.stringify(lockfile), 'utf8');
6997
+ } catch (error) {
6998
+ throw convertNodeError(error, {
6999
+ operation: 'write',
7000
+ path: filePath
7001
+ });
7002
+ }
6732
7003
  }
6733
7004
  promisify(execFile);
6734
7005
  new Set([
@@ -6738,6 +7009,46 @@ new Set([
6738
7009
  'build',
6739
7010
  '__pycache__'
6740
7011
  ]);
7012
+ function parseForComparison(specifier) {
7013
+ const parsed = parseSpecifier(specifier);
7014
+ return {
7015
+ sourcePart: parsed.sourcePart,
7016
+ ref: parsed.ref,
7017
+ path: parsed.path || '/'
7018
+ };
7019
+ }
7020
+ function isSpecifierCompatible(manifestSpecifier, lockSpecifier) {
7021
+ const manifest = parseForComparison(manifestSpecifier);
7022
+ const lock = parseForComparison(lockSpecifier);
7023
+ if (manifest.sourcePart !== lock.sourcePart) return false;
7024
+ if (manifest.path !== lock.path) return false;
7025
+ if (null === manifest.ref) return true;
7026
+ return manifest.ref === lock.ref;
7027
+ }
7028
+ function normalizeInstallDir(dir) {
7029
+ return dir ?? '.agents/skills';
7030
+ }
7031
+ function normalizeLinkTargets(targets) {
7032
+ return targets ?? [];
7033
+ }
7034
+ function arraysEqual(a, b) {
7035
+ if (a.length !== b.length) return false;
7036
+ return a.every((val, i)=>val === b[i]);
7037
+ }
7038
+ function isLockInSync(manifest, lock) {
7039
+ if (!lock) return false;
7040
+ if (normalizeInstallDir(manifest.installDir) !== normalizeInstallDir(lock.installDir)) return false;
7041
+ if (!arraysEqual(normalizeLinkTargets(manifest.linkTargets), normalizeLinkTargets(lock.linkTargets))) return false;
7042
+ const manifestSkills = Object.entries(manifest.skills);
7043
+ const lockSkillNames = Object.keys(lock.skills);
7044
+ if (manifestSkills.length !== lockSkillNames.length) return false;
7045
+ for (const [name, specifier] of manifestSkills){
7046
+ const lockEntry = lock.skills[name];
7047
+ if (!lockEntry) return false;
7048
+ if (!isSpecifierCompatible(specifier, lockEntry.specifier)) return false;
7049
+ }
7050
+ return true;
7051
+ }
6741
7052
  async function ensureDir(dirPath) {
6742
7053
  await mkdir(dirPath, {
6743
7054
  recursive: true
@@ -6753,12 +7064,6 @@ async function replaceSymlink(target, linkPath) {
6753
7064
  async function writeJson(filePath, value) {
6754
7065
  await promises_writeFile(filePath, `${JSON.stringify(value, null, 2)}\n`, 'utf8');
6755
7066
  }
6756
- async function linkSkill(rootDir, installDir, linkTarget, skillName) {
6757
- const absoluteTarget = node_path_0.join(rootDir, installDir, skillName);
6758
- const absoluteLink = node_path_0.join(rootDir, linkTarget, skillName);
6759
- await ensureDir(node_path_0.dirname(absoluteLink));
6760
- await replaceSymlink(absoluteTarget, absoluteLink);
6761
- }
6762
7067
  async function readInstallState(rootDir) {
6763
7068
  const filePath = node_path_0.join(rootDir, '.agents/skills/.skills-pm-install-state.json');
6764
7069
  try {
@@ -6773,6 +7078,12 @@ async function writeInstallState(rootDir, value) {
6773
7078
  const filePath = node_path_0.join(dirPath, '.skills-pm-install-state.json');
6774
7079
  await writeJson(filePath, value);
6775
7080
  }
7081
+ async function linkSkill(rootDir, installDir, linkTarget, skillName) {
7082
+ const absoluteTarget = node_path_0.join(rootDir, installDir, skillName);
7083
+ const absoluteLink = node_path_0.join(rootDir, linkTarget, skillName);
7084
+ await ensureDir(node_path_0.dirname(absoluteLink));
7085
+ await replaceSymlink(absoluteTarget, absoluteLink);
7086
+ }
6776
7087
  async function materializeLocalSkill(rootDir, skillName, sourceRoot, sourcePath, installDir) {
6777
7088
  const relativeSkillPath = sourcePath.replace(/^\//, '');
6778
7089
  const absoluteSkillPath = node_path_0.join(sourceRoot, relativeSkillPath);
@@ -6798,14 +7109,24 @@ async function materializeLocalSkill(rootDir, skillName, sourceRoot, sourcePath,
6798
7109
  }
6799
7110
  const materializeGitSkill_execFileAsync = promisify(execFile);
6800
7111
  async function checkoutCommit(checkoutRoot, commit) {
6801
- await materializeGitSkill_execFileAsync('git', [
6802
- 'checkout',
6803
- commit
6804
- ], {
6805
- cwd: checkoutRoot
6806
- });
7112
+ try {
7113
+ await materializeGitSkill_execFileAsync('git', [
7114
+ 'checkout',
7115
+ commit
7116
+ ], {
7117
+ cwd: checkoutRoot
7118
+ });
7119
+ } catch (error) {
7120
+ throw new GitError({
7121
+ code: codes_ErrorCode.GIT_CHECKOUT_FAILED,
7122
+ operation: 'checkout',
7123
+ ref: commit,
7124
+ message: `Failed to checkout commit ${commit}`,
7125
+ cause: error
7126
+ });
7127
+ }
6807
7128
  }
6808
- async function fetchCommitFallback(checkoutRoot, commit) {
7129
+ async function fetchCommitFallback(checkoutRoot, commit, _repoUrl) {
6809
7130
  try {
6810
7131
  await materializeGitSkill_execFileAsync('git', [
6811
7132
  'fetch',
@@ -6848,18 +7169,33 @@ async function fetchCommitFallback(checkoutRoot, commit) {
6848
7169
  async function materializeGitSkill(rootDir, skillName, repoUrl, commit, sourcePath, installDir) {
6849
7170
  const checkoutRoot = await promises_mkdtemp(node_path_0.join(external_node_os_tmpdir(), 'skills-pm-git-checkout-'));
6850
7171
  try {
6851
- await materializeGitSkill_execFileAsync('git', [
6852
- 'clone',
6853
- '--depth',
6854
- '1',
6855
- repoUrl,
6856
- checkoutRoot
6857
- ]);
7172
+ try {
7173
+ await materializeGitSkill_execFileAsync('git', [
7174
+ 'clone',
7175
+ '--depth',
7176
+ '1',
7177
+ repoUrl,
7178
+ checkoutRoot
7179
+ ]);
7180
+ } catch (error) {
7181
+ throw new GitError({
7182
+ code: codes_ErrorCode.GIT_CLONE_FAILED,
7183
+ operation: 'clone',
7184
+ repoUrl,
7185
+ message: `Failed to clone repository ${repoUrl}`,
7186
+ cause: error
7187
+ });
7188
+ }
6858
7189
  if (commit && 'HEAD' !== commit) try {
6859
7190
  await checkoutCommit(checkoutRoot, commit);
6860
- } catch {
6861
- await fetchCommitFallback(checkoutRoot, commit);
6862
- await checkoutCommit(checkoutRoot, commit);
7191
+ } catch (checkoutError) {
7192
+ if (checkoutError instanceof GitError) try {
7193
+ await fetchCommitFallback(checkoutRoot, commit, repoUrl);
7194
+ await checkoutCommit(checkoutRoot, commit);
7195
+ } catch {
7196
+ throw checkoutError;
7197
+ }
7198
+ else throw checkoutError;
6863
7199
  }
6864
7200
  const skillDocPath = node_path_0.join(checkoutRoot, sourcePath.replace(/^\//, ''), 'SKILL.md');
6865
7201
  await promises_readFile(skillDocPath, 'utf8');
@@ -6960,28 +7296,41 @@ async function linkSkillsFromLock(rootDir, manifest, lockfile) {
6960
7296
  linked: Object.keys(lockfile.skills)
6961
7297
  };
6962
7298
  }
6963
- async function installSkills(rootDir) {
6964
- const manifest = await readSkillsManifest(rootDir);
6965
- if (!manifest) return {
6966
- status: 'skipped',
6967
- reason: 'manifest-missing'
6968
- };
6969
- const currentLock = await readSkillsLock(rootDir);
6970
- const lockfile = await syncSkillsLock(rootDir, manifest, currentLock);
6971
- await fetchSkillsFromLock(rootDir, manifest, lockfile);
6972
- await linkSkillsFromLock(rootDir, manifest, lockfile);
6973
- await writeSkillsLock(rootDir, lockfile);
7299
+ async function installCommand(options) {
7300
+ const manifest = await readSkillsManifest(options.cwd);
7301
+ if (!manifest) throw new ManifestError({
7302
+ code: codes_ErrorCode.MANIFEST_NOT_FOUND,
7303
+ filePath: `${options.cwd}/skills.json`,
7304
+ message: 'No skills.json found in the current directory. Run "spm init" to create one.'
7305
+ });
7306
+ const currentLock = await readSkillsLock(options.cwd);
7307
+ if (options.frozenLockfile) {
7308
+ if (!currentLock) throw new ManifestError({
7309
+ code: codes_ErrorCode.LOCKFILE_NOT_FOUND,
7310
+ filePath: `${options.cwd}/skills-lock.yaml`,
7311
+ message: 'Lockfile is required in frozen mode but none was found. Run "spm install" first.'
7312
+ });
7313
+ if (!isLockInSync(manifest, currentLock)) throw new ManifestError({
7314
+ code: codes_ErrorCode.LOCKFILE_OUTDATED,
7315
+ filePath: `${options.cwd}/skills-lock.yaml`,
7316
+ message: 'Lockfile is out of sync with manifest. Run install without --frozen-lockfile to update.'
7317
+ });
7318
+ await fetchSkillsFromLock(options.cwd, manifest, currentLock);
7319
+ await linkSkillsFromLock(options.cwd, manifest, currentLock);
7320
+ return {
7321
+ status: 'installed',
7322
+ installed: Object.keys(currentLock.skills)
7323
+ };
7324
+ }
7325
+ const lockfile = await syncSkillsLock(options.cwd, manifest, currentLock);
7326
+ await fetchSkillsFromLock(options.cwd, manifest, lockfile);
7327
+ await linkSkillsFromLock(options.cwd, manifest, lockfile);
7328
+ await writeSkillsLock(options.cwd, lockfile);
6974
7329
  return {
6975
7330
  status: 'installed',
6976
7331
  installed: Object.keys(lockfile.skills)
6977
7332
  };
6978
7333
  }
6979
- async function installCommand(options) {
6980
- return installSkills(options.cwd);
6981
- }
6982
- var package_namespaceObject = {
6983
- rE: "0.2.0"
6984
- };
6985
7334
  package_namespaceObject.rE;
6986
7335
  async function preResolution(options = {}) {
6987
7336
  const lockfileDir = options.lockfileDir;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pnpm-plugin-skills",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "type": "commonjs",
5
5
  "repository": {
6
6
  "type": "git",
@@ -13,7 +13,7 @@
13
13
  "pnpmfile.mjs"
14
14
  ],
15
15
  "dependencies": {
16
- "skills-package-manager": "0.2.0"
16
+ "skills-package-manager": "0.3.0"
17
17
  },
18
18
  "devDependencies": {
19
19
  "@rslib/core": "^0.20.0",
package/pnpmfile.mjs CHANGED
@@ -1,4 +1,4 @@
1
1
  // pnpm v11
2
- import { preResolution } from './dist/index.mjs';
2
+ import { preResolution } from './dist/index.mjs'
3
3
 
4
- export const hooks = { preResolution };
4
+ export const hooks = { preResolution }