Skip to content

perf: reduce unnecessary allocations across engine and helpers#461

Merged
rhuanbarreto merged 4 commits into
mainfrom
perf/reduce-allocations
Jul 7, 2026
Merged

perf: reduce unnecessary allocations across engine and helpers#461
rhuanbarreto merged 4 commits into
mainfrom
perf/reduce-allocations

Conversation

@rhuanbarreto

Copy link
Copy Markdown
Contributor

Summary

  • Eliminate ~30 wasteful spread operators that create intermediate arrays/objects without benefit — replace with in-place mutation, .concat(), Array.from(), Object.assign(), and for-of loops
  • Cache compiled Bun.Glob instances in matchFilesToAdrs — avoids ~8000 re-constructions per check run with many ADRs and changed files
  • Hoist resolve(projectRoot) in createRuleContext — avoids ~200+ redundant path normalizations per check run
  • Share Bun.Transpiler and pre-transpiled JS between scanImportedRuleSourcescanRuleSource — avoids double transpilation for imported rule files
  • Pre-build newline offset index with O(log N) binary search in findCodeOccurrences — replaces O(N × fileSize) linear scan per match

Test plan

  • bun run validate passes (lint, typecheck, format:check, 1438 tests, 43/43 ADR rules, knip, build:check)
  • No behavioral changes — all optimizations are mechanical, preserving existing semantics
  • Verified spread removals only where ownership is clear (locally-created or freshly-parsed data not reused by callers)
  • Module-level constants and caller-owned parameters still copied defensively where needed

https://claude.ai/code/session_01P2kzr4bcBvg7AtaRhyHdMa

Eliminate ~30 wasteful spread operators and redundant computations that
create intermediate arrays/objects without benefit. Two categories of
optimization:

**Allocation reduction (19 files):**
- Build Sets incrementally instead of `[...new Set([...a, ...b])]`
- Replace `.push(...largeArray)` with for-of loops (stack-safe)
- Use `.concat()` and `Object.assign()` over spread-concat
- Use `Array.from(set, mapper)` over `[...set].map(fn)`
- Remove defensive clones in settings merge functions where callers
  pass freshly-parsed data they don't reuse
- Use `??=` over spread-override for single-field defaults

**Redundant computation (5 files):**
- Cache compiled `Bun.Glob` instances across ADRs and files in
  `matchFilesToAdrs` — avoids ~8000 re-constructions per check run
- Hoist `resolve(projectRoot)` in `createRuleContext` — avoids ~200+
  redundant path normalizations per check run
- Share `Bun.Transpiler` instance and pre-transpiled JS between
  `scanImportedRuleSource` and `scanRuleSource` — avoids double
  transpilation for imported rule files
- Pre-build newline offset index in `findCodeOccurrences` with O(log N)
  binary search — replaces O(N × fileSize) linear scan per match
- Hoist `new RegExp()` before loop in `getNextId`

Claude-Session: https://claude.ai/code/session_01P2kzr4bcBvg7AtaRhyHdMa
Signed-off-by: Rhuan Barreto <rhuan@barreto.work>
@cursor

cursor Bot commented Jul 7, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: serverGenReqId_8b50d110-debc-40df-a259-0ae6ebde4df0)

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jul 7, 2026

Copy link
Copy Markdown

Deploying archgate-cli with  Cloudflare Pages  Cloudflare Pages

Latest commit: 4bdd118
Status: ✅  Deploy successful!
Preview URL: https://636c2e49.archgate-cli.pages.dev
Branch Preview URL: https://perf-reduce-allocations.archgate-cli.pages.dev

View logs

@coderabbitai

coderabbitai Bot commented Jul 7, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@rhuanbarreto, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 44 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

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 reviews.

How do review limits work?

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

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, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 91cde3cf-01a5-4b5a-9d70-f91bc9793bcb

📥 Commits

Reviewing files that changed from the base of the PR and between 91f22f8 and 4bdd118.

📒 Files selected for processing (3)
  • src/commands/check.ts
  • src/engine/source-positions.ts
  • src/helpers/telemetry.ts
📝 Walkthrough

Walkthrough

Changes

This PR applies a broad set of internal refactors across the CLI, engine, and helpers layers without altering exported function signatures except for one added optional parameter. Key changes include: introducing a glob cache in context.ts, converting several array-flatten-and-dedupe patterns to incremental Set construction in git-files.ts, sharing a single Bun.Transpiler instance and adding a preTranspiled parameter to scanRuleSource, refactoring safePath to accept a pre-resolved root in runner.ts, adding a binary-search-based offsetToPos helper in source-positions.ts, and converting several settings-merge functions (Claude, Opencode, VSCode, Cursor hooks) from copy-and-return to in-place mutation. Numerous smaller changes replace spread syntax with Array.from, push, Object.assign, unshift, or template literals.

Sequence Diagram(s)

Not applicable — the changes are internal refactors of data structure construction and mutation patterns rather than new interaction flows.

Compact metadata

  • Type: Internal refactor / performance optimization
  • Breaking changes: None (one function gains an optional parameter)
  • Exported API changes: scanRuleSource(source: string, preTranspiled?: string)

Related issues: None specified
Related PRs: None specified
Suggested labels: refactor, performance, internal
Suggested reviewers: Team members familiar with engine/runner.ts, rule-scanner.ts, and settings-merge helpers

