diff --git a/web/frontend/src/__tests__/execution-mode-unification.test.ts b/web/frontend/src/__tests__/execution-mode-unification.test.ts
new file mode 100644
index 0000000..3beec72
--- /dev/null
+++ b/web/frontend/src/__tests__/execution-mode-unification.test.ts
@@ -0,0 +1,293 @@
+/**
+ * KIN-FIX-002: Унифицировать localStorage значения execution_mode с 'auto_complete'
+ *
+ * Acceptance Criteria:
+ * 1. Строки 46 и 53 в TaskDetail.vue содержат 'auto_complete' в localStorage операциях
+ * 2. Все вхождения режимов execution_mode используют 'auto_complete', не 'auto'
+ * 3. Grep по всему frontend не находит standalone 'auto' как значение execution_mode
+ * 4. Существующие тесты filter-persistence.test.ts пройдены успешно
+ */
+
+import { describe, it, expect, vi, beforeEach } from 'vitest'
+import { mount, flushPromises } from '@vue/test-utils'
+import { createRouter, createMemoryHistory } from 'vue-router'
+import ProjectView from '../views/ProjectView.vue'
+import TaskDetail from '../views/TaskDetail.vue'
+
+// Mock api
+vi.mock('../api', () => ({
+ api: {
+ project: vi.fn(),
+ taskFull: vi.fn(),
+ patchTask: vi.fn(),
+ patchProject: vi.fn(),
+ },
+}))
+
+import { api } from '../api'
+
+const Stub = { template: '
' }
+
+const MOCK_PROJECT = {
+ id: 'KIN',
+ name: 'Kin',
+ path: '/projects/kin',
+ status: 'active',
+ priority: 5,
+ tech_stack: ['python', 'vue'],
+ created_at: '2024-01-01',
+ total_tasks: 1,
+ done_tasks: 0,
+ active_tasks: 1,
+ blocked_tasks: 0,
+ review_tasks: 0,
+ tasks: [],
+ decisions: [],
+ modules: [],
+}
+
+const localStorageMock = (() => {
+ let store: Record = {}
+ return {
+ getItem: (k: string) => store[k] ?? null,
+ setItem: (k: string, v: string) => { store[k] = v },
+ removeItem: (k: string) => { delete store[k] },
+ clear: () => { store = {} },
+ }
+})()
+Object.defineProperty(globalThis, 'localStorage', { value: localStorageMock, configurable: true })
+
+function makeRouter() {
+ return createRouter({
+ history: createMemoryHistory(),
+ routes: [
+ { path: '/', component: Stub },
+ { path: '/project/:id', component: ProjectView, props: true },
+ { path: '/task/:id', component: TaskDetail, props: true },
+ ],
+ })
+}
+
+beforeEach(() => {
+ localStorageMock.clear()
+ vi.mocked(api.project).mockResolvedValue(MOCK_PROJECT as any)
+})
+
+describe('KIN-FIX-002: execution_mode унификация на "auto_complete"', () => {
+ describe('TaskDetail.vue — localStorage операции (lines 46, 53)', () => {
+ it('toggleMode в TaskDetail сохраняет "auto_complete" в localStorage', async () => {
+ const task = {
+ id: 'KIN-001',
+ project_id: 'KIN',
+ title: 'Test Task',
+ status: 'pending',
+ priority: 5,
+ assigned_role: null,
+ parent_task_id: null,
+ brief: null,
+ spec: null,
+ execution_mode: null,
+ created_at: '2024-01-01',
+ updated_at: '2024-01-01',
+ pipeline_steps: [],
+ related_decisions: [],
+ }
+ vi.mocked(api.taskFull).mockResolvedValue(task as any)
+ vi.mocked(api.patchTask).mockResolvedValue({ execution_mode: 'auto_complete' } as any)
+
+ const router = makeRouter()
+ await router.push('/task/KIN-001')
+
+ const wrapper = mount(TaskDetail, {
+ props: { id: 'KIN-001' },
+ global: { plugins: [router] },
+ })
+ await flushPromises()
+
+ // Найти и кликнуть кнопку тоггла режима (Auto/Review)
+ const toggleBtn = wrapper.findAll('button').find(b =>
+ b.text().includes('Auto') || b.text().includes('Review')
+ )
+
+ if (toggleBtn) {
+ await toggleBtn.trigger('click')
+ await flushPromises()
+
+ // Проверяем, что localStorage содержит 'auto_complete', не 'auto'
+ const stored = localStorageMock.getItem('kin-mode-KIN')
+ expect(stored, 'localStorage должен содержать "auto_complete"').toBe('auto_complete')
+ }
+ })
+
+ it('loadMode в TaskDetail использует "auto_complete" при чтении из localStorage', async () => {
+ // Сначала установим значение в localStorage
+ localStorageMock.setItem('kin-mode-KIN', 'auto_complete')
+
+ const task = {
+ id: 'KIN-001',
+ project_id: 'KIN',
+ title: 'Test Task',
+ status: 'pending',
+ priority: 5,
+ assigned_role: null,
+ parent_task_id: null,
+ brief: null,
+ spec: null,
+ execution_mode: null,
+ created_at: '2024-01-01',
+ updated_at: '2024-01-01',
+ pipeline_steps: [],
+ related_decisions: [],
+ }
+ vi.mocked(api.taskFull).mockResolvedValue(task as any)
+
+ const router = makeRouter()
+ await router.push('/task/KIN-001')
+
+ const wrapper = mount(TaskDetail, {
+ props: { id: 'KIN-001' },
+ global: { plugins: [router] },
+ })
+ await flushPromises()
+
+ // Проверяем, что значение из localStorage прочитано как 'auto_complete'
+ const stored = localStorageMock.getItem('kin-mode-KIN')
+ expect(stored).toBe('auto_complete')
+ })
+ })
+
+ describe('ProjectView.vue — localStorage операции (lines 171, 173, 179, 181, 182)', () => {
+ it('toggleMode в ProjectView сохраняет "auto_complete" в localStorage', async () => {
+ vi.mocked(api.patchProject).mockResolvedValue({ execution_mode: 'auto_complete' } as any)
+
+ const router = makeRouter()
+ await router.push('/project/KIN')
+
+ const wrapper = mount(ProjectView, {
+ props: { id: 'KIN' },
+ global: { plugins: [router] },
+ })
+ await flushPromises()
+
+ // Найти и кликнуть кнопку тоггла режима
+ const toggleBtn = wrapper.findAll('button').find(b =>
+ b.text().includes('Auto') || b.text().includes('Review')
+ )
+
+ if (toggleBtn) {
+ await toggleBtn.trigger('click')
+ await flushPromises()
+
+ // Проверяем, что localStorage содержит 'auto_complete', не 'auto'
+ const stored = localStorageMock.getItem('kin-mode-KIN')
+ expect(stored, 'localStorage должен содержать "auto_complete" в ProjectView').toBe('auto_complete')
+ }
+ })
+
+ it('loadMode в ProjectView использует "auto_complete" при чтении из localStorage', async () => {
+ // Установим значение в localStorage
+ localStorageMock.setItem('kin-mode-KIN', 'auto_complete')
+
+ const router = makeRouter()
+ await router.push('/project/KIN')
+
+ const wrapper = mount(ProjectView, {
+ props: { id: 'KIN' },
+ global: { plugins: [router] },
+ })
+ await flushPromises()
+
+ // Проверяем, что значение из localStorage прочитано корректно
+ const stored = localStorageMock.getItem('kin-mode-KIN')
+ expect(stored).toBe('auto_complete')
+ })
+ })
+
+ describe('Унификация: все значения используют "auto_complete"', () => {
+ it('execution_mode никогда не использует standalone "auto"', async () => {
+ // Проверяем, что при сохранении режима, используется ТОЛЬКО 'auto_complete' или 'review'
+ const task = {
+ id: 'KIN-001',
+ project_id: 'KIN',
+ title: 'Test Task',
+ status: 'pending',
+ priority: 5,
+ assigned_role: null,
+ parent_task_id: null,
+ brief: null,
+ spec: null,
+ execution_mode: 'auto_complete',
+ created_at: '2024-01-01',
+ updated_at: '2024-01-01',
+ pipeline_steps: [],
+ related_decisions: [],
+ }
+ vi.mocked(api.taskFull).mockResolvedValue(task as any)
+ vi.mocked(api.patchTask).mockResolvedValue({ execution_mode: 'review' } as any)
+
+ const router = makeRouter()
+ await router.push('/task/KIN-001')
+
+ const wrapper = mount(TaskDetail, {
+ props: { id: 'KIN-001' },
+ global: { plugins: [router] },
+ })
+ await flushPromises()
+
+ // Переключаемся в режим review
+ const toggleBtn = wrapper.findAll('button').find(b =>
+ b.text().includes('Auto') || b.text().includes('Review')
+ )
+ if (toggleBtn) {
+ await toggleBtn.trigger('click')
+ await flushPromises()
+
+ const stored = localStorageMock.getItem('kin-mode-KIN')
+ // Проверяем, что используется только 'auto_complete' или 'review'
+ expect(
+ stored === 'auto_complete' || stored === 'review',
+ `localStorage должен содержать 'auto_complete' или 'review', получено: "${stored}"`
+ ).toBe(true)
+ // Проверяем, что НЕ используется 'auto'
+ expect(stored).not.toBe('auto')
+ }
+ })
+
+ it('Сравнение в коде используется "auto_complete", не "auto"', async () => {
+ // Установим 'auto_complete' и проверим, что компонент корректно определяет режим
+ localStorageMock.setItem('kin-mode-KIN', 'auto_complete')
+
+ const task = {
+ id: 'KIN-001',
+ project_id: 'KIN',
+ title: 'Test Task',
+ status: 'pending',
+ priority: 5,
+ assigned_role: null,
+ parent_task_id: null,
+ brief: null,
+ spec: null,
+ execution_mode: null,
+ created_at: '2024-01-01',
+ updated_at: '2024-01-01',
+ pipeline_steps: [],
+ related_decisions: [],
+ }
+ vi.mocked(api.taskFull).mockResolvedValue(task as any)
+
+ const router = makeRouter()
+ await router.push('/task/KIN-001')
+
+ const wrapper = mount(TaskDetail, {
+ props: { id: 'KIN-001' },
+ global: { plugins: [router] },
+ })
+ await flushPromises()
+
+ // После загрузки, компонент должен прочитать 'auto_complete' из localStorage
+ // и корректно применить режим (это видно по наличию или отсутствию кнопок Approve/Reject)
+ const stored = localStorageMock.getItem('kin-mode-KIN')
+ expect(stored).toBe('auto_complete')
+ })
+ })
+})
diff --git a/web/frontend/src/__tests__/kanban.test.ts b/web/frontend/src/__tests__/kanban.test.ts
index 9c613c5..bf79a4c 100644
--- a/web/frontend/src/__tests__/kanban.test.ts
+++ b/web/frontend/src/__tests__/kanban.test.ts
@@ -115,7 +115,8 @@ function makeRouter() {
beforeEach(() => {
localStorageMock.clear()
- vi.mocked(api.project).mockResolvedValue(MOCK_PROJECT as any)
+ vi.clearAllMocks()
+ vi.mocked(api.project).mockResolvedValue(JSON.parse(JSON.stringify(MOCK_PROJECT)) as any)
vi.mocked(api.getPhases).mockResolvedValue([])
vi.mocked(api.patchTask).mockResolvedValue(makeTask('KIN-001', 'in_progress') as any)
})