@oh-my-pi/pi-coding-agent 16.2.8 → 16.2.11

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 (67) hide show
  1. package/CHANGELOG.md +59 -0
  2. package/dist/cli.js +3160 -3096
  3. package/dist/types/config/settings-schema.d.ts +41 -13
  4. package/dist/types/extensibility/skills.d.ts +29 -0
  5. package/dist/types/modes/components/mcp-add-wizard.d.ts +8 -0
  6. package/dist/types/modes/components/todo-reminder.d.ts +3 -1
  7. package/dist/types/modes/controllers/mcp-command-controller.d.ts +9 -0
  8. package/dist/types/modes/controllers/tool-args-reveal.d.ts +5 -0
  9. package/dist/types/modes/interactive-mode.d.ts +0 -1
  10. package/dist/types/modes/skill-command.d.ts +1 -1
  11. package/dist/types/modes/types.d.ts +0 -1
  12. package/dist/types/session/agent-session.d.ts +1 -1
  13. package/dist/types/stt/asr-client.d.ts +7 -3
  14. package/dist/types/stt/index.d.ts +1 -0
  15. package/dist/types/stt/stt-controller.d.ts +2 -0
  16. package/dist/types/stt/submit-trigger.d.ts +30 -0
  17. package/dist/types/task/index.d.ts +1 -1
  18. package/dist/types/task/types.d.ts +6 -6
  19. package/dist/types/tiny/models.d.ts +22 -8
  20. package/package.json +14 -13
  21. package/scripts/bundle-dist.ts +23 -4
  22. package/scripts/generate-docs-index.ts +116 -24
  23. package/src/async/job-manager.ts +27 -3
  24. package/src/cli/grep-cli.ts +1 -1
  25. package/src/commit/agentic/agent.ts +1 -1
  26. package/src/commit/agentic/prompts/system.md +1 -1
  27. package/src/commit/agentic/tools/analyze-file.ts +2 -2
  28. package/src/config/model-discovery.ts +118 -76
  29. package/src/config/settings-schema.ts +15 -1
  30. package/src/debug/profiler.ts +7 -1
  31. package/src/extensibility/skills.ts +77 -0
  32. package/src/internal-urls/docs-index.generated.txt +2 -2
  33. package/src/lsp/config.ts +17 -3
  34. package/src/mcp/oauth-flow.ts +35 -8
  35. package/src/modes/acp/acp-agent.ts +6 -9
  36. package/src/modes/components/mcp-add-wizard.ts +43 -3
  37. package/src/modes/components/model-selector.ts +21 -9
  38. package/src/modes/components/todo-reminder.ts +5 -1
  39. package/src/modes/controllers/event-controller.ts +40 -15
  40. package/src/modes/controllers/mcp-command-controller.ts +84 -3
  41. package/src/modes/controllers/selector-controller.ts +57 -35
  42. package/src/modes/controllers/tool-args-reveal.ts +12 -0
  43. package/src/modes/interactive-mode.ts +5 -10
  44. package/src/modes/rpc/rpc-mode.ts +5 -8
  45. package/src/modes/skill-command.ts +8 -20
  46. package/src/modes/types.ts +0 -1
  47. package/src/prompts/agents/tester.md +107 -0
  48. package/src/prompts/system/orchestrate-notice.md +2 -2
  49. package/src/prompts/system/system-prompt.md +2 -5
  50. package/src/prompts/system/thinking-loop-redirect.md +10 -0
  51. package/src/prompts/system/workflow-notice.md +1 -1
  52. package/src/prompts/tools/task.md +2 -9
  53. package/src/session/agent-session.ts +53 -18
  54. package/src/stt/asr-client.ts +87 -27
  55. package/src/stt/downloader.ts +8 -2
  56. package/src/stt/index.ts +1 -0
  57. package/src/stt/stt-controller.ts +31 -2
  58. package/src/stt/submit-trigger.ts +74 -0
  59. package/src/task/agents.ts +4 -4
  60. package/src/task/executor.ts +2 -4
  61. package/src/task/index.ts +32 -10
  62. package/src/task/types.ts +5 -5
  63. package/src/tiny/models.ts +10 -0
  64. package/src/tools/ast-grep.ts +34 -12
  65. package/src/tools/grep.ts +11 -8
  66. package/src/utils/git.ts +22 -1
  67. package/src/prompts/agents/oracle.md +0 -54
