You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
CSV export shows raw cents instead of a formatted amount #62
The amount column in the CSV export (and the CLI's export command,
which shares the same code) prints the raw integer cents, e.g. 1000,
instead of a formatted decimal amount like 10.00.
Expected
The amount column should read 10.00, matching the money formatting
convention used everywhere else (the web page, the balances output).
Look at the amount column: values like 4500, 1200, 3000 instead of 45.00, 12.00, 30.00.
Area
export
Severity
low
Notes for whoever picks this up
src/csv.ts's exportCsv builds the row with r.amountCents directly.
It should use formatCents from src/money/cents.ts (see the handling-money skill: "format only at the edge").
Goal: Make exportCsv write the amount column as a formatted decimal (formatCents, e.g. 10.00) instead of raw integer cents.
Acceptance criteria
AC-1: Every data row from exportCsv has an amount field equal to formatCents(amountCents) for that expense, e.g. a 4500-cent expense exports as 45.00, not 4500. Checked by bun test tests/csv.test.ts (new test exportCsv formats the amount column with formatCents).
AC-2: The CSV header (date,payer,description,amount), the column order, the row count and the escaping of payer/description stay the same, and the CLI export command uses the same output. Checked by bun test tests/csv.test.ts tests/routes.test.ts tests/cli.test.ts.
AC-3: The full gate passes: typecheck, tests and skills validation. Checked by make check.
Non-goals (binding — a diff that crosses one fails verify)
NG-1: No change to src/money/**. formatCents is reused exactly as it is.
NG-2: No change to the CSV header, column set or column order. No currency symbol or thousands separator is added.
NG-3: No change to src/routes/**, src/cli.ts, src/db.ts or any protected path (src/auth/**, .factory/**, .claude/**, .agents/**, .github/**).
NG-4: No change to csvEscape's behaviour. The separate gap where values containing \r are not quoted is left for another issue.
NG-5: No float arithmetic on money anywhere in the change.
Files to touch
src/csv.ts: import formatCents from ./money and use formatCents(r.amountCents) in the row template.
tests/csv.test.ts (new): use an in-memory Database(":memory:") with initSchema and seedDemoData.
Tests to write first
tests/csv.test.ts › exportCsv formats the amount column with formatCents: seeds the database, exports flat-4b, and asserts that every data row's last field matches /^-?\d+\.\d{2}$/ and equals formatCents of the stored cents, including a known value such as 45.00. It must fail on the current code, which writes 4500.
tests/csv.test.ts › exportCsv keeps the header and one row per expense: guards against regressions in the existing header and row count.
Repo skills to apply
handling-money: format to a decimal string only at the output boundary, and use integer cents everywhere else.
Risk: low
Charter low tier: "a change confined to a single non-protected module with no schema or API-shape change". Only src/csv.ts changes, plus a new test file. The CSV columns and the route stay the same; only the amount column's value representation changes. Nothing touches or comes near a protected path.
Gate level: full
Low risk: eligible for auto-approve if the toggle is on. Otherwise, reply /factory approve to proceed, or /factory revise <what to change>.
exportCsv now writes the amount column via formatCents (4500 → 45.00); the new tests/csv.test.ts failed first on 4500 and now passes. typecheck, test and skills gates pass. The advisory audit gate was skipped: it reports existing hono advisories, which this change doesn't touch.
AC-1: pass. bun test tests/csv.test.ts gives 2 pass, 0 fail. The test exportCsv formats the amount column with formatCents checks every flat-4b row against formatCents of the stored cents (45.00, 12.00, 30.00) and the pattern ^-?\d+\.\d{2}$.
AC-2: pass. bun test tests/csv.test.ts tests/routes.test.ts tests/cli.test.ts gives 16 pass, 0 fail across 3 files. The header test passes, and the CLI (src/cli.ts:53) and the route (src/routes/export.ts) both still call exportCsv unchanged.
AC-3: pass. The runner's gate after build reported FACTORY_GATES: status=GREEN passed=3 failed=0 skipped=1 failed_gates=audit. That result was measured on tree 1742ad30d7ff7bc8db023c18804dcf62d200fcb5, which is the current HEAD^{tree}, so the gate was not re-run.
The test that bites
exportCsv > exportCsv formats the amount column with formatCents fails on the base branch. With src/csv.ts restored from main, it expected ["45.00","12.00","30.00"] but received ["4500","1200","3000"] (1 pass, 1 fail). It passes here (bun test tests/csv.test.ts → 2 pass, 0 fail). The fix was then restored and git status was clean.
Reviewer findings
could, confidence 3, at tests/csv.test.ts:15-19. storedCents and fetchExpenseRows both order by created_at ASC with no tiebreaker. Seed timestamps are strictly increasing (src/seed.ts:58), so the two queries agree today, but rows with the same timestamp could make the test flaky. Fix: add , id as a secondary sort, or compare sorted arrays.
could, confidence 3, at tests/csv.test.ts. No test covers negative or sub-dollar amounts. formatCents already handles them correctly (-0.05, 0.05). Fix: add a test that inserts a 5-cent row and a negative row.
Neither finding blocks this verdict.
Non-goals respected
NG-1:src/money/** is untouched; formatCents is imported as it is.
NG-2: The header, column set and column order are unchanged. formatCents emits only [-]digits.dd, with no symbol or thousands separator.
NG-3: Only src/csv.ts and the new tests/csv.test.ts changed. No route, CLI, db or protected-path files were touched.
NG-4:csvEscape is unchanged.
NG-5: The change adds no arithmetic on money.
Note: the factory-verifier subagent was denied Bash in this session, so verify ran its revert check and AC commands directly. The outputs above come from those runs.
Activity
Param-Harrison commentedon Oct 8, 2026
Triage
Disposition: proceed
Type: bug
Risk: low
Confidence: 0.9
Done when:
exportCsvemits amounts formatted viaformatCents(e.g.10.00), proven by a new test, and.factory/gates.shpasses.Files expected:
src/csv.ts,tests/csv.test.ts(new)Gate level: full
Param-Harrison commentedon Oct 8, 2026
Plan (rev 1)
Goal: Make
exportCsvwrite theamountcolumn as a formatted decimal (formatCents, e.g.10.00) instead of raw integer cents.Acceptance criteria
exportCsvhas anamountfield equal toformatCents(amountCents)for that expense, e.g. a 4500-cent expense exports as45.00, not4500. Checked bybun test tests/csv.test.ts(new testexportCsv formats the amount column with formatCents).date,payer,description,amount), the column order, the row count and the escaping of payer/description stay the same, and the CLIexportcommand uses the same output. Checked bybun test tests/csv.test.ts tests/routes.test.ts tests/cli.test.ts.make check.Non-goals (binding — a diff that crosses one fails verify)
src/money/**.formatCentsis reused exactly as it is.src/routes/**,src/cli.ts,src/db.tsor any protected path (src/auth/**,.factory/**,.claude/**,.agents/**,.github/**).csvEscape's behaviour. The separate gap where values containing\rare not quoted is left for another issue.Files to touch
src/csv.ts: importformatCentsfrom./moneyand useformatCents(r.amountCents)in the row template.tests/csv.test.ts(new): use an in-memoryDatabase(":memory:")withinitSchemaandseedDemoData.Tests to write first
tests/csv.test.ts › exportCsv formats the amount column with formatCents: seeds the database, exportsflat-4b, and asserts that every data row's last field matches/^-?\d+\.\d{2}$/and equalsformatCentsof the stored cents, including a known value such as45.00. It must fail on the current code, which writes4500.tests/csv.test.ts › exportCsv keeps the header and one row per expense: guards against regressions in the existing header and row count.Repo skills to apply
handling-money: format to a decimal string only at the output boundary, and use integer cents everywhere else.
Risk: low
Charter low tier: "a change confined to a single non-protected module with no schema or API-shape change". Only
src/csv.tschanges, plus a new test file. The CSV columns and the route stay the same; only the amount column's value representation changes. Nothing touches or comes near a protected path.Gate level: full
Low risk: eligible for auto-approve if the toggle is on. Otherwise, reply
/factory approveto proceed, or/factory revise <what to change>.Param-Harrison commentedon Oct 8, 2026
Status
Stage: build — finished
Progress: 3/5
Elapsed: n/a
Gate line:
FACTORY_GATES: status=GREEN passed=3 failed=0 skipped=1 failed_gates=auditexportCsvnow writes the amount column viaformatCents(4500 →45.00); the newtests/csv.test.tsfailed first on4500and now passes. typecheck, test and skills gates pass. The advisoryauditgate was skipped: it reports existing hono advisories, which this change doesn't touch.factory: build #62
Param-Harrison commentedon Oct 8, 2026
Verdict: pass
Acceptance criteria evidence
bun test tests/csv.test.tsgives 2 pass, 0 fail. The testexportCsv formats the amount column with formatCentschecks everyflat-4brow againstformatCentsof the stored cents (45.00,12.00,30.00) and the pattern^-?\d+\.\d{2}$.bun test tests/csv.test.ts tests/routes.test.ts tests/cli.test.tsgives 16 pass, 0 fail across 3 files. The header test passes, and the CLI (src/cli.ts:53) and the route (src/routes/export.ts) both still callexportCsvunchanged.FACTORY_GATES: status=GREEN passed=3 failed=0 skipped=1 failed_gates=audit. That result was measured on tree1742ad30d7ff7bc8db023c18804dcf62d200fcb5, which is the currentHEAD^{tree}, so the gate was not re-run.The test that bites
exportCsv > exportCsv formats the amount column with formatCentsfails on the base branch. Withsrc/csv.tsrestored frommain, it expected["45.00","12.00","30.00"]but received["4500","1200","3000"](1 pass, 1 fail). It passes here (bun test tests/csv.test.ts→ 2 pass, 0 fail). The fix was then restored andgit statuswas clean.Reviewer findings
tests/csv.test.ts:15-19.storedCentsandfetchExpenseRowsboth order bycreated_at ASCwith no tiebreaker. Seed timestamps are strictly increasing (src/seed.ts:58), so the two queries agree today, but rows with the same timestamp could make the test flaky. Fix: add, idas a secondary sort, or compare sorted arrays.tests/csv.test.ts. No test covers negative or sub-dollar amounts.formatCentsalready handles them correctly (-0.05,0.05). Fix: add a test that inserts a 5-cent row and a negative row.Neither finding blocks this verdict.
Non-goals respected
src/money/**is untouched;formatCentsis imported as it is.formatCentsemits only[-]digits.dd, with no symbol or thousands separator.src/csv.tsand the newtests/csv.test.tschanged. No route, CLI, db or protected-path files were touched.csvEscapeis unchanged.Note: the
factory-verifiersubagent was denied Bash in this session, so verify ran its revert check and AC commands directly. The outputs above come from those runs.