diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..6313b56 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +* text=auto eol=lf diff --git a/README.md b/README.md index c380b3e..5e2a043 100644 --- a/README.md +++ b/README.md @@ -339,7 +339,7 @@ agent-skills/ │ ├── shipping-and-launch/ # Ship │ └── using-agent-skills/ # Meta: how to use this pack ├── agents/ # 4 specialist personas -├── references/ # 5 supplementary checklists +├── references/ # 7 supplementary checklists ├── hooks/ # Session lifecycle hooks ├── .claude/commands/ # 8 slash commands (Claude Code) ├── .gemini/commands/ # 8 slash commands (Gemini CLI) diff --git a/docs/copilot-setup.md b/docs/copilot-setup.md index 0105228..cfe137e 100644 --- a/docs/copilot-setup.md +++ b/docs/copilot-setup.md @@ -7,7 +7,7 @@ Copilot supports creating agent skills using a `.github/skills`, `.claude/skills`, or `.agents/skills` directory in your repository. ```bash -mkdir -p .github +mkdir -p .github/skills/test-driven-development .github/skills/code-review-and-quality # Create files for essential skills cat /path/to/agent-skills/skills/test-driven-development/SKILL.md > .github/skills/test-driven-development/SKILL.md diff --git a/docs/getting-started.md b/docs/getting-started.md index 740f2df..664c1ee 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -132,6 +132,9 @@ The `references/` directory contains supplementary checklists: | `performance-checklist.md` | performance-optimization | | `security-checklist.md` | security-and-hardening | | `accessibility-checklist.md` | frontend-ui-engineering | +| `definition-of-done.md` | all skills / every change | +| `observability-checklist.md` | observability-and-instrumentation | +| `orchestration-patterns.md` | context-engineering | Load a reference when you need detailed patterns beyond what the skill covers. diff --git a/scripts/run-evals.js b/scripts/run-evals.js index b5753de..9272dc1 100644 --- a/scripts/run-evals.js +++ b/scripts/run-evals.js @@ -154,7 +154,7 @@ function loadSkills() { const file = path.join(SKILLS_DIR, dir, 'SKILL.md'); if (!fs.existsSync(file)) continue; const src = fs.readFileSync(file, 'utf8'); - const m = src.match(/^---\n([\s\S]*?)\n---/); + const m = src.match(/^---[ \t]*\r?\n([\s\S]*?)\r?\n---[ \t]*(?:\r?\n|$)/); if (!m) continue; const name = (m[1].match(/^name:\s*(.+)$/m) || [])[1]; const description = (m[1].match(/^description:\s*(.+)$/m) || [])[1]; @@ -178,6 +178,19 @@ function loadCases() { }); } +function resolveFixturePath(root, rel) { + if (path.isAbsolute(rel)) { + throw new Error(`fixture path must be relative: ${rel}`); + } + const resolvedRoot = path.resolve(root); + const resolvedPath = path.resolve(resolvedRoot, rel); + const back = path.relative(resolvedRoot, resolvedPath); + if (back === '' || back === '..' || back.startsWith(`..${path.sep}`) || path.isAbsolute(back)) { + throw new Error(`fixture path escapes workspace: ${rel}`); + } + return resolvedPath; +} + // ---------- tier 2 ---------- function runDeterministic() { @@ -331,11 +344,11 @@ function materializeWorkspace(ev) { // agent has real code to operate on rather than describing what it would do. const workspace = fs.mkdtempSync(path.join(os.tmpdir(), 'agent-skills-eval-')); for (const rel of ev.files || []) { - const src = path.join(FIXTURES_DIR, rel); + const src = resolveFixturePath(FIXTURES_DIR, rel); if (!fs.existsSync(src)) { throw new Error(`fixture listed in files[] not found: evals/fixtures/${rel}`); } - const dest = path.join(workspace, rel); + const dest = resolveFixturePath(workspace, rel); fs.mkdirSync(path.dirname(dest), { recursive: true }); fs.cpSync(src, dest, { recursive: true }); } diff --git a/scripts/validate-commands.js b/scripts/validate-commands.js index 32f34f7..a9ff439 100644 --- a/scripts/validate-commands.js +++ b/scripts/validate-commands.js @@ -145,6 +145,21 @@ function main() { const descGemini = byTool.gemini[tomlStem]; const descAgy = byTool.antigravity[tomlStem]; + const malformed = [ + ['.claude/commands', byTool.claude, claudeStem], + ['.gemini/commands', byTool.gemini, tomlStem], + ['commands/', byTool.antigravity, tomlStem], + ].filter(([, commands, stem]) => Object.prototype.hasOwnProperty.call(commands, stem) && commands[stem] == null); + + if (malformed.length) { + console.log(` ✗ ${claudeStem}`); + for (const [toolDir, , stem] of malformed) { + console.log(` ${toolDir}/${stem} — missing or malformed description`); + } + errors++; + continue; + } + if (descClaude == null || descGemini == null || descAgy == null) { // Missing file already flagged by parity check continue;