peaks-cli 1.0.3 → 1.0.5

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.
Files changed (40) hide show
  1. package/dist/src/cli/commands/codegraph-commands.d.ts +3 -0
  2. package/dist/src/cli/commands/codegraph-commands.js +99 -0
  3. package/dist/src/cli/commands/sc-commands.js +1 -1
  4. package/dist/src/cli/program.js +2 -0
  5. package/dist/src/services/codegraph/codegraph-service.d.ts +41 -0
  6. package/dist/src/services/codegraph/codegraph-service.js +264 -0
  7. package/dist/src/services/rd/rd-service.js +16 -14
  8. package/dist/src/services/recommendations/capability-seed-items.js +10 -1
  9. package/dist/src/services/recommendations/capability-seed-mappings.js +14 -1
  10. package/dist/src/services/recommendations/capability-seed-sources.js +2 -1
  11. package/dist/src/services/refactor/refactor-service.js +7 -4
  12. package/dist/src/services/sc/sc-service.d.ts +2 -1
  13. package/dist/src/services/sc/sc-service.js +35 -23
  14. package/dist/src/services/tech/tech-service.js +59 -15
  15. package/dist/src/services/workflow/workflow-autonomous-service.js +31 -8
  16. package/dist/src/shared/change-id.js +1 -1
  17. package/dist/src/shared/version.d.ts +1 -1
  18. package/dist/src/shared/version.js +1 -1
  19. package/package.json +3 -1
  20. package/schemas/artifact-retention-report.schema.json +31 -10
  21. package/skills/peaks-prd/SKILL.md +61 -0
  22. package/skills/peaks-prd/references/artifact-contracts.md +4 -0
  23. package/skills/peaks-prd/references/workflow.md +29 -0
  24. package/skills/peaks-qa/SKILL.md +49 -3
  25. package/skills/peaks-qa/references/artifact-contracts.md +4 -0
  26. package/skills/peaks-qa/references/regression-gates.md +9 -1
  27. package/skills/peaks-rd/SKILL.md +58 -6
  28. package/skills/peaks-rd/references/artifact-contracts.md +4 -0
  29. package/skills/peaks-rd/references/refactor-workflow.md +11 -3
  30. package/skills/peaks-sc/SKILL.md +3 -3
  31. package/skills/peaks-sc/references/artifact-retention.md +3 -3
  32. package/skills/peaks-solo/SKILL.md +32 -1
  33. package/skills/peaks-solo/references/artifact-contracts.md +4 -0
  34. package/skills/peaks-solo/references/refactor-mode.md +2 -2
  35. package/skills/peaks-solo/references/workflow.md +18 -0
  36. package/skills/peaks-txt/SKILL.md +33 -1
  37. package/skills/peaks-txt/references/artifact-contracts.md +4 -0
  38. package/skills/peaks-txt/references/context-capsule.md +2 -1
  39. package/skills/peaks-ui/SKILL.md +17 -0
  40. package/skills/peaks-ui/references/workflow.md +17 -1
