Skip to content

fix: actionable diagnostics, PR review comments, and SKILL.md size check#88

Merged
odinr merged 13 commits intomainfrom
fix/issue-84-regex-explanation-guidance
Mar 18, 2026
Merged

fix: actionable diagnostics, PR review comments, and SKILL.md size check#88
odinr merged 13 commits intomainfrom
fix/issue-84-regex-explanation-guidance

Conversation

@odinr
Copy link
Collaborator

@odinr odinr commented Mar 18, 2026

Why

The validate:scripts CI check rejects regex literals without a specific comment format, but the error output gives no actionable fix guidance. The error heading groups unrelated issue types together, no documentation surface mentions the rule, and contributors have no self-service path to resolve failures.

Additionally, SKILL.md files have no size guardrails — skills can grow indefinitely without feedback.

Current behavior

  • missing-regex-explanation diagnostics emit file:line:code:statement with no fix hint.
  • Regex-explanation issues are grouped under the misleading heading "Intent-comment check failed".
  • The regex-explanation rule is undocumented in the README, contributor guide, and instructions.
  • CI only logs errors to the workflow console — contributors must dig through logs.
  • No SKILL.md size validation exists.

New behavior

  • Actionable diagnostics: Every issue code now includes a Fix: hint explaining exactly what to do.
  • Distinct error headings: missing-regex-explanation issues print under their own "Regex-explanation check failed" heading, separate from intent-comment issues.
  • JSON output for CI: --json-output <path> flag writes structured diagnostics for downstream tooling.
  • PR review comments: The validate-scripts workflow posts inline review comments on offending lines (reviewdog-style). Stale reviews are dismissed on subsequent pushes.
  • SKILL.md size check: New validate:skill-sizes command warns at 300+ lines and errors at 500+, wired into the skills catalog workflow.
  • Documented rule: Regex-explanation requirement is now in the validate-scripts README, contributor guide §4.4, and the Mermaid flow diagram.

References

Reviewer focus

  • Start here (files/areas): scripts/validate-scripts/format-intent-comment-issues.ts (FIX_HINTS map), .github/workflows/validate-scripts.yml (review comment step)
  • Verify these behavior changes: run bun run validate:scripts locally to see new diagnostic format; check workflow YAML for correct github-script logic; review SKILL.md size thresholds (300 warn / 500 error)
  • Risky or security-sensitive parts: workflow gets pull-requests: write permission for review comments — review the github-script scope carefully

Self review checklist

  • I scoped this PR to one clear purpose.
  • I followed CONTRIBUTING.md.
  • I did not include secrets, credentials, or sensitive data.
  • I reviewed my own diff for clarity and unintended changes.

@odinr odinr self-assigned this Mar 18, 2026
@odinr odinr marked this pull request as ready for review March 18, 2026 01:28
@odinr odinr requested a review from a team as a code owner March 18, 2026 01:28
Copilot AI review requested due to automatic review settings March 18, 2026 01:28
@github-actions
Copy link
Contributor

github-actions bot commented Mar 18, 2026

Changeset summary

Detected 1 changeset file(s):

.changeset/skill-authoring-size-limits.md:

---
"fusion-skill-authoring": patch
---

Document SKILL.md size limits and CI guardrails

- Document 300-line recommended limit (triggers CI warning)
- Document 500-line hard limit (fails CI)
- Clarify expectation to move overflow to references/ early
- Add failure signal for exceeding size thresholds

Relates to: equinor/fusion-core-tasks#84

…comments, SKILL.md size check

- Add per-code fix hints to validate-scripts diagnostics
- Separate regex-explanation issues from intent-comment heading
- Add --json-output flag for structured CI diagnostics
- Post inline PR review comments via actions/github-script
- Dismiss stale bot reviews on subsequent pushes
- Add SKILL.md line-count validation (warn 300+, error 500+)
- Document regex-explanation rule in README and contributor guide
- Fix pre-existing intent-comment issues in release-prepare

Resolves #84
@odinr odinr force-pushed the fix/issue-84-regex-explanation-guidance branch from 0c54596 to 4a2da66 Compare March 18, 2026 01:30
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Improves contributor feedback loops for script validation failures by making diagnostics actionable, adding CI-friendly structured output, and introducing a guardrail against oversized SKILL.md files.

