baldart 5.6.0 → 5.8.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 (50) hide show
  1. package/CHANGELOG.md +98 -0
  2. package/VERSION +1 -1
  3. package/framework/.claude/agents/prd-card-writer.md +23 -0
  4. package/framework/.claude/skills/capture/CHANGELOG.md +4 -0
  5. package/framework/.claude/skills/capture/SKILL.md +2 -1
  6. package/framework/.claude/skills/design-sync/CHANGELOG.md +4 -0
  7. package/framework/.claude/skills/design-sync/SKILL.md +2 -1
  8. package/framework/.claude/skills/ds-edit/CHANGELOG.md +5 -0
  9. package/framework/.claude/skills/ds-edit/SKILL.md +9 -10
  10. package/framework/.claude/skills/ds-handoff/CHANGELOG.md +5 -0
  11. package/framework/.claude/skills/ds-handoff/SKILL.md +3 -2
  12. package/framework/.claude/skills/ds-new/CHANGELOG.md +4 -0
  13. package/framework/.claude/skills/ds-new/SKILL.md +2 -1
  14. package/framework/.claude/skills/ds-render/CHANGELOG.md +4 -0
  15. package/framework/.claude/skills/ds-render/SKILL.md +2 -1
  16. package/framework/.claude/skills/e2e-review/CHANGELOG.md +4 -0
  17. package/framework/.claude/skills/e2e-review/SKILL.md +2 -1
  18. package/framework/.claude/skills/gamification-design/CHANGELOG.md +4 -0
  19. package/framework/.claude/skills/gamification-design/SKILL.md +2 -1
  20. package/framework/.claude/skills/graph-align/CHANGELOG.md +4 -0
  21. package/framework/.claude/skills/graph-align/SKILL.md +2 -1
  22. package/framework/.claude/skills/i18n/CHANGELOG.md +4 -0
  23. package/framework/.claude/skills/i18n/SKILL.md +2 -1
  24. package/framework/.claude/skills/motion-design/CHANGELOG.md +4 -0
  25. package/framework/.claude/skills/motion-design/SKILL.md +2 -1
  26. package/framework/.claude/skills/new/CHANGELOG.md +19 -0
  27. package/framework/.claude/skills/new/SKILL.md +2 -1
  28. package/framework/.claude/skills/new/references/final-review.md +36 -1
  29. package/framework/.claude/skills/new2/CHANGELOG.md +15 -0
  30. package/framework/.claude/skills/new2/SKILL.md +2 -1
  31. package/framework/.claude/skills/prd/CHANGELOG.md +32 -0
  32. package/framework/.claude/skills/prd/SKILL.md +2 -1
  33. package/framework/.claude/skills/prd/assets/epic-template.yml +6 -0
  34. package/framework/.claude/skills/prd/references/backlog-phase.md +36 -0
  35. package/framework/.claude/skills/prd/references/validation-phase.md +30 -0
  36. package/framework/.claude/skills/prd-add/CHANGELOG.md +4 -0
  37. package/framework/.claude/skills/prd-add/SKILL.md +2 -1
  38. package/framework/.claude/skills/skill-creator/references/skill-structure.md +31 -2
  39. package/framework/.claude/skills/skill-creator/scripts/quick_validate.py +25 -3
  40. package/framework/.claude/skills/ui-design/CHANGELOG.md +4 -0
  41. package/framework/.claude/skills/ui-design/SKILL.md +2 -1
  42. package/framework/.claude/skills/ui-implement/CHANGELOG.md +4 -0
  43. package/framework/.claude/skills/ui-implement/SKILL.md +2 -1
  44. package/framework/.claude/workflows/new2.js +24 -3
  45. package/framework/agents/card-schema.md +50 -0
  46. package/package.json +1 -1
  47. package/src/commands/configure.js +9 -2
  48. package/src/commands/doctor.js +65 -0
  49. package/src/utils/skill-gate.js +139 -0
  50. package/src/utils/symlinks.js +72 -7
@@ -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);