peaks-cli 1.4.0 → 1.4.2

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 (58) hide show
  1. package/dist/src/cli/commands/core-artifact-commands.js +21 -0
  2. package/dist/src/cli/commands/memory-commands.d.ts +13 -0
  3. package/dist/src/cli/commands/memory-commands.js +60 -0
  4. package/dist/src/cli/commands/migrate-1-4-1-command.d.ts +11 -0
  5. package/dist/src/cli/commands/migrate-1-4-1-command.js +34 -0
  6. package/dist/src/cli/commands/retrospective-commands.d.ts +9 -0
  7. package/dist/src/cli/commands/retrospective-commands.js +58 -0
  8. package/dist/src/cli/commands/workspace-commands.js +8 -0
  9. package/dist/src/cli/program.js +16 -22
  10. package/dist/src/services/fuzzy-matching/fuzzy-match-service.d.ts +15 -0
  11. package/dist/src/services/fuzzy-matching/fuzzy-match-service.js +56 -0
  12. package/dist/src/services/fuzzy-matching/types.d.ts +20 -0
  13. package/dist/src/services/fuzzy-matching/types.js +1 -0
  14. package/dist/src/services/memory/memory-search-service.d.ts +61 -0
  15. package/dist/src/services/memory/memory-search-service.js +80 -0
  16. package/dist/src/services/recommendations/capability-seed-items.js +0 -1
  17. package/dist/src/services/recommendations/capability-seed-mappings.js +0 -1
  18. package/dist/src/services/recommendations/capability-seed-sources.js +0 -1
  19. package/dist/src/services/retrospective/retrospective-search-service.d.ts +37 -0
  20. package/dist/src/services/retrospective/retrospective-search-service.js +75 -0
  21. package/dist/src/services/standards/project-context.d.ts +1 -1
  22. package/dist/src/services/standards/project-context.js +0 -4
  23. package/dist/src/services/standards/project-standards-service.js +1 -3
  24. package/dist/src/services/workspace/migrate-1-4-1-service.d.ts +44 -0
  25. package/dist/src/services/workspace/migrate-1-4-1-service.js +195 -0
  26. package/dist/src/shared/version.d.ts +1 -1
  27. package/dist/src/shared/version.js +1 -1
  28. package/package.json +3 -7
  29. package/skills/peaks-solo/SKILL.md +1 -1
  30. package/skills/peaks-solo/references/completion-handoff.md +3 -1
  31. package/dist/src/cli/commands/shadcn-commands.d.ts +0 -3
  32. package/dist/src/cli/commands/shadcn-commands.js +0 -35
  33. package/dist/src/cli/commands/skill-scope-commands.d.ts +0 -49
  34. package/dist/src/cli/commands/skill-scope-commands.js +0 -305
  35. package/dist/src/services/shadcn/shadcn-service.d.ts +0 -27
  36. package/dist/src/services/shadcn/shadcn-service.js +0 -128
  37. package/dist/src/services/skill-scope/adapters/_stub-helper.d.ts +0 -39
  38. package/dist/src/services/skill-scope/adapters/_stub-helper.js +0 -98
  39. package/dist/src/services/skill-scope/adapters/claude-code.d.ts +0 -59
  40. package/dist/src/services/skill-scope/adapters/claude-code.js +0 -304
  41. package/dist/src/services/skill-scope/adapters/codex.d.ts +0 -2
  42. package/dist/src/services/skill-scope/adapters/codex.js +0 -12
  43. package/dist/src/services/skill-scope/adapters/cursor.d.ts +0 -2
  44. package/dist/src/services/skill-scope/adapters/cursor.js +0 -13
  45. package/dist/src/services/skill-scope/adapters/qoder.d.ts +0 -2
  46. package/dist/src/services/skill-scope/adapters/qoder.js +0 -13
  47. package/dist/src/services/skill-scope/adapters/tongyi.d.ts +0 -2
  48. package/dist/src/services/skill-scope/adapters/tongyi.js +0 -13
  49. package/dist/src/services/skill-scope/adapters/trae.d.ts +0 -2
  50. package/dist/src/services/skill-scope/adapters/trae.js +0 -12
  51. package/dist/src/services/skill-scope/detect.d.ts +0 -75
  52. package/dist/src/services/skill-scope/detect.js +0 -480
  53. package/dist/src/services/skill-scope/registry.d.ts +0 -41
  54. package/dist/src/services/skill-scope/registry.js +0 -83
  55. package/dist/src/services/skill-scope/source-of-truth.d.ts +0 -44
  56. package/dist/src/services/skill-scope/source-of-truth.js +0 -118
  57. package/dist/src/services/skill-scope/types.d.ts +0 -176
  58. package/dist/src/services/skill-scope/types.js +0 -74