@@ -0,0 +1,3 @@
1
+ import { Command } from 'commander';
2
+ import { type ProgramIO } from '../cli-helpers.js';
3
+ export declare function registerCodegraphCommands(program: Command, io: ProgramIO): void;
@@ -0,0 +1,99 @@
1
+ import { InvalidArgumentError } from 'commander';
2
+ import { createCodegraphInvocation, executeCodegraphInvocation } from '../../services/codegraph/codegraph-service.js';
3
+ import { fail } from '../../shared/result.js';
4
+ import { getErrorMessage, printResult, redactSensitiveErrorMessage } from '../cli-helpers.js';
5
+ function addPeaksJsonOption(command) {
6
+ return command.option('--peaks-json', 'print Peaks error envelope as machine-readable JSON');
7
+ }
8
+ function addProjectOption(command) {
9
+ return addPeaksJsonOption(command.requiredOption('--project <path>', 'target project root'));
10
+ }
11
+ function parsePositiveInteger(value) {
12
+ if (!/^\d+$/.test(value)) {
13
+ throw new InvalidArgumentError('must be a positive integer');
14
+ }
15
+ const parsed = Number(value);
16
+ if (!Number.isSafeInteger(parsed) || parsed < 1) {
17
+ throw new InvalidArgumentError('must be a positive integer');
18
+ }
19
+ return parsed;
20
+ }
21
+ function printCodegraphFailure(io, command, error, asJson, exitCode = 1) {
22
+ printResult(io, fail(command, 'CODEGRAPH_COMMAND_FAILED', redactSensitiveErrorMessage(getErrorMessage(error)), {}, ['Check the codegraph command options and project path before retrying']), asJson);
23
+ process.exitCode = exitCode;
24
+ }
25
+ async function runCodegraphCommand(io, command, options, asJson) {
26
+ try {
27
+ const invocation = createCodegraphInvocation(options);
28
+ const result = await executeCodegraphInvocation(invocation);
29
+ if (result.exitCode !== null && result.exitCode !== 0 && asJson === true) {
30
+ printCodegraphFailure(io, command, new Error(result.stderr || result.stdout || `codegraph exited with code ${result.exitCode}`), true, result.exitCode);
31
+ return;
32
+ }
33
+ const didFail = result.exitCode !== null && result.exitCode !== 0;
34
+ if (result.stdout.length > 0) {
35
+ io.stdout((didFail ? redactSensitiveErrorMessage(result.stdout) : result.stdout).trimEnd());
36
+ }
37
+ if (result.stderr.length > 0) {
38
+ io.stderr((didFail ? redactSensitiveErrorMessage(result.stderr) : result.stderr).trimEnd());
39
+ }
40
+ if (didFail) {
41
+ process.exitCode = result.exitCode;
42
+ }
43
+ }
44
+ catch (error) {
45
+ printCodegraphFailure(io, command, error, asJson);
46
+ }
47
+ }
48
+ export function registerCodegraphCommands(program, io) {
49
+ const codegraph = program.command('codegraph').description('Run upstream codegraph commands through the Peaks launcher');
50
+ addProjectOption(codegraph.command('status').description('Show codegraph status')).action((options) => runCodegraphCommand(io, 'codegraph.status', { subcommand: 'status', project: options.project }, options.peaksJson));
51
+ addProjectOption(codegraph.command('init').description('Initialize codegraph for a project').option('--yes', 'answer yes to upstream prompts')).action((options) => runCodegraphCommand(io, 'codegraph.init', {
52
+ subcommand: 'init',
53
+ project: options.project,
54
+ ...(options.yes === true ? { yes: true } : {})
55
+ }, options.peaksJson));
56
+ addProjectOption(codegraph
57
+ .command('index')
58
+ .description('Index a project with codegraph')
59
+ .option('--force', 'force reindexing')
60
+ .option('--quiet', 'reduce upstream output')).action((options) => runCodegraphCommand(io, 'codegraph.index', {
61
+ subcommand: 'index',
62
+ project: options.project,
63
+ ...(options.force === true ? { force: true } : {}),
64
+ ...(options.quiet === true ? { quiet: true } : {})
65
+ }, options.peaksJson));
66
+ addProjectOption(codegraph
67
+ .command('query')
68
+ .description('Query codegraph')
69
+ .argument('<search>', 'search text')
70
+ .option('--json', 'forward JSON output flag to upstream codegraph')
71
+ .option('--limit <n>', 'maximum result count', parsePositiveInteger)).action((search, options) => runCodegraphCommand(io, 'codegraph.query', {
72
+ subcommand: 'query',
73
+ project: options.project,
74
+ search,
75
+ ...(options.json === true ? { json: true } : {}),
76
+ ...(options.limit !== undefined ? { limit: options.limit } : {})
77
+ }, options.peaksJson));
78
+ addProjectOption(codegraph
79
+ .command('files')
80
+ .description('List codegraph files')
81
+ .option('--json', 'forward JSON output flag to upstream codegraph')
82
+ .option('--max-depth <n>', 'maximum traversal depth', parsePositiveInteger)).action((options) => runCodegraphCommand(io, 'codegraph.files', {
83
+ subcommand: 'files',
84
+ project: options.project,
85
+ ...(options.json === true ? { json: true } : {}),
86
+ ...(options.maxDepth !== undefined ? { maxDepth: options.maxDepth } : {})
87
+ }, options.peaksJson));
88
+ addProjectOption(codegraph.command('context').description('Build task context with codegraph').argument('<task>', 'task text')).action((task, options) => runCodegraphCommand(io, 'codegraph.context', { subcommand: 'context', project: options.project, task }, options.peaksJson));
89
+ addProjectOption(codegraph
90
+ .command('affected')
91
+ .description('Find code affected by files')
92
+ .argument('<files...>', 'project-relative file paths')
93
+ .option('--json', 'forward JSON output flag to upstream codegraph')).action((files, options) => runCodegraphCommand(io, 'codegraph.affected', {
94
+ subcommand: 'affected',
95
+ project: options.project,
96
+ files,
97
+ ...(options.json === true ? { json: true } : {})
98
+ }, options.peaksJson));
99
+ }
@@ -31,7 +31,7 @@ function registerSCArtifactCommands(sc, io) {
31
31
  addJsonOption(sc.command('validate').description('Validate artifact retention for a slice').requiredOption('--slice-id <id>', 'slice identifier')).action((options) => {
32
32
  printResult(io, ok('sc.validate', validateArtifactRetention(options.sliceId)), options.json);
33
33
  });
34
- addJsonOption(sc.command('boundary').description('Record commit boundary for a slice').requiredOption('--slice-id <id>', 'slice identifier').option('--artifact <path>', 'artifact path', multipleOption).option('--code <file>', 'code file path', multipleOption)).action((options) => {
34
+ addJsonOption(sc.command('boundary').description('Record retention boundary for a slice').requiredOption('--slice-id <id>', 'slice identifier').option('--artifact <path>', 'artifact path', multipleOption).option('--code <file>', 'code file path', multipleOption)).action((options) => {
35
35
  printResult(io, ok('sc.boundary', recordCommitBoundary({ sliceId: options.sliceId, ...(options.artifact ? { artifacts: options.artifact } : {}), ...(options.code ? { codeFiles: options.code } : {}) })), options.json);
36
36
  });
37
37
  }
@@ -3,6 +3,7 @@ import { CLI_VERSION } from '../shared/version.js';
3
3
  import { registerCoreAndArtifactCommands } from './commands/core-artifact-commands.js';
4
4
  import { registerWorkflowCommands } from './commands/workflow-commands.js';
5
5
  import { registerCapabilityWorkerConfigAndSCCommands } from './commands/capability-worker-config-sc-commands.js';
6
+ import { registerCodegraphCommands } from './commands/codegraph-commands.js';
6
7
  export { printResult } from './cli-helpers.js';
7
8
  export function createProgram(io = { stdout: (text) => console.log(text), stderr: (text) => console.error(text) }) {
8
9
  const program = new Command();
@@ -24,5 +25,6 @@ export function createProgram(io = { stdout: (text) => console.log(text), stderr
24
25
  registerCoreAndArtifactCommands(program, io);
25
26
  registerWorkflowCommands(program, io);
26
27
  registerCapabilityWorkerConfigAndSCCommands(program, io);
28
+ registerCodegraphCommands(program, io);
27
29
  return program;
28
30
  }
@@ -0,0 +1,41 @@
1
+ declare const CODEGRAPH_PACKAGE_NAME = "@colbymchenry/codegraph@0.7.10";
2
+ declare const CODEGRAPH_EXECUTABLE: string;
3
+ declare const ALLOWED_SUBCOMMANDS: readonly ["status", "init", "index", "query", "files", "context", "affected"];
4
+ type CodegraphSubcommand = (typeof ALLOWED_SUBCOMMANDS)[number];
5
+ type BaseCodegraphInvocationOptions = {
6
+ subcommand: CodegraphSubcommand;
7
+ project: string;
8
+ search?: string;
9
+ files?: string[];
10
+ json?: boolean;
11
+ quiet?: boolean;
12
+ yes?: boolean;
13
+ force?: boolean;
14
+ limit?: number;
15
+ maxDepth?: number;
16
+ };
17
+ type ContextCodegraphInvocationOptions = Omit<BaseCodegraphInvocationOptions, 'subcommand'> & {
18
+ subcommand: 'context';
19
+ task: string;
20
+ };
21
+ type NonContextCodegraphInvocationOptions = BaseCodegraphInvocationOptions & {
22
+ subcommand: Exclude<CodegraphSubcommand, 'context'>;
23
+ task?: never;
24
+ };
25
+ export type CodegraphInvocationOptions = ContextCodegraphInvocationOptions | NonContextCodegraphInvocationOptions;
26
+ export type CodegraphInvocation = {
27
+ executable: typeof CODEGRAPH_EXECUTABLE;
28
+ args: string[];
29
+ cwd: string;
30
+ packageName: typeof CODEGRAPH_PACKAGE_NAME;
31
+ subcommand: CodegraphSubcommand;
32
+ };
33
+ export type CodegraphExecutionResult = {
34
+ exitCode: number | null;
35
+ stdout: string;
36
+ stderr: string;
37
+ };
38
+ export type CodegraphProcessRunner = (invocation: CodegraphInvocation) => Promise<CodegraphExecutionResult>;
39
+ export declare function createCodegraphInvocation(options: CodegraphInvocationOptions): CodegraphInvocation;
40
+ export declare function executeCodegraphInvocation(invocation: CodegraphInvocation, runner?: CodegraphProcessRunner): Promise<CodegraphExecutionResult>;
41
+ export {};
@@ -0,0 +1,264 @@
1
+ import { existsSync, realpathSync, statSync } from 'node:fs';
2
+ import { spawn } from 'node:child_process';
3
+ import { createRequire } from 'node:module';
4
+ import { dirname, isAbsolute, join, relative, resolve, sep } from 'node:path';
5
+ const CODEGRAPH_PACKAGE_NAME = '@colbymchenry/codegraph@0.7.10';
6
+ const CODEGRAPH_EXECUTABLE = process.execPath;
7
+ const CODEGRAPH_NPX_CLI_PATH = resolveNpxCliPath();
8
+ const CODEGRAPH_BINARY_NAME = 'codegraph';
9
+ const CODEGRAPH_PROCESS_TIMEOUT_MS = 600_000;
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
+ const POSITIONAL_ARGUMENT_PREFIX = '-';
15
+ const ALLOWED_SUBCOMMANDS = ['status', 'init', 'index', 'query', 'files', 'context', 'affected'];
16
+ const NUMERIC_FLAG_NAMES = ['limit', 'maxDepth'];
17
+ const COMMON_OPTION_KEYS = ['subcommand', 'project'];
18
+ const ALLOWED_OPTIONS_BY_SUBCOMMAND = {
19
+ status: [],
20
+ init: ['yes'],
21
+ index: ['force', 'quiet'],
22
+ query: ['search', 'json', 'limit'],
23
+ files: ['json', 'maxDepth'],
24
+ context: ['task'],
25
+ affected: ['files', 'json']
26
+ };
27
+ function resolveNpxCliPath() {
28
+ const require = createRequire(import.meta.url);
29
+ try {
30
+ return require.resolve('npm/bin/npx-cli.js');
31
+ }
32
+ catch {
33
+ const nodeBinDirectory = dirname(process.execPath);
34
+ const candidatePaths = [
35
+ join(nodeBinDirectory, 'node_modules', 'npm', 'bin', 'npx-cli.js'),
36
+ resolve(nodeBinDirectory, '..', 'lib', 'node_modules', 'npm', 'bin', 'npx-cli.js')
37
+ ];
38
+ const npxCliPath = candidatePaths.find((candidatePath) => existsSync(candidatePath));
39
+ if (npxCliPath === undefined) {
40
+ throw new Error('Unable to resolve npm npx-cli.js from the current Node installation');
41
+ }
42
+ return npxCliPath;
43
+ }
44
+ }
45
+ function assertSupportedSubcommand(subcommand) {
46
+ if (!ALLOWED_SUBCOMMANDS.includes(subcommand)) {
47
+ throw new Error(`Unsupported codegraph subcommand: ${subcommand}`);
48
+ }
49
+ }
50
+ function resolveProjectRoot(project) {
51
+ const projectRoot = resolve(project);
52
+ try {
53
+ if (!statSync(projectRoot).isDirectory()) {
54
+ throw new Error('Project path must exist and be a directory');
55
+ }
56
+ return realpathSync.native(projectRoot);
57
+ }
58
+ catch {
59
+ throw new Error('Project path must exist and be a directory');
60
+ }
61
+ }
62
+ function assertPositiveInteger(value, flagName) {
63
+ if (value === undefined) {
64
+ return;
65
+ }
66
+ if (!Number.isInteger(value) || value < 1) {
67
+ throw new Error(`${flagName} must be a positive integer`);
68
+ }
69
+ }
70
+ function assertPositionalArgument(value, argumentName) {
71
+ if (value.startsWith(POSITIONAL_ARGUMENT_PREFIX)) {
72
+ throw new Error(`${argumentName} must not start with -`);
73
+ }
74
+ }
75
+ function assertSupportedOptions(options) {
76
+ const allowedOptions = new Set(ALLOWED_OPTIONS_BY_SUBCOMMAND[options.subcommand]);
77
+ const presentOptionKeys = Object.keys(options).filter((key) => !COMMON_OPTION_KEYS.includes(key));
78
+ const unsupportedOption = presentOptionKeys.find((key) => !allowedOptions.has(key));
79
+ if (unsupportedOption) {
80
+ throw new Error(`Unsupported option ${unsupportedOption} for codegraph ${options.subcommand}`);
81
+ }
82
+ }
83
+ function assertRequiredOptions(options) {
84
+ if (options.subcommand === 'query' && (!options.search || options.search.trim() === '')) {
85
+ throw new Error('search must be non-empty');
86
+ }
87
+ if (options.subcommand === 'query' && options.search) {
88
+ assertPositionalArgument(options.search, 'search');
89
+ }
90
+ if (options.subcommand === 'context') {
91
+ assertPositionalArgument(options.task, 'task');
92
+ }
93
+ }
94
+ function assertInsideProject(projectRoot, absolutePath) {
95
+ const relativePath = relative(projectRoot, absolutePath);
96
+ if (relativePath === '' || relativePath.startsWith('..') || isAbsolute(relativePath)) {
97
+ throw new Error('Affected files must stay inside the project');
98
+ }
99
+ }
100
+ function resolveExistingBoundary(absoluteFilePath) {
101
+ if (existsSync(absoluteFilePath)) {
102
+ return absoluteFilePath;
103
+ }
104
+ let currentPath = dirname(absoluteFilePath);
105
+ while (!existsSync(currentPath)) {
106
+ const parentPath = dirname(currentPath);
107
+ if (parentPath === currentPath) {
108
+ return currentPath;
109
+ }
110
+ currentPath = parentPath;
111
+ }
112
+ return currentPath;
113
+ }
114
+ function normalizeProjectRelativeFile(projectRoot, file) {
115
+ assertPositionalArgument(file, 'Affected files');
116
+ const absoluteFilePath = resolve(projectRoot, file);
117
+ assertInsideProject(projectRoot, absoluteFilePath);
118
+ const realBoundary = realpathSync.native(resolveExistingBoundary(absoluteFilePath));
119
+ assertInsideProject(projectRoot, realBoundary);
120
+ return relative(projectRoot, absoluteFilePath).split(sep).join('/');
121
+ }
122
+ function buildAffectedFileArgs(projectRoot, files) {
123
+ if (!files || files.length < 1) {
124
+ throw new Error('affected requires at least one file');
125
+ }
126
+ return files.map((file) => normalizeProjectRelativeFile(projectRoot, file));
127
+ }
128
+ function buildCommandArgs(options, projectRoot) {
129
+ const args = [CODEGRAPH_NPX_CLI_PATH, '--no', '--package', CODEGRAPH_PACKAGE_NAME, CODEGRAPH_BINARY_NAME, options.subcommand];
130
+ if (options.subcommand === 'query' && options.search) {
131
+ args.push(options.search);
132
+ }
133
+ if (options.subcommand === 'context') {
134
+ if (options.task.trim() === '') {
135
+ throw new Error('task must be non-empty');
136
+ }
137
+ args.push(options.task);
138
+ }
139
+ if (options.subcommand === 'affected') {
140
+ args.push(...buildAffectedFileArgs(projectRoot, options.files));
141
+ }
142
+ if (options.yes === true) {
143
+ args.push('--yes');
144
+ }
145
+ if (options.force === true) {
146
+ args.push('--force');
147
+ }
148
+ if (options.json === true) {
149
+ args.push('--json');
150
+ }
151
+ if (options.quiet === true) {
152
+ args.push('--quiet');
153
+ }
154
+ if (options.limit !== undefined) {
155
+ args.push('--limit', String(options.limit));
156
+ }
157
+ if (options.maxDepth !== undefined) {
158
+ args.push('--max-depth', String(options.maxDepth));
159
+ }
160
+ return args;
161
+ }
162
+ function createCodegraphEnvironment(sourceEnv = process.env) {
163
+ const preservedKeys = ['PATH', 'Path', 'HOME', 'USERPROFILE', 'APPDATA', 'LOCALAPPDATA', 'TEMP', 'TMP', 'SystemRoot', 'WINDIR'];
164
+ const environment = {};
165
+ for (const key of preservedKeys) {
166
+ const value = sourceEnv[key];
167
+ if (value !== undefined) {
168
+ environment[key] = value;
169
+ }
170
+ }
171
+ return environment;
172
+ }
173
+ function assertOutputLimit(currentSize, chunkSize) {
174
+ const nextSize = currentSize + chunkSize;
175
+ if (nextSize > CODEGRAPH_OUTPUT_LIMIT_BYTES) {
176
+ throw new Error(`codegraph output exceeded ${CODEGRAPH_OUTPUT_LIMIT_BYTES} bytes`);
177
+ }
178
+ return nextSize;
179
+ }
180
+ function terminateCodegraphProcess(childProcess) {
181
+ if (childProcess.pid === undefined) {
182
+ childProcess.kill();
183
+ return;
184
+ }
185
+ if (process.platform === 'win32') {
186
+ const taskkillPath = process.env.SystemRoot ? join(process.env.SystemRoot, 'System32', 'taskkill.exe') : 'taskkill.exe';
187
+ spawn(taskkillPath, ['/pid', String(childProcess.pid), '/T', '/F'], { shell: false, stdio: 'ignore' });
188
+ return;
189
+ }
190
+ try {
191
+ process.kill(-childProcess.pid, 'SIGTERM');
192
+ }
193
+ catch {
194
+ childProcess.kill('SIGTERM');
195
+ }
196
+ }
197
+ function defaultCodegraphProcessRunner(invocation) {
198
+ return new Promise((resolveResult, reject) => {
199
+ const childProcess = spawn(invocation.executable, invocation.args, {
200
+ cwd: invocation.cwd,
201
+ detached: process.platform !== 'win32',
202
+ env: createCodegraphEnvironment(),
203
+ shell: false
204
+ });
205
+ const timeout = setTimeout(() => {
206
+ terminateCodegraphProcess(childProcess);
207
+ reject(new Error(`codegraph process timed out after ${CODEGRAPH_PROCESS_TIMEOUT_MS}ms`));
208
+ }, CODEGRAPH_PROCESS_TIMEOUT_MS);
209
+ const stdoutChunks = [];
210
+ const stderrChunks = [];
211
+ let stdoutSize = 0;
212
+ let stderrSize = 0;
213
+ childProcess.stdout.on('data', (chunk) => {
214
+ try {
215
+ stdoutSize = assertOutputLimit(stdoutSize, chunk.length);
216
+ stdoutChunks.push(chunk);
217
+ }
218
+ catch (error) {
219
+ terminateCodegraphProcess(childProcess);
220
+ reject(error);
221
+ }
222
+ });
223
+ childProcess.stderr.on('data', (chunk) => {
224
+ try {
225
+ stderrSize = assertOutputLimit(stderrSize, chunk.length);
226
+ stderrChunks.push(chunk);
227
+ }
228
+ catch (error) {
229
+ terminateCodegraphProcess(childProcess);
230
+ reject(error);
231
+ }
232
+ });
233
+ childProcess.on('error', (error) => {
234
+ clearTimeout(timeout);
235
+ reject(error);
236
+ });
237
+ childProcess.on('close', (exitCode) => {
238
+ clearTimeout(timeout);
239
+ resolveResult({
240
+ exitCode,
241
+ stdout: Buffer.concat(stdoutChunks).toString('utf8'),
242
+ stderr: Buffer.concat(stderrChunks).toString('utf8')
243
+ });
244
+ });
245
+ });
246
+ }
247
+ export function createCodegraphInvocation(options) {
248
+ assertSupportedSubcommand(options.subcommand);
249
+ const projectRoot = resolveProjectRoot(options.project);
250
+ assertSupportedOptions(options);
251
+ assertRequiredOptions(options);
252
+ assertPositiveInteger(options.limit, 'limit');
253
+ assertPositiveInteger(options.maxDepth, 'maxDepth');
254
+ return {
255
+ executable: CODEGRAPH_EXECUTABLE,
256
+ args: buildCommandArgs(options, projectRoot),
257
+ cwd: projectRoot,
258
+ packageName: CODEGRAPH_PACKAGE_NAME,
259
+ subcommand: options.subcommand
260
+ };
261
+ }
262
+ export async function executeCodegraphInvocation(invocation, runner = defaultCodegraphProcessRunner) {
263
+ return runner(invocation);
264
+ }
@@ -98,7 +98,9 @@ function readArtifactFile(rootPath, artifactWorkspacePath, artifact) {
98
98
  try {
99
99
  const artifactWorkspaceRealPath = stableRealPath(artifactWorkspacePath);
100
100
  const rootRealPath = stableRealPath(rootPath);
101
- if (!isInsidePath(rootRealPath, artifactWorkspaceRealPath)) {
101
+ const rdRootPath = resolve(rootPath, '..');
102
+ const sessionRootPath = resolve(rdRootPath, '..');
103
+ if (lstatSync(sessionRootPath).isSymbolicLink() || lstatSync(rdRootPath).isSymbolicLink() || lstatSync(rootPath).isSymbolicLink() || !isInsidePath(rootRealPath, artifactWorkspaceRealPath)) {
102
104
  return null;
103
105
  }
104
106
  const artifactStat = lstatSync(artifactPath);
@@ -138,7 +140,7 @@ function getConcreteTargetAreas(request, artifactWorkspacePath, hasApprovedTechA
138
140
  if (!artifactWorkspacePath || !hasApprovedTechArtifacts || !hasPlannerArtifactWorkspace(request, artifactWorkspacePath)) {
139
141
  return [];
140
142
  }
141
- const architectureRoot = join(artifactWorkspacePath, '.peaks', 'changes', request.changeId, 'architecture');
143
+ const architectureRoot = join(artifactWorkspacePath, '.peaks', request.changeId, 'rd', 'architecture');
142
144
  const candidates = TECH_REQUIRED_ARTIFACTS.flatMap((artifact) => {
143
145
  if (artifact === 'tech-approval-record.md') {
144
146
  return [];
@@ -155,7 +157,7 @@ function buildPlan(request) {
155
157
  const executionModelId = request.executionModelId?.trim() || getConfiguredExecutionModelId(undefined);
156
158
  const { workerTarget, blockedReasons } = resolveWorkerTarget(request.maxWorkers);
157
159
  const artifactWorkspacePath = resolveArtifactWorkspacePath(request);
158
- const artifactRoot = buildArtifactRelativePath(request.changeId, 'swarm');
160
+ const artifactRoot = buildArtifactRelativePath(request.changeId, 'rd', 'swarm');
159
161
  const techStatus = getTechStatus({
160
162
  changeId: request.changeId,
161
163
  ...(artifactWorkspacePath ? { artifactWorkspacePath } : {}),
@@ -174,10 +176,10 @@ function buildPlan(request) {
174
176
  conflictGroups: [],
175
177
  artifactRoot,
176
178
  outputs: {
177
- taskGraph: buildArtifactRelativePath(request.changeId, 'swarm', 'task-graph.json'),
179
+ taskGraph: buildArtifactRelativePath(request.changeId, 'rd', 'swarm', 'task-graph.json'),
178
180
  waveManifests: [],
179
181
  workerBriefs: [],
180
- reducerReport: buildArtifactRelativePath(request.changeId, 'swarm', 'reducer-report.md'),
182
+ reducerReport: buildArtifactRelativePath(request.changeId, 'rd', 'swarm', 'reducer-report.md'),
181
183
  },
182
184
  gateStatus: {
183
185
  techApprovalRequired: requiresTechApproval,
@@ -199,10 +201,10 @@ function buildPlan(request) {
199
201
  conflictGroups: [],
200
202
  artifactRoot,
201
203
  outputs: {
202
- taskGraph: buildArtifactRelativePath(request.changeId, 'swarm', 'task-graph.json'),
204
+ taskGraph: buildArtifactRelativePath(request.changeId, 'rd', 'swarm', 'task-graph.json'),
203
205
  waveManifests: [],
204
206
  workerBriefs: [],
205
- reducerReport: buildArtifactRelativePath(request.changeId, 'swarm', 'reducer-report.md'),
207
+ reducerReport: buildArtifactRelativePath(request.changeId, 'rd', 'swarm', 'reducer-report.md'),
206
208
  },
207
209
  gateStatus: {
208
210
  techApprovalRequired: true,
@@ -223,10 +225,10 @@ function buildPlan(request) {
223
225
  conflictGroups: [],
224
226
  artifactRoot,
225
227
  outputs: {
226
- taskGraph: buildArtifactRelativePath(request.changeId, 'swarm', 'task-graph.json'),
228
+ taskGraph: buildArtifactRelativePath(request.changeId, 'rd', 'swarm', 'task-graph.json'),
227
229
  waveManifests: [],
228
230
  workerBriefs: [],
229
- reducerReport: buildArtifactRelativePath(request.changeId, 'swarm', 'reducer-report.md'),
231
+ reducerReport: buildArtifactRelativePath(request.changeId, 'rd', 'swarm', 'reducer-report.md'),
230
232
  },
231
233
  gateStatus: {
232
234
  techApprovalRequired: requiresTechApproval,
@@ -263,7 +265,7 @@ function buildPlan(request) {
263
265
  };
264
266
  const tasks = taskIds.map((taskId, index) => {
265
267
  const wave = index < 8 ? 'discovery' : index < 16 ? 'planning' : index < taskIds.length - 8 ? 'implementation candidates' : index < taskIds.length - 5 ? 'unit-test execution' : index < taskIds.length - 1 ? 'quality gates' : 'reducer';
266
- const briefPath = buildArtifactRelativePath(request.changeId, 'swarm', 'workers', taskId, 'brief.md');
268
+ const briefPath = buildArtifactRelativePath(request.changeId, 'rd', 'swarm', 'workers', taskId, 'brief.md');
267
269
  const implementationIndex = index - 16;
268
270
  const targetArea = wave === 'implementation candidates' && hasConcreteTargetAreas(concreteTargetAreas)
269
271
  ? selectConcreteTargetArea(concreteTargetAreas, implementationIndex)
@@ -294,7 +296,7 @@ function buildPlan(request) {
294
296
  });
295
297
  const conflictGroups = waves.map((wave) => ({
296
298
  groupId: `group-${wave.name.replace(/\s+/g, '-')}`,
297
- ownedPaths: wave.taskIds.map((taskId) => buildArtifactRelativePath(request.changeId, 'swarm', 'workers', taskId, 'brief.md')),
299
+ ownedPaths: wave.taskIds.map((taskId) => buildArtifactRelativePath(request.changeId, 'rd', 'swarm', 'workers', taskId, 'brief.md')),
298
300
  parallelismPolicy: wave.taskIds.length > 1 ? 'parallel' : 'sequential',
299
301
  reason: `${wave.name} work is isolated by worker output path`,
300
302
  }));
@@ -308,10 +310,10 @@ function buildPlan(request) {
308
310
  conflictGroups,
309
311
  artifactRoot,
310
312
  outputs: {
311
- taskGraph: buildArtifactRelativePath(request.changeId, 'swarm', 'task-graph.json'),
312
- waveManifests: waves.map((wave, index) => buildArtifactRelativePath(request.changeId, 'swarm', 'waves', `wave-${index + 1}-${wave.name}.json`)),
313
+ taskGraph: buildArtifactRelativePath(request.changeId, 'rd', 'swarm', 'task-graph.json'),
314
+ waveManifests: waves.map((wave, index) => buildArtifactRelativePath(request.changeId, 'rd', 'swarm', 'waves', `wave-${index + 1}-${wave.name}.json`)),
313
315
  workerBriefs: tasks.map((task) => task.outputs[0]),
314
- reducerReport: buildArtifactRelativePath(request.changeId, 'swarm', 'reducer-report.md'),
316
+ reducerReport: buildArtifactRelativePath(request.changeId, 'rd', 'swarm', 'reducer-report.md'),
315
317
  },
316
318
  gateStatus: {
317
319
  techApprovalRequired: requiresTechApproval,
@@ -83,6 +83,10 @@ export const seedCapabilityItems = [
83
83
  fallback: { mode: 'manual-docs-input', qualityImpact: 'lower', nextAction: 'Ask the user to provide the relevant documentation link or pasted excerpt.' },
84
84
  presentation: { displayName: { en: 'Documentation Lookup', 'zh-CN': '文档查询能力' }, description: { en: 'Fetches current library and API documentation for implementation planning.', 'zh-CN': '用于获取当前库和 API 文档,辅助实现规划。' } }
85
85
  },
86
+ capability('codegraph.project-indexing', 'codegraph', 'Codegraph Project Indexing', 'cli', 'project-analysis', ['engineer'], 'medium', 'peaks-rd-local-scan', 'Use Peaks RD local project scanning when codegraph is unavailable.', 'Codegraph Project Indexing', 'Codegraph 项目索引', 'Indexes a local project through the peaks codegraph execution boundary for role-skill analysis.', '通过 peaks codegraph 执行边界索引本地项目,辅助角色 skill 分析。'),
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
+ 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
+ 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 的辅助证据。'),
86
90
  capability('playwright-mcp.browser-validation', 'playwright-mcp', 'Playwright MCP Browser Validation', 'mcp', 'browser-validation', ['engineer', 'qa'], 'medium', 'manual-browser-test', 'Use local Playwright or manual browser verification.', 'Playwright Browser Validation', 'Playwright 浏览器验证', 'Validates UI flows through controlled browser automation.', '通过受控浏览器自动化验证 UI 流程。'),
87
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、控制台、网络和性能行为。'),
88
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 实现规划。'),
@@ -104,7 +108,12 @@ export const seedCapabilityItems = [
104
108
  capability('ruflo-access-repo.workflow-reference', 'ruflo-access-repo', 'Ruflo Workflow Reference', 'doc', 'workflow-reference', ['engineer'], 'medium', 'peaks-autonomous-planning', 'Use Peaks autonomous planning references without executing external code.', 'Workflow Reference', '工作流参考', 'Catalog-only workflow orchestration reference.', '仅作为目录化的工作流编排参考。'),
105
109
  capability('modelcontextprotocol-servers.collection', 'modelcontextprotocol-servers', 'MCP Server Collection', 'doc', 'mcp-collection', ['engineer'], 'medium', 'future-peaks-mcp-catalog', 'Use future Peaks MCP catalog review before selecting individual servers.', 'MCP Collection', 'MCP 集合', 'Catalog-only MCP server collection reference.', '仅作为目录化的 MCP server 集合参考。'),
106
110
  capability('andrej-karpathy-skills.guidance', 'andrej-karpathy-skills', 'Engineering Guidance', 'doc', 'engineering-guidance', ['engineer'], 'low', 'project-local-standards', 'Use project-local standards before external guidance.', 'Engineering Guidance', '工程指导', 'External engineering guidance reference.', '外部工程指导参考。'),
107
- capability('mattpocock-skills.typescript-guidance', 'mattpocock-skills', 'TypeScript Guidance', 'doc', 'typescript-guidance', ['engineer'], 'low', 'project-local-typescript-standards', 'Use project-local TypeScript standards first.', 'TypeScript Guidance', 'TypeScript 指导', 'External TypeScript guidance reference.', '外部 TypeScript 指导参考。'),
111
+ capability('mattpocock-skills.product-prd-methods', 'mattpocock-skills', 'Product PRD Methods', 'skill', 'product-prd-methods', ['product', 'engineer'], 'low', 'peaks-prd', 'Use Peaks PRD artifacts first; inspect upstream to-prd, zoom-out, and grill-with-docs before applying method ideas.', 'Product PRD Methods', '产品 PRD 方法', 'References to-prd, zoom-out, and grill-with-docs for product shaping while Peaks PRD remains authoritative.', '参考 to-prd、zoom-out 和 grill-with-docs 进行产品塑形,Peaks PRD 仍保持权威。'),
112
+ capability('mattpocock-skills.engineering-diagnosis', 'mattpocock-skills', 'Engineering Diagnosis Methods', 'skill', 'engineering-diagnosis', ['engineer'], 'low', 'peaks-rd', 'Use Peaks RD gates first; inspect upstream diagnose, triage, improve-codebase-architecture, and prototype before applying method ideas.', 'Engineering Diagnosis Methods', '工程诊断方法', 'References diagnosis, triage, architecture review, and prototype methods for RD analysis.', '为 RD 分析参考诊断、分流、架构评审和原型方法。'),
113
+ capability('mattpocock-skills.tdd-method', 'mattpocock-skills', 'TDD Method', 'skill', 'tdd-method', ['engineer', 'qa'], 'low', 'peaks-rd-qa-gates', 'Use Peaks RD and QA test gates first; inspect upstream tdd before applying method ideas.', 'TDD Method', 'TDD 方法', 'References tests-first discipline for RD implementation and QA coverage review.', '为 RD 实现和 QA 覆盖评审参考测试先行纪律。'),
114
+ capability('mattpocock-skills.qa-triage', 'mattpocock-skills', 'QA Triage Methods', 'skill', 'qa-triage', ['qa', 'engineer'], 'low', 'peaks-qa', 'Use Peaks QA validation gates first; inspect upstream triage and grill-with-docs before applying method ideas.', 'QA Triage Methods', 'QA 分流方法', 'References failure triage and document-backed acceptance checks for QA review.', '为 QA 评审参考失败分流和文档支撑的验收检查。'),
115
+ capability('mattpocock-skills.handoff-context', 'mattpocock-skills', 'Handoff Context Methods', 'skill', 'handoff-context', ['product', 'engineer', 'qa'], 'low', 'peaks-txt-context-capsule', 'Use Peaks TXT local capsules first; inspect upstream handoff, to-issues, and write-a-skill before applying method ideas.', 'Handoff Context Methods', '交接上下文方法', 'References compact handoff, follow-up issue shaping, and reusable skill lesson capture for TXT.', '为 TXT 参考紧凑交接、后续 issue 塑形和可复用技能经验沉淀。'),
116
+ capability('mattpocock-skills.git-guardrails', 'mattpocock-skills', 'Git Guardrails References', 'doc', 'git-guardrails', ['engineer'], 'medium', 'peaks-built-in-git-safety', 'Use Peaks and project-local git safety rules; do not install hooks or mutate git configuration automatically.', 'Git Guardrails References', 'Git 护栏参考', 'Catalog-only references for git guardrails and pre-commit setup; not an executable hook action.', 'Git 护栏和 pre-commit 设置的仅目录化参考;不是可执行 hook 动作。'),
108
117
  capability('impeccable.quality-guidance', 'impeccable', 'Quality Guidance', 'doc', 'quality-guidance', ['engineer'], 'low', 'peaks-review-gates', 'Use Peaks review gates as authoritative.', 'Quality Guidance', '质量指导', 'Quality reference catalog entry.', '质量参考目录项。'),
109
118
  capability('vercel-agent-skills.skill-pack', 'vercel-agent-skills', 'Vercel Agent Skills Pack', 'skill', 'skill-pack', ['engineer'], 'medium', 'external-skill-catalog', 'Inspect individual skills before use.', 'Agent Skills Pack', '代理技能包', 'Catalog-only external skill pack.', '仅作为目录化的外部技能包。'),
110
119
  capability('darwin-skill.external-skill', 'darwin-skill', 'Darwin Skill', 'skill', 'external-skill', ['engineer'], 'medium', 'external-skill-catalog', 'Inspect for project fit and safety before use.', 'Darwin Skill', 'Darwin 技能', 'Catalog-only external skill reference.', '仅作为目录化的外部技能参考。'),
@@ -1,6 +1,13 @@
1
1
  export const seedCapabilityLandingMappings = [
2
2
  mapping({ capabilityId: 'ruflo-access-repo.workflow-reference', sourceId: 'ruflo-access-repo', sourceGroup: 'access-repo', landingKind: 'catalog', target: 'peaks autonomous planning reference', guidance: 'Use as workflow orchestration inspiration only; Peaks owns execution boundaries.' }),
3
3
  mapping({ capabilityId: 'context7.docs-lookup', sourceId: 'context7', sourceGroup: 'access-repo', landingKind: 'cli', target: 'peaks recommend', commandPreview: 'peaks recommend --workflow code-refactor --json', guidance: 'Use for current library/API documentation lookup through approved MCP access.' }),
4
+ mapping({ capabilityId: 'codegraph.project-indexing', sourceId: 'codegraph', sourceGroup: 'access-repo', landingKind: 'skill', target: 'peaks-rd', skillName: 'peaks-rd', guidance: 'Dry-run reference only: if local indexing is explicitly approved, peaks-rd may use peaks codegraph index --project <path> before semantic analysis; generated .codegraph artifacts stay local unless explicitly approved.' }),
5
+ mapping({ capabilityId: 'codegraph.semantic-query', sourceId: 'codegraph', sourceGroup: 'access-repo', landingKind: 'skill', target: 'peaks-rd', skillName: 'peaks-rd', guidance: 'Dry-run reference only: peaks-rd may use peaks codegraph query --project <path> <search> for project relationship evidence during RD planning when execution is approved; Peaks RD gates remain authoritative.' }),
6
+ mapping({ capabilityId: 'codegraph.impact-analysis', sourceId: 'codegraph', sourceGroup: 'access-repo', landingKind: 'skill', target: 'peaks-rd', skillName: 'peaks-rd', guidance: 'Dry-run reference only: peaks-rd may use peaks codegraph affected --project <path> <files...> --json to inspect likely impact before slice planning and red-line checks when execution is approved.' }),
7
+ mapping({ capabilityId: 'codegraph.impact-analysis', sourceId: 'codegraph', sourceGroup: 'access-repo', landingKind: 'skill', target: 'peaks-qa', skillName: 'peaks-qa', guidance: 'Use affected output as regression-surface evidence only; QA validation and test evidence remain authoritative.' }),
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
+ 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
+ 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.' }),
4
11
  mapping({ capabilityId: 'playwright-mcp.browser-validation', sourceId: 'playwright-mcp', sourceGroup: 'access-repo', landingKind: 'skill', target: 'peaks-qa', skillName: 'peaks-qa', guidance: 'Use for browser and E2E validation after user-approved app targets are available.' }),
5
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.' }),
6
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,7 +21,13 @@ export const seedCapabilityLandingMappings = [
14
21
  mapping({ capabilityId: 'everything-claude-code.security-review-agent', sourceId: 'everything-claude-code', sourceGroup: 'mcp-server', landingKind: 'skill', target: 'peaks-rd', skillName: 'peaks-rd', guidance: 'Use as security review capability after local diff and test evidence exist.' }),
15
22
  mapping({ capabilityId: 'everything-claude-code.security-review-guidance', sourceId: 'everything-claude-code', sourceGroup: 'mcp-server', landingKind: 'skill', target: 'peaks-qa', skillName: 'peaks-qa', guidance: 'Use as project-local security review standards guidance during QA preflight.' }),
16
23
  mapping({ capabilityId: 'andrej-karpathy-skills.guidance', sourceId: 'andrej-karpathy-skills', sourceGroup: 'mcp-server', landingKind: 'skill', target: 'peaks-rd', skillName: 'peaks-rd', guidance: 'Use as engineering guidance inspiration after project-local standards are scanned.' }),
17
- mapping({ capabilityId: 'mattpocock-skills.typescript-guidance', sourceId: 'mattpocock-skills', sourceGroup: 'mcp-server', landingKind: 'skill', target: 'peaks-rd', skillName: 'peaks-rd', guidance: 'Use as TypeScript guidance only when it fits project-local conventions.' }),
24
+ mapping({ capabilityId: 'mattpocock-skills.product-prd-methods', sourceId: 'mattpocock-skills', sourceGroup: 'mcp-server', landingKind: 'skill', target: 'peaks-prd', skillName: 'peaks-prd', guidance: 'Use to-prd, zoom-out, and grill-with-docs as inspected product-method references; Peaks PRD artifacts remain authoritative.' }),
25
+ mapping({ capabilityId: 'mattpocock-skills.engineering-diagnosis', sourceId: 'mattpocock-skills', sourceGroup: 'mcp-server', landingKind: 'skill', target: 'peaks-rd', skillName: 'peaks-rd', guidance: 'Use diagnose, triage, improve-codebase-architecture, and prototype as inspected engineering references; Peaks RD gates remain authoritative.' }),
26
+ mapping({ capabilityId: 'mattpocock-skills.tdd-method', sourceId: 'mattpocock-skills', sourceGroup: 'mcp-server', landingKind: 'skill', target: 'peaks-rd', skillName: 'peaks-rd', guidance: 'Use tdd as an inspected tests-first reference during RD implementation; Peaks unit-test and review gates remain authoritative.' }),
27
+ mapping({ capabilityId: 'mattpocock-skills.tdd-method', sourceId: 'mattpocock-skills', sourceGroup: 'mcp-server', landingKind: 'skill', target: 'peaks-qa', skillName: 'peaks-qa', guidance: 'Use tdd as an inspected reference for checking whether tests protect changed behavior; Peaks QA evidence gates remain authoritative.' }),
28
+ mapping({ capabilityId: 'mattpocock-skills.qa-triage', sourceId: 'mattpocock-skills', sourceGroup: 'mcp-server', landingKind: 'skill', target: 'peaks-qa', skillName: 'peaks-qa', guidance: 'Use triage and grill-with-docs as inspected QA references for blockers, release risk, and acceptance evidence; Peaks QA remains the acceptance authority.' }),
29
+ mapping({ capabilityId: 'mattpocock-skills.handoff-context', sourceId: 'mattpocock-skills', sourceGroup: 'mcp-server', landingKind: 'skill', target: 'peaks-txt', skillName: 'peaks-txt', guidance: 'Use handoff, to-issues, and write-a-skill as inspected context references; Peaks TXT local capsule and memory-authorization rules remain authoritative.' }),
30
+ mapping({ capabilityId: 'mattpocock-skills.git-guardrails', sourceId: 'mattpocock-skills', sourceGroup: 'mcp-server', landingKind: 'catalog', target: 'git guardrails reference catalog', guidance: 'Catalog only; do not install hooks, mutate git configuration, or write Claude settings from this capability map.' }),
18
31
  mapping({ capabilityId: 'impeccable.quality-guidance', sourceId: 'impeccable', sourceGroup: 'mcp-server', landingKind: 'catalog', target: 'quality reference catalog', guidance: 'Use as quality inspiration; Peaks review gates remain authoritative.' }),
19
32
  mapping({ capabilityId: 'vercel-agent-skills.skill-pack', sourceId: 'vercel-agent-skills', sourceGroup: 'mcp-server', landingKind: 'catalog', target: 'external skill catalog', guidance: 'Catalog only until individual skills are inspected and approved.' }),
20
33
  mapping({ capabilityId: 'agent-browser.browser-agent', sourceId: 'agent-browser', sourceGroup: 'mcp-server', landingKind: 'skill', target: 'peaks-qa', skillName: 'peaks-qa', guidance: 'Use for browser validation; never submit forms or mutate authenticated state without explicit permission.' }),