@fuzzle/opencode-accountant 0.3.0-next.1 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -201,10 +201,9 @@ The `import-pipeline` tool provides an atomic, safe import workflow using git wo
201
201
  - Creates an isolated git worktree
202
202
  - Syncs CSV files from main repo to worktree
203
203
  - Classifies CSV files by provider/currency
204
- - Extracts accounts from rules and creates declarations in year journal
205
204
  - Validates all transactions have matching rules
206
205
  - Imports transactions to the appropriate year journal
207
- - Reconciles closing balance (auto-detected from CSV metadata or data analysis)
206
+ - Reconciles closing balance (if available in CSV metadata)
208
207
  - Merges changes back to main branch with `--no-ff`
209
208
  - Deletes processed CSV files from main repo's import/incoming
210
209
  - Cleans up the worktree
@@ -238,30 +237,13 @@ The tool will use that rules file when processing `transactions.csv`.
238
237
 
239
238
  See the hledger documentation for details on rules file format and syntax.
240
239
 
241
- #### Account Declarations
242
-
243
- The pipeline automatically manages account declarations:
244
-
245
- - Scans rules files matched to the CSVs being imported
246
- - Extracts all accounts (account1 and account2 directives)
247
- - Creates or updates year journal files with sorted account declarations
248
- - Ensures `hledger check --strict` validation passes
249
-
250
- **No manual account setup required.** Account declarations are created proactively before import attempts.
251
-
252
240
  #### Unknown Postings
253
241
 
254
242
  When a transaction doesn't match any `if` pattern in the rules file, hledger assigns it to `income:unknown` or `expenses:unknown` depending on the transaction direction. The pipeline will fail at the validation step, reporting the unknown postings so you can add appropriate rules before retrying.
255
243
 
256
244
  #### Closing Balance Reconciliation
257
245
 
258
- The import pipeline automatically detects closing balance using the following fallback chain:
259
-
260
- 1. **CSV Metadata Extraction**: For providers with closing balance in metadata (e.g., UBS)
261
- 2. **CSV Data Analysis**: Extracts balance from the last transaction row if metadata unavailable
262
- 3. **Manual Override**: Use the `closingBalance` parameter when automatic detection fails
263
-
264
- Configure metadata extraction in `providers.yaml`:
246
+ For providers that include closing balance in CSV metadata (e.g., UBS), the tool automatically validates that the imported transactions result in the correct balance. Configure metadata extraction in `providers.yaml`:
265
247
 
266
248
  ```yaml
267
249
  metadata:
@@ -276,10 +258,7 @@ metadata:
276
258
  column: 1
277
259
  ```
278
260
 
279
- **Note:** For most CSV formats, the closing balance will be detected automatically. Manual override is only needed when:
280
-
281
- - CSV has no balance information in metadata or data
282
- - The auto-detected balance has low confidence
261
+ For providers without closing balance in metadata (e.g., Revolut), provide it manually via the `closingBalance` argument.
283
262
 
284
263
  ## Development
285
264
 
@@ -89,10 +89,9 @@ The `import-pipeline` tool provides an **atomic, safe workflow** using git workt
89
89
  3. **Automatic Processing**: The tool creates an isolated git worktree and:
90
90
  - Syncs CSV files from main repo to worktree
91
91
  - Classifies CSV files by provider/currency
92
- - Extracts required accounts from rules files and updates year journal
93
92
  - Validates all transactions have matching rules
94
93
  - Imports transactions to the appropriate year journal
95
- - Reconciles closing balance (auto-detected from CSV metadata or data, or manual override)
94
+ - Reconciles closing balance (if available in CSV metadata)
96
95
  - Merges changes back to main branch with `--no-ff`
97
96
  - Deletes processed CSV files from main repo's import/incoming
98
97
  - Cleans up the worktree
@@ -109,25 +108,6 @@ The `import-pipeline` tool provides an **atomic, safe workflow** using git workt
109
108
  - Use field names from the `fields` directive for matching
