@oh-my-pi/pi-coding-agent 14.0.2 → 14.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/CHANGELOG.md +11 -1
- package/package.json +7 -7
- package/src/config/model-registry.ts +17 -3
- package/src/edit/modes/chunk.ts +1 -1
- package/src/extensibility/custom-commands/bundled/review/index.ts +34 -13
- package/src/lsp/config.ts +18 -2
- package/src/modes/components/session-observer-overlay.ts +21 -12
- package/src/prompts/review-request.md +6 -0
- package/src/prompts/tools/chunk-edit.md +17 -13
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [14.0.3] - 2026-04-09
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
- Fixed cached Ollama discovery rows so upgraded installs switch to the OpenAI Responses transport instead of staying on the old completions transport
|
|
10
|
+
|
|
5
11
|
## [14.0.2] - 2026-04-09
|
|
6
12
|
### Added
|
|
7
13
|
|
|
@@ -258,6 +264,10 @@
|
|
|
258
264
|
- `/autoresearch` toggles like `/plan` when empty; slash completion no longer suggests `off`/`clear` on an empty prefix after the command
|
|
259
265
|
- Chunk-mode read/edit edge cases (zero-width gap replaces, stale batch diagnostics, grouped Go receivers, line-count headers, parse error locations)
|
|
260
266
|
|
|
267
|
+
### Added
|
|
268
|
+
|
|
269
|
+
- `/review` command now accepts inline args as custom instructions appended to the generated prompt for all structured review modes (PR-style, uncommitted, specific commit). When inline args are provided, option 4 (editor) is suppressed from the menu. The no-UI (Task tool) path forwards args as a focus hint.
|
|
270
|
+
|
|
261
271
|
## [13.19.0] - 2026-04-05
|
|
262
272
|
|
|
263
273
|
### Added
|
|
@@ -6890,4 +6900,4 @@ Initial public release.
|
|
|
6890
6900
|
- Git branch display in footer
|
|
6891
6901
|
- Message queueing during streaming responses
|
|
6892
6902
|
- OAuth integration for Gmail and Google Calendar access
|
|
6893
|
-
- HTML export with syntax highlighting and collapsible sections
|
|
6903
|
+
- HTML export with syntax highlighting and collapsible sections
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@oh-my-pi/pi-coding-agent",
|
|
4
|
-
"version": "14.0.
|
|
4
|
+
"version": "14.0.3",
|
|
5
5
|
"description": "Coding agent CLI with read, bash, edit, write tools and session management",
|
|
6
6
|
"homepage": "https://github.com/can1357/oh-my-pi",
|
|
7
7
|
"author": "Can Boluk",
|
|
@@ -46,12 +46,12 @@
|
|
|
46
46
|
"dependencies": {
|
|
47
47
|
"@agentclientprotocol/sdk": "0.16.1",
|
|
48
48
|
"@mozilla/readability": "^0.6",
|
|
49
|
-
"@oh-my-pi/omp-stats": "14.0.
|
|
50
|
-
"@oh-my-pi/pi-agent-core": "14.0.
|
|
51
|
-
"@oh-my-pi/pi-ai": "14.0.
|
|
52
|
-
"@oh-my-pi/pi-natives": "14.0.
|
|
53
|
-
"@oh-my-pi/pi-tui": "14.0.
|
|
54
|
-
"@oh-my-pi/pi-utils": "14.0.
|
|
49
|
+
"@oh-my-pi/omp-stats": "14.0.3",
|
|
50
|
+
"@oh-my-pi/pi-agent-core": "14.0.3",
|
|
51
|
+
"@oh-my-pi/pi-ai": "14.0.3",
|
|
52
|
+
"@oh-my-pi/pi-natives": "14.0.3",
|
|
53
|
+
"@oh-my-pi/pi-tui": "14.0.3",
|
|
54
|
+
"@oh-my-pi/pi-utils": "14.0.3",
|
|
55
55
|
"@sinclair/typebox": "^0.34",
|
|
56
56
|
"@xterm/headless": "^6.0",
|
|
57
57
|
"ajv": "^8.18",
|
|
@@ -947,7 +947,10 @@ export class ModelRegistry {
|
|
|
947
947
|
}
|
|
948
948
|
const models = this.#applyProviderModelOverrides(
|
|
949
949
|
providerConfig.provider,
|
|
950
|
-
this.#
|
|
950
|
+
this.#normalizeDiscoverableModels(
|
|
951
|
+
providerConfig,
|
|
952
|
+
this.#applyProviderCompat(providerConfig.compat, cache.models),
|
|
953
|
+
),
|
|
951
954
|
);
|
|
952
955
|
cachedModels.push(...models);
|
|
953
956
|
this.#providerDiscoveryStates.set(providerConfig.provider, {
|
|
@@ -967,11 +970,19 @@ export class ModelRegistry {
|
|
|
967
970
|
return models.map(model => ({ ...model, compat: mergeCompat(model.compat, compat) }));
|
|
968
971
|
}
|
|
969
972
|
|
|
973
|
+
#normalizeDiscoverableModels(providerConfig: DiscoveryProviderConfig, models: Model<Api>[]): Model<Api>[] {
|
|
974
|
+
if (providerConfig.provider !== "ollama" || providerConfig.api !== "openai-responses") {
|
|
975
|
+
return models;
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
return models.map(model => (model.api === "openai-completions" ? { ...model, api: "openai-responses" } : model));
|
|
979
|
+
}
|
|
980
|
+
|
|
970
981
|
#addImplicitDiscoverableProviders(configuredProviders: Set<string>): void {
|
|
971
982
|
if (!configuredProviders.has("ollama")) {
|
|
972
983
|
this.#discoverableProviders.push({
|
|
973
984
|
provider: "ollama",
|
|
974
|
-
api: "openai-
|
|
985
|
+
api: "openai-responses",
|
|
975
986
|
baseUrl: Bun.env.OLLAMA_BASE_URL || "http://127.0.0.1:11434",
|
|
976
987
|
discovery: { type: "ollama" },
|
|
977
988
|
optional: true,
|
|
@@ -1203,7 +1214,10 @@ export class ModelRegistry {
|
|
|
1203
1214
|
}
|
|
1204
1215
|
return this.#applyProviderModelOverrides(
|
|
1205
1216
|
providerId,
|
|
1206
|
-
this.#
|
|
1217
|
+
this.#normalizeDiscoverableModels(
|
|
1218
|
+
providerConfig,
|
|
1219
|
+
this.#applyProviderCompat(providerConfig.compat, result.models),
|
|
1220
|
+
),
|
|
1207
1221
|
);
|
|
1208
1222
|
}
|
|
1209
1223
|
|
package/src/edit/modes/chunk.ts
CHANGED
|
@@ -312,7 +312,7 @@ export const chunkToolEditSchema = Type.Object({
|
|
|
312
312
|
"Chunk selector. Format: 'path@region' for insertions, 'path#CRC@region' for replace. Omit @region to target the full chunk. Valid regions: head, body, tail, decl.",
|
|
313
313
|
}),
|
|
314
314
|
content: Type.String({
|
|
315
|
-
description: "New content. Use
|
|
315
|
+
description: "New content. Use \\t for indentation. Do NOT include the chunk's base padding.",
|
|
316
316
|
}),
|
|
317
317
|
});
|
|
318
318
|
export const chunkEditParamsSchema = Type.Object(
|
|
@@ -197,7 +197,7 @@ const MAX_FILES_FOR_INLINE_DIFF = 20; // Don't include diff if more files than t
|
|
|
197
197
|
/**
|
|
198
198
|
* Build the full review prompt with diff stats and distribution guidance.
|
|
199
199
|
*/
|
|
200
|
-
function buildReviewPrompt(mode: string, stats: DiffStats, rawDiff: string): string {
|
|
200
|
+
function buildReviewPrompt(mode: string, stats: DiffStats, rawDiff: string, additionalInstructions?: string): string {
|
|
201
201
|
const agentCount = getRecommendedAgentCount(stats);
|
|
202
202
|
const skipDiff = rawDiff.length > MAX_DIFF_CHARS || stats.files.length > MAX_FILES_FOR_INLINE_DIFF;
|
|
203
203
|
const totalLines = stats.totalAdded + stats.totalRemoved;
|
|
@@ -221,6 +221,7 @@ function buildReviewPrompt(mode: string, stats: DiffStats, rawDiff: string): str
|
|
|
221
221
|
skipDiff,
|
|
222
222
|
rawDiff: rawDiff.trim(),
|
|
223
223
|
linesPerFile,
|
|
224
|
+
additionalInstructions,
|
|
224
225
|
});
|
|
225
226
|
}
|
|
226
227
|
|
|
@@ -230,17 +231,30 @@ export class ReviewCommand implements CustomCommand {
|
|
|
230
231
|
|
|
231
232
|
constructor(private api: CustomCommandAPI) {}
|
|
232
233
|
|
|
233
|
-
async execute(
|
|
234
|
+
async execute(args: string[], ctx: HookCommandContext): Promise<string | undefined> {
|
|
234
235
|
if (!ctx.hasUI) {
|
|
235
|
-
|
|
236
|
+
const base = "Use the Task tool to run the 'reviewer' agent to review recent code changes.";
|
|
237
|
+
return args.length > 0 ? `${base} Focus: ${args.join(" ")}` : base;
|
|
236
238
|
}
|
|
237
239
|
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
240
|
+
// Inline args act as additional instructions appended to the generated prompt.
|
|
241
|
+
// When present, skip option 4 (editor) — the args already provide the instructions.
|
|
242
|
+
const extraInstructions = args.length > 0 ? args.join(" ") : undefined;
|
|
243
|
+
|
|
244
|
+
const menuItems = extraInstructions
|
|
245
|
+
? [
|
|
246
|
+
"1. Review against a base branch (PR Style)",
|
|
247
|
+
"2. Review uncommitted changes",
|
|
248
|
+
"3. Review a specific commit",
|
|
249
|
+
]
|
|
250
|
+
: [
|
|
251
|
+
"1. Review against a base branch (PR Style)",
|
|
252
|
+
"2. Review uncommitted changes",
|
|
253
|
+
"3. Review a specific commit",
|
|
254
|
+
"4. Custom review instructions",
|
|
255
|
+
];
|
|
256
|
+
|
|
257
|
+
const mode = await ctx.ui.select("Review Mode", menuItems);
|
|
244
258
|
|
|
245
259
|
if (!mode) return undefined;
|
|
246
260
|
|
|
@@ -282,6 +296,7 @@ export class ReviewCommand implements CustomCommand {
|
|
|
282
296
|
`Reviewing changes between \`${baseBranch}\` and \`${currentBranch}\` (PR-style)`,
|
|
283
297
|
stats,
|
|
284
298
|
diffText,
|
|
299
|
+
extraInstructions,
|
|
285
300
|
);
|
|
286
301
|
}
|
|
287
302
|
|
|
@@ -318,7 +333,12 @@ export class ReviewCommand implements CustomCommand {
|
|
|
318
333
|
return undefined;
|
|
319
334
|
}
|
|
320
335
|
|
|
321
|
-
return buildReviewPrompt(
|
|
336
|
+
return buildReviewPrompt(
|
|
337
|
+
"Reviewing uncommitted changes (staged + unstaged)",
|
|
338
|
+
stats,
|
|
339
|
+
combinedDiff,
|
|
340
|
+
extraInstructions,
|
|
341
|
+
);
|
|
322
342
|
}
|
|
323
343
|
|
|
324
344
|
case 3: {
|
|
@@ -354,7 +374,7 @@ export class ReviewCommand implements CustomCommand {
|
|
|
354
374
|
return undefined;
|
|
355
375
|
}
|
|
356
376
|
|
|
357
|
-
return buildReviewPrompt(`Reviewing commit \`${hash}\``, stats, diffText);
|
|
377
|
+
return buildReviewPrompt(`Reviewing commit \`${hash}\``, stats, diffText, extraInstructions);
|
|
358
378
|
}
|
|
359
379
|
|
|
360
380
|
case 4: {
|
|
@@ -374,11 +394,12 @@ export class ReviewCommand implements CustomCommand {
|
|
|
374
394
|
if (reviewDiff) {
|
|
375
395
|
const stats = parseDiff(reviewDiff);
|
|
376
396
|
// Even if all files filtered, include the custom instructions
|
|
377
|
-
return
|
|
397
|
+
return buildReviewPrompt(
|
|
378
398
|
`Custom review: ${instructions.split("\n")[0].slice(0, 60)}…`,
|
|
379
399
|
stats,
|
|
380
400
|
reviewDiff,
|
|
381
|
-
|
|
401
|
+
instructions,
|
|
402
|
+
);
|
|
382
403
|
}
|
|
383
404
|
|
|
384
405
|
// No diff available, just pass instructions
|
package/src/lsp/config.ts
CHANGED
|
@@ -197,6 +197,21 @@ const LOCAL_BIN_PATHS: Array<{ markers: string[]; binDir: string }> = [
|
|
|
197
197
|
{ markers: ["go.mod", "go.sum"], binDir: "bin" },
|
|
198
198
|
];
|
|
199
199
|
|
|
200
|
+
const WINDOWS_LOCAL_EXECUTABLE_EXTENSIONS = [".exe", ".cmd", ".bat"] as const;
|
|
201
|
+
|
|
202
|
+
function resolveLocalCommand(basePath: string): string | null {
|
|
203
|
+
if (fs.existsSync(basePath)) return basePath;
|
|
204
|
+
if (process.platform !== "win32") return null;
|
|
205
|
+
|
|
206
|
+
// Package managers write Windows launchers with executable suffixes in node_modules/.bin.
|
|
207
|
+
for (const extension of WINDOWS_LOCAL_EXECUTABLE_EXTENSIONS) {
|
|
208
|
+
const candidate = `${basePath}${extension}`;
|
|
209
|
+
if (fs.existsSync(candidate)) return candidate;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
return null;
|
|
213
|
+
}
|
|
214
|
+
|
|
200
215
|
/**
|
|
201
216
|
* Resolve a command to an executable path.
|
|
202
217
|
* Checks project-local bin directories first, then falls back to $PATH.
|
|
@@ -210,8 +225,9 @@ export function resolveCommand(command: string, cwd: string): string | null {
|
|
|
210
225
|
for (const { markers, binDir } of LOCAL_BIN_PATHS) {
|
|
211
226
|
if (hasRootMarkers(cwd, markers)) {
|
|
212
227
|
const localPath = path.join(cwd, binDir, command);
|
|
213
|
-
|
|
214
|
-
|
|
228
|
+
const resolvedLocalPath = resolveLocalCommand(localPath);
|
|
229
|
+
if (resolvedLocalPath) {
|
|
230
|
+
return resolvedLocalPath;
|
|
215
231
|
}
|
|
216
232
|
}
|
|
217
233
|
}
|
|
@@ -43,6 +43,8 @@ export class SessionObserverOverlayComponent extends Container {
|
|
|
43
43
|
#observeKeys: KeyId[];
|
|
44
44
|
/** Cached parsed transcript per session file to avoid reparsing on every refresh */
|
|
45
45
|
#transcriptCache?: { path: string; bytesRead: number; entries: SessionMessageEntry[] };
|
|
46
|
+
/** Live stats text component, placed after transcript to avoid above-viewport diffs */
|
|
47
|
+
#statsText?: Text;
|
|
46
48
|
|
|
47
49
|
constructor(registry: SessionObserverRegistry, onDone: () => void, observeKeys: KeyId[]) {
|
|
48
50
|
super();
|
|
@@ -87,11 +89,13 @@ export class SessionObserverOverlayComponent extends Container {
|
|
|
87
89
|
this.#mode = "viewer";
|
|
88
90
|
this.children = [];
|
|
89
91
|
this.#viewerContainer = new Container();
|
|
92
|
+
this.#statsText = new Text("", 1, 0);
|
|
90
93
|
this.#refreshViewer();
|
|
91
94
|
|
|
92
95
|
this.addChild(new DynamicBorder());
|
|
93
96
|
this.addChild(this.#viewerContainer);
|
|
94
97
|
this.addChild(new Spacer(1));
|
|
98
|
+
this.addChild(this.#statsText);
|
|
95
99
|
this.addChild(new Text(theme.fg("dim", "Esc: back to picker | Ctrl+S: back to picker"), 1, 0));
|
|
96
100
|
this.addChild(new DynamicBorder());
|
|
97
101
|
}
|
|
@@ -133,16 +137,17 @@ export class SessionObserverOverlayComponent extends Container {
|
|
|
133
137
|
const session = sessions.find(s => s.id === this.#selectedSessionId);
|
|
134
138
|
if (!session) {
|
|
135
139
|
this.#viewerContainer.addChild(new Text(theme.fg("dim", "Session no longer available."), 1, 0));
|
|
140
|
+
this.#updateStats(undefined);
|
|
136
141
|
return;
|
|
137
142
|
}
|
|
138
143
|
|
|
139
144
|
this.#renderSessionHeader(session);
|
|
140
145
|
this.#renderSessionTranscript(session);
|
|
146
|
+
this.#updateStats(session);
|
|
141
147
|
}
|
|
142
148
|
|
|
143
149
|
#renderSessionHeader(session: ObservableSession): void {
|
|
144
150
|
const c = this.#viewerContainer;
|
|
145
|
-
const progress = session.progress;
|
|
146
151
|
|
|
147
152
|
// Header: label + status + [agent]
|
|
148
153
|
const statusColor = session.status === "active" ? "success" : session.status === "failed" ? "error" : "dim";
|
|
@@ -154,17 +159,6 @@ export class SessionObserverOverlayComponent extends Container {
|
|
|
154
159
|
c.addChild(new Text(theme.fg("muted", session.description), 1, 0));
|
|
155
160
|
}
|
|
156
161
|
|
|
157
|
-
// Stats from progress
|
|
158
|
-
if (progress) {
|
|
159
|
-
const stats: string[] = [];
|
|
160
|
-
if (progress.toolCount > 0) stats.push(`${formatNumber(progress.toolCount)} tools`);
|
|
161
|
-
if (progress.tokens > 0) stats.push(`${formatNumber(progress.tokens)} tokens`);
|
|
162
|
-
if (progress.durationMs > 0) stats.push(formatDuration(progress.durationMs));
|
|
163
|
-
if (stats.length > 0) {
|
|
164
|
-
c.addChild(new Text(theme.fg("dim", stats.join(theme.sep.dot)), 1, 0));
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
|
|
168
162
|
if (session.sessionFile) {
|
|
169
163
|
c.addChild(new Text(theme.fg("dim", `Session: ${shortenPath(session.sessionFile)}`), 1, 0));
|
|
170
164
|
}
|
|
@@ -172,6 +166,21 @@ export class SessionObserverOverlayComponent extends Container {
|
|
|
172
166
|
c.addChild(new DynamicBorder());
|
|
173
167
|
}
|
|
174
168
|
|
|
169
|
+
/** Update live stats in-place (below transcript, within viewport). */
|
|
170
|
+
#updateStats(session: ObservableSession | undefined): void {
|
|
171
|
+
if (!this.#statsText) return;
|
|
172
|
+
const progress = session?.progress;
|
|
173
|
+
if (!progress) {
|
|
174
|
+
this.#statsText.setText("");
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
const stats: string[] = [];
|
|
178
|
+
if (progress.toolCount > 0) stats.push(`${formatNumber(progress.toolCount)} tools`);
|
|
179
|
+
if (progress.tokens > 0) stats.push(`${formatNumber(progress.tokens)} tokens`);
|
|
180
|
+
if (progress.durationMs > 0) stats.push(formatDuration(progress.durationMs));
|
|
181
|
+
this.#statsText.setText(stats.length > 0 ? theme.fg("dim", stats.join(theme.sep.dot)) : "");
|
|
182
|
+
}
|
|
183
|
+
|
|
175
184
|
/** Incrementally read and parse the session JSONL, caching already-parsed entries. */
|
|
176
185
|
#loadTranscript(sessionFile: string): SessionMessageEntry[] | null {
|
|
177
186
|
// Invalidate cache if session file changed (e.g. switched to different subagent)
|
|
@@ -7,9 +7,9 @@ Edits files via syntax-aware chunks. Run `read(path="file.ts")` first. The edit
|
|
|
7
7
|
- replacements: `chunk#CRC` or `chunk#CRC@region`
|
|
8
8
|
- Without a `@region` it defaults to the entire chunk including leading trivia. Valid regions: `head`, `body`, `tail`, `decl`.
|
|
9
9
|
- If the exact chunk path is unclear, run `read(path="file", sel="?")` and copy a selector from that listing.
|
|
10
|
-
- Use
|
|
10
|
+
- Use `\t` for indentation in `content`. Write content at indent-level 0 — the tool re-indents it to match the chunk's position in the file. For example, to replace `@body` of a method, write the body starting at column 0:
|
|
11
11
|
```
|
|
12
|
-
content: "if (x) {\n
|
|
12
|
+
content: "if (x) {\n\treturn true;\n}"
|
|
13
13
|
```
|
|
14
14
|
The tool adds the correct base indent automatically. Never manually pad with the chunk's own indentation.
|
|
15
15
|
- `@region` only works on container chunks (classes, functions, impl blocks, sections). Do **not** use `@head`/`@body`/`@tail` on leaf chunks (enum variants, fields, single statements) — use the whole chunk instead.
|
|
@@ -19,6 +19,10 @@ Edits files via syntax-aware chunks. Run `read(path="file.ts")` first. The edit
|
|
|
19
19
|
|
|
20
20
|
<critical>
|
|
21
21
|
You **MUST** use the narrowest region that covers your change. Replacing without a region replaces the **entire chunk including leading comments, decorators, and attributes** — omitting them from `content` deletes them.
|
|
22
|
+
|
|
23
|
+
**`replace` is total, not surgical.** The `content` you supply becomes the *complete* new content for the targeted region. Everything in the original region that you omit from `content` is deleted. Before replacing `@body` on any chunk, verify the chunk does not contain children you intend to keep. If a chunk spans hundreds of lines and your change touches only a few, target a specific child chunk — not the parent.
|
|
24
|
+
|
|
25
|
+
**Group chunks (`stmts_*`, `imports_*`, `decls_*`) are containers.** They hold many sibling items (test functions, import statements, declarations). Replacing `@body` on a group chunk replaces **all** of its children. To edit one item inside a group, target that item's own chunk path. If no child chunk exists, use the specific child's chunk selector from `read` output — do not replace the parent group.
|
|
22
26
|
</critical>
|
|
23
27
|
|
|
24
28
|
<regions>
|
|
@@ -108,9 +112,9 @@ Given this `read` output for `example.ts`:
|
|
|
108
112
|
```
|
|
109
113
|
|
|
110
114
|
**Replace a whole chunk** (rename a function):
|
|
111
|
-
|
|
112
|
-
{ "sel": "fn_createCounter#PQQY", "op": "replace", "content": "function makeCounter(start: number): Counter {\n
|
|
113
|
-
|
|
115
|
+
~~~json
|
|
116
|
+
{ "sel": "fn_createCounter#PQQY", "op": "replace", "content": "function makeCounter(start: number): Counter {\n\tconst c = new Counter();\n\tc.value = start;\n\treturn c;\n}\n" }
|
|
117
|
+
~~~
|
|
114
118
|
Result — the entire chunk is rewritten:
|
|
115
119
|
```
|
|
116
120
|
function makeCounter(start: number): Counter {
|
|
@@ -158,9 +162,9 @@ function createCounter(initial: number): Counter {
|
|
|
158
162
|
```
|
|
159
163
|
|
|
160
164
|
**Insert after a chunk** (`after`):
|
|
161
|
-
|
|
162
|
-
{ "sel": "enum_Status", "op": "after", "content": "\nfunction isActive(s: Status): boolean {\n
|
|
163
|
-
|
|
165
|
+
~~~json
|
|
166
|
+
{ "sel": "enum_Status", "op": "after", "content": "\nfunction isActive(s: Status): boolean {\n\treturn s === Status.Active;\n}\n" }
|
|
167
|
+
~~~
|
|
164
168
|
Result — a new function appears after the enum:
|
|
165
169
|
```
|
|
166
170
|
enum Status {
|
|
@@ -189,9 +193,9 @@ class Counter {
|
|
|
189
193
|
```
|
|
190
194
|
|
|
191
195
|
**Append inside a container** (`@body` + `append`):
|
|
192
|
-
|
|
193
|
-
{ "sel": "class_Counter@body", "op": "append", "content": "\nreset(): void {\n
|
|
194
|
-
|
|
196
|
+
~~~json
|
|
197
|
+
{ "sel": "class_Counter@body", "op": "append", "content": "\nreset(): void {\n\tthis.value = 0;\n}\n" }
|
|
198
|
+
~~~
|
|
195
199
|
Result — a new method is added at the end of the class body, before the closing `}`:
|
|
196
200
|
```
|
|
197
201
|
toString(): string {
|
|
@@ -210,10 +214,10 @@ Result — a new method is added at the end of the class body, before the closin
|
|
|
210
214
|
```
|
|
211
215
|
Result — the method is removed from the class.
|
|
212
216
|
- Indentation rules (important):
|
|
213
|
-
- Use
|
|
217
|
+
- Use `\t` for each indent level. The tool converts tabs to the file's actual style (2-space, 4-space, etc.).
|
|
214
218
|
- Do NOT include the chunk's base indentation — only indent relative to the region's opening level.
|
|
215
219
|
- For `@body` of a function: write at column 0, e.g. `"return x;\n"`. The tool adds the correct base indent.
|
|
216
220
|
- For `@head`: write at the chunk's own depth. A class member's head uses `"/** doc */\nstart(): void {"`.
|
|
217
|
-
- For a top-level item: start at zero indent. Write `"function foo() {\n
|
|
221
|
+
- For a top-level item: start at zero indent. Write `"function foo() {\n\treturn 1;\n}\n"`.
|
|
218
222
|
- The tool strips common leading indentation from your content as a safety net, so accidental over-indentation is corrected.
|
|
219
223
|
</examples>
|