bingocode 1.1.153 → 1.1.154

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.
@@ -19,4 +19,4 @@ description: Activate the Leanchy protocol: execution discipline, diagnostic rig
19
19
 
20
20
  ## Architecture
21
21
  - Two duplications → abstract. Search the full codebase before modifying; reuse over reinvention.
22
- - Module boundaries require explicit contracts. Semantic naming is the documentation.
22
+ - Module boundaries require explicit contracts. Semantic naming is the documentation.
@@ -1,31 +1,70 @@
1
1
  ---
2
2
  name: leanchypro
3
- description: 激活 Leanchy Pro 协议:在基础版上强化“工具所有权”、“多工具协同”与“探针驱动”的高阶执行架构。
3
+ description: Activate the Leanchy Pro protocol: context-density-first execution, delegation discipline, tool ownership, probe-driven delivery, and zero-hallucination engineering.
4
4
  ---
5
5
 
6
- # Leanchy Pro 协议指令 (Professional Execution)
6
+ # Leanchy Pro Protocol
7
7
 
8
- 当执行复杂工程任务、系统重构或高价值交付时,激活 Pro 级指令集。此协议在 Leanchy 基础版之上,核心强化对工具能力的极致挖掘。
8
+ Activated for complex tasks, large-scale refactors, and high-value deliveries. Every bit of context has a budget—Pro execution is measured by average information gain per roundtrip.
9
9
 
10
- ## 0. 工具所有权 (Tool Ownership) - 拒绝吝啬
11
- - **主动探测**:严禁在未经过充分工具验证前说“我认为”、“可能”。工具调用是获取真相的唯一手段。
12
- - **验证冗余**:对于关键逻辑,必须通过不同维度的工具(如 `Grep` 结合 `Read`, `Bash` 结合 `Agent`)进行交叉验证。
13
- - **结果闭环**:工具返回的每一条异常信息都必须有响应和解释,禁止忽略明显的错误信号。
10
+ ---
11
+
12
+ ## 0. Information Density Budget top priority
13
+
14
+ The context window is the scarcest shared resource in the execution system.
15
+
16
+ ### Output density rules
17
+ Every non-tool output must:
18
+ - Lead with conclusion: first line = result or most important statement of this round
19
+ - Sustain ratio ≥ 0.7: information gain / total output ≥ 70%. No filler transitions, no restating what a tool just returned
20
+ - Short beats long, absence beats padding: three short phrases beat one paragraph; delete every non-essential word
21
+
22
+ ### Delegation threshold
23
+ Actions meeting any of these criteria MUST be delegated to Agent/background Bash—do NOT flow raw data into mainline context:
24
+ - Search returning >20 lines
25
+ - Bulk file scan or aggregate stats (Grep results >10 entries)
26
+ - Cross-file pattern verification
27
+
28
+ Agent/Bash returns summary only. Mainline receives anchor → finding → recommendation, never raw dump.
29
+
30
+ ### Three low-density anti-patterns
31
+
32
+ Prohibited: "Let me explain what this code does" → state purpose and key logic point instead
33
+ Prohibited: pasting every Grep result → cherry-pick 2-3 representative samples
34
+ Prohibited: multi-paragraph reasoning → direct conclusion + optional one-line why
35
+
36
+ ---
37
+
38
+ ## 1. Tool Ownership — truth via instrumentation
14
39
 
15
- ## 1. 多工具协同 (Multi-Tool Coordination) - 并行效率
16
- - **原子任务并行**:凡是逻辑独立的操作(如查多个目录、跑不相关的测试),必须在单次响应中通过并行工具调用完成。
17
- - **工具链化**:设计具备前导与后续依赖的工具链。前一个工具寻找锚点,后一个工具执行修改,严禁分多次对话进行。
18
- - **上下文保鲜**:利用 `TaskCreate` 和 `TaskUpdate` 维持长程执行状态。每完成一个物理文件的修改,立即更新任务状态。
40
+ - Banned: "I think", "might be", "should be"
41
+ - Cross-validate: critical logic points confirmed from different tool dimensions (Grep + Read, Bash + Agent). Never speak about a file you haven't read
42
+ - Signal closure: every anomaly from a tool return must be explained. No skipping
19
43
 
20
- ## 2. 探针驱动 (Probe-Driven Execution) - 防御性交付
21
- - **逻辑探针**:在应用大规模重构前,先编写临时脚本(Python/Bash)或插入打印语句进行逻辑路径覆盖测试。
22
- - **副作用探测**:修改完成后,不仅要跑受影响点的测试,必须通过 `Grep` 全局扫描是否存在非显式依赖导致的破坏。
23
- - **预案回滚**:所有高风险工具操作(如 `sed`, `rm`, `git reset`)执行前,必须确保当前工作区已处于 Git 追踪下或有备份。
44
+ ---
45
+
46
+ ## 2. Delegation — offload low-density work
47
+
48
+ - Large searches → Agent. Mainline only receives source → finding → recommendation
49
+ - Data stats / batch aggregation → Bash one-liner. Never scroll raw data in mainline
50
+ - Long-running tasks → `run_in_background`. Never block mainline for polling loops
51
+
52
+ ---
53
+
54
+ ## 3. Probe-Driven Delivery
24
55
 
