baldart 3.14.1 → 3.16.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 (27) hide show
  1. package/CHANGELOG.md +79 -0
  2. package/VERSION +1 -1
  3. package/framework/.claude/agents/REGISTRY.md +10 -0
  4. package/framework/.claude/agents/api-perf-cost-auditor.md +47 -10
  5. package/framework/.claude/agents/coder.md +61 -10
  6. package/framework/.claude/agents/prd-card-writer.md +92 -19
  7. package/framework/.claude/agents/security-reviewer.md +1 -1
  8. package/framework/.claude/skills/bug/references/logging-patterns.md +45 -8
  9. package/framework/.claude/skills/new/SKILL.md +319 -87
  10. package/framework/.claude/skills/prd/SKILL.md +30 -2
  11. package/framework/.claude/skills/prd/assets/card-template.yml +59 -27
  12. package/framework/.claude/skills/prd/assets/epic-template.yml +11 -3
  13. package/framework/.claude/skills/prd/assets/prd-template.md +90 -14
  14. package/framework/.claude/skills/prd/assets/state-template.md +18 -0
  15. package/framework/.claude/skills/prd/references/api-perf-gate.md +102 -52
  16. package/framework/.claude/skills/prd/references/audit-phase.md +13 -13
  17. package/framework/.claude/skills/prd/references/backlog-phase.md +81 -0
  18. package/framework/.claude/skills/prd/references/discovery-phase.md +214 -28
  19. package/framework/.claude/skills/prd/references/prd-writing-phase.md +65 -23
  20. package/framework/.claude/skills/prd/references/research-phase.md +22 -4
  21. package/framework/.claude/skills/prd/references/ui-design-phase.md +115 -3
  22. package/framework/.claude/skills/prd/references/validation-phase.md +22 -2
  23. package/framework/docs/PROJECT-CONFIGURATION.md +41 -1
  24. package/framework/templates/baldart.config.template.yml +25 -0
  25. package/package.json +1 -1
  26. package/src/commands/configure.js +72 -0
  27. package/src/commands/update.js +13 -1
@@ -131,9 +131,49 @@ stack:
131
131
  forbidden: []
132
132
  testing:
133
133
  e2e: "playwright" # or "cypress", or ""
