Skip to content

feat: add file reference validator (MSSCI-14579)#8

Merged
bmadcode merged 5 commits intobmad-code-org:mainfrom
arcaven:feat/validate-file-refs
Feb 9, 2026
Merged

feat: add file reference validator (MSSCI-14579)#8
bmadcode merged 5 commits intobmad-code-org:mainfrom
arcaven:feat/validate-file-refs

Conversation

@arcaven
Copy link
Contributor

@arcaven arcaven commented Feb 8, 2026

Summary

  • Add tools/validate-file-refs.mjs — ESM validator that scans BMB source files for broken cross-file references, absolute path leaks, and stale paths
  • Auto-detects module code (bmb) from src/module.yaml to distinguish internal vs external refs
  • Skips external module refs (core/, bmm/), install-generated files (config.yaml, docs/ KBs), template placeholders ([N], [name]), and runtime variables ({output_folder}, etc.)
  • Inline suppression comments (<!-- validate-file-refs:ignore -->) for documentation examples that contain intentionally broken paths
  • Data directory trap door: refs in */data/ files with EXAMPLE- or invalid- filename prefixes are auto-skipped
  • Reference type metrics: tracks project-root vs relative ref counts, suppressed count, and issue rate percentage
  • Add test/test-validate-file-refs.cjs with 50 tests covering all 10 acceptance criteria plus suppression and trap door features
  • Wire validate:refs and test:refs npm scripts into the test chain and CI workflow

Fixes #7

Prior art

This is the bmad-builder port of the Layer 1 file reference validator originally built for BMAD-METHOD:

PR Repo Title Status
#1494 BMAD-METHOD Cross-file reference validator for BMAD source files ✅ Merged
#1573 BMAD-METHOD Extend Layer 1 file-ref validator to scan CSV workflow-file references ✅ Merged
#1490 BMAD-METHOD Cross-file reference validator (predecessor, superseded by #1494) Closed
#8 bmad-builder File reference validator for BMB source tree This PR

Validation pyramid — bmad-builder

                ┌─────────────┐
                │   Layer 3   │  Graph Validation
                │             │  Step transitions, reachability
                │             │  (planned)
            ┌───┴─────────────┴───┐
            │      Layer 2        │  Schema & Compilation Validation
            │                     │  Agent schema (Zod), agent compilation,
            │                     │  install component tests
            │                     │  ✅ EXISTS
        ┌───┴─────────────────────┴───┐
        │          Layer 1            │  File Reference Validation
        │                             │  Cross-file refs, path resolution,
        │                             │  absolute path leak detection
        │                             │  ◄── THIS PR
    ┌───┴─────────────────────────────┴───┐
    │              Layer 0                │  Formatting, Linting & Docs
    │                                     │  Prettier, ESLint, markdownlint,
    │                                     │  doc link validation, docs build
    │                                     │  ✅ EXISTS
    └─────────────────────────────────────┘

Implementation status across repos

sequenceDiagram
    participant BMB as bmad-builder
    participant BM as BMAD-METHOD

    Note over BMB: ✅ Layer 0 — Formatting, Linting & Docs<br/>Prettier, ESLint, markdownlint,<br/>doc link validation, docs build

    rect rgba(0, 128, 255, 0.1)
    BMB->>BMB: Layer 1 — File Reference Validator<br/>PR #8 (this PR)
    end

    Note over BMB: ✅ Layer 2 — Schema & Compilation<br/>Agent schema (Zod), agent compilation,<br/>install component tests
    Note over BMB: ⬚ Layer 3 — Graph Validation (planned)

    Note over BMB,BM: ── cross-repo status ──

    Note over BM: ✅ Layer 0 — Prettier, ESLint, markdownlint
    Note over BM: ✅ Layer 1 — File Reference Validator<br/>PR #1494 merged + CSV extension #1573 merged
    Note over BM: ✅ Layer 2 — Agent Schema Validator<br/>PR #774 merged
    Note over BM: ⬚ Layer 2b — Workflow Schema (replanning for workflow*.md)
    Note over BM: ⬚ Layer 3 — Graph Validation (planned)
Loading

How it works

