bortexcode 1.2.5 → 1.2.8
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/bin/bortex.js +289 -2
- package/package.json +1 -1
package/bin/bortex.js
CHANGED
|
@@ -1167,6 +1167,111 @@ function classifyLocalPromptIntent(text) {
|
|
|
1167
1167
|
return null;
|
|
1168
1168
|
}
|
|
1169
1169
|
|
|
1170
|
+
function stripMatchingQuotes(value) {
|
|
1171
|
+
const s = String(value || '').trim();
|
|
1172
|
+
if (s.length >= 2 && ((s[0] === '"' && s[s.length - 1] === '"') || (s[0] === '\'' && s[s.length - 1] === '\'') || (s[0] === '`' && s[s.length - 1] === '`'))) {
|
|
1173
|
+
return s.slice(1, -1);
|
|
1174
|
+
}
|
|
1175
|
+
return s;
|
|
1176
|
+
}
|
|
1177
|
+
|
|
1178
|
+
function extractNaturalFilePath(text) {
|
|
1179
|
+
const raw = String(text || '');
|
|
1180
|
+
const quotedAfterFile = raw.match(/(?:file|path|percorso)\s+["'`]([^"'`]+)["'`]/i);
|
|
1181
|
+
if (quotedAfterFile?.[1]) return quotedAfterFile[1].trim();
|
|
1182
|
+
const tokenAfterFile = raw.match(/(?:file|path|percorso)\s+([^\s,;]+)/i);
|
|
1183
|
+
if (tokenAfterFile?.[1]) return stripMatchingQuotes(tokenAfterFile[1]);
|
|
1184
|
+
const absoluteOrRelative = raw.match(/["'`]?((?:[A-Za-z]:[\\/]|\/|~\/|\.{1,2}[\\/])[^"'`\s,;:]+)["'`]?/);
|
|
1185
|
+
if (absoluteOrRelative?.[1]) return absoluteOrRelative[1].trim();
|
|
1186
|
+
return '';
|
|
1187
|
+
}
|
|
1188
|
+
|
|
1189
|
+
function extractNaturalFileContent(text, filePath) {
|
|
1190
|
+
const raw = String(text || '').trim();
|
|
1191
|
+
const marker = raw.match(/(?:con\s+(?:contenuto|testo)|contenente|with\s+(?:content|text)|containing)\s+([\s\S]+)$/i);
|
|
1192
|
+
let content = marker?.[1] || '';
|
|
1193
|
+
if (!content && filePath) {
|
|
1194
|
+
const beforeFile = raw.slice(0, raw.toLowerCase().indexOf(filePath.toLowerCase())).trim();
|
|
1195
|
+
const writePrefix = beforeFile.match(/(?:scrivi|write|salva|metti)\s+([\s\S]+?)(?:\s+(?:nel|nella|in|su|to)\s+(?:file|path|percorso)?\s*)?$/i);
|
|
1196
|
+
if (writePrefix?.[1]) content = writePrefix[1];
|
|
1197
|
+
}
|
|
1198
|
+
content = String(content || '').trim();
|
|
1199
|
+
if (!content) return '';
|
|
1200
|
+
if ((content[0] === '"' || content[0] === '\'' || content[0] === '`')) {
|
|
1201
|
+
const quote = content[0];
|
|
1202
|
+
const end = content.indexOf(quote, 1);
|
|
1203
|
+
if (end > 0) return content.slice(1, end);
|
|
1204
|
+
}
|
|
1205
|
+
content = content
|
|
1206
|
+
.replace(/\s+(?:e|and|poi|then)\s+(?:rispondi|reply|dimmi|dillo|fammi sapere|tell me)\b[\s\S]*$/i, '')
|
|
1207
|
+
.replace(/\s+(?:rispondi|reply)\b[\s\S]*$/i, '')
|
|
1208
|
+
.trim();
|
|
1209
|
+
return stripMatchingQuotes(content);
|
|
1210
|
+
}
|
|
1211
|
+
|
|
1212
|
+
function extractNaturalReplaceParts(text) {
|
|
1213
|
+
const raw = String(text || '').trim();
|
|
1214
|
+
const quotedPair = raw.match(/(?:sostituisci|rimpiazza|replace)\s+(["'`])([\s\S]*?)\1\s+(?:con|with)\s+(["'`])([\s\S]*?)\3/i);
|
|
1215
|
+
if (quotedPair) {
|
|
1216
|
+
return { search: quotedPair[2], replacement: quotedPair[4] };
|
|
1217
|
+
}
|
|
1218
|
+
const plainPair = raw.match(/(?:sostituisci|rimpiazza|replace)\s+([\s\S]+?)\s+(?:con|with)\s+([\s\S]+?)(?:\s+(?:nel|nella|in|su)\s+(?:file|path|percorso)?\s|$)/i);
|
|
1219
|
+
if (!plainPair) return null;
|
|
1220
|
+
const search = stripMatchingQuotes(plainPair[1]);
|
|
1221
|
+
const replacement = stripMatchingQuotes(plainPair[2]);
|
|
1222
|
+
if (!search) return null;
|
|
1223
|
+
return { search, replacement };
|
|
1224
|
+
}
|
|
1225
|
+
|
|
1226
|
+
function buildNaturalLocalFileActionCalls(text) {
|
|
1227
|
+
const raw = String(text || '').trim();
|
|
1228
|
+
const lower = raw.toLowerCase();
|
|
1229
|
+
if (!raw) return null;
|
|
1230
|
+
const filePath = extractNaturalFilePath(raw);
|
|
1231
|
+
if (!filePath) return null;
|
|
1232
|
+
|
|
1233
|
+
const replaceParts = extractNaturalReplaceParts(raw);
|
|
1234
|
+
if (replaceParts) {
|
|
1235
|
+
return [{
|
|
1236
|
+
tool: 'replace',
|
|
1237
|
+
path: filePath,
|
|
1238
|
+
search: replaceParts.search,
|
|
1239
|
+
replacement: replaceParts.replacement,
|
|
1240
|
+
all: true,
|
|
1241
|
+
confirm: true
|
|
1242
|
+
}];
|
|
1243
|
+
}
|
|
1244
|
+
|
|
1245
|
+
if (/(?:^|\b)(leggi|mostra|apri|read|show|cat)(?:\b|$)/i.test(raw) && /(?:file|path|percorso|\/|\\|\.\.?[\\/])/.test(raw)) {
|
|
1246
|
+
return [{ tool: 'read', path: filePath }];
|
|
1247
|
+
}
|
|
1248
|
+
|
|
1249
|
+
const wantsAppend = /(?:^|\b)(appendi|accoda|aggiungi|append|add)(?:\b|$)/i.test(raw) && /(?:file|path|percorso|\/|\\|\.\.?[\\/])/.test(raw);
|
|
1250
|
+
const wantsWrite = /(?:^|\b)(crea|creare|scrivi|write|create|salva|metti)(?:\b|$)/i.test(raw) && /(?:file|path|percorso|\/|\\|\.\.?[\\/])/.test(raw);
|
|
1251
|
+
if (!wantsAppend && !wantsWrite) return null;
|
|
1252
|
+
|
|
1253
|
+
const textValue = extractNaturalFileContent(raw, filePath);
|
|
1254
|
+
if (!textValue && !/(?:^|\b)(crea|create)(?:\b|$)/i.test(raw)) return null;
|
|
1255
|
+
return [{
|
|
1256
|
+
tool: wantsAppend ? 'append' : 'write',
|
|
1257
|
+
path: filePath,
|
|
1258
|
+
text: textValue,
|
|
1259
|
+
confirm: true
|
|
1260
|
+
}];
|
|
1261
|
+
}
|
|
1262
|
+
|
|
1263
|
+
async function runNaturalLocalFileAction(opts, prompt) {
|
|
1264
|
+
const calls = buildNaturalLocalFileActionCalls(prompt);
|
|
1265
|
+
if (!calls) return { handled: false };
|
|
1266
|
+
opts.lastToolRunReport = await executeStructuredToolBatch(opts, calls, {
|
|
1267
|
+
continueOnError: true,
|
|
1268
|
+
goal: prompt,
|
|
1269
|
+
source: 'natural-local-file-action',
|
|
1270
|
+
reportLabel: 'natural-local-action'
|
|
1271
|
+
});
|
|
1272
|
+
return { handled: true, data: opts.lastToolRunReport };
|
|
1273
|
+
}
|
|
1274
|
+
|
|
1170
1275
|
async function runSshStatusCommand(opts, { quiet = false } = {}) {
|
|
1171
1276
|
const cwd = opts?.cwd || process.cwd();
|
|
1172
1277
|
const platform = os.platform();
|
|
@@ -2644,6 +2749,86 @@ async function executeStructuredBuiltinTool(opts, call) {
|
|
|
2644
2749
|
error: res.ok ? null : `${label} exit ${res.code == null ? 'null' : res.code}`
|
|
2645
2750
|
};
|
|
2646
2751
|
};
|
|
2752
|
+
if (tool === 'applyPatch') {
|
|
2753
|
+
return runUnifiedPatchTool(opts, call);
|
|
2754
|
+
}
|
|
2755
|
+
if (tool === 'read') {
|
|
2756
|
+
const filePath = resolveCliPath(opts, call.path);
|
|
2757
|
+
try {
|
|
2758
|
+
const text = fs.readFileSync(filePath, 'utf8');
|
|
2759
|
+
process.stdout.write(text.endsWith('\n') ? text : `${text}\n`);
|
|
2760
|
+
return {
|
|
2761
|
+
ok: true,
|
|
2762
|
+
stdout: text,
|
|
2763
|
+
stderr: null,
|
|
2764
|
+
data: { path: filePath, bytes: Buffer.byteLength(text, 'utf8') }
|
|
2765
|
+
};
|
|
2766
|
+
} catch (err) {
|
|
2767
|
+
return { ok: false, stdout: null, stderr: err.message, data: { path: filePath }, error: err.message };
|
|
2768
|
+
}
|
|
2769
|
+
}
|
|
2770
|
+
if (tool === 'mkdir') {
|
|
2771
|
+
const dirPath = resolveCliPath(opts, call.path || '.');
|
|
2772
|
+
try {
|
|
2773
|
+
fs.mkdirSync(dirPath, { recursive: true });
|
|
2774
|
+
console.log(`Mkdir ok -> ${dirPath}`);
|
|
2775
|
+
return { ok: true, stdout: `Mkdir ok -> ${dirPath}\n`, stderr: null, data: { path: dirPath } };
|
|
2776
|
+
} catch (err) {
|
|
2777
|
+
return { ok: false, stdout: null, stderr: err.message, data: { path: dirPath }, error: err.message };
|
|
2778
|
+
}
|
|
2779
|
+
}
|
|
2780
|
+
if (tool === 'write' || tool === 'append') {
|
|
2781
|
+
const filePath = resolveCliPath(opts, call.path);
|
|
2782
|
+
const text = String(call.text ?? '');
|
|
2783
|
+
try {
|
|
2784
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
2785
|
+
if (tool === 'append') fs.appendFileSync(filePath, text, 'utf8');
|
|
2786
|
+
else fs.writeFileSync(filePath, text, 'utf8');
|
|
2787
|
+
const label = tool === 'append' ? 'Append' : 'Write';
|
|
2788
|
+
console.log(`${label} ok -> ${filePath}`);
|
|
2789
|
+
return {
|
|
2790
|
+
ok: true,
|
|
2791
|
+
stdout: `${label} ok -> ${filePath}\n`,
|
|
2792
|
+
stderr: null,
|
|
2793
|
+
data: { path: filePath, bytes: Buffer.byteLength(text, 'utf8'), mode: tool }
|
|
2794
|
+
};
|
|
2795
|
+
} catch (err) {
|
|
2796
|
+
return { ok: false, stdout: null, stderr: err.message, data: { path: filePath, mode: tool }, error: err.message };
|
|
2797
|
+
}
|
|
2798
|
+
}
|
|
2799
|
+
if (tool === 'replace') {
|
|
2800
|
+
const filePath = resolveCliPath(opts, call.path);
|
|
2801
|
+
const search = String(call.search ?? '');
|
|
2802
|
+
const replacement = String(call.replacement ?? '');
|
|
2803
|
+
const replaceAll = call.all !== false;
|
|
2804
|
+
try {
|
|
2805
|
+
if (!search) {
|
|
2806
|
+
return { ok: false, stdout: null, stderr: 'empty search string', data: { path: filePath }, error: 'empty search string' };
|
|
2807
|
+
}
|
|
2808
|
+
const before = fs.readFileSync(filePath, 'utf8');
|
|
2809
|
+
const count = before.split(search).length - 1;
|
|
2810
|
+
if (count <= 0) {
|
|
2811
|
+
const msg = `Replace no match -> ${filePath}`;
|
|
2812
|
+
console.log(msg);
|
|
2813
|
+
return { ok: false, stdout: `${msg}\n`, stderr: 'no matches', data: { path: filePath, search, count: 0 }, error: 'no matches' };
|
|
2814
|
+
}
|
|
2815
|
+
const after = replaceAll
|
|
2816
|
+
? before.split(search).join(replacement)
|
|
2817
|
+
: before.replace(search, replacement);
|
|
2818
|
+
fs.writeFileSync(filePath, after, 'utf8');
|
|
2819
|
+
const applied = replaceAll ? count : 1;
|
|
2820
|
+
const msg = `Replace ok -> ${filePath} (${applied} occurrence${applied === 1 ? '' : 's'})`;
|
|
2821
|
+
console.log(msg);
|
|
2822
|
+
return {
|
|
2823
|
+
ok: true,
|
|
2824
|
+
stdout: `${msg}\n`,
|
|
2825
|
+
stderr: null,
|
|
2826
|
+
data: { path: filePath, search, replacement, count: applied, all: replaceAll, bytes: Buffer.byteLength(after, 'utf8') }
|
|
2827
|
+
};
|
|
2828
|
+
} catch (err) {
|
|
2829
|
+
return { ok: false, stdout: null, stderr: err.message, data: { path: filePath, search }, error: err.message };
|
|
2830
|
+
}
|
|
2831
|
+
}
|
|
2647
2832
|
if (tool === 'gitStatus') {
|
|
2648
2833
|
const res = await runChild('git', ['status', '--short', ...(call.branch ? ['-b'] : [])], { cwd: opts.cwd, shell: false });
|
|
2649
2834
|
const entries = parseGitStatusShortEntries(res.stdout || '');
|
|
@@ -2900,6 +3085,19 @@ function validateStructuredToolCall(raw) {
|
|
|
2900
3085
|
if (typeof call.text !== 'string') return { ok: false, error: 'Campo `text` mancante o non valido.' };
|
|
2901
3086
|
break;
|
|
2902
3087
|
}
|
|
3088
|
+
case 'replace': {
|
|
3089
|
+
const e1 = requireString('path');
|
|
3090
|
+
if (e1) return { ok: false, error: e1 };
|
|
3091
|
+
const e2 = requireString('search');
|
|
3092
|
+
if (e2) return { ok: false, error: e2 };
|
|
3093
|
+
if (typeof call.replacement !== 'string') return { ok: false, error: 'Campo `replacement` mancante o non valido.' };
|
|
3094
|
+
if (call.all != null && typeof call.all !== 'boolean') return { ok: false, error: '`all` deve essere boolean.' };
|
|
3095
|
+
break;
|
|
3096
|
+
}
|
|
3097
|
+
case 'applyPatch':
|
|
3098
|
+
if (typeof call.patch !== 'string' || !call.patch.trim()) return { ok: false, error: 'Campo `patch` mancante o non valido.' };
|
|
3099
|
+
if (call.checkOnly != null && typeof call.checkOnly !== 'boolean') return { ok: false, error: '`checkOnly` deve essere boolean.' };
|
|
3100
|
+
break;
|
|
2903
3101
|
case 'diff':
|
|
2904
3102
|
case 'gitDiff':
|
|
2905
3103
|
if (call.mode && !['unstaged', 'staged', 'all'].includes(String(call.mode))) {
|
|
@@ -2961,6 +3159,8 @@ function structuredToolCallToLocalCommand(call) {
|
|
|
2961
3159
|
case 'mkdir': return `/mkdir ${call.path || '.'}`;
|
|
2962
3160
|
case 'write': return `/write ${call.path} ${call.text}`;
|
|
2963
3161
|
case 'append': return `/append ${call.path} ${call.text}`;
|
|
3162
|
+
case 'replace': return `replace(${call.path}: ${call.search} -> ${call.replacement}${call.all === false ? ', first' : ', all'})`;
|
|
3163
|
+
case 'applyPatch': return `applyPatch(${call.checkOnly ? 'check' : 'apply'}, ${String(call.patch || '').length} chars)`;
|
|
2964
3164
|
case 'diff': return `/diff ${call.mode || 'unstaged'}`;
|
|
2965
3165
|
case 'gitDiff': return `gitDiff(${call.mode || 'all'}${call.statOnly === false ? ',patch' : ',stat'})`;
|
|
2966
3166
|
case 'search': return `search(${call.pattern}${call.path ? ` in ${call.path}` : ''}${call.glob ? ` glob=${call.glob}` : ''})`;
|
|
@@ -2984,7 +3184,8 @@ function structuredToolCallToLocalCommand(call) {
|
|
|
2984
3184
|
|
|
2985
3185
|
function isRiskyStructuredToolCall(call) {
|
|
2986
3186
|
const t = String(call.tool || '');
|
|
2987
|
-
if (['write', 'append', 'mkdir', 'sh', 'runTest', 'runBuild', 'runLint'].includes(t)) return true;
|
|
3187
|
+
if (['write', 'append', 'replace', 'mkdir', 'sh', 'runTest', 'runBuild', 'runLint'].includes(t)) return true;
|
|
3188
|
+
if (t === 'applyPatch') return call.checkOnly !== true;
|
|
2988
3189
|
if (t === 'git') return !isReadonlyGitArgs(call.args);
|
|
2989
3190
|
return false;
|
|
2990
3191
|
}
|
|
@@ -3222,6 +3423,11 @@ function buildStructuredToolPlanFromGoal(opts, goal) {
|
|
|
3222
3423
|
{ tool: 'review', mode: 'all', fixSuggestions: true }
|
|
3223
3424
|
];
|
|
3224
3425
|
|
|
3426
|
+
const naturalFileCalls = buildNaturalLocalFileActionCalls(g);
|
|
3427
|
+
if (naturalFileCalls) {
|
|
3428
|
+
return naturalFileCalls;
|
|
3429
|
+
}
|
|
3430
|
+
|
|
3225
3431
|
const wantsStatus = /(verifica|controlla|check|mostra|stato|status|attivo|active|running|enabled|on)/.test(lower);
|
|
3226
3432
|
if (wantsStatus && /(\bssh\b|sshd|remote login)/.test(lower)) {
|
|
3227
3433
|
return [{ tool: 'project' }, { tool: 'sshStatus' }];
|
|
@@ -3356,7 +3562,7 @@ async function buildStructuredToolPlanWithLlm(opts, goal, llmOptions = {}) {
|
|
|
3356
3562
|
|
|
3357
3563
|
const profile = detectWorkspaceProfile(opts);
|
|
3358
3564
|
const supportedTools = [
|
|
3359
|
-
'project', 'pwd', 'ls', 'tree', 'glob', 'read', 'readMany', 'mkdir', 'write', 'append',
|
|
3565
|
+
'project', 'pwd', 'ls', 'tree', 'glob', 'read', 'readMany', 'mkdir', 'write', 'append', 'replace', 'applyPatch',
|
|
3360
3566
|
'diff', 'git', 'gitStatus', 'gitDiff', 'search', 'runTest', 'runBuild', 'runLint', 'sshStatus', 'systemStatus', 'processStatus', 'portStatus', 'sh', 'review', 'commitSuggest'
|
|
3361
3567
|
];
|
|
3362
3568
|
const testCmd = profile.suggested.test?.[0] || '';
|
|
@@ -3449,6 +3655,8 @@ async function buildStructuredToolPlanWithLlm(opts, goal, llmOptions = {}) {
|
|
|
3449
3655
|
'Regole:',
|
|
3450
3656
|
'- Preferisci tool read-only prima dei tool rischiosi',
|
|
3451
3657
|
'- Per controlli di stato locale usa sshStatus/systemStatus/processStatus/portStatus invece di sh quando possibile',
|
|
3658
|
+
'- Per modifiche testuali puntuali usa replace invece di sh',
|
|
3659
|
+
'- Per modifiche multi-file o blocchi ampi usa applyPatch con una unified diff valida; usa checkOnly:true se devi solo validare',
|
|
3452
3660
|
'- Includi review e commitSuggest verso la fine',
|
|
3453
3661
|
'- Se proponi `runTest`/`runBuild`/`runLint` preferiscili a `sh` quando possibile',
|
|
3454
3662
|
'- Se proponi `sh`, usa comandi test/build/lint concreti se disponibili',
|
|
@@ -3675,6 +3883,7 @@ function printLocalHelp() {
|
|
|
3675
3883
|
console.log(' /patch-apply <patch-file>');
|
|
3676
3884
|
console.log(' /patch-check-inline incolla patch e valida (.end per terminare)');
|
|
3677
3885
|
console.log(' /patch-apply-inline incolla patch e applica (.end per terminare)');
|
|
3886
|
+
console.log(' /tool {"tool":"applyPatch","patch":"...","checkOnly":true|false}');
|
|
3678
3887
|
console.log(' /todo add|list|done|rm|clear ...');
|
|
3679
3888
|
console.log(' /plan [goal]');
|
|
3680
3889
|
console.log(' /agent-local <goal> planner locale rapido');
|
|
@@ -3806,6 +4015,75 @@ function printPlan(opts) {
|
|
|
3806
4015
|
(opts.plan.steps || []).forEach((s, i) => console.log(` ${i + 1}. ${s}`));
|
|
3807
4016
|
}
|
|
3808
4017
|
|
|
4018
|
+
async function runUnifiedPatchTool(opts, call) {
|
|
4019
|
+
const patch = String(call.patch || '');
|
|
4020
|
+
const checkOnly = call.checkOnly === true;
|
|
4021
|
+
const data = {
|
|
4022
|
+
cwd: opts.cwd,
|
|
4023
|
+
patchChars: patch.length,
|
|
4024
|
+
checkOnly,
|
|
4025
|
+
checkOk: false,
|
|
4026
|
+
applied: false
|
|
4027
|
+
};
|
|
4028
|
+
if (!patch.trim()) {
|
|
4029
|
+
return { ok: false, stdout: null, stderr: 'empty patch', data, error: 'empty patch' };
|
|
4030
|
+
}
|
|
4031
|
+
|
|
4032
|
+
const tmp = path.join(os.tmpdir(), `bortex-tool-${Date.now()}-${Math.random().toString(16).slice(2)}.patch`);
|
|
4033
|
+
fs.writeFileSync(tmp, patch, 'utf8');
|
|
4034
|
+
try {
|
|
4035
|
+
const check = await runChild('git', ['apply', '--check', '--verbose', tmp], { cwd: opts.cwd, shell: false });
|
|
4036
|
+
const checkStdout = String(check.stdout || '');
|
|
4037
|
+
const checkStderr = String(check.stderr || '');
|
|
4038
|
+
if (checkStdout) process.stdout.write(checkStdout.endsWith('\n') ? checkStdout : `${checkStdout}\n`);
|
|
4039
|
+
if (checkStderr) process.stderr.write(checkStderr.endsWith('\n') ? checkStderr : `${checkStderr}\n`);
|
|
4040
|
+
data.checkOk = !!check.ok;
|
|
4041
|
+
data.checkExitCode = check.code == null ? null : check.code;
|
|
4042
|
+
|
|
4043
|
+
if (!check.ok) {
|
|
4044
|
+
const msg = 'Patch check failed';
|
|
4045
|
+
console.log(msg);
|
|
4046
|
+
return {
|
|
4047
|
+
ok: false,
|
|
4048
|
+
stdout: checkStdout || null,
|
|
4049
|
+
stderr: checkStderr || msg,
|
|
4050
|
+
data,
|
|
4051
|
+
error: msg
|
|
4052
|
+
};
|
|
4053
|
+
}
|
|
4054
|
+
|
|
4055
|
+
if (checkOnly) {
|
|
4056
|
+
const msg = 'Patch check ok';
|
|
4057
|
+
console.log(msg);
|
|
4058
|
+
return {
|
|
4059
|
+
ok: true,
|
|
4060
|
+
stdout: [checkStdout, `${msg}\n`].filter(Boolean).join(''),
|
|
4061
|
+
stderr: checkStderr || null,
|
|
4062
|
+
data
|
|
4063
|
+
};
|
|
4064
|
+
}
|
|
4065
|
+
|
|
4066
|
+
const applied = await runChild('git', ['apply', '--verbose', '--whitespace=nowarn', tmp], { cwd: opts.cwd, shell: false });
|
|
4067
|
+
const applyStdout = String(applied.stdout || '');
|
|
4068
|
+
const applyStderr = String(applied.stderr || '');
|
|
4069
|
+
if (applyStdout) process.stdout.write(applyStdout.endsWith('\n') ? applyStdout : `${applyStdout}\n`);
|
|
4070
|
+
if (applyStderr) process.stderr.write(applyStderr.endsWith('\n') ? applyStderr : `${applyStderr}\n`);
|
|
4071
|
+
data.applied = !!applied.ok;
|
|
4072
|
+
data.applyExitCode = applied.code == null ? null : applied.code;
|
|
4073
|
+
const msg = applied.ok ? 'Patch apply ok' : 'Patch apply failed';
|
|
4074
|
+
console.log(msg);
|
|
4075
|
+
return {
|
|
4076
|
+
ok: !!applied.ok,
|
|
4077
|
+
stdout: [checkStdout, applyStdout, `${msg}\n`].filter(Boolean).join(''),
|
|
4078
|
+
stderr: [checkStderr, applyStderr].filter(Boolean).join('\n') || null,
|
|
4079
|
+
data,
|
|
4080
|
+
error: applied.ok ? null : msg
|
|
4081
|
+
};
|
|
4082
|
+
} finally {
|
|
4083
|
+
try { fs.unlinkSync(tmp); } catch (_) {}
|
|
4084
|
+
}
|
|
4085
|
+
}
|
|
4086
|
+
|
|
3809
4087
|
async function runGitApplyInline(opts, mode) {
|
|
3810
4088
|
if (typeof opts._readMultiline !== 'function') {
|
|
3811
4089
|
throw new Error('Patch inline disponibile solo in REPL interattiva.');
|
|
@@ -6397,6 +6675,8 @@ async function runSinglePrompt(opts) {
|
|
|
6397
6675
|
const localResult = await handleLocalCommand(opts, opts.prompt);
|
|
6398
6676
|
if (localResult.handled) return;
|
|
6399
6677
|
}
|
|
6678
|
+
const naturalFileAction = await runNaturalLocalFileAction(opts, opts.prompt);
|
|
6679
|
+
if (naturalFileAction.handled) return;
|
|
6400
6680
|
const localIntent = classifyLocalPromptIntent(opts.prompt);
|
|
6401
6681
|
if (localIntent?.command) {
|
|
6402
6682
|
const localResult = await handleLocalCommand(opts, localIntent.command);
|
|
@@ -6495,6 +6775,13 @@ async function runRepl(opts) {
|
|
|
6495
6775
|
continue;
|
|
6496
6776
|
}
|
|
6497
6777
|
}
|
|
6778
|
+
try {
|
|
6779
|
+
const naturalFileAction = await runNaturalLocalFileAction(opts, line);
|
|
6780
|
+
if (naturalFileAction.handled) continue;
|
|
6781
|
+
} catch (err) {
|
|
6782
|
+
console.error(`Local tool error: ${err.message}`);
|
|
6783
|
+
continue;
|
|
6784
|
+
}
|
|
6498
6785
|
if (opts.offline) {
|
|
6499
6786
|
console.log('Offline mode: use /help for local tools or restart without --offline to use the server.');
|
|
6500
6787
|
continue;
|