thincoder 0.4.0 → 0.6.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.
@@ -1,9 +1,10 @@
1
1
  You are a coding subagent. The parent agent dispatched you to handle a self-contained coding task. The parent CANNOT see your context — it only sees your final report.
2
2
 
3
3
  Guidelines:
4
- - Work independently: read files, make edits, run tests
4
+ - Work independently: use repo_outline, code_search, and doc_search to find relevant code before editing. Then read, edit, and run tests.
5
5
  - Be thorough: include what you did, which files you changed, why, and any caveats
6
6
  - If the task is ambiguous, note the ambiguity in your report; do not ask the user
7
+ - It is always OK to say "this is too hard for me." Bad work is worse than no work — you will not be penalized for escalating
7
8
  - BEFORE finishing, verify your changes:
8
9
  1. Run the project's tests — confirm they pass
9
10
  2. Read every file you changed — catch leftover debug code, stale comments, or incomplete edits
package/src/config.mjs CHANGED
@@ -14,10 +14,11 @@ export const configPath = join(configDir, "config.json")
14
14
 
15
15
  /** 内置提供商预设:/provider add <预设名>、首次启动向导共用 */
16
16
  export const PROVIDER_PRESETS = {
17
- deepseek: { baseURL: "https://api.deepseek.com/v1", model: "deepseek-v4-pro", thinking: { type: "enabled" }, reasoningEffort: "max", desc: "DeepSeek" },
18
- kimi: { baseURL: "https://api.moonshot.cn/v1", model: "kimi-k3", thinking: null, reasoningEffort: "high", desc: "Kimi / Moonshot" },
19
- glm: { baseURL: "https://open.bigmodel.cn/api/paas/v4", model: "glm-5.2", thinking: { type: "enabled" }, reasoningEffort: "max", desc: "智谱 GLM" },
20
- qwen: { baseURL: "https://dashscope.aliyuncs.com/compatible-mode/v1", model: "qwen-plus", desc: "通义千问" },
17
+ deepseek: { baseURL: "https://api.deepseek.com/v1", model: "deepseek-v4-pro", thinking: { type: "enabled" }, reasoningEffort: "max", maxTokens: 393216, desc: "DeepSeek" },
18
+ kimi: { baseURL: "https://api.moonshot.cn/v1", model: "kimi-k3", thinking: null, reasoningEffort: "max", maxTokens: 131072, desc: "Kimi / Moonshot" },
19
+ glm: { baseURL: "https://open.bigmodel.cn/api/paas/v4", model: "glm-5.2", thinking: { type: "enabled" }, reasoningEffort: "max", maxTokens: 131072, desc: "智谱 GLM" },
20
+ qwen: { baseURL: "https://dashscope.aliyuncs.com/compatible-mode/v1", model: "qwen3.7-max", maxTokens: 131072, desc: "通义千问" },
21
+ minimax: { baseURL: "https://api.minimax.chat/v1", chatPath: "/text/chatcompletion_v2", model: "MiniMax-M3", maxTokens: 131072, desc: "MiniMax" },
21
22
  }
22
23
 
23
24
  // 默认 provider 跟 deepseek 预设保持一致(去掉 desc 展示字段)
@@ -45,34 +46,62 @@ const DEFAULTS = {
45
46
  }
46
47
 
47
48
  /**
48
- * 已知模型的上下文窗口(前缀匹配,长的在前)。
49
- * compactThreshold 未显式配置时,按窗口 * COMPACT_RATIO 自动推导。
49
+ * 已知模型的能力规格表(前缀匹配,长的在前)。
50
+ * 用于压缩阈值推导、截断续写协议选择、能力感知优化。
51
+ *
52
+ * context: 上下文窗口(tokens)
53
+ * maxOutput: 最大输出 tokens(默认 context)
54
+ * thinking: 是否支持思考/推理模式
55
+ * partialMode: Kimi/Qwen Partial Mode 截断续写(assistant 消息带 partial:true)
56
+ * prefixMode: DeepSeek Prefix Completion 截断续写(走 /beta 端点,带 prefix:true)
57
+ * multimodal: 是否多模态(支持图片/视觉输入)
58
+ * cacheMode: 上下文缓存方式:"auto"=自动/"prompt"=需显式/"none"=不支持
59
+ * thinkApi: 思考模式 API 类型:"type"=thinking.type 字段 / "effort"=reasoning_effort 字段
50
60
  */