Changes:

  • Add fix-hinted diagnostics and separate headings for regex-explanation vs intent-comment issues in validate:scripts, plus optional JSON diagnostics output.
  • Post inline PR review comments from validate-scripts workflow using JSON diagnostics; dismiss prior bot reviews on subsequent runs.
  • Introduce validate:skill-sizes (300 warn / 500 error) and wire it into the skills catalog validation workflow.

Reviewed changes

Copilot reviewed 12 out of 12 changed files in this pull request and generated 9 comments.

Show a summary per file
File Description
scripts/validate-skills/check-skill-sizes.ts Adds SKILL.md line-count scanning and severity sorting.
scripts/validate-skills/check-skill-sizes-cli.ts Adds CLI wrapper and CI-friendly output/exit behavior for size findings.
scripts/validate-scripts/index.ts Adds --json-output, splits issue headings, and emits JSON diagnostics.
scripts/validate-scripts/format-intent-comment-issues.ts Adds Fix: hints via FIX_HINTS and appends them to formatted diagnostics.
scripts/validate-scripts/README.md Documents the regex-explanation requirement and updates the Mermaid flow.
scripts/release-prepare/index.ts Filters fusion-skills from root changelog package entries.
scripts/tests/validate-skills-sizes.test.ts Adds tests for SKILL.md size findings and ordering.
scripts/tests/validate-scripts-format-issues.test.ts Adds tests asserting Fix: hints are included in formatted issue output.
package.json Adds validate:skill-sizes script entry.
contribute/06-scripts-code-rules.md Documents the regex-explanation comment rule enforced by validate:scripts.
.github/workflows/validate-skills-catalog.yml Runs the new SKILL.md size check in CI and reports it in the summary.
.github/workflows/validate-scripts.yml Enables PR review comments via --json-output + actions/github-script and adds required permissions.

You can also share your feedback on Copilot code review. Take the survey.

