@alavida/agentpack 0.1.1 → 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 (57) hide show
  1. package/README.md +16 -2
  2. package/bin/intent.js +20 -0
  3. package/package.json +11 -5
  4. package/skills/agentpack-cli/SKILL.md +16 -1
  5. package/skills/agentpack-cli/references/skill-lifecycle.md +21 -1
  6. package/skills/authoring-skillgraphs-from-knowledge/SKILL.md +148 -0
  7. package/skills/authoring-skillgraphs-from-knowledge/references/authored-metadata.md +6 -0
  8. package/skills/developing-and-testing-skills/SKILL.md +109 -0
  9. package/skills/developing-and-testing-skills/references/local-workbench.md +7 -0
  10. package/skills/getting-started-skillgraphs/SKILL.md +115 -0
  11. package/skills/getting-started-skillgraphs/references/command-routing.md +7 -0
  12. package/skills/identifying-skill-opportunities/SKILL.md +119 -0
  13. package/skills/identifying-skill-opportunities/references/capability-boundaries.md +6 -0
  14. package/skills/maintaining-skillgraph-freshness/SKILL.md +110 -0
  15. package/skills/repairing-broken-skill-or-plugin-state/SKILL.md +112 -0
  16. package/skills/repairing-broken-skill-or-plugin-state/references/diagnostic-flows.md +6 -0
  17. package/skills/shipping-production-plugins-and-packages/SKILL.md +123 -0
  18. package/skills/shipping-production-plugins-and-packages/references/plugin-delivery.md +6 -0
  19. package/src/application/plugins/build-plugin.js +5 -0
  20. package/src/application/plugins/inspect-plugin-bundle.js +5 -0
  21. package/src/application/plugins/validate-plugin-bundle.js +5 -0
  22. package/src/application/skills/build-skill-workbench-model.js +194 -0
  23. package/src/application/skills/inspect-skill.js +5 -0
  24. package/src/application/skills/list-stale-skills.js +9 -0
  25. package/src/application/skills/run-skill-workbench-action.js +23 -0
  26. package/src/application/skills/start-skill-dev-workbench.js +192 -0
  27. package/src/application/skills/validate-skills.js +5 -0
  28. package/src/cli.js +1 -1
  29. package/src/commands/plugin.js +7 -4
  30. package/src/commands/skills.js +14 -9
  31. package/src/dashboard/App.jsx +343 -0
  32. package/src/dashboard/components/Breadcrumbs.jsx +45 -0
  33. package/src/dashboard/components/ControlStrip.jsx +153 -0
  34. package/src/dashboard/components/InspectorPanel.jsx +203 -0
  35. package/src/dashboard/components/SkillGraph.jsx +567 -0
  36. package/src/dashboard/components/Tooltip.jsx +111 -0
  37. package/src/dashboard/dist/dashboard.js +26692 -0
  38. package/src/dashboard/index.html +81 -0
  39. package/src/dashboard/lib/api.js +19 -0
  40. package/src/dashboard/lib/router.js +15 -0
  41. package/src/dashboard/main.jsx +4 -0
  42. package/src/domain/plugins/load-plugin-definition.js +163 -0
  43. package/src/domain/plugins/plugin-diagnostic-error.js +18 -0
  44. package/src/domain/plugins/plugin-requirements.js +15 -0
  45. package/src/domain/skills/skill-graph.js +137 -0
  46. package/src/domain/skills/skill-model.js +187 -0
  47. package/src/domain/skills/skill-provenance.js +69 -0
  48. package/src/infrastructure/fs/build-state-repository.js +16 -0
  49. package/src/infrastructure/fs/install-state-repository.js +16 -0
  50. package/src/infrastructure/runtime/materialize-skills.js +117 -0
  51. package/src/infrastructure/runtime/open-browser.js +20 -0
  52. package/src/infrastructure/runtime/skill-dev-workbench-server.js +96 -0
  53. package/src/infrastructure/runtime/watch-skill-workbench.js +68 -0
  54. package/src/infrastructure/runtime/watch-tree.js +44 -0
  55. package/src/lib/plugins.js +46 -117
  56. package/src/lib/skills.js +141 -459
  57. package/src/utils/errors.js +33 -1
package/src/lib/skills.js CHANGED
@@ -1,8 +1,35 @@
1
- import { existsSync, mkdirSync, readFileSync, readdirSync, rmSync, symlinkSync, watch, writeFileSync } from 'node:fs';
1
+ import { existsSync, readFileSync, readdirSync, watch, writeFileSync } from 'node:fs';
2
2
  import { dirname, isAbsolute, join, relative, resolve } from 'node:path';
