gentle-pi 0.3.9 → 0.3.10
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.
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
|
-
import { watch } from "node:fs";
|
|
2
|
+
import { existsSync, watch } from "node:fs";
|
|
3
3
|
import {
|
|
4
4
|
access,
|
|
5
5
|
mkdir,
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
} from "node:fs/promises";
|
|
12
12
|
import { homedir } from "node:os";
|
|
13
13
|
import { basename, join, normalize, relative, sep } from "node:path";
|
|
14
|
+
import { fileURLToPath } from "node:url";
|
|
14
15
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
15
16
|
|
|
16
17
|
const REGISTRY_REL_PATH = ".atl/skill-registry.md";
|
|
@@ -26,6 +27,13 @@ const NO_SKILL_REGISTRY_ENV = "GENTLE_PI_NO_SKILL_REGISTRY";
|
|
|
26
27
|
const LEGACY_PROJECT_REGISTRY_REL_PATH = ".pi/extensions/skill-registry.ts";
|
|
27
28
|
const LEGACY_PROJECT_REGISTRY_DISABLED_REL_PATH =
|
|
28
29
|
".pi/extensions/skill-registry.ts.disabled";
|
|
30
|
+
const SKILL_REGISTRY_EXTENSION_SOURCE_KEY =
|
|
31
|
+
"__gentlePiSkillRegistryExtensionSource";
|
|
32
|
+
|
|
33
|
+
interface SkillRegistryExtensionGlobal {
|
|
34
|
+
[SKILL_REGISTRY_EXTENSION_SOURCE_KEY]?: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
29
37
|
async function pathExists(path: string): Promise<boolean> {
|
|
30
38
|
try {
|
|
31
39
|
await access(path);
|
|
@@ -415,6 +423,40 @@ function shouldSkipSkillRegistryStartup(
|
|
|
415
423
|
);
|
|
416
424
|
}
|
|
417
425
|
|
|
426
|
+
function normalizeExtensionSource(source: string): string {
|
|
427
|
+
return source.split(/[?#]/, 1)[0];
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
function extensionSourcePath(source: string): string | undefined {
|
|
431
|
+
const cleanSource = normalizeExtensionSource(source);
|
|
432
|
+
if (!cleanSource.startsWith("file:")) return undefined;
|
|
433
|
+
try {
|
|
434
|
+
return comparablePath(fileURLToPath(cleanSource));
|
|
435
|
+
} catch {
|
|
436
|
+
return undefined;
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
function shouldSkipDuplicateExtensionLoad(
|
|
441
|
+
source = import.meta.url,
|
|
442
|
+
cwd = process.cwd(),
|
|
443
|
+
state = globalThis as typeof globalThis & SkillRegistryExtensionGlobal,
|
|
444
|
+
): boolean {
|
|
445
|
+
const currentPath = extensionSourcePath(source);
|
|
446
|
+
const projectLocalPath = comparablePath(join(cwd, "extensions", "skill-registry.ts"));
|
|
447
|
+
if (currentPath && currentPath !== projectLocalPath && existsSync(projectLocalPath)) {
|
|
448
|
+
return true;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
const currentSource = currentPath ?? normalizeExtensionSource(source);
|
|
452
|
+
const existingSource = state[SKILL_REGISTRY_EXTENSION_SOURCE_KEY];
|
|
453
|
+
if (!existingSource) {
|
|
454
|
+
state[SKILL_REGISTRY_EXTENSION_SOURCE_KEY] = currentSource;
|
|
455
|
+
return false;
|
|
456
|
+
}
|
|
457
|
+
return existingSource !== currentSource;
|
|
458
|
+
}
|
|
459
|
+
|
|
418
460
|
async function startSkillRegistryWatcher(
|
|
419
461
|
cwd: string,
|
|
420
462
|
notify: (message: string) => void,
|
|
@@ -460,9 +502,12 @@ export const __testing = {
|
|
|
460
502
|
parseFrontmatter,
|
|
461
503
|
renderRegistry,
|
|
462
504
|
shouldSkipSkillRegistryStartup,
|
|
505
|
+
shouldSkipDuplicateExtensionLoad,
|
|
463
506
|
};
|
|
464
507
|
|
|
465
508
|
export default function (pi: ExtensionAPI) {
|
|
509
|
+
if (shouldSkipDuplicateExtensionLoad()) return;
|
|
510
|
+
|
|
466
511
|
pi.registerFlag(NO_SKILL_REGISTRY_FLAG, {
|
|
467
512
|
description: "Skip the Gentle AI skill registry refresh and watcher on startup.",
|
|
468
513
|
type: "boolean",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gentle-pi",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.10",
|
|
4
4
|
"description": "Turn Pi into el Gentleman: a senior-architect development harness with SDD/OpenSpec, subagents, strict TDD evidence, review guardrails, and skill discovery.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -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", () => {
|
|
@@ -115,6 +116,43 @@ test("startup skip honors no skill registry controls", () => {
|
|
|
115
116
|
assert.equal(__testing.shouldSkipSkillRegistryStartup(disabled, [], {}), false);
|
|
116
117
|
});
|
|
117
118
|
|
|
119
|
+
test("duplicate extension load is skipped only across different sources", () => {
|
|
120
|
+
const state = {};
|
|
121
|
+
|
|
122
|
+
assert.equal(
|
|
123
|
+
__testing.shouldSkipDuplicateExtensionLoad("file:///repo/extensions/skill-registry.ts?first", "/workspace", state),
|
|
124
|
+
false,
|
|
125
|
+
);
|
|
126
|
+
assert.equal(
|
|
127
|
+
__testing.shouldSkipDuplicateExtensionLoad("file:///repo/extensions/skill-registry.ts?second", "/workspace", state),
|
|
128
|
+
false,
|
|
129
|
+
);
|
|
130
|
+
assert.equal(
|
|
131
|
+
__testing.shouldSkipDuplicateExtensionLoad("file:///home/.pi/node_modules/gentle-pi/extensions/skill-registry.ts", "/workspace", state),
|
|
132
|
+
true,
|
|
133
|
+
);
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
test("project-local skill registry extension wins over installed package copy", () => {
|
|
137
|
+
const cwd = join(tmpdir(), `gentle-pi-local-extension-${Date.now()}`);
|
|
138
|
+
const localExtension = join(cwd, "extensions", "skill-registry.ts");
|
|
139
|
+
mkdirSync(dirname(localExtension), { recursive: true });
|
|
140
|
+
writeFileSync(localExtension, "");
|
|
141
|
+
|
|
142
|
+
assert.equal(
|
|
143
|
+
__testing.shouldSkipDuplicateExtensionLoad(
|
|
144
|
+
"file:///home/.pi/agent/npm/node_modules/gentle-pi/extensions/skill-registry.ts",
|
|
145
|
+
cwd,
|
|
146
|
+
{},
|
|
147
|
+
),
|
|
148
|
+
true,
|
|
149
|
+
);
|
|
150
|
+
assert.equal(
|
|
151
|
+
__testing.shouldSkipDuplicateExtensionLoad(pathToFileURL(localExtension).href, cwd, {}),
|
|
152
|
+
false,
|
|
153
|
+
);
|
|
154
|
+
});
|
|
155
|
+
|
|
118
156
|
test("scope and markdown cells are represented in registry", () => {
|
|
119
157
|
const cwd = join(tmpdir(), `gentle-pi-scope-${Date.now()}`);
|
|
120
158
|
const projectPath = join(cwd, "skills", "docs", "SKILL.md");
|