flowchart TD
    A[Discover source files in src/<br/>YAML, Markdown, CSV] --> B[Read file content]
    B --> C{File type?}
    C -->|YAML| D[Parse YAML tree<br/>walk maps, sequences, scalars]
    C -->|Markdown| E[Parse frontmatter<br/>+ strip code blocks<br/>+ regex body scan]
    C -->|CSV| F[Parse CSV<br/>extract workflow-file column]
    D --> G[Collect raw references]
    E --> G
    F --> G
    G --> H{Resolvable?}
    H -->|Template placeholder<br/>Runtime variable<br/>Mustache tag| I[Skip]
    H -->|Yes| J{External module?}
    J -->|core/, bmm/, etc.| K["Log [INFO] if --include-external"]
    J -->|Internal bmb/| L{Reference type?}
    L -->|project-root / _bmad| M["mapInstalledToSource()<br/>_bmad/bmb/ → src/"]
    L -->|Relative path| N["path.resolve()<br/>from containing file dir"]
    M --> O{fs.existsSync?}
    N --> O
    O -->|Missing| P["Report [BROKEN]<br/>with file:line"]
    O -->|Found| Q["Report [OK]<br/>in verbose mode"]
    B --> R{Absolute path leak?}
    R -->|"/Users/ /home/ C:\"| S["Report [ABS-PATH]<br/>with line number"]
    P --> T[Summary & exit code]
    Q --> T
    S --> T
    T --> U{--strict flag?}
    U -->|Yes + issues| V[Exit 1]
    U -->|No or clean| W[Exit 0]
Loading

Reference types validated

Pattern Example Source
{project-root}/_bmad/ paths {project-root}/_bmad/bmb/workflows/workflow-create-workflow.md Agent YAML, workflow MD
Relative paths ./step-02-analysis.md, ../data/template.md Step MD, workflow MD
Frontmatter path keys nextStepFile, stepTemplate, checklist Step MD
CSV workflow-file column _bmad/bmb/workflows/agent/workflow-create-agent.md Module help CSV
Absolute path leaks /Users/dev/project/..., C:\Users\... Any file

What gets skipped

Category Examples Why
External module refs _bmad/core/, _bmad/bmm/ Not in this repo's source tree
Install-generated paths config.yaml, docs/*.md KBs Only exist after installation
Template placeholders [N], [name], [template] Filled at runtime
Runtime variables {output_folder}, {bmb_creations_output_folder} Resolved during execution
Mustache templates {{variable_name}} Template engine handles
Out-of-scope paths ../../../../core/ (resolves outside src/) Belongs to another module
Inline suppression <!-- validate-file-refs:ignore --> on same line Documentation examples with intentionally broken paths
Next-line suppression <!-- validate-file-refs:ignore-next-line --> on prior line Multi-line suppression
Data dir examples Files in */data/ with EXAMPLE- or invalid- prefix Example/illustration files by naming convention

Current state

26 broken references found against a baseline ratchet of 26. These are pre-existing issues in the source tree — the validator surfaces them without blocking CI (warning-only mode by default, --strict for exit 1).

Reference metrics

Metric Count
References checked 373
project-root refs 140
Relative refs 233
Suppressed (ignored) 6
Broken references 26
Absolute path leaks 0
Issue rate 26 / 373 (7.0%)

CLI flags

Flag Behavior
(none) Warning mode, exit 0
--strict Exit 1 on any issues
--verbose Show all checked refs
--include-external Show external refs as INFO

Suppressing false positives

For documentation files that contain intentionally broken paths (examples, FORBIDDEN patterns tables, etc.), add an inline HTML comment:

<!-- Same line -->
| `thisStepFile: './step-XX.md'` | Remove unless referenced <!-- validate-file-refs:ignore --> |

<!-- Previous line -->
<!-- validate-file-refs:ignore-next-line -->
**Frontmatter:** Add `continueFile: './step-01b-continue.md'`

For data directory files that serve as examples or illustrations, prefix filenames with EXAMPLE- or invalid- and they'll be auto-skipped. As archived, legacy, or no-longer-valid content moves into data/ directories, additional validator handling may emerge as that pattern develops.

Why is this safe to adopt

Non-blocking by design.

