@tduyng/pi-outline 0.0.1 → 0.0.3
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/README.md +34 -8
- package/package.json +1 -1
- package/src/declarations.ts +10 -2
- package/src/outline.ts +15 -7
package/README.md
CHANGED
|
@@ -1,23 +1,49 @@
|
|
|
1
1
|
# @tduyng/pi-outline
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
A map of a source file instead of the file. One `outline` call lists every declaration with its line span, so the read that follows fetches one body rather than a thousand lines.
|
|
4
|
+
|
|
5
|
+
## What it costs
|
|
6
|
+
|
|
7
|
+
`outline` is 161 tokens a request, and that is the whole bill.
|
|
8
|
+
|
|
9
|
+
What it saves is larger and grows with the file. On a 1,213-line, 51 KB TypeScript file the depth-1 outline is 47 rows, 3.4 KB. The whole read would put 51 KB in the transcript and re-bill it on every later turn of the session, which is the cheapest mistake to make in pi and the most expensive to keep.
|
|
4
10
|
|
|
5
11
|
## The tool
|
|
6
12
|
|
|
7
|
-
`outline` takes a `path` and an optional `depth`
|
|
13
|
+
`outline` takes a `path` and an optional `depth` from 0 to 5, default 1. It prints one row per declaration: the span, then the declaration line. Members sit under their class or impl, one indent per level. A row whose declaration has children beyond `depth` ends in `[N nested]`.
|
|
14
|
+
|
|
15
|
+
```text
|
|
16
|
+
outline(path: "src/goal.ts", depth: 1)
|
|
17
|
+
```
|
|
8
18
|
|
|
9
|
-
Covered:
|
|
19
|
+
Covered:
|
|
10
20
|
|
|
11
|
-
|
|
21
|
+
| Language | Declarations |
|
|
22
|
+
| --- | --- |
|
|
23
|
+
| TypeScript, JavaScript | functions, classes, interfaces, type aliases, enums, class methods |
|
|
24
|
+
| Python | `def`, `class` |
|
|
25
|
+
| Go | `func`, `type` |
|
|
26
|
+
| Rust | `fn`, `struct`, `enum`, `trait`, `impl`, `mod` |
|
|
27
|
+
| Markdown | headings |
|
|
12
28
|
|
|
13
|
-
|
|
29
|
+
Headings inside code fences are ignored.
|
|
30
|
+
|
|
31
|
+
## Where it fits
|
|
32
|
+
|
|
33
|
+
A read of a file over 350 lines is blocked by pi-subagent's bulk-read guard, and the block message names `outline` first. The pair is the intended path: outline, pick the span, then a ranged read of the one body you need.
|
|
34
|
+
|
|
35
|
+
With no guard installed the tool still works, it just stops being the thing that catches you.
|
|
14
36
|
|
|
15
37
|
## Limits
|
|
16
38
|
|
|
17
|
-
No parser is used, so the rules are regular expressions over single lines. Spans come from indentation: a span runs to the next line at the same or a lower indent, and includes the closing brace that follows the body.
|
|
39
|
+
No parser is used, so the rules are regular expressions over single lines. Spans come from indentation: a span runs to the next line at the same or a lower indent, and includes the closing brace that follows the body.
|
|
18
40
|
|
|
19
|
-
Files over
|
|
41
|
+
Expect an occasional missed declaration on unusual formatting, and an object-literal method may show as a member. A file the rules do not cover gets a line saying so, with grep or a ranged read as the fallback. Files over 2 MB are refused.
|
|
20
42
|
|
|
21
43
|
## Install
|
|
22
44
|
|
|
23
|
-
|
|
45
|
+
```bash
|
|
46
|
+
pi install npm:@tduyng/pi-outline
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Local checkout: `pi install /path/to/dotfiles/pi/agent/extensions/pi-outline`
|
package/package.json
CHANGED
package/src/declarations.ts
CHANGED
|
@@ -63,6 +63,7 @@ interface Mark {
|
|
|
63
63
|
|
|
64
64
|
function markLines(lines: string[], language: Language): Mark[] {
|
|
65
65
|
let inFence = false
|
|
66
|
+
let inTemplate = false
|
|
66
67
|
return lines.map((line) => {
|
|
67
68
|
const blank = line.trim() === ''
|
|
68
69
|
if (language === 'markdown') {
|
|
@@ -74,9 +75,16 @@ function markLines(lines: string[], language: Language): Mark[] {
|
|
|
74
75
|
const level = heading ? heading[1]!.length : 0
|
|
75
76
|
return { blank, measure: level, declares: heading !== null, boundary: heading !== null }
|
|
76
77
|
}
|
|
78
|
+
const startedInsideTemplate = inTemplate
|
|
79
|
+
if ((line.match(/(?<!\\)`/g) ?? []).length % 2 === 1) inTemplate = !inTemplate
|
|
77
80
|
const measure = /^[ \t]*/.exec(line)![0].length
|
|
78
|
-
const declares = PATTERNS[language].some((pattern) => pattern.test(line))
|
|
79
|
-
return {
|
|
81
|
+
const declares = !startedInsideTemplate && PATTERNS[language].some((pattern) => pattern.test(line))
|
|
82
|
+
return {
|
|
83
|
+
blank,
|
|
84
|
+
measure,
|
|
85
|
+
declares,
|
|
86
|
+
boundary: !startedInsideTemplate && !blank && !CLOSER.test(line)
|
|
87
|
+
}
|
|
80
88
|
})
|
|
81
89
|
}
|
|
82
90
|
|
package/src/outline.ts
CHANGED
|
@@ -33,15 +33,23 @@ export const outlineTool = {
|
|
|
33
33
|
const text = `No outline for ${params.path}: no declaration rules for this file type. Use grep, or read with offset and limit.`
|
|
34
34
|
return { content: [{ type: 'text', text }], details: undefined }
|
|
35
35
|
}
|
|
36
|
-
|
|
37
|
-
|
|
36
|
+
try {
|
|
37
|
+
if (statSync(absolute).size > MAX_BYTES) {
|
|
38
|
+
const text = `${params.path} is over 2MB. Use grep for a name, or read with offset and limit.`
|
|
39
|
+
return { content: [{ type: 'text', text }], details: undefined }
|
|
40
|
+
}
|
|
41
|
+
const content = readFileSync(absolute, 'utf8')
|
|
42
|
+
const declarations = findDeclarations(content, language)
|
|
43
|
+
const lineCount = content.split('\n').length
|
|
44
|
+
const text = renderOutline(params.path, language, lineCount, declarations, params.depth ?? 1)
|
|
38
45
|
return { content: [{ type: 'text', text }], details: undefined }
|
|
46
|
+
} catch (error) {
|
|
47
|
+
const reason = error instanceof Error ? error.message : String(error)
|
|
48
|
+
return {
|
|
49
|
+
content: [{ type: 'text', text: `Could not outline ${params.path}: ${reason}` }],
|
|
50
|
+
details: undefined
|
|
51
|
+
}
|
|
39
52
|
}
|
|
40
|
-
const content = readFileSync(absolute, 'utf8')
|
|
41
|
-
const declarations = findDeclarations(content, language)
|
|
42
|
-
const lineCount = content.split('\n').length
|
|
43
|
-
const text = renderOutline(params.path, language, lineCount, declarations, params.depth ?? 1)
|
|
44
|
-
return { content: [{ type: 'text', text }], details: undefined }
|
|
45
53
|
}
|
|
46
54
|
} satisfies ToolDefinition<typeof parameters>
|
|
47
55
|
|