feat: package Pi extensions as Bun workspaces
This commit is contained in:
@@ -0,0 +1,699 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { mkdir, mkdtemp, rm, symlink, writeFile } from "node:fs/promises";
|
||||
import { tmpdir } from "node:os";
|
||||
import * as path from "node:path";
|
||||
import test from "node:test";
|
||||
import type { AgentProfile, WorkflowFacts } from "./domain.ts";
|
||||
import {
|
||||
eligibleProfiles,
|
||||
loadAgentProfiles,
|
||||
selectInitialDiscoveryProfile,
|
||||
} from "./profiles.ts";
|
||||
|
||||
async function withTempDir(run: (directory: string) => Promise<void>) {
|
||||
const directory = await mkdtemp(path.join(tmpdir(), "workflow-profiles-"));
|
||||
try {
|
||||
await run(directory);
|
||||
} finally {
|
||||
await rm(directory, { recursive: true, force: true });
|
||||
}
|
||||
}
|
||||
|
||||
function profileDocument(options: {
|
||||
name: string;
|
||||
description?: string;
|
||||
roles?: string;
|
||||
selection?: string;
|
||||
extra?: string;
|
||||
body?: string;
|
||||
}) {
|
||||
return `---\nname: ${options.name}\ndescription: ${options.description ?? options.name}\nroles: ${options.roles ?? "[discover]"}\n${options.extra ?? ""}selection:\n${options.selection ?? " stages: [discover]\n anyTags: []\n allTags: []\n pathGlobs: []\n keywords: []\n"}---\n\n${options.body ?? "Follow the assignment."}\n`;
|
||||
}
|
||||
|
||||
function standardAgentDocument(options: {
|
||||
name: string;
|
||||
description?: string;
|
||||
model?: string;
|
||||
tools?: string;
|
||||
thinking?: string;
|
||||
body?: string;
|
||||
}) {
|
||||
return `---\nname: ${options.name}\ndescription: ${options.description ?? options.name}\n${options.model ? `model: ${options.model}\n` : ""}${options.tools ? `tools: ${options.tools}\n` : ""}${options.thinking ? `thinking: ${options.thinking}\n` : ""}subagent_agents: scout, researcher\nsystem-prompt: append\nauto-exit: true\n---\n\n${options.body ?? "Follow the standard assignment."}\n`;
|
||||
}
|
||||
|
||||
async function writeProfile(
|
||||
root: string,
|
||||
file: string,
|
||||
options: Parameters<typeof profileDocument>[0],
|
||||
) {
|
||||
await mkdir(root, { recursive: true });
|
||||
await writeFile(path.join(root, file), profileDocument(options));
|
||||
}
|
||||
|
||||
async function writeStandardAgent(
|
||||
root: string,
|
||||
file: string,
|
||||
options: Parameters<typeof standardAgentDocument>[0],
|
||||
) {
|
||||
await mkdir(root, { recursive: true });
|
||||
await writeFile(path.join(root, file), standardAgentDocument(options));
|
||||
}
|
||||
|
||||
test("bundled profiles form a complete deterministic catalog", async () => {
|
||||
await withTempDir(async (directory) => {
|
||||
const catalog = await loadAgentProfiles({
|
||||
cwd: directory,
|
||||
agentDir: path.join(directory, "agent"),
|
||||
projectTrusted: false,
|
||||
});
|
||||
assert.deepEqual(
|
||||
catalog.profiles.map((profile) => profile.name),
|
||||
[
|
||||
"feature-designer",
|
||||
"feature-implementer",
|
||||
"feature-verifier",
|
||||
"general-discovery",
|
||||
"independent-reviewer",
|
||||
"ui-prototyper",
|
||||
],
|
||||
);
|
||||
assert.equal(catalog.diagnostics.length, 0);
|
||||
assert.ok(
|
||||
catalog.profiles.every((profile) => /^[a-f0-9]{64}$/.test(profile.digest)),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
test("standard package, global, and trusted project agents use role overlays and precedence", async () => {
|
||||
await withTempDir(async (directory) => {
|
||||
const bundled = path.join(directory, "bundled");
|
||||
const packageRoot = path.join(directory, "package-agents");
|
||||
const agentDir = path.join(directory, "agent");
|
||||
const globalRoot = path.join(agentDir, "agents");
|
||||
const projectRoot = path.join(directory, ".pi", "agents");
|
||||
await writeProfile(bundled, "general.md", {
|
||||
name: "general-discovery",
|
||||
});
|
||||
await writeStandardAgent(packageRoot, "scout.md", {
|
||||
name: "scout",
|
||||
model: "openai-codex/gpt-test",
|
||||
tools: "read, grep, find, ls",
|
||||
thinking: "high",
|
||||
body: "Package scout.",
|
||||
});
|
||||
await writeStandardAgent(packageRoot, "worker.md", {
|
||||
name: "worker",
|
||||
tools: "read, write, edit, bash",
|
||||
body: "Package worker.",
|
||||
});
|
||||
await writeStandardAgent(globalRoot, "scout.md", {
|
||||
name: "scout",
|
||||
body: "Global scout.",
|
||||
});
|
||||
await writeStandardAgent(globalRoot, "unmapped.md", {
|
||||
name: "unmapped",
|
||||
});
|
||||
await writeStandardAgent(projectRoot, "worker.md", {
|
||||
name: "worker",
|
||||
body: "Project worker.",
|
||||
});
|
||||
|
||||
const catalog = await loadAgentProfiles({
|
||||
cwd: directory,
|
||||
agentDir,
|
||||
bundledRoot: bundled,
|
||||
standardPackageRoot: packageRoot,
|
||||
projectRoot,
|
||||
projectTrusted: true,
|
||||
standardAgents: {
|
||||
enabled: true,
|
||||
roles: { scout: ["discover"], worker: ["implement"] },
|
||||
},
|
||||
});
|
||||
|
||||
const scout = catalog.profiles.find((profile) => profile.name === "scout");
|
||||
assert.equal(scout?.source, "standard-global");
|
||||
assert.equal(scout?.instructions, "Global scout.");
|
||||
assert.deepEqual(scout?.roles, ["discover"]);
|
||||
assert.deepEqual(scout?.selection.stages, ["discover"]);
|
||||
|
||||
const worker = catalog.profiles.find((profile) => profile.name === "worker");
|
||||
assert.equal(worker?.source, "standard-project");
|
||||
assert.equal(worker?.instructions, "Project worker.");
|
||||
assert.deepEqual(worker?.roles, ["implement"]);
|
||||
assert.equal(catalog.profiles.some((profile) => profile.name === "unmapped"), false);
|
||||
});
|
||||
});
|
||||
|
||||
test("package and global standard definitions cannot self-declare workflow roles", async () => {
|
||||
await withTempDir(async (directory) => {
|
||||
const bundled = path.join(directory, "bundled");
|
||||
const packageRoot = path.join(directory, "package-agents");
|
||||
await writeProfile(bundled, "general.md", { name: "general-discovery" });
|
||||
await writeProfile(packageRoot, "worker.md", {
|
||||
name: "worker",
|
||||
roles: "[review]",
|
||||
selection:
|
||||
" stages: [review]\n anyTags: []\n allTags: []\n pathGlobs: []\n keywords: []\n",
|
||||
body: "Attempted self-declared reviewer.",
|
||||
});
|
||||
await writeProfile(packageRoot, "unmapped.md", {
|
||||
name: "unmapped",
|
||||
roles: "[review]",
|
||||
selection:
|
||||
" stages: [review]\n anyTags: []\n allTags: []\n pathGlobs: []\n keywords: []\n",
|
||||
});
|
||||
|
||||
const catalog = await loadAgentProfiles({
|
||||
cwd: directory,
|
||||
agentDir: path.join(directory, "agent"),
|
||||
bundledRoot: bundled,
|
||||
standardPackageRoot: packageRoot,
|
||||
projectTrusted: false,
|
||||
standardAgents: {
|
||||
enabled: true,
|
||||
roles: { worker: ["implement"] },
|
||||
},
|
||||
});
|
||||
const worker = catalog.profiles.find((profile) => profile.name === "worker");
|
||||
assert.equal(worker?.source, "standard-package");
|
||||
assert.deepEqual(worker?.roles, ["implement"]);
|
||||
assert.deepEqual(worker?.selection.stages, ["implement"]);
|
||||
assert.equal(catalog.profiles.some((profile) => profile.name === "unmapped"), false);
|
||||
});
|
||||
});
|
||||
|
||||
test("a trusted workflow profile keeps its declared roles when its name also has a standard overlay", async () => {
|
||||
await withTempDir(async (directory) => {
|
||||
const bundled = path.join(directory, "bundled");
|
||||
const packageRoot = path.join(directory, "package-agents");
|
||||
const projectRoot = path.join(directory, ".pi", "agents");
|
||||
await writeProfile(bundled, "general.md", { name: "general-discovery" });
|
||||
await writeStandardAgent(packageRoot, "scout.md", { name: "scout" });
|
||||
await writeProfile(projectRoot, "scout.md", {
|
||||
name: "scout",
|
||||
roles: "[verify]",
|
||||
selection:
|
||||
" stages: [verify]\n anyTags: []\n allTags: []\n pathGlobs: []\n keywords: []\n",
|
||||
body: "Project verifier.",
|
||||
});
|
||||
|
||||
const catalog = await loadAgentProfiles({
|
||||
cwd: directory,
|
||||
agentDir: path.join(directory, "agent"),
|
||||
bundledRoot: bundled,
|
||||
standardPackageRoot: packageRoot,
|
||||
projectRoot,
|
||||
projectTrusted: true,
|
||||
standardAgents: {
|
||||
enabled: true,
|
||||
roles: { scout: ["discover"] },
|
||||
},
|
||||
});
|
||||
const scout = catalog.profiles.find((profile) => profile.name === "scout");
|
||||
assert.equal(scout?.source, "project");
|
||||
assert.deepEqual(scout?.roles, ["verify"]);
|
||||
assert.deepEqual(scout?.selection.stages, ["verify"]);
|
||||
assert.equal(scout?.instructions, "Project verifier.");
|
||||
});
|
||||
});
|
||||
|
||||
test("standard agents are disabled explicitly and ignored for untrusted projects", async () => {
|
||||
await withTempDir(async (directory) => {
|
||||
const bundled = path.join(directory, "bundled");
|
||||
const packageRoot = path.join(directory, "package-agents");
|
||||
const projectRoot = path.join(directory, ".pi", "agents");
|
||||
await writeProfile(bundled, "general.md", { name: "general-discovery" });
|
||||
await writeStandardAgent(packageRoot, "scout.md", { name: "scout" });
|
||||
await writeStandardAgent(projectRoot, "worker.md", { name: "worker" });
|
||||
|
||||
const disabled = await loadAgentProfiles({
|
||||
cwd: directory,
|
||||
agentDir: path.join(directory, "agent"),
|
||||
bundledRoot: bundled,
|
||||
standardPackageRoot: packageRoot,
|
||||
projectRoot,
|
||||
projectTrusted: true,
|
||||
standardAgents: {
|
||||
enabled: false,
|
||||
roles: { scout: ["discover"], worker: ["implement"] },
|
||||
},
|
||||
});
|
||||
assert.deepEqual(disabled.profiles.map((profile) => profile.name), ["general-discovery"]);
|
||||
});
|
||||
});
|
||||
|
||||
test("trusted project profiles override bundled profiles while first duplicate wins", async () => {
|
||||
await withTempDir(async (directory) => {
|
||||
const bundled = path.join(directory, "bundled");
|
||||
const project = path.join(directory, "project");
|
||||
await writeProfile(bundled, "10-shared.md", {
|
||||
name: "shared",
|
||||
description: "bundled first",
|
||||
});
|
||||
await writeProfile(bundled, "20-shared.md", {
|
||||
name: "shared",
|
||||
description: "bundled duplicate",
|
||||
});
|
||||
await writeProfile(project, "10-shared.md", {
|
||||
name: "shared",
|
||||
description: "project override",
|
||||
});
|
||||
await writeFile(path.join(project, "20-broken.md"), "not frontmatter");
|
||||
|
||||
const untrusted = await loadAgentProfiles({
|
||||
cwd: directory,
|
||||
bundledRoot: bundled,
|
||||
projectRoot: project,
|
||||
projectTrusted: false,
|
||||
});
|
||||
assert.equal(untrusted.profiles[0]?.description, "bundled first");
|
||||
assert.equal(untrusted.diagnostics.length, 1);
|
||||
assert.doesNotMatch(untrusted.diagnostics[0]?.sourcePath ?? "", /project/);
|
||||
|
||||
const trusted = await loadAgentProfiles({
|
||||
cwd: directory,
|
||||
bundledRoot: bundled,
|
||||
projectRoot: project,
|
||||
projectTrusted: true,
|
||||
});
|
||||
assert.equal(trusted.profiles[0]?.description, "project override");
|
||||
assert.equal(trusted.profiles[0]?.source, "project");
|
||||
assert.ok(
|
||||
trusted.diagnostics.some((item) =>
|
||||
/duplicate profile/.test(item.message),
|
||||
),
|
||||
);
|
||||
assert.ok(
|
||||
trusted.diagnostics.some((item) =>
|
||||
/missing YAML frontmatter/.test(item.message),
|
||||
),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
test("malformed profiles are isolated and unknown fields produce warnings", async () => {
|
||||
await withTempDir(async (directory) => {
|
||||
const bundled = path.join(directory, "bundled");
|
||||
await writeProfile(bundled, "good.md", {
|
||||
name: "good-profile",
|
||||
description: "Coordinates https://example.test: path",
|
||||
roles: "\n - discover",
|
||||
extra:
|
||||
"unknownField: ignored\nmetadata:\n owner: team\nlabels:\n - internal\n",
|
||||
selection:
|
||||
" stages:\n - discover\n anyTags: []\n allTags: []\n pathGlobs: []\n keywords: []\n unknownSelection:\n owner: team\n",
|
||||
});
|
||||
await writeFile(
|
||||
path.join(bundled, "quoted.md"),
|
||||
String.raw`---
|
||||
name: quoted-profile
|
||||
description: "Path C:\\" # comment
|
||||
roles: [discover]
|
||||
selection:
|
||||
stages: [discover]
|
||||
anyTags: []
|
||||
allTags: []
|
||||
pathGlobs: []
|
||||
keywords: ["path\\", dashboard] # comment
|
||||
---
|
||||
|
||||
Quoted values remain valid.
|
||||
`,
|
||||
);
|
||||
await writeFile(
|
||||
path.join(bundled, "single.md"),
|
||||
String.raw`---
|
||||
name: single-profile
|
||||
description: 'Path C:\' # comment
|
||||
roles: [discover]
|
||||
selection:
|
||||
stages: [discover]
|
||||
anyTags: []
|
||||
allTags: []
|
||||
pathGlobs: []
|
||||
keywords: ['path\', dashboard] # comment
|
||||
---
|
||||
|
||||
Single-quoted values keep literal backslashes.
|
||||
`,
|
||||
);
|
||||
await writeProfile(bundled, "bad-provider.md", {
|
||||
name: "bad-provider",
|
||||
extra: "model: provider/model\nprovider: provider\n",
|
||||
});
|
||||
await writeProfile(bundled, "bad-name.md", { name: "Bad Name" });
|
||||
|
||||
const catalog = await loadAgentProfiles({
|
||||
cwd: directory,
|
||||
bundledRoot: bundled,
|
||||
projectTrusted: false,
|
||||
});
|
||||
assert.deepEqual(
|
||||
catalog.profiles.map((profile) => profile.name),
|
||||
["good-profile", "quoted-profile", "single-profile"],
|
||||
);
|
||||
assert.equal(
|
||||
catalog.profiles[0]?.description,
|
||||
"Coordinates https://example.test: path",
|
||||
);
|
||||
assert.deepEqual(catalog.profiles[1]?.selection.keywords, [
|
||||
"path\\",
|
||||
"dashboard",
|
||||
]);
|
||||
assert.deepEqual(catalog.profiles[2]?.selection.keywords, [
|
||||
"path\\",
|
||||
"dashboard",
|
||||
]);
|
||||
assert.ok(
|
||||
catalog.diagnostics.some(
|
||||
(item) =>
|
||||
item.severity === "warning" && /unknown profile/.test(item.message),
|
||||
),
|
||||
);
|
||||
assert.ok(
|
||||
catalog.diagnostics.some((item) =>
|
||||
/unknown selection/.test(item.message),
|
||||
),
|
||||
);
|
||||
assert.ok(
|
||||
catalog.diagnostics.some((item) =>
|
||||
/provider requires/.test(item.message),
|
||||
),
|
||||
);
|
||||
assert.ok(
|
||||
catalog.diagnostics.some((item) => /lowercase kebab/.test(item.message)),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
test("a symlinked profile root is rejected", async () => {
|
||||
await withTempDir(async (directory) => {
|
||||
const actual = path.join(directory, "actual");
|
||||
const linked = path.join(directory, "linked");
|
||||
await writeProfile(actual, "agent.md", { name: "external-root" });
|
||||
await symlink(actual, linked);
|
||||
await assert.rejects(
|
||||
loadAgentProfiles({
|
||||
cwd: directory,
|
||||
bundledRoot: linked,
|
||||
projectTrusted: false,
|
||||
}),
|
||||
/profile root must not be a symlink/,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
test("a symlinked project profile parent is rejected", async () => {
|
||||
await withTempDir(async (directory) => {
|
||||
const repository = path.join(directory, "repository");
|
||||
const bundled = path.join(directory, "bundled");
|
||||
const externalPi = path.join(directory, "external-pi");
|
||||
await mkdir(repository, { recursive: true });
|
||||
await writeProfile(bundled, "agent.md", { name: "bundled-agent" });
|
||||
await writeProfile(path.join(externalPi, "agents"), "agent.md", {
|
||||
name: "external-project",
|
||||
});
|
||||
await symlink(externalPi, path.join(repository, ".pi"));
|
||||
|
||||
await assert.rejects(
|
||||
loadAgentProfiles({
|
||||
cwd: repository,
|
||||
bundledRoot: bundled,
|
||||
projectTrusted: true,
|
||||
}),
|
||||
/project profile ancestry must not be a symlink/,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
test("profile files and symlinks must resolve inside their source root", async () => {
|
||||
await withTempDir(async (directory) => {
|
||||
const bundled = path.join(directory, "bundled");
|
||||
const outside = path.join(directory, "outside.md");
|
||||
await mkdir(bundled, { recursive: true });
|
||||
await writeFile(
|
||||
path.join(bundled, "internal.profile"),
|
||||
profileDocument({ name: "internal-link" }),
|
||||
);
|
||||
await writeFile(outside, profileDocument({ name: "outside-link" }));
|
||||
await symlink("internal.profile", path.join(bundled, "internal.md"));
|
||||
await symlink(outside, path.join(bundled, "outside.md"));
|
||||
await symlink("missing.profile", path.join(bundled, "broken.md"));
|
||||
|
||||
const catalog = await loadAgentProfiles({
|
||||
cwd: directory,
|
||||
bundledRoot: bundled,
|
||||
projectTrusted: false,
|
||||
});
|
||||
assert.deepEqual(
|
||||
catalog.profiles.map((profile) => profile.name),
|
||||
["internal-link"],
|
||||
);
|
||||
assert.ok(catalog.profiles[0]?.resolvedPath.endsWith("internal.profile"));
|
||||
assert.ok(
|
||||
catalog.diagnostics.some((item) =>
|
||||
/escapes the profile root/.test(item.message),
|
||||
),
|
||||
);
|
||||
assert.ok(
|
||||
catalog.diagnostics.some((item) => item.sourcePath.endsWith("broken.md")),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
test("the nearest trusted ancestor project profile root is selected", async () => {
|
||||
await withTempDir(async (directory) => {
|
||||
const bundled = path.join(directory, "bundled");
|
||||
const project = path.join(directory, ".pi", "agents");
|
||||
const nestedCwd = path.join(directory, "packages", "app");
|
||||
await mkdir(nestedCwd, { recursive: true });
|
||||
await writeProfile(bundled, "agent.md", {
|
||||
name: "shared-agent",
|
||||
description: "bundled",
|
||||
});
|
||||
await writeProfile(project, "agent.md", {
|
||||
name: "shared-agent",
|
||||
description: "nearest project",
|
||||
});
|
||||
|
||||
const trusted = await loadAgentProfiles({
|
||||
cwd: nestedCwd,
|
||||
bundledRoot: bundled,
|
||||
projectTrusted: true,
|
||||
});
|
||||
assert.equal(trusted.profiles[0]?.description, "nearest project");
|
||||
|
||||
const untrusted = await loadAgentProfiles({
|
||||
cwd: nestedCwd,
|
||||
bundledRoot: bundled,
|
||||
projectTrusted: false,
|
||||
});
|
||||
assert.equal(untrusted.profiles[0]?.description, "bundled");
|
||||
});
|
||||
});
|
||||
|
||||
test("active profile contents are pinned by digest", async () => {
|
||||
await withTempDir(async (directory) => {
|
||||
const bundled = path.join(directory, "bundled");
|
||||
await writeProfile(bundled, "agent.md", {
|
||||
name: "pinned-agent",
|
||||
body: "Original instructions.",
|
||||
});
|
||||
const first = await loadAgentProfiles({
|
||||
cwd: directory,
|
||||
bundledRoot: bundled,
|
||||
projectTrusted: false,
|
||||
});
|
||||
await writeProfile(bundled, "agent.md", {
|
||||
name: "pinned-agent",
|
||||
body: "Changed instructions.",
|
||||
});
|
||||
const second = await loadAgentProfiles({
|
||||
cwd: directory,
|
||||
bundledRoot: bundled,
|
||||
projectTrusted: false,
|
||||
});
|
||||
assert.equal(first.profiles[0]?.instructions, "Original instructions.");
|
||||
assert.notEqual(first.profiles[0]?.digest, second.profiles[0]?.digest);
|
||||
});
|
||||
});
|
||||
|
||||
function agent(
|
||||
name: string,
|
||||
selection: AgentProfile["selection"],
|
||||
source: AgentProfile["source"] = "bundled",
|
||||
): AgentProfile {
|
||||
return {
|
||||
name,
|
||||
description: name,
|
||||
roles: ["discover"],
|
||||
selection,
|
||||
instructions: "Discover.",
|
||||
source,
|
||||
sourcePath: name,
|
||||
resolvedPath: name,
|
||||
digest: name.padEnd(64, "0"),
|
||||
};
|
||||
}
|
||||
|
||||
const baseFacts: Omit<WorkflowFacts, "stage"> = {
|
||||
taskKind: "feature",
|
||||
requestTerms: ["dashboard"],
|
||||
candidatePaths: ["src/routes/dashboard/page.tsx"],
|
||||
languages: ["typescript"],
|
||||
tags: ["frontend", "ui"],
|
||||
riskTags: [],
|
||||
};
|
||||
|
||||
test("eligibility records exclusion reasons and selects the most specific discovery profile", () => {
|
||||
const fallback = agent("general", {
|
||||
stages: ["discover"],
|
||||
anyTags: [],
|
||||
allTags: [],
|
||||
pathGlobs: [],
|
||||
keywords: [],
|
||||
});
|
||||
const dashboard = agent("dashboard", {
|
||||
stages: ["discover"],
|
||||
anyTags: ["ui"],
|
||||
allTags: ["frontend"],
|
||||
pathGlobs: ["src/**/dashboard/*.tsx"],
|
||||
keywords: ["dashboard"],
|
||||
});
|
||||
const backend = agent("backend", {
|
||||
stages: ["discover"],
|
||||
anyTags: ["backend"],
|
||||
allTags: [],
|
||||
pathGlobs: [],
|
||||
keywords: [],
|
||||
});
|
||||
|
||||
const result = selectInitialDiscoveryProfile(
|
||||
[fallback, dashboard, backend],
|
||||
baseFacts,
|
||||
);
|
||||
assert.equal(result.selected.name, "dashboard");
|
||||
const excluded = result.evaluations.find(
|
||||
(item) => item.profile.name === "backend",
|
||||
);
|
||||
assert.equal(excluded?.eligible, false);
|
||||
assert.deepEqual(excluded?.reasons, ["no optional tag matched"]);
|
||||
|
||||
const preDiscovery = eligibleProfiles(
|
||||
[fallback, dashboard],
|
||||
{
|
||||
...baseFacts,
|
||||
stage: "discover",
|
||||
requestTerms: [],
|
||||
candidatePaths: [],
|
||||
tags: [],
|
||||
},
|
||||
"discover",
|
||||
);
|
||||
assert.equal(
|
||||
preDiscovery.find((item) => item.eligible)?.profile.name,
|
||||
"general",
|
||||
);
|
||||
});
|
||||
|
||||
test("an eligible standard agent replaces the bundled fallback for its role", () => {
|
||||
const emptySelection: AgentProfile["selection"] = {
|
||||
stages: ["discover"],
|
||||
anyTags: [],
|
||||
allTags: [],
|
||||
pathGlobs: [],
|
||||
keywords: [],
|
||||
};
|
||||
const bundled = agent("general-discovery", emptySelection);
|
||||
const scout = agent("scout", emptySelection, "standard-package");
|
||||
|
||||
const result = selectInitialDiscoveryProfile(
|
||||
[bundled, scout],
|
||||
baseFacts,
|
||||
);
|
||||
assert.equal(result.selected.name, "scout");
|
||||
assert.deepEqual(
|
||||
result.evaluations.find((item) => item.profile.name === "general-discovery")?.reasons,
|
||||
["eligible standard agent available for role discover"],
|
||||
);
|
||||
});
|
||||
|
||||
test("eligibility enforces every criterion and uses a code-point name tie-breaker", () => {
|
||||
const emptySelection: AgentProfile["selection"] = {
|
||||
stages: ["discover"],
|
||||
anyTags: [],
|
||||
allTags: [],
|
||||
pathGlobs: [],
|
||||
keywords: [],
|
||||
};
|
||||
const wrongRole = {
|
||||
...agent("wrong-role", emptySelection),
|
||||
roles: ["design"] as AgentProfile["roles"],
|
||||
};
|
||||
const wrongStage = agent("wrong-stage", {
|
||||
...emptySelection,
|
||||
stages: ["design"],
|
||||
});
|
||||
const missingAllTag = agent("missing-all-tag", {
|
||||
...emptySelection,
|
||||
allTags: ["backend"],
|
||||
});
|
||||
const missingAnyTag = agent("missing-any-tag", {
|
||||
...emptySelection,
|
||||
anyTags: ["backend"],
|
||||
});
|
||||
const missingPath = agent("missing-path", {
|
||||
...emptySelection,
|
||||
pathGlobs: ["server/**/*.ts"],
|
||||
});
|
||||
const missingKeyword = agent("missing-keyword", {
|
||||
...emptySelection,
|
||||
keywords: ["api"],
|
||||
});
|
||||
|
||||
const evaluations = eligibleProfiles(
|
||||
[
|
||||
wrongRole,
|
||||
wrongStage,
|
||||
missingAllTag,
|
||||
missingAnyTag,
|
||||
missingPath,
|
||||
missingKeyword,
|
||||
],
|
||||
{ ...baseFacts, stage: "discover" },
|
||||
"discover",
|
||||
);
|
||||
const reasons = new Map(
|
||||
evaluations.map((evaluation) => [
|
||||
evaluation.profile.name,
|
||||
evaluation.reasons,
|
||||
]),
|
||||
);
|
||||
assert.deepEqual(reasons.get("wrong-role"), [
|
||||
"does not support role discover",
|
||||
]);
|
||||
assert.deepEqual(reasons.get("wrong-stage"), [
|
||||
"does not support stage discover",
|
||||
]);
|
||||
assert.deepEqual(reasons.get("missing-all-tag"), [
|
||||
"missing required tags: backend",
|
||||
]);
|
||||
assert.deepEqual(reasons.get("missing-any-tag"), ["no optional tag matched"]);
|
||||
assert.deepEqual(reasons.get("missing-path"), ["no candidate path matched"]);
|
||||
assert.deepEqual(reasons.get("missing-keyword"), [
|
||||
"no request keyword matched",
|
||||
]);
|
||||
|
||||
const tie = eligibleProfiles(
|
||||
[agent("zeta", emptySelection), agent("alpha", emptySelection)],
|
||||
{ ...baseFacts, stage: "discover" },
|
||||
"discover",
|
||||
);
|
||||
assert.deepEqual(
|
||||
tie
|
||||
.filter((evaluation) => evaluation.eligible)
|
||||
.map((evaluation) => evaluation.profile.name),
|
||||
["alpha", "zeta"],
|
||||
);
|
||||
});
|
||||
Reference in New Issue
Block a user