25
- ## 3. 纪律约束 (Discipline Layer)
26
- - **拒绝平庸**:禁止生成模板化的、泛泛而谈的代码。每一行产出都必须符合当前 Repo 的既有范式。
27
- - **零过渡态**:禁止向用户展示未完成的、不可编译的代码片段(除非是为了讨论特定逻辑点)。交付即终态。
28
- - **协议回流**:在执行中发现的高价值模式,必须在任务结束前通过 `Write` 某种 `MEMO` 或 `CLAUDE.md` 的形式留存。
56
+ - Pre-probe: minimal test script to verify logic-path coverage before refactoring
57
+ - Post-probe ghost scan: Grep/Agent to find hidden dependencies or broken chains after changes
58
+ - Rollback prep: ensure Git-clean state before risky operations
29
59
 
30
60
  ---
31
- *Pro 协议不仅是更快的执行,更是更深度的实证主义。*
61
+
62
+ ## 4. Delivery Discipline
63
+
64
+ - Paradigm-locked: every line matches existing repo conventions. Zero generic patterns
65
+ - Zero transient state: never show non-compilable/non-runnable code. What's shown is final
66
+ - Knowledge return: patterns and new dependencies discovered must be archived to MEMO/CLAUDE.md/ADR on completion
67
+
68
+ ---
69
+
70
+ *Pro boils down to: triangulate with tools, offload low-density work, maximize information density in mainline context.*
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bingocode",
3
- "version": "1.1.153",
3
+ "version": "1.1.154",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "claude": "bin/claude-win.cjs",
@@ -72,8 +72,10 @@ import {
72
72
  import {
73
73
  areFileEditsInputsEquivalent,
74
74
  findActualString,
75
+ findClosestLines,
75
76
  getPatchForEdit,
76
77
  preserveQuoteStyle,
78
+ visibleWhitespace,
77
79
  } from './utils.js'
78
80
 
79
81
  // V8/Bun string length limit is ~2^30 characters (~1 billion). For typical
@@ -315,10 +317,15 @@ export const FileEditTool = buildTool({
315
317
  // Use findActualString to handle quote normalization
316
318
  const actualOldString = findActualString(file, old_string)
317
319
  if (!actualOldString) {
320
+ const BASE = 'String to replace not found.'
321
+ const matches = findClosestLines(file, old_string)
322
+ const msg = matches.length
323
+ ? `${BASE}\n→ = tab · = space\nProvided:\n${visibleWhitespace(old_string)}\nClosest matches:\n${matches.map(m => ` line ${m.lineNumber} (${m.diffType})\n ${visibleWhitespace(m.snippet)}`).join('\n')}\n↑ check visible whitespace markers above.`
324
+ : `${BASE}.\n→ = tab · = space\nProvided:\n${visibleWhitespace(old_string)}\n↑ check visible whitespace markers above.`
318
325
  return {
319
326
  result: false,
320
327
  behavior: 'ask',
321
- message: `String to replace not found in file.\nString: ${old_string}`,
328
+ message: msg,
322
329
  meta: {
323
330
  isFilePathAbsolute: String(isAbsolute(file_path)),
324
331
  },
@@ -198,6 +198,75 @@ function applyCurlySingleQuotes(str: string): string {
198
198
  return result.join('')
199
199
  }
200
200
 
201
+ /**
202
+ * Error class for when an edit's old_string can't be found in the file.
203
+ * Carries diagnostics for better error reporting.
204
+ */
205
+ export class EditNotFoundError extends Error {
206
+ diagnostics: {
207
+ searchString: string
208
+ visibleSearch: string
209
+ closestMatches: {
210
+ snippet: string
211
+ lineNumber: number
212
+ diffType: string
213
+ }[]
214
+ }
215
+ constructor(
216
+ message: string,
217
+ diagnostics: EditNotFoundError['diagnostics'],
218
+ ) {
219
+ super(message)
220
+ this.name = 'EditNotFoundError'
221
+ this.diagnostics = diagnostics
222
+ }
223
+ }
224
+
225
+ /**
226
+ * Renders whitespace characters as visible Unicode equivalents:
227
+ * tab → '→', space → '·'
228
+ */
229
+ export function visibleWhitespace(str: string): string {
230
+ return str.replace(/\t/g, '→').replace(/ /g, '·')
231
+ }
232
+
233
+ /**
234
+ * Finds up to 3 lines in fileContent whose content (non-whitespace portion)
235
+ * matches the content of the first line of searchString.
236
+ * Used for diagnostic purposes when findActualString returns null.
237
+ *
238
+ * Returns matches sorted with whitespace-diff first, then content matches.
239
+ */
240
+ export function findClosestLines(
241
+ fileContent: string,
242
+ searchString: string,
243
+ ): { snippet: string; lineNumber: number; diffType: string }[] {
244
+ const firstContent = searchString.split('\n')[0]!.replace(/^\s+/, '')
245
+ if (!firstContent) return []
246
+
247
+ const matches: { snippet: string; lineNumber: number; diffType: string }[] = []
248
+ const fileLines = fileContent.split('\n')
249
+
250
+ for (let i = 0; i < fileLines.length; i++) {
251
+ const line = fileLines[i]!
252
+ if (line.replace(/^\s+/, '') !== firstContent) continue
253
+
254
+ const snippet = line.replace(/\s+$/, '')
255
+
256
+ // Avoid duplicates
257
+ if (!matches.some(m => m.snippet === snippet)) {
258
+ matches.push({
259
+ snippet,
260
+ lineNumber: i + 1,
261
+ diffType: 'content match',
262
+ })
263
+ if (matches.length >= 3) break
264
+ }
265
+ }
266
+
267
+ return matches
268
+ }
269
+
201
270
  /**
202
271
  * Transform edits to ensure replace_all always has a boolean value
203
272
  * @param edits Array of edits with optional replace_all
@@ -323,7 +392,18 @@ export function getPatchForEdits({
323
392
 
324
393
  // If this edit didn't change anything, throw an error
325
394
  if (updatedFile === previousContent) {
326
- throw new Error('String not found in file. Failed to apply edit.')
395
+ const closest = findClosestLines(fileContents, edit.old_string)
396
+ throw new EditNotFoundError(
397
+ closest.length
398
+ ? `Edit failed — closest match:
399
+ ${closest.map(m => ` line ${m.lineNumber}: ${visibleWhitespace(m.snippet)} (${m.diffType})`).join('\n')}`
400
+ : 'Edit failed — string not found in file.',
401
+ {
402
+ searchString: edit.old_string,
403
+ visibleSearch: visibleWhitespace(edit.old_string),
404
+ closestMatches: closest,
405
+ },
406
+ )
327
407
  }
328
408
 
329
409
  // Track the new string that was applied
@@ -50,18 +50,40 @@ ${recentAssistantTexts || '(none yet)'}
50
50
  Respond in JSON only:
51
51
  {"satisfied": true|false, "reason": "<one sentence>", "gap": "<missing item or null>"}`
52
52
 
53
- const response = await client.messages.create({
54
- model: GOAL_EVALUATOR_MODEL,
55
- max_tokens: 256,
56
- messages: [{ role: 'user', content: prompt }],
57
- })
53
+ let text = ''
54
+ try {
55
+ const response = await client.messages.create({
56
+ model: GOAL_EVALUATOR_MODEL,
57
+ max_tokens: 256,
58
+ messages: [{ role: 'user', content: prompt }],
59
+ })
60
+ text = response.content.find((b: any) => b.type === 'text')?.text || ''
61
+ } catch (e) {
62
+ return {
63
+ satisfied: false,
64
+ reason: 'Evaluator API error',
65
+ gap: e instanceof Error ? e.message : String(e),
66
+ }
67
+ }
58
68
 
59
- const text =
60
- response.content.find((b: any) => b.type === 'text')?.text || ''
61
69
  try {
62
- const cleaned = text.replace(/^```(?:json)?\n?|\n?```$/g, '').trim()
70
+ // Strip markdown code fences and find JSON object bounds
71
+ let cleaned = text
72
+ .replace(/```(?:json)?\s*/gi, '')
73
+ .replace(/```/g, '')
74
+ .trim()
75
+ const start = cleaned.indexOf('{')
76
+ const end = cleaned.lastIndexOf('}')
77
+ if (start === -1 || end === -1 || end <= start) {
78
+ throw new Error('No JSON object found')
79
+ }
80
+ cleaned = cleaned.slice(start, end + 1)
63
81
  return JSON.parse(cleaned) as GoalEvalResult
64
- } catch {
65
- return { satisfied: false, reason: 'Evaluator parse error', gap: text }
82
+ } catch (e) {
83
+ return {
84
+ satisfied: false,
85
+ reason: 'Evaluator parse error',
86
+ gap: `${e instanceof Error && e.message !== 'No JSON object found' ? e.message : 'raw output'}: ${text.slice(0, 200)}`,
87
+ }
66
88
  }
67
- }
89
+ }