baldart 3.6.2

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 (230) hide show
  1. package/CHANGELOG.md +599 -0
  2. package/README.md +566 -0
  3. package/VERSION +1 -0
  4. package/bin/baldart.js +143 -0
  5. package/framework/.claude/agents/REGISTRY.md +169 -0
  6. package/framework/.claude/agents/api-perf-cost-auditor.md +291 -0
  7. package/framework/.claude/agents/code-reviewer.md +350 -0
  8. package/framework/.claude/agents/codebase-architect.md +391 -0
  9. package/framework/.claude/agents/coder.md +291 -0
  10. package/framework/.claude/agents/deep-human-insight.md +198 -0
  11. package/framework/.claude/agents/doc-reviewer.md +440 -0
  12. package/framework/.claude/agents/email-deliverability-architect.md +193 -0
  13. package/framework/.claude/agents/hybrid-ml-architect.md +285 -0
  14. package/framework/.claude/agents/hyper-gamification-designer.md +149 -0
  15. package/framework/.claude/agents/legal-counsel-gdpr.md +179 -0
  16. package/framework/.claude/agents/marketing-conversion-strategist.md +162 -0
  17. package/framework/.claude/agents/motion-expert.md +108 -0
  18. package/framework/.claude/agents/onboarding-architect-lead.md +230 -0
  19. package/framework/.claude/agents/plan-auditor.md +546 -0
  20. package/framework/.claude/agents/prd-card-writer.md +372 -0
  21. package/framework/.claude/agents/prd.md +744 -0
  22. package/framework/.claude/agents/qa-sentinel.md +305 -0
  23. package/framework/.claude/agents/remotion-animator-orchestrator.md +218 -0
  24. package/framework/.claude/agents/security-reviewer.md +276 -0
  25. package/framework/.claude/agents/senior-researcher.md +175 -0
  26. package/framework/.claude/agents/seo-analytics-strategist.md +156 -0
  27. package/framework/.claude/agents/skill-improver.md +61 -0
  28. package/framework/.claude/agents/ui-expert.md +191 -0
  29. package/framework/.claude/agents/visual-designer.md +190 -0
  30. package/framework/.claude/agents/website-orchestrator.md +118 -0
  31. package/framework/.claude/agents/wiki-curator.md +145 -0
  32. package/framework/.claude/commands/baldart-push.md +15 -0
  33. package/framework/.claude/commands/check.md +237 -0
  34. package/framework/.claude/commands/codexreview.md +203 -0
  35. package/framework/.claude/commands/design-review.md +11 -0
  36. package/framework/.claude/commands/issue-review.md +34 -0
  37. package/framework/.claude/commands/new.md +331 -0
  38. package/framework/.claude/commands/qa.md +257 -0
  39. package/framework/.claude/hooks/framework-edit-gate.js +208 -0
  40. package/framework/.claude/hooks/lint-before-commit.sh.template +66 -0
  41. package/framework/.claude/settings.local.json.example +32 -0
  42. package/framework/.claude/skills/api-design-principles/SKILL.md +567 -0
  43. package/framework/.claude/skills/api-design-principles/assets/api-design-checklist.md +155 -0
  44. package/framework/.claude/skills/api-design-principles/assets/rest-api-template.py +182 -0
  45. package/framework/.claude/skills/api-design-principles/references/graphql-schema-design.md +583 -0
  46. package/framework/.claude/skills/api-design-principles/references/rest-best-practices.md +408 -0
  47. package/framework/.claude/skills/baldart-push/SKILL.md +222 -0
  48. package/framework/.claude/skills/bug/SKILL.md +200 -0
  49. package/framework/.claude/skills/bug/references/logging-patterns.md +174 -0
  50. package/framework/.claude/skills/capture/SKILL.md +125 -0
  51. package/framework/.claude/skills/capture/references/synthesis-template.md +42 -0
  52. package/framework/.claude/skills/context-primer/SKILL.md +189 -0
  53. package/framework/.claude/skills/copywriting/SKILL.md +273 -0
  54. package/framework/.claude/skills/copywriting/references/copy-frameworks.md +338 -0
  55. package/framework/.claude/skills/copywriting/references/natural-transitions.md +252 -0
  56. package/framework/.claude/skills/doc-writing-for-rag/SKILL.md +119 -0
  57. package/framework/.claude/skills/doc-writing-for-rag/references/before-after-examples.md +291 -0
  58. package/framework/.claude/skills/doc-writing-for-rag/references/compact-templates.md +183 -0
  59. package/framework/.claude/skills/doc-writing-for-rag/references/frontmatter-minimal.md +112 -0
  60. package/framework/.claude/skills/doc-writing-for-rag/references/line-count-targets.md +110 -0
  61. package/framework/.claude/skills/doc-writing-for-rag/references/schemas-and-errors.md +129 -0
  62. package/framework/.claude/skills/find-skills/SKILL.md +133 -0
  63. package/framework/.claude/skills/frontend-design/LICENSE.txt +177 -0
  64. package/framework/.claude/skills/frontend-design/SKILL.md +84 -0
  65. package/framework/.claude/skills/gamification-design/SKILL.md +130 -0
  66. package/framework/.claude/skills/issue-review/SKILL.md +45 -0
  67. package/framework/.claude/skills/kie-ai/SKILL.md +262 -0
  68. package/framework/.claude/skills/kie-ai/references/models-catalog.md +272 -0
  69. package/framework/.claude/skills/kie-ai/scripts/kie_api.sh +209 -0
  70. package/framework/.claude/skills/kie-ai/scripts/remove_greenscreen.py +69 -0
  71. package/framework/.claude/skills/kie-ai/scripts/setup_api_key.sh +77 -0
  72. package/framework/.claude/skills/motion-design/LICENSE +21 -0
  73. package/framework/.claude/skills/motion-design/README.md +82 -0
  74. package/framework/.claude/skills/motion-design/SKILL.md +336 -0
  75. package/framework/.claude/skills/motion-design/director/choreography.md +93 -0
  76. package/framework/.claude/skills/motion-design/director/context-adaptation.md +83 -0
  77. package/framework/.claude/skills/motion-design/director/core-philosophy.md +53 -0
  78. package/framework/.claude/skills/motion-design/director/decision-framework.md +91 -0
  79. package/framework/.claude/skills/motion-design/director/disney-principles.md +102 -0
  80. package/framework/.claude/skills/motion-design/director/emotion-mapping.md +71 -0
  81. package/framework/.claude/skills/motion-design/director/motion-personality.md +89 -0
  82. package/framework/.claude/skills/motion-design/director/narrative-structure.md +62 -0
  83. package/framework/.claude/skills/motion-design/patterns/ambient-continuous.md +81 -0
  84. package/framework/.claude/skills/motion-design/patterns/entrance-exit.md +82 -0
  85. package/framework/.claude/skills/motion-design/patterns/multi-element.md +69 -0
  86. package/framework/.claude/skills/motion-design/patterns/state-feedback.md +96 -0
  87. package/framework/.claude/skills/motion-design/reference/property-selection.md +95 -0
  88. package/framework/.claude/skills/motion-design/reference/quality-checklist.md +67 -0
  89. package/framework/.claude/skills/motion-design/reference/timing-easing-tables.md +106 -0
  90. package/framework/.claude/skills/motion-design/reference/troubleshooting.md +73 -0
  91. package/framework/.claude/skills/new/SKILL.md +1687 -0
  92. package/framework/.claude/skills/playwright-skill/API_REFERENCE.md +652 -0
  93. package/framework/.claude/skills/playwright-skill/SKILL.md +157 -0
  94. package/framework/.claude/skills/playwright-skill/package.json +26 -0
  95. package/framework/.claude/skills/prd/SKILL.md +228 -0
  96. package/framework/.claude/skills/prd/assets/card-template.yml +232 -0
  97. package/framework/.claude/skills/prd/assets/epic-template.yml +190 -0
  98. package/framework/.claude/skills/prd/assets/prd-template.md +230 -0
  99. package/framework/.claude/skills/prd/assets/state-template.md +78 -0
  100. package/framework/.claude/skills/prd/references/api-perf-gate.md +152 -0
  101. package/framework/.claude/skills/prd/references/audit-phase.md +478 -0
  102. package/framework/.claude/skills/prd/references/backlog-phase.md +145 -0
  103. package/framework/.claude/skills/prd/references/discovery-phase.md +359 -0
  104. package/framework/.claude/skills/prd/references/impact-analysis.md +233 -0
  105. package/framework/.claude/skills/prd/references/prd-add-phase.md +214 -0
  106. package/framework/.claude/skills/prd/references/prd-writing-phase.md +145 -0
  107. package/framework/.claude/skills/prd/references/research-phase.md +216 -0
  108. package/framework/.claude/skills/prd/references/ui-design-phase.md +61 -0
  109. package/framework/.claude/skills/prd/references/validation-phase.md +72 -0
  110. package/framework/.claude/skills/prd-add/SKILL.md +222 -0
  111. package/framework/.claude/skills/prd-add/references/impact-analysis.md +233 -0
  112. package/framework/.claude/skills/remotion-best-practices/SKILL.md +48 -0
  113. package/framework/.claude/skills/remotion-best-practices/rules/3d.md +86 -0
  114. package/framework/.claude/skills/remotion-best-practices/rules/animations.md +29 -0
  115. package/framework/.claude/skills/remotion-best-practices/rules/assets/charts-bar-chart.tsx +173 -0
  116. package/framework/.claude/skills/remotion-best-practices/rules/assets/text-animations-typewriter.tsx +100 -0
  117. package/framework/.claude/skills/remotion-best-practices/rules/assets/text-animations-word-highlight.tsx +108 -0
  118. package/framework/.claude/skills/remotion-best-practices/rules/assets.md +78 -0
  119. package/framework/.claude/skills/remotion-best-practices/rules/audio.md +169 -0
  120. package/framework/.claude/skills/remotion-best-practices/rules/calculate-metadata.md +104 -0
  121. package/framework/.claude/skills/remotion-best-practices/rules/can-decode.md +75 -0
  122. package/framework/.claude/skills/remotion-best-practices/rules/charts.md +58 -0
  123. package/framework/.claude/skills/remotion-best-practices/rules/compositions.md +141 -0
  124. package/framework/.claude/skills/remotion-best-practices/rules/display-captions.md +184 -0
  125. package/framework/.claude/skills/remotion-best-practices/rules/extract-frames.md +229 -0
  126. package/framework/.claude/skills/remotion-best-practices/rules/fonts.md +152 -0
  127. package/framework/.claude/skills/remotion-best-practices/rules/get-audio-duration.md +58 -0
  128. package/framework/.claude/skills/remotion-best-practices/rules/get-video-dimensions.md +68 -0
  129. package/framework/.claude/skills/remotion-best-practices/rules/get-video-duration.md +58 -0
  130. package/framework/.claude/skills/remotion-best-practices/rules/gifs.md +141 -0
  131. package/framework/.claude/skills/remotion-best-practices/rules/images.md +130 -0
  132. package/framework/.claude/skills/remotion-best-practices/rules/import-srt-captions.md +69 -0
  133. package/framework/.claude/skills/remotion-best-practices/rules/light-leaks.md +73 -0
  134. package/framework/.claude/skills/remotion-best-practices/rules/lottie.md +67 -0
  135. package/framework/.claude/skills/remotion-best-practices/rules/maps.md +401 -0
  136. package/framework/.claude/skills/remotion-best-practices/rules/measuring-dom-nodes.md +34 -0
  137. package/framework/.claude/skills/remotion-best-practices/rules/measuring-text.md +143 -0
  138. package/framework/.claude/skills/remotion-best-practices/rules/parameters.md +98 -0
  139. package/framework/.claude/skills/remotion-best-practices/rules/sequencing.md +118 -0
  140. package/framework/.claude/skills/remotion-best-practices/rules/subtitles.md +36 -0
  141. package/framework/.claude/skills/remotion-best-practices/rules/tailwind.md +11 -0
  142. package/framework/.claude/skills/remotion-best-practices/rules/text-animations.md +20 -0
  143. package/framework/.claude/skills/remotion-best-practices/rules/timing.md +179 -0
  144. package/framework/.claude/skills/remotion-best-practices/rules/transcribe-captions.md +70 -0
  145. package/framework/.claude/skills/remotion-best-practices/rules/transitions.md +197 -0
  146. package/framework/.claude/skills/remotion-best-practices/rules/transparent-videos.md +106 -0
  147. package/framework/.claude/skills/remotion-best-practices/rules/trimming.md +52 -0
  148. package/framework/.claude/skills/remotion-best-practices/rules/videos.md +171 -0
  149. package/framework/.claude/skills/seo-audit/SKILL.md +394 -0
  150. package/framework/.claude/skills/seo-audit/references/aeo-geo-patterns.md +279 -0
  151. package/framework/.claude/skills/seo-audit/references/ai-writing-detection.md +190 -0
  152. package/framework/.claude/skills/simplify/SKILL.md +137 -0
  153. package/framework/.claude/skills/skill-creator/LICENSE.txt +202 -0
  154. package/framework/.claude/skills/skill-creator/SKILL.md +356 -0
  155. package/framework/.claude/skills/skill-creator/references/output-patterns.md +82 -0
  156. package/framework/.claude/skills/skill-creator/references/workflows.md +28 -0
  157. package/framework/.claude/skills/skill-creator/scripts/init_skill.py +303 -0
  158. package/framework/.claude/skills/skill-creator/scripts/package_skill.py +110 -0
  159. package/framework/.claude/skills/skill-creator/scripts/quick_validate.py +95 -0
  160. package/framework/.claude/skills/ui-design/SKILL.md +199 -0
  161. package/framework/.claude/skills/ui-design/references/component-discovery.md +54 -0
  162. package/framework/.claude/skills/ui-design/references/evaluation.md +171 -0
  163. package/framework/.claude/skills/ui-design/references/generation.md +109 -0
  164. package/framework/.claude/skills/ui-design/references/inventory.md +59 -0
  165. package/framework/.claude/skills/webapp-testing/LICENSE.txt +202 -0
  166. package/framework/.claude/skills/webapp-testing/SKILL.md +123 -0
  167. package/framework/.claude/skills/webapp-testing/examples/console_logging.py +35 -0
  168. package/framework/.claude/skills/webapp-testing/examples/element_discovery.py +40 -0
  169. package/framework/.claude/skills/webapp-testing/examples/static_html_automation.py +33 -0
  170. package/framework/.claude/skills/webapp-testing/scripts/with_server.py +106 -0
  171. package/framework/.claude/skills/worktree-manager/SKILL.md +680 -0
  172. package/framework/AGENTS.md +240 -0
  173. package/framework/agents/api-contracts.md +137 -0
  174. package/framework/agents/architecture.md +145 -0
  175. package/framework/agents/coding-standards.md +148 -0
  176. package/framework/agents/data-model.md +110 -0
  177. package/framework/agents/deployment-protocol.md +232 -0
  178. package/framework/agents/design-review.md +172 -0
  179. package/framework/agents/env-reference.md +171 -0
  180. package/framework/agents/github-issue-subagent.md +252 -0
  181. package/framework/agents/index.md +261 -0
  182. package/framework/agents/llm-wiki-methodology.md +216 -0
  183. package/framework/agents/maintenance-protocol.md +305 -0
  184. package/framework/agents/observability.md +162 -0
  185. package/framework/agents/performance.md +155 -0
  186. package/framework/agents/project-context.md +145 -0
  187. package/framework/agents/runbook.md +208 -0
  188. package/framework/agents/security.md +168 -0
  189. package/framework/agents/skills-mapping.md +286 -0
  190. package/framework/agents/testing.md +111 -0
  191. package/framework/agents/workflows.md +215 -0
  192. package/framework/docs/PROJECT-CONFIGURATION.md +336 -0
  193. package/framework/docs/references/brand-guidelines.md +170 -0
  194. package/framework/docs/references/ui-guidelines.template.md +182 -0
  195. package/framework/routines/code-review.routine.yml +46 -0
  196. package/framework/routines/doc-review.routine.yml +45 -0
  197. package/framework/routines/ds-drift.routine.yml +52 -0
  198. package/framework/routines/full-sweep.routine.yml +51 -0
  199. package/framework/routines/index.yml +70 -0
  200. package/framework/routines/skill-improve.routine.yml +50 -0
  201. package/framework/routines/wiki-review.routine.yml +45 -0
  202. package/framework/templates/baldart.config.template.yml +113 -0
  203. package/framework/templates/breaking-change-checklist.md +484 -0
  204. package/framework/templates/feature-card.template.yml +125 -0
  205. package/framework/templates/overlays/README.md +44 -0
  206. package/framework/templates/overlays/copywriting.fidelity-example.md +62 -0
  207. package/framework/templates/overlays/ui-design.fidelity-example.md +75 -0
  208. package/framework/templates/skill-project-context.snippet.md +19 -0
  209. package/framework/templates/spec.template.md +208 -0
  210. package/package.json +51 -0
  211. package/src/commands/add.js +229 -0
  212. package/src/commands/configure.js +385 -0
  213. package/src/commands/doctor.js +486 -0
  214. package/src/commands/migrate.js +185 -0
  215. package/src/commands/push.js +0 -0
  216. package/src/commands/routines.js +269 -0
  217. package/src/commands/status.js +130 -0
  218. package/src/commands/update.js +419 -0
  219. package/src/commands/version.js +88 -0
  220. package/src/utils/contamination.js +400 -0
  221. package/src/utils/git.js +181 -0
  222. package/src/utils/hooks.js +152 -0
  223. package/src/utils/routine-adapters/claude-code-cloud.js +78 -0
  224. package/src/utils/routine-adapters/cron.js +138 -0
  225. package/src/utils/routine-adapters/github-actions.js +141 -0
  226. package/src/utils/routine-adapters/index.js +21 -0
  227. package/src/utils/routines.js +166 -0
  228. package/src/utils/state.js +143 -0
  229. package/src/utils/symlinks.js +425 -0
  230. package/src/utils/ui.js +133 -0
