@oh-my-pi/pi-coding-agent 14.0.3 → 14.0.4
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 +21 -1
- package/package.json +7 -7
- package/src/edit/index.ts +2 -0
- package/src/edit/modes/chunk.ts +37 -13
- package/src/edit/modes/hashline.ts +2 -2
- package/src/ipy/executor.ts +3 -7
- package/src/ipy/kernel.ts +3 -3
- package/src/lsp/lspmux.ts +2 -2
- package/src/lsp/utils.ts +1 -143
- package/src/modes/theme/theme.ts +2 -161
- package/src/prompts/system/system-prompt.md +40 -2
- package/src/prompts/tools/chunk-edit.md +29 -0
- package/src/prompts/tools/read-chunk.md +6 -0
- package/src/sdk.ts +2 -1
- package/src/tools/ast-edit.ts +2 -2
- package/src/tools/index.ts +2 -3
- package/src/tools/read.ts +2 -0
- package/src/tools/report-tool-issue.ts +2 -2
- package/src/utils/edit-mode.ts +2 -2
- package/src/utils/lang-from-path.ts +239 -0
- package/src/utils/sixel.ts +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,26 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [14.0.4] - 2026-04-10
|
|
6
|
+
### Added
|
|
7
|
+
|
|
8
|
+
- Added `PI_CHUNK_AUTOINDENT` environment variable to control whether chunk read/edit tools normalize indentation to canonical tabs or preserve literal file whitespace
|
|
9
|
+
- Added dynamic chunk tool prompts that automatically adjust guidance based on `PI_CHUNK_AUTOINDENT` setting without exposing a tool parameter
|
|
10
|
+
- Added `<instruction-priority>`, `<output-contract>`, `<default-follow-through>`, `<tool-persistence>`, and `<completeness-contract>` sections to system prompt for improved long-horizon agent workflows
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
|
|
14
|
+
- Updated chunk edit tool to apply `normalizeIndent` setting during edit operations, enabling literal whitespace preservation when `PI_CHUNK_AUTOINDENT=0`
|
|
15
|
+
- Refactored environment variable parsing to use `$flag()` and `$envpos()` utilities from pi-utils for consistent boolean and integer handling across codebase
|
|
16
|
+
- Updated system prompt communication guidelines to emphasize conciseness and information density, and added guidance on avoiding repetition of user requests
|
|
17
|
+
- Enhanced system prompt with explicit rules for design integrity, verification before yielding, and handling of missing context via tool-based retrieval
|
|
18
|
+
- Added `PI_CHUNK_AUTOINDENT` to control whether chunk read/edit tools normalize indentation, and updated chunk prompts to switch guidance automatically based on that setting
|
|
19
|
+
- Refined the default system prompt with explicit instruction-priority, output-contract, tool-persistence, completeness, and verification rules for long-horizon GPT-5.4-style agent workflows
|
|
20
|
+
|
|
21
|
+
### Fixed
|
|
22
|
+
|
|
23
|
+
- Fixed typo in system prompt: 'backwards compatibiltity' → 'backwards compatibility'
|
|
24
|
+
|
|
5
25
|
## [14.0.3] - 2026-04-09
|
|
6
26
|
|
|
7
27
|
### Fixed
|
|
@@ -6900,4 +6920,4 @@ Initial public release.
|
|
|
6900
6920
|
- Git branch display in footer
|
|
6901
6921
|
- Message queueing during streaming responses
|
|
6902
6922
|
- OAuth integration for Gmail and Google Calendar access
|
|
6903
|
-
- HTML export with syntax highlighting and collapsible sections
|
|
6923
|
+
- 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.4",
|
|
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.4",
|
|
50
|
+
"@oh-my-pi/pi-agent-core": "14.0.4",
|
|
51
|
+
"@oh-my-pi/pi-ai": "14.0.4",
|
|
52
|
+
"@oh-my-pi/pi-natives": "14.0.4",
|
|
53
|
+
"@oh-my-pi/pi-tui": "14.0.4",
|
|
54
|
+
"@oh-my-pi/pi-utils": "14.0.4",
|
|
55
55
|
"@sinclair/typebox": "^0.34",
|
|
56
56
|
"@xterm/headless": "^6.0",
|
|
57
57
|
"ajv": "^8.18",
|
package/src/edit/index.ts
CHANGED
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
executeChunkMode,
|
|
20
20
|
isChunkParams,
|
|
21
21
|
resolveAnchorStyle,
|
|
22
|
+
resolveChunkAutoIndent,
|
|
22
23
|
} from "./modes/chunk";
|
|
23
24
|
import { executeHashlineMode, type HashlineParams, hashlineEditParamsSchema, isHashlineParams } from "./modes/hashline";
|
|
24
25
|
import { executePatchMode, isPatchParams, type PatchParams, patchEditSchema } from "./modes/patch";
|
|
@@ -197,6 +198,7 @@ export class EditTool implements AgentTool<TInput> {
|
|
|
197
198
|
description: (session: ToolSession) =>
|
|
198
199
|
prompt.render(chunkEditDescription, {
|
|
199
200
|
anchorStyle: resolveAnchorStyle(session.settings),
|
|
201
|
+
chunkAutoIndent: resolveChunkAutoIndent(),
|
|
200
202
|
}),
|
|
201
203
|
parameters: chunkEditParamsSchema,
|
|
202
204
|
invalidParamsMessage: "Invalid edit parameters for chunk mode.",
|
package/src/edit/modes/chunk.ts
CHANGED
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
ChunkState,
|
|
12
12
|
type EditOperation as NativeEditOperation,
|
|
13
13
|
} from "@oh-my-pi/pi-natives";
|
|
14
|
+
import { $envpos } from "@oh-my-pi/pi-utils";
|
|
14
15
|
import { type Static, Type } from "@sinclair/typebox";
|
|
15
16
|
import type { BunFile } from "bun";
|
|
16
17
|
import { LRUCache } from "lru-cache";
|
|
@@ -63,6 +64,34 @@ const validAnchorStyles: Record<string, ChunkAnchorStyle> = {
|
|
|
63
64
|
bare: ChunkAnchorStyle.Bare,
|
|
64
65
|
};
|
|
65
66
|
|
|
67
|
+
export function resolveChunkAutoIndent(rawValue = Bun.env.PI_CHUNK_AUTOINDENT): boolean {
|
|
68
|
+
if (!rawValue) return true;
|
|
69
|
+
const normalized = rawValue.trim().toLowerCase();
|
|
70
|
+
switch (normalized) {
|
|
71
|
+
case "1":
|
|
72
|
+
case "true":
|
|
73
|
+
case "yes":
|
|
74
|
+
case "on":
|
|
75
|
+
return true;
|
|
76
|
+
case "0":
|
|
77
|
+
case "false":
|
|
78
|
+
case "no":
|
|
79
|
+
case "off":
|
|
80
|
+
return false;
|
|
81
|
+
default:
|
|
82
|
+
throw new Error(`Invalid PI_CHUNK_AUTOINDENT: ${rawValue}`);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function getChunkRenderIndentOptions(): {
|
|
87
|
+
normalizeIndent: boolean;
|
|
88
|
+
tabReplacement: string;
|
|
89
|
+
} {
|
|
90
|
+
return resolveChunkAutoIndent()
|
|
91
|
+
? { normalizeIndent: true, tabReplacement: " " }
|
|
92
|
+
: { normalizeIndent: false, tabReplacement: "\t" };
|
|
93
|
+
}
|
|
94
|
+
|
|
66
95
|
export function resolveAnchorStyle(settings?: Settings): ChunkAnchorStyle {
|
|
67
96
|
const envStyle = Bun.env.PI_ANCHOR_STYLE;
|
|
68
97
|
return (
|
|
@@ -72,16 +101,8 @@ export function resolveAnchorStyle(settings?: Settings): ChunkAnchorStyle {
|
|
|
72
101
|
);
|
|
73
102
|
}
|
|
74
103
|
|
|
75
|
-
const readEnvInt = (name: string, defaultValue: number): number => {
|
|
76
|
-
const value = Bun.env[name];
|
|
77
|
-
if (!value) return defaultValue;
|
|
78
|
-
const parsed = Number.parseInt(value, 10);
|
|
79
|
-
if (Number.isNaN(parsed) || parsed <= 0) return defaultValue;
|
|
80
|
-
return parsed;
|
|
81
|
-
};
|
|
82
|
-
|
|
83
104
|
const chunkStateCache = new LRUCache<string, ChunkCacheEntry>({
|
|
84
|
-
max:
|
|
105
|
+
max: $envpos("PI_CHUNK_CACHE_MAX_ENTRIES", 200),
|
|
85
106
|
});
|
|
86
107
|
|
|
87
108
|
export function invalidateChunkCache(filePath: string): void {
|
|
@@ -215,6 +236,7 @@ export async function formatChunkedRead(params: {
|
|
|
215
236
|
const normalizedLanguage = normalizeLanguage(language);
|
|
216
237
|
const { state } = await loadChunkStateForFile(filePath, normalizedLanguage);
|
|
217
238
|
const displayPath = displayPathForFile(filePath, cwd);
|
|
239
|
+
const renderIndentOptions = getChunkRenderIndentOptions();
|
|
218
240
|
const result = state.renderRead({
|
|
219
241
|
readPath,
|
|
220
242
|
displayPath,
|
|
@@ -224,8 +246,8 @@ export async function formatChunkedRead(params: {
|
|
|
224
246
|
absoluteLineRange: absoluteLineRange
|
|
225
247
|
? { startLine: absoluteLineRange.startLine, endLine: absoluteLineRange.endLine ?? absoluteLineRange.startLine }
|
|
226
248
|
: undefined,
|
|
227
|
-
tabReplacement:
|
|
228
|
-
normalizeIndent:
|
|
249
|
+
tabReplacement: renderIndentOptions.tabReplacement,
|
|
250
|
+
normalizeIndent: renderIndentOptions.normalizeIndent,
|
|
229
251
|
});
|
|
230
252
|
return { text: result.text, resolvedPath: filePath, chunk: result.chunk };
|
|
231
253
|
}
|
|
@@ -280,6 +302,7 @@ export function applyChunkEdits(params: {
|
|
|
280
302
|
const state = ChunkState.parse(normalizedSource, normalizeLanguage(params.language));
|
|
281
303
|
const result = state.applyEdits({
|
|
282
304
|
operations: nativeOperations,
|
|
305
|
+
normalizeIndent: resolveChunkAutoIndent(),
|
|
283
306
|
defaultSelector: params.defaultSelector,
|
|
284
307
|
defaultCrc: params.defaultCrc,
|
|
285
308
|
anchorStyle: params.anchorStyle,
|
|
@@ -312,7 +335,8 @@ export const chunkToolEditSchema = Type.Object({
|
|
|
312
335
|
"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
336
|
}),
|
|
314
337
|
content: Type.String({
|
|
315
|
-
description:
|
|
338
|
+
description:
|
|
339
|
+
"New content. Write indentation relative to the targeted region as described in the tool prompt. Do NOT include the chunk's base padding.",
|
|
316
340
|
}),
|
|
317
341
|
});
|
|
318
342
|
export const chunkEditParamsSchema = Type.Object(
|
|
@@ -387,7 +411,7 @@ async function writeChunkResult(params: {
|
|
|
387
411
|
invalidateFsScanAfterWrite(resolvedPath);
|
|
388
412
|
|
|
389
413
|
const diffResult = generateUnifiedDiffString(result.diffSourceBefore, result.diffSourceAfter);
|
|
390
|
-
const warningsBlock = result.warnings.length > 0 ? `\n\n${result.warnings.join("\n")}` : "";
|
|
414
|
+
const warningsBlock = result.warnings.length > 0 ? `\n\nWarnings:\n${result.warnings.join("\n")}` : "";
|
|
391
415
|
const meta = outputMeta()
|
|
392
416
|
.diagnostics(diagnostics?.summary ?? "", diagnostics?.messages ?? [])
|
|
393
417
|
.get();
|
|
@@ -156,8 +156,8 @@ interface ExecuteHashlineModeOptions {
|
|
|
156
156
|
beginDeferredDiagnosticsForPath: (path: string) => WritethroughDeferredHandle;
|
|
157
157
|
}
|
|
158
158
|
|
|
159
|
-
export function hashlineParseText(edit: string[] | string | null): string[] {
|
|
160
|
-
if (edit
|
|
159
|
+
export function hashlineParseText(edit: string[] | string | null | undefined): string[] {
|
|
160
|
+
if (edit == null) return [];
|
|
161
161
|
if (typeof edit === "string") {
|
|
162
162
|
const normalizedEdit = edit.endsWith("\n") ? edit.slice(0, -1) : edit;
|
|
163
163
|
edit = normalizedEdit.replaceAll("\r", "").split("\n");
|
package/src/ipy/executor.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as path from "node:path";
|
|
2
|
-
import { getAgentDir, getProjectDir, isEnoent, logger } from "@oh-my-pi/pi-utils";
|
|
2
|
+
import { getAgentDir, getProjectDir, isBunTestRuntime, isEnoent, logger } from "@oh-my-pi/pi-utils";
|
|
3
3
|
import { OutputSink } from "../session/streaming-output";
|
|
4
4
|
import { shutdownSharedGateway } from "./gateway-coordinator";
|
|
5
5
|
import {
|
|
@@ -299,10 +299,6 @@ async function writePreludeCache(state: PreludeCacheState, helpers: PreludeHelpe
|
|
|
299
299
|
}
|
|
300
300
|
}
|
|
301
301
|
|
|
302
|
-
function isPythonTestEnvironment(): boolean {
|
|
303
|
-
return Bun.env.BUN_ENV === "test" || Bun.env.NODE_ENV === "test";
|
|
304
|
-
}
|
|
305
|
-
|
|
306
302
|
function getPreludeIntrospectionOptions(
|
|
307
303
|
options: KernelSessionExecutionOptions = {},
|
|
308
304
|
): Pick<KernelExecuteOptions, "signal" | "timeoutMs"> {
|
|
@@ -318,7 +314,7 @@ async function cachePreludeDocs(
|
|
|
318
314
|
cacheState?: PreludeCacheState | null,
|
|
319
315
|
): Promise<PreludeHelper[]> {
|
|
320
316
|
cachedPreludeDocs = docs;
|
|
321
|
-
if (!
|
|
317
|
+
if (!isBunTestRuntime() && docs.length > 0) {
|
|
322
318
|
const state = cacheState ?? (await buildPreludeCacheState(cwd));
|
|
323
319
|
await writePreludeCache(state, docs);
|
|
324
320
|
}
|
|
@@ -416,7 +412,7 @@ export async function warmPythonEnvironment(
|
|
|
416
412
|
cachedPreludeDocs = [];
|
|
417
413
|
return { ok: false, reason, docs: [] };
|
|
418
414
|
}
|
|
419
|
-
if (!
|
|
415
|
+
if (!isBunTestRuntime()) {
|
|
420
416
|
try {
|
|
421
417
|
cacheState = await buildPreludeCacheState(cwd);
|
|
422
418
|
const cached = await readPreludeCache(cacheState);
|
package/src/ipy/kernel.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { $env, logger, Snowflake } from "@oh-my-pi/pi-utils";
|
|
1
|
+
import { $env, $flag, isBunTestRuntime, logger, Snowflake } from "@oh-my-pi/pi-utils";
|
|
2
2
|
import { $ } from "bun";
|
|
3
3
|
import { Settings } from "../config/settings";
|
|
4
4
|
import { htmlToBasicMarkdown } from "../web/scrapers/types";
|
|
@@ -10,7 +10,7 @@ import { filterEnv, resolvePythonRuntime } from "./runtime";
|
|
|
10
10
|
|
|
11
11
|
const TEXT_ENCODER = new TextEncoder();
|
|
12
12
|
const TEXT_DECODER = new TextDecoder();
|
|
13
|
-
const TRACE_IPC = $
|
|
13
|
+
const TRACE_IPC = $flag("PI_PYTHON_IPC_TRACE");
|
|
14
14
|
const PRELUDE_INTROSPECTION_SNIPPET = "import json\nprint(json.dumps(__omp_prelude_docs__()))";
|
|
15
15
|
|
|
16
16
|
class SharedGatewayCreateError extends Error {
|
|
@@ -195,7 +195,7 @@ export interface PythonKernelAvailability {
|
|
|
195
195
|
}
|
|
196
196
|
|
|
197
197
|
export async function checkPythonKernelAvailability(cwd: string): Promise<PythonKernelAvailability> {
|
|
198
|
-
if (
|
|
198
|
+
if (isBunTestRuntime() || $flag("PI_PYTHON_SKIP_CHECK")) {
|
|
199
199
|
return { ok: true };
|
|
200
200
|
}
|
|
201
201
|
|
package/src/lsp/lspmux.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as os from "node:os";
|
|
2
2
|
import * as path from "node:path";
|
|
3
|
-
import { $
|
|
3
|
+
import { $flag, $which, logger } from "@oh-my-pi/pi-utils";
|
|
4
4
|
import { TOML } from "bun";
|
|
5
5
|
|
|
6
6
|
/**
|
|
@@ -135,7 +135,7 @@ export async function detectLspmux(): Promise<LspmuxState> {
|
|
|
135
135
|
return cachedState;
|
|
136
136
|
}
|
|
137
137
|
|
|
138
|
-
if ($
|
|
138
|
+
if ($flag("PI_DISABLE_LSPMUX")) {
|
|
139
139
|
cachedState = { available: false, running: false, binaryPath: null, config: null };
|
|
140
140
|
cacheTimestamp = now;
|
|
141
141
|
return cachedState;
|
package/src/lsp/utils.ts
CHANGED
|
@@ -16,149 +16,7 @@ import type {
|
|
|
16
16
|
WorkspaceEdit,
|
|
17
17
|
} from "./types";
|
|
18
18
|
|
|
19
|
-
|
|
20
|
-
// Language Detection
|
|
21
|
-
// =============================================================================
|
|
22
|
-
|
|
23
|
-
const LANGUAGE_MAP: Record<string, string> = {
|
|
24
|
-
// TypeScript/JavaScript
|
|
25
|
-
".ts": "typescript",
|
|
26
|
-
".tsx": "typescriptreact",
|
|
27
|
-
".js": "javascript",
|
|
28
|
-
".jsx": "javascriptreact",
|
|
29
|
-
".mjs": "javascript",
|
|
30
|
-
".cjs": "javascript",
|
|
31
|
-
".mts": "typescript",
|
|
32
|
-
".cts": "typescript",
|
|
33
|
-
|
|
34
|
-
// Systems languages
|
|
35
|
-
".rs": "rust",
|
|
36
|
-
".go": "go",
|
|
37
|
-
".c": "c",
|
|
38
|
-
".h": "c",
|
|
39
|
-
".cpp": "cpp",
|
|
40
|
-
".cc": "cpp",
|
|
41
|
-
".cxx": "cpp",
|
|
42
|
-
".hpp": "cpp",
|
|
43
|
-
".hxx": "cpp",
|
|
44
|
-
".zig": "zig",
|
|
45
|
-
|
|
46
|
-
// Scripting languages
|
|
47
|
-
".py": "python",
|
|
48
|
-
".rb": "ruby",
|
|
49
|
-
".lua": "lua",
|
|
50
|
-
".sh": "shellscript",
|
|
51
|
-
".bash": "shellscript",
|
|
52
|
-
".zsh": "shellscript",
|
|
53
|
-
".fish": "fish",
|
|
54
|
-
".pl": "perl",
|
|
55
|
-
".pm": "perl",
|
|
56
|
-
".php": "php",
|
|
57
|
-
|
|
58
|
-
// JVM languages
|
|
59
|
-
".java": "java",
|
|
60
|
-
".kt": "kotlin",
|
|
61
|
-
".kts": "kotlin",
|
|
62
|
-
".scala": "scala",
|
|
63
|
-
".groovy": "groovy",
|
|
64
|
-
".clj": "clojure",
|
|
65
|
-
|
|
66
|
-
// .NET languages
|
|
67
|
-
".cs": "csharp",
|
|
68
|
-
".fs": "fsharp",
|
|
69
|
-
".vb": "vb",
|
|
70
|
-
|
|
71
|
-
// Web
|
|
72
|
-
".html": "html",
|
|
73
|
-
".htm": "html",
|
|
74
|
-
".css": "css",
|
|
75
|
-
".scss": "scss",
|
|
76
|
-
".sass": "sass",
|
|
77
|
-
".less": "less",
|
|
78
|
-
".vue": "vue",
|
|
79
|
-
".svelte": "svelte",
|
|
80
|
-
".astro": "astro",
|
|
81
|
-
|
|
82
|
-
// Data formats
|
|
83
|
-
".json": "json",
|
|
84
|
-
".jsonc": "jsonc",
|
|
85
|
-
".yaml": "yaml",
|
|
86
|
-
".yml": "yaml",
|
|
87
|
-
".toml": "toml",
|
|
88
|
-
".xml": "xml",
|
|
89
|
-
".ini": "ini",
|
|
90
|
-
|
|
91
|
-
// Documentation
|
|
92
|
-
".md": "markdown",
|
|
93
|
-
".markdown": "markdown",
|
|
94
|
-
".rst": "restructuredtext",
|
|
95
|
-
".adoc": "asciidoc",
|
|
96
|
-
".tex": "latex",
|
|
97
|
-
|
|
98
|
-
// Other
|
|
99
|
-
".sql": "sql",
|
|
100
|
-
".graphql": "graphql",
|
|
101
|
-
".gql": "graphql",
|
|
102
|
-
".proto": "protobuf",
|
|
103
|
-
".dockerfile": "dockerfile",
|
|
104
|
-
".tf": "terraform",
|
|
105
|
-
".hcl": "hcl",
|
|
106
|
-
".nix": "nix",
|
|
107
|
-
".ex": "elixir",
|
|
108
|
-
".exs": "elixir",
|
|
109
|
-
".erl": "erlang",
|
|
110
|
-
".hrl": "erlang",
|
|
111
|
-
".hs": "haskell",
|
|
112
|
-
".ml": "ocaml",
|
|
113
|
-
".mli": "ocaml",
|
|
114
|
-
".swift": "swift",
|
|
115
|
-
".r": "r",
|
|
116
|
-
".R": "r",
|
|
117
|
-
".jl": "julia",
|
|
118
|
-
".dart": "dart",
|
|
119
|
-
".elm": "elm",
|
|
120
|
-
".v": "v",
|
|
121
|
-
".nim": "nim",
|
|
122
|
-
".cr": "crystal",
|
|
123
|
-
".d": "d",
|
|
124
|
-
".pas": "pascal",
|
|
125
|
-
".pp": "pascal",
|
|
126
|
-
".lisp": "lisp",
|
|
127
|
-
".lsp": "lisp",
|
|
128
|
-
".rkt": "racket",
|
|
129
|
-
".scm": "scheme",
|
|
130
|
-
".ps1": "powershell",
|
|
131
|
-
".psm1": "powershell",
|
|
132
|
-
".bat": "bat",
|
|
133
|
-
".cmd": "bat",
|
|
134
|
-
".tla": "tlaplus",
|
|
135
|
-
".tlaplus": "tlaplus",
|
|
136
|
-
};
|
|
137
|
-
|
|
138
|
-
/**
|
|
139
|
-
* Detect language ID from file path.
|
|
140
|
-
* Returns the LSP language identifier for the file type.
|
|
141
|
-
*/
|
|
142
|
-
export function detectLanguageId(filePath: string): string {
|
|
143
|
-
const ext = path.extname(filePath).toLowerCase();
|
|
144
|
-
const basename = path.basename(filePath).toLowerCase();
|
|
145
|
-
|
|
146
|
-
// Handle special filenames
|
|
147
|
-
if (basename === "dockerfile" || basename.startsWith("dockerfile.") || basename === "containerfile") {
|
|
148
|
-
return "dockerfile";
|
|
149
|
-
}
|
|
150
|
-
if (basename === "makefile" || basename === "gnumakefile") {
|
|
151
|
-
return "makefile";
|
|
152
|
-
}
|
|
153
|
-
if (basename === "justfile") {
|
|
154
|
-
return "just";
|
|
155
|
-
}
|
|
156
|
-
if (basename === "cmakelists.txt" || ext === ".cmake") {
|
|
157
|
-
return "cmake";
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
return LANGUAGE_MAP[ext] ?? "plaintext";
|
|
161
|
-
}
|
|
19
|
+
export { detectLanguageId } from "../utils/lang-from-path";
|
|
162
20
|
|
|
163
21
|
// =============================================================================
|
|
164
22
|
// URI Handling (Cross-Platform)
|
package/src/modes/theme/theme.ts
CHANGED
|
@@ -20,6 +20,8 @@ import { defaultThemes } from "./defaults";
|
|
|
20
20
|
import lightThemeJson from "./light.json" with { type: "json" };
|
|
21
21
|
import { getMermaidAscii } from "./mermaid-cache";
|
|
22
22
|
|
|
23
|
+
export { getLanguageFromPath } from "../../utils/lang-from-path";
|
|
24
|
+
|
|
23
25
|
// ============================================================================
|
|
24
26
|
// Symbol Presets
|
|
25
27
|
// ============================================================================
|
|
@@ -2305,167 +2307,6 @@ export function highlightCode(code: string, lang?: string): string[] {
|
|
|
2305
2307
|
}
|
|
2306
2308
|
}
|
|
2307
2309
|
|
|
2308
|
-
/**
|
|
2309
|
-
* Get language identifier from file path extension.
|
|
2310
|
-
*/
|
|
2311
|
-
export function getLanguageFromPath(filePath: string): string | undefined {
|
|
2312
|
-
const baseName = path.basename(filePath).toLowerCase();
|
|
2313
|
-
if (baseName === ".env" || baseName.startsWith(".env.")) return "env";
|
|
2314
|
-
if (
|
|
2315
|
-
baseName === ".gitignore" ||
|
|
2316
|
-
baseName === ".gitattributes" ||
|
|
2317
|
-
baseName === ".gitmodules" ||
|
|
2318
|
-
baseName === ".editorconfig" ||
|
|
2319
|
-
baseName === ".npmrc" ||
|
|
2320
|
-
baseName === ".prettierrc" ||
|
|
2321
|
-
baseName === ".eslintrc"
|
|
2322
|
-
) {
|
|
2323
|
-
return "conf";
|
|
2324
|
-
}
|
|
2325
|
-
if (baseName === "dockerfile" || baseName.startsWith("dockerfile.") || baseName === "containerfile") {
|
|
2326
|
-
return "dockerfile";
|
|
2327
|
-
}
|
|
2328
|
-
if (baseName === "justfile") return "just";
|
|
2329
|
-
if (baseName === "cmakelists.txt") return "cmake";
|
|
2330
|
-
|
|
2331
|
-
const ext = filePath.split(".").pop()?.toLowerCase();
|
|
2332
|
-
if (!ext) return undefined;
|
|
2333
|
-
|
|
2334
|
-
const extToLang: Record<string, string> = {
|
|
2335
|
-
ts: "typescript",
|
|
2336
|
-
cts: "typescript",
|
|
2337
|
-
mts: "typescript",
|
|
2338
|
-
tsx: "tsx",
|
|
2339
|
-
js: "javascript",
|
|
2340
|
-
jsx: "javascript",
|
|
2341
|
-
mjs: "javascript",
|
|
2342
|
-
cjs: "javascript",
|
|
2343
|
-
py: "python",
|
|
2344
|
-
pyi: "python",
|
|
2345
|
-
rb: "ruby",
|
|
2346
|
-
rbw: "ruby",
|
|
2347
|
-
gemspec: "ruby",
|
|
2348
|
-
rs: "rust",
|
|
2349
|
-
go: "go",
|
|
2350
|
-
java: "java",
|
|
2351
|
-
kt: "kotlin",
|
|
2352
|
-
ktm: "kotlin",
|
|
2353
|
-
kts: "kotlin",
|
|
2354
|
-
swift: "swift",
|
|
2355
|
-
c: "c",
|
|
2356
|
-
h: "c",
|
|
2357
|
-
cpp: "cpp",
|
|
2358
|
-
cc: "cpp",
|
|
2359
|
-
cxx: "cpp",
|
|
2360
|
-
hh: "cpp",
|
|
2361
|
-
hpp: "cpp",
|
|
2362
|
-
cu: "cpp",
|
|
2363
|
-
ino: "cpp",
|
|
2364
|
-
cs: "csharp",
|
|
2365
|
-
clj: "clojure",
|
|
2366
|
-
cljc: "clojure",
|
|
2367
|
-
cljs: "clojure",
|
|
2368
|
-
edn: "clojure",
|
|
2369
|
-
php: "php",
|
|
2370
|
-
sh: "bash",
|
|
2371
|
-
bash: "bash",
|
|
2372
|
-
zsh: "bash",
|
|
2373
|
-
ksh: "bash",
|
|
2374
|
-
bats: "bash",
|
|
2375
|
-
tmux: "bash",
|
|
2376
|
-
cgi: "bash",
|
|
2377
|
-
fcgi: "bash",
|
|
2378
|
-
command: "bash",
|
|
2379
|
-
tool: "bash",
|
|
2380
|
-
fish: "fish",
|
|
2381
|
-
ps1: "powershell",
|
|
2382
|
-
psm1: "powershell",
|
|
2383
|
-
sql: "sql",
|
|
2384
|
-
html: "html",
|
|
2385
|
-
htm: "html",
|
|
2386
|
-
xhtml: "html",
|
|
2387
|
-
astro: "astro",
|
|
2388
|
-
vue: "vue",
|
|
2389
|
-
svelte: "svelte",
|
|
2390
|
-
css: "css",
|
|
2391
|
-
scss: "scss",
|
|
2392
|
-
sass: "sass",
|
|
2393
|
-
less: "less",
|
|
2394
|
-
json: "json",
|
|
2395
|
-
ipynb: "ipynb",
|
|
2396
|
-
hbs: "handlebars",
|
|
2397
|
-
hsb: "handlebars",
|
|
2398
|
-
handlebars: "handlebars",
|
|
2399
|
-
yaml: "yaml",
|
|
2400
|
-
yml: "yaml",
|
|
2401
|
-
toml: "toml",
|
|
2402
|
-
xml: "xml",
|
|
2403
|
-
xsl: "xml",
|
|
2404
|
-
xslt: "xml",
|
|
2405
|
-
svg: "xml",
|
|
2406
|
-
plist: "xml",
|
|
2407
|
-
md: "markdown",
|
|
2408
|
-
markdown: "markdown",
|
|
2409
|
-
mdx: "markdown",
|
|
2410
|
-
diff: "diff",
|
|
2411
|
-
patch: "diff",
|
|
2412
|
-
dockerfile: "dockerfile",
|
|
2413
|
-
containerfile: "dockerfile",
|
|
2414
|
-
makefile: "make",
|
|
2415
|
-
justfile: "just",
|
|
2416
|
-
mk: "make",
|
|
2417
|
-
mak: "make",
|
|
2418
|
-
cmake: "cmake",
|
|
2419
|
-
lua: "lua",
|
|
2420
|
-
jl: "julia",
|
|
2421
|
-
pl: "perl",
|
|
2422
|
-
pm: "perl",
|
|
2423
|
-
perl: "perl",
|
|
2424
|
-
r: "r",
|
|
2425
|
-
scala: "scala",
|
|
2426
|
-
sc: "scala",
|
|
2427
|
-
sbt: "scala",
|
|
2428
|
-
ex: "elixir",
|
|
2429
|
-
exs: "elixir",
|
|
2430
|
-
erl: "erlang",
|
|
2431
|
-
hs: "haskell",
|
|
2432
|
-
nix: "nix",
|
|
2433
|
-
odin: "odin",
|
|
2434
|
-
zig: "zig",
|
|
2435
|
-
star: "starlark",
|
|
2436
|
-
bzl: "starlark",
|
|
2437
|
-
sol: "solidity",
|
|
2438
|
-
v: "verilog",
|
|
2439
|
-
sv: "verilog",
|
|
2440
|
-
svh: "verilog",
|
|
2441
|
-
vh: "verilog",
|
|
2442
|
-
m: "objc",
|
|
2443
|
-
mm: "objc",
|
|
2444
|
-
ml: "ocaml",
|
|
2445
|
-
vim: "vim",
|
|
2446
|
-
graphql: "graphql",
|
|
2447
|
-
proto: "protobuf",
|
|
2448
|
-
tf: "hcl",
|
|
2449
|
-
hcl: "hcl",
|
|
2450
|
-
tfvars: "hcl",
|
|
2451
|
-
txt: "text",
|
|
2452
|
-
text: "text",
|
|
2453
|
-
log: "log",
|
|
2454
|
-
csv: "csv",
|
|
2455
|
-
tsv: "tsv",
|
|
2456
|
-
ini: "ini",
|
|
2457
|
-
cfg: "conf",
|
|
2458
|
-
conf: "conf",
|
|
2459
|
-
config: "conf",
|
|
2460
|
-
properties: "conf",
|
|
2461
|
-
tla: "tlaplus",
|
|
2462
|
-
tlaplus: "tlaplus",
|
|
2463
|
-
env: "env",
|
|
2464
|
-
};
|
|
2465
|
-
|
|
2466
|
-
return extToLang[ext];
|
|
2467
|
-
}
|
|
2468
|
-
|
|
2469
2310
|
export function getSymbolTheme(): SymbolTheme {
|
|
2470
2311
|
const preset = theme.getSymbolPreset();
|
|
2471
2312
|
|
|
@@ -52,9 +52,29 @@ Push back when warranted: state the downside, propose an alternative, but **MUST
|
|
|
52
52
|
<communication>
|
|
53
53
|
- No emojis, filler, or ceremony.
|
|
54
54
|
- (1) Correctness first, (2) Brevity second, (3) Politeness third.
|
|
55
|
-
-
|
|
55
|
+
- Prefer concise, information-dense writing.
|
|
56
|
+
- Avoid repeating the user's request or narrating routine tool calls.
|
|
56
57
|
</communication>
|
|
57
58
|
|
|
59
|
+
<instruction-priority>
|
|
60
|
+
- User instructions override default style, tone, formatting, and initiative preferences.
|
|
61
|
+
- Higher-priority system constraints about safety, permissions, tool boundaries, and task completion do not yield.
|
|
62
|
+
- If a newer user instruction conflicts with an earlier user instruction, follow the newer one.
|
|
63
|
+
- Preserve earlier instructions that do not conflict.
|
|
64
|
+
</instruction-priority>
|
|
65
|
+
|
|
66
|
+
<output-contract>
|
|
67
|
+
- Brief preambles are allowed when they improve orientation, but they **MUST** stay short and **MUST NOT** be treated as completion.
|
|
68
|
+
- Claims about code, tools, tests, docs, or external sources **MUST** be grounded in what you actually observed. If a statement is an inference, say so.
|
|
69
|
+
- Apply brevity to prose, not to evidence, verification, or blocking details.
|
|
70
|
+
</output-contract>
|
|
71
|
+
|
|
72
|
+
<default-follow-through>
|
|
73
|
+
- If the user's intent is clear and the next step is reversible and low-risk, proceed without asking.
|
|
74
|
+
- Ask only when the next step is irreversible, has external side effects, or requires a missing choice that would materially change the outcome.
|
|
75
|
+
- If you proceed, state what you did, what you verified, and what remains optional.
|
|
76
|
+
</default-follow-through>
|
|
77
|
+
|
|
58
78
|
<behavior>
|
|
59
79
|
You **MUST** guard against the completion reflex — the urge to ship something that compiles before you've understood the problem:
|
|
60
80
|
- Compiling ≠ Correctness. "It works" ≠ "Works in all cases".
|
|
@@ -248,6 +268,15 @@ Don't open a file hoping. Hope is not a strategy.
|
|
|
248
268
|
{{#has tools "task"}}- `task` for investigate+edit in one pass — prefer this over a separate explore→task chain{{/has}}
|
|
249
269
|
{{/ifAny}}
|
|
250
270
|
|
|
271
|
+
<tool-persistence>
|
|
272
|
+
- Use tools whenever they materially improve correctness, completeness, or grounding.
|
|
273
|
+
- Do not stop at the first plausible answer if another tool call would materially reduce uncertainty, verify a dependency, or improve coverage.
|
|
274
|
+
- Before taking an action, check whether prerequisite discovery, lookup, or memory retrieval is required. Resolve prerequisites first.
|
|
275
|
+
- If a lookup is empty, partial, or suspiciously narrow, retry with a different strategy before concluding nothing exists.
|
|
276
|
+
- When multiple retrieval steps are independent, parallelize them. When one result determines the next step, keep the workflow sequential.
|
|
277
|
+
- After parallel retrieval, pause to synthesize before making more calls.
|
|
278
|
+
</tool-persistence>
|
|
279
|
+
|
|
251
280
|
{{#if (includes tools "inspect_image")}}
|
|
252
281
|
### Image inspection
|
|
253
282
|
- For image understanding tasks: **MUST** use `inspect_image` over `read` to avoid overloading main session context.
|
|
@@ -262,7 +291,14 @@ These are inviolable. Violation is system failure.
|
|
|
262
291
|
- You **MUST NOT** suppress tests to make code pass. You **MUST NOT** fabricate outputs not observed.
|
|
263
292
|
- You **MUST NOT** solve the wished-for problem instead of the actual problem.
|
|
264
293
|
- You **MUST NOT** ask for information obtainable from tools, repo context, or files.
|
|
265
|
-
- You **MUST** always design a clean solution. You **MUST NOT** introduce unnecessary backwards
|
|
294
|
+
- You **MUST** always design a clean solution. You **MUST NOT** introduce unnecessary backwards compatibility layers, no shims, no gradual migration, no bridges to old code unless user explicitly asks for it. Let the errors guide you on what to include in the refactoring. **ALWAYS default to performing full CUTOVER!**
|
|
295
|
+
|
|
296
|
+
<completeness-contract>
|
|
297
|
+
- Treat the task as incomplete until every requested deliverable is done or explicitly marked [blocked].
|
|
298
|
+
- Keep an internal checklist of requested outcomes, implied cleanup, affected callsites, tests, docs, and follow-on edits.
|
|
299
|
+
- For lists, batches, paginated results, or multi-file migrations, determine expected scope when possible and confirm coverage before yielding.
|
|
300
|
+
- If something is blocked, label it [blocked], say exactly what is missing, and distinguish it from work that is complete.
|
|
301
|
+
</completeness-contract>
|
|
266
302
|
|
|
267
303
|
# Design Integrity
|
|
268
304
|
|
|
@@ -280,6 +316,7 @@ Design integrity means the code tells the truth about what the system currently
|
|
|
280
316
|
{{#has tools "task"}}- You **MUST** determine if the task is parallelizable via `task` tool.{{/has}}
|
|
281
317
|
- If multi-file or imprecisely scoped, you **MUST** write out a step-by-step plan, phased if it warrants, before touching any file.
|
|
282
318
|
- For new work, you **MUST**: (1) think about architecture, (2) search official docs/papers on best practices, (3) review existing codebase, (4) compare research with codebase, (5) implement the best fit or surface tradeoffs.
|
|
319
|
+
- If required context is missing, do **NOT** guess. Prefer tool-based retrieval first, ask a minimal question only when the answer cannot be recovered from tools, repo context, or files.
|
|
283
320
|
## 2. Before You Edit
|
|
284
321
|
- Read the relevant section of any file before editing. Don't edit from a grep snippet alone — context above and below the match changes what the correct edit is.
|
|
285
322
|
- You **MUST** grep for existing examples before implementing any pattern, utility, or abstraction. If the codebase already solves it, you **MUST** use that. Inventing a parallel convention is **PROHIBITED**.
|
|
@@ -313,6 +350,7 @@ When a tool call fails, read the full error before doing anything else. When a f
|
|
|
313
350
|
- Test everything rigorously → Future contributor cannot break behavior without failure. Prefer unit/e2e.
|
|
314
351
|
- You **MUST NOT** rely on mocks — they invent behaviors that never happen in production and hide real bugs.
|
|
315
352
|
- You **SHOULD** run only tests you added/modified unless asked otherwise.
|
|
353
|
+
- Before yielding, verify: (1) every requirement is satisfied, (2) claims match files/tool output/source material, (3) the output format matches the ask, and (4) any high-impact action was either verified or explicitly held for permission.
|
|
316
354
|
- You **MUST NOT** yield without proof when non-trivial work, self-assessment is deceptive: tests, linters, type checks, repro steps… exhaust all external verification.
|
|
317
355
|
|
|
318
356
|
{{#if secretsEnabled}}
|
|
@@ -7,11 +7,20 @@ 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
|
+
{{#if chunkAutoIndent}}
|
|
10
11
|
- 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
12
|
```
|
|
12
13
|
content: "if (x) {\n\treturn true;\n}"
|
|
13
14
|
```
|
|
14
15
|
The tool adds the correct base indent automatically. Never manually pad with the chunk's own indentation.
|
|
16
|
+
{{else}}
|
|
17
|
+
- Match the file's literal tabs/spaces in `content`. Do not convert indentation to canonical `\t`.
|
|
18
|
+
- Write content at indent-level 0 relative to the target region. For example, to replace `@body` of a method, write:
|
|
19
|
+
```
|
|
20
|
+
content: "if (x) {\n return true;\n}"
|
|
21
|
+
```
|
|
22
|
+
The tool adds the correct base indent automatically, then preserves the tabs/spaces you used inside the snippet. Never manually pad with the chunk's own indentation.
|
|
23
|
+
{{/if}}
|
|
15
24
|
- `@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.
|
|
16
25
|
- `replace` requires the current CRC. Insertions do not.
|
|
17
26
|
- **CRCs change after every edit.** Always use the selectors/CRCs from the most recent `read` or edit response. Never reuse a CRC from a previous edit.
|
|
@@ -113,7 +122,11 @@ Given this `read` output for `example.ts`:
|
|
|
113
122
|
|
|
114
123
|
**Replace a whole chunk** (rename a function):
|
|
115
124
|
~~~json
|
|
125
|
+
{{#if chunkAutoIndent}}
|
|
116
126
|
{ "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" }
|
|
127
|
+
{{else}}
|
|
128
|
+
{ "sel": "fn_createCounter#PQQY", "op": "replace", "content": "function makeCounter(start: number): Counter {\n const c = new Counter();\n c.value = start;\n return c;\n}\n" }
|
|
129
|
+
{{/if}}
|
|
117
130
|
~~~
|
|
118
131
|
Result — the entire chunk is rewritten:
|
|
119
132
|
```
|
|
@@ -163,7 +176,11 @@ function createCounter(initial: number): Counter {
|
|
|
163
176
|
|
|
164
177
|
**Insert after a chunk** (`after`):
|
|
165
178
|
~~~json
|
|
179
|
+
{{#if chunkAutoIndent}}
|
|
166
180
|
{ "sel": "enum_Status", "op": "after", "content": "\nfunction isActive(s: Status): boolean {\n\treturn s === Status.Active;\n}\n" }
|
|
181
|
+
{{else}}
|
|
182
|
+
{ "sel": "enum_Status", "op": "after", "content": "\nfunction isActive(s: Status): boolean {\n return s === Status.Active;\n}\n" }
|
|
183
|
+
{{/if}}
|
|
167
184
|
~~~
|
|
168
185
|
Result — a new function appears after the enum:
|
|
169
186
|
```
|
|
@@ -194,7 +211,11 @@ class Counter {
|
|
|
194
211
|
|
|
195
212
|
**Append inside a container** (`@body` + `append`):
|
|
196
213
|
~~~json
|
|
214
|
+
{{#if chunkAutoIndent}}
|
|
197
215
|
{ "sel": "class_Counter@body", "op": "append", "content": "\nreset(): void {\n\tthis.value = 0;\n}\n" }
|
|
216
|
+
{{else}}
|
|
217
|
+
{ "sel": "class_Counter@body", "op": "append", "content": "\nreset(): void {\n this.value = 0;\n}\n" }
|
|
218
|
+
{{/if}}
|
|
198
219
|
~~~
|
|
199
220
|
Result — a new method is added at the end of the class body, before the closing `}`:
|
|
200
221
|
```
|
|
@@ -214,10 +235,18 @@ Result — a new method is added at the end of the class body, before the closin
|
|
|
214
235
|
```
|
|
215
236
|
Result — the method is removed from the class.
|
|
216
237
|
- Indentation rules (important):
|
|
238
|
+
{{#if chunkAutoIndent}}
|
|
217
239
|
- Use `\t` for each indent level. The tool converts tabs to the file's actual style (2-space, 4-space, etc.).
|
|
240
|
+
{{else}}
|
|
241
|
+
- Match the file's real indentation characters in your snippet. The tool preserves your literal tabs/spaces after adding the target region's base indent.
|
|
242
|
+
{{/if}}
|
|
218
243
|
- Do NOT include the chunk's base indentation — only indent relative to the region's opening level.
|
|
219
244
|
- For `@body` of a function: write at column 0, e.g. `"return x;\n"`. The tool adds the correct base indent.
|
|
220
245
|
- For `@head`: write at the chunk's own depth. A class member's head uses `"/** doc */\nstart(): void {"`.
|
|
246
|
+
{{#if chunkAutoIndent}}
|
|
221
247
|
- For a top-level item: start at zero indent. Write `"function foo() {\n\treturn 1;\n}\n"`.
|
|
248
|
+
{{else}}
|
|
249
|
+
- For a top-level item: start at zero indent. Write `"function foo() {\n return 1;\n}\n"`.
|
|
250
|
+
{{/if}}
|
|
222
251
|
- The tool strips common leading indentation from your content as a safety net, so accidental over-indentation is corrected.
|
|
223
252
|
</examples>
|
|
@@ -9,6 +9,12 @@ Each opening anchor `[< full.chunk.path#CCCC ]` in the default output identifies
|
|
|
9
9
|
If you need a canonical target list, run `read(path="file", sel="?")`. That listing shows chunk paths with CRCs.
|
|
10
10
|
Line numbers in the gutter are absolute file line numbers.
|
|
11
11
|
|
|
12
|
+
{{#if chunkAutoIndent}}
|
|
13
|
+
Chunk reads normalize leading indentation so copied content round-trips cleanly into chunk edits.
|
|
14
|
+
{{else}}
|
|
15
|
+
Chunk reads preserve literal leading tabs/spaces from the file. When editing, keep the same whitespace characters you see here.
|
|
16
|
+
{{/if}}
|
|
17
|
+
|
|
12
18
|
Chunk trees: JS, TS, TSX, Python, Rust, Go. Others use blank-line fallback.
|
|
13
19
|
</instruction>
|
|
14
20
|
|
package/src/sdk.ts
CHANGED
|
@@ -15,6 +15,7 @@ import { SearchDb } from "@oh-my-pi/pi-natives";
|
|
|
15
15
|
import type { Component } from "@oh-my-pi/pi-tui";
|
|
16
16
|
import {
|
|
17
17
|
$env,
|
|
18
|
+
$flag,
|
|
18
19
|
getAgentDbPath,
|
|
19
20
|
getAgentDir,
|
|
20
21
|
getProjectDir,
|
|
@@ -1262,7 +1263,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
1262
1263
|
|
|
1263
1264
|
const repeatToolDescriptions = settings.get("repeatToolDescriptions");
|
|
1264
1265
|
const eagerTasks = settings.get("task.eager");
|
|
1265
|
-
const intentField = settings.get("tools.intentTracing") || $
|
|
1266
|
+
const intentField = settings.get("tools.intentTracing") || $flag("PI_INTENT_TRACING") ? INTENT_FIELD : undefined;
|
|
1266
1267
|
const rebuildSystemPrompt = async (toolNames: string[], tools: Map<string, AgentTool>): Promise<string> => {
|
|
1267
1268
|
toolContextStore.setToolNames(toolNames);
|
|
1268
1269
|
const discoverableMCPTools = mcpDiscoveryEnabled ? collectDiscoverableMCPTools(tools.values()) : [];
|
package/src/tools/ast-edit.ts
CHANGED
|
@@ -3,7 +3,7 @@ import type { AgentTool, AgentToolContext, AgentToolResult, AgentToolUpdateCallb
|
|
|
3
3
|
import { type AstReplaceChange, astEdit } from "@oh-my-pi/pi-natives";
|
|
4
4
|
import type { Component } from "@oh-my-pi/pi-tui";
|
|
5
5
|
import { Text } from "@oh-my-pi/pi-tui";
|
|
6
|
-
import { prompt, untilAborted } from "@oh-my-pi/pi-utils";
|
|
6
|
+
import { $envpos, prompt, untilAborted } from "@oh-my-pi/pi-utils";
|
|
7
7
|
import { type Static, Type } from "@sinclair/typebox";
|
|
8
8
|
import { computeLineHash } from "../edit/line-hash";
|
|
9
9
|
import type { RenderResultOptions } from "../extensibility/custom-tools/types";
|
|
@@ -103,7 +103,7 @@ export class AstEditTool implements AgentTool<typeof astEditSchema, AstEditToolD
|
|
|
103
103
|
if (maxReplacements !== undefined && (!Number.isFinite(maxReplacements) || maxReplacements < 1)) {
|
|
104
104
|
throw new ToolError("limit must be a positive number");
|
|
105
105
|
}
|
|
106
|
-
const maxFiles =
|
|
106
|
+
const maxFiles = $envpos("PI_MAX_AST_FILES", 1000);
|
|
107
107
|
|
|
108
108
|
const formatScopePath = (targetPath: string): string => {
|
|
109
109
|
const relative = path.relative(this.session.cwd, targetPath).replace(/\\/g, "/");
|
package/src/tools/index.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { AgentTool } from "@oh-my-pi/pi-agent-core";
|
|
2
2
|
import type { ToolChoice } from "@oh-my-pi/pi-ai";
|
|
3
3
|
import type { SearchDb } from "@oh-my-pi/pi-natives";
|
|
4
|
-
import { $env, logger } from "@oh-my-pi/pi-utils";
|
|
4
|
+
import { $env, $flag, isBunTestRuntime, logger } from "@oh-my-pi/pi-utils";
|
|
5
5
|
import type { AsyncJobManager } from "../async";
|
|
6
6
|
import type { PromptTemplate } from "../config/prompt-templates";
|
|
7
7
|
import type { Settings } from "../config/settings";
|
|
@@ -297,8 +297,7 @@ export async function createTools(session: ToolSession, toolNames?: string[]): P
|
|
|
297
297
|
!skipPythonPreflight &&
|
|
298
298
|
pythonMode !== "bash-only" &&
|
|
299
299
|
(requestedTools === undefined || requestedTools.includes("python"));
|
|
300
|
-
const
|
|
301
|
-
const skipPythonWarm = isTestEnv || $env.PI_PYTHON_SKIP_CHECK === "1";
|
|
300
|
+
const skipPythonWarm = isBunTestRuntime() || $flag("PI_PYTHON_SKIP_CHECK");
|
|
302
301
|
if (shouldCheckPython) {
|
|
303
302
|
const availability = await logger.time("createTools:pythonCheck", checkPythonKernelAvailability, session.cwd);
|
|
304
303
|
pythonAvailable = availability.ok;
|
package/src/tools/read.ts
CHANGED
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
parseChunkReadPath,
|
|
15
15
|
parseChunkSelector,
|
|
16
16
|
resolveAnchorStyle,
|
|
17
|
+
resolveChunkAutoIndent,
|
|
17
18
|
} from "../edit/modes/chunk";
|
|
18
19
|
import type { RenderResultOptions } from "../extensibility/custom-tools/types";
|
|
19
20
|
import { parseInternalUrl } from "../internal-urls/parse";
|
|
@@ -449,6 +450,7 @@ export class ReadTool implements AgentTool<typeof readSchema, ReadToolDetails> {
|
|
|
449
450
|
resolveEditMode(session) === "chunk"
|
|
450
451
|
? prompt.render(readChunkDescription, {
|
|
451
452
|
anchorStyle: resolveAnchorStyle(session.settings),
|
|
453
|
+
chunkAutoIndent: resolveChunkAutoIndent(),
|
|
452
454
|
})
|
|
453
455
|
: prompt.render(readDescription, {
|
|
454
456
|
DEFAULT_LIMIT: String(this.#defaultLimit),
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
import { Database } from "bun:sqlite";
|
|
9
9
|
import path from "node:path";
|
|
10
10
|
import type { AgentTool } from "@oh-my-pi/pi-agent-core";
|
|
11
|
-
import { $
|
|
11
|
+
import { $flag, getAgentDir, logger, VERSION } from "@oh-my-pi/pi-utils";
|
|
12
12
|
import { Type } from "@sinclair/typebox";
|
|
13
13
|
import type { Settings } from "..";
|
|
14
14
|
import type { ToolSession } from "./index";
|
|
@@ -19,7 +19,7 @@ const ReportToolIssueParams = Type.Object({
|
|
|
19
19
|
});
|
|
20
20
|
|
|
21
21
|
export function isAutoQaEnabled(settings?: Settings): boolean {
|
|
22
|
-
return $
|
|
22
|
+
return $flag("PI_AUTO_QA") || !!settings?.get("dev.autoqa");
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
export function getAutoQaDbPath(): string {
|
package/src/utils/edit-mode.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { $env } from "@oh-my-pi/pi-utils";
|
|
1
|
+
import { $env, $flag } from "@oh-my-pi/pi-utils";
|
|
2
2
|
|
|
3
3
|
export type EditMode = "replace" | "patch" | "hashline" | "chunk";
|
|
4
4
|
|
|
@@ -36,7 +36,7 @@ export function resolveEditMode(session: EditModeSessionLike): EditMode {
|
|
|
36
36
|
const envMode = normalizeEditMode($env.PI_EDIT_VARIANT);
|
|
37
37
|
if (envMode) return envMode;
|
|
38
38
|
|
|
39
|
-
if (
|
|
39
|
+
if (!$flag("PI_STRICT_EDIT_MODE")) {
|
|
40
40
|
if (activeModel?.includes("spark")) return "replace";
|
|
41
41
|
if (activeModel?.includes("nano")) return "replace";
|
|
42
42
|
if (activeModel?.includes("mini")) return "replace";
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
import * as path from "node:path";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Extension segment → [highlight language id, LSP language id].
|
|
5
|
+
* Highlight ids match tree-sitter / native highlighter; LSP ids match Language Server Protocol.
|
|
6
|
+
*/
|
|
7
|
+
const EXTENSION_LANG: Record<string, readonly [string, string]> = {
|
|
8
|
+
// TypeScript / JavaScript
|
|
9
|
+
ts: ["typescript", "typescript"],
|
|
10
|
+
cts: ["typescript", "typescript"],
|
|
11
|
+
mts: ["typescript", "typescript"],
|
|
12
|
+
tsx: ["tsx", "typescriptreact"],
|
|
13
|
+
js: ["javascript", "javascript"],
|
|
14
|
+
jsx: ["javascript", "javascriptreact"],
|
|
15
|
+
mjs: ["javascript", "javascript"],
|
|
16
|
+
cjs: ["javascript", "javascript"],
|
|
17
|
+
|
|
18
|
+
// Systems
|
|
19
|
+
rs: ["rust", "rust"],
|
|
20
|
+
go: ["go", "go"],
|
|
21
|
+
c: ["c", "c"],
|
|
22
|
+
h: ["c", "c"],
|
|
23
|
+
cpp: ["cpp", "cpp"],
|
|
24
|
+
cc: ["cpp", "cpp"],
|
|
25
|
+
cxx: ["cpp", "cpp"],
|
|
26
|
+
hh: ["cpp", "cpp"],
|
|
27
|
+
hpp: ["cpp", "cpp"],
|
|
28
|
+
hxx: ["cpp", "cpp"],
|
|
29
|
+
cu: ["cpp", "cpp"],
|
|
30
|
+
ino: ["cpp", "cpp"],
|
|
31
|
+
zig: ["zig", "zig"],
|
|
32
|
+
|
|
33
|
+
// Scripting
|
|
34
|
+
py: ["python", "python"],
|
|
35
|
+
pyi: ["python", "python"],
|
|
36
|
+
rb: ["ruby", "ruby"],
|
|
37
|
+
rbw: ["ruby", "ruby"],
|
|
38
|
+
gemspec: ["ruby", "ruby"],
|
|
39
|
+
lua: ["lua", "lua"],
|
|
40
|
+
sh: ["bash", "shellscript"],
|
|
41
|
+
bash: ["bash", "shellscript"],
|
|
42
|
+
zsh: ["bash", "shellscript"],
|
|
43
|
+
ksh: ["bash", "shellscript"],
|
|
44
|
+
bats: ["bash", "shellscript"],
|
|
45
|
+
tmux: ["bash", "shellscript"],
|
|
46
|
+
cgi: ["bash", "shellscript"],
|
|
47
|
+
fcgi: ["bash", "shellscript"],
|
|
48
|
+
command: ["bash", "shellscript"],
|
|
49
|
+
tool: ["bash", "shellscript"],
|
|
50
|
+
fish: ["fish", "fish"],
|
|
51
|
+
pl: ["perl", "perl"],
|
|
52
|
+
pm: ["perl", "perl"],
|
|
53
|
+
perl: ["perl", "perl"],
|
|
54
|
+
php: ["php", "php"],
|
|
55
|
+
|
|
56
|
+
// JVM
|
|
57
|
+
java: ["java", "java"],
|
|
58
|
+
kt: ["kotlin", "kotlin"],
|
|
59
|
+
ktm: ["kotlin", "kotlin"],
|
|
60
|
+
kts: ["kotlin", "kotlin"],
|
|
61
|
+
scala: ["scala", "scala"],
|
|
62
|
+
sc: ["scala", "scala"],
|
|
63
|
+
sbt: ["scala", "scala"],
|
|
64
|
+
groovy: ["groovy", "groovy"],
|
|
65
|
+
clj: ["clojure", "clojure"],
|
|
66
|
+
cljc: ["clojure", "clojure"],
|
|
67
|
+
cljs: ["clojure", "clojure"],
|
|
68
|
+
edn: ["clojure", "clojure"],
|
|
69
|
+
|
|
70
|
+
// .NET
|
|
71
|
+
cs: ["csharp", "csharp"],
|
|
72
|
+
fs: ["fsharp", "fsharp"],
|
|
73
|
+
vb: ["vb", "vb"],
|
|
74
|
+
|
|
75
|
+
// Web
|
|
76
|
+
html: ["html", "html"],
|
|
77
|
+
htm: ["html", "html"],
|
|
78
|
+
xhtml: ["html", "html"],
|
|
79
|
+
css: ["css", "css"],
|
|
80
|
+
scss: ["scss", "scss"],
|
|
81
|
+
sass: ["sass", "sass"],
|
|
82
|
+
less: ["less", "less"],
|
|
83
|
+
vue: ["vue", "vue"],
|
|
84
|
+
svelte: ["svelte", "svelte"],
|
|
85
|
+
astro: ["astro", "astro"],
|
|
86
|
+
|
|
87
|
+
// Data
|
|
88
|
+
json: ["json", "json"],
|
|
89
|
+
jsonc: ["jsonc", "jsonc"],
|
|
90
|
+
yaml: ["yaml", "yaml"],
|
|
91
|
+
yml: ["yaml", "yaml"],
|
|
92
|
+
toml: ["toml", "toml"],
|
|
93
|
+
xml: ["xml", "xml"],
|
|
94
|
+
xsl: ["xml", "xml"],
|
|
95
|
+
xslt: ["xml", "xml"],
|
|
96
|
+
svg: ["xml", "xml"],
|
|
97
|
+
plist: ["xml", "xml"],
|
|
98
|
+
ini: ["ini", "ini"],
|
|
99
|
+
|
|
100
|
+
// Docs
|
|
101
|
+
md: ["markdown", "markdown"],
|
|
102
|
+
markdown: ["markdown", "markdown"],
|
|
103
|
+
mdx: ["markdown", "markdown"],
|
|
104
|
+
rst: ["restructuredtext", "restructuredtext"],
|
|
105
|
+
adoc: ["asciidoc", "asciidoc"],
|
|
106
|
+
tex: ["latex", "latex"],
|
|
107
|
+
|
|
108
|
+
// Other languages
|
|
109
|
+
sql: ["sql", "sql"],
|
|
110
|
+
graphql: ["graphql", "graphql"],
|
|
111
|
+
gql: ["graphql", "graphql"],
|
|
112
|
+
proto: ["protobuf", "protobuf"],
|
|
113
|
+
dockerfile: ["dockerfile", "dockerfile"],
|
|
114
|
+
containerfile: ["dockerfile", "dockerfile"],
|
|
115
|
+
tf: ["hcl", "terraform"],
|
|
116
|
+
hcl: ["hcl", "hcl"],
|
|
117
|
+
tfvars: ["hcl", "hcl"],
|
|
118
|
+
nix: ["nix", "nix"],
|
|
119
|
+
ex: ["elixir", "elixir"],
|
|
120
|
+
exs: ["elixir", "elixir"],
|
|
121
|
+
erl: ["erlang", "erlang"],
|
|
122
|
+
hrl: ["erlang", "erlang"],
|
|
123
|
+
hs: ["haskell", "haskell"],
|
|
124
|
+
ml: ["ocaml", "ocaml"],
|
|
125
|
+
mli: ["ocaml", "ocaml"],
|
|
126
|
+
swift: ["swift", "swift"],
|
|
127
|
+
r: ["r", "r"],
|
|
128
|
+
jl: ["julia", "julia"],
|
|
129
|
+
dart: ["dart", "dart"],
|
|
130
|
+
elm: ["elm", "elm"],
|
|
131
|
+
v: ["verilog", "v"],
|
|
132
|
+
nim: ["nim", "nim"],
|
|
133
|
+
cr: ["crystal", "crystal"],
|
|
134
|
+
d: ["d", "d"],
|
|
135
|
+
pas: ["pascal", "pascal"],
|
|
136
|
+
pp: ["pascal", "pascal"],
|
|
137
|
+
lisp: ["lisp", "lisp"],
|
|
138
|
+
lsp: ["lisp", "lisp"],
|
|
139
|
+
rkt: ["racket", "racket"],
|
|
140
|
+
scm: ["scheme", "scheme"],
|
|
141
|
+
ps1: ["powershell", "powershell"],
|
|
142
|
+
psm1: ["powershell", "powershell"],
|
|
143
|
+
bat: ["bat", "bat"],
|
|
144
|
+
cmd: ["bat", "bat"],
|
|
145
|
+
tla: ["tlaplus", "tlaplus"],
|
|
146
|
+
tlaplus: ["tlaplus", "tlaplus"],
|
|
147
|
+
m: ["objc", "plaintext"],
|
|
148
|
+
mm: ["objc", "plaintext"],
|
|
149
|
+
sol: ["solidity", "plaintext"],
|
|
150
|
+
odin: ["odin", "plaintext"],
|
|
151
|
+
star: ["starlark", "plaintext"],
|
|
152
|
+
bzl: ["starlark", "plaintext"],
|
|
153
|
+
sv: ["verilog", "plaintext"],
|
|
154
|
+
svh: ["verilog", "plaintext"],
|
|
155
|
+
vh: ["verilog", "plaintext"],
|
|
156
|
+
vim: ["vim", "plaintext"],
|
|
157
|
+
ipynb: ["ipynb", "plaintext"],
|
|
158
|
+
hbs: ["handlebars", "plaintext"],
|
|
159
|
+
hsb: ["handlebars", "plaintext"],
|
|
160
|
+
handlebars: ["handlebars", "plaintext"],
|
|
161
|
+
diff: ["diff", "plaintext"],
|
|
162
|
+
patch: ["diff", "plaintext"],
|
|
163
|
+
makefile: ["make", "plaintext"],
|
|
164
|
+
mk: ["make", "plaintext"],
|
|
165
|
+
mak: ["make", "plaintext"],
|
|
166
|
+
cmake: ["cmake", "cmake"],
|
|
167
|
+
justfile: ["just", "plaintext"],
|
|
168
|
+
txt: ["text", "plaintext"],
|
|
169
|
+
text: ["text", "plaintext"],
|
|
170
|
+
log: ["log", "plaintext"],
|
|
171
|
+
csv: ["csv", "plaintext"],
|
|
172
|
+
tsv: ["tsv", "plaintext"],
|
|
173
|
+
cfg: ["conf", "plaintext"],
|
|
174
|
+
conf: ["conf", "plaintext"],
|
|
175
|
+
config: ["conf", "plaintext"],
|
|
176
|
+
properties: ["conf", "plaintext"],
|
|
177
|
+
env: ["env", "plaintext"],
|
|
178
|
+
gitignore: ["conf", "plaintext"],
|
|
179
|
+
gitattributes: ["conf", "plaintext"],
|
|
180
|
+
gitmodules: ["conf", "plaintext"],
|
|
181
|
+
editorconfig: ["conf", "plaintext"],
|
|
182
|
+
npmrc: ["conf", "plaintext"],
|
|
183
|
+
prettierrc: ["conf", "plaintext"],
|
|
184
|
+
eslintrc: ["conf", "plaintext"],
|
|
185
|
+
prettierignore: ["conf", "plaintext"],
|
|
186
|
+
eslintignore: ["conf", "plaintext"],
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
/** Final segment after the last `.` in the full path (prior theme behavior). */
|
|
190
|
+
function themeExtensionKey(filePath: string): string {
|
|
191
|
+
const extBeg = filePath.lastIndexOf(".");
|
|
192
|
+
return extBeg !== -1 ? filePath.slice(extBeg + 1).toLowerCase() : filePath.toLowerCase();
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function lspExtensionKey(filePath: string): string {
|
|
196
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
197
|
+
return ext.startsWith(".") ? ext.slice(1) : "";
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Language id for syntax highlighting and UI (icons, read tool), or undefined if unknown.
|
|
202
|
+
*/
|
|
203
|
+
export function getLanguageFromPath(filePath: string): string | undefined {
|
|
204
|
+
const pair = EXTENSION_LANG[themeExtensionKey(filePath)];
|
|
205
|
+
if (pair) return pair[0];
|
|
206
|
+
|
|
207
|
+
const baseName = path.basename(filePath).toLowerCase();
|
|
208
|
+
if (baseName.startsWith(".env.")) return "env";
|
|
209
|
+
if (baseName === "dockerfile" || baseName.startsWith("dockerfile.") || baseName === "containerfile") {
|
|
210
|
+
return "dockerfile";
|
|
211
|
+
}
|
|
212
|
+
if (baseName === "justfile") return "just";
|
|
213
|
+
if (baseName === "cmakelists.txt") return "cmake";
|
|
214
|
+
|
|
215
|
+
return undefined;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* LSP language identifier; falls back to `plaintext`.
|
|
220
|
+
*/
|
|
221
|
+
export function detectLanguageId(filePath: string): string {
|
|
222
|
+
const baseName = path.basename(filePath).toLowerCase();
|
|
223
|
+
if (baseName === "dockerfile" || baseName.startsWith("dockerfile.") || baseName === "containerfile") {
|
|
224
|
+
return "dockerfile";
|
|
225
|
+
}
|
|
226
|
+
if (baseName === "makefile" || baseName === "gnumakefile") {
|
|
227
|
+
return "makefile";
|
|
228
|
+
}
|
|
229
|
+
if (baseName === "justfile") {
|
|
230
|
+
return "just";
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
const lspExt = lspExtensionKey(filePath);
|
|
234
|
+
if (baseName === "cmakelists.txt" || lspExt === "cmake") {
|
|
235
|
+
return "cmake";
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
return EXTENSION_LANG[lspExt]?.[1] ?? "plaintext";
|
|
239
|
+
}
|
package/src/utils/sixel.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { $env } from "@oh-my-pi/pi-utils";
|
|
1
|
+
import { $env, $flag } from "@oh-my-pi/pi-utils";
|
|
2
2
|
|
|
3
3
|
const SIXEL_START_REGEX = /\x1bP(?:[0-9;]*)q/u;
|
|
4
4
|
const SIXEL_END_SEQUENCE = "\x1b\\";
|
|
@@ -15,7 +15,7 @@ const SIXEL_PLACEHOLDER_PREFIX = "__OMP_SIXEL_SEQUENCE_";
|
|
|
15
15
|
*/
|
|
16
16
|
export function isSixelPassthroughEnabled(): boolean {
|
|
17
17
|
const forcedProtocol = $env.PI_FORCE_IMAGE_PROTOCOL?.trim().toLowerCase();
|
|
18
|
-
return forcedProtocol === "sixel" && $
|
|
18
|
+
return forcedProtocol === "sixel" && $flag("PI_ALLOW_SIXEL_PASSTHROUGH");
|
|
19
19
|
}
|
|
20
20
|
/** Returns true when the text contains a SIXEL start sequence. */
|
|
21
21
|
export function containsSixelSequence(text: string): boolean {
|