Skip to content

feat: add experiment-gated self-improving learning, bounded memory, background curation, and auto-create/update skill#252

Open
iskandarsulaili wants to merge 25 commits into
Zoo-Code-Org:mainfrom
iskandarsulaili:selfimproving
Open

feat: add experiment-gated self-improving learning, bounded memory, background curation, and auto-create/update skill#252
iskandarsulaili wants to merge 25 commits into
Zoo-Code-Org:mainfrom
iskandarsulaili:selfimproving

Conversation

@iskandarsulaili
Copy link
Copy Markdown

@iskandarsulaili iskandarsulaili commented May 22, 2026

Description

This PR adds an experiment-gated self-improving subsystem so Zoo can learn from task outcomes, user corrections, and recurring workflow patterns instead of repeatedly making the same corrected mistakes.

Key implementation details in this PR:

  • Adds SelfImprovingManager as the orchestration layer for learning, review, and prompt-context generation.
  • Adds bounded persistence for learning events, learned patterns, memory entries, transcript recall, and skill-usage telemetry.
  • Records task completion and user-turn signals from the extension/provider flow and converts them into structured learning events.
  • Injects a small learned-guidance context into future system prompts so successful lessons can influence prompt guidance, tool preferences, and error avoidance.
  • Adds background review/curation components (CuratorService, ReviewPromptFactory, TranscriptRecall, MemoryStore, SkillUsageStore) to keep retained guidance bounded and maintainable over time.
  • Adds PatternAnalyzer to detect recurring patterns in task outcomes and workflow signals.
  • Adds FeedbackCollector to capture user corrections and feedback during task interactions.
  • Adds ImprovementApplier to apply learned improvements derived from analyzed patterns and feedback.
  • Adds CodeIndexAdapter to integrate code index search hits into the self-improving pipeline, enriching learning signals with codebase context.
  • Adds ActionExecutor to execute improvement actions, including skill mutations (auto-create/update skills based on learned patterns).
  • Exposes the feature behind the selfImproving experiment flag and surfaces status through extension state/settings so it remains opt-in and low-risk.
  • Adds an auto-skill toggle experimental setting that, when enabled, automatically creates or updates skills based on learned patterns and recurring task workflows.
  • Adds workspace/global scope controls — settings to control whether self-improving data (memories, learned patterns, skill usage) is scoped to the current workspace or stored globally.
  • Adds design documentation (ARCHITECTURE.md and GAP_ANALYSIS.md) in the service directory to guide future development and review.
  • Adds tests across the new self-improving services and updates related prompt/experiment/extension coverage.
  • Adds AgentMemoryAdapter as an optional memory backend that connects to the agentmemory REST API (https://github.com/rohitg00/agentmemory), providing 124+ memory endpoints and 53 MCP tools as an alternative to the built-in MemoryStore.
  • Adds MemoryBackend interface and MemoryBackendFactory so the system can transparently switch between built-in file-based memory and agentmemory's REST API backend via the memoryBackend configuration option.

Design choices / trade-offs reviewers should note:

  • The entire feature is experiment-gated, so the intended default behavior is effectively zero overhead when disabled.
  • Stored learning and memory are bounded to reduce prompt bloat and long-term drift.
  • Prompt injection is intentionally narrow and derived from curated learning context rather than dumping raw history.
  • This PR introduces the core learning infrastructure first; future iterations can refine signal quality, ranking, and curator behavior once the end-to-end pipeline is in place.
  • The memory backend is pluggable via the MemoryBackend interface — users can choose between the built-in file-based MemoryStore (default, zero external dependencies) or the agentmemory REST API backend for advanced semantic search, consolidation, and cross-session memory features.
  • The agentmemory adapter gracefully degrades when the server is unavailable — all operations return null/empty without throwing, and periodic health checks detect when the server becomes available.
  • The auto-skill feature is also experiment-gated and disabled by default, allowing safe experimentation with skill mutation workflows.
  • Workspace/global scope controls give users flexibility to isolate learning per project or aggregate across sessions.

Video/Screenshot

self-improving-zoo-code.mp4

Commit History

The branch contains 22 commits spanning feature implementation, hardening, and test coverage:

  1. a40bdc15 — feat: implement Self-Improving Manager for adaptive learning
  2. 98057936 — feat: Enhance SelfImprovingManager with MemoryStore and SkillUsageStore integration
  3. c6d0b55c — feat: Implement ReviewPromptFactory and TranscriptRecall for self-improvement
  4. 445e9ede — feat: Enhance self-improvement system with user message tracking and settings management
  5. f15a9a6b — feat: Implement memory backend system with AgentMemoryAdapter and MemoryBackendFactory
  6. fb39c36c — fix: harden self-improving memory deletion and recall
  7. 9f2688ff — fix: tighten self-improving pattern merges
  8. 53490f1b — test: tighten self-improving regression coverage
  9. f1210f7e — fix: harden transcript recall loading
  10. 327a7d34 — refactor: align self-improving shared types
  11. f90b8845 — fix: refine self-improving memory search and scoring
  12. e38aad81 — fix: serialize transcript recall lazy initialization
  13. 7860dc10 — fix: preserve substring text in agentmemory forget search
  14. 4f729743 — fix: capture interactive user corrections for self-improving
  15. 29af44c3 — fix: feed code index search hits into self-improving
  16. f6581653 — test: verify extension startup initializes self-improving
  17. f672d566 — feat: add skill mutation APIs and auto-skill toggle
  18. 732be8ff — feat: wire self-improving auto skill actions
  19. 02ed3aed — feat: expose auto-skill toggle in experimental settings
  20. f6d34080 — test: cover auto-skill experiment gating
  21. 7cf030e5 — Add configurable self-improving memory backend settings
  22. 99ce4f29 — [verified] Add workspace/global self-improving scope controls

Integration Points

Files modified outside the service folder to wire the self-improving subsystem into the extension:

  • src/extension.ts — initializes the SelfImprovingManager on extension startup
  • src/core/prompts/system.ts — injects learned guidance context into system prompts when the experiment is enabled
  • src/core/webview/ClineProvider.ts — exposes self-improving status, settings, and state through the provider
  • src/core/webview/webviewMessageHandler.ts — refreshes runtime behavior on settings updates (toggle, scope, auto-skill, memory backend)
  • packages/types/src/vscode-extension-host.ts — adds self-improving state/status typing to the extension host types
  • webview-ui/src/components/settings/ExperimentalSettings.tsx — adds experiment checkbox, scope selector, auto-skill toggle, memory-backend selector, and agentmemory URL controls
  • webview-ui/src/components/settings/SettingsView.tsx — wires self-improving settings UI into the cached state pattern

Complete List of New Service Files

All under src/services/self-improving/:

File Purpose
SelfImprovingManager.ts Orchestration layer for learning, review, and prompt-context generation
LearningStore.ts Stores structured learning events
MemoryStore.ts File-based memory persistence (default backend)
MemoryBackend.ts Memory backend interface for pluggable storage
MemoryBackendFactory.ts Factory for creating the configured memory backend
AgentMemoryAdapter.ts Agentmemory REST API adapter (optional backend)
SkillUsageStore.ts Tracks skill usage telemetry
TranscriptRecall.ts Persists and recalls task transcripts
CuratorService.ts Background curation, aging, and deduplication of memories
ReviewPromptFactory.ts Generates structured review prompts for curation
ActionExecutor.ts Executes improvement actions, including skill mutations
ImprovementApplier.ts Applies learned improvements to system behavior
PatternAnalyzer.ts Analyzes recurring patterns in task outcomes
FeedbackCollector.ts Collects user feedback and corrections
CodeIndexAdapter.ts Integrates code index search hits into learning signals
types.ts Shared type definitions for the subsystem
ARCHITECTURE.md Architecture documentation and design rationale
GAP_ANALYSIS.md Gap analysis documenting known limitations and future work
__tests__/ Extensive test coverage across all components

Test Procedure

  1. Enable the selfImproving experiment in Zoo Code's experimental settings.
  2. Start a task and interact normally so the extension records user turns and task lifecycle activity.
  3. Complete a task successfully, then run another similar task and verify the self-improving subsystem initializes without disrupting the normal workflow.
  4. Trigger repeated/corrected task patterns and verify the manager can record learning events and expose updated self-improving status through extension state.
  5. Review the new self-improving service tests and run the relevant test suite covering:
    • SelfImprovingManager
    • LearningStore
    • MemoryStore
    • SkillUsageStore
    • CuratorService
    • TranscriptRecall
    • ActionExecutor
    • ReviewPromptFactory
    • AgentMemoryAdapter
    • MemoryBackendFactory
    • PatternAnalyzer
    • FeedbackCollector
    • ImprovementApplier
    • CodeIndexAdapter
  6. Verify prompt generation still works when the experiment is disabled and that no learned guidance is injected in the disabled path.
  7. Verify the new experimental setting appears in the UI and that toggling it updates manager behavior through settings handling.
  8. (Optional) Start the agentmemory server (npx agentmemory) and configure the memoryBackend option to "agentmemory" to verify the REST API adapter connects and stores/recalls memory correctly.
  9. (Optional) Enable the auto-skill toggle and verify that skills are automatically created/updated based on learned patterns.
  10. (Optional) Toggle between workspace and global scope settings and verify that self-improving data is correctly isolated or aggregated.

Environment:

  • VS Code extension environment
  • selfImproving experiment enabled for feature-path validation
  • Existing default-disabled path verified for regression resistance

Pre-Submission Checklist

  • Issue Linked: N/A
  • Scope: N/A
  • Self-Review: I have performed a thorough self-review of my code.
  • Testing: New and/or updated tests have been added to cover my changes (if applicable).
  • Documentation Impact: I have considered if my changes require documentation updates (see "Documentation Updates" section below).
  • Contribution Guidelines: I have read and agree to the Contributor Guidelines.

Documentation Updates

  • No documentation updates are required.
  • Yes, documentation updates are required.

Recommended documentation updates:

  • Document what the selfImproving experiment does.
  • Explain that the feature is opt-in and disabled by default.
  • Describe, at a high level, what signals are retained and how prompt guidance is bounded.
  • Document the auto-skill toggle and workspace/global scope controls.
  • Add reviewer/user guidance for validating the feature in development builds.

Additional Notes

I did not find a directly matching open issue/PR in Zoo-Code-Org/Zoo-Code for this self-improving learning system. The issue is the critical product gap itself: Zoo currently has no bounded mechanism to retain lessons from corrected failures, repeated task outcomes, or recurring workflow signals, which makes it more likely to repeat avoidable mistakes over time. This PR is the implementation path toward solving that gap with an experiment-gated, bounded, and reversible design.

  • The agentmemory integration is optional and configured via the memoryBackend option. When set to "agentmemory", the system connects to a running agentmemory server at http://localhost:4001 (configurable). The adapter uses REST API calls (not npm imports) so no additional dependencies are required.
  • The auto-skill feature is gated behind a separate experimental toggle and can be enabled/disabled independently of the core self-improving experiment.
  • Workspace/global scope controls allow users to choose between per-project isolation and cross-session aggregation of learning data.

Get in Touch

Discord: @iskandarsulaili

Summary by CodeRabbit

Release Notes

  • New Features

    • Added self-improving experiment that learns from task outcomes to enhance prompts, tool preferences, and error handling.
    • Added auto-skills feature to automatically create and update reusable workflow skills.
    • Introduced memory system with builtin and agentmemory backend options for persistent knowledge storage.
    • Added configuration options to set learning scope (workspace/global) and memory backend selection.
    • Added curator service for automatic skill lifecycle management and optimization.
  • Documentation

    • Added multi-language support for self-improving feature (16 languages).

iskandarsulaili and others added 7 commits May 21, 2026 18:56
- Introduced SelfImprovingManager to facilitate background learning from task outcomes.
- Added LearningStore, FeedbackCollector, PatternAnalyzer, ImprovementApplier, and CodeIndexAdapter for modular functionality.
- Implemented experiment gating for enabling/disabling self-improvement features.
- Created comprehensive tests for SelfImprovingManager to ensure functionality and stability.
- Updated localization files to include settings for self-improvement in multiple languages.
…re integration

- Introduced MemoryStore for managing memory entries and snapshots.
- Added SkillUsageStore for tracking skill usage telemetry.
- Implemented ActionExecutor to handle action execution and logging.
- Updated SelfImprovingManager to initialize and utilize MemoryStore and SkillUsageStore.
- Enhanced telemetry reporting in SelfImprovingManager's status.
- Added tests for ActionExecutor, MemoryStore, and SkillUsageStore to ensure functionality.
- Updated types to include new SkillUsageStore and MemoryStore interfaces.
…rovement

- Added ReviewPromptFactory to generate structured review prompts for memory and skill reviews.
- Introduced TranscriptRecall to store and manage transcript entries for task outcomes.
- Enhanced SelfImprovingManager to utilize ReviewPromptFactory and TranscriptRecall.
- Updated CuratorService to support new functionality and maintain skill usage.
- Created unit tests for ReviewPromptFactory, TranscriptRecall, and CuratorService to ensure reliability.
- Modified types and index files to include new services and types.
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 22, 2026

📝 Walkthrough

Walkthrough

Adds an experiment-gated "self-improving" subsystem: Zod types, memory backends, persistent LearningStore, event→pattern analysis, action generation/execution, a SelfImprovingManager orchestrator with timers/curator, prompt injection, provider integration, tests, and i18n strings.

Changes

Self-Improving Learning Subsystem

Layer / File(s) Summary
Learning and memory type schemas
packages/types/src/learning.ts, packages/types/src/memory.ts, packages/types/src/experiment.ts, packages/types/src/__tests__/learning-memory.test.ts
Zod-based schemas define feedback signals, learning events, learned patterns with state/type/metrics, improvement actions, learning telemetry, and memory entries bounded to 10 per context. Tests validate parsing, defaults, and round-tripping.
Shared types and barrel exports
src/services/self-improving/types.ts, src/services/self-improving/index.ts, packages/types/src/index.ts
Service-level contracts (Logger, CodeIndexInfo, TaskEventInfo, PromptContext, SelfImprovingManagerOptions), aliases for DEFAULT_CONFIG/EMPTY_STATE, and new barrel re-exports for the subsystem.
Memory backend and adapters
src/services/self-improving/MemoryBackend.ts, src/services/self-improving/MemoryBackendFactory.ts, src/services/self-improving/AgentMemoryAdapter.ts, src/services/self-improving/CodeIndexAdapter.ts
MemoryBackend interface; factory selects builtin vs agentmemory; AgentMemoryAdapter implements REST integration with health checks and degraded-mode behavior; CodeIndexAdapter wraps optional code-index info retrieval with safe fallbacks.
Filesystem memory store & snapshotting
src/services/self-improving/MemoryStore.ts
Filesystem-backed MemoryStore with two logical stores (environment, userProfile), deduplication, bounded retention, snapshotting for prompt injection, and persistence helpers.
Feedback collection and pattern analysis
src/services/self-improving/FeedbackCollector.ts, src/services/self-improving/PatternAnalyzer.ts
FeedbackCollector converts task/correction/pattern/code-index/prompt-quality signals into LearningEvent objects; PatternAnalyzer runs four extraction passes to derive or merge LearnedPattern records.
Learning state persistence
src/services/self-improving/LearningStore.ts
Atomic file-based LearningStore: versioned state.json, patterns in active/archive dirs, persisted index, bounds enforcement (patterns/events), and mutation/accessor APIs.
Improvement action generation & execution
src/services/self-improving/ImprovementApplier.ts, src/services/self-improving/ActionExecutor.ts
ImprovementApplier turns active patterns into improvement actions (PROMPT_ENRICHMENT, ERROR_AVOIDANCE, TOOL_PREFERENCE, SKILL_SUGGESTION, and skill mutations for auto-skills). ActionExecutor validates payloads, writes guidance to memory, and invokes skill mutation APIs when available.
Skill usage telemetry & curator
src/services/self-improving/SkillUsageStore.ts, src/services/self-improving/CuratorService.ts
SkillUsageStore persists telemetry, supports pinning and lifecycle transitions; CuratorService schedules runs, creates backups, applies deterministic transitions (stale/archive), and writes per-run reports.
Review prompts & transcript recall
src/services/self-improving/ReviewPromptFactory.ts, src/services/self-improving/TranscriptRecall.ts
Factories for structured memory/skill/combined review prompts; TranscriptRecall persists and searches transcript evidence for curator/review input.
SelfImprovingManager orchestration
src/services/self-improving/SelfImprovingManager.ts
Central runtime that wires stores, collectors, analyzers, appliers, backends, action executors, skill telemetry, transcript recall, and curator; manages lifecycle, timers, reconfiguration, ingestion, and exposes prompt context/status.
Provider integration & webview state
src/core/webview/ClineProvider.ts, src/core/webview/webviewMessageHandler.ts
ClineProvider constructs and exposes SelfImprovingManager, records learning signals on task events and user turns, initializes on activation, disposes on shutdown, and extends webview state with self-improving status and config fields.
Prompt injection & Task/tool integration
src/core/prompts/system.ts, src/core/task/Task.ts, src/core/webview/generateSystemPrompt.ts, src/core/tools/CodebaseSearchTool.ts
SYSTEM_PROMPT accepts SelfImprovingManager and appends learned guidance (getPromptContextString); Task and generateSystemPrompt forward manager; CodebaseSearchTool records code-index hits to the manager.
Settings, UI, i18n, and tests
src/extension.ts, src/shared/experiments.ts, webview-ui/src/components/settings/*, webview-ui/src/i18n/locales/*, src/services/self-improving/__tests__/*, other tests
Activation initializes the manager; webviewMessageHandler notifies manager on settings changes; experiments register SELF_IMPROVING flags (disabled by default); UI adds controls and 18 locale strings; extensive tests added for memory, learning store, curator, action execution, applier, manager lifecycle, and integrations.

🎯 4 (Complex) | ⏱️ ~60 minutes

Suggested reviewers:

  • taltas
  • navedmerchant
  • hannesrudolph
  • edelauna
  • JamesRobert20

🐰 With twitching whiskers, I hopped to code,
I gathered signals down the learning road.
Patterns stitched, prompts grew bright,
Actions hummed softly through the night.
Bunny cheers — wiser prompts take flight.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 14

🧹 Nitpick comments (2)
src/services/self-improving/types.ts (1)

78-78: ⚡ Quick win

Tighten getExperiments to the shared Experiments contract.

Line 78 uses Record<string, boolean>, which allows misspelled keys and bypasses compile-time checks for experiment flags.

Suggested patch
 import type {
 	ActionType,
+	Experiments,
 	FeedbackSignal,
@@
-	getExperiments: () => Record<string, boolean> | undefined
+	getExperiments: () => Experiments | undefined
🤖 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 `@src/services/self-improving/types.ts` at line 78, Tighten the getExperiments
signature to return the shared Experiments contract instead of a loose
Record<string, boolean>; update the type of getExperiments from "Record<string,
boolean> | undefined" to "Experiments | undefined", import or reference the
shared Experiments type and adjust any callers of getExperiments to use the
canonical keys defined by Experiments (e.g., update types in the exported
interface where getExperiments is declared and ensure usage sites validate
against the Experiments shape).
src/services/self-improving/SelfImprovingManager.ts (1)

142-166: 💤 Low value

Consider disposing all initialized stores for completeness.

skillUsageStore, curatorService, and transcriptRecall are initialized unconditionally in the constructor but are not disposed in dispose(). If these stores hold file handles or other resources, they may leak when the manager is disposed.

🤖 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 `@src/services/self-improving/SelfImprovingManager.ts` around lines 142 - 166,
The dispose() method currently persists and disposes memoryStore but misses
disposing other stores initialized in the constructor; update dispose() to also
gracefully persist/close and dispose skillUsageStore, curatorService, and
transcriptRecall (if they exist)—for example, call any available persist/flush
methods before calling their dispose/close methods, using optional chaining or
instanceof checks (e.g., this.skillUsageStore?.dispose?.(),
this.curatorService?.dispose?.(), this.transcriptRecall?.dispose?.()) and ensure
these calls occur inside the try block alongside memoryStore disposal so
resources are released even on errors.
🤖 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 `@packages/types/src/__tests__/learning-memory.test.ts`:
- Line 1: The test file header contains a stale Vitest command and should point
to the test's real path; update the comment on line 1 from "npx vitest run
src/__tests__/learning-memory.test.ts" to reference the correct location
"packages/types/src/__tests__/learning-memory.test.ts" (or use a
workspace-root-friendly command) so local runs target the actual file.

In `@packages/types/src/vscode-extension-host.ts`:
- Around line 364-372: Update the selfImprovingStatus type in
packages/types/src/vscode-extension-host.ts so it matches the object returned by
SelfImprovingManager.getStatus(): add the missing fields memoryEntries,
memoryBackend, skillRecords, and curatorStatus (with the same shapes/types used
by getStatus(), e.g., memoryEntries: number, memoryBackend?: string,
skillRecords: number, and curatorStatus as the status object returned by the
manager). Preferably import or reference the getStatus() return/interface if
available to keep types in sync and avoid duplication; otherwise add fields with
the concrete types used by SelfImprovingManager.getStatus().

In `@src/services/self-improving/__tests__/MemoryBackendFactory.spec.ts`:
- Around line 21-23: The test is not exercising the unknown-backend fallback
because it passes "builtin"; update the spec so MemoryBackendFactory.create is
invoked with an unsupported value (e.g., "unknown" or "unsupported") instead of
"builtin", then assert the returned backend is a MemoryStore; specifically
change the call to MemoryBackendFactory.create("unsupported" as any, baseDir,
logger) and keep the expect(backend).toBeInstanceOf(MemoryStore) assertion to
validate the fallback behavior.

In `@src/services/self-improving/__tests__/SkillUsageStore.spec.ts`:
- Around line 20-33: The test currently uses a fixed sleep after calling
SkillUsageStore.getOrCreate which is flaky; replace the setTimeout delay in
SkillUsageStore.spec.ts with a deterministic polling loop that waits for the
persisted file (self-improving/skill-usage.json) to exist and contain the
expected entry within a bounded timeout (e.g., 1–2s), checking repeatedly (using
fs.stat or readFile) and throwing if the timeout elapses; keep assertions the
same and reference SkillUsageStore.getOrCreate and the persisted file path when
implementing the poll.

In `@src/services/self-improving/AgentMemoryAdapter.ts`:
- Around line 191-199: The forgetByContent function currently will match
everything if substring is empty because
entry.content.toLowerCase().includes(substring.toLowerCase()) is true for "", so
add an early guard in forgetByContent to return 0 (or reject) when substring is
empty or only whitespace; check substring.trim().length === 0 before calling
this.search and bail out. Keep the existing case-insensitive check and the loop
that calls this.forget(entry.id) unchanged; reference the forgetByContent
method, the substring parameter, and the includes(...) check when making the
change.

In `@src/services/self-improving/CuratorService.ts`:
- Around line 118-121: The current shouldRun() condition uses this.firstRunDone,
causing shouldRun() to always return false before run() ever executes and
creating a deadlock; remove the dependency on this.firstRunDone from the gating
logic so shouldRun() no longer permanently denies the first execution —
specifically update the condition in shouldRun() (the method named shouldRun in
CuratorService) to not include "!this.firstRunDone" (keep deferral behavior
based on config and lastRunAt if needed) and leave setting this.firstRunDone
inside run() unchanged.

In `@src/services/self-improving/LearningStore.ts`:
- Around line 186-191: The persist() implementation currently writes STATE_FILE,
pattern files, archive files and the index in parallel (via safeWriteJson,
persistPatternFiles, persistPatternFiles, writePatternIndex), which can leave
files inconsistent on partial failure; change persist() to first persist all
pattern artifacts and the index (call persistPatternFiles for this.patternsDir
and this.archiveDir and writePatternIndex sequentially or in a grouped
Promise.all) and only after those complete successfully write the authoritative
state.json last using safeWriteJson (preferably write to a temp file and
atomically rename to STATE_FILE) so recovery reads one clear source of truth;
update function names referenced: persist(), safeWriteJson, persistPatternFiles,
writePatternIndex, and STATE_FILE.

In `@src/services/self-improving/MemoryStore.ts`:
- Around line 99-104: The recall method in MemoryStore concatenates
this.environment and this.userProfile then slices the tail, which can drop newer
entries if array order differs; modify the recall function (after await
this.ensureInitialized()) to merge both arrays, sort the combined array globally
by each entry's timestamp (newest first) and then take the top maxResults before
mapping to this.cloneEntry so results are globally time-accurate; use the
existing variables this.environment, this.userProfile, the recall function and
cloneEntry method to locate and change the logic.
- Around line 126-138: In forgetByContent, calling .includes on an empty string
will match every entry and wipe both stores; add a guard at the start of the
async forgetByContent(substring: string) method (after await
this.ensureInitialized()) to treat empty or whitespace-only substring safely
(e.g., if (!substring || substring.trim() === "") return 0 or throw a specific
error), so that environment and userProfile arrays are not filtered when
substring is empty; reference the forgetByContent function and the environment
and userProfile properties when making this change.

In `@src/services/self-improving/PatternAnalyzer.ts`:
- Around line 124-126: The merge logic in PatternAnalyzer that finds existing
patterns using pattern.summary.includes(toolKey) is fragile; change it to use a
structured context key instead (e.g., check pattern.context?.toolKey === toolKey
or pattern.context?.tools?.includes(toolKey) depending on how tools are
represented) so merges are exact and deterministic; update both the find at the
existingPatterns.find call (patternType "tool") and the similar check around
lines 188-190 to use the structured context property (pattern.context) rather
than substring matching on summary, and ensure any pattern
creation/serialization code sets that context key accordingly.
- Around line 193-199: When updating an existing tool-preference pattern in
PatternAnalyzer (the patterns.push block that spreads existing), preserve
cumulative frequency instead of overwriting it with the current batch count;
change the frequency assignment to use existing.frequency + total (e.g.,
frequency: (existing.frequency ?? 0) + total) so historical evidence is
retained, keep lastSeenAt, successRate and confidenceScore updates as-is, and
ensure you reference the same existing object/variable when computing the new
cumulative value.

In `@src/services/self-improving/TranscriptRecall.ts`:
- Around line 56-67: The record() method mutates and persists this.entries
without ensuring the instance is initialized; call await this.initialize() at
the start of TranscriptRecall.record() (or otherwise ensure initialize()
completes) before pushing/slicing entries and calling this.persist() so prior
on-disk state and any required directories are loaded/created; update references
to this.entries, TranscriptRecall.MAX_ENTRIES, and this.persist() to assume
initialization has completed.
- Around line 119-121: When loading parsed JSON into this.entries in
TranscriptRecall (the block that currently does if (Array.isArray(parsed)) {
this.entries = parsed.slice(-TranscriptRecall.MAX_ENTRIES) }), validate and
sanitize each item before assigning: ensure parsed is an array of objects, each
entry has the expected fields (at minimum a string summary usable by
entry.summary.toLowerCase()), coerce or skip malformed entries, and then take
the last TranscriptRecall.MAX_ENTRIES of the validated list; this prevents
runtime errors during search-time operations like entry.summary.toLowerCase().

In `@src/services/self-improving/types.ts`:
- Around line 104-138: DEFAULT_CONFIG and EMPTY_STATE duplicate canonical
defaults and should be removed here and imported from the single source of
truth; replace the local declarations by importing the canonical DEFAULT_CONFIG
and EMPTY_STATE (or the canonical factory functions) used by the types layer,
ensure the file references the LearningConfig and LearningState types and any
identifiers (DEFAULT_CONFIG, EMPTY_STATE) are updated to the imported symbols,
and remove or replace any direct mutations so the single canonical definitions
drive manager behavior.

---

Nitpick comments:
In `@src/services/self-improving/SelfImprovingManager.ts`:
- Around line 142-166: The dispose() method currently persists and disposes
memoryStore but misses disposing other stores initialized in the constructor;
update dispose() to also gracefully persist/close and dispose skillUsageStore,
curatorService, and transcriptRecall (if they exist)—for example, call any
available persist/flush methods before calling their dispose/close methods,
using optional chaining or instanceof checks (e.g.,
this.skillUsageStore?.dispose?.(), this.curatorService?.dispose?.(),
this.transcriptRecall?.dispose?.()) and ensure these calls occur inside the try
block alongside memoryStore disposal so resources are released even on errors.

In `@src/services/self-improving/types.ts`:
- Line 78: Tighten the getExperiments signature to return the shared Experiments
contract instead of a loose Record<string, boolean>; update the type of
getExperiments from "Record<string, boolean> | undefined" to "Experiments |
undefined", import or reference the shared Experiments type and adjust any
callers of getExperiments to use the canonical keys defined by Experiments
(e.g., update types in the exported interface where getExperiments is declared
and ensure usage sites validate against the Experiments shape).
🪄 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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 12dec26c-96df-46e2-aae0-7bc4f2cbfa8a

📥 Commits

Reviewing files that changed from the base of the PR and between 45b239c and f15a9a6.

📒 Files selected for processing (61)
  • packages/types/src/__tests__/learning-memory.test.ts
  • packages/types/src/experiment.ts
  • packages/types/src/index.ts
  • packages/types/src/learning.ts
  • packages/types/src/memory.ts
  • packages/types/src/vscode-extension-host.ts
  • src/__tests__/extension.spec.ts
  • src/core/prompts/__tests__/system-prompt.spec.ts
  • src/core/prompts/system.ts
  • src/core/task/Task.ts
  • src/core/webview/ClineProvider.ts
  • src/core/webview/generateSystemPrompt.ts
  • src/core/webview/webviewMessageHandler.ts
  • src/extension.ts
  • src/services/self-improving/ActionExecutor.ts
  • src/services/self-improving/AgentMemoryAdapter.ts
  • src/services/self-improving/CodeIndexAdapter.ts
  • src/services/self-improving/CuratorService.ts
  • src/services/self-improving/FeedbackCollector.ts
  • src/services/self-improving/ImprovementApplier.ts
  • src/services/self-improving/LearningStore.ts
  • src/services/self-improving/MemoryBackend.ts
  • src/services/self-improving/MemoryBackendFactory.ts
  • src/services/self-improving/MemoryStore.ts
  • src/services/self-improving/PatternAnalyzer.ts
  • src/services/self-improving/ReviewPromptFactory.ts
  • src/services/self-improving/SelfImprovingManager.ts
  • src/services/self-improving/SkillUsageStore.ts
  • src/services/self-improving/TranscriptRecall.ts
  • src/services/self-improving/__tests__/ActionExecutor.spec.ts
  • src/services/self-improving/__tests__/AgentMemoryAdapter.spec.ts
  • src/services/self-improving/__tests__/CuratorService.spec.ts
  • src/services/self-improving/__tests__/LearningStore.spec.ts
  • src/services/self-improving/__tests__/MemoryBackendFactory.spec.ts
  • src/services/self-improving/__tests__/MemoryStore.spec.ts
  • src/services/self-improving/__tests__/ReviewPromptFactory.spec.ts
  • src/services/self-improving/__tests__/SelfImprovingManager.spec.ts
  • src/services/self-improving/__tests__/SkillUsageStore.spec.ts
  • src/services/self-improving/__tests__/TranscriptRecall.spec.ts
  • src/services/self-improving/index.ts
  • src/services/self-improving/types.ts
  • src/shared/__tests__/experiments.spec.ts
  • src/shared/experiments.ts
  • webview-ui/src/i18n/locales/ca/settings.json
  • webview-ui/src/i18n/locales/de/settings.json
  • webview-ui/src/i18n/locales/en/settings.json
  • webview-ui/src/i18n/locales/es/settings.json
  • webview-ui/src/i18n/locales/fr/settings.json
  • webview-ui/src/i18n/locales/hi/settings.json
  • webview-ui/src/i18n/locales/id/settings.json
  • webview-ui/src/i18n/locales/it/settings.json
  • webview-ui/src/i18n/locales/ja/settings.json
  • webview-ui/src/i18n/locales/ko/settings.json
  • webview-ui/src/i18n/locales/nl/settings.json
  • webview-ui/src/i18n/locales/pl/settings.json
  • webview-ui/src/i18n/locales/pt-BR/settings.json
  • webview-ui/src/i18n/locales/ru/settings.json
  • webview-ui/src/i18n/locales/tr/settings.json
  • webview-ui/src/i18n/locales/vi/settings.json
  • webview-ui/src/i18n/locales/zh-CN/settings.json
  • webview-ui/src/i18n/locales/zh-TW/settings.json

Comment thread packages/types/src/__tests__/learning-memory.test.ts Outdated
Comment thread packages/types/src/vscode-extension-host.ts
Comment thread src/services/self-improving/__tests__/MemoryBackendFactory.spec.ts
Comment thread src/services/self-improving/__tests__/SkillUsageStore.spec.ts
Comment thread src/services/self-improving/AgentMemoryAdapter.ts
Comment thread src/services/self-improving/PatternAnalyzer.ts
Comment thread src/services/self-improving/PatternAnalyzer.ts
Comment thread src/services/self-improving/TranscriptRecall.ts
Comment thread src/services/self-improving/TranscriptRecall.ts
Comment thread src/services/self-improving/types.ts Outdated
@navedmerchant
Copy link
Copy Markdown
Contributor

Hello. Thanks for your contribution! this looks like a really cool feature!

Can you please break down your PR into smaller chunks so we can review it?

@iskandarsulaili
Copy link
Copy Markdown
Author

Hello. Thanks for your contribution! this looks like a really cool feature!

Can you please break down your PR into smaller chunks so we can review it?

That might be challenging as it thight with each other. Any tips?

- guard empty forgetByContent queries in both memory backends
- sort recall results globally by timestamp across stores
- add focused regression tests for deletion guards and recall ordering
- match tool-based patterns by structured context instead of summaries
- preserve cumulative frequency for existing tool preferences
- add regression tests for tool-combination and preference updates
- exercise unknown memory-backend fallback explicitly
- replace fixed sleep with bounded polling in skill usage persistence test
- correct the package types vitest file header path
- lazily initialize transcript recall before recording new entries
- validate persisted transcript entries before loading them
- add regression tests for lazy init and malformed data handling
- reuse shared learning defaults and empty state from @roo-code/types
- tighten experiment access to the shared Experiments contract
- expand webview selfImprovingStatus typing to match manager output
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🤖 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 `@src/services/self-improving/__tests__/TranscriptRecall.spec.ts`:
- Around line 80-81: Update the test assertion in TranscriptRecall.spec.ts to
reflect that getRecent returns entries in newest-first (recency) order: change
the expectation on recall.getRecent(2).map(entry => entry.id) to assert
["entry-1", "entry-0"] so the test aligns with the intended semantics of the
getRecent function.

In `@src/services/self-improving/AgentMemoryAdapter.ts`:
- Around line 192-201: The deletion loop trims and lowercases the input into
normalized but still calls this.search(substring, 50) with the raw substring,
causing mismatches; change the search call in AgentMemoryAdapter (where
substring and normalized are defined) to use normalized instead (i.e.,
this.search(normalized, 50)) so the backend query and the local includes check
(entry.content.toLowerCase().includes(normalized)) use the same
trimmed/lowercased value.

In `@src/services/self-improving/PatternAnalyzer.ts`:
- Around line 193-198: The update currently replaces successRate when merging a
new batch into a pattern; change this to a weighted average so successRate is
cumulative: in PatternAnalyzer where you push into patterns (merging into
existing), compute combinedSuccess = (existing.successRate * existing.frequency
+ successRate * total) / (existing.frequency + total) and set successRate to
that value (using existing.frequency + total as the new frequency), while
keeping frequency, lastSeenAt, and confidenceScore updates as-is; reference the
variables existing, total, successRate and the patterns push/merge logic to
locate and apply the change.

In `@src/services/self-improving/TranscriptRecall.ts`:
- Around line 57-59: Concurrent record() calls can trigger multiple initialize()
runs and later loadFromDisk() can overwrite this.entries; fix by memoizing
initialization behind a shared promise (e.g., add this.initializingPromise).
Change initialize() to set and return this.initializingPromise (and clear
it/mark this.initialized on resolution), and in record() await
this.initializingPromise if present or assign it when calling initialize() so
all callers await the same promise; reference initialize(), record(),
loadFromDisk(), this.entries, and this.initialized when applying the change.
🪄 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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: a5505315-4816-403d-b2e7-4c5f4cd50663

📥 Commits

Reviewing files that changed from the base of the PR and between f15a9a6 and 327a7d3.

📒 Files selected for processing (14)
  • packages/types/src/__tests__/learning-memory.test.ts
  • packages/types/src/vscode-extension-host.ts
  • src/services/self-improving/AgentMemoryAdapter.ts
  • src/services/self-improving/MemoryStore.ts
  • src/services/self-improving/PatternAnalyzer.ts
  • src/services/self-improving/SelfImprovingManager.ts
  • src/services/self-improving/TranscriptRecall.ts
  • src/services/self-improving/__tests__/AgentMemoryAdapter.spec.ts
  • src/services/self-improving/__tests__/MemoryBackendFactory.spec.ts
  • src/services/self-improving/__tests__/MemoryStore.spec.ts
  • src/services/self-improving/__tests__/PatternAnalyzer.spec.ts
  • src/services/self-improving/__tests__/SkillUsageStore.spec.ts
  • src/services/self-improving/__tests__/TranscriptRecall.spec.ts
  • src/services/self-improving/types.ts

Comment on lines +80 to +81
expect(recall.getRecent(2).map((entry) => entry.id)).toEqual(["entry-0", "entry-1"])
})
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.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Fix getRecent ordering assertion to reflect recency semantics.

Line 80 currently expects oldest-first order after appending a newer entry. For a getRecent API, this should typically assert newest-first (["entry-1", "entry-0"]) to avoid locking in inverted behavior.

Suggested patch
-		expect(recall.getRecent(2).map((entry) => entry.id)).toEqual(["entry-0", "entry-1"])
+		expect(recall.getRecent(2).map((entry) => entry.id)).toEqual(["entry-1", "entry-0"])
📝 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
expect(recall.getRecent(2).map((entry) => entry.id)).toEqual(["entry-0", "entry-1"])
})
expect(recall.getRecent(2).map((entry) => entry.id)).toEqual(["entry-1", "entry-0"])
})
🤖 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 `@src/services/self-improving/__tests__/TranscriptRecall.spec.ts` around lines
80 - 81, Update the test assertion in TranscriptRecall.spec.ts to reflect that
getRecent returns entries in newest-first (recency) order: change the
expectation on recall.getRecent(2).map(entry => entry.id) to assert ["entry-1",
"entry-0"] so the test aligns with the intended semantics of the getRecent
function.

Comment on lines +192 to +201
const normalized = substring.trim().toLowerCase()
if (!normalized) {
return 0
}

const entries = await this.search(substring, 50)
let removed = 0

for (const entry of entries) {
if (entry.content.toLowerCase().includes(normalized)) {
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.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Pass the normalized value into search().

The local match uses normalized, but the backend query still uses the raw substring. Inputs like " foo " can miss search results and leave matching memories undeleted. Use the trimmed value for both steps.

Suggested fix
-		const entries = await this.search(substring, 50)
+		const entries = await this.search(normalized, 50)
🤖 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 `@src/services/self-improving/AgentMemoryAdapter.ts` around lines 192 - 201,
The deletion loop trims and lowercases the input into normalized but still calls
this.search(substring, 50) with the raw substring, causing mismatches; change
the search call in AgentMemoryAdapter (where substring and normalized are
defined) to use normalized instead (i.e., this.search(normalized, 50)) so the
backend query and the local includes check
(entry.content.toLowerCase().includes(normalized)) use the same
trimmed/lowercased value.

Comment thread src/services/self-improving/PatternAnalyzer.ts
Comment thread src/services/self-improving/TranscriptRecall.ts
- search agentmemory with normalized forget queries
- keep prompt-pattern success rates cumulative with weighted updates
- add regression coverage for trimmed queries and cumulative scoring
- memoize first-time initialization behind a shared promise
- prevent concurrent record calls from dropping loaded transcript entries
- add regression coverage for concurrent lazy record paths
- pass trimmed substring to agentmemory search instead of lowercasing it
- keep case-insensitive local verification for actual deletion
- tighten regression coverage to assert trim-without-lowercase semantics
@iskandarsulaili iskandarsulaili marked this pull request as draft May 23, 2026 01:51
- record correction feedback when users reply to outstanding task asks
- keep generic user-turn tracking intact for normal follow-up messages
- add a regression test around interactive correction replies
- capture real codebase_search hit counts and top score for learning events
- let self-improving accept explicit code index hit details from callers
- add regression tests for the tool seam and manager event recording
- assert activation calls initializeSelfImproving through the provider
- keep the coverage at the extension startup seam rather than only mocked provider construction
@iskandarsulaili iskandarsulaili changed the title feat: add experiment-gated self-improving learning, bounded memory, and background curation feat: add experiment-gated self-improving learning, bounded memory, background curation, and auto-create/update skill May 23, 2026
@iskandarsulaili iskandarsulaili marked this pull request as ready for review May 23, 2026 07:24
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 9

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/services/skills/SkillsManager.ts (1)

408-456: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Validate SKILL.md structure before persisting create/update content.

Line 427 only enforces non-empty text. If frontmatter is malformed or name mismatches, the write succeeds but discoverSkills() can drop the skill, leaving a “created/updated” result that is not actually loadable.

Proposed guard pattern
 async createSkillFromContent(
   name: string,
   source: "global" | "project",
   description: string,
   content: string,
   modeSlugs?: string[],
 ): Promise<string> {
+  const parsed = matter(content)
+  const contentName = parsed.data?.name
+  const contentDescription = parsed.data?.description
+
+  if (typeof contentName !== "string" || contentName !== name) {
+    throw new Error(t("skills:errors.name_format"))
+  }
+  if (typeof contentDescription !== "string") {
+    throw new Error(t("skills:errors.description_length", { length: 0 }))
+  }
+
   if (!content.trim()) {
     throw new Error(t("skills:errors.description_length", { length: 0 }))
   }
   ...
 }

 async updateSkillContent(
   name: string,
   source: "global" | "project",
   content: string,
   mode?: string,
 ): Promise<void> {
+  const parsed = matter(content)
+  if (parsed.data?.name !== name) {
+    throw new Error(t("skills:errors.name_format"))
+  }
+  if (typeof parsed.data?.description !== "string" || !parsed.data.description.trim()) {
+    throw new Error(t("skills:errors.description_length", { length: 0 }))
+  }
   ...
 }

Also applies to: 461-475

🤖 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 `@src/services/skills/SkillsManager.ts` around lines 408 - 456, The
createSkillFromContent implementation writes SKILL.md after only checking
non-empty content, which allows malformed frontmatter or mismatched names to be
persisted and later dropped by discoverSkills(); before writing (in
createSkillFromContent) parse and validate the SKILL.md structure (frontmatter
fields, required title/name slug match, and any required metadata) using the
same parser discoverSkills uses, and reject with a descriptive error if invalid;
perform the same pre-write validation in the corresponding update function (the
code around lines 461-475 likely updateSkillFromContent) and only write the file
(or atomically write a temp file then rename) after validation succeeds so
discoverSkills will reliably load the new/updated skill.
🧹 Nitpick comments (3)
src/core/tools/CodebaseSearchTool.ts (1)

259-277: ⚡ Quick win

Remove unnecessary as any in ContextProxy.spec.ts

ContextProxy.setValue is already typed as setValue<K extends keyof RooCodeSettings>(key: K, value: RooCodeSettings[K]), and @roo-code/types globalSettingsSchema includes memoryBackend, agentMemoryUrl, selfImprovingScope, and selfImprovingAutoSkillsScope, so the as any casts in the “should persist self-improving …” tests can be removed.

🤖 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 `@src/core/tools/CodebaseSearchTool.ts` around lines 259 - 277, The tests in
ContextProxy.spec.ts use unnecessary `as any` casts when calling
ContextProxy.setValue; since setValue is typed as setValue<K extends keyof
RooCodeSettings>(key: K, value: RooCodeSettings[K]) and the RooCodeSettings keys
(memoryBackend, agentMemoryUrl, selfImprovingScope,
selfImprovingAutoSkillsScope) are defined in the `@roo-code/types`
globalSettingsSchema, remove the `as any` casts from the “should persist
self-improving …” tests and pass the correctly typed values directly to
ContextProxy.setValue so TypeScript correctly infers the types without forced
casts.
src/services/self-improving/AgentMemoryAdapter.ts (1)

191-207: 💤 Low value

Minor inconsistency: search uses substring.trim() but filter uses normalized (trimmed + lowercased).

Line 197 searches with substring.trim() while line 201 filters with normalized (trimmed and lowercased). If the backend search is case-sensitive, this could return fewer results than expected, causing some matches to be missed.

Consider using the same normalized value for consistency:

-		const entries = await this.search(substring.trim(), 50)
+		const entries = await this.search(normalized, 50)
🤖 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 `@src/services/self-improving/AgentMemoryAdapter.ts` around lines 191 - 207, In
AgentMemoryAdapter.forgetByContent, the call to this.search uses
substring.trim() while the subsequent filter uses normalized (trimmed +
lowercased), causing potential mismatches; update the search call to use the
same normalized value (pass normalized into this.search) so that search and the
content filter both use the identical trimmed/lowercased string, keeping the
rest of the logic (including this.forget(entry.id) and removed counting)
unchanged.
src/core/tools/__tests__/CodebaseSearchTool.spec.ts (1)

39-50: ⚡ Quick win

Consider extracting task mock setup into a test helper.

The task mock object is complex with nested structure (providerRef.deref(), getSelfImprovingManager). If additional tests are added (as recommended), this setup will be duplicated. Consider extracting it into a helper function to improve maintainability and reduce duplication.

♻️ Example helper pattern
function createMockTask(overrides?: {
  taskId?: string
  getSelfImprovingManager?: () => any
  cwd?: string
}) {
  return {
    cwd: overrides?.cwd ?? "/workspace",
    taskId: overrides?.taskId ?? "task-1",
    consecutiveMistakeCount: 0,
    providerRef: {
      deref: vi.fn().mockReturnValue({
        context: {},
        getSelfImprovingManager: overrides?.getSelfImprovingManager ?? vi.fn().mockReturnValue(null),
      }),
    },
    say: vi.fn().mockResolvedValue(undefined),
  } as any
}
🤖 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 `@src/core/tools/__tests__/CodebaseSearchTool.spec.ts` around lines 39 - 50,
Extract the complex task mock in
src/core/tools/__tests__/CodebaseSearchTool.spec.ts into a reusable helper
(e.g., createMockTask) to avoid duplication: move the nested providerRef.deref()
mock and getSelfImprovingManager wiring plus cwd, taskId,
consecutiveMistakeCount, and say into the helper, accept optional overrides for
taskId, cwd, and getSelfImprovingManager, and update tests to call
createMockTask() instead of inlining the task object; ensure references to
providerRef.deref, getSelfImprovingManager, and say remain identical so existing
test assertions continue to work.
🤖 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 `@src/core/config/__tests__/ContextProxy.spec.ts`:
- Around line 259-267: The test uses unnecessary `as any` casts when calling
proxy.setValue for "memoryBackend" and "agentMemoryUrl"; remove those casts and
pass properly typed string literals so TypeScript infers the correct types from
RooCodeSettings/globalSettingsSchema, e.g. call proxy.setValue("memoryBackend",
"agentmemory") and proxy.setValue("agentMemoryUrl",
"http://agentmemory.internal:4001") and keep the subsequent assertions via
proxy.getGlobalSettings() unchanged to validate persistence.
- Around line 269-277: The test should assert that ContextProxy.setValue routes
the enum-typed keys to the global state updater instead of merely roundtripping
via getGlobalSettings; modify the test to call
proxy.setValue("selfImprovingScope", "workspace") and
proxy.setValue("selfImprovingAutoSkillsScope", "global") (without sprawling `as
any` casts), and verify that the mock/stub for updateGlobalState was called with
the expected key/value pairs (e.g., updateGlobalState("selfImprovingScope",
"workspace") and updateGlobalState("selfImprovingAutoSkillsScope", "global")),
while keeping the existing checks of getGlobalSettings minimal or removing
them—this ensures setValue uses the global routing logic (not
SECRET_STATE_KEYS/GLOBAL_SECRET_KEYS) and preserves type-safety against the
z.enum-backed globalSettingsSchema.

In `@src/core/task/Task.ts`:
- Around line 1472-1483: The code currently awaits
provider.getSelfImprovingManager?.().recordUserCorrection(...) (inside the
shouldRecordCorrection branch) which blocks submitUserMessage flow and delays
handleWebviewAskResponse; change this to fire-and-forget: call
provider.getSelfImprovingManager?.().recordUserCorrection({...}) without
awaiting (e.g., prefix with void or chain .catch) so it runs asynchronously,
still capturing/logging errors (use .catch((e)=>console.error(...))). Keep the
same payload (taskId: this.taskId, success: false, corrected: true) and leave
the shouldRecordCorrection check in place.

In `@src/core/tools/__tests__/CodebaseSearchTool.spec.ts`:
- Around line 18-68: The test only covers the happy path for
CodebaseSearchTool.execute; add unit tests to cover error and edge cases:
simulate CodeIndexManager.getInstance().searchIndex throwing an error and assert
callbacks.handleError is called; mock getSelfImprovingManager() returning
null/undefined and assert no recordCodeIndexEvent calls and tool still returns
gracefully; make recordCodeIndexEvent reject and assert handleError/logging
behavior; add tests for empty searchIndex results, isFeatureEnabled=false,
isFeatureConfigured=false, results missing score, and multiple search results to
verify hits/topScore aggregation and that vscode.workspace.asRelativePath is
invoked; use the existing symbols CodebaseSearchTool,
CodeIndexManager.getInstance, searchIndex, getSelfImprovingManager,
recordCodeIndexEvent and execute to locate where to add/modify specs.

In `@src/core/webview/ClineProvider.ts`:
- Around line 1944-1946: getGlobalStateSafe currently casts this.contextProxy to
any and uses optional chaining, losing type safety; change its signature to
accept key: keyof GlobalState (or a generic K extends keyof GlobalState) and
call this.contextProxy.getGlobalState(key) directly (remove the any cast and ?.
usage) so the returned type is tied to GlobalState (e.g., GlobalState[K] or
generic T constrained to GlobalState[keyof GlobalState]); update the function
name getGlobalStateSafe and any callers to satisfy the stronger typed key and
return type.

In `@src/services/self-improving/ImprovementApplier.ts`:
- Line 40: The default hasSkill fallback in ImprovementApplier currently ignores
the second parameter; update the default assignment for this.hasSkill (which now
uses options.hasSkill) to accept both (name, source) and return false by default
to avoid accidental matches (i.e., use (name: string, source?: string) =>
false), ensuring callers that pass (skillName, source) receive the safer
behavior; keep the real implementation supplied via options.hasSkill when
auto-skills are enabled and leave getSkillNames untouched.

In `@src/services/skills/SkillsManager.ts`:
- Around line 467-471: The current lookup uses getSkill(name, source, mode)
which fails for skills whose keys are stored under the primary mode slug while
the update passes a secondary mode (e.g., modeSlugs: ["code","architect"]);
change the lookup to fall back to finding by name/source and verifying the
provided mode against the skill's modeSlugs: call this.getSkill(...) first, and
if it returns undefined and mode is provided, call
this.findSkillByNameAndSource(name, source) and ensure the returned skill exists
and (if mode) skill.modeSlugs includes the requested mode before proceeding
(otherwise throw the same not_found error); update the logic around getSkill,
findSkillByNameAndSource, and modeSlugs accordingly.

In `@webview-ui/src/components/settings/__tests__/SettingsView.spec.tsx`:
- Around line 515-587: The tests for SettingsView (in the two "shows ..." it
blocks) currently only assert vscode.postMessage after clicking the save button,
which misses regressions where inputs leak immediately to live state; update
both tests (the one exercising auto-skills controls and the one exercising
memory backend/agentmemory) to assert vscode.postMessage was not called before
any user edits (after getSettingsContent/hydration) and again after performing
user interactions but before clicking the save button (i.e. before invoking
saveButton click) to ensure the cached-state pattern in SettingsView
(renderSettingsView, activateTab, getSettingsContent,
experimental-self-improving-checkbox, self-improving-* inputs) is preserved.

In `@webview-ui/src/components/settings/ExperimentalSettings.tsx`:
- Around line 160-162: Hardcoded user-facing labels "Workspace", "Global", and
"Built-in" in the SelectItem components of ExperimentalSettings.tsx should be
replaced with calls to the project's localization helper (the same translation
function used elsewhere in this file, e.g. the `t`/`formatMessage` from the
existing i18n hook) so the UI strings are localizable; update the SelectItem
children for values "workspace", "global", and "builtin" (and the other
occurrences around the file) to use translation keys like
`settings.scope.workspace`, `settings.scope.global`, `settings.scope.builtin`,
and add those keys to the translation resource files.

---

Outside diff comments:
In `@src/services/skills/SkillsManager.ts`:
- Around line 408-456: The createSkillFromContent implementation writes SKILL.md
after only checking non-empty content, which allows malformed frontmatter or
mismatched names to be persisted and later dropped by discoverSkills(); before
writing (in createSkillFromContent) parse and validate the SKILL.md structure
(frontmatter fields, required title/name slug match, and any required metadata)
using the same parser discoverSkills uses, and reject with a descriptive error
if invalid; perform the same pre-write validation in the corresponding update
function (the code around lines 461-475 likely updateSkillFromContent) and only
write the file (or atomically write a temp file then rename) after validation
succeeds so discoverSkills will reliably load the new/updated skill.

---

Nitpick comments:
In `@src/core/tools/__tests__/CodebaseSearchTool.spec.ts`:
- Around line 39-50: Extract the complex task mock in
src/core/tools/__tests__/CodebaseSearchTool.spec.ts into a reusable helper
(e.g., createMockTask) to avoid duplication: move the nested providerRef.deref()
mock and getSelfImprovingManager wiring plus cwd, taskId,
consecutiveMistakeCount, and say into the helper, accept optional overrides for
taskId, cwd, and getSelfImprovingManager, and update tests to call
createMockTask() instead of inlining the task object; ensure references to
providerRef.deref, getSelfImprovingManager, and say remain identical so existing
test assertions continue to work.

In `@src/core/tools/CodebaseSearchTool.ts`:
- Around line 259-277: The tests in ContextProxy.spec.ts use unnecessary `as
any` casts when calling ContextProxy.setValue; since setValue is typed as
setValue<K extends keyof RooCodeSettings>(key: K, value: RooCodeSettings[K]) and
the RooCodeSettings keys (memoryBackend, agentMemoryUrl, selfImprovingScope,
selfImprovingAutoSkillsScope) are defined in the `@roo-code/types`
globalSettingsSchema, remove the `as any` casts from the “should persist
self-improving …” tests and pass the correctly typed values directly to
ContextProxy.setValue so TypeScript correctly infers the types without forced
casts.

In `@src/services/self-improving/AgentMemoryAdapter.ts`:
- Around line 191-207: In AgentMemoryAdapter.forgetByContent, the call to
this.search uses substring.trim() while the subsequent filter uses normalized
(trimmed + lowercased), causing potential mismatches; update the search call to
use the same normalized value (pass normalized into this.search) so that search
and the content filter both use the identical trimmed/lowercased string, keeping
the rest of the logic (including this.forget(entry.id) and removed counting)
unchanged.
🪄 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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: ff3a3c42-ec1c-4daf-8459-f45b0c317ce7

📥 Commits

Reviewing files that changed from the base of the PR and between 327a7d3 and 99ce4f2.

📒 Files selected for processing (36)
  • packages/types/src/experiment.ts
  • packages/types/src/global-settings.ts
  • packages/types/src/learning.ts
  • packages/types/src/vscode-extension-host.ts
  • src/__tests__/extension.spec.ts
  • src/core/config/__tests__/ContextProxy.spec.ts
  • src/core/task/Task.ts
  • src/core/task/__tests__/Task.spec.ts
  • src/core/tools/CodebaseSearchTool.ts
  • src/core/tools/__tests__/CodebaseSearchTool.spec.ts
  • src/core/webview/ClineProvider.ts
  • src/core/webview/__tests__/ClineProvider.apiHandlerRebuild.spec.ts
  • src/core/webview/__tests__/webviewMessageHandler.spec.ts
  • src/core/webview/webviewMessageHandler.ts
  • src/services/self-improving/ActionExecutor.ts
  • src/services/self-improving/AgentMemoryAdapter.ts
  • src/services/self-improving/ImprovementApplier.ts
  • src/services/self-improving/PatternAnalyzer.ts
  • src/services/self-improving/SelfImprovingManager.ts
  • src/services/self-improving/TranscriptRecall.ts
  • src/services/self-improving/__tests__/ActionExecutor.spec.ts
  • src/services/self-improving/__tests__/AgentMemoryAdapter.spec.ts
  • src/services/self-improving/__tests__/ImprovementApplier.spec.ts
  • src/services/self-improving/__tests__/PatternAnalyzer.spec.ts
  • src/services/self-improving/__tests__/SelfImprovingManager.spec.ts
  • src/services/self-improving/__tests__/TranscriptRecall.spec.ts
  • src/services/self-improving/types.ts
  • src/services/skills/SkillsManager.ts
  • src/services/skills/__tests__/SkillsManager.spec.ts
  • src/shared/__tests__/experiments.spec.ts
  • src/shared/experiments.ts
  • webview-ui/src/components/settings/ExperimentalFeature.tsx
  • webview-ui/src/components/settings/ExperimentalSettings.tsx
  • webview-ui/src/components/settings/SettingsView.tsx
  • webview-ui/src/components/settings/__tests__/SettingsView.spec.tsx
  • webview-ui/src/i18n/locales/en/settings.json
✅ Files skipped from review due to trivial changes (1)
  • src/shared/tests/experiments.spec.ts

Comment on lines +259 to +267
it("should persist self-improving memory backend settings in global settings", async () => {
await proxy.setValue("memoryBackend" as any, "agentmemory" as any)
await proxy.setValue("agentMemoryUrl" as any, "http://agentmemory.internal:4001" as any)

const globalSettings = proxy.getGlobalSettings() as any

expect(globalSettings.memoryBackend).toBe("agentmemory")
expect(globalSettings.agentMemoryUrl).toBe("http://agentmemory.internal:4001")
})
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.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check if memoryBackend and agentMemoryUrl are defined in type files

rg -n --type=ts -C3 'memoryBackend|agentMemoryUrl' 'packages/types/src/'

Repository: Zoo-Code-Org/Zoo-Code

Length of output: 2602


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# 1) Show surrounding tests (around lines 244-267 and 259-267)
spec="src/core/config/__tests__/ContextProxy.spec.ts"
if [ -f "$spec" ]; then
  echo "=== $spec (excerpt) ==="
  nl -ba "$spec" | sed -n '220,290p'
else
  echo "Missing $spec" >&2
fi

# 2) Locate ContextProxy implementation
echo "=== Locate ContextProxy implementation ==="
fd -a "ContextProxy" src/core/config 2>/dev/null || true
# fallback: search for "class ContextProxy" / "setValue("
rg -n --hidden --no-ignore -S "class ContextProxy|setValue\\(" src/core/config | head -n 50

# 3) If we can find an implementation file, print the setValue method body vicinity
impl_files=$(rg -l --hidden --no-ignore -S "class ContextProxy" src/core/config || true)
if [ -z "$impl_files" ]; then
  impl_files=$(rg -l --hidden --no-ignore -S "setValue\\(" src/core/config || true)
fi

for f in $impl_files; do
  echo "=== Candidate impl: $f (setValue-related excerpts) ==="
  # Print lines around setValue + routing-related calls if present
  nl -ba "$f" | sed -n '1,200p' >/dev/null 2>&1 || true
  rg -n "setValue\\(|updateGlobalState|storeSecret|getGlobalSettings" "$f" | head -n 80
done

Repository: Zoo-Code-Org/Zoo-Code

Length of output: 191


🏁 Script executed:

#!/bin/bash
set -euo pipefail

spec="src/core/config/__tests__/ContextProxy.spec.ts"
echo "=== $spec (excerpt) ==="
if [ -f "$spec" ]; then
  # show lines 220-290 with line numbers using cat -n
  cat -n "$spec" | sed -n '220,290p'
else
  echo "Missing $spec" >&2
fi

echo
echo "=== Locate ContextProxy implementation ==="
# List candidate files under src/core/config
rg -n --hidden --no-ignore -S "class ContextProxy" src/core/config | head -n 50 || true
rg -n --hidden --no-ignore -S "setValue\\(" src/core/config | head -n 50 || true

# Pick the most likely implementation file(s) and print setValue-related lines and nearby routing calls
impl_files=$(rg -l --hidden --no-ignore -S "class ContextProxy" src/core/config || true)
if [ -z "$impl_files" ]; then
  impl_files=$(rg -l --hidden --no-ignore -S "setValue\\(" src/core/config || true)
fi

for f in $impl_files; do
  echo
  echo "=== Candidate impl: $f (routing-related snippets) ==="
  # Show some setValue and global-state/secret calls around them
  rg -n "setValue\\(|updateGlobalState|storeSecret|getGlobalSettings" "$f" | head -n 120 || true
done

Repository: Zoo-Code-Org/Zoo-Code

Length of output: 4786


🏁 Script executed:

#!/bin/bash
set -euo pipefail

file="src/core/config/ContextProxy.ts"
echo "=== $file (key sections) ==="

# Show imports/types for GlobalStateKey/SecretStateKey/GlobalSettings if present near top
echo
echo "--- Top (first 120 lines) ---"
sed -n '1,160p' "$file"

# Show GlobalSettings getter + setValue routing area
echo
echo "--- Around setValue implementation (lines 480-570) ---"
sed -n '480,570p' "$file"

# Show updateGlobalState and storeSecret implementations
echo
echo "--- updateGlobalState / storeSecret (lines 320-460) ---"
sed -n '320,460p' "$file"

# Show getGlobalSettings implementation and any mapping
echo
echo "--- getGlobalSettings (lines 410-520 approx) ---"
sed -n '410,520p' "$file"

Repository: Zoo-Code-Org/Zoo-Code

Length of output: 17172


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Locate RooCodeSettings type definition ==="
rg -n --type=ts "type RooCodeSettings|interface RooCodeSettings|export type RooCodeSettings|export interface RooCodeSettings" packages/types/src

echo
echo "=== Inspect memoryBackend/agentMemoryUrl within RooCodeSettings ==="
rg -n --type=ts -C3 "RooCodeSettings.*memoryBackend|memoryBackend.*RooCodeSettings|agentMemoryUrl.*RooCodeSettings|RooCodeSettings.*agentMemoryUrl" packages/types/src

echo
echo "=== Locate GLOBAL_STATE_KEYS / GLOBAL_SETTINGS_KEYS inclusion ==="
rg -n --type=ts "GLOBAL_STATE_KEYS|GLOBAL_SETTINGS_KEYS" packages/types/src

Repository: Zoo-Code-Org/Zoo-Code

Length of output: 287


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== All occurrences of memoryBackend / agentMemoryUrl in packages/types/src ==="
rg -n --type=ts "memoryBackend|agentMemoryUrl" packages/types/src | head -n 200

echo
echo "=== Show global-settings.ts around RooCodeSettings + relevant schemas ==="
file="packages/types/src/global-settings.ts"
if [ -f "$file" ]; then
  # show around where RooCodeSettings is declared
  rg -n "export type RooCodeSettings" "$file"
  line=$(rg -n "export type RooCodeSettings" "$file" | head -n1 | cut -d: -f1)
  start=$((line-80))
  end=$((line+80))
  echo "--- $file ($start-$end) ---"
  sed -n "${start},${end}p" "$file"
else
  echo "Missing $file" >&2
fi

echo
echo "=== Locate key lists used for routing in ContextProxy ==="
rg -n --type=ts "GLOBAL_STATE_KEYS|GLOBAL_SETTINGS_KEYS|GLOBAL_SECRET_KEYS|SECRET_STATE_KEYS|isSecretStateKey|GLOBAL_SECRET_KEYS" packages/types/src | head -n 200

Repository: Zoo-Code-Org/Zoo-Code

Length of output: 7566


Remove unnecessary as any casts for memoryBackend / agentMemoryUrl

memoryBackend and agentMemoryUrl are already present in @roo-code/types (globalSettingsSchemaRooCodeSettings), and ContextProxy.setValue will route them through updateGlobalState (they’re not secret keys). Since the test already asserts via getGlobalSettings(), the as any casts can be removed to improve type safety and consistency.

🤖 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 `@src/core/config/__tests__/ContextProxy.spec.ts` around lines 259 - 267, The
test uses unnecessary `as any` casts when calling proxy.setValue for
"memoryBackend" and "agentMemoryUrl"; remove those casts and pass properly typed
string literals so TypeScript infers the correct types from
RooCodeSettings/globalSettingsSchema, e.g. call proxy.setValue("memoryBackend",
"agentmemory") and proxy.setValue("agentMemoryUrl",
"http://agentmemory.internal:4001") and keep the subsequent assertions via
proxy.getGlobalSettings() unchanged to validate persistence.

Comment on lines +269 to +277
it("should persist self-improving scope settings in global settings", async () => {
await proxy.setValue("selfImprovingScope" as any, "workspace" as any)
await proxy.setValue("selfImprovingAutoSkillsScope" as any, "global" as any)

const globalSettings = proxy.getGlobalSettings() as any

expect(globalSettings.selfImprovingScope).toBe("workspace")
expect(globalSettings.selfImprovingAutoSkillsScope).toBe("global")
})
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.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check if scope settings are defined in type files and GLOBAL_STATE_KEYS

rg -n --type=ts -C3 'selfImprovingScope|selfImprovingAutoSkillsScope' 'packages/types/src/'

# Check if they're in GLOBAL_STATE_KEYS or SECRET_STATE_KEYS
rg -n --type=ts 'selfImprovingScope|selfImprovingAutoSkillsScope' --glob '**/types/**/index.ts' --glob '**/types/**/*.ts'

Repository: Zoo-Code-Org/Zoo-Code

Length of output: 3448


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Find how these keys are stored/routed (GLOBAL_STATE_KEYS/SECRET_STATE_KEYS) and where getGlobalSettings/setValue are implemented.
rg -n --hidden --no-ignore -S 'GLOBAL_STATE_KEYS|SECRET_STATE_KEYS' .
rg -n -S 'getGlobalSettings\(|setValue\(' src/core/config .

# Find all usages of the specific keys outside type definitions.
rg -n -S 'selfImprovingScope|selfImprovingAutoSkillsScope' src/core packages . \
  --glob '!**/node_modules/**' --glob '!**/dist/**' --glob '!**/build/**'

Repository: Zoo-Code-Org/Zoo-Code

Length of output: 19653


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Show where these keys are declared in packages/types/src/global-settings.ts (arrays and schemas)
rg -n "selfImprovingScope|selfImprovingAutoSkillsScope" packages/types/src/global-settings.ts -n -C2

# Show how GLOBAL_STATE_KEYS is constructed (so we can see whether these keys land there)
rg -n "export const GLOBAL_STATE_KEYS" packages/types/src/global-settings.ts -n -C6

# Show ContextProxy.setValue routing logic for global vs secret keys
rg -n "async setValue\\(|setValue\\(" src/core/config/ContextProxy.ts -n -C3
rg -n "getGlobalSettings\\(" src/core/config/ContextProxy.ts -n -C10

# Pull the relevant setValue implementation block
perl -0777 -ne 'print $1 if /public async setValue\([^)]*\)\s*:\s*Promise<[^>]*>\s*{[^}]*\}/s' src/core/config/ContextProxy.ts | head -n 120

# Also show getGlobalState/getSecret helpers if present
rg -n "getGlobalStateSafe|getGlobalState\\(|getSecret\\(" src/core/config/ContextProxy.ts -n -C3

Repository: Zoo-Code-Org/Zoo-Code

Length of output: 2988


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate setValue method in ContextProxy.ts
rg -n "public (async )?setValue\\b" src/core/config/ContextProxy.ts

# Print surrounding lines for the setValue implementation
# (use sed with line numbers from rg output)
sed -n '520,620p' src/core/config/ContextProxy.ts

# Print getValues/getValue and isSecretStateKey helpers usage near bottom
sed -n '620,760p' src/core/config/ContextProxy.ts

# Print getValues() body specifically (search for "public getValues()" first)
rg -n "public getValues\\(" src/core/config/ContextProxy.ts
# Then print around it
sed -n '540,620p' src/core/config/ContextProxy.ts

# Print getGlobalState implementation for routing correctness
rg -n "getGlobalState\\(" src/core/config/ContextProxy.ts
sed -n '300,520p' src/core/config/ContextProxy.ts

Repository: Zoo-Code-Org/Zoo-Code

Length of output: 13372


🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n "selfImprovingScope|selfImprovingAutoSkillsScope" packages/types/src/global-settings.ts -n

# Show definitions of SECRET_STATE_KEYS/GLOBAL_SECRET_KEYS and isSecretStateKey
rg -n "export const SECRET_STATE_KEYS|export const GLOBAL_SECRET_KEYS|function isSecretStateKey|isSecretStateKey\\s*=" packages/types/src/global-settings.ts -n -C3

Repository: Zoo-Code-Org/Zoo-Code

Length of output: 956


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Show GLOBAL_SETTINGS_KEYS array contents around the selfImproving* keys (if present)
rg -n "export const GLOBAL_SETTINGS_KEYS" packages/types/src/global-settings.ts -n
# Print a bounded window around the array to locate selfImprovingScope entries
python3 - <<'PY'
import re, pathlib
p = pathlib.Path("packages/types/src/global-settings.ts")
text = p.read_text(encoding="utf-8")
m = re.search(r"export const GLOBAL_SETTINGS_KEYS\s*=\s*\[(.*?)\]\s*as const", text, flags=re.S)
if not m:
    print("GLOBAL_SETTINGS_KEYS array not found")
    raise SystemExit(0)
arr = m.group(1)
for key in ["selfImprovingScope", "selfImprovingAutoSkillsScope"]:
    idx = arr.find(key)
    print(f"{key} present: {idx!=-1}")
# Print small slices around occurrences
for key in ["selfImprovingScope", "selfImprovingAutoSkillsScope"]:
    idx = arr.find(key)
    if idx!=-1:
        start = max(0, idx-80)
        end = min(len(arr), idx+120)
        print("\n--- context for", key, "---\n")
        print(arr[start:end])
PY

Repository: Zoo-Code-Org/Zoo-Code

Length of output: 179


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Show the exact test setup and assertions around the snippet
sed -n '230,310p' src/core/config/__tests__/ContextProxy.spec.ts | nl -ba

Repository: Zoo-Code-Org/Zoo-Code

Length of output: 108


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Show the exact test setup and assertions around the snippet with line numbers
sed -n '230,310p' src/core/config/__tests__/ContextProxy.spec.ts | cat -n

Repository: Zoo-Code-Org/Zoo-Code

Length of output: 3665


Improve type-safety and assert routing for self-improving scope keys (ContextProxy.spec.ts lines 269-277)

  • selfImprovingScope / selfImprovingAutoSkillsScope are already modeled in types (z.enum(["workspace","global"]), and included in globalSettingsSchema), and ContextProxy.setValue routes them to global state (they are not part of SECRET_STATE_KEYS/GLOBAL_SECRET_KEYS).
  • The test currently only roundtrips via getGlobalSettings() and keeps as any; strengthen it by asserting updateGlobalState is called with the right arguments (like the existing secret vs global routing tests) and drop/limit the as any casts.
🤖 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 `@src/core/config/__tests__/ContextProxy.spec.ts` around lines 269 - 277, The
test should assert that ContextProxy.setValue routes the enum-typed keys to the
global state updater instead of merely roundtripping via getGlobalSettings;
modify the test to call proxy.setValue("selfImprovingScope", "workspace") and
proxy.setValue("selfImprovingAutoSkillsScope", "global") (without sprawling `as
any` casts), and verify that the mock/stub for updateGlobalState was called with
the expected key/value pairs (e.g., updateGlobalState("selfImprovingScope",
"workspace") and updateGlobalState("selfImprovingAutoSkillsScope", "global")),
while keeping the existing checks of getGlobalSettings minimal or removing
them—this ensures setValue uses the global routing logic (not
SECRET_STATE_KEYS/GLOBAL_SECRET_KEYS) and preserves type-safety against the
z.enum-backed globalSettingsSchema.

Comment thread src/core/task/Task.ts
Comment on lines +1472 to +1483
if (shouldRecordCorrection) {
await provider
.getSelfImprovingManager?.()
?.recordUserCorrection({
taskId: this.taskId,
success: false,
corrected: true,
})
.catch((error: unknown) => {
console.error("[Task#submitUserMessage] Failed to record user correction:", error)
})
}
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.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Avoid blocking user message submission on correction telemetry.

Line 1472 waits on recordUserCorrection(...) before handleWebviewAskResponse(...). If memory/self-improving backends are slow, user responses can lag or appear stuck.

Suggested change
 				if (shouldRecordCorrection) {
-					await provider
-						.getSelfImprovingManager?.()
-						?.recordUserCorrection({
-							taskId: this.taskId,
-							success: false,
-							corrected: true,
-						})
-						.catch((error: unknown) => {
-							console.error("[Task#submitUserMessage] Failed to record user correction:", error)
-						})
+					try {
+						const selfImprovingManager = provider.getSelfImprovingManager?.()
+						if (selfImprovingManager) {
+							void selfImprovingManager
+								.recordUserCorrection({
+									taskId: this.taskId,
+									success: false,
+									corrected: true,
+								})
+								.catch((error: unknown) => {
+									console.error("[Task#submitUserMessage] Failed to record user correction:", error)
+								})
+						}
+					} catch (error) {
+						console.error("[Task#submitUserMessage] Failed to start user correction recording:", error)
+					}
 				}
🤖 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 `@src/core/task/Task.ts` around lines 1472 - 1483, The code currently awaits
provider.getSelfImprovingManager?.().recordUserCorrection(...) (inside the
shouldRecordCorrection branch) which blocks submitUserMessage flow and delays
handleWebviewAskResponse; change this to fire-and-forget: call
provider.getSelfImprovingManager?.().recordUserCorrection({...}) without
awaiting (e.g., prefix with void or chain .catch) so it runs asynchronously,
still capturing/logging errors (use .catch((e)=>console.error(...))). Keep the
same payload (taskId: this.taskId, success: false, corrected: true) and leave
the shouldRecordCorrection check in place.

Comment on lines +18 to +68
describe("CodebaseSearchTool", () => {
it("records self-improving code index hit details from search results", async () => {
const recordCodeIndexEvent = vi.fn().mockResolvedValue(undefined)
const getSelfImprovingManager = vi.fn().mockReturnValue({ recordCodeIndexEvent })
const searchIndex = vi.fn().mockResolvedValue([
{
payload: {
filePath: "/workspace/src/example.ts",
startLine: 10,
endLine: 20,
codeChunk: "const answer = 42",
},
score: 0.87,
},
])
vi.mocked(CodeIndexManager.getInstance).mockReturnValue({
isFeatureEnabled: true,
isFeatureConfigured: true,
searchIndex,
} as any)

const task = {
cwd: "/workspace",
taskId: "task-1",
consecutiveMistakeCount: 0,
providerRef: {
deref: vi.fn().mockReturnValue({
context: {},
getSelfImprovingManager,
}),
},
say: vi.fn().mockResolvedValue(undefined),
} as any
const callbacks = {
askApproval: vi.fn().mockResolvedValue(true),
handleError: vi.fn().mockResolvedValue(undefined),
pushToolResult: vi.fn(),
}

const tool = new CodebaseSearchTool()
await tool.execute({ query: "find the answer" }, task, callbacks)

expect(recordCodeIndexEvent).toHaveBeenCalledWith("task-1", {
available: true,
hits: 1,
topScore: 0.87,
})
expect(callbacks.handleError).not.toHaveBeenCalled()
expect(vscode.workspace.asRelativePath).toHaveBeenCalledWith("/workspace/src/example.ts", false)
})
})
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.

🛠️ Refactor suggestion | 🟠 Major | 🏗️ Heavy lift

Expand test coverage beyond the happy path.

This test file only covers the happy path where the code index feature is enabled, configured, and returns results. Critical gaps include:

Missing error scenarios:

  • CodeIndexManager throws an error during search
  • getSelfImprovingManager() returns null/undefined
  • recordCodeIndexEvent rejects

Missing edge cases:

  • Empty search results (searchIndex returns [])
  • Feature disabled (isFeatureEnabled: false)
  • Feature not configured (isFeatureConfigured: false)
  • Search results without score field
  • Multiple search results (verify all are processed)

Missing core functionality tests:

  • Search behavior without self-improving manager present
  • Query formatting and passing to searchIndex
  • Result formatting for the tool output

As per coding guidelines, package-local unit tests should cover error handling and edge cases comprehensively.

🤖 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 `@src/core/tools/__tests__/CodebaseSearchTool.spec.ts` around lines 18 - 68,
The test only covers the happy path for CodebaseSearchTool.execute; add unit
tests to cover error and edge cases: simulate
CodeIndexManager.getInstance().searchIndex throwing an error and assert
callbacks.handleError is called; mock getSelfImprovingManager() returning
null/undefined and assert no recordCodeIndexEvent calls and tool still returns
gracefully; make recordCodeIndexEvent reject and assert handleError/logging
behavior; add tests for empty searchIndex results, isFeatureEnabled=false,
isFeatureConfigured=false, results missing score, and multiple search results to
verify hits/topScore aggregation and that vscode.workspace.asRelativePath is
invoked; use the existing symbols CodebaseSearchTool,
CodeIndexManager.getInstance, searchIndex, getSelfImprovingManager,
recordCodeIndexEvent and execute to locate where to add/modify specs.

Comment on lines +1944 to +1946
private getGlobalStateSafe<T = unknown>(key: string): T | undefined {
return (this.contextProxy as any).getGlobalState?.(key)
}
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.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify ContextProxy API surface
ast-grep --pattern 'class ContextProxy {
  $$$
}'

