mirror of
https://github.com/affaan-m/ECC.git
synced 2026-10-17 13:45:34 +02:00
fix(ci): flag CLAUDE_PROJECT_DIR in skill fences with Codex rewrite guidance
This commit is contained in:
@@ -26,7 +26,9 @@ sees `.agents/skills/`. This script is the cross-tree gate:
|
||||
|
||||
1. Claude-only substitutions in executable `bash` / `sh` / `zsh` fences
|
||||
(`$CLAUDE_SESSION_ID`, `$CLAUDE_SKILL_DIR`, `$CLAUDE_PLUGIN_ROOT`,
|
||||
`$ARGUMENTS`) fail. Codex leaves those tokens as literal text.
|
||||
`$CLAUDE_PROJECT_DIR`, `$ARGUMENTS`) fail. Codex leaves those tokens
|
||||
as literal text. In a Codex copy, rewrite `$CLAUDE_PROJECT_DIR` to
|
||||
`$(pwd)` or an explicit path.
|
||||
2. A Codex copy may only use `name`, `description`, `metadata`, `license`,
|
||||
and `allowed-tools`. `origin: ECC` may stay on the canonical `skills/`
|
||||
copy and must be stripped on the Codex copy.
|
||||
@@ -75,6 +77,7 @@ forbidden pattern:
|
||||
echo "$CLAUDE_SESSION_ID"
|
||||
ls "$CLAUDE_SKILL_DIR"
|
||||
node "$CLAUDE_PLUGIN_ROOT/scripts/setup.js" $ARGUMENTS
|
||||
cd "$CLAUDE_PROJECT_DIR"
|
||||
```
|
||||
|
||||
Do not add `version:` or `origin:` to a Codex copy under `.agents/skills/`.
|
||||
@@ -86,7 +89,9 @@ every `skills/` entry to have a Cursor copy.
|
||||
- Keep canonical `origin: ECC` if the CONTRIBUTING template calls for it.
|
||||
- Drop `origin`, `version`, and `argument-hint` on the Codex copy.
|
||||
- Show host-portable commands in bash fences (`npx`, `ecc`, or a resolved
|
||||
root). Mention Claude env vars in prose, not in executable fences.
|
||||
root). Rewrite `$CLAUDE_PROJECT_DIR` in Codex copies to `$(pwd)` or an
|
||||
explicit path. Mention other Claude env vars in prose, not in executable
|
||||
fences.
|
||||
- Run this script before `npm test` when the change is a new skill.
|
||||
- Use `--inventory` to see checklist warning volume without failing the tree.
|
||||
|
||||
|
||||
@@ -36,5 +36,5 @@ node "$ECC_ROOT/scripts/ci/validate-skill-host-compat.js" --inventory
|
||||
|
||||
1. Run `scripts/ci/validate-skill-host-compat.js` from the ECC root
|
||||
2. Show ERROR and WARN lines to the user
|
||||
3. For substitution errors, move Claude env vars out of bash fences
|
||||
3. For substitution errors, move Claude env vars out of bash fences. Rewrite `$CLAUDE_PROJECT_DIR` to `$(pwd)` or an explicit path
|
||||
4. For Codex-key errors, drop `origin` / `version` / `argument-hint` on the copy
|
||||
|
||||
@@ -5,7 +5,9 @@
|
||||
* Checks:
|
||||
* 1. Claude-only substitutions in executable bash/sh/zsh fences
|
||||
* (${CLAUDE_SESSION_ID}, ${CLAUDE_SKILL_DIR}, ${CLAUDE_PLUGIN_ROOT},
|
||||
* $ARGUMENTS). Codex and a bare shell leave those tokens literal.
|
||||
* ${CLAUDE_PROJECT_DIR}, $ARGUMENTS). Codex and a bare shell leave
|
||||
* those tokens literal. Rewrite ${CLAUDE_PROJECT_DIR} in Codex copies
|
||||
* to $(pwd) or an explicit path.
|
||||
* 2. Codex-copy frontmatter allowlist on .agents/skills/<name>/SKILL.md
|
||||
* (name, description, metadata, license, allowed-tools). origin/version
|
||||
* may remain on the canonical skills/ copy.
|
||||
@@ -47,6 +49,8 @@ const CLAUDE_SUBSTITUTION_PATTERNS = [
|
||||
{ token: '$CLAUDE_SKILL_DIR', regex: /\$CLAUDE_SKILL_DIR\b/g },
|
||||
{ token: '${CLAUDE_PLUGIN_ROOT}', regex: /\$\{CLAUDE_PLUGIN_ROOT\}/g },
|
||||
{ token: '$CLAUDE_PLUGIN_ROOT', regex: /\$CLAUDE_PLUGIN_ROOT\b/g },
|
||||
{ token: '${CLAUDE_PROJECT_DIR}', regex: /\$\{CLAUDE_PROJECT_DIR\}/g },
|
||||
{ token: '$CLAUDE_PROJECT_DIR', regex: /\$CLAUDE_PROJECT_DIR\b/g },
|
||||
{ token: '$ARGUMENTS', regex: /\$ARGUMENTS\b|\$\{ARGUMENTS\}/g },
|
||||
// Claude skill form ${1}. Bare $1 is handled in findSubstitutionTokens so
|
||||
// POSIX `VAR=$1` and Perl/awk `$1 < 80` are not treated as Claude args.
|
||||
@@ -231,6 +235,14 @@ function pushFinding(findings, finding) {
|
||||
findings.push(finding);
|
||||
}
|
||||
|
||||
function substitutionMessage(skillName, fenceLang, token) {
|
||||
const base = `${relSkillMd(skillName)} - ${fenceLang} fence contains Claude-only substitution ${token}`;
|
||||
if (token === '${CLAUDE_PROJECT_DIR}' || token === '$CLAUDE_PROJECT_DIR') {
|
||||
return `${base}; rewrite to $(pwd) or an explicit path`;
|
||||
}
|
||||
return base;
|
||||
}
|
||||
|
||||
function inspectCanonicalSkill(skillName, content, findings) {
|
||||
const { frontmatter, body } = stripFrontmatter(content);
|
||||
const values = frontmatter.present ? inspectFrontmatter(frontmatter.lines).values : {};
|
||||
@@ -289,7 +301,7 @@ function inspectCanonicalSkill(skillName, content, findings) {
|
||||
code: 'substitution',
|
||||
skill: skillName,
|
||||
token,
|
||||
message: `${relSkillMd(skillName)} - ${fence.lang} fence contains Claude-only substitution ${token}`,
|
||||
message: substitutionMessage(skillName, fence.lang, token),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,7 +27,9 @@ sees `.agents/skills/`. This script is the cross-tree gate:
|
||||
|
||||
1. Claude-only substitutions in executable `bash` / `sh` / `zsh` fences
|
||||
(`${CLAUDE_SESSION_ID}`, `${CLAUDE_SKILL_DIR}`, `${CLAUDE_PLUGIN_ROOT}`,
|
||||
`$ARGUMENTS`) fail. Codex leaves those tokens as literal text.
|
||||
`${CLAUDE_PROJECT_DIR}`, `$ARGUMENTS`) fail. Codex leaves those tokens
|
||||
as literal text. In a Codex copy, rewrite `${CLAUDE_PROJECT_DIR}` to
|
||||
`$(pwd)` or an explicit path.
|
||||
2. A Codex copy may only use `name`, `description`, `metadata`, `license`,
|
||||
and `allowed-tools`. `origin: ECC` may stay on the canonical `skills/`
|
||||
copy and must be stripped on the Codex copy.
|
||||
@@ -76,6 +78,7 @@ forbidden pattern:
|
||||
echo "${CLAUDE_SESSION_ID}"
|
||||
ls "${CLAUDE_SKILL_DIR}"
|
||||
node "${CLAUDE_PLUGIN_ROOT}/scripts/setup.js" $ARGUMENTS
|
||||
cd "${CLAUDE_PROJECT_DIR}"
|
||||
```
|
||||
|
||||
Do not add `version:` or `origin:` to a Codex copy under `.agents/skills/`.
|
||||
@@ -87,7 +90,9 @@ every `skills/` entry to have a Cursor copy.
|
||||
- Keep canonical `origin: ECC` if the CONTRIBUTING template calls for it.
|
||||
- Drop `origin`, `version`, and `argument-hint` on the Codex copy.
|
||||
- Show host-portable commands in bash fences (`npx`, `ecc`, or a resolved
|
||||
root). Mention Claude env vars in prose, not in executable fences.
|
||||
root). Rewrite `${CLAUDE_PROJECT_DIR}` in Codex copies to `$(pwd)` or an
|
||||
explicit path. Mention other Claude env vars in prose, not in executable
|
||||
fences.
|
||||
- Run this script before `npm test` when the change is a new skill.
|
||||
- Use `--inventory` to see checklist warning volume without failing the tree.
|
||||
|
||||
|
||||
@@ -150,6 +150,75 @@ check('fail: bash fence contains ${CLAUDE_SESSION_ID}', () => {
|
||||
}
|
||||
});
|
||||
|
||||
check('fail: bash fence contains ${CLAUDE_PROJECT_DIR}', () => {
|
||||
const root = createRoot();
|
||||
try {
|
||||
writeSkill(
|
||||
root,
|
||||
'proj',
|
||||
`${checklistBody('Proj')}\n## Code Examples\n\n\`\`\`bash\ncd "\${CLAUDE_PROJECT_DIR}"\n\`\`\`\n`
|
||||
);
|
||||
const result = runValidator(root);
|
||||
assert.strictEqual(result.status, 1);
|
||||
assert.ok(result.stderr.includes('ERROR:'));
|
||||
assert.ok(result.stderr.includes('proj'));
|
||||
assert.ok(result.stderr.includes('${CLAUDE_PROJECT_DIR}'));
|
||||
assert.ok(result.stderr.includes('rewrite to $(pwd) or an explicit path'));
|
||||
} finally {
|
||||
cleanup(root);
|
||||
}
|
||||
});
|
||||
|
||||
check('fail: $CLAUDE_PROJECT_DIR used as a substitution in a bash fence', () => {
|
||||
const root = createRoot();
|
||||
try {
|
||||
writeSkill(
|
||||
root,
|
||||
'proj',
|
||||
`${checklistBody('Proj')}\n## Code Examples\n\n\`\`\`sh\ncd "$CLAUDE_PROJECT_DIR"\n\`\`\`\n`
|
||||
);
|
||||
const result = runValidator(root);
|
||||
assert.strictEqual(result.status, 1);
|
||||
assert.ok(result.stderr.includes('$CLAUDE_PROJECT_DIR'));
|
||||
assert.ok(result.stderr.includes('rewrite to $(pwd) or an explicit path'));
|
||||
} finally {
|
||||
cleanup(root);
|
||||
}
|
||||
});
|
||||
|
||||
check('bash fence $(pwd) is accepted in place of CLAUDE_PROJECT_DIR', () => {
|
||||
const root = createRoot();
|
||||
try {
|
||||
writeSkill(
|
||||
root,
|
||||
'proj',
|
||||
`${checklistBody('Proj')}\n## Code Examples\n\n\`\`\`bash\ncd "$(pwd)"\n\`\`\`\n`
|
||||
);
|
||||
const result = runValidator(root);
|
||||
assert.strictEqual(result.status, 0, result.stderr);
|
||||
assert.ok(!result.stderr.includes('ERROR:'));
|
||||
assert.ok(!result.stderr.includes('CLAUDE_PROJECT_DIR'));
|
||||
} finally {
|
||||
cleanup(root);
|
||||
}
|
||||
});
|
||||
|
||||
check('prose mention of CLAUDE_PROJECT_DIR does not fail', () => {
|
||||
const root = createRoot();
|
||||
try {
|
||||
writeSkill(
|
||||
root,
|
||||
'prose',
|
||||
`${checklistBody('Prose')}\nThe project root is \`CLAUDE_PROJECT_DIR\`, then cwd.\n`
|
||||
);
|
||||
const result = runValidator(root);
|
||||
assert.strictEqual(result.status, 0, result.stderr);
|
||||
assert.ok(!result.stderr.includes('ERROR:'));
|
||||
} finally {
|
||||
cleanup(root);
|
||||
}
|
||||
});
|
||||
|
||||
check('fail: $ARGUMENTS used as a substitution in a bash fence', () => {
|
||||
const root = createRoot();
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user