@oh-my-pi/pi-coding-agent 10.2.2 → 10.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.
Files changed (65) hide show
  1. package/CHANGELOG.md +51 -0
  2. package/package.json +7 -7
  3. package/src/commit/agentic/prompts/analyze-file.md +7 -7
  4. package/src/commit/agentic/prompts/session-user.md +4 -4
  5. package/src/commit/agentic/prompts/system.md +14 -16
  6. package/src/commit/prompts/analysis-system.md +7 -9
  7. package/src/commit/prompts/analysis-user.md +0 -3
  8. package/src/commit/prompts/changelog-system.md +14 -19
  9. package/src/commit/prompts/file-observer-system.md +2 -2
  10. package/src/commit/prompts/reduce-system.md +13 -23
  11. package/src/commit/prompts/summary-system.md +7 -21
  12. package/src/config/settings-schema.ts +135 -56
  13. package/src/config/settings.ts +0 -6
  14. package/src/cursor.ts +2 -1
  15. package/src/extensibility/extensions/index.ts +0 -11
  16. package/src/extensibility/extensions/types.ts +1 -30
  17. package/src/extensibility/hooks/types.ts +1 -31
  18. package/src/index.ts +0 -11
  19. package/src/ipy/prelude.py +1 -113
  20. package/src/lsp/index.ts +66 -515
  21. package/src/lsp/render.ts +0 -11
  22. package/src/lsp/types.ts +3 -87
  23. package/src/modes/components/settings-defs.ts +3 -2
  24. package/src/modes/components/settings-selector.ts +14 -14
  25. package/src/modes/theme/theme.ts +45 -1
  26. package/src/prompts/agents/designer.md +23 -27
  27. package/src/prompts/agents/explore.md +28 -38
  28. package/src/prompts/agents/init.md +17 -17
  29. package/src/prompts/agents/plan.md +21 -27
  30. package/src/prompts/agents/reviewer.md +37 -37
  31. package/src/prompts/compaction/branch-summary.md +9 -9
  32. package/src/prompts/compaction/compaction-summary.md +8 -12
  33. package/src/prompts/compaction/compaction-update-summary.md +17 -19
  34. package/src/prompts/review-request.md +12 -13
  35. package/src/prompts/system/custom-system-prompt.md +6 -26
  36. package/src/prompts/system/plan-mode-active.md +23 -35
  37. package/src/prompts/system/plan-mode-subagent.md +7 -7
  38. package/src/prompts/system/subagent-system-prompt.md +7 -7
  39. package/src/prompts/system/system-prompt.md +134 -149
  40. package/src/prompts/system/web-search.md +10 -10
  41. package/src/prompts/tools/ask.md +12 -15
  42. package/src/prompts/tools/bash.md +7 -7
  43. package/src/prompts/tools/exit-plan-mode.md +6 -6
  44. package/src/prompts/tools/gemini-image.md +4 -4
  45. package/src/prompts/tools/grep.md +4 -4
  46. package/src/prompts/tools/lsp.md +12 -19
  47. package/src/prompts/tools/patch.md +26 -30
  48. package/src/prompts/tools/python.md +14 -57
  49. package/src/prompts/tools/read.md +4 -4
  50. package/src/prompts/tools/replace.md +8 -8
  51. package/src/prompts/tools/ssh.md +14 -27
  52. package/src/prompts/tools/task.md +23 -35
  53. package/src/prompts/tools/todo-write.md +29 -38
  54. package/src/prompts/tools/write.md +3 -3
  55. package/src/sdk.ts +0 -2
  56. package/src/session/agent-session.ts +27 -6
  57. package/src/system-prompt.ts +1 -219
  58. package/src/task/agents.ts +2 -1
  59. package/src/tools/bash-interceptor.ts +0 -24
  60. package/src/tools/bash.ts +1 -7
  61. package/src/tools/index.ts +8 -3
  62. package/src/tools/read.ts +74 -17
  63. package/src/tools/renderers.ts +0 -2
  64. package/src/lsp/rust-analyzer.ts +0 -184
  65. package/src/tools/ls.ts +0 -307
@@ -5,23 +5,23 @@ Launch agents to handle complex, multi-step tasks autonomously.
5
5
  <critical>
6
6
  ## Context is everything
7
7
 
8
- Subagents fail when context is vague. They cannot read your mind or infer project conventions. Every task needs:
8
+ Subagents fail with vague context. Every task needs:
9
9
  1. **Goal** - What this accomplishes (one sentence)