Repository: Zoo-Code-Org/Zoo-Code

Length of output: 41210


🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE="src/core/webview/ClineProvider.ts"
# Show the helper implementation area
sed -n '1910,1985p' "$FILE"

# Find where contextProxy is declared/typed in ClineProvider
rg -n "contextProxy" "$FILE"

# Show the definition/declaration line(s) for contextProxy (near first match)
# (Use line numbers from rg output to grab a small window around it)
first_line=$(rg -n "contextProxy" "$FILE" | head -n1 | cut -d: -f1)
start=$((first_line-20))
end=$((first_line+40))
sed -n "${start},${end}p" "$FILE"

# Find all usages of getGlobalStateSafe
rg -n "getGlobalStateSafe" "$FILE"

Repository: Zoo-Code-Org/Zoo-Code

Length of output: 8597


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate GlobalStateKey / key lists in ContextProxy.ts
FILE="src/core/config/ContextProxy.ts"
rg -n "type GlobalStateKey|enum GlobalStateKey|interface GlobalStateKey|GLOBAL_STATE_KEYS" "$FILE"
rg -n "\"experiments\"|experiments" "$FILE"

# Show the relevant region where GlobalStateKey and GLOBAL_STATE_KEYS are defined
# (grab around first match of GLOBAL_STATE_KEYS)
first=$(rg -n "GLOBAL_STATE_KEYS" "$FILE" | head -n1 | cut -d: -f1)
start=$((first-40))
end=$((first+120))
sed -n "${start},${end}p" "$FILE"

# Find signature/implementation of getGlobalState for context
rg -n "getGlobalState<" "$FILE"

Repository: Zoo-Code-Org/Zoo-Code

Length of output: 370


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Confirm whether "experiments" is part of GlobalState / keys
rg -n "\"experiments\"" src/core/config || true
rg -n "experiments" src/core/config || true

# Find where GlobalState is defined (type GlobalStateKey = keyof GlobalState suggests GlobalState comes from a type definition import)
# We'll locate the import(s) and the GlobalState type.
FILE="src/core/config/ContextProxy.ts"
rg -n "import .*GlobalState|type GlobalState|interface GlobalState|export type GlobalState" "$FILE" || true

# Show the top of ContextProxy.ts where GlobalState is imported/defined
sed -n '1,120p' "$FILE"

# Find definition of GlobalState in the codebase
rg -n "type GlobalState\\s*=|interface GlobalState|export type GlobalState" src/core/config src | head -n 50

Repository: Zoo-Code-Org/Zoo-Code

Length of output: 3693


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# 1) Find where "experiments" is defined/used across the repo
rg -n "\"experiments\"|experiments" .

# 2) Inspect SelfImprovingManager.onSettingsChanged signature and expected type
rg -n "class SelfImprovingManager|onSettingsChanged\\(" src/core || true

