Report:
Catalog::discover() only scans the package's own Skills/Backend and Skills/Frontend directories (DIR . "/{$engine}"), so a project-local skill scaffolded by 'commandments make' into .commandments/custom/ is never in Catalog::all(). Consequence: sync DOES render its SKILL.md into .claude/skills/, but ClaudeSection::bullets() builds the MANDATORY LOAD / KEEP IN MIND lists from Catalog::inTier(), so a custom skill never appears in the CLAUDE.md briefing and an agent is never told it exists. This is independent of whether the sin has a registered detector. Reproduced here: 'commandments make ClientDecides --engine=frontend', filled in ClientDecidesSkill (Tier::KeepInMind), ran sync — .claude/skills/commandments-frontend-client-decides/SKILL.md is written, but 'grep -c client-decides CLAUDE.md' is 0. The scaffold's own step 7 says sync publishes the skill 'so the agent can load what your finding points at', which is only half true today. Expected: Catalog::discover() also scans the configured custom directory so project-local skills are briefed alongside the shipped ones.
Where: vendor/jessegall/code-commandments/src/Skills/Catalog.php:65
Code (vendor/jessegall/code-commandments/src/Skills/Catalog.php:65):
62 /**
63 * @return list<Skill>
64 */
→ 65 private static function discover(string $engine): array
66 {
67 $skills = [];
68
69 foreach (Discovery::classes(__DIR__ . "/{$engine}", __NAMESPACE__ . "\\{$engine}") as $class) {
70 if (is_subclass_of($class, Skill::class)) {
71 $skills[] = new $class;
72 }
73 }
74
75 return $skills;
76 }
77 }
Where: vendor/jessegall/code-commandments/src/Skills/Catalog.php:43
Code (vendor/jessegall/code-commandments/src/Skills/Catalog.php:43):
40 *
41 * @return list<Skill>
42 */
→ 43 public static function all(): array
44 {
45 $skills = [...self::backend(), ...self::frontend()];
46
47 usort($skills, static fn (Skill $a, Skill $b): int => $a->order <=> $b->order);
48
49 return $skills;
50 }
51
52 /**
53 * The skills loaded in one tier, in briefing order.
54 *
55 * @return list<Skill>
56 */
57 public static function inTier(Tier $tier): array
58 {
59 return array_values(array_filter(self::all(), static fn (Skill $skill): bool => $skill->tier === $tier));
60 }
61
62 /**
63 * @return list<Skill>
64 */
65 private static function discover(string $engine): array
66 {
67 $skills = [];
Where: vendor/jessegall/code-commandments/src/Skills/ClaudeSection.php:194
Code (vendor/jessegall/code-commandments/src/Skills/ClaudeSection.php:194):
191 return self::BEGIN . "\n" . $body . "\n" . self::END;
192 }
193
→ 194 private static function bullets(Tier $tier): string
195 {
196 return implode("\n", array_map(static fn (Skill $skill): string => $skill->bullet(), Catalog::inTier($tier)));
197 }
198 }
Where: .commandments/custom/ClientDecidesSkill.php:1
Code (.commandments/custom/ClientDecidesSkill.php:1):
→ 1 <?php
2
3 declare(strict_types=1);
4
5 namespace Commandments;
6
7 use JesseGall\CodeCommandments\Skills\Skill;
8 use JesseGall\CodeCommandments\Skills\Tier;
9
10 final class ClientDecidesSkill extends Skill
11 {
12 public function __construct()
13 {
14 parent::__construct(
15 slug: 'frontend/client-decides',
16 tier: Tier::KeepInMind,
17 order: 100,
18 );
19 }
20
21 public function title(): string
22 {
23 return 'The backend decides, the client performs';
24 }
25
Filed via commandments report from a consumer project.
Report:
Catalog::discover() only scans the package's own Skills/Backend and Skills/Frontend directories (DIR . "/{$engine}"), so a project-local skill scaffolded by 'commandments make' into .commandments/custom/ is never in Catalog::all(). Consequence: sync DOES render its SKILL.md into .claude/skills/, but ClaudeSection::bullets() builds the MANDATORY LOAD / KEEP IN MIND lists from Catalog::inTier(), so a custom skill never appears in the CLAUDE.md briefing and an agent is never told it exists. This is independent of whether the sin has a registered detector. Reproduced here: 'commandments make ClientDecides --engine=frontend', filled in ClientDecidesSkill (Tier::KeepInMind), ran sync — .claude/skills/commandments-frontend-client-decides/SKILL.md is written, but 'grep -c client-decides CLAUDE.md' is 0. The scaffold's own step 7 says sync publishes the skill 'so the agent can load what your finding points at', which is only half true today. Expected: Catalog::discover() also scans the configured custom directory so project-local skills are briefed alongside the shipped ones.
Where:
vendor/jessegall/code-commandments/src/Skills/Catalog.php:65Code (
vendor/jessegall/code-commandments/src/Skills/Catalog.php:65):Where:
vendor/jessegall/code-commandments/src/Skills/Catalog.php:43Code (
vendor/jessegall/code-commandments/src/Skills/Catalog.php:43):Where:
vendor/jessegall/code-commandments/src/Skills/ClaudeSection.php:194Code (
vendor/jessegall/code-commandments/src/Skills/ClaudeSection.php:194):Where:
.commandments/custom/ClientDecidesSkill.php:1Code (
.commandments/custom/ClientDecidesSkill.php:1):Filed via
commandments reportfrom a consumer project.