peaks-cli 1.0.11 → 1.0.12
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/dist/src/cli/commands/config-commands.js +17 -1
- package/dist/src/cli/commands/workflow-commands.js +94 -56
- package/dist/src/services/codegraph/codegraph-service.js +45 -26
- package/dist/src/services/config/config-service.js +22 -2
- package/dist/src/services/recommendations/capability-seed-items.js +1 -1
- package/dist/src/services/recommendations/capability-seed-mappings.js +1 -1
- package/dist/src/services/recommendations/capability-seed-sources.js +1 -1
- package/dist/src/services/shadcn/shadcn-service.d.ts +0 -4
- package/dist/src/services/shadcn/shadcn-service.js +30 -15
- package/dist/src/shared/version.d.ts +1 -1
- package/dist/src/shared/version.js +1 -1
- package/package.json +3 -2
- package/scripts/strip-internal-exports.mjs +33 -0
- package/skills/peaks-qa/SKILL.md +3 -3
- package/skills/peaks-qa/references/artifact-contracts.md +2 -2
- package/skills/peaks-qa/references/regression-gates.md +2 -2
- package/skills/peaks-rd/SKILL.md +3 -3
- package/skills/peaks-rd/references/artifact-contracts.md +2 -2
- package/skills/peaks-rd/references/refactor-workflow.md +2 -2
- package/skills/peaks-solo/SKILL.md +6 -4
- package/skills/peaks-solo/references/artifact-contracts.md +2 -2
- package/skills/peaks-solo/references/refactor-mode.md +2 -2
- package/skills/peaks-solo/references/workflow.md +1 -1
- package/skills/peaks-ui/SKILL.md +2 -2
- package/skills/peaks-ui/references/workflow.md +1 -1
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { getLocalArtifactPath } from '../../services/artifacts/workspace-service.js';
|
|
2
|
+
import { addWorkspace, ensureWorkspaceConfigForPath, getConfig, getMiniMaxProviderConfig, getMiniMaxProviderStatus, isSensitiveConfigPath, readConfig, redactConfigSecrets, removeWorkspace, setConfig, setCurrentWorkspace, setMiniMaxProviderConfig } from '../../services/config/config-service.js';
|
|
2
3
|
import { testMiniMaxProvider } from '../../services/providers/minimax-provider-service.js';
|
|
3
4
|
import { fail, ok } from '../../shared/result.js';
|
|
4
5
|
import { addJsonOption, getErrorMessage, isArtifactProvider, isArtifactRepoSegment, isMiniMaxHttpsUrl, parseConfigLayer, printInvalidConfigLayer, printResult, redactSensitiveErrorMessage, summarizeMiniMaxSmokeResult } from '../cli-helpers.js';
|
|
@@ -135,6 +136,21 @@ function registerWorkspaceCommands(config, io) {
|
|
|
135
136
|
const cfg = readConfig();
|
|
136
137
|
printResult(io, ok('config.workspace.list', { currentWorkspace: cfg.currentWorkspace, workspaces: cfg.workspaces }), options.json);
|
|
137
138
|
});
|
|
139
|
+
addJsonOption(configWorkspace.command('ensure').description('Ensure a user workspace exists for a project path and make it current').option('--path <path>', 'project path to ensure, defaults to cwd')).action((options) => {
|
|
140
|
+
try {
|
|
141
|
+
const workspace = ensureWorkspaceConfigForPath(options.path ?? process.cwd());
|
|
142
|
+
if (!workspace) {
|
|
143
|
+
printResult(io, fail('config.workspace.ensure', 'WORKSPACE_ENSURE_FAILED', 'Could not resolve a workspace for the provided path', {}, ['Run from inside a project or pass --path <project>']), options.json);
|
|
144
|
+
process.exitCode = 1;
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
printResult(io, ok('config.workspace.ensure', { workspace, currentWorkspace: workspace.workspaceId, artifactWorkspacePath: getLocalArtifactPath(workspace) }), options.json);
|
|
148
|
+
}
|
|
149
|
+
catch (error) {
|
|
150
|
+
printResult(io, fail('config.workspace.ensure', 'WORKSPACE_ENSURE_FAILED', getErrorMessage(error), {}, ['Check that the project path exists and artifact workspace markers are safe']), options.json);
|
|
151
|
+
process.exitCode = 1;
|
|
152
|
+
}
|
|
153
|
+
});
|
|
138
154
|
addJsonOption(configWorkspace.command('add').description('Add a workspace').requiredOption('--id <id>', 'workspace identifier').requiredOption('--name <name>', 'workspace display name').requiredOption('--path <path>', 'workspace root path').option('--provider <provider>', 'artifact repo provider: github or gitlab').option('--repo-owner <owner>', 'artifact repo owner').option('--repo-name <name>', 'artifact repo name').option('--layer <layer>', 'user or project')).action((options) => {
|
|
139
155
|
const layer = parseConfigLayer(options.layer);
|
|
140
156
|
if (layer === null) {
|
|
@@ -11,21 +11,20 @@ import { getLocalArtifactPath } from '../../services/artifacts/workspace-service
|
|
|
11
11
|
import { fail, ok } from '../../shared/result.js';
|
|
12
12
|
import { addJsonOption, failUnsupportedNonDryRun, getErrorMessage, isRecommendationWorkflow, printResult } from '../cli-helpers.js';
|
|
13
13
|
function getCurrentWorkspaceContext() {
|
|
14
|
-
const workspace = getCurrentWorkspaceConfig();
|
|
14
|
+
const workspace = ensureWorkspaceConfigForCurrentPath() ?? getCurrentWorkspaceConfig();
|
|
15
15
|
if (!workspace)
|
|
16
16
|
return {};
|
|
17
17
|
return { workspace, artifactWorkspacePath: getLocalArtifactPath(workspace) };
|
|
18
18
|
}
|
|
19
19
|
function getWorkflowWorkspaceContext() {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
if (!workspace)
|
|
23
|
-
return {};
|
|
24
|
-
return { workspace, artifactWorkspacePath: getLocalArtifactPath(workspace) };
|
|
25
|
-
}
|
|
26
|
-
catch {
|
|
20
|
+
const workspace = ensureWorkspaceConfigForCurrentPath() ?? getCurrentWorkspaceConfig();
|
|
21
|
+
if (!workspace)
|
|
27
22
|
return {};
|
|
28
|
-
}
|
|
23
|
+
return { workspace, artifactWorkspacePath: getLocalArtifactPath(workspace) };
|
|
24
|
+
}
|
|
25
|
+
function failWorkflowWorkspaceContext(io, command, error, asJson) {
|
|
26
|
+
printResult(io, fail(command, 'WORKSPACE_CONTEXT_FAILED', getErrorMessage(error), {}, ['Run peaks config workspace ensure --path <project> --json and resolve the reported workspace issue']), asJson);
|
|
27
|
+
process.exitCode = 1;
|
|
29
28
|
}
|
|
30
29
|
function parseMaxWorkers(io, command, value, asJson) {
|
|
31
30
|
const maxWorkers = Number(value);
|
|
@@ -65,24 +64,39 @@ function runTechPlan(io, options) {
|
|
|
65
64
|
}
|
|
66
65
|
try {
|
|
67
66
|
validatePlanningInput(options.changeId, options.goal);
|
|
68
|
-
const workspaceContext = getCurrentWorkspaceContext();
|
|
69
|
-
const plan = createTechPlan({
|
|
70
|
-
changeId: options.changeId,
|
|
71
|
-
goal: options.goal,
|
|
72
|
-
swarm: options.swarm ?? false,
|
|
73
|
-
dryRun: true,
|
|
74
|
-
...workspaceContext
|
|
75
|
-
});
|
|
76
|
-
printResult(io, ok('tech.plan', plan), options.json);
|
|
77
67
|
}
|
|
78
68
|
catch (error) {
|
|
79
69
|
printResult(io, fail('tech.plan', 'INVALID_CHANGE_ID_OR_GOAL', getErrorMessage(error), {}, ['Use a safe change id and a non-empty goal']), options.json);
|
|
80
70
|
process.exitCode = 1;
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
let workspaceContext;
|
|
74
|
+
try {
|
|
75
|
+
workspaceContext = getCurrentWorkspaceContext();
|
|
76
|
+
}
|
|
77
|
+
catch (error) {
|
|
78
|
+
failWorkflowWorkspaceContext(io, 'tech.plan', error, options.json);
|
|
79
|
+
return;
|
|
81
80
|
}
|
|
81
|
+
const plan = createTechPlan({
|
|
82
|
+
changeId: options.changeId,
|
|
83
|
+
goal: options.goal,
|
|
84
|
+
swarm: options.swarm ?? false,
|
|
85
|
+
dryRun: true,
|
|
86
|
+
...workspaceContext
|
|
87
|
+
});
|
|
88
|
+
printResult(io, ok('tech.plan', plan), options.json);
|
|
82
89
|
}
|
|
83
90
|
function runTechStatus(io, options) {
|
|
91
|
+
let workspaceContext;
|
|
92
|
+
try {
|
|
93
|
+
workspaceContext = getCurrentWorkspaceContext();
|
|
94
|
+
}
|
|
95
|
+
catch (error) {
|
|
96
|
+
failWorkflowWorkspaceContext(io, 'tech.status', error, options.json);
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
84
99
|
try {
|
|
85
|
-
const workspaceContext = getCurrentWorkspaceContext();
|
|
86
100
|
printResult(io, ok('tech.status', getTechStatus({ changeId: options.changeId, ...workspaceContext })), options.json);
|
|
87
101
|
}
|
|
88
102
|
catch (error) {
|
|
@@ -108,23 +122,31 @@ function runWorkflowRoute(io, options) {
|
|
|
108
122
|
return;
|
|
109
123
|
try {
|
|
110
124
|
validatePlanningInput(options.changeId, options.goal);
|
|
111
|
-
const workspaceContext = getWorkflowWorkspaceContext();
|
|
112
|
-
const plan = createWorkflowRouterPlan({
|
|
113
|
-
changeId: options.changeId,
|
|
114
|
-
goal: options.goal,
|
|
115
|
-
mode: options.mode,
|
|
116
|
-
...(soloMode ? { soloMode } : {}),
|
|
117
|
-
maxWorkers,
|
|
118
|
-
dryRun: true,
|
|
119
|
-
config: readConfig(),
|
|
120
|
-
...workspaceContext
|
|
121
|
-
});
|
|
122
|
-
printResult(io, ok('workflow.route', plan), options.json);
|
|
123
125
|
}
|
|
124
126
|
catch (error) {
|
|
125
127
|
printResult(io, fail('workflow.route', 'INVALID_CHANGE_ID_OR_GOAL', getErrorMessage(error), {}, ['Use a safe change id and a non-empty goal']), options.json);
|
|
126
128
|
process.exitCode = 1;
|
|
129
|
+
return;
|
|
127
130
|
}
|
|
131
|
+
let workspaceContext;
|
|
132
|
+
try {
|
|
133
|
+
workspaceContext = getWorkflowWorkspaceContext();
|
|
134
|
+
}
|
|
135
|
+
catch (error) {
|
|
136
|
+
failWorkflowWorkspaceContext(io, 'workflow.route', error, options.json);
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
const plan = createWorkflowRouterPlan({
|
|
140
|
+
changeId: options.changeId,
|
|
141
|
+
goal: options.goal,
|
|
142
|
+
mode: options.mode,
|
|
143
|
+
...(soloMode ? { soloMode } : {}),
|
|
144
|
+
maxWorkers,
|
|
145
|
+
dryRun: true,
|
|
146
|
+
config: readConfig(),
|
|
147
|
+
...workspaceContext
|
|
148
|
+
});
|
|
149
|
+
printResult(io, ok('workflow.route', plan), options.json);
|
|
128
150
|
}
|
|
129
151
|
function runAutonomousWorkflow(io, options) {
|
|
130
152
|
if (options.dryRun === false) {
|
|
@@ -144,23 +166,31 @@ function runAutonomousWorkflow(io, options) {
|
|
|
144
166
|
return;
|
|
145
167
|
try {
|
|
146
168
|
validatePlanningInput(options.changeId, options.goal);
|
|
147
|
-
const workspaceContext = getWorkflowWorkspaceContext();
|
|
148
|
-
const plan = createAutonomousWorkflowPlan({
|
|
149
|
-
changeId: options.changeId,
|
|
150
|
-
goal: options.goal,
|
|
151
|
-
mode: options.mode,
|
|
152
|
-
...(soloMode ? { soloMode } : {}),
|
|
153
|
-
maxWorkers,
|
|
154
|
-
dryRun: true,
|
|
155
|
-
config: readConfig(),
|
|
156
|
-
...workspaceContext
|
|
157
|
-
});
|
|
158
|
-
printResult(io, ok('workflow.autonomous', plan), options.json);
|
|
159
169
|
}
|
|
160
170
|
catch (error) {
|
|
161
171
|
printResult(io, fail('workflow.autonomous', 'INVALID_CHANGE_ID_OR_GOAL', getErrorMessage(error), {}, ['Use a safe change id and a non-empty goal']), options.json);
|
|
162
172
|
process.exitCode = 1;
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
let workspaceContext;
|
|
176
|
+
try {
|
|
177
|
+
workspaceContext = getWorkflowWorkspaceContext();
|
|
163
178
|
}
|
|
179
|
+
catch (error) {
|
|
180
|
+
failWorkflowWorkspaceContext(io, 'workflow.autonomous', error, options.json);
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
const plan = createAutonomousWorkflowPlan({
|
|
184
|
+
changeId: options.changeId,
|
|
185
|
+
goal: options.goal,
|
|
186
|
+
mode: options.mode,
|
|
187
|
+
...(soloMode ? { soloMode } : {}),
|
|
188
|
+
maxWorkers,
|
|
189
|
+
dryRun: true,
|
|
190
|
+
config: readConfig(),
|
|
191
|
+
...workspaceContext
|
|
192
|
+
});
|
|
193
|
+
printResult(io, ok('workflow.autonomous', plan), options.json);
|
|
164
194
|
}
|
|
165
195
|
function runSwarmPlan(io, options) {
|
|
166
196
|
if ((options.skill ?? 'rd') !== 'rd') {
|
|
@@ -177,24 +207,32 @@ function runSwarmPlan(io, options) {
|
|
|
177
207
|
return;
|
|
178
208
|
try {
|
|
179
209
|
validatePlanningInput(options.changeId, options.goal);
|
|
180
|
-
const workspaceContext = getWorkflowWorkspaceContext();
|
|
181
|
-
const config = readConfig();
|
|
182
|
-
const plan = createRdSwarmPlan({
|
|
183
|
-
skill: 'rd',
|
|
184
|
-
changeId: options.changeId,
|
|
185
|
-
goal: options.goal,
|
|
186
|
-
maxWorkers,
|
|
187
|
-
dryRun: true,
|
|
188
|
-
swarmMode: config.swarmMode,
|
|
189
|
-
executionModelId: getEconomyAwareExecutionModelId(config),
|
|
190
|
-
...workspaceContext
|
|
191
|
-
});
|
|
192
|
-
printResult(io, ok('swarm.plan', plan), options.json);
|
|
193
210
|
}
|
|
194
211
|
catch (error) {
|
|
195
212
|
printResult(io, fail('swarm.plan', 'INVALID_CHANGE_ID_OR_GOAL', getErrorMessage(error), {}, ['Use a safe change id and a non-empty goal']), options.json);
|
|
196
213
|
process.exitCode = 1;
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
let workspaceContext;
|
|
217
|
+
try {
|
|
218
|
+
workspaceContext = getWorkflowWorkspaceContext();
|
|
197
219
|
}
|
|
220
|
+
catch (error) {
|
|
221
|
+
failWorkflowWorkspaceContext(io, 'swarm.plan', error, options.json);
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
const config = readConfig();
|
|
225
|
+
const plan = createRdSwarmPlan({
|
|
226
|
+
skill: 'rd',
|
|
227
|
+
changeId: options.changeId,
|
|
228
|
+
goal: options.goal,
|
|
229
|
+
maxWorkers,
|
|
230
|
+
dryRun: true,
|
|
231
|
+
swarmMode: config.swarmMode,
|
|
232
|
+
executionModelId: getEconomyAwareExecutionModelId(config),
|
|
233
|
+
...workspaceContext
|
|
234
|
+
});
|
|
235
|
+
printResult(io, ok('swarm.plan', plan), options.json);
|
|
198
236
|
}
|
|
199
237
|
function addTechPlanOptions(command) {
|
|
200
238
|
return addJsonOption(command
|
|
@@ -1,19 +1,15 @@
|
|
|
1
1
|
import { existsSync, realpathSync, statSync } from 'node:fs';
|
|
2
2
|
import { spawn } from 'node:child_process';
|
|
3
3
|
import { createRequire } from 'node:module';
|
|
4
|
-
import { dirname, isAbsolute,
|
|
4
|
+
import { dirname, isAbsolute, relative, resolve, sep, win32 } from 'node:path';
|
|
5
5
|
const CODEGRAPH_PACKAGE_NAME = '@colbymchenry/codegraph';
|
|
6
6
|
const CODEGRAPH_PACKAGE_VERSION = '0.7.10';
|
|
7
7
|
const CODEGRAPH_EXECUTABLE = process.execPath;
|
|
8
8
|
const CODEGRAPH_BINARY_PATH = resolveCodegraphBinaryPath();
|
|
9
9
|
const CODEGRAPH_PROCESS_TIMEOUT_MS = 600_000;
|
|
10
10
|
const CODEGRAPH_OUTPUT_LIMIT_BYTES = 10 * 1024 * 1024;
|
|
11
|
-
const NODE_OPTIONS_ENV_KEY = 'NODE_OPTIONS';
|
|
12
|
-
const NPM_CONFIG_PREFIX = 'npm_config_';
|
|
13
|
-
const NPM_CONFIG_UPPER_PREFIX = 'NPM_CONFIG_';
|
|
14
11
|
const POSITIONAL_ARGUMENT_PREFIX = '-';
|
|
15
12
|
const ALLOWED_SUBCOMMANDS = ['status', 'init', 'index', 'query', 'files', 'context', 'affected'];
|
|
16
|
-
const NUMERIC_FLAG_NAMES = ['limit', 'maxDepth'];
|
|
17
13
|
const COMMON_OPTION_KEYS = ['subcommand', 'project'];
|
|
18
14
|
const ALLOWED_OPTIONS_BY_SUBCOMMAND = {
|
|
19
15
|
status: [],
|
|
@@ -24,13 +20,16 @@ const ALLOWED_OPTIONS_BY_SUBCOMMAND = {
|
|
|
24
20
|
context: ['task'],
|
|
25
21
|
affected: ['files', 'json']
|
|
26
22
|
};
|
|
23
|
+
function assertCodegraphBinaryExists(binaryPath) {
|
|
24
|
+
if (!existsSync(binaryPath)) {
|
|
25
|
+
throw new Error('Unable to resolve local codegraph binary from @colbymchenry/codegraph');
|
|
26
|
+
}
|
|
27
|
+
}
|
|
27
28
|
function resolveCodegraphBinaryPath() {
|
|
28
29
|
const require = createRequire(import.meta.url);
|
|
29
30
|
const packageJsonPath = require.resolve('@colbymchenry/codegraph/package.json');
|
|
30
31
|
const binaryPath = resolve(dirname(packageJsonPath), 'dist', 'bin', 'codegraph.js');
|
|
31
|
-
|
|
32
|
-
throw new Error('Unable to resolve local codegraph binary from @colbymchenry/codegraph');
|
|
33
|
-
}
|
|
32
|
+
assertCodegraphBinaryExists(binaryPath);
|
|
34
33
|
return binaryPath;
|
|
35
34
|
}
|
|
36
35
|
function assertSupportedSubcommand(subcommand) {
|
|
@@ -38,12 +37,15 @@ function assertSupportedSubcommand(subcommand) {
|
|
|
38
37
|
throw new Error(`Unsupported codegraph subcommand: ${subcommand}`);
|
|
39
38
|
}
|
|
40
39
|
}
|
|
40
|
+
function assertProjectRootDirectory(projectRoot) {
|
|
41
|
+
if (!statSync(projectRoot).isDirectory()) {
|
|
42
|
+
throw new Error('Project path must exist and be a directory');
|
|
43
|
+
}
|
|
44
|
+
}
|
|
41
45
|
function resolveProjectRoot(project) {
|
|
42
46
|
const projectRoot = resolve(project);
|
|
43
47
|
try {
|
|
44
|
-
|
|
45
|
-
throw new Error('Project path must exist and be a directory');
|
|
46
|
-
}
|
|
48
|
+
assertProjectRootDirectory(projectRoot);
|
|
47
49
|
return realpathSync.native(projectRoot);
|
|
48
50
|
}
|
|
49
51
|
catch {
|
|
@@ -88,12 +90,8 @@ function assertInsideProject(projectRoot, absolutePath) {
|
|
|
88
90
|
throw new Error('Affected files must stay inside the project');
|
|
89
91
|
}
|
|
90
92
|
}
|
|
91
|
-
function
|
|
92
|
-
|
|
93
|
-
return absoluteFilePath;
|
|
94
|
-
}
|
|
95
|
-
let currentPath = dirname(absoluteFilePath);
|
|
96
|
-
while (!existsSync(currentPath)) {
|
|
93
|
+
function climbToExistingBoundary(currentPath, pathExists = existsSync) {
|
|
94
|
+
while (!pathExists(currentPath)) {
|
|
97
95
|
const parentPath = dirname(currentPath);
|
|
98
96
|
if (parentPath === currentPath) {
|
|
99
97
|
return currentPath;
|
|
@@ -102,6 +100,9 @@ function resolveExistingBoundary(absoluteFilePath) {
|
|
|
102
100
|
}
|
|
103
101
|
return currentPath;
|
|
104
102
|
}
|
|
103
|
+
function resolveExistingBoundary(absoluteFilePath) {
|
|
104
|
+
return existsSync(absoluteFilePath) ? absoluteFilePath : climbToExistingBoundary(dirname(absoluteFilePath));
|
|
105
|
+
}
|
|
105
106
|
function normalizeProjectRelativeFile(projectRoot, file) {
|
|
106
107
|
assertPositionalArgument(file, 'Affected files');
|
|
107
108
|
const absoluteFilePath = resolve(projectRoot, file);
|
|
@@ -110,10 +111,13 @@ function normalizeProjectRelativeFile(projectRoot, file) {
|
|
|
110
111
|
assertInsideProject(projectRoot, realBoundary);
|
|
111
112
|
return relative(projectRoot, absoluteFilePath).split(sep).join('/');
|
|
112
113
|
}
|
|
113
|
-
function
|
|
114
|
+
function assertAffectedFiles(files) {
|
|
114
115
|
if (!files || files.length < 1) {
|
|
115
116
|
throw new Error('affected requires at least one file');
|
|
116
117
|
}
|
|
118
|
+
}
|
|
119
|
+
function buildAffectedFileArgs(projectRoot, files) {
|
|
120
|
+
assertAffectedFiles(files);
|
|
117
121
|
return files.map((file) => normalizeProjectRelativeFile(projectRoot, file));
|
|
118
122
|
}
|
|
119
123
|
function buildCommandArgs(options, projectRoot) {
|
|
@@ -168,24 +172,36 @@ function assertOutputLimit(currentSize, chunkSize) {
|
|
|
168
172
|
}
|
|
169
173
|
return nextSize;
|
|
170
174
|
}
|
|
171
|
-
function
|
|
175
|
+
function getWindowsTaskkillPath(fileExists = existsSync) {
|
|
176
|
+
const candidates = [
|
|
177
|
+
win32.join('C:\\Windows', 'System32', 'taskkill.exe'),
|
|
178
|
+
win32.join('C:\\WINNT', 'System32', 'taskkill.exe')
|
|
179
|
+
];
|
|
180
|
+
return candidates.find((candidate) => fileExists(candidate)) ?? null;
|
|
181
|
+
}
|
|
182
|
+
function terminateCodegraphProcess(childProcess, platform = process.platform, killProcess = process.kill, spawnProcess = spawn, taskkillPath = getWindowsTaskkillPath()) {
|
|
172
183
|
if (childProcess.pid === undefined) {
|
|
173
184
|
childProcess.kill();
|
|
174
185
|
return;
|
|
175
186
|
}
|
|
176
|
-
if (
|
|
177
|
-
|
|
178
|
-
|
|
187
|
+
if (platform === 'win32') {
|
|
188
|
+
if (!taskkillPath) {
|
|
189
|
+
childProcess.kill();
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
const killerProcess = spawnProcess(taskkillPath, ['/pid', String(childProcess.pid), '/T', '/F'], { shell: false, stdio: 'ignore' });
|
|
193
|
+
killerProcess.on('error', () => childProcess.kill());
|
|
194
|
+
killerProcess.unref();
|
|
179
195
|
return;
|
|
180
196
|
}
|
|
181
197
|
try {
|
|
182
|
-
|
|
198
|
+
killProcess(-childProcess.pid, 'SIGTERM');
|
|
183
199
|
}
|
|
184
200
|
catch {
|
|
185
201
|
childProcess.kill('SIGTERM');
|
|
186
202
|
}
|
|
187
203
|
}
|
|
188
|
-
function
|
|
204
|
+
function runCodegraphProcess(invocation, timeoutMs = CODEGRAPH_PROCESS_TIMEOUT_MS) {
|
|
189
205
|
return new Promise((resolveResult, reject) => {
|
|
190
206
|
const childProcess = spawn(invocation.executable, invocation.args, {
|
|
191
207
|
cwd: invocation.cwd,
|
|
@@ -195,8 +211,8 @@ function defaultCodegraphProcessRunner(invocation) {
|
|
|
195
211
|
});
|
|
196
212
|
const timeout = setTimeout(() => {
|
|
197
213
|
terminateCodegraphProcess(childProcess);
|
|
198
|
-
reject(new Error(`codegraph process timed out after ${
|
|
199
|
-
},
|
|
214
|
+
reject(new Error(`codegraph process timed out after ${timeoutMs}ms`));
|
|
215
|
+
}, timeoutMs);
|
|
200
216
|
const stdoutChunks = [];
|
|
201
217
|
const stderrChunks = [];
|
|
202
218
|
let stdoutSize = 0;
|
|
@@ -235,6 +251,9 @@ function defaultCodegraphProcessRunner(invocation) {
|
|
|
235
251
|
});
|
|
236
252
|
});
|
|
237
253
|
}
|
|
254
|
+
function defaultCodegraphProcessRunner(invocation) {
|
|
255
|
+
return runCodegraphProcess(invocation);
|
|
256
|
+
}
|
|
238
257
|
export function createCodegraphInvocation(options) {
|
|
239
258
|
assertSupportedSubcommand(options.subcommand);
|
|
240
259
|
const projectRoot = resolveProjectRoot(options.project);
|
|
@@ -156,6 +156,25 @@ function validateUserConfigPathForWrite(configPath) {
|
|
|
156
156
|
}
|
|
157
157
|
}
|
|
158
158
|
}
|
|
159
|
+
function getExistingBoundary(path) {
|
|
160
|
+
let currentPath = resolve(path);
|
|
161
|
+
while (!existsSync(currentPath)) {
|
|
162
|
+
const parentPath = dirname(currentPath);
|
|
163
|
+
if (parentPath === currentPath) {
|
|
164
|
+
return currentPath;
|
|
165
|
+
}
|
|
166
|
+
currentPath = parentPath;
|
|
167
|
+
}
|
|
168
|
+
return currentPath;
|
|
169
|
+
}
|
|
170
|
+
function validateArtifactWorkspaceRootBeforeCreate(artifactRoot, workspaceRoot) {
|
|
171
|
+
const workspaceRootReal = realpathSync(workspaceRoot);
|
|
172
|
+
const existingBoundary = getExistingBoundary(artifactRoot);
|
|
173
|
+
const existingBoundaryReal = realpathSync(existingBoundary);
|
|
174
|
+
if (isInsidePath(resolve(artifactRoot), workspaceRoot) || isInsidePath(existingBoundaryReal, workspaceRootReal)) {
|
|
175
|
+
throw new Error('Artifact workspace must stay outside the project root');
|
|
176
|
+
}
|
|
177
|
+
}
|
|
159
178
|
function validateArtifactWorkspaceRoot(artifactRoot, workspaceRoot) {
|
|
160
179
|
const artifactStats = lstatSync(artifactRoot);
|
|
161
180
|
if (!artifactStats.isDirectory() || artifactStats.isSymbolicLink()) {
|
|
@@ -871,6 +890,7 @@ function ensureArtifactWorkspaceMarker(workspace) {
|
|
|
871
890
|
const artifactRoot = getWorkspaceArtifactRoot(workspace);
|
|
872
891
|
const peaksPath = resolve(artifactRoot, '.peaks');
|
|
873
892
|
const markerPath = resolve(peaksPath, 'config.json');
|
|
893
|
+
validateArtifactWorkspaceRootBeforeCreate(artifactRoot, workspace.rootPath);
|
|
874
894
|
ensureDir(artifactRoot);
|
|
875
895
|
validateArtifactWorkspaceRoot(artifactRoot, workspace.rootPath);
|
|
876
896
|
ensureDir(peaksPath);
|
|
@@ -887,7 +907,7 @@ export function ensureWorkspaceConfigForPath(path = process.cwd()) {
|
|
|
887
907
|
const existingWorkspace = findWorkspaceForPath(config.workspaces, path);
|
|
888
908
|
if (existingWorkspace) {
|
|
889
909
|
ensureArtifactWorkspaceMarker(existingWorkspace);
|
|
890
|
-
if (
|
|
910
|
+
if (config.currentWorkspace !== existingWorkspace.workspaceId) {
|
|
891
911
|
writeConfig({ currentWorkspace: existingWorkspace.workspaceId }, 'user');
|
|
892
912
|
}
|
|
893
913
|
return existingWorkspace;
|
|
@@ -903,7 +923,7 @@ export function ensureWorkspaceConfigForPath(path = process.cwd()) {
|
|
|
903
923
|
};
|
|
904
924
|
ensureArtifactWorkspaceMarker(workspace);
|
|
905
925
|
const updatedWorkspaces = [...config.workspaces, workspace];
|
|
906
|
-
writeConfig({ workspaces: updatedWorkspaces,
|
|
926
|
+
writeConfig({ workspaces: updatedWorkspaces, currentWorkspace: workspace.workspaceId }, 'user');
|
|
907
927
|
return workspace;
|
|
908
928
|
}
|
|
909
929
|
export function getWorkspaceConfigForCurrentPath() {
|
|
@@ -87,7 +87,7 @@ export const seedCapabilityItems = [
|
|
|
87
87
|
capability('codegraph.semantic-query', 'codegraph', 'Codegraph Semantic Query', 'cli', 'project-analysis', ['engineer'], 'medium', 'peaks-rd-local-scan', 'Use local Grep/Glob and RD scanning when codegraph semantic query is unavailable.', 'Codegraph Semantic Query', 'Codegraph 语义查询', 'Queries local symbols and project relationships for RD planning evidence.', '查询本地符号和项目关系,为 RD 规划提供证据。'),
|
|
88
88
|
capability('codegraph.impact-analysis', 'codegraph', 'Codegraph Impact Analysis', 'cli', 'impact-analysis', ['engineer', 'qa'], 'medium', 'peaks-rd-qa-impact-review', 'Use RD changed-file analysis and QA regression planning when codegraph affected output is unavailable.', 'Codegraph Impact Analysis', 'Codegraph 影响面分析', 'Analyzes likely impact for changed files so RD and QA can focus planning and regression scope.', '分析变更文件的可能影响面,帮助 RD 与 QA 聚焦规划和回归范围。'),
|
|
89
89
|
capability('codegraph.context-pack', 'codegraph', 'Codegraph Context Pack', 'cli', 'context-pack', ['engineer', 'qa', 'product'], 'medium', 'peaks-txt-context-capsule', 'Use Peaks TXT context capsules and role-skill handoffs when codegraph context output is unavailable.', 'Codegraph Context Pack', 'Codegraph 上下文包', 'Builds task-specific local context that Solo, RD, and TXT can use as supporting evidence.', '生成任务相关的本地上下文,作为 Solo、RD 与 TXT 的辅助证据。'),
|
|
90
|
-
capability('
|
|
90
|
+
capability('gstack-browse.headed-browser-validation', 'gstack-browse', 'gstack Browse Headed Browser Validation', 'cli', 'browser-validation', ['engineer', 'qa'], 'medium', 'blocked-browser-validation', 'Block browser validation until headed gstack/browse/dist/browse can open the actual launched app URL.', 'gstack Browse Validation', 'gstack 有头浏览器验证', 'Validates UI flows in a visible gstack browse session against the app URL advertised by the launched dev server.', '使用有头 gstack browse 会话验证由开发服务器实际输出 URL 对应的 UI 流程。'),
|
|
91
91
|
capability('chrome-devtools-mcp.browser-debug', 'chrome-devtools-mcp', 'Chrome DevTools Browser Debug', 'mcp', 'browser-debug', ['engineer', 'qa'], 'medium', 'manual-devtools-inspection', 'Use browser screenshots, console logs, and network traces supplied by the user.', 'Chrome DevTools Debug', 'Chrome DevTools 调试', 'Inspects runtime UI, console, network, and performance behavior.', '检查运行时 UI、控制台、网络和性能行为。'),
|
|
92
92
|
capability('figma-context-mcp.design-context', 'figma-context-mcp', 'Figma Design Context', 'mcp', 'design-context', ['designer', 'engineer'], 'medium', 'manual-design-input', 'Ask the user for screenshots, tokens, or exported design notes.', 'Figma Design Context', 'Figma 设计上下文', 'Reads design context for UI implementation planning.', '读取设计上下文以辅助 UI 实现规划。'),
|
|
93
93
|
capability('searchcode-mcp.code-search', 'searchcode-mcp', 'SearchCode MCP', 'mcp', 'code-search', ['engineer'], 'medium', 'local-code-search', 'Use local Grep/Glob and ask before sending private code externally.', 'External Code Search', '外部代码搜索', 'Searches code examples or repositories outside the current workspace.', '搜索当前工作区外部的代码示例或仓库。'),
|
|
@@ -8,7 +8,7 @@ export const seedCapabilityLandingMappings = [
|
|
|
8
8
|
mapping({ capabilityId: 'codegraph.context-pack', sourceId: 'codegraph', sourceGroup: 'access-repo', landingKind: 'skill', target: 'peaks-rd', skillName: 'peaks-rd', guidance: 'Dry-run reference only: peaks-rd may use peaks codegraph context --project <path> <task> to gather local evidence for RD analysis when execution is approved, without replacing standards dry-runs.' }),
|
|
9
9
|
mapping({ capabilityId: 'codegraph.context-pack', sourceId: 'codegraph', sourceGroup: 'access-repo', landingKind: 'skill', target: 'peaks-solo', skillName: 'peaks-solo', guidance: 'Solo may attach local context packs or affected summaries before role handoff so RD, QA, and TXT share the same project evidence.' }),
|
|
10
10
|
mapping({ capabilityId: 'codegraph.context-pack', sourceId: 'codegraph', sourceGroup: 'access-repo', landingKind: 'skill', target: 'peaks-txt', skillName: 'peaks-txt', guidance: 'TXT may summarize recorded codegraph context packs into handoffs while treating them as supporting evidence only.' }),
|
|
11
|
-
mapping({ capabilityId: '
|
|
11
|
+
mapping({ capabilityId: 'gstack-browse.headed-browser-validation', sourceId: 'gstack-browse', sourceGroup: 'access-repo', landingKind: 'skill', target: 'peaks-qa', skillName: 'peaks-qa', guidance: 'Launch the app, capture its actual advertised URL, and validate only with headed gstack/browse/dist/browse; block instead of falling back to Playwright MCP or default ports.' }),
|
|
12
12
|
mapping({ capabilityId: 'chrome-devtools-mcp.browser-debug', sourceId: 'chrome-devtools-mcp', sourceGroup: 'access-repo', landingKind: 'skill', target: 'peaks-ui', skillName: 'peaks-ui', guidance: 'Use for runtime UI, console, network, and performance inspection.' }),
|
|
13
13
|
mapping({ capabilityId: 'context-mode.context-management', sourceId: 'context-mode', sourceGroup: 'access-repo', landingKind: 'skill', target: 'peaks-txt', skillName: 'peaks-txt', guidance: 'Use only for explicit context management; durable memory requires user opt-in.' }),
|
|
14
14
|
mapping({ capabilityId: 'modelcontextprotocol-servers.collection', sourceId: 'modelcontextprotocol-servers', sourceGroup: 'access-repo', landingKind: 'catalog', target: 'future peaks mcp catalog', guidance: 'Treat as an unscanned MCP collection; do not auto-install unknown servers.' }),
|
|
@@ -2,7 +2,7 @@ export const seedCapabilitySources = [
|
|
|
2
2
|
{ sourceId: 'ruflo-access-repo', sourceType: 'repo', sourceGroup: 'access-repo', title: 'Ruflo', url: 'https://github.com/ruvnet/ruflo', trustSignals: { notes: ['Workflow orchestration reference; do not execute or install from the capability map.'] }, discoveryStatus: 'unscanned', items: ['ruflo-access-repo.workflow-reference'] },
|
|
3
3
|
{ sourceId: 'context7', sourceType: 'repo', sourceGroup: 'access-repo', title: 'Context7', url: 'https://github.com/upstash/context7', trustSignals: { sourceReputation: 'commonly used docs lookup MCP capability' }, discoveryStatus: 'indexed', items: ['context7.docs-lookup'] },
|
|
4
4
|
{ sourceId: 'codegraph', sourceType: 'repo', sourceGroup: 'access-repo', title: 'codegraph', url: 'https://github.com/colbymchenry/codegraph', trustSignals: { notes: ['Use through peaks codegraph only; do not run upstream install flows from the capability map.', 'Local project indexing can create .codegraph artifacts; do not commit generated databases unless explicitly requested.'] }, discoveryStatus: 'indexed', items: ['codegraph.project-indexing', 'codegraph.semantic-query', 'codegraph.impact-analysis', 'codegraph.context-pack'] },
|
|
5
|
-
{ sourceId: '
|
|
5
|
+
{ sourceId: 'gstack-browse', sourceType: 'repo', sourceGroup: 'access-repo', title: 'gstack browse', url: 'https://github.com/garrytan/gstack', trustSignals: { notes: ['Use headed gstack/browse/dist/browse only after launching the app and discovering its actual advertised URL. Do not substitute Playwright MCP.'] }, discoveryStatus: 'indexed', items: ['gstack-browse.headed-browser-validation'] },
|
|
6
6
|
{ sourceId: 'chrome-devtools-mcp', sourceType: 'website', sourceGroup: 'access-repo', title: 'Chrome DevTools MCP', url: 'https://www.pulsemcp.com/servers/chrome-devtools', trustSignals: { notes: ['Browser inspection and performance debugging capability.'] }, discoveryStatus: 'indexed', items: ['chrome-devtools-mcp.browser-debug'] },
|
|
7
7
|
{ sourceId: 'context-mode', sourceType: 'repo', sourceGroup: 'access-repo', title: 'Context Mode', url: 'https://github.com/mksglu/context-mode', trustSignals: { notes: ['Context and memory management reference.'] }, discoveryStatus: 'indexed', items: ['context-mode.context-management'] },
|
|
8
8
|
{ sourceId: 'modelcontextprotocol-servers', sourceType: 'mcp-collection', sourceGroup: 'access-repo', title: 'Model Context Protocol Servers', url: 'https://github.com/modelcontextprotocol/servers', trustSignals: { sourceReputation: 'official MCP server collection' }, discoveryStatus: 'unscanned', items: ['modelcontextprotocol-servers.collection'] },
|
|
@@ -18,10 +18,6 @@ export type ShadcnExecutionResult = {
|
|
|
18
18
|
stderr: string;
|
|
19
19
|
};
|
|
20
20
|
export type ShadcnProcessRunner = (invocation: ShadcnInvocation) => Promise<ShadcnExecutionResult>;
|
|
21
|
-
declare function createShadcnEnvironment(sourceEnv?: NodeJS.ProcessEnv): NodeJS.ProcessEnv;
|
|
22
21
|
export declare function createShadcnInvocation(options: ShadcnInvocationOptions): ShadcnInvocation;
|
|
23
22
|
export declare function executeShadcnInvocation(invocation: ShadcnInvocation, runner?: ShadcnProcessRunner): Promise<ShadcnExecutionResult>;
|
|
24
|
-
export declare const testInternals: {
|
|
25
|
-
createShadcnEnvironment: typeof createShadcnEnvironment;
|
|
26
|
-
};
|
|
27
23
|
export {};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { existsSync } from 'node:fs';
|
|
2
2
|
import { spawn } from 'node:child_process';
|
|
3
3
|
import { createRequire } from 'node:module';
|
|
4
|
-
import {
|
|
4
|
+
import { win32 } from 'node:path';
|
|
5
5
|
const SHADCN_PACKAGE_NAME = 'shadcn';
|
|
6
6
|
const SHADCN_PACKAGE_VERSION = '4.7.0';
|
|
7
7
|
const SHADCN_EXECUTABLE = process.execPath;
|
|
@@ -10,12 +10,15 @@ const SHADCN_PROCESS_TIMEOUT_MS = 600_000;
|
|
|
10
10
|
const SHADCN_OUTPUT_LIMIT_BYTES = 10 * 1024 * 1024;
|
|
11
11
|
const POSITIONAL_ARGUMENT_PREFIX = '-';
|
|
12
12
|
const PRESERVED_ENV_KEYS = ['PATH', 'Path', 'HOME', 'USERPROFILE', 'APPDATA', 'LOCALAPPDATA', 'TEMP', 'TMP', 'SystemRoot', 'WINDIR'];
|
|
13
|
-
function
|
|
14
|
-
const require = createRequire(import.meta.url);
|
|
15
|
-
const binaryPath = require.resolve('shadcn');
|
|
13
|
+
function assertShadcnBinaryExists(binaryPath) {
|
|
16
14
|
if (!existsSync(binaryPath)) {
|
|
17
15
|
throw new Error('Unable to resolve local shadcn binary from shadcn');
|
|
18
16
|
}
|
|
17
|
+
}
|
|
18
|
+
function resolveShadcnBinaryPath() {
|
|
19
|
+
const require = createRequire(import.meta.url);
|
|
20
|
+
const binaryPath = require.resolve('shadcn');
|
|
21
|
+
assertShadcnBinaryExists(binaryPath);
|
|
19
22
|
return binaryPath;
|
|
20
23
|
}
|
|
21
24
|
function assertShadcnArgs(args) {
|
|
@@ -43,24 +46,36 @@ function assertOutputLimit(currentSize, chunkSize) {
|
|
|
43
46
|
}
|
|
44
47
|
return nextSize;
|
|
45
48
|
}
|
|
46
|
-
function
|
|
49
|
+
function getWindowsTaskkillPath(fileExists = existsSync) {
|
|
50
|
+
const candidates = [
|
|
51
|
+
win32.join('C:\\Windows', 'System32', 'taskkill.exe'),
|
|
52
|
+
win32.join('C:\\WINNT', 'System32', 'taskkill.exe')
|
|
53
|
+
];
|
|
54
|
+
return candidates.find((candidate) => fileExists(candidate)) ?? null;
|
|
55
|
+
}
|
|
56
|
+
function terminateShadcnProcess(childProcess, platform = process.platform, killProcess = process.kill, spawnProcess = spawn, taskkillPath = getWindowsTaskkillPath()) {
|
|
47
57
|
if (childProcess.pid === undefined) {
|
|
48
58
|
childProcess.kill();
|
|
49
59
|
return;
|
|
50
60
|
}
|
|
51
|
-
if (
|
|
52
|
-
|
|
53
|
-
|
|
61
|
+
if (platform === 'win32') {
|
|
62
|
+
if (!taskkillPath) {
|
|
63
|
+
childProcess.kill();
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
const killerProcess = spawnProcess(taskkillPath, ['/pid', String(childProcess.pid), '/T', '/F'], { shell: false, stdio: 'ignore' });
|
|
67
|
+
killerProcess.on('error', () => childProcess.kill());
|
|
68
|
+
killerProcess.unref();
|
|
54
69
|
return;
|
|
55
70
|
}
|
|
56
71
|
try {
|
|
57
|
-
|
|
72
|
+
killProcess(-childProcess.pid, 'SIGTERM');
|
|
58
73
|
}
|
|
59
74
|
catch {
|
|
60
75
|
childProcess.kill('SIGTERM');
|
|
61
76
|
}
|
|
62
77
|
}
|
|
63
|
-
function
|
|
78
|
+
function runShadcnProcess(invocation, timeoutMs = SHADCN_PROCESS_TIMEOUT_MS) {
|
|
64
79
|
return new Promise((resolveResult, reject) => {
|
|
65
80
|
const childProcess = spawn(invocation.executable, invocation.args, {
|
|
66
81
|
cwd: invocation.cwd,
|
|
@@ -70,8 +85,8 @@ function defaultShadcnProcessRunner(invocation) {
|
|
|
70
85
|
});
|
|
71
86
|
const timeout = setTimeout(() => {
|
|
72
87
|
terminateShadcnProcess(childProcess);
|
|
73
|
-
reject(new Error(`shadcn process timed out after ${
|
|
74
|
-
},
|
|
88
|
+
reject(new Error(`shadcn process timed out after ${timeoutMs}ms`));
|
|
89
|
+
}, timeoutMs);
|
|
75
90
|
const stdoutChunks = [];
|
|
76
91
|
const stderrChunks = [];
|
|
77
92
|
let stdoutSize = 0;
|
|
@@ -110,6 +125,9 @@ function defaultShadcnProcessRunner(invocation) {
|
|
|
110
125
|
});
|
|
111
126
|
});
|
|
112
127
|
}
|
|
128
|
+
function defaultShadcnProcessRunner(invocation) {
|
|
129
|
+
return runShadcnProcess(invocation);
|
|
130
|
+
}
|
|
113
131
|
export function createShadcnInvocation(options) {
|
|
114
132
|
assertShadcnArgs(options.args);
|
|
115
133
|
return {
|
|
@@ -123,6 +141,3 @@ export function createShadcnInvocation(options) {
|
|
|
123
141
|
export async function executeShadcnInvocation(invocation, runner = defaultShadcnProcessRunner) {
|
|
124
142
|
return runner(invocation);
|
|
125
143
|
}
|
|
126
|
-
export const testInternals = {
|
|
127
|
-
createShadcnEnvironment
|
|
128
|
-
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const CLI_VERSION = "1.0.
|
|
1
|
+
export declare const CLI_VERSION = "1.0.12";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export const CLI_VERSION = "1.0.
|
|
1
|
+
export const CLI_VERSION = "1.0.12";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "peaks-cli",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.12",
|
|
4
4
|
"description": "Peaks CLI and short skill family for Claude Code automation.",
|
|
5
5
|
"author": "SquabbyZ",
|
|
6
6
|
"license": "MIT",
|
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
"scripts/sync-version.mjs",
|
|
22
22
|
"scripts/install-skills.mjs",
|
|
23
23
|
"scripts/watch.mjs",
|
|
24
|
+
"scripts/strip-internal-exports.mjs",
|
|
24
25
|
"skills/**",
|
|
25
26
|
"!skills/**/test-prompts.json",
|
|
26
27
|
"!skills/**/.DS_Store",
|
|
@@ -28,7 +29,7 @@
|
|
|
28
29
|
"schemas/*.json"
|
|
29
30
|
],
|
|
30
31
|
"scripts": {
|
|
31
|
-
"build": "node ./scripts/sync-version.mjs && node ./scripts/clean-dist.mjs && tsc -p tsconfig.json",
|
|
32
|
+
"build": "node ./scripts/sync-version.mjs && node ./scripts/clean-dist.mjs && tsc -p tsconfig.json && node ./scripts/strip-internal-exports.mjs",
|
|
32
33
|
"prepack": "npm run build",
|
|
33
34
|
"postinstall": "node ./scripts/install-skills.mjs",
|
|
34
35
|
"dev": "tsx src/cli/index.ts",
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { readdirSync, readFileSync, statSync, writeFileSync } from 'node:fs';
|
|
3
|
+
import { join, resolve } from 'node:path';
|
|
4
|
+
import { dirname } from 'node:path';
|
|
5
|
+
import { fileURLToPath } from 'node:url';
|
|
6
|
+
|
|
7
|
+
const packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..');
|
|
8
|
+
const distSrcRoot = join(packageRoot, 'dist', 'src');
|
|
9
|
+
const internalExportPattern = /\/\*\* @internal \*\/\r?\nexport const testInternals = \{[\s\S]*?\r?\n\};\r?\n?/g;
|
|
10
|
+
|
|
11
|
+
function getJavaScriptFiles(directory) {
|
|
12
|
+
return readdirSync(directory).flatMap((entry) => {
|
|
13
|
+
const path = join(directory, entry);
|
|
14
|
+
const stats = statSync(path);
|
|
15
|
+
if (stats.isDirectory()) {
|
|
16
|
+
return getJavaScriptFiles(path);
|
|
17
|
+
}
|
|
18
|
+
return stats.isFile() && path.endsWith('.js') ? [path] : [];
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
for (const filePath of getJavaScriptFiles(distSrcRoot)) {
|
|
23
|
+
const source = readFileSync(filePath, 'utf8');
|
|
24
|
+
if (!source.includes('testInternals')) {
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const strippedSource = source.replace(internalExportPattern, '');
|
|
29
|
+
if (strippedSource === source || strippedSource.includes('testInternals')) {
|
|
30
|
+
throw new Error(`Failed to strip internal test export from ${filePath}`);
|
|
31
|
+
}
|
|
32
|
+
writeFileSync(filePath, strippedSource, 'utf8');
|
|
33
|
+
}
|
package/skills/peaks-qa/SKILL.md
CHANGED
|
@@ -55,17 +55,17 @@ QA cannot pass a change until the report contains evidence for every applicable
|
|
|
55
55
|
|
|
56
56
|
1. **Unit tests** — run the project test command or a focused test command that covers new/changed code. For legacy projects below the target coverage, require coverage for the new or changed code rather than failing on pre-existing uncovered code.
|
|
57
57
|
2. **API validation** — when the change touches API contracts, data loading, request handling, auth, or integrations, exercise the relevant API path and record request/response evidence or a justified local substitute.
|
|
58
|
-
3. **Frontend browser validation** — when the repository has a frontend or the change affects UI, launch the app and use headed `gstack/browse/dist/browse` for real browser end-to-end validation. Verify the visible browser with `browse status`, screenshot evidence, or user confirmation. If login, CAPTCHA, SSO, or MFA appears, wait for the user to complete login and explicitly confirm completion before continuing. Capture sanitized route/actions, sanitized screenshots or observations, sanitized console/network failures, and acceptance result.
|
|
58
|
+
3. **Frontend browser validation** — when the repository has a frontend or the change affects UI, launch the app with the project's actual dev/preview command, capture the actual advertised reachable URL from server output/config/user input, and use headed `gstack/browse/dist/browse` against that exact URL for real browser end-to-end validation. Never assume framework default ports such as 3000, 5173, 4200, 4321, or 8080. Verify the visible browser with `browse status`, screenshot evidence, or user confirmation. If login, CAPTCHA, SSO, or MFA appears, wait for the user to complete login and explicitly confirm completion before continuing. Capture sanitized actual URL origin/path, route/actions, sanitized screenshots or observations, sanitized console/network failures, and acceptance result.
|
|
59
59
|
4. **Browser-error feedback loop** — if `gstack/browse/dist/browse` shows a page error, console exception, broken network request, hydration/render failure, or visible regression, return the work to RD/development with the exact evidence. Do not pass QA until the fixed build is retested in the browser.
|
|
60
60
|
5. **Security check** — run security review for the changed surface and dependency/config changes. Record findings, fixes, and unresolved risks.
|
|
61
61
|
6. **Performance check** — run the project’s available performance check, build-size check, Lighthouse-equivalent check, or browser performance inspection appropriate to the change. Record baseline/after numbers when available.
|
|
62
62
|
7. **Validation report** — write or link a report containing scope, environment, commands, sanitized browser evidence, security/performance results, pass/fail summary, residual risks, and next action.
|
|
63
63
|
|
|
64
|
-
If headed `gstack/browse/dist/browse` is unavailable, mark the gate blocked with the missing capability. Screenshots, logs, manual steps, or other tools must not substitute for the mandatory frontend browser gate. Do not silently downgrade frontend validation to API-only testing.
|
|
64
|
+
If headed `gstack/browse/dist/browse` is unavailable, unstable, closes unexpectedly, or cannot verify a visible browser, mark the gate blocked with the missing/unstable capability. Screenshots, logs, manual steps, Playwright MCP, or other tools must not substitute for the mandatory frontend browser gate. Do not silently downgrade frontend validation to API-only testing.
|
|
65
65
|
|
|
66
66
|
## Local intermediate artifacts
|
|
67
67
|
|
|
68
|
-
QA reports, sanitized browser evidence, logs, matrices, and validation summaries should be written
|
|
68
|
+
QA reports, sanitized browser evidence, logs, matrices, and validation summaries should be written under the Peaks CLI-provided `artifactWorkspacePath` at `.peaks/<session-id>/qa/` by default. Do not store login URLs, cookies, headers, tokens, storage state, browser traces, or screenshots/logs containing PII or SSO/MFA material. Do not default to target-repo `.peaks` storage, git-backed storage, or external artifact sync unless the CLI returned that location or the user/active profile explicitly authorizes it.
|
|
69
69
|
|
|
70
70
|
## Compact handoff
|
|
71
71
|
|
|
@@ -2,6 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
This reference documents artifact-contracts.md for peaks-qa.
|
|
4
4
|
|
|
5
|
-
Default
|
|
5
|
+
Default runtime artifact path: `<artifactWorkspacePath>/.peaks/<session-id>/qa/`, where `artifactWorkspacePath` comes from `peaks config workspace ensure --path <project> --json`.
|
|
6
6
|
|
|
7
|
-
QA artifacts should include regression matrices, API evidence, headed `gstack/browse/dist/browse` E2E evidence, sanitized console/network observations, sanitized screenshots or observations, security/performance checks, validation report, residual risks, and blocked/final handoff capsules. Do not retain login URLs, cookies, headers, tokens, storage state, browser traces, or screenshots/logs containing PII or SSO/MFA material. Keep artifacts
|
|
7
|
+
QA artifacts should include regression matrices, API evidence, headed `gstack/browse/dist/browse` E2E evidence against the actual launched app URL, sanitized console/network observations, sanitized screenshots or observations, security/performance checks, validation report, residual risks, and blocked/final handoff capsules. Do not retain login URLs, cookies, headers, tokens, storage state, browser traces, or screenshots/logs containing PII or SSO/MFA material. Keep runtime artifacts in the configured Peaks artifact workspace by default. Do not commit or sync them unless explicitly authorized.
|
|
@@ -9,7 +9,7 @@ QA must be involved before refactor implementation.
|
|
|
9
9
|
- baseline report;
|
|
10
10
|
- acceptance checks;
|
|
11
11
|
- API validation evidence when API behavior is in scope;
|
|
12
|
-
- headed `gstack/browse/dist/browse` browser E2E evidence when a frontend exists or UI is in scope, with mandatory visible-browser confirmation;
|
|
12
|
+
- headed `gstack/browse/dist/browse` browser E2E evidence when a frontend exists or UI is in scope, using the actual URL advertised by the launched app rather than default framework ports, with mandatory visible-browser confirmation;
|
|
13
13
|
- security check evidence;
|
|
14
14
|
- performance check evidence;
|
|
15
15
|
- validation report;
|
|
@@ -21,4 +21,4 @@ UT coverage below 95%, missing coverage, or unverifiable coverage blocks refacto
|
|
|
21
21
|
|
|
22
22
|
## Frontend failure rule
|
|
23
23
|
|
|
24
|
-
If browser validation shows page errors, console exceptions, failed critical network requests, or visible regressions, QA returns the change to RD with evidence and reruns the browser path after the fix.
|
|
24
|
+
If browser validation shows page errors, console exceptions, failed critical network requests, or visible regressions, QA returns the change to RD with evidence and reruns the browser path after the fix. If headed gstack browse is unavailable, unstable, closes unexpectedly, or cannot confirm a visible browser, QA blocks instead of falling back to Playwright MCP, screenshots-only evidence, or default-port probing.
|
package/skills/peaks-rd/SKILL.md
CHANGED
|
@@ -65,7 +65,7 @@ RD cannot mark a development slice complete until all of these are true:
|
|
|
65
65
|
1. OpenSpec change artifacts exist and are linked for non-trivial work when the target repo already has `openspec/`, or the user has approved adding it;
|
|
66
66
|
2. unit tests covering the new or changed behavior have been added or updated and run successfully;
|
|
67
67
|
3. if the repository is legacy and total UT coverage is below the project target, do not block on historical coverage, but require coverage evidence for newly added or changed code;
|
|
68
|
-
4. for frontend or UI-affecting slices, RD self-test has launched the app and used headed `gstack/browse/dist/browse` for real browser end-to-end validation with visible-browser confirmation, sanitized route/actions, sanitized console/network observations, and acceptance result recorded; if login, CAPTCHA, SSO, or MFA appears, wait for the user to complete login and explicitly confirm completion before continuing;
|
|
68
|
+
4. for frontend or UI-affecting slices, RD self-test has launched the app with the project's actual dev/preview command, captured the actual advertised reachable URL from server output/config/user input, and used headed `gstack/browse/dist/browse` against that exact URL for real browser end-to-end validation with visible-browser confirmation, sanitized route/actions, sanitized console/network observations, and acceptance result recorded; never assume framework default ports such as 3000, 5173, 4200, 4321, or 8080; if `gstack/browse/dist/browse` is unavailable, unstable, closes unexpectedly, or cannot verify a visible browser, mark RD self-test blocked instead of falling back to Playwright MCP, screenshots-only evidence, or default-port probing; if login, CAPTCHA, SSO, or MFA appears, wait for the user to complete login and explicitly confirm completion before continuing;
|
|
69
69
|
5. code review has been performed with findings recorded and CRITICAL/HIGH issues fixed before progression; unresolved CRITICAL/HIGH findings only allow a blocked handoff;
|
|
70
70
|
6. security review has been performed for the changed surface, with CRITICAL/HIGH issues fixed before progression and particular attention to user input, file system access, external calls, auth, secrets, and dependency changes;
|
|
71
71
|
7. the post-check dry-run has passed and is linked in the handoff.
|
|
@@ -84,7 +84,7 @@ If a request is refactor, cleanup, architecture adjustment, module split, or tec
|
|
|
84
84
|
6. call or consume peaks-prd and peaks-qa artifacts even in direct RD mode;
|
|
85
85
|
7. require strict slice spec before each slice;
|
|
86
86
|
8. require 100% acceptance for the slice;
|
|
87
|
-
9. require code changes and intermediate artifacts to be traceable
|
|
87
|
+
9. require code changes and intermediate artifacts to be traceable under the Peaks CLI-provided `artifactWorkspacePath` at `.peaks/<session-id>/` before continuing; commit or sync artifacts only when explicitly authorized.
|
|
88
88
|
|
|
89
89
|
## OpenSpec usage
|
|
90
90
|
|
|
@@ -110,7 +110,7 @@ Application projects generated through this skill must not contain JavaScript so
|
|
|
110
110
|
|
|
111
111
|
## Artifact and standards output
|
|
112
112
|
|
|
113
|
-
When project identification or scanning produces reports, matrices, maps, plans, or validation files, write them under the configured Peaks artifact workspace
|
|
113
|
+
When project identification or scanning produces reports, matrices, maps, plans, or validation files, write them under the configured Peaks artifact workspace returned by `peaks config workspace ensure --path <project> --json`. By default, use local non-git storage at `<artifactWorkspacePath>/.peaks/<session-id>/rd/`. If the artifact workspace is unknown, run/request `peaks config workspace ensure` before writing generated outputs. Use one session directory consistently so generated outputs stay grouped.
|
|
114
114
|
|
|
115
115
|
Do not default to a git-backed artifact repository, external artifact sync, or automatic commits for intermediate artifacts. Git inclusion or sync requires explicit user confirmation or an active profile that clearly authorizes it. Browser evidence must be sanitized before retention: do not store login URLs, cookies, headers, tokens, storage state, browser traces, or screenshots/logs containing PII or SSO/MFA material.
|
|
116
116
|
|
|
@@ -2,6 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
This reference documents artifact-contracts.md for peaks-rd.
|
|
4
4
|
|
|
5
|
-
Default
|
|
5
|
+
Default runtime artifact path: `<artifactWorkspacePath>/.peaks/<session-id>/rd/`, where `artifactWorkspacePath` comes from `peaks config workspace ensure --path <project> --json`.
|
|
6
6
|
|
|
7
|
-
RD artifacts should include scan reports, OpenSpec path notes, slice specs, task graphs, coverage evidence, code-review and security-review reports, post-check dry-run output, and handoff capsules. Keep artifacts
|
|
7
|
+
RD artifacts should include scan reports, OpenSpec path notes, slice specs, task graphs, coverage evidence, code-review and security-review reports, post-check dry-run output, and handoff capsules. Keep runtime artifacts in the configured Peaks artifact workspace by default. Do not mix target-repo `.peaks` with the CLI-returned artifact workspace, and do not commit or sync artifacts unless explicitly authorized.
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
- Each implemented slice must pass unit tests, code review, and security review before RD dry-run.
|
|
20
20
|
- The post-check dry-run runs after tests, CR, and security review, not before them.
|
|
21
21
|
- Each slice must pass 100% acceptance.
|
|
22
|
-
- Code changes and sanitized intermediate artifacts must be traceable
|
|
22
|
+
- Code changes and sanitized intermediate artifacts must be traceable under the Peaks CLI-provided `artifactWorkspacePath` at `.peaks/<session-id>/` before the next slice; commit or sync sanitized artifacts only when explicitly authorized. Browser evidence must not retain login URLs, cookies, headers, tokens, storage state, browser traces, or screenshots/logs containing PII or SSO/MFA material.
|
|
23
23
|
|
|
24
24
|
## Required artifacts
|
|
25
25
|
|
|
@@ -36,4 +36,4 @@
|
|
|
36
36
|
- `security-review-report.md`
|
|
37
37
|
- `post-check-dry-run.md`
|
|
38
38
|
- `validation-report.md`
|
|
39
|
-
- `retention-boundary.md` documenting
|
|
39
|
+
- `retention-boundary.md` documenting `artifactWorkspacePath/.peaks/<session-id>/` traceability, browser-evidence sanitization, and any explicitly authorized commit/sync requirement
|
|
@@ -40,13 +40,15 @@ Use gstack as a concrete orchestration reference for the full `Think → Plan
|
|
|
40
40
|
- map `/retro` to Peaks TXT final context and reusable lessons;
|
|
41
41
|
- preserve Peaks confirmation gates, artifact workspace boundaries, and role separation instead of delegating orchestration to gstack commands.
|
|
42
42
|
|
|
43
|
-
For frontend workflows, Peaks Solo must ensure RD self-test and QA validation use headed `gstack/browse/dist/browse` for real browser end-to-end validation. A visible browser opening is mandatory. If login, CAPTCHA, SSO, or MFA appears, wait for the user to complete login and explicitly confirm completion before continuing. If browser validation reports page, console, network, render, or visible UI errors, route the workflow back to RD for fixes before QA can pass.
|
|
43
|
+
For frontend workflows, Peaks Solo must ensure RD self-test and QA validation launch the app with the project's actual dev/preview command, capture the actual advertised reachable URL from server output/config/user input, and use headed `gstack/browse/dist/browse` against that exact URL for real browser end-to-end validation. Never assume framework default ports such as 3000, 5173, 4200, 4321, or 8080. A visible browser opening is mandatory. If `gstack/browse/dist/browse` is unavailable, unstable, closes unexpectedly, or cannot verify a visible browser, mark the browser gate blocked instead of falling back to Playwright MCP, screenshots-only evidence, or default-port probing. If login, CAPTCHA, SSO, or MFA appears, wait for the user to complete login and explicitly confirm completion before continuing. If browser validation reports page, console, network, render, or visible UI errors, route the workflow back to RD for fixes before QA can pass.
|
|
44
44
|
|
|
45
45
|
Browser validation artifacts must be sanitized before retention: do not store login URLs, cookies, headers, tokens, storage state, browser traces, or screenshots/logs containing PII or SSO/MFA material in `.peaks` artifacts, and do not commit or sync sensitive browser evidence.
|
|
46
46
|
|
|
47
47
|
## Local intermediate artifact workspace
|
|
48
48
|
|
|
49
|
-
Peaks Solo
|
|
49
|
+
Before role handoffs, Peaks Solo must run or require `peaks config workspace ensure --path <project> --json` for the target project. This command must ensure the user `~/.peaks/config.json` contains a workspace for the current project and switch `currentWorkspace` to that workspace. Treat the returned `artifactWorkspacePath` as the single runtime artifact root for the workflow.
|
|
50
|
+
|
|
51
|
+
Store PRD/RD/UI/QA/SC/TXT intermediate artifacts under `<artifactWorkspacePath>/.peaks/<session-id>/` by default, with role subdirectories such as `prd/`, `rd/`, `ui/`, `qa/`, `sc/`, and `txt/`. Do not mix target-repo `.peaks/<session-id>/` with the configured artifact workspace unless the CLI explicitly returns a project-local artifact root. Record workspace id, project root, artifactWorkspacePath, session id, and any OpenSpec paths in every role handoff so later stages read and write the same directories.
|
|
50
52
|
|
|
51
53
|
Do not default to a git-backed local artifact repository, external artifact sync, or automatic commits for intermediate artifacts. Only include sanitized `.peaks` artifacts in git, sync them elsewhere, or create external artifact repositories after explicit user confirmation or an active profile that clearly authorizes it.
|
|
52
54
|
|
|
@@ -128,7 +130,7 @@ It must enforce the shared refactor red lines:
|
|
|
128
130
|
4. split broad refactors into minimal functional slices;
|
|
129
131
|
5. require strict verifiable specs before each slice;
|
|
130
132
|
6. require 100% acceptance for each slice;
|
|
131
|
-
7. require code changes and sanitized intermediate artifacts to be traceable
|
|
133
|
+
7. require code changes and sanitized intermediate artifacts to be traceable under the Peaks CLI-provided `artifactWorkspacePath` at `.peaks/<session-id>/` before the next slice; commit or sync sanitized artifacts only when explicitly authorized.
|
|
132
134
|
|
|
133
135
|
## Completion handoff
|
|
134
136
|
|
|
@@ -140,7 +142,7 @@ Use Peaks TXT for the final, blocked, or interrupted handoff capsule. Keep that
|
|
|
140
142
|
|
|
141
143
|
Codegraph is an optional project-analysis enhancement for role handoff. Solo may coordinate `peaks codegraph context --project <path> "<task>"` or `peaks codegraph affected --project <path> <changed-files...> --json` before assigning work to RD, QA, or TXT when shared project evidence would make the handoff narrower.
|
|
142
144
|
|
|
143
|
-
Record useful output in the local Peaks artifact workspace, such as
|
|
145
|
+
Record useful output in the local Peaks artifact workspace, such as `<artifactWorkspacePath>/.peaks/<session-id>/rd/codegraph-context.md` or `<artifactWorkspacePath>/.peaks/<session-id>/rd/codegraph-affected.json`. Treat codegraph output as untrusted supporting evidence. Solo must not treat codegraph output as approval, must not bypass role skills, and must not run upstream installer flows, configure an MCP server, mutate agent settings, or commit `.codegraph/` artifacts.
|
|
144
146
|
|
|
145
147
|
## Optional capabilities
|
|
146
148
|
|
|
@@ -2,6 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
This reference documents artifact-contracts.md for peaks-solo.
|
|
4
4
|
|
|
5
|
-
Default
|
|
5
|
+
Default runtime artifact root: `<artifactWorkspacePath>/.peaks/<session-id>/`, where `artifactWorkspacePath` comes from `peaks config workspace ensure --path <project> --json`, with role subdirectories `prd/`, `rd/`, `ui/`, `qa/`, `sc/`, and `txt/`.
|
|
6
6
|
|
|
7
|
-
Solo coordinates artifact paths and handoff completeness. Keep artifacts
|
|
7
|
+
Solo coordinates artifact paths and handoff completeness. Keep runtime artifacts in the configured Peaks artifact workspace by default. Do not commit, sync, or move them to a git-backed artifact repository unless explicitly authorized.
|
|
@@ -15,9 +15,9 @@
|
|
|
15
15
|
9. Execute one minimal functional slice at a time.
|
|
16
16
|
10. After every RD slice, coordinate `peaks-qa`; if QA reports any failed, blocked, missing, or unverified item, return the report to RD for repair and repeat QA.
|
|
17
17
|
11. Require 100% acceptance for the slice before completion or the next slice.
|
|
18
|
-
12. Coordinate `peaks-sc` for local artifact retention and the
|
|
18
|
+
12. Coordinate `peaks-sc` for local artifact retention and the `<artifactWorkspacePath>/.peaks/<session-id>/sc/retention-boundary.md` boundary.
|
|
19
19
|
13. Exclude login URLs, cookies, headers, tokens, storage state, browser traces, and PII/SSO/MFA screenshots or logs from retained artifacts.
|
|
20
|
-
14. Refuse the next slice until code changes and sanitized intermediate artifacts are traceable
|
|
20
|
+
14. Refuse the next slice until code changes and sanitized intermediate artifacts are traceable under the Peaks CLI-provided `artifactWorkspacePath` at `.peaks/<session-id>/`; commit or sync only after explicit user or profile authorization.
|
|
21
21
|
|
|
22
22
|
## Runtime resources
|
|
23
23
|
|
|
@@ -21,7 +21,7 @@ A code workflow is not complete until Solo has linked or summarized:
|
|
|
21
21
|
6. security-review evidence;
|
|
22
22
|
7. RD post-check dry-run evidence;
|
|
23
23
|
8. QA API validation when applicable;
|
|
24
|
-
9. sanitized QA headed `gstack/browse/dist/browse` browser E2E evidence for frontend projects, with mandatory visible-browser confirmation and without login URLs, cookies, headers, tokens, storage state, browser traces, or PII/SSO/MFA screenshots/logs;
|
|
24
|
+
9. sanitized QA headed `gstack/browse/dist/browse` browser E2E evidence for frontend projects, using the actual URL advertised by the launched app rather than guessed default ports, with mandatory visible-browser confirmation and without login URLs, cookies, headers, tokens, storage state, browser traces, or PII/SSO/MFA screenshots/logs;
|
|
25
25
|
10. QA security, performance, and validation report evidence;
|
|
26
26
|
11. RD repair evidence for every failed, blocked, missing, or unverified QA item;
|
|
27
27
|
12. final QA report showing all acceptance items passed, or a blocked TXT handoff;
|
package/skills/peaks-ui/SKILL.md
CHANGED
|
@@ -27,7 +27,7 @@ Use gstack as a concrete design-review workflow reference for the `Plan → Revi
|
|
|
27
27
|
- map browser walkthrough concepts to UI regression seeds when runtime validation is approved;
|
|
28
28
|
- keep accessibility, performance, and product-specific visual direction as Peaks UI acceptance inputs.
|
|
29
29
|
|
|
30
|
-
For frontend work, especially full-auto mode, use headed `gstack/browse/dist/browse` to inspect the running page or prototype before accepting the UI direction. Verify that a visible browser actually opened. If login, CAPTCHA, SSO, or MFA appears, wait for the user to complete login and explicitly confirm completion before continuing. Capture only sanitized visible regressions, weak hierarchy, generic template patterns, console errors, and interaction problems as UI feedback that should return to design/RD before handing off to QA; do not retain login URLs, cookies, headers, tokens, storage state, browser traces, or screenshots/logs containing PII or SSO/MFA material.
|
|
30
|
+
For frontend work, especially full-auto mode, launch the app with the project's actual dev/preview command, capture the actual advertised reachable URL from server output/config/user input, and use headed `gstack/browse/dist/browse` against that exact URL to inspect the running page or prototype before accepting the UI direction. Never assume framework default ports such as 3000, 5173, 4200, 4321, or 8080. Verify that a visible browser actually opened. If `gstack/browse/dist/browse` is unavailable, unstable, closes unexpectedly, or cannot verify a visible browser, mark UI browser review blocked instead of falling back to Playwright MCP, screenshots-only evidence, or default-port probing. If login, CAPTCHA, SSO, or MFA appears, wait for the user to complete login and explicitly confirm completion before continuing. Capture only sanitized visible regressions, weak hierarchy, generic template patterns, console errors, and interaction problems as UI feedback that should return to design/RD before handing off to QA; do not retain login URLs, cookies, headers, tokens, storage state, browser traces, or screenshots/logs containing PII or SSO/MFA material.
|
|
31
31
|
|
|
32
32
|
## Performance-safe motion references
|
|
33
33
|
|
|
@@ -58,7 +58,7 @@ When Peaks UI is used in full-auto frontend design, default to the curated taste
|
|
|
58
58
|
5. define design dials before generating UI: design variance, motion intensity, visual density, typography pair, palette, interaction feel, motion budget, and reduced-motion behavior;
|
|
59
59
|
6. reject centered stock heroes, default card grids, unmodified shadcn/library defaults, AI purple-blue gradients, generic three-card feature rows, and safe gray-on-white pages without a point of view;
|
|
60
60
|
7. require loading, empty, error, hover, focus, active, responsive, and reduced-motion states for meaningful surfaces;
|
|
61
|
-
8. browser-check the result with headed `gstack/browse/dist/browse`,
|
|
61
|
+
8. browser-check the result by launching the app, using the actual advertised URL with headed `gstack/browse/dist/browse`, waiting for explicit user confirmation after any login challenge, and iterating until the UI looks intentional, memorable, performant, and product-specific.
|
|
62
62
|
|
|
63
63
|
Full-auto Peaks UI output must include a short taste report: visual direction, references used, rejected generic patterns, motion budget, reduced-motion behavior, browser observations, performance evidence, remaining design risks, and the next visual iteration if the page is not yet good enough.
|
|
64
64
|
|
|
@@ -13,7 +13,7 @@ Use this path before generating or accepting frontend UI:
|
|
|
13
13
|
5. Define a motion budget: allowed animated properties, max intensity, reduced-motion behavior, lazy-loading expectations, and performance evidence required.
|
|
14
14
|
6. Reject generic AI UI tells: centered stock hero, uniform card grids, default shadcn/library styling, purple-blue gradients, three equal feature cards, generic placeholder copy, and static-only happy states.
|
|
15
15
|
7. Require meaningful loading, empty, error, hover, focus, active, responsive, and reduced-motion states.
|
|
16
|
-
8.
|
|
16
|
+
8. Launch the app with the project command, capture the actual advertised URL from server output/config/user input, and use headed `gstack/browse/dist/browse` on that URL to inspect real browser output; visible browser confirmation is mandatory, default framework ports must not be guessed, gstack instability blocks the browser gate instead of falling back to Playwright MCP, and login/CAPTCHA/SSO/MFA requires waiting for explicit user confirmation before continuing.
|
|
17
17
|
9. If the browser view looks generic, visually weak, broken, inaccessible, slow, janky, or has console/runtime errors, return to design/RD and iterate before handing off to QA.
|
|
18
18
|
|
|
19
19
|
## Outputs
|