peaks-cli 1.0.18 → 1.0.20
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/artifacts/artifact-prerequisites.d.ts +2 -0
- package/dist/src/services/artifacts/artifact-prerequisites.js +24 -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 +17 -3
- package/skills/peaks-rd/SKILL.md +19 -1
- package/skills/peaks-solo/SKILL.md +39 -5
- package/skills/peaks-ui/SKILL.md +11 -3
package/bin/peaks.js
CHANGED
|
File without changes
|
|
@@ -8,6 +8,8 @@ export type ArtifactPrerequisite = {
|
|
|
8
8
|
relativePath: string;
|
|
9
9
|
/** Human-readable description of what this artifact represents. */
|
|
10
10
|
description: string;
|
|
11
|
+
/** Optional content markers — when set, the file must contain ALL of these (case-insensitive substring). */
|
|
12
|
+
mustContain?: ReadonlyArray<string>;
|
|
11
13
|
};
|
|
12
14
|
export type PrerequisiteCheckResult = {
|
|
13
15
|
ok: boolean;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { join } from 'node:path';
|
|
2
|
+
import { readFile } from 'node:fs/promises';
|
|
2
3
|
import { pathExists } from '../../shared/fs.js';
|
|
3
4
|
export const VALID_REQUEST_TYPES = [
|
|
4
5
|
'feature',
|
|
@@ -21,7 +22,16 @@ const TEST_CASES = { relativePath: 'qa/test-cases/<rid>.md', description: 'Gener
|
|
|
21
22
|
const TEST_REPORT = { relativePath: 'qa/test-reports/<rid>.md', description: 'Test execution report with actual pass/fail/coverage results' };
|
|
22
23
|
const SECURITY_FINDINGS = { relativePath: 'qa/security-findings.md', description: 'Security test findings (record "no findings" inside if truly clean)' };
|
|
23
24
|
const PERFORMANCE_FINDINGS = { relativePath: 'qa/performance-findings.md', description: 'Performance test findings (record baseline/after numbers or explicit "not applicable" rationale)' };
|
|
25
|
+
// PRD content prereq: ensures the PRD artifact has actual scope/acceptance content
|
|
26
|
+
// before handoff to RD/UI/QA. The SKILL says "Handoff to RD/UI/QA is blocked while
|
|
27
|
+
// the artifact is missing or in `draft` state" — this gives that claim a CLI gate.
|
|
28
|
+
const PRD_CONTENT = {
|
|
29
|
+
relativePath: 'prd/requests/<rid>.md',
|
|
30
|
+
description: 'PRD artifact must contain Goal and Acceptance criteria sections before handoff',
|
|
31
|
+
mustContain: ['## Goals', '## Acceptance']
|
|
32
|
+
};
|
|
24
33
|
const FEATURE_TABLE = {
|
|
34
|
+
'prd:handed-off': [PRD_CONTENT],
|
|
25
35
|
'rd:implemented': [TECH_DOC],
|
|
26
36
|
'rd:qa-handoff': [TECH_DOC, CODE_REVIEW, SECURITY_REVIEW],
|
|
27
37
|
'qa:running': [TEST_CASES],
|
|
@@ -30,6 +40,7 @@ const FEATURE_TABLE = {
|
|
|
30
40
|
// Bugfix: lighter planning artifact (bug-analysis instead of tech-doc), still requires code review + security review + regression test.
|
|
31
41
|
// Performance findings not mandatory for non-perf bugs (use --allow-incomplete --reason if a perf bug requires it).
|
|
32
42
|
const BUGFIX_TABLE = {
|
|
43
|
+
'prd:handed-off': [PRD_CONTENT],
|
|
33
44
|
'rd:implemented': [BUG_ANALYSIS],
|
|
34
45
|
'rd:qa-handoff': [BUG_ANALYSIS, CODE_REVIEW, SECURITY_REVIEW],
|
|
35
46
|
'qa:running': [TEST_CASES],
|
|
@@ -41,6 +52,7 @@ const REFACTOR_TABLE = FEATURE_TABLE;
|
|
|
41
52
|
const NO_GATES = {};
|
|
42
53
|
// Config: security review is the only mandatory check (config changes can break auth, CORS, CSP, secrets handling).
|
|
43
54
|
const CONFIG_TABLE = {
|
|
55
|
+
'prd:handed-off': [PRD_CONTENT],
|
|
44
56
|
'rd:qa-handoff': [SECURITY_REVIEW],
|
|
45
57
|
'qa:verdict-issued': [SECURITY_FINDINGS]
|
|
46
58
|
};
|
|
@@ -71,6 +83,18 @@ export async function checkPrerequisites(options) {
|
|
|
71
83
|
const absolute = join(sessionRoot, relative);
|
|
72
84
|
if (!(await pathExists(absolute))) {
|
|
73
85
|
missing.push({ path: relative, description: prerequisite.description });
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
if (prerequisite.mustContain && prerequisite.mustContain.length > 0) {
|
|
89
|
+
const body = await readFile(absolute, 'utf8');
|
|
90
|
+
const lowered = body.toLowerCase();
|
|
91
|
+
const missingMarkers = prerequisite.mustContain.filter((marker) => !lowered.includes(marker.toLowerCase()));
|
|
92
|
+
if (missingMarkers.length > 0) {
|
|
93
|
+
missing.push({
|
|
94
|
+
path: relative,
|
|
95
|
+
description: `${prerequisite.description} — missing section(s): ${missingMarkers.join(', ')}`
|
|
96
|
+
});
|
|
97
|
+
}
|
|
74
98
|
}
|
|
75
99
|
}
|
|
76
100
|
return { ok: missing.length === 0, missing };
|
|
@@ -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.20";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export const CLI_VERSION = "1.0.
|
|
1
|
+
export const CLI_VERSION = "1.0.20";
|
package/package.json
CHANGED
package/skills/peaks-qa/SKILL.md
CHANGED
|
@@ -34,9 +34,9 @@ Every QA invocation — feature, bug, refactor, clarification — must write **t
|
|
|
34
34
|
|
|
35
35
|
| # | File | Path | Reader | Content |
|
|
36
36
|
|---|------|------|--------|---------|
|
|
37
|
-
| 1 | Test cases | `.peaks/<id>/qa/test-cases/<
|
|
38
|
-
| 2 | Test report | `.peaks/<id>/qa/test-reports/<
|
|
39
|
-
| 3 | Request artifact | `.peaks/<id>/qa/requests/<
|
|
37
|
+
| 1 | Test cases | `.peaks/<session-id>/qa/test-cases/<request-id>.md` | RD (before impl), QA | Generated test scenarios with status |
|
|
38
|
+
| 2 | Test report | `.peaks/<session-id>/qa/test-reports/<request-id>.md` | QA, SC, Solo | Summary, coverage%, security, perf, risks |
|
|
39
|
+
| 3 | Request artifact | `.peaks/<session-id>/qa/requests/<request-id>.md` | Solo, RD↔QA loop | Verdict, boundary check, links to #1 and #2 |
|
|
40
40
|
|
|
41
41
|
Concrete template and rules: `references/artifact-per-request.md`.
|
|
42
42
|
|
|
@@ -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
|
package/skills/peaks-rd/SKILL.md
CHANGED
|
@@ -442,11 +442,29 @@ Before RD work stops, finishes, blocks, or hands off to another role, emit a sho
|
|
|
442
442
|
|
|
443
443
|
**Matt Pocock skills** (`diagnose`, `triage`, `tdd`, `improve-codebase-architecture`, `prototype`): Engineering references only. Inspect before applying; Peaks RD gates remain authoritative.
|
|
444
444
|
|
|
445
|
+
## Matt Pocock skills integration
|
|
446
|
+
|
|
447
|
+
Engineering methods from `mattpocock/skills` can inform RD work but never replace Peaks gates. Inspect upstream skill content before applying any method.
|
|
448
|
+
|
|
449
|
+
- `diagnose` — root-cause investigation before fixing
|
|
450
|
+
- `triage` — prioritize bug surface area
|
|
451
|
+
- `tdd` — drive implementation from failing tests
|
|
452
|
+
- `improve-codebase-architecture` — opportunistic refactor framing
|
|
453
|
+
- `prototype` — throwaway exploration before committing to a slice
|
|
454
|
+
|
|
455
|
+
These are references only; Peaks RD gates remain authoritative for handoff, acceptance, and slice closure.
|
|
456
|
+
|
|
445
457
|
**Understand Anything**: Consume via `peaks understand status/show --json`. Fall back to `peaks codegraph context` or local project scan when absent.
|
|
446
458
|
|
|
447
459
|
**Codegraph**: Optional local analysis via `peaks codegraph context/affected`. Output as untrusted supporting evidence; never commit `.codegraph/` artifacts.
|
|
448
460
|
|
|
449
|
-
|
|
461
|
+
## Codegraph project analysis
|
|
462
|
+
|
|
463
|
+
RD may use `peaks codegraph affected --project <path> <changed-files...> --json` as local project-analysis evidence to inform red-line scope boundaries before writing tech-doc or starting implementation. Treat the output as untrusted supporting evidence — verify against the actual code before relying on it.
|
|
464
|
+
|
|
465
|
+
Do not run upstream installer flows, mutate agent settings, or commit `.codegraph/` artifacts. Peaks RD gates remain authoritative for handoff and acceptance.
|
|
466
|
+
|
|
467
|
+
**Other external resources** (Context7, SearchCode, everything-claude-code, GitNexus, etc.): Use `peaks capabilities --source access-repo/mcp-server --json` for capability discovery before recommending. References only — do not execute upstream installers, do not install upstream resources, do not persist sensitive examples. Peaks RD gates remain authoritative.
|
|
450
468
|
|
|
451
469
|
**OpenSpec and MCP CLI**: Route through Peaks CLI (`peaks openspec show/to-rd/render`, `peaks mcp list/plan/apply/call`). Do not hand-edit `openspec/changes/**` or `~/.claude/settings.json`. Recipes: `references/openspec-mcp-cli.md`.
|
|
452
470
|
|
|
@@ -71,7 +71,7 @@ Use the Peaks CLI for runtime side effects.
|
|
|
71
71
|
|
|
72
72
|
Map gstack stages to Peaks role artifacts; preserve Peaks confirmation gates. Do not delegate orchestration to gstack commands.
|
|
73
73
|
|
|
74
|
-
For frontend workflows, RD and QA must use Playwright MCP for real browser E2E (`peaks mcp plan/apply --capability playwright-mcp.browser-validation --yes`). Chrome DevTools MCP is a secondary CDP surface only. Sanitize browser artifacts before retention (no login URLs, cookies, tokens, PII). See `references/browser-workflow.md`.
|
|
74
|
+
For frontend workflows, RD and QA must use Playwright MCP (`mcp__playwright__` tool namespace) for real browser E2E (`peaks mcp plan/apply --capability playwright-mcp.browser-validation --yes`). Chrome DevTools MCP is a secondary CDP surface only. Sanitize browser artifacts before retention (no login URLs, cookies, tokens, PII). See `references/browser-workflow.md`.
|
|
75
75
|
|
|
76
76
|
## Local intermediate artifact workspace (MANDATORY)
|
|
77
77
|
|
|
@@ -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
|
|
|
@@ -703,7 +731,13 @@ Use Peaks TXT for the compact handoff capsule: mode, validated decisions, artifa
|
|
|
703
731
|
|
|
704
732
|
**Codegraph**: Optional project-analysis before RD handoff. Use `peaks codegraph affected --project <path> <changed-files...> --json` for regression-surface hints. Output as untrusted supporting evidence only; never commit `.codegraph/` artifacts.
|
|
705
733
|
|
|
706
|
-
|
|
734
|
+
## Codegraph orchestration context
|
|
735
|
+
|
|
736
|
+
Solo treats `peaks codegraph affected --project <path> <changed-files...> --json` as an optional project-analysis enhancement that informs the role handoff between PRD, RD, and QA. The output is untrusted supporting evidence — Solo must not treat codegraph output as approval for scope, design, or QA verdict.
|
|
737
|
+
|
|
738
|
+
Do not run upstream installer flows, mutate agent settings, or commit `.codegraph/` artifacts into git. Peaks gates remain authoritative; codegraph context is a hint, never a substitute for role-skill output.
|
|
739
|
+
|
|
740
|
+
**External skills**: All external skill references (`mattpocock/skills`, `awesome-design-md`, `taste-skill`, `shadcn/ui`, `Chrome DevTools MCP`, `Figma Context MCP`, `Context7`, etc.) follow the three-stage pattern: capability discovery via `peaks capabilities` before naming, references only (no execute/install/persist), Peaks CLI for all side effects. Do not execute upstream installers, do not install upstream resources, do not persist sensitive examples — Peaks gates remain authoritative. External skills inform, they do not approve.
|
|
707
741
|
|
|
708
742
|
**OpenSpec lifecycle**: `render → validate → show → to-rd → validate → archive`. Solo's default runbook handles the exit gate (validate → archive after QA pass). Entry-gate validation (to-rd before slicing) is available when `openspec/` exists pre-workflow; Solo delegates it to `peaks-rd` during implementation.
|
|
709
743
|
|
package/skills/peaks-ui/SKILL.md
CHANGED
|
@@ -34,8 +34,8 @@ Every UI invocation that touches user-visible behavior — including bug fixes t
|
|
|
34
34
|
|
|
35
35
|
| # | File | Purpose |
|
|
36
36
|
|---|------|---------|
|
|
37
|
-
| 1 | `.peaks/<id>/ui/design-draft.md` | Design direction, dials, component specs, anti-template checklist |
|
|
38
|
-
| 2 | `.peaks/<id>/ui/requests/<
|
|
37
|
+
| 1 | `.peaks/<session-id>/ui/design-draft.md` | Design direction, dials, component specs, anti-template checklist |
|
|
38
|
+
| 2 | `.peaks/<session-id>/ui/requests/<request-id>.md` | Links to #1, records visual direction decisions, regression seeds |
|
|
39
39
|
|
|
40
40
|
RD consumes the design-draft to implement; QA consumes it for visual regression checks.
|
|
41
41
|
|
|
@@ -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
|