Skip to content

feat(identity): project identity redesign — pure hash ID + display names (#72)#163

Merged
thebtf merged 5 commits into
mainfrom
feat/project-identity
Apr 14, 2026
Merged

feat(identity): project identity redesign — pure hash ID + display names (#72)#163
thebtf merged 5 commits into
mainfrom
feat/project-identity

Conversation

@thebtf
Copy link
Copy Markdown
Owner

@thebtf thebtf commented Apr 14, 2026

Summary

Replaces dirName_hash project slug format with pure hash-based ID. Issue #72.

Core Identity Change

  • ResolveProjectSlug returns sha256(remote+relPath)[:8] as ID (e.g. 67e398f8 not engram_67e398f8)
  • .engram-project anchor file for display names (auto-created, auto-staged in git)
  • Non-git projects store ID in anchor file for stability across moves
  • Updated cmd/engram/main.go resolveProject + OnProjectConnect for 4-return signature

Data Migration (081)

  • Updates all observations.project, issues.source_project/target_project to pure hash
  • Adds display_name column to projects table
  • Old slugs preserved in legacy_ids for backward compatibility
  • Explicit mapping (no regex) — safe for project names with underscores

Display Names

  • API responses include project_display_name for observations and issues
  • Dashboard shows display names in observations list, issues list, issue detail
  • Project names map in issue list response for batch resolution

JS Hooks Alignment

  • lib.js produces pure hash IDs matching Go algorithm
  • 6 new test cases including Go algorithm parity test vector

Tests

  • 3 new Go tests (anchor file custom name, auto-creation, non-git stored ID)
  • 6 new JS tests (pure hash format, cross-platform stability, monorepo, Go parity)

Closes #72

Summary by CodeRabbit

Примечания к выпуску

  • Новые функции

    • Проекты получили отображаемые имена; UI теперь показывает их в представлениях наблюдений и задач.
    • Поддержка локального файла .engram-project: задаёт/переопределяет имя (и при необходимости хранит ID).
  • Баг-фиксы / Улучшения

    • Идентификаторы проектов унифицированы на основе хэш‑фрагментов (без префиксов), улучшена согласованность между рабочими деревьями и плагинами.
  • Миграция БД

    • Добавлена миграция для поля display_name и пересчёта/слияния старых ID.
  • Тесты

    • Обновлены и добавлены тесты для новой логики идентификации и файла‑якоря.

thebtf added 4 commits April 15, 2026 01:26
- Change ResolveProjectSlug signature from (slug, gitRemote, error) to
  (id, displayName, gitRemote, error)
- Git path: id = hash[:8] (pure hash, no dirName prefix)
- Non-git path: id = hash[:6] (pure hash, no dirName prefix)
- displayName = dirName in both cases
- Add .engram-project anchor file support: auto-created on first call,
  overrides displayName (name field) and non-git id (id field)
- Update identity_test.go: new 4-return-value assertions + T006 anchor tests
- Update cmd/engram/main.go: resolveProject and OnProjectConnect log displayName
- Add migration 081_project_identity_pure_hash: strips dirName prefix from
  project IDs still in "dirName_hashN" format after migrations 078/079
- Algorithm: find rows where last _-segment is 6 or 8 lowercase hex chars,
  re-associate observations/issues, rename project to pure hash, preserve old
  slug in legacy_ids and dirName in display_name
- Collision guard: if pure-hash row already exists, merge old row into it
- Add display_name VARCHAR(255) column via ALTER TABLE ... ADD COLUMN IF NOT EXISTS
- UpsertProject already accepts displayName parameter and stores it in the column
  (implemented in an earlier commit on this branch)
- Add "strings" import to migrations.go (required for strings.LastIndex)
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 14, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 5583c4f7-6671-478d-90ce-e0638086dd8b

📥 Commits

Reviewing files that changed from the base of the PR and between 9282155 and 73dd5e6.

📒 Files selected for processing (1)
  • internal/db/gorm/migrations.go

Walkthrough

Переход проектной идентификации с формата dirName_hash на чистые хеш‑ID, добавлен столбец display_name в БД и миграция для нормализации/слияния старых ID, поддержка файла .engram-project для переопределения имени/ID и обновление API/фронтенда для возврата человеко‑читаемых имён проектов.

Changes

Cohort / File(s) Summary
Проектный якорь
.engram-project
Добавлен файл конфигурации/якорь проекта (JSON с полем name).
Разрешение идентичности (прокси)
internal/proxy/identity.go, internal/proxy/identity_test.go
Сигнатура ResolveProjectSlug изменена на (id, displayName, gitRemote, err); ID формируются как чистый хеш (git: 8 hex, fallback: 6 hex); добавлена поддержка .engram-project (чтение/автосоздание и опциональное сохранение id/name); тесты обновлены/расширены.
CLI — интеграция
cmd/engram/main.go
Адаптированы вызовы/кеширование в engramHandler под новые возвращаемые id, displayName; логирование переключено на displayName (id) с соответствующими fallback.
Миграция БД
internal/db/gorm/migrations.go
Добавлена миграция 081: новый столбец projects.display_name; нормализация ID (удаление dirName_ префикса для паттерков вида <dir>_<hex>), обновление внешних ключей, объединение/merge при коллизиях и запись legacy_ids; откат удаляет столбец display_name.
Сервисные обработчики — данные
internal/worker/handlers_data.go
Добавлен getProjectDisplayName (поиск по id или в legacy_ids); handleGetObservations и handleGetObservationByID теперь включают project_display_name в ответы.
Сервисные обработчики — issues
internal/worker/handlers_issues.go
handleListIssues возвращает project_names map; handleGetIssue добавляет source_project_display_name и target_project_display_name в ответ.
Плагин (JS) и тесты
plugin/engram/hooks/lib.js, plugin/engram/hooks/lib.test.js
ID-алгоритмы синхронизированы с Go: git‑ID — 8‑символьный хеш от remoteURL/relativePath, fallback — 6‑символьный хеш от abs‑path; тесты добавлены для детерминизма и формата.
Фронтенд — типы API
ui/src/utils/api.ts
Интерфейсы расширены: ObservationsResponse (+project_display_name?), IssueListResponse (+project_names?), IssueDetailResponse (+source_project_display_name?, target_project_display_name?).
Фронтенд — состояние/компоненты
ui/src/composables/useIssues.ts, ui/src/views/IssuesView.vue, ui/src/views/IssueDetailView.vue, ui/src/views/ObservationsView.vue
Добавлено реактивное projectNames и соответствующее использование project_names/project_display_name для отображения коротких/человекочитаемых имён в списках проблем и наблюдений; обновлены shortProject и загрузка данных.

Sequence Diagram(s)

sequenceDiagram
  participant CLI as CLI (engram)
  participant Proxy as Proxy.ResolveProjectSlug
  participant Git as Git (remote)
  participant FS as Filesystem (.engram-project)
  participant DB as Database (migrations / projects)
  participant API as Server (handlers)
  participant UI as Frontend

  CLI->>Proxy: ResolveProjectSlug(cwd)
  Proxy->>Git: If git repo -> compute sha256(remote/relPath)
  Proxy->>FS: read .engram-project (applyAnchorFile)
  alt anchor exists or Git/non-git resolved
    Proxy-->>CLI: return (id, displayName, gitRemote)
  else anchor missing and non-git
    Proxy->>FS: write .engram-project with id/name
    Proxy-->>CLI: return (id, displayName, "")
  end

  DB->>DB: migration 081 adjusts projects.id -> pure-hash, updates FK, sets display_name, legacy_ids
  UI->>API: listIssues / getObservations (may include project filter)
  API->>DB: query projects / legacy_ids
  API-->>UI: include project_display_name / project_names in JSON
  UI->>UI: map project IDs -> display names for rendering
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related PRs

Poem

🐰
Маленький кролик хрустом метит путь,
Хеш чистый — след, без лишних следов.
Якорь .engram-project шепчет имя суть,
Данные сходятся — объединённый кров.
Прыгаю радостно: проект жив — и слово вновь!

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning The pull request does not implement the coding requirements from linked issue #72: CleanupOrphanPatterns, BatchRecalculateConfidence, POST /api/maintenance/patterns/cleanup endpoint, or the frontend UI for pattern cleanup. Implement the backend functions (CleanupOrphanPatterns, BatchRecalculateConfidence) and the maintenance API endpoint, then add the frontend 'Clean Up Patterns' UI with threshold input and dry-run preview capability.
Out of Scope Changes check ⚠️ Warning The changeset implements project identity redesign features (pure hash IDs, display names, .engram-project anchor file, migration 081, API/UI updates) that are entirely out of scope relative to the linked issue #72 requirement for pattern cleanup with orphan detection and confidence recalculation. Either update the PR to focus on the pattern cleanup objectives from issue #72, or relink to a different issue that covers project identity redesign work.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The pull request title accurately describes the main change: a project identity redesign replacing the old slug format with pure hash-based IDs and display names.
Docstring Coverage ✅ Passed Docstring coverage is 80.00% which is sufficient. The required threshold is 80.00%.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/project-identity

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 golangci-lint (2.11.4)

Error: can't load config: unsupported version of the configuration: "" See https://golangci-lint.run/docs/product/migration-guide for migration instructions
The command is terminated due to an error: can't load config: unsupported version of the configuration: "" See https://golangci-lint.run/docs/product/migration-guide for migration instructions


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

@thebtf
Copy link
Copy Markdown
Owner Author

thebtf commented Apr 14, 2026

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 14, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

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 redesigns project identity by transitioning from "dirName_hash" slugs to pure hash IDs, supported by a new display_name field and .engram-project anchor files for identity persistence. The changes span the database migration logic, Go and JavaScript identity resolution, and UI updates to display human-readable names. Feedback focuses on ensuring the migration covers all tables referencing project IDs, fixing a potential NULL handling bug in the PostgreSQL array_append call, optimizing N+1 database queries in the issue list handler, and addressing a UI regression where display names are missing in the aggregate observations view.

Comment thread internal/db/gorm/migrations.go
Comment thread internal/db/gorm/migrations.go
Comment thread internal/db/gorm/migrations.go
Comment thread internal/worker/handlers_issues.go
})
}
if project != "" {
resp["project_display_name"] = s.getProjectDisplayName(project)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

The project_display_name is only included in the response when a specific project filter is applied. In the "All Projects" view, the UI will now display raw hashes for each observation instead of human-readable names, which is a regression in user experience. Consider populating a map of project IDs to display names for all observations in the list, similar to the implementation in handleListIssues.

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: 6

🧹 Nitpick comments (1)
internal/proxy/identity.go (1)

64-101: Обработка ошибок записи anchor-файла.

Ошибка os.WriteFile на строке 94 игнорируется молча. Хотя это приемлемо для best-effort поведения, стоит рассмотреть логирование для диагностики:

if fileData, marshalErr := json.MarshalIndent(anchor, "", "  "); marshalErr == nil {
    if writeErr := os.WriteFile(anchorPath, append(fileData, '\n'), 0644); writeErr != nil {
        // Опционально: log.Debug для диагностики
    }
    // ...
}

Также обратите внимание: логика !storeID на строке 96 означает, что git add выполняется только для git-репозиториев (где storeID=false), что корректно.

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

In `@internal/proxy/identity.go` around lines 64 - 101, In applyAnchorFile, don't
ignore errors from os.WriteFile: capture the return (e.g., writeErr :=
os.WriteFile(...)) and log a diagnostic message when writeErr != nil (use the
package's logger or log.Printf) including anchorPath and the error so failures
to create the .engram-project file are visible; also consider capturing and
logging the error returned by exec.Command("git", "-C", dir, "add",
".engram-project").Run() so git staging failures are visible for debugging.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.engram-project:
- Around line 1-3: Поле "name" в файле .engram-project содержит
веточно-ориентированное значение "feat-project-identity" и переопределяет
displayName для репозитория; откройте файл .engram-project и замените значение
поля name на корректное имя проекта (реальное displayName), либо полностью
удалите файл если вы хотите использовать значение по умолчанию из
filepath.Base(repoDir); убедитесь, что после изменения функции/припроцессоры,
читающие поле name/displayName, корректно отображают новое значение.

In `@internal/db/gorm/migrations.go`:
- Around line 2541-2617: Migration "081_project_identity_pure_hash" in the
Migrate func does not update all foreign-key columns referring to projects; add
tx.Exec UPDATE statements similar to the existing ones to also update
sdk_sessions.project, vectors.project (used by idx_vectors_doc_type_project),
and issue_comments.author_project whenever you rename or merge an oldID into
newID inside the loop that processes rows; ensure you run these updates in both
the existing-merge branch (existingCount > 0) and the rename-in-place branch so
no orphaned references remain.

In `@internal/worker/handlers_data.go`:
- Around line 764-767: Ответ теперь оборачивает Observation в { observation,
project_display_name }, что ломает клиентов; чтобы сохранить обратную
совместимость, измените обработчик (в месте где вызывается writeJSON) чтобы
возвращать старую форму — сам Observation — (т.е. писать только obs), либо, если
project_display_name нужен, вернуть его через отдельное поле/эндпоинт; обновите
соответствующие символы: функцию writeJSON/обработчик в
internal/worker/handlers_data.go и согласуйте с клиентским helper-ом
fetchObservationById и типом Observation в ui/src/utils/api.ts (обновите типы и
parsing там, если вы всё же хотите официально менять shape ответа).
- Around line 1169-1180: getProjectDisplayName currently issues a raw DB query
without request context and ignores DB errors; change its signature to accept a
context.Context (e.g., getProjectDisplayName(ctx context.Context, projectID
string)), call s.store.GetDB().WithContext(ctx).Raw(...).Scan(&displayName) and
capture the result (res := ...). After Scan, check res.Error and ctx.Err(): if
ctx.Err() != nil return projectID (cancelled) and for other non-nil res.Error
log the error (use the service logger or appropriate logger on Service) and
return projectID; keep the existing fallback of returning projectID when
displayName == "". This ensures cancelled requests don’t continue work and DB
errors are surfaced/logged.

In `@internal/worker/handlers_issues.go`:
- Around line 66-75: The loop over issues currently calls
getProjectDisplayName(...) for every occurrence, causing an N+1 DB lookup;
instead first collect unique project IDs from issues (e.g., by appending
iss.SourceProject and iss.TargetProject into a set if non-empty) and then
iterate that set to call s.getProjectDisplayName once per unique ID and populate
projectNames; update the code around the projectNames map and the loop over
issues in handlers_issues.go to check/set uniqueness before invoking
getProjectDisplayName.

In `@ui/src/views/ObservationsView.vue`:
- Line 808: The friendly display name is only applied inside the obs.memory_type
branch, causing inconsistent project labels; update the other observation card
renderings to call shortProject(obs.project, obs.project === currentProject ?
currentProjectDisplayName : null) so they use the same display name logic as the
memory card. Locate uses of shortProject and places where observation cards
render obs.project (including the span currently using shortProject(...) and the
other card render blocks that render shortProject(obs.project)) and replace
those calls to pass the conditional second argument (currentProjectDisplayName
when obs.project === currentProject) so all cards show the same human-friendly
name.

---

Nitpick comments:
In `@internal/proxy/identity.go`:
- Around line 64-101: In applyAnchorFile, don't ignore errors from os.WriteFile:
capture the return (e.g., writeErr := os.WriteFile(...)) and log a diagnostic
message when writeErr != nil (use the package's logger or log.Printf) including
anchorPath and the error so failures to create the .engram-project file are
visible; also consider capturing and logging the error returned by
exec.Command("git", "-C", dir, "add", ".engram-project").Run() so git staging
failures are visible for debugging.
🪄 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: 09d5e3ba-7a5f-4b4b-b1c7-dc919feabdef

📥 Commits

Reviewing files that changed from the base of the PR and between c540fe5 and 9282155.

📒 Files selected for processing (14)
  • .engram-project
  • cmd/engram/main.go
  • internal/db/gorm/migrations.go
  • internal/proxy/identity.go
  • internal/proxy/identity_test.go
  • internal/worker/handlers_data.go
  • internal/worker/handlers_issues.go
  • plugin/engram/hooks/lib.js
  • plugin/engram/hooks/lib.test.js
  • ui/src/composables/useIssues.ts
  • ui/src/utils/api.ts
  • ui/src/views/IssueDetailView.vue
  • ui/src/views/IssuesView.vue
  • ui/src/views/ObservationsView.vue

Comment thread .engram-project
Comment thread internal/db/gorm/migrations.go
Comment thread internal/worker/handlers_data.go
Comment thread internal/worker/handlers_data.go
Comment thread internal/worker/handlers_issues.go
Comment thread ui/src/views/ObservationsView.vue
…an names

Address review findings:
- Update raw_events, indexed_sessions, user_prompts, injection_log,
  reasoning_traces alongside observations and issues
- Set display_name for clean name-only projects (from migrations 078/079)
- Handle COALESCE for NULL legacy_ids arrays
@thebtf thebtf merged commit c489c0b into main Apr 14, 2026
2 checks passed
@thebtf thebtf deleted the feat/project-identity branch April 14, 2026 22:53
thebtf added a commit that referenced this pull request Apr 14, 2026
- Fix .engram-project name: change "feat-project-identity" to "engram"
- Fix /api/observations/{id} breaking change: flatten project_display_name
  as an extra field on the observation object (embedded struct) instead of
  a wrapper {observation, project_display_name} that broke fetchObservationById
- Fix getProjectDisplayName: add ctx context.Context param, use WithContext(ctx),
  and handle DB errors (return empty string to trigger caller fallback)
- Fix N+1 query in handleListIssues: check projectNames map before calling
  getProjectDisplayName to avoid duplicate DB queries for the same project ID
- Fix ObservationsView: use currentProjectDisplayName in regular observation
  cards (was only used in memory-card branch)
- Fix migration rollback: add comment explaining why project ID changes are
  not reversed (no reverse mapping was stored before migration)
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