@phuc1403/musketeer 0.5.0 → 0.6.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 (85) hide show
  1. package/INSTALLATION.md +47 -32
  2. package/bin/musketeer.js +168 -79
  3. package/manifest.json +2 -0
  4. package/package.json +46 -46
  5. package/src/dotnet-scaffold-copier.js +72 -72
  6. package/src/provisioner/detect.js +113 -93
  7. package/src/self-update.js +77 -73
  8. package/template/.claude/hooks/block-unsafe-adr-title.cjs +72 -70
  9. package/template/.claude/hooks/init-adr-dir.cjs +153 -110
  10. package/template/.claude/hooks/inject-adr-env.cjs +83 -82
  11. package/template/.claude/hooks/lib/adr/command-scan.cjs +107 -0
  12. package/template/.claude/hooks/sync-adr-toc.cjs +111 -111
  13. package/template/dotnet-scaffold/src/__SolutionName__.Api/obj/Debug/net10.0/__SolutionName__.Api.AssemblyInfo.cs +1 -1
  14. package/template/dotnet-scaffold/src/__SolutionName__.Api/obj/Debug/net10.0/__SolutionName__.Api.AssemblyInfoInputs.cache +1 -1
  15. package/template/dotnet-scaffold/src/__SolutionName__.Application/obj/Debug/net10.0/__SolutionName__.Application.AssemblyInfo.cs +1 -1
  16. package/template/dotnet-scaffold/src/__SolutionName__.Application/obj/Debug/net10.0/__SolutionName__.Application.AssemblyInfoInputs.cache +1 -1
  17. package/template/dotnet-scaffold/src/__SolutionName__.Domain/obj/Debug/net10.0/__SolutionName__.Domain.AssemblyInfo.cs +1 -1
  18. package/template/dotnet-scaffold/src/__SolutionName__.Domain/obj/Debug/net10.0/__SolutionName__.Domain.AssemblyInfoInputs.cache +1 -1
  19. package/template/dotnet-scaffold/src/__SolutionName__.Infrastructure/obj/Debug/net10.0/__SolutionName__.Infrastructure.AssemblyInfo.cs +1 -1
  20. package/template/dotnet-scaffold/src/__SolutionName__.Infrastructure/obj/Debug/net10.0/__SolutionName__.Infrastructure.AssemblyInfoInputs.cache +1 -1
  21. package/template/dotnet-scaffold/tests/__SolutionName__.Api.Tests/obj/Debug/net10.0/__SolutionName__.Api.Tests.AssemblyInfo.cs +1 -1
  22. package/template/dotnet-scaffold/tests/__SolutionName__.Api.Tests/obj/Debug/net10.0/__SolutionName__.Api.Tests.AssemblyInfoInputs.cache +1 -1
  23. package/template/dotnet-scaffold/tests/__SolutionName__.Application.Tests/obj/Debug/net10.0/__SolutionName__.Application.Tests.AssemblyInfo.cs +1 -1
  24. package/template/dotnet-scaffold/tests/__SolutionName__.Application.Tests/obj/Debug/net10.0/__SolutionName__.Application.Tests.AssemblyInfoInputs.cache +1 -1
  25. package/template/dotnet-scaffold/tests/__SolutionName__.Domain.Tests/obj/Debug/net10.0/__SolutionName__.Domain.Tests.AssemblyInfo.cs +1 -1
  26. package/template/dotnet-scaffold/tests/__SolutionName__.Domain.Tests/obj/Debug/net10.0/__SolutionName__.Domain.Tests.AssemblyInfoInputs.cache +1 -1
  27. package/template/dotnet-scaffold/tests/__SolutionName__.Infrastructure.Tests/obj/Debug/net10.0/__SolutionName__.Infrastructure.Tests.AssemblyInfo.cs +1 -1
  28. package/template/dotnet-scaffold/tests/__SolutionName__.Infrastructure.Tests/obj/Debug/net10.0/__SolutionName__.Infrastructure.Tests.AssemblyInfoInputs.cache +1 -1
  29. package/template/dotnet-scaffold/src/__SolutionName__.Api/obj/Debug/net10.0/__SolutionName__.Api.assets.cache +0 -0
  30. package/template/dotnet-scaffold/src/__SolutionName__.Api/obj/__SolutionName__.Api.csproj.nuget.dgspec.json +0 -1536
  31. package/template/dotnet-scaffold/src/__SolutionName__.Api/obj/__SolutionName__.Api.csproj.nuget.g.props +0 -16
  32. package/template/dotnet-scaffold/src/__SolutionName__.Api/obj/__SolutionName__.Api.csproj.nuget.g.targets +0 -2
  33. package/template/dotnet-scaffold/src/__SolutionName__.Api/obj/project.assets.json +0 -559
  34. package/template/dotnet-scaffold/src/__SolutionName__.Api/obj/project.nuget.cache +0 -8
  35. package/template/dotnet-scaffold/src/__SolutionName__.Application/obj/Debug/net10.0/__SolutionName__.Application.assets.cache +0 -0
  36. package/template/dotnet-scaffold/src/__SolutionName__.Application/obj/__SolutionName__.Application.csproj.nuget.dgspec.json +0 -690
  37. package/template/dotnet-scaffold/src/__SolutionName__.Application/obj/__SolutionName__.Application.csproj.nuget.g.props +0 -16
  38. package/template/dotnet-scaffold/src/__SolutionName__.Application/obj/__SolutionName__.Application.csproj.nuget.g.targets +0 -2
  39. package/template/dotnet-scaffold/src/__SolutionName__.Application/obj/project.assets.json +0 -376
  40. package/template/dotnet-scaffold/src/__SolutionName__.Application/obj/project.nuget.cache +0 -8
  41. package/template/dotnet-scaffold/src/__SolutionName__.Domain/obj/Debug/net10.0/__SolutionName__.Domain.assets.cache +0 -0
  42. package/template/dotnet-scaffold/src/__SolutionName__.Domain/obj/__SolutionName__.Domain.csproj.nuget.dgspec.json +0 -347
  43. package/template/dotnet-scaffold/src/__SolutionName__.Domain/obj/__SolutionName__.Domain.csproj.nuget.g.props +0 -16
  44. package/template/dotnet-scaffold/src/__SolutionName__.Domain/obj/__SolutionName__.Domain.csproj.nuget.g.targets +0 -2
  45. package/template/dotnet-scaffold/src/__SolutionName__.Domain/obj/project.assets.json +0 -353
  46. package/template/dotnet-scaffold/src/__SolutionName__.Domain/obj/project.nuget.cache +0 -8
  47. package/template/dotnet-scaffold/src/__SolutionName__.Infrastructure/obj/Debug/net10.0/__SolutionName__.Infrastructure.assets.cache +0 -0
  48. package/template/dotnet-scaffold/src/__SolutionName__.Infrastructure/obj/Debug/net10.0/__SolutionName__.Infrastructure.csproj.AssemblyReference.cache +0 -0
  49. package/template/dotnet-scaffold/src/__SolutionName__.Infrastructure/obj/__SolutionName__.Infrastructure.csproj.nuget.dgspec.json +0 -1047
  50. package/template/dotnet-scaffold/src/__SolutionName__.Infrastructure/obj/__SolutionName__.Infrastructure.csproj.nuget.g.props +0 -16
  51. package/template/dotnet-scaffold/src/__SolutionName__.Infrastructure/obj/__SolutionName__.Infrastructure.csproj.nuget.g.targets +0 -7
  52. package/template/dotnet-scaffold/src/__SolutionName__.Infrastructure/obj/project.assets.json +0 -858
  53. package/template/dotnet-scaffold/src/__SolutionName__.Infrastructure/obj/project.nuget.cache +0 -17
  54. package/template/dotnet-scaffold/tests/__SolutionName__.Api.Tests/bin/Debug/net10.0/.msCoverageSourceRootsMapping___SolutionName__.Api.Tests +0 -0
  55. package/template/dotnet-scaffold/tests/__SolutionName__.Api.Tests/obj/Debug/net10.0/__SolutionName__.Api.Tests.assets.cache +0 -0
  56. package/template/dotnet-scaffold/tests/__SolutionName__.Api.Tests/obj/Debug/net10.0/__SolutionName__.Api.Tests.csproj.AssemblyReference.cache +0 -0
  57. package/template/dotnet-scaffold/tests/__SolutionName__.Api.Tests/obj/__SolutionName__.Api.Tests.csproj.nuget.dgspec.json +0 -1908
  58. package/template/dotnet-scaffold/tests/__SolutionName__.Api.Tests/obj/__SolutionName__.Api.Tests.csproj.nuget.g.props +0 -27
  59. package/template/dotnet-scaffold/tests/__SolutionName__.Api.Tests/obj/__SolutionName__.Api.Tests.csproj.nuget.g.targets +0 -16
  60. package/template/dotnet-scaffold/tests/__SolutionName__.Api.Tests/obj/project.assets.json +0 -3283
  61. package/template/dotnet-scaffold/tests/__SolutionName__.Api.Tests/obj/project.nuget.cache +0 -60
  62. package/template/dotnet-scaffold/tests/__SolutionName__.Application.Tests/bin/Debug/net10.0/.msCoverageSourceRootsMapping___SolutionName__.Application.Tests +0 -0
  63. package/template/dotnet-scaffold/tests/__SolutionName__.Application.Tests/obj/Debug/net10.0/__SolutionName__.Application.Tests.assets.cache +0 -0
  64. package/template/dotnet-scaffold/tests/__SolutionName__.Application.Tests/obj/Debug/net10.0/__SolutionName__.Application.Tests.csproj.AssemblyReference.cache +0 -0
  65. package/template/dotnet-scaffold/tests/__SolutionName__.Application.Tests/obj/__SolutionName__.Application.Tests.csproj.nuget.dgspec.json +0 -1055
  66. package/template/dotnet-scaffold/tests/__SolutionName__.Application.Tests/obj/__SolutionName__.Application.Tests.csproj.nuget.g.props +0 -26
  67. package/template/dotnet-scaffold/tests/__SolutionName__.Application.Tests/obj/__SolutionName__.Application.Tests.csproj.nuget.g.targets +0 -11
  68. package/template/dotnet-scaffold/tests/__SolutionName__.Application.Tests/obj/project.assets.json +0 -1662
  69. package/template/dotnet-scaffold/tests/__SolutionName__.Application.Tests/obj/project.nuget.cache +0 -30
  70. package/template/dotnet-scaffold/tests/__SolutionName__.Domain.Tests/bin/Debug/net10.0/.msCoverageSourceRootsMapping___SolutionName__.Domain.Tests +0 -0
  71. package/template/dotnet-scaffold/tests/__SolutionName__.Domain.Tests/obj/Debug/net10.0/__SolutionName__.Domain.Tests.assets.cache +0 -0
  72. package/template/dotnet-scaffold/tests/__SolutionName__.Domain.Tests/obj/Debug/net10.0/__SolutionName__.Domain.Tests.csproj.AssemblyReference.cache +0 -0
  73. package/template/dotnet-scaffold/tests/__SolutionName__.Domain.Tests/obj/__SolutionName__.Domain.Tests.csproj.nuget.dgspec.json +0 -712
  74. package/template/dotnet-scaffold/tests/__SolutionName__.Domain.Tests/obj/__SolutionName__.Domain.Tests.csproj.nuget.g.props +0 -26
  75. package/template/dotnet-scaffold/tests/__SolutionName__.Domain.Tests/obj/__SolutionName__.Domain.Tests.csproj.nuget.g.targets +0 -11
  76. package/template/dotnet-scaffold/tests/__SolutionName__.Domain.Tests/obj/project.assets.json +0 -1644
  77. package/template/dotnet-scaffold/tests/__SolutionName__.Domain.Tests/obj/project.nuget.cache +0 -30
  78. package/template/dotnet-scaffold/tests/__SolutionName__.Infrastructure.Tests/bin/Debug/net10.0/.msCoverageSourceRootsMapping___SolutionName__.Infrastructure.Tests +0 -0
  79. package/template/dotnet-scaffold/tests/__SolutionName__.Infrastructure.Tests/obj/Debug/net10.0/__SolutionName__.Infrastructure.Tests.assets.cache +0 -0
  80. package/template/dotnet-scaffold/tests/__SolutionName__.Infrastructure.Tests/obj/Debug/net10.0/__SolutionName__.Infrastructure.Tests.csproj.AssemblyReference.cache +0 -0
  81. package/template/dotnet-scaffold/tests/__SolutionName__.Infrastructure.Tests/obj/__SolutionName__.Infrastructure.Tests.csproj.nuget.dgspec.json +0 -1412
  82. package/template/dotnet-scaffold/tests/__SolutionName__.Infrastructure.Tests/obj/__SolutionName__.Infrastructure.Tests.csproj.nuget.g.props +0 -26
  83. package/template/dotnet-scaffold/tests/__SolutionName__.Infrastructure.Tests/obj/__SolutionName__.Infrastructure.Tests.csproj.nuget.g.targets +0 -13
  84. package/template/dotnet-scaffold/tests/__SolutionName__.Infrastructure.Tests/obj/project.assets.json +0 -2130
  85. package/template/dotnet-scaffold/tests/__SolutionName__.Infrastructure.Tests/obj/project.nuget.cache +0 -38
