Skip to content

feat: spec driven development features, docs fix, and articles#321

Merged
rubenmarcus merged 1 commit intomainfrom
fix/docs-broken-link-and-sdd-features
Apr 8, 2026
Merged

feat: spec driven development features, docs fix, and articles#321
rubenmarcus merged 1 commit intomainfrom
fix/docs-broken-link-and-sdd-features

Conversation

@rubenmarcus
Copy link
Copy Markdown
Member

Summary

  • Fix broken /docs/cli/figma link in HeroSection (root cause of all docs CI failures)
  • Add spec-validator with completeness scoring (0-100) and --spec-validate flag
  • Add ralph-starter spec command (validate, list, summary) for SDD workflow
  • Create docs/docs/cli/figma.md CLI docs page
  • Add SDD blog post (English), LinkedIn article (Brazilian Portuguese), Twitter/X thread (English)

New Features

  • ralph-starter spec validate -- Validates spec completeness against RFC 2119 keywords, acceptance criteria, design sections
  • ralph-starter spec list -- Lists specs from OpenSpec, Spec-Kit, or raw markdown
  • ralph-starter spec summary -- Shows completeness metrics
  • --spec-validate flag on run -- Stops the loop if spec scores below 40/100

Articles

  • content/linkedin-sdd-ralph-starter-pt-br.md -- PT-BR LinkedIn article on SDD
  • content/twitter-thread-sdd-ralph-starter.md -- English Twitter/X thread
  • docs/blog/2026-04-08-spec-driven-development-ralph-starter.md -- English blog post

Test plan

  • 9 spec-validator tests pass
  • 13 OpenSpec source tests pass
  • Biome lint passes (no new errors)
  • Docs build passes (broken link fixed)
  • Manual: ralph-starter spec validate on sample project

🤖 Generated with Claude Code

- Fix broken /docs/cli/figma link in HeroSection (unblocks all docs CI)
- Add spec-validator with completeness scoring (0-100)
- Add --spec-validate flag to run command
- Add ralph-starter spec command (validate, list, summary)
- Create docs/docs/cli/figma.md
- Add SDD blog post (EN), LinkedIn article (PT-BR), Twitter thread (EN)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Apr 8, 2026

Issue Linking Reminder

This PR doesn't appear to have a linked issue. Consider linking to:

  • This repo: Closes #123
  • ralph-ideas: Closes multivmlabs/ralph-ideas#123

Using Closes, Fixes, or Resolves will auto-close the issue when this PR is merged.


If this PR doesn't need an issue, you can ignore this message.

@github-actions github-actions Bot added candidate-release PR is ready for release core tests labels Apr 8, 2026
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Apr 8, 2026

✔️ Bundle Size Analysis

Metric Value
Base 2746.81 KB
PR 2787.09 KB
Diff 40.28 KB (1.00%)
Bundle breakdown
156K	dist/auth
80K	dist/automation
4.0K	dist/cli.d.ts
4.0K	dist/cli.d.ts.map
24K	dist/cli.js
16K	dist/cli.js.map
700K	dist/commands
28K	dist/config
4.0K	dist/index.d.ts
4.0K	dist/index.d.ts.map
4.0K	dist/index.js
4.0K	dist/index.js.map
916K	dist/integrations
100K	dist/llm
1.3M	dist/loop
188K	dist/mcp
60K	dist/presets
92K	dist/setup
40K	dist/skills
392K	dist/sources
76K	dist/ui
144K	dist/utils
336K	dist/wizard

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Apr 8, 2026

🔗 Docs Preview

Preview URL: https://fix-docs-broken-link-and-sdd.ralph-starter-docs.pages.dev

This preview was deployed from the latest commit on this PR.

