@devshop/crew 0.4.1 → 0.5.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.
package/CHANGELOG.md CHANGED
@@ -1,3 +1,17 @@
1
+ # [0.5.0](https://github.com/devshop-software/crew/compare/v0.4.2...v0.5.0) (2026-04-30)
2
+
3
+
4
+ ### Features
5
+
6
+ * crew update auto-bumps the local package via the project's PM ([9e39abd](https://github.com/devshop-software/crew/commit/9e39abdb7c51fba87c38802a07718af9725dccd2))
7
+
8
+ ## [0.4.2](https://github.com/devshop-software/crew/compare/v0.4.1...v0.4.2) (2026-04-30)
9
+
10
+
11
+ ### Bug Fixes
12
+
13
+ * publish to npm in prepare phase so failed publish can't orphan tags ([6ee8e5a](https://github.com/devshop-software/crew/commit/6ee8e5aacc4ed698e0e51c17891bd8b699faafb7))
14
+
1
15
  # [0.4.0](https://github.com/devshop-software/crew/compare/v0.3.0...v0.4.0) (2026-04-30)
2
16
 
3
17
 
package/README.md CHANGED
@@ -17,6 +17,8 @@ pnpm add -D @devshop/crew
17
17
  pnpm exec crew init
18
18
  ```
19
19
 
20
+ To pull newer skill content later, just run `pnpm exec crew update` — it auto-detects the package manager (pnpm/npm/yarn/bun) from your lockfile, runs `<pm> update @devshop/crew` to refresh the local install, then re-execs the freshly-installed CLI to apply the new skills. Pass `--no-self-update` to skip the package bump and only re-apply what's already on disk.
21
+
20
22
  This copies the skills into `./.claude/skills/`, writes a manifest, and appends a `## Workflow Config` block to `CLAUDE.md` (creating it if absent).
21
23
 
22
24
  ## Commands
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@devshop/crew",
3
- "version": "0.4.1",
3
+ "version": "0.5.0",
4
4
  "description": "Project-agnostic Claude Code skills for spec → implement → qa → review → ship",
5
5
  "bin": {
6
6
  "crew": "scripts/cli.js"
@@ -37,6 +37,10 @@
37
37
  },
38
38
  "license": "MIT",
39
39
  "devDependencies": {
40
- "semantic-release": "^24.2.0"
40
+ "@devshop/crew": "^0.4.1",
41
+ "semantic-release": "^24.2.0",
42
+ "@semantic-release/changelog": "^6.0.3",
43
+ "@semantic-release/exec": "^7.0.3",
44
+ "@semantic-release/git": "^10.0.1"
41
45
  }
42
46
  }
package/scripts/cli.js CHANGED
@@ -19,12 +19,13 @@ function usage() {
19
19
  ' --force Override prompts and refusals.',
20
20
  ' --yes Non-interactive (CI-safe defaults).',
21
21
  ' --dry-run Print actions, write nothing.',
22
- ' --no-claude-md On init only: skip CLAUDE.md append.'
22
+ ' --no-claude-md On init only: skip CLAUDE.md append.',
23
+ ' --no-self-update On update only: skip pulling a newer package via the local PM.'
23
24
  ].join('\n');
24
25
  }
25
26
 
