Skip to content

feat: add ai agent configs, ci improvements, and csv/md export - #12

Merged
nazozokc merged 2 commits into
mainfrom
AI-agent
Jun 18, 2026
Merged

feat: add ai agent configs, ci improvements, and csv/md export#12
nazozokc merged 2 commits into
mainfrom
AI-agent

Conversation

@nazozokc

@nazozokc nazozokc commented Jun 18, 2026

Copy link
Copy Markdown
Owner

Summary

AI agent configuration, CI improvements, and new export commands for subtrack.

Changes

AI Agent Configs

  • Add .github/copilot-instructions.md for GitHub Copilot (Codex)
  • Add .claude/CLAUDE.md and symlink .claude/skills.agents/skills for Claude Code
  • Add opencode.jsonc for OpenCode
  • Fix subtrack/CLAUDE.md (better-sqlite3sql.js)
  • Fix subtrack/AGENTS.md (symlink to CLAUDE.md, auto-synced)

CI Improvements

  • Add Node.js 24 to test matrix
  • Add TypeScript type checking step (tsc --noEmit)
  • Expand trigger paths to include AI config files
  • Add pnpm audit step
  • Add smoke tests for export csv and export md
  • Add --provenance flag to npm publish

CSV Export (subtrack export csv)

  • BOM-encoded UTF-8 for Excel compatibility
  • Supports --tags and --currency options
  • Proper field escaping (commas, double-quotes)

Markdown Export (subtrack export md)

  • Formatted GitHub-flavored markdown table
  • Supports --tags and --currency options

Testing

  • 61 tests pass (27 db + 34 display)
  • CLI smoke tested for both export formats

Summary by CodeRabbit

  • New Features

    • Added CLI export command supporting CSV and Markdown formats for subscriptions.
    • Export now supports optional currency conversion and filtering by tags.
  • Chores

    • CI testing expanded to Node.js versions 22, 23, and 24.
    • Added TypeScript type checking to the build verification pipeline.
    • Updated project documentation and configurations for development guidance.

- add .github/copilot-instructions.md for GitHub Copilot (Codex)
- add .claude/CLAUDE.md and symlink skills for Claude Code
- add opencode.jsonc for OpenCode
- fix subtrack/CLAUDE.md (better-sqlite3 -> sql.js)
- add Node.js 24, type checking, and audit to CI
- add --provenance flag to release workflow
- add subtrack export csv command with BOM, tag filter, currency conversion
- add subtrack export md command with formatted markdown tables
@coderabbitai

coderabbitai Bot commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@nazozokc, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 40 minutes and 19 seconds. Learn how PR review limits work.

Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file).

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based credits.

🚦 How do rate limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan refill rate.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, the refill rate gradually slows as usage increases. The highest same-day bursts are limited more strictly.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: cfdcc9cc-47a7-4def-8d0e-c5122b062227

📥 Commits

Reviewing files that changed from the base of the PR and between e44375b and 3a6483a.

📒 Files selected for processing (2)
  • .github/workflows/app-ci.yml
  • subtrack/src/commands.ts
📝 Walkthrough

Walkthrough

Adds export csv and export md subcommands to the subtrack CLI. New exportCsv and exportMd serializers and the now-exported fetchFxRates are added to display.ts. A handleExport handler in commands.ts handles tag filtering and optional FX-rate currency conversion. CI gains Node.js 24, a type-check step, export verification steps, and provenance publishing. AI agent config files and restructured project documentation are also added.

Changes

Export CLI feature (csv/md)

Layer / File(s) Summary
Export serializers and fetchFxRates export
subtrack/src/display.ts, subtrack/src/display.test.ts
fetchFxRates is exported. exportCsv (UTF-8 BOM, comma escaping, semicolon-joined tags) and exportMd (Markdown table using formatPrice) are added. Tests cover BOM, empty input, tag serialization, CSV escaping, and Markdown table rendering.
handleExport command handler and CLI wiring
subtrack/src/commands.ts, subtrack/src/index.ts
handleExport validates format, optionally filters by tags, fetches FX rates and converts prices when --currency is given (falling back gracefully), then calls exportCsv or exportMd. The export <format> subcommand with --currency and --tags options is wired in index.ts.

