Skip to content

[bug-report] Catalog::discover() only scans the package's own Skills/B… #443

Description

@jessegall

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      }
  193194      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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions