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>
This commit is contained in:
@@ -447,4 +447,114 @@ describe('create command', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('create prompt', () => {
|
||||
it('creates a prompt with content', async () => {
|
||||
vi.mocked(client.post).mockResolvedValueOnce({ id: 'p-1', name: 'test-prompt' });
|
||||
const cmd = createCreateCommand({ client, log });
|
||||
await cmd.parseAsync(['prompt', 'test-prompt', '--content', 'Hello world'], { from: 'user' });
|
||||
|
||||
expect(client.post).toHaveBeenCalledWith('/api/v1/prompts', {
|
||||
name: 'test-prompt',
|
||||
content: 'Hello world',
|
||||
});
|
||||
expect(output.join('\n')).toContain("prompt 'test-prompt' created");
|
||||
});
|
||||
|
||||
it('requires content or content-file', async () => {
|
||||
const cmd = createCreateCommand({ client, log });
|
||||
await expect(
|
||||
cmd.parseAsync(['prompt', 'no-content'], { from: 'user' }),
|
||||
).rejects.toThrow('--content or --content-file is required');
|
||||
});
|
||||
|
||||
it('--priority sets prompt priority', async () => {
|
||||
vi.mocked(client.post).mockResolvedValueOnce({ id: 'p-1', name: 'pri-prompt' });
|
||||
const cmd = createCreateCommand({ client, log });
|
||||
await cmd.parseAsync(['prompt', 'pri-prompt', '--content', 'x', '--priority', '8'], { from: 'user' });
|
||||
|
||||
expect(client.post).toHaveBeenCalledWith('/api/v1/prompts', expect.objectContaining({
|
||||
priority: 8,
|
||||
}));
|
||||
});
|
||||
|
||||
it('--priority validates range 1-10', async () => {
|
||||
const cmd = createCreateCommand({ client, log });
|
||||
await expect(
|
||||
cmd.parseAsync(['prompt', 'bad', '--content', 'x', '--priority', '15'], { from: 'user' }),
|
||||
).rejects.toThrow('--priority must be a number between 1 and 10');
|
||||
});
|
||||
|
||||
it('--priority rejects zero', async () => {
|
||||
const cmd = createCreateCommand({ client, log });
|
||||
await expect(
|
||||
cmd.parseAsync(['prompt', 'bad', '--content', 'x', '--priority', '0'], { from: 'user' }),
|
||||
).rejects.toThrow('--priority must be a number between 1 and 10');
|
||||
});
|
||||
|
||||
it('--link sets linkTarget', async () => {
|
||||
vi.mocked(client.post).mockResolvedValueOnce({ id: 'p-1', name: 'linked' });
|
||||
const cmd = createCreateCommand({ client, log });
|
||||
await cmd.parseAsync(['prompt', 'linked', '--content', 'x', '--link', 'proj/srv:docmost://pages/abc'], { from: 'user' });
|
||||
|
||||
expect(client.post).toHaveBeenCalledWith('/api/v1/prompts', expect.objectContaining({
|
||||
linkTarget: 'proj/srv:docmost://pages/abc',
|
||||
}));
|
||||
});
|
||||
|
||||
it('--project resolves project name to ID', async () => {
|
||||
vi.mocked(client.get).mockResolvedValueOnce([{ id: 'proj-1', name: 'my-project' }] as never);
|
||||
vi.mocked(client.post).mockResolvedValueOnce({ id: 'p-1', name: 'scoped' });
|
||||
const cmd = createCreateCommand({ client, log });
|
||||
await cmd.parseAsync(['prompt', 'scoped', '--content', 'x', '--project', 'my-project'], { from: 'user' });
|
||||
|
||||
expect(client.post).toHaveBeenCalledWith('/api/v1/prompts', expect.objectContaining({
|
||||
projectId: 'proj-1',
|
||||
}));
|
||||
});
|
||||
|
||||
it('--project throws when project not found', async () => {
|
||||
vi.mocked(client.get).mockResolvedValueOnce([] as never);
|
||||
const cmd = createCreateCommand({ client, log });
|
||||
await expect(
|
||||
cmd.parseAsync(['prompt', 'bad', '--content', 'x', '--project', 'nope'], { from: 'user' }),
|
||||
).rejects.toThrow("Project 'nope' not found");
|
||||
});
|
||||
});
|
||||
|
||||
describe('create promptrequest', () => {
|
||||
it('creates a prompt request with priority', async () => {
|
||||
vi.mocked(client.post).mockResolvedValueOnce({ id: 'r-1', name: 'req' });
|
||||
const cmd = createCreateCommand({ client, log });
|
||||
await cmd.parseAsync(['promptrequest', 'req', '--content', 'proposal', '--priority', '7'], { from: 'user' });
|
||||
|
||||
expect(client.post).toHaveBeenCalledWith('/api/v1/promptrequests', expect.objectContaining({
|
||||
name: 'req',
|
||||
content: 'proposal',
|
||||
priority: 7,
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
||||
describe('create project', () => {
|
||||
it('creates a project with --gated', async () => {
|
||||
vi.mocked(client.post).mockResolvedValueOnce({ id: 'proj-1', name: 'gated-proj' });
|
||||
const cmd = createCreateCommand({ client, log });
|
||||
await cmd.parseAsync(['project', 'gated-proj', '--gated'], { from: 'user' });
|
||||
|
||||
expect(client.post).toHaveBeenCalledWith('/api/v1/projects', expect.objectContaining({
|
||||
gated: true,
|
||||
}));
|
||||
});
|
||||
|
||||
it('creates a project with --no-gated', async () => {
|
||||
vi.mocked(client.post).mockResolvedValueOnce({ id: 'proj-1', name: 'open-proj' });
|
||||
const cmd = createCreateCommand({ client, log });
|
||||
await cmd.parseAsync(['project', 'open-proj', '--no-gated'], { from: 'user' });
|
||||
|
||||
expect(client.post).toHaveBeenCalledWith('/api/v1/projects', expect.objectContaining({
|
||||
gated: false,
|
||||
}));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user