@fuzzle/opencode-accountant 0.4.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.
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";
@@ -17509,7 +17509,8 @@ function validatePaths(paths) {
17509
17509
  pending: pathsObj.pending,
17510
17510
  done: pathsObj.done,
17511
17511
  unrecognized: pathsObj.unrecognized,
17512
- rules: pathsObj.rules
17512
+ rules: pathsObj.rules,
17513
+ logs: pathsObj.logs
17513
17514
  };
17514
17515
  }
17515
17516
  function validateDetectionRule(providerName, index, rule) {
@@ -17639,6 +17640,9 @@ function loadImportConfig(directory) {
17639
17640
  for (const [name, config2] of Object.entries(providersObj)) {
17640
17641
  providers[name] = validateProviderConfig(name, config2);
17641
17642
  }
17643
+ if (!paths.logs) {
17644
+ paths.logs = ".memory";
17645
+ }
17642
17646
  return { paths, providers };
17643
17647
  }
17644
17648
 
@@ -17747,242 +17751,22 @@ function detectProvider(filename, content, config2) {
17747
17751
  return null;
17748
17752
  }
17749
17753
 
17750
- // src/utils/worktreeManager.ts
17751
- import { spawnSync } from "child_process";
17752
-
17753
- // node_modules/uuid/dist-node/stringify.js
17754
- var byteToHex = [];
17755
- for (let i2 = 0;i2 < 256; ++i2) {
17756
- byteToHex.push((i2 + 256).toString(16).slice(1));
17757
- }
17758
- function unsafeStringify(arr, offset = 0) {
17759
- 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();
17760
- }
17761
-
17762
- // node_modules/uuid/dist-node/rng.js
17763
- import { randomFillSync } from "crypto";
17764
- var rnds8Pool = new Uint8Array(256);
17765
- var poolPtr = rnds8Pool.length;
17766
- function rng() {
17767
- if (poolPtr > rnds8Pool.length - 16) {
17768
- randomFillSync(rnds8Pool);
17769
- poolPtr = 0;
17770
- }
17771
- return rnds8Pool.slice(poolPtr, poolPtr += 16);
17772
- }
17773
-
17774
- // node_modules/uuid/dist-node/native.js
17775
- import { randomUUID } from "crypto";
17776
- var native_default = { randomUUID };
17777
-
17778
- // node_modules/uuid/dist-node/v4.js
17779
- function _v4(options, buf, offset) {
17780
- options = options || {};
17781
- const rnds = options.random ?? options.rng?.() ?? rng();
17782
- if (rnds.length < 16) {
17783
- throw new Error("Random bytes length must be >= 16");
17784
- }
17785
- rnds[6] = rnds[6] & 15 | 64;
17786
- rnds[8] = rnds[8] & 63 | 128;
17787
- if (buf) {
17788
- offset = offset || 0;
17789
- if (offset < 0 || offset + 16 > buf.length) {
17790
- throw new RangeError(`UUID byte range ${offset}:${offset + 15} is out of buffer bounds`);
17791
- }
17792
- for (let i2 = 0;i2 < 16; ++i2) {
17793
- buf[offset + i2] = rnds[i2];
17794
- }
17795
- return buf;
17796
- }
17797
- return unsafeStringify(rnds);
17798
- }
17799
- function v4(options, buf, offset) {
17800
- if (native_default.randomUUID && !buf && !options) {
17801
- return native_default.randomUUID();
17802
- }
17803
- return _v4(options, buf, offset);
17804
- }
17805
- var v4_default = v4;
17806
- // src/utils/worktreeManager.ts
17754
+ // src/utils/fileUtils.ts
17807
17755
  import * as fs4 from "fs";
17808
17756
  import * as path5 from "path";
17809
- function execGit(args, cwd) {
17810
- const result = spawnSync("git", args, { cwd, encoding: "utf-8" });
17811
- if (result.status !== 0) {
17812
- throw new Error(result.stderr || result.stdout || `git ${args[0]} failed`);
17813
- }
17814
- return (result.stdout || "").trim();
17815
- }
17816
- function execGitSafe(args, cwd) {
17817
- const result = spawnSync("git", args, { cwd, encoding: "utf-8" });
17818
- if (result.status !== 0) {
17819
- return { success: false, output: result.stderr || result.stdout || `git ${args[0]} failed` };
17820
- }
17821
- return { success: true, output: (result.stdout || "").trim() };
17822
- }
17823
- function copyIncomingFiles(mainRepoPath, worktreePath) {
17824
- const sourceDir = path5.join(mainRepoPath, "import/incoming");
17825
- const targetDir = path5.join(worktreePath, "import/incoming");
17826
- if (!fs4.existsSync(sourceDir)) {
17827
- return;
17828
- }
17829
- fs4.mkdirSync(targetDir, { recursive: true });
17830
- const entries = fs4.readdirSync(sourceDir, { withFileTypes: true });
17831
- let copiedCount = 0;
17832
- for (const entry of entries) {
17833
- if (entry.isFile() && !entry.name.startsWith(".")) {
17834
- const srcPath = path5.join(sourceDir, entry.name);
17835
- const destPath = path5.join(targetDir, entry.name);
17836
- fs4.copyFileSync(srcPath, destPath);
17837
- copiedCount++;
17838
- }
17839
- }
17840
- if (copiedCount > 0) {
17841
- console.log(`[INFO] Copied ${copiedCount} file(s) from import/incoming/ to worktree`);
17842
- }
17843
- }
17844
- function createImportWorktree(mainRepoPath, options = {}) {
17845
- const baseDir = options.baseDir ?? "/tmp";
17846
- const uuid3 = v4_default();
17847
- const branch = `import-${uuid3}`;
17848
- const worktreePath = path5.join(baseDir, `import-worktree-${uuid3}`);
17849
- try {
17850
- execGit(["rev-parse", "--git-dir"], mainRepoPath);
17851
- } catch {
17852
- throw new Error(`Not a git repository: ${mainRepoPath}`);
17853
- }
17854
- execGit(["branch", branch], mainRepoPath);
17855
- try {
17856
- execGit(["worktree", "add", worktreePath, branch], mainRepoPath);
17857
- } catch (error45) {
17858
- execGitSafe(["branch", "-D", branch], mainRepoPath);
17859
- throw error45;
17860
- }
17861
- copyIncomingFiles(mainRepoPath, worktreePath);
17862
- return {
17863
- path: worktreePath,
17864
- branch,
17865
- uuid: uuid3,
17866
- mainRepoPath
17867
- };
17868
- }
17869
- function mergeWorktree(context, commitMessage) {
17870
- const status = execGit(["status", "--porcelain"], context.path);
17871
- if (status.length > 0) {
17872
- execGit(["add", "-A"], context.path);
17873
- execGit(["commit", "-m", commitMessage], context.path);
17874
- }
17875
- const currentBranch = execGit(["rev-parse", "--abbrev-ref", "HEAD"], context.mainRepoPath);
17876
- execGit(["merge", "--no-ff", context.branch, "-m", commitMessage], context.mainRepoPath);
17877
- if (currentBranch !== "main" && currentBranch !== "master") {
17878
- execGit(["checkout", currentBranch], context.mainRepoPath);
17879
- }
17880
- }
17881
- function removeWorktree(context, force = false) {
17882
- const forceFlag = force ? "--force" : "";
17883
- const args = ["worktree", "remove", context.path];
17884
- if (forceFlag) {
17885
- args.push(forceFlag);
17886
- }
17887
- const removeResult = execGitSafe(args, context.mainRepoPath);
17888
- if (!removeResult.success) {
17889
- if (!fs4.existsSync(context.path)) {} else {
17890
- return { success: false, error: `Failed to remove worktree: ${removeResult.output}` };
17891
- }
17892
- }
17893
- execGitSafe(["worktree", "prune"], context.mainRepoPath);
17894
- const branchResult = execGitSafe(["branch", "-D", context.branch], context.mainRepoPath);
17895
- if (!branchResult.success) {
17896
- if (!branchResult.output.includes("not found")) {
17897
- return { success: false, error: `Failed to delete branch: ${branchResult.output}` };
17898
- }
17899
- }
17900
- return { success: true };
17901
- }
17902
- function isInWorktree(directory) {
17903
- try {
17904
- const gitDir = execGit(["rev-parse", "--git-dir"], directory);
17905
- return gitDir.includes(".git/worktrees/");
17906
- } catch {
17907
- return false;
17908
- }
17909
- }
17910
- async function withWorktree(directory, operation) {
17911
- let createdWorktree = null;
17912
- try {
17913
- createdWorktree = createImportWorktree(directory);
17914
- const result = await operation(createdWorktree);
17915
- return result;
17916
- } finally {
17917
- if (createdWorktree) {
17918
- removeWorktree(createdWorktree, true);
17919
- }
17920
- }
17921
- }
17922
-
17923
- // src/utils/fileUtils.ts
17924
- import * as fs5 from "fs";
17925
- import * as path6 from "path";
17926
17757
  function findCSVFiles(importsDir) {
17927
- if (!fs5.existsSync(importsDir)) {
17758
+ if (!fs4.existsSync(importsDir)) {
17928
17759
  return [];
17929
17760
  }
17930
- return fs5.readdirSync(importsDir).filter((file2) => file2.toLowerCase().endsWith(".csv")).filter((file2) => {
17931
- const fullPath = path6.join(importsDir, file2);
17932
- 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();
17933
17764
  });
17934
17765
  }
17935
17766
  function ensureDirectory(dirPath) {
17936
- if (!fs5.existsSync(dirPath)) {
17937
- fs5.mkdirSync(dirPath, { recursive: true });
17938
- }
17939
- }
17940
- function syncCSVFilesToWorktree(mainRepoPath, worktreePath, importDir) {
17941
- const result = {
17942
- synced: [],
17943
- errors: []
17944
- };
17945
- const mainImportDir = path6.join(mainRepoPath, importDir);
17946
- const worktreeImportDir = path6.join(worktreePath, importDir);
17947
- const csvFiles = findCSVFiles(mainImportDir);
17948
- if (csvFiles.length === 0) {
17949
- return result;
17950
- }
17951
- ensureDirectory(worktreeImportDir);
17952
- for (const file2 of csvFiles) {
17953
- try {
17954
- const sourcePath = path6.join(mainImportDir, file2);
17955
- const destPath = path6.join(worktreeImportDir, file2);
17956
- fs5.copyFileSync(sourcePath, destPath);
17957
- result.synced.push(file2);
17958
- } catch (error45) {
17959
- const errorMsg = error45 instanceof Error ? error45.message : String(error45);
17960
- result.errors.push({ file: file2, error: errorMsg });
17961
- }
17962
- }
17963
- return result;
17964
- }
17965
- function cleanupProcessedCSVFiles(mainRepoPath, importDir) {
17966
- const result = {
17967
- deleted: [],
17968
- errors: []
17969
- };
17970
- const mainImportDir = path6.join(mainRepoPath, importDir);
17971
- const csvFiles = findCSVFiles(mainImportDir);
17972
- if (csvFiles.length === 0) {
17973
- return result;
17974
- }
17975
- for (const file2 of csvFiles) {
17976
- try {
17977
- const filePath = path6.join(mainImportDir, file2);
17978
- fs5.unlinkSync(filePath);
17979
- result.deleted.push(file2);
17980
- } catch (error45) {
17981
- const errorMsg = error45 instanceof Error ? error45.message : String(error45);
17982
- result.errors.push({ file: file2, error: errorMsg });
17983
- }
17767
+ if (!fs4.existsSync(dirPath)) {
17768
+ fs4.mkdirSync(dirPath, { recursive: true });
17984
17769
  }
17985
- return result;
17986
17770
  }
17987
17771
 
17988
17772
  // src/tools/classify-statements.ts
@@ -18022,20 +17806,20 @@ function planMoves(csvFiles, importsDir, pendingDir, unrecognizedDir, config2) {
18022
17806
  const plannedMoves = [];
18023
17807
  const collisions = [];
18024
17808
  for (const filename of csvFiles) {
18025
- const sourcePath = path7.join(importsDir, filename);
18026
- const content = fs6.readFileSync(sourcePath, "utf-8");
17809
+ const sourcePath = path6.join(importsDir, filename);
17810
+ const content = fs5.readFileSync(sourcePath, "utf-8");
18027
17811
  const detection = detectProvider(filename, content, config2);
18028
17812
  let targetPath;
18029
17813
  let targetFilename;
18030
17814
  if (detection) {
18031
17815
  targetFilename = detection.outputFilename || filename;
18032
- const targetDir = path7.join(pendingDir, detection.provider, detection.currency);
18033
- targetPath = path7.join(targetDir, targetFilename);
17816
+ const targetDir = path6.join(pendingDir, detection.provider, detection.currency);
17817
+ targetPath = path6.join(targetDir, targetFilename);
18034
17818
  } else {
18035
17819
  targetFilename = filename;
18036
- targetPath = path7.join(unrecognizedDir, filename);
17820
+ targetPath = path6.join(unrecognizedDir, filename);
18037
17821
  }
18038
- if (fs6.existsSync(targetPath)) {
17822
+ if (fs5.existsSync(targetPath)) {
18039
17823
  collisions.push({
18040
17824
  filename,
18041
17825
  existingPath: targetPath
@@ -18056,28 +17840,28 @@ function executeMoves(plannedMoves, config2, unrecognizedDir) {
18056
17840
  const unrecognized = [];
18057
17841
  for (const move of plannedMoves) {
18058
17842
  if (move.detection) {
18059
- const targetDir = path7.dirname(move.targetPath);
17843
+ const targetDir = path6.dirname(move.targetPath);
18060
17844
  ensureDirectory(targetDir);
18061
- fs6.renameSync(move.sourcePath, move.targetPath);
17845
+ fs5.renameSync(move.sourcePath, move.targetPath);
18062
17846
  classified.push({
18063
17847
  filename: move.targetFilename,
18064
17848
  originalFilename: move.detection.outputFilename ? move.filename : undefined,
18065
17849
  provider: move.detection.provider,
18066
17850
  currency: move.detection.currency,
18067
- 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)
18068
17852
  });
18069
17853
  } else {
18070
17854
  ensureDirectory(unrecognizedDir);
18071
- fs6.renameSync(move.sourcePath, move.targetPath);
17855
+ fs5.renameSync(move.sourcePath, move.targetPath);
18072
17856
  unrecognized.push({
18073
17857
  filename: move.filename,
18074
- targetPath: path7.join(config2.paths.unrecognized, move.filename)
17858
+ targetPath: path6.join(config2.paths.unrecognized, move.filename)
18075
17859
  });
18076
17860
  }
18077
17861
  }
18078
17862
  return { classified, unrecognized };
18079
17863
  }
18080
- async function classifyStatements(directory, agent, configLoader = loadImportConfig, worktreeChecker = isInWorktree) {
17864
+ async function classifyStatements(directory, agent, configLoader = loadImportConfig) {
18081
17865
  const restrictionError = checkAccountantAgent(agent, "classify statements", {
18082
17866
  classified: [],
18083
17867
  unrecognized: []
@@ -18085,9 +17869,6 @@ async function classifyStatements(directory, agent, configLoader = loadImportCon
18085
17869
  if (restrictionError) {
18086
17870
  return restrictionError;
18087
17871
  }
18088
- if (!worktreeChecker(directory)) {
18089
- return buildErrorResult2("classify-statements must be run inside an import worktree", "Use import-pipeline tool to orchestrate the full workflow");
18090
- }
18091
17872
  let config2;
18092
17873
  try {
18093
17874
  config2 = configLoader(directory);
@@ -18095,9 +17876,9 @@ async function classifyStatements(directory, agent, configLoader = loadImportCon
18095
17876
  const errorMessage = err instanceof Error ? err.message : String(err);
18096
17877
  return buildErrorResult2(errorMessage);
18097
17878
  }
18098
- const importsDir = path7.join(directory, config2.paths.import);
18099
- const pendingDir = path7.join(directory, config2.paths.pending);
18100
- 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);
18101
17882
  const csvFiles = findCSVFiles(importsDir);
18102
17883
  if (csvFiles.length === 0) {
18103
17884
  return buildSuccessResult2([], [], `No CSV files found in ${config2.paths.import}`);
@@ -18118,8 +17899,8 @@ var classify_statements_default = tool({
18118
17899
  }
18119
17900
  });
18120
17901
  // src/tools/import-statements.ts
18121
- import * as fs10 from "fs";
18122
- import * as path10 from "path";
17902
+ import * as fs9 from "fs";
17903
+ import * as path9 from "path";
18123
17904
 
18124
17905
  // node_modules/minimatch/dist/esm/index.js
18125
17906
  var import_brace_expansion = __toESM(require_brace_expansion(), 1);
@@ -18712,11 +18493,11 @@ var qmarksTestNoExtDot = ([$0]) => {
18712
18493
  return (f) => f.length === len && f !== "." && f !== "..";
18713
18494
  };
18714
18495
  var defaultPlatform = typeof process === "object" && process ? typeof process.env === "object" && process.env && process.env.__MINIMATCH_TESTING_PLATFORM__ || process.platform : "posix";
18715
- var path8 = {
18496
+ var path7 = {
18716
18497
  win32: { sep: "\\" },
18717
18498
  posix: { sep: "/" }
18718
18499
  };
18719
- var sep = defaultPlatform === "win32" ? path8.win32.sep : path8.posix.sep;
18500
+ var sep = defaultPlatform === "win32" ? path7.win32.sep : path7.posix.sep;
18720
18501
  minimatch.sep = sep;
18721
18502
  var GLOBSTAR = Symbol("globstar **");
18722
18503
  minimatch.GLOBSTAR = GLOBSTAR;
@@ -20450,7 +20231,7 @@ class LRUCache {
20450
20231
  // node_modules/path-scurry/dist/esm/index.js
20451
20232
  import { posix, win32 } from "path";
20452
20233
  import { fileURLToPath } from "url";
20453
- 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";
20454
20235
  import * as actualFS from "fs";
20455
20236
  import { lstat, readdir, readlink, realpath } from "fs/promises";
20456
20237
 
@@ -21122,7 +20903,7 @@ var realpathSync = rps.native;
21122
20903
  var defaultFS = {
21123
20904
  lstatSync,
21124
20905
  readdir: readdirCB,
21125
- readdirSync: readdirSync4,
20906
+ readdirSync: readdirSync3,
21126
20907
  readlinkSync,
21127
20908
  realpathSync,
21128
20909
  promises: {
@@ -21321,12 +21102,12 @@ class PathBase {
21321
21102
  childrenCache() {
21322
21103
  return this.#children;
21323
21104
  }
21324
- resolve(path9) {
21325
- if (!path9) {
21105
+ resolve(path8) {
21106
+ if (!path8) {
21326
21107
  return this;
21327
21108
  }
21328
- const rootPath = this.getRootString(path9);
21329
- const dir = path9.substring(rootPath.length);
21109
+ const rootPath = this.getRootString(path8);
21110
+ const dir = path8.substring(rootPath.length);
21330
21111
  const dirParts = dir.split(this.splitSep);
21331
21112
  const result = rootPath ? this.getRoot(rootPath).#resolveParts(dirParts) : this.#resolveParts(dirParts);
21332
21113
  return result;
@@ -21855,8 +21636,8 @@ class PathWin32 extends PathBase {
21855
21636
  newChild(name, type2 = UNKNOWN, opts = {}) {
21856
21637
  return new PathWin32(name, type2, this.root, this.roots, this.nocase, this.childrenCache(), opts);
21857
21638
  }
21858
- getRootString(path9) {
21859
- return win32.parse(path9).root;
21639
+ getRootString(path8) {
21640
+ return win32.parse(path8).root;
21860
21641
  }
21861
21642
  getRoot(rootPath) {
21862
21643
  rootPath = uncToDrive(rootPath.toUpperCase());
@@ -21882,8 +21663,8 @@ class PathPosix extends PathBase {
21882
21663
  constructor(name, type2 = UNKNOWN, root, roots, nocase, children, opts) {
21883
21664
  super(name, type2, root, roots, nocase, children, opts);
21884
21665
  }
21885
- getRootString(path9) {
21886
- return path9.startsWith("/") ? "/" : "";
21666
+ getRootString(path8) {
21667
+ return path8.startsWith("/") ? "/" : "";
21887
21668
  }
21888
21669
  getRoot(_rootPath) {
21889
21670
  return this.root;
@@ -21903,8 +21684,8 @@ class PathScurryBase {
21903
21684
  #children;
21904
21685
  nocase;
21905
21686
  #fs;
21906
- constructor(cwd = process.cwd(), pathImpl, sep2, { nocase, childrenCacheSize = 16 * 1024, fs: fs7 = defaultFS } = {}) {
21907
- this.#fs = fsFromOption(fs7);
21687
+ constructor(cwd = process.cwd(), pathImpl, sep2, { nocase, childrenCacheSize = 16 * 1024, fs: fs6 = defaultFS } = {}) {
21688
+ this.#fs = fsFromOption(fs6);
21908
21689
  if (cwd instanceof URL || cwd.startsWith("file://")) {
21909
21690
  cwd = fileURLToPath(cwd);
21910
21691
  }
@@ -21940,11 +21721,11 @@ class PathScurryBase {
21940
21721
  }
21941
21722
  this.cwd = prev;
21942
21723
  }
21943
- depth(path9 = this.cwd) {
21944
- if (typeof path9 === "string") {
21945
- path9 = this.cwd.resolve(path9);
21724
+ depth(path8 = this.cwd) {
21725
+ if (typeof path8 === "string") {
21726
+ path8 = this.cwd.resolve(path8);
21946
21727
  }
21947
- return path9.depth();
21728
+ return path8.depth();
21948
21729
  }
21949
21730
  childrenCache() {
21950
21731
  return this.#children;
@@ -22360,9 +22141,9 @@ class PathScurryBase {
22360
22141
  process2();
22361
22142
  return results;
22362
22143
  }
22363
- chdir(path9 = this.cwd) {
22144
+ chdir(path8 = this.cwd) {
22364
22145
  const oldCwd = this.cwd;
22365
- this.cwd = typeof path9 === "string" ? this.cwd.resolve(path9) : path9;
22146
+ this.cwd = typeof path8 === "string" ? this.cwd.resolve(path8) : path8;
22366
22147
  this.cwd[setAsCwd](oldCwd);
22367
22148
  }
22368
22149
  }
@@ -22380,8 +22161,8 @@ class PathScurryWin32 extends PathScurryBase {
22380
22161
  parseRootPath(dir) {
22381
22162
  return win32.parse(dir).root.toUpperCase();
22382
22163
  }
22383
- newRoot(fs7) {
22384
- 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 });
22385
22166
  }
22386
22167
  isAbsolute(p) {
22387
22168
  return p.startsWith("/") || p.startsWith("\\") || /^[a-z]:(\/|\\)/i.test(p);
@@ -22398,8 +22179,8 @@ class PathScurryPosix extends PathScurryBase {
22398
22179
  parseRootPath(_dir) {
22399
22180
  return "/";
22400
22181
  }
22401
- newRoot(fs7) {
22402
- 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 });
22403
22184
  }
22404
22185
  isAbsolute(p) {
22405
22186
  return p.startsWith("/");
@@ -22651,8 +22432,8 @@ class MatchRecord {
22651
22432
  this.store.set(target, current === undefined ? n : n & current);
22652
22433
  }
22653
22434
  entries() {
22654
- return [...this.store.entries()].map(([path9, n]) => [
22655
- path9,
22435
+ return [...this.store.entries()].map(([path8, n]) => [
22436
+ path8,
22656
22437
  !!(n & 2),
22657
22438
  !!(n & 1)
22658
22439
  ]);
@@ -22855,9 +22636,9 @@ class GlobUtil {
22855
22636
  signal;
22856
22637
  maxDepth;
22857
22638
  includeChildMatches;
22858
- constructor(patterns, path9, opts) {
22639
+ constructor(patterns, path8, opts) {
22859
22640
  this.patterns = patterns;
22860
- this.path = path9;
22641
+ this.path = path8;
22861
22642
  this.opts = opts;
22862
22643
  this.#sep = !opts.posix && opts.platform === "win32" ? "\\" : "/";
22863
22644
  this.includeChildMatches = opts.includeChildMatches !== false;
@@ -22876,11 +22657,11 @@ class GlobUtil {
22876
22657
  });
22877
22658
  }
22878
22659
  }
22879
- #ignored(path9) {
22880
- return this.seen.has(path9) || !!this.#ignore?.ignored?.(path9);
22660
+ #ignored(path8) {
22661
+ return this.seen.has(path8) || !!this.#ignore?.ignored?.(path8);
22881
22662
  }
22882
- #childrenIgnored(path9) {
22883
- return !!this.#ignore?.childrenIgnored?.(path9);
22663
+ #childrenIgnored(path8) {
22664
+ return !!this.#ignore?.childrenIgnored?.(path8);
22884
22665
  }
22885
22666
  pause() {
22886
22667
  this.paused = true;
@@ -23093,8 +22874,8 @@ class GlobUtil {
23093
22874
 
23094
22875
  class GlobWalker extends GlobUtil {
23095
22876
  matches = new Set;
23096
- constructor(patterns, path9, opts) {
23097
- super(patterns, path9, opts);
22877
+ constructor(patterns, path8, opts) {
22878
+ super(patterns, path8, opts);
23098
22879
  }
23099
22880
  matchEmit(e) {
23100
22881
  this.matches.add(e);
@@ -23132,8 +22913,8 @@ class GlobWalker extends GlobUtil {
23132
22913
 
23133
22914
  class GlobStream extends GlobUtil {
23134
22915
  results;
23135
- constructor(patterns, path9, opts) {
23136
- super(patterns, path9, opts);
22916
+ constructor(patterns, path8, opts) {
22917
+ super(patterns, path8, opts);
23137
22918
  this.results = new Minipass({
23138
22919
  signal: this.signal,
23139
22920
  objectMode: true
@@ -23401,8 +23182,8 @@ var glob = Object.assign(glob_, {
23401
23182
  glob.glob = glob;
23402
23183
 
23403
23184
  // src/utils/rulesMatcher.ts
23404
- import * as fs7 from "fs";
23405
- import * as path9 from "path";
23185
+ import * as fs6 from "fs";
23186
+ import * as path8 from "path";
23406
23187
  function parseSourceDirective(content) {
23407
23188
  const match2 = content.match(/^source\s+([^\n#]+)/m);
23408
23189
  if (!match2) {
@@ -23411,28 +23192,28 @@ function parseSourceDirective(content) {
23411
23192
  return match2[1].trim();
23412
23193
  }
23413
23194
  function resolveSourcePath(sourcePath, rulesFilePath) {
23414
- if (path9.isAbsolute(sourcePath)) {
23195
+ if (path8.isAbsolute(sourcePath)) {
23415
23196
  return sourcePath;
23416
23197
  }
23417
- const rulesDir = path9.dirname(rulesFilePath);
23418
- return path9.resolve(rulesDir, sourcePath);
23198
+ const rulesDir = path8.dirname(rulesFilePath);
23199
+ return path8.resolve(rulesDir, sourcePath);
23419
23200
  }
23420
23201
  function loadRulesMapping(rulesDir) {
23421
23202
  const mapping = {};
23422
- if (!fs7.existsSync(rulesDir)) {
23203
+ if (!fs6.existsSync(rulesDir)) {
23423
23204
  return mapping;
23424
23205
  }
23425
- const files = fs7.readdirSync(rulesDir);
23206
+ const files = fs6.readdirSync(rulesDir);
23426
23207
  for (const file2 of files) {
23427
23208
  if (!file2.endsWith(".rules")) {
23428
23209
  continue;
23429
23210
  }
23430
- const rulesFilePath = path9.join(rulesDir, file2);
23431
- const stat = fs7.statSync(rulesFilePath);
23211
+ const rulesFilePath = path8.join(rulesDir, file2);
23212
+ const stat = fs6.statSync(rulesFilePath);
23432
23213
  if (!stat.isFile()) {
23433
23214
  continue;
23434
23215
  }
23435
- const content = fs7.readFileSync(rulesFilePath, "utf-8");
23216
+ const content = fs6.readFileSync(rulesFilePath, "utf-8");
23436
23217
  const sourcePath = parseSourceDirective(content);
23437
23218
  if (!sourcePath) {
23438
23219
  continue;
@@ -23446,22 +23227,22 @@ function findRulesForCsv(csvPath, mapping) {
23446
23227
  if (mapping[csvPath]) {
23447
23228
  return mapping[csvPath];
23448
23229
  }
23449
- const normalizedCsvPath = path9.normalize(csvPath);
23230
+ const normalizedCsvPath = path8.normalize(csvPath);
23450
23231
  for (const [mappedCsv, rulesFile] of Object.entries(mapping)) {
23451
- const normalizedMappedCsv = path9.normalize(mappedCsv);
23232
+ const normalizedMappedCsv = path8.normalize(mappedCsv);
23452
23233
  if (normalizedMappedCsv === normalizedCsvPath) {
23453
23234
  return rulesFile;
23454
23235
  }
23455
23236
  }
23456
23237
  for (const [pattern, rulesFile] of Object.entries(mapping)) {
23457
- if (minimatch(csvPath, pattern) || minimatch(normalizedCsvPath, path9.normalize(pattern))) {
23238
+ if (minimatch(csvPath, pattern) || minimatch(normalizedCsvPath, path8.normalize(pattern))) {
23458
23239
  return rulesFile;
23459
23240
  }
23460
23241
  }
23461
- const csvBasename = path9.basename(csvPath);
23242
+ const csvBasename = path8.basename(csvPath);
23462
23243
  const matches = [];
23463
23244
  for (const rulesFile of Object.values(mapping)) {
23464
- const rulesBasename = path9.basename(rulesFile, ".rules");
23245
+ const rulesBasename = path8.basename(rulesFile, ".rules");
23465
23246
  if (csvBasename.startsWith(rulesBasename)) {
23466
23247
  matches.push({ rulesFile, prefixLength: rulesBasename.length });
23467
23248
  }
@@ -23592,7 +23373,7 @@ async function getAccountBalance(mainJournalPath, account, asOfDate, executor =
23592
23373
  }
23593
23374
 
23594
23375
  // src/utils/rulesParser.ts
23595
- import * as fs8 from "fs";
23376
+ import * as fs7 from "fs";
23596
23377
  function parseSkipRows(rulesContent) {
23597
23378
  const match2 = rulesContent.match(/^skip\s+(\d+)/m);
23598
23379
  return match2 ? parseInt(match2[1], 10) : 0;
@@ -23658,7 +23439,7 @@ function parseAccount1(rulesContent) {
23658
23439
  }
23659
23440
  function getAccountFromRulesFile(rulesFilePath) {
23660
23441
  try {
23661
- const content = fs8.readFileSync(rulesFilePath, "utf-8");
23442
+ const content = fs7.readFileSync(rulesFilePath, "utf-8");
23662
23443
  return parseAccount1(content);
23663
23444
  } catch {
23664
23445
  return null;
@@ -23678,7 +23459,7 @@ function parseRulesFile(rulesContent) {
23678
23459
 
23679
23460
  // src/utils/csvParser.ts
23680
23461
  var import_convert_csv_to_json = __toESM(require_convert_csv_to_json(), 1);
23681
- import * as fs9 from "fs";
23462
+ import * as fs8 from "fs";
23682
23463
 
23683
23464
  // src/utils/balanceUtils.ts
23684
23465
  function parseAmountValue(amountStr) {
@@ -23727,7 +23508,7 @@ function balancesMatch(balance1, balance2) {
23727
23508
 
23728
23509
  // src/utils/csvParser.ts
23729
23510
  function parseCsvFile(csvPath, config2) {
23730
- const csvContent = fs9.readFileSync(csvPath, "utf-8");
23511
+ const csvContent = fs8.readFileSync(csvPath, "utf-8");
23731
23512
  const lines = csvContent.split(`
23732
23513
  `);
23733
23514
  const headerIndex = config2.skipRows;
@@ -23885,21 +23666,21 @@ function buildSuccessResult3(files, summary, message) {
23885
23666
  });
23886
23667
  }
23887
23668
  function findCsvFromRulesFile(rulesFile) {
23888
- const content = fs10.readFileSync(rulesFile, "utf-8");
23669
+ const content = fs9.readFileSync(rulesFile, "utf-8");
23889
23670
  const match2 = content.match(/^source\s+([^\n#]+)/m);
23890
23671
  if (!match2) {
23891
23672
  return null;
23892
23673
  }
23893
23674
  const sourcePath = match2[1].trim();
23894
- const rulesDir = path10.dirname(rulesFile);
23895
- const absolutePattern = path10.resolve(rulesDir, sourcePath);
23675
+ const rulesDir = path9.dirname(rulesFile);
23676
+ const absolutePattern = path9.resolve(rulesDir, sourcePath);
23896
23677
  const matches = glob.sync(absolutePattern);
23897
23678
  if (matches.length === 0) {
23898
23679
  return null;
23899
23680
  }
23900
23681
  matches.sort((a, b) => {
23901
- const aStat = fs10.statSync(a);
23902
- const bStat = fs10.statSync(b);
23682
+ const aStat = fs9.statSync(a);
23683
+ const bStat = fs9.statSync(b);
23903
23684
  return bStat.mtime.getTime() - aStat.mtime.getTime();
23904
23685
  });
23905
23686
  return matches[0];
@@ -23907,7 +23688,7 @@ function findCsvFromRulesFile(rulesFile) {
23907
23688
  async function executeImports(fileResults, directory, pendingDir, doneDir, hledgerExecutor) {
23908
23689
  const importedFiles = [];
23909
23690
  for (const fileResult of fileResults) {
23910
- const rulesFile = fileResult.rulesFile ? path10.join(directory, fileResult.rulesFile) : null;
23691
+ const rulesFile = fileResult.rulesFile ? path9.join(directory, fileResult.rulesFile) : null;
23911
23692
  if (!rulesFile)
23912
23693
  continue;
23913
23694
  const year = fileResult.transactionYear;
@@ -23939,7 +23720,7 @@ async function executeImports(fileResults, directory, pendingDir, doneDir, hledg
23939
23720
  importedFiles.push(importedCsv);
23940
23721
  }
23941
23722
  }
23942
- const mainJournalPath = path10.join(directory, ".hledger.journal");
23723
+ const mainJournalPath = path9.join(directory, ".hledger.journal");
23943
23724
  const validationResult = await validateLedger(mainJournalPath, hledgerExecutor);
23944
23725
  if (!validationResult.valid) {
23945
23726
  return {
@@ -23949,13 +23730,13 @@ async function executeImports(fileResults, directory, pendingDir, doneDir, hledg
23949
23730
  };
23950
23731
  }
23951
23732
  for (const csvFile of importedFiles) {
23952
- const relativePath = path10.relative(pendingDir, csvFile);
23953
- const destPath = path10.join(doneDir, relativePath);
23954
- const destDir = path10.dirname(destPath);
23955
- if (!fs10.existsSync(destDir)) {
23956
- 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 });
23957
23738
  }
23958
- fs10.renameSync(csvFile, destPath);
23739
+ fs9.renameSync(csvFile, destPath);
23959
23740
  }
23960
23741
  return {
23961
23742
  success: true,
@@ -23966,7 +23747,7 @@ async function processCsvFile(csvFile, rulesMapping, directory, hledgerExecutor)
23966
23747
  const rulesFile = findRulesForCsv(csvFile, rulesMapping);
23967
23748
  if (!rulesFile) {
23968
23749
  return {
23969
- csv: path10.relative(directory, csvFile),
23750
+ csv: path9.relative(directory, csvFile),
23970
23751
  rulesFile: null,
23971
23752
  totalTransactions: 0,
23972
23753
  matchedTransactions: 0,
@@ -23977,8 +23758,8 @@ async function processCsvFile(csvFile, rulesMapping, directory, hledgerExecutor)
23977
23758
  const result = await hledgerExecutor(["print", "-f", rulesFile]);
23978
23759
  if (result.exitCode !== 0) {
23979
23760
  return {
23980
- csv: path10.relative(directory, csvFile),
23981
- rulesFile: path10.relative(directory, rulesFile),
23761
+ csv: path9.relative(directory, csvFile),
23762
+ rulesFile: path9.relative(directory, rulesFile),
23982
23763
  totalTransactions: 0,
23983
23764
  matchedTransactions: 0,
23984
23765
  unknownPostings: [],
@@ -23992,8 +23773,8 @@ async function processCsvFile(csvFile, rulesMapping, directory, hledgerExecutor)
23992
23773
  if (years.size > 1) {
23993
23774
  const yearList = Array.from(years).sort().join(", ");
23994
23775
  return {
23995
- csv: path10.relative(directory, csvFile),
23996
- rulesFile: path10.relative(directory, rulesFile),
23776
+ csv: path9.relative(directory, csvFile),
23777
+ rulesFile: path9.relative(directory, rulesFile),
23997
23778
  totalTransactions: transactionCount,
23998
23779
  matchedTransactions: matchedCount,
23999
23780
  unknownPostings: [],
@@ -24003,7 +23784,7 @@ async function processCsvFile(csvFile, rulesMapping, directory, hledgerExecutor)
24003
23784
  const transactionYear = years.size === 1 ? Array.from(years)[0] : undefined;
24004
23785
  if (unknownPostings.length > 0) {
24005
23786
  try {
24006
- const rulesContent = fs10.readFileSync(rulesFile, "utf-8");
23787
+ const rulesContent = fs9.readFileSync(rulesFile, "utf-8");
24007
23788
  const rulesConfig = parseRulesFile(rulesContent);
24008
23789
  const csvRows = parseCsvFile(csvFile, rulesConfig);
24009
23790
  for (const posting of unknownPostings) {
@@ -24020,22 +23801,19 @@ async function processCsvFile(csvFile, rulesMapping, directory, hledgerExecutor)
24020
23801
  }
24021
23802
  }
24022
23803
  return {
24023
- csv: path10.relative(directory, csvFile),
24024
- rulesFile: path10.relative(directory, rulesFile),
23804
+ csv: path9.relative(directory, csvFile),
23805
+ rulesFile: path9.relative(directory, rulesFile),
24025
23806
  totalTransactions: transactionCount,
24026
23807
  matchedTransactions: matchedCount,
24027
23808
  unknownPostings,
24028
23809
  transactionYear
24029
23810
  };
24030
23811
  }
24031
- async function importStatements(directory, agent, options, configLoader = loadImportConfig, hledgerExecutor = defaultHledgerExecutor, worktreeChecker = isInWorktree) {
23812
+ async function importStatements(directory, agent, options, configLoader = loadImportConfig, hledgerExecutor = defaultHledgerExecutor) {
24032
23813
  const restrictionError = checkAccountantAgent(agent, "import statements");
24033
23814
  if (restrictionError) {
24034
23815
  return restrictionError;
24035
23816
  }
24036
- if (!worktreeChecker(directory)) {
24037
- return buildErrorResult3("import-statements must be run inside an import worktree", "Use import-pipeline tool to orchestrate the full workflow");
24038
- }
24039
23817
  let config2;
24040
23818
  try {
24041
23819
  config2 = configLoader(directory);
@@ -24043,9 +23821,9 @@ async function importStatements(directory, agent, options, configLoader = loadIm
24043
23821
  const errorMessage = `Failed to load configuration: ${error45 instanceof Error ? error45.message : String(error45)}`;
24044
23822
  return buildErrorResult3(errorMessage, 'Ensure config/import/providers.yaml exists with required paths including "rules"');
24045
23823
  }
24046
- const pendingDir = path10.join(directory, config2.paths.pending);
24047
- const rulesDir = path10.join(directory, config2.paths.rules);
24048
- 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);
24049
23827
  const rulesMapping = loadRulesMapping(rulesDir);
24050
23828
  const csvFiles = findCsvFiles(pendingDir, options.provider, options.currency);
24051
23829
  if (csvFiles.length === 0) {
@@ -24089,8 +23867,8 @@ async function importStatements(directory, agent, options, configLoader = loadIm
24089
23867
  }
24090
23868
  for (const [_rulesFile, matchingCSVs] of rulesFileToCSVs.entries()) {
24091
23869
  matchingCSVs.sort((a, b) => {
24092
- const aStat = fs10.statSync(a);
24093
- const bStat = fs10.statSync(b);
23870
+ const aStat = fs9.statSync(a);
23871
+ const bStat = fs9.statSync(b);
24094
23872
  return bStat.mtime.getTime() - aStat.mtime.getTime();
24095
23873
  });
24096
23874
  const newestCSV = matchingCSVs[0];
@@ -24179,7 +23957,9 @@ This tool processes CSV files in the pending import directory and uses hledger's
24179
23957
  1. Run with checkOnly: true (or no args)
24180
23958
  2. If unknowns found, add rules to the appropriate .rules file
24181
23959
  3. Repeat until no unknowns
24182
- 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.`,
24183
23963
  args: {
24184
23964
  provider: tool.schema.string().optional().describe('Filter by provider (e.g., "revolut", "ubs"). If omitted, process all providers.'),
24185
23965
  currency: tool.schema.string().optional().describe('Filter by currency (e.g., "chf", "eur"). If omitted, process all currencies for the provider.'),
@@ -24195,23 +23975,14 @@ This tool processes CSV files in the pending import directory and uses hledger's
24195
23975
  }
24196
23976
  });
24197
23977
  // src/tools/reconcile-statement.ts
24198
- import * as fs11 from "fs";
24199
- import * as path11 from "path";
23978
+ import * as fs10 from "fs";
23979
+ import * as path10 from "path";
24200
23980
  function buildErrorResult4(params) {
24201
23981
  return JSON.stringify({
24202
23982
  success: false,
24203
23983
  ...params
24204
23984
  });
24205
23985
  }
24206
- function validateWorktree(directory, worktreeChecker) {
24207
- if (!worktreeChecker(directory)) {
24208
- return buildErrorResult4({
24209
- error: "reconcile-statement must be run inside an import worktree",
24210
- hint: "Use import-pipeline tool to orchestrate the full workflow"
24211
- });
24212
- }
24213
- return null;
24214
- }
24215
23986
  function loadConfiguration(directory, configLoader) {
24216
23987
  try {
24217
23988
  const config2 = configLoader(directory);
@@ -24238,14 +24009,14 @@ function findCsvToReconcile(doneDir, options) {
24238
24009
  };
24239
24010
  }
24240
24011
  const csvFile = csvFiles[csvFiles.length - 1];
24241
- const relativePath = path11.relative(path11.dirname(path11.dirname(doneDir)), csvFile);
24012
+ const relativePath = path10.relative(path10.dirname(path10.dirname(doneDir)), csvFile);
24242
24013
  return { csvFile, relativePath };
24243
24014
  }
24244
24015
  function determineClosingBalance(csvFile, config2, options, relativeCsvPath, rulesDir) {
24245
24016
  let metadata;
24246
24017
  try {
24247
- const content = fs11.readFileSync(csvFile, "utf-8");
24248
- const filename = path11.basename(csvFile);
24018
+ const content = fs10.readFileSync(csvFile, "utf-8");
24019
+ const filename = path10.basename(csvFile);
24249
24020
  const detectionResult = detectProvider(filename, content, config2);
24250
24021
  metadata = detectionResult?.metadata;
24251
24022
  } catch {
@@ -24327,7 +24098,7 @@ function tryExtractClosingBalanceFromCSV(csvFile, rulesDir) {
24327
24098
  if (!rulesFile) {
24328
24099
  return null;
24329
24100
  }
24330
- const rulesContent = fs11.readFileSync(rulesFile, "utf-8");
24101
+ const rulesContent = fs10.readFileSync(rulesFile, "utf-8");
24331
24102
  const rulesConfig = parseRulesFile(rulesContent);
24332
24103
  const csvRows = parseCsvFile(csvFile, rulesConfig);
24333
24104
  if (csvRows.length === 0) {
@@ -24384,23 +24155,19 @@ function tryExtractClosingBalanceFromCSV(csvFile, rulesDir) {
24384
24155
  return null;
24385
24156
  }
24386
24157
  }
24387
- async function reconcileStatement(directory, agent, options, configLoader = loadImportConfig, hledgerExecutor = defaultHledgerExecutor, worktreeChecker = isInWorktree) {
24158
+ async function reconcileStatement(directory, agent, options, configLoader = loadImportConfig, hledgerExecutor = defaultHledgerExecutor) {
24388
24159
  const restrictionError = checkAccountantAgent(agent, "reconcile statement");
24389
24160
  if (restrictionError) {
24390
24161
  return restrictionError;
24391
24162
  }
24392
- const worktreeError = validateWorktree(directory, worktreeChecker);
24393
- if (worktreeError) {
24394
- return worktreeError;
24395
- }
24396
24163
  const configResult = loadConfiguration(directory, configLoader);
24397
24164
  if ("error" in configResult) {
24398
24165
  return configResult.error;
24399
24166
  }
24400
24167
  const { config: config2 } = configResult;
24401
- const doneDir = path11.join(directory, config2.paths.done);
24402
- const rulesDir = path11.join(directory, config2.paths.rules);
24403
- 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");
24404
24171
  const csvResult = findCsvToReconcile(doneDir, options);
24405
24172
  if ("error" in csvResult) {
24406
24173
  return csvResult.error;
@@ -24496,7 +24263,6 @@ var reconcile_statement_default = tool({
24496
24263
  description: `ACCOUNTANT AGENT ONLY: Reconcile imported bank statement against closing balance.
24497
24264
 
24498
24265
  This tool validates that the imported transactions result in the correct closing balance.
24499
- It must be run inside an import worktree (use import-pipeline for the full workflow).
24500
24266
 
24501
24267
  **Workflow:**
24502
24268
  1. Finds the most recently imported CSV in the done directory
@@ -24529,17 +24295,16 @@ It must be run inside an import worktree (use import-pipeline for the full workf
24529
24295
  }
24530
24296
  });
24531
24297
  // src/tools/import-pipeline.ts
24532
- import * as fs13 from "fs";
24533
24298
  import * as path12 from "path";
24534
24299
 
24535
24300
  // src/utils/accountDeclarations.ts
24536
- import * as fs12 from "fs";
24301
+ import * as fs11 from "fs";
24537
24302
  function extractAccountsFromRulesFile(rulesPath) {
24538
24303
  const accounts = new Set;
24539
- if (!fs12.existsSync(rulesPath)) {
24304
+ if (!fs11.existsSync(rulesPath)) {
24540
24305
  return accounts;
24541
24306
  }
24542
- const content = fs12.readFileSync(rulesPath, "utf-8");
24307
+ const content = fs11.readFileSync(rulesPath, "utf-8");
24543
24308
  const lines = content.split(`
24544
24309
  `);
24545
24310
  for (const line of lines) {
@@ -24574,10 +24339,10 @@ function sortAccountDeclarations(accounts) {
24574
24339
  return Array.from(accounts).sort((a, b) => a.localeCompare(b));
24575
24340
  }
24576
24341
  function ensureAccountDeclarations(yearJournalPath, accounts) {
24577
- if (!fs12.existsSync(yearJournalPath)) {
24342
+ if (!fs11.existsSync(yearJournalPath)) {
24578
24343
  throw new Error(`Year journal not found: ${yearJournalPath}`);
24579
24344
  }
24580
- const content = fs12.readFileSync(yearJournalPath, "utf-8");
24345
+ const content = fs11.readFileSync(yearJournalPath, "utf-8");
24581
24346
  const lines = content.split(`
24582
24347
  `);
24583
24348
  const existingAccounts = new Set;
@@ -24639,7 +24404,7 @@ function ensureAccountDeclarations(yearJournalPath, accounts) {
24639
24404
  newContent.push("");
24640
24405
  }
24641
24406
  newContent.push(...otherLines);
24642
- fs12.writeFileSync(yearJournalPath, newContent.join(`
24407
+ fs11.writeFileSync(yearJournalPath, newContent.join(`
24643
24408
  `));
24644
24409
  return {
24645
24410
  added: Array.from(missingAccounts).sort(),
@@ -24647,6 +24412,158 @@ function ensureAccountDeclarations(yearJournalPath, accounts) {
24647
24412
  };
24648
24413
  }
24649
24414
 
24415
+ // src/utils/logger.ts
24416
+ import fs12 from "fs/promises";
24417
+ import path11 from "path";
24418
+
24419
+ class MarkdownLogger {
24420
+ buffer = [];
24421
+ logPath;
24422
+ context = {};
24423
+ autoFlush;
24424
+ sectionDepth = 0;
24425
+ constructor(config2) {
24426
+ this.autoFlush = config2.autoFlush ?? true;
24427
+ this.context = config2.context || {};
24428
+ const filename = config2.filename || `import-${this.getTimestamp()}.md`;
24429
+ this.logPath = path11.join(config2.logDir, filename);
24430
+ this.buffer.push(`# Import Pipeline Log`);
24431
+ this.buffer.push(`**Started**: ${new Date().toLocaleString()}`);
24432
+ this.buffer.push("");
24433
+ }
24434
+ startSection(title, level = 2) {
24435
+ this.buffer.push("");
24436
+ this.buffer.push(`${"#".repeat(level + 1)} ${title}`);
24437
+ this.buffer.push(`**Started**: ${this.getTime()}`);
24438
+ this.buffer.push("");
24439
+ this.sectionDepth++;
24440
+ }
24441
+ endSection() {
24442
+ if (this.sectionDepth > 0) {
24443
+ this.buffer.push("");
24444
+ this.buffer.push("---");
24445
+ this.buffer.push("");
24446
+ this.sectionDepth--;
24447
+ }
24448
+ }
24449
+ info(message) {
24450
+ this.buffer.push(message);
24451
+ if (this.autoFlush)
24452
+ this.flushAsync();
24453
+ }
24454
+ warn(message) {
24455
+ this.buffer.push(`\u26A0\uFE0F **WARNING**: ${message}`);
24456
+ if (this.autoFlush)
24457
+ this.flushAsync();
24458
+ }
24459
+ error(message, error45) {
24460
+ this.buffer.push(`\u274C **ERROR**: ${message}`);
24461
+ if (error45) {
24462
+ const errorStr = error45 instanceof Error ? error45.message : String(error45);
24463
+ this.buffer.push("");
24464
+ this.buffer.push("```");
24465
+ this.buffer.push(errorStr);
24466
+ if (error45 instanceof Error && error45.stack) {
24467
+ this.buffer.push("");
24468
+ this.buffer.push(error45.stack);
24469
+ }
24470
+ this.buffer.push("```");
24471
+ this.buffer.push("");
24472
+ }
24473
+ if (this.autoFlush)
24474
+ this.flushAsync();
24475
+ }
24476
+ debug(message) {
24477
+ this.buffer.push(`\uD83D\uDD0D ${message}`);
24478
+ if (this.autoFlush)
24479
+ this.flushAsync();
24480
+ }
24481
+ logStep(stepName, status, details) {
24482
+ const icon = status === "success" ? "\u2705" : status === "error" ? "\u274C" : "\u25B6\uFE0F";
24483
+ const statusText = status.charAt(0).toUpperCase() + status.slice(1);
24484
+ this.buffer.push(`**${stepName}**: ${icon} ${statusText}`);
24485
+ if (details) {
24486
+ this.buffer.push(` ${details}`);
24487
+ }
24488
+ this.buffer.push("");
24489
+ if (this.autoFlush)
24490
+ this.flushAsync();
24491
+ }
24492
+ logCommand(command, output) {
24493
+ this.buffer.push("```bash");
24494
+ this.buffer.push(`$ ${command}`);
24495
+ if (output) {
24496
+ this.buffer.push("");
24497
+ const lines = output.trim().split(`
24498
+ `);
24499
+ if (lines.length > 50) {
24500
+ this.buffer.push(...lines.slice(0, 50));
24501
+ this.buffer.push(`... (${lines.length - 50} more lines omitted)`);
24502
+ } else {
24503
+ this.buffer.push(output.trim());
24504
+ }
24505
+ }
24506
+ this.buffer.push("```");
24507
+ this.buffer.push("");
24508
+ if (this.autoFlush)
24509
+ this.flushAsync();
24510
+ }
24511
+ logResult(data) {
24512
+ this.buffer.push("```json");
24513
+ this.buffer.push(JSON.stringify(data, null, 2));
24514
+ this.buffer.push("```");
24515
+ this.buffer.push("");
24516
+ if (this.autoFlush)
24517
+ this.flushAsync();
24518
+ }
24519
+ setContext(key, value) {
24520
+ this.context[key] = value;
24521
+ }
24522
+ async flush() {
24523
+ if (this.buffer.length === 0)
24524
+ return;
24525
+ try {
24526
+ await fs12.mkdir(path11.dirname(this.logPath), { recursive: true });
24527
+ await fs12.writeFile(this.logPath, this.buffer.join(`
24528
+ `), "utf-8");
24529
+ } catch {}
24530
+ }
24531
+ getLogPath() {
24532
+ return this.logPath;
24533
+ }
24534
+ flushAsync() {
24535
+ this.flush().catch(() => {});
24536
+ }
24537
+ getTimestamp() {
24538
+ return new Date().toISOString().replace(/:/g, "-").split(".")[0];
24539
+ }
24540
+ getTime() {
24541
+ return new Date().toLocaleTimeString();
24542
+ }
24543
+ }
24544
+ function createLogger(config2) {
24545
+ return new MarkdownLogger(config2);
24546
+ }
24547
+ function createImportLogger(directory, worktreeId, provider) {
24548
+ const context = {};
24549
+ if (worktreeId)
24550
+ context.worktreeId = worktreeId;
24551
+ if (provider)
24552
+ context.provider = provider;
24553
+ const logger = createLogger({
24554
+ logDir: path11.join(directory, ".memory"),
24555
+ autoFlush: true,
24556
+ context
24557
+ });
24558
+ if (worktreeId)
24559
+ logger.info(`**Worktree ID**: ${worktreeId}`);
24560
+ if (provider)
24561
+ logger.info(`**Provider**: ${provider}`);
24562
+ logger.info(`**Repository**: ${directory}`);
24563
+ logger.info("");
24564
+ return logger;
24565
+ }
24566
+
24650
24567
  // src/tools/import-pipeline.ts
24651
24568
  class NoTransactionsError extends Error {
24652
24569
  constructor() {
@@ -24674,73 +24591,38 @@ function buildErrorResult5(result, error45, hint) {
24674
24591
  }
24675
24592
  return JSON.stringify(result);
24676
24593
  }
24677
- function buildCommitMessage(provider, currency, fromDate, untilDate, transactionCount) {
24678
- const providerStr = provider?.toUpperCase() || "statements";
24679
- const currencyStr = currency?.toUpperCase();
24680
- const dateRange = fromDate && untilDate ? ` ${fromDate} to ${untilDate}` : "";
24681
- const txStr = transactionCount > 0 ? ` (${transactionCount} transactions)` : "";
24682
- const parts = ["Import:", providerStr];
24683
- if (currencyStr) {
24684
- parts.push(currencyStr);
24685
- }
24686
- return `${parts.join(" ")}${dateRange}${txStr}`;
24687
- }
24688
- function cleanupIncomingFiles(worktree, context) {
24689
- const incomingDir = path12.join(worktree.mainRepoPath, "import/incoming");
24690
- if (!fs13.existsSync(incomingDir)) {
24691
- return;
24692
- }
24693
- const importStep = context.result.steps.import;
24694
- if (!importStep?.success || !importStep.details) {
24695
- return;
24696
- }
24697
- const importResult = importStep.details;
24698
- if (!importResult.files || !Array.isArray(importResult.files)) {
24699
- return;
24700
- }
24701
- let deletedCount = 0;
24702
- for (const fileResult of importResult.files) {
24703
- if (!fileResult.csv)
24704
- continue;
24705
- const filename = path12.basename(fileResult.csv);
24706
- const filePath = path12.join(incomingDir, filename);
24707
- if (fs13.existsSync(filePath)) {
24708
- try {
24709
- fs13.unlinkSync(filePath);
24710
- deletedCount++;
24711
- } catch (error45) {
24712
- console.error(`[ERROR] Failed to delete ${filename}: ${error45 instanceof Error ? error45.message : String(error45)}`);
24713
- }
24714
- }
24715
- }
24716
- if (deletedCount > 0) {
24717
- console.log(`[INFO] Cleaned up ${deletedCount} file(s) from import/incoming/`);
24718
- }
24719
- }
24720
- async function executeClassifyStep(context, worktree) {
24594
+ async function executeClassifyStep(context, logger) {
24595
+ logger?.startSection("Step 1: Classify Transactions");
24596
+ logger?.logStep("Classify", "start");
24721
24597
  if (context.options.skipClassify) {
24598
+ logger?.info("Classification skipped (skipClassify: true)");
24722
24599
  context.result.steps.classify = buildStepResult(true, "Classification skipped (skipClassify: true)");
24600
+ logger?.endSection();
24723
24601
  return;
24724
24602
  }
24725
- const inWorktree = () => true;
24726
- const classifyResult = await classifyStatements(worktree.path, context.agent, context.configLoader, inWorktree);
24603
+ const classifyResult = await classifyStatements(context.directory, context.agent, context.configLoader);
24727
24604
  const classifyParsed = JSON.parse(classifyResult);
24728
24605
  const success2 = classifyParsed.success !== false;
24729
24606
  let message = success2 ? "Classification complete" : "Classification had issues";
24730
24607
  if (classifyParsed.unrecognized?.length > 0) {
24731
24608
  message = `Classification complete with ${classifyParsed.unrecognized.length} unrecognized file(s)`;
24609
+ logger?.warn(`${classifyParsed.unrecognized.length} unrecognized file(s)`);
24732
24610
  }
24611
+ logger?.logStep("Classify", success2 ? "success" : "error", message);
24733
24612
  const details = {
24734
24613
  success: success2,
24735
24614
  unrecognized: classifyParsed.unrecognized,
24736
24615
  classified: classifyParsed
24737
24616
  };
24738
24617
  context.result.steps.classify = buildStepResult(success2, message, details);
24739
- }
24740
- async function executeAccountDeclarationsStep(context, worktree) {
24741
- const config2 = context.configLoader(worktree.path);
24742
- const pendingDir = path12.join(worktree.path, config2.paths.pending);
24743
- const rulesDir = path12.join(worktree.path, config2.paths.rules);
24618
+ logger?.endSection();
24619
+ }
24620
+ async function executeAccountDeclarationsStep(context, logger) {
24621
+ logger?.startSection("Step 2: Check Account Declarations");
24622
+ logger?.logStep("Check Accounts", "start");
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);
24744
24626
  const csvFiles = findCsvFiles(pendingDir, context.options.provider, context.options.currency);
24745
24627
  if (csvFiles.length === 0) {
24746
24628
  context.result.steps.accountDeclarations = buildStepResult(true, "No CSV files to process", {
@@ -24771,7 +24653,7 @@ async function executeAccountDeclarationsStep(context, worktree) {
24771
24653
  context.result.steps.accountDeclarations = buildStepResult(true, "No accounts found in rules files", {
24772
24654
  accountsAdded: [],
24773
24655
  journalUpdated: "",
24774
- rulesScanned: Array.from(matchedRulesFiles).map((f) => path12.relative(worktree.path, f))
24656
+ rulesScanned: Array.from(matchedRulesFiles).map((f) => path12.relative(context.directory, f))
24775
24657
  });
24776
24658
  return;
24777
24659
  }
@@ -24794,80 +24676,107 @@ async function executeAccountDeclarationsStep(context, worktree) {
24794
24676
  context.result.steps.accountDeclarations = buildStepResult(false, "Could not determine transaction year from CSV files", {
24795
24677
  accountsAdded: [],
24796
24678
  journalUpdated: "",
24797
- rulesScanned: Array.from(matchedRulesFiles).map((f) => path12.relative(worktree.path, f))
24679
+ rulesScanned: Array.from(matchedRulesFiles).map((f) => path12.relative(context.directory, f))
24798
24680
  });
24799
24681
  return;
24800
24682
  }
24801
24683
  let yearJournalPath;
24802
24684
  try {
24803
- yearJournalPath = ensureYearJournalExists(worktree.path, transactionYear);
24685
+ yearJournalPath = ensureYearJournalExists(context.directory, transactionYear);
24804
24686
  } catch (error45) {
24805
24687
  context.result.steps.accountDeclarations = buildStepResult(false, `Failed to create year journal: ${error45 instanceof Error ? error45.message : String(error45)}`, {
24806
24688
  accountsAdded: [],
24807
24689
  journalUpdated: "",
24808
- rulesScanned: Array.from(matchedRulesFiles).map((f) => path12.relative(worktree.path, f))
24690
+ rulesScanned: Array.from(matchedRulesFiles).map((f) => path12.relative(context.directory, f))
24809
24691
  });
24810
24692
  return;
24811
24693
  }
24812
24694
  const result = ensureAccountDeclarations(yearJournalPath, allAccounts);
24813
- const message = result.added.length > 0 ? `Added ${result.added.length} account declaration(s) to ${path12.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";
24696
+ logger?.logStep("Check Accounts", "success", message);
24697
+ if (result.added.length > 0) {
24698
+ for (const account of result.added) {
24699
+ logger?.info(` - ${account}`);
24700
+ }
24701
+ }
24814
24702
  context.result.steps.accountDeclarations = buildStepResult(true, message, {
24815
24703
  accountsAdded: result.added,
24816
- journalUpdated: path12.relative(worktree.path, yearJournalPath),
24817
- rulesScanned: Array.from(matchedRulesFiles).map((f) => path12.relative(worktree.path, f))
24704
+ journalUpdated: path12.relative(context.directory, yearJournalPath),
24705
+ rulesScanned: Array.from(matchedRulesFiles).map((f) => path12.relative(context.directory, f))
24818
24706
  });
24707
+ logger?.endSection();
24819
24708
  }
24820
- async function executeDryRunStep(context, worktree) {
24821
- const inWorktree = () => true;
24822
- const dryRunResult = await importStatements(worktree.path, context.agent, {
24709
+ async function executeDryRunStep(context, logger) {
24710
+ logger?.startSection("Step 3: Dry Run Import");
24711
+ logger?.logStep("Dry Run", "start");
24712
+ const dryRunResult = await importStatements(context.directory, context.agent, {
24823
24713
  provider: context.options.provider,
24824
24714
  currency: context.options.currency,
24825
24715
  checkOnly: true
24826
- }, context.configLoader, context.hledgerExecutor, inWorktree);
24716
+ }, context.configLoader, context.hledgerExecutor);
24827
24717
  const dryRunParsed = JSON.parse(dryRunResult);
24828
24718
  const message = dryRunParsed.success ? `Dry run passed: ${dryRunParsed.summary?.totalTransactions || 0} transactions ready` : `Dry run failed: ${dryRunParsed.summary?.unknown || 0} unknown account(s)`;
24719
+ logger?.logStep("Dry Run", dryRunParsed.success ? "success" : "error", message);
24720
+ if (dryRunParsed.summary?.totalTransactions) {
24721
+ logger?.info(`Found ${dryRunParsed.summary.totalTransactions} transactions`);
24722
+ }
24829
24723
  context.result.steps.dryRun = buildStepResult(dryRunParsed.success, message, {
24830
24724
  success: dryRunParsed.success,
24831
24725
  summary: dryRunParsed.summary
24832
24726
  });
24833
24727
  if (!dryRunParsed.success) {
24728
+ logger?.error("Dry run found unknown accounts or errors");
24729
+ logger?.endSection();
24834
24730
  context.result.error = "Dry run found unknown accounts or errors";
24835
24731
  context.result.hint = "Add rules to categorize unknown transactions, then retry";
24836
24732
  throw new Error("Dry run failed");
24837
24733
  }
24838
24734
  if (dryRunParsed.summary?.totalTransactions === 0) {
24735
+ logger?.endSection();
24839
24736
  throw new NoTransactionsError;
24840
24737
  }
24738
+ logger?.endSection();
24841
24739
  }
24842
- async function executeImportStep(context, worktree) {
24843
- const inWorktree = () => true;
24844
- const importResult = await importStatements(worktree.path, context.agent, {
24740
+ async function executeImportStep(context, logger) {
24741
+ logger?.startSection("Step 4: Import Transactions");
24742
+ logger?.logStep("Import", "start");
24743
+ const importResult = await importStatements(context.directory, context.agent, {
24845
24744
  provider: context.options.provider,
24846
24745
  currency: context.options.currency,
24847
24746
  checkOnly: false
24848
- }, context.configLoader, context.hledgerExecutor, inWorktree);
24747
+ }, context.configLoader, context.hledgerExecutor);
24849
24748
  const importParsed = JSON.parse(importResult);
24850
24749
  const message = importParsed.success ? `Imported ${importParsed.summary?.totalTransactions || 0} transactions` : `Import failed: ${importParsed.error || "Unknown error"}`;
24750
+ logger?.logStep("Import", importParsed.success ? "success" : "error", message);
24851
24751
  context.result.steps.import = buildStepResult(importParsed.success, message, {
24852
24752
  success: importParsed.success,
24853
24753
  summary: importParsed.summary,
24854
24754
  error: importParsed.error
24855
24755
  });
24856
24756
  if (!importParsed.success) {
24757
+ logger?.error("Import failed", new Error(importParsed.error || "Unknown error"));
24758
+ logger?.endSection();
24857
24759
  context.result.error = `Import failed: ${importParsed.error || "Unknown error"}`;
24858
24760
  throw new Error("Import failed");
24859
24761
  }
24762
+ logger?.endSection();
24860
24763
  }
24861
- async function executeReconcileStep(context, worktree) {
24862
- const inWorktree = () => true;
24863
- const reconcileResult = await reconcileStatement(worktree.path, context.agent, {
24764
+ async function executeReconcileStep(context, logger) {
24765
+ logger?.startSection("Step 5: Reconcile Balance");
24766
+ logger?.logStep("Reconcile", "start");
24767
+ const reconcileResult = await reconcileStatement(context.directory, context.agent, {
24864
24768
  provider: context.options.provider,
24865
24769
  currency: context.options.currency,
24866
24770
  closingBalance: context.options.closingBalance,
24867
24771
  account: context.options.account
24868
- }, context.configLoader, context.hledgerExecutor, inWorktree);
24772
+ }, context.configLoader, context.hledgerExecutor);
24869
24773
  const reconcileParsed = JSON.parse(reconcileResult);
24870
24774
  const message = reconcileParsed.success ? `Balance reconciled: ${reconcileParsed.actualBalance}` : `Balance mismatch: expected ${reconcileParsed.expectedBalance}, got ${reconcileParsed.actualBalance}`;
24775
+ logger?.logStep("Reconcile", reconcileParsed.success ? "success" : "error", message);
24776
+ if (reconcileParsed.success) {
24777
+ logger?.info(`Actual: ${reconcileParsed.actualBalance}`);
24778
+ logger?.info(`Expected: ${reconcileParsed.expectedBalance}`);
24779
+ }
24871
24780
  context.result.steps.reconcile = buildStepResult(reconcileParsed.success, message, {
24872
24781
  success: reconcileParsed.success,
24873
24782
  actualBalance: reconcileParsed.actualBalance,
@@ -24876,39 +24785,17 @@ async function executeReconcileStep(context, worktree) {
24876
24785
  error: reconcileParsed.error
24877
24786
  });
24878
24787
  if (!reconcileParsed.success) {
24788
+ logger?.error("Reconciliation failed", new Error(reconcileParsed.error || "Balance mismatch"));
24789
+ logger?.endSection();
24879
24790
  context.result.error = `Reconciliation failed: ${reconcileParsed.error || "Balance mismatch"}`;
24880
24791
  context.result.hint = "Check for missing transactions or incorrect rules";
24881
24792
  throw new Error("Reconciliation failed");
24882
24793
  }
24883
- }
24884
- async function executeMergeStep(context, worktree) {
24885
- const importDetails = context.result.steps.import?.details;
24886
- const reconcileDetails = context.result.steps.reconcile?.details;
24887
- if (!importDetails || !reconcileDetails) {
24888
- throw new Error("Import or reconcile step not completed before merge");
24889
- }
24890
- const commitInfo = {
24891
- fromDate: reconcileDetails.metadata?.["from-date"],
24892
- untilDate: reconcileDetails.metadata?.["until-date"]
24893
- };
24894
- const transactionCount = importDetails.summary?.totalTransactions || 0;
24895
- const commitMessage = buildCommitMessage(context.options.provider, context.options.currency, commitInfo.fromDate, commitInfo.untilDate, transactionCount);
24896
- try {
24897
- mergeWorktree(worktree, commitMessage);
24898
- const mergeDetails = { commitMessage };
24899
- context.result.steps.merge = buildStepResult(true, `Merged to main: "${commitMessage}"`, mergeDetails);
24900
- cleanupIncomingFiles(worktree, context);
24901
- } catch (error45) {
24902
- const message = `Merge failed: ${error45 instanceof Error ? error45.message : String(error45)}`;
24903
- context.result.steps.merge = buildStepResult(false, message);
24904
- context.result.error = "Merge to main branch failed";
24905
- throw new Error("Merge failed");
24906
- }
24794
+ logger?.endSection();
24907
24795
  }
24908
24796
  function handleNoTransactions(result) {
24909
24797
  result.steps.import = buildStepResult(true, "No transactions to import");
24910
24798
  result.steps.reconcile = buildStepResult(true, "Reconciliation skipped (no transactions)");
24911
- result.steps.merge = buildStepResult(true, "Merge skipped (no changes)");
24912
24799
  return buildSuccessResult4(result, "No transactions found to import");
24913
24800
  }
24914
24801
  async function importPipeline(directory, agent, options, configLoader = loadImportConfig, hledgerExecutor = defaultHledgerExecutor) {
@@ -24916,6 +24803,12 @@ async function importPipeline(directory, agent, options, configLoader = loadImpo
24916
24803
  if (restrictionError) {
24917
24804
  return restrictionError;
24918
24805
  }
24806
+ const logger = createImportLogger(directory, undefined, options.provider);
24807
+ logger.startSection("Import Pipeline", 1);
24808
+ logger.info(`Provider filter: ${options.provider || "all"}`);
24809
+ logger.info(`Currency filter: ${options.currency || "all"}`);
24810
+ logger.info(`Skip classify: ${options.skipClassify || false}`);
24811
+ logger.info("");
24919
24812
  const result = {
24920
24813
  success: false,
24921
24814
  steps: {}
@@ -24929,113 +24822,61 @@ async function importPipeline(directory, agent, options, configLoader = loadImpo
24929
24822
  result
24930
24823
  };
24931
24824
  try {
24932
- return await withWorktree(directory, async (worktree) => {
24933
- result.worktreeId = worktree.uuid;
24934
- result.steps.worktree = buildStepResult(true, `Created worktree at ${worktree.path}`, {
24935
- path: worktree.path,
24936
- branch: worktree.branch
24937
- });
24938
- try {
24939
- const config2 = configLoader(directory);
24940
- const syncResult = syncCSVFilesToWorktree(directory, worktree.path, config2.paths.import);
24941
- if (syncResult.synced.length === 0 && syncResult.errors.length === 0) {
24942
- result.steps.sync = buildStepResult(true, "No CSV files to sync", {
24943
- synced: []
24944
- });
24945
- } else if (syncResult.errors.length > 0) {
24946
- result.steps.sync = buildStepResult(true, `Synced ${syncResult.synced.length} file(s) with ${syncResult.errors.length} error(s)`, { synced: syncResult.synced, errors: syncResult.errors });
24947
- } else {
24948
- result.steps.sync = buildStepResult(true, `Synced ${syncResult.synced.length} CSV file(s) to worktree`, { synced: syncResult.synced });
24949
- }
24950
- } catch (error45) {
24951
- const errorMsg = error45 instanceof Error ? error45.message : String(error45);
24952
- result.steps.sync = buildStepResult(false, `Failed to sync CSV files: ${errorMsg}`, { synced: [], errors: [{ file: "unknown", error: errorMsg }] });
24953
- }
24954
- try {
24955
- await executeClassifyStep(context, worktree);
24956
- await executeAccountDeclarationsStep(context, worktree);
24957
- await executeDryRunStep(context, worktree);
24958
- await executeImportStep(context, worktree);
24959
- await executeReconcileStep(context, worktree);
24960
- try {
24961
- const config2 = configLoader(directory);
24962
- const cleanupResult = cleanupProcessedCSVFiles(directory, config2.paths.import);
24963
- if (cleanupResult.deleted.length === 0 && cleanupResult.errors.length === 0) {
24964
- result.steps.cleanup = buildStepResult(true, "No CSV files to cleanup", { csvCleanup: { deleted: [] } });
24965
- } else if (cleanupResult.errors.length > 0) {
24966
- result.steps.cleanup = buildStepResult(true, `Deleted ${cleanupResult.deleted.length} CSV file(s) with ${cleanupResult.errors.length} error(s)`, {
24967
- csvCleanup: {
24968
- deleted: cleanupResult.deleted,
24969
- errors: cleanupResult.errors
24970
- }
24971
- });
24972
- } else {
24973
- result.steps.cleanup = buildStepResult(true, `Deleted ${cleanupResult.deleted.length} CSV file(s) from main repo`, { csvCleanup: { deleted: cleanupResult.deleted } });
24974
- }
24975
- } catch (error45) {
24976
- const errorMsg = error45 instanceof Error ? error45.message : String(error45);
24977
- result.steps.cleanup = buildStepResult(false, `Failed to cleanup CSV files: ${errorMsg}`, {
24978
- csvCleanup: {
24979
- deleted: [],
24980
- errors: [{ file: "unknown", error: errorMsg }]
24981
- }
24982
- });
24983
- }
24984
- await executeMergeStep(context, worktree);
24985
- const existingCleanup = result.steps.cleanup;
24986
- if (existingCleanup) {
24987
- existingCleanup.message += ", worktree cleaned up";
24988
- existingCleanup.details = {
24989
- ...existingCleanup.details,
24990
- cleanedAfterSuccess: true
24991
- };
24992
- }
24993
- const transactionCount = context.result.steps.import?.details?.summary?.totalTransactions || 0;
24994
- return buildSuccessResult4(result, `Successfully imported ${transactionCount} transaction(s)`);
24995
- } catch (error45) {
24996
- result.steps.cleanup = buildStepResult(true, "Worktree cleaned up after failure (CSV files preserved for retry)", {
24997
- cleanedAfterFailure: true,
24998
- csvCleanup: { deleted: [] }
24999
- });
25000
- if (error45 instanceof NoTransactionsError) {
25001
- return handleNoTransactions(result);
25002
- }
25003
- if (!result.error) {
25004
- result.error = error45 instanceof Error ? error45.message : String(error45);
25005
- }
25006
- return buildErrorResult5(result, result.error, result.hint);
25007
- }
25008
- });
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)`);
25009
24840
  } catch (error45) {
25010
- result.steps.worktree = buildStepResult(false, `Failed to create worktree: ${error45 instanceof Error ? error45.message : String(error45)}`);
25011
- result.error = "Failed to create worktree";
25012
- 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);
24850
+ } finally {
24851
+ logger.endSection();
24852
+ await logger.flush();
25013
24853
  }
25014
24854
  }
25015
24855
  var import_pipeline_default = tool({
25016
- 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.
25017
24857
 
25018
- This tool orchestrates the full import workflow in an isolated git worktree:
24858
+ This tool orchestrates the full import workflow:
25019
24859
 
25020
24860
  **Pipeline Steps:**
25021
- 1. **Create Worktree**: Creates an isolated git worktree for safe import
25022
- 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
25023
24863
  3. **Dry Run**: Validates all transactions have known accounts
25024
- 4. **Import**: Imports transactions to the journal
24864
+ 4. **Import**: Imports transactions to the journal (moves CSVs to import/done)
25025
24865
  5. **Reconcile**: Validates closing balance matches CSV metadata
25026
- 6. **Merge**: Merges worktree to main with --no-ff
25027
- 7. **Cleanup**: Removes worktree
25028
24866
 
25029
- **Safety Features:**
25030
- - All changes happen in isolated worktree
25031
- - If any step fails, worktree is discarded (main branch untouched)
25032
- - Balance reconciliation ensures data integrity
25033
- - Atomic commit with merge --no-ff preserves history
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
24871
+
24872
+ **Logging:**
24873
+ - All operations logged to .memory/import-<timestamp>.md
24874
+ - Log includes command output, timing, and error details
25034
24875
 
25035
24876
  **Usage:**
25036
- - Basic: import-pipeline (processes all pending CSVs)
24877
+ - Basic: import-pipeline (processes all CSVs in incoming/)
25037
24878
  - Filtered: import-pipeline --provider ubs --currency chf
25038
- - With manual balance: import-pipeline --closingBalance "CHF 1234.56"
24879
+ - Manual balance: import-pipeline --closingBalance "CHF 1234.56"
25039
24880
  - Skip classify: import-pipeline --skipClassify true`,
25040
24881
  args: {
25041
24882
  provider: tool.schema.string().optional().describe('Filter by provider (e.g., "ubs", "revolut")'),
@@ -25056,7 +24897,7 @@ This tool orchestrates the full import workflow in an isolated git worktree:
25056
24897
  }
25057
24898
  });
25058
24899
  // src/tools/init-directories.ts
25059
- import * as fs14 from "fs";
24900
+ import * as fs13 from "fs";
25060
24901
  import * as path13 from "path";
25061
24902
  async function initDirectories(directory) {
25062
24903
  try {
@@ -25064,8 +24905,8 @@ async function initDirectories(directory) {
25064
24905
  const directoriesCreated = [];
25065
24906
  const gitkeepFiles = [];
25066
24907
  const importBase = path13.join(directory, "import");
25067
- if (!fs14.existsSync(importBase)) {
25068
- fs14.mkdirSync(importBase, { recursive: true });
24908
+ if (!fs13.existsSync(importBase)) {
24909
+ fs13.mkdirSync(importBase, { recursive: true });
25069
24910
  directoriesCreated.push("import");
25070
24911
  }
25071
24912
  const pathsToCreate = [
@@ -25076,19 +24917,19 @@ async function initDirectories(directory) {
25076
24917
  ];
25077
24918
  for (const { path: dirPath } of pathsToCreate) {
25078
24919
  const fullPath = path13.join(directory, dirPath);
25079
- if (!fs14.existsSync(fullPath)) {
25080
- fs14.mkdirSync(fullPath, { recursive: true });
24920
+ if (!fs13.existsSync(fullPath)) {
24921
+ fs13.mkdirSync(fullPath, { recursive: true });
25081
24922
  directoriesCreated.push(dirPath);
25082
24923
  }
25083
24924
  const gitkeepPath = path13.join(fullPath, ".gitkeep");
25084
- if (!fs14.existsSync(gitkeepPath)) {
25085
- fs14.writeFileSync(gitkeepPath, "");
24925
+ if (!fs13.existsSync(gitkeepPath)) {
24926
+ fs13.writeFileSync(gitkeepPath, "");
25086
24927
  gitkeepFiles.push(path13.join(dirPath, ".gitkeep"));
25087
24928
  }
25088
24929
  }
25089
24930
  const gitignorePath = path13.join(importBase, ".gitignore");
25090
24931
  let gitignoreCreated = false;
25091
- if (!fs14.existsSync(gitignorePath)) {
24932
+ if (!fs13.existsSync(gitignorePath)) {
25092
24933
  const gitignoreContent = `# Ignore CSV/PDF files in temporary directories
25093
24934
  /incoming/*.csv
25094
24935
  /incoming/*.pdf
@@ -25106,7 +24947,7 @@ async function initDirectories(directory) {
25106
24947
  .DS_Store
25107
24948
  Thumbs.db
25108
24949
  `;
25109
- fs14.writeFileSync(gitignorePath, gitignoreContent);
24950
+ fs13.writeFileSync(gitignorePath, gitignoreContent);
25110
24951
  gitignoreCreated = true;
25111
24952
  }
25112
24953
  const parts = [];
@@ -25182,8 +25023,8 @@ You can now drop CSV files into import/incoming/ and run import-pipeline.`);
25182
25023
  }
25183
25024
  });
25184
25025
  // src/index.ts
25185
- var __dirname2 = dirname6(fileURLToPath3(import.meta.url));
25186
- 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");
25187
25028
  var AccountantPlugin = async () => {
25188
25029
  const agent = loadAgent(AGENT_FILE);
25189
25030
  return {