10
10
  2. **Constraints** - Hard requirements, banned approaches, naming conventions
11
11
  3. **Existing Code** - File paths and function signatures to use as patterns
12
12
  4. **API Contract** - If the task produces or consumes an interface, spell it out
13
13
 
14
- Subagents CAN grep the parent conversation file for supplementary details. They CANNOT grep for:
14
+ Subagents CAN grep parent conversation file for supplementary details, but CANNOT grep for:
15
15
  - Decisions you made but didn't write down
16
16
  - Conventions that exist only in your head
17
17
  - Which of 50 possible approaches you want
18
- **Rule of thumb:** If you'd need to answer a clarifying question for a junior dev to do this task, that information belongs in context.
18
+ **Rule of thumb:** If you'd answer clarifying question for junior dev, info belongs in context.
19
19
  </critical>
20
20
 
21
21
  <context-structure>
22
22
  ## Required context structure
23
23
 
24
- Use this template. Sections can be omitted only if truly N/A.
24
+ Use this template. Omit sections only if N/A.
25
25
 
26
26
  ````
27
27
  ## Goal
@@ -33,7 +33,7 @@ Use this template. Sections can be omitted only if truly N/A.
33
33
  - [What already exists vs what to create]
34
34
 
35
35
  ## Existing Code
36
- Reference files the agent MUST read or use as patterns:
36
+ Reference files the agent MUST read/use as patterns:
37
37
  - `path/to/file.ts` - [what pattern it demonstrates]
38
38
  - `path/to/other.rs` - [what to reuse from it]
39
39
 
@@ -50,19 +50,13 @@ fn example(input: Type) -> Result<Output>
50
50
  {{files}}
51
51
  ````
52
52
 
53
- ### Bad context (agent will fail or guess wrong)
53
+ ### Bad context (agent fails or guesses wrong)
54
54
 
55
55
  ```
56
56
  N-API migration. Keep highlight sync. Use JsString. No WASM.
57
57
  Task: {{description}} Files: {{files}}
58
58
  ```
59
59
 
60
- Why it fails:
61
- - No existing code to reference - agent doesn't know your patterns
62
- - No API contract - agent will invent signatures that don't match consumers
63
- - No goal - agent doesn't know what success looks like
64
- - "Keep highlight sync" is meaningless without knowing what highlight is or where it lives
65
-
66
60
  ### Good context (agent can act confidently)
67
61
 
68
62
  ````
@@ -71,9 +65,9 @@ Port grep module from WASM to N-API, matching existing text module patterns.
71
65
 
72
66
  ## Constraints
73
67
  - Use `#[napi]` attribute macro on all exports (not `#[napi(js_name = "...")]`)
74
- - Return `napi::Result<T>` for fallible ops, never panic
75
- - Use `spawn_blocking` for any operation that touches filesystem or runs >1ms
76
- - Accept `JsString` for string params (NOT JsStringUtf8 - it has lifetime issues)
68
+ - Return `napi::Result<T>` for fallible ops; never panic
69
+ - Use `spawn_blocking` for filesystem ops or >1ms work
70
+ - Accept `JsString` for string params (NOT JsStringUtf8; lifetime issues)
77
71
  - Keep all existing function names - TS bindings depend on them
78
72
  - No new crate dependencies
79
73
 
@@ -104,36 +98,36 @@ pub async fn search(pattern: JsString, path: JsString, env: Env) -> napi::Result
104
98
 
105
99
  <parallelization>
106
100
  ## When to parallelize vs sequence
107
- **The test:** Can agent B write correct code without seeing agent A's output?
101
+ **Test:** Can agent B write correct code without seeing A's output?
108
102
  - If YES → parallelize
