diff --git a/src/linear/format.test.ts b/src/linear/format.test.ts index 1db1bd96..5ed8c1ab 100644 --- a/src/linear/format.test.ts +++ b/src/linear/format.test.ts @@ -28,10 +28,23 @@ describe('linear/format', () => { heading: 'Task complete', date: new Date('2026-06-23T00:00:00Z'), }); - expect(body.startsWith('**Task complete**')).toBe(true); + expect(body.startsWith('🧭 **[OpenSwarm] Task complete**')).toBe(true); expect(body).toContain('_via OpenSwarm · 2026-06-23_'); }); + it('always starts with the daemon signature regardless of optional content', () => { + const inputs = [ + { heading: 'Minimal' }, + { heading: 'With summary', summary: 'Done.' }, + { heading: 'With sections', sections: [{ label: 'Result', body: 'Done.' }] }, + { heading: 'With meta', meta: { Run: '42' } }, + { heading: 'With all', summary: 'Done.', sections: [{ label: 'Result', body: 'Done.' }], meta: { Run: '42' } }, + ]; + for (const input of inputs) { + expect(formatAutomationComment(input)).toMatch(/^🧭 \*\*\[OpenSwarm\] /); + } + }); + it('keeps short facts inline and renders lists as bullets', () => { const body = formatAutomationComment({ heading: 'Revision requested', diff --git a/src/linear/format.ts b/src/linear/format.ts index 801379e0..6b73562d 100644 --- a/src/linear/format.ts +++ b/src/linear/format.ts @@ -138,7 +138,7 @@ export interface AutomationCommentInput { * carrying just enough trace to find the run later. */ export function formatAutomationComment(input: AutomationCommentInput): string { - const parts: string[] = [`**${input.heading}**`]; + const parts: string[] = [`🧭 **[OpenSwarm] ${input.heading}**`]; if (input.summary?.trim()) parts.push(input.summary.trim()); diff --git a/src/linear/linear.test.ts b/src/linear/linear.test.ts index d2179a04..6f1d7386 100644 --- a/src/linear/linear.test.ts +++ b/src/linear/linear.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it, vi } from 'vitest'; -import { addComment, clearLinearCache, createSubIssue, drainLinearConnection, effectCommentId, fetchIssuesForStates, getInProgressIssues, getNextBacklogIssue, initLinear, LINEAR_ACTIVE_ENRICH_CAP, LINEAR_ACTIVE_MAX_PAGES, LINEAR_ACTIVE_PAGE_SIZE, LINEAR_BACKLOG_PAGE_SIZE, LINEAR_RELATED_PAGE_SIZE, parseBlockerIdentifiers, populateBlockedBy, type LinearIssueInfo, type RawIssueNode } from './linear.js'; +import { addComment, clearLinearCache, createSubIssue, drainLinearConnection, effectCommentId, fetchIssuesForStates, getInProgressIssues, getNextBacklogIssue, initLinear, logBlocked, logWorkComplete, logWorkStart, LINEAR_ACTIVE_ENRICH_CAP, LINEAR_ACTIVE_MAX_PAGES, LINEAR_ACTIVE_PAGE_SIZE, LINEAR_BACKLOG_PAGE_SIZE, LINEAR_RELATED_PAGE_SIZE, parseBlockerIdentifiers, populateBlockedBy, type LinearIssueInfo, type RawIssueNode } from './linear.js'; import { LinearClient } from '@linear/sdk'; // createSubIssue reads the module-level client singleton (getClient()), set only @@ -402,3 +402,23 @@ describe('populateBlockedBy', () => { expect(result[0].blockedBy).toBeUndefined(); }); }); + +describe('state-change comment ordering', () => { + it.each([ + ['start', logWorkStart, ['issue-1', 'agent-1'], 'In Progress'], + ['complete', logWorkComplete, ['issue-1', 'agent-1', 'done'], 'Done'], + ['blocked', logBlocked, ['issue-1', 'agent-1', 'reason'], 'Todo'], + ])('%s comments before changing state', async (_label, action, args, expectedState) => { + const events: string[] = []; + const fakeClient = { + createComment: vi.fn(async () => { events.push('comment'); return { success: true }; }), + issue: vi.fn(async () => ({ team: Promise.resolve({ id: 'team-1' }) })), + team: vi.fn(async () => ({ states: async () => ({ nodes: [{ id: 'state-1', name: expectedState }] }) })), + updateIssue: vi.fn(async () => { events.push('state'); return { success: true }; }), + }; + vi.mocked(LinearClient).mockImplementation(function (this: unknown) { return fakeClient as never; } as never); + initLinear('fake-key', 'team-1'); + await (action as (...values: string[]) => Promise)(...(args as string[])); + expect(events).toEqual(['comment', 'state']); + }); +});