110
109
  - Unknown account pattern: `income:unknown` (positive amounts) / `expenses:unknown` (negative amounts)
111
110
 
112
- ### Automatic Account Declarations
113
-
114
- The import pipeline automatically:
115
-
116
- - Scans matched rules files for all account references (account1, account2 directives)
117
- - Creates/updates year journal files (e.g., ledger/2026.journal) with sorted account declarations
118
- - Prevents `hledger check --strict` failures due to missing account declarations
119
- - No manual account setup required
120
-
121
- ### Automatic Balance Detection
122
-
123
- The reconciliation step attempts to extract closing balance from:
124
-
125
- 1. CSV header metadata (e.g., UBS "Closing balance:" row)
126
- 2. CSV data analysis (balance field in last transaction row)
127
- 3. Manual override via `closingBalance` parameter (fallback)
128
-
129
- For most providers, manual balance input is no longer required.
130
-
131
111
  ## Tool Usage Reference
132
112
 
133
113
  The following are MCP tools available to you. Always call these tools directly - do not attempt to replicate their behavior with shell commands.
@@ -158,13 +138,12 @@ The following are MCP tools available to you. Always call these tools directly -
158
138
  1. Creates isolated git worktree
159
139
  2. Syncs CSV files from main repo to worktree
160
140
  3. Classifies CSV files (unless `skipClassify: true`)
161
- 4. Extracts accounts from matched rules and updates year journal with declarations
162
- 5. Validates all transactions have matching rules (dry run)
163
- 6. Imports transactions to year journal
164
- 7. Reconciles closing balance (auto-detected from CSV metadata/data or manual override)
165
- 8. Merges to main with `--no-ff` commit
166
- 9. Deletes processed CSV files from main repo's import/incoming
167
- 10. Cleans up worktree
141
+ 4. Validates all transactions have matching rules (dry run)
142
+ 5. Imports transactions to year journal
143
+ 6. Reconciles closing balance against CSV metadata or manual value
144
+ 7. Merges to main with `--no-ff` commit
145
+ 8. Deletes processed CSV files from main repo's import/incoming
146
+ 9. Cleans up worktree
168
147
 
169
148
  **Output:** Returns step-by-step results with success/failure for each phase
170
149
 
package/dist/index.js CHANGED
@@ -24191,6 +24191,12 @@ function buildErrorResult4(params) {
24191
24191
  ...params
24192
24192
  });
24193
24193
  }
24194
+ function buildSuccessResult4(params) {
24195
+ return JSON.stringify({
24196
+ success: true,
24197
+ ...params
24198
+ });
24199
+ }
24194
24200
  function validateWorktree(directory, worktreeChecker) {
24195
24201
  if (!worktreeChecker(directory)) {
24196
24202
  return buildErrorResult4({
@@ -24229,7 +24235,7 @@ function findCsvToReconcile(doneDir, options) {
24229
24235
  const relativePath = path11.relative(path11.dirname(path11.dirname(doneDir)), csvFile);
24230
24236
  return { csvFile, relativePath };
24231
24237
  }
24232
- function determineClosingBalance(csvFile, config2, options, relativeCsvPath, rulesDir) {
24238
+ function determineClosingBalance(csvFile, config2, options, relativeCsvPath) {
24233
24239
  let metadata;
24234
24240
  try {
24235
24241
  const content = fs11.readFileSync(csvFile, "utf-8");
@@ -24248,41 +24254,17 @@ function determineClosingBalance(csvFile, config2, options, relativeCsvPath, rul
24248
24254
  }
24249
24255
  }
24250
24256
  if (!closingBalance) {
24251
- const csvAnalysis = tryExtractClosingBalanceFromCSV(csvFile, rulesDir);
24252
- if (csvAnalysis && csvAnalysis.confidence === "high") {
24253
- closingBalance = csvAnalysis.balance;
24254
- return { closingBalance, metadata, fromCSVAnalysis: true };
24255
- }
24256
- }
24257
- if (!closingBalance) {
24258
- const retryCmd = buildRetryCommand(options, "CHF 2324.79", options.account);
24259
24257
  return {
24260
24258
  error: buildErrorResult4({
24261
24259
  csvFile: relativeCsvPath,
24262
- error: "No closing balance found in CSV metadata or data",
24263
- hint: `Provide closingBalance parameter manually. Example retry: ${retryCmd}`,
24260
+ error: "No closing balance found in CSV metadata",
24261
+ hint: "Provide closingBalance parameter manually",
24264
24262
  metadata
24265
24263
  })
24266
24264
  };
24267
24265
  }
24268
24266
  return { closingBalance, metadata };
24269
24267
  }
24270
- function buildRetryCommand(options, closingBalance, account) {
24271
- const parts = ["import-pipeline"];
24272
- if (options.provider) {
24273
- parts.push(`--provider ${options.provider}`);
24274
- }
24275
- if (options.currency) {
24276
- parts.push(`--currency ${options.currency}`);
24277
- }
24278
- if (closingBalance) {
24279
- parts.push(`--closingBalance "${closingBalance}"`);
24280
- }
24281
- if (account) {
24282
- parts.push(`--account "${account}"`);
24283
- }
24284
- return parts.join(" ");
24285
- }
24286
24268
  function determineAccount(csvFile, rulesDir, options, relativeCsvPath, metadata) {
24287
24269
  let account = options.account;
24288
24270
  if (!account) {
@@ -24295,7 +24277,7 @@ function determineAccount(csvFile, rulesDir, options, relativeCsvPath, metadata)
24295
24277
  if (!account) {
24296
24278
  const rulesMapping = loadRulesMapping(rulesDir);
24297
24279
  const rulesFile = findRulesForCsv(csvFile, rulesMapping);
24298
- const rulesHint = rulesFile ? `Add 'account1 assets:bank:...' to ${rulesFile} or retry with: ${buildRetryCommand(options, undefined, "assets:bank:...")}` : `Create a rules file in ${rulesDir} with 'account1' directive or retry with: ${buildRetryCommand(options, undefined, "assets:bank:...")}`;
24280
+ const rulesHint = rulesFile ? `Add 'account1 assets:bank:...' to ${rulesFile} or use --account parameter` : `Create a rules file in ${rulesDir} with 'account1' directive or use --account parameter`;
24299
24281
  return {
24300
24282
  error: buildErrorResult4({
24301
24283
  csvFile: relativeCsvPath,
@@ -24307,70 +24289,6 @@ function determineAccount(csvFile, rulesDir, options, relativeCsvPath, metadata)
24307
24289
  }
24308
24290
  return { account };
24309
24291
  }
24310
- function tryExtractClosingBalanceFromCSV(csvFile, rulesDir) {
24311
- try {
24312
- const rulesMapping = loadRulesMapping(rulesDir);
24313
- const rulesFile = findRulesForCsv(csvFile, rulesMapping);
24314
- if (!rulesFile) {
24315
- return null;
24316
- }
24317
- const rulesContent = fs11.readFileSync(rulesFile, "utf-8");
24318
- const rulesConfig = parseRulesFile(rulesContent);
24319
- const csvRows = parseCsvFile(csvFile, rulesConfig);
24320
- if (csvRows.length === 0) {
24321
- return null;
24322
- }
24323
- const balanceFieldNames = [
24324
- "balance",
24325
- "Balance",
24326
- "BALANCE",
24327
- "closing_balance",
24328
- "Closing Balance",
24329
- "account_balance",
24330
- "Account Balance",
24331
- "saldo",
24332
- "Saldo",
24333
- "SALDO"
24334
- ];
24335
- const lastRow = csvRows[csvRows.length - 1];
24336
- let balanceField;
24337
- let balanceValue;
24338
- for (const fieldName of balanceFieldNames) {
24339
- if (lastRow[fieldName] !== undefined && lastRow[fieldName].trim() !== "") {
24340
- balanceField = fieldName;
24341
- balanceValue = lastRow[fieldName];
24342
- break;
24343
- }
24344
- }
24345
- if (balanceValue && balanceField) {
24346
- const numericValue = parseAmountValue(balanceValue);
24347
- let currency = "";
24348
- const balanceCurrencyMatch = balanceValue.match(/[A-Z]{3}/);
24349
- if (balanceCurrencyMatch) {
24350
- currency = balanceCurrencyMatch[0];
24351
- }
24352
- if (!currency) {
24353
- const amountField = rulesConfig.amountFields.single || rulesConfig.amountFields.credit || rulesConfig.amountFields.debit;
24354
- if (amountField) {
24355
- const amountStr = lastRow[amountField] || "";
24356
- const currencyMatch = amountStr.match(/[A-Z]{3}/);
24357
- if (currencyMatch) {
24358
- currency = currencyMatch[0];
24359
- }
24360
- }
24361
- }
24362
- const balanceStr = currency ? `${currency} ${numericValue.toFixed(2)}` : numericValue.toFixed(2);
24363
- return {
24364
- balance: balanceStr,
24365
- confidence: "high",
24366
- method: `Extracted from ${balanceField} field in last CSV row`
24367
- };
24368
- }
24369
- return null;
24370
- } catch {
24371
- return null;
24372
- }
24373
- }
24374
24292
  async function reconcileStatement(directory, agent, options, configLoader = loadImportConfig, hledgerExecutor = defaultHledgerExecutor, worktreeChecker = isInWorktree) {
24375
24293
  const restrictionError = checkAccountantAgent(agent, "reconcile statement");
24376
24294
  if (restrictionError) {
@@ -24393,11 +24311,11 @@ async function reconcileStatement(directory, agent, options, configLoader = load
24393
24311
  return csvResult.error;
24394
24312
  }
24395
24313
  const { csvFile, relativePath: relativeCsvPath } = csvResult;
24396
- const balanceResult = determineClosingBalance(csvFile, config2, options, relativeCsvPath, rulesDir);
24314
+ const balanceResult = determineClosingBalance(csvFile, config2, options, relativeCsvPath);
24397
24315
  if ("error" in balanceResult) {
24398
24316
  return balanceResult.error;
24399
24317
  }
24400
- const { closingBalance, metadata, fromCSVAnalysis } = balanceResult;
24318
+ const { closingBalance, metadata } = balanceResult;
24401
24319
  const accountResult = determineAccount(csvFile, rulesDir, options, relativeCsvPath, metadata);
24402
24320
  if ("error" in accountResult) {
24403
24321
  return accountResult.error;
@@ -24439,19 +24357,14 @@ async function reconcileStatement(directory, agent, options, configLoader = load
24439
24357
  });
24440
24358
  }
24441
24359
  if (doBalancesMatch) {
24442
- const result = {
24443
- success: true,
24360
+ return buildSuccessResult4({
24444
24361
  csvFile: relativeCsvPath,
24445
24362
  account,
24446
24363
  lastTransactionDate,
24447
24364
  expectedBalance: closingBalance,
24448
24365
  actualBalance,
24449
24366
  metadata
24450
- };
24451
- if (fromCSVAnalysis) {
24452
- result.note = `Closing balance auto-detected from CSV data (no metadata available). Account: ${account}`;
24453
- }
24454
- return JSON.stringify(result);
24367
+ });
24455
24368
  }
24456
24369
  let difference;
24457
24370
  try {
@@ -24516,125 +24429,8 @@ It must be run inside an import worktree (use import-pipeline for the full workf
24516
24429
  }
24517
24430
  });
24518
24431
  // src/tools/import-pipeline.ts
24519
- import * as fs13 from "fs";
24520
- import * as path12 from "path";
24521
-
24522
- // src/utils/accountDeclarations.ts
24523
24432
  import * as fs12 from "fs";
24524
- function extractAccountsFromRulesFile(rulesPath) {
24525
- const accounts = new Set;
24526
- if (!fs12.existsSync(rulesPath)) {
24527
- return accounts;
24528
- }
24529
- const content = fs12.readFileSync(rulesPath, "utf-8");
24530
- const lines = content.split(`
24531
- `);
24532
- for (const line of lines) {
24533
- const trimmed = line.trim();
24534
- if (trimmed.startsWith("#") || trimmed.startsWith(";") || trimmed === "") {
24535
- continue;
24536
- }
24537
- const account1Match = trimmed.match(/^account1\s+(.+?)(?:\s+|$)/);
24538
- if (account1Match) {
24539
- accounts.add(account1Match[1].trim());
24540
- continue;
24541
- }
24542
- const account2Match = trimmed.match(/account2\s+(.+?)(?:\s+|$)/);
24543
- if (account2Match) {
24544
- accounts.add(account2Match[1].trim());
24545
- continue;
24546
- }
24547
- }
24548
- return accounts;
24549
- }
24550
- function getAllAccountsFromRules(rulesPaths) {
24551
- const allAccounts = new Set;
24552
- for (const rulesPath of rulesPaths) {
24553
- const accounts = extractAccountsFromRulesFile(rulesPath);
24554
- for (const account of accounts) {
24555
- allAccounts.add(account);
24556
- }
24557
- }
24558
- return allAccounts;
24559
- }
24560
- function sortAccountDeclarations(accounts) {
24561
- return Array.from(accounts).sort((a, b) => a.localeCompare(b));
24562
- }
24563
- function ensureAccountDeclarations(yearJournalPath, accounts) {
24564
- if (!fs12.existsSync(yearJournalPath)) {
24565
- throw new Error(`Year journal not found: ${yearJournalPath}`);
24566
- }
24567
- const content = fs12.readFileSync(yearJournalPath, "utf-8");
24568
- const lines = content.split(`
24569
- `);
24570
- const existingAccounts = new Set;
24571
- const commentLines = [];
24572
- const accountLines = [];
24573
- const otherLines = [];
24574
- let inAccountSection = false;
24575
- let accountSectionEnded = false;
24576
- for (const line of lines) {
24577
- const trimmed = line.trim();
24578
- if (trimmed.startsWith(";") || trimmed.startsWith("#")) {
24579
- if (!accountSectionEnded) {
24580
- commentLines.push(line);
24581
- } else {
24582
- otherLines.push(line);
24583
- }
24584
- continue;
24585
- }
24586
- if (trimmed.startsWith("account ")) {
24587
- inAccountSection = true;
24588
- const accountMatch = trimmed.match(/^account\s+(.+?)(?:\s+|$)/);
24589
- if (accountMatch) {
24590
- const accountName = accountMatch[1].trim();
24591
- existingAccounts.add(accountName);
24592
- accountLines.push(line);
24593
- }
24594
- continue;
24595
- }
24596
- if (trimmed === "") {
24597
- if (inAccountSection && !accountSectionEnded) {
24598
- accountLines.push(line);
24599
- } else {
24600
- otherLines.push(line);
24601
- }
24602
- continue;
24603
- }
24604
- if (inAccountSection) {
24605
- accountSectionEnded = true;
24606
- }
24607
- otherLines.push(line);
24608
- }
24609
- const missingAccounts = new Set;
24610
- for (const account of accounts) {
24611
- if (!existingAccounts.has(account)) {
24612
- missingAccounts.add(account);
24613
- }
24614
- }
24615
- if (missingAccounts.size === 0) {
24616
- return { added: [], updated: false };
24617
- }
24618
- const allAccounts = new Set([...existingAccounts, ...missingAccounts]);
24619
- const sortedAccounts = sortAccountDeclarations(allAccounts);
24620
- const newAccountLines = sortedAccounts.map((account) => `account ${account}`);
24621
- const newContent = [];
24622
- newContent.push(...commentLines);
24623
- if (newAccountLines.length > 0) {
24624
- newContent.push("");
24625
- newContent.push(...newAccountLines);
24626
- newContent.push("");
24627
- }
24628
- newContent.push(...otherLines);
24629
- fs12.writeFileSync(yearJournalPath, newContent.join(`
24630
- `));
24631
- return {
24632
- added: Array.from(missingAccounts).sort(),
24633
- updated: true
24634
- };
24635
- }
24636
-
24637
- // src/tools/import-pipeline.ts
24433
+ import * as path12 from "path";
24638
24434
  class NoTransactionsError extends Error {
24639
24435
  constructor() {
24640
24436
  super("No transactions to import");
@@ -24648,7 +24444,7 @@ function buildStepResult(success2, message, details) {
24648
24444
  }
24649
24445
  return result;
24650
24446
  }
24651
- function buildSuccessResult4(result, summary) {
24447
+ function buildSuccessResult5(result, summary) {
24652
24448
  result.success = true;
24653
24449
  result.summary = summary;
24654
24450
  return JSON.stringify(result);
@@ -24674,7 +24470,7 @@ function buildCommitMessage(provider, currency, fromDate, untilDate, transaction
24674
24470
  }
24675
24471
  function cleanupIncomingFiles(worktree, context) {
24676
24472
  const incomingDir = path12.join(worktree.mainRepoPath, "import/incoming");
24677
- if (!fs13.existsSync(incomingDir)) {
24473
+ if (!fs12.existsSync(incomingDir)) {
24678
24474
  return;
24679
24475
  }
24680
24476
  const importStep = context.result.steps.import;
@@ -24691,9 +24487,9 @@ function cleanupIncomingFiles(worktree, context) {
24691
24487
  continue;
24692
24488
  const filename = path12.basename(fileResult.csv);
24693
24489
  const filePath = path12.join(incomingDir, filename);
24694
- if (fs13.existsSync(filePath)) {
24490
+ if (fs12.existsSync(filePath)) {
24695
24491
  try {
24696
- fs13.unlinkSync(filePath);
24492
+ fs12.unlinkSync(filePath);
24697
24493
  deletedCount++;
24698
24494
  } catch (error45) {
24699
24495
  console.error(`[ERROR] Failed to delete ${filename}: ${error45 instanceof Error ? error45.message : String(error45)}`);
@@ -24724,86 +24520,6 @@ async function executeClassifyStep(context, worktree) {
24724
24520
  };
24725
24521
  context.result.steps.classify = buildStepResult(success2, message, details);
24726
24522
  }
24727
- async function executeAccountDeclarationsStep(context, worktree) {
24728
- const config2 = context.configLoader(worktree.path);
24729
- const pendingDir = path12.join(worktree.path, config2.paths.pending);
24730
- const rulesDir = path12.join(worktree.path, config2.paths.rules);
24731
- const csvFiles = findCsvFiles(pendingDir, context.options.provider, context.options.currency);
24732
- if (csvFiles.length === 0) {
24733
- context.result.steps.accountDeclarations = buildStepResult(true, "No CSV files to process", {
24734
- accountsAdded: [],
24735
- journalUpdated: "",
24736
- rulesScanned: []
24737
- });
24738
- return;
24739
- }
24740
- const rulesMapping = loadRulesMapping(rulesDir);
24741
- const matchedRulesFiles = new Set;
24742
- for (const csvFile of csvFiles) {
24743
- const rulesFile = findRulesForCsv(csvFile, rulesMapping);
24744
- if (rulesFile) {
24745
- matchedRulesFiles.add(rulesFile);
24746
- }
24747
- }
24748
- if (matchedRulesFiles.size === 0) {
24749
- context.result.steps.accountDeclarations = buildStepResult(true, "No matching rules files found", {
24750
- accountsAdded: [],
24751
- journalUpdated: "",
24752
- rulesScanned: []
24753
- });
24754
- return;
24755
- }
24756
- const allAccounts = getAllAccountsFromRules(Array.from(matchedRulesFiles));
24757
- if (allAccounts.size === 0) {
24758
- context.result.steps.accountDeclarations = buildStepResult(true, "No accounts found in rules files", {
24759
- accountsAdded: [],
24760
- journalUpdated: "",
24761
- rulesScanned: Array.from(matchedRulesFiles).map((f) => path12.relative(worktree.path, f))
24762
- });
24763
- return;
24764
- }
24765
- let transactionYear;
24766
- for (const rulesFile of matchedRulesFiles) {
24767
- try {
24768
- const result2 = await context.hledgerExecutor(["print", "-f", rulesFile]);
24769
- if (result2.exitCode === 0) {
24770
- const years = extractTransactionYears(result2.stdout);
24771
- if (years.size > 0) {
24772
- transactionYear = Array.from(years)[0];
24773
- break;
24774
- }
24775
- }
24776
- } catch {
24777
- continue;
24778
- }
24779
- }
24780
- if (!transactionYear) {
24781
- context.result.steps.accountDeclarations = buildStepResult(false, "Could not determine transaction year from CSV files", {
24782
- accountsAdded: [],
24783
- journalUpdated: "",
24784
- rulesScanned: Array.from(matchedRulesFiles).map((f) => path12.relative(worktree.path, f))
24785
- });
24786
- return;
24787
- }
24788
- let yearJournalPath;
24789
- try {
24790
- yearJournalPath = ensureYearJournalExists(worktree.path, transactionYear);
24791
- } catch (error45) {
24792
- context.result.steps.accountDeclarations = buildStepResult(false, `Failed to create year journal: ${error45 instanceof Error ? error45.message : String(error45)}`, {
24793
- accountsAdded: [],
24794
- journalUpdated: "",
24795
- rulesScanned: Array.from(matchedRulesFiles).map((f) => path12.relative(worktree.path, f))
24796
- });
24797
- return;
24798
- }
24799
- const result = ensureAccountDeclarations(yearJournalPath, allAccounts);
24800
- const message = result.added.length > 0 ? `Added ${result.added.length} account declaration(s) to ${path12.relative(worktree.path, yearJournalPath)}` : "All required accounts already declared";
24801
- context.result.steps.accountDeclarations = buildStepResult(true, message, {
24802
- accountsAdded: result.added,
24803
- journalUpdated: path12.relative(worktree.path, yearJournalPath),
24804
- rulesScanned: Array.from(matchedRulesFiles).map((f) => path12.relative(worktree.path, f))
24805
- });
24806
- }
24807
24523
  async function executeDryRunStep(context, worktree) {
24808
24524
  const inWorktree = () => true;
24809
24525
  const dryRunResult = await importStatements(worktree.path, context.agent, {
@@ -24896,7 +24612,7 @@ function handleNoTransactions(result) {
24896
24612
  result.steps.import = buildStepResult(true, "No transactions to import");
24897
24613
  result.steps.reconcile = buildStepResult(true, "Reconciliation skipped (no transactions)");
24898
24614
  result.steps.merge = buildStepResult(true, "Merge skipped (no changes)");
24899
- return buildSuccessResult4(result, "No transactions found to import");
24615
+ return buildSuccessResult5(result, "No transactions found to import");
24900
24616
  }
24901
24617
  async function importPipeline(directory, agent, options, configLoader = loadImportConfig, hledgerExecutor = defaultHledgerExecutor) {
24902
24618
  const restrictionError = checkAccountantAgent(agent, "import pipeline");
@@ -24940,7 +24656,6 @@ async function importPipeline(directory, agent, options, configLoader = loadImpo
24940
24656
  }
24941
24657
  try {
24942
24658
  await executeClassifyStep(context, worktree);
24943
- await executeAccountDeclarationsStep(context, worktree);
24944
24659
  await executeDryRunStep(context, worktree);
24945
24660
  await executeImportStep(context, worktree);
24946
24661
  await executeReconcileStep(context, worktree);
@@ -24978,7 +24693,7 @@ async function importPipeline(directory, agent, options, configLoader = loadImpo
24978
24693
  };
24979
24694
  }
24980
24695
  const transactionCount = context.result.steps.import?.details?.summary?.totalTransactions || 0;
24981
- return buildSuccessResult4(result, `Successfully imported ${transactionCount} transaction(s)`);
24696
+ return buildSuccessResult5(result, `Successfully imported ${transactionCount} transaction(s)`);
24982
24697
  } catch (error45) {
24983
24698
  result.steps.cleanup = buildStepResult(true, "Worktree cleaned up after failure (CSV files preserved for retry)", {
24984
24699
  cleanedAfterFailure: true,
@@ -25043,7 +24758,7 @@ This tool orchestrates the full import workflow in an isolated git worktree:
25043
24758
  }
25044
24759
  });
25045
24760
  // src/tools/init-directories.ts
25046
- import * as fs14 from "fs";
24761
+ import * as fs13 from "fs";
25047
24762
  import * as path13 from "path";
25048
24763
  async function initDirectories(directory) {
25049
24764
  try {
@@ -25051,8 +24766,8 @@ async function initDirectories(directory) {
25051
24766
  const directoriesCreated = [];
25052
24767
  const gitkeepFiles = [];
25053
24768
  const importBase = path13.join(directory, "import");
25054
- if (!fs14.existsSync(importBase)) {
25055
- fs14.mkdirSync(importBase, { recursive: true });
24769
+ if (!fs13.existsSync(importBase)) {
24770
+ fs13.mkdirSync(importBase, { recursive: true });
25056
24771
  directoriesCreated.push("import");
25057
24772
  }
25058
24773
  const pathsToCreate = [
@@ -25063,19 +24778,19 @@ async function initDirectories(directory) {
25063
24778
  ];
25064
24779
  for (const { path: dirPath } of pathsToCreate) {
25065
24780
  const fullPath = path13.join(directory, dirPath);
25066
- if (!fs14.existsSync(fullPath)) {
25067
- fs14.mkdirSync(fullPath, { recursive: true });
24781
+ if (!fs13.existsSync(fullPath)) {
24782
+ fs13.mkdirSync(fullPath, { recursive: true });
25068
24783
  directoriesCreated.push(dirPath);
25069
24784
  }
25070
24785
  const gitkeepPath = path13.join(fullPath, ".gitkeep");
25071
- if (!fs14.existsSync(gitkeepPath)) {
25072
- fs14.writeFileSync(gitkeepPath, "");
24786
+ if (!fs13.existsSync(gitkeepPath)) {
24787
+ fs13.writeFileSync(gitkeepPath, "");
25073
24788
  gitkeepFiles.push(path13.join(dirPath, ".gitkeep"));
25074
24789
  }
25075
24790
  }
25076
24791
  const gitignorePath = path13.join(importBase, ".gitignore");
25077
24792
  let gitignoreCreated = false;
25078
- if (!fs14.existsSync(gitignorePath)) {
24793
+ if (!fs13.existsSync(gitignorePath)) {
25079
24794
  const gitignoreContent = `# Ignore CSV/PDF files in temporary directories
25080
24795
  /incoming/*.csv
25081
24796
  /incoming/*.pdf
@@ -25093,7 +24808,7 @@ async function initDirectories(directory) {
25093
24808
  .DS_Store
25094
24809
  Thumbs.db
25095
24810
  `;
25096
- fs14.writeFileSync(gitignorePath, gitignoreContent);
24811
+ fs13.writeFileSync(gitignorePath, gitignoreContent);
25097
24812
  gitignoreCreated = true;
25098
24813
  }
25099
24814
  const parts = [];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fuzzle/opencode-accountant",
3
- "version": "0.3.0-next.1",
3
+ "version": "0.3.0",
4
4
  "description": "An OpenCode accounting agent, specialized in double-entry-bookkepping with hledger",
5
5
  "author": {
6
6
  "name": "ali bengali",