109
103
  - If NO → sequence (A completes, then B runs with A's output in context)
110
104
 
111
- ### Dependency patterns that MUST be sequential
105
+ ### Dependencies that MUST be sequential
112
106
 
113
107
  |First|Then|Why|
114
108
  |---|---|---|
115
- |Create Rust API|Update TS bindings|Bindings need to know export names and signatures|
116
- |Define interface/types|Implement consumers|Consumers need the contract|
117
- |Scaffold with signatures|Implement bodies|Implementations need the shape|
109
+ |Create Rust API|Update TS bindings|Bindings need export names and signatures|
110
+ |Define interface/types|Implement consumers|Consumers need contract|
111
+ |Scaffold with signatures|Implement bodies|Implementations need shape|
118
112
  |Core module|Dependent modules|Dependents import from core|
119
113
 
120
114
  ### Safe to parallelize
121
- - Independent modules that don't import each other
115
+ - Independent modules not importing each other
122
116
  - Tests for already-implemented code
123
117
  - Documentation for stable APIs
124
118
  - Refactors in isolated file scopes
125
119
 
126
120
  ### Phased execution pattern
127
121
 
128
- For migrations/refactors with layers:
122
+ For layered migrations/refactors:
129
123
  **Phase 1 - Foundation (do yourself or single task):**
130
- Create the scaffold, define interfaces, establish API shape. Never fan out until the contract is known.
124
+ Create scaffold, define interfaces, establish API shape. Never fan out until contract known.
131
125
  **Phase 2 - Parallel implementation:**
132
- Fan out to independent tasks that all consume the same known interface. Include the API contract from Phase 1 in every task's context.
126
+ Fan out to independent tasks consuming same known interface. Include Phase 1 API contract in every task's context.
133
127
  **Phase 3 - Integration (do yourself):**
134
- Wire things together, update build/CI, fix any mismatches.
128
+ Wire things together, update build/CI, fix mismatches.
135
129
  **Phase 4 - Dependent layer:**
136
- Fan out again for work that consumes Phase 2 outputs.
130
+ Fan out again for work consuming Phase 2 outputs.
137
131
 
138
132
  ### Example: WASM to N-API migration
139
133
  **WRONG** (launched together, will fail):
@@ -168,15 +162,14 @@ tasks: [
168
162
 
169
163
  <parameters>
170
164
  - `agent`: Agent type for all tasks
171
- - `context`: Template with `{{placeholders}}`. **Must follow the structure above.** Include Goal, Constraints, Existing Code references. Subagents can search parent context for background, but core requirements must be explicit here.
165
+ - `context`: Template with `{{placeholders}}`; **Must follow structure above**.
172
166
  - `isolated`: (optional) Run in git worktree, return patches
173
167
  - `tasks`: Array of `{id, description, args}`
174
168
  - `id`: CamelCase identifier (max 32 chars)
175
- - `description`: What the task does (for logging)
169
+ - `description`: What task does (for logging)
176
170
  - `args`: Object with keys matching `{{placeholders}}` in context
177
171
  - `skills`: (optional) Skill names to preload
178
- - `schema`: JTD schema for response structure. **Required.** Use typed properties, not `{ "type": "string" }`.
179
- **Schema goes in `schema` parameter. Never describe output format in `context`.**
172
+ - `schema`: JTD schema for response structure (**required**; use typed properties, not `{ "type": "string" }`). **Schema goes in `schema`; never describe output format in `context`.**
180
173
  </parameters>
181
174
 
182
175
  <agents>
@@ -189,11 +182,6 @@ tasks: [
189
182
  </agents>
190
183
 
191
184
  <avoid>
192
- - Terse context that requires agents to guess conventions
193
- - Launching dependent tasks in parallel (bindings + API, consumer + producer)
194
- - Missing "Existing Code" references - agents need patterns to follow
195
- - Assuming agents know your codebase - they start fresh each time
196
- - Describing output format in context instead of schema
197
185
  - Single tasks doing too much - prefer focused, file-scoped tasks
198
186
  </avoid>
199
187
  ````
@@ -1,49 +1,43 @@
1
1
  # Todo Write
2
2
 
3
- Create and manage a structured task list for your current coding session.
3
+ Create/manage structured task list for coding session.
4
4
 
5
5
  <conditions>
6
- Use this tool proactively in these scenarios:
7
- 1. Complex multi-step tasks - When a task requires 3 or more distinct steps or actions
8
- 2. Non-trivial and complex tasks - Tasks that require careful planning or multiple operations
9
- 3. User explicitly requests todo list - When the user directly asks you to use the todo list
10
- 4. User provides multiple tasks - When users provide a list of things to be done (numbered or comma-separated)
11
- 5. After receiving new instructions - Immediately capture user requirements as todos
12
- 6. When you start working on a task - Mark it as in_progress BEFORE beginning work
13
- 7. After completing a task - Mark it as completed and add any new follow-up tasks discovered during implementation
6
+ Use proactively:
7
+ 1. Complex multi-step tasks requiring 3+ steps/actions
8
+ 2. User requests todo list
9
+ 3. User provides multiple tasks (numbered/comma-separated)
10
+ 4. After new instructions—capture requirements as todos
11
+ 5. Starting task—mark in_progress BEFORE beginning
12
+ 6. After completing—mark completed, add follow-up tasks found
14
13
  </conditions>
15
14
 
16
15
  <protocol>
17
- 1. **Task States**: Use these states to track progress:
18
- - pending: Task not yet started
19
- - in_progress: Currently working on
20
- - completed: Task finished successfully
16
+ 1. **Task States**:
17
+ - pending: not started
18
+ - in_progress: working
19
+ - completed: finished
21
20
  2. **Task Management**:
22
- - Update task status in real-time as you work
23
- - Mark tasks complete IMMEDIATELY after finishing (don't batch completions)
24
- - Multiple tasks may be in_progress simultaneously when working in parallel
25
- - Remove tasks that are no longer relevant from the list entirely
21
+ - Update status in real time
22
+ - Mark complete IMMEDIATELY after finishing (no batching)
23
+ - Multiple tasks may be in_progress in parallel
24
+ - Remove tasks no longer relevant
26
25
  3. **Task Completion Requirements**:
27
- - ONLY mark a task as completed when you have FULLY accomplished it
28
- - If you encounter errors, blockers, or cannot finish, keep the task as in_progress
29
- - When blocked, create a new task describing what needs to be resolved
30
- - Never mark a task as completed if:
31
- - Tests are failing
32
- - Implementation is partial
33
- - You encountered unresolved errors
34
- - You couldn't find necessary files or dependencies
26
+ - ONLY mark completed when FULLY accomplished
27
+ - On errors/blockers/inability to finish, keep in_progress
28
+ - When blocked, create task describing what needs resolving
35
29
  4. **Task Breakdown**:
36
30
  - Create specific, actionable items
37
- - Break complex tasks into smaller, manageable steps
38
- - Use clear, descriptive task names
31
+ - Break complex tasks into smaller steps
32
+ - Use clear, descriptive names
39
33
  </protocol>
40
34
 
41
35
  <output>
42
- Returns confirmation that the todo list has been updated. The updated list is displayed to the user in the UI, showing each task's status (pending, in_progress, completed) and description.
36
+ Returns confirmation todo list updated.
43
37
  </output>
44
38
 
45
39
  <important>
46
- When in doubt, use this tool. Being proactive with task management demonstrates attentiveness and ensures you complete all requirements successfully.
40
+ When in doubt, use this.
47
41
  </important>
48
42
 
49
43
  <example name="use-dark-mode">
@@ -53,20 +47,17 @@ User: Add dark mode toggle to settings. Run tests when done.
53
47
 
54
48
  <example name="use-features">
55
49
  User: Implement user registration, product catalog, shopping cart, checkout.
56
- → Creates todos for each feature, broken into subtasks
50
+ → Creates todos per feature with subtasks
57
51
  </example>
58
52
 
59
53
  <example name="skip">
60
54
  User: Run npm install / Add a comment to this function / What does git status do?
61
- Just do it directly. Single-step or informational tasks don't need tracking.
55
+ Do directly. Single-step/informational tasks need no tracking.
62
56
  </example>
63
57
 
64
58
  <avoid>
65
- Skip using this tool when:
66
- 1. There is only a single, straightforward task
67
- 2. The task is trivial and tracking it provides no organizational benefit
68
- 3. The task can be completed in less than 3 trivial steps
69
- 4. The task is purely conversational or informational
70
-
71
- If there is only one trivial task to do, just do it directly.
59
+ Skip when:
60
+ 1. Single straightforward task
61
+ 2. Task completable in <3 trivial steps
62
+ 3. Task purely conversational/informational
72
63
  </avoid>
@@ -1,14 +1,14 @@
1
1
  # Write
2
2
 
3
- Creates or overwrites a file at the specified path.
3
+ Creates or overwrites file at specified path.
4
4
 
5
5
  <conditions>
6
- - Creating new files explicitly required by the task
6
+ - Creating new files explicitly required by task
7
7
  - Replacing entire file contents when editing would be more complex
8
8
  </conditions>
9
9
 
10
10
  <output>
11
- Confirmation of file creation/write with path. When LSP is available, content may be auto-formatted before writing and diagnostics are returned. Returns error if write fails (permissions, invalid path, disk full).
11
+ Confirmation of file creation/write with path. When LSP available, content may be auto-formatted before writing and diagnostics returned. Returns error if write fails (permissions, invalid path, disk full).
12
12
  </output>
13
13
 
14
14
  <critical>
package/src/sdk.ts CHANGED
@@ -96,7 +96,6 @@ import {
96
96
  FindTool,
97
97
  GrepTool,
98
98
  getWebSearchTools,
99
- LsTool,
100
99
  loadSshTool,
101
100
  PythonTool,
102
101
  ReadTool,
@@ -235,7 +234,6 @@ export {
235
234
  FindTool,
236
235
  GrepTool,
237
236
  loadSshTool,
238
- LsTool,
239
237
  PythonTool,
240
238
  ReadTool,
241
239
  WriteTool,
@@ -2461,13 +2461,15 @@ Be thorough - include exact file paths, function names, error messages, and tech
2461
2461
  * Check if agent stopped with incomplete todos and prompt to continue.
2462
2462
  */
2463
2463
  private async _checkTodoCompletion(): Promise<void> {
2464
- const todoSettings = this.settings.getGroup("todoCompletion");
2465
- if (!todoSettings.enabled) {
2464
+ const remindersEnabled = this.settings.get("todo.reminders");
2465
+ const todosEnabled = this.settings.get("todo.enabled");
2466
+ if (!remindersEnabled || !todosEnabled) {
2466
2467
  this._todoReminderCount = 0;
2467
2468
  return;
2468
2469
  }
2469
2470
 
2470
- if (this._todoReminderCount >= todoSettings.maxReminders) {
2471
+ const remindersMax = this.settings.get("todo.reminders.max");
2472
+ if (this._todoReminderCount >= remindersMax) {
2471
2473
  logger.debug("Todo completion: max reminders reached", { count: this._todoReminderCount });
2472
2474
  return;
2473
2475
  }
@@ -2503,7 +2505,7 @@ Be thorough - include exact file paths, function names, error messages, and tech
2503
2505
  `<system_reminder>\n` +
2504
2506
  `You stopped with ${incomplete.length} incomplete todo item(s):\n${todoList}\n\n` +
2505
2507
  `Please continue working on these tasks or mark them complete if finished.\n` +
2506
- `(Reminder ${this._todoReminderCount}/${todoSettings.maxReminders})\n` +
2508
+ `(Reminder ${this._todoReminderCount}/${remindersMax})\n` +
2507
2509
  `</system_reminder>`;
2508
2510
 
2509
2511
  logger.debug("Todo completion: sending reminder", {
@@ -2516,7 +2518,7 @@ Be thorough - include exact file paths, function names, error messages, and tech
2516
2518
  type: "todo_reminder",
2517
2519
  todos: incomplete,
2518
2520
  attempt: this._todoReminderCount,
2519
- maxAttempts: todoSettings.maxReminders,
2521
+ maxAttempts: remindersMax,
2520
2522
  });
2521
2523
 
2522
2524
  // Inject reminder and continue the conversation
@@ -3811,13 +3813,32 @@ Be thorough - include exact file paths, function names, error messages, and tech
3811
3813
 
3812
3814
  // Include available tools
3813
3815
  const tools = this.agent.state.tools;
3816
+
3817
+ // Recursively strip all fields starting with 'TypeBox.' from an object
3818
+ function stripTypeBoxFields(obj: any): any {
3819
+ if (Array.isArray(obj)) {
3820
+ return obj.map(stripTypeBoxFields);
3821
+ }
3822
+ if (obj && typeof obj === "object") {
3823
+ const result: Record<string, any> = {};
3824
+ for (const [k, v] of Object.entries(obj)) {
3825
+ if (!k.startsWith("TypeBox.")) {
3826
+ result[k] = stripTypeBoxFields(v);
3827
+ }
3828
+ }
3829
+ return result;
3830
+ }
3831
+ return obj;
3832
+ }
3833
+
3814
3834
  if (tools.length > 0) {
3815
3835
  lines.push("## Available Tools\n");
3816
3836
  for (const tool of tools) {
3817
3837
  lines.push(`### ${tool.name}\n`);
3818
3838
  lines.push(tool.description);
3819
3839
  lines.push("\n```yaml");
3820
- lines.push(YAML.stringify(tool.parameters, null, 2));
3840
+ const parametersClean = stripTypeBoxFields(tool.parameters);
3841
+ lines.push(YAML.stringify(parametersClean, null, 2));
3821
3842
  lines.push("```\n");
3822
3843
  }
3823
3844
  lines.push("\n");
@@ -1,11 +1,9 @@
1
1
  /**
2
2
  * System prompt construction and project context loading
3
3
  */
4
- import * as fs from "node:fs/promises";
5
4
  import * as os from "node:os";
6
5
  import * as path from "node:path";
7
- import { FileType, getSystemInfo as getNativeSystemInfo, glob, type SystemInfo } from "@oh-my-pi/pi-natives";
8
- import { untilAborted } from "@oh-my-pi/pi-utils";
6
+ import { getSystemInfo as getNativeSystemInfo, type SystemInfo } from "@oh-my-pi/pi-natives";
9
7
  import { $ } from "bun";
10
8
  import chalk from "chalk";
11
9
  import { contextFileCapability } from "./capability/context-file";
@@ -106,24 +104,6 @@ function parseWmicTable(output: string, header: string): string | null {
106
104
 
107
105
  const AGENTS_MD_PATTERN = "**/AGENTS.md";
108
106
  const AGENTS_MD_LIMIT = 200;
109
- const PROJECT_TREE_LIMIT = 2000;
110
- const PROJECT_TREE_PER_DIR_LIMIT = 10;
111
- const PROJECT_TREE_PER_DIR_DEPTH = 2;
112
- const PROJECT_TREE_IGNORED = new Set([
113
- ".git",
114
- ".hg",
115
- ".svn",
116
- ".next",
117
- ".turbo",
118
- ".cache",
119
- ".venv",
120
- ".idea",
121
- ".vscode",
122
- "build",
123
- "dist",
124
- "node_modules",
125
- "target",
126
- ]);
127
107
 
128
108
  interface AgentsMdSearch {
129
109
  scopePath: string;
@@ -161,201 +141,6 @@ function buildAgentsMdSearch(cwd: string): AgentsMdSearch {
161
141
  };
162
142
  }
163
143
 
164
- type ProjectTreeEntry = {
165
- name: string;
166
- isDirectory: boolean;
167
- path: string;
168
- };
169
-
170
- type ProjectTreeScan = {
171
- children: Map<string, ProjectTreeEntry[]>;
172
- truncated: boolean;
173
- truncatedDirs: Set<string>;
174
- };
175
-
176
- const GLOB_TIMEOUT_MS = 5000;
177
-
178
- /**
179
- * Scan project tree using ripgrep-wasm find with exclusion filters.
180
- * Returns null if scan fails.
181
- */
182
- async function scanProjectTreeWithGlob(root: string): Promise<ProjectTreeScan | null> {
183
- let entries: string[];
184
- const timeoutSignal = AbortSignal.timeout(GLOB_TIMEOUT_MS);
185
- try {
186
- const result = await untilAborted(timeoutSignal, () =>
187
- glob({
188
- pattern: "**/*",
189
- path: root,
190
- fileType: FileType.File,
191
- }),
192
- );
193
- entries = result.matches.map(match => match.path).filter(entry => entry.length > 0);
194
- } catch {
195
- return null;
196
- }
197
-
198
- // Build directory contents map from file list
199
- // Map<dirPath, Map<entryPath, isDirectory>>
200
- const dirContents = new Map<string, Map<string, boolean>>();
201
- dirContents.set(root, new Map());
202
-
203
- for (const entry of entries) {
204
- const filePath = entry;
205
- if (!filePath) continue;
206
- const absolutePath = path.join(root, filePath);
207
- // Check static ignores on path components
208
- const relative = path.relative(root, absolutePath);
209
- const parts = relative.split(path.sep);
210
- if (parts.some(p => PROJECT_TREE_IGNORED.has(p))) continue;
211
-
212
- // Add file to its parent directory
213
- const parent = path.dirname(absolutePath);
214
- if (!dirContents.has(parent)) dirContents.set(parent, new Map());
215
- dirContents.get(parent)!.set(absolutePath, false);
216
-
217
- // Add all intermediate directories
218
- let dir = parent;
219
- while (dir.length >= root.length && dir !== path.dirname(dir)) {
220
- const parentDir = path.dirname(dir);
221
- if (!dirContents.has(parentDir)) dirContents.set(parentDir, new Map());
222
- dirContents.get(parentDir)!.set(dir, true);
223
- dir = parentDir;
224
- }
225
- }
226
-
227
- // BFS to build the tree with limits
228
- const children = new Map<string, ProjectTreeEntry[]>();
229
- let entryCount = 0;
230
- let truncated = false;
231
- const truncatedDirs = new Set<string>();
232
-
233
- const queue: Array<{ dirPath: string; depth: number }> = [{ dirPath: root, depth: 0 }];
234
- let cursor = 0;
235
-
236
- while (cursor < queue.length && !truncated) {
237
- const { dirPath, depth } = queue[cursor];
238
- cursor += 1;
239
-
240
- const contents = dirContents.get(dirPath);
241
- if (!contents || contents.size === 0) continue;
242
-
243
- // Get stats for sorting
244
- const entries = Array.from(contents.entries());
245
- const withStats = await Promise.all(
246
- entries.map(async ([entryPath, isDirectory]) => {
247
- try {
248
- const stats = await fs.stat(entryPath);
249
- return { entryPath, isDirectory, mtimeMs: stats.mtimeMs };
250
- } catch {
251
- return { entryPath, isDirectory, mtimeMs: 0 };
252
- }
253
- }),
254
- );
255
-
256
- withStats.sort((a, b) => {
257
- if (a.mtimeMs !== b.mtimeMs) return b.mtimeMs - a.mtimeMs;
258
- return path.basename(a.entryPath).localeCompare(path.basename(b.entryPath));
259
- });
260
-
261
- const perDirLimit = depth >= PROJECT_TREE_PER_DIR_DEPTH ? PROJECT_TREE_PER_DIR_LIMIT : null;
262
- const limited = perDirLimit === null ? withStats : withStats.slice(0, perDirLimit);
263
- const hasMoreEntries = perDirLimit !== null && withStats.length > perDirLimit;
264
-
265
- const mapped: ProjectTreeEntry[] = [];
266
- for (const { entryPath, isDirectory } of limited) {
267
- if (entryCount >= PROJECT_TREE_LIMIT) {
268
- truncated = true;
269
- break;
270
- }
271
-
272
- mapped.push({
273
- name: path.basename(entryPath),
274
- isDirectory,
275
- path: entryPath,
276
- });
277
- entryCount += 1;
278
-
279
- if (isDirectory) {
280
- queue.push({ dirPath: entryPath, depth: depth + 1 });
281
- }
282
- }
283
-
284
- if (!truncated && hasMoreEntries) {
285
- truncatedDirs.add(dirPath);
286
- }
287
- children.set(dirPath, mapped);
288
- }
289
-
290
- return { children, truncated, truncatedDirs };
291
- }
292
-
293
- async function scanProjectTree(root: string): Promise<ProjectTreeScan> {
294
- const globResult = await scanProjectTreeWithGlob(root);
295
- if (globResult) return globResult;
296
- return { children: new Map(), truncated: false, truncatedDirs: new Set() };
297
- }
298
-
299
- function renderProjectTree(scan: ProjectTreeScan, root: string): string {
300
- const lines: string[] = [];
301
-
302
- const collapseDir = (dirPath: string): { path: string; entries: ProjectTreeEntry[] } | null => {
303
- let currentPath = dirPath;
304
- while (true) {
305
- const entries = scan.children.get(currentPath);
306
- if (!entries || entries.length === 0) return null;
307
- const files = entries.filter(entry => !entry.isDirectory);
308
- const dirs = entries.filter(entry => entry.isDirectory);
309
- if (files.length === 0 && dirs.length === 1 && !scan.truncatedDirs.has(currentPath)) {
310
- currentPath = dirs[0].path;
311
- continue;
312
- }
313
- return { path: currentPath, entries };
314
- }
315
- };
316
-
317
- const renderDir = (dirPath: string, indent: string, isRoot: boolean): void => {
318
- const collapsed = collapseDir(dirPath);
319
- if (!collapsed) return;
320
- const { path: collapsedPath, entries } = collapsed;
321
-
322
- // For non-root directories, print the header and indent contents
323
- const contentIndent = isRoot ? indent : `${indent} `;
324
- if (!isRoot) {
325
- const relative = path.relative(root, collapsedPath) || ".";
326
- lines.push(`${indent}@ ${relative}`);
327
- }
328
-
329
- const files = entries.filter(entry => !entry.isDirectory);
330
- const dirs = entries.filter(entry => entry.isDirectory);
331
-
332
- for (const entry of files) {
333
- lines.push(`${contentIndent}- ${entry.name}`);
334
- }
335
-
336
- if (scan.truncatedDirs.has(collapsedPath)) {
337
- lines.push(`${contentIndent}- …`);
338
- }
339
-
340
- for (const entry of dirs) {
341
- renderDir(entry.path, contentIndent, false);
342
- }
343
- };
344
-
345
- renderDir(root, "", true);
346
-
347
- if (scan.truncated) {
348
- lines.push("…");
349
- }
350
-
351
- return lines.join("\n");
352
- }
353
-
354
- async function buildProjectTreeSnapshot(root: string): Promise<string> {
355
- const scan = await scanProjectTree(root);
356
- return renderProjectTree(scan, root);
357
- }
358
-
359
144
  async function getGpuModel(): Promise<string | null> {
360
145
  switch (process.platform) {
361
146
  case "win32": {
@@ -704,7 +489,6 @@ export async function buildSystemPrompt(options: BuildSystemPromptOptions = {}):
704
489
  // Resolve context files: use provided or discover
705
490
  const contextFiles = providedContextFiles ?? (await loadProjectContextFiles({ cwd: resolvedCwd }));
706
491
  const agentsMdSearch = buildAgentsMdSearch(resolvedCwd);
707
- const projectTree = await buildProjectTreeSnapshot(resolvedCwd);
708
492
 
709
493
  // Build tool descriptions array
710
494
  // Priority: toolNames (explicit list) > tools (Map) > defaults
@@ -742,7 +526,6 @@ export async function buildSystemPrompt(options: BuildSystemPromptOptions = {}):
742
526
  customPrompt: resolvedCustomPrompt,
743
527
  appendPrompt: resolvedAppendPrompt ?? "",
744
528
  contextFiles,
745
- projectTree,
746
529
  agentsMdSearch,
747
530
  git,
748
531
  skills: filteredSkills,
@@ -759,7 +542,6 @@ export async function buildSystemPrompt(options: BuildSystemPromptOptions = {}):
759
542
  environment: await getEnvironmentInfo(),
760
543
  systemPromptCustomization: systemPromptCustomization ?? "",
761
544
  contextFiles,
762
- projectTree,
763
545
  agentsMdSearch,
764
546
  git,
765
547
  skills: filteredSkills,
@@ -18,6 +18,7 @@ import type { AgentDefinition, AgentSource } from "./types";
18
18
  interface AgentFrontmatter {
19
19
  name: string;
20
20
  description: string;
21
+ tools?: string[];
21
22
  spawns?: string;
22
23
  model?: string | string[];
23
24
  thinkingLevel?: string;
@@ -54,7 +55,7 @@ const EMBEDDED_AGENT_DEFS: EmbeddedAgentDef[] = [
54
55
  fileName: "quick_task.md",
55
56
  frontmatter: {
56
57
  name: "quick_task",
57
- description: "Quick task for fast execution",
58
+ description: "Low-reasoning agent for strictly mechanical updates or data collection only",
58
59
  model: "pi/smol",
59
60
  },
60
61
  template: taskMd,
@@ -103,27 +103,3 @@ export function checkBashInterception(
103
103
 
104
104
  return { block: false };
105
105
  }
106
-
107
- /**
108
- * Check if a command is a simple directory listing that should use `ls` tool.
109
- * Only applies to bare `ls` without complex flags.
110
- */
111
- export function checkSimpleLsInterception(command: string, availableTools: string[]): InterceptionResult {
112
- if (!availableTools.includes("ls")) {
113
- return { block: false };
114
- }
115
-
116
- // Match simple ls commands (ls, ls -la, ls /path, etc.)
117
- // Don't intercept complex pipes or commands
118
- const simpleLsPattern = /^\s*ls(\s+(-[a-zA-Z]+\s*)*)?(\s+[^|;&]+)?\s*$/;
119
-
120
- if (simpleLsPattern.test(command.trim())) {
121
- return {
122
- block: true,
123
- message: `Use the \`ls\` tool instead of bash ls. It provides structured output.\n\nOriginal command: ${command}`,
124
- suggestedTool: "ls",
125
- };
126
- }
127
-
128
- return { block: false };
129
- }