package/src/task/index.ts CHANGED
@@ -243,8 +243,11 @@ function validateShapeParams(batchEnabled: boolean, params: TaskParams): string
243
243
  }
244
244
 
245
245
  /**
246
- * Validate the spawn parameter contract against the wire shapes. `agent` is
247
- * always required. With `task.batch` the model-facing shape is
246
+ * Validate the spawn parameter contract against the wire shapes. `agent`
247
+ * defaults to `task` (the schema default; `execute` normalizes the same way for
248
+ * direct callers), so the missing-`agent` guard only fires for callers that
249
+ * invoke this validator with an unnormalized blank agent. With `task.batch` the
250
+ * model-facing shape is
248
251
  * `{ agent, context, tasks[] }` — `tasks` non-empty with per-item assignments
249
252
  * and unique ids, `context` non-empty, no top-level `assignment` alongside.
250
253
  * The flat `{ agent, ...item }` form stays accepted at runtime under either
@@ -328,14 +331,17 @@ function spawnParamsFor(params: TaskParams, item: TaskItem): TaskParams {
328
331
  return spawn;
329
332
  }
330
333
 
334
+ /** Agent type spawned when a `task` call omits `agent`; mirrors the schema default in `getTaskSchema`. */
335
+ const DEFAULT_TASK_AGENT = "task";
336
+
331
337
  /** Generic worker agents whose output sharpens with a tailored `role` rather than the bare type. */
332
- const GENERIC_SPAWN_AGENTS: ReadonlySet<string> = new Set(["task", "quick_task"]);
338
+ const GENERIC_SPAWN_AGENTS: ReadonlySet<string> = new Set(["task", "sonic"]);
333
339
 
334
340
  /**
335
341
  * Advisory — never a rejection — nudging the spawner toward tailored
336
342
  * specialists when it spawns generic role-less workers and still holds spawn
337
343
  * capacity (DepthCapacity: it currently has the `task` tool). Fires when a
338
- * generic `task`/`quick_task` spawn carries no `role`, or when one call clones
344
+ * generic `task`/`sonic` spawn carries no `role`, or when one call clones
339
345
  * the same agent ≥2× all without roles. Returns undefined when no nudge applies.
340
346
  */
