Skip to content

Conversation

@flyingrobots
Copy link
Member

@flyingrobots flyingrobots commented Feb 12, 2026

Summary

  • Extract 9 render functions + emit() from bin/warp-graph.js into bin/presenters/ (json.js, text.js, index.js) — net −460 LOC
  • Add --ndjson flag for compact single-line JSON output (sorted keys, _-prefixed keys stripped)
  • Add NO_COLOR / FORCE_COLOR / CI / pipe detection for automatic ANSI stripping
  • Sanitize _renderedSvg / _renderedAscii from --json output (bugfix)

Test plan

  • 51 new unit tests across test/unit/presenters/ (json, text, present)
  • 6 BATS integration tests in test/bats/cli-ndjson.bats
  • All 3330 existing tests pass
  • Lint + typecheck clean
  • Docker matrix (npm run test:node22)

Summary by CodeRabbit

Release 10.8.0

  • New Features

    • Added --ndjson output (compact NDJSON) with mutual-exclusion against --json and --view
    • Unified output dispatcher supporting text, JSON, NDJSON, ASCII views and SVG/HTML exports
    • File export for SVG/HTML views
  • Bug Fixes

    • Sanitizes JSON/NDJSON to strip internal underscored keys
    • Fixed plain-text view fallbacks to avoid "undefined"
  • Documentation

    • Updated docs and ROADMAP to document NDJSON, color controls, and exclusivity
  • Tests

    • Added unit and CLI tests covering NDJSON, renderers, and color behavior

CI Bot added 2 commits February 11, 2026 16:14
…, v10.8.0)

Extract 9 render functions and emit() from bin/warp-graph.js into
bin/presenters/ (json.js, text.js, index.js). Adds --ndjson flag for
compact single-line JSON output, sanitizes _-prefixed internal keys
from JSON/NDJSON payloads, and respects NO_COLOR/FORCE_COLOR/CI env
vars for automatic ANSI stripping. Net -460 LOC in warp-graph.js.
@flyingrobots
Copy link
Member Author

@coderabbitai review please

@coderabbitai
Copy link

coderabbitai bot commented Feb 12, 2026

📝 Walkthrough

Walkthrough