3
- import { createHash } from 'node:crypto';
4
3
  import { execFileSync } from 'node:child_process';
5
4
  import { findAllWorkbenches, findRepoRoot, findWorkbenchContext, resolveWorkbenchFlag } from './context.js';
5
+ import {
6
+ buildReverseDependencies,
7
+ buildSkillGraph,
8
+ buildSkillStatusMap,
9
+ readNodeStatus,
10
+ } from '../domain/skills/skill-graph.js';
11
+ import { readInstallState } from '../infrastructure/fs/install-state-repository.js';
12
+ import {
13
+ ensureSkillLink,
14
+ rebuildInstallState,
15
+ removePathIfExists,
16
+ removeSkillLinks,
17
+ removeSkillLinksByNames,
18
+ } from '../infrastructure/runtime/materialize-skills.js';
19
+ import {
20
+ normalizeDisplayPath,
21
+ normalizeRepoPath,
22
+ parseSkillFrontmatterFile,
23
+ readPackageMetadata,
24
+ } from '../domain/skills/skill-model.js';
25
+ import {
26
+ buildStateRecordForPackageDir,
27
+ compareRecordedSources,
28
+ hashFile,
29
+ readBuildState,
30
+ writeBuildState,
31
+ } from '../domain/skills/skill-provenance.js';
32
+ import { startSkillDevWorkbench } from '../application/skills/start-skill-dev-workbench.js';
6
33
  import { AgentpackError, EXIT_CODES, NetworkError, NotFoundError, ValidationError } from '../utils/errors.js';
7
34
 
8
35
  const GITHUB_PACKAGES_REGISTRY = 'https://npm.pkg.github.com';
@@ -17,173 +44,6 @@ function inferManagedScope(packageName) {
17
44
  return MANAGED_PACKAGE_SCOPES.find((scope) => packageName?.startsWith(`${scope}/`)) || null;
18
45
  }
19
46
 
