Skip to content

fix(hooks): expose observation IDs in injected context (#32)#139

Merged
thebtf merged 1 commit into
mainfrom
fix/issue-32-feedback-observation-ids
Apr 12, 2026
Merged

fix(hooks): expose observation IDs in injected context (#32)#139
thebtf merged 1 commit into
mainfrom
fix/issue-32-feedback-observation-ids

Conversation

@thebtf
Copy link
Copy Markdown
Owner

@thebtf thebtf commented Apr 12, 2026

Summary

Root cause

2164 feedback records, 0 positive, 0 negative. Agents saw ## 1. [DECISION] Title but no ID to reference in feedback calls.

Changes

  • plugin/engram/hooks/session-start.js:227: add (id:${observation.id}) to header
  • plugin/engram/hooks/user-prompt.js:334: add (id:${obs.id}) to header
  • plugin/engram/.claude-plugin/plugin.json: version 3.7.4 → 3.7.5

Test plan

  • Start a new session — verify <relevant-memory> block shows (id:NNNNN) after each observation title
  • Verify agent can call feedback(action="rate", id=N, rating="useful") with a visible ID
  • Monitor feedback stats after deployment — positive/negative count should start increasing

Summary by CodeRabbit

Улучшения

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

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 12, 2026

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 50b7db64-1605-4e83-aa81-a2ae5517ad24

📥 Commits

Reviewing files that changed from the base of the PR and between f660be0 and ee693d1.

📒 Files selected for processing (3)
  • plugin/engram/.claude-plugin/plugin.json
  • plugin/engram/hooks/session-start.js
  • plugin/engram/hooks/user-prompt.js

Обзор

Версия плагина обновлена с 3.7.4 на 3.7.5. В двух хук-файлах добавлены идентификаторы наблюдений в строки заголовков: в разделе "Project Memory" сессии и в секции "Relevant Knowledge From Previous Sessions" при формировании запроса пользователя.

Изменения

Когорта / Файл(ы) Описание
Обновление версии плагина
plugin/engram/.claude-plugin/plugin.json
Версия плагина увеличена с 3.7.4 на 3.7.5.
Добавление идентификаторов наблюдений
plugin/engram/hooks/session-start.js, plugin/engram/hooks/user-prompt.js
В строки заголовков наблюдений добавлены идентификаторы в формате (id:<obs.id>) для лучшей идентификации записей в разделах Project Memory и Relevant Knowledge.

Оценка трудоёмкости код-ревью

🎯 1 (Тривиально) | ⏱️ ~3 минуты

Возможно связанные PR

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

🐰 Прыг-скок по коду, как в саду цветенье,
Идентификаторы в строки — вот решенье!
Версия растёт, с трёх точки пять и дальше,
Наблюдения теперь со статусом заботы,
Память проекта станет ясна мне, как работа! ✨

🚥 Pre-merge checks | ✅ 1 | ❌ 4

❌ Failed checks (4 warnings)

Check name Status Explanation Resolution
Title check ⚠️ Warning Заголовок PR описывает добавление ID наблюдений в контекст, но основные изменения включают также добавление markdown-рендеринга в UI, что не отражено в названии. Измените заголовок на более полный, например: 'feat: expose observation IDs in hooks and add markdown rendering to issue timeline' или разделите на два PR.
Linked Issues check ⚠️ Warning Связанная issue #32 требует очистки векторов при удалении наблюдений в DeleteObservation и DeleteObservations, но PR этого не реализует. Вместо этого добавлены ID в контекст и markdown-рендеринг. Реализуйте требуемые изменения из issue #32: добавьте вызовы s.cleanupFunc(ctx, ids) в методы удаления наблюдений для очистки векторов pgvector.
Out of Scope Changes check ⚠️ Warning PR содержит существенные изменения вне области scope issue #32: добавление ID в hooks соответствует старой версии PR, но markdown-рендеринг в UI является полностью отдельной функциональностью. Отделите изменения markdown-рендеринга (ui/package.json, useMarkdown.ts, IssueDetailView.vue) в отдельный PR и сосредоточьтесь на требуемой очистке векторов для issue #32.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ 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 fix/issue-32-feedback-observation-ids

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

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 updates the engram plugin to version 3.7.5, adding observation IDs to the context builder, and implements Markdown rendering in the UI using the marked and dompurify libraries. Feedback recommends omitting the ID tag entirely when an ID is missing to prevent agent confusion from placeholder characters. Additionally, it is recommended to use marked.use() instead of the deprecated marked.setOptions() for global configuration in the new useMarkdown composable.

if (i < fullCount) {
const narrative = escapeXmlTags(getString(observation.narrative));
contextBuilder += `## ${i + 1}. [${typeLabel}] ${title}${scopeTag}\n`;
contextBuilder += `## ${i + 1}. [${typeLabel}] ${title}${scopeTag} (id:${observation.id || '?'})\n`;
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

Using a placeholder like ? when an ID is missing might confuse the agent or lead to invalid tool calls if it tries to use the placeholder as an ID. It is better to omit the ID tag entirely if the ID is not available.

Suggested change
contextBuilder += `## ${i + 1}. [${typeLabel}] ${title}${scopeTag} (id:${observation.id || '?'})\n`;
contextBuilder += `## ${i + 1}. [${typeLabel}] ${title}${scopeTag}${observation.id ? ` (id:${observation.id})` : ''}\n`;

const scopeTag = (typeof obs.scope === 'string' && obs.scope === 'global') ? ' [GLOBAL]' : '';

contextBuilder += `## ${idx}. [${obsType}] ${title}${scopeTag}${scoreTag}\n`;
contextBuilder += `## ${idx}. [${obsType}] ${title}${scopeTag}${scoreTag} (id:${obs.id || '?'})\n`;
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

Similar to the session-start hook, using ? as a fallback for missing IDs can be problematic for agents. Omit the ID section if the ID is not present to ensure the agent only sees valid identifiers.

Suggested change
contextBuilder += `## ${idx}. [${obsType}] ${title}${scopeTag}${scoreTag} (id:${obs.id || '?'})\n`;
contextBuilder += `## ${idx}. [${obsType}] ${title}${scopeTag}${scoreTag}${obs.id ? ` (id:${obs.id})` : ''}\n`;

Comment thread ui/src/composables/useMarkdown.ts Outdated
Comment on lines +5 to +8
marked.setOptions({
breaks: true, // GFM line breaks
gfm: true, // GitHub Flavored Markdown
})
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

marked.setOptions is deprecated in marked v18. It is recommended to use marked.use() for global configuration.

marked.use({\n  breaks: true, // GFM line breaks\n  gfm: true,    // GitHub Flavored Markdown\n})

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

🧹 Nitpick comments (3)
ui/src/views/IssueDetailView.vue (1)

358-358: Не вызывайте markdown-рендерер прямо из шаблона.

Line 358 пересчитывает renderMarkdown(event.body) при каждом реактивном рендере страницы. Это повторно гоняет parse + sanitize по всей timeline даже при локальных UI-изменениях. Лучше подготовить HTML один раз в computed/buildTimeline и здесь биндингить уже готовое поле.

♻️ Минимальное направление для рефактора
-            <div v-if="event.body" class="markdown-body mt-1 text-sm text-gray-600 dark:text-gray-400" v-html="renderMarkdown(event.body)" />
+            <div v-if="event.body" class="markdown-body mt-1 text-sm text-gray-600 dark:text-gray-400" v-html="event.htmlBody" />
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ui/src/views/IssueDetailView.vue` at line 358, Шаблон вызывает
renderMarkdown(event.body) прямо в разметке, что пересчитывает
парсинг/санитизацию при каждом рендере; исправьте это, вычислив HTML один раз и
привязывая готовое поле: добавьте computed-свойство или расширьте buildTimeline,
чтобы для каждого event заполнить event.renderedBody =
renderMarkdown(event.body) (или вернуть структуру с renderedBody), и в шаблоне
замените v-html="renderMarkdown(event.body)" на v-html="event.renderedBody"
(используйте тот же идентификатор event и функцию renderMarkdown из компонента).
ui/src/composables/useMarkdown.ts (1)

5-13: Изолируйте конфиг marked от глобального singleton-а.

Line 5 меняет настройки общего экземпляра marked на уровне модуля. Сейчас это работает, но любой другой импорт marked в UI получит эти опции неявно. У marked есть поддержка отдельных экземпляров через new Marked(...), поэтому конфиг лучше держать локально в этом composable. (github.com)

♻️ Возможный рефактор
-import { marked } from 'marked'
+import { Marked } from 'marked'
 import DOMPurify from 'dompurify'
 
-// Configure marked for safe defaults
-marked.setOptions({
+const markdown = new Marked({
   breaks: true, // GFM line breaks
   gfm: true,    // GitHub Flavored Markdown
 })
 
 export function renderMarkdown(text: string): string {
   if (!text) return ''
-  const html = marked.parse(text, { async: false }) as string
+  const html = markdown.parse(text, { async: false }) as string
   return DOMPurify.sanitize(html)
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ui/src/composables/useMarkdown.ts` around lines 5 - 13, Не настраивать
глобальный singleton marked: вместо вызова marked.setOptions(...) создайте
локальный экземпляр через new Marked({ breaks: true, gfm: true }) внутри этого
composable и используйте его в функции renderMarkdown (вызов
instance.parse(text, { async: false }) ), затем по-прежнему пропустите результат
через DOMPurify.sanitize(html). Обновите импорты/использование так, чтобы
renderMarkdown ссылался на локальную переменную экземпляра Marked, а не на
глобальный marked.
ui/package.json (1)

24-24: Уберите лишний @types/dompurify.

dompurify уже поставляется со встроенными типами TypeScript, а @types/dompurify на npm опубликован как stub и прямо говорит, что он не нужен. Оставлять оба пакета — лишний риск рассинхрона типов и ненужный шум в зависимостях. (npmjs.com)

♻️ Предлагаемое упрощение
   "devDependencies": {
     "@fortawesome/fontawesome-free": "^6.7.2",
-    "@types/dompurify": "^3.0.5",
     "@types/node": "^22.10.2",
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ui/package.json` at line 24, Remove the redundant "@types/dompurify"
dependency from package.json (the dependency entry "@types/dompurify") since
dompurify ships its own types; delete that line, then reinstall dependencies
(npm/yarn/pnpm install) to update the lockfile so the package tree no longer
includes the stub types.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@plugin/engram/hooks/session-start.js`:
- Line 227: The template line building contextBuilder inserts observation.id raw
and can cause prompt injection if id is non-numeric; update the logic around
where contextBuilder is appended (the code using contextBuilder and
observation.id) to validate observation.id with a strict numeric check (e.g.,
ensure it parses to an integer or matches /^\d+$/) and only render the numeric
id when validation passes, otherwise substitute a safe placeholder like '?' or
omit the id; keep the rest of the formatting intact so the output remains
predictable and safe.

---

Nitpick comments:
In `@ui/package.json`:
- Line 24: Remove the redundant "@types/dompurify" dependency from package.json
(the dependency entry "@types/dompurify") since dompurify ships its own types;
delete that line, then reinstall dependencies (npm/yarn/pnpm install) to update
the lockfile so the package tree no longer includes the stub types.

In `@ui/src/composables/useMarkdown.ts`:
- Around line 5-13: Не настраивать глобальный singleton marked: вместо вызова
marked.setOptions(...) создайте локальный экземпляр через new Marked({ breaks:
true, gfm: true }) внутри этого composable и используйте его в функции
renderMarkdown (вызов instance.parse(text, { async: false }) ), затем
по-прежнему пропустите результат через DOMPurify.sanitize(html). Обновите
импорты/использование так, чтобы renderMarkdown ссылался на локальную переменную
экземпляра Marked, а не на глобальный marked.

In `@ui/src/views/IssueDetailView.vue`:
- Line 358: Шаблон вызывает renderMarkdown(event.body) прямо в разметке, что
пересчитывает парсинг/санитизацию при каждом рендере; исправьте это, вычислив
HTML один раз и привязывая готовое поле: добавьте computed-свойство или
расширьте buildTimeline, чтобы для каждого event заполнить event.renderedBody =
renderMarkdown(event.body) (или вернуть структуру с renderedBody), и в шаблоне
замените v-html="renderMarkdown(event.body)" на v-html="event.renderedBody"
(используйте тот же идентификатор event и функцию renderMarkdown из компонента).
🪄 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: 300fc7cc-e02c-4d76-817b-3648400519ac

📥 Commits

Reviewing files that changed from the base of the PR and between 3ca8495 and f660be0.

⛔ Files ignored due to path filters (1)
  • ui/package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (6)
  • plugin/engram/.claude-plugin/plugin.json
  • plugin/engram/hooks/session-start.js
  • plugin/engram/hooks/user-prompt.js
  • ui/package.json
  • ui/src/composables/useMarkdown.ts
  • ui/src/views/IssueDetailView.vue

if (i < fullCount) {
const narrative = escapeXmlTags(getString(observation.narrative));
contextBuilder += `## ${i + 1}. [${typeLabel}] ${title}${scopeTag}\n`;
contextBuilder += `## ${i + 1}. [${typeLabel}] ${title}${scopeTag} (id:${observation.id || '?'})\n`;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Валидируйте observation.id перед вставкой в инъецируемый контекст.

Сейчас id подставляется как есть. Если сервер вернёт нечисловое значение, можно сломать формат <engram-context> и получить инъекцию в промпт. Используйте строгую числовую проверку перед рендерингом.

Предлагаемый фикс
-      contextBuilder += `## ${i + 1}. [${typeLabel}] ${title}${scopeTag} (id:${observation.id || '?'})\n`;
+      const safeObservationId = Number.isInteger(observation.id) && observation.id > 0
+        ? observation.id
+        : '?';
+      contextBuilder += `## ${i + 1}. [${typeLabel}] ${title}${scopeTag} (id:${safeObservationId})\n`;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
contextBuilder += `## ${i + 1}. [${typeLabel}] ${title}${scopeTag} (id:${observation.id || '?'})\n`;
const safeObservationId = Number.isInteger(observation.id) && observation.id > 0
? observation.id
: '?';
contextBuilder += `## ${i + 1}. [${typeLabel}] ${title}${scopeTag} (id:${safeObservationId})\n`;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@plugin/engram/hooks/session-start.js` at line 227, The template line building
contextBuilder inserts observation.id raw and can cause prompt injection if id
is non-numeric; update the logic around where contextBuilder is appended (the
code using contextBuilder and observation.id) to validate observation.id with a
strict numeric check (e.g., ensure it parses to an integer or matches /^\d+$/)
and only render the numeric id when validation passes, otherwise substitute a
safe placeholder like '?' or omit the id; keep the rest of the formatting intact
so the output remains predictable and safe.

Agents couldn't rate observations because the injected <relevant-memory>
block showed "## 1. [DECISION] Title" with no ID. The MCP feedback tool
requires feedback(action="rate", id=N) but N was never visible.

Now renders as "## 1. [DECISION] Title (id:12345)" in both:
- session-start.js (initial context injection)
- user-prompt.js (per-prompt context injection)

Closes #32. Plugin version bumped to 3.7.5.
@thebtf thebtf force-pushed the fix/issue-32-feedback-observation-ids branch from f660be0 to ee693d1 Compare April 12, 2026 14:35
@thebtf thebtf merged commit 1ea9ace into main Apr 12, 2026
0 of 2 checks passed
@thebtf thebtf deleted the fix/issue-32-feedback-observation-ids branch April 12, 2026 14:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant