@alavida/agentpack 0.1.3 → 0.1.4

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.
package/README.md CHANGED
@@ -253,6 +253,28 @@ Validate the shipped agent skill:
253
253
  npm run intent:validate
254
254
  ```
255
255
 
256
+ ## Releases
257
+
258
+ This repo now uses Changesets for versioning and publishing.
259
+
260
+ Normal maintainer flow:
261
+
262
+ 1. Add a changeset in any PR that changes user-facing behavior.
263
+ 2. Merge the feature PR to `main`.
264
+ 3. Let GitHub open or update the `Version Packages` release PR.
265
+ 4. Review and merge that release PR.
266
+ 5. The merge publishes to npm automatically.
267
+
268
+ Useful local commands:
269
+
270
+ ```bash
271
+ npx changeset
272
+ npm run version-packages
273
+ npm run release
274
+ ```
275
+
276
+ Manual git tags are no longer the normal release path.
277
+
256
278
  ## Optional Agent Integration
257
279
 
258
280
  This package also ships an agent-facing skill under:
@@ -273,10 +295,12 @@ In authoring repos:
273
295
 
274
296
  - commit `.agentpack/build-state.json`
275
297
  - commit `.agentpack/catalog.json`
298
+ - commit `skills/sync-state.json` when maintaining shipped Intent skills
276
299
 
277
300
  In consumer/runtime repos:
278
301
 
279
302
  - do not commit `.agentpack/install.json`
303
+ - do not commit `.agentpack/dev-session.json`
280
304
 
281
305
  ## License
282
306
 
package/package.json CHANGED
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "name": "@alavida/agentpack",
3
- "version": "0.1.3",
3
+ "version": "0.1.4",
4
4
  "description": "Package-backed skills lifecycle CLI for agent skills and plugins",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "agentpack": "bin/agentpack.js",
8
- "intent": "./bin/intent.js"
8
+ "intent": "bin/intent.js"
9
9
  },
10
10
  "files": [
11
11
  "bin",
@@ -19,6 +19,9 @@
19
19
  "validate:live": "node scripts/live-validation.mjs",
20
20
  "smoke:monorepo": "node scripts/smoke-monorepo.mjs",
21
21
  "docs:dev": "cd docs && mint dev",
22
+ "changeset": "changeset",
23
+ "version-packages": "changeset version",
24
+ "release": "changeset publish",
22
25
  "intent:validate": "npx @tanstack/intent validate",
23
26
  "build:dashboard": "node scripts/build-dashboard.mjs"
24
27
  },
@@ -51,6 +54,7 @@
51
54
  "react-dom": "^19.1.1"
52
55
  },
53
56
  "devDependencies": {
57
+ "@changesets/cli": "^2.30.0",
54
58
  "@tanstack/intent": "^0.0.14",
55
59
  "esbuild": "^0.25.10"
56
60
  },
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: agentpack-cli
3
3
  description: Use the agentpack CLI correctly when treating knowledge as a package. Apply the authored skill lifecycle, plugin lifecycle, source-backed validation, install flow, and bundled plugin artifact flow without mixing those stages together.
4
- library_version: 0.1.2
4
+ library_version: 0.1.3
5
5
  sources:
6
6
  - README.md
7
7
  - docs/introduction.mdx
@@ -71,11 +71,16 @@ Persistence rule:
71
71
  - commit `.agentpack/build-state.json` so stale detection works across GitHub, CI, and teammate machines
72
72
  - commit `.agentpack/catalog.json` in authoring repos
73
73
  - do not commit `.agentpack/install.json`
74
+ - do not commit `.agentpack/dev-session.json`
75
+ - commit `skills/sync-state.json` when maintaining the shipped Intent skills for this package
74
76
 
75
77
  Runtime notes:
76
78
 
77
79
  - after `skills dev` writes to `.claude/skills/` or `.agents/skills/`, start a fresh agent session if the current one was already running
78
80
  - `skills dev` starts a localhost workbench by default for one selected skill, with provenance edges, direct required skills, and actions like validate or stale checks
81
+ - `skills dev` records the active session in `.agentpack/dev-session.json` so the next run can clean up stale runtime links after abnormal termination
82
+ - if a stale local dev session blocks startup, use `agentpack skills dev cleanup` and escalate to `agentpack skills dev cleanup --force` only when the recorded pid is a false positive
83
+ - use `agentpack skills unlink <root> --recursive` when you need to remove one active dev root plus its transitive local runtime links
79
84
  - do not reload `metadata.sources` manually once the dev-linked skill exists; trust the compiled `SKILL.md` artifact unless you are explicitly updating the skill
80
85
  - invoke the resulting skill through the runtime's skill mechanism, not by opening the file and reading it as plain text
81
86
 
@@ -3,7 +3,7 @@ name: authoring-skillgraphs-from-knowledge
3
3
  description: Use when authoring packaged skills from source knowledge with valid SKILL.md metadata, package.json release fields, provenance sources, and requires edges in agentpack.
4
4
  type: core
5
5
  library: agentpack
6
- library_version: "0.1.2"
6
+ library_version: "0.1.3"
7
7
  sources:
8
8
  - "alavida-ai/agentpack:docs/commands.mdx"
9
9
  - "alavida-ai/agentpack:docs/architecture.mdx"
@@ -3,7 +3,7 @@ name: developing-and-testing-skills
3
3
  description: Use when iterating locally on a packaged skill with agentpack skills dev, the localhost workbench, repo-local materialization, and runtime testing feedback loops.
4
4
  type: core
5
5
  library: agentpack
6
- library_version: "0.1.2"
6
+ library_version: "0.1.3"
7
7
  sources:
8
8
  - "alavida-ai/agentpack:docs/commands.mdx"
9
9
  - "alavida-ai/agentpack:docs/introduction.mdx"
@@ -30,7 +30,7 @@ agentpack skills dev domains/value/skills/copywriting
30
30
  agentpack skills dev domains/value/skills/copywriting
31
31
  ```
