Skip to content

feat(skills): custom skills from user folders, config paths and --skill-file (#79) - #91

Merged
karthickmk2001 merged 2 commits into
mainfrom
feat/79-custom-skills
Nov 11, 2026
Merged

karthickmk2001 merged 2 commits into
mainfrom
feat/79-custom-skills

Conversation

@karthickmk2001

Copy link
Copy Markdown
Collaborator

Closes #79

What does this change, and why?

Users can add a skill by dropping a Markdown file in a folder. It shows up in secfoo run --skill, --help, the dashboard and assessments, with no code change and no rebuild. The guide is in docs/SKILLS.md, with two complete examples in examples/skills/.

Where skills load from (skills/loader.py), all merged:

  1. Built-ins.
  2. $SECFOO_HOME/skills/*.md.
  3. [skills] paths in config.
  4. SECFOO_SKILLS_PATH (os.pathsep-separated).
  5. secfoo run --skill-file ./x.md.

Never from the target. None of these locations comes from the scan target. A test plants .secfoo/skills/ and skills/ in a target, chdirs into it, and checks that neither is loaded and that execute_runs refuses the skill id.

File format: the built-ins' frontmatter, plus optional output and category, and # comment lines.

  • output: generic (the default) uses the generic contract.
  • output: <built-in id> reuses that skill's output contract and findings lifecycle. runner.py now dispatches on skill.output_type instead of the skill id, so output: sast findings land in sast_findings and the SAST dashboard.
  • One deliberate safety rule: a custom run only adds and updates findings, via close_stale=False. Its report covers only what that review looked at, so it must never close findings the built-in scan recorded. A test covers this.
  • Secfoo still adds its safety preamble, target, depth guidance and output contract around the custom body, so a skill can't remove them.

Validation and failure handling:

  • id must be lowercase kebab-case, version an integer, output generic or a built-in id, and the body non-empty.
  • Clashes with a built-in or another user skill: the user skill is skipped with a warning naming both files. Custom skills can't override built-ins.
  • Malformed user skills are skipped with a one-line warning, so every other command keeps working. Built-ins still fail hard.
  • No restart needed: the user folders' mtimes are checked on each load, so secfoo serve picks up new or edited files. The sidebar global is now evaluated per render rather than once at import.

CLI: secfoo skills list | new <id> | validate [PATH...] | show <id> [--target], as specified. validate exits 1 on any problem, and CI now runs secfoo skills validate examples/skills. secfoo run accepts --skill-file, and exits 2 if neither --skill nor --skill-file is given.

Traceability: runs.skill_version, skill_source (builtin / user) and skill_sha256 (hash of the definition file) are added to schema.sql, with an idempotent migration in _RUNS_MIGRATIONS. They're recorded on every run and shown in the run receipt on the run page.

Dashboard:

  • A Custom skills sidebar section, grouped by category. It only appears when custom skills exist.
  • A Custom badge in the assessment run's skill picker.
  • Generic custom skills use the existing generic activity page.

Test isolation: a new autouse fixture in tests/conftest.py points SECFOO_HOME, the config path and SECFOO_SKILLS_PATH at an empty folder, so a developer's own skills can't leak into tests.

Not in this PR: pushing custom-skill runs to the enterprise portal with their output type. The portal ingest still dispatches by skill id. Happy to follow up.

Testing

  • pytest tests passes. 1052 passed locally. The only failures are the 4 existing Windows-only POSIX permission and symlink tests that fail on main too; Linux CI is unaffected.
  • ruff check . adds no new findings in changed files.
  • Added tests/test_custom_skills.py (31 tests) for everything the issue's Done when lists:
    • loading from the user folder, config paths, SECFOO_SKILLS_PATH and --skill-file
    • mtime reload
    • clashes with a built-in, and between two user folders
    • six kinds of malformed user skill being skipped while secfoo skills list keeps working
    • built-ins failing hard
    • the target's .secfoo/skills/ being ignored
    • the prompt keeping secfoo's preamble and contract
    • output: sast landing in sast_findings, and a custom SAST run not closing built-in findings
    • runs recording version, source and sha256, for custom and built-in skills
    • the migration on an existing DB
    • all four skills commands and the bundled examples validating
    • the sidebar grouping, the activity page and the picker badge

🤖 Generated with Claude Code

…ll-file (#79)

Load skills from ~/.secfoo/skills, [skills] paths, SECFOO_SKILLS_PATH and secfoo run --skill-file -- never from the scan target. Custom skills may reuse a built-in's output contract and findings lifecycle (output: sast) without ever closing the built-in scan's findings. Malformed or clashing user skills are skipped with a warning naming the files; built-ins still fail hard; user folders reload on mtime change. Adds secfoo skills list/new/validate/show, runs.skill_version/skill_source/skill_sha256 with a migration, a Custom skills sidebar group and picker badge, docs/SKILLS.md, examples/skills/ and a CI validation step.

Closes #79

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
CI's 80-column terminal wrapped long temp paths, splitting phrases like 'missing frontmatter' across lines. Run the CLI wide and compare whitespace-collapsed output.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@karthickmk2001
karthickmk2001 merged commit 9bed9f5 into main Nov 11, 2026
6 checks passed
@karthickmk2001
karthickmk2001 deleted the feat/79-custom-skills branch November 11, 2026 21:41
@github-actions

Copy link
Copy Markdown

Leaderboard update

karthickmk2001 now has 31 points (rank #1, Bronze). 19 more to reach Silver.

Full leaderboard

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Custom skills: add your own skills without changing code

1 participant