Skip to content

feat(settings): personal rules added to system prompts#944

Open
Bl3f wants to merge 2 commits into
mainfrom
cursor/personal-rules-settings-cae6
Open

feat(settings): personal rules added to system prompts#944
Bl3f wants to merge 2 commits into
mainfrom
cursor/personal-rules-settings-cae6

Conversation

@Bl3f

@Bl3f Bl3f commented Jun 20, 2026

Copy link
Copy Markdown
Contributor

Problem

Closes #936. Users had no way to define personal rules/preferences that shape how nao responds — they could only edit memories the agent already created.

What this adds

  • A new Settings → Rules page where each user can create, edit, and delete personal rules.
  • Personal rules are injected into the system prompt under User Rules → Personal Rules, layered on top of the global project rules (RULES.md).
  • Rules are scoped per project and apply only to that user's own chats.
  • Rules are visible to the context recommendation system so it can detect de-facto rules (e.g. if multiple users define similar rules, it can suggest promoting them to shared context).

Implementation notes

Per the issue suggestion, this reuses the existing memories table instead of creating a new one:

  • Added a new memory category personal_rule (apps/backend/src/types/memory.ts).
  • Added a nullable project_id column to memories (sqlite + postgres migrations 0050) to scope personal rules to a project.
  • Personal rules are hidden from the Memory settings page (user.getMemories now excludes them) and surfaced only on the new Rules page.
  • New rule tRPC router (list/create/update/delete), project-scoped.
  • System prompt renders personal rules in the User Rules section (system-prompt.tsx).
  • v_memories view now also exposes project_id and includes project-scoped rows so query_app_db / the recommendation bot can read personal rules; the recommendation prompt now calls out category = "personal_rule" for promotion to shared context.

Testing

  • npm run lint (backend, frontend, shared) ✅
  • npm run -w @nao/backend db:check-migrations
  • Backend vitest: no new failures introduced (10 pre-existing failures on main unrelated to this change).
  • Manual end-to-end testing through the UI (see walkthrough on the agent summary).
Open in Web Open in Cursor 

Review in cubic

Personal rules are user-authored directives stored in the existing memories
table under a new 'personal_rule' category, scoped per project. They are
injected into the system prompt (User Rules > Personal Rules), managed from a
new Settings > Rules page, hidden from the Memory page, and exposed to the
context recommendation system via the v_memories view so de-facto rules can be
promoted to shared context.
@github-actions

github-actions Bot commented Jun 20, 2026

Copy link
Copy Markdown
Contributor

🚀 Preview Deployment

URL https://pr-944-1d02f6b.preview.getnao.io
Commit 1d02f6b

⚠️ No LLM API keys configured - you'll see the API key setup flow when trying to chat.


Preview will be automatically removed when this PR is closed.

@Bl3f
Bl3f marked this pull request as ready for review June 23, 2026 09:33

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Stale comment

Identified one net-new security finding after module triage and deduplication.

Open in Web View Automation 

Sent by Cursor Security Agent: Security Reviewer

if (!content) {
throw new TRPCError({ code: 'BAD_REQUEST', message: 'Rule content cannot be empty.' });
}
const updated = await memoryQueries.updateUserMemoryContent(ctx.user.id, input.ruleId, content);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔒 Agentic Security Review
Severity: MEDIUM

rule.update and rule.delete delegate to generic memory mutators keyed only by userId + ruleId. These routes are project-scoped features, but the write/delete path does not enforce projectId (or personal_rule category), so a known rule ID from another project can still be modified or deleted.

Impact: project-level isolation for personal rules can be bypassed, enabling cross-project rule tampering.

Fix in Cursor Fix in Web

Reviewed by Cursor Security Reviewer for commit bf4eccc. Configure here.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

3 issues found across 23 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="apps/backend/src/trpc/rule.routes.ts">

<violation number="1" location="apps/backend/src/trpc/rule.routes.ts:39">
P1: Update and delete mutations lack project scoping — they don't pass `ctx.project.id`, so a user could modify or delete personal rules from other projects within the same user scope. Breaks the per-project isolation intent.</violation>
</file>

