@oh-my-pi/pi-coding-agent 16.2.7 → 16.2.9

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 (62) hide show
  1. package/CHANGELOG.md +53 -0
  2. package/dist/cli.js +3661 -3482
  3. package/dist/types/config/settings-schema.d.ts +97 -13
  4. package/dist/types/edit/hashline/filesystem.d.ts +1 -0
  5. package/dist/types/modes/components/mcp-add-wizard.d.ts +8 -0
  6. package/dist/types/modes/controllers/mcp-command-controller.d.ts +9 -0
  7. package/dist/types/session/agent-session.d.ts +1 -1
  8. package/dist/types/session/session-context.d.ts +0 -5
  9. package/dist/types/session/settings-stream-fn.d.ts +2 -2
  10. package/dist/types/stt/index.d.ts +1 -0
  11. package/dist/types/stt/stt-controller.d.ts +2 -0
  12. package/dist/types/stt/submit-trigger.d.ts +30 -0
  13. package/dist/types/task/index.d.ts +1 -1
  14. package/dist/types/task/types.d.ts +6 -6
  15. package/dist/types/tiny/models.d.ts +22 -8
  16. package/dist/types/tools/output-schema-validator.d.ts +10 -0
  17. package/package.json +12 -12
  18. package/src/commit/agentic/agent.ts +1 -1
  19. package/src/commit/agentic/prompts/system.md +1 -1
  20. package/src/commit/agentic/tools/analyze-file.ts +2 -2
  21. package/src/config/settings-schema.ts +54 -2
  22. package/src/debug/profiler.ts +7 -1
  23. package/src/discovery/builtin-rules/go-add-cleanup.md +32 -0
  24. package/src/discovery/builtin-rules/go-bench-loop.md +36 -0
  25. package/src/discovery/builtin-rules/go-exp-promoted.md +39 -0
  26. package/src/discovery/builtin-rules/go-ioutil.md +36 -0
  27. package/src/discovery/builtin-rules/go-join-hostport.md +29 -0
  28. package/src/discovery/builtin-rules/go-new-expr.md +44 -0
  29. package/src/discovery/builtin-rules/go-rand-v2.md +40 -0
  30. package/src/discovery/builtin-rules/go-range-int.md +45 -0
  31. package/src/discovery/builtin-rules/index.ts +16 -0
  32. package/src/edit/hashline/filesystem.ts +12 -0
  33. package/src/internal-urls/docs-index.generated.txt +1 -1
  34. package/src/mcp/oauth-flow.ts +35 -8
  35. package/src/modes/components/mcp-add-wizard.ts +43 -3
  36. package/src/modes/components/model-selector.ts +21 -9
  37. package/src/modes/controllers/event-controller.ts +9 -0
  38. package/src/modes/controllers/mcp-command-controller.ts +84 -3
  39. package/src/modes/controllers/tool-args-reveal.ts +1 -1
  40. package/src/modes/interactive-mode.ts +5 -4
  41. package/src/prompts/agents/tester.md +107 -0
  42. package/src/prompts/system/orchestrate-notice.md +2 -2
  43. package/src/prompts/system/system-prompt.md +2 -5
  44. package/src/prompts/system/thinking-loop-redirect.md +10 -0
  45. package/src/prompts/system/workflow-notice.md +1 -1
  46. package/src/prompts/tools/bash.md +1 -4
  47. package/src/prompts/tools/task.md +2 -9
  48. package/src/session/agent-session.ts +161 -24
  49. package/src/session/session-context.ts +13 -2
  50. package/src/session/settings-stream-fn.ts +12 -2
  51. package/src/stt/index.ts +1 -0
  52. package/src/stt/stt-controller.ts +31 -2
  53. package/src/stt/submit-trigger.ts +74 -0
  54. package/src/task/agents.ts +4 -4
  55. package/src/task/executor.ts +1 -1
  56. package/src/task/index.ts +18 -5
  57. package/src/task/types.ts +5 -5
  58. package/src/tiny/models.ts +10 -0
  59. package/src/tools/grep.ts +19 -1
  60. package/src/tools/output-schema-validator.ts +38 -0
  61. package/src/tools/yield.ts +52 -15
  62. package/src/prompts/agents/oracle.md +0 -54