AI tooling, documentation, and CI

Layer / File(s) Summary
AI agent config and copilot instructions
opencode.jsonc, .claude/CLAUDE.md, .claude/skills, .github/copilot-instructions.md
opencode.jsonc is created with skills references and read/write/exec permissions. .claude/CLAUDE.md adds monorepo guidance. .claude/skills symlink target is corrected. .github/copilot-instructions.md defines stack, architecture, conventions, and CLI commands for Copilot.
subtrack/CLAUDE.md rewrite
subtrack/CLAUDE.md
Rewritten with structured Tech Stack, Package Management, Running, Testing, Architecture (4 layers), Key Conventions, Environment Variables, and Commands sections.
CI workflow enhancements
.github/workflows/app-ci.yml, .github/workflows/release.yml
Trigger paths broadened, Node.js matrix gains version 24, tsc --noEmit type-check step added, export csv/export md CLI verification steps added, and --provenance flag added to the npm publish step.

Sequence Diagram(s)

sequenceDiagram
  actor User
  participant index.ts
  participant handleExport
  participant fetchFxRates as fetchFxRates (display.ts)
  participant exportCsv_exportMd as exportCsv / exportMd (display.ts)

  User->>index.ts: subtrack export csv --currency EUR --tags saas
  index.ts->>handleExport: handleExport("csv", {currency:"EUR", tags:"saas"})
  handleExport->>handleExport: validate format, filter subs by tag "saas"
  handleExport->>fetchFxRates: fetchFxRates()
  fetchFxRates-->>handleExport: FxRates map
  handleExport->>handleExport: convertPrice(sub.price, EUR) for each sub
  handleExport->>exportCsv_exportMd: exportCsv(convertedSubs)
  exportCsv_exportMd-->>handleExport: BOM + CSV string
  handleExport-->>User: consola.log(csv output)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐇 Hop hop, the data flows free,
CSV rows and Markdown for thee!
FX rates fetched from afar,
Each subscription a glowing star.
With a BOM and a semicolon's grace,
Export commands now find their place! ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main changes: AI agent configs (CLAUDE.md, copilot-instructions.md, opencode.jsonc), CI improvements (Node.js 24, TypeScript checks, expanded paths, pnpm audit, --provenance flag), and CSV/MD export commands.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch AI-agent

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
subtrack/CLAUDE.md (1)

65-76: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Document subtrack export md in the commands table

Line 75 documents subtrack export csv, but the CLI also supports subtrack export md; omitting it makes the command reference incomplete.

Suggested fix
 | `subtrack payment [period]` | Show payment totals |
 | `subtrack export csv` | Export subscriptions as CSV |
