feat: spec driven development features, docs fix, and articles#321
feat: spec driven development features, docs fix, and articles#321rubenmarcus merged 1 commit intomainfrom
Conversation
- 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>
Issue Linking ReminderThis PR doesn't appear to have a linked issue. Consider linking to:
Using If this PR doesn't need an issue, you can ignore this message. |
✔️ Bundle Size Analysis
Bundle breakdown |
🔗 Docs PreviewPreview URL: https://fix-docs-broken-link-and-sdd.ralph-starter-docs.pages.dev This preview was deployed from the latest commit on this PR. |
There was a problem hiding this comment.
💡 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".
| // Validate spec completeness if requested | ||
| if (options.specValidate && finalTask) { | ||
| const { formatValidationResult, validateSpec } = await import('../loop/spec-validator.js'); | ||
| const result = validateSpec(finalTask); |
There was a problem hiding this comment.
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 👍 / 👎.
| 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; |
There was a problem hiding this comment.
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 👍 / 👎.
| const stat = statSync(specDir); | ||
| if (stat.isFile()) { | ||
| const content = readFileSync(specDir, 'utf-8'); |
There was a problem hiding this comment.
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 👍 / 👎.
| } | ||
| } | ||
|
|
||
| // 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 |
There was a problem hiding this comment.
🔴 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
- User runs:
ralph-starter run --from openspec:thin-feature --spec-validate fetchFromSourcereturnssourceSpec="## Proposal\nFix the bug."(25 chars, no RFC 2119, no design, no tasks).extractTasksFromSpecfinds no tasks →extractedPlanis null.finalTaskis assembled with the no-extractedPlan template (line 1148), which includes## Implementation Trackingand- [ ] Subtask a/- [ ] Subtask bboilerplate totalling ~600+ chars.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.- Validation passes; the agent loop starts and spends tokens on an underspecified task.
- Correct behavior:
validateSpec(sourceSpec)scores +20 (Proposal) + partial RFC credit = 30/100 → valid = false, loop is stopped.
| 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 { |
There was a problem hiding this comment.
🔴 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 return — detectSpecFormat 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:
- Line 58:
const format = detectSpecFormat(cwd)always passescwd, neverspecDir, so format detection ignores the user-specified directory regardless. - Lines 60-70: the if-block only handles
stat.isFile() === truewith an early return. Whenstat.isFile()is false (i.e., the path is a directory), the block falls through with noreturnand no use ofspecDir. Execution falls into theif (format === 'openspec') { validateOpenSpec(cwd) }chain, which was detected againstcwdand then validatescwd.
Concrete step-by-step proof
Scenario: user runs ralph-starter spec validate --path ./my-specs-dir from /project.
specDir = join('/project', './my-specs-dir')produces'/project/my-specs-dir'(path.join works for relative paths)format = detectSpecFormat('/project')reads from cwd, not specDirexistsSync('/project/my-specs-dir')returns truestatSync('/project/my-specs-dir').isFile()returns false (it is a directory)- The if-block exits without return and without using specDir
validateOpenSpec('/project')(or speckit/raw equivalent) is called with cwd- User sees validation output for
/project, with no indication that./my-specs-dirwas 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.
| --- | ||
| 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 | ||
| --- | ||
|
|
There was a problem hiding this comment.
🟡 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
- Open
docs/blog/2026-04-08-spec-driven-development-ralph-starter.md, line 7:image: /img/blog/sdd-ralph-starter.png - Check
docs/static/img/blog/: files present areai-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— nosdd-ralph-starter.png. - Build docs → succeeds with no hard error (warn-only config).
- Open the published blog post and view page source →
<meta property="og:image" content=".../img/blog/sdd-ralph-starter.png" />points to a 404. - Paste the URL into the Twitter Card Validator or LinkedIn Post Inspector → broken/missing thumbnail.
Greptile SummaryThis PR adds spec-driven development support to ralph-starter: a
Confidence Score: 4/5Safe 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).
|
| 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]
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
| 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; | ||
| } | ||
| } |
There was a problem hiding this 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/.
| 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.| interface SpecCommandOptions { | ||
| path?: string; | ||
| } |
There was a problem hiding this 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.
| 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!
| 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)` | ||
| ); | ||
| } | ||
| } |
There was a problem hiding this 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.
| 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.
Summary
/docs/cli/figmalink in HeroSection (root cause of all docs CI failures)--spec-validateflagralph-starter speccommand (validate, list, summary) for SDD workflowdocs/docs/cli/figma.mdCLI docs pageNew Features
ralph-starter spec validate-- Validates spec completeness against RFC 2119 keywords, acceptance criteria, design sectionsralph-starter spec list-- Lists specs from OpenSpec, Spec-Kit, or raw markdownralph-starter spec summary-- Shows completeness metrics--spec-validateflag onrun-- Stops the loop if spec scores below 40/100Articles
content/linkedin-sdd-ralph-starter-pt-br.md-- PT-BR LinkedIn article on SDDcontent/twitter-thread-sdd-ralph-starter.md-- English Twitter/X threaddocs/blog/2026-04-08-spec-driven-development-ralph-starter.md-- English blog postTest plan
ralph-starter spec validateon sample project🤖 Generated with Claude Code