Comment on lines +14 to +15
console.error(
`${prefix}: ${finding.skillPath} has ${finding.lineCount} lines (${finding.level === "error" ? "max 500" : "recommended max 300"}).`,
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Resolved: exported WARN_THRESHOLD and ERROR_THRESHOLD from check-skill-sizes.ts and updated the CLI to use these constants in error messages. This ensures the output won't drift if thresholds are updated in the future. Changes pushed in commit 91e8341.

Comment on lines +123 to +134
// Build inline review comments from diagnostics.
const comments = diagnostics.map(d => ({
path: d.path,
line: d.line,
body: `${marker}\n**\`${d.code}\`**: ${d.message}`,
}));

// Post a new review with inline comments.
await github.rest.pulls.createReview({
owner, repo, pull_number, commit_id,
event: 'COMMENT',
body: `${marker}\n**validate-scripts** found ${diagnostics.length} issue(s). Fix them to pass CI.`,
Comment on lines +271 to +273
// Write all diagnostics to JSON so CI can post inline PR review comments.
const allIntentIssues = [...intentCommentIssues, ...regexExplanationIssues, ...disallowedIssues];
writeDiagnosticsJson(options.jsonOutput, allIntentIssues, repoRoot);
const findings = checkSkillSizes(tempRoot);
expect(findings).toHaveLength(1);
expect(findings[0]?.level).toBe("warning");
expect(findings[0]?.lineCount).toBe(351);
Comment on lines +131 to +136
await github.rest.pulls.createReview({
owner, repo, pull_number, commit_id,
event: 'COMMENT',
body: `${marker}\n**validate-scripts** found ${diagnostics.length} issue(s). Fix them to pass CI.`,
comments,
});
Comment on lines 201 to 205
packages: entry.skillNames
// Exclude the meta-package so only real skill names appear in release notes.
.filter((skillName) => skillName !== "fusion-skills")
// Format each skill name as skill@version for the root changelog.
.map((skillName) => {
Comment on lines +32 to +34
const content = readFileSync(filePath, "utf8");
const lineCount = content.split("\n").length;
const skillPath = relative(repoRoot, filePath);
odinr added 2 commits March 18, 2026 02:34
…thoring workflow

- Add runtime size comparison table to contribute/02-skill-structure-and-content.md
- Add SKILL.md size limits section to copilot-instructions.md
- Add size awareness to fusion-skill-authoring Step 4 and validation failure signals
- Warning at 300 lines, error at 500 lines
- Add --only-diff and --base-ref flags to validate:skill-sizes
- CI only checks SKILL.md files changed in the PR, not the entire catalog
- Full scan still available locally without flags
Copilot AI review requested due to automatic review settings March 18, 2026 01:37
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

odinr and others added 2 commits March 18, 2026 03:02
Export WARN_THRESHOLD and ERROR_THRESHOLD constants from check-skill-sizes.ts
so the CLI uses these values rather than hard-coding them. This ensures the
output stays in sync if thresholds are updated in the future.

- Export thresholds from check-skill-sizes.ts
- Update CLI to import and use thresholds in error messages
- Tests verified and code formatting checked
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings March 18, 2026 02:27
Filter JSON diagnostics to changed hunks in --only-diff mode to avoid invalid inline review positions.\n\nHandle both b/path and --no-prefix diff header formats in diff-line parsing.\n\nAdd regression tests for diff-line map extraction and fallback behavior.
Copy link
Contributor

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

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

validate-scripts found 8 issue(s). Fix them to pass CI.

// Split on "diff --git" markers to isolate per-file blocks.
const fileBlocks = diffOutput.split(/^diff --git/m).slice(1);

for (const block of fileBlocks) {
Copy link
Contributor

Choose a reason for hiding this comment

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

missing-intent-comment: missing-intent-comment: for (const block of fileBlocks) {
Fix: Add a // comment on the line above explaining why this control-flow or iterator exists.

const lines = block.split("\n");
const firstLine = (lines[0] || "").trim();
const headerParts = firstLine.split(/\s+/);
if (headerParts.length < 2) continue;
Copy link
Contributor

Choose a reason for hiding this comment

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

missing-intent-comment: missing-intent-comment: if (headerParts.length < 2) continue;
Fix: Add a // comment on the line above explaining why this control-flow or iterator exists.

if (headerParts.length < 2) continue;

const rawNewPath = headerParts[1];
if (!rawNewPath || rawNewPath === "/dev/null") continue;
Copy link
Contributor

Choose a reason for hiding this comment

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

missing-intent-comment: missing-intent-comment: if (!rawNewPath || rawNewPath === "/dev/null") continue;
Fix: Add a // comment on the line above explaining why this control-flow or iterator exists.

for (const line of lines) {
// Hunk header format: @@ -oldStart,oldCount +newStart,newCount @@
const hunkMatch = line.match(/^@@ -\d+(?:,\d+)? \+(\d+)(?:,(\d+))? @@/);
if (!hunkMatch) continue;
Copy link
Contributor

Choose a reason for hiding this comment

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

missing-intent-comment: missing-intent-comment: if (!hunkMatch) continue;
Fix: Add a // comment on the line above explaining why this control-flow or iterator exists.

const lineMap: DiffLineMap = {};

// Split on "diff --git" markers to isolate per-file blocks.
const fileBlocks = diffOutput.split(/^diff --git/m).slice(1);
Copy link
Contributor

Choose a reason for hiding this comment

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

missing-regex-explanation: missing-regex-explanation: const fileBlocks = diffOutput.split(/^diff --git/m).slice(1);
Fix: Add a // comment containing the word "regex" on the line above to explain what the pattern matches.

for (const block of fileBlocks) {
const lines = block.split("\n");
const firstLine = (lines[0] || "").trim();
const headerParts = firstLine.split(/\s+/);
Copy link
Contributor

Choose a reason for hiding this comment

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

missing-regex-explanation: missing-regex-explanation: const headerParts = firstLine.split(/\s+/);
Fix: Add a // comment containing the word "regex" on the line above to explain what the pattern matches.

// Find hunk headers (@@ -old,count +new,count @@) to extract line ranges in the new file.
for (const line of lines) {
// Hunk header format: @@ -oldStart,oldCount +newStart,newCount @@
const hunkMatch = line.match(/^@@ -\d+(?:,\d+)? \+(\d+)(?:,(\d+))? @@/);
Copy link
Contributor

Choose a reason for hiding this comment

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

missing-regex-explanation: missing-regex-explanation: const hunkMatch = line.match(/^@@ -\d+(?:,\d+)? +(\d+)(?:,(\d+))? @@/);
Fix: Add a // comment containing the word "regex" on the line above to explain what the pattern matches.

@@ -0,0 +1,99 @@
import { execFileSync } from "node:child_process";
Copy link
Contributor

Choose a reason for hiding this comment

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

disallowed-multiple-exported-functions: disallowed-multiple-exported-functions: exported-functions:2
Fix: Split this file so each module exports at most one function.

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 17 out of 17 changed files in this pull request and generated 6 comments.

Comments suppressed due to low confidence (1)

scripts/validate-scripts/index.ts:232

  • The success log message still says "TSDoc, intent-comment, and disallowed-pattern checks passed" but this command now also enforces the regex-explanation rule. Consider updating the message so it reflects the full set of checks, otherwise contributors may think regex-explanation isn’t being validated.
    : collectIntentCommentIssues(scriptsRoot);
  // Keep disallowed-pattern violations separate from general intent-comment findings.
  const disallowedIssueCodes = new Set([
    "disallowed-while-loop",

You can also share your feedback on Copilot code review. Take the survey.

Comment on lines +27 to +35
// Generate a SKILL.md with 350 lines (above warning, below error).
writeFileSync(join(skillDir, "SKILL.md"), "line\n".repeat(350), "utf8");

try {
const findings = checkSkillSizes(tempRoot);
expect(findings).toHaveLength(1);
expect(findings[0]?.level).toBe("warning");
expect(findings[0]?.lineCount).toBe(351);
} finally {
Comment on lines 149 to +172

/**
* Filters issues to only those reported on changed diff lines.
*
* @param issues - Candidate issues to include in JSON diagnostics.
* @param repoRoot - Absolute repository root for relative path display.
* @param diffLineMap - Map of file paths to changed line ranges.
* @returns Issues that map to changed lines in the current diff.
*/
function filterIssuesToDiffLines(
issues: IntentCommentIssue[],
repoRoot: string,
diffLineMap: Record<string, Array<{ start: number; end: number }>>,
): IntentCommentIssue[] {
// Iterate each issue so PR review comments only target changed diff hunks.
return issues.filter((issue) => {
// This regex normalizes Windows path separators to POSIX for diff-map key lookup.
const relPath = relative(repoRoot, issue.filePath).replace(/\\/g, "/");
const ranges = diffLineMap[relPath];
return ranges ? isLineInDiff(issue.line, ranges) : false;
});
}

/**
Comment on lines +130 to +137
// Post a new review with inline comments.
await github.rest.pulls.createReview({
owner, repo, pull_number, commit_id,
event: 'COMMENT',
body: `${marker}\n**validate-scripts** found ${diagnostics.length} issue(s). Fix them to pass CI.`,
comments,
});


// Keep CLI execution scoped to direct invocation to avoid side effects in imports.
if (import.meta.main) {
try {
Comment on lines +50 to +52
- name: Validate SKILL.md file sizes
id: validate_sizes
run: bun run validate:skill-sizes --only-diff --base-ref origin/${{ github.base_ref || 'main' }}
Comment on lines +33 to +35
const content = readFileSync(filePath, "utf8");
const lineCount = content.split("\n").length;
const skillPath = relative(repoRoot, filePath);
Copy link
Contributor

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

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

validate-scripts found 8 issue(s). Fix them to pass CI.

// Split on "diff --git" markers to isolate per-file blocks.
const fileBlocks = diffOutput.split(/^diff --git/m).slice(1);

for (const block of fileBlocks) {
Copy link
Contributor

Choose a reason for hiding this comment

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

missing-intent-comment: missing-intent-comment: for (const block of fileBlocks) {
Fix: Add a // comment on the line above explaining why this control-flow or iterator exists.

const lines = block.split("\n");
const firstLine = (lines[0] || "").trim();
const headerParts = firstLine.split(/\s+/);
if (headerParts.length < 2) continue;
Copy link
Contributor

Choose a reason for hiding this comment

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

missing-intent-comment: missing-intent-comment: if (headerParts.length < 2) continue;
Fix: Add a // comment on the line above explaining why this control-flow or iterator exists.

if (headerParts.length < 2) continue;

const rawNewPath = headerParts[1];
if (!rawNewPath || rawNewPath === "/dev/null") continue;
Copy link
Contributor

Choose a reason for hiding this comment

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

missing-intent-comment: missing-intent-comment: if (!rawNewPath || rawNewPath === "/dev/null") continue;
Fix: Add a // comment on the line above explaining why this control-flow or iterator exists.

for (const line of lines) {
// Hunk header format: @@ -oldStart,oldCount +newStart,newCount @@
const hunkMatch = line.match(/^@@ -\d+(?:,\d+)? \+(\d+)(?:,(\d+))? @@/);
if (!hunkMatch) continue;
Copy link
Contributor

Choose a reason for hiding this comment

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

missing-intent-comment: missing-intent-comment: if (!hunkMatch) continue;
Fix: Add a // comment on the line above explaining why this control-flow or iterator exists.

const lineMap: DiffLineMap = {};

// Split on "diff --git" markers to isolate per-file blocks.
const fileBlocks = diffOutput.split(/^diff --git/m).slice(1);
Copy link
Contributor

Choose a reason for hiding this comment

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

missing-regex-explanation: missing-regex-explanation: const fileBlocks = diffOutput.split(/^diff --git/m).slice(1);
Fix: Add a // comment containing the word "regex" on the line above to explain what the pattern matches.

for (const block of fileBlocks) {
const lines = block.split("\n");
const firstLine = (lines[0] || "").trim();
const headerParts = firstLine.split(/\s+/);
Copy link
Contributor

Choose a reason for hiding this comment

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

missing-regex-explanation: missing-regex-explanation: const headerParts = firstLine.split(/\s+/);
Fix: Add a // comment containing the word "regex" on the line above to explain what the pattern matches.

// Find hunk headers (@@ -old,count +new,count @@) to extract line ranges in the new file.
for (const line of lines) {
// Hunk header format: @@ -oldStart,oldCount +newStart,newCount @@
const hunkMatch = line.match(/^@@ -\d+(?:,\d+)? \+(\d+)(?:,(\d+))? @@/);
Copy link
Contributor

Choose a reason for hiding this comment

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

missing-regex-explanation: missing-regex-explanation: const hunkMatch = line.match(/^@@ -\d+(?:,\d+)? +(\d+)(?:,(\d+))? @@/);
Fix: Add a // comment containing the word "regex" on the line above to explain what the pattern matches.

@@ -0,0 +1,99 @@
import { execFileSync } from "node:child_process";
Copy link
Contributor

Choose a reason for hiding this comment

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

disallowed-multiple-exported-functions: disallowed-multiple-exported-functions: exported-functions:2
Fix: Split this file so each module exports at most one function.

odinr added 6 commits March 18, 2026 03:39
Limit JSON diagnostics in --only-diff mode to changed lines so PR inline review comments don't target non-diff lines.

Improve diff parsing for --no-prefix headers and keep validate-scripts rule compliance in parser/test updates.
…sting

- Fix off-by-one line count caused by trailing newlines via getNormalizedLineCount()
- Support both Unix (LF) and Windows (CRLF) line endings in SKILL.md size validation
- Add boundary-value tests for 300-line warn and 500-line error thresholds
- Chunk PR review comments into 50-per-review batches to avoid GitHub API limits
- Add error handling and fallback for createReview() failures
- Clarify inline comments scope (intent/regex/disallowed diagnostics only)
- Fix validate-skills-catalog.yml checkout fetch-depth for diff-only size checks
- All validation checks pass: validate:scripts, biome:check, test suite
Update the success log message to explicitly list regex-explanation as one of the
checks performed, since it's now a distinct validation category (separate from
general intent-comment checks).
Document the new SKILL.md size limits (300 warn, 500 error) in the skill-authoring
guide, including expectations for moving overflow to references/.
- Added "@biomejs/biome" version "^2.4.5" to devDependencies.
- Removed duplicate entry for "@biomejs/biome".
- Added "biome" version "^0.3.3" to devDependencies.
@odinr odinr merged commit 8cd7d9d into main Mar 18, 2026
11 checks passed
@odinr odinr deleted the fix/issue-84-regex-explanation-guidance branch March 18, 2026 07:49
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.

validate:scripts missing-regex-explanation error gives no actionable fix guidance

2 participants