Skip to content

Conversation

@zpershuai
Copy link

@zpershuai zpershuai commented Jan 10, 2026

Add compact, emoji-rich horizontal format for displaying changes with progress bars, task status, and Git statistics.

Usage:
openspec list --spaceline

Example output:
πŸ“ add-feature | σ°·« β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–‘β–‘ 80%
σ°·« Implementation (4/5) | ✎ 2 | πŸ“‹ 1 open | 󰘬 main (↑3 ↓0)

Summary by CodeRabbit

  • New Features

    • Added --spaceline flag to list command enabling compact, emoji-enhanced output format
    • Includes visual progress bars with percentages, Git diff statistics, task completion status, and change metadata
    • Spaceline output is mutually exclusive with --json flag
  • Documentation

    • Added comprehensive design documentation and detailed specifications for spaceline formatting

✏️ Tip: You can customize this high-level summary in your review settings.

Add compact, emoji-rich horizontal format for displaying changes
with progress bars, task status, and Git statistics.

Usage:
  openspec list --spaceline

Example output:
  πŸ“ add-feature | σ°·« β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–‘β–‘ 80%
  σ°·«  Implementation (4/5) | ✎ 2 | πŸ“‹ 1 open | 󰘬 main (↑3 ↓0)

Co-Authored-By: Claude <[email protected]>
@zpershuai zpershuai requested a review from TabishB as a code owner January 10, 2026 08:39
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 10, 2026

πŸ“ Walkthrough

Walkthrough

This PR introduces a new Spaceline output format for the openspec list command. It adds documentation (design, proposal, and specifications), implements Git statistics utilities, creates a spaceline formatter module, and integrates both into the list command and CLI with a new --spaceline flag that is mutually exclusive with --json.

Changes

Cohort / File(s) Summary
Documentation - Design & Proposal
openspec/changes/archive/2026-01-10-add-list-spaceline/design.md, openspec/changes/archive/2026-01-10-add-list-spaceline/proposal.md, openspec/changes/archive/2026-01-10-add-list-spaceline/specs/cli-list/spec.md
Adds design document, proposal, and formal specification for Spaceline output format including goals, visual design with emojis and progress bars, implementation structure, and interaction with existing flags.
Specifications - CLI List
openspec/specs/cli-list/spec.md
Updates main CLI list specification with comprehensive Spaceline requirements, including scenarios, visual elements, mutually exclusive behavior with --json, and Git statistics handling.
Project Structure
openspec/project.md
Expands project layout documentation and tech stack details (Chalk, Ora, Vitest, changesets) with enhanced error handling and testing strategies.
CLI Integration
src/cli/index.ts
Adds --spaceline flag to list command with mutual exclusion validation against --json flag.
List Command Core
src/core/list.ts
Introduces executeSpaceline() method for spaceline output generation, helper method extractTitle(), and integrates Git stats collection into command execution flow.
Git Statistics Utility
src/utils/git-stats.ts
New module providing getGitDiffStats() and getGitDiffStatsForPath() functions using git diff --numstat with graceful error handling for non-Git environments.
Spaceline Formatter Utility
src/utils/spaceline-formatter.ts
New module exporting EMOJI constants, ChangeData/SpacelineStats types, generateProgressBar(), and formatSpaceline() for multi-line spaceline rendering.
Test Coverage
test/utils/git-stats.test.ts, test/utils/spaceline-formatter.test.ts
Comprehensive test suites for Git statistics and spaceline formatting utilities with temporary repo setup and edge case validation.

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant CLI
    participant ListCommand
    participant FileSystem
    participant GitStats
    participant SpacelineFormatter
    
    User->>CLI: openspec list --spaceline
    CLI->>ListCommand: execute(targetPath, 'changes', {spaceline: true})
    ListCommand->>FileSystem: Read changes directory
    FileSystem-->>ListCommand: Change entries list
    
    loop For each change
        ListCommand->>FileSystem: Read proposal.md for title
        FileSystem-->>ListCommand: Proposal content
        ListCommand->>ListCommand: extractTitle(content, changeName)
        ListCommand->>FileSystem: Count deltas via ChangeParser
        FileSystem-->>ListCommand: Delta count
        ListCommand->>GitStats: getGitDiffStatsForPath(changePath)
        GitStats-->>ListCommand: {added, removed, branch}
    end
    
    ListCommand->>SpacelineFormatter: formatSpaceline(change, stats)
    SpacelineFormatter-->>ListCommand: [line1, line2] with emojis & progress
    ListCommand->>User: Print formatted spaceline output
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • TabishB

Poem

🐰 Spacelines hop in all their glory,
Progress bars tell emoji's story,
Git stats dance with ✨ and grace,
Changes laid in compact space,
A fluffy format, quick and bright,
Making lists feel just right! πŸš€

πŸš₯ 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 PR title 'feat: add --spaceline output format to list command' is clear, concise, and directly describes the main changeβ€”adding a new output format feature to the list command.
Docstring Coverage βœ… Passed Docstring coverage is 100.00% 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

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.

@vibe-kanban-cloud
Copy link

Review Complete

Your review story is ready!

View Story

Comment !reviewfast on this PR to re-generate the story.

Copy link
Contributor

@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: 6

Caution

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

⚠️ Outside diff range comments (2)
src/cli/index.ts (1)

95-119: Decide how to handle --specs --spaceline (currently silently ignored).

If spaceline is changes-only, consider treating --specs --spaceline as misuse (exit code 2) rather than silently falling back to specs output.

Proposed CLI guard
   .action(async (options?: { specs?: boolean; changes?: boolean; sort?: string; json?: boolean; spaceline?: boolean }) => {
     try {
       // Check for mutually exclusive flags
       if (options?.spaceline && options?.json) {
         console.error('Error: Cannot use --spaceline with --json');
         process.exit(2);
       }
+      if (options?.spaceline && options?.specs) {
+        console.error('Error: Cannot use --spaceline with --specs');
+        process.exit(2);
+      }

       const listCommand = new ListCommand();
       const mode: 'changes' | 'specs' = options?.specs ? 'specs' : 'changes';
       const sort = options?.sort === 'name' ? 'name' : 'recent';
       await listCommand.execute('.', mode, { sort, json: options?.json, spaceline: options?.spaceline });
     } catch (error) {
openspec/specs/cli-list/spec.md (1)

49-65: Spec drift: spaceline example formatting is incorrect, and --long flag reference doesn't match the CLI.

  1. Spaceline format issues:

    • Line 1 is missing the status icon between the change ID and progress bar. Should be: πŸ“ {change-id} | σ°·« {progress-bar} {percentage}%
    • Git stats notation is reversed. Should be ↑{added} ↓{removed} (not {added}↑ {removed}↓)
    • Add language identifier text to the fenced code block (lines 117–122)
  2. --long flag reference (line 159): The spec requirement "Spaceline Flag Compatibility" mentions that spaceline should "take precedence over --long", but openspec list does not support a --long flag (only the deprecated openspec change list has it). Remove the reference to --long or clarify this applies only to the deprecated command.

πŸ€– Fix all issues with AI agents
In
@openspec/changes/archive/2026-01-10-add-list-spaceline/specs/cli-list/spec.md:
- Around line 15-33: The spec example block is inconsistent with the implemented
spaceline: move {status-icon} to Line 1 before {progress-bar} and match git stat
ordering to (↑{added} ↓{removed}); also add a language tag to the fenced block
(use "text") and adjust the second line to mirror the formatter: "πŸ“ {change-id}
| {status-icon} {progress-bar} {percentage}%" and "{status-icon} {category}
({completed}/{total}) | {delta-icon} {count} | {open-icon} {open} open |
{branch-icon} {branch} (↑{added} ↓{removed})", updating the WHERE list entries
for ↑{added} and ↓{removed} accordingly.

In @openspec/project.md:
- Around line 19-46: The fenced code block under the "## Project Structure"
section is missing a language tag (MD040); update the opening fence from ``` to
```text so the tree block is explicitly tagged (i.e., replace the triple
backticks before the src/ tree with ```text) to satisfy the markdownlint rule.

In @src/core/list.ts:
- Around line 153-156: The ternary for building sorted in src/core/list.ts
currently sorts by changeData.id in both branches; update the non-'name' branch
(the "recent" case) so it sorts by lastModified instead β€” change the comparator
used in changesData.sort for the else branch to compare
b.changeData.lastModified and a.changeData.lastModified (or parse them as Dates
if they are strings) to produce descending (most recent first) order; keep the
'name' branch using changeData.id.localeCompare and only change the comparator
for the recent sort.

In @src/utils/git-stats.ts:
- Around line 123-158: The getGitDiffStatsForPath function interpolates
relativePath into a shell command causing possible command injection and
failures with spaces/special chars; replace the execSync interpolation with a
safe child_process spawnSync call (or spawn) that passes arguments as an array
(e.g., ['diff','--numstat', base, '--', relativePath]) and read stdout with the
same encoding, updating the function to import spawnSync from 'child_process'
and handle errors the same way so parsing of output, added/removed calculation,
and returned shape remain unchanged.

In @src/utils/spaceline-formatter.ts:
- Around line 6-21: Update the README Prerequisites to note that certain glyphs
used in the UI require a Nerd Font (e.g., FiraCode Nerd Font or Hack Nerd Font)
so the characters for STATUS_IMPLEMENTATION and BRANCH in EMOJI render
correctly; add a brief line in the Prerequisites section naming Nerd Font as a
requirement and suggest a couple of example fonts and how to verify
installation.

In @test/utils/git-stats.test.ts:
- Around line 43-65: The test assumes the repo branch is 'main' which can be
'master' by default; update the test in git-stats.test.ts (the it block that
calls getGitDiffStats) to either initialize the repo with an explicit branch
(use execSync('git init -b main', ...) when setting up the temp repo) or make
the assertion accept both names (e.g., assert stats?.branch is 'main' or
'master' or match /^(main|master)$/) so the test passes regardless of Git's
default branch.
🧹 Nitpick comments (6)
src/utils/git-stats.ts (1)

79-115: Document the edge case for equal additions and deletions.

When a file has equal additions and deletions (additions === deletions), it's not counted in either the added or removed category. While this may be intentional for neutral changes, it should be documented in the function's JSDoc comment to clarify the classification logic.

πŸ“ Suggested documentation enhancement
 /**
  * Get Git diff statistics for the current repository.
  *
  * Uses `git diff --numstat` to count file additions and removals.
  * Files with net additions (additions > deletions) count as "added".
  * Files with net removals (deletions > additions) count as "removed".
+ * Files with equal additions and deletions are not counted in either category.
  *
  * @returns GitStats with added/removed counts, or null if Git is unavailable
openspec/changes/archive/2026-01-10-add-list-spaceline/design.md (1)

19-19: Add language identifier to fenced code block.

The code block at line 19 should specify a language for proper syntax highlighting and linting compliance.

πŸ“ Proposed fix
-```
+```text
 123  45  path/to/file.ts    # 123 additions, 45 deletions β†’ counts as "added" (↑)
 10   50  another/file.ts    # 10 additions, 50 deletions β†’ counts as "removed" (↓)
</details>

</blockquote></details>
<details>
<summary>test/utils/spaceline-formatter.test.ts (1)</summary><blockquote>

`23-49`: **Consider defining behavior for non-positive / non-integer `width`.**

Right now tests only cover a positive integer width; adding a couple of cases (e.g., `width=0`, `width<0`, `width=3.5`) would lock in expected behavior and catch accidental RangeErrors if implementation changes.

</blockquote></details>
<details>
<summary>src/utils/spaceline-formatter.ts (1)</summary><blockquote>

`131-141`: **Consider consistent formatting for missing Git stats.**

When Git stats are unavailable, line 140 shows `'| (?)'`, which omits the branch emoji present when stats are available (line 138: `| ${EMOJI.BRANCH} ${branch} (...)`). This inconsistency could make the output harder to parse visually or programmatically.

Consider using a consistent format:


<details>
<summary>♻️ Suggested consistent fallback</summary>

```diff
   } else {
-    gitStats = '| (?)';
+    gitStats = `| ${EMOJI.BRANCH} (no git data)`;
   }
src/core/list.ts (2)

146-146: Clarify the openItems calculation.

The calculation progress.total - progress.completed represents incomplete tasks, which may not accurately reflect "open items (TODOs, PRs, etc.)" as described in the SpacelineStats interface documentation (line 45 in spaceline-formatter.ts).

Consider either:

  • Updating the interface documentation to clarify this is incomplete tasks, or
  • Implementing a more comprehensive open items count if the current approach doesn't meet requirements

125-133: Consider logging parse errors for debugging.

The try-catch block silently ignores all errors when reading or parsing the proposal. While using fallback values is appropriate, completely silent failures could make debugging difficult if proposal files are malformed.

Consider adding a debug log or comment explaining why errors are intentionally ignored.

♻️ Optional: Add minimal error context
       } catch {
-        // Unable to read proposal or parse deltas
+        // Unable to read proposal or parse deltas - using defaults (title=changeDir, deltaCount=0)
       }
πŸ“œ Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

πŸ“₯ Commits

Reviewing files that changed from the base of the PR and between 4715138 and 7591e24.

πŸ“’ Files selected for processing (12)
  • openspec/changes/archive/2026-01-10-add-list-spaceline/design.md
  • openspec/changes/archive/2026-01-10-add-list-spaceline/proposal.md
  • openspec/changes/archive/2026-01-10-add-list-spaceline/specs/cli-list/spec.md
  • openspec/changes/archive/2026-01-10-add-list-spaceline/tasks.md
  • openspec/project.md
  • openspec/specs/cli-list/spec.md
  • src/cli/index.ts
  • src/core/list.ts
  • src/utils/git-stats.ts
  • src/utils/spaceline-formatter.ts
  • test/utils/git-stats.test.ts
  • test/utils/spaceline-formatter.test.ts
🧰 Additional context used
πŸ““ Path-based instructions (3)
openspec/changes/**/*.md

πŸ“„ CodeRabbit inference engine (openspec/AGENTS.md)

Scaffold proposal using proposal.md, tasks.md, optional design.md, and delta specs under openspec/changes/<id>/

Files:

  • openspec/changes/archive/2026-01-10-add-list-spaceline/specs/cli-list/spec.md
  • openspec/changes/archive/2026-01-10-add-list-spaceline/design.md
  • openspec/changes/archive/2026-01-10-add-list-spaceline/proposal.md
  • openspec/changes/archive/2026-01-10-add-list-spaceline/tasks.md
openspec/changes/**/specs/**/spec.md

πŸ“„ CodeRabbit inference engine (openspec/AGENTS.md)

openspec/changes/**/specs/**/spec.md: Use ## ADDED|MODIFIED|REMOVED|RENAMED Requirements headers in spec delta files
Include at least one #### Scenario: per requirement in spec delta files
Use #### Scenario: Name format (4 hashtags) for scenario headers, not bullets or bold text
Use ## ADDED Requirements for new orthogonal capabilities that can stand alone; use ## MODIFIED Requirements for behavior changes of existing requirements
When using MODIFIED Requirements, paste the full requirement block including header and all scenarios

Files:

  • openspec/changes/archive/2026-01-10-add-list-spaceline/specs/cli-list/spec.md
openspec/specs/**/spec.md

πŸ“„ CodeRabbit inference engine (openspec/AGENTS.md)

Use SHALL/MUST for normative requirements in spec files; avoid should/may unless intentionally non-normative

Files:

  • openspec/specs/cli-list/spec.md
🧠 Learnings (14)
πŸ“š Learning: 2025-11-25T01:08:19.004Z
Learnt from: CR
Repo: Fission-AI/OpenSpec PR: 0
File: openspec/AGENTS.md:0-0
Timestamp: 2025-11-25T01:08:19.004Z
Learning: Applies to openspec/changes/**/specs/**/spec.md : Use `## ADDED|MODIFIED|REMOVED|RENAMED Requirements` headers in spec delta files

Applied to files:

  • openspec/changes/archive/2026-01-10-add-list-spaceline/specs/cli-list/spec.md
  • openspec/changes/archive/2026-01-10-add-list-spaceline/design.md
  • openspec/changes/archive/2026-01-10-add-list-spaceline/proposal.md
  • openspec/specs/cli-list/spec.md
πŸ“š Learning: 2025-11-25T01:08:19.004Z
Learnt from: CR
Repo: Fission-AI/OpenSpec PR: 0
File: openspec/AGENTS.md:0-0
Timestamp: 2025-11-25T01:08:19.004Z
Learning: Applies to openspec/changes/*/proposal.md : Ensure `proposal.md` includes sections: Why (1-2 sentences), What Changes (bullet list with breaking change markers), and Impact (affected specs and code)

Applied to files:

  • openspec/changes/archive/2026-01-10-add-list-spaceline/specs/cli-list/spec.md
  • openspec/changes/archive/2026-01-10-add-list-spaceline/design.md
  • openspec/changes/archive/2026-01-10-add-list-spaceline/proposal.md
  • openspec/changes/archive/2026-01-10-add-list-spaceline/tasks.md
  • openspec/specs/cli-list/spec.md
πŸ“š Learning: 2025-11-25T01:08:19.004Z
Learnt from: CR
Repo: Fission-AI/OpenSpec PR: 0
File: openspec/AGENTS.md:0-0
Timestamp: 2025-11-25T01:08:19.004Z
Learning: Applies to openspec/changes/**/specs/**/spec.md : Use `## ADDED Requirements` for new orthogonal capabilities that can stand alone; use `## MODIFIED Requirements` for behavior changes of existing requirements

Applied to files:

  • openspec/changes/archive/2026-01-10-add-list-spaceline/specs/cli-list/spec.md
  • openspec/changes/archive/2026-01-10-add-list-spaceline/proposal.md
  • openspec/specs/cli-list/spec.md
πŸ“š Learning: 2025-11-25T01:08:19.004Z
Learnt from: CR
Repo: Fission-AI/OpenSpec PR: 0
File: openspec/AGENTS.md:0-0
Timestamp: 2025-11-25T01:08:19.004Z
Learning: Applies to openspec/changes/**/*.md : Scaffold proposal using `proposal.md`, `tasks.md`, optional `design.md`, and delta specs under `openspec/changes/<id>/`

Applied to files:

  • openspec/changes/archive/2026-01-10-add-list-spaceline/specs/cli-list/spec.md
  • openspec/changes/archive/2026-01-10-add-list-spaceline/design.md
  • openspec/changes/archive/2026-01-10-add-list-spaceline/proposal.md
  • openspec/project.md
πŸ“š Learning: 2025-11-25T01:08:19.004Z
Learnt from: CR
Repo: Fission-AI/OpenSpec PR: 0
File: openspec/AGENTS.md:0-0
Timestamp: 2025-11-25T01:08:19.004Z
Learning: Applies to openspec/changes/**/specs/**/spec.md : Include at least one `#### Scenario:` per requirement in spec delta files

Applied to files:

  • openspec/changes/archive/2026-01-10-add-list-spaceline/specs/cli-list/spec.md
  • openspec/specs/cli-list/spec.md
πŸ“š Learning: 2025-11-25T01:08:02.839Z
Learnt from: CR
Repo: Fission-AI/OpenSpec PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-25T01:08:02.839Z
Learning: Use `@/openspec/AGENTS.md` to learn how to create and apply change proposals, spec format and conventions, and project structure and guidelines

Applied to files:

  • openspec/changes/archive/2026-01-10-add-list-spaceline/specs/cli-list/spec.md
  • openspec/changes/archive/2026-01-10-add-list-spaceline/proposal.md
  • openspec/project.md
πŸ“š Learning: 2025-11-25T01:08:19.004Z
Learnt from: CR
Repo: Fission-AI/OpenSpec PR: 0
File: openspec/AGENTS.md:0-0
Timestamp: 2025-11-25T01:08:19.004Z
Learning: Search existing work using `openspec spec list --long`, `openspec list` for enumerating changes; use `rg` only for full-text search

Applied to files:

  • openspec/changes/archive/2026-01-10-add-list-spaceline/specs/cli-list/spec.md
πŸ“š Learning: 2025-11-25T01:08:19.004Z
Learnt from: CR
Repo: Fission-AI/OpenSpec PR: 0
File: openspec/AGENTS.md:0-0
Timestamp: 2025-11-25T01:08:19.004Z
Learning: Applies to openspec/changes/**/specs/**/spec.md : When using MODIFIED Requirements, paste the full requirement block including header and all scenarios

Applied to files:

  • openspec/changes/archive/2026-01-10-add-list-spaceline/specs/cli-list/spec.md
  • openspec/specs/cli-list/spec.md
πŸ“š Learning: 2025-11-25T01:08:19.004Z
Learnt from: CR
Repo: Fission-AI/OpenSpec PR: 0
File: openspec/AGENTS.md:0-0
Timestamp: 2025-11-25T01:08:19.004Z
Learning: Check `openspec/project.md` for project conventions before creating specs

Applied to files:

  • openspec/changes/archive/2026-01-10-add-list-spaceline/specs/cli-list/spec.md
  • openspec/project.md
πŸ“š Learning: 2025-11-25T01:08:19.004Z
Learnt from: CR
Repo: Fission-AI/OpenSpec PR: 0
File: openspec/AGENTS.md:0-0
Timestamp: 2025-11-25T01:08:19.004Z
Learning: Applies to openspec/changes/**/specs/**/spec.md : Use `#### Scenario: Name` format (4 hashtags) for scenario headers, not bullets or bold text

Applied to files:

  • openspec/changes/archive/2026-01-10-add-list-spaceline/specs/cli-list/spec.md
  • openspec/specs/cli-list/spec.md
πŸ“š Learning: 2025-11-25T01:08:19.004Z
Learnt from: CR
Repo: Fission-AI/OpenSpec PR: 0
File: openspec/AGENTS.md:0-0
Timestamp: 2025-11-25T01:08:19.004Z
Learning: Applies to openspec/changes/*/tasks.md : Ensure `tasks.md` contains implementation checklist with numbered sections and checkbox items

Applied to files:

  • openspec/changes/archive/2026-01-10-add-list-spaceline/specs/cli-list/spec.md
  • openspec/changes/archive/2026-01-10-add-list-spaceline/proposal.md
  • openspec/changes/archive/2026-01-10-add-list-spaceline/tasks.md
  • openspec/specs/cli-list/spec.md
πŸ“š Learning: 2025-11-25T01:08:19.004Z
Learnt from: CR
Repo: Fission-AI/OpenSpec PR: 0
File: openspec/AGENTS.md:0-0
Timestamp: 2025-11-25T01:08:19.004Z
Learning: Applies to openspec/specs/**/spec.md : Use SHALL/MUST for normative requirements in spec files; avoid should/may unless intentionally non-normative

Applied to files:

  • openspec/changes/archive/2026-01-10-add-list-spaceline/specs/cli-list/spec.md
  • openspec/specs/cli-list/spec.md
πŸ“š Learning: 2025-11-25T01:08:19.004Z
Learnt from: CR
Repo: Fission-AI/OpenSpec PR: 0
File: openspec/AGENTS.md:0-0
Timestamp: 2025-11-25T01:08:19.004Z
Learning: Run `openspec validate [change-id] --strict` before requesting approval

Applied to files:

  • openspec/changes/archive/2026-01-10-add-list-spaceline/specs/cli-list/spec.md
πŸ“š Learning: 2025-11-25T01:08:19.004Z
Learnt from: CR
Repo: Fission-AI/OpenSpec PR: 0
File: openspec/AGENTS.md:0-0
Timestamp: 2025-11-25T01:08:19.004Z
Learning: Create `design.md` only when needed: cross-cutting changes, new external dependencies, significant data model changes, security/performance complexity, or pre-coding ambiguity

Applied to files:

  • openspec/changes/archive/2026-01-10-add-list-spaceline/design.md
🧬 Code graph analysis (4)
src/cli/index.ts (1)
src/core/list.ts (1)
  • ListCommand (81-289)
src/utils/spaceline-formatter.ts (1)
src/utils/git-stats.ts (1)
  • GitStatsResult (19-19)
test/utils/spaceline-formatter.test.ts (1)
src/utils/spaceline-formatter.ts (5)
  • EMOJI (6-21)
  • generateProgressBar (81-87)
  • ChangeData (26-37)
  • SpacelineStats (42-47)
  • formatSpaceline (115-146)
test/utils/git-stats.test.ts (1)
src/utils/git-stats.ts (2)
  • getGitDiffStats (79-115)
  • getGitDiffStatsForPath (123-158)
πŸͺ› markdownlint-cli2 (0.18.1)
openspec/changes/archive/2026-01-10-add-list-spaceline/design.md

19-19: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

openspec/project.md

20-20: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

openspec/specs/cli-list/spec.md

117-117: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

πŸ”‡ Additional comments (15)
openspec/changes/archive/2026-01-10-add-list-spaceline/proposal.md (1)

1-33: LGTM! Documentation structure follows guidelines.

The proposal document correctly includes all required sections (Why, What Changes, and Impact) with appropriate detail. The structure aligns with the expected format for change proposals.

Based on learnings, the proposal structure is correct.

openspec/changes/archive/2026-01-10-add-list-spaceline/tasks.md (1)

1-47: LGTM! Task checklist follows guidelines.

The tasks document correctly uses numbered sections with checkbox items, covering Implementation, Testing, and Documentation. The deferred item (3.3) is clearly marked with rationale.

Based on learnings, the task structure is correct.

src/utils/git-stats.ts (1)

1-41: LGTM! Helper functions are well-structured.

The type definitions, constants, and helper functions (getCurrentBranch, getDiffBase) are cleanly implemented with appropriate error handling.

test/utils/git-stats.test.ts (1)

27-41: LGTM! Core test structure is solid.

The test setup, teardown, and structure validation tests are well-implemented with appropriate error handling and assertions.

Also applies to: 67-74, 102-113

test/utils/spaceline-formatter.test.ts (1)

1-178: Solid end-to-end coverage for spaceline formatting.

The suite hits constants, progress-bar rounding/clamping, category mapping, and git-stats present/absent paths without overfitting to full-line string equality.

src/cli/index.ts (1)

95-119: Nice: --spaceline is wired cleanly and --json conflict exits with code 2.

This matches the documented β€œmisuse” exit code and prevents ambiguous output formats.

openspec/changes/archive/2026-01-10-add-list-spaceline/specs/cli-list/spec.md (1)

75-92: No action required. The delta spec already includes the complete "Requirement: Flags" block with all three scenarios (Selecting specs, Selecting changes, and Spaceline output format) in their entirety. The MODIFIED Requirements section is correctly formatted and fully complies with the guideline to paste the complete requirement block including all scenarios.

Likely an incorrect or invalid review comment.

src/utils/spaceline-formatter.ts (3)

23-47: LGTM!

The type definitions are well-structured and properly documented with clear JSDoc comments.


49-65: LGTM!

The category mapping logic handles edge cases appropriately. The split('-')[0] approach gracefully handles change IDs without hyphens, and the default fallback to 'Implementation' is reasonable.


67-87: LGTM!

The progress bar generation logic correctly clamps the percentage, handles edge cases (0%, 100%), and includes helpful examples in the JSDoc.

src/core/list.ts (5)

7-9: LGTM!

The new imports are necessary for the spaceline functionality and are properly utilized in the executeSpaceline method.


21-21: LGTM!

The spaceline option follows the established pattern for optional boolean flags in the interface.


82-88: LGTM!

The title extraction logic uses an appropriate regex pattern with good fallback behavior. The case-insensitive matching for "Change:" is a nice touch for robustness.


169-174: LGTM!

The integration of the spaceline mode uses a clean early-return pattern and properly validates both the mode and spaceline flag before delegating to the new method.


143-143: No action required. The function getGitDiffStatsForPath is confirmed to be synchronous (returns GitStatsResult directly, not a Promise). Calling it without await at line 143 is correct and intentional.

Comment on lines +15 to +33
#### Scenario: Spaceline with all elements

- **WHEN** a change has all available information
- **THEN** display:
```
πŸ“ {change-id} | {progress-bar} {percentage}%
{status-icon} {category} ({completed}/{total} tasks) | {delta-icon} {count} | {open-icon} {open} | {branch-icon} {branch} ({added}↑ {removed}↓)
```
- **WHERE**:
- `progress-bar` is a 10-character visual bar using `β–ˆ` and `β–‘`
- `percentage` is task completion percentage
- `status-icon` is implementation stage emoji (σ°·« for implementation, etc.)
- `category` is derived from change ID prefix (e.g., "add" β†’ "Implementation")
- `delta-icon` `✎` shows spec delta count
- `open-icon` `πŸ“‹` shows open TODO/pr items count
- `branch-icon` `󰘬` shows current Git branch
- `added↑` shows number of files with net additions
- `removed↓` shows number of files with net removals

Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Spec example output doesn’t match the implemented spaceline line format (status icon + git stats ordering).

The formatter output (and PR example) includes the status icon on Line 1 and uses ↑{added} ↓{removed} ordering; the spec example currently differs. Also, add a language to the fenced block (MD040).

Proposed spec fix
 #### Scenario: Spaceline with all elements

 - **WHEN** a change has all available information
 - **THEN** display:
-  ```
-  πŸ“ {change-id} | {progress-bar} {percentage}%
-  {status-icon} {category} ({completed}/{total} tasks) | {delta-icon} {count} | {open-icon} {open} | {branch-icon} {branch} ({added}↑ {removed}↓)
-  ```
+  ```text
+  πŸ“ {change-id} | {status-icon} {progress-bar} {percentage}%
+  {status-icon} {category} ({completed}/{total}) | {delta-icon} {count} | {open-icon} {open} open | {branch-icon} {branch} (↑{added} ↓{removed})
+  ```
 - **WHERE**:
   - `progress-bar` is a 10-character visual bar using `β–ˆ` and `β–‘`
   - `percentage` is task completion percentage
   - `status-icon` is implementation stage emoji (σ°·« for implementation, etc.)
   - `category` is derived from change ID prefix (e.g., "add" β†’ "Implementation")
   - `delta-icon` `✎` shows spec delta count
   - `open-icon` `πŸ“‹` shows open TODO/pr items count
   - `branch-icon` `󰘬` shows current Git branch
-  - `added↑` shows number of files with net additions
-  - `removed↓` shows number of files with net removals
+  - `↑{added}` shows number of files with net additions
+  - `↓{removed}` shows number of files with net removals

Also applies to: 34-40

πŸ€– Prompt for AI Agents
In
@openspec/changes/archive/2026-01-10-add-list-spaceline/specs/cli-list/spec.md
around lines 15 - 33, The spec example block is inconsistent with the
implemented spaceline: move {status-icon} to Line 1 before {progress-bar} and
match git stat ordering to (↑{added} ↓{removed}); also add a language tag to the
fenced block (use "text") and adjust the second line to mirror the formatter:
"πŸ“ {change-id} | {status-icon} {progress-bar} {percentage}%" and "{status-icon}
{category} ({completed}/{total}) | {delta-icon} {count} | {open-icon} {open}
open | {branch-icon} {branch} (↑{added} ↓{removed})", updating the WHERE list
entries for ↑{added} and ↓{removed} accordingly.

Comment on lines 19 to 46
## Project Structure
```
src/
β”œβ”€β”€ cli/ # CLI command implementations
β”œβ”€β”€ core/ # Core OpenSpec logic (templates, structure)
└── utils/ # Shared utilities (file operations, rollback)
β”œβ”€β”€ commands/ # CLI command handlers
β”‚ β”œβ”€β”€ change.ts # Change proposal commands
β”‚ β”œβ”€β”€ spec.ts # Spec management commands
β”‚ β”œβ”€β”€ completion.ts # Shell completion commands
β”‚ β”œβ”€β”€ config.ts # Configuration commands
β”‚ β”œβ”€β”€ validate.ts # Validation commands
β”‚ └── show.ts # Display commands
β”œβ”€β”€ core/
β”‚ β”œβ”€β”€ configurators/ # AI tool configuration
β”‚ β”‚ β”œβ”€β”€ registry.ts
β”‚ β”‚ β”œβ”€β”€ [agent].ts
β”‚ β”‚ └── slash/ # Slash command configurators
β”‚ β”œβ”€β”€ completions/ # Shell completion system
β”‚ β”‚ β”œβ”€β”€ generators/ # Bash, Zsh, Fish, PowerShell
β”‚ β”‚ β”œβ”€β”€ installers/
β”‚ β”‚ └── templates/
β”‚ β”œβ”€β”€ artifact-graph/ # Artifact dependency resolution
β”‚ β”œβ”€β”€ templates/ # AI instruction templates
β”‚ β”œβ”€β”€ schemas/ # OpenSpec schema definitions
β”‚ β”œβ”€β”€ parsers/ # Markdown/change parsers
β”‚ └── validation/ # Spec validation logic
└── utils/ # Shared utilities
dist/ # Compiled output (gitignored)
```
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟑 Minor

Add a language tag to the fenced project-structure block (MD040).

Proposed markdownlint fix
 ## Project Structure
-```
+```text
 src/
 β”œβ”€β”€ commands/           # CLI command handlers
 β”‚   β”œβ”€β”€ change.ts      # Change proposal commands
 β”‚   β”œβ”€β”€ spec.ts        # Spec management commands
 β”‚   β”œβ”€β”€ completion.ts  # Shell completion commands
 β”‚   β”œβ”€β”€ config.ts      # Configuration commands
 β”‚   β”œβ”€β”€ validate.ts    # Validation commands
 β”‚   └── show.ts        # Display commands
 β”œβ”€β”€ core/
 β”‚   β”œβ”€β”€ configurators/  # AI tool configuration
 β”‚   β”‚   β”œβ”€β”€ registry.ts
 β”‚   β”‚   β”œβ”€β”€ [agent].ts
 β”‚   β”‚   └── slash/      # Slash command configurators
 β”‚   β”œβ”€β”€ completions/    # Shell completion system
 β”‚   β”‚   β”œβ”€β”€ generators/ # Bash, Zsh, Fish, PowerShell
 β”‚   β”‚   β”œβ”€β”€ installers/
 β”‚   β”‚   └── templates/
 β”‚   β”œβ”€β”€ artifact-graph/ # Artifact dependency resolution
 β”‚   β”œβ”€β”€ templates/      # AI instruction templates
 β”‚   β”œβ”€β”€ schemas/        # OpenSpec schema definitions
 β”‚   β”œβ”€β”€ parsers/        # Markdown/change parsers
 β”‚   └── validation/     # Spec validation logic
 └── utils/              # Shared utilities

 dist/           # Compiled output (gitignored)
</details>

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

<details>
<summary>πŸͺ› markdownlint-cli2 (0.18.1)</summary>

20-20: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

</details>

</details>

<details>
<summary>πŸ€– Prompt for AI Agents</summary>

In @openspec/project.md around lines 19 - 46, The fenced code block under the
"## Project Structure" section is missing a language tag (MD040); update the
opening fence from totext so the tree block is explicitly tagged (i.e.,
replace the triple backticks before the src/ tree with ```text) to satisfy the
markdownlint rule.


</details>

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

Comment on lines +153 to +156
// Sort by preference (spaceline uses alphabetical by default for readability)
const sorted = sort === 'name'
? changesData.sort((a, b) => a.changeData.id.localeCompare(b.changeData.id))
: changesData.sort((a, b) => a.changeData.id.localeCompare(b.changeData.id));
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | πŸ”΄ Critical

Fix the sorting logic bug.

Both branches of the ternary operator perform the same alphabetical sort by changeData.id. The recent sort should order by lastModified instead.

πŸ› Proposed fix for sorting logic
-    const sorted = sort === 'name'
-      ? changesData.sort((a, b) => a.changeData.id.localeCompare(b.changeData.id))
-      : changesData.sort((a, b) => a.changeData.id.localeCompare(b.changeData.id));
+    const sorted = sort === 'name'
+      ? changesData.sort((a, b) => a.changeData.id.localeCompare(b.changeData.id))
+      : changesData.sort((a, b) => b.lastModified.getTime() - a.lastModified.getTime());
πŸ€– Prompt for AI Agents
In @src/core/list.ts around lines 153 - 156, The ternary for building sorted in
src/core/list.ts currently sorts by changeData.id in both branches; update the
non-'name' branch (the "recent" case) so it sorts by lastModified instead β€”
change the comparator used in changesData.sort for the else branch to compare
b.changeData.lastModified and a.changeData.lastModified (or parse them as Dates
if they are strings) to produce descending (most recent first) order; keep the
'name' branch using changeData.id.localeCompare and only change the comparator
for the recent sort.

Comment on lines +123 to +158
export function getGitDiffStatsForPath(filePath: string): GitStatsResult {
try {
const branch = getCurrentBranch();
if (branch === '(no branch)') {
return { ...DEFAULT_GIT_STATS, branch };
}

const base = getDiffBase();
const relativePath = path.relative(process.cwd(), filePath);
const output = execSync(`git diff --numstat ${base} -- ${relativePath}`, { encoding: 'utf-8' });

let added = 0;
let removed = 0;

const lines = output.trim().split('\n');
for (const line of lines) {
if (!line) continue;

const parts = line.split('\t');
if (parts.length < 2) continue;

const additions = parseInt(parts[0], 10) || 0;
const deletions = parseInt(parts[1], 10) || 0;

if (additions > deletions) {
added++;
} else if (deletions > additions) {
removed++;
}
}

return { added, removed, branch };
} catch {
return null;
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Escape or quote the path argument to prevent command injection.

Line 132 directly interpolates relativePath into the shell command without escaping or quoting. If the path contains spaces, quotes, or other special characters, the command could fail or behave unexpectedly. Consider using execSync with an array of arguments or properly escaping the path.

πŸ”’ Proposed fix using array arguments or proper escaping

Option 1: Use array arguments (if execSync supports it via spawn)

Note: execSync doesn't directly support array arguments, but you can use spawn or spawnSync from child_process for safer argument passing.

Option 2: Escape the path for shell usage

     const base = getDiffBase();
     const relativePath = path.relative(process.cwd(), filePath);
-    const output = execSync(`git diff --numstat ${base} -- ${relativePath}`, { encoding: 'utf-8' });
+    // Escape path for shell by wrapping in quotes and escaping internal quotes
+    const escapedPath = relativePath.replace(/'/g, "'\\''");
+    const output = execSync(`git diff --numstat ${base} -- '${escapedPath}'`, { encoding: 'utf-8' });

Alternatively, for more robust handling, consider using a utility library for shell escaping or restructuring to avoid shell interpolation entirely.

πŸ“ 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
export function getGitDiffStatsForPath(filePath: string): GitStatsResult {
try {
const branch = getCurrentBranch();
if (branch === '(no branch)') {
return { ...DEFAULT_GIT_STATS, branch };
}
const base = getDiffBase();
const relativePath = path.relative(process.cwd(), filePath);
const output = execSync(`git diff --numstat ${base} -- ${relativePath}`, { encoding: 'utf-8' });
let added = 0;
let removed = 0;
const lines = output.trim().split('\n');
for (const line of lines) {
if (!line) continue;
const parts = line.split('\t');
if (parts.length < 2) continue;
const additions = parseInt(parts[0], 10) || 0;
const deletions = parseInt(parts[1], 10) || 0;
if (additions > deletions) {
added++;
} else if (deletions > additions) {
removed++;
}
}
return { added, removed, branch };
} catch {
return null;
}
}
export function getGitDiffStatsForPath(filePath: string): GitStatsResult {
try {
const branch = getCurrentBranch();
if (branch === '(no branch)') {
return { ...DEFAULT_GIT_STATS, branch };
}
const base = getDiffBase();
const relativePath = path.relative(process.cwd(), filePath);
// Escape path for shell by wrapping in quotes and escaping internal quotes
const escapedPath = relativePath.replace(/'/g, "'\\''");
const output = execSync(`git diff --numstat ${base} -- '${escapedPath}'`, { encoding: 'utf-8' });
let added = 0;
let removed = 0;
const lines = output.trim().split('\n');
for (const line of lines) {
if (!line) continue;
const parts = line.split('\t');
if (parts.length < 2) continue;
const additions = parseInt(parts[0], 10) || 0;
const deletions = parseInt(parts[1], 10) || 0;
if (additions > deletions) {
added++;
} else if (deletions > additions) {
removed++;
}
}
return { added, removed, branch };
} catch {
return null;
}
}
πŸ€– Prompt for AI Agents
In @src/utils/git-stats.ts around lines 123 - 158, The getGitDiffStatsForPath
function interpolates relativePath into a shell command causing possible command
injection and failures with spaces/special chars; replace the execSync
interpolation with a safe child_process spawnSync call (or spawn) that passes
arguments as an array (e.g., ['diff','--numstat', base, '--', relativePath]) and
read stdout with the same encoding, updating the function to import spawnSync
from 'child_process' and handle errors the same way so parsing of output,
added/removed calculation, and returned shape remain unchanged.

Comment on lines +6 to +21
export const EMOJI = {
/** Change/document icon */
CHANGE: 'πŸ“',
/** Progress bar filled character */
PROGRESS_FULL: 'β–ˆ',
/** Progress bar empty character */
PROGRESS_EMPTY: 'β–‘',
/** Implementation status */
STATUS_IMPLEMENTATION: 'σ°·«',
/** Delta count icon */
DELTA: '✎',
/** Open items icon */
OPEN: 'πŸ“‹',
/** Git branch icon */
BRANCH: '󰘬',
} as const;
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟑 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Search for mentions of Nerd Font or font requirements in documentation
echo "=== Searching for font/Nerd Font mentions ==="
rg -i -C3 'nerd.?font|font.?requirement|prerequisites|requirements' README.md --type=md 2>/dev/null || echo "No matches in README.md"

echo -e "\n=== Checking if docs directory exists ==="
if [ -d "docs" ]; then
  rg -i -C3 'nerd.?font|font.?requirement|prerequisites|requirements' docs/ --type=md 2>/dev/null || echo "No matches in docs/"
else
  echo "docs/ directory not found"
fi

echo -e "\n=== Broader search for installation or setup docs ==="
find . -maxdepth 2 -type f \( -name "INSTALL*" -o -name "SETUP*" -o -name "*.md" \) ! -path "*/node_modules/*" ! -path "*/.git/*" 2>/dev/null | head -20

Repository: Fission-AI/OpenSpec

Length of output: 5773


Document the Nerd Font requirement.

The EMOJI constants include Nerd Font icons (STATUS_IMPLEMENTATION: '󰷫' and BRANCH: '󰘬'), which require users to have a Nerd Font installed. Without it, these glyphs render as broken characters or boxes. This requirement is not currently documented in the README or installation prerequisites.

Add a note to the README's Prerequisites section specifying that a Nerd Font (such as FiraCode Nerd Font or Hack Nerd Font) is required for proper display of UI elements.

πŸ€– Prompt for AI Agents
In @src/utils/spaceline-formatter.ts around lines 6 - 21, Update the README
Prerequisites to note that certain glyphs used in the UI require a Nerd Font
(e.g., FiraCode Nerd Font or Hack Nerd Font) so the characters for
STATUS_IMPLEMENTATION and BRANCH in EMOJI render correctly; add a brief line in
the Prerequisites section naming Nerd Font as a requirement and suggest a couple
of example fonts and how to verify installation.

Comment on lines +43 to +65
it('should return stats when in a Git repository with changes', async () => {
// Initialize a new Git repo in temp dir
process.chdir(tempDir);
execSync('git init', { stdio: 'pipe', cwd: tempDir });
execSync('git config user.email "[email protected]"', { stdio: 'pipe', cwd: tempDir });
execSync('git config user.name "Test User"', { stdio: 'pipe', cwd: tempDir });

// Create initial commit
const initialFile = path.join(tempDir, 'initial.txt');
await fs.writeFile(initialFile, 'initial content');
execSync('git add .', { stdio: 'pipe', cwd: tempDir });
execSync('git commit -m "initial"', { stdio: 'pipe', cwd: tempDir });

// Create a new file with additions (staged but not committed)
const newFile = path.join(tempDir, 'new-file.txt');
await fs.writeFile(newFile, 'new content\nmore content');

const stats = getGitDiffStats();
expect(stats).not.toBeNull();
// Note: git diff --numstat against HEAD shows staged changes
// The new file should appear as an addition
expect(stats?.branch).toBe('main');
});
Copy link
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:

# First, let's find and examine the test file
find . -name "git-stats.test.ts" -type f

Repository: Fission-AI/OpenSpec

Length of output: 93


🏁 Script executed:

# Also search for the getGitDiffStats function to understand what it does
find . -name "*.ts" -type f | head -20

Repository: Fission-AI/OpenSpec

Length of output: 728


🏁 Script executed:

# List the test file to verify content and line numbers
ls -la test/utils/git-stats.test.ts 2>/dev/null || echo "File not found at expected location"

Repository: Fission-AI/OpenSpec

Length of output: 140


🏁 Script executed:

# Read the test file to verify the actual code at lines 43-65
cat -n test/utils/git-stats.test.ts | sed -n '40,70p'

Repository: Fission-AI/OpenSpec

Length of output: 1579


🏁 Script executed:

# Also find and examine the getGitDiffStats function implementation
find . -name "*.ts" -type f -not -path "*/node_modules/*" | xargs rg "export.*getGitDiffStats|function getGitDiffStats" -A 10

Repository: Fission-AI/OpenSpec

Length of output: 1349


🏁 Script executed:

# Examine the getCurrentBranch function and full git-stats.ts implementation
cat -n ./src/utils/git-stats.ts

Repository: Fission-AI/OpenSpec

Length of output: 5100


🏁 Script executed:

# Check if there's any git configuration setup in the test setup
cat -n test/utils/git-stats.test.ts | head -50

Repository: Fission-AI/OpenSpec

Length of output: 2289


🏁 Script executed:

# Check the full test to see if there's any explicit branch setting
cat -n test/utils/git-stats.test.ts | sed -n '43,100p'

Repository: Fission-AI/OpenSpec

Length of output: 2996


🏁 Script executed:

# Verify git init behavior and whether it supports -b flag for setting initial branch
git init --help 2>/dev/null | grep -A 5 "\-b" | head -20 || echo "Checking alternative..."

Repository: Fission-AI/OpenSpec

Length of output: 45


🏁 Script executed:

# Test git init default branch behavior in a sandbox
mkdir -p /tmp/git-test-1
cd /tmp/git-test-1
git init 2>&1 | head -5
git branch --show-current
cd /tmp
rm -rf git-test-1

Repository: Fission-AI/OpenSpec

Length of output: 371


🏁 Script executed:

# Verify that git init -b flag is available as a solution
git init --help 2>/dev/null | grep -E "^\s+-b," || git --version

Repository: Fission-AI/OpenSpec

Length of output: 81


Fix branch name assertion for test reliability.

Line 64 assumes the branch name is 'main', but git init creates a 'master' branch by default. This will cause the test to fail in environments where Git is configured to use the standard default branch name.

Consider either:

  1. Explicitly setting the default branch during test setup using git init -b main, or
  2. Making the assertion flexible to accept either branch name
Suggested fix for flexible branch name handling
     const stats = getGitDiffStats();
     expect(stats).not.toBeNull();
-    // Note: git diff --numstat against HEAD shows staged changes
-    // The new file should appear as an addition
-    expect(stats?.branch).toBe('main');
+    // Branch could be 'main' or 'master' depending on git config
+    expect(['main', 'master']).toContain(stats?.branch);
πŸ“ 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
it('should return stats when in a Git repository with changes', async () => {
// Initialize a new Git repo in temp dir
process.chdir(tempDir);
execSync('git init', { stdio: 'pipe', cwd: tempDir });
execSync('git config user.email "[email protected]"', { stdio: 'pipe', cwd: tempDir });
execSync('git config user.name "Test User"', { stdio: 'pipe', cwd: tempDir });
// Create initial commit
const initialFile = path.join(tempDir, 'initial.txt');
await fs.writeFile(initialFile, 'initial content');
execSync('git add .', { stdio: 'pipe', cwd: tempDir });
execSync('git commit -m "initial"', { stdio: 'pipe', cwd: tempDir });
// Create a new file with additions (staged but not committed)
const newFile = path.join(tempDir, 'new-file.txt');
await fs.writeFile(newFile, 'new content\nmore content');
const stats = getGitDiffStats();
expect(stats).not.toBeNull();
// Note: git diff --numstat against HEAD shows staged changes
// The new file should appear as an addition
expect(stats?.branch).toBe('main');
});
it('should return stats when in a Git repository with changes', async () => {
// Initialize a new Git repo in temp dir
process.chdir(tempDir);
execSync('git init', { stdio: 'pipe', cwd: tempDir });
execSync('git config user.email "[email protected]"', { stdio: 'pipe', cwd: tempDir });
execSync('git config user.name "Test User"', { stdio: 'pipe', cwd: tempDir });
// Create initial commit
const initialFile = path.join(tempDir, 'initial.txt');
await fs.writeFile(initialFile, 'initial content');
execSync('git add .', { stdio: 'pipe', cwd: tempDir });
execSync('git commit -m "initial"', { stdio: 'pipe', cwd: tempDir });
// Create a new file with additions (staged but not committed)
const newFile = path.join(tempDir, 'new-file.txt');
await fs.writeFile(newFile, 'new content\nmore content');
const stats = getGitDiffStats();
expect(stats).not.toBeNull();
// Branch could be 'main' or 'master' depending on git config
expect(['main', 'master']).toContain(stats?.branch);
});
πŸ€– Prompt for AI Agents
In @test/utils/git-stats.test.ts around lines 43 - 65, The test assumes the repo
branch is 'main' which can be 'master' by default; update the test in
git-stats.test.ts (the it block that calls getGitDiffStats) to either initialize
the repo with an explicit branch (use execSync('git init -b main', ...) when
setting up the temp repo) or make the assertion accept both names (e.g., assert
stats?.branch is 'main' or 'master' or match /^(main|master)$/) so the test
passes regardless of Git's default branch.

@TabishB
Copy link
Contributor

TabishB commented Jan 10, 2026

@zpershuai Whats the use case for this?

@zpershuai
Copy link
Author

@TabishB I use claude code,claude code has a feature that the user can customize the statusline, so I can use this feature to see the status of openspec immediately.
image

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.

2 participants