peaks-cli 1.0.18 → 1.0.19
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/bin/peaks.js +0 -0
- package/dist/src/services/standards/project-context.d.ts +24 -0
- package/dist/src/services/standards/project-context.js +318 -0
- package/dist/src/services/standards/project-standards-service.js +145 -40
- package/dist/src/shared/version.d.ts +1 -1
- package/dist/src/shared/version.js +1 -1
- package/package.json +1 -1
- package/skills/peaks-qa/SKILL.md +14 -0
- package/skills/peaks-solo/SKILL.md +31 -3
- package/skills/peaks-ui/SKILL.md +9 -1
package/bin/peaks.js
CHANGED
|
File without changes
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export type BuildTool = 'umi' | 'next' | 'vite' | 'rsbuild' | 'rspack' | 'farm' | 'craco' | 'webpack' | 'gulp' | 'angular' | 'custom' | 'unknown';
|
|
2
|
+
export type ComponentLibrary = {
|
|
3
|
+
readonly name: 'antd' | 'antd-pro' | 'mui' | 'shadcn' | 'element-plus' | 'element-ui' | 'arco' | 'tdesign' | 'semi' | 'nextui' | 'chakra' | 'vant' | 'none';
|
|
4
|
+
readonly majorVersion?: string;
|
|
5
|
+
readonly hasProSuite?: boolean;
|
|
6
|
+
};
|
|
7
|
+
export type CssFramework = 'less' | 'sass' | 'tailwind' | 'css-modules' | 'styled-components' | 'emotion' | 'plain-css' | 'unknown';
|
|
8
|
+
export type ProjectContext = {
|
|
9
|
+
readonly hasPackageJson: boolean;
|
|
10
|
+
readonly buildTool: BuildTool;
|
|
11
|
+
readonly buildConfigPath?: string;
|
|
12
|
+
readonly componentLibrary: ComponentLibrary;
|
|
13
|
+
readonly cssFrameworks: CssFramework[];
|
|
14
|
+
readonly cssConflicts: string[];
|
|
15
|
+
readonly stateManagement: string[];
|
|
16
|
+
readonly routing: string[];
|
|
17
|
+
readonly dataFetching: string[];
|
|
18
|
+
readonly legacySignals: string[];
|
|
19
|
+
readonly notableDeps: string[];
|
|
20
|
+
};
|
|
21
|
+
export declare function detectProjectContext(projectRoot: string): ProjectContext;
|
|
22
|
+
export declare function buildToolLabel(tool: BuildTool): string;
|
|
23
|
+
export declare function componentLibraryLabel(lib: ComponentLibrary): string;
|
|
24
|
+
export declare function cssFrameworkLabel(framework: CssFramework): string;
|
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
import { existsSync, readFileSync, readdirSync, statSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
function readPackageJson(projectRoot) {
|
|
4
|
+
const pkgPath = join(projectRoot, 'package.json');
|
|
5
|
+
if (!existsSync(pkgPath))
|
|
6
|
+
return { exists: false, deps: {} };
|
|
7
|
+
try {
|
|
8
|
+
const raw = readFileSync(pkgPath, 'utf8');
|
|
9
|
+
const parsed = JSON.parse(raw);
|
|
10
|
+
return {
|
|
11
|
+
exists: true,
|
|
12
|
+
deps: {
|
|
13
|
+
...(parsed.dependencies ?? {}),
|
|
14
|
+
...(parsed.devDependencies ?? {}),
|
|
15
|
+
...(parsed.peerDependencies ?? {})
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return { exists: true, deps: {} };
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
function configExists(projectRoot, candidates) {
|
|
24
|
+
for (const candidate of candidates) {
|
|
25
|
+
if (existsSync(join(projectRoot, candidate)))
|
|
26
|
+
return candidate;
|
|
27
|
+
}
|
|
28
|
+
return undefined;
|
|
29
|
+
}
|
|
30
|
+
function detectBuildTool(projectRoot) {
|
|
31
|
+
const map = [
|
|
32
|
+
['umi', ['.umirc.ts', '.umirc.js', 'config/config.ts', 'config/config.js']],
|
|
33
|
+
['next', ['next.config.js', 'next.config.ts', 'next.config.mjs']],
|
|
34
|
+
['vite', ['vite.config.ts', 'vite.config.js', 'vite.config.mjs']],
|
|
35
|
+
['rsbuild', ['rsbuild.config.ts', 'rsbuild.config.js']],
|
|
36
|
+
['rspack', ['rspack.config.ts', 'rspack.config.js']],
|
|
37
|
+
['farm', ['farm.config.ts', 'farm.config.js']],
|
|
38
|
+
['craco', ['craco.config.js', 'craco.config.ts']],
|
|
39
|
+
['webpack', ['webpack.config.js', 'webpack.config.ts']],
|
|
40
|
+
['gulp', ['gulpfile.js', 'gulpfile.ts']],
|
|
41
|
+
['angular', ['angular.json']]
|
|
42
|
+
];
|
|
43
|
+
for (const [tool, candidates] of map) {
|
|
44
|
+
const found = configExists(projectRoot, candidates);
|
|
45
|
+
if (found !== undefined)
|
|
46
|
+
return { tool, path: found };
|
|
47
|
+
}
|
|
48
|
+
return { tool: 'unknown' };
|
|
49
|
+
}
|
|
50
|
+
function majorOf(version) {
|
|
51
|
+
if (version === undefined)
|
|
52
|
+
return undefined;
|
|
53
|
+
const cleaned = version.replace(/^[\^~>=<]+/, '').trim();
|
|
54
|
+
const major = cleaned.split('.')[0];
|
|
55
|
+
return major !== undefined && /^\d+$/.test(major) ? major : undefined;
|
|
56
|
+
}
|
|
57
|
+
function detectComponentLibrary(deps) {
|
|
58
|
+
if ('antd' in deps) {
|
|
59
|
+
const major = majorOf(deps['antd']);
|
|
60
|
+
const hasProSuite = '@ant-design/pro-components' in deps || Object.keys(deps).some((d) => d.startsWith('@ant-design/pro-'));
|
|
61
|
+
return hasProSuite
|
|
62
|
+
? { name: 'antd-pro', ...(major !== undefined ? { majorVersion: major } : {}), hasProSuite: true }
|
|
63
|
+
: { name: 'antd', ...(major !== undefined ? { majorVersion: major } : {}) };
|
|
64
|
+
}
|
|
65
|
+
if ('@mui/material' in deps)
|
|
66
|
+
return { name: 'mui', ...(majorOf(deps['@mui/material']) !== undefined ? { majorVersion: majorOf(deps['@mui/material']) } : {}) };
|
|
67
|
+
if ('tailwindcss' in deps && Object.keys(deps).some((d) => d.startsWith('@radix-ui/')))
|
|
68
|
+
return { name: 'shadcn' };
|
|
69
|
+
if ('element-plus' in deps)
|
|
70
|
+
return { name: 'element-plus' };
|
|
71
|
+
if ('element-ui' in deps)
|
|
72
|
+
return { name: 'element-ui' };
|
|
73
|
+
if ('@arco-design/web-react' in deps)
|
|
74
|
+
return { name: 'arco' };
|
|
75
|
+
if ('tdesign-react' in deps || 'tdesign-vue-next' in deps)
|
|
76
|
+
return { name: 'tdesign' };
|
|
77
|
+
if ('@douyinfe/semi-ui' in deps)
|
|
78
|
+
return { name: 'semi' };
|
|
79
|
+
if ('@nextui-org/react' in deps)
|
|
80
|
+
return { name: 'nextui' };
|
|
81
|
+
if ('@chakra-ui/react' in deps)
|
|
82
|
+
return { name: 'chakra' };
|
|
83
|
+
if ('vant' in deps)
|
|
84
|
+
return { name: 'vant' };
|
|
85
|
+
return { name: 'none' };
|
|
86
|
+
}
|
|
87
|
+
function detectCssFrameworks(projectRoot, deps) {
|
|
88
|
+
const frameworks = [];
|
|
89
|
+
if ('tailwindcss' in deps || existsSync(join(projectRoot, 'tailwind.config.js')) || existsSync(join(projectRoot, 'tailwind.config.ts'))) {
|
|
90
|
+
frameworks.push('tailwind');
|
|
91
|
+
}
|
|
92
|
+
if ('less' in deps || 'less-loader' in deps)
|
|
93
|
+
frameworks.push('less');
|
|
94
|
+
if ('sass' in deps || 'node-sass' in deps || 'sass-loader' in deps)
|
|
95
|
+
frameworks.push('sass');
|
|
96
|
+
if ('styled-components' in deps)
|
|
97
|
+
frameworks.push('styled-components');
|
|
98
|
+
if ('@emotion/react' in deps || '@emotion/styled' in deps)
|
|
99
|
+
frameworks.push('emotion');
|
|
100
|
+
// css-modules detection is heuristic (Umi/Next default); skip unless we see *.module.* in src
|
|
101
|
+
return frameworks;
|
|
102
|
+
}
|
|
103
|
+
function detectCssConflicts(library, frameworks) {
|
|
104
|
+
const conflicts = [];
|
|
105
|
+
const hasTailwind = frameworks.includes('tailwind');
|
|
106
|
+
if (hasTailwind && (library.name === 'antd' || library.name === 'antd-pro')) {
|
|
107
|
+
conflicts.push("Tailwind preflight reset can break antd component base styles; set `corePlugins.preflight: false` in tailwind.config or scope Tailwind via `important: '#root'`.");
|
|
108
|
+
}
|
|
109
|
+
if (hasTailwind && library.name === 'mui') {
|
|
110
|
+
conflicts.push('Tailwind preflight overrides MUI base styles; disable Tailwind preflight or scope it away from MUI roots.');
|
|
111
|
+
}
|
|
112
|
+
const cssInJsCount = [frameworks.includes('styled-components'), frameworks.includes('emotion')].filter(Boolean).length;
|
|
113
|
+
if (cssInJsCount >= 2) {
|
|
114
|
+
conflicts.push('Multiple CSS-in-JS libraries detected (styled-components + emotion); pick one and remove the other.');
|
|
115
|
+
}
|
|
116
|
+
return conflicts;
|
|
117
|
+
}
|
|
118
|
+
function detectStateManagement(deps) {
|
|
119
|
+
const map = [
|
|
120
|
+
['zustand', 'zustand'],
|
|
121
|
+
['jotai', 'jotai'],
|
|
122
|
+
['@reduxjs/toolkit', 'Redux Toolkit'],
|
|
123
|
+
['redux', 'redux'],
|
|
124
|
+
['valtio', 'valtio'],
|
|
125
|
+
['mobx', 'mobx'],
|
|
126
|
+
['hox', 'hox']
|
|
127
|
+
];
|
|
128
|
+
return map.filter(([dep]) => dep in deps).map(([, label]) => label);
|
|
129
|
+
}
|
|
130
|
+
function detectRouting(deps) {
|
|
131
|
+
const out = [];
|
|
132
|
+
if ('react-router-dom' in deps)
|
|
133
|
+
out.push('react-router-dom');
|
|
134
|
+
if ('@umijs/max' in deps || '@umijs/preset-react' in deps)
|
|
135
|
+
out.push('Umi router');
|
|
136
|
+
if ('next' in deps)
|
|
137
|
+
out.push('Next.js file-based');
|
|
138
|
+
if ('vue-router' in deps)
|
|
139
|
+
out.push('vue-router');
|
|
140
|
+
return out;
|
|
141
|
+
}
|
|
142
|
+
function detectDataFetching(deps) {
|
|
143
|
+
const out = [];
|
|
144
|
+
if ('@tanstack/react-query' in deps)
|
|
145
|
+
out.push('@tanstack/react-query');
|
|
146
|
+
if ('swr' in deps)
|
|
147
|
+
out.push('swr');
|
|
148
|
+
if ('ahooks' in deps)
|
|
149
|
+
out.push('ahooks (useRequest)');
|
|
150
|
+
if ('umi-request' in deps)
|
|
151
|
+
out.push('umi-request');
|
|
152
|
+
if ('axios' in deps)
|
|
153
|
+
out.push('axios');
|
|
154
|
+
return out;
|
|
155
|
+
}
|
|
156
|
+
function detectLegacySignals(projectRoot, deps) {
|
|
157
|
+
const signals = [];
|
|
158
|
+
if ('moment' in deps)
|
|
159
|
+
signals.push('`moment` in deps — prefer `dayjs` or `date-fns` for new code');
|
|
160
|
+
if (Object.keys(deps).some((d) => d.startsWith('enzyme')))
|
|
161
|
+
signals.push('Enzyme test suite — write new tests with React Testing Library');
|
|
162
|
+
if ('redux-saga' in deps)
|
|
163
|
+
signals.push('redux-saga — keep saga patterns for existing flows; use Redux Toolkit thunks/RTK Query for new code');
|
|
164
|
+
if ('redux-thunk' in deps && !('@reduxjs/toolkit' in deps))
|
|
165
|
+
signals.push('Plain redux-thunk — prefer Redux Toolkit createAsyncThunk for new code');
|
|
166
|
+
if ('jquery' in deps)
|
|
167
|
+
signals.push('jQuery — do not add new jQuery usage');
|
|
168
|
+
if ('backbone' in deps)
|
|
169
|
+
signals.push('Backbone — legacy; do not add new Backbone code');
|
|
170
|
+
if (deps['vue']?.startsWith('2') === true)
|
|
171
|
+
signals.push('Vue 2 — preserve Options API for existing components');
|
|
172
|
+
// Lightweight heuristic for class components and inline styles in src/
|
|
173
|
+
const srcRoot = join(projectRoot, 'src');
|
|
174
|
+
if (existsSync(srcRoot)) {
|
|
175
|
+
const sample = sampleSourceFiles(srcRoot, 80);
|
|
176
|
+
let classComponentHits = 0;
|
|
177
|
+
let inlineStyleHits = 0;
|
|
178
|
+
for (const filePath of sample) {
|
|
179
|
+
try {
|
|
180
|
+
const content = readFileSync(filePath, 'utf8');
|
|
181
|
+
if (/extends\s+(?:React\.)?Component\b/.test(content))
|
|
182
|
+
classComponentHits += 1;
|
|
183
|
+
const matches = content.match(/style=\{\{/g);
|
|
184
|
+
if (matches !== null)
|
|
185
|
+
inlineStyleHits += matches.length;
|
|
186
|
+
}
|
|
187
|
+
catch {
|
|
188
|
+
// ignore unreadable files
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
if (classComponentHits >= 1)
|
|
192
|
+
signals.push(`React class components detected (${classComponentHits}+ files) — keep class style for existing modules, use function components + hooks for new code`);
|
|
193
|
+
if (inlineStyleHits >= 50)
|
|
194
|
+
signals.push(`Inline styles dominant (${inlineStyleHits}+ occurrences) — match existing styling for new code in same modules`);
|
|
195
|
+
}
|
|
196
|
+
return signals;
|
|
197
|
+
}
|
|
198
|
+
function sampleSourceFiles(root, limit) {
|
|
199
|
+
const out = [];
|
|
200
|
+
const queue = [root];
|
|
201
|
+
while (queue.length > 0 && out.length < limit) {
|
|
202
|
+
const dir = queue.shift();
|
|
203
|
+
let entries;
|
|
204
|
+
try {
|
|
205
|
+
entries = readdirSync(dir);
|
|
206
|
+
}
|
|
207
|
+
catch {
|
|
208
|
+
continue;
|
|
209
|
+
}
|
|
210
|
+
for (const entry of entries) {
|
|
211
|
+
if (entry.startsWith('.') || entry === 'node_modules' || entry === 'dist' || entry === 'build')
|
|
212
|
+
continue;
|
|
213
|
+
const full = join(dir, entry);
|
|
214
|
+
let s;
|
|
215
|
+
try {
|
|
216
|
+
s = statSync(full);
|
|
217
|
+
}
|
|
218
|
+
catch {
|
|
219
|
+
continue;
|
|
220
|
+
}
|
|
221
|
+
if (s.isDirectory()) {
|
|
222
|
+
queue.push(full);
|
|
223
|
+
}
|
|
224
|
+
else if (/\.(tsx|jsx)$/.test(entry)) {
|
|
225
|
+
out.push(full);
|
|
226
|
+
if (out.length >= limit)
|
|
227
|
+
break;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
return out;
|
|
232
|
+
}
|
|
233
|
+
function notableDepsList(deps) {
|
|
234
|
+
const interesting = ['monaco-editor', '@monaco-editor/react', 'react-querybuilder', '@dnd-kit/core', 'react-dnd', 'echarts', 'recharts', '@ant-design/charts', 'antd-style', 'lodash', 'lodash-es', 'rxjs', 'socket.io-client'];
|
|
235
|
+
return interesting.filter((d) => d in deps);
|
|
236
|
+
}
|
|
237
|
+
export function detectProjectContext(projectRoot) {
|
|
238
|
+
const { exists, deps } = readPackageJson(projectRoot);
|
|
239
|
+
const { tool, path } = detectBuildTool(projectRoot);
|
|
240
|
+
const componentLibrary = detectComponentLibrary(deps);
|
|
241
|
+
const cssFrameworks = detectCssFrameworks(projectRoot, deps);
|
|
242
|
+
return {
|
|
243
|
+
hasPackageJson: exists,
|
|
244
|
+
buildTool: tool,
|
|
245
|
+
...(path !== undefined ? { buildConfigPath: path } : {}),
|
|
246
|
+
componentLibrary,
|
|
247
|
+
cssFrameworks,
|
|
248
|
+
cssConflicts: detectCssConflicts(componentLibrary, cssFrameworks),
|
|
249
|
+
stateManagement: detectStateManagement(deps),
|
|
250
|
+
routing: detectRouting(deps),
|
|
251
|
+
dataFetching: detectDataFetching(deps),
|
|
252
|
+
legacySignals: detectLegacySignals(projectRoot, deps),
|
|
253
|
+
notableDeps: notableDepsList(deps)
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
export function buildToolLabel(tool) {
|
|
257
|
+
const labels = {
|
|
258
|
+
umi: 'Umi',
|
|
259
|
+
next: 'Next.js',
|
|
260
|
+
vite: 'Vite',
|
|
261
|
+
rsbuild: 'Rsbuild',
|
|
262
|
+
rspack: 'Rspack',
|
|
263
|
+
farm: 'Farm',
|
|
264
|
+
craco: 'CRA + craco',
|
|
265
|
+
webpack: 'Webpack',
|
|
266
|
+
gulp: 'Gulp (legacy)',
|
|
267
|
+
angular: 'Angular',
|
|
268
|
+
custom: 'Custom build pipeline',
|
|
269
|
+
unknown: 'unknown'
|
|
270
|
+
};
|
|
271
|
+
return labels[tool];
|
|
272
|
+
}
|
|
273
|
+
export function componentLibraryLabel(lib) {
|
|
274
|
+
const base = (() => {
|
|
275
|
+
switch (lib.name) {
|
|
276
|
+
case 'antd':
|
|
277
|
+
return 'Ant Design';
|
|
278
|
+
case 'antd-pro':
|
|
279
|
+
return 'Ant Design + Ant Design Pro';
|
|
280
|
+
case 'mui':
|
|
281
|
+
return 'Material UI';
|
|
282
|
+
case 'shadcn':
|
|
283
|
+
return 'shadcn/ui (Tailwind + Radix)';
|
|
284
|
+
case 'element-plus':
|
|
285
|
+
return 'Element Plus';
|
|
286
|
+
case 'element-ui':
|
|
287
|
+
return 'Element UI';
|
|
288
|
+
case 'arco':
|
|
289
|
+
return 'Arco Design';
|
|
290
|
+
case 'tdesign':
|
|
291
|
+
return 'TDesign';
|
|
292
|
+
case 'semi':
|
|
293
|
+
return 'Semi Design';
|
|
294
|
+
case 'nextui':
|
|
295
|
+
return 'NextUI';
|
|
296
|
+
case 'chakra':
|
|
297
|
+
return 'Chakra UI';
|
|
298
|
+
case 'vant':
|
|
299
|
+
return 'Vant (mobile)';
|
|
300
|
+
case 'none':
|
|
301
|
+
return 'no component library detected';
|
|
302
|
+
}
|
|
303
|
+
})();
|
|
304
|
+
return lib.majorVersion !== undefined ? `${base} v${lib.majorVersion}` : base;
|
|
305
|
+
}
|
|
306
|
+
export function cssFrameworkLabel(framework) {
|
|
307
|
+
const labels = {
|
|
308
|
+
less: 'Less',
|
|
309
|
+
sass: 'Sass/SCSS',
|
|
310
|
+
tailwind: 'TailwindCSS',
|
|
311
|
+
'css-modules': 'CSS Modules',
|
|
312
|
+
'styled-components': 'styled-components',
|
|
313
|
+
emotion: 'Emotion',
|
|
314
|
+
'plain-css': 'plain CSS',
|
|
315
|
+
unknown: 'unknown'
|
|
316
|
+
};
|
|
317
|
+
return labels[framework];
|
|
318
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { closeSync, constants, existsSync, lstatSync, mkdirSync, openSync, realpathSync, readFileSync, writeFileSync } from 'node:fs';
|
|
2
2
|
import { dirname, isAbsolute, join, relative, resolve } from 'node:path';
|
|
3
|
+
import { buildToolLabel, componentLibraryLabel, cssFrameworkLabel, detectProjectContext } from './project-context.js';
|
|
3
4
|
const SOURCE = {
|
|
4
5
|
sourceId: 'everything-claude-code',
|
|
5
6
|
url: 'https://github.com/affaan-m/everything-claude-code',
|
|
@@ -82,8 +83,40 @@ function renderHeader(title) {
|
|
|
82
83
|
''
|
|
83
84
|
].join('\n');
|
|
84
85
|
}
|
|
85
|
-
function
|
|
86
|
-
|
|
86
|
+
function renderProjectStackSection(ctx) {
|
|
87
|
+
if (!ctx.hasPackageJson)
|
|
88
|
+
return '';
|
|
89
|
+
const lines = ['## Detected project stack', ''];
|
|
90
|
+
lines.push(`- Build tool: ${buildToolLabel(ctx.buildTool)}${ctx.buildConfigPath !== undefined ? ` (\`${ctx.buildConfigPath}\`)` : ''}`);
|
|
91
|
+
lines.push(`- Component library: ${componentLibraryLabel(ctx.componentLibrary)}`);
|
|
92
|
+
if (ctx.cssFrameworks.length > 0) {
|
|
93
|
+
lines.push(`- CSS: ${ctx.cssFrameworks.map(cssFrameworkLabel).join(', ')}`);
|
|
94
|
+
}
|
|
95
|
+
if (ctx.stateManagement.length > 0)
|
|
96
|
+
lines.push(`- State management: ${ctx.stateManagement.join(', ')}`);
|
|
97
|
+
if (ctx.routing.length > 0)
|
|
98
|
+
lines.push(`- Routing: ${ctx.routing.join(', ')}`);
|
|
99
|
+
if (ctx.dataFetching.length > 0)
|
|
100
|
+
lines.push(`- Data fetching: ${ctx.dataFetching.join(', ')}`);
|
|
101
|
+
if (ctx.notableDeps.length > 0)
|
|
102
|
+
lines.push(`- Notable deps: ${ctx.notableDeps.join(', ')}`);
|
|
103
|
+
lines.push('');
|
|
104
|
+
if (ctx.cssConflicts.length > 0) {
|
|
105
|
+
lines.push('## CSS framework conflicts', '');
|
|
106
|
+
for (const conflict of ctx.cssConflicts)
|
|
107
|
+
lines.push(`- ${conflict}`);
|
|
108
|
+
lines.push('');
|
|
109
|
+
}
|
|
110
|
+
if (ctx.legacySignals.length > 0) {
|
|
111
|
+
lines.push('## Legacy constraints (preserve for new code in the same modules)', '');
|
|
112
|
+
for (const signal of ctx.legacySignals)
|
|
113
|
+
lines.push(`- ${signal}`);
|
|
114
|
+
lines.push('');
|
|
115
|
+
}
|
|
116
|
+
return lines.join('\n');
|
|
117
|
+
}
|
|
118
|
+
function renderClaudeMd(language, ctx) {
|
|
119
|
+
const head = [
|
|
87
120
|
'# Project Instructions',
|
|
88
121
|
'',
|
|
89
122
|
'> 🤖 AI 生成,请审阅',
|
|
@@ -104,39 +137,109 @@ function renderClaudeMd(language) {
|
|
|
104
137
|
'External reference: https://github.com/affaan-m/everything-claude-code is used as a curated reference only. Do not execute or install external content without explicit approval.',
|
|
105
138
|
''
|
|
106
139
|
].join('\n');
|
|
140
|
+
const stack = renderProjectStackSection(ctx);
|
|
141
|
+
return stack === '' ? head : `${head}\n${stack}`;
|
|
142
|
+
}
|
|
143
|
+
function renderCommonCodingStyle(ctx) {
|
|
144
|
+
const baseRules = [
|
|
145
|
+
'- Prefer simple, readable code over clever abstractions.',
|
|
146
|
+
'- Keep functions focused and files cohesive.',
|
|
147
|
+
'- Use immutable updates unless a language-specific convention explicitly favors mutation.',
|
|
148
|
+
'- Validate user input, external data, file paths, and configuration at system boundaries.',
|
|
149
|
+
'- Preserve existing project conventions when they are stricter than this baseline.'
|
|
150
|
+
];
|
|
151
|
+
const stackRules = [];
|
|
152
|
+
const lib = ctx.componentLibrary.name;
|
|
153
|
+
if (lib === 'antd' || lib === 'antd-pro') {
|
|
154
|
+
const major = ctx.componentLibrary.majorVersion ?? '5';
|
|
155
|
+
stackRules.push(`- Use existing antd v${major} components (\`Button\`, \`Form\`, \`Table\`, \`Modal\`, \`Select\`). Never mix antd v3/v4/v5 APIs.`);
|
|
156
|
+
stackRules.push(`- Customize antd via \`theme.token\` / \`ConfigProvider\` / \`className\` / \`styles\`. Do NOT apply TailwindCSS utility classes directly to antd components.`);
|
|
157
|
+
if (ctx.componentLibrary.hasProSuite === true) {
|
|
158
|
+
stackRules.push('- Use `@ant-design/pro-components` (`ProTable`, `ProForm`, `ProLayout`) where the page is already pro-based — do not introduce a parallel non-pro table/form.');
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
if (lib === 'mui')
|
|
162
|
+
stackRules.push('- Style MUI via `sx`, `styled()`, and `theme`. Do NOT apply TailwindCSS utility classes directly to MUI components.');
|
|
163
|
+
if (lib === 'shadcn')
|
|
164
|
+
stackRules.push('- Use existing shadcn component variants and Tailwind utility classes. Do not introduce a competing component library.');
|
|
165
|
+
if (ctx.cssFrameworks.includes('tailwind') && (lib === 'antd' || lib === 'antd-pro' || lib === 'mui')) {
|
|
166
|
+
stackRules.push('- TailwindCSS is for layout/utility only; component-library tokens own component styling.');
|
|
167
|
+
}
|
|
168
|
+
if (ctx.cssFrameworks.includes('less'))
|
|
169
|
+
stackRules.push('- Less variables in `src/theme/*.less` (or equivalent) are the canonical design tokens — extend them, do not hardcode colors/spacing.');
|
|
170
|
+
if (ctx.stateManagement.length > 0)
|
|
171
|
+
stackRules.push(`- Follow the existing state library (${ctx.stateManagement.join(', ')}); do not introduce a competing state library.`);
|
|
172
|
+
if (ctx.dataFetching.length > 0)
|
|
173
|
+
stackRules.push(`- Reuse the existing data-fetching pattern (${ctx.dataFetching.join(', ')}) for new API calls.`);
|
|
174
|
+
for (const signal of ctx.legacySignals)
|
|
175
|
+
stackRules.push(`- ${signal}`);
|
|
176
|
+
const rules = stackRules.length > 0 ? [...baseRules, '', '## Project-specific rules', ...stackRules] : baseRules;
|
|
177
|
+
return `${renderHeader('Common Coding Standards')}${rules.join('\n')}\n`;
|
|
178
|
+
}
|
|
179
|
+
function renderCodeReview(ctx) {
|
|
180
|
+
const baseRules = [
|
|
181
|
+
'- Review diffs for correctness, maintainability, test coverage, and regression risk.',
|
|
182
|
+
'- Treat missing tests for changed behavior as a blocker unless the change is documentation-only.',
|
|
183
|
+
'- Verify code paths that handle filesystem, external APIs, credentials, user input, or generated artifacts.',
|
|
184
|
+
'- peaks-qa must use this guidance as part of code workflow preflight and final verification.'
|
|
185
|
+
];
|
|
186
|
+
const extra = [];
|
|
187
|
+
const lib = ctx.componentLibrary.name;
|
|
188
|
+
if (lib === 'antd' || lib === 'antd-pro') {
|
|
189
|
+
extra.push('- Block PRs that introduce a second component library (MUI/shadcn/Chakra) alongside antd.');
|
|
190
|
+
extra.push('- Block PRs that import antd v3/v4 APIs in this v5 project, or vice versa.');
|
|
191
|
+
}
|
|
192
|
+
if (ctx.cssFrameworks.includes('tailwind') && (lib === 'antd' || lib === 'antd-pro' || lib === 'mui')) {
|
|
193
|
+
extra.push('- Flag Tailwind utility classes applied directly to component-library primitives; require component-library APIs instead.');
|
|
194
|
+
}
|
|
195
|
+
if (ctx.legacySignals.length > 0) {
|
|
196
|
+
extra.push('- Verify new code in legacy modules preserves the existing patterns (see `.claude/rules/common/coding-style.md` "Project-specific rules").');
|
|
197
|
+
}
|
|
198
|
+
const rules = extra.length > 0 ? [...baseRules, '', '## Project-specific review focus', ...extra] : baseRules;
|
|
199
|
+
return `${renderHeader('Code Review Standards')}${rules.join('\n')}\n`;
|
|
200
|
+
}
|
|
201
|
+
function renderSecurity(ctx) {
|
|
202
|
+
const baseRules = [
|
|
203
|
+
'- Never hardcode secrets, API keys, passwords, tokens, or credentials.',
|
|
204
|
+
'- Do not send private code or secrets to external services without explicit user authorization.',
|
|
205
|
+
'- Guard filesystem writes against path traversal, symlink, and junction escapes.',
|
|
206
|
+
'- Require explicit confirmation for destructive actions, external state changes, and credential use.'
|
|
207
|
+
];
|
|
208
|
+
const extra = [];
|
|
209
|
+
if (ctx.buildTool === 'next')
|
|
210
|
+
extra.push('- Validate request body / query / params at every API route boundary (`pages/api/**` or `app/api/**`).');
|
|
211
|
+
if (ctx.dataFetching.length > 0)
|
|
212
|
+
extra.push(`- Sanitize and validate API responses before rendering or persisting (current fetchers: ${ctx.dataFetching.join(', ')}).`);
|
|
213
|
+
if (ctx.notableDeps.includes('monaco-editor') || ctx.notableDeps.includes('@monaco-editor/react')) {
|
|
214
|
+
extra.push('- Monaco editor content is untrusted; never `eval` or `Function`-construct user-authored code without an explicit, reviewed sandbox.');
|
|
215
|
+
}
|
|
216
|
+
const rules = extra.length > 0 ? [...baseRules, '', '## Project-specific security focus', ...extra] : baseRules;
|
|
217
|
+
return `${renderHeader('Security Review Standards')}${rules.join('\n')}\n`;
|
|
107
218
|
}
|
|
108
|
-
function
|
|
109
|
-
return `${renderHeader('Common Coding Standards')}- Prefer simple, readable code over clever abstractions.
|
|
110
|
-
- Keep functions focused and files cohesive.
|
|
111
|
-
- Use immutable updates unless a language-specific convention explicitly favors mutation.
|
|
112
|
-
- Validate user input, external data, file paths, and configuration at system boundaries.
|
|
113
|
-
- Preserve existing project conventions when they are stricter than this baseline.
|
|
114
|
-
`;
|
|
115
|
-
}
|
|
116
|
-
function renderCodeReview() {
|
|
117
|
-
return `${renderHeader('Code Review Standards')}- Review diffs for correctness, maintainability, test coverage, and regression risk.
|
|
118
|
-
- Treat missing tests for changed behavior as a blocker unless the change is documentation-only.
|
|
119
|
-
- Verify code paths that handle filesystem, external APIs, credentials, user input, or generated artifacts.
|
|
120
|
-
- peaks-qa must use this guidance as part of code workflow preflight and final verification.
|
|
121
|
-
`;
|
|
122
|
-
}
|
|
123
|
-
function renderSecurity() {
|
|
124
|
-
return `${renderHeader('Security Review Standards')}- Never hardcode secrets, API keys, passwords, tokens, or credentials.
|
|
125
|
-
- Do not send private code or secrets to external services without explicit user authorization.
|
|
126
|
-
- Guard filesystem writes against path traversal, symlink, and junction escapes.
|
|
127
|
-
- Require explicit confirmation for destructive actions, external state changes, and credential use.
|
|
128
|
-
`;
|
|
129
|
-
}
|
|
130
|
-
function renderLanguageCodingStyle(language) {
|
|
219
|
+
function renderLanguageCodingStyle(language, ctx) {
|
|
131
220
|
const languageName = language === 'generic' ? 'Generic' : language[0].toUpperCase() + language.slice(1);
|
|
132
221
|
const typeSafetyRule = language === 'typescript' || language === 'javascript'
|
|
133
222
|
? '- Do not add new `any` types; use explicit domain types, generics, or `unknown` with narrowing.\n'
|
|
134
223
|
: '';
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
224
|
+
const baseRules = [
|
|
225
|
+
`- Apply project-local conventions before generic ${language} guidance.`,
|
|
226
|
+
`- Keep public APIs typed or documented according to ${language} ecosystem norms.`,
|
|
227
|
+
typeSafetyRule.trim() !== '' ? typeSafetyRule.trim() : null,
|
|
228
|
+
'- Prefer standard tooling and existing project scripts for formatting, linting, tests, and coverage.',
|
|
229
|
+
`- peaks-rd must check this file before planning code changes in ${language} projects.`
|
|
230
|
+
].filter((line) => line !== null);
|
|
231
|
+
const extra = [];
|
|
232
|
+
if ((language === 'typescript' || language === 'javascript') && (ctx.componentLibrary.name === 'antd' || ctx.componentLibrary.name === 'antd-pro')) {
|
|
233
|
+
extra.push('- Type form values, table records, and API responses with named interfaces; do not rely on `Form.useForm()` inference for shared shapes.');
|
|
234
|
+
}
|
|
235
|
+
if ((language === 'typescript' || language === 'javascript') && ctx.dataFetching.includes('@tanstack/react-query')) {
|
|
236
|
+
extra.push('- Declare query/mutation generics (`useQuery<TData, TError>`) so consumers get typed data.');
|
|
237
|
+
}
|
|
238
|
+
if ((language === 'typescript' || language === 'javascript') && ctx.buildTool === 'umi') {
|
|
239
|
+
extra.push('- Use the project\'s existing service-layer pattern (`src/services/**`) for API calls; do not hand-roll `fetch` in components.');
|
|
240
|
+
}
|
|
241
|
+
const rules = extra.length > 0 ? [...baseRules, '', '## Project-specific rules', ...extra] : baseRules;
|
|
242
|
+
return `${renderHeader(`${languageName} Coding Standards`)}${rules.join('\n')}\n`;
|
|
140
243
|
}
|
|
141
244
|
function renderManagedClaudeMdIndex(language) {
|
|
142
245
|
return [
|
|
@@ -189,19 +292,19 @@ function writeMissingStandardsRules(plan, writes = getPendingStandardsRuleWrites
|
|
|
189
292
|
}
|
|
190
293
|
return writtenFiles;
|
|
191
294
|
}
|
|
192
|
-
function createTemplates(language) {
|
|
295
|
+
function createTemplates(language, ctx) {
|
|
193
296
|
return [
|
|
194
|
-
{ relativePath: 'CLAUDE.md', content: renderClaudeMd(language) },
|
|
195
|
-
{ relativePath: '.claude/rules/common/code-review.md', content: renderCodeReview() },
|
|
196
|
-
{ relativePath: '.claude/rules/common/coding-style.md', content: renderCommonCodingStyle() },
|
|
197
|
-
{ relativePath: '.claude/rules/common/security.md', content: renderSecurity() },
|
|
198
|
-
{ relativePath: `.claude/rules/${language}/coding-style.md`, content: renderLanguageCodingStyle(language) }
|
|
297
|
+
{ relativePath: 'CLAUDE.md', content: renderClaudeMd(language, ctx) },
|
|
298
|
+
{ relativePath: '.claude/rules/common/code-review.md', content: renderCodeReview(ctx) },
|
|
299
|
+
{ relativePath: '.claude/rules/common/coding-style.md', content: renderCommonCodingStyle(ctx) },
|
|
300
|
+
{ relativePath: '.claude/rules/common/security.md', content: renderSecurity(ctx) },
|
|
301
|
+
{ relativePath: `.claude/rules/${language}/coding-style.md`, content: renderLanguageCodingStyle(language, ctx) }
|
|
199
302
|
];
|
|
200
303
|
}
|
|
201
304
|
function createManagedClaudeBlock(language) {
|
|
202
305
|
return renderManagedClaudeMdIndex(language);
|
|
203
306
|
}
|
|
204
|
-
function buildClaudeUpdate(projectRoot, language) {
|
|
307
|
+
function buildClaudeUpdate(projectRoot, language, ctx) {
|
|
205
308
|
const filePath = resolve(projectRoot, 'CLAUDE.md');
|
|
206
309
|
assertSafeClaudeMdPath(filePath, projectRoot);
|
|
207
310
|
const existingContent = readFileIfExists(filePath);
|
|
@@ -211,7 +314,7 @@ function buildClaudeUpdate(projectRoot, language) {
|
|
|
211
314
|
relativePath: 'CLAUDE.md',
|
|
212
315
|
filePath,
|
|
213
316
|
status: 'planned',
|
|
214
|
-
content: `${renderClaudeMd(language).trimEnd()}\n\n${managedBlock}`,
|
|
317
|
+
content: `${renderClaudeMd(language, ctx).trimEnd()}\n\n${managedBlock}`,
|
|
215
318
|
appendBlock: '',
|
|
216
319
|
reviewSuggestions: []
|
|
217
320
|
};
|
|
@@ -281,7 +384,8 @@ export function createProjectStandardsInitPlan(options) {
|
|
|
281
384
|
const projectRoot = normalizeRoot(options.projectRoot);
|
|
282
385
|
assertSafeStandardsRoot(projectRoot);
|
|
283
386
|
const language = options.language === undefined ? detectLanguage(projectRoot) : parseLanguage(options.language);
|
|
284
|
-
const
|
|
387
|
+
const ctx = detectProjectContext(projectRoot);
|
|
388
|
+
const plannedWrites = createTemplates(language, ctx).map((template) => buildWrite(projectRoot, template));
|
|
285
389
|
return {
|
|
286
390
|
apply: options.apply ?? false,
|
|
287
391
|
projectRoot,
|
|
@@ -293,7 +397,8 @@ export function createProjectStandardsInitPlan(options) {
|
|
|
293
397
|
}
|
|
294
398
|
export function createProjectStandardsUpdatePlan(options) {
|
|
295
399
|
const basePlan = createProjectStandardsInitPlan(options);
|
|
296
|
-
const
|
|
400
|
+
const ctx = detectProjectContext(basePlan.projectRoot);
|
|
401
|
+
const claudeMd = buildClaudeUpdate(basePlan.projectRoot, basePlan.language, ctx);
|
|
297
402
|
return {
|
|
298
403
|
...basePlan,
|
|
299
404
|
claudeMd
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const CLI_VERSION = "1.0.
|
|
1
|
+
export declare const CLI_VERSION = "1.0.19";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export const CLI_VERSION = "1.0.
|
|
1
|
+
export const CLI_VERSION = "1.0.19";
|
package/package.json
CHANGED
package/skills/peaks-qa/SKILL.md
CHANGED
|
@@ -85,6 +85,16 @@ peaks openspec validate <change-id> --project <repo> --prefer-external --json
|
|
|
85
85
|
peaks mcp list --json
|
|
86
86
|
peaks mcp plan --capability playwright-mcp.browser-validation --json
|
|
87
87
|
peaks mcp apply --capability playwright-mcp.browser-validation --yes --json
|
|
88
|
+
# DEV-SERVER REQUIREMENT (BLOCKING): a running dev server is REQUIRED for browser E2E.
|
|
89
|
+
# Start the dev server (npm run dev / pnpm dev / umi dev / etc) and capture the actual
|
|
90
|
+
# advertised URL from its stdout (do NOT hard-code localhost:8000). If the dev server
|
|
91
|
+
# fails to start, hangs, or times out (e.g. tailwindcss/plugin slowness, port conflict,
|
|
92
|
+
# missing env), this is a BLOCKER — NOT a reason to skip browser E2E. You MUST:
|
|
93
|
+
# 1. Record the failure and root cause in qa/test-reports/<rid>.md;
|
|
94
|
+
# 2. Return verdict=blocked (or return-to-rd if the root cause is implementation-related);
|
|
95
|
+
# 3. NEVER substitute a production build (`umi build` / `vite build` / `next build`) for
|
|
96
|
+
# browser E2E. A successful production build proves compilation, not runtime behavior,
|
|
97
|
+
# and does NOT satisfy Gate D. Treating prod build as a fallback is a workflow violation.
|
|
88
98
|
# Playwright MCP MUST simulate real user operations — not just take static screenshots.
|
|
89
99
|
# The minimum interaction sequence for every frontend page/flow:
|
|
90
100
|
# mcp__playwright__browser_navigate → URL (after allow-list), launches headed browser
|
|
@@ -221,6 +231,10 @@ ls .peaks/<id>/qa/screenshots/*.png 2>&1
|
|
|
221
231
|
# Expected: one or more .png files
|
|
222
232
|
# "No such file" → BLOCKED. Playwright MCP was not used or screenshots not saved.
|
|
223
233
|
# Screenshots, logs, manual steps, or other tools must NOT substitute for this gate.
|
|
234
|
+
# A successful production build (`umi build` / `vite build` / `next build` exit 0) does
|
|
235
|
+
# NOT substitute for this gate. Compilation success ≠ runtime behavior.
|
|
236
|
+
# If the dev server cannot start, verdict MUST be `blocked` (or `return-to-rd`),
|
|
237
|
+
# NOT `pass`. Record the dev-server failure root cause in the test report.
|
|
224
238
|
# Re-run frontend browser validation (step 7 in runbook) and save screenshots.
|
|
225
239
|
```
|
|
226
240
|
```bash
|
|
@@ -464,6 +464,7 @@ ls .peaks/<id>/qa/requests/<rid>.md
|
|
|
464
464
|
find .peaks/<id>/ -type f | sort
|
|
465
465
|
# Verify: files from gates A-D all appear in this list.
|
|
466
466
|
# Any mandatory file missing → NOT complete. Do not emit TXT.
|
|
467
|
+
# Gate G (CLAUDE.md + .claude/rules/**) must ALSO pass before TXT is emitted.
|
|
467
468
|
```
|
|
468
469
|
|
|
469
470
|
**Gate F — Root pollution check (BLOCKING before completion):**
|
|
@@ -483,6 +484,25 @@ find . -maxdepth 1 -name "*.png" -o -name "*.jpg" -o -name "qa-*.js" -o -name "m
|
|
|
483
484
|
# Legitimate project files (e.g. favicon.png) are fine — only move Peaks artifacts.
|
|
484
485
|
```
|
|
485
486
|
|
|
487
|
+
**Gate G — Project standards present (BLOCKING before workflow completion):**
|
|
488
|
+
```bash
|
|
489
|
+
# After `peaks standards init/update --apply`, verify the files actually landed
|
|
490
|
+
# at the project root. The CLAUDE.md and rules files are required so that
|
|
491
|
+
# subsequent peaks-rd / peaks-qa / peaks-solo runs perform the project-local
|
|
492
|
+
# preflight described in CLAUDE.md (read coding-style.md, code-review.md, security.md).
|
|
493
|
+
ls <repo>/CLAUDE.md
|
|
494
|
+
# "No such file" → BLOCKED. Run `peaks standards init --project <repo> --apply --json`
|
|
495
|
+
# (first time) or `peaks standards update --project <repo> --apply --json` (existing).
|
|
496
|
+
ls <repo>/.claude/rules/common/coding-style.md \
|
|
497
|
+
<repo>/.claude/rules/common/code-review.md \
|
|
498
|
+
<repo>/.claude/rules/common/security.md
|
|
499
|
+
# Any "No such file" → BLOCKED. The standards apply step did not complete; re-run
|
|
500
|
+
# standards init/update with --apply and re-verify.
|
|
501
|
+
# Skipping Gate G (e.g. because the user did not explicitly authorize writes) is
|
|
502
|
+
# only acceptable in `assisted`/`strict` modes where the user actively declined; in
|
|
503
|
+
# `full-auto`/`swarm` the absence of these files is a workflow violation.
|
|
504
|
+
```
|
|
505
|
+
|
|
486
506
|
|
|
487
507
|
## Swarm parallel phase
|
|
488
508
|
|
|
@@ -558,9 +578,17 @@ peaks scan archetype --project <repo> --json
|
|
|
558
578
|
peaks scan existing-system --project <repo> --json
|
|
559
579
|
# → copy tokens, sources, conventions, inconsistencies into .peaks/<session-id>/system/existing-system.md (Gate A.5)
|
|
560
580
|
|
|
561
|
-
# 1. Standards preflight
|
|
562
|
-
|
|
581
|
+
# 1. Standards preflight + apply
|
|
582
|
+
# Run dry-run first to inspect deltas, then APPLY. In full-auto and swarm modes,
|
|
583
|
+
# --apply is the default — Standards files (CLAUDE.md, .claude/rules/**) live INSIDE
|
|
584
|
+
# the target project and are required for downstream skill preflight, so producing
|
|
585
|
+
# them is part of completing the workflow. Assisted/Strict modes pause for [CONFIRM]
|
|
586
|
+
# between dry-run and apply.
|
|
587
|
+
peaks standards init --project <repo> --dry-run --json
|
|
563
588
|
# or: peaks standards update --project <repo> --dry-run --json
|
|
589
|
+
peaks standards init --project <repo> --apply --json
|
|
590
|
+
# or: peaks standards update --project <repo> --apply --json
|
|
591
|
+
# After apply, verify the files actually exist on disk (see Gate G).
|
|
564
592
|
|
|
565
593
|
# 2. PRD (Assisted/Strict: [CONFIRM] before confirmed-by-user)
|
|
566
594
|
# Classify the request type from the PRD: feature | bugfix | refactor | docs | config | chore
|
|
@@ -648,7 +676,7 @@ Before orchestrating an end-to-end code repository workflow, gather the project
|
|
|
648
676
|
- `peaks standards init --project <path> --dry-run`
|
|
649
677
|
- `peaks standards update --project <path> --dry-run`
|
|
650
678
|
|
|
651
|
-
Use `standards init` for first-time creation and `standards update` for existing `CLAUDE.md` append/review behavior.
|
|
679
|
+
Use `standards init` for first-time creation and `standards update` for existing `CLAUDE.md` append/review behavior. In `full-auto` and `swarm` profiles, `--apply` runs automatically after `--dry-run` succeeds — these files live inside the target project, are required for downstream skill preflight, and producing them is part of finishing the workflow (Gate G enforces this). `assisted` and `strict` profiles pause for explicit user confirmation between dry-run and apply.
|
|
652
680
|
|
|
653
681
|
**CRITICAL — Standards must reflect the project scan.** When generating or updating `CLAUDE.md`, the content must reference concrete findings from `.peaks/<id>/rd/project-scan.md`: the detected component library (e.g. "This project uses antd 5.x"), CSS solution (e.g. "Uses Less via Umi"), build tool, state management, and routing. Never emit a generic template that says "read .claude/rules/..." without naming the actual project stack. If the project-scan has not been run yet, run it before standards init/update.
|
|
654
682
|
|
package/skills/peaks-ui/SKILL.md
CHANGED
|
@@ -124,6 +124,14 @@ You cannot declare a phase complete from memory. Each gate below is a `ls` comma
|
|
|
124
124
|
ls .peaks/<id>/ui/design-draft.md
|
|
125
125
|
# Expected output: .peaks/<id>/ui/design-draft.md
|
|
126
126
|
# "No such file" → STOP, write the design-draft first. Do not proceed to handoff.
|
|
127
|
+
|
|
128
|
+
# Gate A also requires an ASCII wireframe section with at least one fenced block.
|
|
129
|
+
grep -c "^## Layout (ASCII wireframe)" .peaks/<id>/ui/design-draft.md
|
|
130
|
+
# Expected: >= 1. Zero → BLOCKED. The mandatory ASCII wireframe section is missing.
|
|
131
|
+
grep -c '^```' .peaks/<id>/ui/design-draft.md
|
|
132
|
+
# Expected: >= 2 (one or more fenced code blocks for ASCII wireframes).
|
|
133
|
+
# Zero → BLOCKED. Prose-only layout description is not acceptable; add ASCII wireframes
|
|
134
|
+
# for the main page and every meaningful modal/drawer/state.
|
|
127
135
|
```
|
|
128
136
|
|
|
129
137
|
**Gate B — Before handoff to RD:**
|
|
@@ -238,7 +246,7 @@ Every UI invocation that touches user-visible behavior MUST produce a design-dra
|
|
|
238
246
|
1. **Component library** — detected library name, version, design-system packages (e.g. `antd 5.x` + `@ant-design/pro-components`). Verify by checking `package.json` and source imports — never assume.
|
|
239
247
|
2. **Style direction** — named visual direction (editorial, bento, Swiss, glass, luxury, product-system, etc.) with 1-2 sentence rationale
|
|
240
248
|
3. **Design dials** — variance (conservative/moderate/bold), motion intensity (minimal/medium/rich), visual density (sparse/comfortable/dense), typography pair (heading + body), palette (primary, surface, text, accent tokens)
|
|
241
|
-
4. **Page/component structure** —
|
|
249
|
+
4. **Page/component structure** — MANDATORY ASCII wireframe (not prose description) under a dedicated `## Layout (ASCII wireframe)` section, component tree (which library components used where), hierarchy (primary/secondary/tertiary content zones). Every meaningful surface (main page, each modal/drawer, key state) must have its own fenced ASCII block. Prose-only layout descriptions do NOT satisfy this section and Gate A will reject the design-draft.
|
|
242
250
|
5. **Component specifications** — for each new or modified component: which library component it uses, which props/tokens customize it, states (loading, empty, error, hover, focus, active, disabled), responsive behavior
|
|
243
251
|
6. **CSS framework rules** — which CSS approach to use (component-library tokens, CSS Modules, TailwindCSS utilities if already present), explicit prohibition against mixing conflicting frameworks
|
|
244
252
|
7. **States and edge cases** — loading skeleton, empty state, error state, edge-case handling for each user-visible surface
|