Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 20 additions & 24 deletions src/agent/core-concepts.ts → docs/core-concepts.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
// AUTO-GENERATED FILE. DO NOT EDIT.
// Source: https://bkper.com/docs/core-concepts.md

export const CORE_CONCEPTS_MARKDOWN = `# Core Concepts
# Core Concepts

Bkper tracks resources — money, inventory, or anything countable — as movements between places. Every financial event is recorded as an amount moving **from** one Account **to** another. This from-to model replaces the traditional language of debits and credits with something intuitive: resources leave one place and arrive at another.

Expand Down Expand Up @@ -76,46 +73,46 @@ The sum of all credits and debits recorded in a Book always tallies to zero —

These examples show the same movement model in concrete situations. Some match the diagrams on this page. Others add common accrual flows that are easy to confuse.

These examples use Bkper's transaction shorthand \`From >> To\`, meaning the amount leaves the Account on the left and arrives at the Account on the right.
These examples use Bkper's transaction shorthand `From >> To`, meaning the amount leaves the Account on the left and arrives at the Account on the right.

| Situation | Transaction |
| --- | --- |
| Salary received | \`Salary >> Bank Account\` |
| Investment funded | \`Bank Account >> Investments\` |
| Dividends received | \`Dividends >> Bank Account\` |
| Loan received | \`Loan >> Bank Account\` |
| Rent paid | \`Bank Account >> Rent\` |
| Transportation bought on credit card | \`Credit Card >> Transportation\` |
| Salary received | `Salary >> Bank Account` |
| Investment funded | `Bank Account >> Investments` |
| Dividends received | `Dividends >> Bank Account` |
| Loan received | `Loan >> Bank Account` |
| Rent paid | `Bank Account >> Rent` |
| Transportation bought on credit card | `Credit Card >> Transportation` |

**Buy on a credit card now, pay it later**

| Step | Transaction |
| --- | --- |
| Purchase | \`Credit Card >> Outgoing\` |
| Payment | \`Bank Account >> Credit Card\` |
| Purchase | `Credit Card >> Outgoing` |
| Payment | `Bank Account >> Credit Card` |

**Sell now and receive cash later**

| Step | Transaction |
| --- | --- |
| Sale on credit | \`Incoming >> Accounts Receivable\` |
| Interest added while unpaid | \`Incoming >> Accounts Receivable\` |
| Collection | \`Accounts Receivable >> Bank Account\` |
| Sale on credit | `Incoming >> Accounts Receivable` |
| Interest added while unpaid | `Incoming >> Accounts Receivable` |
| Collection | `Accounts Receivable >> Bank Account` |

**Receive a supplier bill now and pay it later**

| Step | Transaction |
| --- | --- |
| Bill received | \`Accounts Payable >> Outgoing\` |
| Interest added while unpaid | \`Accounts Payable >> Outgoing\` |
| Payment | \`Bank Account >> Accounts Payable\` |
| Bill received | `Accounts Payable >> Outgoing` |
| Interest added while unpaid | `Accounts Payable >> Outgoing` |
| Payment | `Bank Account >> Accounts Payable` |

**Receive a loan now and repay principal later**

| Step | Transaction |
| --- | --- |
| Loan proceeds | \`Loan >> Bank Account\` |
| Principal repayment | \`Bank Account >> Loan\` |
| Loan proceeds | `Loan >> Bank Account` |
| Principal repayment | `Bank Account >> Loan` |

In each case, the first movement records the position that was created — a receivable or a liability. The later movement settles that position. This keeps Incoming and Outgoing focused on activity, while Asset and Liability Accounts hold positions until they are cleared.

Expand All @@ -134,11 +131,11 @@ Bkper maintains a continuous ledger with no concept of closing periods — the s

**Custom Properties** are key-value pairs attachable to any entity — Books, Accounts, Groups, Transactions, Collections, and Files. They add context, metadata, and meaning beyond core financial data.

By attaching properties like \`invoice: inv123456\` or \`exc_code: BRL\`, entities become rich with information that can drive automation and reporting — without changing the core model.
By attaching properties like `invoice: inv123456` or `exc_code: BRL`, entities become rich with information that can drive automation and reporting — without changing the core model.

## Hashtags

**Hashtags** are lightweight labels on Transactions that enable multi-dimensional tracking. They complement the Account structure by adding dynamic categorization — a single transaction might carry \`#team_marketing #project_alpha #q1_campaign\`, enabling filtering and analysis from any perspective.
**Hashtags** are lightweight labels on Transactions that enable multi-dimensional tracking. They complement the Account structure by adding dynamic categorization — a single transaction might carry `#team_marketing #project_alpha #q1_campaign`, enabling filtering and analysis from any perspective.