# 3) Show the getGlobalStateSafe call sites + their string keys
FILE="src/core/webview/ClineProvider.ts"
rg -n "getGlobalStateSafe\\(" "$FILE"

Repository: Zoo-Code-Org/Zoo-Code

Length of output: 22333


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# 1) Find GlobalState type in `@roo-code/types`
rg -n "export type GlobalState|type GlobalState" packages/types src packages/types | head -n 50

# 2) Check whether "experiments" exists as a key on GlobalState
rg -n "\"experiments\"|experiments:" packages/types src/packages/types | head -n 50

# 3) Show ContextProxy imports/types usage in ClineProvider.ts
FILE="src/core/webview/ClineProvider.ts"
rg -n "from \"\\.\\./core/config/ContextProxy\"|from \".*/ContextProxy\"|import .*ContextProxy" "$FILE" || true
sed -n '1,120p' "$FILE"

# 4) Show any other helper wrappers around contextProxy getters in ClineProvider
rg -n "getGlobalStateSafe|getValue\\(|getGlobalState\\(" "$FILE" -n

Repository: Zoo-Code-Org/Zoo-Code

Length of output: 817


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect GlobalState definition
FILE="packages/types/src/global-settings.ts"
rg -n "export type GlobalState|experiments:" "$FILE"
sed -n '300,360p' "$FILE"
sed -n '160,230p' "$FILE"