26
27
  function parseArgs(argv) {
27
- const flags = { global: false, force: false, yes: false, dryRun: false, noClaudeMd: false };
28
+ const flags = { global: false, force: false, yes: false, dryRun: false, noClaudeMd: false, noSelfUpdate: false };
28
29
  let command = null;
29
30
  for (const a of argv) {
30
31
  if (a.startsWith('--')) {
@@ -34,6 +35,7 @@ function parseArgs(argv) {
34
35
  case '--yes': flags.yes = true; break;
35
36
  case '--dry-run': flags.dryRun = true; break;
36
37
  case '--no-claude-md': flags.noClaudeMd = true; break;
38
+ case '--no-self-update': flags.noSelfUpdate = true; break;
37
39
  case '--help': process.stdout.write(usage() + '\n'); process.exit(0);
38
40
  default:
39
41
  process.stderr.write(`Unknown flag: ${a}\n\n${usage()}\n`);
@@ -3,6 +3,7 @@ const { resolveTarget } = require('../lib/paths');
3
3
  const { readManifest, writeManifest, diffSkills, PACKAGE_NAME, SCHEMA_VERSION } = require('../lib/manifest');
4
4
  const { copyFolder, backupFolder, backupRoot } = require('../lib/fsx');
5
5
  const { chooseEditAction } = require('../lib/prompt');
6
+ const { runSelfUpdate } = require('../lib/self-update');
6
7
  const log = require('../lib/log');
7
8
 
8
9
  const PKG_ROOT = path.resolve(__dirname, '..', '..');
@@ -10,6 +11,13 @@ const PKG_VERSION = require(path.join(PKG_ROOT, 'package.json')).version;
10
11
  const PKG_SKILLS = path.join(PKG_ROOT, 'skills');
11
12
 
12
13
  module.exports = async function update(flags) {
14
+ // If installed as a local dep, bump the package via the project's package
15
+ // manager first, then re-exec the freshly-installed CLI to do the actual
16
+ // skill update. Skipped for global/dlx invocations (no project lockfile).
17
+ const self = runSelfUpdate(flags, log);
18
+ if (self.error) { log.error(self.message); return self.exitCode; }
19
+ if (self.reExec) return self.exitCode;
20
+
13
21
  let target;
14
22
  try { target = resolveTarget(flags); }
15
23
  catch (e) { log.error(e.message); return e.exitCode || 2; }
@@ -0,0 +1,83 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const { spawnSync } = require('child_process');
4
+ const { PACKAGE_NAME } = require('./manifest');
5
+
6
+ function findProjectRoot(start = process.cwd()) {
7
+ let dir = path.resolve(start);
8
+ while (true) {
9
+ if (fs.existsSync(path.join(dir, 'package.json'))) return dir;
10
+ const parent = path.dirname(dir);
11
+ if (parent === dir) return null;
12
+ dir = parent;
13
+ }
14
+ }
15
+
16
+ const LOCKFILES = [
17
+ ['pnpm-lock.yaml', 'pnpm'],
18
+ ['yarn.lock', 'yarn'],
19
+ ['package-lock.json', 'npm'],
20
+ ['bun.lock', 'bun'],
21
+ ['bun.lockb', 'bun']
22
+ ];
23
+
24
+ function detectPm(projectRoot) {
25
+ for (const [file, pm] of LOCKFILES) {
26
+ if (fs.existsSync(path.join(projectRoot, file))) return pm;
27
+ }
28
+ return null;
29
+ }
30
+
31
+ function isLocalDep(projectRoot) {
32
+ try {
33
+ const pkg = JSON.parse(fs.readFileSync(path.join(projectRoot, 'package.json'), 'utf8'));
34
+ return Boolean(
35
+ (pkg.dependencies && pkg.dependencies[PACKAGE_NAME]) ||
36
+ (pkg.devDependencies && pkg.devDependencies[PACKAGE_NAME])
37
+ );
38
+ } catch { return false; }
39
+ }
40
+
41
+ const UPDATE_ARGS = {
42
+ pnpm: ['update', PACKAGE_NAME],
43
+ npm: ['update', PACKAGE_NAME],
44
+ yarn: ['upgrade', PACKAGE_NAME],
45
+ bun: ['update', PACKAGE_NAME]
46
+ };
47
+
48
+ // Returns one of:
49
+ // { skipped: true, reason } — caller should fall through to in-process work
50
+ // { reExec: true, exitCode } — caller should return this exit code
51
+ // { error: true, message, exitCode } — caller should error and return
52
+ function runSelfUpdate(flags, log) {
53
+ if (flags.noSelfUpdate) return { skipped: true, reason: 'flag' };
54
+ if (flags.dryRun) return { skipped: true, reason: 'dry-run' };
55
+
56
+ const projectRoot = findProjectRoot();
57
+ if (!projectRoot) return { skipped: true, reason: 'no project root' };
58
+ if (!isLocalDep(projectRoot)) return { skipped: true, reason: 'not a local dep' };
59
+ const pm = detectPm(projectRoot);
60
+ if (!pm) return { skipped: true, reason: 'no lockfile' };
61
+
62
+ const localCli = path.join(projectRoot, 'node_modules', '@devshop', 'crew', 'scripts', 'cli.js');
63
+ if (!fs.existsSync(localCli)) return { skipped: true, reason: 'local CLI not present' };
64
+
65
+ log.action('self', `${pm} ${UPDATE_ARGS[pm].join(' ')}`);
66
+ const updateResult = spawnSync(pm, UPDATE_ARGS[pm], { cwd: projectRoot, stdio: 'inherit' });
67
+ if (updateResult.status !== 0) {
68
+ return { error: true, message: `${pm} update failed`, exitCode: 3 };
69
+ }
70
+
71
+ const passthrough = ['update', '--no-self-update'];
72
+ if (flags.global) passthrough.push('--global');
73
+ if (flags.force) passthrough.push('--force');
74
+ if (flags.yes) passthrough.push('--yes');
75
+
76
+ const child = spawnSync(process.execPath, [localCli, ...passthrough], {
77
+ cwd: process.cwd(),
78
+ stdio: 'inherit'
79
+ });
80
+ return { reExec: true, exitCode: child.status || 0 };
81
+ }
82
+
83
+ module.exports = { runSelfUpdate, findProjectRoot, detectPm, isLocalDep };