Skip to content

Commit e167cbb

Browse files
committed
fix(cross-platform): add hardcoded path detection and validation
Addresses PR #159 feedback - cross-platform compatibility enforcement ## Changes 1. **Fixed hardcoded path in next-task command** (line 283) - Was: .claude/flow.json - Now: ${stateDir}/flow.json (platform-aware) 2. **Created hardcoded paths checker** (scripts/check-hardcoded-paths.js) - Scans plugins for hardcoded .claude/, .opencode/, .codex/ paths - Excludes docs/examples (SKILL.md in enhance/, RESEARCH.md, etc.) - Reports file:line with fix guidance - Exit code 1 if issues found 3. **Added validation to build process** - package.json: Added validate:paths to validate script - CI: Added hardcoded paths check step (after consistency validation) - Runs on all PRs and pushes to main 4. **Enhanced pre-push hook** (scripts/setup-hooks.js) - Detects modified agents/skills/hooks/prompts - Warns about CLAUDE.md Critical Rule #7 (/enhance requirement) - Prompts user: "Have you run /enhance? (y/N)" - Blocks push unless confirmed (skip with --no-verify) - Existing release tag validation preserved ## Testing - ✅ Hardcoded paths checker runs clean on current codebase - ✅ Excludes legitimate documentation examples - ✅ Pre-push hook installed successfully - ✅ CI step added successfully CLAUDE.md Critical Rule #7 compliance addressed.
1 parent d3ac6c9 commit e167cbb

5 files changed

Lines changed: 244 additions & 4 deletions

File tree

.github/workflows/ci.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,5 +31,8 @@ jobs:
3131
- name: Validate repo consistency
3232
run: npm run validate
3333

34+
- name: Check for hardcoded platform paths
35+
run: node scripts/check-hardcoded-paths.js
36+
3437
- name: MCP smoke test
3538
run: node scripts/mcp-smoke-test.js

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,11 @@
1111
"test": "jest",
1212
"test:watch": "jest --watch",
1313
"test:coverage": "jest --coverage",
14-
"validate": "npm run validate:plugins && npm run validate:cross-platform && npm run validate:consistency",
14+
"validate": "npm run validate:plugins && npm run validate:cross-platform && npm run validate:consistency && npm run validate:paths",
1515
"validate:plugins": "node scripts/validate-plugins.js",
1616
"validate:cross-platform": "node scripts/validate-cross-platform.js",
1717
"validate:consistency": "node scripts/validate-repo-consistency.js",
18+
"validate:paths": "node scripts/check-hardcoded-paths.js",
1819
"detect": "node lib/platform/detect-platform.js",
1920
"verify": "node lib/platform/verify-tools.js",
2021
"prepare": "node scripts/setup-hooks.js"

plugins/next-task/commands/next-task.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -280,7 +280,8 @@ After docs-updater completes, EXPLICITLY invoke /ship:
280280

