@alavida/agentpack 0.1.2 → 0.1.3

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 (43) hide show
  1. package/README.md +11 -1
  2. package/bin/intent.js +20 -0
  3. package/package.json +11 -5
  4. package/skills/agentpack-cli/SKILL.md +4 -1
  5. package/skills/authoring-skillgraphs-from-knowledge/SKILL.md +148 -0
  6. package/skills/authoring-skillgraphs-from-knowledge/references/authored-metadata.md +6 -0
  7. package/skills/developing-and-testing-skills/SKILL.md +109 -0
  8. package/skills/developing-and-testing-skills/references/local-workbench.md +7 -0
  9. package/skills/getting-started-skillgraphs/SKILL.md +115 -0
  10. package/skills/getting-started-skillgraphs/references/command-routing.md +7 -0
  11. package/skills/identifying-skill-opportunities/SKILL.md +119 -0
  12. package/skills/identifying-skill-opportunities/references/capability-boundaries.md +6 -0
  13. package/skills/maintaining-skillgraph-freshness/SKILL.md +110 -0
  14. package/skills/repairing-broken-skill-or-plugin-state/SKILL.md +112 -0
  15. package/skills/repairing-broken-skill-or-plugin-state/references/diagnostic-flows.md +6 -0
  16. package/skills/shipping-production-plugins-and-packages/SKILL.md +123 -0
  17. package/skills/shipping-production-plugins-and-packages/references/plugin-delivery.md +6 -0
  18. package/src/application/skills/build-skill-workbench-model.js +194 -0
  19. package/src/application/skills/run-skill-workbench-action.js +23 -0
  20. package/src/application/skills/start-skill-dev-workbench.js +192 -0
  21. package/src/cli.js +1 -1
  22. package/src/commands/skills.js +7 -1
  23. package/src/dashboard/App.jsx +343 -0
  24. package/src/dashboard/components/Breadcrumbs.jsx +45 -0
  25. package/src/dashboard/components/ControlStrip.jsx +153 -0
  26. package/src/dashboard/components/InspectorPanel.jsx +203 -0
  27. package/src/dashboard/components/SkillGraph.jsx +567 -0
  28. package/src/dashboard/components/Tooltip.jsx +111 -0
  29. package/src/dashboard/dist/dashboard.js +26692 -0
  30. package/src/dashboard/index.html +81 -0
  31. package/src/dashboard/lib/api.js +19 -0
  32. package/src/dashboard/lib/router.js +15 -0
  33. package/src/dashboard/main.jsx +4 -0
  34. package/src/domain/plugins/load-plugin-definition.js +163 -0
  35. package/src/domain/plugins/plugin-diagnostic-error.js +18 -0
  36. package/src/domain/plugins/plugin-requirements.js +15 -0
  37. package/src/domain/skills/skill-graph.js +1 -0
  38. package/src/infrastructure/runtime/open-browser.js +20 -0
  39. package/src/infrastructure/runtime/skill-dev-workbench-server.js +96 -0
  40. package/src/infrastructure/runtime/watch-skill-workbench.js +68 -0
  41. package/src/lib/plugins.js +19 -28
  42. package/src/lib/skills.js +60 -12
  43. package/src/utils/errors.js +33 -1
