Skip to content

Commit c5e0b21

Browse files
cameroncookeclaude
andcommitted
fix(docs-check): Validate CLI refs only in code examples
Restrict docs command validation to fenced and inline code snippets so prose headings are not misclassified as CLI invocations. Restore the original migration heading text now that scanner behavior is context-aware. Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 228ecb4 commit c5e0b21

File tree

2 files changed

+49
-6
lines changed

2 files changed

+49
-6
lines changed

docs/MIGRATION_V2.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,7 @@ For full details on configuration options see [CONFIGURATION.md](CONFIGURATION.m
187187

188188
## 3. CLI and skills
189189

190-
### CLI mode for xcodebuildmcp
190+
### xcodebuildmcp is now a CLI
191191

192192
The `xcodebuildmcp` command can now be used directly in the terminal without an MCP client:
193193

scripts/check-docs-cli-commands.js

Lines changed: 48 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,49 @@ function buildValidationSets(catalog) {
8181
return { validPairs, validWorkflows };
8282
}
8383

84+
function extractCommandCandidates(content) {
85+
const lines = content.split(/\r?\n/u);
86+
const candidates = [];
87+
const inlineCodeRegex = /`([^`\n]+)`/g;
88+
const fenceHeaderRegex = /^\s*```([a-z0-9_-]*)\s*$/iu;
89+
const codeFenceLanguages = new Set(['', 'bash', 'sh', 'zsh', 'shell', 'console']);
90+
91+
let inFence = false;
92+
let shouldScanFence = false;
93+
94+
for (let lineNumber = 1; lineNumber <= lines.length; lineNumber += 1) {
95+
const line = lines[lineNumber - 1];
96+
const fenceMatch = line.match(fenceHeaderRegex);
97+
98+
if (fenceMatch) {
99+
if (!inFence) {
100+
inFence = true;
101+
shouldScanFence = codeFenceLanguages.has(fenceMatch[1].toLowerCase());
102+
} else {
103+
inFence = false;
104+
shouldScanFence = false;
105+
}
106+
continue;
107+
}
108+
109+
if (inFence) {
110+
if (shouldScanFence) {
111+
candidates.push({ lineNumber, text: line });
112+
}
113+
continue;
114+
}
115+
116+
inlineCodeRegex.lastIndex = 0;
117+
let inlineMatch = inlineCodeRegex.exec(line);
118+
while (inlineMatch) {
119+
candidates.push({ lineNumber, text: inlineMatch[1] });
120+
inlineMatch = inlineCodeRegex.exec(line);
121+
}
122+
}
123+
124+
return candidates;
125+
}
126+
84127
function findInvalidCommands(files, validPairs, validWorkflows) {
85128
const validTopLevel = new Set(['mcp', 'tools', 'daemon']);
86129
const validDaemonActions = new Set(['status', 'start', 'stop', 'restart', 'list']);
@@ -92,12 +135,12 @@ function findInvalidCommands(files, validPairs, validWorkflows) {
92135
for (const absoluteFilePath of files) {
93136
const relativePath = path.relative(repoRoot, absoluteFilePath) || absoluteFilePath;
94137
const content = readFileSync(absoluteFilePath, 'utf8');
95-
const lines = content.split(/\r?\n/u);
138+
const candidates = extractCommandCandidates(content);
96139

97-
for (let lineNumber = 1; lineNumber <= lines.length; lineNumber += 1) {
98-
const line = lines[lineNumber - 1];
140+
for (const candidate of candidates) {
141+
const { lineNumber, text } = candidate;
99142
commandRegex.lastIndex = 0;
100-
let match = commandRegex.exec(line);
143+
let match = commandRegex.exec(text);
101144

102145
while (match) {
103146
const first = match[1];
@@ -118,7 +161,7 @@ function findInvalidCommands(files, validPairs, validWorkflows) {
118161
findings.push(`${relativePath}:${lineNumber}: ${command}`);
119162
}
120163

121-
match = commandRegex.exec(line);
164+
match = commandRegex.exec(text);
122165
}
123166
}
124167
}

0 commit comments

Comments
 (0)