Comment thread src/commands/spec.ts
@rubenmarcus rubenmarcus merged commit f4975f8 into main Apr 8, 2026
15 of 16 checks passed
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: dc5e516151

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/commands/run.ts
// Validate spec completeness if requested
if (options.specValidate && finalTask) {
const { formatValidationResult, validateSpec } = await import('../loop/spec-validator.js');
const result = validateSpec(finalTask);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Validate source spec, not the synthesized run prompt

This check runs validateSpec(finalTask), but finalTask already includes boilerplate sections added by runCommand (e.g., checklist scaffolding and uppercase requirement words), so weak source specs can still score above the 40-point gate. In --from flows this makes --spec-validate largely ineffective because it measures the generated instruction wrapper rather than the original spec content that should be quality-gated.

Useful? React with 👍 / 👎.

Comment thread src/commands/run.ts
Comment on lines +1442 to +1446
console.log(chalk.yellow('Spec validation failed. The spec may not have enough detail.'));
console.log(
chalk.dim('Run without --spec-validate to skip this check, or improve the spec.')
);
return;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Exit with failure when spec validation blocks execution

When --spec-validate fails, the command prints warnings and returns without setting a non-zero exit code. In automation/headless usage this is reported as success even though the requested gate failed and no loop ran, which can let pipelines proceed as if validation passed. This branch should signal failure explicitly (e.g., throw or set a failing exit code).

Useful? React with 👍 / 👎.

Comment thread src/commands/spec.ts
Comment on lines +62 to +64
const stat = statSync(specDir);
if (stat.isFile()) {
const content = readFileSync(specDir, 'utf-8');
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Honor directory targets for spec validate --path

The option text says --path accepts a file or directory, but this branch only handles stat.isFile() and otherwise falls back to validating the auto-detected project format from cwd. Passing a directory therefore ignores the user-selected target and can validate unrelated specs, which is misleading for scoped validation workflows.

Useful? React with 👍 / 👎.

Comment thread src/commands/run.ts
Comment on lines 1430 to 1450
}
}

// Validate spec completeness if requested
if (options.specValidate && finalTask) {
const { formatValidationResult, validateSpec } = await import('../loop/spec-validator.js');
const result = validateSpec(finalTask);
console.log(chalk.dim(` Spec validation: ${result.score}/100`));
if (!result.valid) {
console.log();
console.log(formatValidationResult(result));
console.log();
console.log(chalk.yellow('Spec validation failed. The spec may not have enough detail.'));
console.log(
chalk.dim('Run without --spec-validate to skip this check, or improve the spec.')
);
return;
}
}