package/INSTALLATION.md CHANGED
@@ -1,32 +1,47 @@
1
- # Prerequisites & Auto-Install
2
-
3
- `musketeer` auto-installs the external prerequisites required by your selected company
4
- (announced before each install, never silent). When no OS package manager is available — or
5
- an elevation prompt is refused — it falls back to printing the exact manual command below.
6
-
7
- > Filled in detail during Phase 06 (provisioner) / Phase 08 (publish).
8
-
9
- ## Per-prerequisite matrix
10
-
11
- | Prereq | Needed by | Windows | macOS | Linux |
12
- |--------|-----------|---------|-------|-------|
13
- | Node 18+ | core | `winget install OpenJS.NodeJS.LTS` | `brew install node` | NodeSource / distro |
14
- | git | core, code-review | `winget install Git.Git` | `brew install git` | `apt/dnf/pacman install git` |
15
- | Python 3.8+ | core (skill-creator) | `winget install Python.Python.3.12` | `brew install python@3.12` | `apt install python3 python3-pip` |
16
- | Java 8+ | architecture (context-map) | `winget install Microsoft.OpenJDK` | `brew install --cask temurin` | `apt install openjdk-17-jdk` |
17
- | adr-tools | architecture (adr-writer) | download the [release zip](https://github.com/npryce/adr-tools/releases), copy `src/*` into `C:\Program Files\Git\usr\bin` (Git Bash) | `brew install adr-tools` | `apt install adr-tools` · Arch: AUR `adr-tools` · Fedora/RHEL: clone + add `src/` to `PATH` |
18
- | gh | code-review (PR mode) | `winget install GitHub.cli` | `brew install gh` | `apt install gh` |
19
-
20
- > `adr-tools` is a set of bash scripts. On Windows it runs inside Git Bash (already required by
21
- > core), so the skill works but `musketeer muster` cannot detect it from `cmd.exe` and will print
22
- > a manual-install line every run. Expected; ignore it once `adr config` works in Git Bash.
23
- >
24
- > Running `musketeer muster` non-interactively (CI, onboarding scripts)? The `apt`/`brew` install
25
- > commands can prompt for `sudo` or hang waiting for input. Pre-install the prereqs yourself, or
26
- > set `MUSKETEER_SKIP_INSTALL=1` to print the manual command instead of executing it (red team
27
- > finding 8).
28
-
29
- ## Secrets (never auto-installed)
30
-
31
- - `ANTHROPIC_API_KEY` needed only by skill-creator's improve/loop scripts. You will be
32
- prompted / shown how to set it; musketeer never writes it to the repo.
1
+ # Prerequisites & Auto-Install
2
+
3
+ `musketeer` auto-installs the external prerequisites required by your selected company
4
+ (announced before each install, never silent). When no OS package manager is available — or
5
+ an elevation prompt is refused — it falls back to printing the exact manual command below.
6
+
7
+ > Filled in detail during Phase 06 (provisioner) / Phase 08 (publish).
8
+
9
+ ## Per-prerequisite matrix
10
+
11
+ | Prereq | Needed by | Windows | macOS | Linux |
12
+ |--------|-----------|---------|-------|-------|
13
+ | Node 18+ | core | `winget install OpenJS.NodeJS.LTS` | `brew install node` | NodeSource / distro |
14
+ | git | core, code-review | `winget install Git.Git` | `brew install git` | `apt/dnf/pacman install git` |
15
+ | Python 3.8+ | core (skill-creator) | `winget install Python.Python.3.12` | `brew install python@3.12` | `apt install python3 python3-pip` |
16
+ | Java 8+ | architecture (context-map) | `winget install Microsoft.OpenJDK` | `brew install --cask temurin` | `apt install openjdk-17-jdk` |
17
+ | adr-tools | architecture (adr-writer) | download the [release zip](https://github.com/npryce/adr-tools/releases), copy `src/*` into `C:\Program Files\Git\usr\bin` (Git Bash) | `brew install adr-tools` | `apt install adr-tools` · Arch: AUR `adr-tools` · Fedora/RHEL: clone + add `src/` to `PATH` |
18
+ | gh | code-review (PR mode) | `winget install GitHub.cli` | `brew install gh` | `apt install gh` |
19
+
20
+ > `adr-tools` is a set of bash scripts. On Windows it runs inside Git Bash (already required by
21
+ > core), which is where muster now probes for it, so a working install is reported as present.
22
+ >
23
+ > On Windows, keep the project on a path with no spaces. `adr-tools` resolves its ADR directory
24
+ > with an unquoted path test, so a space makes it fall back to a `doc/adr` default and write the
25
+ > record where nothing indexes it. Musketeer refuses the call rather than let that happen, but the
26
+ > underlying tool is still wrong.
27
+
28
+ ### Running muster without a terminal
29
+
30
+ `musketeer muster` opens a picker, which needs a terminal. For CI, onboarding scripts, and agent
31
+ sessions, name the company instead and the picker is skipped:
32
+
33
+ ```bash
34
+ musketeer muster --company architecture,dotnet # set the company to exactly these
35
+ musketeer muster --keep # refresh the company already in .musketeer.json
36
+ ```
37
+
38
+ `--keep` is the one to reach for after upgrading the CLI: it re-applies the latest templates
39
+ without changing which musketeers are installed. Both flags survive the self-update re-exec.
40
+
41
+ The `apt`/`brew` prereq installs can still prompt for `sudo` or wait on input. Pre-install them, or
42
+ set `MUSKETEER_SKIP_INSTALL=1` to print the manual command instead of running it.
43
+
44
+ ## Secrets (never auto-installed)
45
+
46
+ - `ANTHROPIC_API_KEY` — needed only by skill-creator's improve/loop scripts. You will be
47
+ prompted / shown how to set it; musketeer never writes it to the repo.
package/bin/musketeer.js CHANGED
@@ -1,79 +1,168 @@
1
- #!/usr/bin/env node
2
- 'use strict';
3
-
4
- const { parseArgs } = require('node:util');
5
-
6
- const USAGE = `musketeer — scaffold a curated company of Claude Code musketeers into ./.claude
7
-
8
- Usage:
9
- musketeer muster Self-update to latest, then pick your company and refresh all selected skills
10
- musketeer promote Update the musketeer CLI itself to the latest published version
11
- musketeer Show this help
12
- musketeer --help Show this help
13
-
14
- Notes:
15
- * "muster" changes which musketeers are in THIS project; "promote" upgrades the CLI itself.
16
- * On confirm, muster REFRESHES every selected musketeer's files to the latest template (overwrites local edits).
17
- * muster auto-updates the CLI to the latest published version first (set MUSKETEER_NO_SELF_UPDATE=1 to skip).
18
- * "core" is always installed and locked — it cannot be removed.
19
- * The muster is multi-select: check every musketeer you want, then confirm.
20
- * settings.json is GENERATED per-selection; missing prereqs are auto-installed (announced).`;
21
-
22
- function parse(argv) {
23
- return parseArgs({
24
- args: argv,
25
- options: {
26
- help: { type: 'boolean', short: 'h', default: false },
27
- },
28
- allowPositionals: true,
29
- });
30
- }
31
-
32
- async function main(argv = process.argv.slice(2)) {
33
- let parsed;
34
- try {
35
- parsed = parse(argv);
36
- } catch (err) {
37
- process.stderr.write(`${err.message}\n\n${USAGE}\n`);
38
- process.exitCode = 2;
39
- return;
40
- }
41
-
42
- const { values, positionals } = parsed;
43
- const command = positionals[0];
44
-
45
- // `musketeer` alone (or --help) usage.
46
- if (values.help || !command) {
47
- process.stdout.write(`${USAGE}\n`);
48
- return;
49
- }
50
-
51
- if (command === 'muster') {
52
- // Pull the latest published version first, so the picker refreshes installed
53
- // skills to the newest template (not a stale package's). Re-execs when behind.
54
- const { ensureLatest } = require('../src/self-update');
55
- const reexecCode = ensureLatest();
56
- if (reexecCode !== null) {
57
- process.exitCode = reexecCode;
58
- return;
59
- }
60
- // Lazy-require so help never pays the cost of loading the engine/manifest.
61
- const reconcile = require('../src/reconcile');
62
- await reconcile.run({ desiredIds: null, interactive: true });
63
- return;
64
- }
65
-
66
- if (command === 'promote') {
67
- const promote = require('../src/promote');
68
- await promote.run();
69
- return;
70
- }
71
-
72
- process.stderr.write(`Unknown command "${command}".\n\n${USAGE}\n`);
73
- process.exitCode = 2;
74
- }
75
-
76
- main().catch((err) => {
77
- process.stderr.write(`musketeer: ${err && err.message ? err.message : err}\n`);
78
- process.exitCode = 1;
79
- });
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ const { parseArgs } = require('node:util');
5
+
6
+ const USAGE = `musketeer — scaffold a curated company of Claude Code musketeers into ./.claude
7
+
8
+ Usage:
9
+ musketeer muster Self-update to latest, then pick your company and refresh all selected skills
10
+ musketeer muster --keep
11
+ Refresh the company already recorded in .musketeer.json, no picker
12
+ musketeer muster --company a,b
13
+ Set the company to exactly these musketeers, no picker
14
+ musketeer promote Update the musketeer CLI itself to the latest published version
15
+ musketeer Show this help
16
+ musketeer --help Show this help
17
+
18
+ Notes:
19
+ * "muster" changes which musketeers are in THIS project; "promote" upgrades the CLI itself.
20
+ * On confirm, muster REFRESHES every selected musketeer's files to the latest template (overwrites local edits).
21
+ * muster auto-updates the CLI to the latest published version first (set MUSKETEER_NO_SELF_UPDATE=1 to skip).
22
+ * "core" is always installed and locked — it cannot be removed.
23
+ * The muster is multi-select: check every musketeer you want, then confirm.
24
+ * settings.json is GENERATED per-selection; missing prereqs are auto-installed (announced).
25
+ * --keep and --company skip the picker, so muster runs in CI and other sessions with no terminal.
26
+ * Without either flag and without a terminal, muster stops and tells you which flag to use.`;
27
+
28
+ function parse(argv) {
29
+ return parseArgs({
30
+ args: argv,
31
+ options: {
32
+ help: { type: 'boolean', short: 'h', default: false },
33
+ company: { type: 'string' },
34
+ keep: { type: 'boolean', default: false },
35
+ },
36
+ allowPositionals: true,
37
+ });
38
+ }
39
+
40
+ /**
41
+ * Work out what this muster should do before anything touches the disk.
42
+ *
43
+ * Bare `muster` opens the picker, which needs a terminal. Under CI, or any
44
+ * agent without one, the picker used to die inside its own TTY setup with no
45
+ * hint that a flag could have avoided it. Both flags skip the picker: --company
46
+ * states the company outright, --keep reconciles the one already recorded.
47
+ *
48
+ * `forward` is what the self-update must replay when it re-execs the newer
49
+ * binary, so a scripted run does not land back at the picker.
50
+ *
51
+ * @param {{company?:string, keep:boolean}} values parsed flags
52
+ * @param {{isTTY:boolean, projectDir:string, readLockfile:function}} ctx
53
+ * @returns {{desiredIds:?string[], interactive:boolean, forward:string[]}}
54
+ */
55
+ function resolveMusterPlan(values, ctx) {
56
+ if (values.company !== undefined && values.keep) {
57
+ throw new Error('Use either --company or --keep, not both: one sets the company, the other keeps it.');
58
+ }
59
+
60
+ if (values.company !== undefined) {
61
+ const ids = values.company
62
+ .split(',')
63
+ .map((s) => s.trim())
64
+ .filter(Boolean);
65
+ if (ids.length === 0) {
66
+ throw new Error('--company needs at least one musketeer, for example: --company architecture,dotnet');
67
+ }
68
+ return { desiredIds: ids, interactive: false, forward: [`--company=${ids.join(',')}`] };
69
+ }
70
+
71
+ if (values.keep) {
72
+ const prev = ctx.readLockfile(ctx.projectDir);
73
+ if (!prev.musketeers || prev.musketeers.length === 0) {
74
+ throw new Error(
75
+ 'No company recorded in .musketeer.json, so there is nothing for --keep to keep. ' +
76
+ 'Name one with --company, for example: --company architecture,dotnet'
77
+ );
78
+ }
79
+ return { desiredIds: prev.musketeers, interactive: false, forward: ['--keep'] };
80
+ }
81
+
82
+ if (!ctx.isTTY) {
83
+ throw new Error(
84
+ 'muster needs a terminal for its picker, and this session has none. ' +
85
+ 'Refresh the company already recorded with `musketeer muster --keep`, ' +
86
+ 'or state one with `musketeer muster --company architecture,dotnet`.'
87
+ );
88
+ }
89
+
90
+ return { desiredIds: null, interactive: true, forward: [] };
91
+ }
92
+
93
+ async function main(argv = process.argv.slice(2)) {
94
+ let parsed;
95
+ try {
96
+ parsed = parse(argv);
97
+ } catch (err) {
98
+ process.stderr.write(`${err.message}\n\n${USAGE}\n`);
99
+ process.exitCode = 2;
100
+ return;
101
+ }
102
+
103
+ const { values, positionals } = parsed;
104
+ const command = positionals[0];
105
+
106
+ // `musketeer` alone (or --help) → usage.
107
+ if (values.help || !command) {
108
+ process.stdout.write(`${USAGE}\n`);
109
+ return;
110
+ }
111
+
112
+ if (command === 'muster') {
113
+ const { readLockfile } = require('../src/lockfile');
114
+ let plan;
115
+ try {
116
+ plan = resolveMusterPlan(values, {
117
+ isTTY: !!process.stdin.isTTY,
118
+ projectDir: process.cwd(),
119
+ readLockfile,
120
+ });
121
+ } catch (err) {
122
+ process.stderr.write(`${err.message}\n`);
123
+ process.exitCode = 2;
124
+ return;
125
+ }
126
+
127
+ // Pull the latest published version first, so the picker refreshes installed
128
+ // skills to the newest template (not a stale package's). Re-execs when behind.
129
+ const { ensureLatest } = require('../src/self-update');
130
+ const reexecCode = ensureLatest({ args: plan.forward });
131
+ if (reexecCode !== null) {
132
+ if (reexecCode !== 0) {
133
+ // The update landed, the refresh did not. Say so: the CLI is now newer
134
+ // than the templates in this project, and nothing announced the split.
135
+ process.stderr.write(
136
+ 'The CLI was updated but this project was not refreshed — nothing in .claude changed. ' +
137
+ 'Re-run `musketeer muster` once the problem above is resolved.\n'
138
+ );
139
+ }
140
+ process.exitCode = reexecCode;
141
+ return;
142
+ }
143
+ // Lazy-require so help never pays the cost of loading the engine/manifest.
144
+ const reconcile = require('../src/reconcile');
145
+ await reconcile.run({ desiredIds: plan.desiredIds, interactive: plan.interactive });
146
+ return;
147
+ }
148
+
149
+ if (command === 'promote') {
150
+ const promote = require('../src/promote');
151
+ await promote.run();
152
+ return;
153
+ }
154
+
155
+ process.stderr.write(`Unknown command "${command}".\n\n${USAGE}\n`);
156
+ process.exitCode = 2;
157
+ }
158
+
159
+ // Only run as a command. Requiring this file (the tests do, for
160
+ // resolveMusterPlan) must not start a muster.
161
+ if (require.main === module) {
162
+ main().catch((err) => {
163
+ process.stderr.write(`musketeer: ${err && err.message ? err.message : err}\n`);
164
+ process.exitCode = 1;
165
+ });
166
+ }
167
+
168
+ module.exports = { resolveMusterPlan, main };
package/manifest.json CHANGED
@@ -59,6 +59,7 @@
59
59
  "hooks/validate-cml-hook.js",
60
60
  "hooks/validate-characteristics-hook.cjs",
61
61
  "hooks/lib/characteristics/**",
62
+ "hooks/lib/adr/**",
62
63
  "hooks/sync-adr-toc.cjs",
63
64
  "hooks/warn-missing-characteristics.cjs",
64
65
  "hooks/block-unsafe-adr-title.cjs",
@@ -267,6 +268,7 @@
267
268
  },
268
269
  "adr-tools": {
269
270
  "detect": "adr config",
271
+ "detectShell": "bash",
270
272
  "kind": "package",
271
273
  "needs": ["git"],
272
274
  "install": {
package/package.json CHANGED
@@ -1,46 +1,46 @@
1
- {
2
- "name": "@phuc1403/musketeer",
3
- "version": "0.5.0",
4
- "description": "Distributable custom Claude Code harness — one declarative command scaffolds a curated company of musketeers (skills/agents/hooks) into any project's .claude/.",
5
- "type": "commonjs",
6
- "bin": {
7
- "musketeer": "bin/musketeer.js"
8
- },
9
- "engines": {
10
- "node": ">=18"
11
- },
12
- "scripts": {
13
- "test": "node --test"
14
- },
15
- "files": [
16
- "bin",
17
- "src",
18
- "manifest.json",
19
- "template",
20
- "README.md",
21
- "INSTALLATION.md",
22
- "LICENSE"
23
- ],
24
- "keywords": [
25
- "claude",
26
- "claude-code",
27
- "harness",
28
- "scaffolder",
29
- "cli",
30
- "skills",
31
- "agents",
32
- "hooks"
33
- ],
34
- "license": "MIT",
35
- "repository": {
36
- "type": "git",
37
- "url": "git+https://github.com/phamhongphuc1403/musketeer.git"
38
- },
39
- "bugs": {
40
- "url": "https://github.com/phamhongphuc1403/musketeer/issues"
41
- },
42
- "homepage": "https://github.com/phamhongphuc1403/musketeer#readme",
43
- "dependencies": {
44
- "@clack/prompts": "^0.7.0"
45
- }
46
- }
1
+ {
2
+ "name": "@phuc1403/musketeer",
3
+ "version": "0.6.0",
4
+ "description": "Distributable custom Claude Code harness — one declarative command scaffolds a curated company of musketeers (skills/agents/hooks) into any project's .claude/.",
5
+ "type": "commonjs",
6
+ "bin": {
7
+ "musketeer": "bin/musketeer.js"
8
+ },
9
+ "engines": {
10
+ "node": ">=18"
11
+ },
12
+ "scripts": {
13
+ "test": "node --test"
14
+ },
15
+ "files": [
16
+ "bin",
17
+ "src",
18
+ "manifest.json",
19
+ "template",
20
+ "README.md",
21
+ "INSTALLATION.md",
22
+ "LICENSE"
23
+ ],
24
+ "keywords": [
25
+ "claude",
26
+ "claude-code",
27
+ "harness",
28
+ "scaffolder",
29
+ "cli",
30
+ "skills",
31
+ "agents",
32
+ "hooks"
33
+ ],
34
+ "license": "MIT",
35
+ "repository": {
36
+ "type": "git",
37
+ "url": "git+https://github.com/phamhongphuc1403/musketeer.git"
38
+ },
39
+ "bugs": {
40
+ "url": "https://github.com/phamhongphuc1403/musketeer/issues"
41
+ },
42
+ "homepage": "https://github.com/phamhongphuc1403/musketeer#readme",
43
+ "dependencies": {
44
+ "@clack/prompts": "^0.7.0"
45
+ }
46
+ }
@@ -1,72 +1,72 @@
1
- 'use strict';
2
-
3
- const fs = require('fs');
4
- const path = require('path');
5
-
6
- function containedPath(root, candidate) {
7
- const resolvedRoot = path.resolve(root);
8
- const resolved = path.resolve(candidate);
9
- if (resolved !== resolvedRoot && !resolved.startsWith(resolvedRoot + path.sep)) return null;
10
- return resolved;
11
- }
12
-
13
- function substitute(text, token, replacement) {
14
- return text.split(token).join(replacement);
15
- }
16
-
17
- /**
18
- * Recursively copy every file under srcDir into stagingDir, substituting
19
- * `token` with `replacement` in both path segments and file contents. Every
20
- * computed destination path is validated to stay under `stagingDir` — rejects
21
- * (throws) rather than writing outside it.
22
- * @param {string} srcDir
23
- * @param {string} stagingDir
24
- * @param {string} token
25
- * @param {string} replacement
26
- */
27
- function copyTemplateWithTokenSubstitution(srcDir, stagingDir, token, replacement) {
28
- walk(srcDir, stagingDir, stagingDir, token, replacement);
29
- }
30
-
31
- function walk(currentSrc, currentDest, stagingRoot, token, replacement) {
32
- const entries = fs.readdirSync(currentSrc, { withFileTypes: true });
33
- for (const entry of entries) {
34
- const substitutedName = substitute(entry.name, token, replacement);
35
- const srcPath = path.join(currentSrc, entry.name);
36
- const destPath = containedPath(stagingRoot, path.join(currentDest, substitutedName));
37
- if (!destPath) {
38
- throw new Error(`copyTemplateWithTokenSubstitution: computed path escapes staging dir: "${substitutedName}"`);
39
- }
40
-
41
- if (entry.isDirectory()) {
42
- fs.mkdirSync(destPath, { recursive: true });
43
- walk(srcPath, destPath, stagingRoot, token, replacement);
44
- } else if (entry.isFile()) {
45
- fs.mkdirSync(path.dirname(destPath), { recursive: true });
46
- const content = fs.readFileSync(srcPath, 'utf8');
47
- fs.writeFileSync(destPath, substitute(content, token, replacement), 'utf8');
48
- }
49
- }
50
- }
51
-
52
- /**
53
- * Move every top-level entry from stagingDir into projectDir. Pre-checks all
54
- * names for collisions before moving any of them, then moves each via
55
- * fs.renameSync (atomic per entry on the same filesystem). Throws instead of
56
- * silently overwriting if a destination name already exists.
57
- * @param {string} stagingDir
58
- * @param {string} projectDir
59
- */
60
- function promoteStagedScaffold(stagingDir, projectDir) {
61
- const entries = fs.readdirSync(stagingDir, { withFileTypes: true });
62
- for (const entry of entries) {
63
- if (fs.existsSync(path.join(projectDir, entry.name))) {
64
- throw new Error(`promoteStagedScaffold: "${entry.name}" already exists at destination`);
65
- }
66
- }
67
- for (const entry of entries) {
68
- fs.renameSync(path.join(stagingDir, entry.name), path.join(projectDir, entry.name));
69
- }
70
- }
71
-
72
- module.exports = { copyTemplateWithTokenSubstitution, promoteStagedScaffold };
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+
6
+ function containedPath(root, candidate) {
7
+ const resolvedRoot = path.resolve(root);
8
+ const resolved = path.resolve(candidate);
9
+ if (resolved !== resolvedRoot && !resolved.startsWith(resolvedRoot + path.sep)) return null;
10
+ return resolved;
11
+ }
12
+
13
+ function substitute(text, token, replacement) {
14
+ return text.split(token).join(replacement);
15
+ }
16
+
17
+ /**
18
+ * Recursively copy every file under srcDir into stagingDir, substituting
19
+ * `token` with `replacement` in both path segments and file contents. Every
20
+ * computed destination path is validated to stay under `stagingDir` — rejects
21
+ * (throws) rather than writing outside it.
22
+ * @param {string} srcDir
23
+ * @param {string} stagingDir
24
+ * @param {string} token
25
+ * @param {string} replacement
26
+ */
27
+ function copyTemplateWithTokenSubstitution(srcDir, stagingDir, token, replacement) {
28
+ walk(srcDir, stagingDir, stagingDir, token, replacement);
29
+ }
30
+
31
+ function walk(currentSrc, currentDest, stagingRoot, token, replacement) {
32
+ const entries = fs.readdirSync(currentSrc, { withFileTypes: true });
33
+ for (const entry of entries) {
34
+ const substitutedName = substitute(entry.name, token, replacement);
35
+ const srcPath = path.join(currentSrc, entry.name);
36
+ const destPath = containedPath(stagingRoot, path.join(currentDest, substitutedName));
37
+ if (!destPath) {
38
+ throw new Error(`copyTemplateWithTokenSubstitution: computed path escapes staging dir: "${substitutedName}"`);
39
+ }
40
+
41
+ if (entry.isDirectory()) {
42
+ fs.mkdirSync(destPath, { recursive: true });
43
+ walk(srcPath, destPath, stagingRoot, token, replacement);
44
+ } else if (entry.isFile()) {
45
+ fs.mkdirSync(path.dirname(destPath), { recursive: true });
46
+ const content = fs.readFileSync(srcPath, 'utf8');
47
+ fs.writeFileSync(destPath, substitute(content, token, replacement), 'utf8');
48
+ }
49
+ }
50
+ }
51
+
52
+ /**
53
+ * Move every top-level entry from stagingDir into projectDir. Pre-checks all
54
+ * names for collisions before moving any of them, then moves each via
55
+ * fs.renameSync (atomic per entry on the same filesystem). Throws instead of
56
+ * silently overwriting if a destination name already exists.
57
+ * @param {string} stagingDir
58
+ * @param {string} projectDir
59
+ */
60
+ function promoteStagedScaffold(stagingDir, projectDir) {
61
+ const entries = fs.readdirSync(stagingDir, { withFileTypes: true });
62
+ for (const entry of entries) {
63
+ if (fs.existsSync(path.join(projectDir, entry.name))) {
64
+ throw new Error(`promoteStagedScaffold: "${entry.name}" already exists at destination`);
65
+ }
66
+ }
67
+ for (const entry of entries) {
68
+ fs.renameSync(path.join(stagingDir, entry.name), path.join(projectDir, entry.name));
69
+ }
70
+ }
71
+
72
+ module.exports = { copyTemplateWithTokenSubstitution, promoteStagedScaffold };