Skip to content

fix(validator): hint when SHALL/MUST appears only in requirement header#1135

Open
Pluviobyte wants to merge 1 commit into
Fission-AI:mainfrom
Pluviobyte:fix/validator-shall-must-header-only
Open

fix(validator): hint when SHALL/MUST appears only in requirement header#1135
Pluviobyte wants to merge 1 commit into
Fission-AI:mainfrom
Pluviobyte:fix/validator-shall-must-header-only

Conversation

@Pluviobyte
Copy link
Copy Markdown

@Pluviobyte Pluviobyte commented May 28, 2026

Summary

Fixes #356.

When a change delta contains a requirement whose body is missing SHALL/MUST but whose header (the text after ### Requirement:) already contains the keyword, the validator currently emits the generic error:

✗ [ERROR] agent-management/spec.md: MODIFIED "系统 SHALL 支持为 Box 智能体配置 Prompt Caching 开关" must contain SHALL or MUST

Authors then re-read the spec, see SHALL right there in the header, and have no idea what the validator wants. The reporter of #356 hit exactly this case with an LLM-generated spec where the keyword lived only in the heading.

Per the OpenSpec conventions (and openspec/specs/openspec-conventions/spec.md, Structured Format for Behavioral Specs), the SHALL/MUST statement has to live on the requirement body line, i.e. the line immediately after the header. When the keyword is present in the header only, this PR appends a one-sentence hint that points the author at that exact fix:

✗ [ERROR] agent-management/spec.md: MODIFIED "系统 SHALL 支持为 Box 智能体配置 Prompt Caching 开关" must contain SHALL or MUST in the requirement body, not only in the header. Move the SHALL/MUST statement to the line immediately after the "### Requirement: ..." header.

The general "keyword missing everywhere" case keeps the original short message — no behaviour change other than the diagnostic for the header-only situation.

Scope

Two-line touch in src/core/validation/validator.ts (the validateChangeDeltaSpecs ADDED + MODIFIED branches) plus a small helper that picks the message based on whether block.name itself contains SHALL/MUST. RequirementSchema (used by validateSpec) is intentionally untouched — that path already promotes body text to the requirement string and reports its own error; this PR is limited to the change-delta diagnostic the bug report actually triggers.

File Change
src/core/validation/validator.ts +22 -2 — new private buildMissingShallOrMustMessage('ADDED' | 'MODIFIED', name) helper; both error sites delegate to it
test/core/validation.test.ts +86 -2 — three new vitest cases inside Validator > validateChangeDeltaSpecs with metadata

Diff (key sites)

src/core/validation/validator.ts:

-          } else if (!this.containsShallOrMust(requirementText)) {
-            issues.push({ level: 'ERROR', path: entryPath, message: \`ADDED \"\${block.name}\" must contain SHALL or MUST\` });
-          }
+          } else if (!this.containsShallOrMust(requirementText)) {
+            issues.push({ level: 'ERROR', path: entryPath, message: this.buildMissingShallOrMustMessage('ADDED', block.name) });
+          }
-          } else if (!this.containsShallOrMust(requirementText)) {
-            issues.push({ level: 'ERROR', path: entryPath, message: \`MODIFIED \"\${block.name}\" must contain SHALL or MUST\` });
-          }
+          } else if (!this.containsShallOrMust(requirementText)) {
+            issues.push({ level: 'ERROR', path: entryPath, message: this.buildMissingShallOrMustMessage('MODIFIED', block.name) });
+          }
+  private buildMissingShallOrMustMessage(action: 'ADDED' | 'MODIFIED', blockName: string): string {
+    const base = \`\${action} \"\${blockName}\" must contain SHALL or MUST\`;
+    if (this.containsShallOrMust(blockName)) {
+      return \`\${base} in the requirement body, not only in the header. Move the SHALL/MUST statement to the line immediately after the \"### Requirement: ...\" header.\`;
+    }
+    return base;
+  }

Behaviour matrix

Case Header has SHALL/MUST Body has SHALL/MUST Validator result
Original convention (recommended) optional yes ✅ valid
Bug report from #356 yes no ❌ ERROR with new header-only hint pointing at the fix
Keyword missing everywhere no no ❌ ERROR with original generic message (unchanged)
Body literally empty n/a n/a ❌ existing "missing requirement text" error (unchanged)

Reproduction & verification

Reproduced #356 directly with the spec from the bug report against the rebuilt CLI:

$ cat openspec/changes/test-356/specs/agent-management/spec.md
## MODIFIED Requirements

### Requirement:  系统 SHALL 支持为 Box 智能体配置 Prompt Caching 开关
HOW TO DO ? NO CMD(S H A L L /  M U S T) HERE

#### Scenario: Toggle is enabled
- **WHEN** the user enables the toggle
- **THEN** subsequent calls reuse cached prompts

$ openspec validate test-356
Change 'test-356' has issues
✗ [ERROR] agent-management/spec.md: MODIFIED "系统 SHALL 支持为 Box 智能体配置 Prompt Caching 开关" must contain SHALL or MUST in the requirement body, not only in the header. Move the SHALL/MUST statement to the line immediately after the "### Requirement: ..." header.

Author follows the hint and rewrites the body:

$ openspec validate test-356
Change 'test-356' is valid

Generic case (no SHALL/MUST anywhere) keeps the short message:

$ openspec validate test-356
✗ [ERROR] agent-management/spec.md: ADDED "Logging Feature" must contain SHALL or MUST

Regression evidence

Tests genuinely cover the new branch — git stash the validator.ts change, rerun:

FAIL  test/core/validation.test.ts > should hint the author when ADDED requirement only has SHALL/MUST in the header
AssertionError: expected '...must contain SHALL or MUST' to contain 'not only in the header'

FAIL  test/core/validation.test.ts > should hint the author when MODIFIED requirement only has SHALL/MUST in the header
AssertionError: expected '...must contain SHALL or MUST' to contain 'not only in the header'

git stash pop and both pass again.

Local CI

$ pnpm run build
✅ Build completed successfully!

$ pnpm run lint
$ eslint src/
(no output)

$ pnpm test
 Test Files  89 passed (89)
      Tests  1637 passed (1637)

(The previously flaky change-initiative-link.test.ts > sets and surfaces initiative links ... 10s timeout happened on the very first baseline run before any of my changes and did not reproduce on subsequent runs.)

Notes

  • AI-generated code: this change was written with Cursor using Claude Opus 4.7 (claude-4.6-opus-max-thinking). All reasoning, diffs, tests, repro runs, and the PR body were reviewed by me before submission.
  • No new dependencies, no public API changes, no schema or template changes.
  • Conventional commit, single-line subject per README.md Contributing.

Made with Cursor

Summary by CodeRabbit

  • Bug Fixes

    • Improved validation error messages for requirements lacking SHALL/MUST keywords, providing more specific guidance when these keywords appear only in requirement headers rather than bodies.
  • Tests

    • Added validation test cases to verify improved error messaging scenarios for header-only and missing SHALL/MUST keywords.

Review Change Stack

When a change delta has a requirement whose body is missing SHALL/MUST but
whose header (the text after `### Requirement:`) already contains the
keyword, the validator emitted the generic error "must contain SHALL or
MUST". Authors then re-read the spec, see SHALL right there in the header,
and have no idea what the validator wants.

Per the OpenSpec conventions the keyword has to live on the requirement
body line (the line immediately after the header). When the keyword is
present in the header only, append guidance explaining exactly where to
move it. The fix is scoped to the two `validateChangeDeltaSpecs` call
sites (ADDED + MODIFIED) so behaviour for requirements that lack the
keyword everywhere stays unchanged.

Adds three vitest cases under `test/core/validation.test.ts`:
- ADDED block with header-only SHALL → enriched hint
- MODIFIED block with header-only MUST → enriched hint
- Neither header nor body contain SHALL/MUST → generic message preserved

Verified by reproducing the spec from Fission-AI#356, running
`openspec validate <change>` against the rebuilt CLI, and confirming the
new diagnostic guides the author to the fix. Reverting `validator.ts`
makes the two enriched-hint cases fail, so the tests guard the
regression.

Fixes Fission-AI#356

Co-authored-by: Cursor <cursoragent@cursor.com>
@Pluviobyte Pluviobyte requested a review from TabishB as a code owner May 28, 2026 09:11
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 28, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 451ed2f0-296c-492c-a475-3588fd0ae46d

📥 Commits

Reviewing files that changed from the base of the PR and between 11b2690 and bb57212.

📒 Files selected for processing (2)
  • src/core/validation/validator.ts
  • test/core/validation.test.ts

📝 Walkthrough

Walkthrough

The PR refines delta-spec validation error messages to detect when SHALL/MUST keywords appear only in requirement headers and provide helpful guidance to users. A new helper function conditionally selects either a generic error or a header-specific message, replacing fixed templates in ADDED and MODIFIED requirement validation paths. Three test cases validate the improved messaging behavior.

Changes

SHALL/MUST validation error messaging improvement

Layer / File(s) Summary
Conditional error message helper and integration
src/core/validation/validator.ts
New buildMissingShallOrMustMessage() helper examines the requirement block name to detect SHALL/MUST in headers and switches between generic and specific guidance messages. ADDED and MODIFIED validation paths now delegate to this helper instead of using fixed templates.
Test coverage for header-only SHALL/MUST detection
test/core/validation.test.ts
Three new Vitest cases verify validation failure and message content when SHALL/MUST appears only in headers for both ADDED and MODIFIED requirements, and confirm generic error messaging when SHALL/MUST is absent from both header and body.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Suggested reviewers

  • alfred-openspec

Poem

🐰 A clever rabbit hops through specs,
Finding SHALL and MUST in body text,
Not just headers where they hide,
Now the validator is a better guide!
Clear errors help the LLM stay on track,

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: adding a hint when SHALL/MUST appears only in the requirement header, which is the primary fix being implemented.
Linked Issues check ✅ Passed The PR fully addresses issue #356 by implementing a more specific error message that guides users to place SHALL/MUST in the requirement body instead of the header.
Out of Scope Changes check ✅ Passed All changes are directly scoped to fixing issue #356: updating validator logic and adding corresponding test cases, with no unrelated modifications.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ 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.

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.

[BUGFIX] SHALL/MUST should be in body not in the requirement title

1 participant