baldart 3.14.0 → 3.15.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 (26) hide show
  1. package/CHANGELOG.md +70 -15
  2. package/VERSION +1 -1
  3. package/framework/.claude/agents/api-perf-cost-auditor.md +47 -10
  4. package/framework/.claude/agents/coder.md +61 -10
  5. package/framework/.claude/agents/prd-card-writer.md +29 -19
  6. package/framework/.claude/agents/security-reviewer.md +1 -1
  7. package/framework/.claude/skills/bug/references/logging-patterns.md +45 -8
  8. package/framework/.claude/skills/new/SKILL.md +180 -86
  9. package/framework/.claude/skills/prd/SKILL.md +30 -2
  10. package/framework/.claude/skills/prd/assets/card-template.yml +43 -26
  11. package/framework/.claude/skills/prd/assets/epic-template.yml +3 -2
  12. package/framework/.claude/skills/prd/assets/prd-template.md +90 -14
  13. package/framework/.claude/skills/prd/assets/state-template.md +18 -0
  14. package/framework/.claude/skills/prd/references/api-perf-gate.md +102 -52
  15. package/framework/.claude/skills/prd/references/audit-phase.md +13 -13
  16. package/framework/.claude/skills/prd/references/discovery-phase.md +214 -28
  17. package/framework/.claude/skills/prd/references/prd-writing-phase.md +65 -23
  18. package/framework/.claude/skills/prd/references/research-phase.md +22 -4
  19. package/framework/.claude/skills/prd/references/ui-design-phase.md +115 -3
  20. package/framework/.claude/skills/prd/references/validation-phase.md +2 -2
  21. package/framework/docs/PROJECT-CONFIGURATION.md +41 -1
  22. package/framework/templates/baldart.config.template.yml +25 -0
  23. package/package.json +1 -1
  24. package/src/commands/configure.js +72 -0
  25. package/src/commands/update.js +13 -1
  26. package/src/utils/git.js +6 -1
@@ -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.0",
3
+ "version": "3.15.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(', ')}.`);
package/src/utils/git.js CHANGED
@@ -198,11 +198,16 @@ class GitUtils {
198
198
  if (!line) return { ignored: false };
199
199
  // Format: "<source>:<line>:<pattern>\t<path>"
200
200
  const m = /^(.+?):(\d+):([^\t]+)\t/.exec(line);
201
+ const pattern = m ? m[3] : null;
202
+ // A leading `!` makes it a NEGATION pattern — the path is re-included,
203
+ // not ignored. `git check-ignore` still prints it (matching != ignored).
204
+ const isNegation = pattern ? pattern.startsWith('!') : false;
205
+ if (isNegation) return { ignored: false, negatedBy: pattern };
201
206
  return {
202
207
  ignored: true,
203
208
  source: m ? m[1] : 'unknown',
204
209
  line: m ? Number(m[2]) : null,
205
- pattern: m ? m[3] : null,
210
+ pattern,
206
211
  raw: line
207
212
  };
208
213
  } catch (_) {