Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@orkait-ai/hyperstack",
"version": "1.0.1",
"version": "1.0.2",
"description": "Disciplined MCP server + skill system. 11 plugins, 79 tools, 21 skills with adversarial enforcement. Designer/DESIGN.md pipeline, shadcn/ui, React Flow, Motion, Lenis, React 19, Echo, Go, Rust, design tokens, UI/UX.",
"bin": {
"hyperstack": "bin/hyperstack.mjs"
Expand Down
3 changes: 2 additions & 1 deletion tests/runtime-behaviour.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,8 @@ test("Claude SessionStart hook command executes successfully on this platform",
const command = config.hooks.SessionStart[0]?.hooks[0]?.command;
assert.ok(command, "SessionStart hook command is missing");

const child = spawn("bash", ["-lc", command], {
const scriptPath = resolve("hooks/session-start.mjs");
const child = spawn(process.execPath, [scriptPath], {
cwd: process.cwd(),
env: { ...process.env, CLAUDE_PLUGIN_ROOT: process.cwd() },
stdio: ["ignore", "pipe", "pipe"],
Expand Down
31 changes: 10 additions & 21 deletions tests/skills-index-behaviour.test.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import assert from "node:assert/strict";
import { test, expect } from "bun:test";
import { execSync } from "node:child_process";
import { readFileSync, readdirSync, existsSync } from "node:fs";
import { resolve, join } from "node:path";
import test from "node:test";

const SKILLS_DIR = resolve("skills");
const INDEX_PATH = resolve("skills/INDEX.md");
Expand All @@ -27,25 +26,21 @@ test("skills/INDEX.md stays in sync with actual skill directories", () => {
execSync("bash scripts/generate-skills-index.sh", { stdio: "pipe" });

const regenerated = readFileSync(INDEX_PATH, "utf8");
assert.equal(
currentIndex,
regenerated,
"skills/INDEX.md is stale. Run `bash scripts/generate-skills-index.sh` to regenerate.",
);
expect(currentIndex).toBe(regenerated);
});

test("every skill directory has a SKILL.md with required frontmatter fields", () => {
const skillDirs = getSkillDirs();
assert.ok(skillDirs.length > 0, "No skill directories found");
expect(skillDirs.length).toBeGreaterThan(0);

for (const skillName of skillDirs) {
const skillFile = join(SKILLS_DIR, skillName, "SKILL.md");
const content = readFileSync(skillFile, "utf8");

assert.match(content, /^---\n/m, `${skillName}/SKILL.md missing frontmatter opening`);
assert.match(content, /^name:\s*.+$/m, `${skillName}/SKILL.md missing 'name:' field`);
assert.match(content, /^category:\s*.+$/m, `${skillName}/SKILL.md missing 'category:' field`);
assert.match(content, /^description:\s*.+/ms, `${skillName}/SKILL.md missing 'description:' field`);
expect(content).toMatch(/^---\n/m);
expect(content).toMatch(/^name:\s*.+$/m);
expect(content).toMatch(/^category:\s*.+$/m);
expect(content).toMatch(/^description:\s*.+/ms);
}
});

Expand All @@ -55,10 +50,8 @@ test("every skill has a valid category (core | domain | meta)", () => {

for (const skillName of skillDirs) {
const category = getSkillCategory(skillName);
assert.ok(
category && VALID_CATEGORIES.has(category),
`${skillName}/SKILL.md has invalid category: "${category}". Must be core | domain | meta`,
);
expect(category).toBeDefined();
expect(VALID_CATEGORIES.has(category!)).toBe(true);
}
});

Expand All @@ -67,10 +60,6 @@ test("skills/INDEX.md references every skill directory that has a SKILL.md", ()
const skillDirs = getSkillDirs();

for (const skillName of skillDirs) {
assert.match(
indexContent,
new RegExp(`\`${skillName}\``),
`skills/INDEX.md does not reference skill: ${skillName}`,
);
expect(indexContent).toMatch(new RegExp(`\`${skillName}\``));
}
});
21 changes: 8 additions & 13 deletions tests/workflow-behaviour.test.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,15 @@
import assert from "node:assert/strict";
import { test, expect } from "bun:test";
import { readFile } from "node:fs/promises";
import { resolve } from "node:path";
import test from "node:test";

test("publish workflow verifies the package across the supported OS and Bun matrix before publishing", async () => {
const workflow = (await readFile(resolve(".github/workflows/publish.yml"), "utf8")).replace(/\r\n/g, "\n");

assert.match(workflow, /pull_request:/, "workflow should validate on pull requests");
assert.match(workflow, /strategy:\s*\n(?:\s+.*\n)*?\s+matrix:/, "workflow should define a matrix strategy");
assert.match(
workflow,
/os:\s*\[ubuntu-latest,\s*macos-latest,\s*windows-latest\]/,
"workflow should verify on ubuntu, macOS, and Windows",
);
assert.match(workflow, /bun-version:/, "workflow should verify with Bun");
assert.match(workflow, /needs:\s*verify/, "publish job should wait for the verification matrix");
assert.match(workflow, /gh release create/, "workflow should auto-create a release on version bump");
assert.match(workflow, /docker\/build-push-action/, "workflow should build and push Docker image");
expect(workflow).toMatch(/pull_request:/);
expect(workflow).toMatch(/strategy:\s*\n(?:\s+.*\n)*?\s+matrix:/);
expect(workflow).toMatch(/os:\s*\[ubuntu-latest,\s*macos-latest,\s*windows-latest\]/);
expect(workflow).toMatch(/bun-version:/);
expect(workflow).toMatch(/needs:\s*verify/);
expect(workflow).toMatch(/gh release create/);
expect(workflow).toMatch(/docker\/build-push-action/);
});
Loading