# Inspect ContextProxy.getGlobalState signature (already seen, but grab exact lines)
CTX="src/core/config/ContextProxy.ts"
rg -n "getGlobalState<|getGlobalState\\(" "$CTX" | head -n 20
sed -n '330,380p' "$CTX"

# Inspect ClineProvider helper and its surrounding context (imports/types nearby already partly known)
CLINE="src/core/webview/ClineProvider.ts"
sed -n '1,60p' "$CLINE"
sed -n '1925,1970p' "$CLINE"

Repository: Zoo-Code-Org/Zoo-Code

Length of output: 8738


🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE="packages/types/src/global-settings.ts"
for k in experiments memoryBackend agentMemoryUrl selfImprovingScope selfImprovingAutoSkillsScope; do
  echo "---- $k"
  rg -n "\"$k\"|\\b$k\\b" "$FILE" || true
done

Repository: Zoo-Code-Org/Zoo-Code

Length of output: 471


getGlobalStateSafe uses unnecessary any/optional chaining (type safety loss).

ContextProxy already provides getGlobalState, so the (this.contextProxy as any) cast and ?. bypass the typed GlobalState key/value APIs. Type the key as keyof GlobalState and call this.contextProxy.getGlobalState(key) directly.

Suggested change
-	private getGlobalStateSafe<T = unknown>(key: string): T | undefined {
-		return (this.contextProxy as any).getGlobalState?.(key)
-	}
+	private getGlobalStateSafe<K extends keyof GlobalState>(key: K): GlobalState[K] | undefined {
+		return this.contextProxy.getGlobalState(key)
+	}
🤖 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 `@src/core/webview/ClineProvider.ts` around lines 1944 - 1946,
getGlobalStateSafe currently casts this.contextProxy to any and uses optional
chaining, losing type safety; change its signature to accept key: keyof
GlobalState (or a generic K extends keyof GlobalState) and call
this.contextProxy.getGlobalState(key) directly (remove the any cast and ?.
usage) so the returned type is tied to GlobalState (e.g., GlobalState[K] or
generic T constrained to GlobalState[keyof GlobalState]); update the function
name getGlobalStateSafe and any callers to satisfy the stronger typed key and
return type.

