From 99113ab07aa8e4c21f042383c921328b487d32af Mon Sep 17 00:00:00 2001 From: Tim Kleinschmidt Date: Wed, 16 Sep 2026 15:49:30 +0200 Subject: [PATCH 1/3] fix(skills): make directory guidance scope-aware --- src/skills.js | 7 +++++-- test/skills.test.js | 5 +++++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/skills.js b/src/skills.js index c458c34..6829c51 100644 --- a/src/skills.js +++ b/src/skills.js @@ -385,8 +385,11 @@ 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 ${claudeSkillsDir} for one run, set "skillsDir": "${claudeSkillsDir}" in .backpassrc.json ` + + `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.` ); } diff --git a/test/skills.test.js b/test/skills.test.js index e5a9e40..7184d2d 100644 --- a/test/skills.test.js +++ b/test/skills.test.js @@ -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); From 35e0ea8b11cf8bad5c0b8f1208a558c9aa6d6a34 Mon Sep 17 00:00:00 2001 From: Tim Kleinschmidt Date: Wed, 16 Sep 2026 16:02:47 +0200 Subject: [PATCH 2/3] no-mistakes(document): Documented scope-aware skills directory guidance --- README.md | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 0fc4c36..20560b9 100644 --- a/README.md +++ b/README.md @@ -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 @@ -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 ` 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 { From 4d9cefbc51eaae0da1206f70f47380445f916aab Mon Sep 17 00:00:00 2001 From: Tim Kleinschmidt Date: Wed, 16 Sep 2026 16:08:28 +0200 Subject: [PATCH 3/3] no-mistakes(ci): Fixed shell quoting for --skills-dir guidance and added space-containing path coverage. Focused tests (16/16), ESLint, Prettier, and diff checks pass --- src/skills.js | 6 +++++- test/skills.test.js | 17 ++++++++++++++++- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/src/skills.js b/src/skills.js index 6829c51..f7be3d6 100644 --- a/src/skills.js +++ b/src/skills.js @@ -387,12 +387,16 @@ function claudeSkillsDirWarning(claudeSkillsDir = CLAUDE_SKILLS_LINK, target = C `${claudeSkillsDir} is a real directory, not a symlink to ${target}; ` + `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 ${claudeSkillsDir} for one run, set "skillsDir": "${claudeSkillsDir}" in .backpassrc.json ` + + `--skills-dir ${shellQuote(claudeSkillsDir)} for one run, set "skillsDir": "${claudeSkillsDir}" in .backpassrc.json ` + `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; diff --git a/test/skills.test.js b/test/skills.test.js index 7184d2d..2fa962a 100644 --- a/test/skills.test.js +++ b/test/skills.test.js @@ -229,7 +229,7 @@ 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], /--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/); @@ -245,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");