@fuzzle/opencode-accountant 0.5.1 → 0.5.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 +250 -296
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -2686,17 +2686,16 @@ function loadAgent(filePath) {
2686
2686
  if (!match) {
2687
2687
  throw new Error(`Invalid frontmatter format in ${filePath}`);
2688
2688
  }
2689
- const data = jsYaml.load(match[1]);
2689
+ const raw = jsYaml.load(match[1]);
2690
+ if (typeof raw !== "object" || raw === null || typeof raw.description !== "string") {
2691
+ throw new Error(`Invalid frontmatter in ${filePath}: must be an object with a "description" string`);
2692
+ }
2693
+ const data = raw;
2694
+ const { description, ...optional } = data;
2690
2695
  return {
2691
- description: data.description,
2696
+ description,
2692
2697
  prompt: match[2].trim(),
2693
- ...data.mode && { mode: data.mode },
2694
- ...data.model && { model: data.model },
2695
- ...data.temperature !== undefined && { temperature: data.temperature },
2696
- ...data.maxSteps !== undefined && { maxSteps: data.maxSteps },
2697
- ...data.disable !== undefined && { disable: data.disable },
2698
- ...data.tools && { tools: data.tools },
2699
- ...data.permissions && { permissions: data.permissions }
2698
+ ...Object.fromEntries(Object.entries(optional).filter(([, v]) => v !== undefined))
2700
2699
  };
2701
2700
  }
2702
2701
  var init_agentLoader = __esm(() => {
@@ -4293,94 +4292,63 @@ function extractRulePatternsFromFile(rulesPath) {
4293
4292
  }
4294
4293
  return patterns;
4295
4294
  }
4296
- function buildBatchSuggestionPrompt(postings, context) {
4297
- let prompt = `You are an accounting assistant helping categorize bank transactions.
4298
-
4299
- `;
4300
- prompt += `I have ${postings.length} transaction(s) that need account classification:
4301
-
4302
- `;
4303
- if (context.existingAccounts.length > 0) {
4304
- prompt += `## Existing Account Hierarchy
4295
+ function buildAccountHierarchySection(accounts) {
4296
+ if (accounts.length === 0)
4297
+ return "";
4298
+ return `## Existing Account Hierarchy
4305
4299
 
4306
- `;
4307
- prompt += context.existingAccounts.map((acc) => `- ${acc}`).join(`
4308
- `);
4309
- prompt += `
4300
+ ${accounts.map((acc) => `- ${acc}`).join(`
4301
+ `)}
4310
4302
 
4311
4303
  `;
4312
- }
4313
- if (context.existingRules && context.existingRules.length > 0) {
4314
- prompt += `## Example Classification Patterns from Rules
4304
+ }
4305
+ function buildRuleExamplesSection(rules) {
4306
+ if (!rules || rules.length === 0)
4307
+ return "";
4308
+ const sampleSize = Math.min(EXAMPLE_PATTERN_SAMPLE_SIZE, rules.length);
4309
+ const lines = rules.slice(0, sampleSize).map((p) => `- If description matches "${p.condition}" \u2192 ${p.account}`);
4310
+ return `## Example Classification Patterns from Rules
4315
4311
 
4316
- `;
4317
- const sampleSize = Math.min(EXAMPLE_PATTERN_SAMPLE_SIZE, context.existingRules.length);
4318
- for (let i2 = 0;i2 < sampleSize; i2++) {
4319
- const pattern = context.existingRules[i2];
4320
- prompt += `- If description matches "${pattern.condition}" \u2192 ${pattern.account}
4321
- `;
4322
- }
4323
- prompt += `
4324
- `;
4325
- }
4326
- prompt += `## Transactions to Classify
4312
+ ${lines.join(`
4313
+ `)}
4327
4314
 
4328
4315
  `;
4329
- postings.forEach((posting, index) => {
4330
- prompt += `Transaction ${index + 1}:
4331
- `;
4332
- prompt += `- Type: ${posting.account === "income:unknown" ? "Income" : "Expense"}
4333
- `;
4334
- prompt += `- Date: ${posting.date}
4335
- `;
4336
- prompt += `- Description: ${posting.description}
4337
- `;
4338
- prompt += `- Amount: ${posting.amount}
4339
- `;
4316
+ }
4317
+ function buildTransactionsSection(postings) {
4318
+ const lines = postings.map((posting, index) => {
4319
+ const parts = [
4320
+ `Transaction ${index + 1}:`,
4321
+ `- Type: ${posting.account === "income:unknown" ? "Income" : "Expense"}`,
4322
+ `- Date: ${posting.date}`,
4323
+ `- Description: ${posting.description}`,
4324
+ `- Amount: ${posting.amount}`
4325
+ ];
4340
4326
  if (posting.csvRow) {
4341
- prompt += `- CSV Data: ${JSON.stringify(posting.csvRow)}
4342
- `;
4327
+ parts.push(`- CSV Data: ${JSON.stringify(posting.csvRow)}`);
4343
4328
  }
4344
- prompt += `
4345
- `;
4329
+ return parts.join(`
4330
+ `);
4346
4331
  });
4347
- prompt += `## Task
4332
+ return `## Transactions to Classify
4348
4333
 
4349
- `;
4350
- prompt += `For EACH transaction, suggest the most appropriate account. You may:
4351
- `;
4352
- prompt += `1. Suggest an existing account from the hierarchy above
4353
- `;
4354
- prompt += `2. Propose a NEW account following the existing naming patterns
4355
-
4356
- `;
4357
- prompt += `## Response Format
4334
+ ${lines.join(`
4358
4335
 
4359
- `;
4360
- prompt += `Respond with suggestions for ALL transactions in this exact format:
4361
-
4362
- `;
4363
- prompt += `TRANSACTION 1:
4364
- `;
4365
- prompt += `ACCOUNT: {account_name}
4366
- `;
4367
- prompt += `CONFIDENCE: {high|medium|low}
4368
- `;
4369
- prompt += `REASONING: {brief one-sentence explanation}
4370
-
4371
- `;
4372
- prompt += `TRANSACTION 2:
4373
- `;
4374
- prompt += `ACCOUNT: {account_name}
4375
- `;
4376
- prompt += `CONFIDENCE: {high|medium|low}
4377
- `;
4378
- prompt += `REASONING: {brief one-sentence explanation}
4336
+ `)}
4379
4337
 
4380
4338
  `;
4381
- prompt += `... (continue for all transactions)
4382
- `;
4383
- return prompt;
4339
+ }
4340
+ function buildBatchSuggestionPrompt(postings, context) {
4341
+ return [
4342
+ `You are an accounting assistant helping categorize bank transactions.
4343
+ `,
4344
+ `I have ${postings.length} transaction(s) that need account classification:
4345
+ `,
4346
+ buildAccountHierarchySection(context.existingAccounts),
4347
+ buildRuleExamplesSection(context.existingRules),
4348
+ buildTransactionsSection(postings),
4349
+ RESPONSE_FORMAT_SECTION
4350
+ ].join(`
4351
+ `);
4384
4352
  }
4385
4353
  function parseBatchSuggestionResponse(response) {
4386
4354
  const suggestions = [];
@@ -4399,20 +4367,36 @@ function parseBatchSuggestionResponse(response) {
4399
4367
  }
4400
4368
  return suggestions;
4401
4369
  }
4402
- async function suggestAccountsForPostingsBatch(postings, context) {
4403
- if (postings.length === 0) {
4404
- return [];
4405
- }
4406
- const uncachedPostings = [];
4407
- const cachedResults = new Map;
4370
+ function partitionByCacheStatus(postings) {
4371
+ const uncached = [];
4372
+ const cached2 = new Map;
4408
4373
  postings.forEach((posting, index) => {
4409
4374
  const hash2 = hashTransaction(posting);
4410
4375
  if (suggestionCache[hash2]) {
4411
- cachedResults.set(index, suggestionCache[hash2]);
4376
+ cached2.set(index, suggestionCache[hash2]);
4412
4377
  } else {
4413
- uncachedPostings.push(posting);
4378
+ uncached.push(posting);
4414
4379
  }
4415
4380
  });
4381
+ return { uncached, cached: cached2 };
4382
+ }
4383
+ function mergeSuggestions(postings, cachedResults, newSuggestions) {
4384
+ let uncachedIndex = 0;
4385
+ return postings.map((posting, index) => {
4386
+ const suggestion = cachedResults.get(index) || newSuggestions[uncachedIndex++];
4387
+ return {
4388
+ ...posting,
4389
+ suggestedAccount: suggestion?.account,
4390
+ suggestionConfidence: suggestion?.confidence,
4391
+ suggestionReasoning: suggestion?.reasoning
4392
+ };
4393
+ });
4394
+ }
4395
+ async function suggestAccountsForPostingsBatch(postings, context) {
4396
+ if (postings.length === 0) {
4397
+ return [];
4398
+ }
4399
+ const { uncached: uncachedPostings, cached: cachedResults } = partitionByCacheStatus(postings);
4416
4400
  context.logger?.info(`Account suggestions: ${cachedResults.size} cached, ${uncachedPostings.length} to generate`);
4417
4401
  let newSuggestions = [];
4418
4402
  if (uncachedPostings.length > 0) {
@@ -4445,19 +4429,7 @@ ${userPrompt}`;
4445
4429
  return postings;
4446
4430
  }
4447
4431
  }
4448
- const results = [];
4449
- let uncachedIndex = 0;
4450
- postings.forEach((posting, index) => {
4451
- const cachedSuggestion = cachedResults.get(index);
4452
- const suggestion = cachedSuggestion || newSuggestions[uncachedIndex++];
4453
- results.push({
4454
- ...posting,
4455
- suggestedAccount: suggestion?.account,
4456
- suggestionConfidence: suggestion?.confidence,
4457
- suggestionReasoning: suggestion?.reasoning
4458
- });
4459
- });
4460
- return results;
4432
+ return mergeSuggestions(postings, cachedResults, newSuggestions);
4461
4433
  }
4462
4434
  function generateMockSuggestions(postings) {
4463
4435
  let response = "";
@@ -4492,10 +4464,34 @@ function generateMockSuggestions(postings) {
4492
4464
  });
4493
4465
  return response;
4494
4466
  }