Poem
A rabbit hopped through spreads and sets,
Turned copies into mutated bets,
Cached each glob, transpiled once more,
Bisected offsets to find the score,
Hop, hop — the code runs lean today.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main performance-focused allocation reductions across engine and helper code.
Description check ✅ Passed The description matches the changeset and covers the allocation reductions, caching, path hoisting, transpiler reuse, and newline indexing.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.

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.

@github-actions

github-actions Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Code Coverage

Metric Value
Lines 90.5% (7783 / 8596)
Threshold 90% minimum — met
Platforms Linux + Windows

Full HTML report available in workflow artifacts.

Per-directory breakdown
Directory Coverage Lines
src/commands/ 88.7% 2004 / 2259
src/engine/ 91.5% 1659 / 1814
src/formats/ 98.7% 148 / 150
src/helpers/ 90.8% 3972 / 4373

filterFiles.push(...piped) spreads into call arguments and risks stack
overflow on large stdin input. concat() returns a new array without
spreading into the call stack.

Claude-Session: https://claude.ai/code/session_01P2kzr4bcBvg7AtaRhyHdMa
Signed-off-by: Rhuan Barreto <rhuan@barreto.work>
@cursor

cursor Bot commented Jul 7, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: serverGenReqId_31fb87b4-2bc5-478f-80e7-8732496254eb)

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

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

⚠️ Outside diff range comments (1)
src/engine/source-positions.ts (1)

135-161: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Optional: hoist newlineOffsets into remapViolations to avoid rebuilding per unique searchText.

newlineOffsets depends only on source, but findCodeOccurrences rebuilds it on every invocation. Since remapViolations calls findCodeOccurrences once per distinct searchText (cache miss), the O(N) index is recomputed for each unique needle, partially undercutting the intended O(log N) win when a rules file yields several different violations. Building it once and passing it in keeps the optimization intact.

