baldart 5.6.0 → 5.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/CHANGELOG.md +55 -0
  2. package/VERSION +1 -1
  3. package/framework/.claude/skills/capture/CHANGELOG.md +4 -0
  4. package/framework/.claude/skills/capture/SKILL.md +2 -1
  5. package/framework/.claude/skills/design-sync/CHANGELOG.md +4 -0
  6. package/framework/.claude/skills/design-sync/SKILL.md +2 -1
  7. package/framework/.claude/skills/ds-edit/CHANGELOG.md +5 -0
  8. package/framework/.claude/skills/ds-edit/SKILL.md +9 -10
  9. package/framework/.claude/skills/ds-handoff/CHANGELOG.md +5 -0
  10. package/framework/.claude/skills/ds-handoff/SKILL.md +3 -2
  11. package/framework/.claude/skills/ds-new/CHANGELOG.md +4 -0
  12. package/framework/.claude/skills/ds-new/SKILL.md +2 -1
  13. package/framework/.claude/skills/ds-render/CHANGELOG.md +4 -0
  14. package/framework/.claude/skills/ds-render/SKILL.md +2 -1
  15. package/framework/.claude/skills/e2e-review/CHANGELOG.md +4 -0
  16. package/framework/.claude/skills/e2e-review/SKILL.md +2 -1
  17. package/framework/.claude/skills/gamification-design/CHANGELOG.md +4 -0
  18. package/framework/.claude/skills/gamification-design/SKILL.md +2 -1
  19. package/framework/.claude/skills/graph-align/CHANGELOG.md +4 -0
  20. package/framework/.claude/skills/graph-align/SKILL.md +2 -1
  21. package/framework/.claude/skills/i18n/CHANGELOG.md +4 -0
  22. package/framework/.claude/skills/i18n/SKILL.md +2 -1
  23. package/framework/.claude/skills/motion-design/CHANGELOG.md +4 -0
  24. package/framework/.claude/skills/motion-design/SKILL.md +2 -1
  25. package/framework/.claude/skills/new/CHANGELOG.md +4 -0
  26. package/framework/.claude/skills/new/SKILL.md +2 -1
  27. package/framework/.claude/skills/new2/CHANGELOG.md +4 -0
  28. package/framework/.claude/skills/new2/SKILL.md +2 -1
  29. package/framework/.claude/skills/prd/CHANGELOG.md +4 -0
  30. package/framework/.claude/skills/prd/SKILL.md +2 -1
  31. package/framework/.claude/skills/prd-add/CHANGELOG.md +4 -0
  32. package/framework/.claude/skills/prd-add/SKILL.md +2 -1
  33. package/framework/.claude/skills/skill-creator/references/skill-structure.md +31 -2
  34. package/framework/.claude/skills/skill-creator/scripts/quick_validate.py +25 -3
  35. package/framework/.claude/skills/ui-design/CHANGELOG.md +4 -0
  36. package/framework/.claude/skills/ui-design/SKILL.md +2 -1
  37. package/framework/.claude/skills/ui-implement/CHANGELOG.md +4 -0
  38. package/framework/.claude/skills/ui-implement/SKILL.md +2 -1
  39. package/framework/.claude/workflows/new2.js +5 -1
  40. package/package.json +1 -1
  41. package/src/commands/configure.js +9 -2
  42. package/src/commands/doctor.js +65 -0
  43. package/src/utils/skill-gate.js +139 -0
  44. package/src/utils/symlinks.js +72 -7
