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:
|
|
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
|
|
6
|
+
# Leanchy Pro Protocol
|
|
7
7
|
|
|
8
|
-
|
|
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
|
-
|
|
11
|
-
|
|
12
|
-
|
|
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
|
-
|
|
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
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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
|
-
|
|
26
|
-
-
|
|
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
|
-
|
|
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
|
@@ -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:
|
|
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
|
-
|
|
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
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
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
|
-
|
|
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 {
|
|
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
|
+
}
|