@@ -0,0 +1,81 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <title>Skill Dev Workbench</title>
7
+ <link rel="preconnect" href="https://fonts.googleapis.com">
8
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
9
+ <link href="https://fonts.googleapis.com/css2?family=Crimson+Pro:ital,wght@0,400;0,500;0,600;1,400;1,500&family=Noto+Sans+Mono:wght@400;500;600&display=swap" rel="stylesheet">
10
+ <style>
11
+ :root {
12
+ color-scheme: dark;
13
+ --bg: #1a1916;
14
+ --surface: #252320;
15
+ --border: rgba(255, 255, 255, 0.06);
16
+ --border-bright: rgba(255, 255, 255, 0.12);
17
+ --text: #e8e4dc;
18
+ --text-dim: #9a9488;
19
+ --text-faint: #6b655c;
20
+
21
+ --status-current: #8fa67e;
22
+ --status-stale: #d4a45e;
23
+ --status-affected: #c4956e;
24
+ --status-unknown: #9a9488;
25
+
26
+ --edge-provenance: #7a9abb;
27
+ --edge-requires: #8fa67e;
28
+ --accent: #ffbf47;
29
+
30
+ --font-body: 'Crimson Pro', Georgia, 'Times New Roman', serif;
31
+ --font-mono: 'Noto Sans Mono', 'Courier New', monospace;
32
+ }
33
+
34
+ [data-theme="light"] {
35
+ color-scheme: light;
36
+ --bg: #f5f2ed;
37
+ --surface: #e8e4dc;
38
+ --border: rgba(0, 0, 0, 0.10);
39
+ --border-bright: rgba(0, 0, 0, 0.20);
40
+ --text: #1a1816;
41
+ --text-dim: #3d3830;
42
+ --text-faint: #6b655c;
43
+
44
+ --status-current: #3d6e2c;
45
+ --status-stale: #a07020;
46
+ --status-affected: #8c5530;
47
+ --status-unknown: #6b655c;
48
+
49
+ --edge-provenance: #2e6080;
50
+ --edge-requires: #3d6e2c;
51
+ --accent: #a07020;
52
+ }
53
+
54
+ * { box-sizing: border-box; margin: 0; padding: 0; }
55
+
56
+ body {
57
+ font-family: var(--font-body);
58
+ background: var(--bg);
59
+ color: var(--text);
60
+ overflow: hidden;
61
+ height: 100vh;
62
+ -webkit-font-smoothing: antialiased;
63
+ }
64
+
65
+ #app {
66
+ height: 100vh;
67
+ display: flex;
68
+ flex-direction: column;
69
+ }
70
+
71
+ @keyframes stale-pulse {
72
+ 0%, 100% { opacity: 0.4; }
73
+ 50% { opacity: 0.8; }
74
+ }
75
+ </style>
76
+ </head>
77
+ <body>
78
+ <div id="app" data-app-root></div>
79
+ <script type="module" src="/assets/dashboard.js"></script>
80
+ </body>
81
+ </html>
@@ -0,0 +1,19 @@
1
+ export async function fetchWorkbenchModel(skillPackageName) {
2
+ const params = skillPackageName ? `?skill=${encodeURIComponent(skillPackageName)}` : '';
3
+ const response = await fetch(`/api/model${params}`);
4
+ if (!response.ok) {
5
+ const body = await response.json().catch(() => ({}));
6
+ throw new Error(body.error || `Failed to load workbench model: ${response.status}`);
7
+ }
8
+ return response.json();
9
+ }
10
+
11
+ export async function runWorkbenchAction(action) {
12
+ const response = await fetch(`/api/actions/${action}`, {
13
+ method: 'POST',
14
+ });
15
+ if (!response.ok) {
16
+ throw new Error(`Failed to run ${action}: ${response.status}`);
17
+ }
18
+ return response.json();
19
+ }
@@ -0,0 +1,15 @@
1
+ export function getSkillFromHash() {
2
+ const hash = window.location.hash;
3
+ const match = hash.match(/^#\/skill\/(.+)$/);
4
+ return match ? decodeURIComponent(match[1]) : null;
5
+ }
6
+
7
+ export function setSkillHash(packageName) {
8
+ window.location.hash = `#/skill/${encodeURIComponent(packageName)}`;
9
+ }
10
+
11
+ export function onHashChange(callback) {
12
+ window.addEventListener('hashchange', () => {
13
+ callback(getSkillFromHash());
14
+ });
15
+ }
@@ -0,0 +1,4 @@
1
+ import { createRoot } from 'react-dom/client';
2
+ import { App } from './App.jsx';
3
+
4
+ createRoot(document.getElementById('app')).render(<App />);
@@ -0,0 +1,163 @@
1
+ import { existsSync, readFileSync } from 'node:fs';
2
+ import { basename, join, resolve } from 'node:path';
3
+ import { normalizeRepoPath } from '../skills/skill-model.js';
4
+ import { findRepoRoot } from '../../lib/context.js';
5
+ import { NotFoundError } from '../../utils/errors.js';
6
+ import { PluginDiagnosticError } from './plugin-diagnostic-error.js';
7
+ import { getPluginRequirementLevel } from './plugin-requirements.js';
8
+
9
+ function resolvePluginDir(repoRoot, target) {
10
+ const absoluteTarget = resolve(repoRoot, target);
11
+ if (!existsSync(absoluteTarget)) {
12
+ throw new NotFoundError('plugin not found', {
13
+ code: 'plugin_not_found',
14
+ suggestion: `Target: ${target}`,
15
+ });
16
+ }
17
+
18
+ return absoluteTarget;
19
+ }
20
+
21
+ function inferPluginPackageName(pluginDir) {
22
+ return `@alavida-ai/plugin-${basename(pluginDir)}`;
23
+ }
24
+
25
+ function readPluginPackageJson(repoRoot, pluginDir, requirementLevel) {
26
+ const packageJsonPath = join(pluginDir, 'package.json');
27
+ const displayPath = normalizeRepoPath(repoRoot, packageJsonPath);
28
+ const example = {
29
+ name: inferPluginPackageName(pluginDir),
30
+ version: '0.1.0',
31
+ };
32
+
33
+ if (!existsSync(packageJsonPath)) {
34
+ throw new PluginDiagnosticError('No package.json found for plugin target', {
35
+ code: 'missing_plugin_package_json',
36
+ path: displayPath,
37
+ nextSteps: [
38
+ {
39
+ action: 'create_file',
40
+ path: displayPath,
41
+ reason: 'A plugin must declare package metadata before it can be inspected',
42
+ example,
43
+ },
44
+ ],
45
+ details: {
46
+ requirementLevel,
47
+ missing: ['package.json'],
48
+ },
49
+ });
50
+ }
51
+
52
+ let pkg;
53
+ try {
54
+ pkg = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
55
+ } catch {
56
+ throw new PluginDiagnosticError('Plugin package.json contains invalid JSON', {
57
+ code: 'invalid_plugin_package_json',
58
+ path: displayPath,
59
+ nextSteps: [
60
+ {
61
+ action: 'edit_file',
62
+ path: displayPath,
63
+ reason: 'Inspect and validate require a readable package.json file',
64
+ example,
65
+ },
66
+ ],
67
+ details: {
68
+ requirementLevel,
69
+ },
70
+ });
71
+ }
72
+
73
+ const missingFields = ['name', 'version'].filter((field) => !pkg[field]);
74
+ if (missingFields.length > 0) {
75
+ throw new PluginDiagnosticError(`Plugin package.json missing required fields: ${missingFields.join(', ')}`, {
76
+ code: 'missing_plugin_package_fields',
77
+ path: displayPath,
78
+ nextSteps: [
79
+ {
80
+ action: 'edit_file',
81
+ path: displayPath,
82
+ reason: 'A plugin package.json must include required package metadata before inspection can continue',
83
+ example: {
84
+ ...example,
85
+ ...('name' in pkg ? { name: pkg.name } : {}),
86
+ ...('version' in pkg ? { version: pkg.version } : {}),
87
+ },
88
+ },
89
+ ],
90
+ details: {
91
+ requirementLevel,
92
+ missingFields,
93
+ },
94
+ });
95
+ }
96
+
97
+ return {
98
+ packageJsonPath,
99
+ packageJson: pkg,
100
+ packageName: pkg.name,
101
+ packageVersion: pkg.version,
102
+ };
103
+ }
104
+
105
+ function readPluginManifest(repoRoot, pluginDir, requirementLevel) {
106
+ const pluginManifestPath = join(pluginDir, '.claude-plugin', 'plugin.json');
107
+ const displayPath = normalizeRepoPath(repoRoot, pluginManifestPath);
108
+
109
+ if (!existsSync(pluginManifestPath)) {
110
+ throw new PluginDiagnosticError('Plugin manifest is missing', {
111
+ code: 'missing_plugin_manifest',
112
+ path: displayPath,
113
+ nextSteps: [
114
+ {
115
+ action: 'create_file',
116
+ path: displayPath,
117
+ reason: 'Plugin commands require .claude-plugin/plugin.json to describe the runtime plugin entrypoint',
118
+ example: {
119
+ name: basename(pluginDir),
120
+ description: 'Describe what this plugin provides.',
121
+ },
122
+ },
123
+ ],
124
+ details: {
125
+ requirementLevel,
126
+ missing: ['.claude-plugin/plugin.json'],
127
+ },
128
+ });
129
+ }
130
+
131
+ return {
132
+ pluginManifestPath,
133
+ pluginManifest: JSON.parse(readFileSync(pluginManifestPath, 'utf-8')),
134
+ };
135
+ }
136
+
137
+ export function loadPluginDefinition(target, {
138
+ cwd = process.cwd(),
139
+ requirementLevel = 'inspect',
140
+ } = {}) {
141
+ const repoRoot = findRepoRoot(cwd);
142
+ const pluginDir = resolvePluginDir(repoRoot, target);
143
+ const requirements = getPluginRequirementLevel(requirementLevel);
144
+
145
+ const packageData = readPluginPackageJson(repoRoot, pluginDir, requirementLevel);
146
+ const manifestData = requirements.pluginManifest
147
+ ? readPluginManifest(repoRoot, pluginDir, requirementLevel)
148
+ : {
149
+ pluginManifestPath: join(pluginDir, '.claude-plugin', 'plugin.json'),
150
+ pluginManifest: null,
151
+ };
152
+
153
+ return {
154
+ repoRoot,
155
+ pluginDir,
156
+ packageJsonPath: packageData.packageJsonPath,
157
+ packageJson: packageData.packageJson,
158
+ packageName: packageData.packageName,
159
+ packageVersion: packageData.packageVersion,
160
+ pluginManifestPath: manifestData.pluginManifestPath,
161
+ pluginManifest: manifestData.pluginManifest,
162
+ };
163
+ }
@@ -0,0 +1,18 @@
1
+ import { ValidationError } from '../../utils/errors.js';
2
+
3
+ export class PluginDiagnosticError extends ValidationError {
4
+ constructor(message, {
5
+ code,
6
+ path,
7
+ nextSteps = [],
8
+ details = {},
9
+ } = {}) {
10
+ super(message, {
11
+ code,
12
+ path,
13
+ nextSteps,
14
+ details,
15
+ });
16
+ this.name = 'PluginDiagnosticError';
17
+ }
18
+ }
@@ -0,0 +1,15 @@
1
+ export const PLUGIN_REQUIREMENTS = {
2
+ inspect: {
3
+ pluginManifest: true,
4
+ },
5
+ validate: {
6
+ pluginManifest: true,
7
+ },
8
+ build: {
9
+ pluginManifest: true,
10
+ },
11
+ };
12
+
13
+ export function getPluginRequirementLevel(level = 'inspect') {
14
+ return PLUGIN_REQUIREMENTS[level] || PLUGIN_REQUIREMENTS.inspect;
15
+ }
@@ -26,6 +26,7 @@ export function readSkillGraphNode(repoRoot, packageDir, {
26
26
 
27
27
  return {
28
28
  name: skillMetadata.name,
29
+ description: skillMetadata.description,
29
30
  packageName: packageMetadata.packageName,
30
31
  packageVersion: packageMetadata.packageVersion,
31
32
  skillPath: normalizeDisplayPath(repoRoot, packageDir),
@@ -0,0 +1,20 @@
1
+ import { spawn } from 'node:child_process';
2
+
3
+ export function openBrowser(url) {
4
+ if (process.env.AGENTPACK_DISABLE_BROWSER === '1') return;
5
+
6
+ const command = process.platform === 'darwin'
7
+ ? 'open'
8
+ : process.platform === 'win32'
9
+ ? 'start'
10
+ : 'xdg-open';
11
+
12
+ try {
13
+ spawn(command, [url], {
14
+ detached: true,
15
+ stdio: 'ignore',
16
+ }).unref();
17
+ } catch {
18
+ // Browser launch failure should not fail the core dev workflow.
19
+ }
20
+ }
@@ -0,0 +1,96 @@
1
+ import http from 'node:http';
2
+ import { existsSync, readFileSync } from 'node:fs';
3
+ import { dirname, join, resolve } from 'node:path';
4
+ import { fileURLToPath } from 'node:url';
5
+ import { AgentpackError } from '../../utils/errors.js';
6
+
7
+ const __dirname = dirname(fileURLToPath(import.meta.url));
8
+ const projectRoot = join(__dirname, '..', '..', '..');
9
+ const htmlPath = join(projectRoot, 'src', 'dashboard', 'index.html');
10
+ const bundlePath = process.env.AGENTPACK_DASHBOARD_BUNDLE_PATH
11
+ ? resolve(process.cwd(), process.env.AGENTPACK_DASHBOARD_BUNDLE_PATH)
12
+ : join(projectRoot, 'src', 'dashboard', 'dist', 'dashboard.js');
13
+
14
+ function ensureDashboardBundle() {
15
+ if (existsSync(bundlePath)) return;
16
+
17
+ throw new AgentpackError('Skill workbench bundle is missing from this install', {
18
+ code: 'missing_skill_workbench_bundle',
19
+ suggestion: 'Reinstall agentpack or rebuild the dashboard bundle before starting skills dev.',
20
+ path: bundlePath,
21
+ });
22
+ }
23
+
24
+ export async function startSkillDevWorkbenchServer({ buildModel, defaultSkill, onAction = null }) {
25
+ ensureDashboardBundle();
26
+
27
+ const server = http.createServer(async (req, res) => {
28
+ if (!req.url) {
29
+ res.writeHead(404);
30
+ res.end();
31
+ return;
32
+ }
33
+
34
+ const parsedUrl = new URL(req.url, `http://${req.headers.host || 'localhost'}`);
35
+
36
+ if (parsedUrl.pathname === '/api/model') {
37
+ const skillParam = parsedUrl.searchParams.get('skill') || defaultSkill;
38
+ try {
39
+ const model = buildModel(skillParam);
40
+ if (!model) {
41
+ res.writeHead(404, { 'content-type': 'application/json' });
42
+ res.end(JSON.stringify({ error: 'Skill not found' }));
43
+ return;
44
+ }
45
+ res.writeHead(200, { 'content-type': 'application/json' });
46
+ res.end(JSON.stringify(model));
47
+ } catch (error) {
48
+ res.writeHead(500, { 'content-type': 'application/json' });
49
+ res.end(JSON.stringify({ error: error.message }));
50
+ }
51
+ return;
52
+ }
53
+
54
+ if (req.method === 'POST' && parsedUrl.pathname.startsWith('/api/actions/')) {
55
+ const action = parsedUrl.pathname.slice('/api/actions/'.length);
56
+ res.writeHead(200, { 'content-type': 'application/json' });
57
+ try {
58
+ const result = onAction ? await onAction(action) : { refreshed: true };
59
+ res.end(JSON.stringify({ ok: true, action, result }));
60
+ } catch (error) {
61
+ res.statusCode = 500;
62
+ res.end(JSON.stringify({ ok: false, action, error: error.message }));
63
+ }
64
+ return;
65
+ }
66
+
67
+ if (parsedUrl.pathname === '/') {
68
+ res.writeHead(200, { 'content-type': 'text/html; charset=utf-8' });
69
+ res.end(readFileSync(htmlPath, 'utf-8'));
70
+ return;
71
+ }
72
+
73
+ if (parsedUrl.pathname === '/assets/dashboard.js') {
74
+ res.writeHead(200, { 'content-type': 'text/javascript; charset=utf-8' });
75
+ res.end(readFileSync(bundlePath, 'utf-8'));
76
+ return;
77
+ }
78
+
79
+ res.writeHead(404);
80
+ res.end();
81
+ });
82
+
83
+ return new Promise((resolve, reject) => {
84
+ server.once('error', reject);
85
+ server.listen(0, '127.0.0.1', () => {
86
+ const address = server.address();
87
+ resolve({
88
+ port: address.port,
89
+ url: `http://127.0.0.1:${address.port}`,
90
+ close() {
91
+ server.close();
92
+ },
93
+ });
94
+ });
95
+ });
96
+ }
@@ -0,0 +1,68 @@
1
+ import { watch } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import { parseSkillFrontmatterFile } from '../../domain/skills/skill-model.js';
4
+
5
+ export function watchSkillWorkbench(repoRoot, skillDir, onRefresh) {
6
+ const staticWatchers = [];
7
+ const sourceWatchers = new Map();
8
+ let timer = null;
9
+
10
+ const refresh = () => {
11
+ clearTimeout(timer);
12
+ timer = setTimeout(() => {
13
+ onRefresh();
14
+ }, 50);
15
+ };
16
+
17
+ const watchPath = (pathValue) => {
18
+ try {
19
+ const watcher = watch(pathValue, refresh);
20
+ return watcher;
21
+ } catch {
22
+ // Ignore paths that cannot be watched for now.
23
+ return null;
24
+ }
25
+ };
26
+
27
+ const syncSourceWatchers = () => {
28
+ let metadata;
29
+ try {
30
+ metadata = parseSkillFrontmatterFile(skillFile);
31
+ } catch {
32
+ return;
33
+ }
34
+
35
+ const nextSources = new Set(metadata.sources.map((source) => join(repoRoot, source)));
36
+
37
+ for (const [pathValue, watcher] of sourceWatchers.entries()) {
38
+ if (nextSources.has(pathValue)) continue;
39
+ watcher.close();
40
+ sourceWatchers.delete(pathValue);
41
+ }
42
+
43
+ for (const pathValue of nextSources) {
44
+ if (sourceWatchers.has(pathValue)) continue;
45
+ const watcher = watchPath(pathValue);
46
+ if (watcher) sourceWatchers.set(pathValue, watcher);
47
+ }
48
+ };
49
+
50
+ const skillFile = join(skillDir, 'SKILL.md');
51
+ const packageJsonPath = join(skillDir, 'package.json');
52
+ const skillFileWatcher = watch(skillFile, () => {
53
+ syncSourceWatchers();
54
+ refresh();
55
+ });
56
+ staticWatchers.push(skillFileWatcher);
57
+ const packageWatcher = watchPath(packageJsonPath);
58
+ if (packageWatcher) staticWatchers.push(packageWatcher);
59
+ syncSourceWatchers();
60
+
61
+ return {
62
+ close() {
63
+ clearTimeout(timer);
64
+ for (const watcher of staticWatchers) watcher.close();
65
+ for (const watcher of sourceWatchers.values()) watcher.close();
66
+ },
67
+ };
68
+ }
@@ -3,6 +3,7 @@ import { dirname, join, resolve } from 'node:path';
3
3
  import { createRequire } from 'node:module';
4
4
  import { resolveDependencyClosure } from '../domain/skills/skill-graph.js';
5
5
  import { normalizeRepoPath, parseSkillFrontmatterFile, readPackageMetadata } from '../domain/skills/skill-model.js';
6
+ import { loadPluginDefinition } from '../domain/plugins/load-plugin-definition.js';
6
7
  import { watchDirectoryTree } from '../infrastructure/runtime/watch-tree.js';
7
8
  import {
8
9
  findPackageDirByName,
@@ -216,27 +217,16 @@ export function startPluginDev(target, {
216
217
  }
217
218
 
218
219
  export function inspectPluginBundle(target, { cwd = process.cwd() } = {}) {
219
- const repoRoot = findRepoRoot(cwd);
220
- const pluginDir = resolvePluginDir(repoRoot, target);
221
- const pluginManifestPath = join(pluginDir, '.claude-plugin', 'plugin.json');
222
- const pluginManifest = existsSync(pluginManifestPath)
223
- ? JSON.parse(readFileSync(pluginManifestPath, 'utf-8'))
224
- : null;
225
-
226
- const packageMetadata = readPackageMetadata(pluginDir);
227
- if (!packageMetadata.packageName || !packageMetadata.packageVersion) {
228
- throw new ValidationError('plugin package.json missing name or version', {
229
- code: 'missing_plugin_package_metadata',
230
- suggestion: normalizeRepoPath(repoRoot, join(pluginDir, 'package.json')),
231
- });
232
- }
233
-
234
- if (!pluginManifest) {
235
- throw new ValidationError('plugin missing .claude-plugin/plugin.json', {
236
- code: 'missing_plugin_manifest',
237
- suggestion: normalizeRepoPath(repoRoot, join(pluginDir, '.claude-plugin', 'plugin.json')),
238
- });
239
- }
220
+ const definition = loadPluginDefinition(target, { cwd, requirementLevel: 'inspect' });
221
+ const {
222
+ repoRoot,
223
+ pluginDir,
224
+ packageJson,
225
+ packageName,
226
+ packageVersion,
227
+ pluginManifest,
228
+ pluginManifestPath,
229
+ } = definition;
240
230
 
241
231
  const localSkills = collectPluginLocalSkills(pluginDir);
242
232
  const directRequires = [...new Set(localSkills.flatMap((skill) => skill.requires))].sort();
@@ -244,9 +234,9 @@ export function inspectPluginBundle(target, { cwd = process.cwd() } = {}) {
244
234
  const directPackageSet = new Set(directRequires);
245
235
 
246
236
  return {
247
- pluginName: pluginManifest.name || packageMetadata.packageName,
248
- packageName: packageMetadata.packageName,
249
- packageVersion: packageMetadata.packageVersion,
237
+ pluginName: pluginManifest.name || packageName,
238
+ packageName,
239
+ packageVersion,
250
240
  pluginPath: normalizeRepoPath(repoRoot, pluginDir),
251
241
  pluginManifestPath: normalizeRepoPath(repoRoot, pluginManifestPath),
252
242
  localSkills: localSkills.map((skill) => ({
@@ -279,10 +269,11 @@ export function inspectPluginBundle(target, { cwd = process.cwd() } = {}) {
279
269
  }
280
270
 
281
271
  export function validatePluginBundle(target, { cwd = process.cwd() } = {}) {
282
- const repoRoot = findRepoRoot(cwd);
283
- const pluginDir = resolvePluginDir(repoRoot, target);
272
+ const { repoRoot, pluginDir, packageJson } = loadPluginDefinition(target, {
273
+ cwd,
274
+ requirementLevel: 'validate',
275
+ });
284
276
  const result = inspectPluginBundle(target, { cwd });
285
- const packageMetadata = readPackageMetadata(pluginDir);
286
277
  const issues = [];
287
278
  const localSkillNames = new Set(result.localSkills.map((skill) => skill.localName));
288
279
  const bundledSkillNames = new Map();
@@ -299,7 +290,7 @@ export function validatePluginBundle(target, { cwd = process.cwd() } = {}) {
299
290
 
300
291
  for (const localSkill of result.localSkills) {
301
292
  for (const packageName of localSkill.requires) {
302
- const declared = packageMetadata.devDependencies[packageName];
293
+ const declared = packageJson.devDependencies?.[packageName];
303
294
  const resolvedPackage = resolvePackageDir(repoRoot, pluginDir, packageName);
304
295
  if (!declared || !resolvedPackage) {
305
296
  coveredPackages.add(packageName);