281281
```javascript
282282
console.log(`Task #${state.task.id} passed all validation. Invoking /ship...`);
283-
await Task({ subagent_type: "ship:ship", prompt: "Ship the task. State file: .claude/flow.json" });
283+
const stateDir = workflowState.getStateDir(); // Returns platform-aware state directory
284+
await Task({ subagent_type: "ship:ship", prompt: `Ship the task. State file: ${stateDir}/flow.json` });
284285
```
285286

286287
**/ship responsibilities:**

scripts/check-hardcoded-paths.js

Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
#!/usr/bin/env node
2+
/**
3+
* Check for Hardcoded Platform Paths
4+
* Scans plugins for hardcoded .claude/, .opencode/, .codex/ paths
5+
*
6+
* CRITICAL: Per CLAUDE.md cross-platform requirement - all 3 platforms must work
7+
*
8+
* Usage: node scripts/check-hardcoded-paths.js
9+
* Exit code: 0 if clean, 1 if issues found
10+
*
11+
* @author Avi Fenesh
12+
* @license MIT
13+
*/
14+
15+
const fs = require('fs');
16+
const path = require('path');
17+
18+
const REPO_ROOT = path.resolve(__dirname, '..');
19+
20+
// Patterns to detect hardcoded platform paths
21+
const HARDCODED_PATTERNS = [
22+
{
23+
pattern: /\.claude\/(?!.*\(example\)|.*for example|.*Platform|.*State directory)/,
24+
platform: '.claude/',
25+
severity: 'ERROR'
26+
},
27+
{
28+
pattern: /\.opencode\/(?!.*\(example\)|.*for example|.*Platform|.*State directory)/,
29+
platform: '.opencode/',
30+
severity: 'ERROR'
31+
},
32+
{
33+
pattern: /\.codex\/(?!.*\(example\)|.*for example|.*Platform|.*State directory)/,
34+
platform: '.codex/',
35+
severity: 'ERROR'
36+
}
37+
];
38+
39+
// Files to exclude from checks (docs, examples, research)
40+
const EXCLUDE_PATTERNS = [
41+
/RESEARCH\.md$/,
42+
/CLAUDE\.md$/,
43+
/AGENTS\.md$/,
44+
/README\.md$/,
45+
/INSTALLATION\.md$/,
46+
/CROSS_PLATFORM\.md$/,
47+
/ARCHITECTURE\.md$/,
48+
/MCP-TOOLS\.md$/,
49+
/examples?\//,
50+
/\.git\//,
51+
/node_modules\//,
52+
/__tests__\//,
53+
/\.json$/,
54+
// Enhance skills document platform differences - OK to have hardcoded paths in docs
55+
/plugins\/enhance\/skills\/.*\/SKILL\.md$/
56+
];
57+
58+
// Lines that are OK to have hardcoded paths (documentation examples)
59+
const SAFE_CONTEXTS = [
60+
'State stored in',
61+
'State directory:',
62+
'example',
63+
'Example',
64+
'Platform',
65+
'| State Dir |',
66+
'State Dir |',
67+
'detected by',
68+
'Detected by',
69+
'Override with',
70+
'Personal:',
71+
'Project:',
72+
'User settings',
73+
'Project settings',
74+
'Local settings',
75+
'| Claude Code |',
76+
'| OpenCode |',
77+
'| Codex |',
78+
'Claude Code:',
79+
'OpenCode:',
80+
'Codex CLI:',
81+
'Codex:',
82+
"Don't hardcode",
83+
'Support ',
84+
'~/.claude/',
85+
'~/.opencode/',
86+
'~/.codex/',
87+
'MCP in',
88+
'or `~/',
89+
'$CLAUDE_PROJECT_DIR'
90+
];
91+
92+
function shouldExcludeFile(filePath) {
93+
// Normalize path separators for cross-platform regex matching
94+
const normalizedPath = filePath.replace(/\\/g, '/');
95+
return EXCLUDE_PATTERNS.some(pattern => pattern.test(normalizedPath));
96+
}
97+
98+
function isSafeContext(line) {
99+
return SAFE_CONTEXTS.some(ctx => line.includes(ctx));
100+
}
101+
102+
function scanFile(filePath) {
103+
const content = fs.readFileSync(filePath, 'utf8');
104+
const lines = content.split('\n');
105+
const issues = [];
106+
107+
lines.forEach((line, index) => {
108+
// Skip if line is in safe context
109+
if (isSafeContext(line)) {
110+
return;
111+
}
112+
113+
HARDCODED_PATTERNS.forEach(({ pattern, platform, severity }) => {
114+
if (pattern.test(line)) {
115+
issues.push({
116+
file: path.relative(REPO_ROOT, filePath),
117+
line: index + 1,
118+
platform,
119+
severity,
120+
content: line.trim()
121+
});
122+
}
123+
});
124+
});
125+
126+
return issues;
127+
}
128+
129+
function scanDirectory(dir, issues = []) {
130+
const entries = fs.readdirSync(dir, { withFileTypes: true });
131+
132+
for (const entry of entries) {
133+
const fullPath = path.join(dir, entry.name);
134+
135+
if (shouldExcludeFile(fullPath)) {
136+
continue;
137+
}
138+
139+
if (entry.isDirectory()) {
140+
scanDirectory(fullPath, issues);
141+
} else if (entry.isFile() && entry.name.endsWith('.md')) {
142+
const fileIssues = scanFile(fullPath);
143+
issues.push(...fileIssues);
144+
}
145+
}
146+
147+
return issues;
148+
}
149+
150+
function formatIssue(issue) {
151+
return `${issue.severity}: ${issue.file}:${issue.line}
152+
Hardcoded path: ${issue.platform}
153+
Line: ${issue.content}
154+
Fix: Use platform-aware state directory variable (${issue.platform === '.claude/' ? 'STATE_DIR or stateDir' : 'stateDir'})
155+
`;
156+
}
157+
158+
// Main execution
159+
if (require.main === module) {
160+
console.log('[OK] Scanning for hardcoded platform paths...\n');
161+
162+
const pluginsDir = path.join(REPO_ROOT, 'plugins');
163+
const issues = scanDirectory(pluginsDir);
164+
165+
if (issues.length === 0) {
166+
console.log('[OK] No hardcoded platform paths found\n');
167+
console.log('All files use platform-aware state directory variables.');
168+
process.exit(0);
169+
}
170+
171+
console.error(`[ERROR] Found ${issues.length} hardcoded platform path(s):\n`);
172+
173+
issues.forEach(issue => {
174+
console.error(formatIssue(issue));
175+
});
176+
177+
console.error(`
178+
CLAUDE.md Critical Rule:
179+
> 3 platforms: Claude Code + OpenCode + Codex - ALL must work
180+
181+
Fix guide:
182+
1. Replace hardcoded paths with platform-aware variables:
183+
- In agents/commands: Use workflowState.getStateDir()
184+
- In prompts: Use \${stateDir} or \${STATE_DIR} template variables
185+
186+
2. Examples:
187+
BAD: await Task({ prompt: "State file: .claude/flow.json" });
188+
GOOD: await Task({ prompt: \`State file: \${stateDir}/flow.json\` });
189+
190+
BAD: State files in .claude/tasks.json
191+
GOOD: State files in {stateDir}/tasks.json
192+
193+
See: checklists/cross-platform-compatibility.md
194+
`);
195+
196+
process.exit(1);
197+
}
198+
199+
module.exports = { scanDirectory, scanFile, HARDCODED_PATTERNS };

