Skip to content

feat(issues): type field + operator create (#31, #14)#145

Merged
thebtf merged 3 commits into
mainfrom
feat/issue-types
Apr 12, 2026
Merged

feat(issues): type field + operator create (#31, #14)#145
thebtf merged 3 commits into
mainfrom
feat/issue-types

Conversation

@thebtf
Copy link
Copy Markdown
Owner

@thebtf thebtf commented Apr 12, 2026

Summary

Changes

  • Backend: migration, model, store, MCP tool, REST handlers (6 files)
  • Frontend: api.ts, IssuesView.vue, IssueDetailView.vue, useIssues.ts (4 files)
  • Plugin: lib.js injection format, version 3.7.8 (2 files)

Test plan

  • go build ./... clean
  • go test ./... -count=1 passes
  • npm run build passes
  • MCP: issues(action="create", type="bug") creates bug-type issue
  • Dashboard: "New Issue" button → form → create → appears in list
  • Dashboard: type filter shows only selected type
  • Dashboard: type badge visible with correct color
  • Session injection shows [BUG] [HIGH] format

Summary by CodeRabbit

  • Новые функции
    • Проблемы теперь имеют типы: ошибка, функция, улучшение, задача
    • Фильтрация проблем по типу в интерфейсе
    • Выбор типа при создании новых проблем
    • Редактирование типа существующих проблем
    • Отображение типа проблемы в списке и деталях

thebtf added 2 commits April 12, 2026 23:08
Backend:
- Migration 075: add type column (TEXT, default 'task') to issues table
- Issue model: Type field with validation (bug/feature/improvement/task)
- Store: CreateIssue validates type, UpdateIssueFields accepts type,
  ListIssuesEx filters by type
- MCP tool: type param in issues(action="create")
- REST: type in POST/PATCH body, GET query param

Dashboard:
- "New Issue" button + creation modal (title, body, type, priority, target_project)
- Type filter buttons (All/Bug/Feature/Improvement/Task)
- Type badge in issue list (colored: red/blue/green/gray)
- Type badge + edit in issue detail view

Hooks:
- formatIssuesBlock shows [TYPE] tag in session injection

Plugin v3.7.8.
Closes #31, #14.
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 12, 2026

Warning

Rate limit exceeded

@thebtf has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 7 minutes and 43 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 7 minutes and 43 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 184fb0df-4ada-46e6-911a-6032ec9d5f49

📥 Commits

Reviewing files that changed from the base of the PR and between df42b77 and 1e95fd5.

📒 Files selected for processing (5)
  • internal/db/gorm/migrations.go
  • internal/db/gorm/models.go
  • internal/worker/handlers_issues.go
  • ui/src/utils/api.ts
  • ui/src/views/IssuesView.vue

Пояснение

В этот pull request добавлена поддержка типов проблем (bug, feature, improvement, task) во всех слоях системы: базе данных, API обработчиках, пользовательском интерфейсе и плагине. Реализована фильтрация проблем по типу, возможность создания и редактирования типа проблемы с валидацией против разрешённого списка.

Изменения

Слой / Файл(ы) Краткое описание
База данных и модели
internal/db/gorm/models.go, internal/db/gorm/migrations.go
Добавлено поле Type в модель Issue с миграцией, создающей колонку type в таблице issues с типом TEXT и значением по умолчанию 'task'.
Слой персистентности
internal/db/gorm/issue_store.go
Добавлена валидация типа проблемы против allowlist ('bug', 'feature', 'improvement', 'task'), поддержка фильтрации по типу в ListIssuesEx, копирование типа при создании, обновление типа в UpdateIssueFields.
Обработчики API
internal/worker/handlers_issues.go, internal/mcp/tools_issues.go
Расширена обработка запросов для приёма, валидации и передачи типа проблемы через query параметры и JSON payload в создание, обновление и фильтрацию проблем.
Клиентский API слой
ui/src/utils/api.ts
Добавлены функции createIssue() и fetchTrackedProjects(), расширены функции fetchIssues() и updateIssue() для поддержки параметра типа; добавлено типизированное поле type в интерфейс Issue.
UI компоненты
ui/src/views/IssuesView.vue, ui/src/views/IssueDetailView.vue, ui/src/composables/useIssues.ts
Добавлены модальное окно создания проблем, фильтр по типу, редактирование типа в деталях проблемы, отображение badges типов, цветовая кодировка типов (bug/feature/improvement/task).
Плагин и версионирование
plugin/engram/.claude-plugin/plugin.json, plugin/engram/hooks/lib.js
Обновлена версия плагина с 3.7.7 на 3.7.8, расширен вывод списка проблем для отображения type tag в формате #<id> [<type>] [<prio>] ....

Диаграмма последовательности

sequenceDiagram
    participant UI as UI (IssuesView)
    participant API as API Handler<br/>(handlers_issues)
    participant Store as GORM Store<br/>(issue_store)
    participant DB as Database

    rect rgba(100, 150, 200, 0.5)
    Note over UI,DB: Создание проблемы с типом
    UI->>API: POST /issues (title, body, type, priority)
    API->>Store: CreateIssue(ctx, issue{Type, ...})
    Store->>Store: Валидация Type против<br/>allowlist
    Store->>DB: INSERT INTO issues<br/>(type='feature', ...)
    DB-->>Store: OK
    Store-->>API: issue{id, Type, ...}
    API-->>UI: {id, message}
    UI->>UI: Обновить список
    end

    rect rgba(150, 100, 200, 0.5)
    Note over UI,DB: Фильтрация проблем по типу
    UI->>API: GET /issues?type=bug
    API->>Store: ListIssuesEx(ctx,<br/>params{Type='bug'})
    Store->>DB: SELECT * FROM issues<br/>WHERE type = 'bug'
    DB-->>Store: [{id, type, ...}, ...]
    Store-->>API: issues[]
    API-->>UI: [{id, type, ...}, ...]
    UI->>UI: Отобразить filtered<br/>список
    end

    rect rgba(200, 100, 100, 0.5)
    Note over UI,DB: Редактирование типа проблемы
    UI->>API: PATCH /issues/{id}<br/>(fields{type: 'improvement'})
    API->>Store: UpdateIssueFields(ctx, id,<br/>..., issueType='improvement', ...)
    Store->>Store: Валидация issueType
    Store->>DB: UPDATE issues<br/>SET type='improvement'<br/>WHERE id=?
    DB-->>Store: OK
    Store-->>API: OK
    API-->>UI: success
    UI->>UI: Обновить деталь проблемы
    end
Loading

Оценка сложности рецензирования

🎯 4 (Complex) | ⏱️ ~45 минут

Возможно связанные Pull Request'ы

Рекомендуемые метки

ai-review:active

Стихотворение

🐰 Типы задач теперь поём,
С bagами, feature вместе шлём,
Фильтры и поля на месте,
Проблемы ловим мы в цвете!
Type-safety держим в ручке,
И красим все как нужно, кручке! ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 3

❌ Failed checks (3 warnings)

Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning PR не реализует требования из linked issue #31 о защите вектор метрик в SystemView при отключенной БД. Добавьте изменения SystemView.vue для guard вектор метрик и обработку disconnected состояния как требуется в issue #31.
Out of Scope Changes check ⚠️ Warning Все изменения в PR сосредоточены на добавлении поля type для issues, что соответствует заявленным целям и не включает изменения для SystemView из issue #31. Либо реализуйте требования issue #31 (SystemView вектор метрик guard), либо удалите это issue из linked issues, так как оно не входит в PR.
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed Заголовок точно описывает основное изменение: добавление поля type для issues с поддержкой создания оператором.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/issue-types

Comment @coderabbitai help to get the list of available commands and usage tips.

@thebtf
Copy link
Copy Markdown
Owner Author

thebtf commented Apr 12, 2026

@coderabbitai review focus:security

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 12, 2026

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Get the list of changed files in the PR
gh pr diff 145 --name-only 2>/dev/null || git diff --name-only origin/main HEAD 2>/dev/null

echo "=== PR diff ==="
gh pr diff 145 2>/dev/null | head -800

Length of output: 28856


@thebtf Here is the security-focused review of this PR:


🔒 Security Review — PR #145

✅ No Critical Vulnerabilities

The core concerns are low-to-medium severity. Good practices are in place:

  • GORM uses parameterized queries (Where("type = ?", params.Type)) — no SQL injection risk.
  • Input is validated via validIssueTypes allowlist in CreateIssue and UpdateIssueFields.
  • Vue template uses {{ }} auto-escaping throughout — no XSS vectors in new markup.
  • The [TYPE] injection in lib.js is safe: the value is constrained server-side before ever reaching the client.

🟡 Medium — Unvalidated type query parameter at handler level

File: internal/worker/handlers_issues.go, Line 22

typeParam := r.URL.Query().Get("type")
// ...
Type: typeParam,  // no validation here

typeParam is forwarded to the DB layer without any validation. In ListIssuesEx, GORM uses a parameterized query so SQL injection is not possible, but the handler silently returns an empty result set for any garbage value instead of a 400 Bad Request. This is inconsistent with how status is handled (which is split and used verbatim). It also means scanners can probe the filter values without any server-side signal.

Suggested fix:

typeParam := r.URL.Query().Get("type")
validTypes := map[string]bool{"bug": true, "feature": true, "improvement": true, "task": true}
if typeParam != "" && !validTypes[typeParam] {
    http.Error(w, `{"error":"invalid type"}`, http.StatusBadRequest)
    return
}

🟡 Medium — Client-controlled source_agent: "human" bypasses agent attribution

File: ui/src/utils/api.ts, Line 1077

body: JSON.stringify({
  ...data,
  source_agent: 'human',  // ← any caller can set this to anything
}),

Any authenticated user (or unauthenticated caller if the endpoint lacks auth middleware) can POST with source_agent: "human". If the backend ever uses source_agent for trust decisions, access control, or audit trails, this is a trust-boundary issue. Even today it corrupts audit data — an MCP agent could POST directly to the REST endpoint claiming to be "human".

Recommendation: Set source_agent server-side based on the authenticated session/token, not from the request body. If it must stay client-supplied, enforce it to be one of an allowed set in handleCreateIssue.


🟡 Low — fetchTrackedProjects silently suppresses all errors

File: ui/src/utils/api.ts, Lines 1099–1103

} catch {
  return []
}

A 401 Unauthorized or 403 Forbidden from the server will be silently swallowed and returned as an empty array. This masks auth failures from the caller and makes it impossible to distinguish "no projects" from "you are not allowed to see projects." Consider re-throwing auth errors (status 401/403) to let the UI redirect to login.


🟠 Low — GORM model tag missing not null, diverges from migration DDL

File: internal/db/gorm/models.go, Line 434

// Model tag:
Type string `gorm:"type:text;default:task" json:"type"`

// Migration DDL:
ALTER TABLE issues ADD COLUMN IF NOT EXISTS type TEXT NOT NULL DEFAULT 'task'

The migration correctly adds NOT NULL, but the GORM struct tag lacks not null. If AutoMigrate is ever run independently (e.g. in tests or a future refactor), it will produce a nullable column, diverging from the hand-written migration. This can cause constraint surprise at runtime.

Fix:

Type string `gorm:"type:text;not null;default:task" json:"type"`

🔵 Informational — createIssue timeout options are dead code

File: ui/src/utils/api.ts, Line 1062

const { timeout = DEFAULT_TIMEOUT } = {}  // always DEFAULT_TIMEOUT; options param is missing

This is a copy-paste artifact. The destructuring is always from an empty object literal so caller-supplied timeout overrides are impossible. Not a security issue, but it means a hung server will only be bounded by DEFAULT_TIMEOUT and nothing else. Consider accepting an options parameter or removing the dead destructure.


Summary

Severity Finding File
🟡 Medium Unvalidated type query param — no 400 for invalid input handlers_issues.go
🟡 Medium source_agent: "human" client-controlled — trust boundary api.ts
🟡 Low Auth errors silently swallowed in fetchTrackedProjects api.ts
🟠 Low GORM model tag missing not null vs migration models.go
🔵 Info Dead timeout destructure in createIssue api.ts

Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces issue categorization by adding a type field (bug, feature, improvement, task) across the stack. Key changes include a database migration, updated GORM models and store logic for validation and filtering, and frontend enhancements for creating, editing, and filtering issues by type. Feedback suggests refactoring the new createIssue API function to utilize the existing postJson helper for better consistency and reduced boilerplate.

Comment thread ui/src/utils/api.ts
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (2)
internal/worker/handlers_issues.go (1)

22-22: Нормализуйте type (trim + lower) до передачи в store.

Это снимет лишние 400/пустые выборки при вводе вроде " Bug " или "FEATURE".

♻️ Вариант правки
-	typeParam := r.URL.Query().Get("type")
+	typeParam := strings.ToLower(strings.TrimSpace(r.URL.Query().Get("type")))
...
-	issue := &gormdb.Issue{
+	req.Type = strings.ToLower(strings.TrimSpace(req.Type))
+	issue := &gormdb.Issue{
...
-	if req.Title != "" || req.Body != "" || req.Priority != "" || req.Type != "" || req.Labels != nil {
+	req.Type = strings.ToLower(strings.TrimSpace(req.Type))
+	if req.Title != "" || req.Body != "" || req.Priority != "" || req.Type != "" || req.Labels != nil {

Also applies to: 49-50, 106-107, 135-136, 181-182, 190-192

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/worker/handlers_issues.go` at line 22, Normalize the query parameter
value before passing to the store by trimming whitespace and converting to
lowercase: for the variable typeParam (from r.URL.Query().Get("type")) and the
other similar variables used as filters (the occurrences around the other
mentioned contexts), call strings.TrimSpace(...) and strings.ToLower(...) and
then pass that normalized value into the store calls and validations (e.g.,
where typeParam is used to build the store filter or conditional logic). Ensure
all places that read "type" from the request (the instances you flagged) apply
the same normalization so inputs like " Bug " or "FEATURE" map consistently.
internal/db/gorm/models.go (1)

434-434: Синхронизируйте тег модели с ограничением NOT NULL из миграции.

Это улучшит консистентность model metadata и фактической схемы.

♻️ Минимальная правка
-	Type             string                 `gorm:"type:text;default:task" json:"type"`
+	Type             string                 `gorm:"type:text;not null;default:task" json:"type"`
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/db/gorm/models.go` at line 434, The struct field Type currently has
a GORM tag `gorm:"type:text;default:task"` but the migration enforces NOT NULL;
update the Type field's GORM tag to include not null (e.g., change
`gorm:"type:text;default:task"` to `gorm:"type:text;default:task;not null"`) so
the model metadata matches the schema constraint.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@internal/db/gorm/migrations.go`:
- Around line 2199-2208: Update migration "075_issues_type" to enforce allowed
values at the DB level: in the Migrate function, after adding the column `type`
to table `issues`, add a CHECK constraint (e.g., `chk_issues_type`) that
restricts `type` to the set ('bug','feature','improvement','task'); in the
Rollback function, first drop that constraint (`DROP CONSTRAINT IF EXISTS
chk_issues_type`) and then drop the `type` column. Ensure both SQL statements
use the existing `tx.Exec(...).Error` pattern in the `Migrate` and `Rollback`
functions so the constraint is created and removed reliably.

In `@ui/src/utils/api.ts`:
- Around line 1063-1088: In createIssue validate that data.target_project is
present and non-empty before making the fetch: if target_project is
missing/empty, throw a meaningful Error (or reject) so the helper fails fast
instead of sending source_project:'dashboard'; update the validation near the
start of the createIssue function to check data.target_project and return/throw
before constructing the request body and calling fetch.

---

Nitpick comments:
In `@internal/db/gorm/models.go`:
- Line 434: The struct field Type currently has a GORM tag
`gorm:"type:text;default:task"` but the migration enforces NOT NULL; update the
Type field's GORM tag to include not null (e.g., change
`gorm:"type:text;default:task"` to `gorm:"type:text;default:task;not null"`) so
the model metadata matches the schema constraint.

In `@internal/worker/handlers_issues.go`:
- Line 22: Normalize the query parameter value before passing to the store by
trimming whitespace and converting to lowercase: for the variable typeParam
(from r.URL.Query().Get("type")) and the other similar variables used as filters
(the occurrences around the other mentioned contexts), call
strings.TrimSpace(...) and strings.ToLower(...) and then pass that normalized
value into the store calls and validations (e.g., where typeParam is used to
build the store filter or conditional logic). Ensure all places that read "type"
from the request (the instances you flagged) apply the same normalization so
inputs like " Bug " or "FEATURE" map consistently.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 1759a90e-7888-46fe-8b7a-11cc7c044164

📥 Commits

Reviewing files that changed from the base of the PR and between 30c17bc and df42b77.

📒 Files selected for processing (11)
  • internal/db/gorm/issue_store.go
  • internal/db/gorm/migrations.go
  • internal/db/gorm/models.go
  • internal/mcp/tools_issues.go
  • internal/worker/handlers_issues.go
  • plugin/engram/.claude-plugin/plugin.json
  • plugin/engram/hooks/lib.js
  • ui/src/composables/useIssues.ts
  • ui/src/utils/api.ts
  • ui/src/views/IssueDetailView.vue
  • ui/src/views/IssuesView.vue

Comment thread internal/db/gorm/migrations.go
Comment thread ui/src/utils/api.ts Outdated
- migrations.go: add DB-level CHECK constraint issues_type_check for
  type IN ('bug','feature','improvement','task') with proper rollback
- models.go: add not null to GORM tag for Type field to match migration
- handlers_issues.go: normalize type param with TrimSpace+ToLower in
  list, create, and update handlers
- api.ts: refactor createIssue to use postJson helper (removes
  duplicated timeout/abort/retry logic); require non-empty target_project
  and validate eagerly before HTTP call
- IssuesView.vue: pass target_project as string (not undefined) since
  createIssue now validates it directly
@thebtf thebtf merged commit fe2f224 into main Apr 12, 2026
2 checks passed
@thebtf thebtf deleted the feat/issue-types branch April 12, 2026 20:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant