@fuzzle/opencode-accountant 0.5.1-next.1 → 0.5.2
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 +155 -198
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -2687,16 +2687,11 @@ function loadAgent(filePath) {
|
|
|
2687
2687
|
throw new Error(`Invalid frontmatter format in ${filePath}`);
|
|
2688
2688
|
}
|
|
2689
2689
|
const data = jsYaml.load(match[1]);
|
|
2690
|
+
const { description, ...optional } = data;
|
|
2690
2691
|
return {
|
|
2691
|
-
description
|
|
2692
|
+
description,
|
|
2692
2693
|
prompt: match[2].trim(),
|
|
2693
|
-
...
|
|
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 }
|
|
2694
|
+
...Object.fromEntries(Object.entries(optional).filter(([, v]) => v !== undefined))
|
|
2700
2695
|
};
|
|
2701
2696
|
}
|
|
2702
2697
|
var init_agentLoader = __esm(() => {
|
|
@@ -4293,94 +4288,63 @@ function extractRulePatternsFromFile(rulesPath) {
|
|
|
4293
4288
|
}
|
|
4294
4289
|
return patterns;
|
|
4295
4290
|
}
|
|
4296
|
-
function
|
|
4297
|
-
|
|
4291
|
+
function buildAccountHierarchySection(accounts) {
|
|
4292
|
+
if (accounts.length === 0)
|
|
4293
|
+
return "";
|
|
4294
|
+
return `## Existing Account Hierarchy
|
|
4298
4295
|
|
|
4299
|
-
|
|
4300
|
-
|
|
4296
|
+
${accounts.map((acc) => `- ${acc}`).join(`
|
|
4297
|
+
`)}
|
|
4301
4298
|
|
|
4302
4299
|
`;
|
|
4303
|
-
|
|
4304
|
-
|
|
4300
|
+
}
|
|
4301
|
+
function buildRuleExamplesSection(rules) {
|
|
4302
|
+
if (!rules || rules.length === 0)
|
|
4303
|
+
return "";
|
|
4304
|
+
const sampleSize = Math.min(EXAMPLE_PATTERN_SAMPLE_SIZE, rules.length);
|
|
4305
|
+
const lines = rules.slice(0, sampleSize).map((p) => `- If description matches "${p.condition}" \u2192 ${p.account}`);
|
|
4306
|
+
return `## Example Classification Patterns from Rules
|
|
4305
4307
|
|
|
4306
|
-
|
|
4307
|
-
|
|
4308
|
-
`);
|
|
4309
|
-
prompt += `
|
|
4308
|
+
${lines.join(`
|
|
4309
|
+
`)}
|
|
4310
4310
|
|
|
4311
4311
|
`;
|
|
4312
|
-
|
|
4313
|
-
|
|
4314
|
-
|
|
4315
|
-
|
|
4316
|
-
|
|
4317
|
-
|
|
4318
|
-
|
|
4319
|
-
|
|
4320
|
-
|
|
4321
|
-
|
|
4322
|
-
}
|
|
4323
|
-
prompt += `
|
|
4324
|
-
`;
|
|
4325
|
-
}
|
|
4326
|
-
prompt += `## Transactions to Classify
|
|
4327
|
-
|
|
4328
|
-
`;
|
|
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
|
-
`;
|
|
4312
|
+
}
|
|
4313
|
+
function buildTransactionsSection(postings) {
|
|
4314
|
+
const lines = postings.map((posting, index) => {
|
|
4315
|
+
const parts = [
|
|
4316
|
+
`Transaction ${index + 1}:`,
|
|
4317
|
+
`- Type: ${posting.account === "income:unknown" ? "Income" : "Expense"}`,
|
|
4318
|
+
`- Date: ${posting.date}`,
|
|
4319
|
+
`- Description: ${posting.description}`,
|
|
4320
|
+
`- Amount: ${posting.amount}`
|
|
4321
|
+
];
|
|
4340
4322
|
if (posting.csvRow) {
|
|
4341
|
-
|
|
4342
|
-
`;
|
|
4323
|
+
parts.push(`- CSV Data: ${JSON.stringify(posting.csvRow)}`);
|
|
4343
4324
|
}
|
|
4344
|
-
|
|
4345
|
-
|
|
4325
|
+
return parts.join(`
|
|
4326
|
+
`);
|
|
4346
4327
|
});
|
|
4347
|
-
|
|
4348
|
-
|
|
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
|
|
4328
|
+
return `## Transactions to Classify
|
|
4355
4329
|
|
|
4356
|
-
|
|
4357
|
-
prompt += `## Response Format
|
|
4330
|
+
${lines.join(`
|
|
4358
4331
|
|
|
4359
|
-
|
|
4360
|
-
prompt += `Respond with suggestions for ALL transactions in this exact format:
|
|
4332
|
+
`)}
|
|
4361
4333
|
|
|
4362
4334
|
`;
|
|
4363
|
-
|
|
4364
|
-
|
|
4365
|
-
|
|
4366
|
-
|
|
4367
|
-
|
|
4368
|
-
|
|
4369
|
-
|
|
4370
|
-
|
|
4371
|
-
|
|
4372
|
-
|
|
4373
|
-
|
|
4374
|
-
|
|
4375
|
-
|
|
4376
|
-
prompt += `CONFIDENCE: {high|medium|low}
|
|
4377
|
-
`;
|
|
4378
|
-
prompt += `REASONING: {brief one-sentence explanation}
|
|
4379
|
-
|
|
4380
|
-
`;
|
|
4381
|
-
prompt += `... (continue for all transactions)
|
|
4382
|
-
`;
|
|
4383
|
-
return prompt;
|
|
4335
|
+
}
|
|
4336
|
+
function buildBatchSuggestionPrompt(postings, context) {
|
|
4337
|
+
return [
|
|
4338
|
+
`You are an accounting assistant helping categorize bank transactions.
|
|
4339
|
+
`,
|
|
4340
|
+
`I have ${postings.length} transaction(s) that need account classification:
|
|
4341
|
+
`,
|
|
4342
|
+
buildAccountHierarchySection(context.existingAccounts),
|
|
4343
|
+
buildRuleExamplesSection(context.existingRules),
|
|
4344
|
+
buildTransactionsSection(postings),
|
|
4345
|
+
RESPONSE_FORMAT_SECTION
|
|
4346
|
+
].join(`
|
|
4347
|
+
`);
|
|
4384
4348
|
}
|
|
4385
4349
|
function parseBatchSuggestionResponse(response) {
|
|
4386
4350
|
const suggestions = [];
|
|
@@ -4399,20 +4363,36 @@ function parseBatchSuggestionResponse(response) {
|
|
|
4399
4363
|
}
|
|
4400
4364
|
return suggestions;
|
|
4401
4365
|
}
|
|
4402
|
-
|
|
4403
|
-
|
|
4404
|
-
|
|
4405
|
-
}
|
|
4406
|
-
const uncachedPostings = [];
|
|
4407
|
-
const cachedResults = new Map;
|
|
4366
|
+
function partitionByCacheStatus(postings) {
|
|
4367
|
+
const uncached = [];
|
|
4368
|
+
const cached2 = new Map;
|
|
4408
4369
|
postings.forEach((posting, index) => {
|
|
4409
4370
|
const hash2 = hashTransaction(posting);
|
|
4410
4371
|
if (suggestionCache[hash2]) {
|
|
4411
|
-
|
|
4372
|
+
cached2.set(index, suggestionCache[hash2]);
|
|
4412
4373
|
} else {
|
|
4413
|
-
|
|
4374
|
+
uncached.push(posting);
|
|
4414
4375
|
}
|
|
4415
4376
|
});
|
|
4377
|
+
return { uncached, cached: cached2 };
|
|
4378
|
+
}
|
|
4379
|
+
function mergeSuggestions(postings, cachedResults, newSuggestions) {
|
|
4380
|
+
let uncachedIndex = 0;
|
|
4381
|
+
return postings.map((posting, index) => {
|
|
4382
|
+
const suggestion = cachedResults.get(index) || newSuggestions[uncachedIndex++];
|
|
4383
|
+
return {
|
|
4384
|
+
...posting,
|
|
4385
|
+
suggestedAccount: suggestion?.account,
|
|
4386
|
+
suggestionConfidence: suggestion?.confidence,
|
|
4387
|
+
suggestionReasoning: suggestion?.reasoning
|
|
4388
|
+
};
|
|
4389
|
+
});
|
|
4390
|
+
}
|
|
4391
|
+
async function suggestAccountsForPostingsBatch(postings, context) {
|
|
4392
|
+
if (postings.length === 0) {
|
|
4393
|
+
return [];
|
|
4394
|
+
}
|
|
4395
|
+
const { uncached: uncachedPostings, cached: cachedResults } = partitionByCacheStatus(postings);
|
|
4416
4396
|
context.logger?.info(`Account suggestions: ${cachedResults.size} cached, ${uncachedPostings.length} to generate`);
|
|
4417
4397
|
let newSuggestions = [];
|
|
4418
4398
|
if (uncachedPostings.length > 0) {
|
|
@@ -4445,19 +4425,7 @@ ${userPrompt}`;
|
|
|
4445
4425
|
return postings;
|
|
4446
4426
|
}
|
|
4447
4427
|
}
|
|
4448
|
-
|
|
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;
|
|
4428
|
+
return mergeSuggestions(postings, cachedResults, newSuggestions);
|
|
4461
4429
|
}
|
|
4462
4430
|
function generateMockSuggestions(postings) {
|
|
4463
4431
|
let response = "";
|
|
@@ -4492,10 +4460,34 @@ function generateMockSuggestions(postings) {
|
|
|
4492
4460
|
});
|
|
4493
4461
|
return response;
|
|
4494
4462
|
}
|
|
4495
|
-
var EXAMPLE_PATTERN_SAMPLE_SIZE = 10, suggestionCache;
|
|
4463
|
+
var EXAMPLE_PATTERN_SAMPLE_SIZE = 10, suggestionCache, RESPONSE_FORMAT_SECTION;
|
|
4496
4464
|
var init_accountSuggester = __esm(() => {
|
|
4497
4465
|
init_agentLoader();
|
|
4498
4466
|
suggestionCache = {};
|
|
4467
|
+
RESPONSE_FORMAT_SECTION = [
|
|
4468
|
+
`## Task
|
|
4469
|
+
`,
|
|
4470
|
+
"For EACH transaction, suggest the most appropriate account. You may:",
|
|
4471
|
+
"1. Suggest an existing account from the hierarchy above",
|
|
4472
|
+
`2. Propose a NEW account following the existing naming patterns
|
|
4473
|
+
`,
|
|
4474
|
+
`## Response Format
|
|
4475
|
+
`,
|
|
4476
|
+
`Respond with suggestions for ALL transactions in this exact format:
|
|
4477
|
+
`,
|
|
4478
|
+
"TRANSACTION 1:",
|
|
4479
|
+
"ACCOUNT: {account_name}",
|
|
4480
|
+
"CONFIDENCE: {high|medium|low}",
|
|
4481
|
+
`REASONING: {brief one-sentence explanation}
|
|
4482
|
+
`,
|
|
4483
|
+
"TRANSACTION 2:",
|
|
4484
|
+
"ACCOUNT: {account_name}",
|
|
4485
|
+
"CONFIDENCE: {high|medium|low}",
|
|
4486
|
+
`REASONING: {brief one-sentence explanation}
|
|
4487
|
+
`,
|
|
4488
|
+
"... (continue for all transactions)"
|
|
4489
|
+
].join(`
|
|
4490
|
+
`);
|
|
4499
4491
|
});
|
|
4500
4492
|
|
|
4501
4493
|
// src/index.ts
|
|
@@ -16920,13 +16912,7 @@ function findCsvFiles(baseDir, options = {}) {
|
|
|
16920
16912
|
if (!fs3.existsSync(baseDir)) {
|
|
16921
16913
|
return [];
|
|
16922
16914
|
}
|
|
16923
|
-
|
|
16924
|
-
if (options.subdir) {
|
|
16925
|
-
searchDir = path2.join(searchDir, options.subdir);
|
|
16926
|
-
if (options.subsubdir) {
|
|
16927
|
-
searchDir = path2.join(searchDir, options.subsubdir);
|
|
16928
|
-
}
|
|
16929
|
-
}
|
|
16915
|
+
const searchDir = path2.join(...[baseDir, options.subdir, options.subsubdir].filter((p) => !!p));
|
|
16930
16916
|
if (!fs3.existsSync(searchDir)) {
|
|
16931
16917
|
return [];
|
|
16932
16918
|
}
|
|
@@ -17436,17 +17422,7 @@ function createContext(directory, params) {
|
|
|
17436
17422
|
id: randomUUID(),
|
|
17437
17423
|
createdAt: now,
|
|
17438
17424
|
updatedAt: now,
|
|
17439
|
-
|
|
17440
|
-
filePath: params.filePath,
|
|
17441
|
-
provider: params.provider,
|
|
17442
|
-
currency: params.currency,
|
|
17443
|
-
accountNumber: params.accountNumber,
|
|
17444
|
-
originalFilename: params.originalFilename,
|
|
17445
|
-
fromDate: params.fromDate,
|
|
17446
|
-
untilDate: params.untilDate,
|
|
17447
|
-
openingBalance: params.openingBalance,
|
|
17448
|
-
closingBalance: params.closingBalance,
|
|
17449
|
-
account: params.account
|
|
17425
|
+
...params
|
|
17450
17426
|
};
|
|
17451
17427
|
ensureDirectory(path5.join(directory, ".memory"));
|
|
17452
17428
|
const contextPath = getContextPath(directory, context.id);
|
|
@@ -23013,6 +22989,7 @@ function findRulesForCsv(csvPath, mapping) {
|
|
|
23013
22989
|
// src/utils/hledgerExecutor.ts
|
|
23014
22990
|
var {$: $2 } = globalThis.Bun;
|
|
23015
22991
|
var STDERR_TRUNCATE_LENGTH = 500;
|
|
22992
|
+
var TX_HEADER_PATTERN = /^(\d{4})-(\d{2}-\d{2})\s+(.+)$/;
|
|
23016
22993
|
async function defaultHledgerExecutor(cmdArgs) {
|
|
23017
22994
|
try {
|
|
23018
22995
|
const result = await $2`hledger ${cmdArgs}`.quiet().nothrow();
|
|
@@ -23047,10 +23024,10 @@ function parseUnknownPostings(hledgerOutput) {
|
|
|
23047
23024
|
let currentDate = "";
|
|
23048
23025
|
let currentDescription = "";
|
|
23049
23026
|
for (const line of lines) {
|
|
23050
|
-
const headerMatch = line.match(
|
|
23027
|
+
const headerMatch = line.match(TX_HEADER_PATTERN);
|
|
23051
23028
|
if (headerMatch) {
|
|
23052
|
-
currentDate = headerMatch[1]
|
|
23053
|
-
currentDescription = headerMatch[
|
|
23029
|
+
currentDate = `${headerMatch[1]}-${headerMatch[2]}`;
|
|
23030
|
+
currentDescription = headerMatch[3].trim();
|
|
23054
23031
|
continue;
|
|
23055
23032
|
}
|
|
23056
23033
|
const postingMatch = line.match(/^\s+(income:unknown|expenses:unknown)\s+([^\s]+(?:\s+[^\s=]+)?)\s*(?:=\s*(.+))?$/);
|
|
@@ -23071,7 +23048,7 @@ function countTransactions(hledgerOutput) {
|
|
|
23071
23048
|
`);
|
|
23072
23049
|
let count = 0;
|
|
23073
23050
|
for (const line of lines) {
|
|
23074
|
-
if (
|
|
23051
|
+
if (TX_HEADER_PATTERN.test(line)) {
|
|
23075
23052
|
count++;
|
|
23076
23053
|
}
|
|
23077
23054
|
}
|
|
@@ -23082,7 +23059,7 @@ function extractTransactionYears(hledgerOutput) {
|
|
|
23082
23059
|
const lines = hledgerOutput.split(`
|
|
23083
23060
|
`);
|
|
23084
23061
|
for (const line of lines) {
|
|
23085
|
-
const match2 = line.match(
|
|
23062
|
+
const match2 = line.match(TX_HEADER_PATTERN);
|
|
23086
23063
|
if (match2) {
|
|
23087
23064
|
years.add(parseInt(match2[1], 10));
|
|
23088
23065
|
}
|
|
@@ -23142,6 +23119,13 @@ async function getAccountBalance(mainJournalPath, account, asOfDate, executor =
|
|
|
23142
23119
|
|
|
23143
23120
|
// src/utils/rulesParser.ts
|
|
23144
23121
|
import * as fs8 from "fs";
|
|
23122
|
+
function resolveFieldRef(fieldRef, fieldNames) {
|
|
23123
|
+
if (/^\d+$/.test(fieldRef)) {
|
|
23124
|
+
const index = parseInt(fieldRef, 10) - 1;
|
|
23125
|
+
return fieldNames[index] || fieldRef;
|
|
23126
|
+
}
|
|
23127
|
+
return fieldRef;
|
|
23128
|
+
}
|
|
23145
23129
|
function parseSkipRows(rulesContent) {
|
|
23146
23130
|
const match2 = rulesContent.match(/^skip\s+(\d+)/m);
|
|
23147
23131
|
return match2 ? parseInt(match2[1], 10) : 0;
|
|
@@ -23166,24 +23150,13 @@ function parseDateField(rulesContent, fieldNames) {
|
|
|
23166
23150
|
if (!match2) {
|
|
23167
23151
|
return fieldNames[0] || "date";
|
|
23168
23152
|
}
|
|
23169
|
-
|
|
23170
|
-
if (/^\d+$/.test(value)) {
|
|
23171
|
-
const index = parseInt(value, 10) - 1;
|
|
23172
|
-
return fieldNames[index] || value;
|
|
23173
|
-
}
|
|
23174
|
-
return value;
|
|
23153
|
+
return resolveFieldRef(match2[1], fieldNames);
|
|
23175
23154
|
}
|
|
23176
23155
|
function parseAmountFields(rulesContent, fieldNames) {
|
|
23177
23156
|
const result = {};
|
|
23178
23157
|
const simpleMatch = rulesContent.match(/^amount\s+(-?)%(\w+|\d+)/m);
|
|
23179
23158
|
if (simpleMatch) {
|
|
23180
|
-
|
|
23181
|
-
if (/^\d+$/.test(fieldRef)) {
|
|
23182
|
-
const index = parseInt(fieldRef, 10) - 1;
|
|
23183
|
-
result.single = fieldNames[index] || fieldRef;
|
|
23184
|
-
} else {
|
|
23185
|
-
result.single = fieldRef;
|
|
23186
|
-
}
|
|
23159
|
+
result.single = resolveFieldRef(simpleMatch[2], fieldNames);
|
|
23187
23160
|
}
|
|
23188
23161
|
const debitMatch = rulesContent.match(/if\s+%(\w+)\s+\.\s*\n\s*amount\s+-?%\1/m);
|
|
23189
23162
|
if (debitMatch) {
|
|
@@ -23248,15 +23221,18 @@ function parseBalance(balance) {
|
|
|
23248
23221
|
const amount = parseFloat(amountStr.replace(/,/g, ""));
|
|
23249
23222
|
return { currency, amount };
|
|
23250
23223
|
}
|
|
23224
|
+
function validateCurrencies(a, b) {
|
|
23225
|
+
if (a.currency && b.currency && a.currency !== b.currency) {
|
|
23226
|
+
throw new Error(`Currency mismatch: ${a.currency} vs ${b.currency}`);
|
|
23227
|
+
}
|
|
23228
|
+
}
|
|
23251
23229
|
function calculateDifference(expected, actual) {
|
|
23252
23230
|
const expectedParsed = parseBalance(expected);
|
|
23253
23231
|
const actualParsed = parseBalance(actual);
|
|
23254
23232
|
if (!expectedParsed || !actualParsed) {
|
|
23255
23233
|
throw new Error(`Cannot parse balances: expected="${expected}", actual="${actual}"`);
|
|
23256
23234
|
}
|
|
23257
|
-
|
|
23258
|
-
throw new Error(`Currency mismatch: expected ${expectedParsed.currency}, got ${actualParsed.currency}`);
|
|
23259
|
-
}
|
|
23235
|
+
validateCurrencies(expectedParsed, actualParsed);
|
|
23260
23236
|
const diff = actualParsed.amount - expectedParsed.amount;
|
|
23261
23237
|
const sign = diff >= 0 ? "+" : "";
|
|
23262
23238
|
const currency = expectedParsed.currency || actualParsed.currency;
|
|
@@ -23268,9 +23244,7 @@ function balancesMatch(balance1, balance2) {
|
|
|
23268
23244
|
if (!parsed1 || !parsed2) {
|
|
23269
23245
|
return false;
|
|
23270
23246
|
}
|
|
23271
|
-
|
|
23272
|
-
throw new Error(`Currency mismatch: ${parsed1.currency} vs ${parsed2.currency}`);
|
|
23273
|
-
}
|
|
23247
|
+
validateCurrencies(parsed1, parsed2);
|
|
23274
23248
|
return parsed1.amount === parsed2.amount;
|
|
23275
23249
|
}
|
|
23276
23250
|
|
|
@@ -23363,8 +23337,7 @@ function looksLikeTransactionId(fieldName, value) {
|
|
|
23363
23337
|
if (!nameMatches)
|
|
23364
23338
|
return false;
|
|
23365
23339
|
const trimmedValue = value.trim();
|
|
23366
|
-
|
|
23367
|
-
return looksLikeId;
|
|
23340
|
+
return /^[A-Za-z0-9_-]+$/.test(trimmedValue) && trimmedValue.length >= 3;
|
|
23368
23341
|
}
|
|
23369
23342
|
function findTransactionId(row) {
|
|
23370
23343
|
for (const [field, value] of Object.entries(row)) {
|
|
@@ -23381,9 +23354,7 @@ function findMatchingCsvRow(posting, csvRows, config2) {
|
|
|
23381
23354
|
const rowAmount = getRowAmount(row, config2.amountFields);
|
|
23382
23355
|
if (rowDate !== posting.date)
|
|
23383
23356
|
return false;
|
|
23384
|
-
|
|
23385
|
-
return false;
|
|
23386
|
-
return true;
|
|
23357
|
+
return Math.abs(rowAmount - postingAmount) <= AMOUNT_MATCH_TOLERANCE;
|
|
23387
23358
|
});
|
|
23388
23359
|
if (candidates.length === 1) {
|
|
23389
23360
|
return candidates[0];
|
|
@@ -23404,13 +23375,7 @@ function findMatchingCsvRow(posting, csvRows, config2) {
|
|
|
23404
23375
|
const descMatches = candidates.filter((row) => {
|
|
23405
23376
|
return Object.values(row).some((value) => value && value.toLowerCase().includes(descriptionLower));
|
|
23406
23377
|
});
|
|
23407
|
-
|
|
23408
|
-
return descMatches[0];
|
|
23409
|
-
}
|
|
23410
|
-
if (descMatches.length > 1) {
|
|
23411
|
-
return descMatches[0];
|
|
23412
|
-
}
|
|
23413
|
-
return candidates[0];
|
|
23378
|
+
return descMatches[0] || candidates[0];
|
|
23414
23379
|
}
|
|
23415
23380
|
|
|
23416
23381
|
// src/tools/import-statements.ts
|
|
@@ -24118,7 +24083,6 @@ function extractAccountsFromRulesFile(rulesPath) {
|
|
|
24118
24083
|
const account2Match = trimmed.match(/account2\s+(.+?)(?:\s+|$)/);
|
|
24119
24084
|
if (account2Match) {
|
|
24120
24085
|
accounts.add(account2Match[1].trim());
|
|
24121
|
-
continue;
|
|
24122
24086
|
}
|
|
24123
24087
|
}
|
|
24124
24088
|
return accounts;
|
|
@@ -24136,52 +24100,46 @@ function getAllAccountsFromRules(rulesPaths) {
|
|
|
24136
24100
|
function sortAccountDeclarations(accounts) {
|
|
24137
24101
|
return Array.from(accounts).sort((a, b) => a.localeCompare(b));
|
|
24138
24102
|
}
|
|
24139
|
-
function
|
|
24140
|
-
if (!fs12.existsSync(yearJournalPath)) {
|
|
24141
|
-
throw new Error(`Year journal not found: ${yearJournalPath}`);
|
|
24142
|
-
}
|
|
24143
|
-
const content = fs12.readFileSync(yearJournalPath, "utf-8");
|
|
24103
|
+
function parseJournalSections(content) {
|
|
24144
24104
|
const lines = content.split(`
|
|
24145
24105
|
`);
|
|
24146
24106
|
const existingAccounts = new Set;
|
|
24147
24107
|
const commentLines = [];
|
|
24148
|
-
const accountLines = [];
|
|
24149
24108
|
const otherLines = [];
|
|
24150
24109
|
let inAccountSection = false;
|
|
24151
24110
|
let accountSectionEnded = false;
|
|
24152
24111
|
for (const line of lines) {
|
|
24153
24112
|
const trimmed = line.trim();
|
|
24154
24113
|
if (trimmed.startsWith(";") || trimmed.startsWith("#")) {
|
|
24155
|
-
|
|
24156
|
-
commentLines.push(line);
|
|
24157
|
-
} else {
|
|
24158
|
-
otherLines.push(line);
|
|
24159
|
-
}
|
|
24114
|
+
(accountSectionEnded ? otherLines : commentLines).push(line);
|
|
24160
24115
|
continue;
|
|
24161
24116
|
}
|
|
24162
24117
|
if (trimmed.startsWith("account ")) {
|
|
24163
24118
|
inAccountSection = true;
|
|
24164
24119
|
const accountMatch = trimmed.match(/^account\s+(.+?)(?:\s+|$)/);
|
|
24165
24120
|
if (accountMatch) {
|
|
24166
|
-
|
|
24167
|
-
existingAccounts.add(accountName);
|
|
24168
|
-
accountLines.push(line);
|
|
24121
|
+
existingAccounts.add(accountMatch[1].trim());
|
|
24169
24122
|
}
|
|
24170
24123
|
continue;
|
|
24171
24124
|
}
|
|
24172
24125
|
if (trimmed === "") {
|
|
24173
|
-
if (inAccountSection && !accountSectionEnded)
|
|
24174
|
-
|
|
24175
|
-
|
|
24176
|
-
otherLines.push(line);
|
|
24177
|
-
}
|
|
24126
|
+
if (inAccountSection && !accountSectionEnded)
|
|
24127
|
+
continue;
|
|
24128
|
+
otherLines.push(line);
|
|
24178
24129
|
continue;
|
|
24179
24130
|
}
|
|
24180
|
-
if (inAccountSection)
|
|
24131
|
+
if (inAccountSection)
|
|
24181
24132
|
accountSectionEnded = true;
|
|
24182
|
-
}
|
|
24183
24133
|
otherLines.push(line);
|
|
24184
24134
|
}
|
|
24135
|
+
return { existingAccounts, commentLines, otherLines };
|
|
24136
|
+
}
|
|
24137
|
+
function ensureAccountDeclarations(yearJournalPath, accounts) {
|
|
24138
|
+
if (!fs12.existsSync(yearJournalPath)) {
|
|
24139
|
+
throw new Error(`Year journal not found: ${yearJournalPath}`);
|
|
24140
|
+
}
|
|
24141
|
+
const content = fs12.readFileSync(yearJournalPath, "utf-8");
|
|
24142
|
+
const { existingAccounts, commentLines, otherLines } = parseJournalSections(content);
|
|
24185
24143
|
const missingAccounts = new Set;
|
|
24186
24144
|
for (const account of accounts) {
|
|
24187
24145
|
if (!existingAccounts.has(account)) {
|
|
@@ -24247,14 +24205,10 @@ class MarkdownLogger {
|
|
|
24247
24205
|
}
|
|
24248
24206
|
}
|
|
24249
24207
|
info(message) {
|
|
24250
|
-
this.
|
|
24251
|
-
if (this.autoFlush)
|
|
24252
|
-
this.flushAsync();
|
|
24208
|
+
this.log(message);
|
|
24253
24209
|
}
|
|
24254
24210
|
warn(message) {
|
|
24255
|
-
this.
|
|
24256
|
-
if (this.autoFlush)
|
|
24257
|
-
this.flushAsync();
|
|
24211
|
+
this.log(`\u26A0\uFE0F **WARNING**: ${message}`);
|
|
24258
24212
|
}
|
|
24259
24213
|
error(message, error45) {
|
|
24260
24214
|
this.buffer.push(`\u274C **ERROR**: ${message}`);
|
|
@@ -24270,13 +24224,10 @@ class MarkdownLogger {
|
|
|
24270
24224
|
this.buffer.push("```");
|
|
24271
24225
|
this.buffer.push("");
|
|
24272
24226
|
}
|
|
24273
|
-
|
|
24274
|
-
this.flushAsync();
|
|
24227
|
+
this.autoFlushIfEnabled();
|
|
24275
24228
|
}
|
|
24276
24229
|
debug(message) {
|
|
24277
|
-
this.
|
|
24278
|
-
if (this.autoFlush)
|
|
24279
|
-
this.flushAsync();
|
|
24230
|
+
this.log(`\uD83D\uDD0D ${message}`);
|
|
24280
24231
|
}
|
|
24281
24232
|
logStep(stepName, status, details) {
|
|
24282
24233
|
const icon = status === "success" ? "\u2705" : status === "error" ? "\u274C" : "\u25B6\uFE0F";
|
|
@@ -24286,8 +24237,7 @@ class MarkdownLogger {
|
|
|
24286
24237
|
this.buffer.push(` ${details}`);
|
|
24287
24238
|
}
|
|
24288
24239
|
this.buffer.push("");
|
|
24289
|
-
|
|
24290
|
-
this.flushAsync();
|
|
24240
|
+
this.autoFlushIfEnabled();
|
|
24291
24241
|
}
|
|
24292
24242
|
logCommand(command, output) {
|
|
24293
24243
|
this.buffer.push("```bash");
|
|
@@ -24305,16 +24255,14 @@ class MarkdownLogger {
|
|
|
24305
24255
|
}
|
|
24306
24256
|
this.buffer.push("```");
|
|
24307
24257
|
this.buffer.push("");
|
|
24308
|
-
|
|
24309
|
-
this.flushAsync();
|
|
24258
|
+
this.autoFlushIfEnabled();
|
|
24310
24259
|
}
|
|
24311
24260
|
logResult(data) {
|
|
24312
24261
|
this.buffer.push("```json");
|
|
24313
24262
|
this.buffer.push(JSON.stringify(data, null, 2));
|
|
24314
24263
|
this.buffer.push("```");
|
|
24315
24264
|
this.buffer.push("");
|
|
24316
|
-
|
|
24317
|
-
this.flushAsync();
|
|
24265
|
+
this.autoFlushIfEnabled();
|
|
24318
24266
|
}
|
|
24319
24267
|
setContext(key, value) {
|
|
24320
24268
|
this.context[key] = value;
|
|
@@ -24334,6 +24282,15 @@ class MarkdownLogger {
|
|
|
24334
24282
|
getLogPath() {
|
|
24335
24283
|
return this.logPath;
|
|
24336
24284
|
}
|
|
24285
|
+
log(message) {
|
|
24286
|
+
this.buffer.push(message);
|
|
24287
|
+
this.autoFlushIfEnabled();
|
|
24288
|
+
}
|
|
24289
|
+
autoFlushIfEnabled() {
|
|
24290
|
+
if (!this.autoFlush)
|
|
24291
|
+
return;
|
|
24292
|
+
this.flushAsync();
|
|
24293
|
+
}
|
|
24337
24294
|
flushAsync() {
|
|
24338
24295
|
this.pendingFlush = this.flush().catch(() => {});
|
|
24339
24296
|
}
|