gentle-pi 0.3.9 → 0.4.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.
@@ -3,6 +3,7 @@ import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
3
3
  import { tmpdir } from "node:os";
4
4
  import { dirname, join } from "node:path";
5
5
  import test from "node:test";
6
+ import { pathToFileURL } from "node:url";
6
7
  import { __testing } from "../extensions/skill-registry.ts";
7
8
 
8
9
  test("project skill dirs include supported workspace roots", () => {
@@ -13,6 +14,7 @@ test("project skill dirs include supported workspace roots", () => {
13
14
  ".opencode/skills",
14
15
  ".claude/skills",
15
16
  ".gemini/skills",
17
+ ".trae/skills",
16
18
  ".cursor/skills",
17
19
  ".github/skills",
18
20
  ".codex/skills",
@@ -49,6 +51,17 @@ test("registry renders indexed skill paths instead of compact rules", () => {
49
51
  assert.doesNotMatch(registry, /Rules:/);
50
52
  });
51
53
 
54
+ test("frontmatter parser accepts CRLF line endings", () => {
55
+ const parsed = __testing.parseFrontmatter("---\r\nname: windows-skill\r\ndescription: >\r\n Trigger: Windows-authored skills.\r\n Preserve frontmatter metadata.\r\n---\r\n\r\n## Body\r\n");
56
+
57
+ assert.equal(parsed.name, "windows-skill");
58
+ assert.equal(
59
+ parsed.description,
60
+ "Trigger: Windows-authored skills. Preserve frontmatter metadata.",
61
+ );
62
+ assert.match(parsed.body, /## Body/);
63
+ });
64
+
52
65
  test("frontmatter parser keeps full multiline descriptions", () => {
53
66
  const parsed = __testing.parseFrontmatter(`---
54
67
  name: ai-sdk-5
@@ -101,6 +114,26 @@ test("uniqueExistingDirs normalizes duplicates and ignores missing roots", async
101
114
  );
102
115
  });
103
116
 
117
+ test("skill registry watchers close on shutdown", async () => {
118
+ const root = join(tmpdir(), `gentle-pi-watchers-${Date.now()}`);
119
+ const skillPath = join(root, "skills", "docs", "SKILL.md");
120
+ mkdirSync(dirname(skillPath), { recursive: true });
121
+ writeFileSync(skillPath, "---\nname: docs\ndescription: Docs.\n---\n");
122
+
123
+ await __testing.startSkillRegistryWatcher(root, () => undefined);
124
+ const attempted = __testing.activeWatcherCount();
125
+ __testing.closeSkillRegistryWatchers();
126
+ assert.equal(__testing.activeWatcherCount(), 0);
127
+
128
+ await __testing.startSkillRegistryWatcher(root, () => undefined);
129
+ assert.equal(
130
+ __testing.activeWatcherCount(),
131
+ attempted,
132
+ "shutdown must clear watched cwd state so a later session can re-watch",
133
+ );
134
+ __testing.closeSkillRegistryWatchers();
135
+ });
136
+
104
137
  test("startup skip honors no skill registry controls", () => {
105
138
  const enabled = { getFlag: () => true };
106
139
  const disabled = { getFlag: () => false };
@@ -115,6 +148,43 @@ test("startup skip honors no skill registry controls", () => {
115
148
  assert.equal(__testing.shouldSkipSkillRegistryStartup(disabled, [], {}), false);
116
149
  });
117
150
 
151
+ test("duplicate extension load is skipped only across different sources", () => {
152
+ const state = {};
153
+
154
+ assert.equal(
155
+ __testing.shouldSkipDuplicateExtensionLoad("file:///repo/extensions/skill-registry.ts?first", "/workspace", state),
156
+ false,
157
+ );
158
+ assert.equal(
159
+ __testing.shouldSkipDuplicateExtensionLoad("file:///repo/extensions/skill-registry.ts?second", "/workspace", state),
160
+ false,
161
+ );
162
+ assert.equal(
163
+ __testing.shouldSkipDuplicateExtensionLoad("file:///home/.pi/node_modules/gentle-pi/extensions/skill-registry.ts", "/workspace", state),
164
+ true,
165
+ );
166
+ });
167
+
168
+ test("project-local skill registry extension wins over installed package copy", () => {
169
+ const cwd = join(tmpdir(), `gentle-pi-local-extension-${Date.now()}`);
170
+ const localExtension = join(cwd, "extensions", "skill-registry.ts");
171
+ mkdirSync(dirname(localExtension), { recursive: true });
172
+ writeFileSync(localExtension, "");
173
+
174
+ assert.equal(
175
+ __testing.shouldSkipDuplicateExtensionLoad(
176
+ "file:///home/.pi/agent/npm/node_modules/gentle-pi/extensions/skill-registry.ts",
177
+ cwd,
178
+ {},
179
+ ),
180
+ true,
181
+ );
182
+ assert.equal(
183
+ __testing.shouldSkipDuplicateExtensionLoad(pathToFileURL(localExtension).href, cwd, {}),
184
+ false,
185
+ );
186
+ });
187
+
118
188
  test("scope and markdown cells are represented in registry", () => {
119
189
  const cwd = join(tmpdir(), `gentle-pi-scope-${Date.now()}`);
120
190
  const projectPath = join(cwd, "skills", "docs", "SKILL.md");