pi-system-one 1.1.4 → 1.2.1

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.
@@ -0,0 +1,123 @@
1
+ // pi-system-one/tests/extension.test.ts
2
+
3
+ import assert from "node:assert/strict";
4
+ import { existsSync, readFileSync } from "node:fs";
5
+ import { join } from "node:path";
6
+ import { describe, it } from "node:test";
7
+ import piSystemOneExtension, {
8
+ systemOnePromptsDir,
9
+ systemOneSkillsDir,
10
+ } from "../src/extension.ts";
11
+
12
+ describe("extension resources", () => {
13
+ it("contributes a valid system-one skill directory", async () => {
14
+ const prev = process.env.SYSTEM_ONE_BASE_URL;
15
+ process.env.SYSTEM_ONE_BASE_URL = "http://localhost:8008";
16
+ try {
17
+ const handlers: Record<string, (event: never) => unknown> = {};
18
+ const pi = {
19
+ registerTool: () => {},
20
+ registerCommand: () => {},
21
+ on: (channel: string, handler: (event: never) => unknown) => {
22
+ handlers[channel] = handler;
23
+ },
24
+ };
25
+ piSystemOneExtension(pi as never);
26
+ const discover = handlers.resources_discover;
27
+ assert.ok(discover, "resources_discover handler registered");
28
+ const result = (await discover({} as never)) as {
29
+ skillPaths: string[];
30
+ };
31
+ assert.equal(result.skillPaths.length, 1);
32
+ assert.equal(result.skillPaths[0], systemOneSkillsDir());
33
+ const skillMd = join(result.skillPaths[0], "system-one", "SKILL.md");
34
+ assert.ok(existsSync(skillMd), `missing ${skillMd}`);
35
+ const raw = readFileSync(skillMd, "utf-8");
36
+ const frontmatter = raw.split("---")[1] ?? "";
37
+ // YAML forbids `: ` inside unquoted plain scalars (it starts a nested
38
+ // mapping — Pi rejects the skill). Every multi-word value must be
39
+ // quoted or use a block scalar.
40
+ for (const line of frontmatter.split("\n")) {
41
+ const trimmed = line.trim();
42
+ if (!trimmed || trimmed.startsWith("#")) continue;
43
+ const value = trimmed.replace(/^\w+:\s*/, "");
44
+ const quoted =
45
+ (value.startsWith('"') && value.endsWith('"')) ||
46
+ (value.startsWith("'") && value.endsWith("'")) ||
47
+ value.startsWith("|") ||
48
+ value.startsWith(">");
49
+ assert.ok(
50
+ quoted || !value.includes(": "),
51
+ `unquoted ": " in SKILL.md frontmatter: ${trimmed}`,
52
+ );
53
+ }
54
+ const stripQuotes = (s: string) =>
55
+ s.startsWith('"') && s.endsWith('"') ? s.slice(1, -1) : s;
56
+ const name = stripQuotes(
57
+ frontmatter.match(/name:\s*(.+)/)?.[1]?.trim() ?? "",
58
+ );
59
+ const description = stripQuotes(
60
+ frontmatter.match(/description:\s*(.+)/)?.[1]?.trim() ?? "",
61
+ );
62
+ // Pi's skill validation rules (docs/skills.md): name 1-64 chars,
63
+ // lowercase alphanumerics/hyphens, no leading/trailing or consecutive
64
+ // hyphens; description required, max 1024 chars.
65
+ assert.match(name, /^[a-z0-9]([a-z0-9-]{0,62}[a-z0-9])?$/);
66
+ assert.ok(name.length >= 1 && name.length <= 64);
67
+ assert.ok(!name.includes("--"));
68
+ assert.ok(description.length >= 1 && description.length <= 1024);
69
+ assert.match(description, /system_one|System One/);
70
+ assert.ok(
71
+ existsSync(
72
+ join(
73
+ result.skillPaths[0],
74
+ "system-one",
75
+ "references",
76
+ "use-cases.md",
77
+ ),
78
+ ),
79
+ "missing use-cases reference",
80
+ );
81
+ } finally {
82
+ if (prev === undefined) delete process.env.SYSTEM_ONE_BASE_URL;
83
+ else process.env.SYSTEM_ONE_BASE_URL = prev;
84
+ }
85
+ });
86
+
87
+ it("contributes a valid /judge prompt template", async () => {
88
+ const prev = process.env.SYSTEM_ONE_BASE_URL;
89
+ process.env.SYSTEM_ONE_BASE_URL = "http://localhost:8008";
90
+ try {
91
+ const handlers: Record<string, (event: never) => unknown> = {};
92
+ const pi = {
93
+ registerTool: () => {},
94
+ registerCommand: () => {},
95
+ on: (channel: string, handler: (event: never) => unknown) => {
96
+ handlers[channel] = handler;
97
+ },
98
+ };
99
+ piSystemOneExtension(pi as never);
100
+ const discover = handlers.resources_discover;
101
+ assert.ok(discover, "resources_discover handler registered");
102
+ const result = (await discover({} as never)) as {
103
+ promptPaths: string[];
104
+ };
105
+ assert.equal(result.promptPaths.length, 1);
106
+ assert.equal(result.promptPaths[0], systemOnePromptsDir());
107
+ const judgeMd = join(result.promptPaths[0], "judge.md");
108
+ assert.ok(existsSync(judgeMd), `missing ${judgeMd}`);
109
+ const content = readFileSync(judgeMd, "utf-8");
110
+ // Prompt templates take `$@` args and must name the tool explicitly:
111
+ // that explicit naming is what makes routing deterministic.
112
+ assert.match(content, /\$@/);
113
+ assert.match(content, /system_one/);
114
+ assert.match(content, /do not\s+answer it directly/i);
115
+ // noul answers carry no confidence field; the template must not ask
116
+ // the model to report one or it will invent numbers.
117
+ assert.match(content, /for noul the probability/i);
118
+ } finally {
119
+ if (prev === undefined) delete process.env.SYSTEM_ONE_BASE_URL;
120
+ else process.env.SYSTEM_ONE_BASE_URL = prev;
121
+ }
122
+ });
123
+ });
@@ -0,0 +1,62 @@
1
+ // pi-system-one/tests/packaging.test.ts
2
+ //
3
+ // The skill and /judge prompt are runtime behavior (served via
4
+ // resources_discover), not just docs. Assert the published tarball
5
+ // actually contains them by inspecting the real `npm pack` file list
6
+ // rather than the source checkout.
7
+
8
+ import assert from "node:assert/strict";
9
+ import { execFileSync } from "node:child_process";
10
+ import { readFileSync } from "node:fs";
11
+ import { join } from "node:path";
12
+ import { describe, it } from "node:test";
13
+ import { fileURLToPath } from "node:url";
14
+
15
+ const packageRoot = fileURLToPath(new URL("..", import.meta.url));
16
+
17
+ function packedFiles(): string[] {
18
+ const stdout = execFileSync("npm", ["pack", "--dry-run", "--json"], {
19
+ cwd: packageRoot,
20
+ encoding: "utf-8",
21
+ stdio: ["ignore", "pipe", "pipe"],
22
+ });
23
+ const parsed: unknown = JSON.parse(stdout);
24
+ const entry = Array.isArray(parsed) ? parsed[0] : undefined;
25
+ const files = (entry as { files?: Array<{ path?: string }> } | undefined)
26
+ ?.files;
27
+ assert.ok(
28
+ Array.isArray(files) && files.length > 0,
29
+ "npm pack --dry-run --json returned no files",
30
+ );
31
+ return (files as Array<{ path?: string }>)
32
+ .map((f) => f.path ?? "")
33
+ .filter((p) => p.length > 0);
34
+ }
35
+
36
+ describe("npm package runtime resources", () => {
37
+ it("declares the extension entry for both Pi and OMP", () => {
38
+ const packageJson = JSON.parse(
39
+ readFileSync(join(packageRoot, "package.json"), "utf-8"),
40
+ ) as {
41
+ pi?: { extensions?: string[] };
42
+ omp?: { extensions?: string[] };
43
+ };
44
+ assert.deepEqual(packageJson.pi, { extensions: ["./src/extension.ts"] });
45
+ assert.deepEqual(packageJson.omp, { extensions: ["./src/extension.ts"] });
46
+ });
47
+
48
+ it("packs the skill, its references, and the /judge prompt", () => {
49
+ const files = packedFiles();
50
+ for (const required of [
51
+ "skills/system-one/SKILL.md",
52
+ "skills/system-one/references/use-cases.md",
53
+ "prompts/judge.md",
54
+ "src/extension.ts",
55
+ ]) {
56
+ assert.ok(
57
+ files.includes(required),
58
+ `published package is missing runtime resource: ${required}`,
59
+ );
60
+ }
61
+ });
62
+ });