happyskills 0.4.2 → 0.4.4

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/CHANGELOG.md CHANGED
@@ -7,6 +7,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/).
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.4.4] - 2026-03-05
11
+
12
+ ### Fixed
13
+ - Fix all commands (`install`, `list`, `setup`, `update`, `uninstall`, `check`, `bump`, `convert`) inheriting skills from a parent directory; `find_project_root()` now always uses the current working directory and never walks up the directory tree
14
+
15
+ ## [0.4.3] - 2026-03-05
16
+
17
+ ### Fixed
18
+ - Fix `find_project_root()` still treating the home directory as a project root via `~/skills-lock.json`; the home directory is now fully excluded from all project root detection checks
19
+
10
20
  ## [0.4.2] - 2026-03-05
11
21
 
12
22
  ### Fixed
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "happyskills",
3
- "version": "0.4.2",
3
+ "version": "0.4.4",
4
4
  "description": "Package manager for AI agent skills",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "author": "Nicolas Dao <nic@cloudlesslabs.com> (https://cloudlesslabs.com)",
@@ -34,29 +34,7 @@ const lock_file_path = (project_root = process.cwd()) => path.join(project_root,
34
34
 
35
35
  const skill_install_dir = (base_skills_dir, name) => path.join(base_skills_dir, name)
36
36
 
37
- const find_project_root = (start_dir = process.cwd()) => {
38
- let dir = path.resolve(start_dir)
39
- while (true) {
40
- const skills = path.join(dir, '.claude', 'skills')
41
- const lock = path.join(dir, 'skills-lock.json')
42
- // Skip if this would match the global skills dir (~/.claude/skills)
43
- // to avoid treating the home directory as a project root
44
- if (skills !== global_skills_dir()) {
45
- try {
46
- fs.statSync(skills)
47
- return dir
48
- } catch {}
49
- }
50
- try {
51
- fs.statSync(lock)
52
- return dir
53
- } catch {}
54
- const parent = path.dirname(dir)
55
- if (parent === dir) break
56
- dir = parent
57
- }
58
- return start_dir
59
- }
37
+ const find_project_root = (start_dir = process.cwd()) => path.resolve(start_dir)
60
38
 
61
39
  module.exports = {
62
40
  home_dir,
@@ -100,62 +100,47 @@ describe('paths', () => {
100
100
  })
101
101
 
102
102
  describe('find_project_root', () => {
103
- it('finds project root by .claude/skills dir', () => {
103
+ it('returns the given directory as-is', () => {
104
104
  const tmp = fs.realpathSync(fs.mkdtempSync(path.join(os.tmpdir(), 'hs-test-')))
105
105
  try {
106
- fs.mkdirSync(path.join(tmp, '.claude', 'skills'), { recursive: true })
107
- assert.strictEqual(paths.find_project_root(tmp), tmp)
108
- } finally {
109
- fs.rmSync(tmp, { recursive: true })
110
- }
111
- })
112
-
113
- it('finds project root by skills-lock.json', () => {
114
- const tmp = fs.realpathSync(fs.mkdtempSync(path.join(os.tmpdir(), 'hs-test-')))
115
- try {
116
- fs.writeFileSync(path.join(tmp, 'skills-lock.json'), '{}')
117
106
  assert.strictEqual(paths.find_project_root(tmp), tmp)
118
107
  } finally {
119
108
  fs.rmSync(tmp, { recursive: true })
120
109
  }
121
110
  })
122
111
 
123
- it('walks up to find project root from nested dir', () => {
112
+ it('does not walk up to a parent with .claude/skills', () => {
124
113
  const tmp = fs.realpathSync(fs.mkdtempSync(path.join(os.tmpdir(), 'hs-test-')))
125
114
  try {
126
115
  fs.mkdirSync(path.join(tmp, '.claude', 'skills'), { recursive: true })
127
- const sub = path.join(tmp, 'src', 'deep')
116
+ const sub = path.join(tmp, 'sub')
128
117
  fs.mkdirSync(sub, { recursive: true })
129
- assert.strictEqual(paths.find_project_root(sub), tmp)
118
+ // should return sub, not tmp
119
+ assert.strictEqual(paths.find_project_root(sub), sub)
130
120
  } finally {
131
121
  fs.rmSync(tmp, { recursive: true })
132
122
  }
133
123
  })
134
124
 
135
- it('falls back to start_dir when no project markers found', () => {
125
+ it('does not walk up to a parent with skills-lock.json', () => {
136
126
  const tmp = fs.realpathSync(fs.mkdtempSync(path.join(os.tmpdir(), 'hs-test-')))
137
127
  try {
128
+ fs.writeFileSync(path.join(tmp, 'skills-lock.json'), '{}')
138
129
  const sub = path.join(tmp, 'sub')
139
130
  fs.mkdirSync(sub, { recursive: true })
131
+ // should return sub, not tmp
140
132
  assert.strictEqual(paths.find_project_root(sub), sub)
141
133
  } finally {
142
134
  fs.rmSync(tmp, { recursive: true })
143
135
  }
144
136
  })
145
137
 
146
- it('does not treat ~/.claude/skills as a project marker', () => {
147
- const global_skills = path.join(os.homedir(), '.claude', 'skills')
148
- let global_exists = false
149
- try { fs.statSync(global_skills); global_exists = true } catch {}
150
- if (!global_exists) return // skip if global dir not present on this machine
138
+ it('returns home dir only when explicitly called with it', () => {
139
+ assert.strictEqual(paths.find_project_root(os.homedir()), os.homedir())
140
+ })
151
141
 
152
- const tmp = fs.realpathSync(fs.mkdtempSync(path.join(os.tmpdir(), 'hs-test-')))
153
- try {
154
- // tmp has no project markers — should fall back to tmp, not home dir
155
- assert.strictEqual(paths.find_project_root(tmp), tmp)
156
- } finally {
157
- fs.rmSync(tmp, { recursive: true })
158
- }
142
+ it('defaults to cwd when no argument given', () => {
143
+ assert.strictEqual(paths.find_project_root(), path.resolve(process.cwd()))
159
144
  })
160
145
  })
161
146
  })