341
347
  export function buildSpecializationAdvisory(
@@ -559,7 +565,14 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
559
565
  signal?: AbortSignal,
560
566
  onUpdate?: AgentToolUpdateCallback<TaskToolDetails>,
561
567
  ): Promise<AgentToolResult<TaskToolDetails>> {
562
- const params = repairTaskParams(rawParams as TaskParams);
568
+ const repaired = repairTaskParams(rawParams as TaskParams);
569
+ // The schema defaults `agent` to `task` for model calls, but internal
570
+ // callers and stale transcripts build params directly and bypass arktype.
571
+ // Normalize once here so every downstream path sees the resolved agent.
572
+ const params =
573
+ typeof repaired.agent === "string" && repaired.agent.trim() !== ""
574
+ ? repaired
575
+ : { ...repaired, agent: DEFAULT_TASK_AGENT };
563
576
  const batchEnabled = this.#isBatchEnabled();
564
577
  const validationError = validateShapeParams(batchEnabled, params) ?? validateSpawnParams(params, batchEnabled);
565
578
  if (validationError) {
@@ -790,10 +803,19 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
790
803
  async ({ jobId: ownJobId, signal: runSignal, reportProgress, markRunning }) => {
791
804
  const startedAt = Date.now();
792
805
  const semaphore = this.#getSpawnSemaphore();
793
- await semaphore.acquire();
806
+ let semaphoreHeld = false;
807
+ try {
808
+ await semaphore.acquire(runSignal);
809
+ semaphoreHeld = true;
810
+ } catch {
811
+ // Fall through so an acquire-time abort goes through the same
812
+ // path as the post-acquire race below: progress + onSettled
813
+ // have to fire even when the spawn never reached the executor,
814
+ // otherwise the batch aggregate state stays "running" forever.
815
+ }
794
816
  const acquiredAt = Date.now();
795
- if (runSignal.aborted) {
796
- semaphore.release();
817
+ if (!semaphoreHeld || runSignal.aborted) {
818
+ if (semaphoreHeld) semaphore.release();
797
819
  progress.status = "aborted";
798
820
  onSettled?.(true);
799
821
  throw new Error("Aborted before execution");
@@ -896,7 +918,7 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
896
918
  const semaphore = this.#getSpawnSemaphore();
897
919
  if (spawnItems.length === 1) {
898
920
  const invokedAt = Date.now();
899
- await semaphore.acquire();
921
+ await semaphore.acquire(signal);
900
922
  const acquiredAt = Date.now();
901
923
  try {
902
924
  return await this.#executeSync(
@@ -935,7 +957,7 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
935
957
  spawnItems.length,
936
958
  async (item, index, workerSignal) => {
937
959
  const invokedAt = Date.now();
938
- await semaphore.acquire();
960
+ await semaphore.acquire(workerSignal);
939
961
  const acquiredAt = Date.now();
940
962
  try {
941
963
  const itemOnUpdate: AgentToolUpdateCallback<TaskToolDetails> | undefined = onUpdate
package/src/task/types.ts CHANGED
@@ -111,7 +111,7 @@ export interface TaskItem {
111
111
  }
112
112
 
113
113
  export const taskSchema = type({
114
- agent: "string",
114
+ agent: "string = 'task'",
115
115
  "id?": "string",
116
116
  "description?": "string",
117
117
  "role?": ROLE_INPUT_SCHEMA,
@@ -120,7 +120,7 @@ export const taskSchema = type({
120
120
  "+": "delete",
121
121
  });
122
122
  const taskSchemaNoIsolation = type({
123
- agent: "string",
123
+ agent: "string = 'task'",
124
124
  "id?": "string",
125
125
  "description?": "string",
126
126
  "role?": ROLE_INPUT_SCHEMA,
@@ -128,13 +128,13 @@ const taskSchemaNoIsolation = type({
128
128
  "+": "delete",
129
129
  });
130
130
  const taskSchemaBatch = type({
131
- agent: "string",
131
+ agent: "string = 'task'",
132
132
  context: "string",
133
133
  tasks: taskItemSchemaIsolated.array(),
134
134
  "+": "delete",
135
135
  });
136
136
  const taskSchemaBatchNoIsolation = type({
137
- agent: "string",
137
+ agent: "string = 'task'",
138
138
  context: "string",
139
139
  tasks: taskItemSchema.array(),
140
140
  "+": "delete",
@@ -160,7 +160,7 @@ export function getTaskSchema(options: { isolationEnabled: boolean; batchEnabled
160
160
  * transcripts using the flat form keep working under either setting.
161
161
  */
162
162
  export interface TaskParams {
163
- /** Agent type; required. */
163
+ /** Agent type to spawn; defaults to `"task"` (the general-purpose worker) when omitted. */
164
164
  agent?: string;
165
165
  /** Stable agent id (flat form); default = generated AdjectiveNoun. */
166
166
  id?: string;
@@ -132,6 +132,15 @@ export const TINY_MEMORY_LOCAL_MODELS = [
132
132
  unsupportedReason:
133
133
  "onnxruntime-node does not support Qwen3 RotaryEmbedding cache updates in onnx-community/Qwen3-1.7B-ONNX",
134
134
  },
135
+ {
136
+ key: "llama3.2:3b",
137
+ repo: "onnx-community/Llama-3.2-3B-Instruct-ONNX",
138
+ dtype: "q4",
139
+ label: "Llama 3.2 3B",
140
+ description:
141
+ "Larger Llama 3.2 option for local memory/classifier tasks; higher quality potential at higher disk/RAM/latency cost.",
142
+ contextNote: "Use when larger model capacity is preferred over faster load times.",
143
+ },
135
144
  {
136
145
  key: "gemma-3-1b",
137
146
  repo: "onnx-community/gemma-3-1b-it-ONNX",
@@ -161,6 +170,7 @@ export const TINY_MEMORY_LOCAL_MODELS = [
161
170
  export const TINY_MEMORY_MODEL_VALUES = [
162
171
  ONLINE_MEMORY_MODEL_KEY,
163
172
  "qwen3-1.7b",
173
+ "llama3.2:3b",
164
174
  "gemma-3-1b",
165
175
  "qwen2.5-1.5b",
166
176
  "lfm2-1.2b",
@@ -44,6 +44,33 @@ const astGrepSchema = type({
44
44
  "skip?": type("number").describe("matches to skip"),
45
45
  });
46
46
 
47
+ function compareAstFindMatch(left: AstFindMatch, right: AstFindMatch): number {
48
+ const pathCmp = left.path.localeCompare(right.path);
49
+ if (pathCmp !== 0) return pathCmp;
50
+ if (left.startLine !== right.startLine) return left.startLine - right.startLine;
51
+ if (left.startColumn !== right.startColumn) return left.startColumn - right.startColumn;
52
+ if (left.endLine !== right.endLine) return left.endLine - right.endLine;
53
+ if (left.endColumn !== right.endColumn) return left.endColumn - right.endColumn;
54
+ if (left.byteStart !== right.byteStart) return left.byteStart - right.byteStart;
55
+ return left.byteEnd - right.byteEnd;
56
+ }
57
+
58
+ function retainAstFindMatch(matches: AstFindMatch[], capacity: number, candidate: AstFindMatch): void {
59
+ if (matches.length < capacity) {
60
+ matches.push(candidate);
61
+ return;
62
+ }
63
+ let worstIndex = 0;
64
+ for (let index = 1; index < matches.length; index++) {
65
+ if (compareAstFindMatch(matches[index]!, matches[worstIndex]!) > 0) {
66
+ worstIndex = index;
67
+ }
68
+ }
69
+ if (compareAstFindMatch(candidate, matches[worstIndex]!) < 0) {
70
+ matches[worstIndex] = candidate;
71
+ }
72
+ }
73
+
47
74
  async function runMultiTargetAstGrep(
48
75
  targets: Array<{ basePath: string; glob?: string }>,
49
76
  options: { patterns: string[]; commonBasePath: string; skip: number; limit: number; signal?: AbortSignal },
@@ -55,9 +82,11 @@ async function runMultiTargetAstGrep(
55
82
  limitReached: boolean;
56
83
  parseErrors?: string[];
57
84
  }> {
58
- const aggregatedMatches: AstFindMatch[] = [];
85
+ const retainedMatches: AstFindMatch[] = [];
86
+ const retainedCapacity = options.skip + options.limit + 1;
59
87
  const parseErrors: string[] = [];
60
88
  let totalMatches = 0;
89
+ let filesWithMatches = 0;
61
90
  let filesSearched = 0;
62
91
  let limitReached = false;
63
92
  for (const target of targets) {
@@ -71,26 +100,19 @@ async function runMultiTargetAstGrep(
71
100
  signal: options.signal,
72
101
  });
73
102
  totalMatches += targetResult.totalMatches;
103
+ filesWithMatches += targetResult.filesWithMatches;
74
104
  filesSearched += targetResult.filesSearched;
75
105
  limitReached = limitReached || targetResult.limitReached;
76
106
  if (targetResult.parseErrors) parseErrors.push(...targetResult.parseErrors);
77
107
  for (const match of targetResult.matches) {
78
108
  const absolute = path.resolve(target.basePath, match.path);
79
109
  const rebased = path.relative(options.commonBasePath, absolute).replace(/\\/g, "/");
80
- aggregatedMatches.push({ ...match, path: rebased });
110
+ retainAstFindMatch(retainedMatches, retainedCapacity, { ...match, path: rebased });
81
111
  }
82
112
  }
83
- aggregatedMatches.sort((left, right) => {
84
- const pathCmp = left.path.localeCompare(right.path);
85
- if (pathCmp !== 0) return pathCmp;
86
- if (left.startLine !== right.startLine) return left.startLine - right.startLine;
87
- if (left.startColumn !== right.startColumn) return left.startColumn - right.startColumn;
88
- if (left.byteStart !== right.byteStart) return left.byteStart - right.byteStart;
89
- return left.byteEnd - right.byteEnd;
90
- });
91
- const visible = aggregatedMatches.slice(options.skip);
113
+ retainedMatches.sort(compareAstFindMatch);
114
+ const visible = retainedMatches.slice(options.skip);
92
115
  const paged = visible.slice(0, options.limit);
93
- const filesWithMatches = new Set(aggregatedMatches.map(match => match.path)).size;
94
116
  return {
95
117
  matches: paged,
96
118
  totalMatches,
package/src/tools/grep.ts CHANGED
@@ -122,8 +122,10 @@ export const SINGLE_FILE_MATCHES = 200;
122
122
  * pagination headroom so the caller can see total file count. */
123
123
  const INTERNAL_TOTAL_CAP = 2000;
124
124
  /** Mirrors `MAX_FILE_BYTES` in `crates/pi-natives/src/grep.rs`. Native grep
125
- * silently returns no matches for files larger than this; surface a warning
126
- * when the caller explicitly targeted such a file so they know to chunk it. */
125
+ * searches only the first `MAX_FILE_BYTES` of a larger file (a leading mmap
126
+ * window) and drops the rest; matches beyond the window are not returned. We
127
+ * surface a partial-coverage note when the caller explicitly targeted such a
128
+ * file so they know matches past the window are not shown. */
127
129
  const NATIVE_GREP_MAX_FILE_BYTES = 4 * 1024 * 1024;
128
130
  /** Wall-clock budget for a single native grep invocation. Without it, an
129
131
  * aborted or runaway search (huge tree, network mount) keeps burning CPU on
@@ -1260,8 +1262,9 @@ export class GrepTool implements AgentTool<typeof searchSchema, GrepToolDetails>
1260
1262
  const { record: recordFile, list: fileList } = createFileRecorder();
1261
1263
  const fileMatchCounts = new Map<string, number>();
1262
1264
  // Detect explicit file targets that exceed the native grep size cap.
1263
- // Native silently returns no matches above the cap; without this note the
1264
- // caller sees "no matches" for a literal pattern that visibly exists.
1265
+ // Native searches only their first NATIVE_GREP_MAX_FILE_BYTES; without
1266
+ // this note the caller might miss that matches beyond the window
1267
+ // (or "no matches") reflect partial coverage, not the whole file.
1265
1268
  const oversizedNote = await (async (): Promise<string | undefined> => {
1266
1269
  const explicitFileTargets: string[] = [];
1267
1270
  if (exactFilePaths) {
@@ -1285,13 +1288,13 @@ export class GrepTool implements AgentTool<typeof searchSchema, GrepToolDetails>
1285
1288
  );
1286
1289
  if (oversized.length === 0) return undefined;
1287
1290
  const limitMb = Math.floor(NATIVE_GREP_MAX_FILE_BYTES / (1024 * 1024));
1288
- return `Skipped oversized files (>${limitMb}MB grep limit; split the file or narrow with \`read\`): ${oversized.join(", ")}`;
1291
+ return `Searched only the first ${limitMb}MB of large files (matches past the ${limitMb}MB window are not shown; use \`read\` for the rest): ${oversized.join(", ")}`;
1289
1292
  })();
1290
- // Directory/multi-target scopes: native grep counts oversized skips but
1291
- // cannot name them; explicit-file scopes are covered (with names) above.
1293
+ // Directory/multi-target scopes: native counts files it could not map
1294
+ // even a prefix of (rare mmap failures), but cannot name them.
1292
1295
  const oversizedScanNote =
1293
1296
  !oversizedNote && skippedOversizedCount > 0
1294
- ? `Skipped ${skippedOversizedCount} oversized file(s) (>${Math.floor(NATIVE_GREP_MAX_FILE_BYTES / (1024 * 1024))}MB grep limit); target them directly with \`read\``
1297
+ ? `Skipped ${skippedOversizedCount} unreadable large file(s); target them directly with \`read\``
1295
1298
  : undefined;
1296
1299
  const archiveNote =
1297
1300
  archiveUnreadable.length > 0
package/src/utils/git.ts CHANGED
@@ -175,6 +175,14 @@ const SHORT_LIVED_GIT_CONFIG: readonly (readonly [key: string, value: string])[]
175
175
  ["core.untrackedCache", "false"],
176
176
  ];
177
177
  const REMOTE_ALREADY_EXISTS = /remote .* already exists/i;
178
+ const AMBIENT_GIT_ENV = {
179
+ GIT_DIR: undefined,
180
+ GIT_COMMON_DIR: undefined,
181
+ GIT_WORK_TREE: undefined,
182
+ GIT_INDEX_FILE: undefined,
183
+ GIT_OBJECT_DIRECTORY: undefined,
184
+ GIT_ALTERNATE_OBJECT_DIRECTORIES: undefined,
185
+ } satisfies Record<string, undefined>;
178
186
 
179
187
  interface CommandOptions {
180
188
  readonly env?: Record<string, string | undefined>;
@@ -190,6 +198,15 @@ function normalizeStdin(input: CommandOptions["stdin"]): "ignore" | Uint8Array {
190
198
  return new Uint8Array(input);
191
199
  }
192
200
 
201
+ function buildGitEnv(overrides?: Record<string, string | undefined>): Record<string, string | undefined> {
202
+ return {
203
+ ...process.env,
204
+ GIT_OPTIONAL_LOCKS: "0",
205
+ ...AMBIENT_GIT_ENV,
206
+ ...overrides,
207
+ };
208
+ }
209
+
193
210
  function ensureAvailable(): void {
194
211
  if (!$which("git")) {
195
212
  throw new Error("git is not installed.");
@@ -211,7 +228,7 @@ async function git(cwd: string, args: readonly string[], options: CommandOptions
211
228
  const commandArgs = withShortLivedGitConfig(options.readOnly ? withNoOptionalLocks(args) : [...args]);
212
229
  const child = Bun.spawn(["git", ...commandArgs], {
213
230
  cwd,
214
- env: options.env ? { ...process.env, GIT_OPTIONAL_LOCKS: "0", ...options.env } : undefined,
231
+ env: buildGitEnv(options.env),
215
232
  signal: options.signal,
216
233
  stdin: normalizeStdin(options.stdin),
217
234
  stdout: "pipe",
@@ -707,6 +724,7 @@ function resolveHeadStateReftableSync(repository: GitRepository): GitHeadState |
707
724
  const symArgs = withShortLivedGitConfig(withNoOptionalLocks(["symbolic-ref", "HEAD"]));
708
725
  const symResult = Bun.spawnSync(["git", ...symArgs], {
709
726
  cwd: repository.repoRoot,
727
+ env: buildGitEnv(),
710
728
  stdout: "pipe",
711
729
  stderr: "pipe",
712
730
  windowsHide: true,
@@ -715,6 +733,7 @@ function resolveHeadStateReftableSync(repository: GitRepository): GitHeadState |
715
733
  const revArgs = withShortLivedGitConfig(withNoOptionalLocks(["rev-parse", "--verify", "HEAD"]));
716
734
  const revResult = Bun.spawnSync(["git", ...revArgs], {
717
735
  cwd: repository.repoRoot,
736
+ env: buildGitEnv(),
718
737
  stdout: "pipe",
719
738
  stderr: "pipe",
720
739
  windowsHide: true,
@@ -748,6 +767,7 @@ function readRefSync(repository: GitRepository, targetRef: string): string | nul
748
767
  const symArgs = withShortLivedGitConfig(withNoOptionalLocks(["symbolic-ref", targetRef]));
749
768
  const symResult = Bun.spawnSync(["git", ...symArgs], {
750
769
  cwd: repository.repoRoot,
770
+ env: buildGitEnv(),
751
771
  stdout: "pipe",
752
772
  stderr: "pipe",
753
773
  windowsHide: true,
@@ -759,6 +779,7 @@ function readRefSync(repository: GitRepository, targetRef: string): string | nul
759
779
  const revArgs = withShortLivedGitConfig(withNoOptionalLocks(["rev-parse", "--verify", targetRef]));
760
780
  const revResult = Bun.spawnSync(["git", ...revArgs], {
761
781
  cwd: repository.repoRoot,
782
+ env: buildGitEnv(),
762
783
  stdout: "pipe",
763
784
  stderr: "pipe",
764
785
  windowsHide: true,
@@ -1,54 +0,0 @@
1
- ---
2
- name: oracle
3
- description: Wise senior engineer to consult or delegate work to — debugging, architecture, second opinions, and hands-on implementation when asked.
4
- spawns: explore
5
- model: pi/slow
6
- thinking-level: xhigh
7
- ---
8
-
9
- You are the wise guy on the team — a senior engineer with deep judgment that other agents consult when they are stuck, uncertain, or need a second opinion. You also take direct delegation: if the caller hands you work, you do it, including reads, writes, edits, and running commands.
10
-
11
- You diagnose, decide, and execute. You match the mode to the ask:
12
- - **Consult**: explain the root cause, lay out tradeoffs, recommend a path.
13
- - **Delegate**: carry the work to completion — modify files, run verification, deliver a finished change.
14
-
15
- <directives>
16
- - You MUST reason from first principles. The caller already tried the obvious.
17
- - You MUST use tools to verify claims. You NEVER speculate about code behavior — read it.
18
- - You MUST identify root causes, not symptoms. If the caller says "X is broken", determine *why* X is broken.
19
- - You MUST surface hidden assumptions — in the code, in the caller's framing, in the environment.
20
- - You SHOULD consider at least two hypotheses before converging on one.
21
- - You SHOULD invoke tools in parallel when investigating multiple hypotheses.
22
- - When the problem is architectural, you MUST weigh tradeoffs explicitly: what does each option cost, what does it buy, what does it foreclose.
23
- - When delegated implementation work, you MUST finish it: edit the files, run the relevant tests/checks, and report exactly what changed.
24
- </directives>
25
-
26
- <decision-framework>
27
- Apply pragmatic minimalism:
28
- - **Bias toward simplicity**: The right solution is the least complex one that fulfills actual requirements. Resist hypothetical future needs.
29
- - **Leverage what exists**: Favor modifications to current code and established patterns over introducing new components. New dependencies or infrastructure require explicit justification.
30
- - **One clear path**: Present a single primary recommendation. Mention alternatives only when they offer substantially different tradeoffs worth considering.
31
- - **Match depth to complexity**: Quick questions get quick answers. Reserve thorough analysis for genuinely complex problems.
32
- - **Signal the investment**: Tag recommendations with estimated effort — Quick (<1h), Short (1-4h), Medium (1-2d), Large (3d+).
33
- </decision-framework>
34
-
35
- <procedure>
36
- 1. Read the problem statement carefully. Identify what was already tried, what failed, and whether the caller wants advice or execution.
37
- 2. Form 2-3 hypotheses for the root cause (for diagnosis) or 2-3 viable approaches (for design).
38
- 3. Use tools to gather evidence — read relevant code, trace data flow, check types, search for related patterns. Parallelize independent reads.
39
- 4. Eliminate hypotheses based on evidence. Narrow to the most likely cause or best approach.
40
- 5. If consulting: deliver verdict with supporting evidence and a concrete recommendation.
41
- 6. If implementing: make the changes, verify them, and report the diff and verification result.
42
- </procedure>
43
-
44
- <scope-discipline>
45
- - Do ONLY what was asked. No unsolicited refactors or improvements.
46
- - If you notice other issues, list at most 2 as "Optional future considerations" at the end.
47
- - You NEVER expand the problem surface beyond the original request.
48
- - Exhaust provided context before reaching for tools. External lookups fill genuine gaps, not curiosity.
49
- </scope-discipline>
50
-
51
- <critical>
52
- You MUST keep going until the problem is solved or the work is finished. Before finalizing: re-scan for unstated assumptions, verify claims are grounded in code not invented, check for overly strong language not justified by evidence.
53
- The caller came to you because they trust your judgment. Get it right.
54
- </critical>