51
- const MODEL_CONTEXT_WINDOWS = [
52
- ["deepseek-v4-pro", 1_000_000],
53
- ["deepseek-v4-flash", 256_000],
54
- ["deepseek-reasoner", 64_000],
55
- ["deepseek-chat", 64_000],
56
- ["kimi-k3", 256_000],
57
- ["kimi-k2", 128_000],
58
- ["moonshot", 128_000],
59
- ["glm-5", 1_000_000],
60
- ["glm-4", 128_000],
61
- ["gpt-4.1", 1_000_000],
62
- ["gpt-4o", 128_000],
63
- ["qwen", 128_000],
61
+ const MODEL_SPECS = [
62
+ // DeepSeek V4 系列
63
+ ["deepseek-v4-pro", { context: 1_000_000, maxOutput: 384_000, thinking: true, prefixMode: true, cacheMode: "prompt", thinkApi: "type" }],
64
+ ["deepseek-v4-flash", { context: 256_000, maxOutput: 384_000, thinking: false, prefixMode: true, cacheMode: "prompt", thinkApi: "type" }],
65
+ ["deepseek-reasoner", { context: 256_000, maxOutput: 384_000, thinking: true, prefixMode: true, cacheMode: "prompt", thinkApi: "type" }],
66
+ ["deepseek-chat", { context: 256_000, maxOutput: 384_000, thinking: false, prefixMode: true, cacheMode: "prompt", thinkApi: "type" }],
67
+ // Kimi 系列
68
+ ["kimi-k3", { context: 1_000_000, maxOutput: 128_000, thinking: true, partialMode: true, multimodal: true, cacheMode: "prompt", thinkApi: "effort" }],
69
+ ["kimi-k2", { context: 256_000, maxOutput: 128_000, thinking: false, partialMode: true, multimodal: true, cacheMode: "none" }],
70
+ ["moonshot", { context: 128_000, maxOutput: 32_000, thinking: false, cacheMode: "none" }],
71
+ // GLM 系列
72
+ ["glm-5.2", { context: 1_000_000, maxOutput: 128_000, thinking: true, cacheMode: "auto", thinkApi: "type" }],
73
+ ["glm-5", { context: 1_000_000, maxOutput: 128_000, thinking: true, cacheMode: "auto", thinkApi: "type" }],
74
+ ["glm-4", { context: 128_000, maxOutput: 32_000, thinking: true, cacheMode: "auto", thinkApi: "type" }],
75
+ // GPT 系列
76
+ ["gpt-4.1", { context: 1_000_000, maxOutput: 128_000, thinking: false, cacheMode: "prompt" }],
77
+ ["gpt-4o", { context: 128_000, maxOutput: 16_000, thinking: false, multimodal: true, cacheMode: "prompt" }],
78
+ // Qwen 系列
79
+ ["qwen3.7-max", { context: 1_000_000, maxOutput: 128_000, thinking: false, partialMode: true, cacheMode: "none", thinkApi: "effort" }],
80
+ ["qwen3.8-max", { context: 1_000_000, maxOutput: 128_000, thinking: false, partialMode: true, cacheMode: "none", thinkApi: "effort" }],
81
+ ["qwen-max", { context: 1_000_000, maxOutput: 128_000, thinking: false, partialMode: true, cacheMode: "none", thinkApi: "effort" }],
82
+ ["qwen-plus", { context: 1_000_000, maxOutput: 32_000, thinking: false, partialMode: true, cacheMode: "none", thinkApi: "effort" }],
83
+ ["qwen", { context: 1_000_000, maxOutput: 128_000, thinking: false, partialMode: true, cacheMode: "none", thinkApi: "effort" }],
84
+ // MiniMax 系列
85
+ ["MiniMax-M3", { context: 1_000_000, maxOutput: 128_000, thinking: false, cacheMode: "auto" }],
86
+ ["minimax-m3", { context: 1_000_000, maxOutput: 128_000, thinking: false, cacheMode: "auto" }],
87
+ ["minimax-m1", { context: 256_000, maxOutput: 128_000, thinking: false, cacheMode: "auto" }],
64
88
  ]
65
- const DEFAULT_CONTEXT_WINDOW = 128_000
89
+ const DEFAULT_SPEC = { context: 128_000, maxOutput: 32_000, cacheMode: "none" }
66
90
  // 窗口利用率上限:0.8(DeepSeek 内部即全窗口;压缩本身要花一次 LLM 调用,过早压缩是纯浪费。
67
91
  // 留 20% 余量给压缩后的尾部增长与输出 token)
68
92
  const COMPACT_RATIO = 0.8
69
93
 
70
- export function contextWindowForModel(model) {
94
+ /** 按模型名前缀查规格(大小写不敏感),未知模型给保守默认 */
95
+ export function specForModel(model) {
71
96
  const m = (model ?? "").toLowerCase()
72
- for (const [prefix, window] of MODEL_CONTEXT_WINDOWS) {
73
- if (m.startsWith(prefix)) return window
97
+ for (const [prefix, spec] of [...MODEL_SPECS].sort((a,b) => b[0].length - a[0].length)) {
98
+ if (m.startsWith(prefix)) return spec
74
99
  }
75
- return DEFAULT_CONTEXT_WINDOW
100
+ return DEFAULT_SPEC
101
+ }
102
+
103
+ export function contextWindowForModel(model) {
104
+ return specForModel(model).context
76
105
  }
77
106
 
78
107
  /** 推导压缩阈值;explicit 为配置文件中显式设置的值(优先),否则按模型自动算 */
@@ -140,7 +169,7 @@ export function loadConfig() {
140
169
 
141
170
  // apiKey 还可用环境变量兜底(当 providers 里没配 key 时)
142
171
  // 提供商专用的环境变量只对同名 provider 生效,避免 key 串到错误的端点
143
- if (!runtimeProvider.apiKey) {
172
+ if (!runtimeProvider.apiKey?.trim()) {
144
173
  const envMap = { deepseek: "DEEPSEEK_API_KEY", openai: "OPENAI_API_KEY" }
145
174
  const keyVar = envMap[merged.activeProvider]
146
175
  if (keyVar && process.env[keyVar]) runtimeProvider.apiKey = process.env[keyVar]
package/src/context.mjs CHANGED
@@ -1,22 +1,30 @@
1
1
  /**
2
2
  * context.mjs — 上下文管理与压缩
3
- * token 用 length/4 粗估(不引 tokenizer 依赖)。
3
+ * token 无实测值时用估算兜底(ASCII/4 + 非 ASCII/1,不引 tokenizer 依赖);
4
+ * 有实测值(响应 usage.prompt_tokens)以实测为准——估算对 CJK 低估 3-4 倍,靠它触发可能永远来不及压缩。
4
5
  * 压缩策略:保留最早 2 条 + 最近 N 条,中间由 LLM 摘要成一条(学 kimi-code,简化版)。
5
6
  */
6
7
 
7
8
  import { chat } from "./provider.mjs"
8
9
 
10
+ /** 粗估一段文本的 token 数:ASCII 约 4 字符 1 token,CJK 等非 ASCII 约 1 字符 1 token */
11
+ function estimateText(s) {
12
+ let nonAscii = 0
13
+ for (let i = 0; i < s.length; i++) if (s.charCodeAt(i) > 0x7f) nonAscii++
14
+ return Math.ceil((s.length - nonAscii) / 4) + nonAscii
15
+ }
16
+
9
17
  /** 粗估一组消息的 token 数(正文 + 思考链 + tool_calls 参数) */
10
18
  export function estimateTokens(messages) {
11
- let chars = 0
19
+ let tokens = 0
12
20
  for (const m of messages) {
13
- if (typeof m.content === "string") chars += m.content.length
14
- if (typeof m.reasoning_content === "string") chars += m.reasoning_content.length
21
+ if (typeof m.content === "string") tokens += estimateText(m.content)
22
+ if (typeof m.reasoning_content === "string") tokens += estimateText(m.reasoning_content)
15
23
  for (const tc of m.tool_calls ?? []) {
16
- chars += (tc.function?.name?.length ?? 0) + (tc.function?.arguments?.length ?? 0)
24
+ tokens += estimateText(tc.function?.name ?? "") + estimateText(tc.function?.arguments ?? "")
17
25
  }
18
26
  }
19
- return Math.ceil(chars / 4)
27
+ return tokens
20
28
  }
21
29
 
22
30
  const KEEP_HEAD = 2 // 最早的用户意图,不能丢
@@ -24,8 +32,10 @@ const KEEP_TAIL = 10 // 最近的工作现场,不能丢
24
32
 
25
33
  const SUMMARIZE_PROMPT = `你是一个对话压缩器。把下面的 agent 工作记录压缩成一份紧凑的摘要,供后续对话作为上下文使用。
26
34
  要求:
35
+ - 用第一人称、现在时书写——这是"我"的交接笔记,延续自己的思路
27
36
  - 保留:用户的原始需求、做出的决策、修改过的文件及原因、未解决的问题、下一步计划
28
37
  - 丢弃:客套话、重复内容、工具输出的细枝末节
38
+ - 诚实标注不确定项:没有实际验证过的事必须写"未验证",不要把猜测写成事实
29
39
  - 用中文条目式输出,控制在 500 字以内
30
40
 
31
41
  工作记录:
@@ -34,64 +44,88 @@ const SUMMARIZE_PROMPT = `你是一个对话压缩器。把下面的 agent 工
34
44
  /** 压缩后的上下文前缀,告知 agent 发生了什么 */
35
45
  const COMPACTION_PREFIX =
36
46
  "[Context was automatically compacted. Below is a summary of earlier work. " +
37
- "Trust its conclusions don't redo what it reports as done — but re-verify " +
38
- "transient state (open files, running processes) with tools.]\n\n"
47
+ "Treat it as notes, not proof — trust its conclusions (don't redo what it reports as done) " +
48
+ "but re-verify transient state (open files, running processes) with tools before relying on them.]\n\n"
39
49
 
40
- /**
41
- * 如果历史超长则压缩。返回是否发生了压缩。
42
- * 只在循环的安全点调用(history 末尾是 user 消息时)。
43
- * 压缩后自动回注 task 列表状态。
44
- */
45
- export async function compressIfNeeded(agent, threshold) {
46
- const history = agent.history
47
- if (estimateTokens(history) <= threshold) return false
48
- if (history.length <= KEEP_HEAD + KEEP_TAIL + 1) return false
50
+ /** 压缩摘要调用连续失败达到此次数后,降级为确定性截断(丢信息好过任务被 400 打死) */
51
+ export const COMPRESS_FAILURE_LIMIT = 3
49
52
 
50
- // 切分:head / middle(被摘要) / tail
51
- // tail 起点必须避开孤儿 tool 消息(其 assistant tool_calls middle 里无妨,middle 会被整体摘要成纯文本)
52
- let tailStart = history.length - KEEP_TAIL
53
- while (tailStart > KEEP_HEAD && history[tailStart].role === "tool") {
54
- tailStart++
55
- }
56
- if (tailStart <= KEEP_HEAD) return false // 没有可压缩的中间段
53
+ /** task 回注提醒前缀(压缩后重新注入前,先清掉历史里的旧版本,保持单一信息源) */
54
+ const TASK_REINJECT_PREFIX = "[System reminder: your current task list after compaction:"
57
55
 
58
- const head = history.slice(0, KEEP_HEAD)
59
- const middle = history.slice(KEEP_HEAD, tailStart)
60
- const tail = history.slice(tailStart)
56
+ /** 截断兜底笔记(摘要 LLM 连续失败时用,无 LLM 调用) */
57
+ const FALLBACK_NOTE =
58
+ "[Context was truncated after repeated summarization failures. " +
59
+ "The middle portion of earlier work was dropped WITHOUT a summary. " +
60
+ "Re-verify any state you need with tools before relying on it.]\n\n"
61
61
 
62
- const serialized = middle
63
- .map((m) => {
64
- const toolNote = m.tool_calls ? ` [调用了工具: ${m.tool_calls.map((t) => t.function.name).join(", ")}]` : ""
65
- const content = typeof m.content === "string" ? m.content.slice(0, 2000) : ""
66
- return `[${m.role}]${toolNote} ${content}`
67
- })
68
- .join("\n")
62
+ /**
63
+ * 切分 head / middle(被摘要) / tail;没有可压缩的中间段返回 null。
64
+ * head 终点必须避开断头 tool_calls:assistant 带了 tool_calls 时其 tool 响应必须留在 head,
65
+ * 否则响应被摘要成纯文本后协议校验 400(tool_calls must be followed by tool messages)。
66
+ * tail 起点必须包含 tool 结果对应的 assistant——tool 在 tail、assistant 在 middle 时,
67
+ * 摘要会把 assistant 吞掉,留下 orphan tool 结果 → 协议 400。
68
+ */
69
+ function splitHistory(history) {
70
+ if (history.length <= KEEP_HEAD + KEEP_TAIL + 1) return null
71
+ let headEnd = KEEP_HEAD
72
+ while (
73
+ headEnd < history.length &&
74
+ history[headEnd - 1].role === "assistant" &&
75
+ history[headEnd - 1].tool_calls?.length &&
76
+ history[headEnd].role === "tool"
77
+ ) {
78
+ headEnd++
79
+ }
80
+ let tailStart = history.length - KEEP_TAIL
69
81
 
70
- const summary = await chat(agent.provider, {
71
- messages: [{ role: "user", content: SUMMARIZE_PROMPT + serialized }],
72
- })
82
+ // tail 区域内的 tool 消息对应的 assistant tool_calls 若在 middle 里,摘要会把 assistant 吞掉,
83
+ // 剩下 orphan tool 结果 协议 400。从 tail 收集 tool_call_id,往前找回所属 assistant 拉进 tail
84
+ const tailToolIds = new Set()
85
+ for (let i = tailStart; i < history.length; i++) {
86
+ if (history[i].role === "tool") tailToolIds.add(history[i].tool_call_id)
87
+ }
88
+ for (let i = tailStart - 1; i > headEnd; i--) {
89
+ const m = history[i]
90
+ if (m.role === "assistant" && m.tool_calls?.some((tc) => tailToolIds.has(tc.id))) {
91
+ tailStart = i
92
+ break
93
+ }
94
+ }
73
95
 
74
- // 摘要正文内嵌 task 快照(对齐 kimi-code postProcessSummary)——
75
- // 否则二次压缩时 task 列表会随旧提醒消息一起被摘要器丢掉
76
- let compacted = COMPACTION_PREFIX + summary.content
77
- if (agent.tasks.length > 0) {
78
- const taskSummary = agent.tasks.map((t) => `- [${t.status}] ${t.title}`).join("\n")
79
- compacted += `\n\n## Task List\n${taskSummary}`
96
+ // skip orphan tool messages at the new tail boundary (tool whose assistant was pulled in above)
97
+ while (tailStart > headEnd && history[tailStart].role === "tool") {
98
+ tailStart++
80
99
  }
100
+ if (tailStart <= headEnd) return null
101
+ return { headEnd, tailStart }
102
+ }
81
103
 
104
+ /** 用一条笔记替换 middle,并回注 task/plan 状态(LLM 摘要与截断兜底共用) */
105
+ function applyCompression(agent, headEnd, tailStart, note) {
106
+ const head = agent.history.slice(0, headEnd)
107
+ const tail = agent.history.slice(tailStart)
82
108
  agent.history = [
83
109
  ...head,
84
- { role: "user", content: compacted },
85
- { role: "assistant", content: "Understood. I'll continue from this summary, re-verifying anything transient." },
110
+ { role: "user", content: note },
111
+ { role: "assistant", content: "Understood. I'll continue from these notes, re-verifying anything transient." },
86
112
  ...tail,
87
113
  ]
88
-
89
- // 压缩后回注 task 列表(agent 需要知道自己做到哪了)
114
+ // 实测 token 基准随旧历史一起失效(prompt_tokens 对应的是压缩前的上下文),退回估算直到下次响应
115
+ agent._lastPromptTokens = null
116
+ agent._usageAtLen = null
117
+
118
+ // 压缩后回注 task 列表(agent 需要知道自己做到哪了)。
119
+ // 单一信息源:先清掉 tail 里残留的旧回注,再注入最新版本——
120
+ // 不再嵌入摘要正文(会与这里重复且逐渐过时)
121
+ agent.history = agent.history.filter(
122
+ (m) => !(m.role === "user" && typeof m.content === "string" && m.content.startsWith(TASK_REINJECT_PREFIX))
123
+ )
90
124
  if (agent.tasks.length > 0) {
91
125
  const taskSummary = agent.tasks.map((t) => `- [${t.status}] ${t.title}`).join("\n")
92
126
  agent.history.push({
93
127
  role: "user",
94
- content: `[System reminder: your current task list after compaction:\n${taskSummary}\nContinue from where you left off.]`,
128
+ content: `${TASK_REINJECT_PREFIX}\n${taskSummary}\nContinue from where you left off.]`,
95
129
  })
96
130
  }
97
131
 
@@ -106,6 +140,52 @@ export async function compressIfNeeded(agent, threshold) {
106
140
  content: "[System reminder: plan mode is active. Explore the codebase read-only, design your solution, then call plan with action='exit' to present it for user approval.]",
107
141
  })
108
142
  }
143
+ }
109
144
 
145
+ /**
146
+ * 如果历史超长则压缩。返回是否发生了压缩。
147
+ * 只在循环的安全点调用(history 末尾是 user 或 tool 消息——完整交换的边界)。
148
+ * 压缩后自动回注 task 列表状态。
149
+ */
150
+ export async function compressIfNeeded(agent, threshold) {
151
+ const history = agent.history
152
+ // 真实基准优先:上次响应的 prompt_tokens 是完整上下文(system+tools+history)的实测值,
153
+ // 之后追加的消息用估算补增量;无实测(首轮/恢复后/刚压缩完)退化为纯估算
154
+ const tokens =
155
+ agent._lastPromptTokens != null
156
+ ? agent._lastPromptTokens + estimateTokens(history.slice(agent._usageAtLen ?? history.length))
157
+ : estimateTokens(history)
158
+ if (tokens <= threshold) return false
159
+
160
+ const split = splitHistory(history)
161
+ if (!split) return false
162
+
163
+ const middle = history.slice(split.headEnd, split.tailStart)
164
+ const serialized = middle
165
+ .map((m) => {
166
+ const toolNote = m.tool_calls ? ` [调用了工具: ${m.tool_calls.map((t) => t.function.name).join(", ")}]` : ""
167
+ // user 消息放宽到 8000:用户粘贴的长需求被切掉会让摘要丢失原始意图;tool/assistant 2000 足够
168
+ const cap = m.role === "user" ? 8000 : 2000
169
+ const content = typeof m.content === "string" ? m.content.slice(0, cap) : ""
170
+ return `[${m.role}]${toolNote} ${content}`
171
+ })
172
+ .join("\n")
173
+
174
+ const summary = await chat(agent.provider, {
175
+ messages: [{ role: "user", content: SUMMARIZE_PROMPT + serialized }],
176
+ })
177
+
178
+ applyCompression(agent, split.headEnd, split.tailStart, COMPACTION_PREFIX + summary.content)
179
+ return true
180
+ }
181
+
182
+ /**
183
+ * 确定性截断兜底:摘要 LLM 连续失败时调用,不碰网络。
184
+ * 丢掉 middle 换任务能继续跑。返回是否发生了截断。
185
+ */
186
+ export function compressFallback(agent) {
187
+ const split = splitHistory(agent.history)
188
+ if (!split) return false
189
+ applyCompression(agent, split.headEnd, split.tailStart, FALLBACK_NOTE)
110
190
  return true
111
191
  }
@@ -1,8 +1,12 @@
1
1
  You are a codebase exploration specialist — an explore subagent. Your role is to search, read, and analyze. You do NOT have file editing tools.
2
2
 
3
3
  Guidelines:
4
- - On start, quickly orient yourself: run `git branch --show-current`, `git status --short`, and `git log -5 --oneline` to understand the repo state
5
- - Use Glob for file discovery, Grep for content search, Read for known paths
4
+ - Git context (branch, recent commits, working tree state) is injected with your task—use it, no need to re-run git orientation commands
5
+ - Use repo_outline, code_search, and doc_search as primary discovery tools—these replace blind grep:
6
+ - repo_outline for file dependency graph (what imports what)
7
+ - doc_search for design docs, conventions, READMEs
8
+ - code_search for finding symbols, JSDoc, and implementation patterns
9
+ - Use Glob and Grep only for patterns these tools can't answer (e.g. file name wildcards, regex content search)
6
10
  - Run read-only shell commands (git log, git diff, ls, find) when helpful
7
11
  - Use WebSearch or Fetch when external context is needed (docs, error messages)
8
12
  - Issue parallel tool calls whenever possible — read multiple files at once
@@ -0,0 +1,8 @@
1
+ Main-agent rules (only the top-level agent has these tools—subagents do not):
2
+
3
+ - Use the plan tool before complex multi-step tasks: enter plan mode, explore the codebase read-only (use repo_outline → doc_search → code_search to understand structure and conventions), design the architecture, present the plan to the user. When approved, exit plan mode and implement. Skip plan mode for simple single-file edits.
4
+ - For long-running autonomous tasks, use the goal tool to set a persistent objective with a VERIFIABLE completion criterion (a machine-checkable proof, not effort). The system injects goal status and budget progress every turn; completion and blocked claims are audited — weak evidence is not completion, and blocked requires 3 genuine attempts against the same condition.
5
+ - Use the skill tool to list and load project skills (.thincoder/skills/*.md). Skills contain reusable workflows and reference material. Load relevant skills when a task matches their description.
6
+ - For independent research/exploration subtasks, spawn subagents in the SAME response to run them in parallel—they work in isolated contexts and return final reports. Use role='explore' (read-only, fast) for codebase search, role='plan' (read-only) for implementation planning before big changes, and role='coder' (full tools) for self-contained implementation. Delegate breadth-first exploration; do precision edits yourself. Never assign parallel subagents tasks that edit the same files.
7
+ - Before declaring a coding task complete, verify it with the verify tool — it shows your git diff and a self-review checklist. Run it after your last edit, not before. If tests exist, run them and confirm they pass; if the project has tests but none cover your change, add at least one test. If you could not verify, say so explicitly—never present unverified work as done.
8
+ - When a coder subagent finishes, verify its report: read the files it claims to have changed, run tests, and confirm the changes match. Do not trust subagent reports blindly.