Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 10 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,8 +85,9 @@ Canonical user memory is the first existing file in this order: `~/.agents/AGENT
`$CLAUDE_CONFIG_DIR/CLAUDE.md` (default `~/.claude/CLAUDE.md`), and
`$CODEX_HOME/AGENTS.md` (default `~/.codex/AGENTS.md`). User-level skill extractions
default to `~/.agents/skills`, with a warning if Claude's active `skills` path is a
real directory rather than the usual symlink. See [Configuration](#configuration) for
using an existing harness-loaded directory instead.
real directory rather than the usual symlink. Backpass leaves that directory untouched:
see [Configuration](#configuration) for the scope-specific settings and manual merge
guidance.

In user scope every `add`, `rewrite`, or `remove` edit also clears `minGapProjects`
(default `1`): the distinct projects behind its own quotes, counted from the gap
Expand Down Expand Up @@ -735,8 +736,13 @@ regular settings; its path and user-only settings include `memoryFiles`, `skills
`skillsDirs`, `minGapProjects` (default `1`), and these discovery controls:

`skillsDir` defaults to `.agents/skills`. To use an existing harness-loaded directory
instead, such as `.claude/skills`, configure that path; a missing configured directory
falls back to the default. Backpass normalizes path separators and trailing slashes.
instead, such as `.claude/skills`, configure that path: use `--skills-dir <path>` for one
run, `skillsDir` in `.backpassrc.json` for project scope, or `skillsDir` under the `user`
block in `$XDG_CONFIG_HOME/backpass/config.json` (default `~/.config/backpass/config.json`)
for user scope. A missing configured directory falls back to the default. Backpass leaves
a real `.claude/skills` directory untouched and recommends manually merging new skill
directories after checking for conflicts. It never recommends replacing that directory
with a symlink. Backpass normalizes path separators and trailing slashes.

```json
{
Expand Down
11 changes: 9 additions & 2 deletions src/skills.js
Original file line number Diff line number Diff line change
Expand Up @@ -385,11 +385,18 @@ function invalidSkillsDir(configuredDir) {
function claudeSkillsDirWarning(claudeSkillsDir = CLAUDE_SKILLS_LINK, target = CLAUDE_SKILLS_LINK_TARGET) {
return (
`${claudeSkillsDir} is a real directory, not a symlink to ${target}; ` +
`left untouched. Claude will not see skills written to ${CANONICAL_SKILLS_DIR} until you ` +
`merge it in and replace it with the symlink (ln -s ${target} ${claudeSkillsDir}).`
`left untouched. Backpass writes new skills to ${CANONICAL_SKILLS_DIR}, which Claude will not load ` +
`through ${claudeSkillsDir}. To write directly to the existing directory, use ` +
`--skills-dir ${shellQuote(claudeSkillsDir)} for one run, set "skillsDir": "${claudeSkillsDir}" in .backpassrc.json ` +

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Windows guidance uses POSIX quoting

The warning is also emitted on Windows, but it always wraps the --skills-dir value in POSIX single quotes. In cmd.exe, those quotes are treated as part of the path rather than grouping the argument, so copying the suggested command fails even for the default .claude/skills path. Please render the argument for the active platform or provide platform-specific guidance.

`for project scope, or set it under the "user" block in $XDG_CONFIG_HOME/backpass/config.json for user ` +
`scope. Otherwise merge new skill directories manually after checking for conflicts.`
);
}

function shellQuote(value) {
return `'${value.replaceAll("'", "'\\''")}'`;
}

function inspectClaudeSkillsLink(repoRoot, claudeSkillsDir = CLAUDE_SKILLS_LINK) {
const link = path.isAbsolute(claudeSkillsDir) ? claudeSkillsDir : path.join(repoRoot, claudeSkillsDir);
let stat;
Expand Down
20 changes: 20 additions & 0 deletions test/skills.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,11 @@ test("a real .claude/skills directory is warned about and never clobbered", () =
assert.equal(resolved.dir, CANONICAL_SKILLS_DIR);
assert.equal(resolved.warnings.length, 1);
assert.match(resolved.warnings[0], /\.claude\/skills is a real directory/);
assert.match(resolved.warnings[0], /--skills-dir '\.claude\/skills'/);
assert.match(resolved.warnings[0], /"skillsDir": "\.claude\/skills"/);
assert.match(resolved.warnings[0], /"user" block in \$XDG_CONFIG_HOME\/backpass\/config\.json/);
assert.match(resolved.warnings[0], /merge new skill directories manually/);
assert.doesNotMatch(resolved.warnings[0], /ln -s|replace it/);

const result = writeSkill(root, SKILL);
assert.equal(result.warnings.length, 1);
Expand All @@ -240,6 +245,21 @@ test("a real .claude/skills directory is warned about and never clobbered", () =
assert.ok(fs.existsSync(path.join(root, CANONICAL_SKILLS_DIR, "release-signing", "SKILL.md")));
});

test("quotes spaces in the one-run skills directory guidance", () => {
const root = tmpRepo();
const claudeSkillsDir = ".claude config/skills";
fs.mkdirSync(path.join(root, claudeSkillsDir), { recursive: true });

const resolved = resolveOverflowTarget(root, CANONICAL_SKILLS_DIR, { claudeSkillsDir });

assert.equal(resolved.warnings.length, 1);
assert.match(resolved.warnings[0], /--skills-dir '\.claude config\/skills'/);
assert.match(resolved.warnings[0], /project scope/);
assert.match(resolved.warnings[0], /user.*scope/);
assert.match(resolved.warnings[0], /merge new skill directories manually after checking for conflicts/);
assert.doesNotMatch(resolved.warnings[0], /ln -s|replace it/);
});

test("applyDecisions writes accepted extractions through the skills layout and surfaces warnings", () => {
const root = tmpRepo();
fs.writeFileSync(path.join(root, "AGENTS.md"), "# Memory\n\n- Sign releases with the key.\n");
Expand Down
Loading