@namzu/sdk 52.4.0 → 52.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.
- package/CHANGELOG.md +33 -0
- package/dist/authorization/gate.d.ts +11 -0
- package/dist/authorization/gate.d.ts.map +1 -1
- package/dist/authorization/gate.js +35 -0
- package/dist/authorization/gate.js.map +1 -1
- package/dist/authorization/removal.js +11 -6
- package/dist/authorization/removal.js.map +1 -1
- package/dist/authorization/rules.d.ts.map +1 -1
- package/dist/authorization/rules.js +30 -2
- package/dist/authorization/rules.js.map +1 -1
- package/dist/authorization/shell-paths.d.ts +20 -0
- package/dist/authorization/shell-paths.d.ts.map +1 -1
- package/dist/authorization/shell-paths.js +254 -52
- package/dist/authorization/shell-paths.js.map +1 -1
- package/dist/bridge/acp/server.d.ts +8 -0
- package/dist/bridge/acp/server.d.ts.map +1 -1
- package/dist/bridge/acp/server.js +7 -0
- package/dist/bridge/acp/server.js.map +1 -1
- package/dist/manager/resident/outbox.d.ts +4 -4
- package/dist/public-runtime.d.ts +2 -1
- package/dist/public-runtime.d.ts.map +1 -1
- package/dist/public-runtime.js +4 -1
- package/dist/public-runtime.js.map +1 -1
- package/dist/public-types.d.ts +1 -0
- package/dist/public-types.d.ts.map +1 -1
- package/dist/runtime/query/auto-review/classifier.d.ts +130 -0
- package/dist/runtime/query/auto-review/classifier.d.ts.map +1 -0
- package/dist/runtime/query/auto-review/classifier.js +244 -0
- package/dist/runtime/query/auto-review/classifier.js.map +1 -0
- package/dist/runtime/query/auto-review/index.d.ts +7 -0
- package/dist/runtime/query/auto-review/index.d.ts.map +1 -0
- package/dist/runtime/query/auto-review/index.js +4 -0
- package/dist/runtime/query/auto-review/index.js.map +1 -0
- package/dist/runtime/query/auto-review/prompt.d.ts +20 -0
- package/dist/runtime/query/auto-review/prompt.d.ts.map +1 -0
- package/dist/runtime/query/auto-review/prompt.js +89 -0
- package/dist/runtime/query/auto-review/prompt.js.map +1 -0
- package/dist/runtime/query/auto-review/rules.d.ts +49 -0
- package/dist/runtime/query/auto-review/rules.d.ts.map +1 -0
- package/dist/runtime/query/auto-review/rules.js +81 -0
- package/dist/runtime/query/auto-review/rules.js.map +1 -0
- package/dist/runtime/query/auto-review/transcript.d.ts +59 -0
- package/dist/runtime/query/auto-review/transcript.d.ts.map +1 -0
- package/dist/runtime/query/auto-review/transcript.js +116 -0
- package/dist/runtime/query/auto-review/transcript.js.map +1 -0
- package/dist/runtime/query/auto-review/verdict.d.ts +21 -0
- package/dist/runtime/query/auto-review/verdict.d.ts.map +1 -0
- package/dist/runtime/query/auto-review/verdict.js +45 -0
- package/dist/runtime/query/auto-review/verdict.js.map +1 -0
- package/dist/runtime/query/executor.d.ts +7 -0
- package/dist/runtime/query/executor.d.ts.map +1 -1
- package/dist/runtime/query/executor.js +26 -2
- package/dist/runtime/query/executor.js.map +1 -1
- package/dist/runtime/query/index.js +2 -2
- package/dist/runtime/query/index.js.map +1 -1
- package/dist/runtime/query/iteration/phases/context.d.ts +0 -9
- package/dist/runtime/query/iteration/phases/context.d.ts.map +1 -1
- package/dist/runtime/query/iteration/phases/context.js +11 -2
- package/dist/runtime/query/iteration/phases/context.js.map +1 -1
- package/dist/runtime/query/iteration/phases/tool-review.d.ts.map +1 -1
- package/dist/runtime/query/iteration/phases/tool-review.js +39 -0
- package/dist/runtime/query/iteration/phases/tool-review.js.map +1 -1
- package/dist/runtime/query/path-escalation.d.ts.map +1 -1
- package/dist/runtime/query/path-escalation.js +43 -10
- package/dist/runtime/query/path-escalation.js.map +1 -1
- package/dist/runtime/query/resume-pending.d.ts.map +1 -1
- package/dist/runtime/query/resume-pending.js +14 -0
- package/dist/runtime/query/resume-pending.js.map +1 -1
- package/dist/runtime/query/review-policy.d.ts +88 -1
- package/dist/runtime/query/review-policy.d.ts.map +1 -1
- package/dist/runtime/query/review-policy.js +504 -11
- package/dist/runtime/query/review-policy.js.map +1 -1
- package/dist/tools/builtins/bash.d.ts.map +1 -1
- package/dist/tools/builtins/bash.js +2 -0
- package/dist/tools/builtins/bash.js.map +1 -1
- package/dist/tools/builtins/classifier-input.d.ts +9 -0
- package/dist/tools/builtins/classifier-input.d.ts.map +1 -0
- package/dist/tools/builtins/classifier-input.js +20 -0
- package/dist/tools/builtins/classifier-input.js.map +1 -0
- package/dist/tools/builtins/code-analysis.d.ts +2 -0
- package/dist/tools/builtins/code-analysis.d.ts.map +1 -1
- package/dist/tools/builtins/code-analysis.js +196 -119
- package/dist/tools/builtins/code-analysis.js.map +1 -1
- package/dist/tools/builtins/computer-use.d.ts +10 -10
- package/dist/tools/builtins/edit-apply.d.ts.map +1 -1
- package/dist/tools/builtins/edit-apply.js +30 -12
- package/dist/tools/builtins/edit-apply.js.map +1 -1
- package/dist/tools/builtins/edit-match.d.ts +47 -0
- package/dist/tools/builtins/edit-match.d.ts.map +1 -0
- package/dist/tools/builtins/edit-match.js +262 -0
- package/dist/tools/builtins/edit-match.js.map +1 -0
- package/dist/tools/builtins/edit.d.ts.map +1 -1
- package/dist/tools/builtins/edit.js +12 -0
- package/dist/tools/builtins/edit.js.map +1 -1
- package/dist/tools/builtins/monitor.d.ts.map +1 -1
- package/dist/tools/builtins/monitor.js +3 -0
- package/dist/tools/builtins/monitor.js.map +1 -1
- package/dist/tools/builtins/web.d.ts.map +1 -1
- package/dist/tools/builtins/web.js +3 -0
- package/dist/tools/builtins/web.js.map +1 -1
- package/dist/tools/builtins/write-file.d.ts.map +1 -1
- package/dist/tools/builtins/write-file.js +6 -0
- package/dist/tools/builtins/write-file.js.map +1 -1
- package/dist/tools/defineTool.d.ts +2 -0
- package/dist/tools/defineTool.d.ts.map +1 -1
- package/dist/tools/defineTool.js +1 -0
- package/dist/tools/defineTool.js.map +1 -1
- package/dist/types/acp/index.d.ts +7 -0
- package/dist/types/acp/index.d.ts.map +1 -1
- package/dist/types/code-analysis/index.d.ts +54 -3
- package/dist/types/code-analysis/index.d.ts.map +1 -1
- package/dist/types/hitl/index.d.ts +45 -1
- package/dist/types/hitl/index.d.ts.map +1 -1
- package/dist/types/hitl/index.js.map +1 -1
- package/dist/types/tool/index.d.ts +16 -0
- package/dist/types/tool/index.d.ts.map +1 -1
- package/dist/types/tool/index.js.map +1 -1
- package/package.json +1 -1
- package/src/authorization/gate.ts +35 -0
- package/src/authorization/removal.ts +12 -6
- package/src/authorization/rules.ts +27 -2
- package/src/authorization/shell-paths.ts +264 -51
- package/src/bridge/acp/server.ts +20 -0
- package/src/public-runtime.ts +18 -0
- package/src/public-types.ts +11 -0
- package/src/runtime/query/auto-review/classifier.ts +404 -0
- package/src/runtime/query/auto-review/index.ts +23 -0
- package/src/runtime/query/auto-review/prompt.ts +97 -0
- package/src/runtime/query/auto-review/rules.ts +106 -0
- package/src/runtime/query/auto-review/transcript.ts +159 -0
- package/src/runtime/query/auto-review/verdict.ts +50 -0
- package/src/runtime/query/executor.ts +29 -2
- package/src/runtime/query/index.ts +2 -2
- package/src/runtime/query/iteration/phases/context.ts +17 -3
- package/src/runtime/query/iteration/phases/tool-review.ts +41 -0
- package/src/runtime/query/path-escalation.ts +44 -9
- package/src/runtime/query/resume-pending.ts +14 -0
- package/src/runtime/query/review-policy.ts +591 -14
- package/src/tools/builtins/bash.ts +3 -0
- package/src/tools/builtins/classifier-input.ts +19 -0
- package/src/tools/builtins/code-analysis.ts +211 -119
- package/src/tools/builtins/edit-apply.ts +31 -12
- package/src/tools/builtins/edit-match.ts +274 -0
- package/src/tools/builtins/edit.ts +13 -0
- package/src/tools/builtins/monitor.ts +4 -0
- package/src/tools/builtins/web.ts +3 -0
- package/src/tools/builtins/write-file.ts +6 -0
- package/src/tools/defineTool.ts +3 -0
- package/src/types/acp/index.ts +7 -0
- package/src/types/code-analysis/index.ts +51 -3
- package/src/types/hitl/index.ts +49 -1
- package/src/types/tool/index.ts +17 -0
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared helpers for `ToolDefinition.classifierInput`: what the auto-mode
|
|
3
|
+
* classifier is shown of a call to a built-in tool.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/** Cuts `text` to `limit` characters, saying how much was left out. */
|
|
7
|
+
export function clip(text: string, limit: number): string {
|
|
8
|
+
if (text.length <= limit) return text
|
|
9
|
+
return `${text.slice(0, limit)}... [${text.length - limit} more characters]`
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/** The characters a change adds and removes, in a form short enough to read at a glance. */
|
|
13
|
+
export function describeChange(removed: string | undefined, added: string | undefined): string {
|
|
14
|
+
const parts: string[] = []
|
|
15
|
+
if (removed !== undefined && removed !== '')
|
|
16
|
+
parts.push(`removes ${JSON.stringify(clip(removed, 200))}`)
|
|
17
|
+
if (added !== undefined && added !== '') parts.push(`adds ${JSON.stringify(clip(added, 300))}`)
|
|
18
|
+
return parts.length > 0 ? parts.join(', ') : 'no text change'
|
|
19
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { realpath, stat } from 'node:fs/promises'
|
|
2
|
+
import { isAbsolute, relative, sep } from 'node:path'
|
|
2
3
|
import { z } from 'zod'
|
|
3
4
|
|
|
4
5
|
import type {
|
|
@@ -112,7 +113,14 @@ const functionSourceOp = z.object({
|
|
|
112
113
|
),
|
|
113
114
|
function_id: z.number().int().min(0).optional(),
|
|
114
115
|
path: z.string().optional(),
|
|
115
|
-
name: z.string().min(1).optional(),
|
|
116
|
+
name: z.string().min(1).optional().describe('`name` or `Class.method`.'),
|
|
117
|
+
line: z
|
|
118
|
+
.number()
|
|
119
|
+
.int()
|
|
120
|
+
.min(1)
|
|
121
|
+
.optional()
|
|
122
|
+
.describe('With `column`: cut a long function around this position, not at its end.'),
|
|
123
|
+
column: z.number().int().min(1).optional(),
|
|
116
124
|
max_chars: maxChars,
|
|
117
125
|
...paging,
|
|
118
126
|
})
|
|
@@ -123,7 +131,10 @@ const callersOp = z.object({
|
|
|
123
131
|
.describe(
|
|
124
132
|
'Call sites of a callee TEXT (not scope-aware), each with its caller. Per file by default.',
|
|
125
133
|
),
|
|
126
|
-
name: z
|
|
134
|
+
name: z
|
|
135
|
+
.string()
|
|
136
|
+
.min(1)
|
|
137
|
+
.describe('Callee text: `foo`, `obj.foo`, `this.foo`; or a method as `Class.method`.'),
|
|
127
138
|
scope: z
|
|
128
139
|
.enum(['file', 'workspace'])
|
|
129
140
|
.optional()
|
|
@@ -207,122 +218,176 @@ export const CodeAnalysisTool = defineTool({
|
|
|
207
218
|
}
|
|
208
219
|
const root = owner.root
|
|
209
220
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
221
|
+
const outcome = await (async (): Promise<ToolResult> => {
|
|
222
|
+
switch (input.operation) {
|
|
223
|
+
case 'index': {
|
|
224
|
+
const result = await provider.index({
|
|
225
|
+
root,
|
|
226
|
+
// A narrower path indexes just that part of the root, which is
|
|
227
|
+
// how a root too big to index whole is covered in steps.
|
|
228
|
+
...(target !== root ? { scope: target } : {}),
|
|
229
|
+
...(input.force ? { force: true } : {}),
|
|
230
|
+
...signalOf(context),
|
|
231
|
+
})
|
|
232
|
+
return render(result, (value) => {
|
|
233
|
+
const f = value.files
|
|
234
|
+
const lines = [
|
|
235
|
+
`Indexed ${value.root}: ${f.indexed} files (${f.parsed} parsed, ${f.unchanged} unchanged, ${f.removed} removed, ${f.skipped} skipped, ${f.withParseErrors} with parse errors) in ${value.durationMs} ms.`,
|
|
236
|
+
`${value.functions} functions, ${value.strings} string literals, ${value.calls} calls. ${value.persisted ? 'Cached on disk.' : 'Held in memory only; it is gone when this process exits.'}`,
|
|
237
|
+
]
|
|
238
|
+
if (value.truncated) lines.push(value.truncated)
|
|
239
|
+
const reasons = Object.entries(value.skippedReasons)
|
|
240
|
+
if (reasons.length > 0) {
|
|
241
|
+
lines.push(`Skipped: ${reasons.map(([why, n]) => `${n} ${why}`).join(', ')}.`)
|
|
242
|
+
}
|
|
243
|
+
return lines
|
|
244
|
+
})
|
|
245
|
+
}
|
|
246
|
+
case 'find_literal': {
|
|
247
|
+
const result = await provider.findLiteral({
|
|
248
|
+
root,
|
|
249
|
+
text: input.text,
|
|
250
|
+
path: target,
|
|
251
|
+
...(input.case_sensitive === false ? { caseSensitive: false } : {}),
|
|
252
|
+
...pageArgs(input),
|
|
253
|
+
...signalOf(context),
|
|
254
|
+
})
|
|
255
|
+
return renderPage(
|
|
256
|
+
result,
|
|
257
|
+
(hit) => {
|
|
258
|
+
const shown = JSON.stringify(hit.value) + (hit.valueTruncated ? ' (cut)' : '')
|
|
259
|
+
const kind = hit.kind === 'string' ? '' : ` (${hit.kind})`
|
|
260
|
+
const inside = hit.function ? ` in ${shortFunction(hit.function)}` : ' at top level'
|
|
261
|
+
return `${hit.line}:${hit.column} ${shown}${kind}${inside}${staleMark(hit.stale)}`
|
|
262
|
+
},
|
|
263
|
+
(hit) => relativeTo(root, hit.path),
|
|
264
|
+
)
|
|
265
|
+
}
|
|
266
|
+
case 'enclosing_function': {
|
|
267
|
+
const result = await provider.enclosingFunction({
|
|
268
|
+
root,
|
|
269
|
+
path: target,
|
|
270
|
+
line: input.line,
|
|
271
|
+
...(input.column !== undefined ? { column: input.column } : {}),
|
|
272
|
+
maxChars: input.max_chars ?? DEFAULT_MAX_CHARS,
|
|
273
|
+
...signalOf(context),
|
|
274
|
+
})
|
|
275
|
+
return render(result, (value) => {
|
|
276
|
+
if (!value.function) {
|
|
277
|
+
const place = `${relativeTo(root, target)}:${input.line}:${input.column ?? 1}`
|
|
278
|
+
return [
|
|
279
|
+
value.inClass !== undefined
|
|
280
|
+
? `${place} is in the body of class ${value.inClass}, outside every method; not inside any function.`
|
|
281
|
+
: `${place} is not inside any function.`,
|
|
282
|
+
]
|
|
283
|
+
}
|
|
284
|
+
const lines = [describeFunction(value.function, root)]
|
|
285
|
+
if (value.parents.length > 0) {
|
|
286
|
+
lines.push(`parents: ${value.parents.map(shortFunction).join(' < ')}`)
|
|
287
|
+
}
|
|
288
|
+
if (value.source) lines.push(...sourceLines(value.source))
|
|
289
|
+
return lines
|
|
290
|
+
})
|
|
291
|
+
}
|
|
292
|
+
case 'function_source': {
|
|
293
|
+
if (input.function_id === undefined && !(input.path !== undefined && input.name)) {
|
|
294
|
+
return fail('function_source needs `function_id`, or `path` together with `name`.')
|
|
262
295
|
}
|
|
263
|
-
const
|
|
264
|
-
|
|
265
|
-
|
|
296
|
+
const result = await provider.functionSource({
|
|
297
|
+
root,
|
|
298
|
+
...(input.function_id !== undefined ? { id: input.function_id } : {}),
|
|
299
|
+
...(input.name ? { name: input.name, path: target } : {}),
|
|
300
|
+
...(input.line !== undefined
|
|
301
|
+
? {
|
|
302
|
+
around: {
|
|
303
|
+
line: input.line,
|
|
304
|
+
...(input.column !== undefined ? { column: input.column } : {}),
|
|
305
|
+
},
|
|
306
|
+
}
|
|
307
|
+
: {}),
|
|
308
|
+
maxChars: input.max_chars ?? DEFAULT_MAX_CHARS,
|
|
309
|
+
...pageArgs(input),
|
|
310
|
+
...signalOf(context),
|
|
311
|
+
})
|
|
312
|
+
return renderPage(result, (item) =>
|
|
313
|
+
[
|
|
314
|
+
describeFunction(item.function, root),
|
|
315
|
+
item.parents.length > 0
|
|
316
|
+
? `parents: ${item.parents.map(shortFunction).join(' < ')}`
|
|
317
|
+
: undefined,
|
|
318
|
+
...sourceLines(item.source),
|
|
319
|
+
]
|
|
320
|
+
.filter((l): l is string => l !== undefined)
|
|
321
|
+
.join('\n'),
|
|
322
|
+
)
|
|
323
|
+
}
|
|
324
|
+
case 'callers_of': {
|
|
325
|
+
const scope = input.scope ?? 'file'
|
|
326
|
+
if (scope === 'file' && input.path === undefined) {
|
|
327
|
+
return fail(
|
|
328
|
+
'callers_of looks in one file by default, so it needs `path`. Pass `scope: "workspace"` to search the whole index; it matches by name only and will mix unrelated functions that share one.',
|
|
329
|
+
)
|
|
266
330
|
}
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
331
|
+
const result = await provider.callersOf({
|
|
332
|
+
root,
|
|
333
|
+
name: input.name,
|
|
334
|
+
scope,
|
|
335
|
+
...(input.path !== undefined ? { path: target } : {}),
|
|
336
|
+
...(input.match ? { match: input.match } : {}),
|
|
337
|
+
...pageArgs(input),
|
|
338
|
+
...signalOf(context),
|
|
339
|
+
})
|
|
340
|
+
return renderPage(
|
|
341
|
+
result,
|
|
342
|
+
(site) => {
|
|
343
|
+
const inside = site.caller ? ` in ${shortFunction(site.caller)}` : ' at top level'
|
|
344
|
+
const how = site.kind === 'new' ? 'new ' : ''
|
|
345
|
+
const tag =
|
|
346
|
+
site.kind === 'call-via'
|
|
347
|
+
? ' [call/apply/bind]'
|
|
348
|
+
: site.kind === 'tagged'
|
|
349
|
+
? ' [tagged template]'
|
|
350
|
+
: ''
|
|
351
|
+
const label = site.resolution === 'text-only' ? ' [text-only]' : ''
|
|
352
|
+
return `${site.line}:${site.column} ${how}${site.callee}(${site.argumentCount} arg${site.argumentCount === 1 ? '' : 's'})${tag}${label}${inside}${staleMark(site.stale)}`
|
|
353
|
+
},
|
|
354
|
+
(site) => relativeTo(root, site.path),
|
|
355
|
+
)
|
|
274
356
|
}
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
357
|
+
case 'imports': {
|
|
358
|
+
const result = await provider.imports({
|
|
359
|
+
root,
|
|
360
|
+
path: target,
|
|
361
|
+
...pageArgs(input),
|
|
362
|
+
...signalOf(context),
|
|
363
|
+
})
|
|
364
|
+
return render(
|
|
365
|
+
result,
|
|
366
|
+
(page) =>
|
|
367
|
+
page.items.length === 0
|
|
368
|
+
? [page.total === 0 ? 'No results.' : 'No results on this page.']
|
|
369
|
+
: [
|
|
370
|
+
relativeTo(root, target),
|
|
371
|
+
...page.items.map((edge) => ` ${describeEdge(edge)}`),
|
|
372
|
+
page.nextCursor !== undefined
|
|
373
|
+
? `-- ${page.items.length} of ${page.total} shown. next cursor: ${page.nextCursor}`
|
|
374
|
+
: `-- ${page.items.length} of ${page.total} shown. last page.`,
|
|
375
|
+
],
|
|
376
|
+
(page) => ({
|
|
377
|
+
total: page.total,
|
|
378
|
+
count: page.items.length,
|
|
379
|
+
items: page.items,
|
|
380
|
+
...(page.nextCursor !== undefined ? { nextCursor: page.nextCursor } : {}),
|
|
381
|
+
}),
|
|
300
382
|
)
|
|
301
383
|
}
|
|
302
|
-
const result = await provider.callersOf({
|
|
303
|
-
root,
|
|
304
|
-
name: input.name,
|
|
305
|
-
scope,
|
|
306
|
-
...(input.path !== undefined ? { path: target } : {}),
|
|
307
|
-
...(input.match ? { match: input.match } : {}),
|
|
308
|
-
...pageArgs(input),
|
|
309
|
-
...signalOf(context),
|
|
310
|
-
})
|
|
311
|
-
return renderPage(result, (site) => {
|
|
312
|
-
const inside = site.caller ? ` in ${describeFunction(site.caller)}` : ' at top level'
|
|
313
|
-
return `${site.path}:${site.line}:${site.column} ${site.kind === 'new' ? 'new ' : ''}${site.callee}(${site.argumentCount} arg${site.argumentCount === 1 ? '' : 's'})${inside}${staleMark(site.stale)}`
|
|
314
|
-
})
|
|
315
|
-
}
|
|
316
|
-
case 'imports': {
|
|
317
|
-
const result = await provider.imports({
|
|
318
|
-
root,
|
|
319
|
-
path: target,
|
|
320
|
-
...pageArgs(input),
|
|
321
|
-
...signalOf(context),
|
|
322
|
-
})
|
|
323
|
-
return renderPage(result, (edge) => describeEdge(edge, target))
|
|
324
384
|
}
|
|
325
|
-
}
|
|
385
|
+
})()
|
|
386
|
+
// With one root, a relative path is unambiguous. With several, the same
|
|
387
|
+
// `src/a.ts` could be in any of them, so the reader is told which this is.
|
|
388
|
+
return roots.length > 1 && outcome.success
|
|
389
|
+
? { ...outcome, output: `paths are relative to ${root}\n${outcome.output}` }
|
|
390
|
+
: outcome
|
|
326
391
|
},
|
|
327
392
|
})
|
|
328
393
|
|
|
@@ -381,13 +446,27 @@ async function owningRoot(roots: readonly string[], target: string): Promise<Own
|
|
|
381
446
|
return sawFile ? { kind: 'file' } : { kind: 'none' }
|
|
382
447
|
}
|
|
383
448
|
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
449
|
+
/** `path` relative to the root (POSIX separators), or `path` itself when it is not under it. */
|
|
450
|
+
function relativeTo(root: string, path: string): string {
|
|
451
|
+
const rel = relative(root, path)
|
|
452
|
+
// `..` as a whole segment is outside the root; `..x` is a directory named so.
|
|
453
|
+
if (rel === '' || rel === '..' || rel.startsWith(`..${sep}`) || isAbsolute(rel)) return path
|
|
454
|
+
return sep === '/' ? rel : rel.split(sep).join('/')
|
|
387
455
|
}
|
|
388
456
|
|
|
389
|
-
|
|
390
|
-
|
|
457
|
+
/** `Svc.init#12 L10-40`: enough to cite and to ask for by id, no repeated path or kind. */
|
|
458
|
+
function shortFunction(fn: AnalysisFunction): string {
|
|
459
|
+
const lines = fn.endLine === fn.line ? `L${fn.line}` : `L${fn.line}-${fn.endLine}`
|
|
460
|
+
return `${fn.qualifiedName ?? fn.name ?? '(anonymous)'}#${fn.id} ${lines}`
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
/** The full line a single function gets: kind and place included. */
|
|
464
|
+
function describeFunction(fn: AnalysisFunction, root: string): string {
|
|
465
|
+
return `${shortFunction(fn)} [${fn.kind}] ${relativeTo(root, fn.path)}:${fn.line}:${fn.column}`
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
function describeEdge(edge: AnalysisModuleEdge): string {
|
|
469
|
+
const where = `${edge.line}:${edge.column}`
|
|
391
470
|
if (edge.direction === 'import') {
|
|
392
471
|
const names = edge.names.length > 0 ? ` { ${edge.names.join(', ')} }` : ''
|
|
393
472
|
return `${where} import (${edge.kind})${names} from ${JSON.stringify(edge.source)}`
|
|
@@ -400,7 +479,9 @@ function sourceLines(slice: AnalysisSourceSlice): string[] {
|
|
|
400
479
|
const lines = ['--- source ---', slice.text]
|
|
401
480
|
if (slice.truncated) {
|
|
402
481
|
lines.push(
|
|
403
|
-
|
|
482
|
+
slice.window
|
|
483
|
+
? `--- cut: showing ${slice.text.length} of ${slice.chars} characters, the function's first ${slice.window.headerChars} and the part around the position; raise max_chars to see more ---`
|
|
484
|
+
: `--- cut: showing ${slice.text.length} of ${slice.chars} characters; raise max_chars to see more ---`,
|
|
404
485
|
)
|
|
405
486
|
}
|
|
406
487
|
return lines
|
|
@@ -449,6 +530,8 @@ function render<T>(
|
|
|
449
530
|
function renderPage<T>(
|
|
450
531
|
result: CodeAnalysisResult<AnalysisPage<T>>,
|
|
451
532
|
line: (item: T) => string,
|
|
533
|
+
/** Groups consecutive items of one file under a single heading. */
|
|
534
|
+
fileOf?: (item: T) => string,
|
|
452
535
|
): ToolResult {
|
|
453
536
|
return render(
|
|
454
537
|
result,
|
|
@@ -456,7 +539,16 @@ function renderPage<T>(
|
|
|
456
539
|
if (page.items.length === 0) {
|
|
457
540
|
return [page.total === 0 ? 'No results.' : 'No results on this page.']
|
|
458
541
|
}
|
|
459
|
-
const lines =
|
|
542
|
+
const lines: string[] = []
|
|
543
|
+
let current: string | undefined
|
|
544
|
+
for (const item of page.items) {
|
|
545
|
+
const file = fileOf?.(item)
|
|
546
|
+
if (file !== undefined && file !== current) {
|
|
547
|
+
lines.push(file)
|
|
548
|
+
current = file
|
|
549
|
+
}
|
|
550
|
+
lines.push(file === undefined ? line(item) : ` ${line(item)}`)
|
|
551
|
+
}
|
|
460
552
|
lines.push(
|
|
461
553
|
page.nextCursor !== undefined
|
|
462
554
|
? `-- ${page.items.length} of ${page.total} shown. next cursor: ${page.nextCursor}`
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { reconcileNewString, resolveOldString } from './edit-match.js'
|
|
1
2
|
import type { EditInput } from './edit.js'
|
|
2
3
|
|
|
3
4
|
/**
|
|
@@ -177,27 +178,47 @@ function applyOne(
|
|
|
177
178
|
|
|
178
179
|
const replacement = normalizeLineEndings(content, input)
|
|
179
180
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
181
|
+
// Exact first — which, after the CRLF reconciliation above, is what nearly
|
|
182
|
+
// every call is. On a miss, recover the span across a difference the model
|
|
183
|
+
// did not mean: a curly quote for a straight one, a `\uXXXX` escape for the
|
|
184
|
+
// character or the reverse. `resolveOldString` returns the real span in the
|
|
185
|
+
// file, and `reconcileNewString` carries the same change onto the
|
|
186
|
+
// replacement so it fits where it lands. See `edit-match.ts`.
|
|
187
|
+
let oldString = replacement.oldString
|
|
188
|
+
let newString = replacement.newString
|
|
189
|
+
if (!content.includes(oldString)) {
|
|
190
|
+
const resolved = resolveOldString(content, oldString)
|
|
191
|
+
if (resolved === null) {
|
|
192
|
+
return {
|
|
193
|
+
success: false,
|
|
194
|
+
error:
|
|
195
|
+
'old_string/oldStr not found in file. Make sure the string matches exactly, including whitespace and indentation.',
|
|
196
|
+
}
|
|
185
197
|
}
|
|
198
|
+
newString = reconcileNewString(oldString, resolved, newString)
|
|
199
|
+
oldString = resolved
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// A deletion takes the line's trailing newline with it, so removing a whole
|
|
203
|
+
// line does not leave a blank one behind. Only when the anchor is not
|
|
204
|
+
// already newline-terminated and the file has it followed by one.
|
|
205
|
+
if (newString === '' && !oldString.endsWith('\n') && content.includes(`${oldString}\n`)) {
|
|
206
|
+
oldString = `${oldString}\n`
|
|
186
207
|
}
|
|
187
208
|
|
|
188
209
|
if (replacement.replace_all) {
|
|
189
|
-
const parts = content.split(
|
|
210
|
+
const parts = content.split(oldString)
|
|
190
211
|
const replacements = parts.length - 1
|
|
191
212
|
return {
|
|
192
213
|
success: true,
|
|
193
|
-
content: parts.join(
|
|
214
|
+
content: parts.join(newString),
|
|
194
215
|
replacements,
|
|
195
216
|
}
|
|
196
217
|
}
|
|
197
218
|
|
|
198
219
|
// Uniqueness check: old_string/oldStr must appear exactly once
|
|
199
|
-
const firstIndex = content.indexOf(
|
|
200
|
-
const secondIndex = content.indexOf(
|
|
220
|
+
const firstIndex = content.indexOf(oldString)
|
|
221
|
+
const secondIndex = content.indexOf(oldString, firstIndex + 1)
|
|
201
222
|
|
|
202
223
|
if (secondIndex !== -1) {
|
|
203
224
|
const lineNumber = content.slice(0, firstIndex).split('\n').length
|
|
@@ -211,9 +232,7 @@ function applyOne(
|
|
|
211
232
|
return {
|
|
212
233
|
success: true,
|
|
213
234
|
content:
|
|
214
|
-
content.slice(0, firstIndex) +
|
|
215
|
-
replacement.newString +
|
|
216
|
-
content.slice(firstIndex + replacement.oldString.length),
|
|
235
|
+
content.slice(0, firstIndex) + newString + content.slice(firstIndex + oldString.length),
|
|
217
236
|
replacements: 1,
|
|
218
237
|
}
|
|
219
238
|
}
|