feat: gated project experience & prompt intelligence
Implements the full gated session flow and prompt intelligence system:
- Prisma schema: add gated, priority, summary, chapters, linkTarget fields
- Session gate: state machine (gated → begin_session → ungated) with LLM-powered
tool selection based on prompt index
- Tag matcher: intelligent prompt-to-tool matching with project/server/action tags
- LLM selector: tiered provider selection (fast for gating, heavy for complex tasks)
- Link resolver: cross-project MCP resource references (project/server:uri format)
- Prompt summary service: LLM-generated summaries and chapter extraction
- System project bootstrap: ensures default project exists on startup
- Structural link health checks: enrichWithLinkStatus on prompt GET endpoints
- CLI: create prompt --priority/--link, create project --gated/--no-gated,
describe project shows prompts section, get prompts shows PRI/LINK/STATUS
- Apply/edit: priority, linkTarget, gated fields supported
- Shell completions: fish updated with new flags
- 1,253 tests passing across all packages
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 23:22:42 +00:00
|
|
|
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
2026-02-25 23:27:59 +00:00
|
|
|
import { bootstrapSystemProject, SYSTEM_PROJECT_NAME, getSystemPromptNames } from '../src/bootstrap/system-project.js';
|
feat: gated project experience & prompt intelligence
Implements the full gated session flow and prompt intelligence system:
- Prisma schema: add gated, priority, summary, chapters, linkTarget fields
- Session gate: state machine (gated → begin_session → ungated) with LLM-powered
tool selection based on prompt index
- Tag matcher: intelligent prompt-to-tool matching with project/server/action tags
- LLM selector: tiered provider selection (fast for gating, heavy for complex tasks)
- Link resolver: cross-project MCP resource references (project/server:uri format)
- Prompt summary service: LLM-generated summaries and chapter extraction
- System project bootstrap: ensures default project exists on startup
- Structural link health checks: enrichWithLinkStatus on prompt GET endpoints
- CLI: create prompt --priority/--link, create project --gated/--no-gated,
describe project shows prompts section, get prompts shows PRI/LINK/STATUS
- Apply/edit: priority, linkTarget, gated fields supported
- Shell completions: fish updated with new flags
- 1,253 tests passing across all packages
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 23:22:42 +00:00
|
|
|
import type { PrismaClient } from '@prisma/client';
|
|
|
|
|
|
|
|
|
|
function mockPrisma(): PrismaClient {
|
|
|
|
|
const prompts = new Map<string, { id: string; name: string; projectId: string }>();
|
|
|
|
|
let promptIdCounter = 1;
|
|
|
|
|
|
|
|
|
|
return {
|
2026-02-25 23:27:59 +00:00
|
|
|
user: {
|
|
|
|
|
upsert: vi.fn(async () => ({
|
|
|
|
|
id: 'sys-user-id',
|
|
|
|
|
email: 'system@mcpctl.local',
|
|
|
|
|
name: 'System',
|
|
|
|
|
})),
|
|
|
|
|
},
|
feat: gated project experience & prompt intelligence
Implements the full gated session flow and prompt intelligence system:
- Prisma schema: add gated, priority, summary, chapters, linkTarget fields
- Session gate: state machine (gated → begin_session → ungated) with LLM-powered
tool selection based on prompt index
- Tag matcher: intelligent prompt-to-tool matching with project/server/action tags
- LLM selector: tiered provider selection (fast for gating, heavy for complex tasks)
- Link resolver: cross-project MCP resource references (project/server:uri format)
- Prompt summary service: LLM-generated summaries and chapter extraction
- System project bootstrap: ensures default project exists on startup
- Structural link health checks: enrichWithLinkStatus on prompt GET endpoints
- CLI: create prompt --priority/--link, create project --gated/--no-gated,
describe project shows prompts section, get prompts shows PRI/LINK/STATUS
- Apply/edit: priority, linkTarget, gated fields supported
- Shell completions: fish updated with new flags
- 1,253 tests passing across all packages
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 23:22:42 +00:00
|
|
|
project: {
|
|
|
|
|
upsert: vi.fn(async (args: { where: { name: string }; create: Record<string, unknown>; update: Record<string, unknown> }) => ({
|
|
|
|
|
id: 'sys-proj-id',
|
|
|
|
|
name: args.where.name,
|
|
|
|
|
...args.create,
|
|
|
|
|
})),
|
|
|
|
|
},
|
|
|
|
|
prompt: {
|
|
|
|
|
findFirst: vi.fn(async (args: { where: { name: string; projectId: string } }) => {
|
|
|
|
|
return prompts.get(`${args.where.projectId}:${args.where.name}`) ?? null;
|
|
|
|
|
}),
|
|
|
|
|
create: vi.fn(async (args: { data: { name: string; content: string; priority: number; projectId: string } }) => {
|
|
|
|
|
const id = `prompt-${promptIdCounter++}`;
|
|
|
|
|
const prompt = { id, ...args.data };
|
|
|
|
|
prompts.set(`${args.data.projectId}:${args.data.name}`, prompt);
|
|
|
|
|
return prompt;
|
|
|
|
|
}),
|
|
|
|
|
},
|
|
|
|
|
} as unknown as PrismaClient;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
describe('bootstrapSystemProject', () => {
|
|
|
|
|
let prisma: PrismaClient;
|
|
|
|
|
|
|
|
|
|
beforeEach(() => {
|
|
|
|
|
prisma = mockPrisma();
|
|
|
|
|
});
|
|
|
|
|
|
2026-02-25 23:27:59 +00:00
|
|
|
it('creates a system user via upsert', async () => {
|
|
|
|
|
await bootstrapSystemProject(prisma);
|
|
|
|
|
|
|
|
|
|
expect(prisma.user.upsert).toHaveBeenCalledWith(
|
|
|
|
|
expect.objectContaining({
|
|
|
|
|
where: { email: 'system@mcpctl.local' },
|
|
|
|
|
create: expect.objectContaining({
|
|
|
|
|
email: 'system@mcpctl.local',
|
|
|
|
|
name: 'System',
|
|
|
|
|
}),
|
|
|
|
|
}),
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
|
feat: gated project experience & prompt intelligence
Implements the full gated session flow and prompt intelligence system:
- Prisma schema: add gated, priority, summary, chapters, linkTarget fields
- Session gate: state machine (gated → begin_session → ungated) with LLM-powered
tool selection based on prompt index
- Tag matcher: intelligent prompt-to-tool matching with project/server/action tags
- LLM selector: tiered provider selection (fast for gating, heavy for complex tasks)
- Link resolver: cross-project MCP resource references (project/server:uri format)
- Prompt summary service: LLM-generated summaries and chapter extraction
- System project bootstrap: ensures default project exists on startup
- Structural link health checks: enrichWithLinkStatus on prompt GET endpoints
- CLI: create prompt --priority/--link, create project --gated/--no-gated,
describe project shows prompts section, get prompts shows PRI/LINK/STATUS
- Apply/edit: priority, linkTarget, gated fields supported
- Shell completions: fish updated with new flags
- 1,253 tests passing across all packages
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 23:22:42 +00:00
|
|
|
it('creates the mcpctl-system project via upsert', async () => {
|
|
|
|
|
await bootstrapSystemProject(prisma);
|
|
|
|
|
|
|
|
|
|
expect(prisma.project.upsert).toHaveBeenCalledWith(
|
|
|
|
|
expect.objectContaining({
|
|
|
|
|
where: { name: SYSTEM_PROJECT_NAME },
|
|
|
|
|
create: expect.objectContaining({
|
|
|
|
|
name: SYSTEM_PROJECT_NAME,
|
2026-02-25 23:27:59 +00:00
|
|
|
ownerId: 'sys-user-id',
|
feat: gated project experience & prompt intelligence
Implements the full gated session flow and prompt intelligence system:
- Prisma schema: add gated, priority, summary, chapters, linkTarget fields
- Session gate: state machine (gated → begin_session → ungated) with LLM-powered
tool selection based on prompt index
- Tag matcher: intelligent prompt-to-tool matching with project/server/action tags
- LLM selector: tiered provider selection (fast for gating, heavy for complex tasks)
- Link resolver: cross-project MCP resource references (project/server:uri format)
- Prompt summary service: LLM-generated summaries and chapter extraction
- System project bootstrap: ensures default project exists on startup
- Structural link health checks: enrichWithLinkStatus on prompt GET endpoints
- CLI: create prompt --priority/--link, create project --gated/--no-gated,
describe project shows prompts section, get prompts shows PRI/LINK/STATUS
- Apply/edit: priority, linkTarget, gated fields supported
- Shell completions: fish updated with new flags
- 1,253 tests passing across all packages
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 23:22:42 +00:00
|
|
|
gated: false,
|
|
|
|
|
}),
|
|
|
|
|
update: {},
|
|
|
|
|
}),
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('creates all system prompts', async () => {
|
|
|
|
|
await bootstrapSystemProject(prisma);
|
|
|
|
|
|
|
|
|
|
const expectedNames = getSystemPromptNames();
|
|
|
|
|
expect(expectedNames.length).toBeGreaterThanOrEqual(4);
|
|
|
|
|
|
|
|
|
|
for (const name of expectedNames) {
|
|
|
|
|
expect(prisma.prompt.findFirst).toHaveBeenCalledWith(
|
|
|
|
|
expect.objectContaining({
|
|
|
|
|
where: { name, projectId: 'sys-proj-id' },
|
|
|
|
|
}),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
expect(prisma.prompt.create).toHaveBeenCalledTimes(expectedNames.length);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('creates system prompts with priority 10', async () => {
|
|
|
|
|
await bootstrapSystemProject(prisma);
|
|
|
|
|
|
|
|
|
|
const createCalls = vi.mocked(prisma.prompt.create).mock.calls;
|
|
|
|
|
for (const call of createCalls) {
|
|
|
|
|
const data = (call[0] as { data: { priority: number } }).data;
|
|
|
|
|
expect(data.priority).toBe(10);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('does not re-create existing prompts (idempotent)', async () => {
|
|
|
|
|
// First call creates everything
|
|
|
|
|
await bootstrapSystemProject(prisma);
|
|
|
|
|
const firstCallCount = vi.mocked(prisma.prompt.create).mock.calls.length;
|
|
|
|
|
|
|
|
|
|
// Second call — prompts already exist in mock, should not create again
|
|
|
|
|
await bootstrapSystemProject(prisma);
|
|
|
|
|
|
|
|
|
|
// create should not have been called additional times
|
|
|
|
|
expect(vi.mocked(prisma.prompt.create).mock.calls.length).toBe(firstCallCount);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('re-creates deleted prompts on subsequent startup', async () => {
|
|
|
|
|
// First run creates everything
|
|
|
|
|
await bootstrapSystemProject(prisma);
|
|
|
|
|
|
|
|
|
|
// Simulate deletion: clear the map so findFirst returns null
|
|
|
|
|
vi.mocked(prisma.prompt.findFirst).mockResolvedValue(null);
|
|
|
|
|
vi.mocked(prisma.prompt.create).mockClear();
|
|
|
|
|
|
|
|
|
|
// Second run should recreate
|
|
|
|
|
await bootstrapSystemProject(prisma);
|
|
|
|
|
|
|
|
|
|
const expectedNames = getSystemPromptNames();
|
|
|
|
|
expect(vi.mocked(prisma.prompt.create).mock.calls.length).toBe(expectedNames.length);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('system project has gated=false', async () => {
|
|
|
|
|
await bootstrapSystemProject(prisma);
|
|
|
|
|
|
|
|
|
|
const upsertCall = vi.mocked(prisma.project.upsert).mock.calls[0]![0];
|
|
|
|
|
expect((upsertCall as { create: { gated: boolean } }).create.gated).toBe(false);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
describe('getSystemPromptNames', () => {
|
|
|
|
|
it('returns all system prompt names', () => {
|
|
|
|
|
const names = getSystemPromptNames();
|
|
|
|
|
expect(names).toContain('gate-instructions');
|
|
|
|
|
expect(names).toContain('gate-encouragement');
|
|
|
|
|
expect(names).toContain('gate-intercept-preamble');
|
|
|
|
|
expect(names).toContain('session-greeting');
|
|
|
|
|
});
|
|
|
|
|
});
|