32
32
 
33
- This links the selected skill into `.claude/skills/` and `.agents/skills/` and starts a localhost workbench by default.
33
+ This links the selected skill into `.claude/skills/` and `.agents/skills/`, records the active dev session in `.agentpack/dev-session.json`, and starts a localhost workbench by default.
34
34
 
35
35
  ### Use CLI-only mode when you explicitly do not want the dashboard
36
36
 
@@ -44,6 +44,24 @@ agentpack skills dev --no-dashboard domains/value/skills/copywriting
44
44
  agentpack skills unlink value-copywriting
45
45
  ```
46
46
 
47
+ If the previous dev process was killed badly and left stale runtime links behind:
48
+
49
+ ```bash
50
+ agentpack skills dev cleanup
51
+ ```
52
+
53
+ If a wrapper-managed process left a false-positive live pid in `.agentpack/dev-session.json`:
54
+
55
+ ```bash
56
+ agentpack skills dev cleanup --force
57
+ ```
58
+
59
+ If you need to remove the active root plus its recorded transitive links in one shot:
60
+
61
+ ```bash
62
+ agentpack skills unlink value-copywriting --recursive
63
+ ```
64
+
47
65
  ## Common Mistakes
48
66
 
49
67
  ### HIGH Expecting the current agent session to pick up new links
@@ -3,7 +3,7 @@ name: getting-started-skillgraphs
3
3
  description: Use when starting from an empty repo or empty skillgraph and needing the first correct authoring loop, lifecycle framing, repo-root routing, and inspect/validate/dev command flow in agentpack.
4
4
  type: lifecycle
5
5
  library: agentpack
6
- library_version: "0.1.2"
6
+ library_version: "0.1.3"
7
7
  sources:
8
8
  - "alavida-ai/agentpack:docs/introduction.mdx"
9
9
  - "alavida-ai/agentpack:docs/commands.mdx"
@@ -3,7 +3,7 @@ name: identifying-skill-opportunities
3
3
  description: Use when deciding what raw knowledge should become a packaged skill, a reusable capability boundary, a requires edge, or a plugin-local wrapper in an agentpack skillgraph.
4
4
  type: core
5
5
  library: agentpack
6
- library_version: "0.1.2"
6
+ library_version: "0.1.3"
7
7
  sources:
8
8
  - "alavida-ai/agentpack:docs/architecture.mdx"
9
9
  - "alavida-ai/agentpack:docs/overview.mdx"
@@ -3,7 +3,7 @@ name: maintaining-skillgraph-freshness
3
3
  description: Use when validating authored skills, checking stale state, and keeping build-state and catalog metadata aligned with changing knowledge in an agentpack skillgraph.
4
4
  type: lifecycle
5
5
  library: agentpack
6
- library_version: "0.1.2"
6
+ library_version: "0.1.3"
7
7
  sources:
8
8
  - "alavida-ai/agentpack:docs/commands.mdx"
9
9
  - "alavida-ai/agentpack:docs/current-state.mdx"
@@ -3,7 +3,7 @@ name: repairing-broken-skill-or-plugin-state
3
3
  description: Use when auditing or repairing stale skills, unresolved requires, missing runtime dependencies, affected dependents, or malformed plugin definition files in agentpack.
4
4
  type: lifecycle
5
5
  library: agentpack
6
- library_version: "0.1.2"
6
+ library_version: "0.1.3"
7
7
  sources:
8
8
  - "alavida-ai/agentpack:docs/current-state.mdx"
9
9
  - "alavida-ai/agentpack:docs/commands.mdx"
@@ -100,11 +100,15 @@ Correct:
100
100
 
101
101
  ```bash
