@fuzzle/opencode-accountant 0.4.1-next.1 → 0.4.2-next.1

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.
Files changed (2) hide show
  1. package/dist/index.js +228 -677
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1548,18 +1548,18 @@ var require_brace_expansion = __commonJS((exports, module) => {
1548
1548
 
1549
1549
  // node_modules/convert-csv-to-json/src/util/fileUtils.js
1550
1550
  var require_fileUtils = __commonJS((exports, module) => {
1551
- var fs9 = __require("fs");
1551
+ var fs8 = __require("fs");
1552
1552
 
1553
1553
  class FileUtils {
1554
1554
  readFile(fileInputName, encoding) {
1555
- return fs9.readFileSync(fileInputName, encoding).toString();
1555
+ return fs8.readFileSync(fileInputName, encoding).toString();
1556
1556
  }
1557
1557
  readFileAsync(fileInputName, encoding = "utf8") {
1558
- if (fs9.promises && typeof fs9.promises.readFile === "function") {
1559
- return fs9.promises.readFile(fileInputName, encoding).then((buf) => buf.toString());
1558
+ if (fs8.promises && typeof fs8.promises.readFile === "function") {
1559
+ return fs8.promises.readFile(fileInputName, encoding).then((buf) => buf.toString());
1560
1560
  }
1561
1561
  return new Promise((resolve2, reject) => {
1562
- fs9.readFile(fileInputName, encoding, (err, data) => {
1562
+ fs8.readFile(fileInputName, encoding, (err, data) => {
1563
1563
  if (err) {
1564
1564
  reject(err);
1565
1565
  return;
@@ -1569,7 +1569,7 @@ var require_fileUtils = __commonJS((exports, module) => {
1569
1569
  });
1570
1570
  }
1571
1571
  writeFile(json3, fileOutputName) {
1572
- fs9.writeFile(fileOutputName, json3, function(err) {
1572
+ fs8.writeFile(fileOutputName, json3, function(err) {
1573
1573
  if (err) {
1574
1574
  throw err;
1575
1575
  } else {
@@ -1578,11 +1578,11 @@ var require_fileUtils = __commonJS((exports, module) => {
1578
1578
  });
1579
1579
  }
1580
1580
  writeFileAsync(json3, fileOutputName) {
1581
- if (fs9.promises && typeof fs9.promises.writeFile === "function") {
1582
- return fs9.promises.writeFile(fileOutputName, json3);
1581
+ if (fs8.promises && typeof fs8.promises.writeFile === "function") {
1582
+ return fs8.promises.writeFile(fileOutputName, json3);
1583
1583
  }
1584
1584
  return new Promise((resolve2, reject) => {
1585
- fs9.writeFile(fileOutputName, json3, (err) => {
1585
+ fs8.writeFile(fileOutputName, json3, (err) => {
1586
1586
  if (err)
1587
1587
  return reject(err);
1588
1588
  resolve2();
@@ -2147,7 +2147,7 @@ var require_convert_csv_to_json = __commonJS((exports) => {
2147
2147
  });
2148
2148
 
2149
2149
  // src/index.ts
2150
- import { dirname as dirname6, join as join13 } from "path";
2150
+ import { dirname as dirname5, join as join12 } from "path";
2151
2151
  import { fileURLToPath as fileURLToPath3 } from "url";
2152
2152
 
2153
2153
  // src/utils/agentLoader.ts
@@ -17479,8 +17479,8 @@ var fetch_currency_prices_default = tool({
17479
17479
  }
17480
17480
  });
17481
17481
  // src/tools/classify-statements.ts
17482
- import * as fs6 from "fs";
17483
- import * as path7 from "path";
17482
+ import * as fs5 from "fs";
17483
+ import * as path6 from "path";
17484
17484
 
17485
17485
  // src/utils/importConfig.ts
17486
17486
  import * as fs3 from "fs";
@@ -17751,263 +17751,23 @@ function detectProvider(filename, content, config2) {
17751
17751
  return null;
17752
17752
  }
17753
17753
 
17754
- // src/utils/worktreeManager.ts
17755
- import { spawnSync } from "child_process";
17756
-
17757
- // node_modules/uuid/dist-node/stringify.js
17758
- var byteToHex = [];
17759
- for (let i2 = 0;i2 < 256; ++i2) {
17760
- byteToHex.push((i2 + 256).toString(16).slice(1));
17761
- }
17762
- function unsafeStringify(arr, offset = 0) {
17763
- return (byteToHex[arr[offset + 0]] + byteToHex[arr[offset + 1]] + byteToHex[arr[offset + 2]] + byteToHex[arr[offset + 3]] + "-" + byteToHex[arr[offset + 4]] + byteToHex[arr[offset + 5]] + "-" + byteToHex[arr[offset + 6]] + byteToHex[arr[offset + 7]] + "-" + byteToHex[arr[offset + 8]] + byteToHex[arr[offset + 9]] + "-" + byteToHex[arr[offset + 10]] + byteToHex[arr[offset + 11]] + byteToHex[arr[offset + 12]] + byteToHex[arr[offset + 13]] + byteToHex[arr[offset + 14]] + byteToHex[arr[offset + 15]]).toLowerCase();
17764
- }
17765
-
17766
- // node_modules/uuid/dist-node/rng.js
17767
- import { randomFillSync } from "crypto";
17768
- var rnds8Pool = new Uint8Array(256);
17769
- var poolPtr = rnds8Pool.length;
17770
- function rng() {
17771
- if (poolPtr > rnds8Pool.length - 16) {
17772
- randomFillSync(rnds8Pool);
17773
- poolPtr = 0;
17774
- }
17775
- return rnds8Pool.slice(poolPtr, poolPtr += 16);
17776
- }
17777
-
17778
- // node_modules/uuid/dist-node/native.js
17779
- import { randomUUID } from "crypto";
17780
- var native_default = { randomUUID };
17781
-
17782
- // node_modules/uuid/dist-node/v4.js
17783
- function _v4(options, buf, offset) {
17784
- options = options || {};
17785
- const rnds = options.random ?? options.rng?.() ?? rng();
17786
- if (rnds.length < 16) {
17787
- throw new Error("Random bytes length must be >= 16");
17788
- }
17789
- rnds[6] = rnds[6] & 15 | 64;
17790
- rnds[8] = rnds[8] & 63 | 128;
17791
- if (buf) {
17792
- offset = offset || 0;
17793
- if (offset < 0 || offset + 16 > buf.length) {
17794
- throw new RangeError(`UUID byte range ${offset}:${offset + 15} is out of buffer bounds`);
17795
- }
17796
- for (let i2 = 0;i2 < 16; ++i2) {
17797
- buf[offset + i2] = rnds[i2];
17798
- }
17799
- return buf;
17800
- }
17801
- return unsafeStringify(rnds);
17802
- }
17803
- function v4(options, buf, offset) {
17804
- if (native_default.randomUUID && !buf && !options) {
17805
- return native_default.randomUUID();
17806
- }
17807
- return _v4(options, buf, offset);
17808
- }
17809
- var v4_default = v4;
17810
- // src/utils/worktreeManager.ts
17754
+ // src/utils/fileUtils.ts
17811
17755
  import * as fs4 from "fs";
17812
17756
  import * as path5 from "path";
17813
- function execGit(args, cwd) {
17814
- const result = spawnSync("git", args, { cwd, encoding: "utf-8" });
17815
- if (result.status !== 0) {
17816
- throw new Error(result.stderr || result.stdout || `git ${args[0]} failed`);
17817
- }
17818
- return (result.stdout || "").trim();
17819
- }
17820
- function execGitSafe(args, cwd) {
17821
- const result = spawnSync("git", args, { cwd, encoding: "utf-8" });
17822
- if (result.status !== 0) {
17823
- return { success: false, output: result.stderr || result.stdout || `git ${args[0]} failed` };
17824
- }
17825
- return { success: true, output: (result.stdout || "").trim() };
17826
- }
17827
- function copyIncomingFiles(mainRepoPath, worktreePath) {
17828
- const sourceDir = path5.join(mainRepoPath, "import/incoming");
17829
- const targetDir = path5.join(worktreePath, "import/incoming");
17830
- if (!fs4.existsSync(sourceDir)) {
17831
- return;
17832
- }
17833
- fs4.mkdirSync(targetDir, { recursive: true });
17834
- const entries = fs4.readdirSync(sourceDir, { withFileTypes: true });
17835
- let copiedCount = 0;
17836
- for (const entry of entries) {
17837
- if (entry.isFile() && !entry.name.startsWith(".")) {
17838
- const srcPath = path5.join(sourceDir, entry.name);
17839
- const destPath = path5.join(targetDir, entry.name);
17840
- fs4.copyFileSync(srcPath, destPath);
17841
- copiedCount++;
17842
- }
17843
- }
17844
- if (copiedCount > 0) {
17845
- console.log(`[INFO] Copied ${copiedCount} file(s) from import/incoming/ to worktree`);
17846
- }
17847
- }
17848
- function createImportWorktree(mainRepoPath, options = {}) {
17849
- const baseDir = options.baseDir ?? "/tmp";
17850
- const uuid3 = v4_default();
17851
- const branch = `import-${uuid3}`;
17852
- const worktreePath = path5.join(baseDir, `import-worktree-${uuid3}`);
17853
- try {
17854
- execGit(["rev-parse", "--git-dir"], mainRepoPath);
17855
- } catch {
17856
- throw new Error(`Not a git repository: ${mainRepoPath}`);
17857
- }
17858
- execGit(["branch", branch], mainRepoPath);
17859
- try {
17860
- execGit(["worktree", "add", worktreePath, branch], mainRepoPath);
17861
- } catch (error45) {
17862
- execGitSafe(["branch", "-D", branch], mainRepoPath);
17863
- throw error45;
17864
- }
17865
- copyIncomingFiles(mainRepoPath, worktreePath);
17866
- return {
17867
- path: worktreePath,
17868
- branch,
17869
- uuid: uuid3,
17870
- mainRepoPath
17871
- };
17872
- }
17873
- function mergeWorktree(context, commitMessage) {
17874
- const status = execGit(["status", "--porcelain"], context.path);
17875
- if (status.length > 0) {
17876
- execGit(["add", "-A"], context.path);
17877
- execGit(["commit", "-m", commitMessage], context.path);
17878
- }
17879
- const currentBranch = execGit(["rev-parse", "--abbrev-ref", "HEAD"], context.mainRepoPath);
17880
- execGit(["merge", "--no-ff", context.branch, "-m", commitMessage], context.mainRepoPath);
17881
- if (currentBranch !== "main" && currentBranch !== "master") {
17882
- execGit(["checkout", currentBranch], context.mainRepoPath);
17883
- }
17884
- }
17885
- function removeWorktree(context, force = false) {
17886
- const forceFlag = force ? "--force" : "";
17887
- const args = ["worktree", "remove", context.path];
17888
- if (forceFlag) {
17889
- args.push(forceFlag);
17890
- }
17891
- const removeResult = execGitSafe(args, context.mainRepoPath);
17892
- if (!removeResult.success) {
17893
- if (!fs4.existsSync(context.path)) {} else {
17894
- return { success: false, error: `Failed to remove worktree: ${removeResult.output}` };
17895
- }
17896
- }
17897
- execGitSafe(["worktree", "prune"], context.mainRepoPath);
17898
- const branchResult = execGitSafe(["branch", "-D", context.branch], context.mainRepoPath);
17899
- if (!branchResult.success) {
17900
- if (!branchResult.output.includes("not found")) {
17901
- return { success: false, error: `Failed to delete branch: ${branchResult.output}` };
17902
- }
17903
- }
17904
- return { success: true };
17905
- }
17906
- function isInWorktree(directory) {
17907
- try {
17908
- const gitDir = execGit(["rev-parse", "--git-dir"], directory);
17909
- return gitDir.includes(".git/worktrees/");
17910
- } catch {
17911
- return false;
17912
- }
17913
- }
17914
- async function withWorktree(directory, operation, options) {
17915
- let createdWorktree = null;
17916
- let operationSucceeded = false;
17917
- const logger = options?.logger;
17918
- const keepOnError = options?.keepOnError ?? true;
17919
- try {
17920
- logger?.logStep("Create Worktree", "start");
17921
- createdWorktree = createImportWorktree(directory);
17922
- logger?.logStep("Create Worktree", "success", `Path: ${createdWorktree.path}`);
17923
- logger?.info(`Branch: ${createdWorktree.branch}`);
17924
- logger?.info(`UUID: ${createdWorktree.uuid}`);
17925
- const result = await operation(createdWorktree);
17926
- operationSucceeded = true;
17927
- return result;
17928
- } finally {
17929
- if (createdWorktree) {
17930
- if (operationSucceeded) {
17931
- logger?.logStep("Cleanup Worktree", "start");
17932
- removeWorktree(createdWorktree, true);
17933
- logger?.logStep("Cleanup Worktree", "success", "Worktree removed");
17934
- } else if (!keepOnError) {
17935
- logger?.warn("Operation failed, but keepOnError=false, removing worktree");
17936
- removeWorktree(createdWorktree, true);
17937
- } else {
17938
- logger?.warn("Operation failed, worktree preserved for debugging");
17939
- logger?.info(`Worktree path: ${createdWorktree.path}`);
17940
- logger?.info(`To clean up manually: git worktree remove ${createdWorktree.path}`);
17941
- logger?.info(`To list all worktrees: git worktree list`);
17942
- }
17943
- }
17944
- }
17945
- }
17946
-
17947
- // src/utils/fileUtils.ts
17948
- import * as fs5 from "fs";
17949
- import * as path6 from "path";
17950
17757
  function findCSVFiles(importsDir) {
17951
- if (!fs5.existsSync(importsDir)) {
17758
+ if (!fs4.existsSync(importsDir)) {
17952
17759
  return [];
17953
17760
  }
17954
- return fs5.readdirSync(importsDir).filter((file2) => file2.toLowerCase().endsWith(".csv")).filter((file2) => {
17955
- const fullPath = path6.join(importsDir, file2);
17956
- return fs5.statSync(fullPath).isFile();
17761
+ return fs4.readdirSync(importsDir).filter((file2) => file2.toLowerCase().endsWith(".csv")).filter((file2) => {
17762
+ const fullPath = path5.join(importsDir, file2);
17763
+ return fs4.statSync(fullPath).isFile();
17957
17764
  });
17958
17765
  }
17959
17766
  function ensureDirectory(dirPath) {
17960
- if (!fs5.existsSync(dirPath)) {
17961
- fs5.mkdirSync(dirPath, { recursive: true });
17767
+ if (!fs4.existsSync(dirPath)) {
17768
+ fs4.mkdirSync(dirPath, { recursive: true });
17962
17769
  }
17963
17770
  }
17964
- function syncCSVFilesToWorktree(mainRepoPath, worktreePath, importDir) {
17965
- const result = {
17966
- synced: [],
17967
- errors: []
17968
- };
17969
- const mainImportDir = path6.join(mainRepoPath, importDir);
17970
- const worktreeImportDir = path6.join(worktreePath, importDir);
17971
- const csvFiles = findCSVFiles(mainImportDir);
17972
- if (csvFiles.length === 0) {
17973
- return result;
17974
- }
17975
- ensureDirectory(worktreeImportDir);
17976
- for (const file2 of csvFiles) {
17977
- try {
17978
- const sourcePath = path6.join(mainImportDir, file2);
17979
- const destPath = path6.join(worktreeImportDir, file2);
17980
- fs5.copyFileSync(sourcePath, destPath);
17981
- result.synced.push(file2);
17982
- } catch (error45) {
17983
- const errorMsg = error45 instanceof Error ? error45.message : String(error45);
17984
- result.errors.push({ file: file2, error: errorMsg });
17985
- }
17986
- }
17987
- return result;
17988
- }
17989
- function cleanupProcessedCSVFiles(mainRepoPath, importDir) {
17990
- const result = {
17991
- deleted: [],
17992
- errors: []
17993
- };
17994
- const mainImportDir = path6.join(mainRepoPath, importDir);
17995
- const csvFiles = findCSVFiles(mainImportDir);
17996
- if (csvFiles.length === 0) {
17997
- return result;
17998
- }
17999
- for (const file2 of csvFiles) {
18000
- try {
18001
- const filePath = path6.join(mainImportDir, file2);
18002
- fs5.unlinkSync(filePath);
18003
- result.deleted.push(file2);
18004
- } catch (error45) {
18005
- const errorMsg = error45 instanceof Error ? error45.message : String(error45);
18006
- result.errors.push({ file: file2, error: errorMsg });
18007
- }
18008
- }
18009
- return result;
18010
- }
18011
17771
 
18012
17772
  // src/tools/classify-statements.ts
18013
17773
  function buildSuccessResult2(classified, unrecognized, message) {
@@ -18046,20 +17806,20 @@ function planMoves(csvFiles, importsDir, pendingDir, unrecognizedDir, config2) {
18046
17806
  const plannedMoves = [];
18047
17807
  const collisions = [];
18048
17808
  for (const filename of csvFiles) {
18049
- const sourcePath = path7.join(importsDir, filename);
18050
- const content = fs6.readFileSync(sourcePath, "utf-8");
17809
+ const sourcePath = path6.join(importsDir, filename);
17810
+ const content = fs5.readFileSync(sourcePath, "utf-8");
18051
17811
  const detection = detectProvider(filename, content, config2);
18052
17812
  let targetPath;
18053
17813
  let targetFilename;
18054
17814
  if (detection) {
18055
17815
  targetFilename = detection.outputFilename || filename;
18056
- const targetDir = path7.join(pendingDir, detection.provider, detection.currency);
18057
- targetPath = path7.join(targetDir, targetFilename);
17816
+ const targetDir = path6.join(pendingDir, detection.provider, detection.currency);
17817
+ targetPath = path6.join(targetDir, targetFilename);
18058
17818
  } else {
18059
17819
  targetFilename = filename;
18060
- targetPath = path7.join(unrecognizedDir, filename);
17820
+ targetPath = path6.join(unrecognizedDir, filename);
18061
17821
  }
18062
- if (fs6.existsSync(targetPath)) {
17822
+ if (fs5.existsSync(targetPath)) {
18063
17823
  collisions.push({
18064
17824
  filename,
18065
17825
  existingPath: targetPath
@@ -18080,28 +17840,28 @@ function executeMoves(plannedMoves, config2, unrecognizedDir) {
18080
17840
  const unrecognized = [];
18081
17841
  for (const move of plannedMoves) {
18082
17842
  if (move.detection) {
18083
- const targetDir = path7.dirname(move.targetPath);
17843
+ const targetDir = path6.dirname(move.targetPath);
18084
17844
  ensureDirectory(targetDir);
18085
- fs6.renameSync(move.sourcePath, move.targetPath);
17845
+ fs5.renameSync(move.sourcePath, move.targetPath);
18086
17846
  classified.push({
18087
17847
  filename: move.targetFilename,
18088
17848
  originalFilename: move.detection.outputFilename ? move.filename : undefined,
18089
17849
  provider: move.detection.provider,
18090
17850
  currency: move.detection.currency,
18091
- targetPath: path7.join(config2.paths.pending, move.detection.provider, move.detection.currency, move.targetFilename)
17851
+ targetPath: path6.join(config2.paths.pending, move.detection.provider, move.detection.currency, move.targetFilename)
18092
17852
  });
18093
17853
  } else {
18094
17854
  ensureDirectory(unrecognizedDir);
18095
- fs6.renameSync(move.sourcePath, move.targetPath);
17855
+ fs5.renameSync(move.sourcePath, move.targetPath);
18096
17856
  unrecognized.push({
18097
17857
  filename: move.filename,
18098
- targetPath: path7.join(config2.paths.unrecognized, move.filename)
17858
+ targetPath: path6.join(config2.paths.unrecognized, move.filename)
18099
17859
  });
18100
17860
  }
18101
17861
  }
18102
17862
  return { classified, unrecognized };
18103
17863
  }
18104
- async function classifyStatements(directory, agent, configLoader = loadImportConfig, worktreeChecker = isInWorktree) {
17864
+ async function classifyStatements(directory, agent, configLoader = loadImportConfig) {
18105
17865
  const restrictionError = checkAccountantAgent(agent, "classify statements", {
18106
17866
  classified: [],
18107
17867
  unrecognized: []
@@ -18109,9 +17869,6 @@ async function classifyStatements(directory, agent, configLoader = loadImportCon
18109
17869
  if (restrictionError) {
18110
17870
  return restrictionError;
18111
17871
  }
18112
- if (!worktreeChecker(directory)) {
18113
- return buildErrorResult2("classify-statements must be run inside an import worktree", "Use import-pipeline tool to orchestrate the full workflow");
18114
- }
18115
17872
  let config2;
18116
17873
  try {
18117
17874
  config2 = configLoader(directory);
@@ -18119,9 +17876,9 @@ async function classifyStatements(directory, agent, configLoader = loadImportCon
18119
17876
  const errorMessage = err instanceof Error ? err.message : String(err);
18120
17877
  return buildErrorResult2(errorMessage);
18121
17878
  }
18122
- const importsDir = path7.join(directory, config2.paths.import);
18123
- const pendingDir = path7.join(directory, config2.paths.pending);
18124
- const unrecognizedDir = path7.join(directory, config2.paths.unrecognized);
17879
+ const importsDir = path6.join(directory, config2.paths.import);
17880
+ const pendingDir = path6.join(directory, config2.paths.pending);
17881
+ const unrecognizedDir = path6.join(directory, config2.paths.unrecognized);
18125
17882
  const csvFiles = findCSVFiles(importsDir);
18126
17883
  if (csvFiles.length === 0) {
18127
17884
  return buildSuccessResult2([], [], `No CSV files found in ${config2.paths.import}`);
@@ -18142,8 +17899,8 @@ var classify_statements_default = tool({
18142
17899
  }
18143
17900
  });
18144
17901
  // src/tools/import-statements.ts
18145
- import * as fs10 from "fs";
18146
- import * as path10 from "path";
17902
+ import * as fs9 from "fs";
17903
+ import * as path9 from "path";
18147
17904
 
18148
17905
  // node_modules/minimatch/dist/esm/index.js
18149
17906
  var import_brace_expansion = __toESM(require_brace_expansion(), 1);
@@ -18736,11 +18493,11 @@ var qmarksTestNoExtDot = ([$0]) => {
18736
18493
  return (f) => f.length === len && f !== "." && f !== "..";
18737
18494
  };
18738
18495
  var defaultPlatform = typeof process === "object" && process ? typeof process.env === "object" && process.env && process.env.__MINIMATCH_TESTING_PLATFORM__ || process.platform : "posix";
18739
- var path8 = {
18496
+ var path7 = {
18740
18497
  win32: { sep: "\\" },
18741
18498
  posix: { sep: "/" }
18742
18499
  };
18743
- var sep = defaultPlatform === "win32" ? path8.win32.sep : path8.posix.sep;
18500
+ var sep = defaultPlatform === "win32" ? path7.win32.sep : path7.posix.sep;
18744
18501
  minimatch.sep = sep;
18745
18502
  var GLOBSTAR = Symbol("globstar **");
18746
18503
  minimatch.GLOBSTAR = GLOBSTAR;
@@ -20474,7 +20231,7 @@ class LRUCache {
20474
20231
  // node_modules/path-scurry/dist/esm/index.js
20475
20232
  import { posix, win32 } from "path";
20476
20233
  import { fileURLToPath } from "url";
20477
- import { lstatSync, readdir as readdirCB, readdirSync as readdirSync4, readlinkSync, realpathSync as rps } from "fs";
20234
+ import { lstatSync, readdir as readdirCB, readdirSync as readdirSync3, readlinkSync, realpathSync as rps } from "fs";
20478
20235
  import * as actualFS from "fs";
20479
20236
  import { lstat, readdir, readlink, realpath } from "fs/promises";
20480
20237
 
@@ -21146,7 +20903,7 @@ var realpathSync = rps.native;
21146
20903
  var defaultFS = {
21147
20904
  lstatSync,
21148
20905
  readdir: readdirCB,
21149
- readdirSync: readdirSync4,
20906
+ readdirSync: readdirSync3,
21150
20907
  readlinkSync,
21151
20908
  realpathSync,
21152
20909
  promises: {
@@ -21345,12 +21102,12 @@ class PathBase {
21345
21102
  childrenCache() {
21346
21103
  return this.#children;
21347
21104
  }
21348
- resolve(path9) {
21349
- if (!path9) {
21105
+ resolve(path8) {
21106
+ if (!path8) {
21350
21107
  return this;
21351
21108
  }
21352
- const rootPath = this.getRootString(path9);
21353
- const dir = path9.substring(rootPath.length);
21109
+ const rootPath = this.getRootString(path8);
21110
+ const dir = path8.substring(rootPath.length);
21354
21111
  const dirParts = dir.split(this.splitSep);
21355
21112
  const result = rootPath ? this.getRoot(rootPath).#resolveParts(dirParts) : this.#resolveParts(dirParts);
21356
21113
  return result;
@@ -21879,8 +21636,8 @@ class PathWin32 extends PathBase {
21879
21636
  newChild(name, type2 = UNKNOWN, opts = {}) {
21880
21637
  return new PathWin32(name, type2, this.root, this.roots, this.nocase, this.childrenCache(), opts);
21881
21638
  }
21882
- getRootString(path9) {
21883
- return win32.parse(path9).root;
21639
+ getRootString(path8) {
21640
+ return win32.parse(path8).root;
21884
21641
  }
21885
21642
  getRoot(rootPath) {
21886
21643
  rootPath = uncToDrive(rootPath.toUpperCase());
@@ -21906,8 +21663,8 @@ class PathPosix extends PathBase {
21906
21663
  constructor(name, type2 = UNKNOWN, root, roots, nocase, children, opts) {
21907
21664
  super(name, type2, root, roots, nocase, children, opts);
21908
21665
  }
21909
- getRootString(path9) {
21910
- return path9.startsWith("/") ? "/" : "";
21666
+ getRootString(path8) {
21667
+ return path8.startsWith("/") ? "/" : "";
21911
21668
  }
21912
21669
  getRoot(_rootPath) {
21913
21670
  return this.root;
@@ -21927,8 +21684,8 @@ class PathScurryBase {
21927
21684
  #children;
21928
21685
  nocase;
21929
21686
  #fs;
21930
- constructor(cwd = process.cwd(), pathImpl, sep2, { nocase, childrenCacheSize = 16 * 1024, fs: fs7 = defaultFS } = {}) {
21931
- this.#fs = fsFromOption(fs7);
21687
+ constructor(cwd = process.cwd(), pathImpl, sep2, { nocase, childrenCacheSize = 16 * 1024, fs: fs6 = defaultFS } = {}) {
21688
+ this.#fs = fsFromOption(fs6);
21932
21689
  if (cwd instanceof URL || cwd.startsWith("file://")) {
21933
21690
  cwd = fileURLToPath(cwd);
21934
21691
  }
@@ -21964,11 +21721,11 @@ class PathScurryBase {
21964
21721
  }
21965
21722
  this.cwd = prev;
21966
21723
  }
21967
- depth(path9 = this.cwd) {
21968
- if (typeof path9 === "string") {
21969
- path9 = this.cwd.resolve(path9);
21724
+ depth(path8 = this.cwd) {
21725
+ if (typeof path8 === "string") {
21726
+ path8 = this.cwd.resolve(path8);
21970
21727
  }
21971
- return path9.depth();
21728
+ return path8.depth();
21972
21729
  }
21973
21730
  childrenCache() {
21974
21731
  return this.#children;
@@ -22384,9 +22141,9 @@ class PathScurryBase {
22384
22141
  process2();
22385
22142
  return results;
22386
22143
  }
22387
- chdir(path9 = this.cwd) {
22144
+ chdir(path8 = this.cwd) {
22388
22145
  const oldCwd = this.cwd;
22389
- this.cwd = typeof path9 === "string" ? this.cwd.resolve(path9) : path9;
22146
+ this.cwd = typeof path8 === "string" ? this.cwd.resolve(path8) : path8;
22390
22147
  this.cwd[setAsCwd](oldCwd);
22391
22148
  }
22392
22149
  }
@@ -22404,8 +22161,8 @@ class PathScurryWin32 extends PathScurryBase {
22404
22161
  parseRootPath(dir) {
22405
22162
  return win32.parse(dir).root.toUpperCase();
22406
22163
  }
22407
- newRoot(fs7) {
22408
- return new PathWin32(this.rootPath, IFDIR, undefined, this.roots, this.nocase, this.childrenCache(), { fs: fs7 });
22164
+ newRoot(fs6) {
22165
+ return new PathWin32(this.rootPath, IFDIR, undefined, this.roots, this.nocase, this.childrenCache(), { fs: fs6 });
22409
22166
  }
22410
22167
  isAbsolute(p) {
22411
22168
  return p.startsWith("/") || p.startsWith("\\") || /^[a-z]:(\/|\\)/i.test(p);
@@ -22422,8 +22179,8 @@ class PathScurryPosix extends PathScurryBase {
22422
22179
  parseRootPath(_dir) {
22423
22180
  return "/";
22424
22181
  }
22425
- newRoot(fs7) {
22426
- return new PathPosix(this.rootPath, IFDIR, undefined, this.roots, this.nocase, this.childrenCache(), { fs: fs7 });
22182
+ newRoot(fs6) {
22183
+ return new PathPosix(this.rootPath, IFDIR, undefined, this.roots, this.nocase, this.childrenCache(), { fs: fs6 });
22427
22184
  }
22428
22185
  isAbsolute(p) {
22429
22186
  return p.startsWith("/");
@@ -22675,8 +22432,8 @@ class MatchRecord {
22675
22432
  this.store.set(target, current === undefined ? n : n & current);
22676
22433
  }
22677
22434
  entries() {
22678
- return [...this.store.entries()].map(([path9, n]) => [
22679
- path9,
22435
+ return [...this.store.entries()].map(([path8, n]) => [
22436
+ path8,
22680
22437
  !!(n & 2),
22681
22438
  !!(n & 1)
22682
22439
  ]);
@@ -22879,9 +22636,9 @@ class GlobUtil {
22879
22636
  signal;
22880
22637
  maxDepth;
22881
22638
  includeChildMatches;
22882
- constructor(patterns, path9, opts) {
22639
+ constructor(patterns, path8, opts) {
22883
22640
  this.patterns = patterns;
22884
- this.path = path9;
22641
+ this.path = path8;
22885
22642
  this.opts = opts;
22886
22643
  this.#sep = !opts.posix && opts.platform === "win32" ? "\\" : "/";
22887
22644
  this.includeChildMatches = opts.includeChildMatches !== false;
@@ -22900,11 +22657,11 @@ class GlobUtil {
22900
22657
  });
22901
22658
  }
22902
22659
  }
22903
- #ignored(path9) {
22904
- return this.seen.has(path9) || !!this.#ignore?.ignored?.(path9);
22660
+ #ignored(path8) {
22661
+ return this.seen.has(path8) || !!this.#ignore?.ignored?.(path8);
22905
22662
  }
22906
- #childrenIgnored(path9) {
22907
- return !!this.#ignore?.childrenIgnored?.(path9);
22663
+ #childrenIgnored(path8) {
22664
+ return !!this.#ignore?.childrenIgnored?.(path8);
22908
22665
  }
22909
22666
  pause() {
22910
22667
  this.paused = true;
@@ -23117,8 +22874,8 @@ class GlobUtil {
23117
22874
 
23118
22875
  class GlobWalker extends GlobUtil {
23119
22876
  matches = new Set;
23120
- constructor(patterns, path9, opts) {
23121
- super(patterns, path9, opts);
22877
+ constructor(patterns, path8, opts) {
22878
+ super(patterns, path8, opts);
23122
22879
  }
23123
22880
  matchEmit(e) {
23124
22881
  this.matches.add(e);
@@ -23156,8 +22913,8 @@ class GlobWalker extends GlobUtil {
23156
22913
 
23157
22914
  class GlobStream extends GlobUtil {
23158
22915
  results;
23159
- constructor(patterns, path9, opts) {
23160
- super(patterns, path9, opts);
22916
+ constructor(patterns, path8, opts) {
22917
+ super(patterns, path8, opts);
23161
22918
  this.results = new Minipass({
23162
22919
  signal: this.signal,
23163
22920
  objectMode: true
@@ -23425,8 +23182,8 @@ var glob = Object.assign(glob_, {
23425
23182
  glob.glob = glob;
23426
23183
 
23427
23184
  // src/utils/rulesMatcher.ts
23428
- import * as fs7 from "fs";
23429
- import * as path9 from "path";
23185
+ import * as fs6 from "fs";
23186
+ import * as path8 from "path";
23430
23187
  function parseSourceDirective(content) {
23431
23188
  const match2 = content.match(/^source\s+([^\n#]+)/m);
23432
23189
  if (!match2) {
@@ -23435,28 +23192,28 @@ function parseSourceDirective(content) {
23435
23192
  return match2[1].trim();
23436
23193
  }
23437
23194
  function resolveSourcePath(sourcePath, rulesFilePath) {
23438
- if (path9.isAbsolute(sourcePath)) {
23195
+ if (path8.isAbsolute(sourcePath)) {
23439
23196
  return sourcePath;
23440
23197
  }
23441
- const rulesDir = path9.dirname(rulesFilePath);
23442
- return path9.resolve(rulesDir, sourcePath);
23198
+ const rulesDir = path8.dirname(rulesFilePath);
23199
+ return path8.resolve(rulesDir, sourcePath);
23443
23200
  }
23444
23201
  function loadRulesMapping(rulesDir) {
23445
23202
  const mapping = {};
23446
- if (!fs7.existsSync(rulesDir)) {
23203
+ if (!fs6.existsSync(rulesDir)) {
23447
23204
  return mapping;
23448
23205
  }
23449
- const files = fs7.readdirSync(rulesDir);
23206
+ const files = fs6.readdirSync(rulesDir);
23450
23207
  for (const file2 of files) {
23451
23208
  if (!file2.endsWith(".rules")) {
23452
23209
  continue;
23453
23210
  }
23454
- const rulesFilePath = path9.join(rulesDir, file2);
23455
- const stat = fs7.statSync(rulesFilePath);
23211
+ const rulesFilePath = path8.join(rulesDir, file2);
23212
+ const stat = fs6.statSync(rulesFilePath);
23456
23213
  if (!stat.isFile()) {
23457
23214
  continue;
23458
23215
  }
23459
- const content = fs7.readFileSync(rulesFilePath, "utf-8");
23216
+ const content = fs6.readFileSync(rulesFilePath, "utf-8");
23460
23217
  const sourcePath = parseSourceDirective(content);
23461
23218
  if (!sourcePath) {
23462
23219
  continue;
@@ -23470,22 +23227,22 @@ function findRulesForCsv(csvPath, mapping) {
23470
23227
  if (mapping[csvPath]) {
23471
23228
  return mapping[csvPath];
23472
23229
  }
23473
- const normalizedCsvPath = path9.normalize(csvPath);
23230
+ const normalizedCsvPath = path8.normalize(csvPath);
23474
23231
  for (const [mappedCsv, rulesFile] of Object.entries(mapping)) {
23475
- const normalizedMappedCsv = path9.normalize(mappedCsv);
23232
+ const normalizedMappedCsv = path8.normalize(mappedCsv);
23476
23233
  if (normalizedMappedCsv === normalizedCsvPath) {
23477
23234
  return rulesFile;
23478
23235
  }
23479
23236
  }
23480
23237
  for (const [pattern, rulesFile] of Object.entries(mapping)) {
23481
- if (minimatch(csvPath, pattern) || minimatch(normalizedCsvPath, path9.normalize(pattern))) {
23238
+ if (minimatch(csvPath, pattern) || minimatch(normalizedCsvPath, path8.normalize(pattern))) {
23482
23239
  return rulesFile;
23483
23240
  }
23484
23241
  }
23485
- const csvBasename = path9.basename(csvPath);
23242
+ const csvBasename = path8.basename(csvPath);
23486
23243
  const matches = [];
23487
23244
  for (const rulesFile of Object.values(mapping)) {
23488
- const rulesBasename = path9.basename(rulesFile, ".rules");
23245
+ const rulesBasename = path8.basename(rulesFile, ".rules");
23489
23246
  if (csvBasename.startsWith(rulesBasename)) {
23490
23247
  matches.push({ rulesFile, prefixLength: rulesBasename.length });
23491
23248
  }
@@ -23616,7 +23373,7 @@ async function getAccountBalance(mainJournalPath, account, asOfDate, executor =
23616
23373
  }
23617
23374
 
23618
23375
  // src/utils/rulesParser.ts
23619
- import * as fs8 from "fs";
23376
+ import * as fs7 from "fs";
23620
23377
  function parseSkipRows(rulesContent) {
23621
23378
  const match2 = rulesContent.match(/^skip\s+(\d+)/m);
23622
23379
  return match2 ? parseInt(match2[1], 10) : 0;
@@ -23682,7 +23439,7 @@ function parseAccount1(rulesContent) {
23682
23439
  }
23683
23440
  function getAccountFromRulesFile(rulesFilePath) {
23684
23441
  try {
23685
- const content = fs8.readFileSync(rulesFilePath, "utf-8");
23442
+ const content = fs7.readFileSync(rulesFilePath, "utf-8");
23686
23443
  return parseAccount1(content);
23687
23444
  } catch {
23688
23445
  return null;
@@ -23702,7 +23459,7 @@ function parseRulesFile(rulesContent) {
23702
23459
 
23703
23460
  // src/utils/csvParser.ts
23704
23461
  var import_convert_csv_to_json = __toESM(require_convert_csv_to_json(), 1);
23705
- import * as fs9 from "fs";
23462
+ import * as fs8 from "fs";
23706
23463
 
23707
23464
  // src/utils/balanceUtils.ts
23708
23465
  function parseAmountValue(amountStr) {
@@ -23751,7 +23508,7 @@ function balancesMatch(balance1, balance2) {
23751
23508
 
23752
23509
  // src/utils/csvParser.ts
23753
23510
  function parseCsvFile(csvPath, config2) {
23754
- const csvContent = fs9.readFileSync(csvPath, "utf-8");
23511
+ const csvContent = fs8.readFileSync(csvPath, "utf-8");
23755
23512
  const lines = csvContent.split(`
23756
23513
  `);
23757
23514
  const headerIndex = config2.skipRows;
@@ -23909,21 +23666,21 @@ function buildSuccessResult3(files, summary, message) {
23909
23666
  });
23910
23667
  }
23911
23668
  function findCsvFromRulesFile(rulesFile) {
23912
- const content = fs10.readFileSync(rulesFile, "utf-8");
23669
+ const content = fs9.readFileSync(rulesFile, "utf-8");
23913
23670
  const match2 = content.match(/^source\s+([^\n#]+)/m);
23914
23671
  if (!match2) {
23915
23672
  return null;
23916
23673
  }
23917
23674
  const sourcePath = match2[1].trim();
23918
- const rulesDir = path10.dirname(rulesFile);
23919
- const absolutePattern = path10.resolve(rulesDir, sourcePath);
23675
+ const rulesDir = path9.dirname(rulesFile);
23676
+ const absolutePattern = path9.resolve(rulesDir, sourcePath);
23920
23677
  const matches = glob.sync(absolutePattern);
23921
23678
  if (matches.length === 0) {
23922
23679
  return null;
23923
23680
  }
23924
23681
  matches.sort((a, b) => {
23925
- const aStat = fs10.statSync(a);
23926
- const bStat = fs10.statSync(b);
23682
+ const aStat = fs9.statSync(a);
23683
+ const bStat = fs9.statSync(b);
23927
23684
  return bStat.mtime.getTime() - aStat.mtime.getTime();
23928
23685
  });
23929
23686
  return matches[0];
@@ -23931,7 +23688,7 @@ function findCsvFromRulesFile(rulesFile) {
23931
23688
  async function executeImports(fileResults, directory, pendingDir, doneDir, hledgerExecutor) {
23932
23689
  const importedFiles = [];
23933
23690
  for (const fileResult of fileResults) {
23934
- const rulesFile = fileResult.rulesFile ? path10.join(directory, fileResult.rulesFile) : null;
23691
+ const rulesFile = fileResult.rulesFile ? path9.join(directory, fileResult.rulesFile) : null;
23935
23692
  if (!rulesFile)
23936
23693
  continue;
23937
23694
  const year = fileResult.transactionYear;
@@ -23963,7 +23720,7 @@ async function executeImports(fileResults, directory, pendingDir, doneDir, hledg
23963
23720
  importedFiles.push(importedCsv);
23964
23721
  }
23965
23722
  }
23966
- const mainJournalPath = path10.join(directory, ".hledger.journal");
23723
+ const mainJournalPath = path9.join(directory, ".hledger.journal");
23967
23724
  const validationResult = await validateLedger(mainJournalPath, hledgerExecutor);
23968
23725
  if (!validationResult.valid) {
23969
23726
  return {
@@ -23973,13 +23730,13 @@ async function executeImports(fileResults, directory, pendingDir, doneDir, hledg
23973
23730
  };
23974
23731
  }
23975
23732
  for (const csvFile of importedFiles) {
23976
- const relativePath = path10.relative(pendingDir, csvFile);
23977
- const destPath = path10.join(doneDir, relativePath);
23978
- const destDir = path10.dirname(destPath);
23979
- if (!fs10.existsSync(destDir)) {
23980
- fs10.mkdirSync(destDir, { recursive: true });
23733
+ const relativePath = path9.relative(pendingDir, csvFile);
23734
+ const destPath = path9.join(doneDir, relativePath);
23735
+ const destDir = path9.dirname(destPath);
23736
+ if (!fs9.existsSync(destDir)) {
23737
+ fs9.mkdirSync(destDir, { recursive: true });
23981
23738
  }
23982
- fs10.renameSync(csvFile, destPath);
23739
+ fs9.renameSync(csvFile, destPath);
23983
23740
  }
23984
23741
  return {
23985
23742
  success: true,
@@ -23990,7 +23747,7 @@ async function processCsvFile(csvFile, rulesMapping, directory, hledgerExecutor)
23990
23747
  const rulesFile = findRulesForCsv(csvFile, rulesMapping);
23991
23748
  if (!rulesFile) {
23992
23749
  return {
23993
- csv: path10.relative(directory, csvFile),
23750
+ csv: path9.relative(directory, csvFile),
23994
23751
  rulesFile: null,
23995
23752
  totalTransactions: 0,
23996
23753
  matchedTransactions: 0,
@@ -24001,8 +23758,8 @@ async function processCsvFile(csvFile, rulesMapping, directory, hledgerExecutor)
24001
23758
  const result = await hledgerExecutor(["print", "-f", rulesFile]);
24002
23759
  if (result.exitCode !== 0) {
24003
23760
  return {
24004
- csv: path10.relative(directory, csvFile),
24005
- rulesFile: path10.relative(directory, rulesFile),
23761
+ csv: path9.relative(directory, csvFile),
23762
+ rulesFile: path9.relative(directory, rulesFile),
24006
23763
  totalTransactions: 0,
24007
23764
  matchedTransactions: 0,
24008
23765
  unknownPostings: [],
@@ -24016,8 +23773,8 @@ async function processCsvFile(csvFile, rulesMapping, directory, hledgerExecutor)
24016
23773
  if (years.size > 1) {
24017
23774
  const yearList = Array.from(years).sort().join(", ");
24018
23775
  return {
24019
- csv: path10.relative(directory, csvFile),
24020
- rulesFile: path10.relative(directory, rulesFile),
23776
+ csv: path9.relative(directory, csvFile),
23777
+ rulesFile: path9.relative(directory, rulesFile),
24021
23778
  totalTransactions: transactionCount,
24022
23779
  matchedTransactions: matchedCount,
24023
23780
  unknownPostings: [],
@@ -24027,7 +23784,7 @@ async function processCsvFile(csvFile, rulesMapping, directory, hledgerExecutor)
24027
23784
  const transactionYear = years.size === 1 ? Array.from(years)[0] : undefined;
24028
23785
  if (unknownPostings.length > 0) {
24029
23786
  try {
24030
- const rulesContent = fs10.readFileSync(rulesFile, "utf-8");
23787
+ const rulesContent = fs9.readFileSync(rulesFile, "utf-8");
24031
23788
  const rulesConfig = parseRulesFile(rulesContent);
24032
23789
  const csvRows = parseCsvFile(csvFile, rulesConfig);
24033
23790
  for (const posting of unknownPostings) {
@@ -24044,22 +23801,19 @@ async function processCsvFile(csvFile, rulesMapping, directory, hledgerExecutor)
24044
23801
  }
24045
23802
  }
24046
23803
  return {
24047
- csv: path10.relative(directory, csvFile),
24048
- rulesFile: path10.relative(directory, rulesFile),
23804
+ csv: path9.relative(directory, csvFile),
23805
+ rulesFile: path9.relative(directory, rulesFile),
24049
23806
  totalTransactions: transactionCount,
24050
23807
  matchedTransactions: matchedCount,
24051
23808
  unknownPostings,
24052
23809
  transactionYear
24053
23810
  };
24054
23811
  }
24055
- async function importStatements(directory, agent, options, configLoader = loadImportConfig, hledgerExecutor = defaultHledgerExecutor, worktreeChecker = isInWorktree, _logger) {
23812
+ async function importStatements(directory, agent, options, configLoader = loadImportConfig, hledgerExecutor = defaultHledgerExecutor) {
24056
23813
  const restrictionError = checkAccountantAgent(agent, "import statements");
24057
23814
  if (restrictionError) {
24058
23815
  return restrictionError;
24059
23816
  }
24060
- if (!worktreeChecker(directory)) {
24061
- return buildErrorResult3("import-statements must be run inside an import worktree", "Use import-pipeline tool to orchestrate the full workflow");
24062
- }
24063
23817
  let config2;
24064
23818
  try {
24065
23819
  config2 = configLoader(directory);
@@ -24067,9 +23821,9 @@ async function importStatements(directory, agent, options, configLoader = loadIm
24067
23821
  const errorMessage = `Failed to load configuration: ${error45 instanceof Error ? error45.message : String(error45)}`;
24068
23822
  return buildErrorResult3(errorMessage, 'Ensure config/import/providers.yaml exists with required paths including "rules"');
24069
23823
  }
24070
- const pendingDir = path10.join(directory, config2.paths.pending);
24071
- const rulesDir = path10.join(directory, config2.paths.rules);
24072
- const doneDir = path10.join(directory, config2.paths.done);
23824
+ const pendingDir = path9.join(directory, config2.paths.pending);
23825
+ const rulesDir = path9.join(directory, config2.paths.rules);
23826
+ const doneDir = path9.join(directory, config2.paths.done);
24073
23827
  const rulesMapping = loadRulesMapping(rulesDir);
24074
23828
  const csvFiles = findCsvFiles(pendingDir, options.provider, options.currency);
24075
23829
  if (csvFiles.length === 0) {
@@ -24113,8 +23867,8 @@ async function importStatements(directory, agent, options, configLoader = loadIm
24113
23867
  }
24114
23868
  for (const [_rulesFile, matchingCSVs] of rulesFileToCSVs.entries()) {
24115
23869
  matchingCSVs.sort((a, b) => {
24116
- const aStat = fs10.statSync(a);
24117
- const bStat = fs10.statSync(b);
23870
+ const aStat = fs9.statSync(a);
23871
+ const bStat = fs9.statSync(b);
24118
23872
  return bStat.mtime.getTime() - aStat.mtime.getTime();
24119
23873
  });
24120
23874
  const newestCSV = matchingCSVs[0];
@@ -24203,7 +23957,9 @@ This tool processes CSV files in the pending import directory and uses hledger's
24203
23957
  1. Run with checkOnly: true (or no args)
24204
23958
  2. If unknowns found, add rules to the appropriate .rules file
24205
23959
  3. Repeat until no unknowns
24206
- 4. Run with checkOnly: false to import`,
23960
+ 4. Run with checkOnly: false to import
23961
+
23962
+ Note: This tool is typically called via import-pipeline for the full workflow.`,
24207
23963
  args: {
24208
23964
  provider: tool.schema.string().optional().describe('Filter by provider (e.g., "revolut", "ubs"). If omitted, process all providers.'),
24209
23965
  currency: tool.schema.string().optional().describe('Filter by currency (e.g., "chf", "eur"). If omitted, process all currencies for the provider.'),
@@ -24219,23 +23975,14 @@ This tool processes CSV files in the pending import directory and uses hledger's
24219
23975
  }
24220
23976
  });
24221
23977
  // src/tools/reconcile-statement.ts
24222
- import * as fs11 from "fs";
24223
- import * as path11 from "path";
23978
+ import * as fs10 from "fs";
23979
+ import * as path10 from "path";
24224
23980
  function buildErrorResult4(params) {
24225
23981
  return JSON.stringify({
24226
23982
  success: false,
24227
23983
  ...params
24228
23984
  });
24229
23985
  }
24230
- function validateWorktree(directory, worktreeChecker) {
24231
- if (!worktreeChecker(directory)) {
24232
- return buildErrorResult4({
24233
- error: "reconcile-statement must be run inside an import worktree",
24234
- hint: "Use import-pipeline tool to orchestrate the full workflow"
24235
- });
24236
- }
24237
- return null;
24238
- }
24239
23986
  function loadConfiguration(directory, configLoader) {
24240
23987
  try {
24241
23988
  const config2 = configLoader(directory);
@@ -24262,14 +24009,14 @@ function findCsvToReconcile(doneDir, options) {
24262
24009
  };
24263
24010
  }
24264
24011
  const csvFile = csvFiles[csvFiles.length - 1];
24265
- const relativePath = path11.relative(path11.dirname(path11.dirname(doneDir)), csvFile);
24012
+ const relativePath = path10.relative(path10.dirname(path10.dirname(doneDir)), csvFile);
24266
24013
  return { csvFile, relativePath };
24267
24014
  }
24268
24015
  function determineClosingBalance(csvFile, config2, options, relativeCsvPath, rulesDir) {
24269
24016
  let metadata;
24270
24017
  try {
24271
- const content = fs11.readFileSync(csvFile, "utf-8");
24272
- const filename = path11.basename(csvFile);
24018
+ const content = fs10.readFileSync(csvFile, "utf-8");
24019
+ const filename = path10.basename(csvFile);
24273
24020
  const detectionResult = detectProvider(filename, content, config2);
24274
24021
  metadata = detectionResult?.metadata;
24275
24022
  } catch {
@@ -24351,7 +24098,7 @@ function tryExtractClosingBalanceFromCSV(csvFile, rulesDir) {
24351
24098
  if (!rulesFile) {
24352
24099
  return null;
24353
24100
  }
24354
- const rulesContent = fs11.readFileSync(rulesFile, "utf-8");
24101
+ const rulesContent = fs10.readFileSync(rulesFile, "utf-8");
24355
24102
  const rulesConfig = parseRulesFile(rulesContent);
24356
24103
  const csvRows = parseCsvFile(csvFile, rulesConfig);
24357
24104
  if (csvRows.length === 0) {
@@ -24408,23 +24155,19 @@ function tryExtractClosingBalanceFromCSV(csvFile, rulesDir) {
24408
24155
  return null;
24409
24156
  }
24410
24157
  }
24411
- async function reconcileStatement(directory, agent, options, configLoader = loadImportConfig, hledgerExecutor = defaultHledgerExecutor, worktreeChecker = isInWorktree) {
24158
+ async function reconcileStatement(directory, agent, options, configLoader = loadImportConfig, hledgerExecutor = defaultHledgerExecutor) {
24412
24159
  const restrictionError = checkAccountantAgent(agent, "reconcile statement");
24413
24160
  if (restrictionError) {
24414
24161
  return restrictionError;
24415
24162
  }
24416
- const worktreeError = validateWorktree(directory, worktreeChecker);
24417
- if (worktreeError) {
24418
- return worktreeError;
24419
- }
24420
24163
  const configResult = loadConfiguration(directory, configLoader);
24421
24164
  if ("error" in configResult) {
24422
24165
  return configResult.error;
24423
24166
  }
24424
24167
  const { config: config2 } = configResult;
24425
- const doneDir = path11.join(directory, config2.paths.done);
24426
- const rulesDir = path11.join(directory, config2.paths.rules);
24427
- const mainJournalPath = path11.join(directory, ".hledger.journal");
24168
+ const doneDir = path10.join(directory, config2.paths.done);
24169
+ const rulesDir = path10.join(directory, config2.paths.rules);
24170
+ const mainJournalPath = path10.join(directory, ".hledger.journal");
24428
24171
  const csvResult = findCsvToReconcile(doneDir, options);
24429
24172
  if ("error" in csvResult) {
24430
24173
  return csvResult.error;
@@ -24520,7 +24263,6 @@ var reconcile_statement_default = tool({
24520
24263
  description: `ACCOUNTANT AGENT ONLY: Reconcile imported bank statement against closing balance.
24521
24264
 
24522
24265
  This tool validates that the imported transactions result in the correct closing balance.
24523
- It must be run inside an import worktree (use import-pipeline for the full workflow).
24524
24266
 
24525
24267
  **Workflow:**
24526
24268
  1. Finds the most recently imported CSV in the done directory
@@ -24553,17 +24295,16 @@ It must be run inside an import worktree (use import-pipeline for the full workf
24553
24295
  }
24554
24296
  });
24555
24297
  // src/tools/import-pipeline.ts
24556
- import * as fs14 from "fs";
24557
- import * as path13 from "path";
24298
+ import * as path12 from "path";
24558
24299
 
24559
24300
  // src/utils/accountDeclarations.ts
24560
- import * as fs12 from "fs";
24301
+ import * as fs11 from "fs";
24561
24302
  function extractAccountsFromRulesFile(rulesPath) {
24562
24303
  const accounts = new Set;
24563
- if (!fs12.existsSync(rulesPath)) {
24304
+ if (!fs11.existsSync(rulesPath)) {
24564
24305
  return accounts;
24565
24306
  }
24566
- const content = fs12.readFileSync(rulesPath, "utf-8");
24307
+ const content = fs11.readFileSync(rulesPath, "utf-8");
24567
24308
  const lines = content.split(`
24568
24309
  `);
24569
24310
  for (const line of lines) {
@@ -24598,10 +24339,10 @@ function sortAccountDeclarations(accounts) {
24598
24339
  return Array.from(accounts).sort((a, b) => a.localeCompare(b));
24599
24340
  }
24600
24341
  function ensureAccountDeclarations(yearJournalPath, accounts) {
24601
- if (!fs12.existsSync(yearJournalPath)) {
24342
+ if (!fs11.existsSync(yearJournalPath)) {
24602
24343
  throw new Error(`Year journal not found: ${yearJournalPath}`);
24603
24344
  }
24604
- const content = fs12.readFileSync(yearJournalPath, "utf-8");
24345
+ const content = fs11.readFileSync(yearJournalPath, "utf-8");
24605
24346
  const lines = content.split(`
24606
24347
  `);
24607
24348
  const existingAccounts = new Set;
@@ -24663,7 +24404,7 @@ function ensureAccountDeclarations(yearJournalPath, accounts) {
24663
24404
  newContent.push("");
24664
24405
  }
24665
24406
  newContent.push(...otherLines);
24666
- fs12.writeFileSync(yearJournalPath, newContent.join(`
24407
+ fs11.writeFileSync(yearJournalPath, newContent.join(`
24667
24408
  `));
24668
24409
  return {
24669
24410
  added: Array.from(missingAccounts).sort(),
@@ -24672,8 +24413,8 @@ function ensureAccountDeclarations(yearJournalPath, accounts) {
24672
24413
  }
24673
24414
 
24674
24415
  // src/utils/logger.ts
24675
- import fs13 from "fs/promises";
24676
- import path12 from "path";
24416
+ import fs12 from "fs/promises";
24417
+ import path11 from "path";
24677
24418
 
24678
24419
  class MarkdownLogger {
24679
24420
  buffer = [];
@@ -24685,7 +24426,7 @@ class MarkdownLogger {
24685
24426
  this.autoFlush = config2.autoFlush ?? true;
24686
24427
  this.context = config2.context || {};
24687
24428
  const filename = config2.filename || `import-${this.getTimestamp()}.md`;
24688
- this.logPath = path12.join(config2.logDir, filename);
24429
+ this.logPath = path11.join(config2.logDir, filename);
24689
24430
  this.buffer.push(`# Import Pipeline Log`);
24690
24431
  this.buffer.push(`**Started**: ${new Date().toLocaleString()}`);
24691
24432
  this.buffer.push("");
@@ -24782,8 +24523,8 @@ class MarkdownLogger {
24782
24523
  if (this.buffer.length === 0)
24783
24524
  return;
24784
24525
  try {
24785
- await fs13.mkdir(path12.dirname(this.logPath), { recursive: true });
24786
- await fs13.writeFile(this.logPath, this.buffer.join(`
24526
+ await fs12.mkdir(path11.dirname(this.logPath), { recursive: true });
24527
+ await fs12.writeFile(this.logPath, this.buffer.join(`
24787
24528
  `), "utf-8");
24788
24529
  } catch {}
24789
24530
  }
@@ -24810,7 +24551,7 @@ function createImportLogger(directory, worktreeId, provider) {
24810
24551
  if (provider)
24811
24552
  context.provider = provider;
24812
24553
  const logger = createLogger({
24813
- logDir: path12.join(directory, ".memory"),
24554
+ logDir: path11.join(directory, ".memory"),
24814
24555
  autoFlush: true,
24815
24556
  context
24816
24557
  });
@@ -24850,51 +24591,8 @@ function buildErrorResult5(result, error45, hint) {
24850
24591
  }
24851
24592
  return JSON.stringify(result);
24852
24593
  }
24853
- function buildCommitMessage(provider, currency, fromDate, untilDate, transactionCount) {
24854
- const providerStr = provider?.toUpperCase() || "statements";
24855
- const currencyStr = currency?.toUpperCase();
24856
- const dateRange = fromDate && untilDate ? ` ${fromDate} to ${untilDate}` : "";
24857
- const txStr = transactionCount > 0 ? ` (${transactionCount} transactions)` : "";
24858
- const parts = ["Import:", providerStr];
24859
- if (currencyStr) {
24860
- parts.push(currencyStr);
24861
- }
24862
- return `${parts.join(" ")}${dateRange}${txStr}`;
24863
- }
24864
- function cleanupIncomingFiles(worktree, context) {
24865
- const incomingDir = path13.join(worktree.mainRepoPath, "import/incoming");
24866
- if (!fs14.existsSync(incomingDir)) {
24867
- return;
24868
- }
24869
- const importStep = context.result.steps.import;
24870
- if (!importStep?.success || !importStep.details) {
24871
- return;
24872
- }
24873
- const importResult = importStep.details;
24874
- if (!importResult.files || !Array.isArray(importResult.files)) {
24875
- return;
24876
- }
24877
- let deletedCount = 0;
24878
- for (const fileResult of importResult.files) {
24879
- if (!fileResult.csv)
24880
- continue;
24881
- const filename = path13.basename(fileResult.csv);
24882
- const filePath = path13.join(incomingDir, filename);
24883
- if (fs14.existsSync(filePath)) {
24884
- try {
24885
- fs14.unlinkSync(filePath);
24886
- deletedCount++;
24887
- } catch (error45) {
24888
- console.error(`[ERROR] Failed to delete ${filename}: ${error45 instanceof Error ? error45.message : String(error45)}`);
24889
- }
24890
- }
24891
- }
24892
- if (deletedCount > 0) {
24893
- console.log(`[INFO] Cleaned up ${deletedCount} file(s) from import/incoming/`);
24894
- }
24895
- }
24896
- async function executeClassifyStep(context, worktree, logger) {
24897
- logger?.startSection("Step 2: Classify Transactions");
24594
+ async function executeClassifyStep(context, logger) {
24595
+ logger?.startSection("Step 1: Classify Transactions");
24898
24596
  logger?.logStep("Classify", "start");
24899
24597
  if (context.options.skipClassify) {
24900
24598
  logger?.info("Classification skipped (skipClassify: true)");
@@ -24902,8 +24600,7 @@ async function executeClassifyStep(context, worktree, logger) {
24902
24600
  logger?.endSection();
24903
24601
  return;
24904
24602
  }
24905
- const inWorktree = () => true;
24906
- const classifyResult = await classifyStatements(worktree.path, context.agent, context.configLoader, inWorktree);
24603
+ const classifyResult = await classifyStatements(context.directory, context.agent, context.configLoader);
24907
24604
  const classifyParsed = JSON.parse(classifyResult);
24908
24605
  const success2 = classifyParsed.success !== false;
24909
24606
  let message = success2 ? "Classification complete" : "Classification had issues";
@@ -24920,12 +24617,12 @@ async function executeClassifyStep(context, worktree, logger) {
24920
24617
  context.result.steps.classify = buildStepResult(success2, message, details);
24921
24618
  logger?.endSection();
24922
24619
  }
24923
- async function executeAccountDeclarationsStep(context, worktree, logger) {
24924
- logger?.startSection("Step 3: Check Account Declarations");
24620
+ async function executeAccountDeclarationsStep(context, logger) {
24621
+ logger?.startSection("Step 2: Check Account Declarations");
24925
24622
  logger?.logStep("Check Accounts", "start");
24926
- const config2 = context.configLoader(worktree.path);
24927
- const pendingDir = path13.join(worktree.path, config2.paths.pending);
24928
- const rulesDir = path13.join(worktree.path, config2.paths.rules);
24623
+ const config2 = context.configLoader(context.directory);
24624
+ const pendingDir = path12.join(context.directory, config2.paths.pending);
24625
+ const rulesDir = path12.join(context.directory, config2.paths.rules);
24929
24626
  const csvFiles = findCsvFiles(pendingDir, context.options.provider, context.options.currency);
24930
24627
  if (csvFiles.length === 0) {
24931
24628
  context.result.steps.accountDeclarations = buildStepResult(true, "No CSV files to process", {
@@ -24956,7 +24653,7 @@ async function executeAccountDeclarationsStep(context, worktree, logger) {
24956
24653
  context.result.steps.accountDeclarations = buildStepResult(true, "No accounts found in rules files", {
24957
24654
  accountsAdded: [],
24958
24655
  journalUpdated: "",
24959
- rulesScanned: Array.from(matchedRulesFiles).map((f) => path13.relative(worktree.path, f))
24656
+ rulesScanned: Array.from(matchedRulesFiles).map((f) => path12.relative(context.directory, f))
24960
24657
  });
24961
24658
  return;
24962
24659
  }
@@ -24979,23 +24676,23 @@ async function executeAccountDeclarationsStep(context, worktree, logger) {
24979
24676
  context.result.steps.accountDeclarations = buildStepResult(false, "Could not determine transaction year from CSV files", {
24980
24677
  accountsAdded: [],
24981
24678
  journalUpdated: "",
24982
- rulesScanned: Array.from(matchedRulesFiles).map((f) => path13.relative(worktree.path, f))
24679
+ rulesScanned: Array.from(matchedRulesFiles).map((f) => path12.relative(context.directory, f))
24983
24680
  });
24984
24681
  return;
24985
24682
  }
24986
24683
  let yearJournalPath;
24987
24684
  try {
24988
- yearJournalPath = ensureYearJournalExists(worktree.path, transactionYear);
24685
+ yearJournalPath = ensureYearJournalExists(context.directory, transactionYear);
24989
24686
  } catch (error45) {
24990
24687
  context.result.steps.accountDeclarations = buildStepResult(false, `Failed to create year journal: ${error45 instanceof Error ? error45.message : String(error45)}`, {
24991
24688
  accountsAdded: [],
24992
24689
  journalUpdated: "",
24993
- rulesScanned: Array.from(matchedRulesFiles).map((f) => path13.relative(worktree.path, f))
24690
+ rulesScanned: Array.from(matchedRulesFiles).map((f) => path12.relative(context.directory, f))
24994
24691
  });
24995
24692
  return;
24996
24693
  }
24997
24694
  const result = ensureAccountDeclarations(yearJournalPath, allAccounts);
24998
- const message = result.added.length > 0 ? `Added ${result.added.length} account declaration(s) to ${path13.relative(worktree.path, yearJournalPath)}` : "All required accounts already declared";
24695
+ const message = result.added.length > 0 ? `Added ${result.added.length} account declaration(s) to ${path12.relative(context.directory, yearJournalPath)}` : "All required accounts already declared";
24999
24696
  logger?.logStep("Check Accounts", "success", message);
25000
24697
  if (result.added.length > 0) {
25001
24698
  for (const account of result.added) {
@@ -25004,20 +24701,19 @@ async function executeAccountDeclarationsStep(context, worktree, logger) {
25004
24701
  }
25005
24702
  context.result.steps.accountDeclarations = buildStepResult(true, message, {
25006
24703
  accountsAdded: result.added,
25007
- journalUpdated: path13.relative(worktree.path, yearJournalPath),
25008
- rulesScanned: Array.from(matchedRulesFiles).map((f) => path13.relative(worktree.path, f))
24704
+ journalUpdated: path12.relative(context.directory, yearJournalPath),
24705
+ rulesScanned: Array.from(matchedRulesFiles).map((f) => path12.relative(context.directory, f))
25009
24706
  });
25010
24707
  logger?.endSection();
25011
24708
  }
25012
- async function executeDryRunStep(context, worktree, logger) {
25013
- logger?.startSection("Step 4: Dry Run Import");
24709
+ async function executeDryRunStep(context, logger) {
24710
+ logger?.startSection("Step 3: Dry Run Import");
25014
24711
  logger?.logStep("Dry Run", "start");
25015
- const inWorktree = () => true;
25016
- const dryRunResult = await importStatements(worktree.path, context.agent, {
24712
+ const dryRunResult = await importStatements(context.directory, context.agent, {
25017
24713
  provider: context.options.provider,
25018
24714
  currency: context.options.currency,
25019
24715
  checkOnly: true
25020
- }, context.configLoader, context.hledgerExecutor, inWorktree);
24716
+ }, context.configLoader, context.hledgerExecutor);
25021
24717
  const dryRunParsed = JSON.parse(dryRunResult);
25022
24718
  const message = dryRunParsed.success ? `Dry run passed: ${dryRunParsed.summary?.totalTransactions || 0} transactions ready` : `Dry run failed: ${dryRunParsed.summary?.unknown || 0} unknown account(s)`;
25023
24719
  logger?.logStep("Dry Run", dryRunParsed.success ? "success" : "error", message);
@@ -25041,15 +24737,14 @@ async function executeDryRunStep(context, worktree, logger) {
25041
24737
  }
25042
24738
  logger?.endSection();
25043
24739
  }
25044
- async function executeImportStep(context, worktree, logger) {
25045
- logger?.startSection("Step 5: Import Transactions");
24740
+ async function executeImportStep(context, logger) {
24741
+ logger?.startSection("Step 4: Import Transactions");
25046
24742
  logger?.logStep("Import", "start");
25047
- const inWorktree = () => true;
25048
- const importResult = await importStatements(worktree.path, context.agent, {
24743
+ const importResult = await importStatements(context.directory, context.agent, {
25049
24744
  provider: context.options.provider,
25050
24745
  currency: context.options.currency,
25051
24746
  checkOnly: false
25052
- }, context.configLoader, context.hledgerExecutor, inWorktree);
24747
+ }, context.configLoader, context.hledgerExecutor);
25053
24748
  const importParsed = JSON.parse(importResult);
25054
24749
  const message = importParsed.success ? `Imported ${importParsed.summary?.totalTransactions || 0} transactions` : `Import failed: ${importParsed.error || "Unknown error"}`;
25055
24750
  logger?.logStep("Import", importParsed.success ? "success" : "error", message);
@@ -25066,16 +24761,15 @@ async function executeImportStep(context, worktree, logger) {
25066
24761
  }
25067
24762
  logger?.endSection();
25068
24763
  }
25069
- async function executeReconcileStep(context, worktree, logger) {
25070
- logger?.startSection("Step 6: Reconcile Balance");
24764
+ async function executeReconcileStep(context, logger) {
24765
+ logger?.startSection("Step 5: Reconcile Balance");
25071
24766
  logger?.logStep("Reconcile", "start");
25072
- const inWorktree = () => true;
25073
- const reconcileResult = await reconcileStatement(worktree.path, context.agent, {
24767
+ const reconcileResult = await reconcileStatement(context.directory, context.agent, {
25074
24768
  provider: context.options.provider,
25075
24769
  currency: context.options.currency,
25076
24770
  closingBalance: context.options.closingBalance,
25077
24771
  account: context.options.account
25078
- }, context.configLoader, context.hledgerExecutor, inWorktree);
24772
+ }, context.configLoader, context.hledgerExecutor);
25079
24773
  const reconcileParsed = JSON.parse(reconcileResult);
25080
24774
  const message = reconcileParsed.success ? `Balance reconciled: ${reconcileParsed.actualBalance}` : `Balance mismatch: expected ${reconcileParsed.expectedBalance}, got ${reconcileParsed.actualBalance}`;
25081
24775
  logger?.logStep("Reconcile", reconcileParsed.success ? "success" : "error", message);
@@ -25099,42 +24793,9 @@ async function executeReconcileStep(context, worktree, logger) {
25099
24793
  }
25100
24794
  logger?.endSection();
25101
24795
  }
25102
- async function executeMergeStep(context, worktree, logger) {
25103
- logger?.startSection("Step 7: Merge to Main");
25104
- logger?.logStep("Merge", "start");
25105
- const importDetails = context.result.steps.import?.details;
25106
- const reconcileDetails = context.result.steps.reconcile?.details;
25107
- if (!importDetails || !reconcileDetails) {
25108
- throw new Error("Import or reconcile step not completed before merge");
25109
- }
25110
- const commitInfo = {
25111
- fromDate: reconcileDetails.metadata?.["from-date"],
25112
- untilDate: reconcileDetails.metadata?.["until-date"]
25113
- };
25114
- const transactionCount = importDetails.summary?.totalTransactions || 0;
25115
- const commitMessage = buildCommitMessage(context.options.provider, context.options.currency, commitInfo.fromDate, commitInfo.untilDate, transactionCount);
25116
- try {
25117
- logger?.info(`Commit message: "${commitMessage}"`);
25118
- mergeWorktree(worktree, commitMessage);
25119
- logger?.logStep("Merge", "success", "Merged to main branch");
25120
- const mergeDetails = { commitMessage };
25121
- context.result.steps.merge = buildStepResult(true, `Merged to main: "${commitMessage}"`, mergeDetails);
25122
- cleanupIncomingFiles(worktree, context);
25123
- logger?.endSection();
25124
- } catch (error45) {
25125
- logger?.logStep("Merge", "error");
25126
- logger?.error("Merge to main branch failed", error45);
25127
- logger?.endSection();
25128
- const message = `Merge failed: ${error45 instanceof Error ? error45.message : String(error45)}`;
25129
- context.result.steps.merge = buildStepResult(false, message);
25130
- context.result.error = "Merge to main branch failed";
25131
- throw new Error("Merge failed");
25132
- }
25133
- }
25134
24796
  function handleNoTransactions(result) {
25135
24797
  result.steps.import = buildStepResult(true, "No transactions to import");
25136
24798
  result.steps.reconcile = buildStepResult(true, "Reconciliation skipped (no transactions)");
25137
- result.steps.merge = buildStepResult(true, "Merge skipped (no changes)");
25138
24799
  return buildSuccessResult4(result, "No transactions found to import");
25139
24800
  }
25140
24801
  async function importPipeline(directory, agent, options, configLoader = loadImportConfig, hledgerExecutor = defaultHledgerExecutor) {
@@ -25147,7 +24808,6 @@ async function importPipeline(directory, agent, options, configLoader = loadImpo
25147
24808
  logger.info(`Provider filter: ${options.provider || "all"}`);
25148
24809
  logger.info(`Currency filter: ${options.currency || "all"}`);
25149
24810
  logger.info(`Skip classify: ${options.skipClassify || false}`);
25150
- logger.info(`Keep worktree on error: ${options.keepWorktreeOnError ?? true}`);
25151
24811
  logger.info("");
25152
24812
  const result = {
25153
24813
  success: false,
@@ -25162,176 +24822,68 @@ async function importPipeline(directory, agent, options, configLoader = loadImpo
25162
24822
  result
25163
24823
  };
25164
24824
  try {
25165
- return await withWorktree(directory, async (worktree) => {
25166
- logger.setContext("worktreeId", worktree.uuid);
25167
- logger.setContext("worktreePath", worktree.path);
25168
- result.worktreeId = worktree.uuid;
25169
- result.steps.worktree = buildStepResult(true, `Created worktree at ${worktree.path}`, {
25170
- path: worktree.path,
25171
- branch: worktree.branch
25172
- });
25173
- logger.startSection("Step 1: Sync Files");
25174
- logger.logStep("Sync Files", "start");
25175
- try {
25176
- const config2 = configLoader(directory);
25177
- const syncResult = syncCSVFilesToWorktree(directory, worktree.path, config2.paths.import);
25178
- if (syncResult.synced.length === 0 && syncResult.errors.length === 0) {
25179
- logger.logStep("Sync Files", "success", "No CSV files to sync");
25180
- result.steps.sync = buildStepResult(true, "No CSV files to sync", {
25181
- synced: []
25182
- });
25183
- } else if (syncResult.errors.length > 0) {
25184
- logger.warn(`Synced ${syncResult.synced.length} file(s) with ${syncResult.errors.length} error(s)`);
25185
- result.steps.sync = buildStepResult(true, `Synced ${syncResult.synced.length} file(s) with ${syncResult.errors.length} error(s)`, { synced: syncResult.synced, errors: syncResult.errors });
25186
- } else {
25187
- logger.logStep("Sync Files", "success", `Synced ${syncResult.synced.length} CSV file(s)`);
25188
- for (const file2 of syncResult.synced) {
25189
- logger.info(` - ${file2}`);
25190
- }
25191
- result.steps.sync = buildStepResult(true, `Synced ${syncResult.synced.length} CSV file(s) to worktree`, { synced: syncResult.synced });
25192
- }
25193
- logger.endSection();
25194
- } catch (error45) {
25195
- logger.logStep("Sync Files", "error");
25196
- logger.error("Failed to sync CSV files", error45);
25197
- logger.endSection();
25198
- const errorMsg = error45 instanceof Error ? error45.message : String(error45);
25199
- result.steps.sync = buildStepResult(false, `Failed to sync CSV files: ${errorMsg}`, { synced: [], errors: [{ file: "unknown", error: errorMsg }] });
25200
- }
25201
- try {
25202
- await executeClassifyStep(context, worktree, logger);
25203
- await executeAccountDeclarationsStep(context, worktree, logger);
25204
- await executeDryRunStep(context, worktree, logger);
25205
- await executeImportStep(context, worktree, logger);
25206
- await executeReconcileStep(context, worktree, logger);
25207
- try {
25208
- const config2 = configLoader(directory);
25209
- const cleanupResult = cleanupProcessedCSVFiles(directory, config2.paths.import);
25210
- if (cleanupResult.deleted.length === 0 && cleanupResult.errors.length === 0) {
25211
- result.steps.cleanup = buildStepResult(true, "No CSV files to cleanup", { csvCleanup: { deleted: [] } });
25212
- } else if (cleanupResult.errors.length > 0) {
25213
- result.steps.cleanup = buildStepResult(true, `Deleted ${cleanupResult.deleted.length} CSV file(s) with ${cleanupResult.errors.length} error(s)`, {
25214
- csvCleanup: {
25215
- deleted: cleanupResult.deleted,
25216
- errors: cleanupResult.errors
25217
- }
25218
- });
25219
- } else {
25220
- result.steps.cleanup = buildStepResult(true, `Deleted ${cleanupResult.deleted.length} CSV file(s) from main repo`, { csvCleanup: { deleted: cleanupResult.deleted } });
25221
- }
25222
- } catch (error45) {
25223
- const errorMsg = error45 instanceof Error ? error45.message : String(error45);
25224
- result.steps.cleanup = buildStepResult(false, `Failed to cleanup CSV files: ${errorMsg}`, {
25225
- csvCleanup: {
25226
- deleted: [],
25227
- errors: [{ file: "unknown", error: errorMsg }]
25228
- }
25229
- });
25230
- }
25231
- await executeMergeStep(context, worktree, logger);
25232
- const existingCleanup = result.steps.cleanup;
25233
- if (existingCleanup) {
25234
- existingCleanup.message += ", worktree cleaned up";
25235
- existingCleanup.details = {
25236
- ...existingCleanup.details,
25237
- cleanedAfterSuccess: true
25238
- };
25239
- }
25240
- const transactionCount = context.result.steps.import?.details?.summary?.totalTransactions || 0;
25241
- logger.startSection("Summary");
25242
- logger.info(`\u2705 Import completed successfully`);
25243
- logger.info(`Total transactions imported: ${transactionCount}`);
25244
- if (context.result.steps.reconcile?.details?.actualBalance) {
25245
- logger.info(`Balance reconciliation: \u2705 Matched (${context.result.steps.reconcile.details.actualBalance})`);
25246
- }
25247
- logger.info(`Log file: ${logger.getLogPath()}`);
25248
- logger.endSection();
25249
- return buildSuccessResult4(result, `Successfully imported ${transactionCount} transaction(s)`);
25250
- } catch (error45) {
25251
- const worktreePath = context.result.steps.worktree?.details?.path;
25252
- const keepWorktree = options.keepWorktreeOnError ?? true;
25253
- logger.error("Pipeline step failed", error45);
25254
- if (keepWorktree && worktreePath) {
25255
- logger.warn(`Worktree preserved at: ${worktreePath}`);
25256
- logger.info(`To continue manually: cd ${worktreePath}`);
25257
- logger.info(`To clean up: git worktree remove ${worktreePath}`);
25258
- }
25259
- logger.info(`Log file: ${logger.getLogPath()}`);
25260
- result.steps.cleanup = buildStepResult(true, keepWorktree ? `Worktree preserved for debugging (CSV files preserved for retry)` : "Worktree cleaned up after failure (CSV files preserved for retry)", {
25261
- cleanedAfterFailure: !keepWorktree,
25262
- worktreePreserved: keepWorktree,
25263
- worktreePath,
25264
- preserveReason: keepWorktree ? "error occurred" : undefined,
25265
- csvCleanup: { deleted: [] }
25266
- });
25267
- if (error45 instanceof NoTransactionsError) {
25268
- return handleNoTransactions(result);
25269
- }
25270
- if (!result.error) {
25271
- result.error = error45 instanceof Error ? error45.message : String(error45);
25272
- }
25273
- return buildErrorResult5(result, result.error, result.hint);
25274
- }
25275
- }, {
25276
- keepOnError: options.keepWorktreeOnError ?? true,
25277
- logger
25278
- });
24825
+ await executeClassifyStep(context, logger);
24826
+ await executeAccountDeclarationsStep(context, logger);
24827
+ await executeDryRunStep(context, logger);
24828
+ await executeImportStep(context, logger);
24829
+ await executeReconcileStep(context, logger);
24830
+ const transactionCount = context.result.steps.import?.details?.summary?.totalTransactions || 0;
24831
+ logger.startSection("Summary");
24832
+ logger.info(`Import completed successfully`);
24833
+ logger.info(`Total transactions imported: ${transactionCount}`);
24834
+ if (context.result.steps.reconcile?.details?.actualBalance) {
24835
+ logger.info(`Balance: ${context.result.steps.reconcile.details.actualBalance}`);
24836
+ }
24837
+ logger.info(`Log file: ${logger.getLogPath()}`);
24838
+ logger.endSection();
24839
+ return buildSuccessResult4(result, `Successfully imported ${transactionCount} transaction(s)`);
25279
24840
  } catch (error45) {
25280
- logger.error("Pipeline failed", error45);
25281
- result.steps.worktree = buildStepResult(false, `Failed to create worktree: ${error45 instanceof Error ? error45.message : String(error45)}`);
25282
- result.error = "Failed to create worktree";
25283
- return buildErrorResult5(result, result.error);
24841
+ logger.error("Pipeline step failed", error45);
24842
+ logger.info(`Log file: ${logger.getLogPath()}`);
24843
+ if (error45 instanceof NoTransactionsError) {
24844
+ return handleNoTransactions(result);
24845
+ }
24846
+ if (!result.error) {
24847
+ result.error = error45 instanceof Error ? error45.message : String(error45);
24848
+ }
24849
+ return buildErrorResult5(result, result.error, result.hint);
25284
24850
  } finally {
25285
24851
  logger.endSection();
25286
24852
  await logger.flush();
25287
24853
  }
25288
24854
  }
25289
24855
  var import_pipeline_default = tool({
25290
- description: `ACCOUNTANT AGENT ONLY: Complete import pipeline with git worktree isolation and balance reconciliation.
24856
+ description: `ACCOUNTANT AGENT ONLY: Complete import pipeline with balance reconciliation.
25291
24857
 
25292
- This tool orchestrates the full import workflow in an isolated git worktree:
24858
+ This tool orchestrates the full import workflow:
25293
24859
 
25294
24860
  **Pipeline Steps:**
25295
- 1. **Create Worktree**: Creates an isolated git worktree for safe import
25296
- 2. **Classify**: Moves CSVs from import to pending directory (optional, skip with skipClassify)
24861
+ 1. **Classify**: Moves CSVs from import/incoming to import/pending (optional, skip with skipClassify)
24862
+ 2. **Account Declarations**: Ensures all required accounts are declared in year journal
25297
24863
  3. **Dry Run**: Validates all transactions have known accounts
25298
- 4. **Import**: Imports transactions to the journal
24864
+ 4. **Import**: Imports transactions to the journal (moves CSVs to import/done)
25299
24865
  5. **Reconcile**: Validates closing balance matches CSV metadata
25300
- 6. **Merge**: Merges worktree to main with --no-ff
25301
- 7. **Cleanup**: Removes worktree (or preserves on error)
25302
24866
 
25303
- **Safety Features:**
25304
- - All changes happen in isolated worktree
25305
- - If any step fails, worktree is preserved by default for debugging
25306
- - Balance reconciliation ensures data integrity
25307
- - Atomic commit with merge --no-ff preserves history
25308
-
25309
- **Worktree Cleanup:**
25310
- - On success: Worktree is always cleaned up
25311
- - On error (default): Worktree is kept at /tmp/import-worktree-<uuid> for debugging
25312
- - On error (--keepWorktreeOnError false): Worktree is removed (old behavior)
25313
- - Manual cleanup: git worktree remove /tmp/import-worktree-<uuid>
25314
- - Auto cleanup: System reboot (worktrees are in /tmp)
24867
+ **Important:**
24868
+ - All changes remain uncommitted in your working directory
24869
+ - If any step fails, changes remain in place for inspection
24870
+ - CSV files move from incoming/ \u2192 pending/ \u2192 done/ during the process
25315
24871
 
25316
24872
  **Logging:**
25317
- - All operations are logged to .memory/import-<timestamp>.md
25318
- - Log includes full command output, timing, and error details
25319
- - Log path is included in tool output for easy access
25320
- - NO console output (avoids polluting OpenCode TUI)
24873
+ - All operations logged to .memory/import-<timestamp>.md
24874
+ - Log includes command output, timing, and error details
25321
24875
 
25322
24876
  **Usage:**
25323
- - Basic: import-pipeline (processes all pending CSVs)
24877
+ - Basic: import-pipeline (processes all CSVs in incoming/)
25324
24878
  - Filtered: import-pipeline --provider ubs --currency chf
25325
- - With manual balance: import-pipeline --closingBalance "CHF 1234.56"
25326
- - Skip classify: import-pipeline --skipClassify true
25327
- - Always cleanup: import-pipeline --keepWorktreeOnError false`,
24879
+ - Manual balance: import-pipeline --closingBalance "CHF 1234.56"
24880
+ - Skip classify: import-pipeline --skipClassify true`,
25328
24881
  args: {
25329
24882
  provider: tool.schema.string().optional().describe('Filter by provider (e.g., "ubs", "revolut")'),
25330
24883
  currency: tool.schema.string().optional().describe('Filter by currency (e.g., "chf", "eur")'),
25331
24884
  closingBalance: tool.schema.string().optional().describe("Manual closing balance override (if not in CSV metadata)"),
25332
24885
  account: tool.schema.string().optional().describe("Manual account override (auto-detected from rules file if not provided)"),
25333
- skipClassify: tool.schema.boolean().optional().describe("Skip the classify step (default: false)"),
25334
- keepWorktreeOnError: tool.schema.boolean().optional().describe("Keep worktree on error for debugging (default: true)")
24886
+ skipClassify: tool.schema.boolean().optional().describe("Skip the classify step (default: false)")
25335
24887
  },
25336
24888
  async execute(params, context) {
25337
24889
  const { directory, agent } = context;
@@ -25340,22 +24892,21 @@ This tool orchestrates the full import workflow in an isolated git worktree:
25340
24892
  currency: params.currency,
25341
24893
  closingBalance: params.closingBalance,
25342
24894
  account: params.account,
25343
- skipClassify: params.skipClassify,
25344
- keepWorktreeOnError: params.keepWorktreeOnError
24895
+ skipClassify: params.skipClassify
25345
24896
  });
25346
24897
  }
25347
24898
  });
25348
24899
  // src/tools/init-directories.ts
25349
- import * as fs15 from "fs";
25350
- import * as path14 from "path";
24900
+ import * as fs13 from "fs";
24901
+ import * as path13 from "path";
25351
24902
  async function initDirectories(directory) {
25352
24903
  try {
25353
24904
  const config2 = loadImportConfig(directory);
25354
24905
  const directoriesCreated = [];
25355
24906
  const gitkeepFiles = [];
25356
- const importBase = path14.join(directory, "import");
25357
- if (!fs15.existsSync(importBase)) {
25358
- fs15.mkdirSync(importBase, { recursive: true });
24907
+ const importBase = path13.join(directory, "import");
24908
+ if (!fs13.existsSync(importBase)) {
24909
+ fs13.mkdirSync(importBase, { recursive: true });
25359
24910
  directoriesCreated.push("import");
25360
24911
  }
25361
24912
  const pathsToCreate = [
@@ -25365,20 +24916,20 @@ async function initDirectories(directory) {
25365
24916
  { key: "unrecognized", path: config2.paths.unrecognized }
25366
24917
  ];
25367
24918
  for (const { path: dirPath } of pathsToCreate) {
25368
- const fullPath = path14.join(directory, dirPath);
25369
- if (!fs15.existsSync(fullPath)) {
25370
- fs15.mkdirSync(fullPath, { recursive: true });
24919
+ const fullPath = path13.join(directory, dirPath);
24920
+ if (!fs13.existsSync(fullPath)) {
24921
+ fs13.mkdirSync(fullPath, { recursive: true });
25371
24922
  directoriesCreated.push(dirPath);
25372
24923
  }
25373
- const gitkeepPath = path14.join(fullPath, ".gitkeep");
25374
- if (!fs15.existsSync(gitkeepPath)) {
25375
- fs15.writeFileSync(gitkeepPath, "");
25376
- gitkeepFiles.push(path14.join(dirPath, ".gitkeep"));
24924
+ const gitkeepPath = path13.join(fullPath, ".gitkeep");
24925
+ if (!fs13.existsSync(gitkeepPath)) {
24926
+ fs13.writeFileSync(gitkeepPath, "");
24927
+ gitkeepFiles.push(path13.join(dirPath, ".gitkeep"));
25377
24928
  }
25378
24929
  }
25379
- const gitignorePath = path14.join(importBase, ".gitignore");
24930
+ const gitignorePath = path13.join(importBase, ".gitignore");
25380
24931
  let gitignoreCreated = false;
25381
- if (!fs15.existsSync(gitignorePath)) {
24932
+ if (!fs13.existsSync(gitignorePath)) {
25382
24933
  const gitignoreContent = `# Ignore CSV/PDF files in temporary directories
25383
24934
  /incoming/*.csv
25384
24935
  /incoming/*.pdf
@@ -25396,7 +24947,7 @@ async function initDirectories(directory) {
25396
24947
  .DS_Store
25397
24948
  Thumbs.db
25398
24949
  `;
25399
- fs15.writeFileSync(gitignorePath, gitignoreContent);
24950
+ fs13.writeFileSync(gitignorePath, gitignoreContent);
25400
24951
  gitignoreCreated = true;
25401
24952
  }
25402
24953
  const parts = [];
@@ -25472,8 +25023,8 @@ You can now drop CSV files into import/incoming/ and run import-pipeline.`);
25472
25023
  }
25473
25024
  });
25474
25025
  // src/index.ts
25475
- var __dirname2 = dirname6(fileURLToPath3(import.meta.url));
25476
- var AGENT_FILE = join13(__dirname2, "..", "agent", "accountant.md");
25026
+ var __dirname2 = dirname5(fileURLToPath3(import.meta.url));
25027
+ var AGENT_FILE = join12(__dirname2, "..", "agent", "accountant.md");
25477
25028
  var AccountantPlugin = async () => {
25478
25029
  const agent = loadAgent(AGENT_FILE);
25479
25030
  return {