this.getSkillProvenance = options.getSkillProvenance ?? (() => "unknown")
this.getSkillProvenanceForSource =
options.getSkillProvenanceForSource ?? ((name: string) => this.getSkillProvenance(name))
this.hasSkill = options.hasSkill ?? ((name: string) => this.getSkillNames().includes(name))
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.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Default hasSkill implementation ignores the source parameter.

The default fallback (name: string) => this.getSkillNames().includes(name) ignores the source parameter, but callers at line 186 pass both skillName and source. This could cause incorrect skill existence checks when distinguishing global vs project skills.

-		this.hasSkill = options.hasSkill ?? ((name: string) => this.getSkillNames().includes(name))
+		this.hasSkill = options.hasSkill ?? ((_name: string, _source: "global" | "project") => false)

A safer default is to return false (skill doesn't exist), which prevents accidental updates to non-existent skills. The actual implementation should be provided when auto-skills are enabled.

📝 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
this.hasSkill = options.hasSkill ?? ((name: string) => this.getSkillNames().includes(name))
this.hasSkill = options.hasSkill ?? ((_name: string, _source: "global" | "project") => 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 `@src/services/self-improving/ImprovementApplier.ts` at line 40, The default
hasSkill fallback in ImprovementApplier currently ignores the second parameter;
update the default assignment for this.hasSkill (which now uses
options.hasSkill) to accept both (name, source) and return false by default to
avoid accidental matches (i.e., use (name: string, source?: string) => false),
ensuring callers that pass (skillName, source) receive the safer behavior; keep
the real implementation supplied via options.hasSkill when auto-skills are
enabled and leave getSkillNames untouched.

Comment on lines +467 to 471
const skill = mode ? this.getSkill(name, source, mode) : this.findSkillByNameAndSource(name, source)
if (!skill) {
const modeInfo = mode ? ` (mode: ${mode})` : ""
throw new Error(t("skills:errors.not_found", { name, source, modeInfo }))
}
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.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Mode-scoped updates can fail for valid multi-mode skills.

Line 467 uses getSkill(name, source, mode), but keys are indexed by only the primary mode slug. A skill with modeSlugs: ["code", "architect"] won’t be found when updating with mode = "architect".

Suggested lookup fix
-const skill = mode ? this.getSkill(name, source, mode) : this.findSkillByNameAndSource(name, source)
+const skill = mode
+  ? Array.from(this.skills.values()).find(
+      (s) =>
+        s.name === name &&
+        s.source === source &&
+        (!s.modeSlugs || s.modeSlugs.length === 0 || s.modeSlugs.includes(mode)),
+    )
+  : this.findSkillByNameAndSource(name, source)
📝 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
const skill = mode ? this.getSkill(name, source, mode) : this.findSkillByNameAndSource(name, source)
if (!skill) {
const modeInfo = mode ? ` (mode: ${mode})` : ""
throw new Error(t("skills:errors.not_found", { name, source, modeInfo }))
}
const skill = mode
? Array.from(this.skills.values()).find(
(s) =>
s.name === name &&
s.source === source &&
(!s.modeSlugs || s.modeSlugs.length === 0 || s.modeSlugs.includes(mode)),
)
: this.findSkillByNameAndSource(name, source)
if (!skill) {
const modeInfo = mode ? ` (mode: ${mode})` : ""
throw new Error(t("skills:errors.not_found", { name, source, modeInfo }))
}
🤖 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 `@src/services/skills/SkillsManager.ts` around lines 467 - 471, The current
lookup uses getSkill(name, source, mode) which fails for skills whose keys are
stored under the primary mode slug while the update passes a secondary mode
(e.g., modeSlugs: ["code","architect"]); change the lookup to fall back to
finding by name/source and verifying the provided mode against the skill's
modeSlugs: call this.getSkill(...) first, and if it returns undefined and mode
is provided, call this.findSkillByNameAndSource(name, source) and ensure the
returned skill exists and (if mode) skill.modeSlugs includes the requested mode
before proceeding (otherwise throw the same not_found error); update the logic
around getSkill, findSkillByNameAndSource, and modeSlugs accordingly.

Comment on lines +515 to +587
it("shows the auto-skills sub-option under self-improving and saves it", () => {
const { activateTab, getSettingsContent } = renderSettingsView()

activateTab("experimental")

const content = getSettingsContent()
const selfImprovingCheckbox = within(content).getByTestId("experimental-self-improving-checkbox")
fireEvent.click(selfImprovingCheckbox)
expect(selfImprovingCheckbox).toBeChecked()

const autoSkillsCheckbox = within(content).getByTestId("experimental-self-improving-auto-skills-checkbox")
fireEvent.click(autoSkillsCheckbox)
expect(autoSkillsCheckbox).toBeChecked()

const selfImprovingScopeSelect = within(content).getByTestId("self-improving-scope-select")
fireEvent.change(selfImprovingScopeSelect, { target: { value: "workspace" } })

const autoSkillsScopeSelect = within(content).getByTestId("self-improving-auto-skills-scope-select")
fireEvent.change(autoSkillsScopeSelect, { target: { value: "global" } })

const saveButton = screen.getByTestId("save-button")
fireEvent.click(saveButton)

expect(vscode.postMessage).toHaveBeenCalledWith(
expect.objectContaining({
type: "updateSettings",
updatedSettings: expect.objectContaining({
selfImprovingScope: "workspace",
selfImprovingAutoSkillsScope: "global",
experiments: expect.objectContaining({
selfImproving: true,
selfImprovingAutoSkills: true,
}),
}),
}),
)
})

it("shows memory backend controls for self-improving and saves agentmemory settings", () => {
const { activateTab, getSettingsContent } = renderSettingsView()

activateTab("experimental")

const content = getSettingsContent()
const selfImprovingCheckbox = within(content).getByTestId("experimental-self-improving-checkbox")
fireEvent.click(selfImprovingCheckbox)
expect(selfImprovingCheckbox).toBeChecked()

const selfImprovingScopeSelect = within(content).getByTestId("self-improving-scope-select")
fireEvent.change(selfImprovingScopeSelect, { target: { value: "global" } })

const backendSelect = within(content).getByTestId("self-improving-memory-backend-select")
fireEvent.change(backendSelect, { target: { value: "agentmemory" } })

const urlInput = within(content).getByTestId("self-improving-agent-memory-url-input")
fireEvent.change(urlInput, { target: { value: "http://agentmemory.internal:4001" } })
expect(urlInput).toHaveValue("http://agentmemory.internal:4001")

const saveButton = screen.getByTestId("save-button")
fireEvent.click(saveButton)

expect(vscode.postMessage).toHaveBeenCalledWith(
expect.objectContaining({
type: "updateSettings",
updatedSettings: expect.objectContaining({
selfImprovingScope: "global",
memoryBackend: "agentmemory",
agentMemoryUrl: "http://agentmemory.internal:4001",
}),
}),
)
})
})
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.

🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Assert no postMessage before Save for the new experimental controls.

These assertions only verify the final payload after Save; they would still pass if changes leaked to live state immediately (the exact race the cached-state pattern is meant to prevent). Add an explicit pre-save expect(vscode.postMessage).not.toHaveBeenCalled() check after user edits (and ideally after hydration before edits).

Suggested test hardening
 it("shows the auto-skills sub-option under self-improving and saves it", () => {
   const { activateTab, getSettingsContent } = renderSettingsView()
   activateTab("experimental")
   const content = getSettingsContent()

   const selfImprovingCheckbox = within(content).getByTestId("experimental-self-improving-checkbox")
   fireEvent.click(selfImprovingCheckbox)

   const autoSkillsCheckbox = within(content).getByTestId("experimental-self-improving-auto-skills-checkbox")
   fireEvent.click(autoSkillsCheckbox)

   const selfImprovingScopeSelect = within(content).getByTestId("self-improving-scope-select")
   fireEvent.change(selfImprovingScopeSelect, { target: { value: "workspace" } })

   const autoSkillsScopeSelect = within(content).getByTestId("self-improving-auto-skills-scope-select")
   fireEvent.change(autoSkillsScopeSelect, { target: { value: "global" } })

+  expect(vscode.postMessage).not.toHaveBeenCalled()
+
   const saveButton = screen.getByTestId("save-button")
   fireEvent.click(saveButton)

   expect(vscode.postMessage).toHaveBeenCalledWith(

As per coding guidelines, webview-ui/src/**/SettingsView.{ts,tsx}: “For SettingsView, preserve the cached-state pattern: inputs should operate on local cachedState until the user saves, and tests should distinguish automatic initialization from real user edits.”

📝 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
it("shows the auto-skills sub-option under self-improving and saves it", () => {
const { activateTab, getSettingsContent } = renderSettingsView()
activateTab("experimental")
const content = getSettingsContent()
const selfImprovingCheckbox = within(content).getByTestId("experimental-self-improving-checkbox")
fireEvent.click(selfImprovingCheckbox)
expect(selfImprovingCheckbox).toBeChecked()
const autoSkillsCheckbox = within(content).getByTestId("experimental-self-improving-auto-skills-checkbox")
fireEvent.click(autoSkillsCheckbox)
expect(autoSkillsCheckbox).toBeChecked()
const selfImprovingScopeSelect = within(content).getByTestId("self-improving-scope-select")
fireEvent.change(selfImprovingScopeSelect, { target: { value: "workspace" } })
const autoSkillsScopeSelect = within(content).getByTestId("self-improving-auto-skills-scope-select")
fireEvent.change(autoSkillsScopeSelect, { target: { value: "global" } })
const saveButton = screen.getByTestId("save-button")
fireEvent.click(saveButton)
expect(vscode.postMessage).toHaveBeenCalledWith(
expect.objectContaining({
type: "updateSettings",
updatedSettings: expect.objectContaining({
selfImprovingScope: "workspace",
selfImprovingAutoSkillsScope: "global",
experiments: expect.objectContaining({
selfImproving: true,
selfImprovingAutoSkills: true,
}),
}),
}),
)
})
it("shows memory backend controls for self-improving and saves agentmemory settings", () => {
const { activateTab, getSettingsContent } = renderSettingsView()
activateTab("experimental")
const content = getSettingsContent()
const selfImprovingCheckbox = within(content).getByTestId("experimental-self-improving-checkbox")
fireEvent.click(selfImprovingCheckbox)
expect(selfImprovingCheckbox).toBeChecked()
const selfImprovingScopeSelect = within(content).getByTestId("self-improving-scope-select")
fireEvent.change(selfImprovingScopeSelect, { target: { value: "global" } })
const backendSelect = within(content).getByTestId("self-improving-memory-backend-select")
fireEvent.change(backendSelect, { target: { value: "agentmemory" } })
const urlInput = within(content).getByTestId("self-improving-agent-memory-url-input")
fireEvent.change(urlInput, { target: { value: "http://agentmemory.internal:4001" } })
expect(urlInput).toHaveValue("http://agentmemory.internal:4001")
const saveButton = screen.getByTestId("save-button")
fireEvent.click(saveButton)
expect(vscode.postMessage).toHaveBeenCalledWith(
expect.objectContaining({
type: "updateSettings",
updatedSettings: expect.objectContaining({
selfImprovingScope: "global",
memoryBackend: "agentmemory",
agentMemoryUrl: "http://agentmemory.internal:4001",
}),
}),
)
})
})
it("shows the auto-skills sub-option under self-improving and saves it", () => {
const { activateTab, getSettingsContent } = renderSettingsView()
activateTab("experimental")
const content = getSettingsContent()
const selfImprovingCheckbox = within(content).getByTestId("experimental-self-improving-checkbox")
fireEvent.click(selfImprovingCheckbox)
const autoSkillsCheckbox = within(content).getByTestId("experimental-self-improving-auto-skills-checkbox")
fireEvent.click(autoSkillsCheckbox)
const selfImprovingScopeSelect = within(content).getByTestId("self-improving-scope-select")
fireEvent.change(selfImprovingScopeSelect, { target: { value: "workspace" } })
const autoSkillsScopeSelect = within(content).getByTestId("self-improving-auto-skills-scope-select")
fireEvent.change(autoSkillsScopeSelect, { target: { value: "global" } })
expect(vscode.postMessage).not.toHaveBeenCalled()
const saveButton = screen.getByTestId("save-button")
fireEvent.click(saveButton)
expect(vscode.postMessage).toHaveBeenCalledWith(
expect.objectContaining({
type: "updateSettings",
updatedSettings: expect.objectContaining({
selfImprovingScope: "workspace",
selfImprovingAutoSkillsScope: "global",
experiments: expect.objectContaining({
selfImproving: true,
selfImprovingAutoSkills: true,
}),
}),
}),
)
})
it("shows memory backend controls for self-improving and saves agentmemory settings", () => {
const { activateTab, getSettingsContent } = renderSettingsView()
activateTab("experimental")
const content = getSettingsContent()
const selfImprovingCheckbox = within(content).getByTestId("experimental-self-improving-checkbox")
fireEvent.click(selfImprovingCheckbox)
const selfImprovingScopeSelect = within(content).getByTestId("self-improving-scope-select")
fireEvent.change(selfImprovingScopeSelect, { target: { value: "global" } })
const backendSelect = within(content).getByTestId("self-improving-memory-backend-select")
fireEvent.change(backendSelect, { target: { value: "agentmemory" } })
const urlInput = within(content).getByTestId("self-improving-agent-memory-url-input")
fireEvent.change(urlInput, { target: { value: "http://agentmemory.internal:4001" } })
expect(vscode.postMessage).not.toHaveBeenCalled()
const saveButton = screen.getByTestId("save-button")
fireEvent.click(saveButton)
expect(vscode.postMessage).toHaveBeenCalledWith(
expect.objectContaining({
type: "updateSettings",
updatedSettings: expect.objectContaining({
selfImprovingScope: "global",
memoryBackend: "agentmemory",
agentMemoryUrl: "http://agentmemory.internal:4001",
}),
}),
)
})
🤖 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 `@webview-ui/src/components/settings/__tests__/SettingsView.spec.tsx` around
lines 515 - 587, The tests for SettingsView (in the two "shows ..." it blocks)
currently only assert vscode.postMessage after clicking the save button, which
misses regressions where inputs leak immediately to live state; update both
tests (the one exercising auto-skills controls and the one exercising memory
backend/agentmemory) to assert vscode.postMessage was not called before any user
edits (after getSettingsContent/hydration) and again after performing user
interactions but before clicking the save button (i.e. before invoking
saveButton click) to ensure the cached-state pattern in SettingsView
(renderSettingsView, activateTab, getSettingsContent,
experimental-self-improving-checkbox, self-improving-* inputs) is preserved.

Comment on lines +160 to +162
<SelectItem value="workspace">Workspace</SelectItem>
<SelectItem value="global">Global</SelectItem>
</SelectContent>
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.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Localize newly added select option labels instead of hardcoded English.

Workspace, Global, and Built-in are user-facing strings in a translated settings UI; hardcoding them creates l10n drift.

Also applies to: 201-203, 229-230

🤖 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 `@webview-ui/src/components/settings/ExperimentalSettings.tsx` around lines 160
- 162, Hardcoded user-facing labels "Workspace", "Global", and "Built-in" in the
SelectItem components of ExperimentalSettings.tsx should be replaced with calls
to the project's localization helper (the same translation function used
elsewhere in this file, e.g. the `t`/`formatMessage` from the existing i18n
hook) so the UI strings are localizable; update the SelectItem children for
values "workspace", "global", and "builtin" (and the other occurrences around
the file) to use translation keys like `settings.scope.workspace`,
`settings.scope.global`, `settings.scope.builtin`, and add those keys to the
translation resource files.

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.

2 participants