134
+
135
+ # Stack-cardinal scalars (since v3.15.0). Used by /prd, coder,
136
+ # api-perf-cost-auditor, /new, code-reviewer, security-reviewer to branch
137
+ # vocabulary, gate sections, anti-pattern checklists, deploy commands.
138
+ database: "firestore" # firestore|supabase|postgres|mysql|mongodb|dynamodb|sqlite|none|""
139
+ auth_provider: "firebase-auth" # firebase-auth|supabase-auth|clerk|auth0|cognito|nextauth|lucia|custom|none|""
140
+ framework: "nextjs" # nextjs|remix|sveltekit|astro|nuxt|rails|django|fastapi|express|none|""
141
+ deployment: "vercel" # vercel|firebase|aws|gcp|cloudflare|render|fly|self-hosted|none|""
134
142
  ```
135
143
 
136
- Skills propose only `canonical` libraries and refuse `forbidden` ones. Empty `canonical` = ask the user before proposing a library.
144
+ Skills propose only `canonical` libraries and refuse `forbidden` ones. Empty
145
+ `canonical` = ask the user before proposing a library.
146
+
147
+ **Stack-cardinal scalars (v3.15.0+).** The four scalar keys (`database`,
148
+ `auth_provider`, `framework`, `deployment`) drive stack-aware branching across
149
+ the skill/agent set:
150
+
151
+ - `/prd` Schema Verification Gate picks the registry path matching
152
+ `stack.database` (Firestore field-registry, Prisma schema, SQL migrations,
153
+ Mongo validator).
154
+ - `/prd` Section 5 of the PRD template renders the index variant matching
155
+ `stack.database` (Firestore composite, SQL B-tree, Mongo compound,
156
+ DynamoDB GSI/LSI).
157
+ - `api-perf-gate` keyword scan adds DB-specific anti-patterns only when the
158
+ matching `stack.database` is set; universal patterns always run.
159
+ - `coder.md` Database Index Invariant ships the right migration kind for
160
+ the resolved `stack.database`.
161
+ - `api-perf-cost-auditor` reads the four scalars at session start and cites
162
+ them in every audit header, so the user knows which variant applied.
163
+ - `/new` Production Readiness Checklist picks deploy commands matching
164
+ `stack.deployment` + `stack.database` (firebase deploy, vercel deploy,
165
+ supabase db push, CDK/Terraform apply).
166
+ - `security-reviewer` evaluates access rules per `stack.database`
167
+ (Firestore rules, Supabase RLS, Mongo validators, DynamoDB IAM,
168
+ Postgres RLS+GRANT).
169
+ - `bug/logging-patterns.md` picks the framework-native debug switches
170
+ matching `stack.framework`.
171
+
172
+ **Always-ask contract on empty.** When a scalar is `""` and a skill needs it,
173
+ the skill asks the user once and suggests running `npx baldart configure` to
174
+ persist the answer. Skills NEVER assume a default — empty means "ask".
175
+ `baldart update` flags newly-introduced scalar keys via the schema-drift
176
+ detector and auto-offers `baldart configure`.
137
177
 
138
178
  ### 4.5 `features` — explicit booleans gating BLOCKING reads
139
179
 
@@ -85,6 +85,31 @@ stack:
85
85
  # E2E framework — drives playwright-skill behaviour.
86
86
  e2e: "" # e.g. "playwright" | "cypress" | ""
87
87
 
88
+ # Persistence layer. Drives /prd Schema Verification Gate, index strategies
89
+ # in prd-template, coder Database Index Invariant, api-perf-cost-auditor
90
+ # pricing model, audit-phase security-rules detection.
91
+ # Recognized values: "firestore" | "supabase" | "postgres" | "mysql" |
92
+ # "mongodb" | "dynamodb" | "sqlite" | "none" | "" (unset → skill asks).
93
+ database: ""
94
+
95
+ # Authentication provider. Drives security-reviewer rule sets and discovery
96
+ # E2E credential gathering defaults.
97
+ # Recognized values: "firebase-auth" | "supabase-auth" | "clerk" | "auth0" |
98
+ # "cognito" | "nextauth" | "lucia" | "custom" | "none" | "".
99
+ auth_provider: ""
100
+
101
+ # Application framework. Drives api-perf-gate caching defaults, bug
102
+ # logging-patterns, code-reviewer convention checks, coder file layout.
103
+ # Recognized values: "nextjs" | "remix" | "sveltekit" | "astro" | "nuxt" |
104
+ # "rails" | "django" | "fastapi" | "express" | "none" | "".
105
+ framework: ""
106
+
107
+ # Deployment target. Drives /new deploy commands, api-perf-cost-auditor
108
+ # cost model, framework-edit-gate path checks.
109
+ # Recognized values: "vercel" | "firebase" | "aws" | "gcp" | "cloudflare" |
110
+ # "render" | "fly" | "self-hosted" | "none" | "".
111
+ deployment: ""
112
+
88
113
  # ─── FEATURES ────────────────────────────────────────────────────────────
89
114
  # Explicit booleans. ALL keys must be present (true | false) once `baldart
90
115
  # configure` has run. An absent flag means the user hasn't been asked yet —
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "baldart",
3
- "version": "3.14.1",
3
+ "version": "3.16.0",
4
4
  "description": "Claude Agent Framework - Reusable framework for coordinating AI agents and humans in software projects",