20
- function parseScalar(value) {
21
- const trimmed = value.trim();
22
- if (
23
- (trimmed.startsWith('"') && trimmed.endsWith('"')) ||
24
- (trimmed.startsWith("'") && trimmed.endsWith("'"))
25
- ) {
26
- return trimmed.slice(1, -1);
27
- }
28
- return trimmed;
29
- }
30
-
31
- function foldBlockScalar(lines, startIndex, baseIndent) {
32
- const values = [];
33
- let index = startIndex + 1;
34
-
35
- while (index < lines.length) {
36
- const rawLine = lines[index];
37
- if (!rawLine.trim()) {
38
- values.push('');
39
- index += 1;
40
- continue;
41
- }
42
-
43
- const indentMatch = rawLine.match(/^(\s*)/);
44
- const indent = indentMatch ? indentMatch[1].length : 0;
45
- if (indent <= baseIndent) break;
46
-
47
- values.push(rawLine.slice(baseIndent + 2).trimEnd());
48
- index += 1;
49
- }
50
-
51
- const folded = values
52
- .join('\n')
53
- .split('\n\n')
54
- .map((chunk) => chunk.split('\n').join(' ').trim())
55
- .filter((chunk, idx, arr) => chunk.length > 0 || idx < arr.length - 1)
56
- .join('\n\n')
57
- .trim();
58
-
59
- return { value: folded, nextIndex: index };
60
- }
61
-
62
- function ensureContainer(target, key) {
63
- if (!target[key] || typeof target[key] !== 'object' || Array.isArray(target[key])) {
64
- target[key] = {};
65
- }
66
- return target[key];
67
- }
68
-
69
- export function parseSkillFrontmatterFile(skillFilePath) {
70
- if (!existsSync(skillFilePath)) {
71
- throw new NotFoundError(`skill file not found: ${skillFilePath}`, { code: 'skill_not_found' });
72
- }
73
-
74
- const content = readFileSync(skillFilePath, 'utf-8');
75
- if (!content.startsWith('---\n')) {
76
- throw new ValidationError('SKILL.md missing frontmatter', { code: 'missing_frontmatter' });
77
- }
78
-
79
- const fmEnd = content.indexOf('\n---', 4);
80
- if (fmEnd === -1) {
81
- throw new ValidationError('SKILL.md has unclosed frontmatter', { code: 'unclosed_frontmatter' });
82
- }
83
-
84
- const lines = content.slice(4, fmEnd).split('\n');
85
- const fields = {};
86
- let activeArrayKey = null;
87
- let activeArrayTarget = null;
88
- let activeParentKey = null;
89
-
90
- for (let index = 0; index < lines.length; index += 1) {
91
- const rawLine = lines[index];
92
- const line = rawLine.trimEnd();
93
- if (!line.trim()) continue;
94
-
95
- const listMatch = rawLine.match(/^(\s*)-\s+(.+)$/);
96
- if (listMatch && activeArrayKey && activeArrayTarget) {
97
- activeArrayTarget[activeArrayKey].push(parseScalar(listMatch[2]));
98
- continue;
99
- }
100
-
101
- const nestedKeyMatch = rawLine.match(/^\s{2}([A-Za-z][\w-]*):\s*(.*)$/);
102
- if (nestedKeyMatch && activeParentKey) {
103
- const [, key, value] = nestedKeyMatch;
104
- const parent = ensureContainer(fields, activeParentKey);
105
- if (value === '') {
106
- parent[key] = [];
107
- activeArrayKey = key;
108
- activeArrayTarget = parent;
109
- continue;
110
- }
111
-
112
- parent[key] = parseScalar(value);
113
- activeArrayKey = null;
114
- activeArrayTarget = null;
115
- continue;
116
- }
117
-
118
- const keyMatch = rawLine.match(/^([A-Za-z][\w-]*):\s*(.*)$/);
119
- if (!keyMatch) continue;
120
-
121
- const [, key, value] = keyMatch;
122
- if (value === '>' || value === '|') {
123
- const { value: blockValue, nextIndex } = foldBlockScalar(lines, index, 0);
124
- fields[key] = blockValue;
125
- activeParentKey = null;
126
- activeArrayKey = null;
127
- activeArrayTarget = null;
128
- index = nextIndex - 1;
129
- continue;
130
- }
131
-
132
- if (value === '') {
133
- fields[key] = fields[key] && typeof fields[key] === 'object' && !Array.isArray(fields[key])
134
- ? fields[key]
135
- : [];
136
- activeParentKey = key;
137
- activeArrayKey = Array.isArray(fields[key]) ? key : null;
138
- activeArrayTarget = Array.isArray(fields[key]) ? fields : null;
139
- continue;
140
- }
141
-
142
- fields[key] = parseScalar(value);
143
- activeParentKey = null;
144
- activeArrayKey = null;
145
- activeArrayTarget = null;
146
- }
147
-
148
- if (!fields.name) {
149
- throw new ValidationError('SKILL.md frontmatter missing "name" field', { code: 'missing_name' });
150
- }
151
- if (!fields.description) {
152
- throw new ValidationError('SKILL.md frontmatter missing "description" field', { code: 'missing_description' });
153
- }
154
-
155
- return {
156
- name: fields.name,
157
- description: fields.description,
158
- sources: Array.isArray(fields.metadata?.sources)
159
- ? fields.metadata.sources
160
- : (Array.isArray(fields.sources) ? fields.sources : []),
161
- requires: Array.isArray(fields.metadata?.requires)
162
- ? fields.metadata.requires
163
- : (Array.isArray(fields.requires) ? fields.requires : []),
164
- status: typeof fields.metadata?.status === 'string' ? fields.metadata.status : null,
165
- replacement: typeof fields.metadata?.replacement === 'string' ? fields.metadata.replacement : null,
166
- message: typeof fields.metadata?.message === 'string' ? fields.metadata.message : null,
167
- };
168
- }
169
-
170
- function normalizeDisplayPath(repoRoot, absolutePath) {
171
- return relative(repoRoot, absolutePath).split('\\').join('/');
172
- }
173
-
174
- function ensureDir(pathValue) {
175
- mkdirSync(pathValue, { recursive: true });
176
- }
177
-
178
- function ensureSkillLink(repoRoot, baseDir, skillName, skillDir) {
179
- const skillsDir = join(repoRoot, baseDir, 'skills');
180
- ensureDir(skillsDir);
181
- const linkPath = join(skillsDir, skillName);
182
- removePathIfExists(linkPath);
183
- symlinkSync(skillDir, linkPath, 'dir');
184
- return normalizeDisplayPath(repoRoot, linkPath);
185
- }
186
-
187
47
  function resolveDevLinkedSkills(repoRoot, rootSkillDir) {
188
48
  const queue = [rootSkillDir];
189
49
  const seenDirs = new Set();
@@ -296,32 +156,6 @@ export function findPackageDirByName(repoRoot, packageName) {
296
156
  return null;
297
157
  }
298
158
 
299
- export function readPackageMetadata(packageDir) {
300
- const packageJsonPath = join(packageDir, 'package.json');
301
- if (!existsSync(packageJsonPath)) {
302
- return {
303
- packageName: null,
304
- packageVersion: null,
305
- dependencies: {},
306
- devDependencies: {},
307
- files: null,
308
- repository: null,
309
- publishConfigRegistry: null,
310
- };
311
- }
312
-
313
- const pkg = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
314
- return {
315
- packageName: pkg.name || null,
316
- packageVersion: pkg.version || null,
317
- dependencies: pkg.dependencies || {},
318
- devDependencies: pkg.devDependencies || {},
319
- files: Array.isArray(pkg.files) ? pkg.files : null,
320
- repository: pkg.repository || null,
321
- publishConfigRegistry: pkg.publishConfig?.registry || null,
322
- };
323
- }
324
-
325
159
  function readPackageJson(packageDir) {
326
160
  const packageJsonPath = join(packageDir, 'package.json');
327
161
  if (!existsSync(packageJsonPath)) {
@@ -398,8 +232,8 @@ export function devSkill(target, {
398
232
  const links = [];
399
233
 
400
234
  for (const linkedSkill of linkedSkills) {
401
- links.push(ensureSkillLink(repoRoot, '.claude', linkedSkill.name, linkedSkill.skillDir));
402
- links.push(ensureSkillLink(repoRoot, '.agents', linkedSkill.name, linkedSkill.skillDir));
235
+ links.push(ensureSkillLink(repoRoot, '.claude', linkedSkill.name, linkedSkill.skillDir, normalizeDisplayPath));
236
+ links.push(ensureSkillLink(repoRoot, '.agents', linkedSkill.name, linkedSkill.skillDir, normalizeDisplayPath));
403
237
  }
404
238
 
405
239
  return {
@@ -427,46 +261,30 @@ export function devSkill(target, {
427
261
  }
428
262
  }
429
263
 
430
- function removeSkillLinks(repoRoot, name) {
431
- const removed = [];
432
- for (const pathValue of [
433
- join(repoRoot, '.claude', 'skills', name),
434
- join(repoRoot, '.agents', 'skills', name),
435
- ]) {
436
- if (!existsSync(pathValue)) continue;
437
- removePathIfExists(pathValue);
438
- removed.push(normalizeDisplayPath(repoRoot, pathValue));
439
- }
440
- return removed;
441
- }
442
-
443
- function removeSkillLinksByNames(repoRoot, names) {
444
- const removed = [];
445
- for (const name of names) {
446
- removed.push(...removeSkillLinks(repoRoot, name));
447
- }
448
- return [...new Set(removed)];
449
- }
450
-
451
264
  export function startSkillDev(target, {
452
265
  cwd = process.cwd(),
453
266
  sync = true,
267
+ dashboard = true,
454
268
  onStart = () => {},
455
269
  onRebuild = () => {},
456
270
  } = {}) {
457
- const repoRoot = findRepoRoot(cwd);
458
- const { skillDir } = resolveLocalPackagedSkillDir(repoRoot, target);
271
+ const outerRepoRoot = findRepoRoot(cwd);
272
+ const { skillDir } = resolveLocalPackagedSkillDir(outerRepoRoot, target);
273
+ const repoRoot = findRepoRoot(skillDir);
459
274
  let closed = false;
460
275
  let timer = null;
461
276
  let currentNames = [];
462
277
  let watcher = null;
278
+ let workbench = null;
279
+ let initialResult = null;
463
280
 
464
281
  const cleanup = () => {
465
282
  if (closed) return { name: currentNames[0] || null, unlinked: false, removed: [] };
466
283
  closed = true;
467
284
  clearTimeout(timer);
468
285
  if (watcher) watcher.close();
469
- const removed = removeSkillLinksByNames(repoRoot, currentNames);
286
+ if (workbench) workbench.close();
287
+ const removed = removeSkillLinksByNames(repoRoot, currentNames, normalizeDisplayPath);
470
288
  detachProcessCleanup();
471
289
  return {
472
290
  name: currentNames[0] || null,
@@ -493,19 +311,56 @@ export function startSkillDev(target, {
493
311
  processCleanupHandlers.clear();
494
312
  };
495
313
 
496
- const run = () => {
497
- const result = devSkill(target, { cwd, sync });
314
+ const enrichResult = (result) => ({
315
+ ...result,
316
+ workbench: workbench
317
+ ? {
318
+ enabled: true,
319
+ url: workbench.url,
320
+ port: workbench.port,
321
+ }
322
+ : {
323
+ enabled: false,
324
+ url: null,
325
+ port: null,
326
+ },
327
+ });
328
+
329
+ const applyDevResult = (result) => {
498
330
  const nextNames = result.linkedSkills.map((entry) => entry.name);
499
331
  const staleNames = currentNames.filter((name) => !nextNames.includes(name));
500
332
  if (staleNames.length > 0) {
501
- removeSkillLinksByNames(repoRoot, staleNames);
333
+ removeSkillLinksByNames(repoRoot, staleNames, normalizeDisplayPath);
502
334
  }
503
335
  currentNames = nextNames;
504
336
  return result;
505
337
  };
506
338
 
507
- const initialResult = run();
508
- onStart(initialResult);
339
+ const startOrRefreshWorkbench = async () => {
340
+ if (dashboard && !workbench) {
341
+ workbench = await startSkillDevWorkbench({
342
+ repoRoot,
343
+ skillDir,
344
+ open: true,
345
+ disableBrowser: process.env.AGENTPACK_DISABLE_BROWSER === '1',
346
+ });
347
+ } else if (workbench) {
348
+ workbench.refresh();
349
+ }
350
+ };
351
+
352
+ initialResult = enrichResult(applyDevResult(devSkill(target, { cwd, sync })));
353
+ const ready = Promise.resolve(startOrRefreshWorkbench())
354
+ .then(() => {
355
+ const result = enrichResult(initialResult);
356
+ initialResult = result;
357
+ onStart(result);
358
+ return result;
359
+ })
360
+ .catch((error) => {
361
+ cleanup();
362
+ throw error;
363
+ });
509
364
 
510
365
  attachProcessCleanup();
511
366
 
@@ -513,17 +368,22 @@ export function startSkillDev(target, {
513
368
  if (closed) return;
514
369
  clearTimeout(timer);
515
370
  timer = setTimeout(() => {
516
- try {
517
- const result = run();
518
- onRebuild(result);
519
- } catch (error) {
520
- onRebuild({ error });
521
- }
371
+ Promise.resolve()
372
+ .then(() => applyDevResult(devSkill(target, { cwd, sync })))
373
+ .then(async (result) => {
374
+ await startOrRefreshWorkbench();
375
+ return enrichResult(result);
376
+ })
377
+ .then(onRebuild)
378
+ .catch((error) => {
379
+ onRebuild({ error });
380
+ });
522
381
  }, 100);
523
382
  });
524
383
 
525
384
  return {
526
385
  initialResult,
386
+ ready,
527
387
  close() {
528
388
  return cleanup();
529
389
  },
@@ -544,7 +404,7 @@ export function unlinkSkill(name, { cwd = process.cwd() } = {}) {
544
404
  });
545
405
  }
546
406
 
547
- const removed = removeSkillLinks(repoRoot, name);
407
+ const removed = removeSkillLinks(repoRoot, name, normalizeDisplayPath);
548
408
 
549
409
  return {
550
410
  name,
@@ -553,10 +413,6 @@ export function unlinkSkill(name, { cwd = process.cwd() } = {}) {
553
413
  };
554
414
  }
555
415
 
556
- export function normalizeRepoPath(repoRoot, absolutePath) {
557
- return normalizeDisplayPath(repoRoot, absolutePath);
558
- }
559
-
560
416
  function buildValidateNextSteps(packageMetadata, valid) {
561
417
  if (!valid || !packageMetadata.packageName) return [];
562
418
 
@@ -737,48 +593,16 @@ export function inspectSkill(target, { cwd = process.cwd() } = {}) {
737
593
  };
738
594
  }
739
595
 
740
- function readSkillGraphNode(repoRoot, packageDir, directInstallNames = new Set()) {
741
- const skillFile = join(packageDir, 'SKILL.md');
742
- if (!existsSync(skillFile)) return null;
743
-
744
- const skillMetadata = parseSkillFrontmatterFile(skillFile);
745
- const packageMetadata = readPackageMetadata(packageDir);
746
- if (!packageMetadata.packageName) return null;
747
-
748
- const dependencyNames = Object.keys(packageMetadata.dependencies || {})
749
- .filter((dependencyName) => {
750
- const localPackageDir = findPackageDirByName(repoRoot, dependencyName);
751
- if (localPackageDir && existsSync(join(localPackageDir, 'SKILL.md'))) return true;
752
- const installedPackageDir = join(repoRoot, 'node_modules', ...dependencyName.split('/'));
753
- return existsSync(join(installedPackageDir, 'SKILL.md'));
754
- })
755
- .sort((a, b) => a.localeCompare(b));
756
-
757
- return {
758
- name: skillMetadata.name,
759
- packageName: packageMetadata.packageName,
760
- packageVersion: packageMetadata.packageVersion,
761
- skillPath: normalizeDisplayPath(repoRoot, packageDir),
762
- skillFile: normalizeDisplayPath(repoRoot, skillFile),
763
- direct: directInstallNames.has(packageMetadata.packageName),
764
- dependencies: dependencyNames,
765
- };
766
- }
767
-
768
596
  function buildAuthoredSkillGraph(repoRoot) {
769
- const nodes = new Map();
770
-
771
- for (const packageDir of listPackagedSkillDirs(repoRoot)) {
772
- const node = readSkillGraphNode(repoRoot, packageDir);
773
- if (!node) continue;
774
- nodes.set(node.packageName, node);
775
- }
776
-
777
- return nodes;
597
+ return buildSkillGraph(repoRoot, listPackagedSkillDirs(repoRoot), {
598
+ parseSkillFrontmatterFile,
599
+ readPackageMetadata,
600
+ findPackageDirByName,
601
+ normalizeDisplayPath,
602
+ });
778
603
  }
779
604
 
780
605
  function buildInstalledSkillGraph(repoRoot) {
781
- const nodes = new Map();
782
606
  const installState = readInstallState(repoRoot);
783
607
  const directInstallNames = new Set(
784
608
  Object.entries(installState.installs || {})
@@ -786,73 +610,19 @@ function buildInstalledSkillGraph(repoRoot) {
786
610
  .map(([packageName]) => packageName)
787
611
  );
788
612
 
789
- for (const packageDir of listInstalledPackageDirs(join(repoRoot, 'node_modules'))) {
790
- const node = readSkillGraphNode(repoRoot, packageDir, directInstallNames);
791
- if (!node) continue;
792
- nodes.set(node.packageName, node);
793
- }
794
-
795
- return nodes;
796
- }
797
-
798
- function buildReverseDependencies(nodes) {
799
- const reverse = new Map();
800
- for (const packageName of nodes.keys()) reverse.set(packageName, []);
801
-
802
- for (const node of nodes.values()) {
803
- for (const dependencyName of node.dependencies) {
804
- if (!reverse.has(dependencyName)) continue;
805
- reverse.get(dependencyName).push(node.packageName);
806
- }
807
- }
808
-
809
- for (const values of reverse.values()) values.sort((a, b) => a.localeCompare(b));
810
- return reverse;
613
+ return buildSkillGraph(repoRoot, listInstalledPackageDirs(join(repoRoot, 'node_modules')), {
614
+ directInstallNames,
615
+ parseSkillFrontmatterFile,
616
+ readPackageMetadata,
617
+ findPackageDirByName,
618
+ normalizeDisplayPath,
619
+ });
811
620
  }
812
621
 
813
- function buildSkillStatusMap(repoRoot) {
622
+ function buildSkillStatusMapForRepo(repoRoot) {
814
623
  const nodes = buildAuthoredSkillGraph(repoRoot);
815
624
  const staleSkills = new Set(listStaleSkills({ cwd: repoRoot }).map((skill) => skill.packageName));
816
- const cache = new Map();
817
-
818
- function resolveStatus(packageName, seen = new Set()) {
819
- if (cache.has(packageName)) return cache.get(packageName);
820
- if (staleSkills.has(packageName)) {
821
- cache.set(packageName, 'stale');
822
- return 'stale';
823
- }
824
-
825
- if (seen.has(packageName)) return 'current';
826
- seen.add(packageName);
827
-
828
- const node = nodes.get(packageName);
829
- if (!node) {
830
- cache.set(packageName, null);
831
- return null;
832
- }
833
-
834
- const dependencyStatuses = node.dependencies
835
- .map((dependencyName) => resolveStatus(dependencyName, new Set(seen)))
836
- .filter(Boolean);
837
-
838
- const status = dependencyStatuses.some((value) => value === 'stale' || value === 'affected')
839
- ? 'affected'
840
- : 'current';
841
-
842
- cache.set(packageName, status);
843
- return status;
844
- }
845
-
846
- for (const packageName of nodes.keys()) {
847
- resolveStatus(packageName);
848
- }
849
-
850
- return cache;
851
- }
852
-
853
- function readNodeStatus(statusMap, packageName) {
854
- if (!statusMap) return null;
855
- return statusMap.get(packageName) || null;
625
+ return buildSkillStatusMap(nodes, staleSkills);
856
626
  }
857
627
 
858
628
  export function inspectSkillDependencies(target, {
@@ -863,7 +633,7 @@ export function inspectSkillDependencies(target, {
863
633
  const authoredNodes = buildAuthoredSkillGraph(repoRoot);
864
634
  const installedNodes = buildInstalledSkillGraph(repoRoot);
865
635
  const statusRoot = discoveryRoot ? resolve(discoveryRoot) : repoRoot;
866
- const statusMap = buildSkillStatusMap(statusRoot);
636
+ const statusMap = buildSkillStatusMapForRepo(statusRoot);
867
637
 
868
638
  const authoredTarget = authoredNodes.get(target) || null;
869
639
  const installedTarget = installedNodes.get(target) || null;
@@ -1086,6 +856,26 @@ export function validateSkills(target, { cwd = process.cwd() } = {}) {
1086
856
  const validCount = skills.filter((skill) => skill.valid).length;
1087
857
  const invalidCount = skills.length - validCount;
1088
858
 
859
+ if (validCount > 0) {
860
+ const buildState = readBuildState(repoRoot);
861
+
862
+ for (const packageDir of packageDirs) {
863
+ const packageMetadata = readPackageMetadata(packageDir);
864
+ const result = skills.find((skill) => skill.packageName === packageMetadata.packageName);
865
+ if (!result?.valid) continue;
866
+
867
+ const { packageName, record } = buildStateRecordForPackageDir(repoRoot, packageDir, {
868
+ parseSkillFrontmatterFile,
869
+ readPackageMetadata,
870
+ normalizeDisplayPath,
871
+ });
872
+ if (!packageName) continue;
873
+ buildState.skills[packageName] = record;
874
+ }
875
+
876
+ writeBuildState(repoRoot, buildState);
877
+ }
878
+
1089
879
  return {
1090
880
  valid: invalidCount === 0,
1091
881
  count: skills.length,
@@ -1094,67 +884,16 @@ export function validateSkills(target, { cwd = process.cwd() } = {}) {
1094
884
  skills,
1095
885
  };
1096
886
  }
1097
-
1098
-
1099
- function hashFile(filePath) {
1100
- const digest = createHash('sha256').update(readFileSync(filePath)).digest('hex');
1101
- return `sha256:${digest}`;
1102
- }
1103
-
1104
887
  function normalizeRelativePath(pathValue) {
1105
888
  return pathValue.split('\\').join('/');
1106
889
  }
1107
890
 
1108
- function readBuildState(repoRoot) {
1109
- const buildStatePath = join(repoRoot, '.agentpack', 'build-state.json');
1110
- if (!existsSync(buildStatePath)) {
1111
- return { version: 1, skills: {} };
1112
- }
1113
-
1114
- return JSON.parse(readFileSync(buildStatePath, 'utf-8'));
1115
- }
1116
-
1117
- function readInstallState(repoRoot) {
1118
- const installStatePath = join(repoRoot, '.agentpack', 'install.json');
1119
- if (!existsSync(installStatePath)) {
1120
- return { version: 1, installs: {} };
1121
- }
1122
-
1123
- return JSON.parse(readFileSync(installStatePath, 'utf-8'));
1124
- }
1125
-
1126
- function writeInstallState(repoRoot, state) {
1127
- mkdirSync(join(repoRoot, '.agentpack'), { recursive: true });
1128
- writeFileSync(join(repoRoot, '.agentpack', 'install.json'), JSON.stringify(state, null, 2) + '\n');
1129
- }
1130
-
1131
891
  function normalizeRequestedTarget(target, cwd = process.cwd()) {
1132
892
  if (typeof target !== 'string') return target;
1133
893
  if (target.startsWith('@')) return target;
1134
894
  return normalizeRelativePath(resolve(cwd, target));
1135
895
  }
1136
896
 
1137
- function compareRecordedSources(repoRoot, record) {
1138
- const changes = [];
1139
- const recordedSources = record.sources || {};
1140
-
1141
- for (const [sourcePath, sourceRecord] of Object.entries(recordedSources)) {
1142
- const absoluteSourcePath = join(repoRoot, sourcePath);
1143
- const currentHash = hashFile(absoluteSourcePath);
1144
- const recordedHash = sourceRecord.hash;
1145
-
1146
- if (currentHash !== recordedHash) {
1147
- changes.push({
1148
- path: sourcePath,
1149
- recorded: recordedHash,
1150
- current: currentHash,
1151
- });
1152
- }
1153
- }
1154
-
1155
- return changes;
1156
- }
1157
-
1158
897
  function listPackagedSkillDirs(repoRoot) {
1159
898
  const stack = [repoRoot];
1160
899
  const results = [];
@@ -1387,73 +1126,6 @@ function resolveNpmInstallTargets(directTargetMap) {
1387
1126
  return [...new Set(npmInstallTargets)];
1388
1127
  }
1389
1128
 
1390
- function ensureSymlink(targetPath, linkPath) {
1391
- rmSync(linkPath, { recursive: true, force: true });
1392
- mkdirSync(dirname(linkPath), { recursive: true });
1393
- symlinkSync(targetPath, linkPath, 'dir');
1394
- }
1395
-
1396
- function removePathIfExists(pathValue) {
1397
- rmSync(pathValue, { recursive: true, force: true });
1398
- }
1399
-
1400
- function buildInstallRecord(repoRoot, packageDir, directTargetMap) {
1401
- const packageMetadata = readPackageMetadata(packageDir);
1402
- if (!packageMetadata.packageName) return null;
1403
-
1404
- const skillMetadata = parseSkillFrontmatterFile(join(packageDir, 'SKILL.md'));
1405
- const skillDirName = skillMetadata.name;
1406
- const materializations = [];
1407
-
1408
- const claudeTargetAbs = join(repoRoot, '.claude', 'skills', skillDirName);
1409
- ensureSymlink(packageDir, claudeTargetAbs);
1410
- materializations.push({
1411
- target: normalizeRelativePath(relative(repoRoot, claudeTargetAbs)),
1412
- mode: 'symlink',
1413
- });
1414
-
1415
- const agentsTargetAbs = join(repoRoot, '.agents', 'skills', skillDirName);
1416
- ensureSymlink(packageDir, agentsTargetAbs);
1417
- materializations.push({
1418
- target: normalizeRelativePath(relative(repoRoot, agentsTargetAbs)),
1419
- mode: 'symlink',
1420
- });
1421
-
1422
- return {
1423
- packageName: packageMetadata.packageName,
1424
- direct: directTargetMap.has(packageMetadata.packageName),
1425
- requestedTarget: directTargetMap.get(packageMetadata.packageName) || null,
1426
- packageVersion: packageMetadata.packageVersion,
1427
- sourcePackagePath: normalizeRelativePath(relative(repoRoot, packageDir)),
1428
- materializations,
1429
- };
1430
- }
1431
-
1432
- function rebuildInstallState(repoRoot, directTargetMap) {
1433
- const packageDirs = listInstalledPackageDirs(join(repoRoot, 'node_modules'));
1434
- const installs = {};
1435
-
1436
- for (const packageDir of packageDirs) {
1437
- const skillFile = join(packageDir, 'SKILL.md');
1438
- if (!existsSync(skillFile)) continue;
1439
-
1440
- const record = buildInstallRecord(repoRoot, packageDir, directTargetMap);
1441
- if (!record) continue;
1442
-
1443
- installs[record.packageName] = {
1444
- direct: record.direct,
1445
- requested_target: record.requestedTarget,
1446
- package_version: record.packageVersion,
1447
- source_package_path: record.sourcePackagePath,
1448
- materializations: record.materializations,
1449
- };
1450
- }
1451
-
1452
- const state = { version: 1, installs };
1453
- writeInstallState(repoRoot, state);
1454
- return state;
1455
- }
1456
-
1457
1129
  export function installSkills(targets, { cwd = process.cwd() } = {}) {
1458
1130
  const repoRoot = findRepoRoot(cwd);
1459
1131
  const previousState = readInstallState(repoRoot);
@@ -1493,7 +1165,12 @@ export function installSkills(targets, { cwd = process.cwd() } = {}) {
1493
1165
  stdio: ['pipe', 'pipe', 'pipe'],
1494
1166
  });
1495
1167
 
1496
- return rebuildInstallState(repoRoot, directTargetMap);
1168
+ return rebuildInstallState(repoRoot, directTargetMap, {
1169
+ listInstalledPackageDirs,
1170
+ parseSkillFrontmatterFile,
1171
+ readPackageMetadata,
1172
+ normalizeRelativePath,
1173
+ });
1497
1174
  }
1498
1175
 
1499
1176
  export function inspectSkillsEnv({ cwd = process.cwd() } = {}) {
@@ -1809,7 +1486,12 @@ export function uninstallSkills(target, { cwd = process.cwd() } = {}) {
1809
1486
  });
1810
1487
  }
1811
1488
 
1812
- const nextState = rebuildInstallState(repoRoot, nextDirectTargetMap);
1489
+ const nextState = rebuildInstallState(repoRoot, nextDirectTargetMap, {
1490
+ listInstalledPackageDirs,
1491
+ parseSkillFrontmatterFile,
1492
+ readPackageMetadata,
1493
+ normalizeRelativePath,
1494
+ });
1813
1495
  const remainingTargets = new Set(
1814
1496
  Object.values(nextState.installs)
1815
1497
  .flatMap((install) => (install.materializations || []).map((entry) => entry.target))