package/CHANGELOG.md CHANGED
@@ -5,6 +5,61 @@ All notable changes to BALDART will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [5.7.0] - 2026-07-06
9
+
10
+ **Feature-gated skill linking — stop wasting Codex's 2% skill-description budget.** Codex
11
+ reserves ~2% of its context window for skill descriptions; BALDART's ~40 skills alone
12
+ already exceed it (~6.5k tokens), so Codex warns *"Skill descriptions were shortened to fit
13
+ the 2% skills context budget"* and truncates them (degrading skill routing). Until now the
14
+ installer linked EVERY framework skill into a consumer — including ones whose gating
15
+ `features.*` flag is OFF (e.g. the `ds-*` cluster, `i18n`, `graph-align`, `e2e-review`),
16
+ which then merely refuse at runtime: dead weight on the budget. Now a skill is linked only
17
+ when its declared feature is enabled, trimming ~2.5k tokens (~40%) on a typical project
18
+ without design-system/i18n/code-graph/e2e.
19
+
20
+ - **`requires_feature` frontmatter (SSOT) + `src/utils/skill-gate.js`**: an OPTIONAL
21
+ `requires_feature: has_x` (string or list = AND) on a skill's `SKILL.md`. Makes
22
+ machine-readable a dependency the skills already declared in prose (their "Project
23
+ Context" header) — no second SSOT, same pattern as `version:`/`effort:`. `resolveSkillGate`
24
+ reads `features.*` **directly** from `baldart.config.yml` (NOT `agent-slots.resolveAgentFlags`,
25
+ which omits `has_prd_workflow`/`has_e2e_review`) and **fails open** globally (config
26
+ absent/unreadable → link everything) and per-skill (malformed frontmatter → link that skill).
27
+ - **Applied to BOTH runtimes from one code path** (`mergeSkills` in `src/utils/symlinks.js`):
28
+ the gate is shared SEMANTICS (which skills are relevant), not runtime mechanics, so Claude
29
+ (`.claude/skills/`) and Codex (`.agents/skills/`) stay identical by construction. Adds the
30
+ **prune** direction — a feature flipped off (or a fail-open first install) now removes stale
31
+ framework symlinks, never touching a user skill dir or a user-override symlink pointing
32
+ elsewhere.
33
+ - **17 skills gated**: `has_design_system` → ui-design, ui-implement, ds-new, ds-edit,
34
+ ds-render, design-sync, ds-handoff, motion-design, gamification-design · `has_i18n` → i18n ·
35
+ `has_code_graph` → graph-align · `has_e2e_review` → e2e-review · `has_prd_workflow` → prd,
36
+ prd-add · `has_backlog` → new, new2 · `has_wiki_overlay` → capture. The five bootstrap
37
+ skills (`design-system-init`, `i18n-adopt`, `graphify-bootstrap`, `lsp-bootstrap`,
38
+ `toolchain-bootstrap`) stay ALWAYS-linked so a feature can always be turned on — then
39
+ `configure`/`update` re-merge and the cluster appears.
40
+ - **Lifecycle wiring**: `configure` now re-runs `mergeSkills` after writing config (first
41
+ install linked fail-open before the config existed — this is what actually prunes);
42
+ `update` already re-merges (prune committed via `BALDART_MANAGED_PATTERNS`); `doctor` gains
43
+ a non-blocking `reconcile-skills` advisory when a gated-off skill is still linked, and its
44
+ skill sample-probe no longer samples gatable skills (`prd`/`capture`). `skill-creator`:
45
+ `quick_validate.py` allow-lists `requires_feature` (it previously HARD-FAILED on unknown
46
+ keys) + soft-warns unknown flags; `references/skill-structure.md` documents the field.
47
+ `new2.js` binding-gate now guards on `has_design_system`.
48
+ - **Behaviour change for Claude consumers**: a skill whose feature is off now *disappears*
49
+ from `.claude/skills/` instead of appearing and refusing at runtime. Mitigated: bootstraps
50
+ stay visible, and re-enabling the feature (then any merge) brings the cluster back.
51
+ - **Rider — two skill descriptions made `quick_validate`-clean** (pre-existing failures, both
52
+ among the heaviest so this also shrinks the Codex budget): `ds-edit` description trimmed
53
+ 1080→1014 chars (was over the 1024 max), `ds-handoff` angle-bracket placeholder
54
+ (`<screens-or-feature>`) rephrased bracket-free — no trigger phrases lost.
55
+ - Verified: unit-level gate partitioning (design_system-off/backlog-on → 15 gated off, `new`
56
+ linked, bootstrap present), YAML validity of all 17 frontmatters, `quick_validate` PASS.
57
+ - **No new `baldart.config.yml` key** (consumes existing `features.*`) → the schema-change
58
+ propagation rule does NOT apply; it is skill-frontmatter metadata like `version`/`effort`.
59
+ - **Codex parity: portable** — single semantic filter, both runtimes, install-time only;
60
+ `requires_feature` is ignored by both skill loaders (they read only name+description, as
61
+ they already do for `version`/`effort`).
62
+
8
63
  ## [5.6.0] - 2026-07-04
9
64
 
10
65
  **Outcome-integrity wave (FEAT-0068 post-mortem — mayo, 72.78M token, DONE falso).** Il
package/VERSION CHANGED
@@ -1 +1 @@
1
- 5.6.0
1
+ 5.7.0
@@ -2,6 +2,10 @@
2
2
 
3
3
  Formato: [Keep a Changelog](https://keepachangelog.com/) · [SemVer](https://semver.org/).
4
4
 
5
+ ## 1.0.1 — 2026-07-06
6
+
7
+ - **Feature-gate**: added `requires_feature: has_wiki_overlay` frontmatter — this skill is now linked into a consumer only when `features.has_wiki_overlay` is enabled (it already refused at runtime otherwise). Trims Codex's ~2% skill-description budget on projects without the feature. Install-time gate, portable across Claude + Codex.
8
+
5
9
  ## 1.0.0 — 2026-07-01
6
10
 
7
11
  - Baseline: versioning per-skill introdotto al framework v4.82.0.
@@ -2,7 +2,8 @@
2
2
  name: capture
3
3
  effort: medium
4
4
  description: Distilla una sintesi cross-document della conversazione recente in una synthesis page riusabile del wiki overlay (path da `paths.wiki_dir`/syntheses/ in `baldart.config.yml`). Usare quando l'utente invoca `/capture`, dice "capture synthesis", "salva sintesi", "distilla conversazione", "save this as wiki", "questa conversazione in wiki", o quando chiede di trasformare un ragionamento cross-document appena fatto in una pagina stabile. Due modalità — manual (sempre funzionante) e proactive (conditional sui candidati in `.claude/capture-queue/`). Implementa il loop query→wiki file-back di Karpathy.
5
- version: 1.0.0
5
+ version: 1.0.1
6
+ requires_feature: has_wiki_overlay
6
7
  ---
7
8
 
8
9
  # /capture — Synthesis Capture Skill
@@ -2,6 +2,10 @@
2
2
 
3
3
  Formato: [Keep a Changelog](https://keepachangelog.com/) · [SemVer](https://semver.org/).
4
4
 
5
+ ## 1.0.1 — 2026-07-06
6
+
7
+ - **Feature-gate**: added `requires_feature: has_design_system` frontmatter — this skill is now linked into a consumer only when `features.has_design_system` is enabled (it already refused at runtime otherwise). Trims Codex's ~2% skill-description budget on projects without the feature. Install-time gate, portable across Claude + Codex.
8
+
5
9
  ## 1.0.0 — 2026-07-01
6
10
 
7
11
  - Baseline: versioning per-skill introdotto al framework v4.82.0.
@@ -1,7 +1,8 @@
1
1
  ---
2
2
  name: design-sync
3
3
  effort: high
4
- version: 1.0.0
4
+ version: 1.0.1
5
+ requires_feature: has_design_system
5
6
  description: >
6
7
  Keep the in-repo design system (the SSOT) and its Claude Design satellite
7
8
  "mirror" project in sync — the code→design half of the closed loop. Three modes:
@@ -2,6 +2,11 @@
2
2
 
3
3
  Formato: [Keep a Changelog](https://keepachangelog.com/) · [SemVer](https://semver.org/).
4
4
 
5
+ ## 1.1.1 — 2026-07-06
6
+
7
+ - **Feature-gate**: added `requires_feature: has_design_system` frontmatter — this skill is now linked into a consumer only when `features.has_design_system` is enabled (it already refused at runtime otherwise). Trims Codex's ~2% skill-description budget on projects without the feature. Install-time gate, portable across Claude + Codex.
8
+ - **Description trimmed to pass `quick_validate`**: the `description` was 1080 chars (over the 1024 max); compressed to 1014 without losing any trigger phrase. Also shrinks the Codex skill-description budget.
9
+
5
10
  ## 1.1.0 — 2026-07-02
6
11
 
7
12
  - **P1 locate hardening.** Reso esplicito che lo step P1 è un lookup deterministico
@@ -1,23 +1,22 @@
1
1
  ---
2
2
  name: ds-edit
3
3
  effort: medium
4
- version: 1.1.0
4
+ version: 1.1.1
5
+ requires_feature: has_design_system
5
6
  description: >
6
7
  Update ONE existing canonical design-system element correctly — the deliberate
7
8
  edit twin of /ds-new. Classifies the change (resync the spec to changed source /
8
- extend with a new variant or prop / breaking change with migration / re-govern
9
- the closed-set role) and runs it end to end: apply the source change (delegated
10
- to ui-expert), regenerate the spec via the serializer (HEAD from source, agentic
11
- + prose PRESERVED, the canonical template
12
- framework/templates/component-spec.template.md filled if a spec is missing),
9
+ extend with a variant or prop / breaking change + migration / re-govern the
10
+ closed-set role) and runs it end to end: apply the source change (via ui-expert),
11
+ regenerate the spec via the serializer (HEAD from source, agentic + prose
12
+ PRESERVED, the canonical component-spec template filled if a spec is missing),
13
13
  re-govern if needed, verify. A THIN orchestrator — delegates every rule to the
14
14
  existing SSOTs, never hand-writes a spec HEAD. Use when the user says /ds-edit,
15
15
  "aggiorna un componente", "modifica una primitive", "aggiungi una variante",
16
16
  "estendi il componente", "resync dello spec", "breaking change su un
17
- componente", or when `/prd` confirms a REUSE+VARIANT extension. The pure
18
- mechanical doc-resync after a code change is ALREADY automatic (serializer +
19
- `DS_COMPONENT_STALE` gate) — this skill is for the DELIBERATE change. Gated on
20
- features.has_design_system.
17
+ componente", or when `/prd` confirms a REUSE+VARIANT extension. The mechanical
18
+ doc-resync after a code change is ALREADY automatic (`DS_COMPONENT_STALE` gate)
19
+ — this skill is for the DELIBERATE change. Gated on features.has_design_system.
21
20
  ---
22
21
 
23
22
  # DS Edit — update one existing canonical element
@@ -2,6 +2,11 @@
2
2
 
3
3
  Formato: [Keep a Changelog](https://keepachangelog.com/) · [SemVer](https://semver.org/).
4
4
 
5
+ ## 1.2.1 — 2026-07-06
6
+
7
+ - **Feature-gate**: added `requires_feature: has_design_system` frontmatter — this skill is now linked into a consumer only when `features.has_design_system` is enabled (it already refused at runtime otherwise). Trims Codex's ~2% skill-description budget on projects without the feature. Install-time gate, portable across Claude + Codex.
8
+ - **Description made `quick_validate`-clean**: removed the angle-bracket placeholder (`<screens-or-feature>`) the validator rejects, rephrased bracket-free without losing the standalone-invocation trigger.
9
+
5
10
  ## 1.2.0 — 2026-07-02
6
11
 
7
12
  - **Analysis-profile contract (framework v4.94.0)**: the standalone screen-resolution probe passes `PROFILE=ui` to `codebase-architect` per `framework/agents/analysis-profiles.md`. The Step 3 field-extraction retrieval order is unchanged.
@@ -1,7 +1,8 @@
1
1
  ---
2
2
  name: ds-handoff
3
3
  effort: medium
4
- version: 1.2.0
4
+ version: 1.2.1
5
+ requires_feature: has_design_system
5
6
  description: >
6
7
  Produce a FIELD-LEVEL, 1:1, interaction-complete handoff brief for Claude Design
7
8
  — every screen's real fields (label, control type, required, validation, default,
@@ -9,7 +10,7 @@ description: >
9
10
  data schemas (Zod / TS types / form schemas) via a coverage gate that BLOCKS prompt
10
11
  emission when a backed screen's fields are unverified. The single SSOT for the
11
12
  Claude Design handoff prompt: `/prd` Step 3.0 Branch B delegates here; usable
12
- standalone via `/ds-handoff <screens-or-feature>`. A THIN orchestrator — cites
13
+ standalone via `/ds-handoff` with a screen list or feature. A THIN orchestrator — cites
13
14
  `design-system-protocol.md` (Functional Traceability Gate, Reference Tables) and
14
15
  reads `mockup_analysis` as input, never duplicating their rules; reuses existing
15
16
  retrieval (code-graph / LSP / codebase-architect / Grep) rather than shipping a
@@ -2,6 +2,10 @@
2
2
 
3
3
  Formato: [Keep a Changelog](https://keepachangelog.com/) · [SemVer](https://semver.org/).
4
4
 
5
+ ## 1.1.1 — 2026-07-06
6
+
7
+ - **Feature-gate**: added `requires_feature: has_design_system` frontmatter — this skill is now linked into a consumer only when `features.has_design_system` is enabled (it already refused at runtime otherwise). Trims Codex's ~2% skill-description budget on projects without the feature. Install-time gate, portable across Claude + Codex.
8
+
5
9
  ## 1.1.0 — 2026-07-02
6
10
 
7
11
  - **Reuse-first hardening.** Reso esplicito che lo step 1 reuse-first resta il
@@ -1,7 +1,8 @@
1
1
  ---
2
2
  name: ds-new
3
3
  effort: medium
4
- version: 1.1.0
4
+ version: 1.1.1
5
+ requires_feature: has_design_system
5
6
  description: >
6
7
  Create ONE canonical design-system element — a component OR a token — end to
7
8
  end with the full discipline: reuse-first research, (optional) scaffold, then
@@ -2,6 +2,10 @@
2
2
 
3
3
  Formato: [Keep a Changelog](https://keepachangelog.com/) · [SemVer](https://semver.org/).
4
4
 
5
+ ## 1.0.1 — 2026-07-06
6
+
7
+ - **Feature-gate**: added `requires_feature: has_design_system` frontmatter — this skill is now linked into a consumer only when `features.has_design_system` is enabled (it already refused at runtime otherwise). Trims Codex's ~2% skill-description budget on projects without the feature. Install-time gate, portable across Claude + Codex.
8
+
5
9
  ## 1.0.0 — 2026-07-01
6
10
 
7
11
  - Baseline: versioning per-skill introdotto al framework v4.82.0.
@@ -1,7 +1,8 @@
1
1
  ---
2
2
  name: ds-render
3
3
  effort: medium
4
- version: 1.0.0
4
+ version: 1.0.1
5
+ requires_feature: has_design_system
5
6
  description: >
6
7
  Render the design-system primitives in ISOLATION (one component × variant at a
7
8
  time) and capture a PNG per entry — the render harness BALDART never had (today
@@ -2,6 +2,10 @@
2
2
 
3
3
  Formato: [Keep a Changelog](https://keepachangelog.com/) · [SemVer](https://semver.org/).
4
4
 
5
+ ## 1.3.2 — 2026-07-06
6
+
7
+ - **Feature-gate**: added `requires_feature: has_e2e_review` frontmatter — this skill is now linked into a consumer only when `features.has_e2e_review` is enabled (it already refused at runtime otherwise). Trims Codex's ~2% skill-description budget on projects without the feature. Install-time gate, portable across Claude + Codex.
8
+
5
9
  ## 1.3.1 — 2026-07-03
6
10
 
7
11
  - Boundary pointer: "Not a design generator" ora punta solo a `ui-design`
@@ -1,7 +1,8 @@
1
1
  ---
2
2
  name: e2e-review
3
3
  effort: medium
4
- version: 1.3.1
4
+ version: 1.3.2
5
+ requires_feature: has_e2e_review
5
6
  description: >
6
7
  Deterministic, BLOCKING end-to-end review run after a feature is implemented.
7
8
  Combines functional E2E (Playwright spec written by `coder`, executed via
@@ -2,6 +2,10 @@
2
2
 
3
3
  Formato: [Keep a Changelog](https://keepachangelog.com/) · [SemVer](https://semver.org/).
4
4
 
5
+ ## 1.0.2 — 2026-07-06
6
+
7
+ - **Feature-gate**: added `requires_feature: has_design_system` frontmatter — this skill is now linked into a consumer only when `features.has_design_system` is enabled (it already refused at runtime otherwise). Trims Codex's ~2% skill-description budget on projects without the feature. Install-time gate, portable across Claude + Codex.
8
+
5
9
  ## 1.0.1 — 2026-07-03
6
10
 
7
11
  - Related Skills: `ui-ux-pro-max` (superseded) sostituito con `ui-design` v2
@@ -1,7 +1,8 @@
1
1
  ---
2
2
  name: gamification-design
3
3
  effort: medium
4
- version: 1.0.1
4
+ version: 1.0.2
5
+ requires_feature: has_design_system
5
6
  description: Use when designing or analyzing points systems, reward loops, progression mechanics, engagement features, or loyalty programs. Triggers include "points system," "rewards design," "progression," "engagement loop," "battle pass," "loyalty mechanics," "gamification," or "retention hooks." For B2C customer-facing features in this codebase, this skill invokes the hyper-gamification-designer agent.
6
7
  ---
7
8
 
@@ -2,6 +2,10 @@
2
2
 
3
3
  Formato: [Keep a Changelog](https://keepachangelog.com/) · [SemVer](https://semver.org/).
4
4
 
5
+ ## 1.0.1 — 2026-07-06
6
+
7
+ - **Feature-gate**: added `requires_feature: has_code_graph` frontmatter — this skill is now linked into a consumer only when `features.has_code_graph` is enabled (it already refused at runtime otherwise). Trims Codex's ~2% skill-description budget on projects without the feature. Install-time gate, portable across Claude + Codex.
8
+
5
9
  ## 1.0.0 — 2026-07-01
6
10
 
7
11
  - Baseline: versioning per-skill introdotto al framework v4.82.0.
@@ -1,7 +1,8 @@
1
1
  ---
2
2
  name: graph-align
3
3
  effort: medium
4
- version: 1.0.0
4
+ version: 1.0.1
5
+ requires_feature: has_code_graph
5
6
  description: >
6
7
  On-demand documentation alignment via the Graphify code knowledge graph.
7
8
  Rebuilds the graph offline (graphify update .), then cross-checks graph
@@ -2,6 +2,10 @@
2
2
 
3
3
  Formato: [Keep a Changelog](https://keepachangelog.com/) · [SemVer](https://semver.org/).
4
4
 
5
+ ## 1.0.1 — 2026-07-06
6
+
7
+ - **Feature-gate**: added `requires_feature: has_i18n` frontmatter — this skill is now linked into a consumer only when `features.has_i18n` is enabled (it already refused at runtime otherwise). Trims Codex's ~2% skill-description budget on projects without the feature. Install-time gate, portable across Claude + Codex.
8
+
5
9
  ## 1.0.0 — 2026-07-01
6
10
 
7
11
  - Baseline: versioning per-skill introdotto al framework v4.82.0.
@@ -1,7 +1,8 @@
1
1
  ---
2
2
  name: i18n
3
3
  effort: medium
4
- version: 1.0.0
4
+ version: 1.0.1
5
+ requires_feature: has_i18n
5
6
  description: >
6
7
  On-demand internationalization audit + context-aware translation. Audits the
7
8
  i18n context registry against the t() calls and native locale files (drift
@@ -2,6 +2,10 @@
2
2
 
3
3
  Formato: [Keep a Changelog](https://keepachangelog.com/) · [SemVer](https://semver.org/).
4
4
 
5
+ ## 1.0.1 — 2026-07-06
6
+
7
+ - **Feature-gate**: added `requires_feature: has_design_system` frontmatter — this skill is now linked into a consumer only when `features.has_design_system` is enabled (it already refused at runtime otherwise). Trims Codex's ~2% skill-description budget on projects without the feature. Install-time gate, portable across Claude + Codex.
8
+
5
9
  ## 1.0.0 — 2026-07-01
6
10
 
7
11
  - Baseline: versioning per-skill introdotto al framework v4.82.0.
@@ -10,7 +10,8 @@ description: >
10
10
  license: MIT
11
11
  metadata:
12
12
  author: LottieFiles
13
- version: 1.0.0
13
+ version: 1.0.1
14
+ requires_feature: has_design_system
14
15
  ---
15
16
 
16
17
  # Motion Design Skill
@@ -2,6 +2,10 @@
2
2
 
3
3
  Formato: [Keep a Changelog](https://keepachangelog.com/) · [SemVer](https://semver.org/).
4
4
 
5
+ ## 2.3.1 — 2026-07-06
6
+
7
+ - **Feature-gate**: added `requires_feature: has_backlog` frontmatter — this skill is now linked into a consumer only when `features.has_backlog` is enabled (it already refused at runtime otherwise). Trims Codex's ~2% skill-description budget on projects without the feature. Install-time gate, portable across Claude + Codex.
8
+
5
9
  ## 2.3.0 — 2026-07-04 (FEAT-0068 post-mortem: integrità dell'esito)
6
10
 
7
11
  - **Chiusura epic: solo in merge phase, predicato esteso** — `commit.md` c-bis RITIRATO
@@ -1,7 +1,8 @@
1
1
  ---
2
2
  name: new
3
3
  effort: medium
4
- version: 2.3.0
4
+ version: 2.3.1
5
+ requires_feature: has_backlog
5
6
  description: >
6
7
  Orchestrate a team of specialized agents to implement one or more backlog cards
7
8
  end-to-end inside a dedicated worktree, with code review, doc review, QA, and
@@ -2,6 +2,10 @@
2
2
 
3
3
  Formato: [Keep a Changelog](https://keepachangelog.com/) · [SemVer](https://semver.org/).
4
4
 
5
+ ## 1.8.1 — 2026-07-06
6
+
7
+ - **Feature-gate**: added `requires_feature: has_backlog` frontmatter — this skill is now linked into a consumer only when `features.has_backlog` is enabled (it already refused at runtime otherwise). Trims Codex's ~2% skill-description budget on projects without the feature. Install-time gate, portable across Claude + Codex.
8
+
5
9
  ## 1.8.0 — 2026-07-04 (FEAT-0068 post-mortem: integrità dell'esito + igiene git)
6
10
 
7
11
  - **`new2.js`**: step 4b (chiusura epic) RIMOSSO dal commit agent — la chiusura vive
@@ -1,7 +1,8 @@
1
1
  ---
2
2
  name: new2
3
3
  effort: high
4
- version: 1.8.0
4
+ version: 1.8.1
5
+ requires_feature: has_backlog
5
6
  description: >
6
7
  Workflow-hosted batch engine for /new. Implements one or
7
8
  more backlog cards end-to-end by delegating the WHOLE batch to a background
@@ -2,6 +2,10 @@
2
2
 
3
3
  Formato: [Keep a Changelog](https://keepachangelog.com/) · [SemVer](https://semver.org/).
4
4
 
5
+ ## 1.6.1 — 2026-07-06
6
+
7
+ - **Feature-gate**: added `requires_feature: has_prd_workflow` frontmatter — this skill is now linked into a consumer only when `features.has_prd_workflow` is enabled (it already refused at runtime otherwise). Trims Codex's ~2% skill-description budget on projects without the feature. Install-time gate, portable across Claude + Codex.
8
+
5
9
  ## 1.6.0 — 2026-07-04 (FEAT-0068 post-mortem: il root cause era qui)
6
10
 
7
11
  - **ISA "UI ROLE FAMILY (registry-first)"** (`discovery-phase.md` Dimension 9, gated su
@@ -1,7 +1,8 @@
1
1
  ---
2
2
  name: prd
3
3
  effort: high
4
- version: 1.6.0
4
+ version: 1.6.1
5
+ requires_feature: has_prd_workflow
5
6
  description: "Structured PRD creation skill. Use when the user says /prd, 'crea un prd', 'nuova funzionalita', 'pianifica feature', or wants to plan a new feature end-to-end. Also handles /prd-add, 'aggiungi requisito', 'serve anche', 'manca un endpoint' for change requests on active PRD sessions — runs ICIAS impact analysis to determine which phases need SKIP/PATCH/REDO. Produces PRD + UI design + backlog cards, all validated and committed. Multi-turn conversation: asks questions, waits for answers, iterates through discovery, design, spec writing, card creation, and validation phases."
6
7
  ---
7
8
 
@@ -2,6 +2,10 @@
2
2
 
3
3
  Formato: [Keep a Changelog](https://keepachangelog.com/) · [SemVer](https://semver.org/).
4
4
 
5
+ ## 1.1.1 — 2026-07-06
6
+
7
+ - **Feature-gate**: added `requires_feature: has_prd_workflow` frontmatter — this skill is now linked into a consumer only when `features.has_prd_workflow` is enabled (it already refused at runtime otherwise). Trims Codex's ~2% skill-description budget on projects without the feature. Install-time gate, portable across Claude + Codex.
8
+
5
9
  ## 1.1.0 — 2026-07-02
6
10
 
7
11
  - **Analysis-profile contract (framework v4.94.0)**: PATCH-mode re-discovery passes `PROFILE=impact` to `codebase-architect` (blast-radius scoping per `framework/agents/analysis-profiles.md`).
@@ -1,7 +1,8 @@
1
1
  ---
2
2
  name: prd-add
3
3
  effort: medium
4
- version: 1.1.0
4
+ version: 1.1.1
5
+ requires_feature: has_prd_workflow
5
6
  description: "Change Request skill for active PRD sessions. Use when the user says /prd-add, 'aggiungi requisito', 'serve anche', 'manca un endpoint', or describes a new requirement that impacts an existing PRD. Also auto-triggered by /prd during discovery when the user's answer reveals a new sub-feature not covered by the original scope. Runs ICIAS impact analysis (semantic scan + structural propagation + scoring) to determine which PRD phases need SKIP/PATCH/REDO, then executes only affected phases."
6
7
  ---
7
8
 
@@ -27,13 +27,42 @@ when the skill needs them** — no empty placeholder dirs (that was upstream
27
27
  name: <kebab-case> # matches the directory name
28
28
  effort: medium # low | medium | high | xhigh | max (effort-protocol.md)
29
29
  version: 1.0.0 # the skill's OWN SemVer
30
+ requires_feature: has_x # OPTIONAL — install-time feature-gate (see below)
30
31
  description: > # triggers + what/when; NO angle brackets
31
32
  ...
32
33
  ---
33
34
  ```
34
35
 
35
- Order: `name` → `effort` → `version` → `description`. `license` / `metadata` are
36
- tolerated only on imported third-party skills; do not add other keys.
36
+ Order: `name` → `effort` → `version` → (`requires_feature`) `description`.
37
+ `license` / `metadata` are tolerated only on imported third-party skills; do not
38
+ add other keys.
39
+
40
+ ### `requires_feature` — install-time feature-gate (OPTIONAL)
41
+
42
+ A skill that is only meaningful when a `baldart.config.yml` `features.*` flag is
43
+ on (and already REFUSES at runtime otherwise) declares that dependency here, so
44
+ the installer links it into a consumer ONLY when the feature is enabled. This
45
+ trims Codex's ~2% skill-description budget on projects that don't use the feature.
46
+
47
+ ```yaml
48
+ requires_feature: has_design_system # string → link iff true
49
+ requires_feature: [has_design_system, has_e2e_review] # list = AND (ALL true)
50
+ ```
51
+
52
+ - **Omit it** for any skill that should always be linked (the default). The five
53
+ bootstrap skills that ENABLE a feature (`design-system-init`, `i18n-adopt`,
54
+ `graphify-bootstrap`, `lsp-bootstrap`, `toolchain-bootstrap`) MUST stay ungated
55
+ — otherwise the feature could never be turned on.
56
+ - The gate is **install-time only** and **portable**: both Claude (`.claude/skills/`)
57
+ and Codex (`.agents/skills/`) simply see fewer directories. Neither runtime reads
58
+ the field — it is ignored exactly like `version`/`effort`. Resolution lives in
59
+ `src/utils/skill-gate.js`; it reads `features.*` directly and **fails open** (a
60
+ missing config, or a malformed frontmatter, links the skill rather than hiding it).
61
+ - **NOT a `baldart.config.yml` key** — it consumes existing `features.*`, so the
62
+ schema-change propagation rule does NOT apply. It is skill-frontmatter metadata.
63
+ - **Invariant** (latent, for future Codex `agents/openai.yaml` metadata): a gated
64
+ skill MUST NOT be declared as a `dependencies:` target of an always-linked skill's
65
+ `openai.yaml` — pruning the gated skill would leave a dangling dependency.
37
66
 
38
67
  ## Body headers (in order, when applicable)
39
68
 
@@ -39,10 +39,22 @@ def validate_skill(skill_path):
39
39
  return False, f"Invalid YAML in frontmatter: {e}"
40
40
 
41
41
  # Define allowed properties
42
- # BALDART additions: `effort` (per-skill reasoning baseline — effort-protocol.md)
43
- # and `version` (per-skill SemVer — canonical structure, see references/skill-structure.md).
42
+ # BALDART additions: `effort` (per-skill reasoning baseline — effort-protocol.md),
43
+ # `version` (per-skill SemVer — canonical structure, see references/skill-structure.md)
44
+ # and `requires_feature` (install-time feature-gate — a skill is linked into a
45
+ # consumer only when this `features.*` flag is on; see references/skill-structure.md
46
+ # and src/utils/skill-gate.js). All three are OPTIONAL frontmatter metadata,
47
+ # ignored by the Claude/Codex skill loaders (they only read name+description).
44
48
  ALLOWED_PROPERTIES = {'name', 'description', 'license', 'allowed-tools', 'metadata',
45
- 'effort', 'version'}
49
+ 'effort', 'version', 'requires_feature'}
50
+
51
+ # Known feature flags a `requires_feature` value may reference (mirrors the
52
+ # features.* block of baldart.config.template.yml). Used for a SOFT warning
53
+ # only — an unknown value is not fatal (fail-open: the gate links the skill).
54
+ KNOWN_FEATURES = {'has_design_system', 'multi_tenant_theming', 'has_api_docs',
55
+ 'has_backlog', 'has_adrs', 'has_prd_workflow', 'has_wiki_overlay',
56
+ 'has_lsp_layer', 'has_code_graph', 'has_e2e_review',
57
+ 'has_toolchain', 'has_i18n'}
46
58
 
47
59
  # Check for unexpected properties (excluding nested keys under metadata)
48
60
  unexpected_keys = set(frontmatter.keys()) - ALLOWED_PROPERTIES
@@ -90,6 +102,16 @@ def validate_skill(skill_path):
90
102
  # `version` + a per-skill CHANGELOG.md are REQUIRED for new skills but WARNED (not
91
103
  # hard-failed) here for backwards-compat with skills authored before the convention.
92
104
  warnings = []
105
+ # Soft-validate requires_feature values against the known features.* set.
106
+ if 'requires_feature' in frontmatter:
107
+ req = frontmatter['requires_feature']
108
+ req_list = req if isinstance(req, list) else [req]
109
+ unknown = [str(f) for f in req_list if str(f) not in KNOWN_FEATURES]
110
+ if unknown:
111
+ warnings.append(
112
+ f"`requires_feature` references unknown flag(s): {', '.join(sorted(unknown))}. "
113
+ f"The gate fails open (skill still linked). Known: {', '.join(sorted(KNOWN_FEATURES))}"
114
+ )
93
115
  if 'version' not in frontmatter:
94
116
  warnings.append("missing `version:` in frontmatter (add `version: 1.0.0`)")
95
117
  if 'effort' not in frontmatter:
@@ -2,6 +2,10 @@
2
2
 
3
3
  Formato: [Keep a Changelog](https://keepachangelog.com/) · [SemVer](https://semver.org/).
4
4
 
5
+ ## 2.0.1 — 2026-07-06
6
+
7
+ - **Feature-gate**: added `requires_feature: has_design_system` frontmatter — this skill is now linked into a consumer only when `features.has_design_system` is enabled (it already refused at runtime otherwise). Trims Codex's ~2% skill-description budget on projects without the feature. Install-time gate, portable across Claude + Codex.
8
+
5
9
  ## 2.0.0 — 2026-07-03
6
10
 
7
11
  **The local design studio.** Root-level refactor (framework v5.2.0): the
@@ -1,7 +1,8 @@
1
1
  ---
2
2
  name: ui-design
3
3
  effort: medium
4
- version: 2.0.0
4
+ version: 2.0.1
5
+ requires_feature: has_design_system
5
6
  description: "The local design studio — THE framework skill for designing, prototyping and restyling UI directly in the repo (the internal twin of the Claude Design handoff). Produces context-aware, token-compliant HTML mockups in 3 distinct directions, with a locked Design Read, a deterministic anti-slop craft gate and separated generator/evaluator verification. Use when: (1) designing new pages, screens or components, (2) the /prd skill reaches its UI design phase (Step 3), (3) redesigning/restyling existing pages ('rifai la UI', 'migliora il design', 'restyle'), (4) exploring design options ('3 opzioni', 'opzioni di design', 'direzioni estetiche'), (5) prototyping a page/landing/dashboard locally ('prototipo', 'mockup', 'progetta interfaccia', 'design UI'). SUPERSEDES frontend-design (now a router) and ui-ux-pro-max for ALL design/prototyping work. Not for: implementing an already-approved mockup (→ /ui-implement), in-code UI fixes with no design decision (→ ui-expert agent), motion specs (→ /motion-design)."
6
7
  ---
7
8
 
@@ -2,6 +2,10 @@
2
2
 
3
3
  Formato: [Keep a Changelog](https://keepachangelog.com/) · [SemVer](https://semver.org/).
4
4
 
5
+ ## 1.1.2 — 2026-07-06
6
+
7
+ - **Feature-gate**: added `requires_feature: has_design_system` frontmatter — this skill is now linked into a consumer only when `features.has_design_system` is enabled (it already refused at runtime otherwise). Trims Codex's ~2% skill-description budget on projects without the feature. Install-time gate, portable across Claude + Codex.
8
+
5
9
  ## 1.1.1 — 2026-07-02
6
10
 
7
11
  - Return Contract (SKILL.md): documentati i campi `bindings` + `lane_coverage`/`lanes`
@@ -1,7 +1,8 @@
1
1
  ---
2
2
  name: ui-implement
3
3
  effort: medium
4
- version: 1.1.1
4
+ version: 1.1.2
5
+ requires_feature: has_design_system
5
6
  description: >
6
7
  Implement an ALREADY-APPROVED mockup into the codebase end-to-end and
7
8
  self-verify its fidelity. Takes a mockup in any channel — a Claude Design
@@ -634,7 +634,11 @@ async function runCard(cardId, cardPath) {
634
634
  : `Phase 1 fallback: the architect specialist was unavailable — do the Phase 1 claim+architect exploration yourself per ${REF}/implement.md and persist the baseline to ${baselinePath} before coding.`
635
635
  // T1.A (v4.90.0) — mockup cards: binding-compliance is verified deterministically by the
636
636
  // check-bindings.mjs script (ui-implement Step 5b is the SSOT; this is the new2 parallel).
637
- const bindingBit = node.hasMockup && node.hasBindings
637
+ // Guard on has_design_system: component_bindings only exist when a /prd design
638
+ // phase (which requires the design system) produced them, so absent-DS + bindings
639
+ // is a malformed hand-authored card — don't emit a gate whose script path could
640
+ // resolve empty. Mirrors the has_design_system ds-gate guard above.
641
+ const bindingBit = features.has_design_system && node.hasMockup && node.hasBindings
638
642
  ? `\nBINDING GATE (deterministic — ui-implement Step 5b parallel): after building, run \`node "$(ls .claude/skills/ui-implement/scripts/check-bindings.mjs .framework/framework/.claude/skills/ui-implement/scripts/check-bindings.mjs 2>/dev/null | head -1)" --bindings-json '<the card component_bindings as JSON>' --files <your touched implementation files>\`. Exit 3 = a reuse/reuse-variant binding is NOT used in the code: the map is AUTHORITATIVE — fix it before returning (one pass). Include the script's one-line JSON output as bindingCheck in your return.\n`
639
643
  : ''
640
644
  let impl
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "baldart",
3
- "version": "5.6.0",
3
+ "version": "5.7.0",
4
4
  "description": "Claude Agent Framework - Reusable framework for coordinating AI agents and humans in software projects",
5
5
  "bin": {
6
6
  "baldart": "./bin/baldart.js"
@@ -1508,10 +1508,17 @@ async function configure(opts = {}) {
1508
1508
  const enabledTools = (merged.tools && Array.isArray(merged.tools.enabled) && merged.tools.enabled.length)
1509
1509
  ? merged.tools.enabled : ['claude'];
1510
1510
  if (fs.existsSync(path.join(cwd, '.framework'))) {
1511
- new SymlinkUtils(cwd).mergeAgents({ tools: enabledTools });
1511
+ const symlinks = new SymlinkUtils(cwd);
1512
+ symlinks.mergeAgents({ tools: enabledTools });
1513
+ // Also re-merge SKILLS: the feature-gate (src/utils/skill-gate.js) depends
1514
+ // on the `features.*` just written. On first install `mergeSkills` ran
1515
+ // fail-open BEFORE this config existed (linking everything), so this pass
1516
+ // is what actually prunes the gated-off skills. Without it, gating never
1517
+ // takes effect until the next `baldart update`.
1518
+ symlinks.mergeSkills({ tools: enabledTools });
1512
1519
  }
1513
1520
  } catch (err) {
1514
- UI.warning(`Agent re-merge after configure failed (${err.message}) — run \`npx baldart doctor\` to heal.`);
1521
+ UI.warning(`Agent/skill re-merge after configure failed (${err.message}) — run \`npx baldart doctor\` to heal.`);
1515
1522
  }
1516
1523
 
1517
1524
  // v5.1.0 — research library: create + seed ${paths.research_dir} right after
@@ -409,6 +409,47 @@ async function detectState(cwd, opts = {}) {
409
409
  }
410
410
  } catch (_) { /* never block doctor on probe */ }
411
411
 
412
+ // ---- Feature-gated skill drift (skill-gate) ------------------------
413
+ // A skill whose `requires_feature` frontmatter is no longer satisfied
414
+ // (feature flipped off in baldart.config.yml) may still be linked from a
415
+ // prior install. `configure` and `update` both re-merge (which prunes), but
416
+ // a user who edits the config and runs ONLY `doctor` should be told. The
417
+ // reverse (a feature just turned ON, gated skill not yet linked) is covered
418
+ // by the missing-skills path. Advisory only (fix = `reconcile-skills`, a
419
+ // plain re-merge); never blocks. Fully fail-safe.
420
+ state.gatedSkillsLinked = [];
421
+ try {
422
+ if (state.frameworkPresent) {
423
+ const { resolveSkillGate } = require('../utils/skill-gate');
424
+ const { getAdapter } = require('../utils/tool-adapters');
425
+ const frameworkSkillsDir = path.join(cwd, '.framework', 'framework', '.claude', 'skills');
426
+ const gate = resolveSkillGate(frameworkSkillsDir, cwd);
427
+ if (!gate.failOpen && gate.gatedOff.length) {
428
+ const toolsEnabled = (config && !config.__malformed && config.tools && Array.isArray(config.tools.enabled))
429
+ ? config.tools.enabled : ['claude'];
430
+ const seen = new Set();
431
+ for (const toolName of toolsEnabled) {
432
+ let adapter;
433
+ try { adapter = getAdapter(toolName, cwd); } catch (_) { continue; }
434
+ const skillsRel = adapter.skillsDir();
435
+ for (const name of gate.gatedOff) {
436
+ if (seen.has(name)) continue;
437
+ const linkPath = path.join(cwd, skillsRel, name);
438
+ try {
439
+ const lst = fs.lstatSync(linkPath);
440
+ if (!lst.isSymbolicLink()) continue;
441
+ const resolved = path.resolve(path.dirname(linkPath), fs.readlinkSync(linkPath));
442
+ if (resolved === path.join(frameworkSkillsDir, name)) {
443
+ seen.add(name);
444
+ state.gatedSkillsLinked.push(name);
445
+ }
446
+ } catch (_) { /* absent */ }
447
+ }
448
+ }
449
+ }
450
+ }
451
+ } catch (_) { /* never block doctor on probe */ }
452
+
412
453
  // ---- Workflow symlink integrity (since v4.14.0) --------------------
413
454
  // Dynamic workflows (.js) are Claude-only and per-item symlinked into
414
455
  // `.claude/workflows/`. Unlike agents there is NO hard discovery gate —
@@ -1086,6 +1127,30 @@ function planActions(state) {
1086
1127
  });
1087
1128
  }
1088
1129
 
1130
+ // Feature-gated skill drift (skill-gate). Advisory — these skills' gating
1131
+ // feature is now off, but they're still linked from a prior install; they'd
1132
+ // only refuse at runtime and waste the Codex skill-description budget. A plain
1133
+ // re-merge prunes them (same rationale as the agent/workflow repairs).
1134
+ if (state.gatedSkillsLinked && state.gatedSkillsLinked.length > 0) {
1135
+ actions.push({
1136
+ key: 'reconcile-skills',
1137
+ label: `Unlink ${state.gatedSkillsLinked.length} feature-gated skill(s)`,
1138
+ why: `These skills are still linked but their gating feature is now off in baldart.config.yml: ${state.gatedSkillsLinked.join(', ')}. They refuse at runtime and consume Codex's ~2% skill-description budget. Re-merging prunes them (re-enable the feature to bring them back).`,
1139
+ autoOk: true,
1140
+ run: async () => {
1141
+ const SymlinkUtils = require('../utils/symlinks');
1142
+ const cfg = loadConfig(process.cwd());
1143
+ const enabledTools = (cfg && !cfg.__malformed
1144
+ && cfg.tools && Array.isArray(cfg.tools.enabled))
1145
+ ? cfg.tools.enabled
1146
+ : ['claude'];
1147
+ const symlinks = new SymlinkUtils();
1148
+ symlinks.mergeSkills({ tools: enabledTools });
1149
+ UI.success(`Reconciled feature-gated skills for tools: ${enabledTools.join(', ')}`);
1150
+ },
1151
+ });
1152
+ }
1153
+
1089
1154
  // Root primitives integrity (since v4.83.0). Regenerates AGENTS.md / CLAUDE.md
1090
1155
  // from the versioned skeleton + config + overlay when missing, still a legacy
1091
1156
  // symlink, or drifted. Offline + idempotent. A handwritten file is NOT touched
@@ -0,0 +1,139 @@
1
+ /**
2
+ * skill-gate.js — install-time feature-gating for framework skills.
3
+ *
4
+ * BALDART ships ~40 skills; Codex reserves only ~2% of its context window for
5
+ * skill descriptions, so linking every skill (including ones whose gating
6
+ * `features.*` flag is OFF, which then refuse at runtime anyway) wastes that
7
+ * budget. This module decides, per install/update, WHICH framework skills to
8
+ * link into a consumer, based on an OPTIONAL `requires_feature` frontmatter
9
+ * field in each skill's `SKILL.md`.
10
+ *
11
+ * The gate is PURELY install-time (resolved here by the Node installer) — both
12
+ * Claude (`.claude/skills/`) and Codex (`.agents/skills/`) simply see fewer
13
+ * directories. Neither runtime interprets `requires_feature`; it is a BALDART
14
+ * authoring construct, ignored by the skill loaders exactly like `version:` /
15
+ * `effort:`. So the filter is natively portable across both tools and applied
16
+ * from a single code path (the gate is shared SEMANTICS, not runtime mechanics).
17
+ *
18
+ * Frontmatter contract (all OPTIONAL — a skill with no `requires_feature` is
19
+ * ALWAYS linked, backwards-compatible default):
20
+ * requires_feature: has_design_system # string → link iff true
21
+ * requires_feature: [has_design_system, has_e2e_review] # list = AND (all true)
22
+ *
23
+ * Fail-open everywhere (never hide a skill by accident — the safety net must
24
+ * not become a tribunal):
25
+ * - config absent / unreadable / malformed → link EVERYTHING.
26
+ * - a single skill's frontmatter malformed → link THAT skill (per-skill),
27
+ * never crash the whole merge.
28
+ *
29
+ * NOTE: we read `features.*` DIRECTLY from baldart.config.yml rather than reuse
30
+ * `agent-slots.resolveAgentFlags()` — that flag set intentionally covers only
31
+ * the keys the agent SLOT sources reference and omits `has_prd_workflow` /
32
+ * `has_e2e_review`, which ARE skill gates. Reading the raw block keeps the gate
33
+ * correct for every existing (and future) `features.*` key with no coupling.
34
+ *
35
+ * This introduces NO new `baldart.config.yml` key (it consumes existing
36
+ * `features.*`) — the schema-change propagation rule does NOT apply. It is
37
+ * skill-frontmatter metadata, like `version`/`effort`.
38
+ */
39
+
40
+ const fs = require('fs');
41
+ const path = require('path');
42
+
43
+ /**
44
+ * Load the `features` block from baldart.config.yml.
45
+ * @returns {{ features: Object|null }} `features: null` signals "config
46
+ * unavailable" → callers must fail-open (link everything).
47
+ */
48
+ function loadFeatures(cwd) {
49
+ try {
50
+ const yaml = require('js-yaml');
51
+ const cfgPath = path.join(cwd, 'baldart.config.yml');
52
+ if (!fs.existsSync(cfgPath)) return { features: null };
53
+ const cfg = yaml.load(fs.readFileSync(cfgPath, 'utf8'));
54
+ if (!cfg || typeof cfg !== 'object') return { features: null };
55
+ const features = (cfg.features && typeof cfg.features === 'object') ? cfg.features : {};
56
+ return { features };
57
+ } catch (_) {
58
+ return { features: null };
59
+ }
60
+ }
61
+
62
+ /**
63
+ * Extract the OPTIONAL `requires_feature` from a skill's SKILL.md frontmatter.
64
+ * @returns {string[]|null} normalized list of required feature keys, or null
65
+ * when the skill is ungated / has no SKILL.md / frontmatter is unparseable
66
+ * (fail-open: null ⇒ always link).
67
+ */
68
+ function readRequiredFeatures(skillDir) {
69
+ try {
70
+ const skillMd = path.join(skillDir, 'SKILL.md');
71
+ if (!fs.existsSync(skillMd)) return null;
72
+ const raw = fs.readFileSync(skillMd, 'utf8');
73
+ if (!raw.startsWith('---')) return null;
74
+ // Frontmatter is between the first two `---` fences.
75
+ const end = raw.indexOf('\n---', 3);
76
+ if (end === -1) return null;
77
+ const fmText = raw.slice(3, end);
78
+ const yaml = require('js-yaml');
79
+ const fm = yaml.load(fmText);
80
+ if (!fm || typeof fm !== 'object') return null;
81
+ const req = fm.requires_feature;
82
+ if (req == null) return null;
83
+ const list = (Array.isArray(req) ? req : [req])
84
+ .map(x => String(x).trim())
85
+ .filter(Boolean);
86
+ return list.length ? list : null;
87
+ } catch (_) {
88
+ // Malformed frontmatter for THIS skill → fail-open (link it).
89
+ return null;
90
+ }
91
+ }
92
+
93
+ /**
94
+ * Decide which framework skills to link vs skip for this consumer.
95
+ *
96
+ * @param {string} frameworkSkillsDir absolute path to
97
+ * `.framework/framework/.claude/skills/`
98
+ * @param {string} cwd consumer repo root (where baldart.config.yml lives)
99
+ * @returns {{ toLink: string[], gatedOff: string[], failOpen: boolean }}
100
+ * `toLink` = skill dir names to link; `gatedOff` = skill dir names to skip
101
+ * (and prune if already linked); `failOpen` = true when config was
102
+ * unavailable so nothing was gated.
103
+ */
104
+ function resolveSkillGate(frameworkSkillsDir, cwd) {
105
+ const result = { toLink: [], gatedOff: [], failOpen: false };
106
+
107
+ let names = [];
108
+ try {
109
+ names = fs.readdirSync(frameworkSkillsDir).filter(name => {
110
+ if (name.startsWith('.')) return false;
111
+ try { return fs.lstatSync(path.join(frameworkSkillsDir, name)).isDirectory(); }
112
+ catch (_) { return false; }
113
+ });
114
+ } catch (_) {
115
+ return result; // no framework skills dir → empty sets, caller handles
116
+ }
117
+
118
+ const { features } = loadFeatures(cwd);
119
+
120
+ // Config unavailable → fail-open: link everything, gate nothing.
121
+ if (features === null) {
122
+ result.failOpen = true;
123
+ result.toLink = names.slice();
124
+ return result;
125
+ }
126
+
127
+ for (const name of names) {
128
+ const required = readRequiredFeatures(path.join(frameworkSkillsDir, name));
129
+ if (!required) { result.toLink.push(name); continue; }
130
+ // AND semantics: link iff EVERY required feature is explicitly true.
131
+ const allOn = required.every(f => features[f] === true);
132
+ if (allOn) result.toLink.push(name);
133
+ else result.gatedOff.push(name);
134
+ }
135
+
136
+ return result;
137
+ }
138
+
139
+ module.exports = { resolveSkillGate, readRequiredFeatures, loadFeatures };
@@ -210,7 +210,7 @@ class SymlinkUtils {
210
210
  */
211
211
  mergeSkills(opts = {}) {
212
212
  const tools = (opts.tools && opts.tools.length) ? opts.tools : ['claude'];
213
- const aggregate = { linked: [], skipped: [], conflicts: [] };
213
+ const aggregate = { linked: [], skipped: [], conflicts: [], pruned: [], gatedOff: [] };
214
214
 
215
215
  const frameworkSkillsDir = path.join(this.cwd, FRAMEWORK_PAYLOAD, '.claude', 'skills');
216
216
  if (!fs.existsSync(frameworkSkillsDir)) {
@@ -218,17 +218,31 @@ class SymlinkUtils {
218
218
  return aggregate;
219
219
  }
220
220
 
221
- const frameworkSkills = fs.readdirSync(frameworkSkillsDir).filter(name => {
222
- if (name.startsWith('.')) return false;
223
- const full = path.join(frameworkSkillsDir, name);
224
- return fs.lstatSync(full).isDirectory();
225
- });
221
+ // Feature-gate: link only the skills whose `requires_feature` frontmatter is
222
+ // satisfied by this consumer's `features.*` (fail-open when config is
223
+ // unavailable link everything). Resolved ONCE and applied to EVERY tool
224
+ // from this single code path — the gate is shared semantics, so Claude and
225
+ // Codex stay identical by construction. See src/utils/skill-gate.js.
226
+ const gate = require('./skill-gate').resolveSkillGate(frameworkSkillsDir, this.cwd);
227
+ const frameworkSkills = gate.toLink;
228
+ aggregate.gatedOff = gate.gatedOff.slice();
229
+ if (gate.gatedOff.length) {
230
+ UI.info(`Feature-gate: ${gate.gatedOff.length} skill(s) skipped (gating feature off): ${gate.gatedOff.join(', ')}`);
231
+ }
226
232
 
227
233
  for (const tool of tools) {
228
234
  const result = this._mergeSkillsForTool(tool, frameworkSkills, frameworkSkillsDir);
229
235
  aggregate.linked.push(...result.linked);
230
236
  aggregate.skipped.push(...result.skipped);
231
237
  aggregate.conflicts.push(...result.conflicts);
238
+
239
+ // Prune direction: remove any pre-existing framework symlink for a skill
240
+ // that is now gated-off (e.g. a feature flipped false after a prior
241
+ // install, or a fail-open install that linked everything before config
242
+ // existed). Only OUR framework symlinks are removed — never a real user
243
+ // skill dir, never a user-override symlink pointing elsewhere.
244
+ const pruneResult = this._pruneGatedSkillsForTool(tool, gate.gatedOff, frameworkSkillsDir);
245
+ aggregate.pruned.push(...pruneResult.pruned);
232
246
  }
233
247
 
234
248
  if (aggregate.conflicts.length > 0) {
@@ -329,6 +343,54 @@ class SymlinkUtils {
329
343
  return result;
330
344
  }
331
345
 
346
+ /**
347
+ * Prune framework skill symlinks that are now gated-off — internal.
348
+ *
349
+ * This is the reconcile (removal) direction of the feature-gate: turning a
350
+ * `features.*` flag off (or a fail-open first install that linked everything
351
+ * before config existed) must actually REMOVE the stale symlinks, or the
352
+ * skill-description budget never shrinks. Each `name` in `gatedOff` is a
353
+ * framework skill name by construction (it came from the framework catalog),
354
+ * so basename∈catalog is guaranteed; we still verify the link RESOLVES to our
355
+ * framework source for that exact skill before unlinking. Safety invariants:
356
+ * - never touch a real directory (a user-authored skill of the same name);
357
+ * - never touch a symlink pointing ANYWHERE ELSE (a user override, cf. the
358
+ * `user-symlink-override` branch in _mergeSkillsForTool).
359
+ */
360
+ _pruneGatedSkillsForTool(toolName, gatedOff, frameworkSkillsDir) {
361
+ const { getAdapter } = require('./tool-adapters');
362
+ const adapter = getAdapter(toolName, this.cwd);
363
+ const result = { pruned: [] };
364
+ if (!gatedOff || !gatedOff.length) return result;
365
+
366
+ const skillsRel = adapter.skillsDir();
367
+ const userSkillsDir = path.join(this.cwd, skillsRel);
368
+ if (!fs.existsSync(userSkillsDir)) return result;
369
+
370
+ for (const name of gatedOff) {
371
+ const linkPath = path.join(userSkillsDir, name);
372
+ let lstat = null;
373
+ try { lstat = fs.lstatSync(linkPath); } catch (_) { continue; /* absent */ }
374
+
375
+ // Only ever remove a symlink — a real dir here is a user-authored skill.
376
+ if (!lstat.isSymbolicLink()) continue;
377
+
378
+ const current = fs.readlinkSync(linkPath);
379
+ const resolved = path.resolve(path.dirname(linkPath), current);
380
+ const frameworkTarget = path.join(frameworkSkillsDir, name);
381
+
382
+ // Unlink ONLY when it resolves to OUR framework source for this skill.
383
+ // A symlink pointing elsewhere is a deliberate user override → leave it.
384
+ if (resolved === frameworkTarget) {
385
+ fs.unlinkSync(linkPath);
386
+ UI.info(`[${adapter.label}] Skill unlinked (feature gated off): ${path.join(skillsRel, name)}`);
387
+ result.pruned.push({ tool: toolName, name });
388
+ }
389
+ }
390
+
391
+ return result;
392
+ }
393
+
332
394
  // -----------------------------------------------------------------------
333
395
  // Public composite operations
334
396
  // -----------------------------------------------------------------------
@@ -827,7 +889,10 @@ class SymlinkUtils {
827
889
  UI.warning(`[${adapter.label}] Legacy bulk skills symlink at ${skillsRel}. Run \`npx baldart update\` to convert to per-item.`);
828
890
  allValid = false;
829
891
  } else {
830
- const sample = ['skill-creator', 'frontend-design', 'bug', 'prd', 'capture'];
892
+ // Sample ONLY always-linked skills — never feature-gated ones (e.g.
893
+ // prd/capture), which are legitimately absent when their feature is
894
+ // off and would make this probe brittle / falsely alarming.
895
+ const sample = ['skill-creator', 'bug', 'simplify', 'overlay', 'find-skills'];
831
896
  let frameworkLinks = 0;
832
897
  sample.forEach(name => {
833
898
  const p = path.join(skillsDir, name);