102
102
  agentpack skills unlink value-copywriting
103
+ agentpack skills unlink value-copywriting --recursive
104
+ agentpack skills dev cleanup
103
105
  agentpack skills install @alavida-ai/value-copywriting
104
106
  ```
105
107
 
106
108
  Runtime state should be repaired through agentpack lifecycle commands, not direct edits under `.claude/skills` or `.agents/skills`.
107
109
 
110
+ Use `skills dev cleanup --force` only when a wrapper-managed process or pid reuse leaves a false-positive active session in `.agentpack/dev-session.json`.
111
+
108
112
  Source: docs/architecture.mdx
109
113
 
110
114
  ## References
@@ -3,7 +3,7 @@ name: shipping-production-plugins-and-packages
3
3
  description: Use when turning maintained skills into deployable bundled plugins or publishable standalone packages with explicit dependency closure, hooks, MCP tools, and production checks in agentpack.
4
4
  type: core
5
5
  library: agentpack
6
- library_version: "0.1.2"
6
+ library_version: "0.1.3"
7
7
  sources:
8
8
  - "alavida-ai/agentpack:docs/commands.mdx"
9
9
  - "alavida-ai/agentpack:docs/architecture.mdx"
@@ -0,0 +1,83 @@
1
+ {
2
+ "version": 1,
3
+ "skills": {
4
+ "agentpack-cli": {
5
+ "library_version": "0.1.3",
6
+ "synced_at": "2026-03-12T00:00:00.000Z",
7
+ "sources_sha": {
8
+ "README.md": "2680d4fa603b070e6bd92c162b3d172131b71c39c9dc39351ed452d95567b0bd",
9
+ "docs/introduction.mdx": "c1a8989382dc81a3a33dae1e8f243d159b92835a1999a8793741a25b317b455d",
10
+ "docs/overview.mdx": "48151b11dfef153223ba2aed7100eeeafc100c475694c98e053f9c4ffb025357",
11
+ "docs/architecture.mdx": "400617fb8ba4a5f09a78b998e3b1a01262caed05e0344206ca1f8fd71519ea22",
12
+ "docs/build-lifecycle.mdx": "0e697180c639a72514272e6421f900ac3d5248ecc09220409efc716bf0d4fa71",
13
+ "docs/commands.mdx": "5f2ec2839caa8a6758f36daaa150f6af8d3700e26b135333fad6a0719dacb05b",
14
+ "docs/current-state.mdx": "b4ad89e40706aff29bbe3149daeeb3feac6d07bb3ddfd4f164e630cd5b7997a8",
15
+ "docs/distribution.mdx": "060e47b28c3da13ff61e6b1b04e018beb8dc5d6ebd30b5a3d1574fceb289e3f4",
16
+ "docs/end-to-end-test-plan.md": "c1e37589660a438cb1a9742f68e769ed68f6f8849f1510d8445e99c2bff1ec21"
17
+ }
18
+ },
19
+ "authoring-skillgraphs-from-knowledge": {
20
+ "library_version": "0.1.3",
21
+ "synced_at": "2026-03-12T00:00:00.000Z",
22
+ "sources_sha": {
23
+ "alavida-ai/agentpack:docs/commands.mdx": "5f2ec2839caa8a6758f36daaa150f6af8d3700e26b135333fad6a0719dacb05b",
24
+ "alavida-ai/agentpack:docs/architecture.mdx": "400617fb8ba4a5f09a78b998e3b1a01262caed05e0344206ca1f8fd71519ea22",
25
+ "alavida-ai/agentpack:skills/agentpack-cli/SKILL.md": "54d9a43172ff6b7e7218bb9a9454b56d38233e9d1a6df9362e7167ceb9dc2249"
26
+ }
27
+ },
28
+ "developing-and-testing-skills": {
29
+ "library_version": "0.1.3",
30
+ "synced_at": "2026-03-12T00:00:00.000Z",
31
+ "sources_sha": {
32
+ "alavida-ai/agentpack:docs/commands.mdx": "5f2ec2839caa8a6758f36daaa150f6af8d3700e26b135333fad6a0719dacb05b",
33
+ "alavida-ai/agentpack:docs/introduction.mdx": "c1a8989382dc81a3a33dae1e8f243d159b92835a1999a8793741a25b317b455d",
34
+ "alavida-ai/agentpack:README.md": "2680d4fa603b070e6bd92c162b3d172131b71c39c9dc39351ed452d95567b0bd"
35
+ }
36
+ },
37
+ "getting-started-skillgraphs": {
38
+ "library_version": "0.1.3",
39
+ "synced_at": "2026-03-12T00:00:00.000Z",
40
+ "sources_sha": {
41
+ "alavida-ai/agentpack:docs/introduction.mdx": "c1a8989382dc81a3a33dae1e8f243d159b92835a1999a8793741a25b317b455d",
42
+ "alavida-ai/agentpack:docs/commands.mdx": "5f2ec2839caa8a6758f36daaa150f6af8d3700e26b135333fad6a0719dacb05b",
43
+ "alavida-ai/agentpack:README.md": "2680d4fa603b070e6bd92c162b3d172131b71c39c9dc39351ed452d95567b0bd"
44
+ }
45
+ },
46
+ "identifying-skill-opportunities": {
47
+ "library_version": "0.1.3",
48
+ "synced_at": "2026-03-12T00:00:00.000Z",
49
+ "sources_sha": {
50
+ "alavida-ai/agentpack:docs/architecture.mdx": "400617fb8ba4a5f09a78b998e3b1a01262caed05e0344206ca1f8fd71519ea22",
51
+ "alavida-ai/agentpack:docs/overview.mdx": "48151b11dfef153223ba2aed7100eeeafc100c475694c98e053f9c4ffb025357",
52
+ "alavida-ai/agentpack:skills/agentpack-cli/SKILL.md": "54d9a43172ff6b7e7218bb9a9454b56d38233e9d1a6df9362e7167ceb9dc2249"
53
+ }
54
+ },
55
+ "maintaining-skillgraph-freshness": {
56
+ "library_version": "0.1.3",
57
+ "synced_at": "2026-03-12T00:00:00.000Z",
58
+ "sources_sha": {
59
+ "alavida-ai/agentpack:docs/commands.mdx": "5f2ec2839caa8a6758f36daaa150f6af8d3700e26b135333fad6a0719dacb05b",
60
+ "alavida-ai/agentpack:docs/current-state.mdx": "b4ad89e40706aff29bbe3149daeeb3feac6d07bb3ddfd4f164e630cd5b7997a8",
61
+ "alavida-ai/agentpack:README.md": "2680d4fa603b070e6bd92c162b3d172131b71c39c9dc39351ed452d95567b0bd"
62
+ }
63
+ },
64
+ "repairing-broken-skill-or-plugin-state": {
65
+ "library_version": "0.1.3",
66
+ "synced_at": "2026-03-12T00:00:00.000Z",
67
+ "sources_sha": {
68
+ "alavida-ai/agentpack:docs/current-state.mdx": "b4ad89e40706aff29bbe3149daeeb3feac6d07bb3ddfd4f164e630cd5b7997a8",
69
+ "alavida-ai/agentpack:docs/commands.mdx": "5f2ec2839caa8a6758f36daaa150f6af8d3700e26b135333fad6a0719dacb05b",
70
+ "alavida-ai/agentpack:src/domain/plugins/load-plugin-definition.js": "3aac67288351229e878d0877097b41cd369cc594fc8683fccf2506b94a5f17e7"
71
+ }
72
+ },
73
+ "shipping-production-plugins-and-packages": {
74
+ "library_version": "0.1.3",
75
+ "synced_at": "2026-03-12T00:00:00.000Z",
76
+ "sources_sha": {
77
+ "alavida-ai/agentpack:docs/commands.mdx": "5f2ec2839caa8a6758f36daaa150f6af8d3700e26b135333fad6a0719dacb05b",
78
+ "alavida-ai/agentpack:docs/architecture.mdx": "400617fb8ba4a5f09a78b998e3b1a01262caed05e0344206ca1f8fd71519ea22",
79
+ "alavida-ai/agentpack:README.md": "2680d4fa603b070e6bd92c162b3d172131b71c39c9dc39351ed452d95567b0bd"
80
+ }
81
+ }
82
+ }
83
+ }
@@ -11,6 +11,7 @@ import {
11
11
  installSkills,
12
12
  listOutdatedSkills,
13
13
  resolveInstallTargets,
14
+ cleanupSkillDevSession,
14
15
  startSkillDev,
15
16
  unlinkSkill,
16
17
  uninstallSkills,
@@ -22,13 +23,16 @@ export function skillsCommand() {
22
23
  const cmd = new Command('skills')
23
24
  .description('Inspect and manage package-backed skills');
24
25
 
25
- cmd
26
+ const devCmd = cmd
26
27
  .command('dev')
27
28
  .description('Link one local packaged skill for local Claude and agent discovery')
28
29
  .option('--no-sync', 'Skip syncing managed package dependencies from requires')
29
30
  .option('--no-dashboard', 'Skip starting the local skill development workbench')
30
- .argument('<target>', 'Packaged skill directory or SKILL.md path')
31
+ .argument('[target]', 'Packaged skill directory or SKILL.md path')
31
32
  .action(async (target, opts, command) => {
33
+ if (!target) {
34
+ command.help({ error: true });
35
+ }
32
36
  const globalOpts = command.optsWithGlobals();
33
37
  const session = startSkillDev(target, {
34
38
  sync: opts.sync,
@@ -70,23 +74,37 @@ export function skillsCommand() {
70
74
  },
71
75
  });
72
76
 
73
- const stop = () => {
74
- session.close();
75
- process.exit(0);
76
- };
77
-
78
- process.once('SIGTERM', stop);
79
- process.once('SIGINT', stop);
80
77
  await session.ready;
81
78
  });
82
79
 
80
+ devCmd
81
+ .command('cleanup')
82
+ .description('Remove recorded skills dev links for a stale session')
83
+ .option('--force', 'Remove recorded links even if the session pid still appears alive')
84
+ .action((opts, command) => {
85
+ const globalOpts = command.optsWithGlobals();
86
+ const result = cleanupSkillDevSession({ force: opts.force });
87
+
88
+ if (globalOpts.json) {
89
+ output.json(result);
90
+ return;
91
+ }
92
+
93
+ output.write(`Cleaned: ${result.cleaned}`);
94
+ if (result.name) output.write(`Root Skill: ${result.name}`);
95
+ for (const removed of result.removed) {
96
+ output.write(`Removed: ${removed}`);
97
+ }
98
+ });
99
+
83
100
  cmd
84
101
  .command('unlink')
85
102
  .description('Remove one locally linked skill from Claude and agent discovery paths')
103
+ .option('--recursive', 'Remove the active dev root and its recorded transitive links')
86
104
  .argument('<name>', 'Skill frontmatter name')
87
105
  .action((name, opts, command) => {
88
106
  const globalOpts = command.optsWithGlobals();
89
- const result = unlinkSkill(name);
107
+ const result = unlinkSkill(name, { recursive: opts.recursive });
90
108
 
91
109
  if (globalOpts.json) {
92
110
  output.json(result);
@@ -0,0 +1,25 @@
1
+ import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+
4
+ function getDevSessionPath(repoRoot) {
5
+ return join(repoRoot, '.agentpack', 'dev-session.json');
6
+ }
7
+
8
+ export function readDevSession(repoRoot) {
9
+ const sessionPath = getDevSessionPath(repoRoot);
10
+ if (!existsSync(sessionPath)) return null;
11
+ return JSON.parse(readFileSync(sessionPath, 'utf-8'));
12
+ }
13
+
14
+ export function writeDevSession(repoRoot, session) {
15
+ mkdirSync(join(repoRoot, '.agentpack'), { recursive: true });
16
+ writeFileSync(getDevSessionPath(repoRoot), JSON.stringify(session, null, 2) + '\n');
17
+ }
18
+
19
+ export function removeDevSession(repoRoot) {
20
+ rmSync(getDevSessionPath(repoRoot), { force: true });
21
+ }
22
+
23
+ export function devSessionExists(repoRoot) {
24
+ return existsSync(getDevSessionPath(repoRoot));
25
+ }
@@ -1,5 +1,5 @@
1
1
  import { existsSync, mkdirSync, rmSync, symlinkSync } from 'node:fs';
2
- import { dirname, join, relative } from 'node:path';
2
+ import { dirname, join, relative, resolve } from 'node:path';
3
3
  import { writeInstallState } from '../fs/install-state-repository.js';
4
4
 
5
5
  function ensureDir(pathValue) {
@@ -40,6 +40,23 @@ export function removeSkillLinksByNames(repoRoot, names, normalizeDisplayPath) {
40
40
  return [...new Set(removed)];
41
41
  }
42
42
 
43
+ export function removeSkillLinksByPaths(repoRoot, paths, normalizeDisplayPath) {
44
+ const removed = [];
45
+ const allowedRoots = [
46
+ resolve(repoRoot, '.claude', 'skills'),
47
+ resolve(repoRoot, '.agents', 'skills'),
48
+ ];
49
+ for (const relativePath of paths || []) {
50
+ const pathValue = resolve(repoRoot, relativePath);
51
+ const inAllowedRoot = allowedRoots.some((root) => pathValue === root || pathValue.startsWith(`${root}/`));
52
+ if (!inAllowedRoot) continue;
53
+ if (!existsSync(pathValue)) continue;
54
+ removePathIfExists(pathValue);
55
+ removed.push(normalizeDisplayPath(repoRoot, pathValue));
56
+ }
57
+ return [...new Set(removed)];
58
+ }
59
+
43
60
  function ensureSymlink(targetPath, linkPath) {
44
61
  removePathIfExists(linkPath);
45
62
  mkdirSync(dirname(linkPath), { recursive: true });
package/src/lib/skills.js CHANGED
@@ -9,11 +9,13 @@ import {
9
9
  readNodeStatus,
10
10
  } from '../domain/skills/skill-graph.js';
11
11
  import { readInstallState } from '../infrastructure/fs/install-state-repository.js';
12
+ import { readDevSession, writeDevSession, removeDevSession } from '../infrastructure/fs/dev-session-repository.js';
12
13
  import {
13
14
  ensureSkillLink,
14
15
  rebuildInstallState,
15
16
  removePathIfExists,
16
17
  removeSkillLinks,
18
+ removeSkillLinksByPaths,
17
19
  removeSkillLinksByNames,
18
20
  } from '../infrastructure/runtime/materialize-skills.js';
19
21
  import {
@@ -171,6 +173,100 @@ function readPackageJson(packageDir) {
171
173
  };
172
174
  }
173
175
 
176
+ function isProcessAlive(pid) {
177
+ if (!Number.isInteger(pid) || pid <= 0) return false;
178
+ try {
179
+ process.kill(pid, 0);
180
+ return true;
181
+ } catch (error) {
182
+ if (error.code === 'EPERM') return true;
183
+ if (error.code === 'ESRCH') return false;
184
+ return false;
185
+ }
186
+ }
187
+
188
+ function buildDevSessionNextSteps(command) {
189
+ return [{
190
+ action: 'run_command',
191
+ command,
192
+ reason: 'Use the dev session cleanup flow to remove recorded linked skills for this repo',
193
+ }];
194
+ }
195
+
196
+ function toDevSessionRecord(repoRoot, target, result, existing = null) {
197
+ const now = new Date().toISOString();
198
+ const rootSkill = result.linkedSkills.find((entry) => entry.name === result.name) || result.linkedSkills[0] || null;
199
+ return {
200
+ version: 1,
201
+ session_id: existing?.session_id || `dev-${now.replaceAll(':', '-').replaceAll('.', '-')}`,
202
+ status: 'active',
203
+ pid: process.pid,
204
+ repo_root: repoRoot,
205
+ target,
206
+ root_skill: rootSkill
207
+ ? {
208
+ name: rootSkill.name,
209
+ package_name: rootSkill.packageName,
210
+ path: rootSkill.path,
211
+ }
212
+ : null,
213
+ linked_skills: result.linkedSkills.map((entry) => ({
214
+ name: entry.name,
215
+ package_name: entry.packageName,
216
+ path: entry.path,
217
+ })),
218
+ links: result.links,
219
+ started_at: existing?.started_at || now,
220
+ updated_at: now,
221
+ };
222
+ }
223
+
224
+ function cleanupRecordedDevSession(repoRoot, session, status = 'stale') {
225
+ if (!session) {
226
+ return {
227
+ cleaned: false,
228
+ removed: [],
229
+ session: null,
230
+ };
231
+ }
232
+
233
+ writeDevSession(repoRoot, {
234
+ ...session,
235
+ status,
236
+ updated_at: new Date().toISOString(),
237
+ });
238
+ const removed = removeSkillLinksByPaths(repoRoot, session.links || [], normalizeDisplayPath);
239
+ removeDevSession(repoRoot);
240
+ return {
241
+ cleaned: removed.length > 0 || Boolean(session),
242
+ removed,
243
+ session,
244
+ };
245
+ }
246
+
247
+ function reconcileDevSession(repoRoot) {
248
+ const session = readDevSession(repoRoot);
249
+ if (!session) return null;
250
+
251
+ if (session.status === 'active' && isProcessAlive(session.pid)) {
252
+ throw new AgentpackError('A skills dev session is already active in this repo', {
253
+ code: 'skills_dev_session_active',
254
+ exitCode: EXIT_CODES.GENERAL,
255
+ nextSteps: [
256
+ ...buildDevSessionNextSteps('agentpack skills dev cleanup'),
257
+ ...buildDevSessionNextSteps('agentpack skills dev cleanup --force'),
258
+ ],
259
+ details: {
260
+ rootSkill: session.root_skill?.name || null,
261
+ pid: session.pid,
262
+ startedAt: session.started_at || null,
263
+ },
264
+ });
265
+ }
266
+
267
+ return cleanupRecordedDevSession(repoRoot, session, 'stale');
268
+ }
269
+
174
270
  export function syncSkillDependencies(skillDir) {
175
271
  const skillFile = join(skillDir, 'SKILL.md');
176
272
  const metadata = parseSkillFrontmatterFile(skillFile);
@@ -271,12 +367,14 @@ export function startSkillDev(target, {
271
367
  const outerRepoRoot = findRepoRoot(cwd);
272
368
  const { skillDir } = resolveLocalPackagedSkillDir(outerRepoRoot, target);
273
369
  const repoRoot = findRepoRoot(skillDir);
370
+ reconcileDevSession(repoRoot);
274
371
  let closed = false;
275
372
  let timer = null;
276
373
  let currentNames = [];
277
374
  let watcher = null;
278
375
  let workbench = null;
279
376
  let initialResult = null;
377
+ let sessionRecord = null;
280
378
 
281
379
  const cleanup = () => {
282
380
  if (closed) return { name: currentNames[0] || null, unlinked: false, removed: [] };
@@ -284,10 +382,20 @@ export function startSkillDev(target, {
284
382
  clearTimeout(timer);
285
383
  if (watcher) watcher.close();
286
384
  if (workbench) workbench.close();
287
- const removed = removeSkillLinksByNames(repoRoot, currentNames, normalizeDisplayPath);
385
+ if (sessionRecord) {
386
+ writeDevSession(repoRoot, {
387
+ ...sessionRecord,
388
+ status: 'cleaning',
389
+ updated_at: new Date().toISOString(),
390
+ });
391
+ }
392
+ const removed = sessionRecord
393
+ ? removeSkillLinksByPaths(repoRoot, sessionRecord.links || [], normalizeDisplayPath)
394
+ : removeSkillLinksByNames(repoRoot, currentNames, normalizeDisplayPath);
395
+ removeDevSession(repoRoot);
288
396
  detachProcessCleanup();
289
397
  return {
290
- name: currentNames[0] || null,
398
+ name: sessionRecord?.root_skill?.name || currentNames[0] || null,
291
399
  unlinked: removed.length > 0,
292
400
  removed,
293
401
  };
@@ -295,9 +403,14 @@ export function startSkillDev(target, {
295
403
 
296
404
  const processCleanupHandlers = new Map();
297
405
  const attachProcessCleanup = () => {
298
- for (const eventName of ['exit', 'beforeExit', 'SIGINT', 'SIGTERM', 'SIGHUP']) {
406
+ const exitHandler = () => cleanup();
407
+ processCleanupHandlers.set('exit', exitHandler);
408
+ process.once('exit', exitHandler);
409
+
410
+ for (const eventName of ['SIGINT', 'SIGTERM', 'SIGHUP']) {
299
411
  const handler = () => {
300
412
  cleanup();
413
+ process.exit(0);
301
414
  };
302
415
  processCleanupHandlers.set(eventName, handler);
303
416
  process.once(eventName, handler);
@@ -333,6 +446,8 @@ export function startSkillDev(target, {
333
446
  removeSkillLinksByNames(repoRoot, staleNames, normalizeDisplayPath);
334
447
  }
335
448
  currentNames = nextNames;
449
+ sessionRecord = toDevSessionRecord(repoRoot, target, result, sessionRecord);
450
+ writeDevSession(repoRoot, sessionRecord);
336
451
  return result;
337
452
  };
338
453
 
@@ -390,8 +505,37 @@ export function startSkillDev(target, {
390
505
  };
391
506
  }
392
507
 
393
- export function unlinkSkill(name, { cwd = process.cwd() } = {}) {
508
+ export function unlinkSkill(name, { cwd = process.cwd(), recursive = false } = {}) {
394
509
  const repoRoot = findRepoRoot(cwd);
510
+ const session = readDevSession(repoRoot);
511
+
512
+ if (recursive) {
513
+ if (!session || session.root_skill?.name !== name) {
514
+ throw new AgentpackError('Recursive unlink requires the active dev-session root skill', {
515
+ code: 'linked_skill_recursive_unlink_requires_root',
516
+ exitCode: EXIT_CODES.GENERAL,
517
+ nextSteps: session?.root_skill?.name
518
+ ? [{
519
+ action: 'run_command',
520
+ command: `agentpack skills unlink ${session.root_skill.name} --recursive`,
521
+ reason: 'Recursive unlink in v1 only works for the recorded dev-session root skill',
522
+ }]
523
+ : buildDevSessionNextSteps('agentpack skills dev cleanup --force'),
524
+ details: {
525
+ rootSkill: session?.root_skill?.name || null,
526
+ },
527
+ });
528
+ }
529
+
530
+ const removed = removeSkillLinksByPaths(repoRoot, session.links || [], normalizeDisplayPath);
531
+ removeDevSession(repoRoot);
532
+ return {
533
+ name,
534
+ unlinked: removed.length > 0,
535
+ removed,
536
+ };
537
+ }
538
+
395
539
  const existing = [
396
540
  join(repoRoot, '.claude', 'skills', name),
397
541
  join(repoRoot, '.agents', 'skills', name),
@@ -413,6 +557,43 @@ export function unlinkSkill(name, { cwd = process.cwd() } = {}) {
413
557
  };
414
558
  }
415
559
 
560
+ export function cleanupSkillDevSession({ cwd = process.cwd(), force = false } = {}) {
561
+ const repoRoot = findRepoRoot(cwd);
562
+ const session = readDevSession(repoRoot);
563
+ if (!session) {
564
+ return {
565
+ cleaned: false,
566
+ active: false,
567
+ removed: [],
568
+ };
569
+ }
570
+
571
+ if (!force && session.status === 'active' && isProcessAlive(session.pid)) {
572
+ throw new AgentpackError('A skills dev session is still active in this repo', {
573
+ code: 'skills_dev_session_active',
574
+ exitCode: EXIT_CODES.GENERAL,
575
+ nextSteps: [
576
+ ...buildDevSessionNextSteps('agentpack skills dev cleanup'),
577
+ ...buildDevSessionNextSteps('agentpack skills dev cleanup --force'),
578
+ ],
579
+ details: {
580
+ rootSkill: session.root_skill?.name || null,
581
+ pid: session.pid,
582
+ startedAt: session.started_at || null,
583
+ },
584
+ });
585
+ }
586
+
587
+ const result = cleanupRecordedDevSession(repoRoot, session, 'stale');
588
+ return {
589
+ cleaned: true,
590
+ active: false,
591
+ forced: force,
592
+ name: session.root_skill?.name || null,
593
+ removed: result.removed,
594
+ };
595
+ }
596
+
416
597
  function buildValidateNextSteps(packageMetadata, valid) {
417
598
  if (!valid || !packageMetadata.packageName) return [];
418
599