@malmhq/intermesh-cli 0.1.0

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 (95) hide show
  1. package/LICENSE +198 -0
  2. package/README.md +77 -0
  3. package/dist/generation/.opencode/AGENTS.md +38 -0
  4. package/dist/generation/.opencode/agents/generation-builder.md +9 -0
  5. package/dist/generation/.opencode/agents/generation-fidelity.md +9 -0
  6. package/dist/generation/.opencode/agents/generation-source.md +13 -0
  7. package/dist/generation/.opencode/prompts/analyze-capabilities.md +61 -0
  8. package/dist/generation/.opencode/prompts/analyze-source.md +41 -0
  9. package/dist/generation/.opencode/prompts/generate.md +9 -0
  10. package/dist/generation/.opencode/prompts/repair-capability-proposal.md +5 -0
  11. package/dist/generation/.opencode/skills/generation-building/SKILL.md +32 -0
  12. package/dist/generation/.opencode/skills/generation-package-review/SKILL.md +36 -0
  13. package/dist/generation/.opencode/skills/generation-source-analysis/SKILL.md +36 -0
  14. package/dist/generation/AGENTS.md +38 -0
  15. package/dist/generation/agent-skill-installer/index.ts +4 -0
  16. package/dist/generation/agent-skill-installer/install.ts +503 -0
  17. package/dist/generation/agent-skill-installer/lock.ts +128 -0
  18. package/dist/generation/agent-skill-installer/manifest.ts +271 -0
  19. package/dist/generation/agent-skill-installer/targets.ts +169 -0
  20. package/dist/generation/runtime-entrypoint.js +39980 -0
  21. package/dist/generation/scripts/check-package.mjs +45 -0
  22. package/dist/generation/scripts/installed-help.mjs +98 -0
  23. package/dist/generation/scripts/lead-input.mjs +584 -0
  24. package/dist/generation/scripts/prepare-opencode.mjs +33 -0
  25. package/dist/generation/scripts/prepared-work.mjs +197 -0
  26. package/dist/generation/scripts/repair-loop.mjs +519 -0
  27. package/dist/generation/scripts/run-reviewers.mjs +306 -0
  28. package/dist/generation/scripts/runtime-capture.mjs +119 -0
  29. package/dist/generation/scripts/stage-timings.mjs +53 -0
  30. package/dist/generation/scripts/validate-source-brief.mjs +220 -0
  31. package/dist/generation/scripts/verify-package.mjs +365 -0
  32. package/dist/generation/templates/npm-cli/CONTRIBUTING.md +20 -0
  33. package/dist/generation/templates/npm-cli/GENERATION-PATTERNS.md +143 -0
  34. package/dist/generation/templates/npm-cli/README.md +67 -0
  35. package/dist/generation/templates/npm-cli/capabilities.json +1 -0
  36. package/dist/generation/templates/npm-cli/mock/routes.ts +3 -0
  37. package/dist/generation/templates/npm-cli/mock/server.ts +15 -0
  38. package/dist/generation/templates/npm-cli/mock/types.ts +7 -0
  39. package/dist/generation/templates/npm-cli/package.json +36 -0
  40. package/dist/generation/templates/npm-cli/pnpm-lock.yaml +495 -0
  41. package/dist/generation/templates/npm-cli/pnpm-workspace.yaml +2 -0
  42. package/dist/generation/templates/npm-cli/skills/customer-cli-management/SKILL.md +22 -0
  43. package/dist/generation/templates/npm-cli/skills/customer-workflow/SKILL.md +22 -0
  44. package/dist/generation/templates/npm-cli/src/auth/commands.ts +77 -0
  45. package/dist/generation/templates/npm-cli/src/auth/credentials.ts +66 -0
  46. package/dist/generation/templates/npm-cli/src/auth/prompt.ts +27 -0
  47. package/dist/generation/templates/npm-cli/src/cli.ts +53 -0
  48. package/dist/generation/templates/npm-cli/src/commands.ts +6 -0
  49. package/dist/generation/templates/npm-cli/src/customer.ts +16 -0
  50. package/dist/generation/templates/npm-cli/src/index.ts +11 -0
  51. package/dist/generation/templates/npm-cli/src/lifecycle/commands.ts +344 -0
  52. package/dist/generation/templates/npm-cli/src/lifecycle/config.ts +32 -0
  53. package/dist/generation/templates/npm-cli/src/shared/dry-run.ts +32 -0
  54. package/dist/generation/templates/npm-cli/src/shared/errors.ts +162 -0
  55. package/dist/generation/templates/npm-cli/src/shared/files.ts +45 -0
  56. package/dist/generation/templates/npm-cli/src/shared/http.ts +125 -0
  57. package/dist/generation/templates/npm-cli/src/shared/output.ts +33 -0
  58. package/dist/generation/templates/npm-cli/test/auth.test.ts +113 -0
  59. package/dist/generation/templates/npm-cli/test/fixture-cli.ts +51 -0
  60. package/dist/generation/templates/npm-cli/test/foundation.test.ts +316 -0
  61. package/dist/generation/templates/npm-cli/test/helpers.ts +34 -0
  62. package/dist/generation/templates/npm-cli/test/mock-lifecycle.test.ts +86 -0
  63. package/dist/generation/templates/npm-cli/test/run-with-mock.ts +142 -0
  64. package/dist/generation/templates/npm-cli/tsconfig.build.json +5 -0
  65. package/dist/generation/templates/npm-cli/tsconfig.json +11 -0
  66. package/dist/generation/templates/opencode-bootstrap/package-lock.json +402 -0
  67. package/dist/generation/templates/opencode-bootstrap/package.json +7 -0
  68. package/dist/index.js +51965 -0
  69. package/dist/runtime/candidate-inspection/inspect.Dockerfile +4 -0
  70. package/dist/runtime/candidate-inspection/install.Dockerfile +5 -0
  71. package/dist/runtime/candidate-inspection/registry-proxy.mjs +37 -0
  72. package/dist/runtime/harbor/bootstrap/package-lock.json +402 -0
  73. package/dist/runtime/harbor/bootstrap/package.json +7 -0
  74. package/dist/runtime/harbor/evaluation.Dockerfile +31 -0
  75. package/dist/runtime/harbor/harbor_launcher.py +62 -0
  76. package/dist/runtime/harbor/intermesh_opencode.py +44 -0
  77. package/dist/runtime/harbor/model-forwarder.mjs +52 -0
  78. package/dist/runtime/live-task.md +13 -0
  79. package/package.json +44 -0
  80. package/skills/inter-cli-management/SKILL.md +38 -0
  81. package/skills/inter-cli-management/evals/evals.json +40 -0
  82. package/skills/inter-context/SKILL.md +72 -0
  83. package/skills/inter-context/evals/cases.json +82 -0
  84. package/skills/inter-eval-authoring/SKILL.md +70 -0
  85. package/skills/inter-eval-authoring/evals/evals.json +69 -0
  86. package/skills/inter-eval-authoring/evals/trigger-queries.json +34 -0
  87. package/skills/inter-eval-authoring/references/authoring-guide.md +70 -0
  88. package/skills/inter-eval-authoring/references/eval-plan.example.json +92 -0
  89. package/skills/inter-eval-authoring/references/eval-plan.schema.json +304 -0
  90. package/skills/inter-evaluation/SKILL.md +51 -0
  91. package/skills/inter-evaluation/evals/evals.json +49 -0
  92. package/skills/inter-evaluation/evals/trigger-queries.json +30 -0
  93. package/skills/inter-generation/SKILL.md +42 -0
  94. package/skills/inter-generation/evals/evals.json +81 -0
  95. package/skills/inter-generation/evals/trigger-queries.json +58 -0
