codeep 2.1.4 → 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -13,7 +13,7 @@
13
13
  </p>
14
14
 
15
15
  <p align="center">
16
- AI-powered coding assistant built for the terminal. Multiple LLM providers, project-aware context, and a seamless development workflow.
16
+ Autonomous AI coding agent that reads your project, runs commands, and writes &amp; verifies code — deeper than autocomplete. Any model (Claude, Gemini, DeepSeek, GLM, OpenAI, or your own local/custom), in the terminal and your editor.
17
17
  </p>
18
18
 
19
19
  <p align="center">
@@ -242,6 +242,50 @@ Agent learns your coding preferences:
242
242
  - Preferred libraries
243
243
  - Custom rules you define
244
244
 
245
+ ### User Profile (`/me`)
246
+ A durable, human-readable description of **you** that Codeep injects into the agent's context on every run — so it adapts to how you work (reply language, response style, default stack, "always / never" rules). It flows to every surface (terminal, VS Code, Zed) since they share the same files.
247
+
248
+ Two layers:
249
+ - **`~/.codeep/profile.md`** — global: who you are across all projects
250
+ - **`.codeep/profile.md`** — project: your role, goals, and constraints for this repo
251
+
252
+ Commands:
253
+ - `/me` — view your profile + status
254
+ - `/me init [project]` — scaffold a template to fill in
255
+ - `/me on` / `/me off` — toggle injection
256
+ - `/me learn [on|off]` — opt-in auto-learn: Codeep extracts durable preferences from sessions and merges them into `profile.learned.md` (kept separate from your hand-written file). Off by default.
257
+ - `/me learn project` — one-off learn scoped to this repo
258
+ - `/me forget` — clear the auto-learned profile(s)
259
+
260
+ Sync your global profile across machines (and edit it on the web) from the [dashboard](https://codeep.dev/dashboard) with `codeep account sync`. In VS Code: **Codeep: Edit Profile** and **Codeep: Toggle Profile Auto-Learn**.
261
+
262
+ ### Sub-agents (delegation)
263
+ The agent can delegate a self-contained sub-task to a specialist **sub-agent** that runs in its own fresh context window and returns only a summary — keeping the main context small and letting each sub-task run with a tuned persona and scoped tools.
264
+
265
+ Built-ins:
266
+ - **`planner`** — read-only; investigates, then returns a step-by-step implementation plan
267
+ - **`researcher`** — read-only; explores the codebase/web and returns a tight, cited summary
268
+ - **`reviewer`** — read-only; senior review for correctness, security, and design
269
+ - **`tester`** — writes and runs tests, iterates to green
270
+
271
+ Run `/agents` to list them. The agent invokes them itself via the `delegate` tool (you'll see `⤷ <agent>: …` lines). Add your own with a frontmatter `.md` in `.codeep/agents/<name>.md` (project) or `~/.codeep/agents/<name>.md` (global):
272
+
273
+ ```markdown
274
+ ---
275
+ name: migrator
276
+ description: Writes and runs database migrations
277
+ tools: [read_file, write_file, edit_file, execute_command] # allowlist; omit = all
278
+ model: glm-5.1 # optional override
279
+ personality: senior-reviewer # optional preset
280
+ maxIterations: 12 # optional budget
281
+ ---
282
+ You write safe, reversible DB migrations…
283
+ ```
284
+
285
+ `tools` is an allowlist enforced at dispatch (a `researcher` literally can't write files). Sub-agents inherit your profile + project rules, and their changes are covered by `/undo`.
286
+
287
+ **Guaranteed review:** enable **Agent Auto-Review** in `/settings` (`agentAutoReview`) and after any run that changes files, Codeep automatically delegates to the `reviewer` and appends its findings — a review stage that always happens. Off by default.
288
+
245
289
  ### Project Rules
246
290
  Define project-specific instructions that the AI always follows. Create a rules file in your project root:
247
291
 
@@ -644,6 +644,61 @@ Anything else the agent should know — edge cases, gotchas, things to double-ch
644
644
  response: `Active personality: **${p.displayName}** (\`${p.name}\`, ${p.scope})\n\n_${p.description}_\n\nClear with \`/personality off\`.`,
645
645
  };
646
646
  }
647
+ case 'agents': {
648
+ const { formatAgentList } = await import('../utils/agents.js');
649
+ return { handled: true, response: formatAgentList(session.workspaceRoot) };
650
+ }
651
+ case 'me': {
652
+ const { formatProfileView, scaffoldProfile, updateLearnedProfile, clearLearnedProfile, } = await import('../utils/userProfile.js');
653
+ const sub = args[0]?.toLowerCase();
654
+ if (sub === 'on' || sub === 'off') {
655
+ config.set('userProfile', sub === 'on');
656
+ return { handled: true, response: sub === 'on'
657
+ ? "Profile injection on — your profile is added to the agent's context."
658
+ : 'Profile injection off — profile is saved but not used.', configOptionsChanged: true };
659
+ }
660
+ if (sub === 'init') {
661
+ const scope = args[1]?.toLowerCase() === 'project' ? 'project' : 'global';
662
+ if (scope === 'project' && !session.workspaceRoot) {
663
+ return { handled: true, response: 'No project here — use `/me init` for a global profile.' };
664
+ }
665
+ const res = scaffoldProfile(scope, session.workspaceRoot);
666
+ if (!res)
667
+ return { handled: true, response: 'Could not create the profile file.' };
668
+ return { handled: true, response: res.created
669
+ ? `Created ${scope} profile: \`${res.path}\` — edit it and Codeep uses it automatically.`
670
+ : `${scope === 'global' ? 'Global' : 'Project'} profile already exists: \`${res.path}\`.` };
671
+ }
672
+ if (sub === 'learn') {
673
+ const arg = args[1]?.toLowerCase();
674
+ if (arg === 'on' || arg === 'off') {
675
+ config.set('autoLearnProfile', arg === 'on');
676
+ return { handled: true, response: arg === 'on'
677
+ ? 'Auto-learn on — Codeep updates your learned profile (global + project) from sessions.'
678
+ : 'Auto-learn off — Codeep stops updating the learned profile.', configOptionsChanged: true };
679
+ }
680
+ const scope = arg === 'project' ? 'project' : 'global';
681
+ if (scope === 'project' && !session.workspaceRoot) {
682
+ return { handled: true, response: 'No project here — run `/me learn` for your global profile.' };
683
+ }
684
+ if (session.history.filter((m) => m.role !== 'system').length < 2) {
685
+ return { handled: true, response: 'Not enough conversation yet to learn from.' };
686
+ }
687
+ const res = await updateLearnedProfile(session.history, scope, session.workspaceRoot);
688
+ if (!res)
689
+ return { handled: true, response: 'Nothing durable to learn right now (or the model call failed).' };
690
+ const file = scope === 'global' ? '~/.codeep/profile.learned.md' : '.codeep/profile.learned.md';
691
+ return { handled: true, response: res.updated
692
+ ? `Updated your ${scope} learned profile (\`${file}\`):\n\n${res.facts}`
693
+ : `No changes — your ${scope} learned profile already covers this:\n\n${res.facts}` };
694
+ }
695
+ if (sub === 'forget') {
696
+ return { handled: true, response: clearLearnedProfile(session.workspaceRoot)
697
+ ? 'Cleared the auto-learned profile(s).'
698
+ : 'No learned profile to clear.' };
699
+ }
700
+ return { handled: true, response: formatProfileView(session.workspaceRoot) };
701
+ }
647
702
  case 'insights': {
648
703
  const { formatInsights } = await import('../utils/insights.js');
649
704
  let days = 7;
@@ -1375,6 +1430,8 @@ function buildHelp() {
1375
1430
  '| Command | Description |',
1376
1431
  '|---------|-------------|',
1377
1432
  '| `/memory <note>` | Add a project note (or `list` / `remove <n>` / `clear`) |',
1433
+ '| `/me` | Your user profile — reply language, style, stack (`init [project]`, `learn [on\\|off\\|project]`, `forget`, `on`/`off`) |',
1434
+ '| `/agents` | List sub-agents the agent can delegate self-contained tasks to |',
1378
1435
  '| `/profile save <name>` | Save current provider/model/settings (or `load` / `delete` / `list`) |',
1379
1436
  '| `/hooks` | List installed lifecycle hooks (`.codeep/hooks/<event>.sh`) |',
1380
1437
  '',
@@ -58,6 +58,8 @@ const AVAILABLE_COMMANDS = [
58
58
  { name: 'go', description: 'Execute the pending plan from /plan' },
59
59
  // Personalities + insights (2.0.3)
60
60
  { name: 'personality', description: 'List or switch agent tone preset', input: { hint: '[name | off]' } },
61
+ { name: 'me', description: 'Your user profile — adapts the agent to you (reply language, style, stack)', input: { hint: '[init [project] | on | off | learn [on|off|project] | forget]' } },
62
+ { name: 'agents', description: 'List sub-agents the agent can delegate self-contained tasks to' },
61
63
  { name: 'insights', description: 'Activity summary over the last N days (default 7)', input: { hint: '[--days N]' } },
62
64
  // Project intelligence
63
65
  { name: 'scan', description: 'Scan project structure and generate summary' },
@@ -769,7 +771,9 @@ export function startAcpServer() {
769
771
  }
770
772
  else if (configId === 'agentConfirmDeleteFile' ||
771
773
  configId === 'agentConfirmExecuteCommand' ||
772
- configId === 'agentConfirmWriteFile') {
774
+ configId === 'agentConfirmWriteFile' ||
775
+ configId === 'userProfile' ||
776
+ configId === 'autoLearnProfile') {
773
777
  // Accept boolean or "true"/"false" string from Zed/VSCode
774
778
  const bool = value === true || value === 'true';
775
779
  config.set(configId, bool);
@@ -32,6 +32,16 @@ interface ConfigSchema {
32
32
  * discarding them — so long sessions keep early decisions/constraints.
33
33
  * Default true; set false to fall back to plain truncation (no extra call). */
34
34
  autoSummarizeHistory: boolean;
35
+ /** Inject the user profile (`~/.codeep/profile.md` + project
36
+ * `.codeep/profile.md`) into the agent's system prompt so it adapts to the
37
+ * user (reply language, style, stack, preferences). Default true; set false
38
+ * to keep the profile files but stop injecting them. Managed via `/me`. */
39
+ userProfile: boolean;
40
+ /** Auto-learn: at session save, run one LLM pass to extract durable facts /
41
+ * preferences about the user and merge them into `~/.codeep/profile.learned.md`
42
+ * (injected alongside the hand-written profile). OFF by default — opt in via
43
+ * `/me learn on`. Throttled + single-flight so it doesn't spam API calls. */
44
+ autoLearnProfile: boolean;
35
45
  /** Absolute workspace roots whose project-local `.codeep/hooks/*` the user
36
46
  * has approved to run. Untrusted projects' hooks are skipped (a cloned repo
37
47
  * can't execute shell on first tool call). Granted via `/hooks trust`. */
@@ -52,6 +62,10 @@ interface ConfigSchema {
52
62
  agentAutoCommit: boolean;
53
63
  agentAutoCommitBranch: boolean;
54
64
  agentAutoVerify: 'off' | 'build' | 'typecheck' | 'test' | 'all';
65
+ /** After a top-level agent run that changed files, delegate to the `reviewer`
66
+ * sub-agent and append its findings — a guaranteed review stage. Default
67
+ * false (opt-in); one extra nested LLM pass when on. */
68
+ agentAutoReview: boolean;
55
69
  agentMaxFixAttempts: number;
56
70
  agentMaxIterations: number;
57
71
  agentMaxDuration: number;
@@ -152,6 +152,7 @@ function createConfig() {
152
152
  agentAutoCommit: false,
153
153
  agentAutoCommitBranch: false,
154
154
  agentAutoVerify: 'off',
155
+ agentAutoReview: false,
155
156
  // One fix attempt is enough for modern models — if verification fails twice
156
157
  // with the same approach, the agent usually needs human input, not more loops.
157
158
  agentMaxFixAttempts: 1,
@@ -168,6 +169,8 @@ function createConfig() {
168
169
  autoSave: true,
169
170
  autoSessionTitle: true,
170
171
  autoSummarizeHistory: true,
172
+ userProfile: true,
173
+ autoLearnProfile: false,
171
174
  trustedHookProjects: [],
172
175
  currentSessionId: '',
173
176
  temperature: 0.7,
@@ -182,6 +185,19 @@ function createConfig() {
182
185
  syncToken: '',
183
186
  deviceId: '',
184
187
  };
188
+ // Test/CI isolation: when CODEEP_CONFIG_DIR is set, keep config in that
189
+ // directory (one per test worker) so parallel workers don't race on a shared
190
+ // on-disk config file (e.g. clobbering trustedHookProjects). Never set in
191
+ // normal use, so the production resolution below is unchanged.
192
+ const overrideDir = process.env.CODEEP_CONFIG_DIR;
193
+ if (overrideDir) {
194
+ try {
195
+ return new Conf({ projectName: 'codeep', cwd: overrideDir, defaults });
196
+ }
197
+ catch {
198
+ // fall through to standard resolution
199
+ }
200
+ }
185
201
  // First try standard location
186
202
  try {
187
203
  const standardConfig = new Conf({
@@ -686,6 +702,16 @@ export function saveSession(name, history, projectPath) {
686
702
  && history.filter(m => m.role !== 'system').length >= 3) {
687
703
  void maybeGenerateSessionTitle(name, projectPath).catch(() => { });
688
704
  }
705
+ // Fire-and-forget: when the user opted into profile learning, observe the
706
+ // session and merge durable facts into ~/.codeep/profile.learned.md.
707
+ // Throttled + single-flight inside maybeLearnUserProfile, so the 5s
708
+ // autosave cadence doesn't spawn an LLM call every tick. Default off.
709
+ if (config.get('autoLearnProfile') === true
710
+ && history.filter(m => m.role !== 'system').length >= 4) {
711
+ void import('../utils/userProfile.js')
712
+ .then(({ maybeLearnUserProfile }) => maybeLearnUserProfile(name, history, projectPath))
713
+ .catch(() => { });
714
+ }
689
715
  return true;
690
716
  }
691
717
  catch (error) {
@@ -94,6 +94,8 @@ const COMMAND_DESCRIPTIONS = {
94
94
  'plan': 'Generate a numbered plan for a task — review before /go executes it',
95
95
  'go': 'Execute the pending plan from /plan',
96
96
  'personality': 'Switch agent tone: concise / verbose / security / senior-reviewer / etc',
97
+ 'me': 'Your user profile (reply language, style, stack) — adapts the agent to you. /me init, /me learn',
98
+ 'agents': 'List sub-agents the agent can delegate self-contained tasks to (researcher / reviewer / tester / custom)',
97
99
  'insights': 'Activity summary over the last N days (default 7): runs, files, tools, projects',
98
100
  'recall': 'Search across ALL saved sessions (cross-session; /search is current-session only)',
99
101
  };
@@ -241,6 +243,10 @@ export class App {
241
243
  'personality', 'insights',
242
244
  // 2.1.0 — cross-session recall.
243
245
  'recall',
246
+ // 2.2.0 — user profile.
247
+ 'me',
248
+ // 2.3.0 — sub-agents / delegation.
249
+ 'agents',
244
250
  'c', 't', 'd', 'r', 'f', 'e', 'o', 'b', 'p',
245
251
  ];
246
252
  constructor(options) {
@@ -252,6 +252,9 @@ export async function handleCommand(command, args, ctx) {
252
252
  commands: 'https://codeep.dev/docs/commands#custom-commands',
253
253
  openrouter: 'https://codeep.dev/docs/providers#openrouter',
254
254
  memory: 'https://codeep.dev/docs/commands#intelligence',
255
+ me: 'https://codeep.dev/docs/agent#user-profile',
256
+ agents: 'https://codeep.dev/docs/agent#sub-agents',
257
+ delegate: 'https://codeep.dev/docs/agent#sub-agents',
255
258
  profile: 'https://codeep.dev/docs/commands#settings',
256
259
  compact: 'https://codeep.dev/docs/commands#session',
257
260
  cost: 'https://codeep.dev/docs/dashboard',
@@ -311,6 +314,90 @@ export async function handleCommand(command, args, ctx) {
311
314
  });
312
315
  break;
313
316
  }
317
+ case 'agents': {
318
+ // List sub-agents the agent can `delegate` to (built-in + .codeep/agents/).
319
+ const { formatAgentList } = await import('../utils/agents.js');
320
+ ctx.app.addMessage({ role: 'system', content: formatAgentList(ctx.projectPath) });
321
+ break;
322
+ }
323
+ case 'me': {
324
+ // User profile (global ~/.codeep/profile.md + project .codeep/profile.md)
325
+ // injected into the agent's context. NOT the provider-profile feature
326
+ // (that's `/profile`). See src/utils/userProfile.ts.
327
+ const { formatProfileView, scaffoldProfile, updateLearnedProfile, clearLearnedProfile } = await import('../utils/userProfile.js');
328
+ const sub = args[0]?.toLowerCase();
329
+ if (sub === 'on' || sub === 'off') {
330
+ config.set('userProfile', sub === 'on');
331
+ ctx.app.notify(sub === 'on'
332
+ ? "Profile injection on — your profile is added to the agent's context."
333
+ : 'Profile injection off — profile is saved but not used.');
334
+ break;
335
+ }
336
+ if (sub === 'learn') {
337
+ const arg = args[1]?.toLowerCase();
338
+ if (arg === 'on' || arg === 'off') {
339
+ config.set('autoLearnProfile', arg === 'on');
340
+ ctx.app.notify(arg === 'on'
341
+ ? 'Auto-learn on — Codeep quietly updates your learned profile (global + project) from sessions.'
342
+ : 'Auto-learn off — Codeep stops updating the learned profile.');
343
+ break;
344
+ }
345
+ // Manual one-off. `/me learn project` targets this repo; otherwise global.
346
+ const scope = arg === 'project' ? 'project' : 'global';
347
+ if (scope === 'project' && !ctx.projectPath) {
348
+ ctx.app.notify('No project detected here — open a project, or run /me learn for your global profile.');
349
+ break;
350
+ }
351
+ const { loadSession } = await import('../config/index.js');
352
+ const history = loadSession(ctx.sessionId, ctx.projectPath) || [];
353
+ if (history.filter((m) => m.role !== 'system').length < 2) {
354
+ ctx.app.notify('Not enough conversation yet to learn from — chat a bit, then run /me learn.');
355
+ break;
356
+ }
357
+ ctx.app.notify(`Learning ${scope} preferences from this session…`);
358
+ const res = await updateLearnedProfile(history, scope, ctx.projectPath);
359
+ if (!res) {
360
+ ctx.app.notify('Nothing durable to learn right now (or the model call failed).');
361
+ break;
362
+ }
363
+ const file = scope === 'global' ? '~/.codeep/profile.learned.md' : '.codeep/profile.learned.md';
364
+ ctx.app.addMessage({
365
+ role: 'system',
366
+ content: res.updated
367
+ ? `Updated your ${scope} learned profile (\`${file}\`):\n\n${res.facts}\n\nClear it anytime with \`/me forget\`.`
368
+ : `No changes — your ${scope} learned profile already covers this:\n\n${res.facts}`,
369
+ });
370
+ break;
371
+ }
372
+ if (sub === 'forget') {
373
+ ctx.app.notify(clearLearnedProfile(ctx.projectPath)
374
+ ? 'Cleared the auto-learned profile(s).'
375
+ : 'No learned profile to clear.');
376
+ break;
377
+ }
378
+ if (sub === 'init') {
379
+ const scope = args[1]?.toLowerCase() === 'project' ? 'project' : 'global';
380
+ if (scope === 'project' && !ctx.projectPath) {
381
+ ctx.app.notify('No project detected here. Use /me init for a global profile, or open a project first.');
382
+ break;
383
+ }
384
+ const res = scaffoldProfile(scope, ctx.projectPath);
385
+ if (!res) {
386
+ ctx.app.notify('Could not create the profile file.');
387
+ break;
388
+ }
389
+ ctx.app.addMessage({
390
+ role: 'system',
391
+ content: res.created
392
+ ? `Created ${scope} profile: \`${res.path}\`\n\nEdit it in your editor — Codeep uses it automatically. View anytime with \`/me\`.`
393
+ : `${scope === 'global' ? 'Global' : 'Project'} profile already exists: \`${res.path}\`\n\nEdit it directly, or view it with \`/me\`.`,
394
+ });
395
+ break;
396
+ }
397
+ // Default: show the profile view.
398
+ ctx.app.addMessage({ role: 'system', content: formatProfileView(ctx.projectPath) });
399
+ break;
400
+ }
314
401
  case 'plan': {
315
402
  // Plan mode: ask the model for a plan, surface it, hold as pending.
316
403
  // The user runs /go to execute or /plan <revised> to revise. See
@@ -1799,6 +1886,15 @@ Describe what this skill does. The agent reads this body verbatim when it invoke
1799
1886
  results.push(`✓ ${pulled} new profile(s) pulled`);
1800
1887
  }
1801
1888
  }
1889
+ // Sync the hand-written user profile (~/.codeep/profile.md). Push sends
1890
+ // the local file; pull is additive (writes only if no local profile).
1891
+ if (subCmd === 'all' || subCmd === 'profile') {
1892
+ const { pushUserProfile, pullUserProfile } = await import('../utils/codeepCloud.js');
1893
+ if (await pushUserProfile())
1894
+ results.push('✓ Your profile (about you) pushed');
1895
+ if ((await pullUserProfile()) === 1)
1896
+ results.push('✓ Your profile pulled to this machine');
1897
+ }
1802
1898
  ctx.app.addMessage({
1803
1899
  role: 'system',
1804
1900
  content: `## Sync\n\n${results.map(r => `- ${r}`).join('\n')}`,
@@ -128,6 +128,10 @@ export const helpCategories = [
128
128
  { key: '/openrouter', description: 'OpenRouter routing prefs (prefer/ignore providers, fallbacks, privacy)' },
129
129
  { key: '/personality', description: 'List or switch agent tone (concise / verbose / security / senior-reviewer / …)' },
130
130
  { key: '/personality <name>', description: 'Activate a personality. /personality off to clear.' },
131
+ { key: '/me', description: 'Your user profile (reply language, style, stack) — adapts the agent to you' },
132
+ { key: '/me init [project]', description: 'Scaffold a profile template (global, or for this project). /me off to disable' },
133
+ { key: '/me learn [on|off]', description: 'Learn durable prefs from this session now; on/off toggles auto-learn. /me forget clears it' },
134
+ { key: '/agents', description: 'List sub-agents the agent can delegate to (researcher / reviewer / tester / your own)' },
131
135
  { key: '/insights [--days N]', description: 'Activity summary — runs, files, tools, projects over the last N days (default 7)' },
132
136
  ],
133
137
  },
@@ -179,6 +179,16 @@ export const SETTINGS = [
179
179
  { value: 'all', label: 'Build + Typecheck + Test' },
180
180
  ],
181
181
  },
182
+ {
183
+ key: 'agentAutoReview',
184
+ label: 'Agent Auto-Review',
185
+ getValue: () => config.get('agentAutoReview'),
186
+ type: 'select',
187
+ options: [
188
+ { value: true, label: 'On (reviewer pass after changes)' },
189
+ { value: false, label: 'Off' },
190
+ ],
191
+ },
182
192
  {
183
193
  key: 'agentAutoCommit',
184
194
  label: 'Agent Auto-Commit',
@@ -56,6 +56,20 @@ export interface AgentOptions {
56
56
  role: 'user' | 'assistant';
57
57
  content: string;
58
58
  }>;
59
+ /** Delegated sub-agent run. Skips the undo/history session + progress log so
60
+ * it doesn't clobber the parent's (history.ts uses a module-level
61
+ * `currentSession` singleton — a nested startSession would reset it). The
62
+ * sub-agent's tool actions still record into the parent's session, so undo
63
+ * spans delegation. */
64
+ nested?: boolean;
65
+ /** Delegation depth. 0 = top-level orchestrator (gets the `delegate` tool);
66
+ * sub-agents run at depth 1 and cannot delegate further (v1). */
67
+ depth?: number;
68
+ /** Tool allowlist for a scoped sub-agent. Undefined = all tools. Enforced at
69
+ * dispatch — a disallowed tool call returns an error result. */
70
+ allowedTools?: string[];
71
+ /** Role system-prompt addendum injected for a delegated sub-agent. */
72
+ roleAddendum?: string;
59
73
  }
60
74
  export interface AgentResult {
61
75
  success: boolean;