<file name="apps/backend/src/components/ai/system-prompt.tsx">

<violation number="1" location="apps/backend/src/components/ai/system-prompt.tsx:200">
P3: Missing `key` prop on `ListItem` in `personalRules.map()`. React requires a stable key for list reconciliation; omitting it causes unnecessary re-renders and potential state bugs if the list can change.</violation>
</file>

<file name="apps/backend/migrations-sqlite/0050_add_memories_project_id.sql">

<violation number="1" location="apps/backend/migrations-sqlite/0050_add_memories_project_id.sql:1">
P1: SQLite migration misses `ON DELETE CASCADE` for `memories.project_id`, causing schema/migration mismatch and potentially leaving project-scoped memories undeleted.</violation>
</file>

Tip: cubic can generate docs of your entire codebase and keep them up to date. Try it here.

Re-trigger cubic

if (!content) {
throw new TRPCError({ code: 'BAD_REQUEST', message: 'Rule content cannot be empty.' });
}
const updated = await memoryQueries.updateUserMemoryContent(ctx.user.id, input.ruleId, content);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1: Update and delete mutations lack project scoping — they don't pass ctx.project.id, so a user could modify or delete personal rules from other projects within the same user scope. Breaks the per-project isolation intent.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/backend/src/trpc/rule.routes.ts, line 39:

<comment>Update and delete mutations lack project scoping — they don't pass `ctx.project.id`, so a user could modify or delete personal rules from other projects within the same user scope. Breaks the per-project isolation intent.</comment>

<file context>
@@ -0,0 +1,62 @@
+			if (!content) {
+				throw new TRPCError({ code: 'BAD_REQUEST', message: 'Rule content cannot be empty.' });
+			}
+			const updated = await memoryQueries.updateUserMemoryContent(ctx.user.id, input.ruleId, content);
+			if (!updated) {
+				throw new TRPCError({ code: 'NOT_FOUND', message: 'Rule not found.' });
</file context>

@@ -0,0 +1,2 @@
ALTER TABLE `memories` ADD `project_id` text REFERENCES project(id);--> statement-breakpoint

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1: SQLite migration misses ON DELETE CASCADE for memories.project_id, causing schema/migration mismatch and potentially leaving project-scoped memories undeleted.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/backend/migrations-sqlite/0050_add_memories_project_id.sql, line 1:

<comment>SQLite migration misses `ON DELETE CASCADE` for `memories.project_id`, causing schema/migration mismatch and potentially leaving project-scoped memories undeleted.</comment>

<file context>
@@ -0,0 +1,2 @@
+ALTER TABLE `memories` ADD `project_id` text REFERENCES project(id);--> statement-breakpoint
+CREATE INDEX `memories_projectId_idx` ON `memories` (`project_id`);
\ No newline at end of file
</file context>

<Title level={3}>Personal Rules</Title>
<Span>These rules were defined by the user in their settings. Always follow them.</Span>
<List>
{personalRules.map((rule) => (

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P3: Missing key prop on ListItem in personalRules.map(). React requires a stable key for list reconciliation; omitting it causes unnecessary re-renders and potential state bugs if the list can change.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/backend/src/components/ai/system-prompt.tsx, line 200:

<comment>Missing `key` prop on `ListItem` in `personalRules.map()`. React requires a stable key for list reconciliation; omitting it causes unnecessary re-renders and potential state bugs if the list can change.</comment>

<file context>
@@ -186,10 +188,21 @@ export function SystemPrompt({
+								<Title level={3}>Personal Rules</Title>
+								<Span>These rules were defined by the user in their settings. Always follow them.</Span>
+								<List>
+									{personalRules.map((rule) => (
+										<ListItem>{rule.content}</ListItem>
+									))}
</file context>

…-settings-cae6

# Conflicts:
#	apps/backend/migrations-postgres/meta/0050_snapshot.json
#	apps/backend/migrations-postgres/meta/_journal.json
#	apps/backend/migrations-sqlite/meta/0050_snapshot.json
#	apps/backend/migrations-sqlite/meta/_journal.json
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.

[feature] Personal rules - user-level context rules in settings

2 participants