4495
- var EXAMPLE_PATTERN_SAMPLE_SIZE = 10, suggestionCache;
4467
+ var EXAMPLE_PATTERN_SAMPLE_SIZE = 10, suggestionCache, RESPONSE_FORMAT_SECTION;
4496
4468
  var init_accountSuggester = __esm(() => {
4497
4469
  init_agentLoader();
4498
4470
  suggestionCache = {};
4471
+ RESPONSE_FORMAT_SECTION = [
4472
+ `## Task
4473
+ `,
4474
+ "For EACH transaction, suggest the most appropriate account. You may:",
4475
+ "1. Suggest an existing account from the hierarchy above",
4476
+ `2. Propose a NEW account following the existing naming patterns
4477
+ `,
4478
+ `## Response Format
4479
+ `,
4480
+ `Respond with suggestions for ALL transactions in this exact format:
4481
+ `,
4482
+ "TRANSACTION 1:",
4483
+ "ACCOUNT: {account_name}",
4484
+ "CONFIDENCE: {high|medium|low}",
4485
+ `REASONING: {brief one-sentence explanation}
4486
+ `,
4487
+ "TRANSACTION 2:",
4488
+ "ACCOUNT: {account_name}",
4489
+ "CONFIDENCE: {high|medium|low}",
4490
+ `REASONING: {brief one-sentence explanation}
4491
+ `,
4492
+ "... (continue for all transactions)"
4493
+ ].join(`
4494
+ `);
4499
4495
  });
4500
4496
 
4501
4497
  // src/index.ts
@@ -16920,13 +16916,7 @@ function findCsvFiles(baseDir, options = {}) {
16920
16916
  if (!fs3.existsSync(baseDir)) {
16921
16917
  return [];
16922
16918
  }
16923
- let searchDir = baseDir;
16924
- if (options.subdir) {
16925
- searchDir = path2.join(searchDir, options.subdir);
16926
- if (options.subsubdir) {
16927
- searchDir = path2.join(searchDir, options.subsubdir);
16928
- }
16929
- }
16919
+ const searchDir = path2.join(...[baseDir, options.subdir, options.subsubdir].filter((p) => !!p));
16930
16920
  if (!fs3.existsSync(searchDir)) {
16931
16921
  return [];
16932
16922
  }
@@ -16957,6 +16947,13 @@ function findCsvFiles(baseDir, options = {}) {
16957
16947
  }
16958
16948
  return csvFiles.sort();
16959
16949
  }
