diff --git a/CLAUDE.md b/CLAUDE.md index 6c08a6bd..3e8e8ed0 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -240,6 +240,70 @@ When `reviewTrigger` is absent, the system falls back to legacy booleans: - `reviewRequested` → `onReviewRequested` (default `false`) - `externalPrs` always `false` in legacy mode (no legacy equivalent) +### PM Agent Trigger Modes + +Briefing, planning, and implementation agents each have independent toggles for their PM triggers. **All modes default to `true`** for backward compatibility. + +#### Trello card-moved triggers + +| Flag | Description | +|------|-------------| +| `cardMovedToBriefing` | Trigger briefing agent when a card is moved to the Briefing list | +| `cardMovedToPlanning` | Trigger planning agent when a card is moved to the Planning list | +| `cardMovedToTodo` | Trigger implementation agent when a card is moved to the Todo list | + +#### JIRA issue-transitioned triggers (per-agent) + +The `issueTransitioned` field supports both a legacy boolean (applies to all agents) and a nested per-agent object: + +| Agent | Field | Description | +|-------|-------|-------------| +| briefing | `issueTransitioned.briefing` | Trigger briefing when issue transitions to Briefing status | +| planning | `issueTransitioned.planning` | Trigger planning when issue transitions to Planning status | +| implementation | `issueTransitioned.implementation` | Trigger implementation when issue transitions to Todo status | + +#### Setting via CLI + +```bash +# Disable Trello card-moved trigger for briefing agent +cascade projects pm-trigger-set --no-card-moved-to-briefing + +# Disable JIRA issue-transitioned for implementation agent only +cascade projects pm-trigger-set --no-issue-transitioned-implementation + +# Enable JIRA triggers for briefing and planning, disable for implementation +cascade projects pm-trigger-set \ + --issue-transitioned-briefing \ + --issue-transitioned-planning \ + --no-issue-transitioned-implementation + +# Disable all Trello card-moved triggers +cascade projects pm-trigger-set \ + --no-card-moved-to-briefing \ + --no-card-moved-to-planning \ + --no-card-moved-to-todo +``` + +#### Setting via Dashboard + +In the **Agent Configs** tab, the briefing, planning, and implementation agent sections each show: +- **Card moved to [list]** — Trello card-moved toggle (Trello projects only) +- **Issue Transitioned** — JIRA per-agent transition toggle (JIRA projects only) +- **Ready to Process label** — label-based trigger toggle + +#### Direct JSON Config + +```bash +# Disable JIRA issue-transitioned for implementation only +cascade projects integration-set \ + --category pm --provider jira --config '{"projectKey":"PROJ","statuses":{...}}' \ + --triggers '{"issueTransitioned":{"briefing":true,"planning":true,"implementation":false}}' +``` + +#### Backward Compatibility + +The legacy `issueTransitioned: true/false` boolean is still supported — it applies to all agents uniformly. + ## Claude Code Backend CASCADE supports using Claude Code SDK as an alternative agent backend. Configure per-project: diff --git a/src/cli/dashboard/projects/pm-trigger-set.ts b/src/cli/dashboard/projects/pm-trigger-set.ts new file mode 100644 index 00000000..b56709ce --- /dev/null +++ b/src/cli/dashboard/projects/pm-trigger-set.ts @@ -0,0 +1,196 @@ +import { Args, Flags } from '@oclif/core'; +import { DashboardCommand } from '../_shared/base.js'; + +/** + * CLI command for configuring PM trigger modes per agent type. + * + * Usage: + * cascade projects pm-trigger-set [--card-moved-to-briefing] [--issue-transitioned-briefing] ... + * + * At least one flag must be provided. Pass `--no-` to disable a mode. + * Uses the `projects.integrations.updateTriggers` tRPC endpoint, updating the + * PM integration triggers config for the project. + * + * Trello flags update the top-level boolean keys (cardMovedToBriefing, etc.). + * JIRA flags update the nested `issueTransitioned` object per agent type. + */ +export default class ProjectsPmTriggerSet extends DashboardCommand { + static override description = + 'Configure PM trigger modes per agent type (card-moved for Trello, issue-transitioned for JIRA).'; + + static override aliases = ['projects:pm-trigger-set']; + + static override args = { + id: Args.string({ description: 'Project ID', required: true }), + }; + + static override flags = { + ...DashboardCommand.baseFlags, + // Trello card-moved triggers + 'card-moved-to-briefing': Flags.boolean({ + description: 'Enable briefing agent when a card is moved to the Briefing list (Trello).', + allowNo: true, + default: undefined, + }), + 'card-moved-to-planning': Flags.boolean({ + description: 'Enable planning agent when a card is moved to the Planning list (Trello).', + allowNo: true, + default: undefined, + }), + 'card-moved-to-todo': Flags.boolean({ + description: 'Enable implementation agent when a card is moved to the Todo list (Trello).', + allowNo: true, + default: undefined, + }), + // JIRA issue-transitioned triggers (per-agent) + 'issue-transitioned-briefing': Flags.boolean({ + description: + 'Enable briefing agent when a JIRA issue transitions to the configured Briefing status.', + allowNo: true, + default: undefined, + }), + 'issue-transitioned-planning': Flags.boolean({ + description: + 'Enable planning agent when a JIRA issue transitions to the configured Planning status.', + allowNo: true, + default: undefined, + }), + 'issue-transitioned-implementation': Flags.boolean({ + description: + 'Enable implementation agent when a JIRA issue transitions to the configured Todo status.', + allowNo: true, + default: undefined, + }), + }; + + /** Build the triggers patch object from parsed flag values. */ + private buildTriggers(parsedFlags: { + cardMovedToBriefing: boolean | undefined; + cardMovedToPlanning: boolean | undefined; + cardMovedToTodo: boolean | undefined; + issueTransitionedBriefing: boolean | undefined; + issueTransitionedPlanning: boolean | undefined; + issueTransitionedImplementation: boolean | undefined; + }): Record> { + const { + cardMovedToBriefing, + cardMovedToPlanning, + cardMovedToTodo, + issueTransitionedBriefing, + issueTransitionedPlanning, + issueTransitionedImplementation, + } = parsedFlags; + + const triggers: Record> = {}; + + if (cardMovedToBriefing !== undefined) triggers.cardMovedToBriefing = cardMovedToBriefing; + if (cardMovedToPlanning !== undefined) triggers.cardMovedToPlanning = cardMovedToPlanning; + if (cardMovedToTodo !== undefined) triggers.cardMovedToTodo = cardMovedToTodo; + + const issueTransitioned: Record = {}; + if (issueTransitionedBriefing !== undefined) + issueTransitioned.briefing = issueTransitionedBriefing; + if (issueTransitionedPlanning !== undefined) + issueTransitioned.planning = issueTransitionedPlanning; + if (issueTransitionedImplementation !== undefined) + issueTransitioned.implementation = issueTransitionedImplementation; + + if (Object.keys(issueTransitioned).length > 0) { + triggers.issueTransitioned = issueTransitioned; + } + + return triggers; + } + + /** Format a human-readable summary of changed triggers. */ + private formatOutput( + projectId: string, + parsedFlags: { + cardMovedToBriefing: boolean | undefined; + cardMovedToPlanning: boolean | undefined; + cardMovedToTodo: boolean | undefined; + issueTransitionedBriefing: boolean | undefined; + issueTransitionedPlanning: boolean | undefined; + issueTransitionedImplementation: boolean | undefined; + }, + ): string { + const { + cardMovedToBriefing, + cardMovedToPlanning, + cardMovedToTodo, + issueTransitionedBriefing, + issueTransitionedPlanning, + issueTransitionedImplementation, + } = parsedFlags; + + const lines: string[] = [`PM trigger modes updated for project: ${projectId}`]; + if (cardMovedToBriefing !== undefined) + lines.push(` cardMovedToBriefing: ${cardMovedToBriefing}`); + if (cardMovedToPlanning !== undefined) + lines.push(` cardMovedToPlanning: ${cardMovedToPlanning}`); + if (cardMovedToTodo !== undefined) lines.push(` cardMovedToTodo: ${cardMovedToTodo}`); + if (issueTransitionedBriefing !== undefined) + lines.push(` issueTransitioned.briefing: ${issueTransitionedBriefing}`); + if (issueTransitionedPlanning !== undefined) + lines.push(` issueTransitioned.planning: ${issueTransitionedPlanning}`); + if (issueTransitionedImplementation !== undefined) + lines.push(` issueTransitioned.implementation: ${issueTransitionedImplementation}`); + return lines.join('\n'); + } + + async run(): Promise { + const { args, flags } = await this.parse(ProjectsPmTriggerSet); + + const cardMovedToBriefing = flags['card-moved-to-briefing']; + const cardMovedToPlanning = flags['card-moved-to-planning']; + const cardMovedToTodo = flags['card-moved-to-todo']; + const issueTransitionedBriefing = flags['issue-transitioned-briefing']; + const issueTransitionedPlanning = flags['issue-transitioned-planning']; + const issueTransitionedImplementation = flags['issue-transitioned-implementation']; + + const hasAnyFlag = + cardMovedToBriefing !== undefined || + cardMovedToPlanning !== undefined || + cardMovedToTodo !== undefined || + issueTransitionedBriefing !== undefined || + issueTransitionedPlanning !== undefined || + issueTransitionedImplementation !== undefined; + + if (!hasAnyFlag) { + this.error( + 'At least one flag must be provided: ' + + '--card-moved-to-briefing, --card-moved-to-planning, --card-moved-to-todo, ' + + '--issue-transitioned-briefing, --issue-transitioned-planning, --issue-transitioned-implementation ' + + '(use --no- to disable).', + ); + } + + const parsedFlags = { + cardMovedToBriefing, + cardMovedToPlanning, + cardMovedToTodo, + issueTransitionedBriefing, + issueTransitionedPlanning, + issueTransitionedImplementation, + }; + + const triggers = this.buildTriggers(parsedFlags); + + try { + await this.client.projects.integrations.updateTriggers.mutate({ + projectId: args.id, + category: 'pm', + triggers, + }); + + if (flags.json) { + this.outputJson({ ok: true, triggers }); + return; + } + + this.log(this.formatOutput(args.id, parsedFlags)); + } catch (err) { + this.handleError(err); + } + } +} diff --git a/src/config/triggerConfig.ts b/src/config/triggerConfig.ts index e9a1f0a8..a95e320f 100644 --- a/src/config/triggerConfig.ts +++ b/src/config/triggerConfig.ts @@ -33,12 +33,29 @@ export const TrelloTriggerConfigSchema = z.object({ commentMention: z.boolean().default(true), }); +/** + * Per-agent issue-transitioned configuration for JIRA. + * Each agent type can independently toggle whether the issue-transitioned trigger fires for it. + */ +export const IssueTransitionedSchema = z + .union([ + z.boolean(), + z.object({ + briefing: z.boolean().default(true), + planning: z.boolean().default(true), + implementation: z.boolean().default(true), + }), + ]) + .optional(); + +export type IssueTransitionedConfig = z.infer; + /** * Trigger configuration for JIRA integrations. * All triggers default to `true` for backward compatibility. */ export const JiraTriggerConfigSchema = z.object({ - issueTransitioned: z.boolean().default(true), + issueTransitioned: IssueTransitionedSchema, readyToProcessLabel: ReadyToProcessLabelSchema, commentMention: z.boolean().default(true), }); @@ -170,6 +187,30 @@ export function resolveReadyToProcessEnabled( return true; } +/** + * Resolve whether the issue-transitioned trigger is enabled for a specific agent type. + * Supports both the new nested object format and the legacy boolean format. + * Returns `true` when no config is present (backward compatible). + */ +export function resolveIssueTransitionedEnabled( + config: Partial | undefined, + agentType: string, +): boolean { + if (!config) return true; + const it = config.issueTransitioned as IssueTransitionedConfig; + if (it === undefined) return true; + if (typeof it === 'boolean') { + // Legacy: boolean applies to all agents + return it; + } + // Nested object: check per-agent toggle + if (agentType === 'briefing') return it.briefing ?? true; + if (agentType === 'planning') return it.planning ?? true; + if (agentType === 'implementation') return it.implementation ?? true; + // Unknown agent type — default to enabled + return true; +} + /** * Resolve whether a JIRA trigger is enabled based on project trigger config. * Returns `true` (enabled) when no config is present (backward compatible). @@ -186,6 +227,13 @@ export function resolveJiraTriggerEnabled( if (typeof rtp === 'boolean') return rtp; return rtp.briefing || rtp.planning || rtp.implementation; } + if (key === 'issueTransitioned') { + const it = value as IssueTransitionedConfig; + if (it === undefined) return true; + if (typeof it === 'boolean') return it; + // Object form: enabled if any agent is enabled + return it.briefing || it.planning || it.implementation; + } return value === undefined ? true : (value as boolean); } diff --git a/src/triggers/jira/issue-transitioned.ts b/src/triggers/jira/issue-transitioned.ts index 1c3d647c..7d5fea27 100644 --- a/src/triggers/jira/issue-transitioned.ts +++ b/src/triggers/jira/issue-transitioned.ts @@ -5,7 +5,10 @@ * a CASCADE agent type (briefing, planning, implementation). */ -import { resolveJiraTriggerEnabled } from '../../config/triggerConfig.js'; +import { + resolveIssueTransitionedEnabled, + resolveJiraTriggerEnabled, +} from '../../config/triggerConfig.js'; import { getJiraConfig } from '../../pm/config.js'; import type { TriggerContext, TriggerHandler, TriggerResult } from '../../types/index.js'; import { logger } from '../../utils/logging.js'; @@ -121,6 +124,15 @@ export class JiraIssueTransitionedTrigger implements TriggerHandler { return null; } + // Check per-agent toggle for issueTransitioned + if (!resolveIssueTransitionedEnabled(jiraConfig?.triggers, agentType)) { + logger.debug('JIRA issue-transitioned trigger disabled for agent', { + issueKey, + agentType, + }); + return null; + } + logger.info('JIRA issue transitioned to agent-triggering status', { issueKey, fromStatus: statusChange.fromString, diff --git a/tests/unit/config/projects.test.ts b/tests/unit/config/projects.test.ts index e7375d81..b1f493f7 100644 --- a/tests/unit/config/projects.test.ts +++ b/tests/unit/config/projects.test.ts @@ -170,6 +170,15 @@ describe('config provider', () => { }); describe('getIntegrationCredential', () => { + // These tests go through getIntegrationCredentialOrNull which checks process.env first. + // Use vi.stubEnv to prevent any env vars from shadowing the DB mock. + beforeEach(() => { + vi.stubEnv('TRELLO_API_KEY', ''); + }); + afterEach(() => { + vi.unstubAllEnvs(); + }); + it('resolves credential from DB', async () => { vi.mocked(resolveIntegrationCredential).mockResolvedValue('db-secret-value'); @@ -187,6 +196,14 @@ describe('config provider', () => { }); describe('getIntegrationCredentialOrNull', () => { + // Clear any env vars that might shadow the mock (implementer_token maps to GITHUB_TOKEN_IMPLEMENTER). + beforeEach(() => { + vi.stubEnv('GITHUB_TOKEN_IMPLEMENTER', ''); + }); + afterEach(() => { + vi.unstubAllEnvs(); + }); + it('returns credential value when found', async () => { vi.mocked(resolveIntegrationCredential).mockResolvedValue('secret-value'); @@ -227,6 +244,15 @@ describe('config provider', () => { }); describe('getProjectGitHubToken', () => { + // getProjectGitHubToken calls getIntegrationCredentialOrNull which checks process.env first. + // Use vi.stubEnv to prevent the env var from shadowing the mock. + beforeEach(() => { + vi.stubEnv('GITHUB_TOKEN_IMPLEMENTER', ''); + }); + afterEach(() => { + vi.unstubAllEnvs(); + }); + it('returns implementer token when available', async () => { vi.mocked(resolveIntegrationCredential).mockResolvedValue('implementer-token'); diff --git a/tests/unit/config/triggerConfig.test.ts b/tests/unit/config/triggerConfig.test.ts index 0b912414..78993637 100644 --- a/tests/unit/config/triggerConfig.test.ts +++ b/tests/unit/config/triggerConfig.test.ts @@ -4,6 +4,7 @@ import { JiraTriggerConfigSchema, TrelloTriggerConfigSchema, resolveGitHubTriggerEnabled, + resolveIssueTransitionedEnabled, resolveJiraTriggerEnabled, resolveReadyToProcessEnabled, resolveReviewTriggerConfig, @@ -46,14 +47,28 @@ describe('TrelloTriggerConfigSchema', () => { }); describe('JiraTriggerConfigSchema', () => { - it('defaults boolean fields to true, readyToProcessLabel optional', () => { + it('defaults commentMention to true, issueTransitioned and readyToProcessLabel optional', () => { const result = JiraTriggerConfigSchema.parse({}); - expect(result).toEqual({ - issueTransitioned: true, - commentMention: true, - }); + expect(result.commentMention).toBe(true); + expect(result.issueTransitioned).toBeUndefined(); expect(result.readyToProcessLabel).toBeUndefined(); }); + + it('accepts legacy boolean issueTransitioned', () => { + const result = JiraTriggerConfigSchema.parse({ issueTransitioned: false }); + expect(result.issueTransitioned).toBe(false); + }); + + it('accepts per-agent issueTransitioned object', () => { + const result = JiraTriggerConfigSchema.parse({ + issueTransitioned: { briefing: true, planning: false, implementation: true }, + }); + expect(result.issueTransitioned).toEqual({ + briefing: true, + planning: false, + implementation: true, + }); + }); }); describe('GitHubTriggerConfigSchema', () => { @@ -145,7 +160,7 @@ describe('resolveJiraTriggerEnabled', () => { expect(resolveJiraTriggerEnabled(undefined, 'commentMention')).toBe(true); }); - it('returns false when key is explicitly disabled', () => { + it('returns false when issueTransitioned is explicitly false (legacy boolean)', () => { expect(resolveJiraTriggerEnabled({ issueTransitioned: false }, 'issueTransitioned')).toBe( false, ); @@ -154,6 +169,24 @@ describe('resolveJiraTriggerEnabled', () => { it('returns true when config is empty (no explicit settings)', () => { expect(resolveJiraTriggerEnabled({}, 'issueTransitioned')).toBe(true); }); + + it('returns true for issueTransitioned object when any agent is enabled', () => { + expect( + resolveJiraTriggerEnabled( + { issueTransitioned: { briefing: false, planning: true, implementation: false } }, + 'issueTransitioned', + ), + ).toBe(true); + }); + + it('returns false for issueTransitioned object when all agents disabled', () => { + expect( + resolveJiraTriggerEnabled( + { issueTransitioned: { briefing: false, planning: false, implementation: false } }, + 'issueTransitioned', + ), + ).toBe(false); + }); }); describe('resolveGitHubTriggerEnabled', () => { @@ -230,6 +263,48 @@ describe('resolveReadyToProcessEnabled', () => { }); }); +describe('resolveIssueTransitionedEnabled', () => { + it('returns true when config is undefined (backward compatible)', () => { + expect(resolveIssueTransitionedEnabled(undefined, 'briefing')).toBe(true); + expect(resolveIssueTransitionedEnabled(undefined, 'planning')).toBe(true); + expect(resolveIssueTransitionedEnabled(undefined, 'implementation')).toBe(true); + }); + + it('returns true when issueTransitioned is not set', () => { + expect(resolveIssueTransitionedEnabled({}, 'briefing')).toBe(true); + }); + + it('applies legacy boolean true to all agents', () => { + const config = { issueTransitioned: true as const }; + expect(resolveIssueTransitionedEnabled(config, 'briefing')).toBe(true); + expect(resolveIssueTransitionedEnabled(config, 'planning')).toBe(true); + expect(resolveIssueTransitionedEnabled(config, 'implementation')).toBe(true); + }); + + it('applies legacy boolean false to all agents', () => { + const config = { issueTransitioned: false as const }; + expect(resolveIssueTransitionedEnabled(config, 'briefing')).toBe(false); + expect(resolveIssueTransitionedEnabled(config, 'planning')).toBe(false); + expect(resolveIssueTransitionedEnabled(config, 'implementation')).toBe(false); + }); + + it('returns per-agent value from nested object', () => { + const config = { + issueTransitioned: { briefing: true, planning: false, implementation: true }, + }; + expect(resolveIssueTransitionedEnabled(config, 'briefing')).toBe(true); + expect(resolveIssueTransitionedEnabled(config, 'planning')).toBe(false); + expect(resolveIssueTransitionedEnabled(config, 'implementation')).toBe(true); + }); + + it('defaults to true for unknown agent types', () => { + const config = { + issueTransitioned: { briefing: false, planning: false, implementation: false }, + }; + expect(resolveIssueTransitionedEnabled(config, 'unknown-agent')).toBe(true); + }); +}); + describe('resolveReviewTriggerConfig', () => { it('maps legacy defaults when config is undefined (backward compatible)', () => { // No config → legacy fallback: checkSuiteSuccess defaults to true → ownPrsOnly=true diff --git a/tests/unit/triggers/jira-issue-transitioned.test.ts b/tests/unit/triggers/jira-issue-transitioned.test.ts index d9a2efe0..dc962132 100644 --- a/tests/unit/triggers/jira-issue-transitioned.test.ts +++ b/tests/unit/triggers/jira-issue-transitioned.test.ts @@ -36,9 +36,15 @@ function buildCtx( issueKey?: string; statusChangeItems?: Array<{ field?: string; fromString?: string; toString?: string }>; noJiraConfig?: boolean; + triggers?: Record; } = {}, ): TriggerContext { - const project = overrides.noJiraConfig ? { ...mockProject, jira: undefined } : mockProject; + const baseJira = overrides.triggers + ? { ...mockProject.jira, triggers: overrides.triggers } + : mockProject.jira; + const project = overrides.noJiraConfig + ? { ...mockProject, jira: undefined } + : { ...mockProject, jira: baseJira }; return { project: project as TriggerContext['project'], @@ -238,5 +244,81 @@ describe('JiraIssueTransitionedTrigger', () => { expect(result).toBeNull(); }); + + describe('per-agent issueTransitioned toggle', () => { + it('fires when issueTransitioned toggle is true for agent (legacy boolean)', async () => { + const ctx = buildCtx({ + statusChangeItems: [{ field: 'status', fromString: 'Backlog', toString: 'Briefing' }], + triggers: { issueTransitioned: true }, + }); + + const result = await trigger.handle(ctx); + + expect(result?.agentType).toBe('briefing'); + }); + + it('returns null when issueTransitioned disabled globally (legacy boolean false)', async () => { + const ctx = buildCtx({ + statusChangeItems: [{ field: 'status', fromString: 'Backlog', toString: 'Briefing' }], + triggers: { issueTransitioned: false }, + }); + + const result = await trigger.handle(ctx); + + expect(result).toBeNull(); + }); + + it('fires when per-agent issueTransitioned.briefing is enabled', async () => { + const ctx = buildCtx({ + statusChangeItems: [{ field: 'status', fromString: 'Backlog', toString: 'Briefing' }], + triggers: { + issueTransitioned: { briefing: true, planning: false, implementation: false }, + }, + }); + + const result = await trigger.handle(ctx); + + expect(result?.agentType).toBe('briefing'); + }); + + it('returns null when per-agent issueTransitioned.briefing is disabled', async () => { + const ctx = buildCtx({ + statusChangeItems: [{ field: 'status', fromString: 'Backlog', toString: 'Briefing' }], + triggers: { + issueTransitioned: { briefing: false, planning: true, implementation: true }, + }, + }); + + const result = await trigger.handle(ctx); + + expect(result).toBeNull(); + }); + + it('fires planning agent when issueTransitioned.planning is enabled', async () => { + const ctx = buildCtx({ + statusChangeItems: [{ field: 'status', fromString: 'Briefing', toString: 'Planning' }], + triggers: { + issueTransitioned: { briefing: false, planning: true, implementation: false }, + }, + }); + + const result = await trigger.handle(ctx); + + expect(result?.agentType).toBe('planning'); + }); + + it('returns null when per-agent issueTransitioned.implementation is disabled', async () => { + const ctx = buildCtx({ + statusChangeItems: [{ field: 'status', fromString: 'Planning', toString: 'To Do' }], + triggers: { + issueTransitioned: { briefing: true, planning: true, implementation: false }, + }, + }); + + const result = await trigger.handle(ctx); + + expect(result).toBeNull(); + }); + }); }); }); diff --git a/web/src/lib/trigger-agent-mapping.ts b/web/src/lib/trigger-agent-mapping.ts index 6dd6a810..b7cdda9c 100644 --- a/web/src/lib/trigger-agent-mapping.ts +++ b/web/src/lib/trigger-agent-mapping.ts @@ -44,15 +44,6 @@ export const LIFECYCLE_TRIGGERS: TriggerDef[] = [ * Displayed once in a dedicated section rather than duplicated per-agent. */ export const SHARED_PM_TRIGGERS: TriggerDef[] = [ - { - key: 'issueTransitioned', - label: 'Issue Transitioned', - description: - 'Trigger agent when a JIRA issue transitions to a configured status. Affects briefing, planning, and implementation agents.', - defaultValue: true, - pmProvider: 'jira', - category: 'pm', - }, { key: 'commentMention', label: 'Comment @mention', @@ -76,6 +67,15 @@ export const AGENT_TRIGGER_MAP: Record = { pmProvider: 'trello', category: 'pm', }, + { + key: 'issueTransitioned.briefing', + label: 'Issue Transitioned', + description: + 'Trigger briefing agent when a JIRA issue transitions to the configured Briefing status.', + defaultValue: true, + pmProvider: 'jira', + category: 'pm', + }, { key: 'readyToProcessLabel.briefing', label: 'Ready to Process label', @@ -94,6 +94,15 @@ export const AGENT_TRIGGER_MAP: Record = { pmProvider: 'trello', category: 'pm', }, + { + key: 'issueTransitioned.planning', + label: 'Issue Transitioned', + description: + 'Trigger planning agent when a JIRA issue transitions to the configured Planning status.', + defaultValue: true, + pmProvider: 'jira', + category: 'pm', + }, { key: 'readyToProcessLabel.planning', label: 'Ready to Process label', @@ -112,6 +121,15 @@ export const AGENT_TRIGGER_MAP: Record = { pmProvider: 'trello', category: 'pm', }, + { + key: 'issueTransitioned.implementation', + label: 'Issue Transitioned', + description: + 'Trigger implementation agent when a JIRA issue transitions to the configured Todo status.', + defaultValue: true, + pmProvider: 'jira', + category: 'pm', + }, { key: 'readyToProcessLabel.implementation', label: 'Ready to Process label',