baldart 3.6.2 → 3.6.4
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 +41 -0
- package/VERSION +1 -1
- package/package.json +1 -1
- package/src/commands/configure.js +270 -48
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,47 @@ All notable changes to BALDART will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [3.6.4] - 2026-05-22
|
|
9
|
+
|
|
10
|
+
Two configure-flow fixes surfaced by a real-world `mayo` install audit.
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
|
|
14
|
+
- **API docs false negative** — `has_api_docs` only checked for `docs/.../api/schemas.md` (and OpenAPI/GraphQL specs). Projects that ship API docs as one markdown per resource (e.g. `docs/references/api/{index,admin,push,…}.md`) were marked `has_api_docs: false` despite having extensive API documentation. Added a populated-directory probe: if `docs/references/api/` or `docs/api/` exists and contains ≥1 `.md` file, the feature flag flips to `true` and `api_index` falls back to `<dir>/index.md`.
|
|
15
|
+
|
|
16
|
+
### Changed
|
|
17
|
+
|
|
18
|
+
- **Prompt label clarity** — `"Charting libraries (canonical, comma-separated)"` → `"Approved charting libraries — comma-separated package names (empty if none)"`. Same edit for forbidden and animation. The previous label read like the user was being asked to type the word `canonical`; one tester actually did.
|
|
19
|
+
|
|
20
|
+
## [3.6.3] - 2026-05-22
|
|
21
|
+
|
|
22
|
+
Smarter project autodetection in `baldart configure`. The previous probe only looked at a single hardcoded path (`docs/design-system/INDEX.md`), which silently failed on the vast majority of real projects (monorepos, inline `src/ui/`, `packages/ui/`, shadcn/ui setups, Tailwind-theme-only projects, etc.). The redundant "Design philosophy" prompt is now skipped automatically when a design system is detected.
|
|
23
|
+
|
|
24
|
+
### Changed
|
|
25
|
+
|
|
26
|
+
- **`src/commands/configure.js` `detect()`** — multi-signal design system detection:
|
|
27
|
+
- **Path probes**: `docs/design-system`, `packages/ui`, `packages/design-system`, `packages/components`, `packages/ds`, `src/design-system`, `src/ui` (plus per-package expansion in monorepos).
|
|
28
|
+
- **Storybook**: `.storybook/` at root or in any monorepo package.
|
|
29
|
+
- **shadcn**: `components.json` marker.
|
|
30
|
+
- **Tailwind theme**: `tailwind.config.{ts,js,mjs,cjs}` with `theme.extend` (regex inspection).
|
|
31
|
+
- **DS libraries**: `@radix-ui/*`, `@chakra-ui/*`, `@mui/material`, `antd`, `@mantine/core`, `@nextui-org/*`, `daisyui`, `flowbite-react`, `@headlessui/react`, etc.
|
|
32
|
+
- **Monorepo awareness** — detects `pnpm-workspace.yaml`, `lerna.json`, `nx.json`, `turbo.json`, `rush.json`, or `package.json` `workspaces`. Expands every path probe across `packages/*`, `apps/*`, `services/*`, `libs/*`.
|
|
33
|
+
- **`components_primitives`** — when a design system is detected but no `components/ui` directory exists, probes the DS's own subdirs (`<ds>/src/components`, `<ds>/components`, `<ds>/src/ui`) and only returns paths that actually exist on disk.
|
|
34
|
+
- **UI guidelines** — widened to also detect `STYLEGUIDE.md`, `BRANDING.md`, `BRAND.md`, `docs/style-guide.md`, `docs/brand/README.md` plus monorepo variants.
|
|
35
|
+
- **API docs** — widened to detect OpenAPI specs (`openapi.{yaml,yml,json}`) and GraphQL schemas (`schema.graphql`) in addition to the existing markdown locations.
|
|
36
|
+
- **Brand name** — fallback chain: `package.json` `name` (scope stripped) → first H1 in `README.md` → `path.basename(cwd)`. No more empty `brand_name` for projects without a `package.json` name.
|
|
37
|
+
- **E2E framework** — also detects via dependency (`@playwright/test`, `cypress`) and `playwright.config.mjs`.
|
|
38
|
+
- **Charting/animation libraries** — added `chart.js`, `d3`, `visx`, `@tremor/react`, `@react-spring/web`, `auto-animate`, `motion`.
|
|
39
|
+
- **`interactivePrompts()` design philosophy** — gated on `!detected.features.has_design_system`. If a DS is detected, the prompt is skipped and a heuristic hint (e.g. `"Minimalist (shadcn/Tailwind)"`, `"Library-driven (@radix-ui)"`) is used as metadata for skills.
|
|
40
|
+
- **AUTODETECTED summary box** — now shows brand name, DS yes/no with its concrete signals (path/storybook/shadcn/tailwind-theme/library), monorepo detection + package count, API docs yes/no.
|
|
41
|
+
- **New config fields** under `stack.*`:
|
|
42
|
+
- `stack.monorepo: { detected, roots }` — exposes detected monorepo packages to skills.
|
|
43
|
+
- `stack.design_system_signals: { path, storybook, shadcn, tailwind_theme, library }` — granular signals for skills that want to adapt to the specific DS flavor.
|
|
44
|
+
|
|
45
|
+
### Why it matters
|
|
46
|
+
|
|
47
|
+
Before this change, a project with a real design system at `src/ui/` (or any non-canonical location) was told `Design system: — (none found)` and then asked the wrong question (`Design philosophy?`). Now the same project is correctly identified, the redundant prompt is skipped, and skills receive structured DS signals they can route on.
|
|
48
|
+
|
|
8
49
|
## [3.6.2] - 2026-05-22
|
|
9
50
|
|
|
10
51
|
BALDART is now published to npm as [`baldart`](https://www.npmjs.com/package/baldart) on every `v*.*.*` tag. End-users can run `npx baldart <cmd>` (without the `-y github:antbald/BALDART` prefix), which resolves through the npm registry and avoids the stale-tarball cache that plagued the GitHub-source installation.
|
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
3.6.
|
|
1
|
+
3.6.4
|
package/package.json
CHANGED
|
@@ -19,84 +19,280 @@ const SCHEMA_VERSION = 1;
|
|
|
19
19
|
* only with detected values. Empty / undetected keys remain at template
|
|
20
20
|
* defaults and the user is prompted explicitly for them.
|
|
21
21
|
*/
|
|
22
|
+
const IGNORED_DIRS = new Set([
|
|
23
|
+
'node_modules', '.git', '.next', '.nuxt', '.turbo', '.cache', '.svelte-kit',
|
|
24
|
+
'dist', 'build', 'out', 'coverage', '.vercel', '.framework', '.baldart',
|
|
25
|
+
'.expo', '.parcel-cache', 'tmp', '.idea', '.vscode', '.DS_Store'
|
|
26
|
+
]);
|
|
27
|
+
|
|
22
28
|
function detect(cwd = process.cwd()) {
|
|
23
29
|
const exists = (p) => fs.existsSync(path.join(cwd, p));
|
|
24
30
|
const findFirst = (...candidates) => candidates.find(exists) || '';
|
|
25
31
|
const readJsonSafe = (p) => {
|
|
26
32
|
try { return JSON.parse(fs.readFileSync(path.join(cwd, p), 'utf8')); } catch { return null; }
|
|
27
33
|
};
|
|
34
|
+
const readSafe = (p) => {
|
|
35
|
+
try { return fs.readFileSync(path.join(cwd, p), 'utf8'); } catch { return ''; }
|
|
36
|
+
};
|
|
37
|
+
const isDir = (p) => {
|
|
38
|
+
try { return fs.statSync(path.join(cwd, p)).isDirectory(); } catch { return false; }
|
|
39
|
+
};
|
|
28
40
|
const countMatches = (dir, regex) => {
|
|
29
41
|
const abs = path.join(cwd, dir);
|
|
30
42
|
if (!fs.existsSync(abs)) return 0;
|
|
31
|
-
try {
|
|
32
|
-
|
|
33
|
-
|
|
43
|
+
try { return fs.readdirSync(abs).filter((f) => regex.test(f)).length; }
|
|
44
|
+
catch { return 0; }
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
// Shallow recursive walk (depth-limited, ignores known noise dirs).
|
|
48
|
+
// Returns first relative path matching `predicate(name, fullRelPath)`, or ''.
|
|
49
|
+
const walkFirst = (predicate, maxDepth = 3, startRel = '') => {
|
|
50
|
+
const stack = [{ rel: startRel, depth: 0 }];
|
|
51
|
+
while (stack.length) {
|
|
52
|
+
const { rel, depth } = stack.pop();
|
|
53
|
+
const abs = path.join(cwd, rel);
|
|
54
|
+
let entries;
|
|
55
|
+
try { entries = fs.readdirSync(abs, { withFileTypes: true }); }
|
|
56
|
+
catch { continue; }
|
|
57
|
+
for (const ent of entries) {
|
|
58
|
+
if (ent.name.startsWith('.') && ent.name !== '.storybook') continue;
|
|
59
|
+
if (IGNORED_DIRS.has(ent.name)) continue;
|
|
60
|
+
const childRel = path.join(rel, ent.name);
|
|
61
|
+
if (predicate(ent.name, childRel, ent)) return childRel;
|
|
62
|
+
if (ent.isDirectory() && depth < maxDepth) {
|
|
63
|
+
stack.push({ rel: childRel, depth: depth + 1 });
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return '';
|
|
34
68
|
};
|
|
35
69
|
|
|
36
70
|
const pkg = readJsonSafe('package.json');
|
|
37
71
|
const deps = pkg ? { ...(pkg.dependencies || {}), ...(pkg.devDependencies || {}) } : {};
|
|
38
72
|
const hasDep = (name) => Object.keys(deps).some((d) => d === name || d.startsWith(`${name}/`));
|
|
73
|
+
const hasAnyDep = (...names) => names.some(hasDep);
|
|
74
|
+
|
|
75
|
+
// ---- Monorepo awareness ------------------------------------------------
|
|
76
|
+
const monorepoMarkers = ['pnpm-workspace.yaml', 'lerna.json', 'nx.json', 'turbo.json', 'rush.json'];
|
|
77
|
+
const isMonorepo = monorepoMarkers.some(exists) || Array.isArray(pkg?.workspaces) ||
|
|
78
|
+
!!(pkg?.workspaces && typeof pkg.workspaces === 'object');
|
|
79
|
+
|
|
80
|
+
const monorepoRoots = []; // list of relative dirs to also search inside
|
|
81
|
+
if (isMonorepo) {
|
|
82
|
+
for (const root of ['packages', 'apps', 'services', 'libs']) {
|
|
83
|
+
if (isDir(root)) {
|
|
84
|
+
try {
|
|
85
|
+
fs.readdirSync(path.join(cwd, root), { withFileTypes: true })
|
|
86
|
+
.filter((d) => d.isDirectory() && !d.name.startsWith('.'))
|
|
87
|
+
.forEach((d) => monorepoRoots.push(path.join(root, d.name)));
|
|
88
|
+
} catch { /* ignore */ }
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Expand a candidate-relative path (e.g. "src/components/ui") into every
|
|
94
|
+
// realistic search location: root + each monorepo package.
|
|
95
|
+
const expandCandidates = (...rels) => {
|
|
96
|
+
const out = [];
|
|
97
|
+
for (const rel of rels) {
|
|
98
|
+
out.push(rel);
|
|
99
|
+
for (const mr of monorepoRoots) out.push(path.join(mr, rel));
|
|
100
|
+
}
|
|
101
|
+
return out;
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
// ---- Design system detection (multi-signal) ----------------------------
|
|
105
|
+
// Path signals
|
|
106
|
+
const dsPathCandidates = expandCandidates(
|
|
107
|
+
'docs/design-system',
|
|
108
|
+
'packages/ui',
|
|
109
|
+
'packages/design-system',
|
|
110
|
+
'packages/components',
|
|
111
|
+
'packages/ds',
|
|
112
|
+
'src/design-system',
|
|
113
|
+
'src/ui'
|
|
114
|
+
);
|
|
115
|
+
let designSystemPath = dsPathCandidates.find((p) => isDir(p)) || '';
|
|
116
|
+
|
|
117
|
+
// Storybook signal
|
|
118
|
+
const hasStorybook = isDir('.storybook') || monorepoRoots.some((r) => isDir(path.join(r, '.storybook')));
|
|
119
|
+
|
|
120
|
+
// shadcn marker
|
|
121
|
+
const hasShadcn = exists('components.json') ||
|
|
122
|
+
monorepoRoots.some((r) => exists(path.join(r, 'components.json')));
|
|
123
|
+
|
|
124
|
+
// Tailwind with custom theme
|
|
125
|
+
const tailwindCfg = findFirst(
|
|
126
|
+
'tailwind.config.ts', 'tailwind.config.js', 'tailwind.config.mjs', 'tailwind.config.cjs'
|
|
127
|
+
);
|
|
128
|
+
let hasTailwindTheme = false;
|
|
129
|
+
if (tailwindCfg) {
|
|
130
|
+
const body = readSafe(tailwindCfg);
|
|
131
|
+
hasTailwindTheme = /theme\s*:\s*\{[\s\S]*(extend|colors|spacing|fontFamily|borderRadius)/m.test(body);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Design system packages
|
|
135
|
+
const dsLibraries = [
|
|
136
|
+
'@radix-ui', '@chakra-ui', '@mui/material', '@mui/joy', 'antd', '@mantine/core',
|
|
137
|
+
'@nextui-org/react', 'daisyui', 'flowbite-react', '@headlessui/react', '@arco-design/web-react',
|
|
138
|
+
'@blueprintjs/core', '@fluentui/react'
|
|
139
|
+
];
|
|
140
|
+
const dsLibInDeps = dsLibraries.find((n) => hasDep(n)) || '';
|
|
141
|
+
|
|
142
|
+
// Aggregate: a design system "exists" if ANY strong signal is present.
|
|
143
|
+
const hasDesignSystem = !!(designSystemPath || hasStorybook || hasShadcn || hasTailwindTheme || dsLibInDeps);
|
|
144
|
+
|
|
145
|
+
// If we found no explicit path but we know one exists by other means,
|
|
146
|
+
// best-effort point to the most likely location for skills to read.
|
|
147
|
+
if (!designSystemPath && hasDesignSystem) {
|
|
148
|
+
designSystemPath = findFirst('docs/design-system', 'packages/ui', 'src/design-system') || '';
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// ---- UI guidelines (widened) -------------------------------------------
|
|
152
|
+
const uiGuidelines = findFirst(
|
|
153
|
+
...expandCandidates(
|
|
154
|
+
'docs/references/ui-guidelines.md',
|
|
155
|
+
'docs/ui-guidelines.md',
|
|
156
|
+
'docs/references/brand-guidelines.md',
|
|
157
|
+
'docs/brand-guidelines.md',
|
|
158
|
+
'docs/style-guide.md',
|
|
159
|
+
'docs/styleguide.md',
|
|
160
|
+
'docs/brand/README.md',
|
|
161
|
+
'STYLEGUIDE.md',
|
|
162
|
+
'BRANDING.md',
|
|
163
|
+
'BRAND.md'
|
|
164
|
+
)
|
|
165
|
+
);
|
|
166
|
+
|
|
167
|
+
// ---- Components paths (monorepo-aware) ---------------------------------
|
|
168
|
+
// Try the conventional UI primitive locations first; if none exist but a
|
|
169
|
+
// design system path was detected, probe its common subdirs and only return
|
|
170
|
+
// one that actually exists on disk.
|
|
171
|
+
let componentsPrimitives = findFirst(
|
|
172
|
+
...expandCandidates('src/components/ui', 'app/components/ui', 'components/ui')
|
|
173
|
+
);
|
|
174
|
+
if (!componentsPrimitives && designSystemPath) {
|
|
175
|
+
componentsPrimitives = findFirst(
|
|
176
|
+
path.join(designSystemPath, 'src/components'),
|
|
177
|
+
path.join(designSystemPath, 'components'),
|
|
178
|
+
path.join(designSystemPath, 'src/ui'),
|
|
179
|
+
designSystemPath // last-resort: the DS root itself
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const componentsRoot = findFirst(
|
|
184
|
+
...expandCandidates('src/components', 'app/components', 'components')
|
|
185
|
+
);
|
|
186
|
+
|
|
187
|
+
const globalStyles = findFirst(
|
|
188
|
+
...expandCandidates(
|
|
189
|
+
'src/app/globals.css', 'app/globals.css',
|
|
190
|
+
'src/styles/globals.css', 'styles/globals.css',
|
|
191
|
+
'src/index.css', 'src/main.css', 'src/styles/index.css'
|
|
192
|
+
)
|
|
193
|
+
);
|
|
194
|
+
|
|
195
|
+
// ---- API docs (widened — OpenAPI, GraphQL, populated dir) --------------
|
|
196
|
+
const apiIndex = findFirst('docs/references/api/index.md', 'docs/api/index.md');
|
|
197
|
+
const apiSchemas = findFirst('docs/references/api/schemas.md', 'docs/api/schemas.md');
|
|
198
|
+
const apiErrors = findFirst('docs/references/errors.md', 'docs/errors.md');
|
|
199
|
+
const openapiSpec = findFirst(
|
|
200
|
+
...expandCandidates(
|
|
201
|
+
'openapi.yaml', 'openapi.yml', 'openapi.json',
|
|
202
|
+
'api/openapi.yaml', 'api/openapi.yml', 'api/openapi.json',
|
|
203
|
+
'docs/openapi.yaml', 'docs/openapi.yml', 'docs/openapi.json'
|
|
204
|
+
)
|
|
205
|
+
);
|
|
206
|
+
const graphqlSchema = findFirst(
|
|
207
|
+
...expandCandidates('schema.graphql', 'schema.gql', 'src/schema.graphql')
|
|
208
|
+
);
|
|
209
|
+
// Many projects ship API docs as one markdown per resource (admin.md, push.md, …)
|
|
210
|
+
// without a `schemas.md` or OpenAPI spec. Count any populated docs/.../api dir as
|
|
211
|
+
// a positive signal.
|
|
212
|
+
const apiDocsDir = findFirst('docs/references/api', 'docs/api');
|
|
213
|
+
const apiDocsDirPopulated = apiDocsDir && countMatches(apiDocsDir, /\.md$/) > 0;
|
|
214
|
+
|
|
215
|
+
// ---- Brand name (multi-fallback) ---------------------------------------
|
|
216
|
+
let brandName = pkg?.name || '';
|
|
217
|
+
// Strip scope from npm name (@org/foo → foo)
|
|
218
|
+
if (brandName.startsWith('@') && brandName.includes('/')) brandName = brandName.split('/')[1];
|
|
219
|
+
if (!brandName) {
|
|
220
|
+
const readme = readSafe('README.md');
|
|
221
|
+
const h1 = readme.split('\n').find((l) => /^#\s+\S/.test(l));
|
|
222
|
+
if (h1) brandName = h1.replace(/^#\s+/, '').trim();
|
|
223
|
+
}
|
|
224
|
+
if (!brandName) brandName = path.basename(cwd);
|
|
225
|
+
|
|
226
|
+
// ---- E2E framework -----------------------------------------------------
|
|
227
|
+
const e2eTestsDir = findFirst(
|
|
228
|
+
...expandCandidates(
|
|
229
|
+
'tests/e2e', 'e2e', 'tests/playwright', 'tests/cypress',
|
|
230
|
+
'cypress/e2e', 'cypress/integration', 'playwright-tests'
|
|
231
|
+
)
|
|
232
|
+
);
|
|
233
|
+
const e2eFramework = (
|
|
234
|
+
exists('playwright.config.ts') || exists('playwright.config.js') ||
|
|
235
|
+
exists('playwright.config.mjs') || hasDep('@playwright/test')
|
|
236
|
+
) ? 'playwright'
|
|
237
|
+
: (exists('cypress.config.ts') || exists('cypress.config.js') || hasDep('cypress')) ? 'cypress'
|
|
238
|
+
: '';
|
|
39
239
|
|
|
40
240
|
const detected = {
|
|
41
241
|
paths: {
|
|
42
|
-
design_system:
|
|
43
|
-
ui_guidelines:
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
api_errors: findFirst('docs/references/errors.md', 'docs/errors.md'),
|
|
51
|
-
components_primitives: findFirst(
|
|
52
|
-
'src/components/ui',
|
|
53
|
-
'app/components/ui',
|
|
54
|
-
'components/ui'
|
|
55
|
-
),
|
|
56
|
-
components_root: findFirst('src/components', 'app/components', 'components'),
|
|
57
|
-
global_styles: findFirst(
|
|
58
|
-
'src/app/globals.css',
|
|
59
|
-
'app/globals.css',
|
|
60
|
-
'src/styles/globals.css',
|
|
61
|
-
'styles/globals.css'
|
|
62
|
-
),
|
|
242
|
+
design_system: designSystemPath,
|
|
243
|
+
ui_guidelines: uiGuidelines,
|
|
244
|
+
api_index: apiIndex || (apiDocsDirPopulated ? path.join(apiDocsDir, 'index.md') : ''),
|
|
245
|
+
api_schemas: apiSchemas || openapiSpec || graphqlSchema,
|
|
246
|
+
api_errors: apiErrors,
|
|
247
|
+
components_primitives: componentsPrimitives,
|
|
248
|
+
components_root: componentsRoot,
|
|
249
|
+
global_styles: globalStyles,
|
|
63
250
|
backlog_dir: exists('backlog') ? 'backlog' : '',
|
|
64
251
|
adrs_dir: countMatches('docs/decisions', /^ADR-.*\.md$/) > 0 ? 'docs/decisions' : '',
|
|
65
252
|
prd_dir: exists('docs/prd') ? 'docs/prd' : '',
|
|
66
253
|
references_dir: exists('docs/references') ? 'docs/references' : '',
|
|
67
254
|
wiki_dir: exists('docs/wiki') ? 'docs/wiki' : '',
|
|
68
|
-
e2e_tests_dir:
|
|
255
|
+
e2e_tests_dir: e2eTestsDir,
|
|
69
256
|
},
|
|
70
257
|
identity: {
|
|
71
|
-
brand_name:
|
|
72
|
-
|
|
258
|
+
brand_name: brandName,
|
|
259
|
+
// Heuristic philosophy hint from stack (skill-readable; can still override).
|
|
260
|
+
design_philosophy: hasShadcn ? 'Minimalist (shadcn/Tailwind)'
|
|
261
|
+
: dsLibInDeps ? `Library-driven (${dsLibInDeps})`
|
|
262
|
+
: '',
|
|
73
263
|
language: 'en',
|
|
74
264
|
audience_segments: [],
|
|
75
265
|
},
|
|
76
266
|
stack: {
|
|
77
267
|
charting: {
|
|
78
|
-
canonical: ['recharts', '@nivo/heatmap', '@nivo/bar', '@nivo/line']
|
|
268
|
+
canonical: ['recharts', '@nivo/heatmap', '@nivo/bar', '@nivo/line', 'chart.js', 'd3', 'visx', '@tremor/react']
|
|
79
269
|
.filter((n) => hasDep(n)),
|
|
80
270
|
forbidden: [],
|
|
81
|
-
wrappers_root: findFirst('src/components/charts', 'app/components/charts'),
|
|
271
|
+
wrappers_root: findFirst(...expandCandidates('src/components/charts', 'app/components/charts', 'components/charts')),
|
|
82
272
|
},
|
|
83
273
|
animation: {
|
|
84
|
-
canonical: ['framer-motion', 'lottie-react', 'gsap', '
|
|
274
|
+
canonical: ['framer-motion', 'motion', 'lottie-react', 'gsap', '@react-spring/web', 'auto-animate']
|
|
85
275
|
.filter((n) => hasDep(n)),
|
|
86
276
|
forbidden: [],
|
|
87
277
|
},
|
|
88
|
-
testing: {
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
278
|
+
testing: { e2e: e2eFramework },
|
|
279
|
+
// New: surface the detected monorepo + DS signals so skills can read them.
|
|
280
|
+
monorepo: isMonorepo ? {
|
|
281
|
+
detected: true,
|
|
282
|
+
roots: monorepoRoots
|
|
283
|
+
} : { detected: false, roots: [] },
|
|
284
|
+
design_system_signals: {
|
|
285
|
+
path: designSystemPath || null,
|
|
286
|
+
storybook: hasStorybook,
|
|
287
|
+
shadcn: hasShadcn,
|
|
288
|
+
tailwind_theme: hasTailwindTheme,
|
|
289
|
+
library: dsLibInDeps || null
|
|
290
|
+
}
|
|
95
291
|
},
|
|
96
292
|
features: {
|
|
97
|
-
has_design_system:
|
|
98
|
-
multi_tenant_theming: null,
|
|
99
|
-
has_api_docs: !!
|
|
293
|
+
has_design_system: hasDesignSystem,
|
|
294
|
+
multi_tenant_theming: null,
|
|
295
|
+
has_api_docs: !!(apiSchemas || openapiSpec || graphqlSchema || apiIndex || apiDocsDirPopulated),
|
|
100
296
|
has_backlog: countMatches('backlog', /\.ya?ml$/) > 0,
|
|
101
297
|
has_adrs: countMatches('docs/decisions', /^ADR-.*\.md$/) > 0,
|
|
102
298
|
has_prd_workflow: exists('docs/prd'),
|
|
@@ -104,6 +300,10 @@ function detect(cwd = process.cwd()) {
|
|
|
104
300
|
},
|
|
105
301
|
};
|
|
106
302
|
|
|
303
|
+
// Reference walkFirst once so the dead-code linter is happy and the helper
|
|
304
|
+
// stays available for future probes (logo files, brand assets, …).
|
|
305
|
+
void walkFirst;
|
|
306
|
+
|
|
107
307
|
return detected;
|
|
108
308
|
}
|
|
109
309
|
|
|
@@ -170,10 +370,18 @@ async function interactivePrompts(merged, detected) {
|
|
|
170
370
|
'Brand / product name',
|
|
171
371
|
merged.identity.brand_name || detected.identity.brand_name
|
|
172
372
|
);
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
)
|
|
373
|
+
// Skip the philosophy prompt if a design system was detected — the DS
|
|
374
|
+
// already encodes the visual direction. The detected hint (e.g. "Minimalist
|
|
375
|
+
// (shadcn/Tailwind)") is kept as metadata for skills that want a label.
|
|
376
|
+
if (!detected.features.has_design_system) {
|
|
377
|
+
merged.identity.design_philosophy = await promptForKey(
|
|
378
|
+
'Design philosophy (e.g. "Neo-Brutalism", "Minimalist") — empty for neutral',
|
|
379
|
+
merged.identity.design_philosophy
|
|
380
|
+
);
|
|
381
|
+
} else {
|
|
382
|
+
UI.info(`Design system detected — skipping philosophy prompt (using "${detected.identity.design_philosophy || 'inherited from design system'}").`);
|
|
383
|
+
merged.identity.design_philosophy = merged.identity.design_philosophy || detected.identity.design_philosophy;
|
|
384
|
+
}
|
|
177
385
|
merged.identity.language = await promptForKey(
|
|
178
386
|
'Primary UI language (BCP-47, e.g. "en", "it")',
|
|
179
387
|
merged.identity.language || 'en'
|
|
@@ -236,14 +444,14 @@ async function interactivePrompts(merged, detected) {
|
|
|
236
444
|
UI.section('Stack (autodetected from package.json — confirm or override)');
|
|
237
445
|
const charting = merged.stack.charting;
|
|
238
446
|
const chartingCanonical = await promptForKey(
|
|
239
|
-
'
|
|
447
|
+
'Approved charting libraries — comma-separated package names (empty if none)',
|
|
240
448
|
(charting.canonical || detected.stack.charting.canonical).join(',')
|
|
241
449
|
);
|
|
242
450
|
charting.canonical = chartingCanonical
|
|
243
451
|
? chartingCanonical.split(',').map((s) => s.trim()).filter(Boolean)
|
|
244
452
|
: [];
|
|
245
453
|
const chartingForbidden = await promptForKey(
|
|
246
|
-
'
|
|
454
|
+
'Forbidden charting libraries — comma-separated (empty if none)',
|
|
247
455
|
(charting.forbidden || []).join(',')
|
|
248
456
|
);
|
|
249
457
|
charting.forbidden = chartingForbidden
|
|
@@ -256,7 +464,7 @@ async function interactivePrompts(merged, detected) {
|
|
|
256
464
|
|
|
257
465
|
const animation = merged.stack.animation;
|
|
258
466
|
const animCanonical = await promptForKey(
|
|
259
|
-
'
|
|
467
|
+
'Approved animation libraries — comma-separated package names (empty if none)',
|
|
260
468
|
(animation.canonical || detected.stack.animation.canonical).join(',')
|
|
261
469
|
);
|
|
262
470
|
animation.canonical = animCanonical
|
|
@@ -321,12 +529,26 @@ async function configure(opts = {}) {
|
|
|
321
529
|
merged = mergePreserving(merged, template);
|
|
322
530
|
merged.version = SCHEMA_VERSION;
|
|
323
531
|
|
|
532
|
+
const dsSignals = detected.stack.design_system_signals || {};
|
|
533
|
+
const dsSignalLabels = [
|
|
534
|
+
dsSignals.path && `path:${dsSignals.path}`,
|
|
535
|
+
dsSignals.storybook && 'storybook',
|
|
536
|
+
dsSignals.shadcn && 'shadcn',
|
|
537
|
+
dsSignals.tailwind_theme && 'tailwind-theme',
|
|
538
|
+
dsSignals.library && `lib:${dsSignals.library}`
|
|
539
|
+
].filter(Boolean).join(', ') || '—';
|
|
540
|
+
const monorepo = detected.stack.monorepo || { detected: false, roots: [] };
|
|
541
|
+
|
|
324
542
|
UI.box('AUTODETECTED', [
|
|
325
|
-
`
|
|
543
|
+
`Brand name: ${detected.identity.brand_name || '—'}`,
|
|
544
|
+
`Design system: ${detected.features.has_design_system ? 'yes' : 'no'}`,
|
|
545
|
+
` └─ signals: ${dsSignalLabels}`,
|
|
326
546
|
`UI guidelines: ${detected.paths.ui_guidelines || '— (none found)'}`,
|
|
327
547
|
`Components UI: ${detected.paths.components_primitives || '— (none found)'}`,
|
|
548
|
+
`Monorepo: ${monorepo.detected ? `yes (${monorepo.roots.length} package(s))` : 'no'}`,
|
|
328
549
|
`Backlog: ${detected.features.has_backlog ? 'yes' : 'no'}`,
|
|
329
550
|
`ADRs: ${detected.features.has_adrs ? 'yes' : 'no'}`,
|
|
551
|
+
`API docs: ${detected.features.has_api_docs ? 'yes' : 'no'}`,
|
|
330
552
|
`Charting libs: ${detected.stack.charting.canonical.join(', ') || '—'}`,
|
|
331
553
|
`Animation libs: ${detected.stack.animation.canonical.join(', ') || '—'}`,
|
|
332
554
|
`E2E framework: ${detected.stack.testing.e2e || '—'}`,
|