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.
- package/CHANGELOG.md +70 -15
- package/VERSION +1 -1
- package/framework/.claude/agents/api-perf-cost-auditor.md +47 -10
- package/framework/.claude/agents/coder.md +61 -10
- package/framework/.claude/agents/prd-card-writer.md +29 -19
- package/framework/.claude/agents/security-reviewer.md +1 -1
- package/framework/.claude/skills/bug/references/logging-patterns.md +45 -8
- package/framework/.claude/skills/new/SKILL.md +180 -86
- package/framework/.claude/skills/prd/SKILL.md +30 -2
- package/framework/.claude/skills/prd/assets/card-template.yml +43 -26
- package/framework/.claude/skills/prd/assets/epic-template.yml +3 -2
- package/framework/.claude/skills/prd/assets/prd-template.md +90 -14
- package/framework/.claude/skills/prd/assets/state-template.md +18 -0
- package/framework/.claude/skills/prd/references/api-perf-gate.md +102 -52
- package/framework/.claude/skills/prd/references/audit-phase.md +13 -13
- package/framework/.claude/skills/prd/references/discovery-phase.md +214 -28
- package/framework/.claude/skills/prd/references/prd-writing-phase.md +65 -23
- package/framework/.claude/skills/prd/references/research-phase.md +22 -4
- package/framework/.claude/skills/prd/references/ui-design-phase.md +115 -3
- package/framework/.claude/skills/prd/references/validation-phase.md +2 -2
- package/framework/docs/PROJECT-CONFIGURATION.md +41 -1
- package/framework/templates/baldart.config.template.yml +25 -0
- package/package.json +1 -1
- package/src/commands/configure.js +72 -0
- package/src/commands/update.js +13 -1
- 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
|
@@ -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) {
|
package/src/commands/update.js
CHANGED
|
@@ -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
|
-
|
|
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
|
|
210
|
+
pattern,
|
|
206
211
|
raw: line
|
|
207
212
|
};
|
|
208
213
|
} catch (_) {
|