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,152 @@
1
+ /**
2
+ * Consumer-side hook registration for `.claude/settings.json`.
3
+ *
4
+ * BALDART ships a Claude Code PreToolUse hook (`framework-edit-gate`) that
5
+ * intercepts Edit / Write / MultiEdit calls targeting files inside
6
+ * `.framework/` and blocks them with an informative reason if the new
7
+ * content contains project-specific tokens (Neo-Brutalism, merchant,
8
+ * Recharts, secrets, hardcoded paths, …).
9
+ *
10
+ * Registration is auto-handled by `baldart add` and re-checked by
11
+ * `baldart update` / `baldart doctor`. The user can remove the entry from
12
+ * `.claude/settings.json` if they want to disable it.
13
+ *
14
+ * Schema: the hook config under `hooks.PreToolUse[]` follows Claude Code's
15
+ * documented hook format. Each entry has a `matcher` (tool name regex) and
16
+ * an inner `hooks[]` array of `{ type: "command", command: <shell> }`.
17
+ */
18
+
19
+ const fs = require('fs');
20
+ const path = require('path');
21
+
22
+ const SETTINGS_FILE = path.join('.claude', 'settings.json');
23
+ const HOOK_ID = 'baldart-framework-edit-gate';
24
+ const HOOK_COMMAND = 'node .framework/framework/.claude/hooks/framework-edit-gate.js';
25
+ const HOOK_MATCHER = 'Edit|Write|MultiEdit|NotebookEdit';
26
+
27
+ function readSettings(cwd = process.cwd()) {
28
+ const full = path.join(cwd, SETTINGS_FILE);
29
+ if (!fs.existsSync(full)) return { path: full, settings: null, malformed: false };
30
+ try {
31
+ const raw = fs.readFileSync(full, 'utf8');
32
+ if (!raw.trim()) return { path: full, settings: {}, malformed: false };
33
+ return { path: full, settings: JSON.parse(raw), malformed: false };
34
+ } catch (_) {
35
+ return { path: full, settings: null, malformed: true };
36
+ }
37
+ }
38
+
39
+ function writeSettings(settingsPath, settings) {
40
+ fs.mkdirSync(path.dirname(settingsPath), { recursive: true });
41
+ fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n', 'utf8');
42
+ }
43
+
44
+ /**
45
+ * Returns true iff the framework-edit-gate hook is already registered in
46
+ * `.claude/settings.json` (matching either by command string or by the
47
+ * HOOK_ID marker we plant in a sibling property).
48
+ */
49
+ function isRegistered(cwd = process.cwd()) {
50
+ const { settings } = readSettings(cwd);
51
+ if (!settings || !settings.hooks || !Array.isArray(settings.hooks.PreToolUse)) return false;
52
+ for (const entry of settings.hooks.PreToolUse) {
53
+ if (!entry || !Array.isArray(entry.hooks)) continue;
54
+ for (const h of entry.hooks) {
55
+ if (!h) continue;
56
+ if (h.id === HOOK_ID) return true;
57
+ if (h.type === 'command' && typeof h.command === 'string') {
58
+ if (h.command.includes('framework-edit-gate.js')) return true;
59
+ }
60
+ }
61
+ }
62
+ return false;
63
+ }
64
+
65
+ /**
66
+ * Register the hook idempotently. Returns:
67
+ * { status: 'created', path } — settings.json created from scratch
68
+ * { status: 'updated', path } — existing settings.json merged
69
+ * { status: 'already', path } — hook was already registered, no-op
70
+ * { status: 'malformed', path } — existing settings.json is invalid JSON
71
+ */
72
+ function register(cwd = process.cwd()) {
73
+ const { path: settingsPath, settings: existing, malformed } = readSettings(cwd);
74
+ if (malformed) return { status: 'malformed', path: settingsPath };
75
+
76
+ const settings = existing && typeof existing === 'object' ? { ...existing } : {};
77
+ settings.hooks = settings.hooks && typeof settings.hooks === 'object'
78
+ ? { ...settings.hooks }
79
+ : {};
80
+
81
+ const list = Array.isArray(settings.hooks.PreToolUse) ? [...settings.hooks.PreToolUse] : [];
82
+
83
+ // Already present?
84
+ for (const entry of list) {
85
+ if (!entry || !Array.isArray(entry.hooks)) continue;
86
+ for (const h of entry.hooks) {
87
+ if (!h) continue;
88
+ if (h.id === HOOK_ID) return { status: 'already', path: settingsPath };
89
+ if (h.type === 'command' && typeof h.command === 'string'
90
+ && h.command.includes('framework-edit-gate.js')) {
91
+ return { status: 'already', path: settingsPath };
92
+ }
93
+ }
94
+ }
95
+
96
+ list.push({
97
+ matcher: HOOK_MATCHER,
98
+ hooks: [{
99
+ id: HOOK_ID,
100
+ type: 'command',
101
+ command: HOOK_COMMAND,
102
+ }],
103
+ });
104
+
105
+ settings.hooks.PreToolUse = list;
106
+ writeSettings(settingsPath, settings);
107
+ return { status: existing ? 'updated' : 'created', path: settingsPath };
108
+ }
109
+
110
+ /**
111
+ * Remove the hook from settings.json. Used by tests and as an undo helper.
112
+ */
113
+ function unregister(cwd = process.cwd()) {
114
+ const { path: settingsPath, settings, malformed } = readSettings(cwd);
115
+ if (malformed || !settings || !settings.hooks || !Array.isArray(settings.hooks.PreToolUse)) {
116
+ return { status: 'not-found', path: settingsPath };
117
+ }
118
+
119
+ const filtered = settings.hooks.PreToolUse
120
+ .map((entry) => {
121
+ if (!entry || !Array.isArray(entry.hooks)) return entry;
122
+ const innerKept = entry.hooks.filter((h) => {
123
+ if (!h) return true;
124
+ if (h.id === HOOK_ID) return false;
125
+ if (h.type === 'command' && typeof h.command === 'string'
126
+ && h.command.includes('framework-edit-gate.js')) return false;
127
+ return true;
128
+ });
129
+ if (innerKept.length === 0) return null;
130
+ return { ...entry, hooks: innerKept };
131
+ })
132
+ .filter(Boolean);
133
+
134
+ if (filtered.length === settings.hooks.PreToolUse.length) {
135
+ return { status: 'not-found', path: settingsPath };
136
+ }
137
+
138
+ const next = { ...settings, hooks: { ...settings.hooks, PreToolUse: filtered } };
139
+ if (next.hooks.PreToolUse.length === 0) delete next.hooks.PreToolUse;
140
+ if (Object.keys(next.hooks).length === 0) delete next.hooks;
141
+ writeSettings(settingsPath, next);
142
+ return { status: 'removed', path: settingsPath };
143
+ }
144
+
145
+ module.exports = {
146
+ isRegistered,
147
+ register,
148
+ unregister,
149
+ HOOK_ID,
150
+ HOOK_COMMAND,
151
+ SETTINGS_FILE,
152
+ };
@@ -0,0 +1,78 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+
4
+ /**
5
+ * Claude Code Cloud adapter.
6
+ *
7
+ * Generates a routine config file under `.claude/routines/<name>.json` that
8
+ * Claude Code Cloud can pick up via `/schedule` or the RemoteTrigger mechanism.
9
+ *
10
+ * The actual remote trigger creation is interactive (user must run `/schedule`
11
+ * inside Claude Code). The adapter writes the config and prints the exact
12
+ * command the user needs to run.
13
+ */
14
+ class ClaudeCodeCloudAdapter {
15
+ constructor(cwd = process.cwd()) {
16
+ this.cwd = cwd;
17
+ }
18
+
19
+ get name() { return 'claude-code-cloud'; }
20
+ get label() { return 'Claude Code Cloud (RemoteTrigger)'; }
21
+ get description() {
22
+ return 'Schedules the agent via Claude Code Cloud RemoteTrigger. Best when you already use Claude Code Cloud.';
23
+ }
24
+
25
+ configDir() {
26
+ return path.join(this.cwd, '.claude', 'routines');
27
+ }
28
+
29
+ configPath(name) {
30
+ return path.join(this.configDir(), `${name}.json`);
31
+ }
32
+
33
+ install(spec) {
34
+ if (!fs.existsSync(this.configDir())) {
35
+ fs.mkdirSync(this.configDir(), { recursive: true });
36
+ }
37
+ const config = {
38
+ $schema: 'baldart-routine-config-v1',
39
+ name: spec.name,
40
+ description: spec.description,
41
+ schedule: spec.schedule,
42
+ agent: spec.agent,
43
+ prompt: spec.prompt,
44
+ output: spec.output,
45
+ backend: this.name,
46
+ installed_at: new Date().toISOString()
47
+ };
48
+ fs.writeFileSync(this.configPath(spec.name), JSON.stringify(config, null, 2) + '\n');
49
+ return {
50
+ backend: this.name,
51
+ artifacts: [path.relative(this.cwd, this.configPath(spec.name))],
52
+ followup: [
53
+ `Open Claude Code in this repo and run:`,
54
+ ` /schedule create --config .claude/routines/${spec.name}.json`,
55
+ `(or invoke the agent once with /loop ${spec.schedule.cron} <command> to test the cadence)`
56
+ ]
57
+ };
58
+ }
59
+
60
+ uninstall(name) {
61
+ const p = this.configPath(name);
62
+ if (fs.existsSync(p)) fs.unlinkSync(p);
63
+ return {
64
+ backend: this.name,
65
+ followup: [
66
+ `Open Claude Code and run:`,
67
+ ` /schedule delete ${name}`,
68
+ `to remove the remote trigger.`
69
+ ]
70
+ };
71
+ }
72
+
73
+ detect(name) {
74
+ return fs.existsSync(this.configPath(name));
75
+ }
76
+ }
77
+
78
+ module.exports = ClaudeCodeCloudAdapter;
@@ -0,0 +1,138 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+
4
+ /**
5
+ * Local cron adapter.
6
+ *
7
+ * Generates `scripts/routines/<name>.sh` (a self-contained wrapper) and prints
8
+ * the exact crontab line the user must install. Does NOT touch the user's
9
+ * crontab directly — that would be too invasive.
10
+ *
11
+ * Requires the `claude` CLI to be available on the user's PATH.
12
+ */
13
+ class CronAdapter {
14
+ constructor(cwd = process.cwd()) {
15
+ this.cwd = cwd;
16
+ }
17
+
18
+ get name() { return 'cron'; }
19
+ get label() { return 'Local cron (shell wrapper + crontab line)'; }
20
+ get description() {
21
+ return 'Generates a shell wrapper. You add a single line to your crontab. Best for self-hosted runs.';
22
+ }
23
+
24
+ scriptDir() {
25
+ return path.join(this.cwd, 'scripts', 'routines');
26
+ }
27
+
28
+ scriptPath(name) {
29
+ return path.join(this.scriptDir(), `${name}.sh`);
30
+ }
31
+
32
+ install(spec) {
33
+ if (!fs.existsSync(this.scriptDir())) {
34
+ fs.mkdirSync(this.scriptDir(), { recursive: true });
35
+ }
36
+ const sh = this._renderShellWrapper(spec);
37
+ fs.writeFileSync(this.scriptPath(spec.name), sh);
38
+ fs.chmodSync(this.scriptPath(spec.name), 0o755);
39
+
40
+ const absPath = path.resolve(this.cwd, this.scriptPath(spec.name));
41
+ const crontabLine = `${spec.schedule.cron} ${absPath} >> ${path.dirname(absPath)}/${spec.name}.log 2>&1`;
42
+
43
+ return {
44
+ backend: this.name,
45
+ artifacts: [path.relative(this.cwd, this.scriptPath(spec.name))],
46
+ followup: [
47
+ `Add this line to your crontab (\`crontab -e\`):`,
48
+ ``,
49
+ ` ${crontabLine}`,
50
+ ``,
51
+ `Make sure the \`claude\` CLI is on PATH for the cron user and that ANTHROPIC_API_KEY is exported in the script env (edit the wrapper to suit).`,
52
+ `Manual test: ${absPath}`
53
+ ],
54
+ crontab_line: crontabLine
55
+ };
56
+ }
57
+
58
+ uninstall(name) {
59
+ const p = this.scriptPath(name);
60
+ if (fs.existsSync(p)) fs.unlinkSync(p);
61
+ return {
62
+ backend: this.name,
63
+ followup: [
64
+ `Remove the crontab line that referenced scripts/routines/${name}.sh.`,
65
+ `Run \`crontab -e\` and delete the matching entry.`
66
+ ]
67
+ };
68
+ }
69
+
70
+ detect(name) {
71
+ return fs.existsSync(this.scriptPath(name));
72
+ }
73
+
74
+ _renderShellWrapper(spec) {
75
+ const outputPath = (spec.output && spec.output.path) || `docs/reports/{{YYYYMMDD}}-${spec.name}.md`;
76
+ const commitEnabled = spec.output && spec.output.commit && spec.output.commit.enabled;
77
+ const commitPrefix = (spec.output && spec.output.commit && spec.output.commit.prefix) || `[${spec.name.toUpperCase()}]`;
78
+ const promptHeredoc = spec.prompt;
79
+ const repoRoot = '"$(cd "$(dirname "$0")/../.." && pwd)"';
80
+
81
+ return `#!/usr/bin/env bash
82
+ # Generated by BALDART (v${require('../../../package.json').version}) — npx baldart routines
83
+ # Routine: ${spec.name}
84
+ # ${spec.description}
85
+ #
86
+ # Schedule: ${spec.schedule.cron} (${spec.schedule.cadence_label || 'scheduled'} ${spec.schedule.timezone || 'UTC'})
87
+ #
88
+ # Add to crontab (\`crontab -e\`):
89
+ # ${spec.schedule.cron} ${'$(pwd)'}/scripts/routines/${spec.name}.sh >> ${'$(pwd)'}/scripts/routines/${spec.name}.log 2>&1
90
+
91
+ set -euo pipefail
92
+
93
+ # Resolve repo root and move into it so relative paths in the prompt work.
94
+ REPO_ROOT=${repoRoot}
95
+ cd "$REPO_ROOT"
96
+
97
+ # Required: \`claude\` CLI on PATH and ANTHROPIC_API_KEY set.
98
+ if ! command -v claude >/dev/null 2>&1; then
99
+ echo "ERROR: claude CLI not found on PATH" >&2
100
+ exit 1
101
+ fi
102
+ : "\${ANTHROPIC_API_KEY:?ANTHROPIC_API_KEY must be set (export it from the cron env or your shell profile)}"
103
+
104
+ DATE=$(date -u +%Y%m%d)
105
+ OUT_PATH="${outputPath}"
106
+ OUT_PATH="\${OUT_PATH//\\{\\{YYYYMMDD\\}\\}/$DATE}"
107
+ mkdir -p "$(dirname "$OUT_PATH")"
108
+
109
+ claude --print --output-format json --mode bypassPermissions \\
110
+ --agent ${spec.agent} \\
111
+ > /tmp/baldart-${spec.name}.json <<'PROMPT'
112
+ ${promptHeredoc}
113
+ PROMPT
114
+
115
+ # Persist the output if the agent did not already write to disk
116
+ if [ ! -s "$OUT_PATH" ]; then
117
+ jq -r '.result // .' /tmp/baldart-${spec.name}.json > "$OUT_PATH" 2>/dev/null \\
118
+ || cp /tmp/baldart-${spec.name}.json "$OUT_PATH"
119
+ fi
120
+ ${commitEnabled ? `
121
+ # Auto-commit the report and any inline fixes
122
+ if git diff --quiet && git diff --staged --quiet; then
123
+ echo "No changes to commit."
124
+ else
125
+ git add -A
126
+ git commit -m "${commitPrefix} routine $(date -u +%Y-%m-%d)"
127
+ # Push only when on a branch with a configured upstream
128
+ if git rev-parse --abbrev-ref --symbolic-full-name '@{upstream}' >/dev/null 2>&1; then
129
+ git push
130
+ fi
131
+ fi
132
+ ` : ''}
133
+ echo "OK: ${spec.name} finished at $(date -u)"
134
+ `;
135
+ }
136
+ }
137
+
138
+ module.exports = CronAdapter;
@@ -0,0 +1,141 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+
4
+ /**
5
+ * GitHub Actions adapter.
6
+ *
7
+ * Generates `.github/workflows/baldart-<name>.yml` with a `schedule` trigger
8
+ * and a `workflow_dispatch` trigger for manual runs. The workflow checks out
9
+ * the repo, sets up Node, installs Claude Code, and invokes the agent via
10
+ * the headless Claude CLI.
11
+ *
12
+ * Requires the user to set the `ANTHROPIC_API_KEY` secret in the repo.
13
+ */
14
+ class GitHubActionsAdapter {
15
+ constructor(cwd = process.cwd()) {
16
+ this.cwd = cwd;
17
+ }
18
+
19
+ get name() { return 'github-actions'; }
20
+ get label() { return 'GitHub Actions'; }
21
+ get description() {
22
+ return 'Schedules via a workflow with cron + workflow_dispatch. Requires the ANTHROPIC_API_KEY secret.';
23
+ }
24
+
25
+ workflowDir() {
26
+ return path.join(this.cwd, '.github', 'workflows');
27
+ }
28
+
29
+ workflowPath(name) {
30
+ return path.join(this.workflowDir(), `baldart-${name}.yml`);
31
+ }
32
+
33
+ install(spec) {
34
+ if (!fs.existsSync(this.workflowDir())) {
35
+ fs.mkdirSync(this.workflowDir(), { recursive: true });
36
+ }
37
+ const yml = this._renderWorkflow(spec);
38
+ fs.writeFileSync(this.workflowPath(spec.name), yml);
39
+ return {
40
+ backend: this.name,
41
+ artifacts: [path.relative(this.cwd, this.workflowPath(spec.name))],
42
+ followup: [
43
+ `Set the repository secret ANTHROPIC_API_KEY in GitHub.`,
44
+ `Commit and push .github/workflows/baldart-${spec.name}.yml to enable the schedule.`,
45
+ `Manual test: gh workflow run baldart-${spec.name}.yml`
46
+ ]
47
+ };
48
+ }
49
+
50
+ uninstall(name) {
51
+ const p = this.workflowPath(name);
52
+ if (fs.existsSync(p)) fs.unlinkSync(p);
53
+ return {
54
+ backend: this.name,
55
+ followup: [
56
+ `Commit the removal of .github/workflows/baldart-${name}.yml to disable the schedule.`
57
+ ]
58
+ };
59
+ }
60
+
61
+ detect(name) {
62
+ return fs.existsSync(this.workflowPath(name));
63
+ }
64
+
65
+ _renderWorkflow(spec) {
66
+ const cron = spec.schedule.cron;
67
+ const promptIndented = spec.prompt.split('\n').map(l => ' ' + l).join('\n');
68
+ const outputPath = (spec.output && spec.output.path) || `docs/reports/{{YYYYMMDD}}-${spec.name}.md`;
69
+ const commitEnabled = spec.output && spec.output.commit && spec.output.commit.enabled;
70
+ const commitPrefix = (spec.output && spec.output.commit && spec.output.commit.prefix) || `[${spec.name.toUpperCase()}]`;
71
+
72
+ return `# Generated by BALDART (v${require('../../../package.json').version}) — npx baldart routines
73
+ # Routine: ${spec.name}
74
+ # ${spec.description}
75
+
76
+ name: BALDART ${spec.name}
77
+
78
+ on:
79
+ schedule:
80
+ - cron: '${cron}' # ${spec.schedule.cadence_label || 'scheduled'}
81
+ workflow_dispatch: # manual trigger from the Actions tab
82
+
83
+ permissions:
84
+ contents: write
85
+ pull-requests: write
86
+
87
+ jobs:
88
+ run:
89
+ runs-on: ubuntu-latest
90
+ timeout-minutes: 60
91
+ steps:
92
+ - name: Checkout
93
+ uses: actions/checkout@v4
94
+ with:
95
+ fetch-depth: 0
96
+
97
+ - name: Setup Node
98
+ uses: actions/setup-node@v4
99
+ with:
100
+ node-version: '20'
101
+
102
+ - name: Install Claude Code CLI
103
+ run: npm install -g @anthropic-ai/claude-code
104
+
105
+ - name: Run ${spec.name}
106
+ env:
107
+ ANTHROPIC_API_KEY: \${{ secrets.ANTHROPIC_API_KEY }}
108
+ run: |
109
+ DATE=$(date -u +%Y%m%d)
110
+ OUT_PATH="${outputPath}"
111
+ OUT_PATH="\${OUT_PATH//\\{\\{YYYYMMDD\\}\\}/$DATE}"
112
+ mkdir -p "$(dirname "$OUT_PATH")"
113
+
114
+ claude --print --output-format json --mode bypassPermissions \\
115
+ --agent ${spec.agent} \\
116
+ > /tmp/claude-output.json <<'PROMPT'
117
+ ${promptIndented}
118
+ PROMPT
119
+
120
+ # Persist the output if the agent did not already write to disk
121
+ if [ ! -s "$OUT_PATH" ]; then
122
+ jq -r '.result // .' /tmp/claude-output.json > "$OUT_PATH" || \\
123
+ cp /tmp/claude-output.json "$OUT_PATH"
124
+ fi
125
+ ${commitEnabled ? `
126
+ - name: Commit report
127
+ run: |
128
+ git config user.name "baldart-routine"
129
+ git config user.email "baldart-routine@users.noreply.github.com"
130
+ git add -A
131
+ if ! git diff --staged --quiet; then
132
+ git commit -m "${commitPrefix} \${{ github.workflow }} run \$(date -u +%Y-%m-%d)"
133
+ git push
134
+ else
135
+ echo "No changes to commit."
136
+ fi
137
+ ` : ''}`;
138
+ }
139
+ }
140
+
141
+ module.exports = GitHubActionsAdapter;
@@ -0,0 +1,21 @@
1
+ const ClaudeCodeCloudAdapter = require('./claude-code-cloud');
2
+ const GitHubActionsAdapter = require('./github-actions');
3
+ const CronAdapter = require('./cron');
4
+
5
+ const REGISTRY = {
6
+ 'claude-code-cloud': ClaudeCodeCloudAdapter,
7
+ 'github-actions': GitHubActionsAdapter,
8
+ 'cron': CronAdapter
9
+ };
10
+
11
+ function listAdapters() {
12
+ return Object.keys(REGISTRY);
13
+ }
14
+
15
+ function getAdapter(name, cwd) {
16
+ const Cls = REGISTRY[name];
17
+ if (!Cls) throw new Error(`Unknown adapter: ${name}. Available: ${listAdapters().join(', ')}`);
18
+ return new Cls(cwd);
19
+ }
20
+
21
+ module.exports = { listAdapters, getAdapter, REGISTRY };
@@ -0,0 +1,166 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const yaml = require('js-yaml');
4
+
5
+ const FRAMEWORK_DIR = '.framework';
6
+ const ROUTINES_SUBPATH = path.join('framework', 'routines');
7
+ const LOCK_DIR = '.baldart';
8
+ const LOCK_FILE = 'routines.lock.json';
9
+
10
+ class Routines {
11
+ constructor(cwd = process.cwd()) {
12
+ this.cwd = cwd;
13
+ }
14
+
15
+ // -------- Catalog reading ------------------------------------------------
16
+
17
+ routinesDir() {
18
+ return path.join(this.cwd, FRAMEWORK_DIR, ROUTINES_SUBPATH);
19
+ }
20
+
21
+ catalogPath() {
22
+ return path.join(this.routinesDir(), 'index.yml');
23
+ }
24
+
25
+ /** Read the routines catalog (index.yml). Returns {version, routines: []} or null. */
26
+ readCatalog() {
27
+ const p = this.catalogPath();
28
+ if (!fs.existsSync(p)) return null;
29
+ try {
30
+ return yaml.load(fs.readFileSync(p, 'utf8'));
31
+ } catch (err) {
32
+ throw new Error(`Failed to parse ${p}: ${err.message}`);
33
+ }
34
+ }
35
+
36
+ /** Read a single routine spec by name. Throws if missing or malformed. */
37
+ readSpec(name) {
38
+ const catalog = this.readCatalog();
39
+ if (!catalog) throw new Error('Routines catalog not found. Is BALDART installed?');
40
+ const entry = (catalog.routines || []).find(r => r.name === name);
41
+ if (!entry) throw new Error(`Unknown routine: ${name}`);
42
+ const specPath = path.join(this.routinesDir(), entry.file);
43
+ if (!fs.existsSync(specPath)) {
44
+ throw new Error(`Routine spec missing on disk: ${specPath}`);
45
+ }
46
+ const spec = yaml.load(fs.readFileSync(specPath, 'utf8'));
47
+ spec._catalogEntry = entry;
48
+ spec._specPath = specPath;
49
+ return spec;
50
+ }
51
+
52
+ /** Return the full list of available routines (catalog entries enriched with spec). */
53
+ list() {
54
+ const catalog = this.readCatalog();
55
+ if (!catalog) return [];
56
+ return (catalog.routines || []).map(entry => {
57
+ try {
58
+ return this.readSpec(entry.name);
59
+ } catch (err) {
60
+ return Object.assign({}, entry, { _error: err.message });
61
+ }
62
+ });
63
+ }
64
+
65
+ // -------- Lock file ------------------------------------------------------
66
+
67
+ lockDir() {
68
+ return path.join(this.cwd, LOCK_DIR);
69
+ }
70
+
71
+ lockPath() {
72
+ return path.join(this.lockDir(), LOCK_FILE);
73
+ }
74
+
75
+ readLock() {
76
+ const p = this.lockPath();
77
+ if (!fs.existsSync(p)) return { version: 1, routines: {} };
78
+ try {
79
+ return JSON.parse(fs.readFileSync(p, 'utf8'));
80
+ } catch (err) {
81
+ throw new Error(`Failed to parse ${p}: ${err.message}`);
82
+ }
83
+ }
84
+
85
+ writeLock(lock) {
86
+ if (!fs.existsSync(this.lockDir())) {
87
+ fs.mkdirSync(this.lockDir(), { recursive: true });
88
+ }
89
+ fs.writeFileSync(this.lockPath(), JSON.stringify(lock, null, 2) + '\n');
90
+ }
91
+
92
+ /** Update the lock entry for a routine. Status is "installed" | "skipped" | "disabled". */
93
+ setLockEntry(name, entry) {
94
+ const lock = this.readLock();
95
+ lock.routines = lock.routines || {};
96
+ lock.routines[name] = Object.assign({}, lock.routines[name] || {}, entry, {
97
+ updated_at: new Date().toISOString()
98
+ });
99
+ this.writeLock(lock);
100
+ }
101
+
102
+ removeLockEntry(name) {
103
+ const lock = this.readLock();
104
+ if (lock.routines && lock.routines[name]) {
105
+ delete lock.routines[name];
106
+ this.writeLock(lock);
107
+ }
108
+ }
109
+
110
+ // -------- Status computation --------------------------------------------
111
+
112
+ /**
113
+ * Compute status for every routine in the catalog.
114
+ * Returns array of { spec, lockEntry, status, requiredArtifacts: {present, missing} }
115
+ */
116
+ computeStatus() {
117
+ const lock = this.readLock();
118
+ return this.list().map(spec => {
119
+ if (spec._error) {
120
+ return { spec, lockEntry: null, status: 'error', error: spec._error };
121
+ }
122
+ const lockEntry = (lock.routines || {})[spec.name] || null;
123
+ const required = (spec.required_artifacts || []).map(p => {
124
+ const full = path.join(this.cwd, p);
125
+ return { path: p, exists: fs.existsSync(full) };
126
+ });
127
+ const missingRequired = required.filter(r => !r.exists);
128
+ const isOptional = !!spec.optional;
129
+
130
+ let status;
131
+ if (lockEntry && lockEntry.status === 'installed') {
132
+ status = 'installed';
133
+ } else if (lockEntry && lockEntry.status === 'skipped') {
134
+ status = 'skipped';
135
+ } else if (lockEntry && lockEntry.status === 'disabled') {
136
+ status = 'disabled';
137
+ } else if (missingRequired.length > 0 && isOptional) {
138
+ status = 'unavailable';
139
+ } else if (missingRequired.length > 0) {
140
+ status = 'blocked';
141
+ } else {
142
+ status = 'available';
143
+ }
144
+
145
+ return {
146
+ spec,
147
+ lockEntry,
148
+ status,
149
+ requiredArtifacts: { present: required.filter(r => r.exists), missing: missingRequired },
150
+ optional: isOptional
151
+ };
152
+ });
153
+ }
154
+
155
+ // -------- New routines since last seen ----------------------------------
156
+
157
+ /**
158
+ * Returns routines that exist in the catalog but have no lock entry — i.e.
159
+ * routines the user has never been prompted about. Used by add.js/update.js.
160
+ */
161
+ newRoutines() {
162
+ return this.computeStatus().filter(r => r.status === 'available' && !r.lockEntry);
163
+ }
164
+ }
165
+
166
+ module.exports = Routines;