pi-monofold 0.3.0 → 0.3.2
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 +172 -206
- package/file-read-preview.ts +208 -0
- package/index.ts +1729 -1621
- package/package.json +61 -58
- package/path-normalize.ts +25 -0
- package/read-caps.ts +137 -0
package/README.md
CHANGED
|
@@ -1,206 +1,172 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
[![
|
|
4
|
-
[![Publish
|
|
5
|
-
[![
|
|
6
|
-
[![
|
|
7
|
-
[![
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
##
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
```
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
/monofold
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
## Agent API
|
|
175
|
-
|
|
176
|
-
Pi agents use strict `monofold_*` tools behind the natural-language command surface:
|
|
177
|
-
|
|
178
|
-
- `monofold_list`: show manifest and git status summary.
|
|
179
|
-
- `monofold_read`: read files, search text, or show a tree inside readable workspaces.
|
|
180
|
-
- `monofold_write`: create routed Markdown outputs by `routeType`, `title`, and `body`.
|
|
181
|
-
- `monofold_git`: run guarded workspace git `status`, `commit`, `push`, or `commitPush`.
|
|
182
|
-
- `monofold_init`: queue `/monofold:init`.
|
|
183
|
-
|
|
184
|
-
Project Workspaces are listed under `workspaces[].projects`. Their `path` is relative to the parent workspace, `tags` are combined with parent tags, `capabilities` inherit unless explicitly replaced, and missing routes default to `default: "."` when the effective target has `writeDocs`.
|
|
185
|
-
|
|
186
|
-
## Updating configuration
|
|
187
|
-
|
|
188
|
-
`.pi/monofold.yaml` is the canonical config file. Legacy `.pi/monofold.yml` is still readable. Intent commands try to migrate a legacy-only config automatically, show a notice, and continue with the legacy config if migration fails. `/monofold:update` migrates or cleans up legacy config, writes timestamped backups such as `.pi/monofold.yml.bak-20260524-153012`, and removes the legacy file after a successful write. If both `.yaml` and `.yml` exist, normal intent commands prefer canonical `.yaml`; `/monofold:update` handles legacy cleanup.
|
|
189
|
-
|
|
190
|
-
`/monofold:update` is a configuration migration command, not a Pi package updater. Use `pi update`, `pi update --extensions`, or `pi install ...@new-ref` for package updates.
|
|
191
|
-
|
|
192
|
-
After migration, you can provide a natural-language configuration change request. The command hands that request to the Pi agent, which edits `.pi/monofold.yaml` directly and validates the result through the manifest path:
|
|
193
|
-
|
|
194
|
-
```text
|
|
195
|
-
/monofold:update add 4_Project/NewApp as a Project Workspace under Obsidian Vault with tags project,newapp and progress route Progress
|
|
196
|
-
```
|
|
197
|
-
|
|
198
|
-
## Guard
|
|
199
|
-
|
|
200
|
-
When `.pi/monofold.yaml` or legacy `.pi/monofold.yml` exists, Pi Monofold guards standard `read/write/edit/grep/find/bash` calls against workspace capabilities.
|
|
201
|
-
|
|
202
|
-
- Unknown path: confirm in UI, block without UI.
|
|
203
|
-
- Docs write: requires `writeDocs`.
|
|
204
|
-
- Code edit: requires `editCode`.
|
|
205
|
-
- Bash: requires workspace cwd and `runCommands`.
|
|
206
|
-
- Git commit/push via bash: blocked; use `/monofold:git` or the `monofold_git` agent tool.
|
|
1
|
+
# pi-monofold
|
|
2
|
+
|
|
3
|
+
[](https://github.com/eiei114/pi-monofold/actions/workflows/ci.yml)
|
|
4
|
+
[](https://github.com/eiei114/pi-monofold/actions/workflows/publish.yml)
|
|
5
|
+
[](https://www.npmjs.com/package/pi-monofold)
|
|
6
|
+
[](https://www.npmjs.com/package/pi-monofold)
|
|
7
|
+
[](./LICENSE)
|
|
8
|
+
[](https://github.com/eiei114/pi-monofold)
|
|
9
|
+
[](https://docs.npmjs.com/generating-provenance-statements)
|
|
10
|
+
|
|
11
|
+
Pi extension that folds multiple local repositories and folders into a guarded **Virtual Monorepo** for AI agents.
|
|
12
|
+
|
|
13
|
+
## What this is
|
|
14
|
+
|
|
15
|
+
Pi Monofold (`pi-monofold`) keeps repositories physically separate while giving Pi a lightweight manifest, routed writes, workspace-aware reads, guarded commands, and explicit git flows. Documentation, rules, product context, and implementation code can appear as one connected system without migrating everything into a single git repository.
|
|
16
|
+
|
|
17
|
+
See [docs/usage.md](./docs/usage.md) for configuration, commands, agent tools, and guard behavior.
|
|
18
|
+
|
|
19
|
+
## Features
|
|
20
|
+
|
|
21
|
+
- **Virtual monorepo manifest** — declare workspaces and project workspaces in `.pi/monofold.yaml`
|
|
22
|
+
- **Routed Markdown writes** — route PRDs, progress notes, and other doc types to configured folders
|
|
23
|
+
- **Workspace-aware reads** — list, read, search, and tree views scoped to readable workspaces
|
|
24
|
+
- **Capability guard** — block or confirm `read` / `write` / `edit` / `grep` / `find` / `bash` based on workspace tags
|
|
25
|
+
- **Focus presets** — tag-based focus targets for the control workspace
|
|
26
|
+
- **Natural-language commands** — `/monofold:explore`, `/monofold:write`, `/monofold:config`, `/monofold:git`, and more
|
|
27
|
+
- **Strict agent tools** — `monofold_*` tools for programmatic access behind the command surface
|
|
28
|
+
- **Config migration** — upgrade legacy `.pi/monofold.yml` with backups and validation
|
|
29
|
+
|
|
30
|
+
## Install
|
|
31
|
+
|
|
32
|
+
Pi Monofold is a Pi package. Install it with Pi's package installer from git or npm.
|
|
33
|
+
|
|
34
|
+
> Security: Pi packages run with full system access. Review packages before installing third-party code.
|
|
35
|
+
|
|
36
|
+
### From git
|
|
37
|
+
|
|
38
|
+
```powershell
|
|
39
|
+
pi install git:github.com/eiei114/pi-monofold
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Project-local install:
|
|
43
|
+
|
|
44
|
+
```powershell
|
|
45
|
+
pi install -l git:github.com/eiei114/pi-monofold
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Pin a version:
|
|
49
|
+
|
|
50
|
+
```powershell
|
|
51
|
+
pi install git:github.com/eiei114/pi-monofold@v0.3.2
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Try without installing:
|
|
55
|
+
|
|
56
|
+
```powershell
|
|
57
|
+
pi -e git:github.com/eiei114/pi-monofold
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### From npm
|
|
61
|
+
|
|
62
|
+
```powershell
|
|
63
|
+
pi install npm:pi-monofold
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Project-local install:
|
|
67
|
+
|
|
68
|
+
```powershell
|
|
69
|
+
pi install -l npm:pi-monofold
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Pin a version:
|
|
73
|
+
|
|
74
|
+
```powershell
|
|
75
|
+
pi install npm:pi-monofold@0.3.2
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Try without installing:
|
|
79
|
+
|
|
80
|
+
```powershell
|
|
81
|
+
pi -e npm:pi-monofold
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## Quick start
|
|
85
|
+
|
|
86
|
+
1. Install the extension (see [Install](#install)).
|
|
87
|
+
2. In your control repository, create `.pi/monofold.yaml` with at least one workspace entry (or run `/monofold:init`).
|
|
88
|
+
3. Start Pi in the control repository and run `/monofold:explore show the project workspaces`.
|
|
89
|
+
4. Use `/monofold:write` for routed Markdown outputs and `/monofold:git` for guarded git workflows.
|
|
90
|
+
|
|
91
|
+
Example command flows: [docs/examples.md](./docs/examples.md).
|
|
92
|
+
|
|
93
|
+
## Usage summary
|
|
94
|
+
|
|
95
|
+
| Surface | Purpose |
|
|
96
|
+
|---------|---------|
|
|
97
|
+
| `/monofold:explore` | List, read, search, or inspect workspace trees |
|
|
98
|
+
| `/monofold:write` | Create routed Markdown outputs |
|
|
99
|
+
| `/monofold:config` | Add or change workspaces and project workspaces |
|
|
100
|
+
| `/monofold:git` | Run guarded git status, commit, push, or commit+push |
|
|
101
|
+
| `/monofold:guide` | Interactive guide for common flows |
|
|
102
|
+
| `/monofold:init` | Create or update `.pi/monofold.yaml` |
|
|
103
|
+
| `/monofold:update` | Migrate legacy config and optionally request config edits |
|
|
104
|
+
|
|
105
|
+
Agent tools (`monofold_list`, `monofold_read`, `monofold_write`, `monofold_git`, `monofold_init`) sit behind these commands. Full reference: [docs/usage.md](./docs/usage.md).
|
|
106
|
+
|
|
107
|
+
## Package contents
|
|
108
|
+
|
|
109
|
+
```text
|
|
110
|
+
pi-monofold/
|
|
111
|
+
├── .github/workflows/
|
|
112
|
+
│ ├── auto-release.yml # Auto-tag + release on merge to main
|
|
113
|
+
│ ├── ci.yml # Validate on PR / push
|
|
114
|
+
│ └── publish.yml # Publish to npm (Trusted Publishing)
|
|
115
|
+
├── docs/
|
|
116
|
+
│ ├── usage.md # Config, commands, agent API, guard
|
|
117
|
+
│ ├── examples.md # Command examples
|
|
118
|
+
│ └── release.md # Release and publish flow
|
|
119
|
+
├── tests/
|
|
120
|
+
│ └── focus-preset.test.ts
|
|
121
|
+
├── CHANGELOG.md
|
|
122
|
+
├── SECURITY.md
|
|
123
|
+
├── focus-preset.ts
|
|
124
|
+
├── index.ts
|
|
125
|
+
├── LICENSE
|
|
126
|
+
├── package.json
|
|
127
|
+
├── README.md
|
|
128
|
+
├── validation.ts
|
|
129
|
+
└── tsconfig.json
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
## Development
|
|
133
|
+
|
|
134
|
+
Clone and validate:
|
|
135
|
+
|
|
136
|
+
```powershell
|
|
137
|
+
git clone https://github.com/eiei114/pi-monofold.git
|
|
138
|
+
cd pi-monofold
|
|
139
|
+
npm install
|
|
140
|
+
npm run check
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
Try the local checkout without installing:
|
|
144
|
+
|
|
145
|
+
```powershell
|
|
146
|
+
pi -e .
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
## Release
|
|
150
|
+
|
|
151
|
+
Releases are automated. See [docs/release.md](./docs/release.md) for details.
|
|
152
|
+
|
|
153
|
+
1. Bump `version` in `package.json` and update `CHANGELOG.md`.
|
|
154
|
+
2. Merge to `main`.
|
|
155
|
+
3. **Auto Release** tags `v<version>` and creates a GitHub release when the tag is new.
|
|
156
|
+
4. The tag triggers **Publish**, which publishes to npm with OIDC provenance.
|
|
157
|
+
|
|
158
|
+
## Security
|
|
159
|
+
|
|
160
|
+
Pi Monofold intercepts standard Pi tool calls when monofold config is present. Writes and shell commands are allowed only when the resolved workspace grants the matching capability. Git commit/push via raw `bash` is blocked; use `/monofold:git` or `monofold_git` instead.
|
|
161
|
+
|
|
162
|
+
Report vulnerabilities per [SECURITY.md](./SECURITY.md).
|
|
163
|
+
|
|
164
|
+
## Links
|
|
165
|
+
|
|
166
|
+
- **Repository**: <https://github.com/eiei114/pi-monofold>
|
|
167
|
+
- **npm**: <https://www.npmjs.com/package/pi-monofold>
|
|
168
|
+
- **Issues**: <https://github.com/eiei114/pi-monofold/issues>
|
|
169
|
+
|
|
170
|
+
## License
|
|
171
|
+
|
|
172
|
+
[MIT](LICENSE)
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
export const DEFAULT_PREVIEW_LINES = 20;
|
|
2
|
+
export const DEFAULT_PREVIEW_CHARS = 2_000;
|
|
3
|
+
|
|
4
|
+
export type FileReadOptions = {
|
|
5
|
+
includeContent?: boolean;
|
|
6
|
+
maxChars?: number;
|
|
7
|
+
head?: number;
|
|
8
|
+
tail?: number;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export type FileReadFileStat = {
|
|
12
|
+
size: number;
|
|
13
|
+
mtime: Date;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export type FileReadDetails = {
|
|
17
|
+
byteSize: number;
|
|
18
|
+
characterCount: number;
|
|
19
|
+
lineCount: number;
|
|
20
|
+
modifiedTime: string;
|
|
21
|
+
truncated: boolean;
|
|
22
|
+
previewLineCount: number;
|
|
23
|
+
previewCharacterCount: number;
|
|
24
|
+
includeContent: boolean;
|
|
25
|
+
maxChars?: number;
|
|
26
|
+
head?: number;
|
|
27
|
+
tail?: number;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export type FileReadResponse = {
|
|
31
|
+
text: string;
|
|
32
|
+
details: FileReadDetails;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export function assertPositiveInt(value: number, label: string): number {
|
|
36
|
+
if (!Number.isInteger(value) || value <= 0) {
|
|
37
|
+
throw new Error(`${label} must be a positive integer`);
|
|
38
|
+
}
|
|
39
|
+
return value;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function splitLines(content: string): string[] {
|
|
43
|
+
if (content.length === 0) {
|
|
44
|
+
return [""];
|
|
45
|
+
}
|
|
46
|
+
const normalized = content.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
|
|
47
|
+
const lines = normalized.split("\n");
|
|
48
|
+
if (lines.at(-1) === "" && (content.endsWith("\n") || content.endsWith("\r"))) {
|
|
49
|
+
lines.pop();
|
|
50
|
+
}
|
|
51
|
+
return lines.length === 0 ? [""] : lines;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function takeHeadLines(lines: string[], count: number): string[] {
|
|
55
|
+
return lines.slice(0, count);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function takeTailLines(lines: string[], count: number): string[] {
|
|
59
|
+
if (count >= lines.length) {
|
|
60
|
+
return lines;
|
|
61
|
+
}
|
|
62
|
+
return lines.slice(lines.length - count);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function truncateChars(text: string, maxChars: number): { text: string; truncated: boolean } {
|
|
66
|
+
if (text.length <= maxChars) {
|
|
67
|
+
return { text, truncated: false };
|
|
68
|
+
}
|
|
69
|
+
return { text: text.slice(0, maxChars), truncated: true };
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function truncationHint(details: FileReadDetails): string {
|
|
73
|
+
const parts: string[] = [];
|
|
74
|
+
if (details.truncated) {
|
|
75
|
+
parts.push(
|
|
76
|
+
`Showing ${details.previewLineCount} of ${details.lineCount} lines and ${details.previewCharacterCount} of ${details.characterCount} characters.`,
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
parts.push(
|
|
80
|
+
"Pass includeContent: true for full content, or use head, tail, and/or maxChars for a larger bounded range.",
|
|
81
|
+
);
|
|
82
|
+
return parts.join(" ");
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function buildHeadTailPreview(lines: string[], head: number | undefined, tail: number | undefined): string {
|
|
86
|
+
const headCount = head ?? 0;
|
|
87
|
+
const tailCount = tail ?? 0;
|
|
88
|
+
if (headCount > 0 && tailCount > 0) {
|
|
89
|
+
const headLines = takeHeadLines(lines, headCount);
|
|
90
|
+
const tailLines = takeTailLines(lines, tailCount);
|
|
91
|
+
if (headCount + tailCount >= lines.length) {
|
|
92
|
+
return lines.join("\n");
|
|
93
|
+
}
|
|
94
|
+
const omitted = lines.length - headLines.length - tailLines.length;
|
|
95
|
+
return [
|
|
96
|
+
...headLines,
|
|
97
|
+
`... [${omitted} line${omitted === 1 ? "" : "s"} omitted] ...`,
|
|
98
|
+
...tailLines,
|
|
99
|
+
].join("\n");
|
|
100
|
+
}
|
|
101
|
+
if (headCount > 0) {
|
|
102
|
+
return takeHeadLines(lines, headCount).join("\n");
|
|
103
|
+
}
|
|
104
|
+
if (tailCount > 0) {
|
|
105
|
+
return takeTailLines(lines, tailCount).join("\n");
|
|
106
|
+
}
|
|
107
|
+
return takeHeadLines(lines, DEFAULT_PREVIEW_LINES).join("\n");
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function resolveExplicitLimits(options: FileReadOptions): {
|
|
111
|
+
includeContent: boolean;
|
|
112
|
+
maxChars?: number;
|
|
113
|
+
head?: number;
|
|
114
|
+
tail?: number;
|
|
115
|
+
} {
|
|
116
|
+
const includeContent = options.includeContent === true;
|
|
117
|
+
const maxChars = options.maxChars === undefined ? undefined : assertPositiveInt(options.maxChars, "maxChars");
|
|
118
|
+
const head = options.head === undefined ? undefined : assertPositiveInt(options.head, "head");
|
|
119
|
+
const tail = options.tail === undefined ? undefined : assertPositiveInt(options.tail, "tail");
|
|
120
|
+
return { includeContent, maxChars, head, tail };
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export function buildFileReadResponse(
|
|
124
|
+
content: string,
|
|
125
|
+
options: FileReadOptions,
|
|
126
|
+
fileStat: FileReadFileStat,
|
|
127
|
+
context: { relativePath: string },
|
|
128
|
+
): FileReadResponse {
|
|
129
|
+
const limits = resolveExplicitLimits(options);
|
|
130
|
+
const lines = splitLines(content);
|
|
131
|
+
const lineCount = lines.length;
|
|
132
|
+
const characterCount = content.length;
|
|
133
|
+
|
|
134
|
+
let previewText: string;
|
|
135
|
+
let truncated: boolean;
|
|
136
|
+
let previewLineCount: number;
|
|
137
|
+
let previewCharacterCount: number;
|
|
138
|
+
|
|
139
|
+
if (limits.includeContent && limits.maxChars === undefined && limits.head === undefined && limits.tail === undefined) {
|
|
140
|
+
previewText = content;
|
|
141
|
+
truncated = false;
|
|
142
|
+
previewLineCount = lineCount;
|
|
143
|
+
previewCharacterCount = characterCount;
|
|
144
|
+
} else if (limits.head !== undefined || limits.tail !== undefined) {
|
|
145
|
+
const headCount = limits.head ?? 0;
|
|
146
|
+
const tailCount = limits.tail ?? 0;
|
|
147
|
+
previewText = buildHeadTailPreview(lines, limits.head, limits.tail);
|
|
148
|
+
const maxChars = limits.maxChars ?? Number.POSITIVE_INFINITY;
|
|
149
|
+
const charResult = truncateChars(previewText, maxChars);
|
|
150
|
+
previewText = charResult.text;
|
|
151
|
+
const linesFullyShown =
|
|
152
|
+
headCount + tailCount >= lineCount ||
|
|
153
|
+
(headCount > 0 && tailCount === 0 && headCount >= lineCount) ||
|
|
154
|
+
(tailCount > 0 && headCount === 0 && tailCount >= lineCount);
|
|
155
|
+
truncated = charResult.truncated || !linesFullyShown;
|
|
156
|
+
previewLineCount = previewText === "" ? 0 : previewText.split("\n").length;
|
|
157
|
+
previewCharacterCount = previewText.length;
|
|
158
|
+
} else if (limits.maxChars !== undefined) {
|
|
159
|
+
const charResult = truncateChars(content, limits.maxChars);
|
|
160
|
+
previewText = charResult.text;
|
|
161
|
+
truncated = charResult.truncated || previewText.length < content.length;
|
|
162
|
+
previewLineCount = previewText === "" ? 0 : previewText.split("\n").length;
|
|
163
|
+
previewCharacterCount = previewText.length;
|
|
164
|
+
} else {
|
|
165
|
+
const defaultLines = takeHeadLines(lines, DEFAULT_PREVIEW_LINES);
|
|
166
|
+
previewText = defaultLines.join("\n");
|
|
167
|
+
const charResult = truncateChars(previewText, DEFAULT_PREVIEW_CHARS);
|
|
168
|
+
previewText = charResult.text;
|
|
169
|
+
truncated = charResult.truncated || defaultLines.length < lines.length;
|
|
170
|
+
previewLineCount = previewText === "" ? 0 : previewText.split("\n").length;
|
|
171
|
+
previewCharacterCount = previewText.length;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const details: FileReadDetails = {
|
|
175
|
+
byteSize: fileStat.size,
|
|
176
|
+
characterCount,
|
|
177
|
+
lineCount,
|
|
178
|
+
modifiedTime: fileStat.mtime.toISOString(),
|
|
179
|
+
truncated,
|
|
180
|
+
previewLineCount,
|
|
181
|
+
previewCharacterCount,
|
|
182
|
+
includeContent: limits.includeContent,
|
|
183
|
+
...(limits.maxChars === undefined ? {} : { maxChars: limits.maxChars }),
|
|
184
|
+
...(limits.head === undefined ? {} : { head: limits.head }),
|
|
185
|
+
...(limits.tail === undefined ? {} : { tail: limits.tail }),
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
const metadataLines = [
|
|
189
|
+
`Path: ${context.relativePath}`,
|
|
190
|
+
`Byte size: ${details.byteSize}`,
|
|
191
|
+
`Characters: ${details.characterCount}`,
|
|
192
|
+
`Lines: ${details.lineCount}`,
|
|
193
|
+
`Modified: ${details.modifiedTime}`,
|
|
194
|
+
];
|
|
195
|
+
|
|
196
|
+
const sections = [metadataLines.join("\n")];
|
|
197
|
+
if (previewText.length > 0) {
|
|
198
|
+
sections.push("", "--- preview ---", previewText);
|
|
199
|
+
}
|
|
200
|
+
if (truncated) {
|
|
201
|
+
sections.push("", `[truncated] ${truncationHint(details)}`);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
return {
|
|
205
|
+
text: sections.join("\n"),
|
|
206
|
+
details,
|
|
207
|
+
};
|
|
208
|
+
}
|