diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..ed6192d --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,28 @@ +# Changelog + +All notable changes to this project are documented here. + +## [Unreleased] + +### Added +- `node:test` test suite covering the CLI, skill parsing, catalog generation, and the drift guard (`npm test`). +- `lib/skill-schema.js` as the single source of frontmatter field rules. +- `scripts/check-catalog-drift.js` (`npm run check:catalog`) — CI fails if committed catalog artifacts are stale. +- `install --force` to overwrite already-installed skills. +- `AG_SKILLS_DIR` environment variable to override the install destination (reported by `doctor`). +- Validator now checks that backticked referenced files (`resources/`, `references/`, `assets/`, `scripts/`) exist. +- `SECURITY.md` private vulnerability disclosure channel. + +### Changed +- `install` now skips already-installed skills by default (use `--force` to overwrite) and reports an honest installed/skipped/failed summary. +- `install` exits non-zero when nothing could be installed; no longer prints "Installation complete!" on total failure. +- `update` exits non-zero on real failures (no installation, invalid name, not installed). +- Search scoring now also matches the catalog `triggers` field. +- Corrupt `bundles.json`/`aliases.json` now produce an explicit warning instead of degrading silently. +- `detectCategory` no longer treats generic "compliance" as `security`. +- `--write-baseline` now requires `--yes` and prints what it would grandfather. + +### Fixed +- `listSkillIds`/`readSkill` no longer crash on a skill directory missing `SKILL.md`; `build-catalog` surfaces frontmatter parse warnings. +- Removed 228 dangling helper-file references across 165 skills. +- Removed dead code: `parseInlineList`/`stripQuotes` exports and the unreachable `name !== id` alias branch. diff --git a/SECURITY.md b/SECURITY.md index 62d56a5..08fdb3c 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -12,4 +12,6 @@ This repository ships skills that may recommend terminal commands or infrastruct ## Reporting -If you discover a security issue in this repository, please open an issue with details and reproduction steps. +For routine, non-sensitive issues, open a GitHub issue with details and reproduction steps. + +For sensitive vulnerabilities (anything that could be exploited before a fix ships), report privately by email to **yudhi@rmyndharis.com** instead of opening a public issue. Include reproduction steps and impact. Please allow a reasonable window for a fix before any public disclosure. diff --git a/scripts/validate-skills.js b/scripts/validate-skills.js index deb6fab..6a48d2e 100644 --- a/scripts/validate-skills.js +++ b/scripts/validate-skills.js @@ -20,6 +20,8 @@ const isStrict = process.argv.includes('--strict') const writeBaseline = process.argv.includes('--write-baseline') || process.env.WRITE_BASELINE === '1' || process.env.WRITE_BASELINE === 'true'; +const confirmWriteBaseline = process.argv.includes('--yes') + || process.env.WRITE_BASELINE_YES === '1'; function isPlainObject(value) { return value && typeof value === 'object' && !Array.isArray(value); @@ -250,15 +252,27 @@ addStrictSectionErrors('Instructions', missingInstructionsSection, baselineInstr addStrictSectionErrors(`SKILL.md line count <= ${LIMITS.skillLines}`, longFiles, baselineLongFile); if (writeBaseline) { - const baselineData = { - generatedAt: new Date().toISOString(), + const toGrandfather = { useSection: [...missingUseSection].sort(), doNotUseSection: [...missingDoNotUseSection].sort(), instructionsSection: [...missingInstructionsSection].sort(), longFile: [...longFiles].sort(), }; - fs.writeFileSync(BASELINE_PATH, JSON.stringify(baselineData, null, 2)); - console.log(`Baseline written to ${BASELINE_PATH}`); + const totalGrandfathered = Object.values(toGrandfather).reduce((sum, arr) => sum + arr.length, 0); + + console.warn('\n⚠ --write-baseline will grandfather the following soft violations,'); + console.warn(' silencing them in strict mode (CI). Review carefully:'); + for (const [key, arr] of Object.entries(toGrandfather)) { + console.warn(` - ${key}: ${arr.length}`); + } + + if (!confirmWriteBaseline) { + console.warn(`\nNot written. Re-run with --yes to grandfather ${totalGrandfathered} violation(s).`); + } else { + const baselineData = { generatedAt: new Date().toISOString(), ...toGrandfather }; + fs.writeFileSync(BASELINE_PATH, JSON.stringify(baselineData, null, 2)); + console.log(`Baseline written to ${BASELINE_PATH}`); + } } if (warnings.length) { diff --git a/tests/validate-baseline.test.js b/tests/validate-baseline.test.js new file mode 100644 index 0000000..9e128ad --- /dev/null +++ b/tests/validate-baseline.test.js @@ -0,0 +1,23 @@ +const test = require('node:test'); +const assert = require('node:assert'); +const { spawnSync } = require('node:child_process'); +const fs = require('node:fs'); +const path = require('node:path'); + +const ROOT = path.resolve(__dirname, '..'); +const VALIDATOR = path.join(ROOT, 'scripts', 'validate-skills.js'); +const BASELINE = path.join(ROOT, 'validation-baseline.json'); + +function runValidator(args) { + const result = spawnSync('node', [VALIDATOR, ...args], { encoding: 'utf8', cwd: ROOT }); + return { code: result.status || 0, out: (result.stdout || '') + (result.stderr || '') }; +} + +test('--write-baseline without --yes does NOT write the baseline file', () => { + const before = fs.readFileSync(BASELINE, 'utf8'); + const r = runValidator(['--write-baseline']); + const after = fs.readFileSync(BASELINE, 'utf8'); + assert.strictEqual(after, before, 'baseline must be unchanged without --yes'); + assert.ok(/Not written/.test(r.out), 'should explain it was not written'); + assert.ok(/--yes/.test(r.out), 'should mention the --yes flag'); +});