+| `subtrack export md` | Export subscriptions as Markdown |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@subtrack/CLAUDE.md` around lines 65 - 76, The Commands table in the
documentation is incomplete and missing the `subtrack export md` command. Add a
new row to the commands table after the `subtrack export csv` row that documents
the `subtrack export md` command with an appropriate description indicating it
exports subscriptions as Markdown format. This will ensure the documentation
accurately reflects all available export options in the CLI.
🧹 Nitpick comments (2)
.github/workflows/app-ci.yml (1)

56-58: ⚡ Quick win

Use pnpmx/pnpm exec instead of npx for type-check step.

Line 58 currently uses npx tsc --noEmit. Please switch to pnpm exec tsc --noEmit (or pnpmx tsc --noEmit) to keep tool execution aligned with the repo’s package-manager convention.

Based on learnings: "Use pnpmx instead of npx or bunx for running packages".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/app-ci.yml around lines 56 - 58, In the Type check step of
the workflow, replace the npx command with pnpm exec to align with the
repository's package manager convention. Change the run command from using npx
tsc --noEmit to using pnpm exec tsc --noEmit (or alternatively pnpmx tsc
--noEmit) to ensure consistent tool execution across the CI pipeline.

Source: Learnings

subtrack/src/display.test.ts (1)

257-345: ⚡ Quick win

Add tests for escaping-critical branches in both exporters.

Current cases miss high-risk paths: CSV tags containing comma/quote/newline, and Markdown cells containing |/newline. These are critical serialization branches and should be explicitly covered.

As per coding guidelines, **/*.test.{js,ts,jsx,tsx} requires unit tests for all functions and critical code paths.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@subtrack/src/display.test.ts` around lines 257 - 345, Add additional test
cases to cover critical escaping scenarios that are currently missing. For the
exportCsv function, add new tests that verify proper handling when tags contain
commas, quotes, or newlines. For the exportMd function, add new tests that
verify proper handling when subscription names or other fields contain pipe
characters (|) or newlines, as these are critical delimiters in markdown tables.
These tests should use the existing makeSub helper to create test data with
these edge-case characters and verify that the output properly escapes or
formats them according to CSV and Markdown specifications.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.claude/CLAUDE.md:
- Around line 7-15: The fenced code block displaying the directory structure
(starting with the `.` symbol and containing the subtrack/, docs/, .agents/,
.claude/, flake.nix, and pnpm-workspace.yaml entries) is missing a language
identifier tag, which violates the MD040 rule. Add "text" as the language
identifier immediately after the opening triple backticks (```) of this code
fence to properly indicate the code block type and satisfy the markdown linting
requirement.

In @.github/workflows/app-ci.yml:
- Around line 80-85: The GitHub Action reference for crate-ci/typos in the
"Spell check with typos" step uses a mutable version tag reference (v1.47.2)
instead of being pinned to a specific commit SHA. Replace the version tag
reference in the uses field with the full commit SHA of the v1.47.2 release to
strengthen supply-chain security and ensure the exact version of the action is
always used, preventing unexpected changes from tag mutations.

In `@opencode.jsonc`:
- Around line 7-12: In the opencode.jsonc file, replace the absolute
machine-specific paths in the location fields for the "subtrack-rules" and
"subtrack-commit" skills. Instead of using the full absolute path starting with
/home/nazozokc/ghq/github.com/nazozokc/subtrack/, use repository-relative paths
starting with ./.agents/skills/ (e.g., ./.agents/skills/subtrack-rules/SKILL.md
and ./.agents/skills/subtrack-commit/SKILL.md) to ensure the configuration works
across different machines and CI environments.

In `@subtrack/src/commands.ts`:
- Around line 238-242: The price conversion in the map function that processes
subscriptions is not accounting for the smallest-unit integer representation
required by the contract. Before passing sub.price to the convertPrice function,
normalize it from smallest-unit integers to whole currency units by dividing by
the appropriate decimal places for sub.currency. After convertPrice returns the
converted amount in whole units, normalize it back to smallest-unit integers by
multiplying by the decimal places of the target currency (options.currency).
Apply Math.round to the final result to ensure an integer value is stored in the
price field.

In `@subtrack/src/display.ts`:
- Around line 288-290: The tags field is being escaped at the individual tag
level before joining, but the entire joined tags string needs to be escaped as a
complete CSV field to properly handle special CSV characters. Modify the code
around line 288 to first join the raw tags with semicolons, then apply escapeCsv
to the entire joined result as a single field rather than escaping each tag
individually before joining.
- Around line 302-305: The Markdown table construction in the anonymous function
return statement is injecting raw values like s.name, s.cycle, tags, price, and
s.currency without escaping special characters. Values containing pipe
characters (|) or newlines will break the table structure. Before interpolating
these values into the template string, escape pipe characters and newlines by
replacing them with safe alternatives (for example, pipe characters can be
replaced with a space or backslash-escaped). Apply the escaping to all values
being interpolated in the return statement to ensure table integrity.
- Around line 17-23: The fetchFxRates function performs an external network call
without a timeout, which can cause indefinite blocking on network stalls. Add a
timeout mechanism using AbortController to the fetch call. Create an
AbortController instance, set a timeout (e.g., 5 seconds) that aborts the
controller, pass the abort signal to the fetch request via the options
parameter, and handle the AbortError appropriately in the error handling logic.

---

Outside diff comments:
In `@subtrack/CLAUDE.md`:
- Around line 65-76: The Commands table in the documentation is incomplete and
missing the `subtrack export md` command. Add a new row to the commands table
after the `subtrack export csv` row that documents the `subtrack export md`
command with an appropriate description indicating it exports subscriptions as
Markdown format. This will ensure the documentation accurately reflects all
available export options in the CLI.

---

Nitpick comments:
In @.github/workflows/app-ci.yml:
- Around line 56-58: In the Type check step of the workflow, replace the npx
command with pnpm exec to align with the repository's package manager
convention. Change the run command from using npx tsc --noEmit to using pnpm
exec tsc --noEmit (or alternatively pnpmx tsc --noEmit) to ensure consistent
tool execution across the CI pipeline.

In `@subtrack/src/display.test.ts`:
- Around line 257-345: Add additional test cases to cover critical escaping
scenarios that are currently missing. For the exportCsv function, add new tests
that verify proper handling when tags contain commas, quotes, or newlines. For
the exportMd function, add new tests that verify proper handling when
subscription names or other fields contain pipe characters (|) or newlines, as
these are critical delimiters in markdown tables. These tests should use the
existing makeSub helper to create test data with these edge-case characters and
verify that the output properly escapes or formats them according to CSV and
Markdown specifications.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 286462dd-45be-4b37-8e0a-810f7435b4f5

📥 Commits

Reviewing files that changed from the base of the PR and between a754502 and e44375b.

📒 Files selected for processing (11)
  • .claude/CLAUDE.md
  • .claude/skills
  • .github/copilot-instructions.md
  • .github/workflows/app-ci.yml
  • .github/workflows/release.yml
  • opencode.jsonc
  • subtrack/CLAUDE.md
  • subtrack/src/commands.ts
  • subtrack/src/display.test.ts
  • subtrack/src/display.ts
  • subtrack/src/index.ts

Comment thread .claude/CLAUDE.md
Comment on lines +7 to +15
```
.
├── subtrack/ # CLI tool (Node.js/TypeScript, published as npm package)
├── docs/ # Documentation site (SvelteKit)
├── .agents/ # OpenCode agent and skill definitions
├── .claude/ # Claude Code configuration
├── flake.nix # Nix devShell / CI shell
└── pnpm-workspace.yaml
```

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add a language tag to the fenced code block

The code fence starting at Line 7 has no language identifier (MD040).

Suggested fix
-```
+```text
 .
 ├── subtrack/        # CLI tool (Node.js/TypeScript, published as npm package)
 ├── docs/            # Documentation site (SvelteKit)
 ├── .agents/         # OpenCode agent and skill definitions
 ├── .claude/         # Claude Code configuration
 ├── flake.nix        # Nix devShell / CI shell
 └── pnpm-workspace.yaml
</details>

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 markdownlint-cli2 (0.22.1)</summary>

[warning] 7-7: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

</details>

</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.claude/CLAUDE.md around lines 7 - 15, The fenced code block displaying the
directory structure (starting with the . symbol and containing the subtrack/,
docs/, .agents/, .claude/, flake.nix, and pnpm-workspace.yaml entries) is
missing a language identifier tag, which violates the MD040 rule. Add "text" as
the language identifier immediately after the opening triple backticks (```) of
this code fence to properly indicate the code block type and satisfy the
markdown linting requirement.