The validator runs in warning mode by default (exit 0). Broken references appear in the build log for visibility, but build results are unaffected. No existing CI checks, pre-commit hooks, or npm scripts are modified in behavior. The validator is purely additive.

Every existing CI check continues to enforce exactly as before:

CI check Before this PR After this PR
Prettier, ESLint, markdownlint Enforced (exit 1) Enforced (exit 1)
Doc links, docs build Enforced (exit 1) Enforced (exit 1)
Schema validation, agent tests, install tests Enforced (exit 1) Enforced (exit 1)
File ref validation (new) Does not exist Warning only (exit 0)

When ready to enforce, one change in package.json:

- "validate:refs": "node tools/validate-file-refs.mjs"
+ "validate:refs": "node tools/validate-file-refs.mjs --strict"

The validator only reads files. It makes no changes to disk.

First-run findings

On first run against main, the validator found 32 raw findings (29 broken references + 3 absolute path leaks) across 162 source files (373 references checked, 67 external refs correctly skipped). After analysis, 26 are confirmed real bugs and 6 are false positives (documentation examples). Fix PRs have been filed for 24 of the 26. The 6 false positives are now suppressed with inline ignore comments in this PR.

All findings

# File Line Type Finding Status Issue
1 module/steps-c/step-01-load-brief.md 7 BROKEN ../../templates/agent-spec-template.md — wrong depth + wrong dir Valid #13
2 module/steps-c/step-01-load-brief.md 8 BROKEN ../../templates/workflow-spec-template.md — wrong depth Valid #14
3 module/steps-c/step-01-load-brief.md 9 BROKEN ../../data/module-standards.md — wrong depth Valid #9
4 module/steps-c/step-01-load-brief.md 10 BROKEN ../../data/module-yaml-conventions.md — wrong depth Valid #10
5 module/steps-c/step-02-structure.md 6 BROKEN ../../data/module-standards.md — wrong depth Valid #9
6 module/steps-c/step-03-config.md 6 BROKEN ../../data/module-yaml-conventions.md — wrong depth Valid #10
7 module/steps-c/step-04-agents.md 6 BROKEN ../../templates/agent-spec-template.md — wrong depth + wrong dir Valid #13
8 module/steps-c/step-04-agents.md 7 BROKEN ../../data/agent-architecture.md — wrong depth Valid #11
9 module/steps-c/step-05-workflows.md 6 BROKEN ../../templates/workflow-spec-template.md — wrong depth Valid #14
10 module/steps-c/step-07-complete.md 8 BROKEN ../steps-v/step-01-validate.md — wrong filename Valid #15
11 module/steps-c/step-01b-continue.md 5 BROKEN ../workflow.md — wrong filename Valid #18
12 module/steps-b/step-13-review.md 6 BROKEN ../../templates/brief-template.md — wrong depth Valid #12
13 module/steps-b/step-14-finalize.md 5 BROKEN ../../templates/brief-template.md — wrong depth Valid #12
14 module/steps-e/step-01-load-target.md 6 BROKEN ../../data/module-standards.md — wrong depth Valid #9
15 module/steps-v/step-02-file-structure.md 6 BROKEN ../../data/module-standards.md — wrong depth Valid #9
16 module/steps-v/step-03-module-yaml.md 6 BROKEN ../../data/module-yaml-conventions.md — wrong depth Valid #10
17 module/steps-v/step-04-agent-specs.md 6 BROKEN ../../templates/agent-spec-template.md — wrong depth + wrong dir Valid #13
18 module/steps-v/step-04-agent-specs.md 7 BROKEN ../../data/agent-architecture.md — wrong depth Valid #11
19 module/steps-v/step-04-agent-specs.md 8 BROKEN {project-root}/…/agent/steps-v/step-01-validate.md — wrong filename Valid #16
20 module/steps-v/step-05-workflow-specs.md 6 BROKEN ../../templates/workflow-spec-template.md — wrong depth Valid #14
21 module/steps-v/step-08-report.md 6 BROKEN {project-root}/…/agent/steps-v/step-01-validate.md — wrong filename Valid #16
22 module/workflow-validate-module.md 6 BROKEN ./steps-v/step-01-validate.md — wrong filename Valid #15
23 module/workflow-edit-module.md 6 BROKEN ./steps-e/step-01-assess.md — wrong filename Valid #19
24 agent/steps-e/e-01-load-existing.md 7 BROKEN ../workflow.md — wrong filename Valid #17
25 workflow/workflow-rework-workflow.md 5 BROKEN ./steps-r/step-01-assess-rework.md — missing dir (stub) Valid #20
26 workflow/templates/step-1b-template.md 20 BROKEN ./step-01b-continue.md — template self-ref Valid #21
27 workflow/data/frontmatter-standards.md 113 BROKEN ./step-XX.md — example in FORBIDDEN patterns table Fixed (this PR)
28 workflow/data/frontmatter-standards.md 114 BROKEN ./workflow.md — example in FORBIDDEN patterns table Fixed (this PR)
29 workflow/data/step-type-patterns.md 82 BROKEN ./step-01b-continue.md — instructional example Fixed (this PR)
30 agent/data/agent-validation.md 80 ABS-PATH /Users/ — checklist text about what to avoid Fixed (this PR)
31 workflow/data/frontmatter-standards.md 19 ABS-PATH /Users/user/dev/BMAD-METHOD — variable example table Fixed (this PR)
32 workflow/data/frontmatter-standards.md 21 ABS-PATH /Users/user/dev/BMAD-METHOD/output — variable example table Fixed (this PR)

