Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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
9 changes: 7 additions & 2 deletions packages/web/src/components/workflows/WorkflowCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,13 @@ export function WorkflowCard({
}: WorkflowCardProps): React.ReactElement {
const parsed = parseWorkflowDescription(workflow.description ?? '');
const displayName = getWorkflowDisplayName(workflow.name);
const category = getWorkflowCategory(workflow.name, workflow.description ?? '');
const tags = getWorkflowTags(workflow.name, parsed);
const wf = workflow as Record<string, unknown>;
const category = getWorkflowCategory(
workflow.name,
workflow.description ?? '',
wf.category as string | undefined
);
const tags = getWorkflowTags(workflow.name, parsed, wf.tags as string[] | undefined);
const iconName = getWorkflowIconName(workflow.name, category);
const CARD_ICON = ICON_MAP[iconName];

Expand Down
6 changes: 5 additions & 1 deletion packages/web/src/components/workflows/WorkflowList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,11 @@ export function WorkflowList(): React.ReactElement {
}
// Category filter
if (activeCategory !== 'All') {
const cat = getWorkflowCategory(wf.name, wf.description ?? '');
const cat = getWorkflowCategory(
wf.name,
wf.description ?? '',
(wf as Record<string, unknown>).category as string | undefined
);
if (cat !== activeCategory) return false;
}
return true;
Expand Down
31 changes: 31 additions & 0 deletions packages/web/src/lib/workflow-metadata.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,18 @@ describe('getWorkflowCategory', () => {
expect(getWorkflowCategory('archon-assist', 'General help')).toBe('Development');
expect(getWorkflowCategory('archon-idea-to-pr', 'From idea to PR')).toBe('Development');
});

test('uses explicit category when provided', () => {
expect(getWorkflowCategory('my-gitlab-workflow', 'Does GitLab things', 'Automation')).toBe(
'Automation'
);
});

test('falls back to inference when explicit category is invalid', () => {
expect(getWorkflowCategory('archon-smart-pr-review', 'Review PR', 'InvalidCategory')).toBe(
'Code Review'
);
});
});

describe('getWorkflowTags', () => {
Expand All @@ -200,6 +212,25 @@ describe('getWorkflowTags', () => {
const githubCount = tags.filter(t => t === 'GitHub').length;
expect(githubCount).toBeLessThanOrEqual(1);
});

test('uses explicit tags when provided', () => {
const parsed = parseWorkflowDescription('A GitLab workflow');
const tags = getWorkflowTags('review-gitlab-mr', parsed, ['GitLab', 'Review']);
expect(tags).toEqual(['GitLab', 'Review']);
});

test('falls back to inference when no explicit tags', () => {
const parsed = parseWorkflowDescription('Does: review PR on GitHub');
const tags = getWorkflowTags('archon-pr-review', parsed, undefined);
expect(tags).toContain('GitHub');
expect(tags).toContain('Review');
});

test('deduplicates explicit tags', () => {
const parsed = parseWorkflowDescription('anything');
const tags = getWorkflowTags('test', parsed, ['GitLab', 'GitLab', 'Review']);
expect(tags).toEqual(['GitLab', 'Review']);
});
Comment thread
lraphael marked this conversation as resolved.
});

describe('getWorkflowIconName', () => {
Expand Down
25 changes: 22 additions & 3 deletions packages/web/src/lib/workflow-metadata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,9 +119,18 @@ export const CATEGORIES: WorkflowCategory[] = [

/**
* Derive a category from the workflow name and description.
* Uses word-boundary checks for short tokens to avoid false positives.
* If the workflow defines an explicit `category` in YAML that matches a known
* category, it is used directly. Otherwise, the category is inferred from keywords.
*/
export function getWorkflowCategory(name: string, description: string): WorkflowCategory {
export function getWorkflowCategory(
name: string,
description: string,
explicitCategory?: string
): WorkflowCategory {
if (explicitCategory && CATEGORIES.includes(explicitCategory as WorkflowCategory)) {
return explicitCategory as WorkflowCategory;
}

const lower = `${name} ${description}`.toLowerCase();

// Code Review
Expand Down Expand Up @@ -163,8 +172,18 @@ export function getWorkflowCategory(name: string, description: string): Workflow

/**
* Derive tags from the workflow name and parsed description.
* If the workflow defines explicit `tags` in YAML, those are used directly.
* Otherwise, tags are inferred from keywords in the name and description.
*/
export function getWorkflowTags(name: string, parsed: ParsedDescription): string[] {
export function getWorkflowTags(
name: string,
parsed: ParsedDescription,
explicitTags?: string[]
): string[] {
if (explicitTags && explicitTags.length > 0) {
return [...new Set(explicitTags)];
}
Comment thread
lraphael marked this conversation as resolved.
Outdated

const tags: string[] = [];
const text = `${name} ${parsed.raw}`.toLowerCase();

Expand Down
2 changes: 2 additions & 0 deletions packages/workflows/src/schemas/workflow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ export const workflowBaseSchema = z.object({
fallbackModel: z.string().min(1).optional(),
betas: z.array(z.string().min(1)).nonempty("'betas' must be a non-empty array").optional(),
sandbox: sandboxSettingsSchema.optional(),
tags: z.array(z.string().min(1)).optional(),
category: z.string().min(1).optional(),
Comment thread
coderabbitai[bot] marked this conversation as resolved.
});

export type WorkflowBase = z.infer<typeof workflowBaseSchema>;
Expand Down