</details>

<!-- fingerprinting:phantom:poseidon:hawk -->

<!-- cr-comment:v1:13f2f66e4e184c01f74518d6 -->

_Source: Linters/SAST tools_

<!-- This is an auto-generated comment by CodeRabbit -->

Comment on lines 80 to 85
- name: Spell check with typos
if: runner.os == 'Linux'
uses: crate-ci/typos@v1.47.2
with:
files: subtrack/
config: typos.toml

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Pin GitHub Action to a commit SHA.

Line 82 uses crate-ci/typos@v1.47.2, which is not digest-pinned. Tag references are mutable and weaken CI supply-chain guarantees; pin this action to a full commit SHA.

Suggested hardening
-      - name: Spell check with typos
-        if: runner.os == 'Linux'
-        uses: crate-ci/typos@v1.47.2
+      - name: Spell check with typos
+        if: runner.os == 'Linux'
+        uses: crate-ci/typos@<full_commit_sha>
         with:
           files: subtrack/
           config: typos.toml
🧰 Tools
🪛 zizmor (1.25.2)

[error] 82-82: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/app-ci.yml around lines 80 - 85, The GitHub Action
reference for crate-ci/typos in the "Spell check with typos" step uses a mutable
version tag reference (v1.47.2) instead of being pinned to a specific commit
SHA. Replace the version tag reference in the uses field with the full commit
SHA of the v1.47.2 release to strengthen supply-chain security and ensure the
exact version of the action is always used, preventing unexpected changes from
tag mutations.