// Extract acceptance criteria if requested
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔴 When --spec-validate is used with --from, spec validation runs on the already-augmented finalTask (which includes ## Implementation Tracking boilerplate and extracted - [ ] checklists) instead of the raw sourceSpec. This means a minimal spec with only a Proposal section (true score: ~20/100) can score 45–55/100 and falsely pass the 40-point gate, defeating the feature's entire purpose. Fix: call validateSpec(sourceSpec) instead of validateSpec(finalTask) when sourceSpec is available.

Extended reasoning...

What the bug is and how it manifests

The --spec-validate flag is documented (in the blog post, LinkedIn article, and Twitter thread) as gating loop execution on raw spec quality before tokens are spent. When used with --from, however, validateSpec() is called on finalTask after it has been assembled from the raw spec plus large boilerplate injected by the source-fetching code path (lines 1117–1148 of src/commands/run.ts). The result is that the validator scores the combined assembled prompt, not the user's actual spec.

The specific code path

After fetching from a source (e.g. --from openspec:my-feature), three assembly paths all prepend an ## Implementation Tracking header to finalTask (lines 1117, 1135, and 1148). In the no-extractedPlan branch, the template also includes literal - [ ] Subtask a and - [ ] Subtask b example lines. The spec-validate block at line 1433 then calls validateSpec(finalTask), scoring this augmented string.

Why existing code doesn't prevent it

There is no guard that checks whether sourceSpec is available. The validator simply receives whatever string is passed to it and scores it faithfully. The boilerplate was added to guide the agent, not to represent spec quality—but the validator cannot distinguish the two.

What the impact would be

A concrete proof: a user writes a minimal OpenSpec change with only a proposal.md (20–30 words, no RFC 2119 keywords, no design section, no tasks). Raw score components: +20 (Proposal section), +0 RFC 2119 (no keywords, but >200 chars → +10 partial), total ≈ 30/100 → FAIL. After assembly into finalTask: ## Implementation Tracking matches the design regex (/^#+\s*(design|architecture|technical|approach|implementation|how)/im) due to the implementation keyword → +15. The combined string exceeds 500 chars → +10. If no extractedPlan, the example - [ ] Subtask a lines match the hasTasks check → +15. Total: 30+15+10+15 = 70/100 → PASS. The feature that is supposed to stop the loop for thin specs silently lets a thin spec through and wastes tokens—the exact opposite of the documented behavior.

How to fix it

At the spec-validate block (~line 1430), check whether sourceSpec is non-null; if so, call validateSpec(sourceSpec) instead of validateSpec(finalTask). This validates the raw user-authored content before any boilerplate is added, which matches the documented intent: "Stops the loop if spec scores below 40/100." Ideally, the validation should also occur before finalTask assembly and before the IMPLEMENTATION_PLAN.md is written, to avoid any side effects from partial execution on a spec that will ultimately be rejected.

Step-by-step proof

  1. User runs: ralph-starter run --from openspec:thin-feature --spec-validate
  2. fetchFromSource returns sourceSpec = "## Proposal\nFix the bug." (25 chars, no RFC 2119, no design, no tasks).
  3. extractTasksFromSpec finds no tasks → extractedPlan is null.
  4. finalTask is assembled with the no-extractedPlan template (line 1148), which includes ## Implementation Tracking and - [ ] Subtask a / - [ ] Subtask b boilerplate totalling ~600+ chars.
  5. validateSpec(finalTask) runs: +20 (Proposal match), +10 (>200 chars partial RFC credit), +15 (Implementation Tracking matches design regex), +15 (example - [ ] lines match hasTasks), +10 (>500 chars) = 70/100 → valid = true.
  6. Validation passes; the agent loop starts and spends tokens on an underspecified task.
  7. Correct behavior: validateSpec(sourceSpec) scores +20 (Proposal) + partial RFC credit = 30/100 → valid = false, loop is stopped.

Comment thread src/commands/spec.ts
Comment on lines +55 to +78
console.log();

const specDir = options.path ? join(cwd, options.path) : null;
const format = detectSpecFormat(cwd);

if (specDir && existsSync(specDir)) {
// Validate a specific file or directory
const stat = statSync(specDir);
if (stat.isFile()) {
const content = readFileSync(specDir, 'utf-8');
const result = validateSpec(content);
console.log(chalk.dim(`File: ${options.path}`));
console.log(formatValidationResult(result));
return;
}
}

if (format === 'openspec') {
validateOpenSpec(cwd);
} else if (format === 'speckit') {
validateSpecKit(cwd);
} else if (format === 'raw') {
validateRawSpecs(cwd);
} else {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔴 The --path option in ralph-starter spec validate is silently ignored in two distinct cases. First, path.join(cwd, options.path) does not handle absolute paths — Node's path.join('/project', '/abs/path') returns '/project/abs/path', so absolute --path values produce a nonexistent path, existsSync returns false, and the code falls through to CWD-based detection without error. Second, even for valid relative paths pointing to a directory, stat.isFile() is false so the if-block exits without a returndetectSpecFormat and all three validate* calls then use cwd instead of the user-specified directory. In both cases the command silently validates the wrong location. Fix by using path.resolve(cwd, options.path) instead of path.join, and by passing specDir to detectSpecFormat and the validate* functions when a directory path is given.

Extended reasoning...

Bug overview

The --path option documented as 'Path to spec file or directory' in the new ralph-starter spec validate command is broken in two compounding ways, both introduced in this PR.

Bug 1 — absolute paths: path.join vs path.resolve (src/commands/spec.ts:57)

Node's path.join does not treat a subsequent absolute path as an override; it concatenates literally. path.join('/project', '/home/user/myspec.md') returns '/project/home/user/myspec.md', not '/home/user/myspec.md'. When a user passes --path /home/user/myspec.md, specDir becomes something like /cwd/home/user/myspec.md. That path almost certainly does not exist, existsSync(specDir) returns false, the whole if-block is skipped, and the command silently proceeds to auto-detect the spec format from cwd. No error is shown. The fix is to use path.resolve(cwd, options.path), which correctly handles both relative and absolute inputs.

Bug 2 — directory paths: falls through without using specDir (src/commands/spec.ts:58-77)

Even when --path points to an existing relative directory, the logic is wrong in two sub-ways:

  1. Line 58: const format = detectSpecFormat(cwd) always passes cwd, never specDir, so format detection ignores the user-specified directory regardless.
  2. Lines 60-70: the if-block only handles stat.isFile() === true with an early return. When stat.isFile() is false (i.e., the path is a directory), the block falls through with no return and no use of specDir. Execution falls into the if (format === 'openspec') { validateOpenSpec(cwd) } chain, which was detected against cwd and then validates cwd.

Concrete step-by-step proof

Scenario: user runs ralph-starter spec validate --path ./my-specs-dir from /project.

  1. specDir = join('/project', './my-specs-dir') produces '/project/my-specs-dir' (path.join works for relative paths)
  2. format = detectSpecFormat('/project') reads from cwd, not specDir
  3. existsSync('/project/my-specs-dir') returns true
  4. statSync('/project/my-specs-dir').isFile() returns false (it is a directory)
  5. The if-block exits without return and without using specDir
  6. validateOpenSpec('/project') (or speckit/raw equivalent) is called with cwd
  7. User sees validation output for /project, with no indication that ./my-specs-dir was ignored

Why existing code does not prevent it

The option parsing in src/cli.ts correctly captures the --path value and passes it through. The issue is purely in specValidate(): the missing return after the directory branch, the use of cwd in detectSpecFormat, and the path.join vs path.resolve mismatch.

Impact

Both the file-path (absolute) and directory-path use cases of the --path option are non-functional as shipped. Users relying on this flag will silently get results from their current working directory. Since the option is documented and prominently featured in the blog post and social content in this PR, users are likely to attempt it immediately.

How to fix

Change line 57 to use resolve(cwd, options.path) instead of join(cwd, options.path). Add a directory branch in the if-block that calls detectSpecFormat(specDir) and the appropriate validate* function with specDir, then returns.

Comment on lines +1 to +9
---
slug: spec-driven-development-ralph-starter
title: Spec Driven Development with ralph-starter
authors: [ruben]
tags: [ralph-starter, sdd, openspec, specs, workflow]
description: How ralph-starter brings Spec Driven Development to any AI coding agent, with native OpenSpec support, spec validation, and multi-source spec fetching.
image: /img/blog/sdd-ralph-starter.png
---

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 The blog post references a missing OG image /img/blog/sdd-ralph-starter.png that doesn't exist in docs/static/img/blog/. This will produce a broken og:image meta tag, causing social preview images to fail when the post is shared on Twitter/X, LinkedIn, etc.

Extended reasoning...

What the bug is and how it manifests

The blog post front matter at line 7 contains image: /img/blog/sdd-ralph-starter.png. Docusaurus uses this field to populate the <meta property="og:image"> tag in the generated HTML. When a user shares the blog URL on LinkedIn, Twitter, or any other platform that reads Open Graph tags, the social card will attempt to load the missing image and display a broken or empty preview.

The specific code path that triggers it

Docusaurus reads the image key from the blog post front matter and injects it into the HTML <head> as <meta property="og:image" content="https://your-site.com/img/blog/sdd-ralph-starter.png" />. The static file docs/static/img/blog/sdd-ralph-starter.png is the expected source, but it was never added. The verifier confirmed the directory contains 14 other blog images but not sdd-ralph-starter.png.

Why existing code doesn't prevent it

The Docusaurus config has onBrokenMarkdownImages: 'warn' (not 'throw'), so the build will succeed without surfacing this as a hard error. The front matter image field is not validated against the static file system at build time in the same way broken markdown links are. The PR's own test plan also marks "Docs build passes" as unchecked ([ ]), indicating this area was not fully verified before opening the PR.

What the impact would be

Any share of this blog post URL on social media will render with a broken preview image. Given the PR explicitly adds social content (LinkedIn article, Twitter thread) intended to promote this exact blog post, the broken OG image undercuts the social sharing effort the PR is designed to support.

How to fix it

Add the missing file: create or export a suitable cover image and save it to docs/static/img/blog/sdd-ralph-starter.png. Alternatively, change the image field in the front matter to reference an existing image such as /img/blog/specs-new-code.png or /img/blog/validation-driven-dev.png.

Step-by-step proof

  1. Open docs/blog/2026-04-08-spec-driven-development-ralph-starter.md, line 7: image: /img/blog/sdd-ralph-starter.png
  2. Check docs/static/img/blog/: files present are ai-agents-comparison.png, auto-mode-github.png, claude-code-setup.png, cost-tracking.png, figma-to-code.png, connect-your-tools.png, first-ralph-loop.png, linear-workflow.png, ralph-wiggum-technique.png, validation-driven-dev.png, specs-new-code.png, vs-manual.png, why-autonomous-coding.png, why-i-built-ralph-starter.png — no sdd-ralph-starter.png.
  3. Build docs → succeeds with no hard error (warn-only config).
  4. Open the published blog post and view page source → <meta property="og:image" content=".../img/blog/sdd-ralph-starter.png" /> points to a 404.
  5. Paste the URL into the Twitter Card Validator or LinkedIn Post Inspector → broken/missing thumbnail.

@claude claude Bot mentioned this pull request Apr 8, 2026
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Apr 8, 2026

Greptile Summary

This PR adds spec-driven development support to ralph-starter: a spec-validator module (0–100 completeness scoring), a new ralph-starter spec command (validate, list, summary), a --spec-validate flag on run, and fixes a broken /docs/cli/figma link that was blocking docs CI.

  • P1 (src/commands/spec.ts:60-70): When --path targets a directory, the guard exits only for files; for directories it silently falls through to detectSpecFormat(cwd), meaning the user-supplied path is completely ignored and the project root is validated instead.

Confidence Score: 4/5

Safe to merge after fixing the --path directory fallback bug; all other findings are P2 style/UX issues.

One P1 logic bug: spec validate --path

silently ignores the directory and validates the project root instead, which is actively misleading to users. The core validator, the run --spec-validate integration, and the docs fix are all clean. P2s (interface vs type, empty-dir output) do not block merge.

src/commands/spec.ts — specifically the --path directory handling in specValidate (lines 60–70).

Vulnerabilities

No security concerns identified. File system access is scoped to the project's working directory (cwd). No user input is executed or interpolated into shell commands. The readFileSync calls on spec files are path-joined against trusted constants, not raw user strings.

Important Files Changed

Filename Overview
src/commands/spec.ts New spec command (validate/list/summary) — has a P1 bug where --path <directory> is silently ignored and falls back to cwd detection; also uses interface instead of type per project convention, and validateRawSpecs produces no output for empty directories.
src/loop/spec-validator.ts New spec completeness validator scoring proposals, RFC 2119 keywords, design sections, tasks, and acceptance criteria — logic is clean and score arithmetic is correct (max 100, partial credit handled).
src/commands/run.ts Adds --spec-validate flag that validates finalTask content before starting the loop; integration is clean and correctly short-circuits on failure.
src/cli.ts Registers the new spec <action> command and --spec-validate flag on run — wiring is correct and consistent with other command registrations.
docs/src/components/HeroSection/index.tsx Fixes the broken /docs/cli/figma link that was causing docs CI failures; updated component logic and integration links look correct.
src/loop/tests/spec-validator.test.ts 9 tests covering core validator paths (complete spec, minimal spec, RFC 2119, GWT, tasks, design warnings, empty content) plus formatter tests — good coverage of the happy and sad paths.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[ralph-starter spec validate] --> B{--path provided?}
    B -- yes --> C{path exists?}
    C -- yes --> D{is file?}
    D -- yes --> E[Read & validate single file\nprint result & return]
    D -- no --> F[⚠️ Falls through: path ignored]
    C -- no --> F
    B -- no --> F
    F --> G{detectSpecFormat cwd}
    G -- openspec --> H[validateOpenSpec\nscores each change]
    G -- speckit --> I[validateSpecKit\nscores combined docs]
    G -- raw --> J[validateRawSpecs\nscores each .md]
    G -- null --> K[No specs found message]

    L[ralph-starter run --spec-validate] --> M{finalTask set?}
    M -- yes --> N[validateSpec finalTask\nscores 0-100]
    N --> O{score >= 40?}
    O -- no --> P[Print warnings & return\nloop does NOT start]
    O -- yes --> Q[Continue loop execution]
Loading
Prompt To Fix All With AI
This is a comment left during a code review.
Path: src/commands/spec.ts
Line: 60-70

Comment:
**`--path` directory argument silently falls through to `cwd` detection**

When `--path` points to a directory (not a file), the `if (stat.isFile())` guard fails and execution falls through to `detectSpecFormat(cwd)`, completely ignoring the user-supplied path. Running `ralph-starter spec validate --path my-feature/` will validate the project root instead of `my-feature/`.

```suggestion
  if (specDir && existsSync(specDir)) {
    const stat = statSync(specDir);
    if (stat.isFile()) {
      const content = readFileSync(specDir, 'utf-8');
      const result = validateSpec(content);
      console.log(chalk.dim(`File: ${options.path}`));
      console.log(formatValidationResult(result));
      return;
    }
    // Validate the specified directory directly
    const files = readdirSync(specDir).filter((f) => f.endsWith('.md'));
    if (files.length === 0) {
      console.log(chalk.yellow(`No markdown files found in ${options.path}`));
      return;
    }
    for (const file of files) {
      const content = readFileSync(join(specDir, file), 'utf-8');
      const result = validateSpec(content);
      console.log(`  ${result.valid ? chalk.green('PASS') : chalk.red('FAIL')} ${chalk.bold(file)} (${result.score}/100)`);
    }
    return;
  }
```

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: src/commands/spec.ts
Line: 15-17

Comment:
**Prefer `type` over `interface` for simple data shapes**

`SpecCommandOptions` is a plain data structure with no inheritance or extension — use `type` instead of `interface`.

```suggestion
type SpecCommandOptions = {
  path?: string;
};
```

**Rule Used:** Use `type` by default in TypeScript unless you spe... ([source](https://app.greptile.com/review/custom-context?memory=c862f053-5655-4b41-be69-c840e3c9f280))

**Learnt From**
[cytonic-network/ai-frontend#48](https://github.com/cytonic-network/ai-frontend/pull/48)

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: src/commands/spec.ts
Line: 180-196

Comment:
**`validateRawSpecs` produces no output when `specs/` is empty**

If `specs/` exists but contains no `.md` files, `files.length` is 0, the loop is skipped, and the user sees only the format header with no indication that nothing was validated.

```suggestion
function validateRawSpecs(cwd: string): void {
  const specsDir = join(cwd, 'specs');
  const files = readdirSync(specsDir).filter((f) => f.endsWith('.md'));

  console.log(
    chalk.dim(`Format: Raw markdown (${files.length} file${files.length > 1 ? 's' : ''})`)
  );
  console.log();

  if (files.length === 0) {
    console.log(chalk.yellow('No markdown files found in specs/'));
    return;
  }

  for (const file of files) {
    const content = readFileSync(join(specsDir, file), 'utf-8');
    const result = validateSpec(content);
    console.log(
      `  ${result.valid ? chalk.green('PASS') : chalk.red('FAIL')} ${chalk.bold(file)} (${result.score}/100)`
    );
  }
}
```

How can I resolve this? If you propose a fix, please make it concise.

Reviews (1): Last reviewed commit: "feat: spec driven development features, ..." | Re-trigger Greptile

Comment thread src/commands/spec.ts
Comment on lines +60 to +70
if (specDir && existsSync(specDir)) {
// Validate a specific file or directory
const stat = statSync(specDir);
if (stat.isFile()) {
const content = readFileSync(specDir, 'utf-8');
const result = validateSpec(content);
console.log(chalk.dim(`File: ${options.path}`));
console.log(formatValidationResult(result));
return;
}
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1 --path directory argument silently falls through to cwd detection

When --path points to a directory (not a file), the if (stat.isFile()) guard fails and execution falls through to detectSpecFormat(cwd), completely ignoring the user-supplied path. Running ralph-starter spec validate --path my-feature/ will validate the project root instead of my-feature/.

Suggested change
if (specDir && existsSync(specDir)) {
// Validate a specific file or directory
const stat = statSync(specDir);
if (stat.isFile()) {
const content = readFileSync(specDir, 'utf-8');
const result = validateSpec(content);
console.log(chalk.dim(`File: ${options.path}`));
console.log(formatValidationResult(result));
return;
}
}
if (specDir && existsSync(specDir)) {
const stat = statSync(specDir);
if (stat.isFile()) {
const content = readFileSync(specDir, 'utf-8');
const result = validateSpec(content);
console.log(chalk.dim(`File: ${options.path}`));
console.log(formatValidationResult(result));
return;
}
// Validate the specified directory directly
const files = readdirSync(specDir).filter((f) => f.endsWith('.md'));
if (files.length === 0) {
console.log(chalk.yellow(`No markdown files found in ${options.path}`));
return;
}
for (const file of files) {
const content = readFileSync(join(specDir, file), 'utf-8');
const result = validateSpec(content);
console.log(` ${result.valid ? chalk.green('PASS') : chalk.red('FAIL')} ${chalk.bold(file)} (${result.score}/100)`);
}
return;
}
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/commands/spec.ts
Line: 60-70

Comment:
**`--path` directory argument silently falls through to `cwd` detection**

When `--path` points to a directory (not a file), the `if (stat.isFile())` guard fails and execution falls through to `detectSpecFormat(cwd)`, completely ignoring the user-supplied path. Running `ralph-starter spec validate --path my-feature/` will validate the project root instead of `my-feature/`.

```suggestion
  if (specDir && existsSync(specDir)) {
    const stat = statSync(specDir);
    if (stat.isFile()) {
      const content = readFileSync(specDir, 'utf-8');
      const result = validateSpec(content);
      console.log(chalk.dim(`File: ${options.path}`));
      console.log(formatValidationResult(result));
      return;
    }
    // Validate the specified directory directly
    const files = readdirSync(specDir).filter((f) => f.endsWith('.md'));
    if (files.length === 0) {
      console.log(chalk.yellow(`No markdown files found in ${options.path}`));
      return;
    }
    for (const file of files) {
      const content = readFileSync(join(specDir, file), 'utf-8');
      const result = validateSpec(content);
      console.log(`  ${result.valid ? chalk.green('PASS') : chalk.red('FAIL')} ${chalk.bold(file)} (${result.score}/100)`);
    }
    return;
  }
```

How can I resolve this? If you propose a fix, please make it concise.

Comment thread src/commands/spec.ts
Comment on lines +15 to +17
interface SpecCommandOptions {
path?: string;
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Prefer type over interface for simple data shapes

SpecCommandOptions is a plain data structure with no inheritance or extension — use type instead of interface.

Suggested change
interface SpecCommandOptions {
path?: string;
}
type SpecCommandOptions = {
path?: string;
};

Rule Used: Use type by default in TypeScript unless you spe... (source)

Learnt From
cytonic-network/ai-frontend#48

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/commands/spec.ts
Line: 15-17

Comment:
**Prefer `type` over `interface` for simple data shapes**

`SpecCommandOptions` is a plain data structure with no inheritance or extension — use `type` instead of `interface`.

```suggestion
type SpecCommandOptions = {
  path?: string;
};
```

**Rule Used:** Use `type` by default in TypeScript unless you spe... ([source](https://app.greptile.com/review/custom-context?memory=c862f053-5655-4b41-be69-c840e3c9f280))

**Learnt From**
[cytonic-network/ai-frontend#48](https://github.com/cytonic-network/ai-frontend/pull/48)

How can I resolve this? If you propose a fix, please make it concise.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Comment thread src/commands/spec.ts
Comment on lines +180 to +196
function validateRawSpecs(cwd: string): void {
const specsDir = join(cwd, 'specs');
const files = readdirSync(specsDir).filter((f) => f.endsWith('.md'));

console.log(
chalk.dim(`Format: Raw markdown (${files.length} file${files.length > 1 ? 's' : ''})`)
);
console.log();

for (const file of files) {
const content = readFileSync(join(specsDir, file), 'utf-8');
const result = validateSpec(content);
console.log(
` ${result.valid ? chalk.green('PASS') : chalk.red('FAIL')} ${chalk.bold(file)} (${result.score}/100)`
);
}
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 validateRawSpecs produces no output when specs/ is empty

If specs/ exists but contains no .md files, files.length is 0, the loop is skipped, and the user sees only the format header with no indication that nothing was validated.

Suggested change
function validateRawSpecs(cwd: string): void {
const specsDir = join(cwd, 'specs');
const files = readdirSync(specsDir).filter((f) => f.endsWith('.md'));
console.log(
chalk.dim(`Format: Raw markdown (${files.length} file${files.length > 1 ? 's' : ''})`)
);
console.log();
for (const file of files) {
const content = readFileSync(join(specsDir, file), 'utf-8');
const result = validateSpec(content);
console.log(
` ${result.valid ? chalk.green('PASS') : chalk.red('FAIL')} ${chalk.bold(file)} (${result.score}/100)`
);
}
}
function validateRawSpecs(cwd: string): void {
const specsDir = join(cwd, 'specs');
const files = readdirSync(specsDir).filter((f) => f.endsWith('.md'));
console.log(
chalk.dim(`Format: Raw markdown (${files.length} file${files.length > 1 ? 's' : ''})`)
);
console.log();
if (files.length === 0) {
console.log(chalk.yellow('No markdown files found in specs/'));
return;
}
for (const file of files) {
const content = readFileSync(join(specsDir, file), 'utf-8');
const result = validateSpec(content);
console.log(
` ${result.valid ? chalk.green('PASS') : chalk.red('FAIL')} ${chalk.bold(file)} (${result.score}/100)`
);
}
}
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/commands/spec.ts
Line: 180-196

Comment:
**`validateRawSpecs` produces no output when `specs/` is empty**

If `specs/` exists but contains no `.md` files, `files.length` is 0, the loop is skipped, and the user sees only the format header with no indication that nothing was validated.

```suggestion
function validateRawSpecs(cwd: string): void {
  const specsDir = join(cwd, 'specs');
  const files = readdirSync(specsDir).filter((f) => f.endsWith('.md'));

  console.log(
    chalk.dim(`Format: Raw markdown (${files.length} file${files.length > 1 ? 's' : ''})`)
  );
  console.log();

  if (files.length === 0) {
    console.log(chalk.yellow('No markdown files found in specs/'));
    return;
  }

  for (const file of files) {
    const content = readFileSync(join(specsDir, file), 'utf-8');
    const result = validateSpec(content);
    console.log(
      `  ${result.valid ? chalk.green('PASS') : chalk.red('FAIL')} ${chalk.bold(file)} (${result.score}/100)`
    );
  }
}
```

How can I resolve this? If you propose a fix, please make it concise.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants