@cortexkit/aft-opencode 0.38.0 → 0.39.0

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/dist/index.js CHANGED
@@ -11607,25 +11607,26 @@ function maybeAppendConflictsHint(output) {
11607
11607
  return output;
11608
11608
  return output + CONFLICT_HINT;
11609
11609
  }
11610
- function commandLeadsWithCodeSearch(command) {
11611
- const trimmed = command.trim();
11612
- if (!trimmed)
11610
+ function commandInvokesCodeSearch(command) {
11611
+ const statements = splitTopLevelStatements(command);
11612
+ if (statements === null)
11613
11613
  return false;
11614
- const afterCd = peelLeadingCdAnd(trimmed);
11615
- if (afterCd === null)
11616
- return false;
11617
- const firstStage = firstPipelineStage(afterCd);
11618
- if (firstStage === null)
11619
- return false;
11620
- const firstToken = readShellToken(firstStage, skipSpaces(firstStage, 0));
11621
- if (firstToken === null)
11622
- return false;
11623
- return firstToken.token === "grep" || firstToken.token === "rg";
11614
+ for (const statement of statements) {
11615
+ const firstStage = firstPipelineStage(statement);
11616
+ if (firstStage === null)
11617
+ continue;
11618
+ const firstToken = readShellToken(firstStage, skipSpaces(firstStage, 0));
11619
+ if (firstToken === null)
11620
+ continue;
11621
+ if (firstToken.token === "grep" || firstToken.token === "rg")
11622
+ return true;
11623
+ }
11624
+ return false;
11624
11625
  }
11625
11626
  function maybeAppendGrepSearchHint(output, command, aftSearchRegistered) {
11626
11627
  if (output === "")
11627
11628
  return output;
11628
- if (!commandLeadsWithCodeSearch(command))
11629
+ if (!commandInvokesCodeSearch(command))
11629
11630
  return output;
11630
11631
  if (output.includes(GREP_SEARCH_HINT_PREFIX))
11631
11632
  return output;
@@ -11634,21 +11635,78 @@ function maybeAppendGrepSearchHint(output, command, aftSearchRegistered) {
11634
11635
 
11635
11636
  ${hint}`;
11636
11637
  }
11637
- function peelLeadingCdAnd(command) {
11638
- const first = readShellToken(command, skipSpaces(command, 0));
11639
- if (first === null)
11640
- return null;
11641
- if (first.token !== "cd")
11642
- return command;
11643
- const dir = readShellToken(command, skipSpaces(command, first.end));
11644
- if (dir === null)
11638
+ function splitTopLevelStatements(command) {
11639
+ const statements = [];
11640
+ let start = 0;
11641
+ let quote = "none";
11642
+ let escaped = false;
11643
+ let inBacktick = false;
11644
+ let parenDepth = 0;
11645
+ for (let index = 0;index < command.length; index++) {
11646
+ const ch = command[index];
11647
+ if (escaped) {
11648
+ escaped = false;
11649
+ continue;
11650
+ }
11651
+ if (quote === "single") {
11652
+ if (ch === "'")
11653
+ quote = "none";
11654
+ continue;
11655
+ }
11656
+ if (quote === "double") {
11657
+ if (ch === "\\")
11658
+ escaped = true;
11659
+ else if (ch === '"')
11660
+ quote = "none";
11661
+ continue;
11662
+ }
11663
+ if (inBacktick) {
11664
+ if (ch === "`")
11665
+ inBacktick = false;
11666
+ continue;
11667
+ }
11668
+ if (ch === "\\") {
11669
+ escaped = true;
11670
+ continue;
11671
+ }
11672
+ if (ch === "'") {
11673
+ quote = "single";
11674
+ continue;
11675
+ }
11676
+ if (ch === '"') {
11677
+ quote = "double";
11678
+ continue;
11679
+ }
11680
+ if (ch === "`") {
11681
+ inBacktick = true;
11682
+ continue;
11683
+ }
11684
+ if (ch === "(") {
11685
+ parenDepth++;
11686
+ continue;
11687
+ }
11688
+ if (ch === ")") {
11689
+ if (parenDepth > 0)
11690
+ parenDepth--;
11691
+ continue;
11692
+ }
11693
+ if (parenDepth > 0)
11694
+ continue;
11695
+ const next = command[index + 1];
11696
+ if (ch === "&" && next === "&" || ch === "|" && next === "|") {
11697
+ statements.push(command.slice(start, index));
11698
+ index++;
11699
+ start = index + 1;
11700
+ } else if (ch === ";" || ch === `
11701
+ ` || ch === "&") {
11702
+ statements.push(command.slice(start, index));
11703
+ start = index + 1;
11704
+ }
11705
+ }
11706
+ if (quote !== "none" || inBacktick || escaped)
11645
11707
  return null;
11646
- if (!dir.token)
11647
- return command;
11648
- const afterDir = skipSpaces(command, dir.end);
11649
- if (!command.startsWith("&&", afterDir))
11650
- return command;
11651
- return command.slice(afterDir + 2).trim();
11708
+ statements.push(command.slice(start));
11709
+ return statements;
11652
11710
  }