5
5
  "bin": {
6
6
  "baldart": "./bin/baldart.js"
@@ -284,6 +284,50 @@ function detect(cwd = process.cwd()) {
284
284
 
285
285
  const mergeStrategyProbe = detectMergeStrategy(cwd);
286
286
 
287
+ // ---- Persistence layer (stack.database) --------------------------------
288
+ let detectedDatabase = '';
289
+ if (hasAnyDep('firebase', 'firebase-admin', '@google-cloud/firestore', '@firebase/firestore')) detectedDatabase = 'firestore';
290
+ else if (hasAnyDep('@supabase/supabase-js', '@supabase/ssr', '@supabase/auth-helpers-nextjs')) detectedDatabase = 'supabase';
291
+ else if (hasAnyDep('mongoose', 'mongodb')) detectedDatabase = 'mongodb';
292
+ else if (hasAnyDep('pg', 'postgres', '@neondatabase/serverless', '@vercel/postgres')) detectedDatabase = 'postgres';
293
+ else if (hasAnyDep('mysql', 'mysql2', '@planetscale/database')) detectedDatabase = 'mysql';
294
+ else if (hasAnyDep('@aws-sdk/client-dynamodb', 'dynamodb-toolbox')) detectedDatabase = 'dynamodb';
295
+ else if (hasAnyDep('better-sqlite3', 'sqlite3', '@libsql/client')) detectedDatabase = 'sqlite';
296
+
297
+ // ---- Auth provider (stack.auth_provider) -------------------------------
298
+ // Order matters: more specific deps win over the catch-all firebase/supabase.
299
+ let detectedAuthProvider = '';
300
+ if (hasAnyDep('@clerk/nextjs', '@clerk/clerk-sdk-node', '@clerk/clerk-js', '@clerk/backend')) detectedAuthProvider = 'clerk';
301
+ else if (hasAnyDep('@auth0/nextjs-auth0', '@auth0/auth0-react', 'auth0')) detectedAuthProvider = 'auth0';
302
+ else if (hasAnyDep('next-auth', '@auth/core')) detectedAuthProvider = 'nextauth';
303
+ else if (hasAnyDep('lucia', '@lucia-auth/core', '@lucia-auth/adapter-prisma')) detectedAuthProvider = 'lucia';
304
+ else if (hasAnyDep('amazon-cognito-identity-js', '@aws-sdk/client-cognito-identity-provider')) detectedAuthProvider = 'cognito';
305
+ else if (hasAnyDep('@supabase/auth-helpers-nextjs', '@supabase/ssr')) detectedAuthProvider = 'supabase-auth';
306
+ else if (hasAnyDep('firebase', '@firebase/auth', 'firebase-admin')) detectedAuthProvider = 'firebase-auth';
307
+
308
+ // ---- Application framework (stack.framework) ---------------------------
309
+ let detectedFramework = '';
310
+ if (hasDep('next')) detectedFramework = 'nextjs';
311
+ else if (hasAnyDep('@remix-run/react', '@remix-run/node', '@remix-run/server-runtime')) detectedFramework = 'remix';
312
+ else if (hasAnyDep('@sveltejs/kit', 'svelte')) detectedFramework = 'sveltekit';
313
+ else if (hasDep('astro')) detectedFramework = 'astro';
314
+ else if (hasDep('nuxt')) detectedFramework = 'nuxt';
315
+ else if (hasDep('express')) detectedFramework = 'express';
316
+ else if (exists('Gemfile') && /\bgem\s+['"]rails['"]/.test((readSafe('Gemfile') || ''))) detectedFramework = 'rails';
317
+ else if (exists('manage.py') || (exists('requirements.txt') && /\bdjango\b/i.test(readSafe('requirements.txt') || ''))) detectedFramework = 'django';
318
+ else if (exists('requirements.txt') && /\bfastapi\b/i.test(readSafe('requirements.txt') || '')) detectedFramework = 'fastapi';
319
+
320
+ // ---- Deployment target (stack.deployment) ------------------------------
321
+ let detectedDeployment = '';
322
+ if (exists('vercel.json') || hasDep('@vercel/node')) detectedDeployment = 'vercel';
323
+ else if (exists('firebase.json')) detectedDeployment = 'firebase';
324
+ else if (exists('wrangler.toml') || exists('wrangler.jsonc') || exists('wrangler.json')) detectedDeployment = 'cloudflare';
325
+ else if (exists('fly.toml')) detectedDeployment = 'fly';
326
+ else if (exists('render.yaml')) detectedDeployment = 'render';
327
+ else if (exists('amplify.yml') || exists('serverless.yml')) detectedDeployment = 'aws';
328
+ else if (exists('app.yaml') || exists('cloudbuild.yaml')) detectedDeployment = 'gcp';
329
+ // Note: bare Dockerfile → leave empty; user picks "self-hosted" or actual target.
330
+
287
331
  const detected = {
288
332
  paths: {
289
333
  design_system: designSystemPath,
@@ -323,6 +367,10 @@ function detect(cwd = process.cwd()) {
323
367
  forbidden: [],
324
368
  },
325
369
  testing: { e2e: e2eFramework },
370
+ database: detectedDatabase,
371
+ auth_provider: detectedAuthProvider,
372
+ framework: detectedFramework,
373
+ deployment: detectedDeployment,
326
374
  // New: surface the detected monorepo + DS signals so skills can read them.
327
375
  monorepo: isMonorepo ? {
328
376
  detected: true,
@@ -692,6 +740,26 @@ async function interactivePrompts(merged, detected) {
692
740
  merged.stack.testing.e2e || detected.stack.testing.e2e || ''
693
741
  );
694
742
 
743
+ merged.stack.database = await promptForKey(
744
+ 'Persistence layer ("firestore" / "supabase" / "postgres" / "mysql" / "mongodb" / "dynamodb" / "sqlite" / "none" — empty to leave unset)',
745
+ merged.stack.database || detected.stack.database || ''
746
+ );
747
+
748
+ merged.stack.auth_provider = await promptForKey(
749
+ 'Auth provider ("firebase-auth" / "supabase-auth" / "clerk" / "auth0" / "cognito" / "nextauth" / "lucia" / "custom" / "none" — empty to leave unset)',
750
+ merged.stack.auth_provider || detected.stack.auth_provider || ''
751
+ );
752
+
753
+ merged.stack.framework = await promptForKey(
754
+ 'Application framework ("nextjs" / "remix" / "sveltekit" / "astro" / "nuxt" / "rails" / "django" / "fastapi" / "express" / "none" — empty to leave unset)',
755
+ merged.stack.framework || detected.stack.framework || ''
756
+ );
757
+
758
+ merged.stack.deployment = await promptForKey(
759
+ 'Deployment target ("vercel" / "firebase" / "aws" / "gcp" / "cloudflare" / "render" / "fly" / "self-hosted" / "none" — empty to leave unset)',
760
+ merged.stack.deployment || detected.stack.deployment || ''
761
+ );
762
+
695
763
  return merged;
696
764
  }
697
765
 
@@ -771,6 +839,10 @@ async function configure(opts = {}) {
771
839
  `Charting libs: ${detected.stack.charting.canonical.join(', ') || '—'}`,
772
840
  `Animation libs: ${detected.stack.animation.canonical.join(', ') || '—'}`,
773
841
  `E2E framework: ${detected.stack.testing.e2e || '—'}`,
842
+ `Database: ${detected.stack.database || '—'}`,
843
+ `Auth provider: ${detected.stack.auth_provider || '—'}`,
844
+ `Framework: ${detected.stack.framework || '—'}`,
845
+ `Deployment: ${detected.stack.deployment || '—'}`,
774
846
  ]);
775
847
 
776
848
  if (opts.nonInteractive) {
@@ -452,7 +452,19 @@ async function update(options = {}) {
452
452
  .filter((k) => !(k in (cur2.paths || {})));
453
453
  const missingGit = Object.keys(tpl.git || {})
454
454
  .filter((k) => !(k in (cur2.git || {})));
455
- const allMissing = [...missingPaths, ...missingFeatures, ...missingGit.map((k) => `git.${k}`)];
455
+ // Scalar (string) top-level stack.* keys — e.g. stack.database,
456
+ // stack.auth_provider, stack.framework, stack.deployment. Sub-object
457
+ // keys (charting, animation, testing) are intentionally skipped:
458
+ // their drift is handled by individual sub-prompts in configure.
459
+ const missingStack = Object.keys(tpl.stack || {})
460
+ .filter((k) => typeof tpl.stack[k] === 'string')
461
+ .filter((k) => !(k in (cur2.stack || {})));
462
+ const allMissing = [
463
+ ...missingPaths,
464
+ ...missingFeatures,
465
+ ...missingGit.map((k) => `git.${k}`),
466
+ ...missingStack.map((k) => `stack.${k}`),
467
+ ];
456
468
  if (allMissing.length) {
457
469
  UI.newline();
458
470
  UI.warning(`New config keys in this version: ${allMissing.join(', ')}.`);