@@ -1,305 +0,0 @@
1
- /**
2
- * `peaks skill scope` CLI surface (slice 025.1).
3
- *
4
- * Four subcommands (mutually exclusive):
5
- * - `--detect` — dry-run; prints the relevance matrix, never touches files.
6
- * - `--apply` — writes the source-of-truth + IDE-native config.
7
- * - `--show` — reads the source-of-truth + native config back.
8
- * - `--reset` — removes the source-of-truth + IDE-native config.
9
- *
10
- * Exit code matrix (tech-doc §6.3):
11
- * 0 success
12
- * 1 uncaught error
13
- * 2 invalid usage (missing/incompatible flags)
14
- * 3 source-of-truth written but adapter returned NOT_SUPPORTED
15
- * 4 adapter failure other than NOT_SUPPORTED
16
- */
17
- import { existsSync } from 'node:fs';
18
- import { join } from 'node:path';
19
- import { detectSkillScope, } from '../../services/skill-scope/detect.js';
20
- import { resolveActiveAdapter, getScopeAdapter } from '../../services/skill-scope/registry.js';
21
- import { ideCompanionFilePath, readIdeCompanion, readSourceOfTruth, removeIfExists, scopeFilePath, writeSourceOfTruth, } from '../../services/skill-scope/source-of-truth.js';
22
- import { ALWAYS_RELEVANT_SKILLS } from '../../services/skill-scope/types.js';
23
- import { fail, getErrorMessage, ok } from '../../shared/result.js';
24
- import { addJsonOption, printResult } from '../cli-helpers.js';
25
- const VALID_ACTIONS = ['detect', 'apply', 'show', 'reset'];
26
- const VALID_IDES = ['claude-code', 'trae', 'codex', 'cursor', 'qoder', 'tongyi-lingma'];
27
- function isValidIde(value) {
28
- return VALID_IDES.includes(value);
29
- }
30
- /**
31
- * G6: enforce the peaks-* allowlist. Re-adds any peak-* skill that is
32
- * missing from the allowlist, and removes any peak-* skill from the
33
- * denylist. The list is the same one declared in `types.ts`.
34
- */
35
- function enforcePeaksAllowlist(allowlist) {
36
- const set = new Set(allowlist);
37
- for (const name of ALWAYS_RELEVANT_SKILLS) {
38
- if (name.startsWith('peaks-'))
39
- set.add(name);
40
- }
41
- return [...set];
42
- }
43
- function stripPeaksFromDenylist(denylist) {
44
- return denylist.filter((name) => !name.startsWith('peaks-'));
45
- }
46
- /**
47
- * Determine the IDE. Caller-supplied `--ide` wins; otherwise the registry
48
- * probes the project root.
49
- */
50
- async function resolveIde(projectRoot, override) {
51
- if (override !== undefined) {
52
- if (!isValidIde(override)) {
53
- throw new Error(`Unknown IDE: ${override}. Valid: ${VALID_IDES.join(', ')}`);
54
- }
55
- return { ide: override, isFallback: false };
56
- }
57
- const resolved = await resolveActiveAdapter(projectRoot);
58
- return { ide: resolved.adapter.ide, isFallback: resolved.isFallback };
59
- }
60
- /**
61
- * Stable timestamp (no millisecond jitter) for the `generatedAt` field.
62
- * `Date.now()` would still be deterministic per-run; we keep the natural
63
- * one to ensure `generatedAt` matches what the user sees on disk.
64
- */
65
- function nowIso() {
66
- return new Date().toISOString();
67
- }
68
- /** Run the --detect subcommand. */
69
- async function runDetect(input) {
70
- try {
71
- const result = await detectSkillScope({ projectRoot: input.project });
72
- const envelope = ok('skill.scope.detect', result);
73
- const stdout = input.json === true ? JSON.stringify(envelope, null, 2) : JSON.stringify(result, null, 2);
74
- return { exitCode: 0, envelope, stdout, stderr: '' };
75
- }
76
- catch (error) {
77
- const envelope = fail('skill.scope.detect', 'DETECT_FAILED', getErrorMessage(error), null);
78
- return { exitCode: 1, envelope, stdout: '', stderr: envelope.message ?? 'detect failed' };
79
- }
80
- }
81
- /** Build the final ScopeConfig (applies G6 enforcement + override). */
82
- function buildScopeConfig(args) {
83
- const strict = args.strict;
84
- const detected = args.detected;
85
- // Build allowlist from detected.relevant + (in loose) borderline.
86
- const allowFromDetect = detected.skills
87
- .filter((s) => s.relevance === 'relevant' || (!strict && s.relevance === 'borderline'))
88
- .map((s) => s.name);
89
- const merged = args.allowOverride !== undefined ? [...args.allowOverride, ...allowFromDetect] : allowFromDetect;
90
- const enforced = enforcePeaksAllowlist(merged);
91
- // Denylist: irrelevant skills (strict + loose both), minus anything in allowlist.
92
- const denyFromDetect = detected.skills
93
- .filter((s) => s.relevance === 'irrelevant' && !enforced.includes(s.name))
94
- .map((s) => s.name);
95
- const finalDeny = stripPeaksFromDenylist(denyFromDetect);
96
- return {
97
- generatedAt: nowIso(),
98
- ide: args.ide,
99
- strict,
100
- allowlist: enforced,
101
- denylist: finalDeny,
102
- skills: detected.skills,
103
- signals: detected.projectSignals,
104
- };
105
- }
106
- /** Run the --apply subcommand. */
107
- async function runApply(input) {
108
- // 1. Detect the scope.
109
- const detected = await detectSkillScope({ projectRoot: input.project });
110
- // --strict wins when both flags are passed. Default is --loose per PRD.
111
- const isStrict = input.strict === true && input.loose !== true;
112
- const loose = !isStrict;
113
- const { ide, isFallback } = await resolveIde(input.project, input.ide);
114
- const adapter = getScopeAdapter(ide);
115
- const config = buildScopeConfig({
116
- ide,
117
- strict: isStrict,
118
- detected,
119
- ...(input.overrideAllowlist !== undefined ? { allowOverride: input.overrideAllowlist } : {}),
120
- });
121
- // 2. Write the source-of-truth first (atomic). Test seam: simulate failure.
122
- let writtenFiles = [];
123
- let sourceWritten = false;
124
- try {
125
- if (input.simulateSourceOfTruthWriteFailure) {
126
- throw new Error('simulated source-of-truth write failure');
127
- }
128
- const file = await writeSourceOfTruth(input.project, config);
129
- writtenFiles.push(file);
130
- sourceWritten = true;
131
- }
132
- catch (error) {
133
- const envelope = fail('skill.scope.apply', 'WRITE_FAILED', getErrorMessage(error), { ide, sourceWritten: false }, ['Fix filesystem permissions on the project root and retry']);
134
- return { exitCode: 4, envelope, stdout: '', stderr: envelope.message ?? 'write failed' };
135
- }
136
- // 3. Call the adapter. Stub adapters return notSupported=true; we surface it.
137
- const adapterInput = {
138
- allowlist: config.allowlist,
139
- denylist: config.denylist,
140
- strict: config.strict,
141
- projectRoot: input.project,
142
- sourceConfig: config,
143
- shadowFallback: input.shadowFallback === true,
144
- };
145
- let result;
146
- try {
147
- result = await adapter.applyScope(adapterInput);
148
- }
149
- catch (error) {
150
- // Roll back the source-of-truth on adapter failure.
151
- await removeIfExists(scopeFilePath(input.project));
152
- const envelope = fail('skill.scope.apply', 'ADAPTER_FAILED', getErrorMessage(error), { ide, sourceWritten: false, writtenFiles: [] }, ['Inspect the adapter error and retry']);
153
- return { exitCode: 4, envelope, stdout: '', stderr: envelope.message ?? 'adapter failed' };
154
- }
155
- // The stub adapter also writes the canonical skills.json — that's
156
- // already on disk from step 2, so its second write is a no-op update.
157
- const finalWrittenFiles = [...writtenFiles, ...result.writtenFiles];
158
- const envelope = ok('skill.scope.apply', {
159
- ide,
160
- isFallback,
161
- strict: isStrict,
162
- loose,
163
- allowlist: config.allowlist,
164
- denylist: config.denylist,
165
- signals: config.signals,
166
- writtenFiles: finalWrittenFiles,
167
- usedShadowStub: result.usedShadowStub,
168
- notSupported: result.notSupported,
169
- strippedFromDenylist: result.strippedFromDenylist ?? [],
170
- error: result.error,
171
- });
172
- const stdout = input.json === true ? JSON.stringify(envelope, null, 2) : JSON.stringify(envelope.data, null, 2);
173
- if (result.notSupported) {
174
- // Stub adapter: NOT_SUPPORTED → exit 3, write error to stderr.
175
- const stderr = `${result.error?.code ?? 'NOT_SUPPORTED'}: ${result.error?.message ?? 'not supported'}`;
176
- return { exitCode: 3, envelope, stdout, stderr };
177
- }
178
- return { exitCode: 0, envelope, stdout, stderr: '' };
179
- }
180
- /** Run the --show subcommand. */
181
- async function runShow(input) {
182
- const source = await readSourceOfTruth(input.project);
183
- const { ide } = await resolveIde(input.project, input.ide);
184
- const companionPath = ideCompanionFilePath(input.project, ide);
185
- const companion = await readIdeCompanion(input.project, ide);
186
- // For Claude Code, the native config is `.claude/settings.local.json`.
187
- const nativeSettingsPath = join(input.project, '.claude', 'settings.local.json');
188
- const nativeExists = existsSync(nativeSettingsPath);
189
- let native = companion;
190
- if (nativeExists) {
191
- try {
192
- const { readFile } = await import('node:fs/promises');
193
- native = JSON.parse(await readFile(nativeSettingsPath, 'utf8'));
194
- }
195
- catch {
196
- native = null;
197
- }
198
- }
199
- const data = {
200
- ide,
201
- source,
202
- native,
203
- nativeSettingsPath: nativeExists ? '.claude/settings.local.json' : null,
204
- companionPath: existsSync(companionPath) ? companionPath : null,
205
- };
206
- const envelope = ok('skill.scope.show', data);
207
- const stdout = input.json === true ? JSON.stringify(envelope, null, 2) : JSON.stringify(data, null, 2);
208
- return { exitCode: 0, envelope, stdout, stderr: '' };
209
- }
210
- /** Run the --reset subcommand. */
211
- async function runReset(input) {
212
- const { ide } = await resolveIde(input.project, input.ide);
213
- const adapter = getScopeAdapter(ide);
214
- const resetResult = await adapter.resetScope({ projectRoot: input.project });
215
- const sourceFile = scopeFilePath(input.project);
216
- const sourceRemoved = await removeIfExists(sourceFile);
217
- const allRemoved = [...resetResult.removedFiles, ...(sourceRemoved ? [sourceFile] : [])];
218
- const envelope = ok('skill.scope.reset', {
219
- ide,
220
- removedFiles: allRemoved,
221
- });
222
- // Always include the canonical source-of-truth path in the human-readable
223
- // summary, even if it didn't exist (so the user knows what was targeted).
224
- const displayFiles = allRemoved.length > 0 ? allRemoved : [sourceFile, join(input.project, '.claude', 'settings.local.json')];
225
- const summary = `removed: ${displayFiles.join(', ')}`;
226
- const stdout = input.json === true ? JSON.stringify(envelope, null, 2) : summary;
227
- return { exitCode: 0, envelope, stdout, stderr: '' };
228
- }
229
- /**
230
- * Programmatic entry point for `peaks skill scope`. Used by the CLI shim
231
- * AND by the unit tests.
232
- */
233
- export async function runSkillScopeCommand(input) {
234
- if (!VALID_ACTIONS.includes(input.subcommand)) {
235
- const envelope = fail('skill.scope', 'INVALID_USAGE', `Unknown action: ${input.subcommand}`, null);
236
- return { exitCode: 2, envelope, stdout: '', stderr: envelope.message ?? 'invalid usage' };
237
- }
238
- switch (input.subcommand) {
239
- case 'detect': return runDetect(input);
240
- case 'apply': return runApply(input);
241
- case 'show': return runShow(input);
242
- case 'reset': return runReset(input);
243
- }
244
- }
245
- /**
246
- * Register the `peaks skill scope` subcommand on the `skill` command group.
247
- * Mutually-exclusive flags: exactly one of --detect / --apply / --show / --reset.
248
- */
249
- export function registerSkillScopeCommands(program, io) {
250
- // Find the existing 'skill' subcommand if any.
251
- let skillCmd = program.commands.find((c) => c.name() === 'skill');
252
- if (skillCmd === undefined) {
253
- skillCmd = program.command('skill').description('Manage Peaks skills');
254
- }
255
- const scope = skillCmd
256
- .command('scope')
257
- .description('Per-project skill scoping: detect, apply, show, reset');
258
- addJsonOption(scope
259
- .option('--detect', 'dry-run: print the relevance matrix')
260
- .option('--apply', 'apply the scope (writes source-of-truth + IDE config)')
261
- .option('--show', 'show the currently applied scope')
262
- .option('--reset', 'remove the scope config')
263
- .option('--project <path>', 'target project root (defaults to cwd)', process.cwd())
264
- .option('--strict', '--apply: only `relevant` skills in the allowlist')
265
- .option('--loose', '--apply: `relevant` + `borderline` in the allowlist (default)')
266
- .option('--ide <name>', 'force a specific IDE adapter (overrides auto-detect)')
267
- .option('--shadow-fallback', '--apply: Claude Code uses shadow stubs for the denylist')).action(async (options) => {
268
- const flags = [options.detect, options.apply, options.show, options.reset].filter(Boolean).length;
269
- if (flags !== 1) {
270
- const envelope = fail('skill.scope', 'INVALID_USAGE', 'Exactly one of --detect / --apply / --show / --reset is required', null, ['Pass exactly one action flag']);
271
- printResult(io, envelope, options.json === true);
272
- process.exitCode = 2;
273
- return;
274
- }
275
- const subcommand = options.detect
276
- ? 'detect'
277
- : options.apply
278
- ? 'apply'
279
- : options.show
280
- ? 'show'
281
- : 'reset';
282
- const result = await runSkillScopeCommand({
283
- subcommand,
284
- project: options.project ?? process.cwd(),
285
- ...(options.strict !== undefined ? { strict: options.strict } : {}),
286
- ...(options.loose !== undefined ? { loose: options.loose } : {}),
287
- ...(options.ide !== undefined ? { ide: options.ide } : {}),
288
- ...(options.shadowFallback !== undefined ? { shadowFallback: options.shadowFallback } : {}),
289
- ...(options.json !== undefined ? { json: options.json } : {}),
290
- });
291
- if (options.json === true) {
292
- if (result.envelope !== null)
293
- printResult(io, result.envelope, true);
294
- }
295
- else {
296
- if (result.stdout.length > 0)
297
- io.stdout(result.stdout);
298
- if (result.stderr.length > 0)
299
- io.stderr(result.stderr);
300
- }
301
- if (result.exitCode !== 0) {
302
- process.exitCode = result.exitCode;
303
- }
304
- });
305
- }
@@ -1,27 +0,0 @@
1
- declare const SHADCN_PACKAGE_NAME = "shadcn";
2
- declare const SHADCN_PACKAGE_VERSION = "4.7.0";
3
- declare const SHADCN_EXECUTABLE: string;
4
- export type ShadcnInvocationOptions = {
5
- args: string[];
6
- cwd?: string;
7
- };
8
- export type ShadcnInvocation = {
9
- executable: typeof SHADCN_EXECUTABLE;
10
- args: string[];
11
- cwd: string;
12
- packageName: typeof SHADCN_PACKAGE_NAME;
13
- packageVersion: typeof SHADCN_PACKAGE_VERSION;
14
- };
15
- export type ShadcnExecutionResult = {
16
- exitCode: number | null;
17
- stdout: string;
18
- stderr: string;
19
- };
20
- export type ShadcnProcessRunner = (invocation: ShadcnInvocation) => Promise<ShadcnExecutionResult>;
21
- declare function createShadcnEnvironment(sourceEnv?: NodeJS.ProcessEnv): NodeJS.ProcessEnv;
22
- export declare function createShadcnInvocation(options: ShadcnInvocationOptions): ShadcnInvocation;
23
- export declare function executeShadcnInvocation(invocation: ShadcnInvocation, runner?: ShadcnProcessRunner): Promise<ShadcnExecutionResult>;
24
- export declare const testInternals: {
25
- createShadcnEnvironment: typeof createShadcnEnvironment;
26
- };
27
- export {};
@@ -1,128 +0,0 @@
1
- import { existsSync } from 'node:fs';
2
- import { spawn } from 'node:child_process';
3
- import { createRequire } from 'node:module';
4
- import { resolve } from 'node:path';
5
- const SHADCN_PACKAGE_NAME = 'shadcn';
6
- const SHADCN_PACKAGE_VERSION = '4.7.0';
7
- const SHADCN_EXECUTABLE = process.execPath;
8
- const SHADCN_BINARY_PATH = resolveShadcnBinaryPath();
9
- const SHADCN_PROCESS_TIMEOUT_MS = 600_000;
10
- const SHADCN_OUTPUT_LIMIT_BYTES = 10 * 1024 * 1024;
11
- const POSITIONAL_ARGUMENT_PREFIX = '-';
12
- const PRESERVED_ENV_KEYS = ['PATH', 'Path', 'HOME', 'USERPROFILE', 'APPDATA', 'LOCALAPPDATA', 'TEMP', 'TMP', 'SystemRoot', 'WINDIR'];
13
- function resolveShadcnBinaryPath() {
14
- const require = createRequire(import.meta.url);
15
- const binaryPath = require.resolve('shadcn');
16
- if (!existsSync(binaryPath)) {
17
- throw new Error('Unable to resolve local shadcn binary from shadcn');
18
- }
19
- return binaryPath;
20
- }
21
- function assertShadcnArgs(args) {
22
- if (args.length === 0) {
23
- throw new Error('shadcn arguments are required');
24
- }
25
- if (args[0]?.startsWith(POSITIONAL_ARGUMENT_PREFIX)) {
26
- throw new Error('shadcn command must not start with -');
27
- }
28
- }
29
- function createShadcnEnvironment(sourceEnv = process.env) {
30
- const environment = {};
31
- for (const key of PRESERVED_ENV_KEYS) {
32
- const value = sourceEnv[key];
33
- if (value !== undefined) {
34
- environment[key] = value;
35
- }
36
- }
37
- return environment;
38
- }
39
- function assertOutputLimit(currentSize, chunkSize) {
40
- const nextSize = currentSize + chunkSize;
41
- if (nextSize > SHADCN_OUTPUT_LIMIT_BYTES) {
42
- throw new Error(`shadcn output exceeded ${SHADCN_OUTPUT_LIMIT_BYTES} bytes`);
43
- }
44
- return nextSize;
45
- }
46
- function terminateShadcnProcess(childProcess) {
47
- if (childProcess.pid === undefined) {
48
- childProcess.kill();
49
- return;
50
- }
51
- if (process.platform === 'win32') {
52
- const taskkillPath = process.env.SystemRoot ? resolve(process.env.SystemRoot, 'System32', 'taskkill.exe') : 'taskkill.exe';
53
- spawn(taskkillPath, ['/pid', String(childProcess.pid), '/T', '/F'], { shell: false, stdio: 'ignore' });
54
- return;
55
- }
56
- try {
57
- process.kill(-childProcess.pid, 'SIGTERM');
58
- }
59
- catch {
60
- childProcess.kill('SIGTERM');
61
- }
62
- }
63
- function defaultShadcnProcessRunner(invocation) {
64
- return new Promise((resolveResult, reject) => {
65
- const childProcess = spawn(invocation.executable, invocation.args, {
66
- cwd: invocation.cwd,
67
- detached: process.platform !== 'win32',
68
- env: createShadcnEnvironment(),
69
- shell: false
70
- });
71
- const timeout = setTimeout(() => {
72
- terminateShadcnProcess(childProcess);
73
- reject(new Error(`shadcn process timed out after ${SHADCN_PROCESS_TIMEOUT_MS}ms`));
74
- }, SHADCN_PROCESS_TIMEOUT_MS);
75
- const stdoutChunks = [];
76
- const stderrChunks = [];
77
- let stdoutSize = 0;
78
- let stderrSize = 0;
79
- childProcess.stdout.on('data', (chunk) => {
80
- try {
81
- stdoutSize = assertOutputLimit(stdoutSize, chunk.length);
82
- stdoutChunks.push(chunk);
83
- }
84
- catch (error) {
85
- terminateShadcnProcess(childProcess);
86
- reject(error);
87
- }
88
- });
89
- childProcess.stderr.on('data', (chunk) => {
90
- try {
91
- stderrSize = assertOutputLimit(stderrSize, chunk.length);
92
- stderrChunks.push(chunk);
93
- }
94
- catch (error) {
95
- terminateShadcnProcess(childProcess);
96
- reject(error);
97
- }
98
- });
99
- childProcess.on('error', (error) => {
100
- clearTimeout(timeout);
101
- reject(error);
102
- });
103
- childProcess.on('close', (exitCode) => {
104
- clearTimeout(timeout);
105
- resolveResult({
106
- exitCode,
107
- stdout: Buffer.concat(stdoutChunks).toString('utf8'),
108
- stderr: Buffer.concat(stderrChunks).toString('utf8')
109
- });
110
- });
111
- });
112
- }
113
- export function createShadcnInvocation(options) {
114
- assertShadcnArgs(options.args);
115
- return {
116
- executable: SHADCN_EXECUTABLE,
117
- args: [SHADCN_BINARY_PATH, ...options.args],
118
- cwd: options.cwd ?? process.cwd(),
119
- packageName: SHADCN_PACKAGE_NAME,
120
- packageVersion: SHADCN_PACKAGE_VERSION
121
- };
122
- }
123
- export async function executeShadcnInvocation(invocation, runner = defaultShadcnProcessRunner) {
124
- return runner(invocation);
125
- }
126
- export const testInternals = {
127
- createShadcnEnvironment
128
- };
@@ -1,39 +0,0 @@
1
- /**
2
- * Shared `makeStubAdapter` helper for the 5 non-shipped IDEs (Trae, Cursor,
3
- * Codex, Qoder, Tongyi Lingma).
4
- *
5
- * Each stub adapter:
6
- * 1. Implements `SkillScopeAdapter` with `supported: false`.
7
- * 2. In `applyScope`, ALWAYS writes the companion source-of-truth
8
- * `.peaks/scope/<ide>-skills.json` first, then returns a NOT_SUPPORTED
9
- * ApplyResult (the test contract asserts the source-of-truth is on disk
10
- * even when the adapter can't apply it natively).
11
- * 3. In `showScope`, reads from the companion source-of-truth file.
12
- * 4. In `resetScope`, removes the companion source-of-truth file.
13
- * 5. In `detect`, returns 0.0 (the stub does not actually probe).
14
- *
15
- * The TODO comment in each stub file points at the follow-up slice (025.2+).
16
- */
17
- import type { SkillScopeAdapter } from '../types.js';
18
- /**
19
- * IDE-id -> companion source-of-truth shape. The companion file is a
20
- * parallel record so the user can see "this is what would have applied"
21
- * even when the IDE doesn't support a real implementation.
22
- */
23
- export interface StubSourceOfTruth {
24
- readonly ide: string;
25
- readonly generatedAt: string;
26
- readonly strict: boolean;
27
- readonly allowlist: readonly string[];
28
- readonly denylist: readonly string[];
29
- readonly todoRef: string;
30
- readonly notes: string;
31
- }
32
- /**
33
- * The factory: every stub is a thin wrapper around this function. The
34
- * `applyScope` implementation ALWAYS writes the source-of-truth, then
35
- * returns a NOT_SUPPORTED ApplyResult (NOT a thrown error — the contract
36
- * for stub adapters is "return ok:false, notSupported:true" so the CLI
37
- * can keep going and surface the error to the user).
38
- */
39
- export declare function makeStubAdapter(ide: SkillScopeAdapter['ide'], todoRef: string, displayName: string): SkillScopeAdapter;
@@ -1,98 +0,0 @@
1
- /**
2
- * Shared `makeStubAdapter` helper for the 5 non-shipped IDEs (Trae, Cursor,
3
- * Codex, Qoder, Tongyi Lingma).
4
- *
5
- * Each stub adapter:
6
- * 1. Implements `SkillScopeAdapter` with `supported: false`.
7
- * 2. In `applyScope`, ALWAYS writes the companion source-of-truth
8
- * `.peaks/scope/<ide>-skills.json` first, then returns a NOT_SUPPORTED
9
- * ApplyResult (the test contract asserts the source-of-truth is on disk
10
- * even when the adapter can't apply it natively).
11
- * 3. In `showScope`, reads from the companion source-of-truth file.
12
- * 4. In `resetScope`, removes the companion source-of-truth file.
13
- * 5. In `detect`, returns 0.0 (the stub does not actually probe).
14
- *
15
- * The TODO comment in each stub file points at the follow-up slice (025.2+).
16
- */
17
- import { existsSync } from 'node:fs';
18
- import { readFile } from 'node:fs/promises';
19
- import { ideCompanionFilePath, removeIfExists, scopeFilePath, writeJsonAtomic } from '../source-of-truth.js';
20
- async function writeStubCompanion(ide, input, todoRef) {
21
- const file = ideCompanionFilePath(input.projectRoot, ide);
22
- const data = {
23
- ide,
24
- generatedAt: input.sourceConfig.generatedAt,
25
- strict: input.strict,
26
- allowlist: input.allowlist,
27
- denylist: input.denylist,
28
- todoRef,
29
- notes: `Stub source-of-truth for ${ide}. The real config format has not yet been researched. ` +
30
- `This file is written so the user's intent is captured and can be ported when ` +
31
- `the follow-up slice (${todoRef}) lands.`,
32
- };
33
- await writeJsonAtomic(file, data);
34
- return file;
35
- }
36
- async function readStubCompanion(ide, projectRoot) {
37
- const file = ideCompanionFilePath(projectRoot, ide);
38
- if (!existsSync(file))
39
- return null;
40
- try {
41
- return JSON.parse(await readFile(file, 'utf8'));
42
- }
43
- catch {
44
- return null;
45
- }
46
- }
47
- /**
48
- * The factory: every stub is a thin wrapper around this function. The
49
- * `applyScope` implementation ALWAYS writes the source-of-truth, then
50
- * returns a NOT_SUPPORTED ApplyResult (NOT a thrown error — the contract
51
- * for stub adapters is "return ok:false, notSupported:true" so the CLI
52
- * can keep going and surface the error to the user).
53
- */
54
- export function makeStubAdapter(ide, todoRef, displayName) {
55
- const ideStr = String(ide);
56
- return {
57
- ide,
58
- supported: false,
59
- async detect() {
60
- // Stubs never "win" detection; they return 0.0 so the registry falls
61
- // back to the shipped adapter (Claude Code).
62
- return 0.0;
63
- },
64
- async applyScope(input) {
65
- // 1. Always write the source-of-truth first.
66
- const companion = await writeStubCompanion(ideStr, input, todoRef);
67
- // 2. Always write the canonical .peaks/scope/skills.json too.
68
- const canonical = scopeFilePath(input.projectRoot);
69
- await writeJsonAtomic(canonical, input.sourceConfig);
70
- // 3. Surface NOT_SUPPORTED with a clear, IDE-named message.
71
- const message = `${displayName} (${ideStr}) config format not yet researched — ${todoRef} follow-up. ` +
72
- `Source-of-truth written to ${companion}.`;
73
- return {
74
- ide,
75
- ok: false,
76
- writtenFiles: [companion, canonical],
77
- usedShadowStub: false,
78
- notSupported: true,
79
- error: { code: 'NOT_SUPPORTED', message },
80
- };
81
- },
82
- async showScope(projectRoot) {
83
- const native = await readStubCompanion(ideStr, projectRoot);
84
- return { source: null, native, ide };
85
- },
86
- async resetScope(input) {
87
- const removed = [];
88
- const companion = ideCompanionFilePath(input.projectRoot, ideStr);
89
- if (await removeIfExists(companion))
90
- removed.push(companion);
91
- // Also remove the canonical source-of-truth on reset.
92
- const canonical = scopeFilePath(input.projectRoot);
93
- if (await removeIfExists(canonical))
94
- removed.push(canonical);
95
- return { ide, removedFiles: removed };
96
- },
97
- };
98
- }
@@ -1,59 +0,0 @@
1
- /**
2
- * `peaks skill scope` — Claude Code adapter (full impl, slice 025.1).
3
- *
4
- * Strategy (tech-doc-025 §3):
5
- * 1. PRIMARY: write `.claude/settings.local.json` with
6
- * `permissions.allow: ["Skill(name)", ...]` + `permissions.deny: [...]`.
7
- * 2. FALLBACK (R1, `--shadow-fallback`): when the runtime probe determines
8
- * Claude Code rejects `Skill(name)` in `permissions.deny`, write a
9
- * shadow stub at `.claude/skills/<name>/SKILL.md` for each denylisted
10
- * skill. Tagged with `_peaks_scope_disabled: true` (R6).
11
- *
12
- * Idempotency: dedupe the allow/deny arrays; shadow-stub writes skip
13
- * when the marker is already present. AC11.
14
- */
15
- import type { ApplyResult, ApplyScopeInput, ResetScopeInput, ResetScopeResult, ShowScopeResult, SkillScopeAdapter } from '../types.js';
16
- /** Format the `Skill(name)` string Claude Code's permission system uses. */
17
- export declare function skillRef(name: string): string;
18
- interface ClaudePermissions {
19
- readonly allow: string[];
20
- readonly deny: string[];
21
- }
22
- interface ClaudeSettings {
23
- readonly permissions: ClaudePermissions;
24
- readonly [key: string]: unknown;
25
- }
26
- /**
27
- * Map allowlist/denylist → permissions.allow/permissions.deny. Never sorts;
28
- * preserves input order. Always dedupes.
29
- */
30
- export declare function toPermissions(allowlist: readonly string[], denylist: readonly string[]): ClaudeSettings;
31
- /**
32
- * Strip any peaks-* name from the denylist (G6 hard constraint). Returns
33
- * the cleaned denylist + the list of stripped names for the audit log.
34
- */
35
- export declare function stripPeaksFromDenylist(denylist: readonly string[]): {
36
- readonly cleaned: readonly string[];
37
- readonly stripped: readonly string[];
38
- };
39
- /**
40
- * Runtime probe for whether Claude Code supports `Skill(name)` syntax in
41
- * `permissions.deny` (R1). For slice 025.1 we return `unknown` and let
42
- * the caller decide. Replace this with a real check when Claude Code's
43
- * `permissions.deny` schema is documented.
44
- */
45
- export declare function probeSkillDenySupport(): Promise<'support-allow-and-deny' | 'support-allow-only' | 'unknown'>;
46
- export declare class ClaudeCodeSkillScope implements SkillScopeAdapter {
47
- readonly ide: "claude-code";
48
- readonly supported = true;
49
- constructor(_opts?: {
50
- readonly projectRoot?: string;
51
- });
52
- /** detect(): returns 1.0 when the project root has a .claude/ dir. */
53
- detect(projectRoot: string): Promise<number>;
54
- applyScope(input: ApplyScopeInput): Promise<ApplyResult>;
55
- showScope(projectRoot: string): Promise<ShowScopeResult>;
56
- resetScope(input: ResetScopeInput): Promise<ResetScopeResult>;
57
- }
58
- export declare const CLAUDE_CODE_SKILL_SCOPE: SkillScopeAdapter;
59
- export {};