@@ -11,11 +11,11 @@ import exploreMd from "../prompts/agents/explore.md" with { type: "text" };
11
11
  // Embed agent markdown files at build time
12
12
  import agentFrontmatterTemplate from "../prompts/agents/frontmatter.md" with { type: "text" };
13
13
  import librarianMd from "../prompts/agents/librarian.md" with { type: "text" };
14
- import oracleMd from "../prompts/agents/oracle.md" with { type: "text" };
15
14
 
16
15
  import planMd from "../prompts/agents/plan.md" with { type: "text" };
17
16
  import reviewerMd from "../prompts/agents/reviewer.md" with { type: "text" };
18
17
  import taskMd from "../prompts/agents/task.md" with { type: "text" };
18
+ import testerMd from "../prompts/agents/tester.md" with { type: "text" };
19
19
 
20
20
  import type { AgentDefinition, AgentSource } from "./types";
21
21
 
@@ -47,7 +47,7 @@ const EMBEDDED_AGENT_DEFS: EmbeddedAgentDef[] = [
47
47
  { fileName: "designer.md", template: designerMd },
48
48
  { fileName: "reviewer.md", template: reviewerMd },
49
49
  { fileName: "librarian.md", template: librarianMd },
50
- { fileName: "oracle.md", template: oracleMd },
50
+ { fileName: "tester.md", template: testerMd },
51
51
  {
52
52
  fileName: "task.md",
53
53
  frontmatter: {
@@ -59,9 +59,9 @@ const EMBEDDED_AGENT_DEFS: EmbeddedAgentDef[] = [
59
59
  template: taskMd,
60
60
  },
61
61
  {
62
- fileName: "quick_task.md",
62
+ fileName: "sonic.md",
63
63
  frontmatter: {
64
- name: "quick_task",
64
+ name: "sonic",
65
65
  description: "Low-reasoning agent for strictly mechanical updates or data collection only",
66
66
  model: "pi/smol",
67
67
  thinkingLevel: Effort.Medium,
@@ -87,7 +87,7 @@ const MCP_CALL_TIMEOUT_MS = 60_000;
87
87
  */
88
88
  export const SOFT_REQUEST_BUDGET: Record<string, number> = {
89
89
  explore: 40,
90
- quick_task: 40,
90
+ sonic: 40,
91
91
  default: 90,
92
92
  };
93
93
 
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) {
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",
package/src/tools/grep.ts CHANGED
@@ -85,8 +85,26 @@ const searchSchema = type({
85
85
  });
86
86
 
87
87
  export type GrepToolInput = typeof searchSchema.infer;
88
+ function parseStringEncodedPathArray(input: string): string[] | null {
89
+ const trimmed = input.trim();
90
+ if (!trimmed.startsWith("[") || !trimmed.endsWith("]")) return null;
91
+
92
+ let parsed: unknown;
93
+ try {
94
+ parsed = JSON.parse(trimmed);
95
+ } catch {
96
+ return null;
97
+ }
98
+
99
+ if (!Array.isArray(parsed) || parsed.some(entry => typeof entry !== "string")) {
100
+ return null;
101
+ }
102
+ return parsed;
103
+ }
104
+
88
105
  export function toPathList(input: string | string[] | undefined): string[] {
89
- return typeof input === "string" ? [input] : (input ?? []);
106
+ if (typeof input === "string") return parseStringEncodedPathArray(input) ?? [input];
107
+ return input ?? [];
90
108
  }
91
109
 
92
110
  /** Maximum number of distinct files surfaced in a single response. The
@@ -21,6 +21,16 @@ export interface OutputValidator {
21
21
  validate(value: unknown): JsonSchemaValidationResult;
22
22
  /** Top-level required property names. Empty if the schema has no `required` array at root. */
23
23
  readonly requiredFields: readonly string[];
24
+ /**
25
+ * Per-label validators for incremental yields (`type: ["<label>"]`). Each entry validates the
26
+ * `data` payload of a single section against the matching top-level property's sub-schema —
27
+ * array-typed properties (e.g. `findings`) use the items schema since each yield contributes
28
+ * one element, while scalar properties use the property schema directly. Unknown labels (not
29
+ * top-level properties) have no entry and skip per-call validation. Lets the yield tool give
30
+ * the model retry feedback on a section as soon as it arrives, instead of deferring every
31
+ * mismatch to the parent's post-mortem `schema_violation`.
32
+ */
33
+ readonly validateSection: ReadonlyMap<string, (value: unknown) => JsonSchemaValidationResult>;
24
34
  }
25
35
 
26
36
  export interface BuildOutputValidatorResult {
@@ -72,10 +82,38 @@ export function buildOutputValidator(schema: unknown): BuildOutputValidatorResul
72
82
  validator: {
73
83
  requiredFields: required,
74
84
  validate: value => validateJsonSchemaValue(jsonSchemaRecord, value),
85
+ validateSection: buildSectionValidators(jsonSchemaRecord),
75
86
  },
76
87
  };
77
88
  }
78
89
 
90
+ /**
91
+ * Build per-top-level-property validators for incremental yields.
92
+ *
93
+ * Each entry validates the `data` payload of one `type: ["<label>"]` section against the
94
+ * matching property's sub-schema — array-typed properties (e.g. `findings`, derived from JTD
95
+ * `elements`) use the items schema since each yield contributes one element, while scalar
96
+ * properties use the property schema directly. Unknown labels (anything not declared as a
97
+ * top-level property) are deliberately omitted so user-defined section labels still pass.
98
+ */
99
+ function buildSectionValidators(
100
+ jsonSchema: Record<string, unknown>,
101
+ ): ReadonlyMap<string, (value: unknown) => JsonSchemaValidationResult> {
102
+ const validators = new Map<string, (value: unknown) => JsonSchemaValidationResult>();
103
+ const properties = jsonSchema.properties;
104
+ if (properties === null || typeof properties !== "object") return validators;
105
+ for (const [label, raw] of Object.entries(properties as Record<string, unknown>)) {
106
+ if (raw === null || typeof raw !== "object") continue;
107
+ const propRecord = raw as Record<string, unknown>;
108
+ const sectionSchema =
109
+ propRecord.type === "array" && propRecord.items !== undefined && propRecord.items !== null
110
+ ? (propRecord.items as Record<string, unknown>)
111
+ : propRecord;
112
+ validators.set(label, value => validateJsonSchemaValue(sectionSchema, value));
113
+ }
114
+ return validators;
115
+ }
116
+
79
117
  /** Produce the executor's headline+missing-required summary from a failed validation. */
80
118
  export function summarizeValidationFailure(
81
119
  result: JsonSchemaValidationResult,
@@ -98,6 +98,16 @@ function parseYieldType(value: unknown): string | string[] | undefined {
98
98
  throw new Error("type must be a string or non-empty array of strings");
99
99
  }
100
100
 
101
+ /**
102
+ * Render an incremental yield's `type: [...]` labels as a quoted, comma-separated list for
103
+ * model-facing retry messages — keeps the failed section labelled even when the yield carried
104
+ * multiple labels at once.
105
+ */
106
+ function formatYieldLabels(labels: readonly string[]): string {
107
+ if (labels.length === 0) return '""';
108
+ return labels.map(label => `"${label}"`).join(", ");
109
+ }
110
+
101
111
  /**
102
112
  * Expand a plain-object `data` schema into a strict union that ALSO accepts each
103
113
  * top-level section value (and array element) on its own. Agents that yield
@@ -202,10 +212,12 @@ export class YieldTool implements AgentTool<TSchema, YieldDetails> {
202
212
  lenientArgValidation = true;
203
213
 
204
214
  readonly #validate?: (value: unknown) => JsonSchemaValidationResult;
215
+ readonly #validateSection?: ReadonlyMap<string, (value: unknown) => JsonSchemaValidationResult>;
205
216
  #schemaValidationFailures = 0;
206
217
 
207
218
  constructor(session: ToolSession) {
208
219
  let validate: ((value: unknown) => JsonSchemaValidationResult) | undefined;
220
+ let validateSection: ReadonlyMap<string, (value: unknown) => JsonSchemaValidationResult> | undefined;
209
221
  let parameters: TSchema;
210
222
 
211
223
  try {
@@ -217,6 +229,7 @@ export class YieldTool implements AgentTool<TSchema, YieldDetails> {
217
229
  } = buildOutputValidator(session.outputSchema);
218
230
  if (validator) {
219
231
  validate = value => validator.validate(value);
232
+ validateSection = validator.validateSection;
220
233
  }
221
234
 
222
235
  const schemaHint = formatSchema(normalizedSchema ?? session.outputSchema);
@@ -266,6 +279,7 @@ export class YieldTool implements AgentTool<TSchema, YieldDetails> {
266
279
  }
267
280
 
268
281
  this.#validate = validate;
282
+ this.#validateSection = validateSection;
269
283
  this.parameters = parameters;
270
284
  }
271
285
 
@@ -307,22 +321,25 @@ export class YieldTool implements AgentTool<TSchema, YieldDetails> {
307
321
  if (data === null) {
308
322
  throw new Error("data is required when yield indicates success");
309
323
  }
310
- if (this.#validate && !isIncremental) {
311
- const parsed = this.#validate(data);
312
- if (!parsed.success) {
313
- this.#schemaValidationFailures++;
314
- if (this.#schemaValidationFailures <= MAX_SCHEMA_RETRIES) {
315
- const remaining = MAX_SCHEMA_RETRIES - this.#schemaValidationFailures;
316
- const retryHint =
317
- remaining > 0
318
- ? ` Call yield again with the corrected shape — ${remaining} retry attempt(s) remain before the schema constraint is dropped.`
319
- : " Call yield again with the corrected shape — this is the final retry before the schema constraint is dropped.";
320
- throw new Error(
321
- `Output does not match schema: ${formatAllValidationIssues(parsed.issues)}.${retryHint}`,
322
- );
323
- }
324
- schemaValidationOverridden = true;
324
+ const sectionFailure = isIncremental
325
+ ? this.#validateIncrementalSection(yieldType as string[], data)
326
+ : this.#validate
327
+ ? this.#validate(data)
328
+ : undefined;
329
+ if (sectionFailure && !sectionFailure.success) {
330
+ this.#schemaValidationFailures++;
331
+ if (this.#schemaValidationFailures <= MAX_SCHEMA_RETRIES) {
332
+ const remaining = MAX_SCHEMA_RETRIES - this.#schemaValidationFailures;
333
+ const retryHint =
334
+ remaining > 0
335
+ ? ` Call yield again with the corrected shape — ${remaining} retry attempt(s) remain before the schema constraint is dropped.`
336
+ : " Call yield again with the corrected shape — this is the final retry before the schema constraint is dropped.";
337
+ const scope = isIncremental ? `Section ${formatYieldLabels(yieldType as string[])}` : "Output";
338
+ throw new Error(
339
+ `${scope} does not match schema: ${formatAllValidationIssues(sectionFailure.issues)}.${retryHint}`,
340
+ );
325
341
  }
342
+ schemaValidationOverridden = true;
326
343
  }
327
344
  }
328
345
 
@@ -344,6 +361,26 @@ export class YieldTool implements AgentTool<TSchema, YieldDetails> {
344
361
  },
345
362
  };
346
363
  }
364
+
365
+ /**
366
+ * Validate the `data` payload of an incremental yield (`type: ["<label>", …]`) against
367
+ * the matching property's sub-validator. Returns the first failure across all known labels,
368
+ * or `undefined` when no label is recognised (user-defined section labels stay loose) or
369
+ * when all known labels accept the value. Lets the model see the same retry feedback that
370
+ * the terminal-yield path already produces, instead of leaking the mismatch through to
371
+ * the parent's post-mortem `schema_violation`.
372
+ */
373
+ #validateIncrementalSection(labels: string[], data: unknown): JsonSchemaValidationResult | undefined {
374
+ const subValidators = this.#validateSection;
375
+ if (!subValidators || subValidators.size === 0) return undefined;
376
+ for (const label of labels) {
377
+ const sub = subValidators.get(label);
378
+ if (!sub) continue;
379
+ const parsed = sub(data);
380
+ if (!parsed.success) return parsed;
381
+ }
382
+ return undefined;
383
+ }
347
384
  }
348
385
 
349
386
  // Register subprocess tool handler for extraction + termination.
@@ -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>