Findings by root cause

Root cause Findings Unique targets Fix
Wrong path depth (../../ should be ../) 17 6 files PR #23
Wrong validation step filename 4 2 files PR #22
Wrong workflow/step entry point filename 3 3 files PR #24
Missing directory / stub feature 1 1 dir #20 (issue only)
Template self-reference 1 1 file #21 (issue only)
Documentation examples (false positive) 6 3 files Fixed (this PR — inline ignore comments)
Total 32

Reconciliation

Count
Validator raw output 32 (29 broken refs + 3 abs path leaks)
False positives (doc examples) -6 (suppressed with inline ignore comments)
Real findings 26
Covered by fix PRs 24 (across 3 PRs, 11 issues)
Issue-only (low confidence) 2 (2 issues)

Test plan

  • All 50 tests pass locally (RED → GREEN TDD)
  • npm run validate:refs runs successfully
  • npm run test:refs passes
  • CI quality workflow includes validate:refs step
  • CI pipeline passes on PR

Ad hoc: wrong-depth findings verified against simulated install

The 17 wrong-depth findings (../../../) are the largest finding category. To confirm these aren't valid at install time (e.g., if the installer adds intermediate directories that would make ../../ correct), we tested paths against both the source tree and a simulated installed output.

Method: Ran bmad-method install (local dev CLI, non-interactive mode per docs) to a temp directory, then resolved paths from the installed output.

node tools/cli/bmad-cli.js install \
  --directory /tmp/bmad-test-install \
  --modules bmb --tools none --user-name TestUser --yes

Installed structure — steps-*, data/, and templates/ remain siblings under module/:

_bmad/bmb/workflows/module/
├── data/          ← module-standards.md, agent-architecture.md, etc.
├── templates/     ← brief-template.md, workflow-spec-template.md
├── steps-b/
├── steps-c/       ← step files referencing ../../data/
├── steps-e/
└── steps-v/

Path resolution from the installed steps-c/:

cd /tmp/bmad-test-install/_bmad/bmb/workflows/module/steps-c

ls ../data/module-standards.md     # exists ✅
ls ../../data/module-standards.md  # No such file or directory ❌

All 17 tested against both source tree and installed output:

Result Count Details
Confirmed (../../ broken, ../ works) 14 Pure depth correction
Both broken (wrong depth + wrong dir) 3 templates/agent-spec-template.md — file lives at data/agent-spec-template.md

All 17 are confirmed bugs. The installer preserves relative directory structure with no path rewriting (manager.js:762).

Summary by CodeRabbit

Release Notes

  • New Features

    • Added automated validation for cross-file references to detect broken links and absolute path leaks in documentation and configuration files.
  • Tests

    • Introduced comprehensive test suite with fixtures covering valid references, invalid references, and edge cases such as external references, templates, and runtime variables.
  • Chores

    • Integrated file reference validation into CI/CD pipeline.
    • Added new npm scripts: validate:refs and test:refs.
    • Updated documentation with validation markers.