♻️ Proposed refactor
 function findCodeOccurrences(
   source: string,
   needle: string,
-  nonCodeRanges: Array<[number, number]>
+  nonCodeRanges: Array<[number, number]>,
+  newlineOffsets: number[]
 ): SourcePos[] {
-  // Pre-build newline offset index for O(log N) line/column lookup.
-  // Sentinel -1 at index 0 represents the virtual newline before line 1.
-  const newlineOffsets = [-1];
-  for (let i = 0; i < source.length; i++) {
-    if (source[i] === "\n") newlineOffsets.push(i);
-  }
-
   const results: SourcePos[] = [];
   const nonCodeRanges = buildNonCodeRanges(original);
+  // Pre-build newline offset index once; sentinel -1 = virtual newline before line 1.
+  const newlineOffsets = [-1];
+  for (let i = 0; i < original.length; i++) {
+    if (original[i] === "\n") newlineOffsets.push(i);
+  }
   const occurrenceCache = new Map<string, SourcePos[]>();

   return rawViolations.map((rv) => {
     let positions = occurrenceCache.get(rv.searchText);
     if (!positions) {
-      positions = findCodeOccurrences(original, rv.searchText, nonCodeRanges);
+      positions = findCodeOccurrences(original, rv.searchText, nonCodeRanges, newlineOffsets);
       occurrenceCache.set(rv.searchText, positions);
     }
🤖 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 `@src/engine/source-positions.ts` around lines 135 - 161, The newline offset
index is being rebuilt inside findCodeOccurrences for every distinct searchText,
even though it only depends on source. Hoist newlineOffsets into remapViolations
(or another shared caller) and pass it into findCodeOccurrences so the index is
computed once per source, while keeping offsetToPos and the occurrence search
logic unchanged.
🤖 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 `@src/helpers/telemetry.ts`:
- Line 275: The telemetry properties merge is mutating the object returned by
getCommonProperties(), which assumes that helper always returns a fresh object.
Update the properties composition in the telemetry helper to avoid mutating the
base object, and instead build a new merged object when combining
getCommonProperties() with properties. Use the getCommonProperties() call site
in src/helpers/telemetry.ts to locate the merge and keep the shared base data
immutable.

---

Outside diff comments:
In `@src/engine/source-positions.ts`:
- Around line 135-161: The newline offset index is being rebuilt inside
findCodeOccurrences for every distinct searchText, even though it only depends
on source. Hoist newlineOffsets into remapViolations (or another shared caller)
and pass it into findCodeOccurrences so the index is computed once per source,
while keeping offsetToPos and the occurrence search logic unchanged.
🪄 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: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: a6bb6db4-5171-4290-84b5-3aacf9dbf985

📥 Commits

Reviewing files that changed from the base of the PR and between c9c25f3 and 91f22f8.

📒 Files selected for processing (19)
  • src/cli.ts
  • src/commands/check.ts
  • src/engine/context.ts
  • src/engine/git-files.ts
  • src/engine/js-parser.ts
  • src/engine/rule-scanner.ts
  • src/engine/runner.ts
  • src/engine/source-positions.ts
  • src/engine/suppressions.ts
  • src/helpers/adr-writer.ts
  • src/helpers/claude-settings.ts
  • src/helpers/install-info.ts
  • src/helpers/opencode-settings.ts
  • src/helpers/plugin-install.ts
  • src/helpers/repo.ts
  • src/helpers/telemetry-config.ts
  • src/helpers/telemetry.ts
  • src/helpers/user-error.ts
  • src/helpers/vscode-settings.ts
📜 Review details
⚠️ CI failures not shown inline (4)

GitHub Actions: DCO / DCO Sign-off Check: perf: reduce unnecessary allocations across engine and helpers

Conclusion: failure

View job details

##[group]Run base="c9c25f3ae93236a7af9d15d19402f45db199b484"
 �[36;1mbase="c9c25f3ae93236a7af9d15d19402f45db199b484"�[0m
 �[36;1mhead="91f22f833df54d3c346e99aae3d3c58afc61d0e1"�[0m
 �[36;1mfailed=0�[0m
 �[36;1m�[0m
 �[36;1mfor sha in $(git rev-list --no-merges "$base".."$head"); do�[0m
 �[36;1m  if ! git log -1 --format='%B' "$sha" | grep -qiE '^Signed-off-by: .+ <.+>'; then�[0m
 �[36;1m    echo "::error::Commit $sha is missing a DCO Signed-off-by line."�[0m

GitHub Actions: DCO / 0_DCO Sign-off Check.txt: perf: reduce unnecessary allocations across engine and helpers

Conclusion: failure

View job details

##[group]Run base="c9c25f3ae93236a7af9d15d19402f45db199b484"
 �[36;1mbase="c9c25f3ae93236a7af9d15d19402f45db199b484"�[0m
 �[36;1mhead="91f22f833df54d3c346e99aae3d3c58afc61d0e1"�[0m
 �[36;1mfailed=0�[0m
 �[36;1m�[0m
 �[36;1mfor sha in $(git rev-list --no-merges "$base".."$head"); do�[0m
 �[36;1m  if ! git log -1 --format='%B' "$sha" | grep -qiE '^Signed-off-by: .+ <.+>'; then�[0m
 �[36;1m    echo "::error::Commit $sha is missing a DCO Signed-off-by line."�[0m

GitHub Actions: Code Quality: PR #461 / 4_Analyze (go).txt: Code Quality: PR #461

Conclusion: failure

View job details

tracting types for package crypto/aes.
 [] [build-stderr] 2026/07/07 10:29:14 Done extracting types for package crypto/aes.
 [] [build-stderr] 2026/07/07 10:29:14 Processing package crypto/des.
 [] [build-stderr] 2026/07/07 10:29:14 Extracting types for package crypto/des.
 [] [build-stderr] 2026/07/07 10:29:14 Done extracting types for package crypto/des.
 [] [build-stderr] 2026/07/07 10:29:14 Processing package crypto/internal/fips140/nistec/fiat.
 [] [build-stderr] 2026/07/07 10:29:14 Extracting types for package crypto/internal/fips140/nistec/fiat.
 [] [build-stderr] 2026/07/07 10:29:14 Done extracting types for package crypto/internal/fips140/nistec/fiat.
 [] [build-stderr] 2026/07/07 10:29:14 Processing package crypto/internal/fips140/nistec.
 [] [build-stderr] 2026/07/07 10:29:14 Extracting types for package crypto/internal/fips140/nistec.
 [] [build-stderr] 2026/07/07 10:29:14 Done extracting types for package crypto/internal/fips140/nistec.
 [] [build-stderr] 2026/07/07 10:29:14 Processing package crypto/internal/fips140/ecdh.
 [] [build-stderr] 2026/07/07 10:29:14 Extracting types for package crypto/internal/fips140/ecdh.
 [] [build-stderr] 2026/07/07 10:29:14 Done extracting types for package crypto/internal/fips140/ecdh.
 [] [build-stderr] 2026/07/07 10:29:14 Processing package crypto/internal/fips140/edwards25519/field.
 [] [build-stderr] 2026/07/07 10:29:14 Extracting types for package crypto/internal/fips140/edwards25519/field.
 [] [build-stderr] 2026/07/07 10:29:14 Done extracting types for package crypto/internal/fips140/edwards25519/field.
 [] [build-stderr] 2026/07/07 10:29:14 Processing package crypto/ecdh.
 [] [build-stderr] 2026/07/07 10:29:14 Extracting types for package crypto/ecdh.
 [] [build-stderr] 2026/07/07 10:29:14 Done extracting types for package crypto/ecdh.
 [] [build-stderr] 2026/07/07 10:29:14 Processing package crypto/elliptic.
 [] [build-stderr] 2026/07/07 10:29:14 Extracting types for package crypto/elliptic.
 [] [build-stde...

GitHub Actions: Code Quality: PR #461 / Analyze (go): Code Quality: PR #461

Conclusion: failure

View job details

"bundleSupportsIncludeLogs":true,"bundleSupportsOverlay":true,"databaseInterpretResultsSupportsSarifRunProperty":true,"featuresInVersionResult":true,"indirectTracingSupportsStaticBinaries":false,"informsAboutUnsupportedPathFilters":true,"supportsPython312":true,"mrvaPackCreate":true,"threatModelOption":true,"traceCommandUseBuildMode":true,"v2ramSizing":true,"mrvaPackCreateMultipleQueries":true,"setsCodeqlRunnerEnvVar":true,"sarifMergeRunsFromEqualCategory":true,"forceOverwrite":true,"generateSummarySymbolMap":true,"pythonDefaultIsToNotExtractStdlib":true,"queryServerRunQueries":true,"queryServerTrimCacheWithMode":true,"builtinExtractorsSpecifyDefaultQueries":true,"bqrsDiffResultSets":true,"bundleSupportsIncludeOption":true,"suppressesMissingFileBaselineWarning":true}}}
   CODEQL_ACTION_GO_BINARY: /home/runner/work/_temp/codeql-action-go-tracing/bin/go
   CODEQL_RAM: 14579
   CODEQL_THREADS: 4
   CODEQL_PROXY_HOST:
   CODEQL_PROXY_PORT:
   CODEQL_PROXY_CA_CERTIFICATE:
   CODEQL_PROXY_URLS:
 ##[endgroup]
 ##[group]Attempting to automatically build go code
 [command]/opt/hostedtoolcache/CodeQL/2.25.6/x64/codeql/codeql database trace-command --use-build-mode --working-dir /home/runner/work/cli/cli /home/runner/work/_temp/codeql_databases/go
 Picked up JAVA_TOOL_OPTIONS:  -Dhttp.keepAlive=false -Dmaven.wagon.http.pool=false
 Running command in /home/runner/work/cli/cli: [/opt/hostedtoolcache/CodeQL/2.25.6/x64/codeql/go/tools/autobuild.sh]
 [] [build-stderr] 2026/07/07 10:29:06 Autobuilder was built with go1.26.0, environment has go1.24.13
 [] [build-stderr] 2026/07/07 10:29:06 LGTM_SRC is /home/runner/work/cli/cli
 [] [build-stderr] 2026/07/07 10:29:06 Found no go.work files in the workspace; looking for go.mod files...
 [] [build-stderr] 2026/07/07 10:29:06 Found 1 go.mod files in: shims/go/go.mod.
 [] [build-stderr] 2026/07/07 10:29:06 Found 1 go.mod file(s).
 [] [build-stderr] 2026/07/07 10:29:06 Import path is 'github.com/archgate/cli'
 [] [build-stderr] 2026/07/0...
🧰 Additional context used
📓 Path-based instructions (13)
src/**/*.ts

📄 CodeRabbit inference engine (.archgate/adrs/ARCH-002-error-handling.md)

src/**/*.ts: Use logError() from src/helpers/log.ts for user-facing errors instead of printing errors directly.
Exit with code 1 for expected failures such as missing config, invalid input, or validation violations.
Let unexpected errors crash naturally so they are treated as internal errors with exit code 2.
Include actionable suggestions in user-facing error messages whenever possible.
Write user-facing errors to stderr via logError(); do not send them to stdout.
When findProjectRoot() returns null for commands that do not require .archgate/, fall back to process.cwd() as the path key or working directory.
Catch ExitPromptError from Inquirer in the top-level error boundary and treat it as user cancellation: exit with code 130 without logging an error or sending it to Sentry.
Do not use console.error() directly; use logError() for consistent formatting.
Do not exit with codes other than 0, 1, 2, or 130.
Do not send user-cancellation errors such as Inquirer ExitPromptError to Sentry; filter them in beforeSend.

src/**/*.ts: Use styleText(format, text) from node:util for all terminal colors and formatting in CLI source files; do not use raw ANSI escape codes or third-party color libraries.
Commands that produce structured results and support --json must emit machine-readable JSON to stdout with no colors or decorative formatting.
When a command supports --json, use formatJSON() from src/helpers/output.ts for JSON serialization, and pass forcePretty: true when the user explicitly provided --json.
Use isAgentContext() from src/helpers/output.ts to enable auto-JSON behavior for commands that support both human-readable and JSON output modes.
CLI output must not include emoji; use text symbols and colors instead.
Send normal command output to stdout with console.log(), and send errors, warnings, and debug messages to stderr via logError(), logWarn(), and logDebug().
Keep CLI output concise and scannabl...

Files:

  • src/helpers/user-error.ts
  • src/helpers/adr-writer.ts
  • src/helpers/repo.ts
  • src/helpers/install-info.ts
  • src/helpers/opencode-settings.ts
  • src/commands/check.ts
  • src/helpers/telemetry.ts
  • src/cli.ts
  • src/engine/suppressions.ts
  • src/engine/js-parser.ts
  • src/helpers/telemetry-config.ts
  • src/helpers/vscode-settings.ts
  • src/engine/rule-scanner.ts
  • src/helpers/plugin-install.ts
  • src/engine/context.ts
  • src/helpers/claude-settings.ts
  • src/engine/git-files.ts
  • src/engine/source-positions.ts
  • src/engine/runner.ts
src/{helpers,engine}/**/*.ts

📄 CodeRabbit inference engine (.archgate/adrs/ARCH-002-error-handling.md)

Do not use console.log(), console.warn(), or console.info() directly in helper or engine files; use logInfo() or logWarn() instead. Command files are exempt because they are the I/O layer.

Files:

  • src/helpers/user-error.ts
  • src/helpers/adr-writer.ts
  • src/helpers/repo.ts
  • src/helpers/install-info.ts
  • src/helpers/opencode-settings.ts
  • src/helpers/telemetry.ts
  • src/engine/suppressions.ts
  • src/engine/js-parser.ts
  • src/helpers/telemetry-config.ts
  • src/helpers/vscode-settings.ts
  • src/engine/rule-scanner.ts
  • src/helpers/plugin-install.ts
  • src/engine/context.ts
  • src/helpers/claude-settings.ts
  • src/engine/git-files.ts
  • src/engine/source-positions.ts
  • src/engine/runner.ts
**/*.{ts,tsx,js,jsx,mjs,cjs}

📄 CodeRabbit inference engine (.archgate/adrs/ARCH-006-dependency-policy.md)

**/*.{ts,tsx,js,jsx,mjs,cjs}: Use Bun built-ins for file I/O (Bun.file, Bun.write), HTTP, subprocess execution (Bun.spawn), globbing (Bun.Glob), and testing (bun:test).
Do not use Node.js-specific APIs when Bun alternatives exist; for example, use Bun.file() instead of fs.readFile() for simple reads.
Prefer node: built-in modules such as node:util, node:path, and node:fs over npm alternatives.
Do not use utility libraries for single functions, such as importing lodash for one helper like pick.
Do not use path aliases (tsconfig paths); use relative imports with Bun's native module resolution.
Do not install packages globally during development; use bunx for one-off tools.

Files:

  • src/helpers/user-error.ts
  • src/helpers/adr-writer.ts
  • src/helpers/repo.ts
  • src/helpers/install-info.ts
  • src/helpers/opencode-settings.ts
  • src/commands/check.ts
  • src/helpers/telemetry.ts
  • src/cli.ts
  • src/engine/suppressions.ts
  • src/engine/js-parser.ts
  • src/helpers/telemetry-config.ts
  • src/helpers/vscode-settings.ts
  • src/engine/rule-scanner.ts
  • src/helpers/plugin-install.ts
  • src/engine/context.ts
  • src/helpers/claude-settings.ts
  • src/engine/git-files.ts
  • src/engine/source-positions.ts
  • src/engine/runner.ts
src/**/!(*platform).ts

📄 CodeRabbit inference engine (.archgate/adrs/ARCH-009-platform-detection-helper.md)

src/**/!(*platform).ts: In src/ TypeScript source files, do not read process.platform directly; use src/helpers/platform.ts (isWindows(), isMacOS(), isLinux(), isWSL(), getPlatformInfo()) for all platform detection.
Use the centralized platform helper instead of duplicating OS/WSL detection logic inline anywhere in src/ TypeScript source.
When behavior differs between Linux and Windows, account for WSL by using isWSL() rather than assuming `

Files:

  • src/helpers/user-error.ts
  • src/helpers/adr-writer.ts
  • src/helpers/repo.ts
  • src/helpers/install-info.ts
  • src/helpers/opencode-settings.ts
  • src/commands/check.ts
  • src/helpers/telemetry.ts
  • src/cli.ts
  • src/engine/suppressions.ts
  • src/engine/js-parser.ts
  • src/helpers/telemetry-config.ts
  • src/helpers/vscode-settings.ts
  • src/engine/rule-scanner.ts
  • src/helpers/plugin-install.ts
  • src/engine/context.ts
  • src/helpers/claude-settings.ts
  • src/engine/git-files.ts
  • src/engine/source-positions.ts
  • src/engine/runner.ts
{src,tests}/**/*.ts

📄 CodeRabbit inference engine (.archgate/adrs/LEGAL-001-spdx-license-headers.md)

{src,tests}/**/*.ts: Every TypeScript source file in src/ and tests/ must begin with // SPDX-License-Identifier: Apache-2.0 followed by // Copyright 2026 Archgate.
If a TypeScript file has a shebang line (for example #!/usr/bin/env bun in src/cli.ts), the SPDX license header must appear immediately after the shebang.
Use single-line // comments for the SPDX header; do not use block comments (/* */) or alternate license identifiers.

Files:

  • src/helpers/user-error.ts
  • src/helpers/adr-writer.ts
  • src/helpers/repo.ts
  • src/helpers/install-info.ts
  • src/helpers/opencode-settings.ts
  • src/commands/check.ts
  • src/helpers/telemetry.ts
  • src/cli.ts
  • src/engine/suppressions.ts
  • src/engine/js-parser.ts
  • src/helpers/telemetry-config.ts
  • src/helpers/vscode-settings.ts
  • src/engine/rule-scanner.ts
  • src/helpers/plugin-install.ts
  • src/engine/context.ts
  • src/helpers/claude-settings.ts
  • src/engine/git-files.ts
  • src/engine/source-positions.ts
  • src/engine/runner.ts
**

⚙️ CodeRabbit configuration file

**: # CLAUDE.md

Archgate is a CLI tool for AI governance via Architecture Decision Records (ADRs) — combining human-readable docs with machine-checkable rules. The CLI dogfoods itself via ADRs in .archgate/adrs/. AI features are delivered as a Claude Code plugin (../plugins/claude-code), not via direct API calls.

Technology Stack

  • Runtime: Bun (>=1.2.21) — not Node.js compatible
  • Language: TypeScript (strict mode, ESNext, ES modules)
  • CLI framework: Commander.js (@commander-js/extra-typings)
  • Linter: Oxlint | Formatter: Oxfmt | Dead exports: Knip | Commits: Conventional Commits

Commands

bun run src/cli.ts <command>  # run CLI locally
bun run lint                  # oxlint
bun run typecheck             # tsc --build
bun run format                # oxfmt --write
bun run format:check          # oxfmt --check
bun run test                  # all tests (not bare `bun test` — picks up --timeout; see GEN-003)
bun run knip                  # dead export detection
bun run validate              # MANDATORY: lint + typecheck + format:check + test + ADR check + knip + build check
bun run build:check            # verify build compiles (CI builds binaries via release workflow)
bun run commit                # conventional commit wizard

Validation Gate

bun run validate must pass before any task is considered complete. Fail-fast pipeline: lint → typecheck → format:check → test → ADR check → knip → build check. Mirrors CI in .github/workflows/code-pull-request.yml.

Git Hooks (Git 2.54+)

Config-based hooks in .githooks run validation locally before commits and pushes:

  • pre-commit: lint + typecheck + format:check (~15s)
  • pre-push: full bun run validate (~60s, mirrors CI)

Activate once per clone:

git config --local include.path ../.githooks

Opt out of a specific hook: git config --local hook.<name>.enabled false. Skip all hooks for a single commit: git commit --no-verify.

#...

Files:

  • src/helpers/user-error.ts
  • src/helpers/adr-writer.ts
  • src/helpers/repo.ts
  • src/helpers/install-info.ts
  • src/helpers/opencode-settings.ts
  • src/commands/check.ts
  • src/helpers/telemetry.ts
  • src/cli.ts
  • src/engine/suppressions.ts
  • src/engine/js-parser.ts
  • src/helpers/telemetry-config.ts
  • src/helpers/vscode-settings.ts
  • src/engine/rule-scanner.ts
  • src/helpers/plugin-install.ts
  • src/engine/context.ts
  • src/helpers/claude-settings.ts
  • src/engine/git-files.ts
  • src/engine/source-positions.ts
  • src/engine/runner.ts

⚙️ CodeRabbit configuration file

**: This project uses Archgate — an AI governance framework based on
Architecture Decision Records (ADRs). The ADRs in .archgate/adrs/
are the authoritative rules for this codebase. Each ADR has a companion
.rules.ts file with automated checks that run via archgate check.

When reviewing, you must:

  1. Treat ADR violations as blocking issues, not suggestions.
  2. Cite the specific ADR ID when flagging a violation (e.g., "Violates ARCH-006").
  3. Focus on semantic and contextual violations that automated rules cannot catch —
    the .rules.ts files already cover syntactic/structural patterns.
  4. If you are unsure whether something violates an ADR, flag it as a question
    rather than approving it.

Files:

  • src/helpers/user-error.ts
  • src/helpers/adr-writer.ts
  • src/helpers/repo.ts
  • src/helpers/install-info.ts
  • src/helpers/opencode-settings.ts
  • src/commands/check.ts
  • src/helpers/telemetry.ts
  • src/cli.ts
  • src/engine/suppressions.ts
  • src/engine/js-parser.ts
  • src/helpers/telemetry-config.ts
  • src/helpers/vscode-settings.ts
  • src/engine/rule-scanner.ts
  • src/helpers/plugin-install.ts
  • src/engine/context.ts
  • src/helpers/claude-settings.ts
  • src/engine/git-files.ts
  • src/engine/source-positions.ts
  • src/engine/runner.ts
src/commands/**/*.ts

📄 CodeRabbit inference engine (.archgate/adrs/ARCH-001-command-structure.md)

src/commands/**/*.ts: Each command module under src/commands/ must export a register*Command(program) function, with one command per file (or one command group via index.ts).
Command files must be thin: they should only parse arguments, call engine/helpers, and format output; business logic must live in src/engine/, src/helpers/, or src/formats/.
Command modules must not use executableDir() for command discovery, must not call .parse() themselves, and must not spawn child processes for subcommand execution.
Subcommand groups should live in nested directories with an index.ts that composes child commands (for example, src/commands/adr/index.ts).

src/commands/**/*.ts: In command files under src/commands/**/*.ts, options that need type narrowing beyond plain strings MUST use new Option() with .addOption() instead of plain .option().
In src/commands/**/*.ts, import Option from @commander-js/extra-typings alongside Command so typed options get full type inference.
In src/commands/**/*.ts, enum-like options with a fixed set of allowed values must use .choices() on an Option to provide runtime validation and compile-time type narrowing.
In src/commands/**/*.ts, options that require type conversion must use .argParser() on an Option object, not a parser function passed as the third argument to .option().
In src/commands/**/*.ts, register typed options with .addOption() rather than .option().
In src/commands/**/*.ts, pass both the choices array and default values with as const when using .choices() and .default() to preserve literal types.
In src/commands/**/*.ts, do not add manual validation for choice options (for example if (!VALID.includes(val))), because Commander handles invalid-value rejection automatically.

src/commands/**/*.ts: Commands that operate on .archgate/ project resources must use findProjectRoot() from src/helpers/paths.ts to locate the project root; direct use of `process.cw...

Files:

  • src/commands/check.ts
src/commands/{*.ts,*/index.ts}

📄 CodeRabbit inference engine (.archgate/adrs/ARCH-015-cli-command-documentation-coverage.md)

Every top-level CLI command registered via register*Command(program) must live in src/commands/<name>.ts or src/commands/<name>/index.ts so it can be matched to a docs page.

Files:

  • src/commands/check.ts
{src/commands/{*.ts,*/index.ts},docs/src/content/docs/reference/cli/!(index).mdx}

📄 CodeRabbit inference engine (.archgate/adrs/ARCH-015-cli-command-documentation-coverage.md)

{src/commands/{*.ts,*/index.ts},docs/src/content/docs/reference/cli/!(index).mdx}: When a top-level command is added, its docs page must be added in the same change; when a top-level command is removed, its matching docs page must be deleted in the same change.
Keep command and documentation stems aligned when renaming a top-level command file; renaming src/commands/<name>.ts or src/commands/<name>/index.ts must be paired with renaming docs/src/content/docs/reference/cli/<name>.mdx.

Files:

  • src/commands/check.ts
src/cli.ts

📄 CodeRabbit inference engine (.archgate/adrs/ARCH-001-command-structure.md)

src/cli.ts: The main entry point must explicitly import and register every command; no auto-discovery is allowed.
All async bootstrap logic in src/cli.ts must be wrapped in an async main() function and invoked with main().catch((err) => { logError(String(err)); process.exit(2); }); top-level await is forbidden.
The CLI must run in-process in the same Bun process as the entry point; command execution should not rely on child processes.

The CLI entry point must be src/cli.ts and use the #!/usr/bin/env bun shebang.

Files:

  • src/cli.ts
src/engine/rule-scanner.ts

📄 CodeRabbit inference engine (.archgate/adrs/ARCH-022-ast-aware-rule-context.md)

src/engine/rule-scanner.ts: Keep rule-scanner.ts using the shared parseModule() helper instead of duplicating the parser call inline again.
rule-scanner.ts must continue blocking banned imports, dangerous Bun.* property access, eval/Function, non-literal dynamic import(), and globalThis/process.env mutation in .rules.ts source.
The sandbox must continue to block Bun.spawn and Bun.spawnSync from .rules.ts code.
The sandbox must continue to forbid .rules.ts code from reaching subprocess primitives directly, leaving ctx.ast() as the only sanctioned path.

Files:

  • src/engine/rule-scanner.ts
src/helpers/plugin-install.ts

📄 CodeRabbit inference engine (CLAUDE.md)

When adding a new editor target, implement is<Editor>CliAvailable() and any install/download helper; for tarball-based editors use installEditorPluginBundle(); for desktop apps without a CLI, add a broader is<Editor>Available() that also checks shared-state fallback paths.

Files:

  • src/helpers/plugin-install.ts
src/engine/runner.ts

📄 CodeRabbit inference engine (.archgate/adrs/ARCH-022-ast-aware-rule-context.md)

src/engine/runner.ts: Implement ctx.ast(path, language) inside createRuleContext() with all dispatch hidden from rule authors.
For language: "typescript" and "javascript", ctx.ast() must reuse the existing in-process meriyah parser; no subprocess may be spawned for these branches.
Factor the duplicated parseModule() logic into a shared helper that is used by both rule-scanner.ts and the ctx.ast() TypeScript/JavaScript branch.
For language: "python" and "ruby", ctx.ast() must use Bun.spawn with array-based arguments only, with no shell interpolation.
Before any Python/Ruby interpreter is invoked, ctx.ast() must run the guardrail sequence in order: path safety, language plausibility check, interpreter availability probe, then guarded invocation.
ctx.ast() must use the same safePath() sandboxing as readFile/glob before accessing a target file.
ctx.ast() must reject files whose extension and/or leading content do not plausibly match the requested language before running an interpreter.
ctx.ast() must probe interpreter availability once per check invocation, cache the result, and reuse the first working candidate executable name.
On Windows, the interpreter probe must consider platform-appropriate candidates in order (including py via isWindows()); on non-Windows, it must use the non-Windows candidate order.
The Python branch must run in isolated mode (python -I -c ...) to prevent the working directory from shadowing standard-library imports.
The Python and Ruby serializers must strip a leading UTF-8 BOM before parsing.
ctx.ast() must throw on missing interpreter or parse failure, and must not return null or any other sentinel value.
The thrown error messages from ctx.ast() must distinguish interpreter-unavailable failures from parse-failure errors.
ctx.ast() must not expose Bun.spawn, child_process, or any other raw subprocess primitive on RuleContext; it is the only sanctioned tooling entrypoint.
`ctx.ast(...

Files:

  • src/engine/runner.ts
🧠 Learnings (1)
📚 Learning: 2026-07-02T16:03:33.031Z
Learnt from: rhuanbarreto
Repo: archgate/cli PR: 446
File: src/helpers/session-context-opencode.ts:81-100
Timestamp: 2026-07-02T16:03:33.031Z
Learning: For synchronous helper functions that use Bun’s sqlite sync API (i.e., they must remain synchronous), it’s acceptable to use `existsSync` from `node:fs` to check whether the SQLite database file exists. Avoid using `Bun.file(path).exists()` for this purpose because it’s async and would force the helper to become async (no equivalent synchronous Bun alternative). If the DB file is missing, throw/return a clear, actionable "No database found" error (per ARCH-006) rather than letting the sqlite open fail with a generic DB-open error.

Applied to files:

  • src/helpers/user-error.ts
  • src/helpers/adr-writer.ts
  • src/helpers/repo.ts
  • src/helpers/install-info.ts
  • src/helpers/opencode-settings.ts
  • src/helpers/telemetry.ts
  • src/helpers/telemetry-config.ts
  • src/helpers/vscode-settings.ts
  • src/helpers/plugin-install.ts
  • src/helpers/claude-settings.ts
🔇 Additional comments (19)
src/engine/suppressions.ts (1)

155-155: LGTM!

src/commands/check.ts (1)

122-122: LGTM!

src/engine/context.ts (1)

117-131: LGTM!

Also applies to: 160-160

src/engine/git-files.ts (1)

102-118: LGTM!

Also applies to: 171-174, 290-293

src/engine/runner.ts (1)

43-52: LGTM!

Also applies to: 153-153, 180-180, 290-290, 330-330, 340-340, 366-379, 422-422, 530-530

src/cli.ts (1)

125-136: LGTM!

src/helpers/adr-writer.ts (1)

29-31: LGTM!

src/helpers/install-info.ts (1)

118-118: LGTM!

src/helpers/repo.ts (1)

234-241: LGTM!

src/helpers/telemetry-config.ts (1)

92-97: LGTM!

src/helpers/user-error.ts (1)

35-35: LGTM!

src/engine/source-positions.ts (1)

107-124: LGTM!

src/engine/js-parser.ts (1)

27-41: LGTM!

src/engine/rule-scanner.ts (1)

83-115: LGTM!

Also applies to: 284-286, 395-398

src/helpers/claude-settings.ts (2)

48-48: LGTM!


68-72: 🩺 Stability & Availability

No issue: permissions.allow is always an array here. ClaudePermissionsSchema defaults allow to [], and the empty-object fallback already covers the merge path.

			> Likely an incorrect or invalid review comment.
src/helpers/opencode-settings.ts (1)

43-45: 🎯 Functional Correctness | ⚡ Quick win

in check may skip the default when the key is present but undefined.

default_agent uses .optional().catch(undefined). If the on-disk config has an invalid default_agent, Zod can emit the key with value undefined; "default_agent" in existing is then true, so DEFAULT_AGENT is never applied and the written config drops the key entirely (JSON omits undefined). A truthy check (if (!existing.default_agent)) would restore the default in that case. Confirm whether the caught-undefined path can retain the key.

src/helpers/vscode-settings.ts (1)

41-41: LGTM!

Also applies to: 55-66

src/helpers/plugin-install.ts (1)

187-188: LGTM!

Also applies to: 269-273

Comment thread src/helpers/telemetry.ts Outdated
concat() allocates a new array — use the same for-of push pattern as
the rest of the PR to mutate in place without stack risk.

Claude-Session: https://claude.ai/code/session_01P2kzr4bcBvg7AtaRhyHdMa
Signed-off-by: Rhuan Barreto <rhuan@barreto.work>
@cursor

cursor Bot commented Jul 7, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: serverGenReqId_7f434dd3-8881-4602-9414-ba8ecc6930c0)

- Hoist newlineOffsets into remapViolations so the index is built once
  per source instead of once per distinct searchText
- Use Object.assign({}, ...) in trackEvent to avoid mutating the object
  returned by getCommonProperties(), which would break if it's ever
  cached

Claude-Session: https://claude.ai/code/session_01P2kzr4bcBvg7AtaRhyHdMa
Signed-off-by: Rhuan Barreto <rhuan@barreto.work>
@cursor

cursor Bot commented Jul 7, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: serverGenReqId_bd17146e-8dcd-437e-81f8-3e66108e7c32)

@rhuanbarreto rhuanbarreto merged commit 49983a6 into main Jul 7, 2026
23 checks passed
@rhuanbarreto rhuanbarreto deleted the perf/reduce-allocations branch July 7, 2026 10:52
@archgatebot archgatebot Bot mentioned this pull request Jul 7, 2026
rhuanbarreto pushed a commit that referenced this pull request Jul 7, 2026
# archgate

## [0.48.0](v0.47.0...v0.48.0)
(2026-07-07)

### Features

* **telemetry:** track project languages, runtimes, and frameworks on
check ([#458](#458))
([2d1c065](2d1c065))

### Performance Improvements

* reduce unnecessary allocations across engine and helpers
([#461](#461))
([49983a6](49983a6))

---
This PR was generated with
[simple-release](https://github.com/TrigenSoftware/simple-release).

<details>
<summary>📄 Cheatsheet</summary>
<br>



You can configure the bot's behavior through a pull request comment
using the `!simple-release/set-options` command.

### Command Format

````md
!simple-release/set-options

```json
{
  "bump": {},
  "publish": {}
}
```
````

### Useful Parameters

#### Bump

| Parameter | Type | Description |
|-----------|------|-------------|
| `version` | `string` | Force set specific version |
| `as` | `'major' \| 'minor' \| 'patch' \| 'prerelease'` | Release type
|
| `prerelease` | `string` | Pre-release identifier (e.g., "alpha",
"beta") |
| `firstRelease` | `boolean` | Whether this is the first release |
| `skip` | `boolean` | Skip version bump |
| `byProject` | `Record<string, object>` | Per-project bump options for
monorepos |

#### Publish

| Parameter | Type | Description |
|-----------|------|-------------|
| `skip` | `boolean` | Skip publishing |
| `access` | `'public' \| 'restricted'` | Package access level |
| `tag` | `string` | Tag for npm publication |

### Usage Examples

#### Force specific version

````md
!simple-release/set-options

```json
{
  "bump": {
    "version": "2.0.0"
  }
}
```
````

#### Force major bump

````md
!simple-release/set-options

```json
{
  "bump": {
    "as": "major"
  }
}
```
````

#### Create alpha pre-release

````md
!simple-release/set-options

```json
{
  "bump": {
    "prerelease": "alpha"
  }
}
```
````

#### Publish with specific access and tag

````md
!simple-release/set-options

```json
{
  "bump": {
    "prerelease": "beta"
  },
  "publish": {
    "access": "public",
    "tag": "beta"
  }
}
```
````

### Access Restrictions

The command can only be used by users with permissions:
- repository owner
- organization member
- collaborator

### Notes

- The last comment with `!simple-release/set-options` command takes
priority
- JSON must be valid, otherwise the command will be ignored
- Parameters apply only to the current release execution
- The command can be updated by editing the comment or adding a new one


</details>

<!--
  Please do not edit this comment.
  simple-release-pull-request: true
  simple-release-branch-from: release
  simple-release-branch-to: main
-->

Signed-off-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
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