Unlike Account structures, Hashtags can be added or removed as needs evolve, making them ideal for cost allocation, project tracking, and ad-hoc analysis.

Expand All @@ -153,4 +150,3 @@ Collections can also serve as references for automations (Bots or Apps) that wor
Every action in a Book — posting a transaction, editing an account, adding a comment — generates an **Event**. Events record _who_ (a user) or _what_ (a bot, an automation) performed the action and _when_, forming a complete audit trail essential for collaboration and trust.

Events are also the foundation of Bkper's automation model. Bots and Agents listen for specific event types and react automatically — for example, calculating taxes when a transaction is posted or converting currencies when one is checked.
`;
25 changes: 6 additions & 19 deletions scripts/fetch-core-concepts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ const REQUIRED_CORE_CONCEPTS_HEADINGS = [

function resolveOutputPath(): string {
const scriptDir = path.dirname(fileURLToPath(import.meta.url));
return path.resolve(scriptDir, '..', 'src', 'agent', 'core-concepts.ts');
return path.resolve(scriptDir, '..', 'docs', 'core-concepts.md');
}

function validateCoreConceptsMarkdown(markdown: string): void {
Expand All @@ -27,25 +27,11 @@ function validateCoreConceptsMarkdown(markdown: string): void {
}
}

function escapeForTemplateLiteral(value: string): string {
return value
.replace(/\\/g, '\\\\')
.replace(/`/g, '\\`')
.replace(/\$\{/g, '\\${');
}

function renderCoreConceptsModule(markdown: string): string {
validateCoreConceptsMarkdown(markdown);

const escapedMarkdown = escapeForTemplateLiteral(markdown);
return `// AUTO-GENERATED FILE. DO NOT EDIT.\n// Source: ${CORE_CONCEPTS_CANONICAL_URL}\n\nexport const CORE_CONCEPTS_MARKDOWN = \`${escapedMarkdown}\`;\n`;
}

async function fetchCoreConceptsMarkdown(): Promise<string> {
const response = await fetch(CORE_CONCEPTS_CANONICAL_URL, {
headers: {
'Accept': 'text/markdown,text/plain,*/*',
'User-Agent': 'Mozilla/5.0 (compatible; bkper-cli build)',
'User-Agent': 'Mozilla/5.0 (compatible; bkper-cli sync)',
},
});

Expand All @@ -55,16 +41,17 @@ async function fetchCoreConceptsMarkdown(): Promise<string> {
);
}

return response.text();
const markdown = await response.text();
validateCoreConceptsMarkdown(markdown);
return markdown;
}

async function main(): Promise<void> {
const markdown = await fetchCoreConceptsMarkdown();
const outputPath = resolveOutputPath();
const moduleSource = renderCoreConceptsModule(markdown);

await mkdir(path.dirname(outputPath), { recursive: true });
await writeFile(outputPath, moduleSource, 'utf8');
await writeFile(outputPath, markdown, 'utf8');
}

