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
Expand Up @@ -23,7 +23,7 @@
"install-skills": "node scripts/install.mjs",
"optimize-context": "node scripts/optimize-context.mjs",
"reconcile-context": "node scripts/reconcile-context.mjs",
"test": "node scripts/validate.mjs && node --test scripts/install.test.mjs scripts/context.test.mjs scripts/audit-context.test.mjs scripts/worktree-audit.test.mjs scripts/sync-upstream.test.mjs scripts/runtime.test.mjs scripts/environment.test.mjs"
"test": "node scripts/validate.mjs && node --test scripts/install.test.mjs scripts/context.test.mjs scripts/audit-context.test.mjs scripts/worktree-audit.test.mjs scripts/sync-upstream.test.mjs scripts/runtime.test.mjs scripts/environment.test.mjs scripts/skill-integrity.test.mjs"
},
"bin": {
"mstack": "scripts/install.mjs"
Expand Down
7 changes: 4 additions & 3 deletions scripts/check-upstream.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ if (args.includes("--help")) {
}

const targetRoot = resolve(valueAfter("--target") ?? repoRoot);
const strict = args.includes("--strict");
const syncLock = acquireUpstreamSyncLock(targetRoot, { recoverStale: false });
const releaseReadLockAtExit = () => {
try {
Expand All @@ -46,8 +47,9 @@ if (!pstack || typeof pstack !== "object") throw new Error("profiles/upstreams.j

const source = valueAfter("--source") ?? process.env.MSTACK_PSTACK_SOURCE;
if (!source) {
console.log("Pass --source <pstack checkout> or set MSTACK_PSTACK_SOURCE to check the upstream inventory.");
process.exit(0);
const message = "Pass --source <pstack checkout> or set MSTACK_PSTACK_SOURCE to check the upstream inventory.";
(strict ? console.error : console.log)(message);
process.exit(strict ? 1 : 0);
}

const sourceRoot = resolve(source);
Expand Down Expand Up @@ -102,7 +104,6 @@ function resolveInside(root, path, label) {

const sourceSkills = resolveInside(sourceRoot, "skills", "upstream skills directory");
if (!existsSync(sourceSkills)) throw new Error(`Upstream skills directory not found: ${sourceSkills}`);
const strict = args.includes("--strict");

function gitOutput(arguments_) {
try {
Expand Down
70 changes: 70 additions & 0 deletions scripts/skill-integrity.test.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import assert from "node:assert/strict";
import { existsSync, readFileSync, readdirSync } from "node:fs";
import { dirname, join, resolve } from "node:path";
import { fileURLToPath } from "node:url";
import test from "node:test";

const repoRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..");
const skillsRoot = join(repoRoot, "skills");

function readSkill(name) {
return readFileSync(join(skillsRoot, name, "SKILL.md"), "utf8");
}

function walkMarkdownFiles(directory) {
return readdirSync(directory, { withFileTypes: true }).flatMap((entry) => {
const path = join(directory, entry.name);
if (entry.isDirectory()) return walkMarkdownFiles(path);
return entry.isFile() && path.endsWith(".md") ? [path] : [];
});
}

test("meta-mode retains the workflow contract after portability adaptation", () => {
const content = readSkill("meta-mode");
for (const heading of [
"## Non-negotiables",
"## Principles",
"## Autonomy",
"## Subagents",
"## Writing the reply",
"## Comments",
"## Playbooks",
]) {
assert.match(content, new RegExp(`^${heading}$`, "m"));
}
assert.doesNotMatch(content, /(?:poteto|Cursor|AskQuestion|subagent_type|run_in_background|disable-model-invocation|prethe current)/i);
});

test("meta-agent points to an existing complete entry skill", () => {
const agent = readFileSync(join(repoRoot, "agents", "meta-agent.md"), "utf8");
assert.match(agent, /`meta-mode` skill's `SKILL\.md`/);
assert.match(readSkill("meta-mode"), /^## Principles$/m);
});

test("portable skill files contain no broken replacement artifacts", () => {
const forbidden = [
/prethe current harness/i,
/(?:poteto-mode|setup-pstack|poteto-agent)/i,
/worker type:\s*generalPurpose/i,
/cursor/i,
/application support\/the current harness/i,
];
const skillDirectories = readdirSync(skillsRoot, { withFileTypes: true })
.filter((entry) => entry.isDirectory())
.map((entry) => join(skillsRoot, entry.name));
const skillFiles = skillDirectories.map((directory) => join(directory, "SKILL.md"));
assert.equal(
skillFiles.filter((path) => existsSync(path)).length,
skillDirectories.length,
"every skill directory must contain SKILL.md",
);
const files = skillDirectories.flatMap(walkMarkdownFiles);
assert.ok(files.length >= skillFiles.length, "integrity scan must include every skill markdown file");
for (const path of files) {
const content = readFileSync(path, "utf8");
const normalized = content.replaceAll("`", "").replace(/\s+/g, " ");
for (const pattern of forbidden) {
assert.doesNotMatch(normalized, pattern, `${path} contains ${pattern}`);
}
}
});
6 changes: 6 additions & 0 deletions scripts/sync-upstream.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,12 @@ import test from "node:test";
const synchronizer = resolve("scripts", "sync-upstream.mjs");
const checker = resolve("scripts", "check-upstream.mjs");

test("strict upstream checks require a source checkout", () => {
const checked = run(checker, ["--strict"]);
assert.notEqual(checked.status, 0);
assert.match(checked.stderr, /Pass --source <pstack checkout>/);
});

function write(path, content) {
mkdirSync(resolve(path, ".."), { recursive: true });
writeFileSync(path, content, "utf8");
Expand Down
6 changes: 3 additions & 3 deletions skills/how/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ When in doubt, take the simple path.

Decompose the question into 2 to 4 exploration angles, each a distinct slice of the subsystem. Spawn all explorers in a single message:

- `worker type`: `generalPurpose`
- `worker role`: `explorer`
- `model`: the active mstack `explorer` role, defaulting to `inherit-parent`
- `readonly`: `true`

Expand All @@ -30,7 +30,7 @@ Each explorer gets the prompt in `references/explorer-prompt.md` with its angle

Spawn one Task subagent that explores and explains in one pass:

- `worker type`: `generalPurpose`
- `worker role`: `synthesizer`
- `model`: the active mstack `synthesizer` role, defaulting to `inherit-parent`
- `readonly`: `true`

Expand All @@ -40,7 +40,7 @@ Build its prompt from `references/explainer-prompt.md` without the explorer-find

Once all explorers have returned, spawn one Task subagent to synthesize their findings into one explanation:

- `worker type`: `generalPurpose`
- `worker role`: `synthesizer`
- `model`: the active mstack `synthesizer` role, defaulting to `inherit-parent`
- `readonly`: `true`

Expand Down
2 changes: 1 addition & 1 deletion skills/interrogate/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ Launch all reviewers in a single message using the delegation tool. Use the conf
| Reviewer | mstack `reviewer` role |

For each reviewer:
- `worker type`: `generalPurpose`
- `worker role`: `reviewer`
- `model`: the configured mstack `reviewer` role, or `inherit-parent` when no override exists
- `readonly`: `true`

Expand Down
Loading