11653
11711
  function firstPipelineStage(command) {
11654
11712
  let quote = "none";
@@ -35872,6 +35930,91 @@ var z8 = tool8.schema;
35872
35930
  function diagnosticsOnEditDefault(ctx) {
35873
35931
  return ctx.config.lsp?.diagnostics_on_edit ?? false;
35874
35932
  }
35933
+ async function readCurrentFileForPreview(filePath) {
35934
+ try {
35935
+ return await fs6.promises.readFile(filePath, "utf-8");
35936
+ } catch (error50) {
35937
+ if (error50 && typeof error50 === "object" && "code" in error50 && error50.code === "ENOENT") {
35938
+ return "";
35939
+ }
35940
+ throw error50;
35941
+ }
35942
+ }
35943
+ function previewDiffFromResponse(data, filePath) {
35944
+ const previewDiff = data.preview_diff;
35945
+ if (typeof previewDiff === "string")
35946
+ return previewDiff;
35947
+ const diff = data.diff;
35948
+ if (typeof diff?.before === "string" && typeof diff.after === "string") {
35949
+ return buildUnifiedDiff(filePath, diff.before, diff.after);
35950
+ }
35951
+ return "";
35952
+ }
35953
+ function virtualPatchContent(virtualFiles, filePath) {
35954
+ return virtualFiles.has(filePath) ? virtualFiles.get(filePath) ?? null : undefined;
35955
+ }
35956
+ async function readPatchPreviewContent(virtualFiles, filePath, action, patchPath) {
35957
+ const virtualContent = virtualPatchContent(virtualFiles, filePath);
35958
+ if (virtualContent !== undefined) {
35959
+ if (virtualContent === null) {
35960
+ throw new Error(`Failed to ${action} ${patchPath}: file not found: ${filePath}`);
35961
+ }
35962
+ return virtualContent;
35963
+ }
35964
+ try {
35965
+ return await fs6.promises.readFile(filePath, "utf-8");
35966
+ } catch (error50) {
35967
+ throw new Error(`Failed to ${action} ${patchPath}: ${formatError2(error50)}`);
35968
+ }
35969
+ }
35970
+ async function buildApplyPatchPreview(hunks, projectRoot) {
35971
+ const virtualFiles = new Map;
35972
+ const patches = [];
35973
+ let firstFilePath = "";
35974
+ for (const hunk of hunks) {
35975
+ const filePath = resolvePathFromProjectRoot(projectRoot, hunk.path);
35976
+ if (!firstFilePath)
35977
+ firstFilePath = filePath;
35978
+ switch (hunk.type) {
35979
+ case "add": {
35980
+ const virtualContent = virtualPatchContent(virtualFiles, filePath);
35981
+ const exists = virtualContent !== undefined ? virtualContent !== null : fs6.existsSync(filePath);
35982
+ if (exists) {
35983
+ throw new Error(`Failed to create ${hunk.path}: file already exists. Use *** Update File: to modify, or *** Delete File: first if you want to replace it entirely.`);
35984
+ }
35985
+ const after = hunk.contents.endsWith(`
35986
+ `) ? hunk.contents : `${hunk.contents}
35987
+ `;
35988
+ patches.push(buildUnifiedDiff(filePath, "", after));
35989
+ virtualFiles.set(filePath, after);
35990
+ break;
35991
+ }
35992
+ case "delete": {
35993
+ const before = await readPatchPreviewContent(virtualFiles, filePath, "delete", hunk.path);
35994
+ patches.push(buildUnifiedDiff(filePath, before, ""));
35995
+ virtualFiles.set(filePath, null);
35996
+ break;
35997
+ }
35998
+ case "update": {
35999
+ const before = await readPatchPreviewContent(virtualFiles, filePath, "update", hunk.path);
36000
+ let after;
36001
+ try {
36002
+ after = applyUpdateChunks(before, filePath, hunk.chunks);
36003
+ } catch (error50) {
36004
+ throw new Error(`Failed to update ${hunk.path}: ${formatError2(error50)}`);
36005
+ }
36006
+ const targetPath = hunk.move_path ? resolvePathFromProjectRoot(projectRoot, hunk.move_path) : filePath;
36007
+ patches.push(buildUnifiedDiff(targetPath, before, after));
36008
+ if (hunk.move_path)
36009
+ virtualFiles.set(filePath, null);
36010
+ virtualFiles.set(targetPath, after);
36011
+ break;
36012
+ }
36013
+ }
36014
+ }
36015
+ return { filepath: firstFilePath || projectRoot, diff: patches.join(`
36016
+ `) };
36017
+ }
35875
36018
  var READ_DESCRIPTION = `Read file contents or list directory entries.
35876
36019
 
35877
36020
  Use either startLine/endLine OR offset/limit to read a section of a file.
@@ -36016,16 +36159,18 @@ function createWriteTool(ctx, editToolName = "edit") {
36016
36159
  const filePath = resolvePathFromProjectRoot(projectRoot, file2);
36017
36160
  const relPath = path4.relative(projectRoot, filePath);
36018
36161
  {
36019
- const denial = await assertExternalDirectoryPermission(context, filePath);
36020
- if (denial)
36021
- return permissionDeniedResponse(denial);
36022
- }
36023
- await runAsk(context.ask({
36024
- permission: "edit",
36025
- patterns: [relPath],
36026
- always: ["*"],
36027
- metadata: { filepath: filePath }
36028
- }));
36162
+ const denial2 = await assertExternalDirectoryPermission(context, filePath);
36163
+ if (denial2)
36164
+ return permissionDeniedResponse(denial2);
36165
+ }
36166
+ const currentContent = await readCurrentFileForPreview(filePath);
36167
+ const previewDiff = buildUnifiedDiff(filePath, currentContent, content);
36168
+ const denial = await askEditPermission(context, [relPath], {
36169
+ filepath: filePath,
36170
+ diff: previewDiff
36171
+ });
36172
+ if (denial)
36173
+ return permissionDeniedResponse(denial);
36029
36174
  const data = await callBridge(ctx, context, "write", {
36030
36175
  file: filePath,
36031
36176
  content,
@@ -36165,16 +36310,10 @@ function createEditTool(ctx, writeToolName = "write") {
36165
36310
  const filePath = resolvePathFromProjectRoot(projectRoot, file2);
36166
36311
  const relPath = path4.relative(projectRoot, filePath);
36167
36312
  {
36168
- const denial = await assertExternalDirectoryPermission(context, filePath);
36169
- if (denial)
36170
- return permissionDeniedResponse(denial);
36313
+ const denial2 = await assertExternalDirectoryPermission(context, filePath);
36314
+ if (denial2)
36315
+ return permissionDeniedResponse(denial2);
36171
36316
  }
36172
- await runAsk(context.ask({
36173
- permission: "edit",
36174
- patterns: [relPath],
36175
- always: ["*"],
36176
- metadata: { filepath: filePath }
36177
- }));
36178
36317
  const params = { file: filePath };
36179
36318
  let command;
36180
36319
  if (typeof args.appendContent === "string") {
@@ -36217,6 +36356,21 @@ function createEditTool(ctx, writeToolName = "write") {
36217
36356
  const hint = typeof args.content === "string" ? ` To write the whole file, use the '${writeToolName}' tool. To edit existing content, provide 'oldString' (and optionally 'newString'), 'symbol' + 'content', or an 'edits' array.` : " Provide 'oldString' (+ optional 'newString'), 'symbol' + 'content', or an 'edits' array.";
36218
36357
  throw new Error(`edit: no edit mode resolved from arguments.${hint}`);
36219
36358
  }
36359
+ const previewData = await callBridge(ctx, context, command, {
36360
+ ...params,
36361
+ preview: true,
36362
+ include_diff_content: true
36363
+ });
36364
+ if (previewData.success === false) {
36365
+ throw new Error(previewData.message || "edit preview failed");
36366
+ }
36367
+ const previewDiff = previewDiffFromResponse(previewData, filePath);
36368
+ const denial = await askEditPermission(context, [relPath], {
36369
+ filepath: filePath,
36370
+ diff: previewDiff
36371
+ });
36372
+ if (denial)
36373
+ return permissionDeniedResponse(denial);
36220
36374
  params.diagnostics = diagnosticsOnEditDefault(ctx);
36221
36375
  params.include_diff_content = true;
36222
36376
  const data = await callBridge(ctx, context, command, params);
@@ -36380,17 +36534,18 @@ function createApplyPatchTool(ctx) {
36380
36534
  if (asked.has(filePath))
36381
36535
  continue;
36382
36536
  asked.add(filePath);
36383
- const denial = await assertExternalDirectoryPermission(context, filePath);
36384
- if (denial)
36385
- return permissionDeniedResponse(denial);
36537
+ const denial2 = await assertExternalDirectoryPermission(context, filePath);
36538
+ if (denial2)
36539
+ return permissionDeniedResponse(denial2);
36386
36540
  }
36387
36541
  }
36388
- await runAsk(context.ask({
36389
- permission: "edit",
36390
- patterns: relPaths,
36391
- always: ["*"],
36392
- metadata: {}
36393
- }));
36542
+ const preview2 = await buildApplyPatchPreview(hunks, projectRoot);
36543
+ const denial = await askEditPermission(context, relPaths, {
36544
+ filepath: preview2.filepath,
36545
+ diff: preview2.diff
36546
+ });
36547
+ if (denial)
36548
+ return permissionDeniedResponse(denial);
36394
36549
  const checkpointPaths = Array.from(affectedAbs).filter((abs) => !newlyCreatedAbs.has(abs));
36395
36550
  const checkpointName = `apply_patch_${Date.now()}`;
36396
36551
  let checkpointCreated = false;
@@ -36762,16 +36917,18 @@ function aftPrefixedTools(ctx) {
36762
36917
  const filePath = resolvePathFromProjectRoot(projectRoot, file2);
36763
36918
  const relPath = path4.relative(projectRoot, filePath);
36764
36919
  {
36765
- const denial = await assertExternalDirectoryPermission(context, filePath);
36766
- if (denial)
36767
- return permissionDeniedResponse(denial);
36920
+ const denial2 = await assertExternalDirectoryPermission(context, filePath);
36921
+ if (denial2)
36922
+ return permissionDeniedResponse(denial2);
36768
36923
  }
36769
- await runAsk(context.ask({
36770
- permission: "edit",
36771
- patterns: [relPath],
36772
- always: ["*"],
36773
- metadata: { filepath: filePath }
36774
- }));
36924
+ const currentContent = await readCurrentFileForPreview(filePath);
36925
+ const previewDiff = buildUnifiedDiff(filePath, currentContent, normalizedArgs.content);
36926
+ const denial = await askEditPermission(context, [relPath], {
36927
+ filepath: filePath,
36928
+ diff: previewDiff
36929
+ });
36930
+ if (denial)
36931
+ return permissionDeniedResponse(denial);
36775
36932
  const writeParams = {
36776
36933
  file: filePath,
36777
36934
  content: normalizedArgs.content,
@@ -38218,12 +38375,12 @@ var PLUGIN_VERSION = (() => {
38218
38375
  return "0.0.0";
38219
38376
  }
38220
38377
  })();
38221
- var ANNOUNCEMENT_VERSION = "0.38.0";
38378
+ var ANNOUNCEMENT_VERSION = "0.39.0";
38222
38379
  var ANNOUNCEMENT_FEATURES = [
38223
- "Code Health you can trust: dead code and unused exports are back in the sidebar and status displays, rebuilt on a real TS/JS module-graph engine (oxc) barrel re-exports, entry points, and dynamic imports resolve correctly now.",
38224
- "Tools stay responsive during builds: filesystem events are processed off the request thread, so a cargo/webpack compile no longer queues your tool calls behind an event flood.",
38225
- "Pascal support in outline, zoom, and the AST tools.",
38226
- "Sidebar reliability: the status panel reconnects after heavy host load instead of going dark, and transient environment errors (schema fetch timeouts) no longer count as code errors."
38380
+ "Accurate Rust dead code: constructors and associated functions reached through receiver-type inference are no longer mis-reported as dead.",
38381
+ "Code Health scans reparse only the file you changed instead of the whole project on every edit — a single-file edit dropped from ~3s of scan work to ~130ms on this repo, byte-identical results.",
38382
+ "R language support in outline, zoom, and the AST tools.",
38383
+ "Edit approval prompts now show the pending diff instead of 'No diff provided', so you can review a change before approving it."
38227
38384
  ];
38228
38385
  var ANNOUNCEMENT_FOOTER = "Join us on Discord: https://discord.gg/DSa65w8wuf";
38229
38386
  var plugin = async (input) => initializePluginForDirectory(input);
@@ -1 +1 @@
1
- {"version":3,"file":"hoisted.d.ts","sourceRoot":"","sources":["../../src/tools/hoisted.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAKH,OAAO,KAAK,EAAE,cAAc,EAAc,MAAM,qBAAqB,CAAC;AAItE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAwEjD,wEAAwE;AACxE,eAAO,MAAM,wBAAwB,GAAI,IAAI,MAAM,EAAE,QAAQ,MAAM,EAAE,OAAO,MAAM,KAAG,MAChD,CAAC;AA4RtC;;GAEG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,aAAa,GAAG,cAAc,CAwJjE;AA+mCD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CA0B/E;AAMD;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CA+FnF"}
1
+ {"version":3,"file":"hoisted.d.ts","sourceRoot":"","sources":["../../src/tools/hoisted.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAKH,OAAO,KAAK,EAAE,cAAc,EAAc,MAAM,qBAAqB,CAAC;AAItE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAyEjD,wEAAwE;AACxE,eAAO,MAAM,wBAAwB,GAAI,IAAI,MAAM,EAAE,QAAQ,MAAM,EAAE,OAAO,MAAM,KAAG,MAChD,CAAC;AA8YtC;;GAEG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,aAAa,GAAG,cAAc,CAwJjE;AAmnCD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CA0B/E;AAMD;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAkGnF"}
package/dist/tui.js CHANGED
@@ -1,7 +1,7 @@
1
1
  // src/tui/index.tsx
2
2
  import { createMemo as createMemo2, createSignal as createSignal2, onCleanup as onCleanup2 } from "solid-js";
3
3
  // package.json
4
- var version = "0.38.0";
4
+ var version = "0.39.0";
5
5
 
6
6
  // src/shared/rpc-client.ts
7
7
  import { existsSync, readdirSync, readFileSync, unlinkSync } from "node:fs";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cortexkit/aft-opencode",
3
- "version": "0.38.0",
3
+ "version": "0.39.0",
4
4
  "type": "module",
5
5
  "description": "OpenCode plugin for Agent File Tools (AFT) — tree-sitter and lsp powered code analysis",
6
6
  "main": "dist/index.js",
@@ -30,19 +30,19 @@
30
30
  },
31
31
  "dependencies": {
32
32
  "@clack/prompts": "^1.2.0",
33
- "@cortexkit/aft-bridge": "0.38.0",
33
+ "@cortexkit/aft-bridge": "0.39.0",
34
34
  "@xterm/headless": "^5.5.0",
35
35
  "comment-json": "^4.6.2",
36
36
  "undici": "^7.25.0",
37
37
  "zod": "^4.1.8"
38
38
  },
39
39
  "optionalDependencies": {
40
- "@cortexkit/aft-darwin-arm64": "0.38.0",
41
- "@cortexkit/aft-darwin-x64": "0.38.0",
42
- "@cortexkit/aft-linux-arm64": "0.38.0",
43
- "@cortexkit/aft-linux-x64": "0.38.0",
44
- "@cortexkit/aft-win32-arm64": "0.38.0",
45
- "@cortexkit/aft-win32-x64": "0.38.0"
40
+ "@cortexkit/aft-darwin-arm64": "0.39.0",
41
+ "@cortexkit/aft-darwin-x64": "0.39.0",
42
+ "@cortexkit/aft-linux-arm64": "0.39.0",
43
+ "@cortexkit/aft-linux-x64": "0.39.0",
44
+ "@cortexkit/aft-win32-arm64": "0.39.0",
45
+ "@cortexkit/aft-win32-x64": "0.39.0"
46
46
  },
47
47
  "devDependencies": {
48
48
  "@opencode-ai/plugin": "^1.15.11",