void main().catch(error => {
Expand Down
23 changes: 18 additions & 5 deletions src/agent/system-prompt.ts
Original file line number Diff line number Diff line change
@@ -1,31 +1,44 @@
import {fileURLToPath} from 'node:url';
import path from 'node:path';
import {CORE_CONCEPTS_MARKDOWN} from './core-concepts.js';

function resolveCliReferencePath(): string {
const thisDir = path.dirname(fileURLToPath(import.meta.url));
return path.resolve(thisDir, '..', 'docs', 'cli-reference.md');
}

function resolveCoreConceptsPath(): string {
const thisDir = path.dirname(fileURLToPath(import.meta.url));
return path.resolve(thisDir, '..', 'docs', 'core-concepts.md');
}

export function getBkperAgentSystemPrompt(): string {
const cliRefPath = resolveCliReferencePath();
const coreConceptsPath = resolveCoreConceptsPath();
return `${BKPER_AGENT_SYSTEM_PROMPT}
## Bkper CLI Usage
## Reference Loading Rules

If the task touches Bkper accounting semantics or data modeling — such as Accounts, Transactions, balances, account types, groups, books, or mapping real-world flows into Bkper — read:

\`\`\`
${coreConceptsPath}
\`\`\`

Before executing \`bkper\` CLI commands, **read the full CLI reference** at:
If the task involves using or executing \`bkper\` CLI commands, read:

\`\`\`
${cliRefPath}
\`\`\`

For generic engineering work, you may proceed without loading either reference unless those semantics become relevant.

When in doubt, read first.
`;
}

export const BKPER_AGENT_SYSTEM_PROMPT = `# You are a Bkper team member

You think in resources, movements, and balances — not debits and credits. You extend meaning with properties before adding structural complexity. You protect the zero-sum invariant above all else.

${CORE_CONCEPTS_MARKDOWN}

## Operating Principles

- Preserve invariants and data integrity first, then user intent, then implementation convenience.
Expand Down
26 changes: 18 additions & 8 deletions test/unit/agent/core-concepts.test.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,29 @@
import { readFileSync } from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { expect } from '../helpers/test-setup.js';
import { CORE_CONCEPTS_MARKDOWN } from '../../../src/agent/core-concepts.js';

function resolveCoreConceptsPath(): string {
const thisDir = path.dirname(fileURLToPath(import.meta.url));
return path.resolve(thisDir, '..', '..', '..', 'docs', 'core-concepts.md');
}

describe('agent core concepts', function () {
it('should export non-empty markdown', function () {
expect(CORE_CONCEPTS_MARKDOWN.trim().length).to.be.greaterThan(0);
it('should provide a non-empty markdown snapshot', function () {
const markdown = readFileSync(resolveCoreConceptsPath(), 'utf8');
expect(markdown.trim().length).to.be.greaterThan(0);
});

it('should include the required core concepts headings', function () {
expect(CORE_CONCEPTS_MARKDOWN).to.include('# Core Concepts');
expect(CORE_CONCEPTS_MARKDOWN).to.include('## Accounts');
expect(CORE_CONCEPTS_MARKDOWN).to.include('## Transactions');
expect(CORE_CONCEPTS_MARKDOWN).to.include('## Books');
const markdown = readFileSync(resolveCoreConceptsPath(), 'utf8');
expect(markdown).to.include('# Core Concepts');
expect(markdown).to.include('## Accounts');
expect(markdown).to.include('## Transactions');
expect(markdown).to.include('## Books');
});

it('should preserve markdown examples with backticks', function () {
expect(CORE_CONCEPTS_MARKDOWN).to.include("These examples use Bkper's transaction shorthand `From >> To`");
const markdown = readFileSync(resolveCoreConceptsPath(), 'utf8');
expect(markdown).to.include("These examples use Bkper's transaction shorthand `From >> To`");
});
});
31 changes: 24 additions & 7 deletions test/unit/agent/system-prompt.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,36 @@ describe('agent system prompt', function () {
expect(BKPER_AGENT_SYSTEM_PROMPT).to.include('You are a Bkper team member');
});

it('should include the canonical core concepts content', function () {
expect(BKPER_AGENT_SYSTEM_PROMPT).to.include('# Core Concepts');
expect(BKPER_AGENT_SYSTEM_PROMPT).to.include('## Transactions');
expect(BKPER_AGENT_SYSTEM_PROMPT).to.include('## Books');
it('should not include a partial core concepts canon', function () {
expect(BKPER_AGENT_SYSTEM_PROMPT).to.not.include('## Core Concepts Canon');
expect(BKPER_AGENT_SYSTEM_PROMPT).to.not.include('## Critical Flow Reminders');
expect(BKPER_AGENT_SYSTEM_PROMPT).to.not.include('Credit card purchase: `Credit Card >> Outgoing`');
});

it('should not include the duplicated core concepts canon heading', function () {
expect(BKPER_AGENT_SYSTEM_PROMPT).to.not.include('## Core Concepts Canon');
it('should include concise loading rules for core concepts', function () {
const full = getBkperAgentSystemPrompt();
expect(full).to.include('If the task touches Bkper accounting semantics or data modeling');
expect(full).to.include('Accounts, Transactions, balances, account types, groups, books');
expect(full).to.include('mapping real-world flows into Bkper');
expect(full).to.include('When in doubt, read first.');
expect(full).to.include('core-concepts.md');
});

it('should not include remote documentation navigation or long enumerated loading rules', function () {
const full = getBkperAgentSystemPrompt();
expect(full).to.not.include('https://bkper.com/llms.txt');
expect(full).to.not.include('credit cards, receivables, payables, or loans');
expect(full).to.not.include('tax, inventory, portfolio, exchange, or subledger logic');
});

it('should not inline the full core concepts reference', function () {
expect(BKPER_AGENT_SYSTEM_PROMPT).to.not.include('## Example Flows');
expect(BKPER_AGENT_SYSTEM_PROMPT).to.not.include("These examples use Bkper's transaction shorthand `From >> To`");
});

it('should include CLI usage section with reference path', function () {
const full = getBkperAgentSystemPrompt();
expect(full).to.include('## Bkper CLI Usage');
expect(full).to.include('If the task involves using or executing `bkper` CLI commands');
expect(full).to.include('cli-reference.md');
});
});
Loading