@@ -0,0 +1,271 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { access, lstat, readFile, readdir } from 'node:fs/promises';
3
+ import { basename, join, relative, resolve, sep } from 'node:path';
4
+
5
+ export type InstallerErrorCode =
6
+ 'SKILL_CONFLICT' | 'SKILL_PATH_UNSAFE' | 'SKILL_INSTALLATION_BUSY' | 'SKILL_INSTALLATION_FAILED';
7
+
8
+ export class SkillInstallerError extends Error {
9
+ constructor(
10
+ readonly code: InstallerErrorCode,
11
+ message: string,
12
+ readonly hint: string,
13
+ ) {
14
+ super(message);
15
+ this.name = 'SkillInstallerError';
16
+ }
17
+ }
18
+
19
+ export interface ManagedManifest {
20
+ schema_version: '1.0.0';
21
+ package: string;
22
+ binary: string;
23
+ version: string;
24
+ skills: Record<string, string>;
25
+ }
26
+
27
+ export interface BundledSuite {
28
+ sourceRoot: string;
29
+ packageName: string;
30
+ binary: string;
31
+ version: string;
32
+ skills: Record<string, string>;
33
+ }
34
+
35
+ export type InstalledSkillState =
36
+ 'installed' | 'missing' | 'outdated' | 'managed_modified' | 'unmanaged';
37
+
38
+ export interface InstalledSkillInspection {
39
+ name: string;
40
+ state: InstalledSkillState;
41
+ expectedHash: string;
42
+ actualHash?: string;
43
+ }
44
+
45
+ const skillNamePattern = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
46
+ const binaryPattern = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
47
+ const packagePattern = /^(?:@[a-z0-9][a-z0-9._-]*\/)?[a-z0-9][a-z0-9._-]*$/;
48
+ const versionPattern = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/;
49
+ const hashPattern = /^sha256:[a-f0-9]{64}$/;
50
+
51
+ function isObject(value: unknown): value is Record<string, unknown> {
52
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
53
+ }
54
+
55
+ export function validateIdentity(packageName: string, binary: string, version: string): void {
56
+ if (
57
+ !packagePattern.test(packageName) ||
58
+ !binaryPattern.test(binary) ||
59
+ !versionPattern.test(version)
60
+ ) {
61
+ throw new SkillInstallerError(
62
+ 'SKILL_PATH_UNSAFE',
63
+ 'Skill package identity is invalid',
64
+ 'Reinstall or regenerate the CLI package.',
65
+ );
66
+ }
67
+ }
68
+
69
+ export function validateSkillName(name: string): void {
70
+ if (!skillNamePattern.test(name) || basename(name) !== name) {
71
+ throw new SkillInstallerError(
72
+ 'SKILL_PATH_UNSAFE',
73
+ 'Skill name is invalid',
74
+ 'Use a lowercase Agent Skill name containing letters, numbers and hyphens.',
75
+ );
76
+ }
77
+ }
78
+
79
+ export function manifestFilename(binary: string): string {
80
+ if (!binaryPattern.test(binary)) {
81
+ throw new SkillInstallerError(
82
+ 'SKILL_PATH_UNSAFE',
83
+ 'CLI binary name is invalid',
84
+ 'Reinstall or regenerate the CLI package.',
85
+ );
86
+ }
87
+ return `.${binary}-managed.json`;
88
+ }
89
+
90
+ export function containedPath(root: string, ...parts: string[]): string {
91
+ const resolvedRoot = resolve(root);
92
+ const target = resolve(resolvedRoot, ...parts);
93
+ if (target !== resolvedRoot && !target.startsWith(`${resolvedRoot}${sep}`)) {
94
+ throw new SkillInstallerError(
95
+ 'SKILL_PATH_UNSAFE',
96
+ 'Skill path escapes the selected destination',
97
+ 'Choose a safe project directory and retry setup.',
98
+ );
99
+ }
100
+ return target;
101
+ }
102
+
103
+ async function hashDirectory(root: string, current = root, hash = createHash('sha256')) {
104
+ const entries = await readdir(current, { withFileTypes: true });
105
+ entries.sort((left, right) => left.name.localeCompare(right.name));
106
+ for (const entry of entries) {
107
+ const path = join(current, entry.name);
108
+ const info = await lstat(path);
109
+ if (info.isSymbolicLink()) {
110
+ throw new SkillInstallerError(
111
+ 'SKILL_PATH_UNSAFE',
112
+ 'Skill directories cannot contain symbolic links',
113
+ 'Replace the symbolic link with regular files and retry.',
114
+ );
115
+ }
116
+ const name = relative(root, path).split(sep).join('/');
117
+ if (info.isDirectory()) await hashDirectory(root, path, hash);
118
+ else if (info.isFile())
119
+ hash
120
+ .update(name)
121
+ .update('\0')
122
+ .update(await readFile(path))
123
+ .update('\0');
124
+ else {
125
+ throw new SkillInstallerError(
126
+ 'SKILL_PATH_UNSAFE',
127
+ 'Skill directories may contain only regular files and directories',
128
+ 'Remove unsupported filesystem entries and retry.',
129
+ );
130
+ }
131
+ }
132
+ return hash;
133
+ }
134
+
135
+ export async function hashSkillDirectory(path: string): Promise<string> {
136
+ const info = await lstat(path);
137
+ if (!info.isDirectory() || info.isSymbolicLink()) {
138
+ throw new SkillInstallerError(
139
+ 'SKILL_PATH_UNSAFE',
140
+ 'Skill must be a regular directory',
141
+ 'Replace the Skill with a regular directory and retry.',
142
+ );
143
+ }
144
+ return `sha256:${(await hashDirectory(path)).digest('hex')}`;
145
+ }
146
+
147
+ export async function inspectBundledSuite(input: {
148
+ sourceRoot: string;
149
+ packageName: string;
150
+ binary: string;
151
+ version: string;
152
+ }): Promise<BundledSuite> {
153
+ validateIdentity(input.packageName, input.binary, input.version);
154
+ const entries = await readdir(input.sourceRoot, { withFileTypes: true });
155
+ const skills: Record<string, string> = {};
156
+ for (const entry of entries.sort((left, right) => left.name.localeCompare(right.name))) {
157
+ validateSkillName(entry.name);
158
+ if (!entry.isDirectory() || entry.isSymbolicLink()) {
159
+ throw new SkillInstallerError(
160
+ 'SKILL_PATH_UNSAFE',
161
+ 'Bundled Skills must be regular directories',
162
+ 'Rebuild the CLI package with regular Skill directories.',
163
+ );
164
+ }
165
+ const skillRoot = containedPath(input.sourceRoot, entry.name);
166
+ try {
167
+ await access(containedPath(skillRoot, 'SKILL.md'));
168
+ } catch {
169
+ throw new SkillInstallerError(
170
+ 'SKILL_INSTALLATION_FAILED',
171
+ 'A bundled Skill is missing SKILL.md',
172
+ 'Rebuild the CLI package with complete Agent Skill directories.',
173
+ );
174
+ }
175
+ skills[entry.name] = await hashSkillDirectory(skillRoot);
176
+ }
177
+ if (Object.keys(skills).length === 0) {
178
+ throw new SkillInstallerError(
179
+ 'SKILL_INSTALLATION_FAILED',
180
+ 'The CLI package contains no Skills',
181
+ 'Reinstall or regenerate the CLI package.',
182
+ );
183
+ }
184
+ return { ...input, skills };
185
+ }
186
+
187
+ export function parseManifest(value: unknown, expectedBinary?: string): ManagedManifest {
188
+ if (
189
+ !isObject(value) ||
190
+ value['schema_version'] !== '1.0.0' ||
191
+ typeof value['package'] !== 'string' ||
192
+ typeof value['binary'] !== 'string' ||
193
+ typeof value['version'] !== 'string' ||
194
+ !isObject(value['skills'])
195
+ ) {
196
+ throw new SkillInstallerError(
197
+ 'SKILL_CONFLICT',
198
+ 'The managed Skill manifest is invalid',
199
+ 'Inspect or remove the invalid manifest before retrying setup.',
200
+ );
201
+ }
202
+ validateIdentity(value['package'], value['binary'], value['version']);
203
+ if (expectedBinary && value['binary'] !== expectedBinary) {
204
+ throw new SkillInstallerError(
205
+ 'SKILL_CONFLICT',
206
+ 'The managed Skill manifest belongs to another CLI',
207
+ 'Choose another destination or remove the conflicting installation.',
208
+ );
209
+ }
210
+ const skills: Record<string, string> = {};
211
+ for (const [name, hash] of Object.entries(value['skills'])) {
212
+ validateSkillName(name);
213
+ if (typeof hash !== 'string' || !hashPattern.test(hash)) {
214
+ throw new SkillInstallerError(
215
+ 'SKILL_CONFLICT',
216
+ 'The managed Skill manifest contains an invalid hash',
217
+ 'Inspect or remove the invalid manifest before retrying setup.',
218
+ );
219
+ }
220
+ skills[name] = hash;
221
+ }
222
+ return {
223
+ schema_version: '1.0.0',
224
+ package: value['package'],
225
+ binary: value['binary'],
226
+ version: value['version'],
227
+ skills,
228
+ };
229
+ }
230
+
231
+ export async function readManagedManifest(
232
+ root: string,
233
+ binary: string,
234
+ ): Promise<ManagedManifest | undefined> {
235
+ const path = containedPath(root, manifestFilename(binary));
236
+ try {
237
+ const info = await lstat(path);
238
+ if (!info.isFile() || info.isSymbolicLink()) throw new Error('unsafe manifest');
239
+ return parseManifest(JSON.parse(await readFile(path, 'utf8')), binary);
240
+ } catch (error) {
241
+ if (isObject(error) && error['code'] === 'ENOENT') return undefined;
242
+ if (error instanceof SkillInstallerError) throw error;
243
+ throw new SkillInstallerError(
244
+ 'SKILL_CONFLICT',
245
+ 'The managed Skill manifest cannot be read safely',
246
+ 'Inspect or remove the invalid manifest before retrying setup.',
247
+ );
248
+ }
249
+ }
250
+
251
+ export async function inspectInstalledSkill(
252
+ root: string,
253
+ name: string,
254
+ expectedHash: string,
255
+ manifest?: ManagedManifest,
256
+ ): Promise<InstalledSkillInspection> {
257
+ const path = containedPath(root, name);
258
+ try {
259
+ const actualHash = await hashSkillDirectory(path);
260
+ const recordedHash = manifest?.skills[name];
261
+ if (!recordedHash) return { name, state: 'unmanaged', expectedHash, actualHash };
262
+ if (actualHash !== recordedHash)
263
+ return { name, state: 'managed_modified', expectedHash, actualHash };
264
+ if (actualHash !== expectedHash) return { name, state: 'outdated', expectedHash, actualHash };
265
+ return { name, state: 'installed', expectedHash, actualHash };
266
+ } catch (error) {
267
+ if (isObject(error) && error['code'] === 'ENOENT')
268
+ return { name, state: 'missing', expectedHash };
269
+ throw error;
270
+ }
271
+ }
@@ -0,0 +1,169 @@
1
+ import { lstat } from 'node:fs/promises';
2
+ import { homedir } from 'node:os';
3
+ import { relative, resolve } from 'node:path';
4
+ import {
5
+ containedPath,
6
+ inspectInstalledSkill,
7
+ readManagedManifest,
8
+ SkillInstallerError,
9
+ type BundledSuite,
10
+ } from './manifest.ts';
11
+
12
+ export type AgentTarget = 'codex' | 'claude' | 'opencode' | 'cursor';
13
+ export type InstallScope = 'project' | 'global';
14
+
15
+ const sharedAgents: AgentTarget[] = ['codex', 'claude', 'opencode', 'cursor'];
16
+
17
+ export interface SkillDestination {
18
+ agents: AgentTarget[];
19
+ scope: InstallScope;
20
+ root: string;
21
+ location: string;
22
+ compatibility?: Array<{ agents: AgentTarget[]; root: string; location: string }>;
23
+ }
24
+
25
+ export interface DiscoveryCopy {
26
+ agent: AgentTarget;
27
+ scope: InstallScope;
28
+ root: string;
29
+ location: string;
30
+ skill: string;
31
+ state: 'managed' | 'managed_modified' | 'outdated' | 'unmanaged' | 'different_owner';
32
+ }
33
+
34
+ const projectLocations: Record<AgentTarget, string> = {
35
+ codex: '.agents/skills',
36
+ claude: '.claude/skills',
37
+ opencode: '.opencode/skills',
38
+ cursor: '.cursor/skills',
39
+ };
40
+
41
+ const globalLocations: Record<AgentTarget, string> = {
42
+ codex: '.agents/skills',
43
+ claude: '.claude/skills',
44
+ opencode: '.config/opencode/skills',
45
+ cursor: '.cursor/skills',
46
+ };
47
+
48
+ function destinationRoot(input: {
49
+ agent: AgentTarget;
50
+ scope: InstallScope;
51
+ projectDir?: string;
52
+ homeDir: string;
53
+ }): { root: string; location: string } {
54
+ const location =
55
+ input.scope === 'project' ? projectLocations[input.agent] : globalLocations[input.agent];
56
+ if (input.scope === 'project') {
57
+ if (!input.projectDir) {
58
+ throw new SkillInstallerError(
59
+ 'SKILL_PATH_UNSAFE',
60
+ 'Project directory is required for project Skill installation',
61
+ 'Provide an explicit project directory and retry setup.',
62
+ );
63
+ }
64
+ return { root: resolve(input.projectDir, location), location };
65
+ }
66
+ return { root: resolve(input.homeDir, location), location: `~/${location}` };
67
+ }
68
+
69
+ export function resolveDestinations(input: {
70
+ scope: InstallScope;
71
+ projectDir?: string;
72
+ homeDir?: string;
73
+ }): SkillDestination[] {
74
+ const shared = destinationRoot({
75
+ agent: 'codex',
76
+ scope: input.scope,
77
+ ...(input.projectDir ? { projectDir: input.projectDir } : {}),
78
+ homeDir: input.homeDir ?? homedir(),
79
+ });
80
+ const compatibility = (['claude', 'opencode', 'cursor'] as const).map((agent) => ({
81
+ agents: [agent],
82
+ ...destinationRoot({
83
+ agent,
84
+ scope: input.scope,
85
+ ...(input.projectDir ? { projectDir: input.projectDir } : {}),
86
+ homeDir: input.homeDir ?? homedir(),
87
+ }),
88
+ }));
89
+ return [
90
+ {
91
+ agents: sharedAgents,
92
+ scope: input.scope,
93
+ ...shared,
94
+ compatibility,
95
+ },
96
+ ];
97
+ }
98
+
99
+ function discoveryDestinations(input: {
100
+ projectDir: string;
101
+ homeDir: string;
102
+ }): Array<SkillDestination & { agent: AgentTarget }> {
103
+ return (['project', 'global'] as const).map((scope) => ({
104
+ agent: 'codex',
105
+ agents: sharedAgents,
106
+ scope,
107
+ ...destinationRoot({
108
+ agent: 'codex',
109
+ scope,
110
+ projectDir: input.projectDir,
111
+ homeDir: input.homeDir,
112
+ }),
113
+ }));
114
+ }
115
+
116
+ async function exists(path: string): Promise<boolean> {
117
+ try {
118
+ await lstat(path);
119
+ return true;
120
+ } catch (error) {
121
+ if (typeof error === 'object' && error !== null && 'code' in error && error.code === 'ENOENT')
122
+ return false;
123
+ throw error;
124
+ }
125
+ }
126
+
127
+ export async function inspectDiscoveryCopies(input: {
128
+ suite: BundledSuite;
129
+ projectDir: string;
130
+ homeDir?: string;
131
+ }): Promise<DiscoveryCopy[]> {
132
+ const copies: DiscoveryCopy[] = [];
133
+ for (const destination of discoveryDestinations({
134
+ projectDir: resolve(input.projectDir),
135
+ homeDir: resolve(input.homeDir ?? homedir()),
136
+ })) {
137
+ const manifest = await readManagedManifest(destination.root, input.suite.binary);
138
+ for (const [skill, expectedHash] of Object.entries(input.suite.skills)) {
139
+ if (!(await exists(containedPath(destination.root, skill)))) continue;
140
+ const inspection = await inspectInstalledSkill(
141
+ destination.root,
142
+ skill,
143
+ expectedHash,
144
+ manifest,
145
+ );
146
+ let state: DiscoveryCopy['state'];
147
+ if (!manifest?.skills[skill]) state = 'unmanaged';
148
+ else if (
149
+ manifest.package !== input.suite.packageName ||
150
+ manifest.binary !== input.suite.binary
151
+ )
152
+ state = 'different_owner';
153
+ else if (manifest.version !== input.suite.version) state = 'outdated';
154
+ else state = inspection.state === 'installed' ? 'managed' : 'managed_modified';
155
+ copies.push({
156
+ agent: destination.agent,
157
+ scope: destination.scope,
158
+ root: destination.root,
159
+ location:
160
+ destination.scope === 'global'
161
+ ? destination.location
162
+ : relative(input.projectDir, destination.root) || '.',
163
+ skill,
164
+ state,
165
+ });
166
+ }
167
+ }
168
+ return copies;
169
+ }