scripts/setup-hooks.js

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,44 @@ fi
2424
`;
2525

2626
const prePushHook = `#!/bin/sh
27-
# Block version tag pushes until release checklist validation passes
28-
# See: checklists/release.md
27+
# Pre-push validations:
28+
# 1. Warn if agents/skills/hooks/prompts modified (run /enhance)
29+
# 2. Block version tag pushes until release checklist passes
30+
# See: CLAUDE.md Critical Rule #7, checklists/release.md
31+
32+
# Check for modified agents/skills/hooks/prompts
33+
modified_files=$(git diff --name-only origin/\$(git remote show origin | grep "HEAD branch" | cut -d' ' -f5)..HEAD 2>/dev/null || git diff --name-only HEAD~1..HEAD)
34+
35+
agents_modified=$(echo "$modified_files" | grep -E "agents/.*\\.md$" || true)
36+
skills_modified=$(echo "$modified_files" | grep -E "skills/.*/SKILL\\.md$" || true)
37+
hooks_modified=$(echo "$modified_files" | grep -E "hooks/.*\\.md$" || true)
38+
prompts_modified=$(echo "$modified_files" | grep -E "prompts/.*\\.md$" || true)
39+
40+
if [ -n "$agents_modified" ] || [ -n "$skills_modified" ] || [ -n "$hooks_modified" ] || [ -n "$prompts_modified" ]; then
41+
echo ""
42+
echo "=============================================="
43+
echo " [WARN] Enhanced content modified"
44+
echo "=============================================="
45+
echo ""
46+
echo "CLAUDE.md Critical Rule #7 requires running /enhance"
47+
echo "on modified agents, skills, hooks, or prompts."
48+
echo ""
49+
echo "Modified files:"
50+
echo "$agents_modified$skills_modified$hooks_modified$prompts_modified"
51+
echo ""
52+
echo "ACTION REQUIRED:"
53+
echo " 1. Run: /enhance"
54+
echo " 2. Address any HIGH certainty findings"
55+
echo " 3. Push again"
56+
echo ""
57+
echo "Skip this check: git push --no-verify"
58+
echo ""
59+
read -p "Have you run /enhance? (y/N) " response
60+
if [ "$response" != "y" ] && [ "$response" != "Y" ]; then
61+
echo "[BLOCKED] Run /enhance first"
62+
exit 1
63+
fi
64+
fi
2965
3066
# Check if pushing a version tag (v*)
3167
pushing_tag=false

0 commit comments

Comments
 (0)