Skip to content

feat: add typed prompt relationships#174

Open
legeling wants to merge 2 commits into
mainfrom
codex/prompt-relationships-complete-20260614120253
Open

feat: add typed prompt relationships#174
legeling wants to merge 2 commits into
mainfrom
codex/prompt-relationships-complete-20260614120253

Conversation

@legeling

@legeling legeling commented Jun 14, 2026

Copy link
Copy Markdown
Owner

Summary

  • add a durable prompt_relations table for related_to, variant_of, depends_on, and next_step
  • keep drag as the primary UI: center-row drop opens a compact relationship chooser, while grouped_under continues using the prompt tree
  • render relation badges in the prompt list and include prompt relations in desktop backup export/restore
  • update the active relationship design/spec record after merging the contributor tree feature

Verification

  • pnpm --dir apps/desktop exec vitest run tests/unit/main/prompt-relation-db.test.ts tests/unit/main/prompt-db.test.ts tests/unit/main/database-migration-locks.test.ts tests/unit/components/prompt-list-view-relations.test.tsx
  • pnpm --dir apps/desktop exec vitest run tests/unit/services/database-backup-format.test.ts tests/unit/services/database-backup.test.ts tests/integration/services/database-backup-filesystem.integration.test.ts
  • pnpm --filter @prompthub/desktop typecheck
  • pnpm --filter @prompthub/desktop lint
  • pnpm --filter @prompthub/desktop build
  • git diff --check

Summary by CodeRabbit

发布说明

  • 新功能
    • 添加提示词关系管理,支持关联、变体、依赖和流程等关系类型
    • 拖拽提示词时可创建或调整关系,列表视图展示关系徽标
    • 关系数据支持备份导出和导入还原

@qodo-code-review

qodo-code-review Bot commented Jun 14, 2026

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (3) 📘 Rule violations (5)

Grey Divider


Action required

1. Relation IPC lacks validation 📘 Rule violation ⛨ Security 8.2
Description
The new prompt relation IPC handlers accept unvalidated inputs and do not translate failures into
structured error responses. Malformed payloads or thrown DB errors can propagate unpredictably
across the main-process IPC boundary.
Code

apps/desktop/src/main/ipc/prompt.ipc.ts[R284-308]

+  ipcMain.handle(IPC_CHANNELS.PROMPT_RELATION_CREATE, async (_, data: CreatePromptRelationDTO) => {
+    const relation = relationDb.create(data);
+    syncWorkspace();
+    return relation;
+  });
+
+  ipcMain.handle(IPC_CHANNELS.PROMPT_RELATION_LIST, async (_, query?: PromptRelationQuery) => {
+    return relationDb.list(query);
+  });
+
+  ipcMain.handle(IPC_CHANNELS.PROMPT_RELATION_UPDATE, async (_, id: string, data: UpdatePromptRelationDTO) => {
+    const relation = relationDb.update(id, data);
+    if (relation) {
+      syncWorkspace();
+    }
+    return relation;
+  });
+
+  ipcMain.handle(IPC_CHANNELS.PROMPT_RELATION_DELETE, async (_, id: string) => {
+    const deleted = relationDb.delete(id);
+    if (deleted) {
+      syncWorkspace();
+    }
+    return deleted;
+  });
Evidence
PR Compliance ID 14 requires IPC handlers to validate inputs and return structured errors. The added
handlers directly pass data, query, and id into DB calls without validation or error shaping.

Rule 8.2: IPC Handlers Must Validate Inputs and Propagate Structured Errors Without Crashing the Main Process
apps/desktop/src/main/ipc/prompt.ipc.ts[284-308]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
IPC handlers for prompt relations do not validate payloads/ids and do not return structured error objects on failure.

## Issue Context
Per IPC boundary requirements, handlers must reject malformed inputs and propagate structured errors without risking main-process instability.

## Fix Focus Areas
- apps/desktop/src/main/ipc/prompt.ipc.ts[284-308]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Inline style added to menu 📘 Rule violation ⚙ Maintainability 8.6
Description
A new JSX style={{ ... }} prop is introduced for the relation chooser menu positioning. This
violates the Tailwind-only styling requirement and adds hard-to-maintain inline styling.
Code

apps/desktop/src/renderer/components/prompt/PromptListView.tsx[R744-749]

+        <div
+          className="fixed z-50 w-44 rounded-md border border-border bg-popover p-1 shadow-lg"
+          style={{
+            left: pendingRelationDrop.x,
+            top: pendingRelationDrop.y,
+          }}
Evidence
PR Compliance ID 22 forbids introducing inline style props. The new relation chooser menu uses
style={{ left: pendingRelationDrop.x, top: pendingRelationDrop.y }}.

Rule 8.6: UI Styling Must Use Tailwind Classes Only (No Inline style Props)
apps/desktop/src/renderer/components/prompt/PromptListView.tsx[744-749]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The relation chooser menu uses an inline `style` prop for `left/top`, which is prohibited.

## Issue Context
UI styling must use Tailwind classes only; inline style props are disallowed.

## Fix Focus Areas
- apps/desktop/src/renderer/components/prompt/PromptListView.tsx[744-749]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. backup as any type assertion 📘 Rule violation ⚙ Maintainability 8.1
Description
New as any assertions are introduced when normalizing imported backups. This weakens type safety
and can hide runtime shape mismatches for backup payloads.
Code

apps/desktop/src/renderer/services/database-backup-format.ts[R100-107]

        ? backup.exportedAt
        : new Date().toISOString(),
    prompts: Array.isArray(backup?.prompts) ? backup.prompts : [],
+    promptRelations: Array.isArray(backup?.promptRelations)
+      ? backup.promptRelations
+      : Array.isArray((backup as any)?.relations)
+        ? (backup as any).relations
+        : [],
Evidence
PR Compliance ID 11 prohibits introducing any and unsafe type assertions without justification.
The new code uses (backup as any) to read relations and promptVersions instead of narrowing
unknown shapes safely.

Rule 8.1: TypeScript Strictness: No any, No @ts-ignore, and Restricted Type Assertions
apps/desktop/src/renderer/services/database-backup-format.ts[89-107]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`normalizeImportedBackup()` uses `(backup as any)` to access legacy fields (`promptVersions`, `relations`), violating the no-`any`/restricted assertions rule.

## Issue Context
TypeScript strictness requires avoiding `any` and unsafe assertions; prefer explicit legacy envelope types and runtime type guards.

## Fix Focus Areas
- apps/desktop/src/renderer/services/database-backup-format.ts[89-107]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


View more (2)
4. Chinese string added in code 📘 Rule violation ⚙ Maintainability 8.3
Description
A new hardcoded Chinese user-facing message is added in a non-locale TypeScript file. This violates
the rule forbidding Chinese characters outside locale JSON resources.
Code

apps/desktop/src/renderer/services/database-backup.ts[R996-998]

+  if (normalizedMessage.includes("prompt relationships require")) {
+    return "该备份包含 Prompt 关系数据,需要在桌面版中导入。";
+  }
Evidence
PR Compliance ID 16 forbids introducing Chinese characters outside locale files. The added error
message string contains Chinese characters directly in a .ts file.

Rule 8.3: No Hardcoded Chinese Characters Outside Locale Files
apps/desktop/src/renderer/services/database-backup.ts[996-998]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Hardcoded Chinese text was added in a non-locale source file.

## Issue Context
All Chinese characters must live only in locale JSON files; user-facing strings should be localized via i18n keys.

## Fix Focus Areas
- apps/desktop/src/renderer/services/database-backup.ts[996-998]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


5. Undirected query misses edges 🐞 Bug ≡ Correctness
Description
PromptRelationDB canonicalizes related_to as an undirected edge by sorting endpoints, but
list() applies incoming/outgoing filters strictly to source_prompt_id or target_prompt_id.
As a result, list({ promptId, direction: 'outgoing'|'incoming', kind: 'related_to' }) will
silently drop relations depending on prompt id ordering.
Code

packages/db/src/prompt-relation.ts[R105-120]