Introduces a unified CLI presenter system (new bin/presenters/*) that centralizes output rendering, adds NDJSON (--ndjson) and color-control behavior, extracts text renderers from warp-graph.js, updates warp-graph.js to call present(), and adds tests and docs. Version bumped to 10.8.0.

Changes

Cohort / File(s) Summary
Version & Roadmap
jsr.json, package.json, CHANGELOG.md, ROADMAP.md
Bump to 10.8.0, mark M2.T2.PRESENTER DONE, include bin/presenters in package files.
Presenter Modules
bin/presenters/json.js, bin/presenters/text.js, bin/presenters/index.js
Add JSON utilities (stableStringify, compactStringify, sanitizePayload), extract nine text renderers, implement present() dispatcher, view/file export handling, and color-stripping decisions.
CLI Wiring
bin/warp-graph.js
Replace ad-hoc emit with present(); add --ndjson flag and mutual-exclusion checks; route errors and command outputs through presenters.
Documentation
docs/GUIDE.md
Document --ndjson, updated mutual-exclusion semantics (--json/--ndjson/--view), and color/T TY/ENV behavior.
Tests
test/bats/cli-ndjson.bats, test/unit/presenters/*.test.js
Add BATS tests for NDJSON and mutual-exclusion; add unit tests for JSON utilities, present() dispatch, shouldStripColor, and all text renderers.
Refactor / Removal
bin/warp-graph.js (large deletions)
Remove in-file renderers and stringify helpers, delegating output responsibilities to presenters.

Sequence Diagram

sequenceDiagram
    participant CLI as "CLI User"
    participant Warp as "bin/warp-graph.js"
    participant Presenter as "bin/presenters/index.js"
    participant Formatter as "json.js / text.js"
    participant Std as "stdout / stderr"

    CLI->>Warp: run command (args)
    Warp->>Warp: parse args, execute command
    Warp->>Presenter: present(payload, {format, command, view})

    alt payload.error
        Presenter->>Std: renderError(payload) -> stderr
    else format == "ndjson"
        Presenter->>Formatter: sanitizePayload → compactStringify
        Formatter->>Std: write single-line JSON
    else format == "json"
        Presenter->>Formatter: sanitizePayload → stableStringify
        Formatter->>Std: write pretty JSON
    else view present (svg/html)
        Presenter->>Formatter: writeHtmlExport / handleFileExport
        Formatter->>Std: write file path message
    else text
        Presenter->>Formatter: TEXT_RENDERERS[command](payload)
        Formatter->>Std: write human-readable text
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

Poem

🐇 A little rabbit sings:
Hop! Presenters gathered, neat and spry,
NDJSON hums a single-line tune,
Colors obey NO_COLOR or FORCE_COLOR's cry,
Output routed, tidy beneath the moon. 🎨✨

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 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: extracting CLI presenters, adding --ndjson flag, and adding color control support, while also noting the version bump. It is concise, specific, and directly reflects the primary purpose of the changeset.
Docstring Coverage ✅ Passed Docstring coverage is 90.48% which is sufficient. The required threshold is 80.00%.

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

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch presenter

No actionable comments were generated in the recent review. 🎉

🧹 Recent nitpick comments
test/unit/presenters/present.test.js (1)

132-142: isTTY cleanup is fragile — consider try/finally or a shared afterEach.

If the assertion on Line 135 throws, the cleanup on Line 136 (isTTY back to undefined) is skipped, potentially leaking state to later tests. While the subsequent test (Line 140) happens to set isTTY explicitly, this pattern is brittle if tests are reordered or new ones are added.

♻️ Safer cleanup with try/finally
   it('strips when CI is set (with TTY)', () => {
     Object.defineProperty(process.stdout, 'isTTY', { value: true, configurable: true });
     process.env.CI = 'true';
-    expect(shouldStripColor()).toBe(true);
-    Object.defineProperty(process.stdout, 'isTTY', { value: undefined, configurable: true });
+    try {
+      expect(shouldStripColor()).toBe(true);
+    } finally {
+      Object.defineProperty(process.stdout, 'isTTY', { value: undefined, configurable: true });
+    }
   });

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
Copy link

coderabbitai bot commented Feb 12, 2026

@flyingrobots I'll review the changes in this PR right away.

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@github-actions
Copy link

Release Preflight

  • package version: 10.8.0
  • prerelease: false
  • npm dist-tag on release: latest
  • npm pack dry-run: passed
  • jsr publish dry-run: passed

If you tag this commit as v10.8.0, release workflow will publish.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Caution

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

⚠️ Outside diff range comments (2)
package.json (1)

43-55: ⚠️ Potential issue | 🔴 Critical

bin/presenters/ is missing from the files array — published package will break at runtime.

bin/warp-graph.js imports from ./presenters/json.js and ./presenters/index.js, but bin/presenters/ is not listed in the files array. When published to npm, the presenters directory will be excluded from the tarball, causing a MODULE_NOT_FOUND crash on every CLI invocation.

🐛 Proposed fix
   "files": [
     "bin/warp-graph.js",
+    "bin/presenters",
     "bin/git-warp",
     "src",
bin/warp-graph.js (1)

91-98: ⚠️ Potential issue | 🟡 Minor

CliOptions typedef is missing the ndjson property.

The ndjson field is added to createDefaultOptions (line 237), parsed in consumeBaseArg (line 267), and read in main() (lines 2367, 2370, 2403), but it was not added to the CliOptions JSDoc typedef. This will silently pass if the typedef isn't enforced, but it creates a documentation gap and may cause issues if stricter type checking is applied later.

Proposed fix
 /**
  * `@typedef` {Object} CliOptions
  * `@property` {string} repo
  * `@property` {boolean} json
+ * `@property` {boolean} ndjson
  * `@property` {string|null} view
  * `@property` {string|null} graph
  * `@property` {string} writer
  * `@property` {boolean} help
  */
🧹 Nitpick comments (3)
bin/presenters/index.js (1)

187-208: Potential null access when command === 'query' and _renderedAscii is missing.

Line 197 accesses payload._renderedAscii without a guard. If a query command reaches view mode without pre-rendered ASCII (e.g., due to an upstream bug), this would write "undefined\n" to stdout. A defensive check would be consistent with the guard pattern used in handleFileExport (line 106).

♻️ Suggested defensive guard
   if (command === 'query') {
-    writeText(`${payload._renderedAscii}\n`, strip);
+    writeText(`${payload._renderedAscii ?? ''}\n`, strip);
     return;
   }
test/bats/cli-ndjson.bats (1)

57-68: Minor: error test uses a relaxed line-count assertion (-le 2).

Line 63 asserts line_count -le 2 rather than -eq 1. If NDJSON errors should produce exactly one JSON line, tightening this to -eq 1 would catch regressions where extra output leaks through. The current assertion would pass even if a warning line is also emitted.

test/unit/presenters/present.test.js (1)

117-120: CI env-var branch may not be reached in this test.

In typical test runners process.stdout.isTTY is undefined/falsy, so shouldStripColor() returns true from the !process.stdout.isTTY check before reaching the CI guard. The assertion passes coincidentally rather than exercising the intended branch. Consider stubbing isTTY so the CI-specific path is actually covered:

Proposed fix
   it('strips when CI is set', () => {
+    Object.defineProperty(process.stdout, 'isTTY', { value: true, configurable: true });
     process.env.CI = 'true';
     expect(shouldStripColor()).toBe(true);
+    Object.defineProperty(process.stdout, 'isTTY', { value: undefined, configurable: true });
   });

… test coverage

- Add bin/presenters to package.json files array (critical: npm publish fix)
- Add ndjson to CliOptions JSDoc typedef
- Add ?? '' null guard on _renderedAscii in view query path
- Tighten BATS ndjson error test to assert exactly 1 line
- Stub isTTY in CI env test so CI branch is actually exercised
- Add 3 regression tests (npm files, null guard, non-TTY strip)
@github-actions
Copy link

Release Preflight

  • package version: 10.8.0
  • prerelease: false
  • npm dist-tag on release: latest
  • npm pack dry-run: passed
  • jsr publish dry-run: passed

If you tag this commit as v10.8.0, release workflow will publish.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@bin/presenters/index.js`:
- Around line 34-57: The implementation of shouldStripColor() does not match its
docstring precedence: currently FORCE_COLOR being set to any non-empty value
short-circuits and ignores NO_COLOR; to fix, adjust the logic in
shouldStripColor() so NO_COLOR is evaluated before the FORCE_COLOR non-empty
branch (or alternatively update the docstring if FORCE_COLOR is intentionally
meant to override NO_COLOR) — locate the shouldStripColor function and either
move the NO_COLOR check above the FORCE_COLOR !== '' branch or change the
comment to state that FORCE_COLOR overrides NO_COLOR.

In `@test/unit/presenters/present.test.js`:
- Around line 117-120: The CI test is passing for the wrong reason because
shouldStripColor() checks !process.stdout.isTTY before process.env.CI; update
the test in present.test.js to explicitly set process.stdout.isTTY = true before
setting process.env.CI = 'true' (and restore both in afterEach) so the CI branch
in shouldStripColor() is exercised; likewise, when adding a test for the final
`return false` path, set process.stdout.isTTY = true and ensure process.env.CI
is unset (and restore afterward) so the test targets the intended branch.
🧹 Nitpick comments (3)
test/bats/cli-ndjson.bats (2)

57-67: Error test allows up to 2 lines but comment says "single-line JSON".

Line 63 asserts line_count -le 2 while the comment on Line 60 says "stdout should be single-line JSON." If the contract is truly single-line NDJSON even for errors, tighten this to -eq 1. If the slack is intentional (e.g., an extra trailing newline), a brief comment explaining why would help.


34-43: _-prefixed key check is shallow — consistent with sanitizePayload, but worth noting.

Object.keys(obj) only inspects top-level keys. If nested objects ever carry _-prefixed keys, this test won't catch them. This matches the current shallow sanitizePayload implementation, so no issue today, but consider adding a recursive check if the sanitization contract expands.

test/unit/presenters/present.test.js (1)

14-26: Monkey-patching process.stdout.write is reasonable here but consider vi.spyOn for cleaner teardown.

Using vi.spyOn(process.stdout, 'write').mockImplementation(...) would auto-restore in afterEach with vi.restoreAllMocks() and avoids manual save/restore. Minor ergonomic nit.

…dence

FORCE_COLOR!='' intentionally overrides NO_COLOR per the de facto
standard. Updated JSDoc to document the full precedence chain and
added a regression test for the FORCE_COLOR > NO_COLOR override.
@github-actions
Copy link

Release Preflight

  • package version: 10.8.0
  • prerelease: false
  • npm dist-tag on release: latest
  • npm pack dry-run: passed
  • jsr publish dry-run: passed

If you tag this commit as v10.8.0, release workflow will publish.

@flyingrobots flyingrobots merged commit cd2a853 into main Feb 12, 2026
7 checks passed
@flyingrobots flyingrobots deleted the presenter branch February 12, 2026 08:25
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