@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 CHANGED
@@ -1,23 +1,49 @@
1
1
  # @tduyng/pi-outline
2
2
 
3
- Declarations of a source file with the line span of each, so a read fetches one body by offset and limit instead of the whole file.
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` (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]`.
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: TypeScript and JavaScript (functions, classes, interfaces, type aliases, enums, and class methods), Python (`def` and `class`), Go (`func` and `type`), Rust (`fn`, `struct`, `enum`, `trait`, `impl`, `mod`), and Markdown headings. Headings inside code fences are ignored.
19
+ Covered:
10
20
 
11
- ## Why it is cheap
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
- A big read is blocked by pi-subagent's bulk-read guard, and the block message now names `outline` first. On pi-goal's `goal.ts` (1,213 lines, 51KB) the depth-1 outline is 47 rows, 3.4KB. A whole read would put all 51KB into context and re-bill it on every later turn.
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. 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.
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 2MB are refused.
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
- Local checkout, as part of this dotfiles package: the root `package.json` lists `./extensions/pi-outline/src/outline.ts`.
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tduyng/pi-outline",
3
- "version": "0.0.1",
3
+ "version": "0.0.3",
4
4
  "description": "Declarations of a source file with line spans, so a read fetches one body instead of the whole file.",
5
5
  "keywords": [
6
6
  "pi",
@@ -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 { blank, measure, declares, boundary: !blank && !CLOSER.test(line) }
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
- if (statSync(absolute).size > MAX_BYTES) {
37
- const text = `${params.path} is over 2MB. Use grep for a name, or read with offset and limit.`
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