+  list(query: PromptRelationQuery = {}): PromptRelation[] {
+    const clauses: string[] = [];
+    const values: string[] = [];
+
+    if (query.promptId) {
+      if (query.direction === "outgoing") {
+        clauses.push("source_prompt_id = ?");
+        values.push(query.promptId);
+      } else if (query.direction === "incoming") {
+        clauses.push("target_prompt_id = ?");
+        values.push(query.promptId);
+      } else {
+        clauses.push("(source_prompt_id = ? OR target_prompt_id = ?)");
+        values.push(query.promptId, query.promptId);
+      }
+    }
Evidence
The DB intentionally canonicalizes related_to endpoints, but the current list() implementation’s
directional filters only match one column, so related_to edges where the prompt is stored as the
opposite endpoint are excluded.

packages/db/src/prompt-relation.ts[105-120]
packages/db/src/prompt-relation.ts[173-186]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`PromptRelationDB.normalizeEndpoints()` stores `related_to` relations in a canonical (sorted) orientation, but `PromptRelationDB.list()` treats `direction: 'incoming'|'outgoing'` as directional storage. This makes directional queries for `related_to` incomplete.

### Issue Context
`related_to` is intended to be undirected (canonicalized), so querying should not depend on which endpoint ended up in `source_prompt_id`.

### Fix Focus Areas
- packages/db/src/prompt-relation.ts[105-126]
- packages/db/src/prompt-relation.ts[173-186]

### Suggested fix
In `list()` when `query.kind === 'related_to'` and `query.promptId` is set, ignore `query.direction` and always add the symmetric predicate:
- `clauses.push('(source_prompt_id = ? OR target_prompt_id = ?)')`
- `values.push(query.promptId, query.promptId)`

(Optionally) if `query.kind` is not specified but `direction` is, consider whether `related_to` should appear in both incoming/outgoing sets; if yes, include it via an OR condition keyed on `kind = 'related_to'`.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

6. MainContent.tsx expanded past limit 📘 Rule violation ⚙ Maintainability 0.7
Description
New changes further expand MainContent.tsx, which is already over the 2,000 line limit. This
increases review and maintenance risk and violates the size-limit policy for legacy oversized files.
Code

apps/desktop/src/renderer/components/layout/MainContent.tsx[R1992-2003]

            <PromptListView
              prompts={visiblePrompts}
+              relations={relations}
              selectedId={selectedId}
              selectedIds={selectedIds}
              onSelect={(id) => selectPrompt(id)}
              onToggleFavorite={toggleFavorite}
              onCopy={handleCopyPrompt}
              onContextMenu={handleContextMenu}
              onMovePrompt={movePrompt}
+              onCreateRelation={createRelation}
            />
Evidence
PR Compliance ID 28 forbids expanding legacy oversized files beyond 2,000 lines. The modified area
is at line ~1992+ and adds new props/logic, demonstrating the file is already above the limit and is
being expanded further.

Rule 0.7: File and Function Size Limits Must Be Respected (No Expanding Legacy Oversized Files)
apps/desktop/src/renderer/components/layout/MainContent.tsx[1992-2003]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`MainContent.tsx` is already >2000 lines and this PR adds more logic/props, expanding a legacy oversized file.

## Issue Context
Oversized legacy files should not be expanded; instead extract new/adjacent logic into smaller components.

## Fix Focus Areas
- apps/desktop/src/renderer/components/layout/MainContent.tsx[1992-2003]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


7. Update can hit UNIQUE 🐞 Bug ☼ Reliability
Description
PromptRelationDB.update() can change kind (and may reorder endpoints when switching to
related_to) but never checks whether the new (source_prompt_id, target_prompt_id, kind) already
exists. Because the schema enforces a UNIQUE constraint on these columns, such updates can throw a
SQLite constraint error and bubble up through IPC.
Code

packages/db/src/prompt-relation.ts[R67-96]

+  update(id: string, data: UpdatePromptRelationDTO): PromptRelation | null {
+    const existing = this.getById(id);
+    if (!existing) return null;
+
+    const kind = data.kind ?? existing.kind;
+    this.assertRelationKind(kind);
+    const { sourcePromptId, targetPromptId } = this.normalizeEndpoints(
+      existing.sourcePromptId,
+      existing.targetPromptId,
+      kind,
+    );
+    const now = Date.now();
+
+    this.db
+      .prepare(
+        `UPDATE prompt_relations
+         SET source_prompt_id = ?, target_prompt_id = ?, kind = ?, note = ?, updated_at = ?
+         WHERE id = ?`,
+      )
+      .run(
+        sourcePromptId,
+        targetPromptId,
+        kind,
+        data.note === undefined ? existing.note : data.note,
+        now,
+        id,
+      );
+
+    return this.getById(id);
+  }
Evidence
The update path rewrites key columns that participate in a UNIQUE constraint, but there is no
preflight check or merge logic, so SQLite can reject the UPDATE at runtime.

packages/db/src/prompt-relation.ts[67-96]
packages/db/src/schema.ts[59-72]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`PromptRelationDB.update()` updates `(source_prompt_id, target_prompt_id, kind)` without checking for collisions. The `prompt_relations` table enforces `UNIQUE(source_prompt_id, target_prompt_id, kind)`, so an update that changes `kind` (or triggers `related_to` endpoint normalization) can violate the constraint and throw.

### Issue Context
`create()` already treats duplicates as idempotent via `findExisting()`. `update()` should provide similar safety (either by rejecting cleanly or merging).

### Fix Focus Areas
- packages/db/src/prompt-relation.ts[67-96]
- packages/db/src/schema.ts[59-72]

### Suggested fix
Before running the UPDATE:
1. Compute the new `(sourcePromptId, targetPromptId, kind)`.
2. Look up an existing row with those fields (similar to `findExisting`).
3. If a different relation exists:
  - Decide a policy (recommended: merge): update the existing row’s note if needed, delete the current row, and return the existing relation.
  - Or return a controlled error instead of letting SQLite throw.

Also consider wrapping the UPDATE in try/catch to convert constraint errors into a deterministic return/error.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


8. Unhandled relation drop errors 🐞 Bug ☼ Reliability
Description
The relation chooser’s click handler calls void commitRelationDrop(kind), while
commitRelationDrop() is async and awaits onCreateRelation without any error handling. If
relation creation rejects, this becomes an unhandled promise rejection and the menu is already
dismissed, leaving the user with no recovery context.
Code

apps/desktop/src/renderer/components/prompt/PromptListView.tsx[R764-769]

+              <button
+                key={kind}
+                type="button"
+                disabled={disabled}
+                onClick={() => void commitRelationDrop(kind)}
+                className="flex w-full items-center gap-2 rounded px-2 py-1.5 text-left text-xs text-foreground transition-colors hover:bg-accent disabled:cursor-not-allowed disabled:opacity-40"
Evidence
The click handler explicitly discards the async return value, and the async function has an awaited
call that can reject with no surrounding try/catch, which is the standard recipe for unhandled
promise rejections.

apps/desktop/src/renderer/components/prompt/PromptListView.tsx[459-486]
apps/desktop/src/renderer/components/prompt/PromptListView.tsx[743-769]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`commitRelationDrop()` awaits `onCreateRelation?.(...)`, but the UI triggers it via `void commitRelationDrop(kind)` and does not handle rejections. If IPC/DB errors occur, the renderer may emit unhandled promise rejections and the chooser is dismissed before the operation completes.

### Issue Context
The menu is cleared (`setPendingRelationDrop(null)`) before awaiting, so failures currently provide no UI feedback and lose the user’s selection context.

### Fix Focus Areas
- apps/desktop/src/renderer/components/prompt/PromptListView.tsx[459-486]
- apps/desktop/src/renderer/components/prompt/PromptListView.tsx[743-776]

### Suggested fix
Option A (localized): handle the promise at the call site:
```ts
onClick={() => {
 commitRelationDrop(kind).catch((err) => {
   console.error('Failed to create relation', err);
   // optionally restore pendingRelationDrop or show a toast
 });
}}
```

Option B (preferred): handle inside `commitRelationDrop`:
- Capture the current `pendingRelationDrop` in a local.
- Wrap `await onCreateRelation(...)` in try/catch.
- On error, log + optionally restore the pending drop state or show an error toast.

Either approach prevents unhandled rejections and preserves debuggability.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Qodo Logo

@vercel

vercel Bot commented Jun 14, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
prompt-hub Ready Ready Preview, Comment Jun 14, 2026 9:48am

@coderabbitai

coderabbitai Bot commented Jun 14, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

新增 prompt_relations SQLite 表与 PromptRelationDB 数据访问层,支持 related_tovariant_ofdepends_onnext_step 四类图关系。通过 IPC→preload→renderer service→Zustand store 完整链路暴露关系 CRUD;PromptListView 拖拽到列表项中心时弹出关系选择菜单并渲染关系徽标;备份流程新增关系的导出、校验与导入重建;新增 7 个语言包的关系文案及完整的单元/集成测试覆盖。

Changes

Prompt 图关系功能全链路

Layer / File(s) Summary
共享类型、IPC 通道与数据库 Schema
packages/shared/types/prompt.ts, packages/shared/constants/ipc-channels.ts, packages/db/src/schema.ts, packages/db/src/init.ts
定义 PromptRelationKindPromptRelation 及相关 DTO/查询接口;新增四个 IPC 通道常量;prompt_relations 表添加外键约束、自引用禁止 CHECK、三元组唯一性约束及查询索引;将该表加入 REQUIRED_TABLES
PromptRelationDB 数据访问层
packages/db/src/prompt-relation.ts, packages/db/src/index.ts, apps/desktop/src/main/database/index.ts
实现 create(幂等 + related_to 端点规范化)、updatelist(支持 outgoing/incoming/both 方向过滤)、delete 方法及内部校验辅助函数;从 packages/db 与桌面数据库模块重导出 PromptRelationDB
IPC 处理器与 Preload API
apps/desktop/src/main/ipc/index.ts, apps/desktop/src/main/ipc/prompt.ipc.ts, apps/desktop/src/preload/api/prompt.ts
将四个 relation 通道加入 REBINDABLE_DB_CHANNELS;注册 PROMPT_RELATION_* IPC 处理器(写操作后调用 syncWorkspace);preload promptApi 暴露 createRelation/listRelations/updateRelation/deleteRelation
Renderer Service 与 Zustand Store
apps/desktop/src/renderer/services/database.ts, apps/desktop/src/renderer/stores/prompt.store.ts
database.ts 新增四个关系 CRUD 导出函数,含能力缺失降级策略;usePromptStore 新增 relations 状态字段,fetchPrompts 并行加载关系,deletePrompt 时同步清理关联记录。
PromptListView 关系选择器与徽标
apps/desktop/src/renderer/components/prompt/PromptListView.tsx, apps/desktop/src/renderer/components/layout/MainContent.tsx, apps/desktop/src/renderer/i18n/locales/*
拖拽到 inside 区域时弹出关系选择菜单(含 commitRelationDrop 分流逻辑);基于 relations 生成 relationBadgesByPrompt 并在节点下渲染带计数徽标;MainContent 向下传递 relationsonCreateRelation;7 个语言包新增 prompt.relationships 文案键。
备份格式与导入/导出流程
apps/desktop/src/renderer/services/database-backup-format.ts, apps/desktop/src/renderer/services/database-backup.ts
DatabaseBackup 新增 promptRelations 字段;新增 hasPromptRelationShape 校验与引用一致性严格模式;sanitizeImportedBackup 宽松清洗;导出时并行收集关系数据;importDatabaseViaMainProcess 在 prompts 恢复后逐条重建关系,缺少 API 时阻断;选择性导出过滤两端均在集合内的关系。
单元测试与集成测试
apps/desktop/tests/unit/main/prompt-relation-db.test.ts, apps/desktop/tests/unit/components/prompt-list-view-relations.test.tsx, apps/desktop/tests/unit/services/database-backup-format.test.ts, apps/desktop/tests/unit/services/database-backup.test.ts, apps/desktop/tests/integration/services/database-backup-filesystem.integration.test.ts
新增 PromptRelationDB 完整行为测试(规范化、校验、级联删除);新增拖拽关系创建与徽标渲染 UI 测试;备份格式严格/宽松解析用例;backup round-trip 测试覆盖 promptRelation;集成测试验证备份/还原流程中关系数据的导出与回写。
设计文档与规范
spec/changes/active/desktop-prompt-relationship-tree/...
更新 design.md、implementation.md、proposal.md、spec.md、tasks.md,记录关系语义、schema 约束、UI 交互模型、备份恢复流程及后续工作项。

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant PromptListView
  participant usePromptStore
  participant database.ts
  participant promptApi as preload promptApi
  participant Main as prompt.ipc.ts
  participant PromptRelationDB

  User->>PromptListView: 拖拽 Prompt B 到 Prompt A 中心区域
  PromptListView->>PromptListView: 设置 pendingRelationDrop + 计算菜单坐标
  User->>PromptListView: 点击菜单项 (e.g. related_to)
  PromptListView->>usePromptStore: createRelation(dto)
  usePromptStore->>database.ts: createPromptRelation(dto)
  database.ts->>promptApi: createRelation(dto)
  promptApi->>Main: ipcRenderer.invoke(PROMPT_RELATION_CREATE)
  Main->>PromptRelationDB: create(dto)
  PromptRelationDB-->>Main: PromptRelation
  Main->>Main: syncWorkspace()
  Main-->>promptApi: PromptRelation
  promptApi-->>database.ts: PromptRelation
  database.ts-->>usePromptStore: PromptRelation
  usePromptStore->>usePromptStore: 更新 relations 状态
  usePromptStore-->>PromptListView: relations 更新,渲染徽标
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • legeling/PromptHub#173: 同样修改 PromptListView.tsx 中的拖拽/移动交互(onMovePrompt),本 PR 在此基础上新增 onCreateRelation 分流逻辑,两者存在直接的代码级衔接。
  • legeling/PromptHub#172: 引入了 PROMPT_MOVE IPC 通道及层级移动流程,本 PR 的 grouped_under 分支复用该移动逻辑,REBINDABLE_DB_CHANNELS 也在此基础上追加了 relation 通道。

Poem

🐇 小兔子跳跳跳,关系图谱建起来,
related_to 无向边,depends_on 有方向,
拖到中间弹菜单,徽标闪烁树下方,
备份导出带关系,还原一键全回放,
✨ 从此 Prompt 不孤单,图谱相连共成长!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 6.25% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed 标题'feat: add typed prompt relationships'准确地反映了本次变更的主要内容,清晰简洁地总结了新增功能。
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ 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 codex/prompt-relationships-complete-20260614120253

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

Add typed prompt relationships with SQLite persistence, UI chooser, and backup support
✨ Enhancement 🧪 Tests 📝 Documentation 🕐 40+ Minutes

Grey Divider

Walkthroughs

Description
• Persist non-tree prompt relationships in new SQLite prompt_relations table.
• Add drag-and-drop relationship chooser and render relation badges in the prompt list.
• Export/import prompt relations in desktop backups with strict validation and restore support.
Diagram
graph TD
  UI["Prompt list UI"] --> Store["Prompt store"] --> RDB["Renderer DB API"] --> Preload["Preload prompt API"] --> IPC["Main IPC"] --> RelDB["PromptRelationDB"] --> SQLite[("SQLite")]
  Backup["Backup service"] --> RDB
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Unify all relationship kinds into prompt_relations (including grouped_under)
  • ➕ Single source of truth for all relationship semantics
  • ➕ Enables future graph queries/views without special-casing tree data
  • ➖ Higher migration risk and larger rewrite of existing tree/list behaviors
  • ➖ Potential performance regressions for the common tree browsing path
2. Store relations as JSON metadata on prompts
  • ➕ Fewer tables and simpler schema evolution
  • ➕ Potentially faster writes for small graphs
  • ➖ Harder to query, validate, dedupe, and index
  • ➖ More error-prone backup/import sanitization and integrity constraints
3. Make relation creation purely UI-side until a dedicated editor exists
  • ➕ Less main-process/IPC surface area in the short term
  • ➕ Faster iteration on UX
  • ➖ Not durable/portable without backup support; violates stated durability goals
  • ➖ Harder to enforce constraints like dedupe and cascade deletes

Recommendation: The PR’s approach is the best tradeoff for V1: keep grouped_under on prompts.parent_id for fast tree operations, and introduce a dedicated prompt_relations table for typed graph edges with DB-enforced integrity. The canonicalization/idempotent-create behavior for related_to is a pragmatic UX/data-quality choice and keeps future graph exploration feasible without destabilizing the existing tree model.

Grey Divider

File Changes

Enhancement (23)
index.ts Re-export PromptRelationDB from desktop main database module +1/-0

Re-export PromptRelationDB from desktop main database module

• Adds PromptRelationDB to the desktop main-process database exports so IPC handlers and tests can import it consistently.

apps/desktop/src/main/database/index.ts


index.ts Allow prompt move + relation CRUD channels to be rebound +5/-0

Allow prompt move + relation CRUD channels to be rebound

• Adds PROMPT_MOVE and new prompt relation IPC channels to the rebindable channel list, keeping IPC wiring consistent with other DB-backed operations.

apps/desktop/src/main/ipc/index.ts


prompt.ipc.ts Add main-process IPC handlers for prompt relation CRUD +31/-0

Add main-process IPC handlers for prompt relation CRUD

• Instantiates PromptRelationDB and registers create/list/update/delete IPC handlers. Triggers workspace sync after relation mutations to keep renderer state consistent.

apps/desktop/src/main/ipc/prompt.ipc.ts


prompt.ts Expose prompt relation APIs via preload +11/-0

Expose prompt relation APIs via preload

• Adds createRelation/listRelations/updateRelation/deleteRelation functions that invoke the corresponding IPC channels from the renderer.

apps/desktop/src/preload/api/prompt.ts


MainContent.tsx Wire prompt relations and creation callback into PromptListView +4/-0

Wire prompt relations and creation callback into PromptListView

• Plumbs relations state and createRelation action from the prompt store into PromptListView so UI can render badges and create relations on drop.

apps/desktop/src/renderer/components/layout/MainContent.tsx


PromptListView.tsx Add drop-center relationship chooser and relation badges in prompt list +229/-11

Add drop-center relationship chooser and relation badges in prompt list

• Changes center-row drops to open a compact relationship menu; choosing grouped_under performs the existing tree move, while other kinds call onCreateRelation. Computes per-prompt relation badge counts and renders localized badges for connected prompts.

apps/desktop/src/renderer/components/prompt/PromptListView.tsx


de.json Add German strings for relationship chooser and badges +9/-0

Add German strings for relationship chooser and badges

• Introduces prompt.relationships translations for menu label, relation kinds, and cancel action.

apps/desktop/src/renderer/i18n/locales/de.json


en.json Add English strings for relationship chooser and badges +9/-0

Add English strings for relationship chooser and badges

• Introduces prompt.relationships translations for menu label, relation kinds, and cancel action.

apps/desktop/src/renderer/i18n/locales/en.json


es.json Add Spanish strings for relationship chooser and badges +9/-0

Add Spanish strings for relationship chooser and badges

• Introduces prompt.relationships translations for menu label, relation kinds, and cancel action.

apps/desktop/src/renderer/i18n/locales/es.json


fr.json Add French strings for relationship chooser and badges +9/-0

Add French strings for relationship chooser and badges

• Introduces prompt.relationships translations for menu label, relation kinds, and cancel action.

apps/desktop/src/renderer/i18n/locales/fr.json


ja.json Add Japanese strings for relationship chooser and badges +9/-0

Add Japanese strings for relationship chooser and badges

• Introduces prompt.relationships translations for menu label, relation kinds, and cancel action.

apps/desktop/src/renderer/i18n/locales/ja.json


zh-TW.json Add Traditional Chinese strings for relationship chooser and badges +9/-0

Add Traditional Chinese strings for relationship chooser and badges

• Introduces prompt.relationships translations for menu label, relation kinds, and cancel action.

apps/desktop/src/renderer/i18n/locales/zh-TW.json


zh.json Add Simplified Chinese strings for relationship chooser and badges +9/-0

Add Simplified Chinese strings for relationship chooser and badges

• Introduces prompt.relationships translations for menu label, relation kinds, and cancel action.

apps/desktop/src/renderer/i18n/locales/zh.json


database-backup-format.ts Extend backup schema and validation to include promptRelations +65/-1

Extend backup schema and validation to include promptRelations

• Adds promptRelations to the backup format with strict shape validation and lenient sanitization that drops malformed/orphaned relations. Tracks skipped relation counts and treats relations as meaningful backup content.

apps/desktop/src/renderer/services/database-backup-format.ts


database-backup.ts Export/import promptRelations and restore via main process +40/-1

Export/import promptRelations and restore via main process

• Includes promptRelations in export and selective export (filtered to exported prompts). Restores relations via main-process API after prompts are restored, and fails fast when only IndexedDB fallback is available for relation-bearing backups.

apps/desktop/src/renderer/services/database-backup.ts


database.ts Add renderer database service functions for prompt relation CRUD +48/-1

Add renderer database service functions for prompt relation CRUD

• Introduces create/list/update/delete helpers that call the desktop API when available and return safe defaults otherwise. Ensures relation operations fail loudly when the required desktop API is missing.

apps/desktop/src/renderer/services/database.ts


prompt.store.ts Add prompt relations state + actions to prompt store +56/-3

Add prompt relations state + actions to prompt store

• Adds relations to Zustand state, loads them alongside prompts, and provides create/update/delete actions that also schedule save/sync. Cleans up relations when a prompt is deleted.

apps/desktop/src/renderer/stores/prompt.store.ts


index.ts Export PromptRelationDB from db package +1/-0

Export PromptRelationDB from db package

• Adds PromptRelationDB to the public DB package exports so consumers (desktop main) can construct it.

packages/db/src/index.ts


init.ts Require prompt_relations table in DB initialization +1/-0

Require prompt_relations table in DB initialization

• Adds prompt_relations to the REQUIRED_TABLES list to ensure schema completeness checks include the new table.

packages/db/src/init.ts


prompt-relation.ts Introduce PromptRelationDB SQLite DAO with validation and canonicalization +237/-0

Introduce PromptRelationDB SQLite DAO with validation and canonicalization

• Implements create/list/update/delete for prompt_relations with kind validation, prompt existence checks, self-relation prevention, and related_to endpoint canonicalization for undirected semantics. Uses a unique constraint + idempotent create behavior (optionally updating note).

packages/db/src/prompt-relation.ts


schema.ts Add prompt_relations table and supporting indexes +18/-0

Add prompt_relations table and supporting indexes

• Introduces prompt_relations with foreign keys, self-edge check, kind constraint, and uniqueness across endpoints+kind. Adds indexes for source, target, and kind to support lookups.

packages/db/src/schema.ts


ipc-channels.ts Add IPC channel constants for prompt relation CRUD +4/-0

Add IPC channel constants for prompt relation CRUD

• Defines PROMPT_RELATION_CREATE/LIST/UPDATE/DELETE channel names used across main, preload, and renderer layers.

packages/shared/constants/ipc-channels.ts


prompt.ts Add typed prompt relation kinds, DTOs, and query shapes +38/-0

Add typed prompt relation kinds, DTOs, and query shapes

• Introduces PromptRelationKind vs PromptGraphRelationKind, PromptRelation entity shape, and Create/Update DTOs plus query filters for listing relations.

packages/shared/types/prompt.ts


Tests (5)
database-backup-filesystem.integration.test.ts Integration coverage for filesystem backup export/restore with relations +69/-3

Integration coverage for filesystem backup export/restore with relations

• Extends the filesystem backup integration test to export promptRelations and restore them through mocked desktop APIs, including cleanup on prompt deletion.

apps/desktop/tests/integration/services/database-backup-filesystem.integration.test.ts


prompt-list-view-relations.test.tsx Unit tests for relation drop chooser and badge rendering +130/-0

Unit tests for relation drop chooser and badge rendering

• Adds tests verifying that a center-row drop opens the chooser and calls onCreateRelation with the selected kind. Also asserts that connected prompts render relation badges for both endpoints.

apps/desktop/tests/unit/components/prompt-list-view-relations.test.tsx


prompt-relation-db.test.ts Unit tests for PromptRelationDB behavior and constraints +133/-0

Unit tests for PromptRelationDB behavior and constraints

• Adds tests for directed relations, related_to canonicalization/idempotency, validation failures, update/delete flows, and cascade deletion when prompts are removed.

apps/desktop/tests/unit/main/prompt-relation-db.test.ts


database-backup-format.test.ts Backup-format tests for parsing and sanitizing promptRelations +115/-0

Backup-format tests for parsing and sanitizing promptRelations

• Adds strict parsing coverage for promptRelations and lenient-mode behavior that drops relations referencing prompts that were dropped during sanitization.

apps/desktop/tests/unit/services/database-backup-format.test.ts


database-backup.test.ts Backup service tests for exporting/restoring promptRelations +33/-3

Backup service tests for exporting/restoring promptRelations

• Extends backup export expectations to include promptRelations and verifies restore recreates relations via the desktop API after prompt restore.

apps/desktop/tests/unit/services/database-backup.test.ts


Documentation (5)
design.md Update design record to include prompt_relations and UI/backup semantics +36/-7

Update design record to include prompt_relations and UI/backup semantics

• Documents prompt_relations as the durable source for non-tree relationships, defines canonicalization and cascade behavior, and describes the drop-center chooser and backup/restore rules.

spec/changes/active/desktop-prompt-relationship-tree/design.md


implementation.md Document implementation deltas and verification for relation feature +12/-2

Document implementation deltas and verification for relation feature

• Adds implementation notes covering PromptRelationDB, IPC/preload/store wiring, UI chooser/badges, and backup/restore changes, plus updated test commands.

spec/changes/active/desktop-prompt-relationship-tree/implementation.md


proposal.md Expand proposal to cover typed graph relationships and backup inclusion +9/-2

Expand proposal to cover typed graph relationships and backup inclusion

• Updates scope and risks to explicitly add prompt_relations, the chooser-based drag workflow, list badges, and backup durability expectations.

spec/changes/active/desktop-prompt-relationship-tree/proposal.md


spec.md Update relationship spec to mark graph relations as implemented +16/-6

Update relationship spec to mark graph relations as implemented

• Revises the relationship model and scenarios to include typed graph links, chooser behavior on center-row drop, cascade deletion of relation edges, and backup export/restore expectations.

spec/changes/active/desktop-prompt-relationship-tree/specs/prompt-relationships/spec.md


tasks.md Track completion of relation persistence, UI, backup, and tests +6/-0

Track completion of relation persistence, UI, backup, and tests

• Marks tasks complete for prompt_relations persistence, IPC/service/store integration, UI chooser + badges, backup support, and regression tests.

spec/changes/active/desktop-prompt-relationship-tree/tasks.md


Grey Divider

Qodo Logo

@qodo-code-review

Copy link
Copy Markdown

CI Feedback 🧐

A test triggered by this PR failed. Here is an AI-generated analysis of the failure:

Action: desktop-verify

Failed stage: Unit tests [❌]

Failed test name: rules workspace storage > always includes built-in global rule descriptors even when target files are missing

Failure summary:

The GitHub Action failed because pnpm test:unit (Vitest) exited with status 1 due to multiple
failing unit tests in apps/desktop.

Failing tests and reasons:
- tests/unit/main/rules-workspace.test.ts (rules workspace storage >
always includes built-in global rule descriptors even when target files are missing) failed at
tests/unit/main/rules-workspace.test.ts:320:25 with an assertion mismatch: listRuleDescriptors()
returned an empty array ([]) but the test expected it to contain built-in global rule descriptors
(e.g., opencode-global / AGENTS.md, claude-global / CLAUDE.md).
-
tests/unit/main/updater-install.test.ts (updater install backup > blocks in-app install for
Homebrew-installed macOS builds) failed at tests/unit/main/updater-install.test.ts:170:44 because a
spy (specifically autoUpdater.quitAndInstall) was expected not to be called, but it was called once.

- tests/unit/main/updater.test.ts had 3 failures where install source detection returned unknown
instead of expected values:
- registers installSource handler and replaces old updater handlers on
re-register failed at tests/unit/main/updater.test.ts:239:42 (expected 'unknown' to be 'direct').

- detects Homebrew-installed macOS app from Caskroom path failed at
tests/unit/main/updater.test.ts:304:11 (expected 'unknown' to be 'homebrew').
- treats regular
macOS app bundle path as direct install failed at tests/unit/main/updater.test.ts:317:11 (expected
'unknown' to be 'direct').

Since there were 5 total failed tests, Vitest returned a non-zero exit code, causing
ERR_PNPM_RECURSIVE_RUN_FIRST_FAIL and the job to fail.

Relevant error logs:
1:  ##[group]Runner Image Provisioner
2:  Hosted Compute Agent
...

232:  > @prompthub/desktop@0.5.8 typecheck /home/runner/work/PromptHub/PromptHub/apps/desktop
233:  > tsc --noEmit
234:  ##[group]Run pnpm test:unit
235:  �[36;1mpnpm test:unit�[0m
236:  shell: /usr/bin/bash -e {0}
237:  env:
238:  PNPM_HOME: /home/runner/setup-pnpm/node_modules/.bin
239:  ##[endgroup]
240:  > prompthub-monorepo@0.5.8 test:unit /home/runner/work/PromptHub/PromptHub
241:  > pnpm --filter @prompthub/desktop test:unit
242:  > @prompthub/desktop@0.5.8 test:unit /home/runner/work/PromptHub/PromptHub/apps/desktop
243:  > vitest run tests/unit
244:  �[33mThe CJS build of Vite's Node API is deprecated. See https://vite.dev/guide/troubleshooting.html#vite-cjs-node-api-deprecated for more details.�[39m
245:  �[1m�[7m�[36m RUN �[39m�[27m�[22m �[36mv2.1.9 �[39m�[90m/home/runner/work/PromptHub/PromptHub/apps/desktop�[39m
246:  �[90mstderr�[2m | tests/unit/stores/skill.store.test.ts�[2m > �[22m�[2mskill store�[2m > �[22m�[2mblocks installing official registry skills when only placeholder frontmatter is available
247:  �[22m�[39mFailed to resolve latest SKILL.md for "pdf", falling back to cached registry content: Error: network down
248:  at �[90m/home/runner/work/PromptHub/PromptHub/apps/desktop/�[39mtests/unit/stores/skill.store.test.ts:530:26
249:  at file:///home/runner/work/PromptHub/PromptHub/node_modules/�[4m.pnpm�[24m/@vitest+runner@2.1.9/node_modules/�[4m@vitest/runner�[24m/dist/index.js:146:14
250:  at file:///home/runner/work/PromptHub/PromptHub/node_modules/�[4m.pnpm�[24m/@vitest+runner@2.1.9/node_modules/�[4m@vitest/runner�[24m/dist/index.js:533:11
251:  at runWithTimeout (file:///home/runner/work/PromptHub/PromptHub/node_modules/�[4m.pnpm�[24m/@vitest+runner@2.1.9/node_modules/�[4m@vitest/runner�[24m/dist/index.js:39:7)
252:  at runTest (file:///home/runner/work/PromptHub/PromptHub/node_modules/�[4m.pnpm�[24m/@vitest+runner@2.1.9/node_modules/�[4m@vitest/runner�[24m/dist/index.js:1056:17)
253:  at runSuite (file:///home/runner/work/PromptHub/PromptHub/node_modules/�[4m.pnpm�[24m/@vitest+runner@2.1.9/node_modules/�[4m@vitest/runner�[24m/dist/index.js:1205:15)
254:  at runSuite (file:///home/runner/work/PromptHub/PromptHub/node_modules/�[4m.pnpm�[24m/@vitest+runner@2.1.9/node_modules/�[4m@vitest/runner�[24m/dist/index.js:1205:15)
255:  at runFiles (file:///home/runner/work/PromptHub/PromptHub/node_modules/�[4m.pnpm�[24m/@vitest+runner@2.1.9/node_modules/�[4m@vitest/runner�[24m/dist/index.js:1262:5)
256:  at startTests (file:///home/runner/work/PromptHub/PromptHub/node_modules/�[4m.pnpm�[24m/@vitest+runner@2.1.9/node_modules/�[4m@vitest/runner�[24m/dist/index.js:1271:3)
257:  at file:///home/runner/work/PromptHub/PromptHub/node_modules/�[4m.pnpm�[24m/vitest@2.1.9_@types+node@24.12.4_jsdom@27.3.0/node_modules/�[4mvitest�[24m/dist/chunks/runBaseTests.3qpJUEJM.js:126:11
258:  �[90mstderr�[2m | tests/unit/stores/skill.store.test.ts�[2m > �[22m�[2mskill store�[2m > �[22m�[2mrolls back a created package skill when remote package persistence fails
259:  �[22m�[39mFailed to create local repo for registry skill "failed-package": Error: clone failed
260:  at �[90m/home/runner/work/PromptHub/PromptHub/apps/desktop/�[39mtests/unit/stores/skill.store.test.ts:1560:26
261:  at file:///home/runner/work/PromptHub/PromptHub/node_modules/�[4m.pnpm�[24m/@vitest+runner@2.1.9/node_modules/�[4m@vitest/runner�[24m/dist/index.js:146:14
262:  at file:///home/runner/work/PromptHub/PromptHub/node_modules/�[4m.pnpm�[24m/@vitest+runner@2.1.9/node_modules/�[4m@vitest/runner�[24m/dist/index.js:533:11
263:  at runWithTimeout (file:///home/runner/work/PromptHub/PromptHub/node_modules/�[4m.pnpm�[24m/@vitest+runner@2.1.9/node_modules/�[4m@vitest/runner�[24m/dist/index.js:39:7)
264:  at runTest (file:///home/runner/work/PromptHub/PromptHub/node_modules/�[4m.pnpm�[24m/@vitest+runner@2.1.9/node_modules/�[4m@vitest/runner�[24m/dist/index.js:1056:17)
265:  at runSuite (file:///home/runner/work/PromptHub/PromptHub/node_modules/�[4m.pnpm�[24m/@vitest+runner@2.1.9/node_modules/�[4m@vitest/runner�[24m/dist/index.js:1205:15)
266:  at runSuite (file:///home/runner/work/PromptHub/PromptHub/node_modules/�[4m.pnpm�[24m/@vitest+runner@2.1.9/node_modules/�[4m@vitest/runner�[24m/dist/index.js:1205:15)
267:  at runFiles (file:///home/runner/work/PromptHub/PromptHub/node_modules/�[4m.pnpm�[24m/@vitest+runner@2.1.9/node_modules/�[4m@vitest/runner�[24m/dist/index.js:1262:5)
268:  at startTests (file:///home/runner/work/PromptHub/PromptHub/node_modules/�[4m.pnpm�[24m/@vitest+runner@2.1.9/node_modules/�[4m@vitest/runner�[24m/dist/index.js:1271:3)
269:  at file:///home/runner/work/PromptHub/PromptHub/node_modules/�[4m.pnpm�[24m/vitest@2.1.9_@types+node@24.12.4_jsdom@27.3.0/node_modules/�[4mvitest�[24m/dist/chunks/runBaseTests.3qpJUEJM.js:126:11
270:  �[32m✓�[39m tests/unit/stores/skill.store.test.ts �[2m(�[22m�[2m52 tests�[22m�[2m)�[22m�[90m 269�[2mms�[22m�[39m
271:  �[90mstderr�[2m | tests/unit/main/skill-installer.test.ts�[2m > �[22m�[2mSkillInstaller.deleteRepoByPath�[2m > �[22m�[2mblocks deletion of paths outside skills directory
272:  �[22m�[39m[Security] Path traversal blocked on delete: /tmp/skill-installer-test-PuDC2X/outside-dir
273:  �[90mstderr�[2m | tests/unit/main/skill-installer.test.ts�[2m > �[22m�[2mSkillInstaller.deleteRepoByPath�[2m > �[22m�[2mblocks path traversal via ../
274:  �[22m�[39m[Security] Path traversal blocked on delete: /tmp/skill-installer-test-Au9VdF/data/other-dir
275:  �[90mstderr�[2m | tests/unit/components/skill-store-remote.test.tsx�[2m > �[22m�[2mSkillStore remote loading�[2m > �[22m�[2mdoes not retry indefinitely after a remote fetch failure
276:  �[22m�[39mFailed to load remote store claude-code: Error: Failed to reach GitHub. Check your network connection or switch to another network and retry.
277:  at mapGitHubStoreError �[90m(/home/runner/work/PromptHub/PromptHub/apps/desktop/�[39msrc/renderer/services/github-skill-store.ts:159:12�[90m)�[39m
278:  at Module.loadGitHubSkillRepo �[90m(/home/runner/work/PromptHub/PromptHub/apps/desktop/�[39msrc/renderer/services/github-skill-store.ts:251:11�[90m)�[39m
279:  �[90m    at processTicksAndRejections (node:internal/process/task_queues:104:5)�[39m
280:  at �[90m/home/runner/work/PromptHub/PromptHub/apps/desktop/�[39msrc/renderer/components/skill/store-remote-sync.ts:323:16
281:  at �[90m/home/runner/work/PromptHub/PromptHub/apps/desktop/�[39msrc/renderer/components/skill/store-remote-sync.ts:800:19
282:  �[90mstderr�[2m | tests/unit/components/skill-store-remote.test.tsx�[2m > �[22m�[2mSkillStore remote loading�[2m > �[22m�[2mshows retry and network guidance for GitHub rate-limit failures
283:  �[22m�[39mFailed to load remote store claude-code: Error: GitHub API rate limit reached. Try again in a few minutes, or switch to another network and retry.
284:  at mapGitHubStoreError �[90m(/home/runner/work/PromptHub/PromptHub/apps/desktop/�[39msrc/renderer/services/github-skill-store.ts:151:12�[90m)�[39m
285:  at Module.loadGitHubSkillRepo �[90m(/home/runner/work/PromptHub/PromptHub/apps/desktop/�[39msrc/renderer/services/github-skill-store.ts:251:11�[90m)�[39m
...

295:  �[22m�[39mSuccessfully installed skill __lock-test-1__ to claude
296:  �[90mstdout�[2m | tests/unit/main/skill-installer.test.ts�[2m > �[22m�[2mP1-11: platform config concurrent safety�[2m > �[22m�[2mconcurrent installToPlatform calls are serialized (no data loss)
297:  �[22m�[39mSuccessfully installed skill __lock-test-2__ to claude
298:  �[90mstdout�[2m | tests/unit/main/skill-installer.test.ts�[2m > �[22m�[2mP1-11: platform config concurrent safety�[2m > �[22m�[2mconcurrent installToPlatform calls are serialized (no data loss)
299:  �[22m�[39mSuccessfully installed skill __lock-test-3__ to claude
300:  �[90mstdout�[2m | tests/unit/main/skill-installer.test.ts�[2m > �[22m�[2mP1-11: platform config concurrent safety�[2m > �[22m�[2mconcurrent installToPlatform calls are serialized (no data loss)
301:  �[22m�[39mSuccessfully installed skill __lock-test-4__ to claude
302:  �[90mstdout�[2m | tests/unit/main/skill-installer.test.ts�[2m > �[22m�[2mP1-11: platform config concurrent safety�[2m > �[22m�[2mconcurrent installToPlatform calls are serialized (no data loss)
303:  �[22m�[39mSuccessfully uninstalled skill __lock-test-0__ from claude
304:  Successfully uninstalled skill __lock-test-1__ from claude
305:  �[90mstdout�[2m | tests/unit/main/skill-installer.test.ts�[2m > �[22m�[2mP1-11: platform config concurrent safety�[2m > �[22m�[2mconcurrent installToPlatform calls are serialized (no data loss)
306:  �[22m�[39mSuccessfully uninstalled skill __lock-test-2__ from claude
307:  Successfully uninstalled skill __lock-test-3__ from claude
308:  Successfully uninstalled skill __lock-test-4__ from claude
309:  �[90mstderr�[2m | tests/unit/components/skill-store-remote.test.tsx�[2m > �[22m�[2mSkillStore remote loading�[2m > �[22m�[2mshows network guidance when GitHub cannot be reached
310:  �[22m�[39mFailed to load remote store claude-code: Error: 无法连接到 GitHub,请检查当前网络,或切换网络后再试。
311:  at mapGitHubStoreError �[90m(/home/runner/work/PromptHub/PromptHub/apps/desktop/�[39msrc/renderer/services/github-skill-store.ts:159:12�[90m)�[39m
312:  at Module.loadGitHubSkillRepo �[90m(/home/runner/work/PromptHub/PromptHub/apps/desktop/�[39msrc/renderer/services/github-skill-store.ts:251:11�[90m)�[39m
313:  at �[90m/home/runner/work/PromptHub/PromptHub/apps/desktop/�[39msrc/renderer/components/skill/store-remote-sync.ts:323:16
314:  at �[90m/home/runner/work/PromptHub/PromptHub/apps/desktop/�[39msrc/renderer/components/skill/store-remote-sync.ts:800:19
315:  �[90mstderr�[2m | tests/unit/components/skill-store-remote.test.tsx�[2m > �[22m�[2mSkillStore remote loading�[2m > �[22m�[2mshows invalid repository guidance when the GitHub repo is missing
316:  �[22m�[39mFailed to load remote store claude-code: Error: 仓库不存在,或仓库地址无效,请检查 GitHub 仓库地址后重试。
317:  at mapGitHubStoreError �[90m(/home/runner/work/PromptHub/PromptHub/apps/desktop/�[39msrc/renderer/services/github-skill-store.ts:155:12�[90m)�[39m
318:  at Module.loadGitHubSkillRepo �[90m(/home/runner/work/PromptHub/PromptHub/apps/desktop/�[39msrc/renderer/services/github-skill-store.ts:251:11�[90m)�[39m
...

387:  Scan path does not exist, skipping: /tmp/scanlocal-test-AOE4jI/.config/opencode/skills
388:  Scan path does not exist, skipping: /tmp/scanlocal-test-AOE4jI/.cline/skills
389:  Scan path does not exist, skipping: /tmp/scanlocal-test-AOE4jI/.codex/skills
390:  Scan path does not exist, skipping: /tmp/scanlocal-test-AOE4jI/.kilo/skills
391:  Scan path does not exist, skipping: /tmp/scanlocal-test-AOE4jI/.config/amp/skills
392:  �[90mstdout�[2m | tests/unit/main/skill-installer.test.ts�[2m > �[22m�[2mSkillInstaller.scanLocal (with real DB)�[2m > �[22m�[2mreturns zero imported and empty skipped for empty directories
393:  �[22m�[39mScan path does not exist, skipping: /tmp/scanlocal-test-AOE4jI/.openclaw/skills
394:  Scan path does not exist, skipping: /tmp/scanlocal-test-AOE4jI/.qoder/skills
395:  Scan path does not exist, skipping: /tmp/scanlocal-test-AOE4jI/.qoderwork/skills
396:  Scan path does not exist, skipping: /tmp/scanlocal-test-AOE4jI/.hermes/skills
397:  Scan path does not exist, skipping: /tmp/scanlocal-test-AOE4jI/.codebuddy/skills
398:  �[90mstdout�[2m | tests/unit/main/skill-installer.test.ts�[2m > �[22m�[2mSkillInstaller.installFromGithub�[2m > �[22m�[2maccepts self-hosted git repository URLs
399:  �[22m�[39mCloning https://gitea.example.com/icelemon/skills to /tmp/skill-installer-test-QLSq2O/data/skills/icelemon-skills
400:  �[90mstdout�[2m | tests/unit/main/skill-installer.test.ts�[2m > �[22m�[2mSkillInstaller.installFromGithub�[2m > �[22m�[2mremoves the temporary clone after moving a GitHub install into the managed repo
401:  �[22m�[39mCloning https://github.com/owner/repo to /tmp/skill-installer-test-4cJ6G6/data/skills/owner-repo
402:  �[90mstdout�[2m | tests/unit/main/skill-installer.test.ts�[2m > �[22m�[2mSkillInstaller.installFromGithub�[2m > �[22m�[2mrolls back the created DB row when post-create persistence fails
403:  �[22m�[39mCloning https://github.com/owner/repo to /tmp/skill-installer-test-ss5The/data/skills/owner-repo
404:  �[90mstderr�[2m | tests/unit/main/skill-installer.test.ts�[2m > �[22m�[2mSkillInstaller.installFromGithub�[2m > �[22m�[2mrolls back the created DB row when post-create persistence fails
405:  �[22m�[39mInstallation failed: Error: update failed
406:  at Object.<anonymous> �[90m(/home/runner/work/PromptHub/PromptHub/apps/desktop/�[39mtests/unit/main/skill-installer.test.ts:3172:15�[90m)�[39m
...

445:  �[32m✓�[39m tests/unit/components/skill-projects-view.test.tsx �[2m(�[22m�[2m22 tests�[22m�[2m)�[22m�[33m 12163�[2mms�[22m�[39m
446:  �[33m�[2m✓�[22m�[39m SkillProjectsView�[2m > �[22mshows project skill cards first and opens project detail actions after click �[33m2503�[2mms�[22m�[39m
447:  �[33m�[2m✓�[22m�[39m SkillProjectsView�[2m > �[22mshows a source-target action for external symlink project skills �[33m313�[2mms�[22m�[39m
448:  �[33m�[2m✓�[22m�[39m SkillProjectsView�[2m > �[22mremoves a registered project after confirmation without deleting files �[33m428�[2mms�[22m�[39m
449:  �[33m�[2m✓�[22m�[39m SkillProjectsView�[2m > �[22mkeeps the project skill view selected when opening and returning from project detail �[33m461�[2mms�[22m�[39m
450:  �[33m�[2m✓�[22m�[39m SkillProjectsView�[2m > �[22mshows imported card shortcuts and keeps project detail actions for imported skills �[33m564�[2mms�[22m�[39m
451:  �[33m�[2m✓�[22m�[39m SkillProjectsView�[2m > �[22mdoes not treat same-name project skills as imported when paths differ �[33m304�[2mms�[22m�[39m
452:  �[33m�[2m✓�[22m�[39m SkillProjectsView�[2m > �[22mtreats a project copy with the same directory fingerprint as a My Skills install �[33m605�[2mms�[22m�[39m
453:  �[33m�[2m✓�[22m�[39m SkillProjectsView�[2m > �[22mimports project skills into my skills with copy mode �[33m446�[2mms�[22m�[39m
454:  �[33m�[2m✓�[22m�[39m SkillProjectsView�[2m > �[22mshows remove from project for already imported project skills �[33m374�[2mms�[22m�[39m
455:  �[33m�[2m✓�[22m�[39m SkillProjectsView�[2m > �[22mallows importing selected library skills from the project header �[33m309�[2mms�[22m�[39m
456:  �[33m�[2m✓�[22m�[39m SkillProjectsView�[2m > �[22mmarks library skills already present in the selected project target �[33m319�[2mms�[22m�[39m
457:  �[33m�[2m✓�[22m�[39m SkillProjectsView�[2m > �[22msupports advanced import targets and custom folders �[33m1031�[2mms�[22m�[39m
458:  �[33m�[2m✓�[22m�[39m SkillProjectsView�[2m > �[22msupports symlink mode when importing my skills into a project �[33m703�[2mms�[22m�[39m
459:  �[33m�[2m✓�[22m�[39m SkillProjectsView�[2m > �[22mremembers project import preferences after reopening the modal �[33m2237�[2mms�[22m�[39m
460:  �[33m�[2m✓�[22m�[39m SkillProjectsView�[2m > �[22mwarns when background rescan fails after a successful import �[33m314�[2mms�[22m�[39m
461:  �[33m�[2m✓�[22m�[39m SkillProjectsView�[2m > �[22mdeploys a project-local skill to the default project target �[33m452�[2mms�[22m�[39m
462:  �[33m�[2m✓�[22m�[39m SkillProjectsView�[2m > �[22mblocks redeploying a project-local skill into its current target tree �[33m361�[2mms�[22m�[39m
463:  �[32m✓�[39m tests/unit/services/self-hosted-sync.test.ts �[2m(�[22m�[2m9 tests�[22m�[2m)�[22m�[90m 37�[2mms�[22m�[39m
464:  �[90mstderr�[2m | tests/unit/components/data-settings.test.tsx�[2m > �[22m�[2mDataSettings�[2m > �[22m�[2mkeeps S3 fields visible but disabled until storage is enabled
465:  �[22m�[39mNot implemented: navigation to another Document
466:  �[32m✓�[39m tests/unit/components/ai-settings-prototype.test.tsx �[2m(�[22m�[2m32 tests�[22m�[2m)�[22m�[33m 12997�[2mms�[22m�[39m
467:  �[33m�[2m✓�[22m�[39m AISettingsPrototype�[2m > �[22mrenders translated English copy instead of hard-coded Chinese �[33m931�[2mms�[22m�[39m
468:  �[33m�[2m✓�[22m�[39m AISettingsPrototype�[2m > �[22mupdates endpoint api key and url inline without opening the edit dialog �[33m499�[2mms�[22m�[39m
469:  �[33m�[2m✓�[22m�[39m AISettingsPrototype�[2m > �[22madds a provider endpoint without creating a model �[33m346�[2mms�[22m�[39m
470:  �[33m�[2m✓�[22m�[39m AISettingsPrototype�[2m > �[22mpersists chat parameters when adding a chat model �[33m920�[2mms�[22m�[39m
471:  �[33m�[2m✓�[22m�[39m AISettingsPrototype�[2m > �[22mpersists the vision model capability flag �[33m483�[2mms�[22m�[39m
472:  �[33m�[2m✓�[22m�[39m AISettingsPrototype�[2m > �[22mpersists image generation as a model capability from the capability section �[33m374�[2mms�[22m�[39m
473:  �[33m�[2m✓�[22m�[39m AISettingsPrototype�[2m > �[22mpreserves both chat and image parameters for dual-capability models �[33m491�[2mms�[22m�[39m
474:  �[33m�[2m✓�[22m�[39m AISettingsPrototype�[2m > �[22mkeeps advanced parameters collapsed by default in the add model modal �[33m544�[2mms�[22m�[39m
475:  �[33m�[2m✓�[22m�[39m AISettingsPrototype�[2m > �[22mpersists image parameters when adding an image model �[33m1218�[2mms�[22m�[39m
476:  �[33m�[2m✓�[22m�[39m AISettingsPrototype�[2m > �[22mmaps raw network failures to a friendlier connection message �[33m412�[2mms�[22m�[39m
477:  �[33m�[2m✓�[22m�[39m AISettingsPrototype�[2m > �[22mincludes the model name in success toasts when testing a draft chat model �[33m402�[2mms�[22m�[39m
478:  �[33m�[2m✓�[22m�[39m AISettingsPrototype�[2m > �[22mincludes the model name in failure toasts when testing a draft chat model �[33m412�[2mms�[22m�[39m
479:  �[33m�[2m✓�[22m�[39m AISettingsPrototype�[2m > �[22mdefaults known image models to image type when selected from fetched models �[33m405�[2mms�[22m�[39m
...

490:  �[33m�[2m✓�[22m�[39m DataSettings�[2m > �[22mlets users choose one active sync source while keeping multiple backup targets enabled �[33m394�[2mms�[22m�[39m
491:  �[33m�[2m✓�[22m�[39m DataSettings�[2m > �[22menables S3 actions once storage is enabled in settings �[33m316�[2mms�[22m�[39m
492:  �[33m�[2m✓�[22m�[39m DataSettings�[2m > �[22mshows only the latest three upgrade backups until expanded �[33m314�[2mms�[22m�[39m
493:  �[90mstderr�[2m | tests/unit/components/sidebar.test.tsx�[2m > �[22m�[2mSidebar�[2m > �[22m�[2muses the combined shell width for the classic sidebar layout
494:  �[22m�[39mWarning: An update to Sidebar inside a test was not wrapped in act(...).
495:  When testing, code that causes React state updates should be wrapped into act(...):
496:  act(() => {
497:  /* fire events that update state */
498:  });
499:  /* assert on the output */
500:  This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act
501:  at Sidebar (/home/runner/work/PromptHub/PromptHub/apps/desktop/src/renderer/components/layout/Sidebar.tsx:116:3)
502:  at I18nextProvider (file:///home/runner/work/PromptHub/PromptHub/node_modules/.pnpm/react-i18next@15.7.4_i18next@24.2.3_typescript@5.9.3__react-dom@18.3.1_react@18.3.1__react@18.3.1_typescript@5.9.3/node_modules/react-i18next/dist/es/I18nextProvider.js:4:3)
503:  at Wrapper (/home/runner/work/PromptHub/PromptHub/apps/desktop/tests/helpers/i18n.tsx:45:22)
504:  �[90mstderr�[2m | tests/unit/services/database-backup.test.ts�[2m > �[22m�[2mdatabase-backup restore�[2m > �[22m�[2mfails export when referenced media cannot be read completely
505:  �[22m�[39mFailed to read image broken-image.png: Error: missing image
506:  �[32m✓�[39m tests/unit/services/database-backup.test.ts �[2m(�[22m�[2m19 tests�[22m�[2m)�[22m�[90m 200�[2mms�[22m�[39m
507:  at �[90m/home/runner/work/PromptHub/PromptHub/apps/desktop/�[39mtests/unit/services/database-backup.test.ts:482:52
508:  at file:///home/runner/work/PromptHub/PromptHub/node_modules/�[4m.pnpm�[24m/@vitest+runner@2.1.9/node_modules/�[4m@vitest/runner�[24m/dist/index.js:146:14
509:  at file:///home/runner/work/PromptHub/PromptHub/node_modules/�[4m.pnpm�[24m/@vitest+runner@2.1.9/node_modules/�[4m@vitest/runner�[24m/dist/index.js:533:11
510:  at runWithTimeout (file:///home/runner/work/PromptHub/PromptHub/node_modules/�[4m.pnpm�[24m/@vitest+runner@2.1.9/node_modules/�[4m@vitest/runner�[24m/dist/index.js:39:7)
511:  at runTest (file:///home/runner/work/PromptHub/PromptHub/node_modules/�[4m.pnpm�[24m/@vitest+runner@2.1.9/node_modules/�[4m@vitest/runner�[24m/dist/index.js:1056:17)
512:  at runSuite (file:///home/runner/work/PromptHub/PromptHub/node_modules/�[4m.pnpm�[24m/@vitest+runner@2.1.9/node_modules/�[4m@vitest/runner�[24m/dist/index.js:1205:15)
513:  at runSuite (file:///home/runner/work/PromptHub/PromptHub/node_modules/�[4m.pnpm�[24m/@vitest+runner@2.1.9/node_modules/�[4m@vitest/runner�[24m/dist/index.js:1205:15)
514:  at runFiles (file:///home/runner/work/PromptHub/PromptHub/node_modules/�[4m.pnpm�[24m/@vitest+runner@2.1.9/node_modules/�[4m@vitest/runner�[24m/dist/index.js:1262:5)
515:  at startTests (file:///home/runner/work/PromptHub/PromptHub/node_modules/�[4m.pnpm�[24m/@vitest+runner@2.1.9/node_modules/�[4m@vitest/runner�[24m/dist/index.js:1271:3)
516:  at file:///home/runner/work/PromptHub/PromptHub/node_modules/�[4m.pnpm�[24m/vitest@2.1.9_@types+node@24.12.4_jsdom@27.3.0/node_modules/�[4mvitest�[24m/dist/chunks/runBaseTests.3qpJUEJM.js:126:11
517:  �[90mstderr�[2m | tests/unit/services/database-backup.test.ts�[2m > �[22m�[2mdatabase-backup restore�[2m > �[22m�[2mfails export when skill metadata cannot be collected completely
518:  �[22m�[39mFailed to get versions for skill writer: Error: db busy
519:  at �[90m/home/runner/work/PromptHub/PromptHub/apps/desktop/�[39mtests/unit/services/database-backup.test.ts:498:54
520:  at file:///home/runner/work/PromptHub/PromptHub/node_modules/�[4m.pnpm�[24m/@vitest+runner@2.1.9/node_modules/�[4m@vitest/runner�[24m/dist/index.js:146:14
521:  at file:///home/runner/work/PromptHub/PromptHub/node_modules/�[4m.pnpm�[24m/@vitest+runner@2.1.9/node_modules/�[4m@vitest/runner�[24m/dist/index.js:533:11
522:  at runWithTimeout (file:///home/runner/work/PromptHub/PromptHub/node_modules/�[4m.pnpm�[24m/@vitest+runner@2.1.9/node_modules/�[4m@vitest/runner�[24m/dist/index.js:39:7)
523:  at runTest (file:///home/runner/work/PromptHub/PromptHub/node_modules/�[4m.pnpm�[24m/@vitest+runner@2.1.9/node_modules/�[4m@vitest/runner�[24m/dist/index.js:1056:17)
524:  at runSuite (file:///home/runner/work/PromptHub/PromptHub/node_modules/�[4m.pnpm�[24m/@vitest+runner@2.1.9/node_modules/�[4m@vitest/runner�[24m/dist/index.js:1205:15)
525:  at runSuite (file:///home/runner/work/PromptHub/PromptHub/node_modules/�[4m.pnpm�[24m/@vitest+runner@2.1.9/node_modules/�[4m@vitest/runner�[24m/dist/index.js:1205:15)
526:  at runFiles (file:///home/runner/work/PromptHub/PromptHub/node_modules/�[4m.pnpm�[24m/@vitest+runner@2.1.9/node_modules/�[4m@vitest/runner�[24m/dist/index.js:1262:5)
527:  at startTests (file:///home/runner/work/PromptHub/PromptHub/node_modules/�[4m.pnpm�[24m/@vitest+runner@2.1.9/node_modules/�[4m@vitest/runner�[24m/dist/index.js:1271:3)
528:  at file:///home/runner/work/PromptHub/PromptHub/node_modules/�[4m.pnpm�[24m/vitest@2.1.9_@types+node@24.12.4_jsdom@27.3.0/node_modules/�[4mvitest�[24m/dist/chunks/runBaseTests.3qpJUEJM.js:126:11
529:  �[90mstderr�[2m | tests/unit/services/database-backup.test.ts�[2m > �[22m�[2mdatabase-backup restore�[2m > �[22m�[2mthrows when backup restore cannot fully write assets
530:  �[22m�[39mFailed to restore image image-1.png: Error: disk full
531:  at �[90m/home/runner/work/PromptHub/PromptHub/apps/desktop/�[39mtests/unit/services/database-backup.test.ts:734:52
...

540:  at file:///home/runner/work/PromptHub/PromptHub/node_modules/�[4m.pnpm�[24m/vitest@2.1.9_@types+node@24.12.4_jsdom@27.3.0/node_modules/�[4mvitest�[24m/dist/chunks/runBaseTests.3qpJUEJM.js:126:11
541:  �[32m✓�[39m tests/unit/main/prompt-db.test.ts �[2m(�[22m�[2m57 tests�[22m�[2m)�[22m�[33m 828�[2mms�[22m�[39m
542:  �[32m✓�[39m tests/unit/components/sidebar.test.tsx �[2m(�[22m�[2m27 tests�[22m�[2m)�[22m�[33m 9040�[2mms�[22m�[39m
543:  �[33m�[2m✓�[22m�[39m Sidebar�[2m > �[22mshows Project Skills as a first-level skill navigation entry on desktop �[33m418�[2mms�[22m�[39m
544:  �[33m�[2m✓�[22m�[39m Sidebar�[2m > �[22mshows skill library tags only in My Skills �[33m303�[2mms�[22m�[39m
545:  �[33m�[2m✓�[22m�[39m Sidebar�[2m > �[22mdoes not show status filters as first-level skill navigation �[33m359�[2mms�[22m�[39m
546:  �[33m�[2m✓�[22m�[39m Sidebar�[2m > �[22mshows preconfigured community store sources in the skill store group �[33m336�[2mms�[22m�[39m
547:  �[33m�[2m✓�[22m�[39m Sidebar�[2m > �[22mkeeps many skill store sources inside an internal scroll region �[33m932�[2mms�[22m�[39m
548:  �[33m�[2m✓�[22m�[39m Sidebar�[2m > �[22mcollapses the expanded skill store source list from the first-level store entry �[33m523�[2mms�[22m�[39m
549:  �[33m�[2m✓�[22m�[39m Sidebar�[2m > �[22mkeeps skill store sources expanded after switching to another skill section �[33m917�[2mms�[22m�[39m
550:  �[33m�[2m✓�[22m�[39m Sidebar�[2m > �[22mswitches to the store view when a nested store source is clicked �[33m772�[2mms�[22m�[39m
551:  �[33m�[2m✓�[22m�[39m Sidebar�[2m > �[22mswitches to the Rules module from the new left rail �[33m1054�[2mms�[22m�[39m
552:  �[33m�[2m✓�[22m�[39m Sidebar�[2m > �[22mupdates the selected rule when clicking a project rule item �[33m424�[2mms�[22m�[39m
553:  �[33m�[2m✓�[22m�[39m Sidebar�[2m > �[22mreplaces active tags when tag filter mode is single �[33m711�[2mms�[22m�[39m
554:  �[33m�[2m✓�[22m�[39m Sidebar�[2m > �[22mtoggles tags cumulatively when tag filter mode is multi �[33m649�[2mms�[22m�[39m
555:  �[90mstderr�[2m | tests/unit/main/skill-installer-utils.test.ts�[2m > �[22m�[2mskill-installer-utils�[2m > �[22m�[2mgetPlatformSkillsDir�[2m > �[22m�[2mhandles DB read failure gracefully (returns built-in path)
556:  �[22m�[39mFailed to read built-in agent overrides: Error: DB not available
557:  at Proxy.<anonymous> �[90m(/home/runner/work/PromptHub/PromptHub/apps/desktop/�[39mtests/unit/main/skill-installer-utils.test.ts:256:15�[90m)�[39m
558:  at Proxy.mockCall (file:///home/runner/work/PromptHub/PromptHub/node_modules/�[4m.pnpm�[24m/@vitest+spy@2.1.9/node_modules/�[4m@vitest/spy�[24m/dist/index.js:61:17)
559:  at Proxy.spy (file:///home/runner/work/PromptHub/PromptHub/node_modules/�[4m.pnpm�[24m/tinyspy@3.0.2/node_modules/�[4mtinyspy�[24m/dist/index.js:45:80)
560:  at readBuiltinAgentOverridesFromSettings �[90m(/home/runner/work/PromptHub/PromptHub/apps/desktop/�[39msrc/main/services/skill-installer-utils.ts:400:16�[90m)�[39m
561:  at getBuiltinAgentOverride �[90m(/home/runner/work/PromptHub/PromptHub/apps/desktop/�[39msrc/main/services/skill-installer-utils.ts:551:10�[90m)�[39m
562:  at getPlatformRootDir �[90m(/home/runner/work/PromptHub/PromptHub/apps/desktop/�[39msrc/main/services/skill-installer-utils.ts:558:27�[90m)�[39m
563:  at Module.getPlatformSkillsDir �[90m(/home/runner/work/PromptHub/PromptHub/apps/desktop/�[39msrc/main/services/skill-installer-utils.ts:577:19�[90m)�[39m
564:  at �[90m/home/runner/work/PromptHub/PromptHub/apps/desktop/�[39mtests/unit/main/skill-installer-utils.test.ts:263:28
565:  at file:///home/runner/work/PromptHub/PromptHub/node_modules/�[4m.pnpm�[24m/@vitest+runner@2.1.9/node_modules/�[4m@vitest/runner�[24m/dist/index.js:146:14
566:  at file:///home/runner/work/PromptHub/PromptHub/node_modules/�[4m.pnpm�[24m/@vitest+runner@2.1.9/node_modules/�[4m@vitest/runner�[24m/dist/index.js:533:11
567:  �[90mstderr�[2m | tests/unit/main/skill-installer-utils.test.ts�[2m > �[22m�[2mskill-installer-utils�[2m > �[22m�[2mgetPlatformSkillsDir�[2m > �[22m�[2mhandles malformed JSON in DB gracefully
568:  �[22m�[39mFailed to read built-in agent overrides: SyntaxError: Unexpected token 'o', "not valid json!" is not valid JSON
569:  at JSON.parse (<anonymous>)
570:  at parseJsonSetting �[90m(/home/runner/work/PromptHub/PromptHub/apps/desktop/�[39msrc/main/services/skill-installer-utils.ts:384:15�[90m)�[39m
571:  at readBuiltinAgentOverridesFromSettings �[90m(/home/runner/work/PromptHub/PromptHub/apps/desktop/�[39msrc/main/services/skill-installer-utils.ts:419:7�[90m)�[39m
572:  at getBuiltinAgentOverride �[90m(/home/runner/work/PromptHub/PromptHub/apps/desktop/�[39msrc/main/services/skill-installer-utils.ts:551:10�[90m)�[39m
573:  at getPlatformRootDir �[90m(/home/runner/work/PromptHub/PromptHub/apps/desktop/�[39msrc/main/services/skill-installer-utils.ts:558:27�[90m)�[39m
574:  at Module.getPlatformSkillsDir �[90m(/home/runner/work/PromptHub/PromptHub/apps/desktop/�[39msrc/main/services/skill-installer-utils.ts:577:19�[90m)�[39m
575:  at �[90m/home/runner/work/PromptHub/PromptHub/apps/desktop/�[39mtests/unit/main/skill-installer-utils.test.ts:279:28
576:  at file:///home/runner/work/PromptHub/PromptHub/node_modules/�[4m.pnpm�[24m/@vitest+runner@2.1.9/node_modules/�[4m@vitest/runner�[24m/dist/index.js:146:14
577:  at file:///home/runner/work/PromptHub/PromptHub/node_modules/�[4m.pnpm�[24m/@vitest+runner@2.1.9/node_modules/�[4m@vitest/runner�[24m/dist/index.js:533:11
578:  at runWithTimeout (file:///home/runner/work/PromptHub/PromptHub/node_modules/�[4m.pnpm�[24m/@vitest+runner@2.1.9/node_modules/�[4m@vitest/runner�[24m/dist/index.js:39:7)
579:  �[90mstderr�[2m | tests/unit/main/skill-installer-utils.test.ts�[2m > �[22m�[2mskill-installer-utils�[2m > �[22m�[2mgetPlatformGlobalRulePath�[2m > �[22m�[2mderives the Windsurf global rules file from the platform root
580:  �[22m�[39mFailed to read built-in agent overrides: SyntaxError: Unexpected token 'o', "not valid json!" is not valid JSON
581:  at JSON.parse (<anonymous>)
582:  at parseJsonSetting �[90m(/home/runner/work/PromptHub/PromptHub/apps/desktop/�[39msrc/main/services/skill-installer-utils.ts:384:15�[90m)�[39m
583:  at readBuiltinAgentOverridesFromSettings �[90m(/home/runner/work/PromptHub/PromptHub/apps/desktop/�[39msrc/main/services/skill-installer-utils.ts:419:7�[90m)�[39m
584:  at getBuiltinAgentOverride �[90m(/home/runner/work/PromptHub/PromptHub/apps/desktop/�[39msrc/main/services/skill-installer-utils.ts:551:10�[90m)�[39m
585:  at Module.getPlatformGlobalRulePath �[90m(/home/runner/work/PromptHub/PromptHub/apps/desktop/�[39msrc/main/services/skill-installer-utils.ts:590:5�[90m)�[39m
586:  at �[90m/home/runner/work/PromptHub/PromptHub/apps/desktop/�[39mtests/unit/main/skill-installer-utils.test.ts:289:28
587:  at file:///home/runner/work/PromptHub/PromptHub/node_modules/�[4m.pnpm�[24m/@vitest+runner@2.1.9/node_modules/�[4m@vitest/runner�[24m/dist/index.js:146:14
588:  at file:///home/runner/work/PromptHub/PromptHub/node_modules/�[4m.pnpm�[24m/@vitest+runner@2.1.9/node_modules/�[4m@vitest/runner�[24m/dist/index.js:533:11
589:  at runWithTimeout (file:///home/runner/work/PromptHub/PromptHub/node_modules/�[4m.pnpm�[24m/@vitest+runner@2.1.9/node_modules/�[4m@vitest/runner�[24m/dist/index.js:39:7)
590:  at runTest (file:///home/runner/work/PromptHub/PromptHub/node_modules/�[4m.pnpm�[24m/@vitest+runner@2.1.9/node_modules/�[4m@vitest/runner�[24m/dist/index.js:1056:17)
591:  �[90mstderr�[2m | tests/unit/main/skill-installer-utils.test.ts�[2m > �[22m�[2mskill-installer-utils�[2m > �[22m�[2mgetPlatformGlobalRulePath�[2m > �[22m�[2muses explicit root overrides for the Windsurf global rules file
592:  �[22m�[39mFailed to read built-in agent overrides: SyntaxError: Unexpected token 'o', "not valid json!" is not valid JSON
593:  at JSON.parse (<anonymous>)
...

779:  �[22m�[39m[Recovery] Backed up current DB to: /tmp/data-recovery-test-wxrh8Y/target/prompthub.db.pre-recovery-2026-06-14T09-49-09-438Z
780:  [Recovery] Copied database from /tmp/data-recovery-test-wxrh8Y/source/prompthub.db to /tmp/data-recovery-test-wxrh8Y/target/prompthub.db
781:  [Recovery] Copied config file: shortcuts.json
782:  �[90mstdout�[2m | tests/unit/main/data-recovery.test.ts�[2m > �[22m�[2mData Recovery�[2m > �[22m�[2mperformDatabaseRecovery�[2m > �[22m�[2mcopies renderer browser storage directories
783:  �[22m�[39m[Recovery] Merged asset directory: IndexedDB
784:  �[90mstdout�[2m | tests/unit/main/data-recovery.test.ts�[2m > �[22m�[2mData Recovery�[2m > �[22m�[2mperformDatabaseRecovery�[2m > �[22m�[2mcopies workspace prompt files during recovery
785:  �[22m�[39m[Recovery] Merged asset directory: workspace
786:  �[90mstdout�[2m | tests/unit/main/data-recovery.test.ts�[2m > �[22m�[2mData Recovery�[2m > �[22m�[2mperformDatabaseRecovery�[2m > �[22m�[2mdoes not overwrite existing config files in target
787:  �[22m�[39m[Recovery] Backed up current DB to: /tmp/data-recovery-test-p1qpxe/target/prompthub.db.pre-recovery-2026-06-14T09-49-09-641Z
788:  [Recovery] Copied database from /tmp/data-recovery-test-p1qpxe/source/prompthub.db to /tmp/data-recovery-test-p1qpxe/target/prompthub.db
789:  �[90mstdout�[2m | tests/unit/main/data-recovery.test.ts�[2m > �[22m�[2mData Recovery�[2m > �[22m�[2mperformDatabaseRecovery�[2m > �[22m�[2mrestores data directly from a standalone database backup file
790:  �[22m�[39m[Recovery] Backed up current DB to: /tmp/data-recovery-test-vxPmpd/target/prompthub.db.pre-recovery-2026-06-14T09-49-09-814Z
791:  [Recovery] Copied database from /tmp/data-recovery-test-vxPmpd/source/prompthub.db.backup-before-0.5.3.2026-04-18T10-00-00-000Z.db to /tmp/data-recovery-test-vxPmpd/target/prompthub.db
792:  �[90mstdout�[2m | tests/unit/main/data-recovery.test.ts�[2m > �[22m�[2mData Recovery�[2m > �[22m�[2mperformDatabaseRecovery�[2m > �[22m�[2mhandles recovery when target directory has no existing database
793:  �[22m�[39m[Recovery] Copied database from /tmp/data-recovery-test-CjDWQX/source/prompthub.db to /tmp/data-recovery-test-CjDWQX/target/data/prompthub.db
794:  �[22m�[39m[Recovery] Failed to inspect candidate database at /tmp/data-recovery-test-fEiXld/corrupt/prompthub.db: SQLite3Error: file is not a database
795:  at Database._handleError (/home/runner/work/PromptHub/PromptHub/node_modules/�[4m.pnpm�[24m/node-sqlite3-wasm@0.8.53/node_modules/�[4mnode-sqlite3-wasm�[24m/dist/node-sqlite3-wasm.js:1:5811)
796:  at new Statement (/home/runner/work/PromptHub/PromptHub/node_modules/�[4m.pnpm�[24m/node-sqlite3-wasm@0.8.53/node_modules/�[4mnode-sqlite3-wasm�[24m/dist/node-sqlite3-wasm.js:1:5931)
...

804:  at runTest (file:///home/runner/work/PromptHub/PromptHub/node_modules/�[4m.pnpm�[24m/@vitest+runner@2.1.9/node_modules/�[4m@vitest/runner�[24m/dist/index.js:1056:17)
805:  �[32m✓�[39m tests/unit/main/data-recovery.test.ts �[2m(�[22m�[2m30 tests�[22m�[2m)�[22m�[33m 2730�[2mms�[22m�[39m
806:  �[90mstdout�[2m | tests/unit/main/rules-workspace.test.ts�[2m > �[22m�[2mrules workspace storage�[2m > �[22m�[2mcreates a managed project rule and indexes it in SQLite
807:  �[22m�[39mMigrating: Aligning prompt current_version with latest stored version
808:  Database initialized at: /tmp/prompthub-rules-Aik5N1/data/prompthub.db
809:  �[90mstdout�[2m | tests/unit/main/rules-workspace.test.ts�[2m > �[22m�[2mrules workspace storage�[2m > �[22m�[2mcreates a managed project rule and indexes it in SQLite
810:  �[22m�[39m[startup] Pre-0.5.3 backup created at: /tmp/prompthub-rules-Aik5N1/data/prompthub.db.backup-before-0.5.3.2026-06-14T09-49-11-152Z.db
811:  �[90mstdout�[2m | tests/unit/main/rules-workspace.test.ts�[2m > �[22m�[2mrules workspace storage�[2m > �[22m�[2msaves managed content, writes versions, and updates rule index state
812:  �[22m�[39mMigrating: Aligning prompt current_version with latest stored version
813:  Database initialized at: /tmp/prompthub-rules-WuXnPx/data/prompthub.db
814:  �[90mstdout�[2m | tests/unit/main/rules-workspace.test.ts�[2m > �[22m�[2mrules workspace storage�[2m > �[22m�[2msaves managed content, writes versions, and updates rule index state
815:  �[22m�[39m[startup] Pre-0.5.3 backup created at: /tmp/prompthub-rules-WuXnPx/data/prompthub.db.backup-before-0.5.3.2026-06-14T09-49-11-327Z.db
816:  �[32m✓�[39m tests/unit/main/prompt-workspace.test.ts �[2m(�[22m�[2m14 tests�[22m�[2m)�[22m�[33m 432�[2mms�[22m�[39m
817:  �[90mstderr�[2m | tests/unit/main/prompt-workspace.test.ts�[2m > �[22m�[2mprompt workspace storage�[2m > �[22m�[2mimportPromptWorkspaceIntoDatabase resolves duplicate prompt.id by newer updatedAt, trashing losers
818:  �[22m�[39m[prompt-workspace] same-id conflict for prompt dup-id-abc: moving /tmp/prompthub-workspace-q4yErg/data/prompts/loser__dup-id-abc/prompt.md to .trash/conflicts
819:  [prompt-workspace] import completed with skips: 0 folders, 0 prompts, 1 conflict losers. See errors above. User data on disk is untouched; see .trash for orphans and .trash/conflicts for conflict losers.
820:  �[90mstderr�[2m | tests/unit/main/prompt-workspace.test.ts�[2m > �[22m�[2mprompt workspace storage�[2m > �[22m�[2mQ4 passes skippedPromptDirs to Phase 2 so insert-failing imports are not trashed as orphans
821:  �[22m�[39m[prompt-workspace] failed to import prompt fk-fail-xyz789 (FK Fail): SQLite3Error: FOREIGN KEY constraint failed
822:  at Database._handleError (/home/runner/work/PromptHub/PromptHub/node_modules/�[4m.pnpm�[24m/node-sqlite3-wasm@0.8.53/node_modules/�[4mnode-sqlite3-wasm�[24m/dist/node-sqlite3-wasm.js:1:5811)
823:  at Statement._step (/home/runner/work/PromptHub/PromptHub/node_modules/�[4m.pnpm�[24m/node-sqlite3-wasm@0.8.53/node_modules/�[4mnode-sqlite3-wasm�[24m/dist/node-sqlite3-wasm.js:1:7500)
824:  at Statement.run (/home/runner/work/PromptHub/PromptHub/node_modules/�[4m.pnpm�[24m/node-sqlite3-wasm@0.8.53/node_modules/�[4mnode-sqlite3-wasm�[24m/dist/node-sqlite3-wasm.js:1:6254)
825:  at Statement.run (/home/runner/work/PromptHub/PromptHub/packages/db/src/adapter.ts:58:19)
826:  at PromptDB.insertPromptDirect (/home/runner/work/PromptHub/PromptHub/packages/db/src/prompt.ts:531:8)
827:  at importPromptWorkspaceIntoDatabase �[90m(/home/runner/work/PromptHub/PromptHub/apps/desktop/�[39msrc/main/services/prompt-workspace.ts:1235:16�[90m)�[39m
828:  at Module.bootstrapPromptWorkspace �[90m(/home/runner/work/PromptHub/PromptHub/apps/desktop/�[39msrc/main/services/prompt-workspace.ts:1392:20�[90m)�[39m
829:  at �[90m/home/runner/work/PromptHub/PromptHub/apps/desktop/�[39mtests/unit/main/prompt-workspace.test.ts:624:20
830:  at file:///home/runner/work/PromptHub/PromptHub/node_modules/�[4m.pnpm�[24m/@vitest+runner@2.1.9/node_modules/�[4m@vitest/runner�[24m/dist/index.js:146:14
831:  at file:///home/runner/work/PromptHub/PromptHub/node_modules/�[4m.pnpm�[24m/@vitest+runner@2.1.9/node_modules/�[4m@vitest/runner�[24m/dist/index.js:533:11
832:  [prompt-workspace] import completed with skips: 0 folders, 1 prompts, 0 conflict losers. See errors above. User data on disk is untouched; see .trash for orphans and .trash/conflicts for conflict losers.
833:  �[90mstdout�[2m | tests/unit/main/rules-workspace.test.ts�[2m > �[22m�[2mrules workspace storage�[2m > �[22m�[2mreports external target edits as out-of-sync with both file versions
...

892:  �[90mstdout�[2m | tests/unit/main/rules-workspace.test.ts�[2m > �[22m�[2mrules workspace storage�[2m > �[22m�[2mdrops cached custom rule descriptors when the custom agent is no longer configured
893:  �[22m�[39mMigrating: Aligning prompt current_version with latest stored version
894:  Database initialized at: /tmp/prompthub-rules-sDdZtV/data/prompthub.db
895:  �[90mstdout�[2m | tests/unit/main/rules-workspace.test.ts�[2m > �[22m�[2mrules workspace storage�[2m > �[22m�[2mdrops cached custom rule descriptors when the custom agent is no longer configured
896:  �[22m�[39m[startup] Pre-0.5.3 backup created at: /tmp/prompthub-rules-sDdZtV/data/prompthub.db.backup-before-0.5.3.2026-06-14T09-49-13-224Z.db
897:  �[90mstdout�[2m | tests/unit/main/rules-workspace.test.ts�[2m > �[22m�[2mrules workspace storage�[2m > �[22m�[2mskips missing version files and repairs the index instead of crashing
898:  �[22m�[39mMigrating: Aligning prompt current_version with latest stored version
899:  Database initialized at: /tmp/prompthub-rules-Miivsa/data/prompthub.db
900:  �[90mstdout�[2m | tests/unit/main/rules-workspace.test.ts�[2m > �[22m�[2mrules workspace storage�[2m > �[22m�[2mskips missing version files and repairs the index instead of crashing
901:  �[22m�[39m[startup] Pre-0.5.3 backup created at: /tmp/prompthub-rules-Miivsa/data/prompthub.db.backup-before-0.5.3.2026-06-14T09-49-13-350Z.db
902:  �[90mstdout�[2m | tests/unit/main/rules-workspace.test.ts�[2m > �[22m�[2mrules workspace storage�[2m > �[22m�[2mdoes not create duplicate initial versions when re-materializing a global rule
903:  �[22m�[39mMigrating: Aligning prompt current_version with latest stored version
904:  Database initialized at: /tmp/prompthub-rules-COnRrg/data/prompthub.db
905:  �[90mstdout�[2m | tests/unit/main/rules-workspace.test.ts�[2m > �[22m�[2mrules workspace storage�[2m > �[22m�[2mdoes not create duplicate initial versions when re-materializing a global rule
906:  �[22m�[39m[startup] Pre-0.5.3 backup created at: /tmp/prompthub-rules-COnRrg/data/prompthub.db.backup-before-0.5.3.2026-06-14T09-49-13-471Z.db
907:  �[31m❯�[39m tests/unit/main/rules-workspace.test.ts �[2m(�[22m�[2m17 tests�[22m�[2m | �[22m�[31m1 failed�[39m�[2m)�[22m�[33m 2661�[2mms�[22m�[39m
908:  �[33m�[2m✓�[22m�[39m rules workspace storage�[2m > �[22mkeeps unique history after the version retention limit �[33m344�[2mms�[22m�[39m
...

912:  �[32m✓�[39m tests/unit/components/top-bar.test.tsx �[2m(�[22m�[2m12 tests�[22m�[2m)�[22m�[33m 895�[2mms�[22m�[39m
913:  �[33m�[2m✓�[22m�[39m TopBar�[2m > �[22mrenders the create mode dropdown in a portal when the split button is opened �[33m340�[2mms�[22m�[39m
914:  �[32m✓�[39m tests/unit/components/skill-detail-project-distribution.test.tsx �[2m(�[22m�[2m17 tests�[22m�[2m)�[22m�[33m 11527�[2mms�[22m�[39m
915:  �[33m�[2m✓�[22m�[39m Skill detail project distribution�[2m > �[22mdefaults to global distribution when opening a skill detail �[33m870�[2mms�[22m�[39m
916:  �[33m�[2m✓�[22m�[39m Skill detail project distribution�[2m > �[22mrequires confirmation before uninstalling a global platform skill �[33m885�[2mms�[22m�[39m
917:  �[33m�[2m✓�[22m�[39m Skill detail project distribution�[2m > �[22mshows the skill and platform name after a global platform install succeeds �[33m532�[2mms�[22m�[39m
918:  �[33m�[2m✓�[22m�[39m Skill detail project distribution�[2m > �[22mlets users keep copied distributions when deleting from PromptHub �[33m435�[2mms�[22m�[39m
919:  �[33m�[2m✓�[22m�[39m Skill detail project distribution�[2m > �[22mdeletes project symlink distributions when deleting the PromptHub skill �[33m408�[2mms�[22m�[39m
920:  �[33m�[2m✓�[22m�[39m Skill detail project distribution�[2m > �[22muses repo path and skip semantics when distributing from detail page �[33m564�[2mms�[22m�[39m
921:  �[33m�[2m✓�[22m�[39m Skill detail project distribution�[2m > �[22msupports advanced project target folders from the detail page �[33m1540�[2mms�[22m�[39m
922:  �[33m�[2m✓�[22m�[39m Skill detail project distribution�[2m > �[22mreuses saved project import target preferences from the detail page �[33m901�[2mms�[22m�[39m
923:  �[33m�[2m✓�[22m�[39m Skill detail project distribution�[2m > �[22mskips already imported project targets �[33m643�[2mms�[22m�[39m
924:  �[33m�[2m✓�[22m�[39m Skill detail project distribution�[2m > �[22mremoves an already distributed project target from the detail page �[33m979�[2mms�[22m�[39m
925:  �[33m�[2m✓�[22m�[39m Skill detail project distribution�[2m > �[22mwarns instead of copying when the selected target is the source location �[33m661�[2mms�[22m�[39m
926:  �[33m�[2m✓�[22m�[39m Skill detail project distribution�[2m > �[22msupports symlink mode with the same skip behavior �[33m1542�[2mms�[22m�[39m
927:  �[33m�[2m✓�[22m�[39m Skill detail project distribution�[2m > �[22mshows an error when selected projects have no deploy targets �[33m817�[2mms�[22m�[39m
928:  �[33m�[2m✓�[22m�[39m Skill detail project distribution�[2m > �[22mwarns when background rescan fails after a successful distribution �[33m599�[2mms�[22m�[39m
929:  �[32m✓�[39m tests/unit/components/rules-manager.test.tsx �[2m(�[22m�[2m7 tests�[22m�[2m)�[22m�[33m 2097�[2mms�[22m�[39m
...

994:  �[32m✓�[39m tests/unit/services/skills-sh-store.test.ts �[2m(�[22m�[2m11 tests�[22m�[2m)�[22m�[90m 17�[2mms�[22m�[39m
995:  �[32m✓�[39m tests/unit/components/update-dialog.test.tsx �[2m(�[22m�[2m5 tests�[22m�[2m)�[22m�[33m 899�[2mms�[22m�[39m
996:  �[33m�[2m✓�[22m�[39m UpdateDialog�[2m > �[22mkeeps download enabled because install creates an automatic data snapshot �[33m524�[2mms�[22m�[39m
997:  �[32m✓�[39m tests/unit/components/skill-detail-utils.test.ts �[2m(�[22m�[2m11 tests�[22m�[2m)�[22m�[90m 23�[2mms�[22m�[39m
998:  �[90mstdout�[2m | tests/unit/components/prompt-modal-structure.test.tsx�[2m > �[22m�[2mPrompt modal structure�[2m > �[22m�[2mgenerates an AI rewrite draft and allows undoing it
999:  �[22m�[39m[AI Service] Stream mode: �[33mfalse�[39m Callbacks provided: �[33mfalse�[39m
1000:  �[32m✓�[39m tests/unit/components/skill-settings.test.tsx �[2m(�[22m�[2m10 tests�[22m�[2m)�[22m�[33m 9133�[2mms�[22m�[39m
1001:  �[33m�[2m✓�[22m�[39m SkillSettings�[2m > �[22mshows the preferred default platform order �[33m466�[2mms�[22m�[39m
1002:  �[33m�[2m✓�[22m�[39m SkillSettings�[2m > �[22mreorders platforms through drag and drop �[33m427�[2mms�[22m�[39m
1003:  �[33m�[2m✓�[22m�[39m SkillSettings�[2m > �[22madds a custom agent root and shows derived asset previews �[33m1471�[2mms�[22m�[39m
1004:  �[33m�[2m✓�[22m�[39m SkillSettings�[2m > �[22mfills the custom agent root path from folder picker �[33m1171�[2mms�[22m�[39m
1005:  �[33m�[2m✓�[22m�[39m SkillSettings�[2m > �[22mrequires confirmation before deleting a custom agent �[33m2321�[2mms�[22m�[39m
1006:  �[33m�[2m✓�[22m�[39m SkillSettings�[2m > �[22mupdates built-in agent override fields from the unified config section �[33m1126�[2mms�[22m�[39m
1007:  �[33m�[2m✓�[22m�[39m SkillSettings�[2m > �[22mresets built-in edit form without persisting until save �[33m343�[2mms�[22m�[39m
1008:  �[33m�[2m✓�[22m�[39m SkillSettings�[2m > �[22msaves cleared built-in override fields as defaults instead of keeping stale values �[33m1304�[2mms�[22m�[39m
1009:  �[90mstdout�[2m | tests/unit/components/prompt-modal-structure.test.tsx�[2m > �[22m�[2mPrompt modal structure�[2m > �[22m�[2msurfaces rewrite failures from the AI service
1010:  �[22m�[39m[AI Service] Stream mode: �[33mfalse�[39m Callbacks provided: �[33mfalse�[39m
...

1021:  �[32m✓�[39m tests/unit/main/skill-crud-ipc.test.ts �[2m(�[22m�[2m5 tests�[22m�[2m)�[22m�[90m 79�[2mms�[22m�[39m
1022:  �[32m✓�[39m tests/unit/components/security-settings.test.tsx �[2m(�[22m�[2m7 tests�[22m�[2m)�[22m�[33m 1382�[2mms�[22m�[39m
1023:  �[33m�[2m✓�[22m�[39m SecuritySettings�[2m > �[22mdoes not submit when setting a master password with mismatched confirmation �[33m398�[2mms�[22m�[39m
1024:  �[32m✓�[39m tests/unit/services/skill-identity.test.ts �[2m(�[22m�[2m8 tests�[22m�[2m)�[22m�[90m 29�[2mms�[22m�[39m
1025:  �[32m✓�[39m tests/unit/services/database.test.ts �[2m(�[22m�[2m10 tests�[22m�[2m)�[22m�[90m 17�[2mms�[22m�[39m
1026:  �[32m✓�[39m tests/unit/components/skill-store-installed-state.test.tsx �[2m(�[22m�[2m2 tests�[22m�[2m)�[22m�[33m 679�[2mms�[22m�[39m
1027:  �[33m�[2m✓�[22m�[39m SkillStore installed state�[2m > �[22mshows a Claude Code store skill as imported when a legacy install only matches by content URL �[33m407�[2mms�[22m�[39m
1028:  �[32m✓�[39m tests/unit/services/ai-defaults.test.ts �[2m(�[22m�[2m9 tests�[22m�[2m)�[22m�[90m 9�[2mms�[22m�[39m
1029:  �[32m✓�[39m tests/unit/components/settings-page.test.tsx �[2m(�[22m�[2m7 tests�[22m�[2m)�[22m�[33m 2385�[2mms�[22m�[39m
1030:  �[33m�[2m✓�[22m�[39m SettingsPage�[2m > �[22mshows enabled badge on active cloud backup targets in the data submenu �[33m1139�[2mms�[22m�[39m
1031:  �[33m�[2m✓�[22m�[39m SettingsPage�[2m > �[22mlets the model service page own its provider middle column �[33m449�[2mms�[22m�[39m
1032:  �[90mstdout�[2m | tests/unit/components/prompt-quick-rewrite-dialog.test.tsx�[2m > �[22m�[2mPromptQuickRewriteDialog�[2m > �[22m�[2mrenders quick rewrite entry and previews generated draft
1033:  �[22m�[39m[AI Service] Stream mode: �[33mfalse�[39m Callbacks provided: �[33mfalse�[39m
1034:  �[90mstdout�[2m | tests/unit/components/prompt-quick-rewrite-dialog.test.tsx�[2m > �[22m�[2mPromptQuickRewriteDialog�[2m > �[22m�[2mapplies the draft and opens the editor when continue editing is selected
1035:  �[22m�[39m[AI Service] Stream mode: �[33mfalse�[39m Callbacks provided: �[33mfalse�[39m
1036:  �[90mstdout�[2m | tests/unit/components/prompt-quick-rewrite-dialog.test.tsx�[2m > �[22m�[2mPromptQuickRewriteDialog�[2m > �[22m�[2mshows errors when AI rewrite response is invalid
1037:  �[22m�[39m[AI Service] Stream mode: �[33mfalse�[39m Callbacks provided: �[33mfalse�[39m
...

1121:  �[33m�[2m✓�[22m�[39m SkillVersionHistoryModal�[2m > �[22mdeletes one skill snapshot from version history �[33m682�[2mms�[22m�[39m
1122:  �[32m✓�[39m tests/unit/stores/settings-startup.test.ts �[2m(�[22m�[2m4 tests�[22m�[2m)�[22m�[90m 42�[2mms�[22m�[39m
1123:  �[32m✓�[39m tests/unit/components/use-skill-platform.test.ts �[2m(�[22m�[2m3 tests�[22m�[2m)�[22m�[90m 4�[2mms�[22m�[39m
1124:  �[32m✓�[39m tests/unit/components/ai-settings-legacy.test.tsx �[2m(�[22m�[2m1 test�[22m�[2m)�[22m�[33m 730�[2mms�[22m�[39m
1125:  �[33m�[2m✓�[22m�[39m AISettings legacy�[2m > �[22mpreserves apiProtocol when adding a Google chat model �[33m728�[2mms�[22m�[39m
1126:  �[32m✓�[39m tests/unit/services/prompt-filter.test.ts �[2m(�[22m�[2m3 tests�[22m�[2m)�[22m�[90m 15�[2mms�[22m�[39m
1127:  �[32m✓�[39m tests/unit/main/shortcuts.test.ts �[2m(�[22m�[2m3 tests�[22m�[2m)�[22m�[90m 49�[2mms�[22m�[39m
1128:  �[32m✓�[39m tests/unit/components/project-skill-preview-sidebar-i18n.test.tsx �[2m(�[22m�[2m1 test�[22m�[2m)�[22m�[33m 607�[2mms�[22m�[39m
1129:  �[33m�[2m✓�[22m�[39m ProjectSkillPreviewSidebar i18n�[2m > �[22mrenders the project deployment panel in Simplified Chinese without English fallback text �[33m606�[2mms�[22m�[39m
1130:  �[32m✓�[39m tests/unit/components/close-dialog.test.tsx �[2m(�[22m�[2m3 tests�[22m�[2m)�[22m�[33m 310�[2mms�[22m�[39m
1131:  �[32m✓�[39m tests/unit/main/skill-version-ipc.test.ts �[2m(�[22m�[2m2 tests�[22m�[2m)�[22m�[90m 65�[2mms�[22m�[39m
1132:  �[32m✓�[39m tests/unit/components/toast.test.tsx �[2m(�[22m�[2m4 tests�[22m�[2m)�[22m�[90m 298�[2mms�[22m�[39m
1133:  �[32m✓�[39m tests/unit/services/skill-filter.test.ts �[2m(�[22m�[2m4 tests�[22m�[2m)�[22m�[90m 6�[2mms�[22m�[39m
1134:  �[90mstderr�[2m | tests/unit/main/settings-startup.test.ts�[2m > �[22m�[2mgetMinimizeOnLaunchSetting (issue #115)�[2m > �[22m�[2msurvives a malformed JSON value without throwing
1135:  �[32m✓�[39m tests/unit/main/settings-startup.test.ts �[2m(�[22m�[2m6 tests�[22m�[2m)�[22m�[90m 126�[2mms�[22m�[39m
1136:  �[22m�[39mFailed to read minimizeOnLaunch setting: SyntaxError: Expected property name or '}' in JSON at position 1 (line 1 column 2)
1137:  at JSON.parse (<anonymous>)
...

1177:  �[32m✓�[39m tests/unit/services/ai-url-preview.test.ts �[2m(�[22m�[2m6 tests�[22m�[2m)�[22m�[90m 4�[2mms�[22m�[39m
1178:  �[32m✓�[39m tests/unit/main/skill-db-versioning.test.ts �[2m(�[22m�[2m1 test�[22m�[2m)�[22m�[90m 246�[2mms�[22m�[39m
1179:  �[32m✓�[39m tests/unit/services/issue-version-label.test.ts �[2m(�[22m�[2m5 tests�[22m�[2m)�[22m�[90m 6�[2mms�[22m�[39m
1180:  �[32m✓�[39m tests/unit/components/skill-file-icons.test.ts �[2m(�[22m�[2m2 tests�[22m�[2m)�[22m�[90m 4�[2mms�[22m�[39m
1181:  �[32m✓�[39m tests/unit/components/Button.test.tsx �[2m(�[22m�[2m5 tests�[22m�[2m)�[22m�[90m 58�[2mms�[22m�[39m
1182:  �[32m✓�[39m tests/unit/services/skill-stats.test.ts �[2m(�[22m�[2m1 test�[22m�[2m)�[22m�[90m 12�[2mms�[22m�[39m
1183:  �[32m✓�[39m tests/unit/components/prompt-detail-modal.test.tsx �[2m(�[22m�[2m1 test�[22m�[2m)�[22m�[33m 474�[2mms�[22m�[39m
1184:  �[33m�[2m✓�[22m�[39m PromptDetailModal�[2m > �[22mshows AI quick edit action in the header �[33m473�[2mms�[22m�[39m
1185:  �[32m✓�[39m tests/unit/stores/settings-language.test.ts �[2m(�[22m�[2m2 tests�[22m�[2m)�[22m�[90m 35�[2mms�[22m�[39m
1186:  �[32m✓�[39m tests/unit/services/skill-registry-builtins.test.ts �[2m(�[22m�[2m2 tests�[22m�[2m)�[22m�[90m 4�[2mms�[22m�[39m
1187:  �[32m✓�[39m tests/unit/components/select.test.tsx �[2m(�[22m�[2m1 test�[22m�[2m)�[22m�[33m 306�[2mms�[22m�[39m
1188:  �[33m�[2m✓�[22m�[39m Select�[2m > �[22mrenders dropdown in a portal attached to document.body �[33m305�[2mms�[22m�[39m
1189:  �[32m✓�[39m tests/unit/components/web-workspace-settings.test.tsx �[2m(�[22m�[2m1 test�[22m�[2m)�[22m�[90m 84�[2mms�[22m�[39m
1190:  �[90mstderr�[2m | tests/unit/hooks/use-prompt-media-manager.test.ts�[2m > �[22m�[2musePromptMediaManager�[2m > �[22m�[2mshows a dedicated message when self-hosted web blocks internal image URLs
1191:  �[32m✓�[39m tests/unit/hooks/use-prompt-media-manager.test.ts �[2m(�[22m�[2m1 test�[22m�[2m)�[22m�[90m 21�[2mms�[22m�[39m
1192:  �[22m�[39mFailed to upload image from URL: Error: Access to internal network addresses is not allowed
1193:  at �[90m/home/runner/work/PromptHub/PromptHub/apps/desktop/�[39mtests/unit/hooks/use-prompt-media-manager.test.ts:11:28
...

1210:  �[32m✓�[39m tests/unit/components/local-image.test.tsx �[2m(�[22m�[2m1 test�[22m�[2m)�[22m�[90m 46�[2mms�[22m�[39m
1211:  �[32m✓�[39m tests/unit/services/platform-visibility-integration.test.ts �[2m(�[22m�[2m1 test�[22m�[2m)�[22m�[90m 5�[2mms�[22m�[39m
1212:  �[32m✓�[39m tests/unit/components/skill-icon.test.tsx �[2m(�[22m�[2m1 test�[22m�[2m)�[22m�[90m 163�[2mms�[22m�[39m
1213:  �[90mstdout�[2m | tests/unit/main/skill-installer-platform.test.ts�[2m > �[22m�[2mskill-installer-platform symlink install�[2m > �[22m�[2mcopies the managed skill directory into the platform directory
1214:  �[22m�[39mSuccessfully installed skill directory for "demo-skill" to Claude Code at /platform/skills/demo-skill
1215:  �[90mstdout�[2m | tests/unit/main/skill-installer-platform.test.ts�[2m > �[22m�[2mskill-installer-platform symlink install�[2m > �[22m�[2mdereferences a root symlink source when copy-installing to a platform
1216:  �[22m�[39mSuccessfully installed skill directory for "linked-demo" to Claude Code at /platform/skills/linked-demo
1217:  �[90mstdout�[2m | tests/unit/main/skill-installer-platform.test.ts�[2m > �[22m�[2mskill-installer-platform symlink install�[2m > �[22m�[2mregisters Cherry Studio installs through its database-backed adapter
1218:  �[22m�[39mSuccessfully registered skill directory for "demo-skill" in Cherry Studio
1219:  �[90mstdout�[2m | tests/unit/main/skill-installer-platform.test.ts�[2m > �[22m�[2mskill-installer-platform symlink install�[2m > �[22m�[2mallows symlink installs for custom agents
1220:  �[22m�[39mSymlinked "demo-skill" repo directory → Team Agents: /prompthub/skills/demo-skill → /platform/skills/demo-skill
1221:  �[90mstderr�[2m | tests/unit/main/skill-installer-platform.test.ts�[2m > �[22m�[2mskill-installer-platform symlink install�[2m > �[22m�[2mfalls back to Cherry Studio copy registration when DB-backed symlink creation is not permitted
1222:  �[22m�[39mSymlink install unsupported for "demo-skill" on Cherry Studio; falling back to copy install. Reason: EPERM: operation not permitted
1223:  �[90mstderr�[2m | tests/unit/main/skill-installer-platform.test.ts�[2m > �[22m�[2mskill-installer-platform symlink install�[2m > �[22m�[2mfalls back to copy install when symlink creation returns EPERM
1224:  �[22m�[39mSymlink install unsupported for "demo-skill" on Claude Code; falling back to copy install. Reason: EPERM: operation not permitted
1225:  �[90mstderr�[2m | tests/unit/main/skill-installer-platform.test.ts�[2m > �[22m�[2mskill-installer-platform symlink install�[2m > �[22m�[2mfalls back to copy install for UNKNOWN errors (Windows without Developer Mode)
1226:  �[22m�[39mSymlink install unsupported for "demo-skill" on Claude Code; falling back to copy install. Reason: UNKNOWN: unknown symlink failure
1227:  �[90mstderr�[2m | tests/unit/main/skill-installer-platform.test.ts�[2m > �[22m�[2mskill-installer-platform symlink install�[2m > �[22m�[2mrethrows with an actionable error message when no fallback applies
1228:  �[22m�[39mFailed to create symlink for "demo-skill" to Claude Code: Error: disk is full
1229:  at �[90m/home/runner/work/PromptHub/PromptHub/apps/desktop/�[39mtests/unit/main/skill-installer-platform.test.ts:356:37
1230:  at file:///home/runner/work/PromptHub/PromptHub/node_modules/�[4m.pnpm�[24m/@vitest+runner@2.1.9/node_modules/�[4m@vitest/runner�[24m/dist/index.js:146:14
1231:  at file:///home/runner/work/PromptHub/PromptHub/node_modules/�[4m.pnpm�[24m/@vitest+runner@2.1.9/node_modules/�[4m@vitest/runner�[24m/dist/index.js:533:11
1232:  at runWithTimeout (file:///home/runner/work/PromptHub/PromptHub/node_modules/�[4m.pnpm�[24m/@vitest+runner@2.1.9/node_modules/�[4m@vitest/runner�[24m/dist/index.js:39:7)
1233:  at runTest (file:///home/runner/work/PromptHub/PromptHub/node_modules/�[4m.pnpm�[24m/@vitest+runner@2.1.9/node_modules/�[4m@vitest/runner�[24m/dist/index.js:1056:17)
1234:  �[90m    at processTicksAndRejections (node:internal/process/task_queues:104:5)�[39m
1235:  at runSuite (file:///home/runner/work/PromptHub/PromptHub/node_modules/�[4m.pnpm�[24m/@vitest+runner@2.1.9/node_modules/�[4m@vitest/runner�[24m/dist/index.js:1205:15)
1236:  at runSuite (file:///home/runner/work/PromptHub/PromptHub/node_modules/�[4m.pnpm�[24m/@vitest+runner@2.1.9/node_modules/�[4m@vitest/runner�[24m/dist/index.js:1205:15)
1237:  at runFiles (file:///home/runner/work/PromptHub/PromptHub/node_modules/�[4m.pnpm�[24m/@vitest+runner@2.1.9/node_modules/�[4m@vitest/runner�[24m/dist/index.js:1262:5)
1238:  at startTests (file:///home/runner/work/PromptHub/PromptHub/node_modules/�[4m.pnpm�[24m/@vitest+runner@2.1.9/node_modules/�[4m@vitest/runner�[24m/dist/index.js:1271:3) {
1239:  code: �[32m'ENOSPC'�[39m
1240:  }
1241:  �[90mstderr�[2m | tests/unit/main/skill-installer-platform.test.ts�[2m > �[22m�[2mskill-installer-platform symlink install�[2m > �[22m�[2mpropagates Cherry Studio built-in uninstall rejection without clearing activation or deleting files
1242:  �[22m�[39mFailed to uninstall SKILL.md from Cherry Studio: Error: Cannot uninstall Cherry Studio built-in skill
1243:  at �[90m/home/runner/work/PromptHub/PromptHub/apps/desktop/�[39mtests/unit/main/skill-installer-platform.test.ts:589:26
1244:  at file:///home/runner/work/PromptHub/PromptHub/node_modules/�[4m.pnpm�[24m/@vitest+runner@2.1.9/node_modules/�[4m@vitest/runner�[24m/dist/index.js:146:14
1245:  at file:///home/runner/work/PromptHub/PromptHub/node_modules/�[4m.pnpm�[24m/@vitest+runner@2.1.9/node_modules/�[4m@vitest/runner�[24m/dist/index.js:533:11
1246:  at runWithTimeout (file:///home/runner/work/PromptHub/PromptHub/node_modules/�[4m.pnpm�[24m/@vitest+runner@2.1.9/node_modules/�[4m@vitest/runner�[24m/dist/index.js:39:7)
1247:  at runTest (file:///home/runner/work/PromptHub/PromptHub/node_modules/�[4m.pnpm�[24m/@vitest+runner@2.1.9/node_modules/�[4m@vitest/runner�[24m/dist/index.js:1056:17)
1248:  �[90m    at processTicksAndRejections (node:internal/process/task_queues:104:5)�[39m
1249:  at runSuite (file:///home/runner/work/PromptHub/PromptHub/node_modules/�[4m.pnpm�[24m/@vitest+runner@2.1.9/node_modules/�[4m@vitest/runner�[24m/dist/index.js:1205:15)
1250:  at runSuite (file:///home/runner/work/PromptHub/PromptHub/node_modules/�[4m.pnpm�[24m/@vitest+runner@2.1.9/node_modules/�[4m@vitest/runner�[24m/dist/index.js:1205:15)
1251:  at runFiles (file:///home/runner/work/PromptHub/PromptHub/node_modules/�[4m.pnpm�[24m/@vitest+runner@2.1.9/node_modules/�[4m@vitest/runner�[24m/dist/index.js:1262:5)
1252:  at startTests (file:///home/runner/work/PromptHub/PromptHub/node_modules/�[4m.pnpm�[24m/@vitest+runner@2.1.9/node_modules/�[4m@vitest/runner�[24m/dist/index.js:1271:3)
1253:  �[90mstdout�[2m | tests/unit/main/skill-installer-platform.test.ts�[2m > �[22m�[2mskill-installer-platform symlink install�[2m > �[22m�[2mfalls back to Cherry Studio copy registration when DB-backed symlink creation is not permitted
1254:  �[22m�[39mSuccessfully registered skill directory for "demo-skill" in Cherry Studio
1255:  �[90mstdout�[2m | tests/unit/main/skill-installer-platform.test.ts�[2m > �[22m�[2mskill-installer-platform symlink install�[2m > �[22m�[2mfalls back to copy install when symlink creation returns EPERM
1256:  �[22m�[39mSuccessfully installed skill directory for "demo-skill" to Claude Code at /platform/skills/demo-skill
1257:  �[90mstdout�[2m | tests/unit/main/skill-installer-platform.test.ts�[2m > �[22m�[2mskill-installer-platform symlink install�[2m > �[22m�[2msymlinks the whole skill directory into the platform directory
1258:  �[22m�[39mSymlinked "demo-skill" repo directory → Claude Code: /prompthub/skills/demo-skill → /platform/skills/demo-skill
1259:  �[90mstdout�[2m | tests/unit/main/skill-installer-platform.test.ts�[2m > �[22m�[2mskill-installer-platform symlink install�[2m > �[22m�[2mfalls back to copy install for UNKNOWN errors (Windows without Developer Mode)
1260:  �[22m�[39mSuccessfully installed skill directory for "demo-skill" to Claude Code at /platform/skills/demo-skill
...

1268:  �[22m�[39mSuccessfully uninstalled SKILL.md for "writer" from Claude Code
1269:  �[32m✓�[39m tests/unit/main/skill-installer-platform.test.ts �[2m(�[22m�[2m20 tests�[22m�[2m)�[22m�[90m 36�[2mms�[22m�[39m
1270:  �[32m✓�[39m tests/unit/components/platform-icon.test.tsx �[2m(�[22m�[2m1 test�[22m�[2m)�[22m�[90m 220�[2mms�[22m�[39m
1271:  �[90mstdout�[2m | tests/unit/main/data-layout-migration.test.ts�[2m > �[22m�[2mdata-layout-migration�[2m > �[22m�[2mmigrates legacy root entries into data/config and writes a marker after snapshotting
1272:  �[22m�[39m[data-layout-migration] Moved "workspace" → "data"
1273:  [data-layout-migration] Moved "skills" → "data/skills"
1274:  [data-layout-migration] Moved "images" → "data/assets/images"
1275:  [data-layout-migration] Moved "prompthub.db" → "data/prompthub.db"
1276:  [data-layout-migration] Moved "shortcuts.json" → "config/shortcuts.json"
1277:  �[32m✓�[39m tests/unit/main/cherry-studio-skill-platform.test.ts �[2m(�[22m�[2m16 tests�[22m�[2m)�[22m�[33m 690�[2mms�[22m�[39m
1278:  �[90mstdout�[2m | tests/unit/ma...

Comment on lines +284 to +308
ipcMain.handle(IPC_CHANNELS.PROMPT_RELATION_CREATE, async (_, data: CreatePromptRelationDTO) => {
const relation = relationDb.create(data);
syncWorkspace();
return relation;
});

ipcMain.handle(IPC_CHANNELS.PROMPT_RELATION_LIST, async (_, query?: PromptRelationQuery) => {
return relationDb.list(query);
});

ipcMain.handle(IPC_CHANNELS.PROMPT_RELATION_UPDATE, async (_, id: string, data: UpdatePromptRelationDTO) => {
const relation = relationDb.update(id, data);
if (relation) {
syncWorkspace();
}
return relation;
});

ipcMain.handle(IPC_CHANNELS.PROMPT_RELATION_DELETE, async (_, id: string) => {
const deleted = relationDb.delete(id);
if (deleted) {
syncWorkspace();
}
return deleted;
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Action required

1. Relation ipc lacks validation 📘 Rule violation ⛨ Security

8.2

The new prompt relation IPC handlers accept unvalidated inputs and do not translate failures into
structured error responses. Malformed payloads or thrown DB errors can propagate unpredictably
across the main-process IPC boundary.
Agent Prompt
## Issue description
IPC handlers for prompt relations do not validate payloads/ids and do not return structured error objects on failure.

## Issue Context
Per IPC boundary requirements, handlers must reject malformed inputs and propagate structured errors without risking main-process instability.

## Fix Focus Areas
- apps/desktop/src/main/ipc/prompt.ipc.ts[284-308]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +744 to +749
<div
className="fixed z-50 w-44 rounded-md border border-border bg-popover p-1 shadow-lg"
style={{
left: pendingRelationDrop.x,
top: pendingRelationDrop.y,
}}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Action required

2. Inline style added to menu 📘 Rule violation ⚙ Maintainability

8.6

A new JSX style={{ ... }} prop is introduced for the relation chooser menu positioning. This
violates the Tailwind-only styling requirement and adds hard-to-maintain inline styling.
Agent Prompt
## Issue description
The relation chooser menu uses an inline `style` prop for `left/top`, which is prohibited.

## Issue Context
UI styling must use Tailwind classes only; inline style props are disallowed.

## Fix Focus Areas
- apps/desktop/src/renderer/components/prompt/PromptListView.tsx[744-749]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines 100 to +107
? backup.exportedAt
: new Date().toISOString(),
prompts: Array.isArray(backup?.prompts) ? backup.prompts : [],
promptRelations: Array.isArray(backup?.promptRelations)
? backup.promptRelations
: Array.isArray((backup as any)?.relations)
? (backup as any).relations
: [],

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Action required

3. backup as any type assertion 📘 Rule violation ⚙ Maintainability

8.1

New as any assertions are introduced when normalizing imported backups. This weakens type safety
and can hide runtime shape mismatches for backup payloads.
Agent Prompt
## Issue description
`normalizeImportedBackup()` uses `(backup as any)` to access legacy fields (`promptVersions`, `relations`), violating the no-`any`/restricted assertions rule.

## Issue Context
TypeScript strictness requires avoiding `any` and unsafe assertions; prefer explicit legacy envelope types and runtime type guards.

## Fix Focus Areas
- apps/desktop/src/renderer/services/database-backup-format.ts[89-107]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +996 to +998
if (normalizedMessage.includes("prompt relationships require")) {
return "该备份包含 Prompt 关系数据,需要在桌面版中导入。";
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Action required

4. Chinese string added in code 📘 Rule violation ⚙ Maintainability

8.3

A new hardcoded Chinese user-facing message is added in a non-locale TypeScript file. This violates
the rule forbidding Chinese characters outside locale JSON resources.
Agent Prompt
## Issue description
Hardcoded Chinese text was added in a non-locale source file.

## Issue Context
All Chinese characters must live only in locale JSON files; user-facing strings should be localized via i18n keys.

## Fix Focus Areas
- apps/desktop/src/renderer/services/database-backup.ts[996-998]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +105 to +120
list(query: PromptRelationQuery = {}): PromptRelation[] {
const clauses: string[] = [];
const values: string[] = [];

if (query.promptId) {
if (query.direction === "outgoing") {
clauses.push("source_prompt_id = ?");
values.push(query.promptId);
} else if (query.direction === "incoming") {
clauses.push("target_prompt_id = ?");
values.push(query.promptId);
} else {
clauses.push("(source_prompt_id = ? OR target_prompt_id = ?)");
values.push(query.promptId, query.promptId);
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Action required

6. Undirected query misses edges 🐞 Bug ≡ Correctness

PromptRelationDB canonicalizes related_to as an undirected edge by sorting endpoints, but
list() applies incoming/outgoing filters strictly to source_prompt_id or target_prompt_id.
As a result, list({ promptId, direction: 'outgoing'|'incoming', kind: 'related_to' }) will
silently drop relations depending on prompt id ordering.
Agent Prompt
### Issue description
`PromptRelationDB.normalizeEndpoints()` stores `related_to` relations in a canonical (sorted) orientation, but `PromptRelationDB.list()` treats `direction: 'incoming'|'outgoing'` as directional storage. This makes directional queries for `related_to` incomplete.

### Issue Context
`related_to` is intended to be undirected (canonicalized), so querying should not depend on which endpoint ended up in `source_prompt_id`.

### Fix Focus Areas
- packages/db/src/prompt-relation.ts[105-126]
- packages/db/src/prompt-relation.ts[173-186]

### Suggested fix
In `list()` when `query.kind === 'related_to'` and `query.promptId` is set, ignore `query.direction` and always add the symmetric predicate:
- `clauses.push('(source_prompt_id = ? OR target_prompt_id = ?)')`
- `values.push(query.promptId, query.promptId)`

(Optionally) if `query.kind` is not specified but `direction` is, consider whether `related_to` should appear in both incoming/outgoing sets; if yes, include it via an OR condition keyed on `kind = 'related_to'`.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🧹 Nitpick comments (4)
spec/changes/active/desktop-prompt-relationship-tree/design.md (1)

47-52: 💤 Low value

规范文档中存在连续句式重复,建议用更多样的表达方式提高可读性。 三个文件在列举功能或场景时,均出现多个连续句子以相同动词或结构开头的情况。虽然内容准确,但改善句式多样性能提升文档质量。

  • spec/changes/active/desktop-prompt-relationship-tree/design.md#L47-L52:第 47-52 行的四个列表项均以"Add"开头,建议用更多样的动词(如"Reject"、"Prevent"、"Create")重述。
  • spec/changes/active/desktop-prompt-relationship-tree/implementation.md#L15-L20:第 15-20 行的四个功能项中三个以"Added"开头,建议用不同的表达(如"Introduced"、"Rendered"、"Extended")来改进可读性。
  • spec/changes/active/desktop-prompt-relationship-tree/specs/prompt-relationships/spec.md#L42-L53:第 48-53 行的六个场景项中五个以"When a user"或类似结构开头,可用"Pressing"、"Deleting"、"Exporting"等简洁表述来避免重复。
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@spec/changes/active/desktop-prompt-relationship-tree/design.md` around lines
47 - 52, Improve sentence structure variety across three specification documents
to enhance readability. In
spec/changes/active/desktop-prompt-relationship-tree/design.md lines 47-52,
replace the repetitive "Add" verb used in multiple list items with varied verbs
such as "Reject" for self-relation constraints and "Prevent" for duplicate
prevention. In
spec/changes/active/desktop-prompt-relationship-tree/implementation.md lines
15-20, replace the three consecutive items starting with "Added" by using
alternative expressions like "Introduced" or "Extended" to describe the
features. In
spec/changes/active/desktop-prompt-relationship-tree/specs/prompt-relationships/spec.md
lines 42-53, replace the five scenario items starting with "When a user" with
more concise and varied verb-based phrases such as "Pressing", "Deleting", or
"Exporting" to avoid repetitive grammatical structure while maintaining clarity.
apps/desktop/src/renderer/services/database-backup.ts (1)

996-998: 💤 Low value

硬编码中文字符串应使用 i18n。

新增的错误消息遵循了 formatBackupImportError 函数中的现有模式,但根据编码指南,所有面向用户的文本都应通过 t() 进行国际化处理。建议后续统一重构此函数以支持 i18n。

As per coding guidelines: "All text visible to users must go through t() from react-i18next"

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/desktop/src/renderer/services/database-backup.ts` around lines 996 -
998, The hardcoded Chinese string in the `formatBackupImportError` function
violates i18n guidelines. Replace the hardcoded string "该备份包含 Prompt
关系数据,需要在桌面版中导入。" with a call to the `t()` function from react-i18next, using an
appropriate translation key. Add the Chinese text as a translation entry in the
i18n configuration files to support multiple languages.

Source: Coding guidelines

apps/desktop/tests/integration/services/database-backup-filesystem.integration.test.ts (1)

434-440: ⚡ Quick win

补充关系 note 的回写断言,避免元数据静默丢失

当前仅校验 sourcePromptId/targetPromptId/kind,但此用例初始数据包含 note: "Shared context"。建议在恢复后同时断言 note,确保关系元数据在文件系统备份链路中完整往返。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@apps/desktop/tests/integration/services/database-backup-filesystem.integration.test.ts`
around lines 434 - 440, The test assertion for promptRelations in the
backup-restore verification is incomplete. It currently only validates
sourcePromptId, targetPromptId, and kind fields using expect.objectContaining,
but the initial test data includes a note field with value "Shared context". Add
the note field to the expect.objectContaining object in the assertion to verify
that the relationship metadata (including the note) is properly preserved and
restored through the filesystem backup chain, preventing silent metadata loss.
apps/desktop/tests/unit/services/database-backup-format.test.ts (1)

260-311: ⚡ Quick win

让宽松模式关系过滤用例同时覆盖“保留有效关系”分支

这个用例只断言“全部丢弃”,无法防止实现退化为“无条件清空 promptRelations”。建议同一 payload 增加一条双端点都有效的 relation,并断言它被保留、skipped.promptRelations 仅统计无效项。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/desktop/tests/unit/services/database-backup-format.test.ts` around lines
260 - 311, The current test case for the lenient parser only verifies that
invalid prompt relations are dropped, but doesn't verify that valid relations
are preserved, which means a buggy implementation could unconditionally empty
all promptRelations without being caught. Add another prompt to the payload
(such as "prompt-keep-2") that will be retained, modify one of the existing
promptRelations to reference only kept prompts (for example, update the
relation-keep to have targetPromptId "prompt-keep-2" that now exists), and
update the assertions to expect backup.promptRelations to contain this one valid
relation while skipped.promptRelations should be 1 (counting only the
relation-drop that references the dropped prompt-drop).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/desktop/src/main/ipc/prompt.ipc.ts`:
- Around line 284-308: The four relation IPC handlers (PROMPT_RELATION_CREATE,
PROMPT_RELATION_LIST, PROMPT_RELATION_UPDATE, PROMPT_RELATION_DELETE) lack input
validation and error handling. For each handler, add runtime type validation for
the input parameters (validate that id is a non-empty string, query is a valid
object if provided, and data matches the expected DTO structure), wrap the
handler logic in a try/catch block, and return a structured error response
object (instead of throwing or crashing) when validation fails or database
operations throw errors. This prevents malformed payloads from reaching the
database layer and ensures consistent error responses to the renderer process.

In `@apps/desktop/src/renderer/components/prompt/PromptListView.tsx`:
- Around line 459-486: The commitRelationDrop callback clears the UI state
(setPendingRelationDrop) before awaiting the async onCreateRelation operation,
and since the function is called with void at the click handler, any Promise
rejection from onCreateRelation will go unhandled. Wrap the await
onCreateRelation call in a try-catch block to handle potential errors (such as
duplicate relations or IPC failures), and either restore the UI state on error
or display an error message to the user so they receive feedback when the
relation creation fails.

In `@apps/desktop/src/renderer/stores/prompt.store.ts`:
- Around line 96-100: The current implementation combines the critical prompt
data fetch with the secondary relations data fetch in a single Promise.all(),
which means if the relations query fails, both prompts and relations fail to
update. Since relations are enhancement data and should not block the main
prompt list availability, separate the relations query from the main data fetch.
Keep the getAllPrompts() call as the primary data source, and handle the
listPromptRelations() query independently with error handling that allows it to
fail gracefully (either silently or with a default empty value) without
preventing the prompts from being set in the store. This way, the prompt list
will be available even if the relations query fails.

In `@apps/desktop/tests/unit/main/prompt-relation-db.test.ts`:
- Around line 31-132: The test suite for prompt relations is missing
comprehensive validation of the note field and other text fields for malicious
and complex input handling. Add new test cases that verify the
relationDb.create() and relationDb.update() methods correctly store and retrieve
the note field with high-risk inputs including XSS vectors (like
<script>alert(1)</script>), HTML entities, event handler strings, CJK and emoji
characters (including multi-codepoint sequences), RTL text, zero-width
characters, control characters (including \x00), and strings exceeding 10KB in
length. Each test should verify that values are stored and retrieved without
silent truncation, contamination, or data loss through write→read round-trip
validation.

In `@packages/db/src/prompt-relation.ts`:
- Around line 146-171: The normalizeCreateInput method does not validate or
remove null bytes from string inputs (sourcePromptId, targetPromptId, and note),
which can cause data loss when writing to SQLite. Add null byte validation or
cleaning for all string fields in the normalizeCreateInput method to either
reject inputs containing null bytes or strip them before returning.
Additionally, ensure the update method applies the same null byte handling to
maintain consistency across all database write operations, and add test cases
that verify this behavior by attempting to create or update relations with null
bytes in these fields.

---

Nitpick comments:
In `@apps/desktop/src/renderer/services/database-backup.ts`:
- Around line 996-998: The hardcoded Chinese string in the
`formatBackupImportError` function violates i18n guidelines. Replace the
hardcoded string "该备份包含 Prompt 关系数据,需要在桌面版中导入。" with a call to the `t()`
function from react-i18next, using an appropriate translation key. Add the
Chinese text as a translation entry in the i18n configuration files to support
multiple languages.

In
`@apps/desktop/tests/integration/services/database-backup-filesystem.integration.test.ts`:
- Around line 434-440: The test assertion for promptRelations in the
backup-restore verification is incomplete. It currently only validates
sourcePromptId, targetPromptId, and kind fields using expect.objectContaining,
but the initial test data includes a note field with value "Shared context". Add
the note field to the expect.objectContaining object in the assertion to verify
that the relationship metadata (including the note) is properly preserved and
restored through the filesystem backup chain, preventing silent metadata loss.

In `@apps/desktop/tests/unit/services/database-backup-format.test.ts`:
- Around line 260-311: The current test case for the lenient parser only
verifies that invalid prompt relations are dropped, but doesn't verify that
valid relations are preserved, which means a buggy implementation could
unconditionally empty all promptRelations without being caught. Add another
prompt to the payload (such as "prompt-keep-2") that will be retained, modify
one of the existing promptRelations to reference only kept prompts (for example,
update the relation-keep to have targetPromptId "prompt-keep-2" that now
exists), and update the assertions to expect backup.promptRelations to contain
this one valid relation while skipped.promptRelations should be 1 (counting only
the relation-drop that references the dropped prompt-drop).

In `@spec/changes/active/desktop-prompt-relationship-tree/design.md`:
- Around line 47-52: Improve sentence structure variety across three
specification documents to enhance readability. In
spec/changes/active/desktop-prompt-relationship-tree/design.md lines 47-52,
replace the repetitive "Add" verb used in multiple list items with varied verbs
such as "Reject" for self-relation constraints and "Prevent" for duplicate
prevention. In
spec/changes/active/desktop-prompt-relationship-tree/implementation.md lines
15-20, replace the three consecutive items starting with "Added" by using
alternative expressions like "Introduced" or "Extended" to describe the
features. In
spec/changes/active/desktop-prompt-relationship-tree/specs/prompt-relationships/spec.md
lines 42-53, replace the five scenario items starting with "When a user" with
more concise and varied verb-based phrases such as "Pressing", "Deleting", or
"Exporting" to avoid repetitive grammatical structure while maintaining clarity.
🪄 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: c41012fe-acb8-4c99-821b-400f5142c4d1

📥 Commits

Reviewing files that changed from the base of the PR and between db0e94a and b2cccf7.

📒 Files selected for processing (33)
  • apps/desktop/src/main/database/index.ts
  • apps/desktop/src/main/ipc/index.ts
  • apps/desktop/src/main/ipc/prompt.ipc.ts
  • apps/desktop/src/preload/api/prompt.ts
  • apps/desktop/src/renderer/components/layout/MainContent.tsx
  • apps/desktop/src/renderer/components/prompt/PromptListView.tsx
  • apps/desktop/src/renderer/i18n/locales/de.json
  • apps/desktop/src/renderer/i18n/locales/en.json
  • apps/desktop/src/renderer/i18n/locales/es.json
  • apps/desktop/src/renderer/i18n/locales/fr.json
  • apps/desktop/src/renderer/i18n/locales/ja.json
  • apps/desktop/src/renderer/i18n/locales/zh-TW.json
  • apps/desktop/src/renderer/i18n/locales/zh.json
  • apps/desktop/src/renderer/services/database-backup-format.ts
  • apps/desktop/src/renderer/services/database-backup.ts
  • apps/desktop/src/renderer/services/database.ts
  • apps/desktop/src/renderer/stores/prompt.store.ts
  • apps/desktop/tests/integration/services/database-backup-filesystem.integration.test.ts
  • apps/desktop/tests/unit/components/prompt-list-view-relations.test.tsx
  • apps/desktop/tests/unit/main/prompt-relation-db.test.ts
  • apps/desktop/tests/unit/services/database-backup-format.test.ts
  • apps/desktop/tests/unit/services/database-backup.test.ts
  • packages/db/src/index.ts
  • packages/db/src/init.ts
  • packages/db/src/prompt-relation.ts
  • packages/db/src/schema.ts
  • packages/shared/constants/ipc-channels.ts
  • packages/shared/types/prompt.ts
  • spec/changes/active/desktop-prompt-relationship-tree/design.md
  • spec/changes/active/desktop-prompt-relationship-tree/implementation.md
  • spec/changes/active/desktop-prompt-relationship-tree/proposal.md
  • spec/changes/active/desktop-prompt-relationship-tree/specs/prompt-relationships/spec.md
  • spec/changes/active/desktop-prompt-relationship-tree/tasks.md

Comment on lines +284 to +308
ipcMain.handle(IPC_CHANNELS.PROMPT_RELATION_CREATE, async (_, data: CreatePromptRelationDTO) => {
const relation = relationDb.create(data);
syncWorkspace();
return relation;
});

ipcMain.handle(IPC_CHANNELS.PROMPT_RELATION_LIST, async (_, query?: PromptRelationQuery) => {
return relationDb.list(query);
});

ipcMain.handle(IPC_CHANNELS.PROMPT_RELATION_UPDATE, async (_, id: string, data: UpdatePromptRelationDTO) => {
const relation = relationDb.update(id, data);
if (relation) {
syncWorkspace();
}
return relation;
});

ipcMain.handle(IPC_CHANNELS.PROMPT_RELATION_DELETE, async (_, id: string) => {
const deleted = relationDb.delete(id);
if (deleted) {
syncWorkspace();
}
return deleted;
});

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 | ⚡ Quick win

请在关系 IPC 处理器中补齐入参校验与结构化错误返回。

Line 284-308 的 4 个新增 handler 直接信任 renderer 传入数据,且未做统一 try/catch。建议先校验 id/query/data 的运行时类型,再统一返回结构化错误对象,避免把 malformed payload 直接下沉到 DB 并产生非结构化拒绝。

建议修复(示例)
+  const toIpcError = (error: unknown) => ({
+    ok: false as const,
+    error: { message: error instanceof Error ? error.message : "Unknown error" },
+  });
+
+  const assertRelationId = (value: unknown): string => {
+    if (typeof value !== "string" || value.trim().length === 0) {
+      throw new Error("Relation id is required");
+    }
+    return value;
+  };
+
   ipcMain.handle(IPC_CHANNELS.PROMPT_RELATION_CREATE, async (_, data: CreatePromptRelationDTO) => {
-    const relation = relationDb.create(data);
-    syncWorkspace();
-    return relation;
+    try {
+      // TODO: 替换为仓库统一的 runtime DTO 校验函数
+      const relation = relationDb.create(data);
+      syncWorkspace();
+      return { ok: true as const, data: relation };
+    } catch (error) {
+      return toIpcError(error);
+    }
   });

As per coding guidelines, “apps/desktop/src/main/ipc/**: All IPC handlers must validate input types and reject malformed payloads before processing” and “IPC handlers must catch errors and return structured error responses, never crash the main process silently”.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/desktop/src/main/ipc/prompt.ipc.ts` around lines 284 - 308, The four
relation IPC handlers (PROMPT_RELATION_CREATE, PROMPT_RELATION_LIST,
PROMPT_RELATION_UPDATE, PROMPT_RELATION_DELETE) lack input validation and error
handling. For each handler, add runtime type validation for the input parameters
(validate that id is a non-empty string, query is a valid object if provided,
and data matches the expected DTO structure), wrap the handler logic in a
try/catch block, and return a structured error response object (instead of
throwing or crashing) when validation fails or database operations throw errors.
This prevents malformed payloads from reaching the database layer and ensures
consistent error responses to the renderer process.

Source: Coding guidelines

Comment on lines +459 to +486
const commitRelationDrop = useCallback(
async (kind: PromptRelationKind) => {
if (!pendingRelationDrop) {
return;
}

const { sourcePromptId, targetPromptId } = pendingRelationDrop;

if (kind === 'grouped_under') {
if (canMoveToParent(sourcePromptId, targetPromptId)) {
setExpandedIds((current) => new Set(current).add(targetPromptId));
onMovePrompt(
sourcePromptId,
targetPromptId,
getChildren(targetPromptId).length,
);
}
setPendingRelationDrop(null);
return;
}

setPendingRelationDrop(null);
await onCreateRelation?.({
sourcePromptId,
targetPromptId,
kind,
});
},

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 | 🟡 Minor | ⚡ Quick win

关系创建失败时缺少错误处理,可能产生未处理 Promise 拒绝。

当前先关闭菜单再异步创建关系,且点击处用 void 丢弃返回 Promise;一旦创建失败(如重复关系/IPC 报错),这里没有兜底处理,用户也拿不到反馈。

建议修复
   const commitRelationDrop = useCallback(
     async (kind: PromptRelationKind) => {
       if (!pendingRelationDrop) {
         return;
       }

       const { sourcePromptId, targetPromptId } = pendingRelationDrop;
@@
-      setPendingRelationDrop(null);
-      await onCreateRelation?.({
-        sourcePromptId,
-        targetPromptId,
-        kind,
-      });
+      if (!onCreateRelation) {
+        setPendingRelationDrop(null);
+        return;
+      }
+
+      try {
+        await onCreateRelation({
+          sourcePromptId,
+          targetPromptId,
+          kind,
+        });
+        setPendingRelationDrop(null);
+      } catch (error) {
+        console.error("Failed to create prompt relation:", error);
+      }
     },

Also applies to: 768-769

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/desktop/src/renderer/components/prompt/PromptListView.tsx` around lines
459 - 486, The commitRelationDrop callback clears the UI state
(setPendingRelationDrop) before awaiting the async onCreateRelation operation,
and since the function is called with void at the click handler, any Promise
rejection from onCreateRelation will go unhandled. Wrap the await
onCreateRelation call in a try-catch block to handle potential errors (such as
duplicate relations or IPC failures), and either restore the UI state on error
or display an error message to the user so they receive feedback when the
relation creation fails.

Comment on lines +96 to +100
const [prompts, relations] = await Promise.all([
db.getAllPrompts(),
db.listPromptRelations(),
]);
set({ prompts, relations });

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 | ⚡ Quick win

关系查询失败会阻断主 Prompt 列表加载。

这里把核心数据和关系数据绑在同一个 Promise.all,任一失败都会走 catch,导致 prompts 也不更新。关系是增强信息,不应让主列表不可用。建议将关系查询降级为“失败不阻断主流程”。

建议修复
       fetchPrompts: async () => {
         set({ isLoading: true });
         try {
-          // Get data from IndexedDB
-          const [prompts, relations] = await Promise.all([
-            db.getAllPrompts(),
-            db.listPromptRelations(),
-          ]);
-          set({ prompts, relations });
+          const prompts = await db.getAllPrompts();
+          let relations = get().relations;
+          try {
+            relations = await db.listPromptRelations();
+          } catch (relationError) {
+            console.warn("Failed to fetch prompt relations:", relationError);
+          }
+          set({ prompts, relations });
         } catch (error) {
           console.error("Failed to fetch prompts:", error);
         } finally {
           set({ isLoading: false });
         }
       },
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/desktop/src/renderer/stores/prompt.store.ts` around lines 96 - 100, The
current implementation combines the critical prompt data fetch with the
secondary relations data fetch in a single Promise.all(), which means if the
relations query fails, both prompts and relations fail to update. Since
relations are enhancement data and should not block the main prompt list
availability, separate the relations query from the main data fetch. Keep the
getAllPrompts() call as the primary data source, and handle the
listPromptRelations() query independently with error handling that allows it to
fail gracefully (either silently or with a default empty value) without
preventing the prompts from being set in the store. This way, the prompt list
will be available even if the relations query fails.

Comment on lines +31 to +132
it("creates and lists directed prompt relations", () => {
const source = createPrompt("Source");
const target = createPrompt("Target");

const relation = relationDb.create({
sourcePromptId: source.id,
targetPromptId: target.id,
kind: "depends_on",
note: "needs context",
});

expect(relation.sourcePromptId).toBe(source.id);
expect(relation.targetPromptId).toBe(target.id);
expect(relation.kind).toBe("depends_on");
expect(relation.note).toBe("needs context");
expect(relationDb.list({ promptId: source.id, direction: "outgoing" })).toEqual([
relation,
]);
expect(relationDb.list({ promptId: target.id, direction: "incoming" })).toEqual([
relation,
]);
});

it("canonicalizes related_to as an undirected relation", () => {
const first = createPrompt("A");
const second = createPrompt("B");

const original = relationDb.create({
sourcePromptId: second.id,
targetPromptId: first.id,
kind: "related_to",
});
const duplicate = relationDb.create({
sourcePromptId: first.id,
targetPromptId: second.id,
kind: "related_to",
});

expect(duplicate.id).toBe(original.id);
expect(relationDb.list({ promptId: first.id })).toHaveLength(1);
expect(relationDb.list({ promptId: second.id })).toHaveLength(1);
});

it("rejects invalid relation endpoints and kinds", () => {
const prompt = createPrompt("Prompt");

expect(() =>
relationDb.create({
sourcePromptId: prompt.id,
targetPromptId: prompt.id,
kind: "related_to",
}),
).toThrow("Prompt relation cannot point to itself");
expect(() =>
relationDb.create({
sourcePromptId: prompt.id,
targetPromptId: "missing",
kind: "related_to",
}),
).toThrow("Target prompt does not exist");
expect(() =>
relationDb.create({
sourcePromptId: prompt.id,
targetPromptId: "missing",
kind: "grouped_under" as "related_to",
}),
).toThrow("Unsupported prompt relation kind");
});

it("updates and deletes relations", () => {
const source = createPrompt("Source");
const target = createPrompt("Target");
const relation = relationDb.create({
sourcePromptId: source.id,
targetPromptId: target.id,
kind: "variant_of",
});

const updated = relationDb.update(relation.id, {
kind: "next_step",
note: "run after source",
});

expect(updated?.kind).toBe("next_step");
expect(updated?.note).toBe("run after source");
expect(relationDb.delete(relation.id)).toBe(true);
expect(relationDb.list()).toEqual([]);
});

it("deletes relations when a referenced prompt is deleted", () => {
const source = createPrompt("Source");
const target = createPrompt("Target");
relationDb.create({
sourcePromptId: source.id,
targetPromptId: target.id,
kind: "next_step",
});

expect(relationDb.list()).toHaveLength(1);
expect(promptDb.delete(source.id)).toBe(true);
expect(relationDb.list()).toEqual([]);
});

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 | ⚡ Quick win

补齐关系文本字段的恶意输入与多语言回写测试

当前仅覆盖普通文本,缺少对 prompt_relations.note / 相关文本字段的高风险输入回写验证。建议补充 write→read 场景:<script>alert(1)</script>、HTML entities、事件处理器字符串、CJK/emoji(含多码点)/RTL/零宽字符、控制字符(含 \x00)及 10KB+ 长文本,确保不会发生静默截断或污染。
As per coding guidelines, “Store and retrieve <script>alert(1)</script>...”, “Full round-trip ... CJK/emoji/RTL/zero-width...”, and “Test 10KB+ strings ...” are required for **/*.test.ts.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/desktop/tests/unit/main/prompt-relation-db.test.ts` around lines 31 -
132, The test suite for prompt relations is missing comprehensive validation of
the note field and other text fields for malicious and complex input handling.
Add new test cases that verify the relationDb.create() and relationDb.update()
methods correctly store and retrieve the note field with high-risk inputs
including XSS vectors (like <script>alert(1)</script>), HTML entities, event
handler strings, CJK and emoji characters (including multi-codepoint sequences),
RTL text, zero-width characters, control characters (including \x00), and
strings exceeding 10KB in length. Each test should verify that values are stored
and retrieved without silent truncation, contamination, or data loss through
write→read round-trip validation.

Source: Coding guidelines

Comment on lines +146 to +171
private normalizeCreateInput(
data: CreatePromptRelationDTO,
): CreatePromptRelationDTO {
this.assertPromptId(data.sourcePromptId, "Source prompt id");
this.assertPromptId(data.targetPromptId, "Target prompt id");
this.assertRelationKind(data.kind);

if (data.sourcePromptId === data.targetPromptId) {
throw new Error("Prompt relation cannot point to itself");
}

this.assertPromptExists(data.sourcePromptId, "Source prompt does not exist");
this.assertPromptExists(data.targetPromptId, "Target prompt does not exist");

const endpoints = this.normalizeEndpoints(
data.sourcePromptId,
data.targetPromptId,
data.kind,
);

return {
...data,
...endpoints,
note: data.note ?? null,
};
}

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 | 🟡 Minor | ⚡ Quick win

建议在写入前校验或移除 null byte。

根据编码规范,SQLite 字符串输入可能在 null byte (\x00) 处截断丢失数据。当前 assertPromptId 只检查非空,note 字段也未过滤 null byte。建议在 normalizeCreateInputupdate 中对所有字符串字段(sourcePromptIdtargetPromptIdnote)进行 null byte 校验或清理,并在测试中覆盖这一行为。

🛡️ 建议的修复示例
  private assertPromptId(value: string, label: string): void {
    if (typeof value !== "string" || value.trim().length === 0) {
      throw new Error(`${label} is required`);
    }
+   if (value.includes('\x00')) {
+     throw new Error(`${label} contains null byte`);
+   }
  }

或在 normalizeCreateInput 中统一处理:

  private normalizeCreateInput(
    data: CreatePromptRelationDTO,
  ): CreatePromptRelationDTO {
    this.assertPromptId(data.sourcePromptId, "Source prompt id");
    this.assertPromptId(data.targetPromptId, "Target prompt id");
    this.assertRelationKind(data.kind);

+   const cleanNote = data.note?.replace(/\x00/g, '') ?? null;
+
    if (data.sourcePromptId === data.targetPromptId) {
      throw new Error("Prompt relation cannot point to itself");
    }

    this.assertPromptExists(data.sourcePromptId, "Source prompt does not exist");
    this.assertPromptExists(data.targetPromptId, "Target prompt does not exist");

    const endpoints = this.normalizeEndpoints(
      data.sourcePromptId,
      data.targetPromptId,
      data.kind,
    );

    return {
      ...data,
      ...endpoints,
-     note: data.note ?? null,
+     note: cleanNote,
    };
  }

As per coding guidelines: "SQLite string inputs can lose data around null bytes (\x00); input validation should strip or reject null bytes before database writes and test this behavior"

📝 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
private normalizeCreateInput(
data: CreatePromptRelationDTO,
): CreatePromptRelationDTO {
this.assertPromptId(data.sourcePromptId, "Source prompt id");
this.assertPromptId(data.targetPromptId, "Target prompt id");
this.assertRelationKind(data.kind);
if (data.sourcePromptId === data.targetPromptId) {
throw new Error("Prompt relation cannot point to itself");
}
this.assertPromptExists(data.sourcePromptId, "Source prompt does not exist");
this.assertPromptExists(data.targetPromptId, "Target prompt does not exist");
const endpoints = this.normalizeEndpoints(
data.sourcePromptId,
data.targetPromptId,
data.kind,
);
return {
...data,
...endpoints,
note: data.note ?? null,
};
}
private normalizeCreateInput(
data: CreatePromptRelationDTO,
): CreatePromptRelationDTO {
this.assertPromptId(data.sourcePromptId, "Source prompt id");
this.assertPromptId(data.targetPromptId, "Target prompt id");
this.assertRelationKind(data.kind);
const cleanNote = data.note?.replace(/\x00/g, '') ?? null;
if (data.sourcePromptId === data.targetPromptId) {
throw new Error("Prompt relation cannot point to itself");
}
this.assertPromptExists(data.sourcePromptId, "Source prompt does not exist");
this.assertPromptExists(data.targetPromptId, "Target prompt does not exist");
const endpoints = this.normalizeEndpoints(
data.sourcePromptId,
data.targetPromptId,
data.kind,
);
return {
...data,
...endpoints,
note: cleanNote,
};
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/db/src/prompt-relation.ts` around lines 146 - 171, The
normalizeCreateInput method does not validate or remove null bytes from string
inputs (sourcePromptId, targetPromptId, and note), which can cause data loss
when writing to SQLite. Add null byte validation or cleaning for all string
fields in the normalizeCreateInput method to either reject inputs containing
null bytes or strip them before returning. Additionally, ensure the update
method applies the same null byte handling to maintain consistency across all
database write operations, and add test cases that verify this behavior by
attempting to create or update relations with null bytes in these fields.

Source: Coding guidelines

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