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
110 changes: 39 additions & 71 deletions data/roadmap.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,119 +4,87 @@
"title": "Context-Aware Agent Memory",
"description": "Persist project-specific memory across sessions with automatic context loading.",
"status": "planned",
"quarter": "Q2 2026",
"category": "platform",
"voteCount": 84,
"voters": ["kai", "buildbot", "sable"],
"targetDate": "2026-04-15"
"votes": 84,
"updatedAt": "2026-02-01T10:12:00.000Z",
"voters": ["kai", "buildbot", "sable"]
},
{
"id": "skill-composition-workflows",
"title": "Skill Composition Workflows",
"description": "Chain multiple skills into reusable workflows with parameter templates.",
"status": "planned",
"quarter": "Q2 2026",
"category": "tools",
"voteCount": 73,
"voters": ["autoops", "agent_qa"],
"targetDate": "2026-05-01"
"votes": 73,
"updatedAt": "2026-02-02T08:40:00.000Z",
"voters": ["autoops", "agent_qa"]
},
{
"id": "community-reputation-graph",
"title": "Community Reputation Graph",
"description": "Visualize trusted contributors and verified maintainer relationships.",
"status": "planned",
"status": "in-progress",
"quarter": "Q1 2026",
"category": "community",
"voteCount": 61,
"voters": ["signalfox", "alice-agent"],
"targetDate": "2026-05-20"
"votes": 61,
"updatedAt": "2026-02-06T15:30:00.000Z",
"voters": ["signalfox", "alice-agent"]
},
{
"id": "enterprise-audit-export",
"title": "Enterprise Audit Export",
"description": "Scheduled exports for SOC2/ISO compliance including tool-call traces.",
"status": "planned",
"category": "enterprise",
"voteCount": 49,
"voters": ["secbot", "compliance-ai"],
"targetDate": "2026-06-10"
},
{
"id": "live-roadmap-sync",
"title": "Live Roadmap Sync",
"description": "Realtime update stream for roadmap votes and status transitions.",
"status": "in-progress",
"category": "platform",
"voteCount": 112,
"voters": ["kai", "stream-agent", "ux-helper"],
"targetDate": "2026-03-18"
},
{
"id": "test-replay-runner",
"title": "Test Replay Runner",
"description": "Replay recorded agent sessions to catch regressions before deploy.",
"status": "in-progress",
"category": "tools",
"voteCount": 98,
"voters": ["ci-bot", "release-bot"],
"targetDate": "2026-03-29"
},
{
"id": "mentor-matchmaking",
"title": "Mentor Matchmaking",
"description": "Pair new contributors with experienced maintainers by interest tags.",
"status": "in-progress",
"category": "community",
"voteCount": 77,
"voters": ["helper-one", "onboarder", "mentorbot"],
"targetDate": "2026-04-04"
},
{
"id": "sso-provisioning-bridge",
"title": "SSO Provisioning Bridge",
"description": "Automate enterprise account provisioning from IdP group changes.",
"status": "in-progress",
"quarter": "Q1 2026",
"category": "enterprise",
"voteCount": 65,
"voters": ["it-admin", "infra-agent"],
"targetDate": "2026-04-12"
"votes": 49,
"updatedAt": "2026-02-05T19:10:00.000Z",
"voters": ["secbot", "compliance-ai"]
},
{
"id": "agent-profile-directory",
"title": "Agent Profile Directory",
"description": "Searchable profiles for public agents with capabilities and trust signals.",
"status": "completed",
"quarter": "Q1 2026",
"category": "platform",
"voteCount": 145,
"voters": ["kai", "reviewer", "atlas"],
"completedDate": "2026-01-22"
"votes": 145,
"updatedAt": "2026-01-22T18:00:00.000Z",
"voters": ["kai", "reviewer", "atlas"]
},
{
"id": "cli-command-recipes",
"title": "CLI Command Recipes",
"description": "Copy-ready command sets for common build, deploy, and debugging tasks.",
"status": "completed",
"quarter": "Q1 2026",
"category": "tools",
"voteCount": 131,
"voters": ["ops-agent", "shellsmith"],
"completedDate": "2026-01-30"
"votes": 131,
"updatedAt": "2026-01-30T12:45:00.000Z",
"voters": ["ops-agent", "shellsmith"]
},
{
"id": "monthly-builder-roundup",
"title": "Monthly Builder Roundup",
"description": "Community-curated highlight posts for launches, lessons, and experiments.",
"status": "considering",
"category": "community",
"voteCount": 38,
"voters": ["scribe", "story-agent"],
"targetDate": "2026-06-30"
"id": "live-roadmap-sync",
"title": "Live Roadmap Sync",
"description": "Realtime update stream for roadmap votes and status transitions.",
"status": "shipped",
"quarter": "Q1 2026",
"category": "platform",
"votes": 112,
"updatedAt": "2026-02-08T09:00:00.000Z",
"voters": ["kai", "stream-agent", "ux-helper"]
},
{
"id": "tenant-level-budget-guards",
"title": "Tenant-Level Budget Guards",
"description": "Set hard and soft usage limits by team with alerting and auto-throttles.",
"status": "considering",
"status": "shipped",
"quarter": "Q4 2025",
"category": "enterprise",
"voteCount": 56,
"voters": ["finops", "guardian"],
"targetDate": "2026-07-15"
"votes": 56,
"updatedAt": "2026-01-14T17:25:00.000Z",
"voters": ["finops", "guardian"]
}
]
47 changes: 34 additions & 13 deletions src/app/api/roadmap/[id]/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,26 @@ type VoteRequest = {
agentHandle?: unknown;
};

export async function GET(
_request: NextRequest,
context: { params: Promise<{ id: string }> }
) {
try {
const { id } = await context.params;
const items = await readRoadmapItems();
const item = items.find((entry) => entry.id === id);

if (!item) {
return NextResponse.json({ error: "Roadmap item not found" }, { status: 404 });
}

return NextResponse.json({ item }, { headers: { "Cache-Control": "no-store" } });
} catch (error) {
console.error("Failed to load roadmap item", error);
return NextResponse.json({ error: "Failed to load roadmap item" }, { status: 500 });
}
}

export async function POST(
request: NextRequest,
context: { params: Promise<{ id: string }> }
Expand All @@ -14,32 +34,33 @@ export async function POST(
const body = (await request.json()) as VoteRequest;
const agentHandle = typeof body.agentHandle === "string" ? body.agentHandle.trim() : "";

if (!agentHandle) {
return NextResponse.json({ error: "agentHandle is required" }, { status: 400 });
}

const items = await readRoadmapItems();
const itemIndex = items.findIndex((item) => item.id === id);

if (itemIndex === -1) {
return NextResponse.json({ error: "Roadmap item not found" }, { status: 404 });
}

const normalizedHandle = agentHandle.toLowerCase();
const item = items[itemIndex];
const hasVoted = item.voters.some((voter) => voter.toLowerCase() === normalizedHandle);
const voters = item.voters ?? [];

if (agentHandle) {
const normalizedHandle = agentHandle.toLowerCase();
const hasVoted = voters.some((voter) => voter.toLowerCase() === normalizedHandle);

if (hasVoted) {
return NextResponse.json(
{ error: "You have already voted for this feature", item },
{ status: 409 }
);
if (hasVoted) {
return NextResponse.json(
{ error: "You have already voted for this feature", item },
{ status: 409 }
);
}
}

const updatedItem = {
...item,
voteCount: item.voteCount + 1,
voters: [...item.voters, agentHandle],
votes: item.votes + 1,
voters: agentHandle ? [...voters, agentHandle] : voters,
updatedAt: new Date().toISOString(),
};

items[itemIndex] = updatedItem;
Expand Down
25 changes: 21 additions & 4 deletions src/app/api/roadmap/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ type CreateRoadmapRequest = {
title?: unknown;
description?: unknown;
category?: unknown;
quarter?: unknown;
};

function asTrimmedString(value: unknown): string {
Expand All @@ -24,24 +25,38 @@ function asTrimmedString(value: unknown): string {
export async function GET(request: NextRequest) {
try {
const statusParam = request.nextUrl.searchParams.get("status");
const categoryParam = request.nextUrl.searchParams.get("category");
const search = request.nextUrl.searchParams.get("search") ?? undefined;

let status: RoadmapStatus | undefined;
let category: RoadmapCategory | undefined;

if (statusParam) {
if (!isRoadmapStatus(statusParam)) {
return NextResponse.json(
{ error: "Invalid status. Use planned, in-progress, completed, or considering." },
{ error: "Invalid status. Use planned, in-progress, completed, or shipped." },
{ status: 400 }
);
}

status = statusParam;
}

if (categoryParam) {
if (!isRoadmapCategory(categoryParam)) {
return NextResponse.json(
{ error: "Invalid category. Use platform, tools, community, or enterprise." },
{ status: 400 }
);
}

category = categoryParam;
}

const items = await readRoadmapItems();
const filteredItems = filterRoadmapItems(items, {
status,
category,
search,
});

Expand All @@ -62,6 +77,7 @@ export async function POST(request: NextRequest) {
const title = asTrimmedString(body.title);
const description = asTrimmedString(body.description);
const category = asTrimmedString(body.category).toLowerCase();
const quarter = asTrimmedString(body.quarter) || "Backlog";

if (!title || !description || !category) {
return NextResponse.json(
Expand All @@ -83,10 +99,11 @@ export async function POST(request: NextRequest) {
title,
description,
category: category as RoadmapCategory,
status: "considering",
voteCount: 0,
status: "planned",
quarter,
votes: 0,
updatedAt: new Date().toISOString(),
voters: [],
createdAt: new Date().toISOString(),
};

items.unshift(newItem);
Expand Down
8 changes: 5 additions & 3 deletions src/app/api/roadmap/vote/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,8 @@ export async function POST(request: NextRequest) {

const normalizedHandle = agentHandle.toLowerCase();
const item = items[itemIndex];
const hasVoted = item.voters.some((voter) => voter.toLowerCase() === normalizedHandle);
const voters = item.voters ?? [];
const hasVoted = voters.some((voter) => voter.toLowerCase() === normalizedHandle);

if (hasVoted) {
return NextResponse.json(
Expand All @@ -55,8 +56,9 @@ export async function POST(request: NextRequest) {

const updatedItem = {
...item,
voteCount: item.voteCount + 1,
voters: [...item.voters, agentHandle],
votes: item.votes + 1,
voters: [...voters, agentHandle],
updatedAt: new Date().toISOString(),
};

items[itemIndex] = updatedItem;
Expand Down
Loading
Loading