Sets up project-as-a-skill for any project. Use "link" to connect an existing skill folder. Use without arguments (or "new") to create a full project from scratch. Use whenever the user wants to set up a new project, initialize project tracking in Obsidian, link a vault folder to a repo, create checkpoint tracking, or scaffold project skills.
Resources
1Install
npx skillscat add vadirn/nix/project-setup Install via the SkillsCat registry.
Setup Project
Creates or links a project-as-a-skill.
Requires obsidian CLI (1.12+). If unavailable, the skill falls back to asking the user for vault paths manually.
Pseudocode
vault_root = discover_vault_root()
if command == "link":
link(vault_root)
elif command == "check-permissions":
check_permissions(vault_root)
else:
setup(vault_root) // handles "already exists" via duplicate checkLink command
Links an existing vault project folder to the current repo.
Pseudocode
link(vault_root):
path = ask "Vault path?" (e.g. "41 projects/visa-agent")
target = "<vault_root>/<path>"
if target/context.md does not exist:
error "No context.md found at <target>. Use /project-setup new to create one."
return
name = last segment of path // e.g. "visa-agent" from "41 projects/visa-agent"
title = infer from context.md heading or ask user
write_vault_config() // .claude/.vault.config.json in repo
write_thin_wrapper() // .claude/skills/<name>/SKILL.md in repo
register_in_settings() // add Skill(<name>), Skill(vault), Read, Bash(vault-cli *)
report linked, suggest /clearCheck-permissions command
Verifies a project skill has the right entries in .claude/settings.local.json.
Pseudocode
check_permissions(vault_root):
name = ask "Which project skill?" or infer from context
skill_dir = ".claude/skills/<name>"
if skill_dir does not exist:
error "No skill named <name>. Run /project-setup link first."
return
real_dir = resolve symlinks of skill_dir // realpath
path = relative path of real_dir within vault_root
settings_file = .claude/settings.local.json
Read(settings_file)
expected_skill = "Skill(<name>)"
expected_read = "Read(<vault_root>/<path>/**)"
missing = items from [expected_skill, expected_read] not in allow list
if missing:
add missing to allow list, report added
else:
report all presentSetup command (default)
Creates the full project-as-a-skill structure for a new project.
Pseudocode
setup(vault_root):
inputs = collect(path, name, title, description, result)
check_duplicates(vault_root, inputs) // covers "already set up" case, offers to link instead
target = "<vault_root>/<path>"
if target/SKILL.md exists:
ask "Skill files already exist. Link instead?" (yes/no)
if yes:
link(vault_root) // reuse link flow
return
mkdir target
write_project_note() // from Project.md template
write_checkpoints_base() // via Bash cat (oxfmt mangles .base)
write_context_md() // context.md only (no SKILL.md, start.md, save.md — vault skill handles routing)
write_vault_config() // .claude/.vault.config.json in repo
write_thin_wrapper() // .claude/skills/<name>/SKILL.md in repo (delegates to /vault)
register_in_settings() // add Skill(<name>), Skill(vault), Read, Bash(vault-cli *)
report created files, suggest /clearReference
Discovering vault root
Run timeout 30 obsidian vaults verbose.
One vault → use its path. Multiple → AskUserQuestion. Command fails → ask user for absolute path.
Store as <vault_root>.
Collecting inputs
Ask the user:
- Vault path — where the project lives (e.g.
41 projects/subarea/my-project). Required. - Project name — kebab-case skill name (e.g.
my-project) - Project title — human-readable (e.g.
My Project) - Description — 1-2 sentences
- Result — what "done" looks like (1 sentence)
Duplicate check
- Glob
<vault_root>/<path>/and.claude/skills/<name>/ - If either exists, ask whether to proceed or edit existing
Writing the project note
Path: <vault_root>/<path>/<Title>.md
Template: <vault_root>/templates/Project.md. Fill frontmatter (result, status: in progress). Replace <path> placeholders in body. Replace description comment with real description.
Writing Checkpoints.base
Path: <vault_root>/<path>/Checkpoints.base
Write via Bash cat (oxfmt mangles YAML in .base files). See template below.
Writing context.md
In <vault_root>/<path>/context.md. Substitute <path>, <title>, <description>, <result>. See context.md template.
No SKILL.md, start.md, or save.md in the vault project folder. The /vault skill handles routing.
Writing .vault.config.json
Path: <repo>/.claude/.vault.config.json
{
"vault_root": "<vault_root>",
"project": {
"name": "<name>",
"title": "<title>",
"path": "<path>"
}
}After writing, ensure .claude/.vault.config.json is in .gitignore (it contains absolute paths). Read .gitignore, append the line if missing.
Writing thin wrapper skill
Path: <repo>/.claude/skills/<name>/SKILL.md
See thin wrapper template below.
Registering in settings
Read .claude/settings.local.json (user-specific, absolute paths don't belong in shared settings). Add these entries to the allow list:
Skill(<name>)— insert alphabetically among existingSkill(...)entriesSkill(vault)— the universal vault skillRead(<vault_root>/<path>/**)— allows reading project files and checkpoints from the vaultBash(vault-cli *)— allows vault-cli commandsBash(~/.claude/skills/vault/scripts/vault-cli *)— full path variant
Checkpoints.base template
Substitute <path> with the actual vault path before writing. Write via Bash cat, because oxfmt mangles .base files.
Template: Read(dir/templates/checkpoints-base.yaml). Replace all <path> occurrences with the actual vault path.
Thin wrapper template
Per-repo convenience skill at <repo>/.claude/skills/<name>/SKILL.md. Substitute <name>, <title>.
---
name: <name>
description: "<title> project sessions. Use /<name> start to resume or /<name> save to checkpoint."
---
Delegates to /vault.
```
config = Read(.claude/.vault.config.json)
command = user's command after /<name>
Skill(vault) with "{config.project.name} {command}"
```context.md template
Per-project context. Substitute <path>, <title>, <description>, <result>.
# <title> Context
<description>
- Project note: [[<path>/<title>]]
- Checkpoints: `<path>/`
## Result
<result>
## Tech stack
<!-- filled by user or Claude during setup -->
## Conventions
<!-- project-specific conventions -->Notes
/clearor restart is required after setup for the new skill to load.- The generated files live in the vault. Wikilinks work. The user adds project-specific context to context.md.
- Write checkpoints via the Write tool.
obsidian createfails with multiline content. - Query checkpoints via
base:query.obsidian searchbreaks on paths with spaces.