@@ -0,0 +1,486 @@
1
+ /**
2
+ * `baldart doctor` — smart diagnostic + suggested action.
3
+ *
4
+ * Invoked with no arguments (`npx baldart`) or explicitly (`npx baldart doctor`).
5
+ * Inspects the consumer repo, prints a status table, then proposes the next
6
+ * sensible action(s) and (with confirmation) runs them.
7
+ *
8
+ * Detection priority (precondition order, lowest-permissive first):
9
+ *
10
+ * 1. no git repo → suggest `git init`, abort.
11
+ * 2. no .framework/ → suggest `add`.
12
+ * 3. legacy v2.0.x bulk skills → suggest `migrate`.
13
+ * 4. no baldart.config.yml → suggest `configure`.
14
+ * 5. config schema drift → suggest `configure` (idempotent merge).
15
+ * 6. remote ahead AND local changes → suggest `update` then `push` (two prompts).
16
+ * 7. remote ahead → suggest `update`.
17
+ * 8. local changes → suggest `push`.
18
+ * 9. all clean → print extended status + hints.
19
+ *
20
+ * Flags:
21
+ * --auto run with no confirmation prompts (CI-friendly). Errors out
22
+ * on multi-choice ambiguity rather than guessing.
23
+ * --offline skip the upstream fetch (no remote drift check).
24
+ */
25
+
26
+ const fs = require('fs');
27
+ const path = require('path');
28
+ const yaml = require('js-yaml');
29
+ const GitUtils = require('../utils/git');
30
+ const UI = require('../utils/ui');
31
+ const State = require('../utils/state');
32
+ const Hooks = require('../utils/hooks');
33
+
34
+ const FRAMEWORK_DIR = '.framework';
35
+ const REPO_DEFAULT = 'antbald/BALDART';
36
+ const CONFIG_FILE = 'baldart.config.yml';
37
+
38
+ // ──────────────────────────────────────────────────────────────────────
39
+ // State detection
40
+ // ──────────────────────────────────────────────────────────────────────
41
+
42
+ function fileExists(p) {
43
+ try { return fs.existsSync(p); } catch (_) { return false; }
44
+ }
45
+
46
+ function isLegacySkillsLayout(cwd) {
47
+ const skills = path.join(cwd, '.claude', 'skills');
48
+ if (!fileExists(skills)) return false;
49
+ try { return fs.lstatSync(skills).isSymbolicLink(); }
50
+ catch (_) { return false; }
51
+ }
52
+
53
+ function loadConfig(cwd) {
54
+ const full = path.join(cwd, CONFIG_FILE);
55
+ if (!fileExists(full)) return null;
56
+ try { return yaml.load(fs.readFileSync(full, 'utf8')); }
57
+ catch (_) { return { __malformed: true }; }
58
+ }
59
+
60
+ function loadConfigTemplate(cwd) {
61
+ const tpl = path.join(cwd, FRAMEWORK_DIR, 'framework', 'templates', 'baldart.config.template.yml');
62
+ if (!fileExists(tpl)) return null;
63
+ try { return yaml.load(fs.readFileSync(tpl, 'utf8')); }
64
+ catch (_) { return null; }
65
+ }
66
+
67
+ function configSchemaDrift(config, template) {
68
+ if (!config || !template || config.__malformed) return [];
69
+ const drift = [];
70
+ for (const section of ['paths', 'features']) {
71
+ const tplKeys = Object.keys(template[section] || {});
72
+ const cfgKeys = Object.keys(config[section] || {});
73
+ for (const k of tplKeys) {
74
+ if (!cfgKeys.includes(k)) drift.push(`${section}.${k}`);
75
+ }
76
+ }
77
+ return drift;
78
+ }
79
+
80
+ function countOverlays(cwd) {
81
+ const dir = path.join(cwd, '.baldart', 'overlays');
82
+ if (!fileExists(dir)) return 0;
83
+ try {
84
+ return fs.readdirSync(dir).filter((f) => f.endsWith('.md')).length;
85
+ } catch (_) { return 0; }
86
+ }
87
+
88
+ function overlayDrift(cwd, frameworkVersion) {
89
+ if (!frameworkVersion) return [];
90
+ const dir = path.join(cwd, '.baldart', 'overlays');
91
+ if (!fileExists(dir)) return [];
92
+ const drifted = [];
93
+ try {
94
+ for (const f of fs.readdirSync(dir)) {
95
+ if (!f.endsWith('.md')) continue;
96
+ const txt = fs.readFileSync(path.join(dir, f), 'utf8');
97
+ const fm = txt.match(/^---\n([\s\S]*?)\n---/);
98
+ if (!fm) continue;
99
+ try {
100
+ const meta = yaml.load(fm[1]) || {};
101
+ if (meta.base_skill_version && meta.base_skill_version !== frameworkVersion) {
102
+ drifted.push({ name: f, target: meta.base_skill_version, installed: frameworkVersion });
103
+ }
104
+ } catch (_) { /* ignore unparseable */ }
105
+ }
106
+ } catch (_) { /* ignore */ }
107
+ return drifted;
108
+ }
109
+
110
+ async function describeRemote(git, repo, offline) {
111
+ // Best-effort remote drift detection. Returns { ahead, behind, fetched }.
112
+ if (offline) return { fetched: false };
113
+ try { await git.fetch(repo); }
114
+ catch (_) { return { fetched: false }; }
115
+ try {
116
+ const out = await git.git.raw(['rev-list', '--left-right', '--count', 'HEAD...FETCH_HEAD']);
117
+ const [ahead, behind] = out.trim().split('\t').map(Number);
118
+ return { ahead, behind, fetched: true };
119
+ } catch (_) { return { fetched: false }; }
120
+ }
121
+
122
+ async function localFrameworkChanges(git) {
123
+ // Files modified vs HEAD inside .framework/. Distinct from "ahead of remote"
124
+ // (which counts commits). This is the working-tree dirty count.
125
+ try {
126
+ const out = await git.git.raw(['status', '--porcelain', '--', FRAMEWORK_DIR]);
127
+ return out.split('\n').filter((l) => l.trim()).length;
128
+ } catch (_) { return 0; }
129
+ }
130
+
131
+ async function commitsAheadOfRemote(git) {
132
+ try {
133
+ const out = await git.git.raw(['rev-list', '--count', 'FETCH_HEAD..HEAD', '--', FRAMEWORK_DIR]);
134
+ return Number(out.trim()) || 0;
135
+ } catch (_) { return 0; }
136
+ }
137
+
138
+ async function detectState(cwd, opts = {}) {
139
+ const git = new GitUtils(cwd);
140
+ const state = {
141
+ cwd,
142
+ isGitRepo: await git.isGitRepo(),
143
+ frameworkPresent: await git.frameworkExists(),
144
+ legacyLayout: false,
145
+ frameworkVersion: null,
146
+ configPresent: false,
147
+ configMalformed: false,
148
+ configSchemaMissing: [],
149
+ statePresent: false,
150
+ stateInstalled: null,
151
+ stateInstallDate: null,
152
+ stateLastUpdate: null,
153
+ stateLastPush: null,
154
+ remote: { fetched: false },
155
+ commitsAhead: 0,
156
+ workingTreeDirty: 0,
157
+ overlays: 0,
158
+ overlaysDrifted: [],
159
+ };
160
+
161
+ if (!state.isGitRepo) return state;
162
+
163
+ if (state.frameworkPresent) {
164
+ state.legacyLayout = isLegacySkillsLayout(cwd);
165
+ try { state.frameworkVersion = await git.getFrameworkVersion(); } catch (_) {}
166
+
167
+ const config = loadConfig(cwd);
168
+ state.configPresent = !!config && !config.__malformed;
169
+ state.configMalformed = !!(config && config.__malformed);
170
+ if (state.configPresent) {
171
+ const tpl = loadConfigTemplate(cwd);
172
+ state.configSchemaMissing = configSchemaDrift(config, tpl);
173
+ }
174
+
175
+ const ledger = State.load(cwd);
176
+ state.statePresent = !!ledger.installed_version;
177
+ state.stateInstalled = ledger.installed_version;
178
+ state.stateInstallDate = ledger.install_date;
179
+ state.stateLastUpdate = ledger.last_update_date;
180
+ state.stateLastPush = ledger.last_pushed_version
181
+ ? `v${ledger.last_pushed_version} (${(ledger.last_push_date || '').slice(0, 10) || '—'})`
182
+ : null;
183
+
184
+ state.remote = await describeRemote(git, ledger.framework_repo || REPO_DEFAULT, !!opts.offline);
185
+ state.commitsAhead = await commitsAheadOfRemote(git);
186
+ state.workingTreeDirty = await localFrameworkChanges(git);
187
+
188
+ state.overlays = countOverlays(cwd);
189
+ state.overlaysDrifted = overlayDrift(cwd, state.frameworkVersion);
190
+
191
+ state.editGateRegistered = false;
192
+ try { state.editGateRegistered = Hooks.isRegistered(cwd); } catch (_) {}
193
+ }
194
+
195
+ return state;
196
+ }
197
+
198
+ // ──────────────────────────────────────────────────────────────────────
199
+ // Action planner
200
+ // ──────────────────────────────────────────────────────────────────────
201
+
202
+ /**
203
+ * Returns an array of action proposals. Each item:
204
+ * { key, label, why, run() }
205
+ *
206
+ * Order matters: actions are presented and (with confirmation) executed in
207
+ * the order returned. Empty array means "nothing to do".
208
+ */
209
+ function planActions(state) {
210
+ const actions = [];
211
+
212
+ if (!state.isGitRepo) {
213
+ actions.push({
214
+ key: 'git-init',
215
+ label: 'Initialize a git repo',
216
+ why: 'BALDART requires a git repository to install (it uses git subtree).',
217
+ run: () => {
218
+ UI.error('Run `git init` first, then re-run `npx baldart`.');
219
+ process.exit(1);
220
+ },
221
+ });
222
+ return actions;
223
+ }
224
+
225
+ if (!state.frameworkPresent) {
226
+ actions.push({
227
+ key: 'add',
228
+ label: 'Install BALDART framework',
229
+ why: 'No .framework/ directory found in this repo.',
230
+ run: async () => {
231
+ const add = require('./add');
232
+ await add(REPO_DEFAULT, { branch: 'main' });
233
+ },
234
+ });
235
+ return actions;
236
+ }
237
+
238
+ if (state.legacyLayout) {
239
+ actions.push({
240
+ key: 'migrate',
241
+ label: 'Migrate legacy v2.0.x layout',
242
+ why: '.claude/skills/ is a bulk symlink — v2.0.x layout. v2.1.1+ uses per-item merge.',
243
+ run: () => require('./migrate')(),
244
+ });
245
+ return actions;
246
+ }
247
+
248
+ if (state.configMalformed) {
249
+ actions.push({
250
+ key: 'configure-malformed',
251
+ label: 'Rewrite baldart.config.yml (current file is malformed)',
252
+ why: 'Existing baldart.config.yml is not valid YAML — re-running configure will rebuild it.',
253
+ run: () => require('./configure')(),
254
+ });
255
+ return actions;
256
+ }
257
+
258
+ if (!state.configPresent) {
259
+ actions.push({
260
+ key: 'configure',
261
+ label: 'Generate baldart.config.yml',
262
+ why: 'Skills require a config to resolve project-specific paths/identity/stack. Without it they will prompt on every invocation.',
263
+ run: () => require('./configure')(),
264
+ });
265
+ return actions;
266
+ }
267
+
268
+ if (state.configSchemaMissing.length > 0) {
269
+ actions.push({
270
+ key: 'configure-drift',
271
+ label: `Refresh baldart.config.yml (${state.configSchemaMissing.length} new key(s) from framework template)`,
272
+ why: 'Framework added config keys this consumer doesn\'t yet declare: ' + state.configSchemaMissing.join(', '),
273
+ run: () => require('./configure')(),
274
+ });
275
+ // Drift is non-blocking — keep checking for other actions after.
276
+ }
277
+
278
+ if (state.editGateRegistered === false) {
279
+ actions.push({
280
+ key: 'register-edit-gate',
281
+ label: 'Register framework-edit-gate hook (.claude/settings.json)',
282
+ why: 'Prevents project-specific tokens (Neo-Brutalism, merchant, …) from being written to .framework/ accidentally. Standard in v3.3.0+; missing on installs predating that version.',
283
+ run: async () => {
284
+ const res = require('../utils/hooks').register();
285
+ UI.success(`Hook registered: ${res.status} (${res.path})`);
286
+ },
287
+ });
288
+ }
289
+
290
+ const remoteAhead = state.remote.fetched && state.remote.behind > 0;
291
+ const hasLocalWork = state.workingTreeDirty > 0 || state.commitsAhead > 0;
292
+
293
+ if (remoteAhead) {
294
+ actions.push({
295
+ key: 'update',
296
+ label: `Pull ${state.remote.behind} commit(s) from upstream`,
297
+ why: 'Remote framework is ahead of your local copy. Updating first keeps your contributions on a fresh base.',
298
+ run: () => require('./update')(),
299
+ });
300
+ }
301
+
302
+ if (hasLocalWork) {
303
+ actions.push({
304
+ key: 'push',
305
+ label: 'Push local framework improvements upstream',
306
+ why: state.workingTreeDirty
307
+ ? `${state.workingTreeDirty} uncommitted file(s) in .framework/ — commit them through \`baldart push\`.`
308
+ : `${state.commitsAhead} commit(s) on .framework/ not yet pushed.`,
309
+ run: () => require('./push')(),
310
+ });
311
+ }
312
+
313
+ return actions;
314
+ }
315
+
316
+ // ──────────────────────────────────────────────────────────────────────
317
+ // Diagnostic table
318
+ // ──────────────────────────────────────────────────────────────────────
319
+
320
+ function fmtDate(iso) {
321
+ if (!iso) return '—';
322
+ try { return iso.slice(0, 10); } catch (_) { return iso; }
323
+ }
324
+
325
+ function statusLine(label, value, severity) {
326
+ const symbol = severity === 'ok' ? '✓' : severity === 'warn' ? '⚠' : severity === 'err' ? '✗' : '·';
327
+ return `${symbol} ${label.padEnd(15)} ${value}`;
328
+ }
329
+
330
+ function renderDiagnostic(state) {
331
+ UI.header('BALDART DOCTOR');
332
+ console.log(`Repository: ${state.cwd}`);
333
+ console.log();
334
+
335
+ console.log(statusLine('Git repo', state.isGitRepo ? 'yes' : 'no', state.isGitRepo ? 'ok' : 'err'));
336
+
337
+ if (!state.isGitRepo) return;
338
+
339
+ console.log(statusLine(
340
+ 'Framework',
341
+ state.frameworkPresent ? `v${state.frameworkVersion}` : 'not installed',
342
+ state.frameworkPresent ? 'ok' : 'warn'
343
+ ));
344
+
345
+ if (!state.frameworkPresent) return;
346
+
347
+ console.log(statusLine(
348
+ 'Layout',
349
+ state.legacyLayout ? 'legacy v2.0.x (migration needed)' : 'current',
350
+ state.legacyLayout ? 'warn' : 'ok'
351
+ ));
352
+
353
+ console.log(statusLine(
354
+ 'Config',
355
+ state.configMalformed
356
+ ? 'malformed YAML'
357
+ : state.configPresent
358
+ ? (state.configSchemaMissing.length
359
+ ? `${state.configSchemaMissing.length} key(s) missing from current schema`
360
+ : 'present')
361
+ : 'missing',
362
+ state.configMalformed || !state.configPresent ? 'err'
363
+ : state.configSchemaMissing.length ? 'warn'
364
+ : 'ok'
365
+ ));
366
+
367
+ console.log(statusLine(
368
+ 'State ledger',
369
+ state.statePresent
370
+ ? `installed v${state.stateInstalled} (${fmtDate(state.stateInstallDate)})`
371
+ : 'not seeded',
372
+ state.statePresent ? 'ok' : 'warn'
373
+ ));
374
+
375
+ if (state.stateLastUpdate) {
376
+ console.log(statusLine('Last update', fmtDate(state.stateLastUpdate), 'ok'));
377
+ }
378
+ if (state.stateLastPush) {
379
+ console.log(statusLine('Last push', state.stateLastPush, 'ok'));
380
+ }
381
+
382
+ if (state.remote.fetched) {
383
+ const behind = state.remote.behind || 0;
384
+ console.log(statusLine(
385
+ 'Remote',
386
+ behind > 0 ? `${behind} commit(s) ahead — update available` : 'up to date',
387
+ behind > 0 ? 'warn' : 'ok'
388
+ ));
389
+ } else {
390
+ console.log(statusLine('Remote', 'offline / not fetched', 'warn'));
391
+ }
392
+
393
+ console.log(statusLine(
394
+ 'Local changes',
395
+ state.workingTreeDirty > 0
396
+ ? `${state.workingTreeDirty} file(s) in .framework/`
397
+ : state.commitsAhead > 0
398
+ ? `${state.commitsAhead} unpushed commit(s)`
399
+ : 'clean',
400
+ (state.workingTreeDirty + state.commitsAhead) > 0 ? 'warn' : 'ok'
401
+ ));
402
+
403
+ console.log(statusLine(
404
+ 'Overlays',
405
+ state.overlays === 0 ? 'none authored' : `${state.overlays} authored`,
406
+ state.overlaysDrifted.length ? 'warn' : 'ok'
407
+ ));
408
+ if (state.overlaysDrifted.length) {
409
+ state.overlaysDrifted.forEach((o) =>
410
+ console.log(` • ${o.name} targets v${o.target}, installed v${o.installed}`));
411
+ }
412
+
413
+ console.log(statusLine(
414
+ 'Edit gate',
415
+ state.editGateRegistered ? 'registered' : 'NOT registered (.claude/settings.json)',
416
+ state.editGateRegistered ? 'ok' : 'warn'
417
+ ));
418
+
419
+ console.log();
420
+ }
421
+
422
+ // ──────────────────────────────────────────────────────────────────────
423
+ // Main
424
+ // ──────────────────────────────────────────────────────────────────────
425
+
426
+ async function doctor(opts = {}) {
427
+ const cwd = process.cwd();
428
+ const auto = !!opts.auto;
429
+ const offline = !!opts.offline;
430
+
431
+ const state = await detectState(cwd, { offline });
432
+ renderDiagnostic(state);
433
+
434
+ const actions = planActions(state);
435
+
436
+ if (actions.length === 0) {
437
+ UI.success('Nothing to do — framework is healthy and in sync.');
438
+ UI.newline();
439
+ UI.info('Available actions:');
440
+ UI.list([
441
+ '`npx baldart push` — contribute local improvements upstream',
442
+ '`npx baldart configure` — re-run config prompts (e.g. after enabling a feature)',
443
+ '`npx baldart status` — symlink + overlay validity check',
444
+ '`npx baldart version` — installed version + remote drift',
445
+ ], 'cyan');
446
+ return;
447
+ }
448
+
449
+ UI.section('Recommended action(s)');
450
+ actions.forEach((a, i) =>
451
+ console.log(` ${i + 1}. ${a.label}\n ${a.why}`));
452
+ UI.newline();
453
+
454
+ // Multi-action ambiguity check in --auto mode.
455
+ if (auto && actions.length > 1 && actions.some((a) => a.key !== 'configure-drift')) {
456
+ // Drift refresh is a no-op-style action; pairs with other actions safely.
457
+ // Anything else that yields >1 step is genuine ambiguity in CI.
458
+ const ambiguous = actions.filter((a) => a.key !== 'configure-drift').length > 1;
459
+ if (ambiguous) {
460
+ UI.error('Multiple actions proposed; --auto refuses to chain in ambiguous state.');
461
+ UI.info('Run them explicitly with the corresponding `baldart <subcommand>` calls.');
462
+ process.exit(2);
463
+ }
464
+ }
465
+
466
+ for (const action of actions) {
467
+ let proceed = auto;
468
+ if (!auto) {
469
+ proceed = await UI.confirm(`Run: ${action.label}?`, true);
470
+ }
471
+ if (!proceed) {
472
+ UI.info(`Skipped: ${action.label}`);
473
+ break;
474
+ }
475
+ try {
476
+ await action.run();
477
+ } catch (err) {
478
+ UI.error(`Action "${action.key}" failed: ${err.message}`);
479
+ process.exit(1);
480
+ }
481
+ }
482
+ }
483
+
484
+ module.exports = doctor;
485
+ module.exports.detectState = detectState;
486
+ module.exports.planActions = planActions;
@@ -0,0 +1,185 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const chalk = require('chalk');
4
+ const UI = require('../utils/ui');
5
+ const SymlinkUtils = require('../utils/symlinks');
6
+
7
+ /**
8
+ * `npx baldart migrate` — fixes installation state on existing repos.
9
+ *
10
+ * Today handles two known migration paths:
11
+ *
12
+ * 1. **v1.x → v2.1.1**: no `.claude/skills/` directory existed; the v2.0.x
13
+ * update tried to install a bulk symlink and (depending on the user's
14
+ * state) may have renamed their existing skills directory to
15
+ * `.claude/skills.backup`. We surface that, convert to per-item layout,
16
+ * and offer to restore the backup contents alongside framework skills.
17
+ *
18
+ * 2. **v2.0.x → v2.1.1**: the v2.0.x bulk symlink at `.claude/skills` is
19
+ * replaced with a real directory + per-item framework skill symlinks.
20
+ * Any user skills that ended up in `.claude/skills.backup` are restored
21
+ * into the new directory (with name-collision detection).
22
+ *
23
+ * The command is idempotent: running it on an already-migrated repo simply
24
+ * reports "nothing to do".
25
+ */
26
+ async function migrate() {
27
+ UI.header('BALDART Migration');
28
+
29
+ const cwd = process.cwd();
30
+ const symlinks = new SymlinkUtils(cwd);
31
+
32
+ const skillsPath = path.join(cwd, '.claude', 'skills');
33
+ const skillsBackup = path.join(cwd, '.claude', 'skills.backup');
34
+ const frameworkDir = path.join(cwd, '.framework');
35
+ const frameworkSkillsDir = path.join(frameworkDir, 'framework', '.claude', 'skills');
36
+
37
+ // --- Pre-flight ---------------------------------------------------------
38
+
39
+ if (!fs.existsSync(frameworkDir)) {
40
+ UI.error('No `.framework/` directory found. BALDART is not installed in this repo.');
41
+ UI.info('Run `npx baldart add` first.');
42
+ return;
43
+ }
44
+
45
+ if (!fs.existsSync(frameworkSkillsDir)) {
46
+ UI.warning('Framework appears to be older than v2.0.0 (no skills directory). Run `npx baldart update` first.');
47
+ return;
48
+ }
49
+
50
+ // --- Inspect current state ---------------------------------------------
51
+
52
+ UI.section('Inspecting current installation');
53
+
54
+ let skillsState = 'none'; // none | bulk-symlink | real-dir
55
+ let backupPresent = false;
56
+
57
+ if (fs.existsSync(skillsPath)) {
58
+ const stat = fs.lstatSync(skillsPath);
59
+ if (stat.isSymbolicLink()) {
60
+ skillsState = 'bulk-symlink';
61
+ const linkTarget = fs.readlinkSync(skillsPath);
62
+ UI.warning(`.claude/skills is a legacy bulk symlink → ${linkTarget}`);
63
+ } else {
64
+ skillsState = 'real-dir';
65
+ const entries = fs.readdirSync(skillsPath);
66
+ UI.info(`.claude/skills/ is a real directory with ${entries.length} entries.`);
67
+ }
68
+ } else {
69
+ UI.info('.claude/skills/ does not exist yet.');
70
+ }
71
+
72
+ if (fs.existsSync(skillsBackup)) {
73
+ backupPresent = true;
74
+ const entries = fs.readdirSync(skillsBackup).filter(n => !n.startsWith('.'));
75
+ UI.warning(`Found .claude/skills.backup with ${entries.length} entries (from a prior failed update).`);
76
+ }
77
+
78
+ // --- Plan ---------------------------------------------------------------
79
+
80
+ const actions = [];
81
+ if (skillsState === 'bulk-symlink') {
82
+ actions.push('Replace legacy bulk symlink with a real directory (.claude/skills/).');
83
+ }
84
+ if (skillsState === 'none') {
85
+ actions.push('Create .claude/skills/ as a real directory.');
86
+ }
87
+ actions.push('Merge framework skills as per-item symlinks (existing user skills preserved).');
88
+ if (backupPresent) {
89
+ actions.push('Restore user skills from .claude/skills.backup into .claude/skills/ (no overwrites).');
90
+ }
91
+ actions.push('Record any name collisions in .baldart/skill-conflicts.json.');
92
+
93
+ UI.section('Planned actions');
94
+ UI.list(actions);
95
+ UI.newline();
96
+
97
+ const proceed = await UI.confirm('Proceed with migration?', true);
98
+ if (!proceed) {
99
+ UI.info('Aborted. No changes made.');
100
+ return;
101
+ }
102
+
103
+ // --- Step 1: convert bulk symlink → real directory ---------------------
104
+
105
+ if (skillsState === 'bulk-symlink') {
106
+ UI.section('Step 1: convert legacy bulk symlink');
107
+ fs.unlinkSync(skillsPath);
108
+ fs.mkdirSync(skillsPath, { recursive: true });
109
+ UI.success('Replaced bulk symlink with empty real directory.');
110
+ } else if (skillsState === 'none') {
111
+ fs.mkdirSync(skillsPath, { recursive: true });
112
+ UI.success('Created .claude/skills/ directory.');
113
+ }
114
+
115
+ // --- Step 2: per-item framework skill merge ----------------------------
116
+
117
+ UI.section('Step 2: merge framework skills (per-item)');
118
+ const mergeResult = symlinks.mergeSkills();
119
+ UI.info(`Linked ${mergeResult.linked.length} framework skills, kept ${mergeResult.skipped.length} as-is, ${mergeResult.conflicts.length} conflict(s).`);
120
+
121
+ // --- Step 3: restore user skills from .backup --------------------------
122
+
123
+ if (backupPresent) {
124
+ UI.section('Step 3: restore user skills from .backup');
125
+ const entries = fs.readdirSync(skillsBackup).filter(n => !n.startsWith('.'));
126
+ let restored = 0;
127
+ let collided = 0;
128
+
129
+ entries.forEach(name => {
130
+ const src = path.join(skillsBackup, name);
131
+ const dst = path.join(skillsPath, name);
132
+
133
+ if (fs.existsSync(dst)) {
134
+ const stat = fs.lstatSync(dst);
135
+ if (stat.isSymbolicLink()) {
136
+ // Framework symlink already there — name collision
137
+ UI.warning(`Collision: backup has "${name}" but framework also ships it. Backup kept at .claude/skills.backup/${name}.`);
138
+ collided++;
139
+ return;
140
+ }
141
+ }
142
+
143
+ // Move (atomic rename) from backup into skills/
144
+ fs.renameSync(src, dst);
145
+ UI.success(`Restored user skill: .claude/skills/${name}`);
146
+ restored++;
147
+ });
148
+
149
+ // Remove the backup dir if empty
150
+ const remaining = fs.readdirSync(skillsBackup).filter(n => !n.startsWith('.'));
151
+ if (remaining.length === 0) {
152
+ fs.rmSync(skillsBackup, { recursive: true, force: true });
153
+ UI.success('Removed empty .claude/skills.backup.');
154
+ } else {
155
+ UI.warning(`${remaining.length} item(s) still in .claude/skills.backup (collisions kept for manual review).`);
156
+ }
157
+
158
+ UI.info(`Restored ${restored} user skill(s). ${collided} collision(s) left in .backup.`);
159
+ }
160
+
161
+ // --- Step 4: report ----------------------------------------------------
162
+
163
+ UI.newline();
164
+ UI.header('✓ MIGRATION COMPLETE');
165
+
166
+ const summaryLines = [
167
+ `Framework skills linked: ${mergeResult.linked.length}`,
168
+ `Existing user skills kept: ${mergeResult.skipped.filter(s => s.reason !== 'already-linked').length}`,
169
+ `Name collisions recorded: ${mergeResult.conflicts.length}`
170
+ ];
171
+ if (backupPresent) {
172
+ summaryLines.push('.claude/skills.backup processed (see warnings above).');
173
+ }
174
+ UI.box('Migration summary', summaryLines);
175
+
176
+ UI.section('Next steps');
177
+ UI.list([
178
+ 'Run `npx baldart routines list` to review schedule status (v2.1.0+).',
179
+ 'Run `npx baldart status` for an overall installation check.',
180
+ 'Commit the resulting changes (`.claude/skills/`, `.baldart/skill-conflicts.json` if any) so the team sees the same state.'
181
+ ]);
182
+ UI.newline();
183
+ }
184
+
185
+ module.exports = migrate;
Binary file