Tests cover all 10 acceptance criteria for MSSCI-14579:
- _testing exports (path mapping, ref extraction, skip logic)
- Module auto-detect from module.yaml
- External ref detection and skip
- Template placeholder detection
- Absolute path leak detection
- CLI exit codes (default vs --strict)
- npm scripts (validate:refs, test:refs)
- CI integration (quality.yaml step)
- Live source tree baseline ratchet (30 known broken refs)

15 tests failing, 1 accidentally passing — RED state confirmed.
Implements validate-file-refs.mjs that catches broken cross-file
references, absolute path leaks, and auto-detects module code from
module.yaml. Skips external module refs, install-generated files,
template placeholders, and runtime variables.

- Add validate:refs and test:refs npm scripts
- Wire test:refs into npm test chain
- Add validate:refs step to CI quality workflow
- All 40 tests pass (29 broken refs against 30 baseline)
@coderabbitai
Copy link

coderabbitai bot commented Feb 8, 2026

Walkthrough

Adds a new file-reference validator tool, tests, fixtures, npm scripts, and CI workflow step to scan YAML/Markdown/CSV under src/ and report unresolved or unsafe file references.

Changes

Cohort / File(s) Summary
CI / npm scripts
.github/workflows/quality.yaml, package.json
Add CI step and npm scripts validate:refs and test:refs; wire validation into quality workflow.
Validator tool
tools/validate-file-refs.mjs
New CLI tool that discovers source files, extracts refs (YAML/MD/CSV), maps {project-root}/_bmad to src/, detects absolute path leaks, external/install-generated/placeholder skips, verbose/strict/include-external flags, GHA summary output, and exports _testing.
Test runner
test/test-validate-file-refs.cjs
New comprehensive test harness exercising _testing exports, resolvability logic, module detection, skip rules, absolute-path leaks, CLI/exit behavior, npm wiring, and baseline validation.
Fixtures — invalid
test/fixtures/file-refs/invalid/*
\absolute-path-leak.md`, `broken-internal-ref.agent.yaml`, `broken-relative-ref.md`, `wrong-depth.md``
Add fixtures demonstrating broken refs and absolute-path leaks for negative tests.
Fixtures — skip
test/fixtures/file-refs/skip/*
\external-core-ref.md`, `external-relative-ref.md`, `install-generated-ref.md`, `template-placeholder.md`, `unresolvable-vars.md``
Add fixtures for refs that should be skipped (external modules, climbing-out relative refs, install-generated, template placeholders, runtime vars).
Fixtures — valid
test/fixtures/file-refs/valid/*
\internal-bmb-ref.agent.yaml`, `relative-ref.md`, `relative-ref-target.md``
Add fixtures demonstrating resolvable internal/project-root and relative references.
Docs / annotations
src/.../agent-validation.md, src/.../frontmatter-standards.md, src/.../step-type-patterns.md
Add inline HTML comments <!-- validate-file-refs:ignore --> to specific doc lines to opt out of validation checks.

Sequence Diagram

sequenceDiagram
    participant CLI as User/CI
    participant Validator as Validator Tool
    participant FS as File System
    participant Parser as Reference Parser
    participant Resolver as Path Resolver
    participant Reporter as Report Generator

    CLI->>Validator: npm run validate:refs [--flags]
    Validator->>FS: Discover src/ files
    FS-->>Validator: YAML, Markdown, CSV files
    Validator->>FS: Load module.yaml for external detection
    FS-->>Validator: Module mappings
    Validator->>Parser: Extract refs from files
    Parser->>Parser: Parse frontmatter, body, CSV columns
    Parser-->>Validator: Extracted references
    Validator->>Resolver: Resolve refs (relative, {project-root})
    Resolver->>Resolver: Map {project-root}/_bmad → src/
    Resolver->>Resolver: Detect absolute path leaks & placeholders
    Resolver->>FS: Verify existence
    FS-->>Resolver: Exists / Missing
    Resolver-->>Validator: Resolution results
    Validator->>Reporter: Aggregate and format issues
    Reporter-->>CLI: Print summary and set exit code
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related issues

Poem

🐰
Hop, I scanned each file and tree,
I chased the refs from root to lea,
Leaks and ghosts I sniffed and found,
Placeholders left untouched, safe and sound,
Hooray — the links now mind where they be!

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 78.95% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically describes the main change: adding a file reference validator tool. It uses a conventional 'feat:' prefix, is concise, and directly relates to the primary objective of the PR.

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

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

No actionable comments were generated in the recent review. 🎉

🧹 Recent nitpick comments
tools/validate-file-refs.mjs (4)

460-465: startsWith(SRC_DIR) path-prefix check can match unrelated directories.

SRC_DIR resolves to e.g. /repo/src without a trailing separator, so a hypothetical sibling directory like /repo/src-extras/file.md would also pass the startsWith guard. In practice this is very unlikely given the src naming, but the canonical fix is to compare with a trailing separator.

Proposed fix
-    if (!resolved.startsWith(SRC_DIR)) return null;
+    if (!resolved.startsWith(SRC_DIR + path.sep)) return null;

641-654: GHA step-summary table rows are not escaping pipe characters.

If iss.ref (which can come from leak.content, a truncated raw line) contains |, the Markdown table will be malformed. A quick escape pass would keep the summary robust.

Proposed fix
+    const escMd = (s) => String(s).replaceAll('|', '\\|');
     for (const iss of allIssues) {
-      summary += `| ${iss.file} | ${iss.line} | ${iss.ref} | ${iss.issue} |\n`;
+      summary += `| ${escMd(iss.file)} | ${iss.line} | ${escMd(iss.ref)} | ${iss.issue} |\n`;
     }

328-392: Frontmatter line-number lookup uses indexOf which could mis-locate duplicate keys.

Line 342: content.indexOf(${key}:) finds the first occurrence. If the same string (e.g., nextStepFile:) appears both in frontmatter and in the body text, the line number will be correct by luck (frontmatter comes first). But if a key name is a substring of another key that appears earlier (e.g., stepFile matched inside continueStepFile:), indexOf would return the wrong offset. This is cosmetic (only affects diagnostic line numbers), so low priority.


470-491: Exposing internals via _testing is a pragmatic choice for testability.

The leading underscore convention makes the intent clear. Consider freezing the object (Object.freeze) to prevent accidental mutation in tests, though this is not critical.

test/test-validate-file-refs.cjs (4)

70-80: error.code may be null when the child process is killed by a signal.

If the node process is killed by a signal rather than exiting normally, error.code will be null (and error.signal will be set). This would cause exitCode to be null instead of a number, potentially making exit-code assertions behave unexpectedly.

Defensive fix
     execFile('node', [TOOL_PATH, ...args], { cwd: path.join(__dirname, '..') }, (error, stdout, stderr) => {
       resolve({
         stdout: stdout || '',
         stderr: stderr || '',
-        exitCode: error ? error.code : 0,
+        exitCode: error ? (error.code ?? error.status ?? 1) : 0,
       });

87-125: Repeated await import(TOOL_PATH) + _testing extraction pattern across ~10 test functions.

Every test function duplicates the same import/extraction/guard boilerplate. Extracting a shared helper (e.g., async function loadTesting()) would reduce ~80 lines of duplication and make each test focus on its assertions.

Example helper
async function loadTesting() {
  const mod = await import(TOOL_PATH);
  const _testing = mod._testing || (mod.default && mod.default._testing);
  if (!_testing) throw new Error('Module does not export _testing');
  return _testing;
}

Then each test becomes:

async function testPathMapping() {
  section('AC2: Path mapping');
  let _testing;
  try {
    _testing = await loadTesting();
  } catch {
    fail('Path mapping (import)', 'Cannot import module');
    return;
  }
  const { mapInstalledToSource } = _testing;
  // ...assertions...
}

396-430: --json flag is not supported by the tool; the test always falls through to text parsing.

The runCLI(['--json']) call on Line 399 passes an unrecognized flag. The JSON.parse will always fail and the catch branch does the actual work via regex. This isn't buggy — the fallback works — but the dead try block is misleading. Consider removing the JSON parse attempt (or adding --json support to the tool) so the intent is clear.

Also, the issueMatch regex (/Issues found:\s*(\d+)/) won't match the tool's actual output format (Total issues: ...), making that branch dead code as well. Only brokenMatch is effective.


338-363: CLI exit code tests depend on the live source tree having broken refs.

The --strict test on Line 350 assumes broken refs currently exist (expecting exit 1). If all broken refs are eventually fixed, this test will fail. Consider adding a comment noting this coupling to the baseline, or using a fixture directory to guarantee a broken ref exists.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🤖 Fix all issues with AI agents
In `@test/test-validate-file-refs.cjs`:
- Around line 129-133: The JSDoc AC number in the test comment is inconsistent
with the test section and other tests: update the doc comment above function
testPathMapping so the AC number matches the section title and related tests
(change "AC3" to "AC2"); locate the comment immediately above the function
testPathMapping and adjust the identifier in the JSDoc to "AC2" so the comment,
section('AC2: Path mapping') and testModuleAutoDetect references are consistent.
- Line 18: Update the file usage comment string to reference the actual
filename: replace "node test/test-validate-file-refs.js" with "node
test/test-validate-file-refs.cjs" in the top-of-file usage comment so the
documented invocation matches the real file name.
- Line 425: The code uses logical OR to compute brokenCount which treats 0 as
falsy; update the expression in test/test-validate-file-refs.cjs that assigns
brokenCount (currently using data.brokenCount || data.broken_refs || 0) to use
nullish coalescing so it only falls back when values are null/undefined (i.e.,
change to use ?? between data.brokenCount, data.broken_refs, and the final 0) to
preserve a legitimate 0 result.

In `@tools/validate-file-refs.mjs`:
- Around line 511-516: The inner fs.existsSync(resolved) check is unreachable
because it's inside if (!fs.existsSync(resolved)); move the directory-exists
check so it happens before or outside the negated guard: first compute hasExt =
path.extname(resolved) !== '' and if (!hasExt && fs.existsSync(resolved))
continue to skip directories, then only afterwards run if
(!fs.existsSync(resolved)) { broken.push({ ref, resolved:
path.relative(PROJECT_ROOT, resolved) }); brokenRefs++; } — adjust the logic
around resolved, hasExt, fs.existsSync, broken.push and brokenRefs accordingly.
- Line 114: The ABS_PATH_LEAK regex currently uses [A-Z]:\\\\ which matches two
literal backslashes (e.g. "C:\\\\") and therefore misses typical Windows paths
with a single backslash; update the pattern to match a single backslash by
replacing [A-Z]:\\\\ with [A-Z]:\\ so ABS_PATH_LEAK =
/(?:\/Users\/|\/home\/|[A-Z]:\\)/ (or alternatively use a variant that allows
one or two backslashes if you want to accept both forms).
🧹 Nitpick comments (7)
tools/validate-file-refs.mjs (4)

406-408: Use String#slice() instead of String#substring().

Per ESLint unicorn/prefer-string-slice rule flagged by static analysis.

Proposed fix
-      leaks.push({ file: filePath, line: i + 1, content: line.trim().substring(0, 100) });
+      leaks.push({ file: filePath, line: i + 1, content: line.trim().slice(0, 100) });

219-221: issue() factory function appears unused.

This function is defined but never called — the main loop constructs issue objects inline at lines 534 and 545. Consider removing it or using it consistently.


59-83: Hardcoded UNRESOLVABLE_VARS allowlist requires code changes for each new runtime variable.

This is acceptable for a project-specific tool, but worth noting that any new runtime variable patterns in source files will need a corresponding entry here to avoid false-positive broken-ref reports. A comment noting this maintenance requirement (or a broader pattern-based approach like matching any {snake_case_var}) could reduce future churn.


416-434: Path containment check uses startsWith(SRC_DIR) without a trailing separator.

resolved.startsWith(SRC_DIR) could match a sibling directory like src-backup/. In practice this is unlikely given the project structure, but a more robust check would be:

if (!resolved.startsWith(SRC_DIR + path.sep) && resolved !== SRC_DIR) return null;
test/test-validate-file-refs.cjs (3)

28-28: FIXTURE_DIR is declared but never used.

ESLint flags this (no-unused-vars). Either remove it or use it in fixture-based tests that currently hardcode paths or inline content.

Proposed fix (remove)
-const FIXTURE_DIR = path.join(__dirname, 'fixtures', 'file-refs');

88-104: Consider extracting repeated _testing import into a shared helper.

Every test function independently imports the module and extracts _testing with identical boilerplate (~15 lines each). A shared loadTesting() helper would eliminate this duplication and make it easier to maintain.

Example helper
let _testingCache;
async function loadTesting() {
  if (_testingCache) return _testingCache;
  const mod = await import(TOOL_PATH);
  const t = mod._testing || (mod.default && mod.default._testing);
  if (!t) throw new Error('_testing namespace not exported');
  _testingCache = t;
  return t;
}

95-95: Address remaining ESLint violations.

A few linter flags worth cleaning up:

  • Line 95: Rename catch parameter errerror (unicorn/catch-error-name).
  • Line 415: Use Number.parseInt(…) instead of bare parseInt(…) (unicorn/prefer-number-properties).

The unicorn/no-negated-condition warnings (Lines 234, 294, 483) and no-process-exit warnings (Lines 554, 558, 563) are acceptable as-is — the negated conditions read naturally in assertion context, and process.exit() is appropriate for a CLI test runner.

Also applies to: 415-415

@arcaven arcaven marked this pull request as draft February 8, 2026 21:17
- Fix eslint unicorn/prefer-string-slice (substring → slice)
- Fix prettier formatting on both validator and test files
- Remove unused issue() function (dead code)
- Remove unreachable hasExt/existsSync branch
Fix 13 CJS-specific eslint errors (catch-error-name, no-negated-condition,
prefer-number-properties, no-process-exit) and 4 CodeRabbit suggestions
(comment filename, AC number, nullish coalescing, Windows path regex).
arcaven added a commit to arcaven/bmad-builder that referenced this pull request Feb 8, 2026
Module validate workflow and steps referenced step-01-validate.md which doesn't
exist. Corrected to step-01-load-target.md (module) and v-01-load-review.md
(agent) which are the actual entry points for their validation flows.

Fixes bmad-code-org#15, bmad-code-org#16
Found by file reference validator (PR bmad-code-org#8)
arcaven added a commit to arcaven/bmad-builder that referenced this pull request Feb 8, 2026
All module workflow steps referenced ../../data/ and ../../templates/ but the
actual files are at ../data/ and ../templates/ (one level closer). Also corrects
agent-spec-template.md references from templates/ to data/ where it actually lives.

Fixes bmad-code-org#9, bmad-code-org#10, bmad-code-org#11, bmad-code-org#12, bmad-code-org#13, bmad-code-org#14
Found by file reference validator (PR bmad-code-org#8)
arcaven added a commit to arcaven/bmad-builder that referenced this pull request Feb 8, 2026
Agent edit step referenced ../workflow.md (doesn't exist) instead of
../workflow-edit-agent.md. Module create continue step referenced
../workflow.md instead of ../workflow-create-module.md. Module edit
workflow referenced step-01-assess.md instead of step-01-load-target.md.

Fixes bmad-code-org#17, bmad-code-org#18, bmad-code-org#19
Found by file reference validator (PR bmad-code-org#8)
…trics

Inline suppression: <!-- validate-file-refs:ignore --> on the same line
or <!-- validate-file-refs:ignore-next-line --> on the previous line
suppresses both broken-ref and abs-path-leak findings. Applied to 6
known false positives (doc examples, forbidden-pattern tables, checklist
text) across 3 data/ files — drops findings from 32 to 26.

Data-dir trap door: refs in data/ directories whose filename starts with
EXAMPLE- or invalid- are auto-skipped. Convention for documentation
authors writing illustrative examples without triggering validation.
Legacy/archived content moved to data/ may need additional handling as
that pattern emerges.

Ref metrics: summary now reports reference counts by type (project-root
vs relative), suppressed count, and issue rate (issues / total refs %).

50 tests (up from 40), baseline ratcheted from 30 to 26.
@arcaven arcaven marked this pull request as ready for review February 9, 2026 00:54
@bmadcode bmadcode merged commit fee6610 into bmad-code-org:main Feb 9, 2026
1 of 11 checks passed
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.

feat: cross-file reference validation for BMB source files and generated output

2 participants