16950
+ function sortByMtimeNewestFirst(files) {
16951
+ return [...files].sort((a, b) => {
16952
+ const aStat = fs3.statSync(a);
16953
+ const bStat = fs3.statSync(b);
16954
+ return bStat.mtime.getTime() - aStat.mtime.getTime();
16955
+ });
16956
+ }
16960
16957
  function ensureDirectory(dirPath) {
16961
16958
  if (!fs3.existsSync(dirPath)) {
16962
16959
  fs3.mkdirSync(dirPath, { recursive: true });
@@ -17079,9 +17076,6 @@ function buildPricehistArgs(startDate, endDate, currencyConfig) {
17079
17076
  }
17080
17077
  return cmdArgs;
17081
17078
  }
17082
- function buildErrorResult(error45) {
17083
- return buildToolErrorResult(error45);
17084
- }
17085
17079
  function buildSuccessResult(results, endDate, backfill) {
17086
17080
  return buildToolSuccessResult({
17087
17081
  success: results.every((r) => !("error" in r)),
@@ -17116,7 +17110,7 @@ async function fetchCurrencyPrices(directory, agent, backfill, priceFetcher = de
17116
17110
  config2 = configLoader(directory);
17117
17111
  } catch (err) {
17118
17112
  const errorMessage = err instanceof Error ? err.message : String(err);
17119
- return buildErrorResult(errorMessage);
17113
+ return buildToolErrorResult(errorMessage);
17120
17114
  }
17121
17115
  const endDate = getYesterday();
17122
17116
  const defaultBackfillDate = getDefaultBackfillDate();
@@ -17433,28 +17427,15 @@ import { randomUUID } from "crypto";
17433
17427
  function getContextPath(directory, contextId) {
17434
17428
  return path5.join(directory, ".memory", `${contextId}.json`);
17435
17429
  }
17436
- function ensureMemoryDir(directory) {
17437
- ensureDirectory(path5.join(directory, ".memory"));
17438
- }
17439
17430
  function createContext(directory, params) {
17440
17431
  const now = new Date().toISOString();
17441
17432
  const context = {
17442
17433
  id: randomUUID(),
17443
17434
  createdAt: now,
17444
17435
  updatedAt: now,
17445
- filename: params.filename,
17446
- filePath: params.filePath,
17447
- provider: params.provider,
17448
- currency: params.currency,
17449
- accountNumber: params.accountNumber,
17450
- originalFilename: params.originalFilename,
17451
- fromDate: params.fromDate,
17452
- untilDate: params.untilDate,
17453
- openingBalance: params.openingBalance,
17454
- closingBalance: params.closingBalance,
17455
- account: params.account
17456
- };
17457
- ensureMemoryDir(directory);
17436
+ ...params
17437
+ };
17438
+ ensureDirectory(path5.join(directory, ".memory"));
17458
17439
  const contextPath = getContextPath(directory, context.id);
17459
17440
  fs5.writeFileSync(contextPath, JSON.stringify(context, null, 2), "utf-8");
17460
17441
  return context;
@@ -17515,20 +17496,6 @@ function buildSuccessResult2(classified, unrecognized, message) {
17515
17496
  }
17516
17497
  });
17517
17498
  }
17518
- function buildErrorResult2(error45, hint) {
17519
- return buildToolErrorResult(error45, hint, {
17520
- classified: [],
17521
- unrecognized: []
17522
- });
17523
- }
17524
- function buildCollisionError(collisions) {
17525
- const error45 = `Cannot classify: ${collisions.length} file(s) would overwrite existing pending files.`;
17526
- return buildToolErrorResult(error45, undefined, {
17527
- collisions,
17528
- classified: [],
17529
- unrecognized: []
17530
- });
17531
- }
17532
17499
  function planMoves(csvFiles, importsDir, pendingDir, unrecognizedDir, config2) {
17533
17500
  const plannedMoves = [];
17534
17501
  const collisions = [];
@@ -17629,7 +17596,10 @@ async function classifyStatements(directory, agent, configLoader = loadImportCon
17629
17596
  config2 = configLoader(directory);
17630
17597
  } catch (err) {
17631
17598
  const errorMessage = err instanceof Error ? err.message : String(err);
17632
- return buildErrorResult2(errorMessage);
17599
+ return buildToolErrorResult(errorMessage, undefined, {
17600
+ classified: [],
17601
+ unrecognized: []
17602
+ });
17633
17603
  }
17634
17604
  const importsDir = path6.join(directory, config2.paths.import);
17635
17605
  const pendingDir = path6.join(directory, config2.paths.pending);
@@ -17640,7 +17610,12 @@ async function classifyStatements(directory, agent, configLoader = loadImportCon
17640
17610
  }
17641
17611
  const { plannedMoves, collisions } = planMoves(csvFiles, importsDir, pendingDir, unrecognizedDir, config2);
17642
17612
  if (collisions.length > 0) {
17643
- return buildCollisionError(collisions);
17613
+ const error45 = `Cannot classify: ${collisions.length} file(s) would overwrite existing pending files.`;
17614
+ return buildToolErrorResult(error45, undefined, {
17615
+ collisions,
17616
+ classified: [],
17617
+ unrecognized: []
17618
+ });
17644
17619
  }
17645
17620
  const { classified, unrecognized } = executeMoves(plannedMoves, config2, unrecognizedDir, directory);
17646
17621
  return buildSuccessResult2(classified, unrecognized);
@@ -23025,6 +23000,7 @@ function findRulesForCsv(csvPath, mapping) {
23025
23000
  // src/utils/hledgerExecutor.ts
23026
23001
  var {$: $2 } = globalThis.Bun;
23027
23002
  var STDERR_TRUNCATE_LENGTH = 500;
23003
+ var TX_HEADER_PATTERN = /^(\d{4})-(\d{2}-\d{2})\s+(.+)$/;
23028
23004
  async function defaultHledgerExecutor(cmdArgs) {
23029
23005
  try {
23030
23006
  const result = await $2`hledger ${cmdArgs}`.quiet().nothrow();
@@ -23059,10 +23035,10 @@ function parseUnknownPostings(hledgerOutput) {
23059
23035
  let currentDate = "";
23060
23036
  let currentDescription = "";
23061
23037
  for (const line of lines) {
23062
- const headerMatch = line.match(/^(\d{4}-\d{2}-\d{2})\s+(.+)$/);
23038
+ const headerMatch = line.match(TX_HEADER_PATTERN);
23063
23039
  if (headerMatch) {
23064
- currentDate = headerMatch[1];
23065
- currentDescription = headerMatch[2].trim();
23040
+ currentDate = `${headerMatch[1]}-${headerMatch[2]}`;
23041
+ currentDescription = headerMatch[3].trim();
23066
23042
  continue;
23067
23043
  }
23068
23044
  const postingMatch = line.match(/^\s+(income:unknown|expenses:unknown)\s+([^\s]+(?:\s+[^\s=]+)?)\s*(?:=\s*(.+))?$/);
@@ -23083,7 +23059,7 @@ function countTransactions(hledgerOutput) {
23083
23059
  `);
23084
23060
  let count = 0;
23085
23061
  for (const line of lines) {
23086
- if (/^\d{4}-\d{2}-\d{2}\s+/.test(line)) {
23062
+ if (TX_HEADER_PATTERN.test(line)) {
23087
23063
  count++;
23088
23064
  }
23089
23065
  }
@@ -23094,7 +23070,7 @@ function extractTransactionYears(hledgerOutput) {
23094
23070
  const lines = hledgerOutput.split(`
23095
23071
  `);
23096
23072
  for (const line of lines) {
23097
- const match2 = line.match(/^(\d{4})-\d{2}-\d{2}\s+/);
23073
+ const match2 = line.match(TX_HEADER_PATTERN);
23098
23074
  if (match2) {
23099
23075
  years.add(parseInt(match2[1], 10));
23100
23076
  }
@@ -23154,6 +23130,13 @@ async function getAccountBalance(mainJournalPath, account, asOfDate, executor =
23154
23130
 
23155
23131
  // src/utils/rulesParser.ts
23156
23132
  import * as fs8 from "fs";
23133
+ function resolveFieldRef(fieldRef, fieldNames) {
23134
+ if (/^\d+$/.test(fieldRef)) {
23135
+ const index = parseInt(fieldRef, 10) - 1;
23136
+ return fieldNames[index] || fieldRef;
23137
+ }
23138
+ return fieldRef;
23139
+ }
23157
23140
  function parseSkipRows(rulesContent) {
23158
23141
  const match2 = rulesContent.match(/^skip\s+(\d+)/m);
23159
23142
  return match2 ? parseInt(match2[1], 10) : 0;
@@ -23178,24 +23161,13 @@ function parseDateField(rulesContent, fieldNames) {
23178
23161
  if (!match2) {
23179
23162
  return fieldNames[0] || "date";
23180
23163
  }
23181
- const value = match2[1];
23182
- if (/^\d+$/.test(value)) {
23183
- const index = parseInt(value, 10) - 1;
23184
- return fieldNames[index] || value;
23185
- }
23186
- return value;
23164
+ return resolveFieldRef(match2[1], fieldNames);
23187
23165
  }
23188
23166
  function parseAmountFields(rulesContent, fieldNames) {
23189
23167
  const result = {};
23190
23168
  const simpleMatch = rulesContent.match(/^amount\s+(-?)%(\w+|\d+)/m);
23191
23169
  if (simpleMatch) {
23192
- const fieldRef = simpleMatch[2];
23193
- if (/^\d+$/.test(fieldRef)) {
23194
- const index = parseInt(fieldRef, 10) - 1;
23195
- result.single = fieldNames[index] || fieldRef;
23196
- } else {
23197
- result.single = fieldRef;
23198
- }
23170
+ result.single = resolveFieldRef(simpleMatch[2], fieldNames);
23199
23171
  }
23200
23172
  const debitMatch = rulesContent.match(/if\s+%(\w+)\s+\.\s*\n\s*amount\s+-?%\1/m);
23201
23173
  if (debitMatch) {
@@ -23260,29 +23232,34 @@ function parseBalance(balance) {
23260
23232
  const amount = parseFloat(amountStr.replace(/,/g, ""));
23261
23233
  return { currency, amount };
23262
23234
  }
23235
+ function validateCurrencies(a, b) {
23236
+ if (a.currency && b.currency && a.currency !== b.currency) {
23237
+ throw new Error(`Currency mismatch: ${a.currency} vs ${b.currency}`);
23238
+ }
23239
+ }
23263
23240
  function calculateDifference(expected, actual) {
23264
23241
  const expectedParsed = parseBalance(expected);
23265
23242
  const actualParsed = parseBalance(actual);
23266
23243
  if (!expectedParsed || !actualParsed) {
23267
23244
  throw new Error(`Cannot parse balances: expected="${expected}", actual="${actual}"`);
23268
23245
  }
23269
- if (expectedParsed.currency && actualParsed.currency && expectedParsed.currency !== actualParsed.currency) {
23270
- throw new Error(`Currency mismatch: expected ${expectedParsed.currency}, got ${actualParsed.currency}`);
23271
- }
23246
+ validateCurrencies(expectedParsed, actualParsed);
23272
23247
  const diff = actualParsed.amount - expectedParsed.amount;
23273
23248
  const sign = diff >= 0 ? "+" : "";
23274
23249
  const currency = expectedParsed.currency || actualParsed.currency;
23275
23250
  return currency ? `${currency} ${sign}${diff.toFixed(2)}` : `${sign}${diff.toFixed(2)}`;
23276
23251
  }
23252
+ function formatBalance(amount, currency) {
23253
+ const formattedAmount = amount.toFixed(2);
23254
+ return currency ? `${currency} ${formattedAmount}` : formattedAmount;
23255
+ }
23277
23256
  function balancesMatch(balance1, balance2) {
23278
23257
  const parsed1 = parseBalance(balance1);
23279
23258
  const parsed2 = parseBalance(balance2);
23280
23259
  if (!parsed1 || !parsed2) {
23281
23260
  return false;
23282
23261
  }
23283
- if (parsed1.currency && parsed2.currency && parsed1.currency !== parsed2.currency) {
23284
- throw new Error(`Currency mismatch: ${parsed1.currency} vs ${parsed2.currency}`);
23285
- }
23262
+ validateCurrencies(parsed1, parsed2);
23286
23263
  return parsed1.amount === parsed2.amount;
23287
23264
  }
23288
23265
 
@@ -23299,13 +23276,13 @@ function parseCsvFile(csvPath, config2) {
23299
23276
  const csvWithHeader = lines.slice(headerIndex).join(`
23300
23277
  `);
23301
23278
  const useFieldNames = config2.fieldNames.length > 0;
23302
- const result = import_papaparse2.default.parse(csvWithHeader, {
23303
- header: !useFieldNames,
23304
- delimiter: config2.separator,
23305
- skipEmptyLines: true
23306
- });
23307
23279
  if (useFieldNames) {
23308
- const rawRows = result.data;
23280
+ const result2 = import_papaparse2.default.parse(csvWithHeader, {
23281
+ header: false,
23282
+ delimiter: config2.separator,
23283
+ skipEmptyLines: true
23284
+ });
23285
+ const rawRows = result2.data;
23309
23286
  const dataRows = rawRows.slice(1);
23310
23287
  return dataRows.map((values) => {
23311
23288
  const row = {};
@@ -23315,6 +23292,11 @@ function parseCsvFile(csvPath, config2) {
23315
23292
  return row;
23316
23293
  });
23317
23294
  }
23295
+ const result = import_papaparse2.default.parse(csvWithHeader, {
23296
+ header: true,
23297
+ delimiter: config2.separator,
23298
+ skipEmptyLines: true
23299
+ });
23318
23300
  return result.data;
23319
23301
  }
23320
23302
  function getRowAmount(row, amountFields) {
@@ -23375,8 +23357,7 @@ function looksLikeTransactionId(fieldName, value) {
23375
23357
  if (!nameMatches)
23376
23358
  return false;
23377
23359
  const trimmedValue = value.trim();
23378
- const looksLikeId = /^[A-Za-z0-9_-]+$/.test(trimmedValue) && trimmedValue.length >= 3;
23379
- return looksLikeId;
23360
+ return /^[A-Za-z0-9_-]+$/.test(trimmedValue) && trimmedValue.length >= 3;
23380
23361
  }
23381
23362
  function findTransactionId(row) {
23382
23363
  for (const [field, value] of Object.entries(row)) {
@@ -23393,9 +23374,7 @@ function findMatchingCsvRow(posting, csvRows, config2) {
23393
23374
  const rowAmount = getRowAmount(row, config2.amountFields);
23394
23375
  if (rowDate !== posting.date)
23395
23376
  return false;
23396
- if (Math.abs(rowAmount - postingAmount) > AMOUNT_MATCH_TOLERANCE)
23397
- return false;
23398
- return true;
23377
+ return Math.abs(rowAmount - postingAmount) <= AMOUNT_MATCH_TOLERANCE;
23399
23378
  });
23400
23379
  if (candidates.length === 1) {
23401
23380
  return candidates[0];
@@ -23416,17 +23395,11 @@ function findMatchingCsvRow(posting, csvRows, config2) {
23416
23395
  const descMatches = candidates.filter((row) => {
23417
23396
  return Object.values(row).some((value) => value && value.toLowerCase().includes(descriptionLower));
23418
23397
  });
23419
- if (descMatches.length === 1) {
23420
- return descMatches[0];
23421
- }
23422
- if (descMatches.length > 1) {
23423
- return descMatches[0];
23424
- }
23425
- return candidates[0];
23398
+ return descMatches[0] || candidates[0];
23426
23399
  }
23427
23400
 
23428
23401
  // src/tools/import-statements.ts
23429
- function buildErrorResult3(error45, hint) {
23402
+ function buildErrorResult(error45, hint) {
23430
23403
  return buildToolErrorResult(error45, hint, {
23431
23404
  files: [],
23432
23405
  summary: {
@@ -23447,23 +23420,16 @@ function buildSuccessResult3(files, summary, message) {
23447
23420
  }
23448
23421
  function findCsvFromRulesFile(rulesFile) {
23449
23422
  const content = fs10.readFileSync(rulesFile, "utf-8");
23450
- const match2 = content.match(/^source\s+([^\n#]+)/m);
23451
- if (!match2) {
23423
+ const sourcePath = parseSourceDirective(content);
23424
+ if (!sourcePath) {
23452
23425
  return null;
23453
23426
  }
23454
- const sourcePath = match2[1].trim();
23455
- const rulesDir = path9.dirname(rulesFile);
23456
- const absolutePattern = path9.resolve(rulesDir, sourcePath);
23427
+ const absolutePattern = resolveSourcePath(sourcePath, rulesFile);
23457
23428
  const matches = glob.sync(absolutePattern);
23458
23429
  if (matches.length === 0) {
23459
23430
  return null;
23460
23431
  }
23461
- matches.sort((a, b) => {
23462
- const aStat = fs10.statSync(a);
23463
- const bStat = fs10.statSync(b);
23464
- return bStat.mtime.getTime() - aStat.mtime.getTime();
23465
- });
23466
- return matches[0];
23432
+ return sortByMtimeNewestFirst(matches)[0];
23467
23433
  }
23468
23434
  async function executeImports(fileResults, directory, pendingDir, doneDir, hledgerExecutor) {
23469
23435
  const importedFiles = [];
@@ -23597,7 +23563,7 @@ async function importStatements(directory, agent, options, configLoader = loadIm
23597
23563
  config2 = configLoader(directory);
23598
23564
  } catch (error45) {
23599
23565
  const errorMessage = `Failed to load configuration: ${error45 instanceof Error ? error45.message : String(error45)}`;
23600
- return buildErrorResult3(errorMessage, 'Ensure config/import/providers.yaml exists with required paths including "rules"');
23566
+ return buildErrorResult(errorMessage, 'Ensure config/import/providers.yaml exists with required paths including "rules"');
23601
23567
  }
23602
23568
  const pendingDir = path9.join(directory, config2.paths.pending);
23603
23569
  const rulesDir = path9.join(directory, config2.paths.rules);
@@ -23606,7 +23572,7 @@ async function importStatements(directory, agent, options, configLoader = loadIm
23606
23572
  const importContext = loadContext(directory, options.contextId);
23607
23573
  const csvPath = path9.join(directory, importContext.filePath);
23608
23574
  if (!fs10.existsSync(csvPath)) {
23609
- return buildErrorResult3(`CSV file not found: ${importContext.filePath}`, "The file may have been moved or deleted");
23575
+ return buildErrorResult(`CSV file not found: ${importContext.filePath}`, "The file may have been moved or deleted");
23610
23576
  }
23611
23577
  const csvFiles = [csvPath];
23612
23578
  const fileResults = [];
@@ -23639,12 +23605,7 @@ async function importStatements(directory, agent, options, configLoader = loadIm
23639
23605
  totalUnknown += fileResult.unknownPostings.length;
23640
23606
  }
23641
23607
  for (const [_rulesFile, matchingCSVs] of rulesFileToCSVs.entries()) {
23642
- matchingCSVs.sort((a, b) => {
23643
- const aStat = fs10.statSync(a);
23644
- const bStat = fs10.statSync(b);
23645
- return bStat.mtime.getTime() - aStat.mtime.getTime();
23646
- });
23647
- const newestCSV = matchingCSVs[0];
23608
+ const newestCSV = sortByMtimeNewestFirst(matchingCSVs)[0];
23648
23609
  const fileResult = await processCsvFile(newestCSV, rulesMapping, directory, hledgerExecutor);
23649
23610
  fileResults.push(fileResult);
23650
23611
  if (fileResult.error) {
@@ -23758,7 +23719,7 @@ Note: This tool is typically called via import-pipeline for the full workflow.`,
23758
23719
  // src/tools/reconcile-statement.ts
23759
23720
  import * as fs11 from "fs";
23760
23721
  import * as path10 from "path";
23761
- function buildErrorResult4(params) {
23722
+ function buildErrorResult2(params) {
23762
23723
  return buildToolErrorResult(params.error, params.hint, {
23763
23724
  account: params.account ?? "",
23764
23725
  expectedBalance: params.expectedBalance ?? "",
@@ -23775,7 +23736,7 @@ function loadConfiguration(directory, configLoader) {
23775
23736
  return { config: config2 };
23776
23737
  } catch (error45) {
23777
23738
  return {
23778
- error: buildErrorResult4({
23739
+ error: buildErrorResult2({
23779
23740
  error: `Failed to load configuration: ${error45 instanceof Error ? error45.message : String(error45)}`,
23780
23741
  hint: "Ensure config/import/providers.yaml exists"
23781
23742
  })
@@ -23786,7 +23747,7 @@ function verifyCsvExists(directory, importContext) {
23786
23747
  const csvFile = path10.join(directory, importContext.filePath);
23787
23748
  if (!fs11.existsSync(csvFile)) {
23788
23749
  return {
23789
- error: buildErrorResult4({
23750
+ error: buildErrorResult2({
23790
23751
  error: `CSV file not found: ${importContext.filePath}`,
23791
23752
  hint: `The file may have been moved or deleted. Context ID: ${importContext.id}`
23792
23753
  })
@@ -23853,7 +23814,7 @@ function determineClosingBalance(csvFile, config2, importContext, manualClosingB
23853
23814
  const exampleBalance = `${currency} <amount>`;
23854
23815
  const retryCmd = buildRetryCommand(importContext.id, exampleBalance);
23855
23816
  return {
23856
- error: buildErrorResult4({
23817
+ error: buildErrorResult2({
23857
23818
  csvFile: relativeCsvPath,
23858
23819
  error: "No closing balance found in CSV metadata or data",
23859
23820
  hint: `Provide closingBalance parameter manually. Example retry: ${retryCmd}`,
@@ -23883,7 +23844,7 @@ function determineAccount(csvFile, rulesDir, importContext, manualAccount, relat
23883
23844
  if (!account) {
23884
23845
  const rulesHint = rulesFile ? `Add 'account1 assets:bank:...' to ${rulesFile} or retry with: ${buildRetryCommand(importContext.id, undefined, "assets:bank:...")}` : `Create a rules file in ${rulesDir} with 'account1' directive or retry with: ${buildRetryCommand(importContext.id, undefined, "assets:bank:...")}`;
23885
23846
  return {
23886
- error: buildErrorResult4({
23847
+ error: buildErrorResult2({
23887
23848
  csvFile: relativeCsvPath,
23888
23849
  error: "Could not determine account from rules file",
23889
23850
  hint: rulesHint,
@@ -23945,7 +23906,7 @@ function tryExtractClosingBalanceFromCSV(csvFile, rulesDir) {
23945
23906
  }
23946
23907
  }
23947
23908
  }
23948
- const balanceStr = currency ? `${currency} ${numericValue.toFixed(2)}` : numericValue.toFixed(2);
23909
+ const balanceStr = formatBalance(numericValue, currency || undefined);
23949
23910
  return {
23950
23911
  balance: balanceStr,
23951
23912
  confidence: "high",
@@ -23966,7 +23927,7 @@ async function reconcileStatement(directory, agent, options, configLoader = load
23966
23927
  try {
23967
23928
  importContext = loadContext(directory, options.contextId);
23968
23929
  } catch {
23969
- return buildErrorResult4({
23930
+ return buildErrorResult2({
23970
23931
  error: `Failed to load import context: ${options.contextId}`,
23971
23932
  hint: "Ensure the context ID is valid and the context file exists in .memory/"
23972
23933
  });
@@ -23995,7 +23956,7 @@ async function reconcileStatement(directory, agent, options, configLoader = load
23995
23956
  const { account } = accountResult;
23996
23957
  const lastTransactionDate = await getLastTransactionDate(mainJournalPath, account, hledgerExecutor);
23997
23958
  if (!lastTransactionDate) {
23998
- return buildErrorResult4({
23959
+ return buildErrorResult2({
23999
23960
  csvFile: relativeCsvPath,
24000
23961
  account,
24001
23962
  error: "No transactions found for account",
@@ -24005,7 +23966,7 @@ async function reconcileStatement(directory, agent, options, configLoader = load
24005
23966
  }
24006
23967
  const actualBalance = await getAccountBalance(mainJournalPath, account, lastTransactionDate, hledgerExecutor);
24007
23968
  if (actualBalance === null) {
24008
- return buildErrorResult4({
23969
+ return buildErrorResult2({
24009
23970
  csvFile: relativeCsvPath,
24010
23971
  account,
24011
23972
  lastTransactionDate,
@@ -24018,7 +23979,7 @@ async function reconcileStatement(directory, agent, options, configLoader = load
24018
23979
  try {
24019
23980
  doBalancesMatch = balancesMatch(closingBalance, actualBalance);
24020
23981
  } catch (error45) {
24021
- return buildErrorResult4({
23982
+ return buildErrorResult2({
24022
23983
  csvFile: relativeCsvPath,
24023
23984
  account,
24024
23985
  lastTransactionDate,
@@ -24047,7 +24008,7 @@ async function reconcileStatement(directory, agent, options, configLoader = load
24047
24008
  try {
24048
24009
  difference = calculateDifference(closingBalance, actualBalance);
24049
24010
  } catch (error45) {
24050
- return buildErrorResult4({
24011
+ return buildErrorResult2({
24051
24012
  csvFile: relativeCsvPath,
24052
24013
  account,
24053
24014
  lastTransactionDate,
@@ -24057,7 +24018,7 @@ async function reconcileStatement(directory, agent, options, configLoader = load
24057
24018
  metadata
24058
24019
  });
24059
24020
  }
24060
- return buildErrorResult4({
24021
+ return buildErrorResult2({
24061
24022
  csvFile: relativeCsvPath,
24062
24023
  account,
24063
24024
  lastTransactionDate,
@@ -24130,7 +24091,6 @@ function extractAccountsFromRulesFile(rulesPath) {
24130
24091
  const account2Match = trimmed.match(/account2\s+(.+?)(?:\s+|$)/);
24131
24092
  if (account2Match) {
24132
24093
  accounts.add(account2Match[1].trim());
24133
- continue;
24134
24094
  }
24135
24095
  }
24136
24096
  return accounts;
@@ -24148,52 +24108,46 @@ function getAllAccountsFromRules(rulesPaths) {
24148
24108
  function sortAccountDeclarations(accounts) {
24149
24109
  return Array.from(accounts).sort((a, b) => a.localeCompare(b));
24150
24110
  }
24151
- function ensureAccountDeclarations(yearJournalPath, accounts) {
24152
- if (!fs12.existsSync(yearJournalPath)) {
24153
- throw new Error(`Year journal not found: ${yearJournalPath}`);
24154
- }
24155
- const content = fs12.readFileSync(yearJournalPath, "utf-8");
24111
+ function parseJournalSections(content) {
24156
24112
  const lines = content.split(`
24157
24113
  `);
24158
24114
  const existingAccounts = new Set;
24159
24115
  const commentLines = [];
24160
- const accountLines = [];
24161
24116
  const otherLines = [];
24162
24117
  let inAccountSection = false;
24163
24118
  let accountSectionEnded = false;
24164
24119
  for (const line of lines) {
24165
24120
  const trimmed = line.trim();
24166
24121
  if (trimmed.startsWith(";") || trimmed.startsWith("#")) {
24167
- if (!accountSectionEnded) {
24168
- commentLines.push(line);
24169
- } else {
24170
- otherLines.push(line);
24171
- }
24122
+ (accountSectionEnded ? otherLines : commentLines).push(line);
24172
24123
  continue;
24173
24124
  }
24174
24125
  if (trimmed.startsWith("account ")) {
24175
24126
  inAccountSection = true;
24176
24127
  const accountMatch = trimmed.match(/^account\s+(.+?)(?:\s+|$)/);
24177
24128
  if (accountMatch) {
24178
- const accountName = accountMatch[1].trim();
24179
- existingAccounts.add(accountName);
24180
- accountLines.push(line);
24129
+ existingAccounts.add(accountMatch[1].trim());
24181
24130
  }
24182
24131
  continue;
24183
24132
  }
24184
24133
  if (trimmed === "") {
24185
- if (inAccountSection && !accountSectionEnded) {
24186
- accountLines.push(line);
24187
- } else {
24188
- otherLines.push(line);
24189
- }
24134
+ if (inAccountSection && !accountSectionEnded)
24135
+ continue;
24136
+ otherLines.push(line);
24190
24137
  continue;
24191
24138
  }
24192
- if (inAccountSection) {
24139
+ if (inAccountSection)
24193
24140
  accountSectionEnded = true;
24194
- }
24195
24141
  otherLines.push(line);
24196
24142
  }
24143
+ return { existingAccounts, commentLines, otherLines };
24144
+ }
24145
+ function ensureAccountDeclarations(yearJournalPath, accounts) {
24146
+ if (!fs12.existsSync(yearJournalPath)) {
24147
+ throw new Error(`Year journal not found: ${yearJournalPath}`);
24148
+ }
24149
+ const content = fs12.readFileSync(yearJournalPath, "utf-8");
24150
+ const { existingAccounts, commentLines, otherLines } = parseJournalSections(content);
24197
24151
  const missingAccounts = new Set;
24198
24152
  for (const account of accounts) {
24199
24153
  if (!existingAccounts.has(account)) {
@@ -24259,14 +24213,10 @@ class MarkdownLogger {
24259
24213
  }
24260
24214
  }
24261
24215
  info(message) {
24262
- this.buffer.push(message);
24263
- if (this.autoFlush)
24264
- this.flushAsync();
24216
+ this.log(message);
24265
24217
  }
24266
24218
  warn(message) {
24267
- this.buffer.push(`\u26A0\uFE0F **WARNING**: ${message}`);
24268
- if (this.autoFlush)
24269
- this.flushAsync();
24219
+ this.log(`\u26A0\uFE0F **WARNING**: ${message}`);
24270
24220
  }
24271
24221
  error(message, error45) {
24272
24222
  this.buffer.push(`\u274C **ERROR**: ${message}`);
@@ -24282,13 +24232,10 @@ class MarkdownLogger {
24282
24232
  this.buffer.push("```");
24283
24233
  this.buffer.push("");
24284
24234
  }
24285
- if (this.autoFlush)
24286
- this.flushAsync();
24235
+ this.autoFlushIfEnabled();
24287
24236
  }
24288
24237
  debug(message) {
24289
- this.buffer.push(`\uD83D\uDD0D ${message}`);
24290
- if (this.autoFlush)
24291
- this.flushAsync();
24238
+ this.log(`\uD83D\uDD0D ${message}`);
24292
24239
  }
24293
24240
  logStep(stepName, status, details) {
24294
24241
  const icon = status === "success" ? "\u2705" : status === "error" ? "\u274C" : "\u25B6\uFE0F";
@@ -24298,8 +24245,7 @@ class MarkdownLogger {
24298
24245
  this.buffer.push(` ${details}`);
24299
24246
  }
24300
24247
  this.buffer.push("");
24301
- if (this.autoFlush)
24302
- this.flushAsync();
24248
+ this.autoFlushIfEnabled();
24303
24249
  }
24304
24250
  logCommand(command, output) {
24305
24251
  this.buffer.push("```bash");
@@ -24317,16 +24263,14 @@ class MarkdownLogger {
24317
24263
  }
24318
24264
  this.buffer.push("```");
24319
24265
  this.buffer.push("");
24320
- if (this.autoFlush)
24321
- this.flushAsync();
24266
+ this.autoFlushIfEnabled();
24322
24267
  }
24323
24268
  logResult(data) {
24324
24269
  this.buffer.push("```json");
24325
24270
  this.buffer.push(JSON.stringify(data, null, 2));
24326
24271
  this.buffer.push("```");
24327
24272
  this.buffer.push("");
24328
- if (this.autoFlush)
24329
- this.flushAsync();
24273
+ this.autoFlushIfEnabled();
24330
24274
  }
24331
24275
  setContext(key, value) {
24332
24276
  this.context[key] = value;
@@ -24346,6 +24290,15 @@ class MarkdownLogger {
24346
24290
  getLogPath() {
24347
24291
  return this.logPath;
24348
24292
  }
24293
+ log(message) {
24294
+ this.buffer.push(message);
24295
+ this.autoFlushIfEnabled();
24296
+ }
24297
+ autoFlushIfEnabled() {
24298
+ if (!this.autoFlush)
24299
+ return;
24300
+ this.flushAsync();
24301
+ }
24349
24302
  flushAsync() {
24350
24303
  this.pendingFlush = this.flush().catch(() => {});
24351
24304
  }
@@ -24490,7 +24443,8 @@ async function executeAccountDeclarationsStep(context, contextId, logger) {
24490
24443
  break;
24491
24444
  }
24492
24445
  }
24493
- } catch {
24446
+ } catch (error45) {
24447
+ logger?.debug(`Failed to extract year from rules file ${rulesFile}: ${error45 instanceof Error ? error45.message : String(error45)}`);
24494
24448
  continue;
24495
24449
  }
24496
24450
  }
@@ -24528,6 +24482,37 @@ async function executeAccountDeclarationsStep(context, contextId, logger) {
24528
24482
  });
24529
24483
  logger?.endSection();
24530
24484
  }
24485
+ async function buildSuggestionContext(context, contextId, logger) {
24486
+ const { loadExistingAccounts: loadExistingAccounts2, extractRulePatternsFromFile: extractRulePatternsFromFile2 } = await Promise.resolve().then(() => (init_accountSuggester(), exports_accountSuggester));
24487
+ const config2 = context.configLoader(context.directory);
24488
+ const rulesDir = path12.join(context.directory, config2.paths.rules);
24489
+ const importCtx = loadContext(context.directory, contextId);
24490
+ const csvPath = path12.join(context.directory, importCtx.filePath);
24491
+ const rulesMapping = loadRulesMapping(rulesDir);
24492
+ const rulesFile = findRulesForCsv(csvPath, rulesMapping);
24493
+ if (!rulesFile) {
24494
+ return { existingAccounts: [], logger };
24495
+ }
24496
+ let yearJournalPath;
24497
+ try {
24498
+ const result = await context.hledgerExecutor(["print", "-f", rulesFile]);
24499
+ if (result.exitCode === 0) {
24500
+ const years = extractTransactionYears(result.stdout);
24501
+ if (years.size > 0) {
24502
+ yearJournalPath = ensureYearJournalExists(context.directory, Array.from(years)[0]);
24503
+ }
24504
+ }
24505
+ } catch (error45) {
24506
+ logger?.debug(`Could not determine year journal: ${error45 instanceof Error ? error45.message : String(error45)}`);
24507
+ }
24508
+ return {
24509
+ existingAccounts: yearJournalPath ? loadExistingAccounts2(yearJournalPath) : [],
24510
+ rulesFilePath: rulesFile,
24511
+ existingRules: extractRulePatternsFromFile2(rulesFile),
24512
+ yearJournalPath,
24513
+ logger
24514
+ };
24515
+ }
24531
24516
  async function executeDryRunStep(context, contextId, logger) {
24532
24517
  logger?.startSection("Step 3: Dry Run Import");
24533
24518
  logger?.logStep("Dry Run", "start");
@@ -24551,39 +24536,8 @@ async function executeDryRunStep(context, contextId, logger) {
24551
24536
  }
24552
24537
  if (allUnknownPostings.length > 0) {
24553
24538
  try {
24554
- const {
24555
- suggestAccountsForPostingsBatch: suggestAccountsForPostingsBatch2,
24556
- loadExistingAccounts: loadExistingAccounts2,
24557
- extractRulePatternsFromFile: extractRulePatternsFromFile2
24558
- } = await Promise.resolve().then(() => (init_accountSuggester(), exports_accountSuggester));
24559
- const config2 = context.configLoader(context.directory);
24560
- const rulesDir = path12.join(context.directory, config2.paths.rules);
24561
- const importCtx = loadContext(context.directory, contextId);
24562
- const csvPath = path12.join(context.directory, importCtx.filePath);
24563
- const rulesMapping = loadRulesMapping(rulesDir);
24564
- let yearJournalPath;
24565
- let firstRulesFile;
24566
- const rulesFile = findRulesForCsv(csvPath, rulesMapping);
24567
- if (rulesFile) {
24568
- firstRulesFile = rulesFile;
24569
- try {
24570
- const result = await context.hledgerExecutor(["print", "-f", rulesFile]);
24571
- if (result.exitCode === 0) {
24572
- const years = extractTransactionYears(result.stdout);
24573
- if (years.size > 0) {
24574
- const transactionYear = Array.from(years)[0];
24575
- yearJournalPath = ensureYearJournalExists(context.directory, transactionYear);
24576
- }
24577
- }
24578
- } catch {}
24579
- }
24580
- const suggestionContext = {
24581
- existingAccounts: yearJournalPath ? loadExistingAccounts2(yearJournalPath) : [],
24582
- rulesFilePath: firstRulesFile,
24583
- existingRules: firstRulesFile ? extractRulePatternsFromFile2(firstRulesFile) : undefined,
24584
- yearJournalPath,
24585
- logger
24586
- };
24539
+ const { suggestAccountsForPostingsBatch: suggestAccountsForPostingsBatch2 } = await Promise.resolve().then(() => (init_accountSuggester(), exports_accountSuggester));
24540
+ const suggestionContext = await buildSuggestionContext(context, contextId, logger);
24587
24541
  postingsWithSuggestions = await suggestAccountsForPostingsBatch2(allUnknownPostings, suggestionContext);
24588
24542
  } catch (error45) {
24589
24543
  logger?.error(`[ERROR] Failed to generate account suggestions: ${error45 instanceof Error ? error45.message : String(error45)}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fuzzle/opencode-accountant",
3
- "version": "0.5.1",
3
+ "version": "0.5.2-next.1",
4
4
  "description": "An OpenCode accounting agent, specialized in double-entry-bookkepping with hledger",
5
5
  "author": {
6
6
  "name": "ali bengali",