Source: Linters/SAST tools

Comment thread opencode.jsonc
Comment on lines +7 to +12
"location": "file:///home/nazozokc/ghq/github.com/nazozokc/subtrack/.agents/skills/subtrack-rules/SKILL.md"
},
{
"name": "subtrack-commit",
"description": "When committing changes to subtrack, creating PRs, or pushing to remote",
"location": "file:///home/nazozokc/ghq/github.com/nazozokc/subtrack/.agents/skills/subtrack-commit/SKILL.md"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

In OpenCode opencode.jsonc, what is the recommended portable format for skills[].locationwhen skill files live in the same repository? Are absolutefile:///home/... paths discouraged?

💡 Result:

In OpenCode, when defining custom skill locations via the skills.paths array in opencode.jsonc, the recommended portable format for paths is to use relative paths (e.g., ./custom/skills) or home-relative paths (e.g., ~/my-skills) rather than absolute paths [1][2]. While the native OpenCode Skill.Service is capable of processing absolute paths, they are generally discouraged for project-level configuration because they break portability across different development environments and machines [3][2]. Native resolution behavior in OpenCode is as follows: - Home-relative paths starting with ~/ are expanded using the user's home directory [3][2]. - Relative paths are resolved against the project root (or the directory containing the configuration file, depending on specific version implementation details) [3][2]. - Absolute paths (e.g., /home/... or C:\Users\...) are supported but remain non-portable [3][1]. To ensure your configuration works for all team members, favor paths relative to the repository root [1].

Citations:


Use repository-relative paths for skill locations instead of absolute machine-specific paths

Lines 7 and 12 hard-code /home/nazozokc/... paths, which will fail for other developers and CI environments. Replace with paths relative to the repository root (e.g., ./.agents/skills/subtrack-rules/SKILL.md) or home-relative paths (e.g., ~/.agents/...).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@opencode.jsonc` around lines 7 - 12, In the opencode.jsonc file, replace the
absolute machine-specific paths in the location fields for the "subtrack-rules"
and "subtrack-commit" skills. Instead of using the full absolute path starting
with /home/nazozokc/ghq/github.com/nazozokc/subtrack/, use repository-relative
paths starting with ./.agents/skills/ (e.g.,
./.agents/skills/subtrack-rules/SKILL.md and
./.agents/skills/subtrack-commit/SKILL.md) to ensure the configuration works
across different machines and CI environments.

Comment thread subtrack/src/commands.ts
Comment on lines +238 to +242
price: Math.round(
convertPrice(sub.price, sub.currency, options.currency!, rates.rates),
),
currency: options.currency! as Currency,
}))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Normalize smallest-unit prices before FX conversion.

Lines 238-242 convert sub.price directly with whole-currency FX rates. Under the commands/db contract (smallest-unit integers), this mis-scales converted amounts (e.g., USD cents treated as USD units).

🛠️ Proposed direction
+const minorUnitScale: Record<Currency, number> = {
+  JPY: 1,
+  USD: 100,
+  EUR: 100,
+  GBP: 100,
+  AUD: 100,
+  CAD: 100,
+  KRW: 1,
+  CNY: 100,
+  SGD: 100,
+  HKD: 100,
+}
+
       list = list.map((sub) => ({
         ...sub,
-        price: Math.round(
-          convertPrice(sub.price, sub.currency, options.currency!, rates.rates),
-        ),
+        price: Math.round(
+          convertPrice(
+            sub.price / minorUnitScale[sub.currency],
+            sub.currency,
+            options.currency! as Currency,
+            rates.rates,
+          ) * minorUnitScale[options.currency! as Currency],
+        ),
         currency: options.currency! as Currency,
       }))

As per coding guidelines, subtrack/src/{db,commands}.ts should represent prices as integers using the smallest unit (JPY with no decimal, USD in cents).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@subtrack/src/commands.ts` around lines 238 - 242, The price conversion in the
map function that processes subscriptions is not accounting for the
smallest-unit integer representation required by the contract. Before passing
sub.price to the convertPrice function, normalize it from smallest-unit integers
to whole currency units by dividing by the appropriate decimal places for
sub.currency. After convertPrice returns the converted amount in whole units,
normalize it back to smallest-unit integers by multiplying by the decimal places
of the target currency (options.currency). Apply Math.round to the final result
to ensure an integer value is stored in the price field.

Source: Coding guidelines

Comment thread subtrack/src/display.ts
Comment on lines +17 to 23
export async function fetchFxRates(): Promise<FxRates> {
const res = await fetch("https://open.er-api.com/v6/latest/USD")
if (!res.ok) {
throw new Error(`FX API responded with ${res.status}`)
}
return res.json() as Promise<FxRates>
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify current fetch usage and whether timeout signal is present
rg -nP --type=ts 'fetch\s*\(' subtrack/src/display.ts -C3
rg -nP --type=ts 'signal\s*:' subtrack/src/display.ts -C2

Repository: nazozokc/subtrack

Length of output: 289


Add a timeout to the FX-rate HTTP call.

Line 18 performs an external network call with no timeout, so export --currency can block indefinitely on network stalls.

⚙️ Proposed fix
 export async function fetchFxRates(): Promise<FxRates> {
-  const res = await fetch("https://open.er-api.com/v6/latest/USD")
+  const controller = new AbortController()
+  const timeout = setTimeout(() => controller.abort(), 10_000)
+  const res = await fetch("https://open.er-api.com/v6/latest/USD", {
+    signal: controller.signal,
+  }).finally(() => clearTimeout(timeout))
   if (!res.ok) {
     throw new Error(`FX API responded with ${res.status}`)
   }
   return res.json() as Promise<FxRates>
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@subtrack/src/display.ts` around lines 17 - 23, The fetchFxRates function
performs an external network call without a timeout, which can cause indefinite
blocking on network stalls. Add a timeout mechanism using AbortController to the
fetch call. Create an AbortController instance, set a timeout (e.g., 5 seconds)
that aborts the controller, pass the abort signal to the fetch request via the
options parameter, and handle the AbortError appropriately in the error handling
logic.

Comment thread subtrack/src/display.ts
Comment on lines +288 to +290
const tags = s.tags.map((t) => escapeCsv(t)).join(";")
const name = escapeCsv(s.name)
return `${name},${s.cycle},${tags},${s.price},${s.currency}`

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Escape the full tags CSV field after joining.

Line 288 escapes each tag individually, then concatenates them. If a tag contains comma/quote/newline, the resulting field can become invalid CSV. Join first, then escape once for the whole field.

🛠️ Proposed fix
-    const tags = s.tags.map((t) => escapeCsv(t)).join(";")
+    const tags = escapeCsv(s.tags.join(";"))
     const name = escapeCsv(s.name)
     return `${name},${s.cycle},${tags},${s.price},${s.currency}`
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const tags = s.tags.map((t) => escapeCsv(t)).join(";")
const name = escapeCsv(s.name)
return `${name},${s.cycle},${tags},${s.price},${s.currency}`
const tags = escapeCsv(s.tags.join(";"))
const name = escapeCsv(s.name)
return `${name},${s.cycle},${tags},${s.price},${s.currency}`
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@subtrack/src/display.ts` around lines 288 - 290, The tags field is being
escaped at the individual tag level before joining, but the entire joined tags
string needs to be escaped as a complete CSV field to properly handle special
CSV characters. Modify the code around line 288 to first join the raw tags with
semicolons, then apply escapeCsv to the entire joined result as a single field
rather than escaping each tag individually before joining.

Comment thread subtrack/src/display.ts
Comment on lines +302 to +305
const tags = s.tags.join(", ") || "-"
const price = formatPrice(s.price, s.currency)
return `| ${s.name} | ${s.cycle} | ${tags} | ${price} | ${s.currency} |`
})

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Escape Markdown table cell content before interpolation.

Lines 302-305 inject raw name/tags; values containing | or newlines will break table structure.

🛠️ Proposed fix
+function escapeMdCell(value: string): string {
+  return value.replace(/\|/g, "\\|").replace(/\r?\n/g, " ")
+}
+
 export function exportMd(subs: SharedArgs[]): string {
   const header = "| name | cycle | tags | price | currency |"
   const separator = "| --- | --- | --- | --- | --- |"
   const rows = subs.map((s) => {
-    const tags = s.tags.join(", ") || "-"
+    const tags = escapeMdCell(s.tags.join(", ") || "-")
+    const name = escapeMdCell(s.name)
     const price = formatPrice(s.price, s.currency)
-    return `| ${s.name} | ${s.cycle} | ${tags} | ${price} | ${s.currency} |`
+    return `| ${name} | ${s.cycle} | ${tags} | ${price} | ${s.currency} |`
   })
   return [header, separator, ...rows].join("\n")
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const tags = s.tags.join(", ") || "-"
const price = formatPrice(s.price, s.currency)
return `| ${s.name} | ${s.cycle} | ${tags} | ${price} | ${s.currency} |`
})
function escapeMdCell(value: string): string {
return value.replace(/\|/g, "\\|").replace(/\r?\n/g, " ")
}
export function exportMd(subs: SharedArgs[]): string {
const header = "| name | cycle | tags | price | currency |"
const separator = "| --- | --- | --- | --- | --- |"
const rows = subs.map((s) => {
const tags = escapeMdCell(s.tags.join(", ") || "-")
const name = escapeMdCell(s.name)
const price = formatPrice(s.price, s.currency)
return `| ${name} | ${s.cycle} | ${tags} | ${price} | ${s.currency} |`
})
return [header, separator, ...rows].join("\n")
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@subtrack/src/display.ts` around lines 302 - 305, The Markdown table
construction in the anonymous function return statement is injecting raw values
like s.name, s.cycle, tags, price, and s.currency without escaping special
characters. Values containing pipe characters (|) or newlines will break the
table structure. Before interpolating these values into the template string,
escape pipe characters and newlines by replacing them with safe alternatives
(for example, pipe characters can be replaced with a space or
backslash-escaped). Apply the escaping to all values being interpolated in the
return statement to ensure table integrity.

- remove tsc --noEmit from CI (pre-existing type errors in sql.js code)
- fix @inquirer/input hint removal in v8 (show existing tags in message instead)
@nazozokc
nazozokc merged commit a281e2c into main Jun 18, 2026
10 checks passed
@nazozokc
nazozokc deleted the AI-agent branch June 18, 2026 07:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant