Skip to content

feat: add openspec check command for static analysis integration#1105

Open
magicyuan876 wants to merge 1 commit into
Fission-AI:mainfrom
magicyuan876:main
Open

feat: add openspec check command for static analysis integration#1105
magicyuan876 wants to merge 1 commit into
Fission-AI:mainfrom
magicyuan876:main

Conversation

@magicyuan876
Copy link
Copy Markdown

@magicyuan876 magicyuan876 commented May 20, 2026

Summary

This PR adds a new openspec check command that enables AI-generated code to be validated against language-specific static analysis tools (lint, type-check, format-check, etc.) before being considered "done".

What's New

1. openspec check [change-name] CLI command

  • Runs user-configured static checks against the implementation
  • Auto-detects the active change if none is specified
  • Supports --json output for machine-readable reports
  • Supports --concurrency for parallel execution
  • Exit code 0 = all passed, 1 = any failed

2. checks configuration in openspec/config.yaml

3. Auto-detection (detectChecks())

Inspects project files and suggests appropriate checks for:

  • TypeScript / JavaScript (tsc, eslint, prettier)
  • Rust (cargo check, clippy)
  • Python (ruff, mypy)
  • Go (go vet)
  • Ruby (rubocop)
  • Java (Maven compile, Gradle check, Checkstyle, SpotBugs, PMD)

4. Core module (src/core/code-checker/)

  • runner.ts — executes shell commands with timeout, output parsing, file glob filtering
  • detector.ts — best-effort project type detection
  • types.ts — shared interfaces

5. Tests

  • test/core/code-checker/runner.test.ts (8 tests)
  • test/core/code-checker/detector.test.ts (14 tests)
  • test/commands/check.test.ts (7 E2E tests)

Why this helps

AI agents often generate code with type errors or lint violations that aren't caught until human review. By embedding openspec check into the workflow, agents get structured, actionable feedback immediately — reducing token waste from iterative error-fixing loops.

Summary by CodeRabbit

  • New Features

    • Added a top-level "check" command to run configured static checks with concurrency, file-filtering, non-interactive mode, and JSON output.
    • Auto-detection of common linters, type checkers, and formatters for multiple languages.
    • Support for declaring custom checks in project configuration.
  • Tests

    • Added end-to-end and unit tests covering the check command, detection heuristics, and check runner behavior (parsing, skipping, timeouts).

Review Change Stack

@magicyuan876 magicyuan876 requested a review from TabishB as a code owner May 20, 2026 00:21
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 20, 2026

📝 Walkthrough

Walkthrough

This PR introduces a check CLI command that runs static code checks on OpenSpec changes. Users configure checks in openspec/config.yaml or let the system auto-detect common language tooling (TypeScript, Cargo, Python, Go, Maven, Gradle, Ruby). Checks execute concurrently with timeout protection, parse output for structured diagnostics, and filter by affected project files. Results aggregate into pass/fail/skip reports, available as JSON or formatted console output.

Changes

Static Check Verification

Layer / File(s) Summary
Type contracts and configuration
src/core/code-checker/types.ts, src/core/project-config.ts, src/commands/check.ts
Defines CheckEntry, CheckIssue, CheckResult, CheckReport interfaces; extends ProjectConfigSchema to parse checks array; introduces CheckOptions for CLI flags.
Check execution and detection engine
src/core/code-checker/runner.ts, src/core/code-checker/detector.ts, src/core/code-checker/index.ts
runCheck spawns commands with timeout (SIGTERM→SIGKILL), accumulates stdout/stderr, parses diagnostics (TypeScript, ESLint, Cargo, generic formats), and filters by affected files via glob matching; detectChecks scans for project markers (TypeScript, Rust, Python, Go, Maven, Gradle, Ruby) and returns discovered checks plus guidance.
CLI command wiring and CheckCommand
src/cli/index.ts, src/commands/check.ts
Registers check [change-name] command with --json, --concurrency <n>, --no-interactive options; CheckCommand.execute resolves the change, loads/detects checks, runs them concurrently, detects affected files via tasks.md/design.md heuristics, and prints aggregated results.
Example configuration
openspec/config.yaml
Demonstrates check configuration for TypeScript, ESLint, and Prettier over src/**/*.ts.
CheckCommand integration tests
test/commands/check.test.ts
Verifies no-config messaging, pass/fail exit codes, JSON output structure, single-change auto-selection, file-pattern filtering, and auto-detection suggestion.
Detector unit tests
test/core/code-checker/detector.test.ts
Validates detection heuristics for TypeScript, ESLint, Rust, Python, Go, Maven (compile and plugins), Gradle, and Ruby projects.
Runner unit tests
test/core/code-checker/runner.test.ts
Tests exit-code handling, TypeScript/ESLint diagnostic parsing, file-glob filtering (spawn skip behavior), spawn/process error surfacing, and timeout enforcement.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Suggested reviewers

  • TabishB

🐰 A curious rabbit hops through the code,
Finding checks in every project's mode,
TypeScript, Python, Go so grand,
Auto-detect across the land!
Concurrent runners, parsed with care,
Reports of passes—crisp and fair! 🐇✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: add openspec check command for static analysis integration' accurately and concisely summarizes the main change: a new CLI check command for running static analysis checks, which is the primary objective of this PR.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

Copy link
Copy Markdown
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: 3

🧹 Nitpick comments (1)
src/commands/check.ts (1)

94-106: ⚡ Quick win

Keep report ordering deterministic.

Line 95 appends by completion order, so check order can vary run-to-run. Store by currentIndex instead.

💡 Suggested fix
-    const results: CheckResult[] = [];
+    const results: CheckResult[] = new Array(queue.length);
@@
           task()
             .then((res) => {
-              results.push(res);
+              results[currentIndex] = res;
             })
             .catch((error: any) => {
               const message = error?.message || 'Unknown error';
-              results.push({
+              results[currentIndex] = {
                 name: checks[currentIndex]?.name ?? 'unknown',
                 passed: false,
                 durationMs: 0,
                 stdout: '',
                 stderr: message,
                 issues: [{ level: 'ERROR', path: 'check', message }],
               });
             })
@@
-    const report: CheckReport = {
+    const orderedResults = results.filter((r): r is CheckResult => Boolean(r));
+    const report: CheckReport = {
       changeName: resolvedChangeName,
-      checks: results,
+      checks: orderedResults,
       summary: {
-        total: results.length,
-        passed: results.filter((r) => r.passed && !r.skipped).length,
-        failed: results.filter((r) => !r.passed && !r.skipped).length,
-        skipped: results.filter((r) => r.skipped).length,
+        total: orderedResults.length,
+        passed: orderedResults.filter((r) => r.passed && !r.skipped).length,
+        failed: orderedResults.filter((r) => !r.passed && !r.skipped).length,
+        skipped: orderedResults.filter((r) => r.skipped).length,
       },
     };

Also applies to: 121-129

🤖 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/commands/check.ts` around lines 94 - 106, The code appends per-completion
into results causing nondeterministic ordering; instead initialize results to
the same length as checks and assign the result object at results[currentIndex]
(use results[currentIndex] = { name: checks[currentIndex]?.name ?? 'unknown',
passed: false, ... }) rather than results.push(...), and do the same replacement
for the other catch block handling failures; ensure results is pre-sized (e.g.,
results = new Array(checks.length)) so later consumers see deterministic
ordering by check index.
🤖 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/core/code-checker/runner.ts`:
- Around line 125-128: The timeout force-kill check uses child.killed which is
set to true immediately after sending SIGTERM, so SIGKILL never runs; update the
logic in runner.ts where the child process is escalated (the timeout callback
referencing child.kill) to instead check that the process is still running by
testing child.exitCode === null && child.signalCode === null before calling
child.kill('SIGKILL'), ensuring the SIGKILL fallback executes only when the
process hasn't actually exited.
- Around line 216-229: The glob-to-regex conversion in the block that builds
regexStr (using normalizedPattern, placeholders '{{GLOBSTAR}}' and '{{STAR}}',
and then producing regex/test against normalizedFile) only escapes dots and
leaves other regex metacharacters unescaped; update the logic to first
substitute ** and * with the existing placeholders, then escape all regex
metacharacters (+ ? ( ) [ ] { } | ^ $ . \ and similar) in regexStr, and finally
restore the placeholders to their intended regex fragments ('.*' and '[^/]*'),
then apply the existing anchoring and new RegExp; ensure you modify the code
surrounding regexStr replacement and placeholder handling rather than the
regex.test call.

In `@src/core/project-config.ts`:
- Around line 170-190: The current code validates the entire raw.checks array
with checksField.safeParse which fails the whole array if any single entry is
malformed; change to validate entries individually so valid checks are
preserved: define the check schema (e.g., checkSchema = z.object({name:...,
command:..., files:...})), ensure raw.checks is an array, then iterate
raw.checks and safeParse each item (or use checkSchema.safeParse) to collect
only the successfully parsed checks into config.checks, emit a warning for each
invalid item (instead of discarding the whole array), and keep references to
raw.checks, checksField/checkSchema, checksResult, and config.checks to locate
the logic to update.

---

Nitpick comments:
In `@src/commands/check.ts`:
- Around line 94-106: The code appends per-completion into results causing
nondeterministic ordering; instead initialize results to the same length as
checks and assign the result object at results[currentIndex] (use
results[currentIndex] = { name: checks[currentIndex]?.name ?? 'unknown', passed:
false, ... }) rather than results.push(...), and do the same replacement for the
other catch block handling failures; ensure results is pre-sized (e.g., results
= new Array(checks.length)) so later consumers see deterministic ordering by
check index.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: b6871df5-afd1-4cdf-aa55-6de43734963e

📥 Commits

Reviewing files that changed from the base of the PR and between 8498042 and 37b29b8.

📒 Files selected for processing (11)
  • openspec/config.yaml
  • src/cli/index.ts
  • src/commands/check.ts
  • src/core/code-checker/detector.ts
  • src/core/code-checker/index.ts
  • src/core/code-checker/runner.ts
  • src/core/code-checker/types.ts
  • src/core/project-config.ts
  • test/commands/check.test.ts
  • test/core/code-checker/detector.test.ts
  • test/core/code-checker/runner.test.ts

Comment thread src/core/code-checker/runner.ts
Comment thread src/core/code-checker/runner.ts Outdated
Comment thread src/core/project-config.ts Outdated
- Add src/core/code-checker/ module with runner, detector, and types
- Add openspec check CLI command for lint/type-check/static analysis
- Extend project config schema with checks array
- Auto-detect checks for TS/JS, Rust, Python, Go, Ruby, Java
- Support Java offline tools: Checkstyle, SpotBugs, PMD (Maven/Gradle)
- Add file glob filtering, JSON output, concurrency control
- Add comprehensive unit and E2E tests
- Update dogfood config.yaml
Copy link
Copy Markdown
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.

🧹 Nitpick comments (1)
test/core/code-checker/detector.test.ts (1)

23-30: ⚡ Quick win

Add coverage for Prettier in the TypeScript/JavaScript detection test.

This test currently validates only the TypeScript check, but the feature contract includes Prettier auto-detection too. Add a Prettier assertion here to protect against regressions in that path.

Suggested test addition
   const result = detectChecks(testDir);
   expect(result).not.toBeNull();
   expect(result!.detected.some((c) => c.name === 'TypeScript type check')).toBe(true);
+  expect(result!.detected.some((c) => /prettier/i.test(c.name))).toBe(true);
🤖 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 `@test/core/code-checker/detector.test.ts` around lines 23 - 30, The test
"detects TypeScript checks when package.json + tsconfig.json exist" calls
detectChecks(testDir) but only asserts TypeScript detection; add an assertion
that the Prettier check is also detected by verifying result!.detected contains
an entry with name "Prettier" (e.g. expect(result!.detected.some(c => c.name ===
'Prettier')).toBe(true)). Update the same test (function name detectChecks) to
include this Prettier assertion immediately after the existing TypeScript
assertion so the JS/TS Prettier auto-detection path is covered.
🤖 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.

Nitpick comments:
In `@test/core/code-checker/detector.test.ts`:
- Around line 23-30: The test "detects TypeScript checks when package.json +
tsconfig.json exist" calls detectChecks(testDir) but only asserts TypeScript
detection; add an assertion that the Prettier check is also detected by
verifying result!.detected contains an entry with name "Prettier" (e.g.
expect(result!.detected.some(c => c.name === 'Prettier')).toBe(true)). Update
the same test (function name detectChecks) to include this Prettier assertion
immediately after the existing TypeScript assertion so the JS/TS Prettier
auto-detection path is covered.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 5d45afe1-3e5f-487a-a6b2-a9ebc220af67

📥 Commits

Reviewing files that changed from the base of the PR and between 37b29b8 and 4dfef10.

📒 Files selected for processing (11)
  • openspec/config.yaml
  • src/cli/index.ts
  • src/commands/check.ts
  • src/core/code-checker/detector.ts
  • src/core/code-checker/index.ts
  • src/core/code-checker/runner.ts
  • src/core/code-checker/types.ts
  • src/core/project-config.ts
  • test/commands/check.test.ts
  • test/core/code-checker/detector.test.ts
  • test/core/code-checker/runner.test.ts
✅ Files skipped from review due to trivial changes (2)
  • src/core/code-checker/index.ts
  • src/core/code-checker/types.ts
🚧 Files skipped from review as they are similar to previous changes (7)
  • src/cli/index.ts
  • openspec/config.yaml
  • src/commands/check.ts
  • src/core/code-checker/detector.ts
  • test/core/code-checker/runner.test.ts
  • test/commands/check.test.ts
  • src/core/project-config.ts

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