@phuc1403/musketeer 0.5.0 → 0.7.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/INSTALLATION.md +52 -32
- package/bin/musketeer.js +168 -79
- package/manifest.json +9 -9
- package/package.json +48 -46
- package/src/dotnet-scaffold-copier.js +79 -72
- package/src/provisioner/detect.js +93 -93
- package/src/self-update.js +77 -73
- package/template/.claude/hooks/block-unsafe-adr-title.cjs +85 -70
- package/template/.claude/hooks/init-adr-dir.cjs +173 -110
- package/template/.claude/hooks/inject-adr-flags.cjs +94 -0
- package/template/.claude/hooks/lib/adr/command-scan.cjs +115 -0
- package/template/.claude/hooks/sync-adr-toc.cjs +146 -111
- package/template/.claude/skills/adr-writer/SKILL.md +48 -46
- package/template/.claude/hooks/inject-adr-env.cjs +0 -82
- package/template/dotnet-scaffold/src/__SolutionName__.Api/obj/Debug/net10.0/.NETCoreApp,Version=v10.0.AssemblyAttributes.cs +0 -4
- package/template/dotnet-scaffold/src/__SolutionName__.Api/obj/Debug/net10.0/__SolutionName__.Api.AssemblyInfo.cs +0 -22
- package/template/dotnet-scaffold/src/__SolutionName__.Api/obj/Debug/net10.0/__SolutionName__.Api.AssemblyInfoInputs.cache +0 -1
- package/template/dotnet-scaffold/src/__SolutionName__.Api/obj/Debug/net10.0/__SolutionName__.Api.GeneratedMSBuildEditorConfig.editorconfig +0 -23
- package/template/dotnet-scaffold/src/__SolutionName__.Api/obj/Debug/net10.0/__SolutionName__.Api.GlobalUsings.g.cs +0 -17
- package/template/dotnet-scaffold/src/__SolutionName__.Api/obj/Debug/net10.0/__SolutionName__.Api.assets.cache +0 -0
- package/template/dotnet-scaffold/src/__SolutionName__.Api/obj/__SolutionName__.Api.csproj.nuget.dgspec.json +0 -1536
- package/template/dotnet-scaffold/src/__SolutionName__.Api/obj/__SolutionName__.Api.csproj.nuget.g.props +0 -16
- package/template/dotnet-scaffold/src/__SolutionName__.Api/obj/__SolutionName__.Api.csproj.nuget.g.targets +0 -2
- package/template/dotnet-scaffold/src/__SolutionName__.Api/obj/project.assets.json +0 -559
- package/template/dotnet-scaffold/src/__SolutionName__.Api/obj/project.nuget.cache +0 -8
- package/template/dotnet-scaffold/src/__SolutionName__.Application/obj/Debug/net10.0/.NETCoreApp,Version=v10.0.AssemblyAttributes.cs +0 -4
- package/template/dotnet-scaffold/src/__SolutionName__.Application/obj/Debug/net10.0/__SolutionName__.Application.AssemblyInfo.cs +0 -22
- package/template/dotnet-scaffold/src/__SolutionName__.Application/obj/Debug/net10.0/__SolutionName__.Application.AssemblyInfoInputs.cache +0 -1
- package/template/dotnet-scaffold/src/__SolutionName__.Application/obj/Debug/net10.0/__SolutionName__.Application.GeneratedMSBuildEditorConfig.editorconfig +0 -17
- package/template/dotnet-scaffold/src/__SolutionName__.Application/obj/Debug/net10.0/__SolutionName__.Application.GlobalUsings.g.cs +0 -8
- package/template/dotnet-scaffold/src/__SolutionName__.Application/obj/Debug/net10.0/__SolutionName__.Application.assets.cache +0 -0
- package/template/dotnet-scaffold/src/__SolutionName__.Application/obj/__SolutionName__.Application.csproj.nuget.dgspec.json +0 -690
- package/template/dotnet-scaffold/src/__SolutionName__.Application/obj/__SolutionName__.Application.csproj.nuget.g.props +0 -16
- package/template/dotnet-scaffold/src/__SolutionName__.Application/obj/__SolutionName__.Application.csproj.nuget.g.targets +0 -2
- package/template/dotnet-scaffold/src/__SolutionName__.Application/obj/project.assets.json +0 -376
- package/template/dotnet-scaffold/src/__SolutionName__.Application/obj/project.nuget.cache +0 -8
- package/template/dotnet-scaffold/src/__SolutionName__.Domain/obj/Debug/net10.0/.NETCoreApp,Version=v10.0.AssemblyAttributes.cs +0 -4
- package/template/dotnet-scaffold/src/__SolutionName__.Domain/obj/Debug/net10.0/__SolutionName__.Domain.AssemblyInfo.cs +0 -22
- package/template/dotnet-scaffold/src/__SolutionName__.Domain/obj/Debug/net10.0/__SolutionName__.Domain.AssemblyInfoInputs.cache +0 -1
- package/template/dotnet-scaffold/src/__SolutionName__.Domain/obj/Debug/net10.0/__SolutionName__.Domain.GeneratedMSBuildEditorConfig.editorconfig +0 -17
- package/template/dotnet-scaffold/src/__SolutionName__.Domain/obj/Debug/net10.0/__SolutionName__.Domain.GlobalUsings.g.cs +0 -8
- package/template/dotnet-scaffold/src/__SolutionName__.Domain/obj/Debug/net10.0/__SolutionName__.Domain.assets.cache +0 -0
- package/template/dotnet-scaffold/src/__SolutionName__.Domain/obj/__SolutionName__.Domain.csproj.nuget.dgspec.json +0 -347
- package/template/dotnet-scaffold/src/__SolutionName__.Domain/obj/__SolutionName__.Domain.csproj.nuget.g.props +0 -16
- package/template/dotnet-scaffold/src/__SolutionName__.Domain/obj/__SolutionName__.Domain.csproj.nuget.g.targets +0 -2
- package/template/dotnet-scaffold/src/__SolutionName__.Domain/obj/project.assets.json +0 -353
- package/template/dotnet-scaffold/src/__SolutionName__.Domain/obj/project.nuget.cache +0 -8
- package/template/dotnet-scaffold/src/__SolutionName__.Infrastructure/obj/Debug/net10.0/.NETCoreApp,Version=v10.0.AssemblyAttributes.cs +0 -4
- package/template/dotnet-scaffold/src/__SolutionName__.Infrastructure/obj/Debug/net10.0/__SolutionName__.Infrastructure.AssemblyInfo.cs +0 -22
- package/template/dotnet-scaffold/src/__SolutionName__.Infrastructure/obj/Debug/net10.0/__SolutionName__.Infrastructure.AssemblyInfoInputs.cache +0 -1
- package/template/dotnet-scaffold/src/__SolutionName__.Infrastructure/obj/Debug/net10.0/__SolutionName__.Infrastructure.GeneratedMSBuildEditorConfig.editorconfig +0 -17
- package/template/dotnet-scaffold/src/__SolutionName__.Infrastructure/obj/Debug/net10.0/__SolutionName__.Infrastructure.GlobalUsings.g.cs +0 -8
- package/template/dotnet-scaffold/src/__SolutionName__.Infrastructure/obj/Debug/net10.0/__SolutionName__.Infrastructure.assets.cache +0 -0
- package/template/dotnet-scaffold/src/__SolutionName__.Infrastructure/obj/Debug/net10.0/__SolutionName__.Infrastructure.csproj.AssemblyReference.cache +0 -0
- package/template/dotnet-scaffold/src/__SolutionName__.Infrastructure/obj/__SolutionName__.Infrastructure.csproj.nuget.dgspec.json +0 -1047
- package/template/dotnet-scaffold/src/__SolutionName__.Infrastructure/obj/__SolutionName__.Infrastructure.csproj.nuget.g.props +0 -16
- package/template/dotnet-scaffold/src/__SolutionName__.Infrastructure/obj/__SolutionName__.Infrastructure.csproj.nuget.g.targets +0 -7
- package/template/dotnet-scaffold/src/__SolutionName__.Infrastructure/obj/project.assets.json +0 -858
- package/template/dotnet-scaffold/src/__SolutionName__.Infrastructure/obj/project.nuget.cache +0 -17
- package/template/dotnet-scaffold/tests/__SolutionName__.Api.Tests/bin/Debug/net10.0/.msCoverageSourceRootsMapping___SolutionName__.Api.Tests +0 -0
- package/template/dotnet-scaffold/tests/__SolutionName__.Api.Tests/obj/Debug/net10.0/.NETCoreApp,Version=v10.0.AssemblyAttributes.cs +0 -4
- package/template/dotnet-scaffold/tests/__SolutionName__.Api.Tests/obj/Debug/net10.0/__SolutionName__.Api.Tests.AssemblyInfo.cs +0 -22
- package/template/dotnet-scaffold/tests/__SolutionName__.Api.Tests/obj/Debug/net10.0/__SolutionName__.Api.Tests.AssemblyInfoInputs.cache +0 -1
- package/template/dotnet-scaffold/tests/__SolutionName__.Api.Tests/obj/Debug/net10.0/__SolutionName__.Api.Tests.GeneratedMSBuildEditorConfig.editorconfig +0 -17
- package/template/dotnet-scaffold/tests/__SolutionName__.Api.Tests/obj/Debug/net10.0/__SolutionName__.Api.Tests.GlobalUsings.g.cs +0 -9
- package/template/dotnet-scaffold/tests/__SolutionName__.Api.Tests/obj/Debug/net10.0/__SolutionName__.Api.Tests.assets.cache +0 -0
- package/template/dotnet-scaffold/tests/__SolutionName__.Api.Tests/obj/Debug/net10.0/__SolutionName__.Api.Tests.csproj.AssemblyReference.cache +0 -0
- package/template/dotnet-scaffold/tests/__SolutionName__.Api.Tests/obj/__SolutionName__.Api.Tests.csproj.nuget.dgspec.json +0 -1908
- package/template/dotnet-scaffold/tests/__SolutionName__.Api.Tests/obj/__SolutionName__.Api.Tests.csproj.nuget.g.props +0 -27
- package/template/dotnet-scaffold/tests/__SolutionName__.Api.Tests/obj/__SolutionName__.Api.Tests.csproj.nuget.g.targets +0 -16
- package/template/dotnet-scaffold/tests/__SolutionName__.Api.Tests/obj/project.assets.json +0 -3283
- package/template/dotnet-scaffold/tests/__SolutionName__.Api.Tests/obj/project.nuget.cache +0 -60
- package/template/dotnet-scaffold/tests/__SolutionName__.Application.Tests/bin/Debug/net10.0/.msCoverageSourceRootsMapping___SolutionName__.Application.Tests +0 -0
- package/template/dotnet-scaffold/tests/__SolutionName__.Application.Tests/obj/Debug/net10.0/.NETCoreApp,Version=v10.0.AssemblyAttributes.cs +0 -4
- package/template/dotnet-scaffold/tests/__SolutionName__.Application.Tests/obj/Debug/net10.0/__SolutionName__.Application.Tests.AssemblyInfo.cs +0 -22
- package/template/dotnet-scaffold/tests/__SolutionName__.Application.Tests/obj/Debug/net10.0/__SolutionName__.Application.Tests.AssemblyInfoInputs.cache +0 -1
- package/template/dotnet-scaffold/tests/__SolutionName__.Application.Tests/obj/Debug/net10.0/__SolutionName__.Application.Tests.GeneratedMSBuildEditorConfig.editorconfig +0 -17
- package/template/dotnet-scaffold/tests/__SolutionName__.Application.Tests/obj/Debug/net10.0/__SolutionName__.Application.Tests.GlobalUsings.g.cs +0 -9
- package/template/dotnet-scaffold/tests/__SolutionName__.Application.Tests/obj/Debug/net10.0/__SolutionName__.Application.Tests.assets.cache +0 -0
- package/template/dotnet-scaffold/tests/__SolutionName__.Application.Tests/obj/Debug/net10.0/__SolutionName__.Application.Tests.csproj.AssemblyReference.cache +0 -0
- package/template/dotnet-scaffold/tests/__SolutionName__.Application.Tests/obj/__SolutionName__.Application.Tests.csproj.nuget.dgspec.json +0 -1055
- package/template/dotnet-scaffold/tests/__SolutionName__.Application.Tests/obj/__SolutionName__.Application.Tests.csproj.nuget.g.props +0 -26
- package/template/dotnet-scaffold/tests/__SolutionName__.Application.Tests/obj/__SolutionName__.Application.Tests.csproj.nuget.g.targets +0 -11
- package/template/dotnet-scaffold/tests/__SolutionName__.Application.Tests/obj/project.assets.json +0 -1662
- package/template/dotnet-scaffold/tests/__SolutionName__.Application.Tests/obj/project.nuget.cache +0 -30
- package/template/dotnet-scaffold/tests/__SolutionName__.Domain.Tests/bin/Debug/net10.0/.msCoverageSourceRootsMapping___SolutionName__.Domain.Tests +0 -0
- package/template/dotnet-scaffold/tests/__SolutionName__.Domain.Tests/obj/Debug/net10.0/.NETCoreApp,Version=v10.0.AssemblyAttributes.cs +0 -4
- package/template/dotnet-scaffold/tests/__SolutionName__.Domain.Tests/obj/Debug/net10.0/__SolutionName__.Domain.Tests.AssemblyInfo.cs +0 -22
- package/template/dotnet-scaffold/tests/__SolutionName__.Domain.Tests/obj/Debug/net10.0/__SolutionName__.Domain.Tests.AssemblyInfoInputs.cache +0 -1
- package/template/dotnet-scaffold/tests/__SolutionName__.Domain.Tests/obj/Debug/net10.0/__SolutionName__.Domain.Tests.GeneratedMSBuildEditorConfig.editorconfig +0 -17
- package/template/dotnet-scaffold/tests/__SolutionName__.Domain.Tests/obj/Debug/net10.0/__SolutionName__.Domain.Tests.GlobalUsings.g.cs +0 -9
- package/template/dotnet-scaffold/tests/__SolutionName__.Domain.Tests/obj/Debug/net10.0/__SolutionName__.Domain.Tests.assets.cache +0 -0
- package/template/dotnet-scaffold/tests/__SolutionName__.Domain.Tests/obj/Debug/net10.0/__SolutionName__.Domain.Tests.csproj.AssemblyReference.cache +0 -0
- package/template/dotnet-scaffold/tests/__SolutionName__.Domain.Tests/obj/__SolutionName__.Domain.Tests.csproj.nuget.dgspec.json +0 -712
- package/template/dotnet-scaffold/tests/__SolutionName__.Domain.Tests/obj/__SolutionName__.Domain.Tests.csproj.nuget.g.props +0 -26
- package/template/dotnet-scaffold/tests/__SolutionName__.Domain.Tests/obj/__SolutionName__.Domain.Tests.csproj.nuget.g.targets +0 -11
- package/template/dotnet-scaffold/tests/__SolutionName__.Domain.Tests/obj/project.assets.json +0 -1644
- package/template/dotnet-scaffold/tests/__SolutionName__.Domain.Tests/obj/project.nuget.cache +0 -30
- package/template/dotnet-scaffold/tests/__SolutionName__.Infrastructure.Tests/bin/Debug/net10.0/.msCoverageSourceRootsMapping___SolutionName__.Infrastructure.Tests +0 -0
- package/template/dotnet-scaffold/tests/__SolutionName__.Infrastructure.Tests/obj/Debug/net10.0/.NETCoreApp,Version=v10.0.AssemblyAttributes.cs +0 -4
- package/template/dotnet-scaffold/tests/__SolutionName__.Infrastructure.Tests/obj/Debug/net10.0/__SolutionName__.Infrastructure.Tests.AssemblyInfo.cs +0 -22
- package/template/dotnet-scaffold/tests/__SolutionName__.Infrastructure.Tests/obj/Debug/net10.0/__SolutionName__.Infrastructure.Tests.AssemblyInfoInputs.cache +0 -1
- package/template/dotnet-scaffold/tests/__SolutionName__.Infrastructure.Tests/obj/Debug/net10.0/__SolutionName__.Infrastructure.Tests.GeneratedMSBuildEditorConfig.editorconfig +0 -17
- package/template/dotnet-scaffold/tests/__SolutionName__.Infrastructure.Tests/obj/Debug/net10.0/__SolutionName__.Infrastructure.Tests.GlobalUsings.g.cs +0 -9
- package/template/dotnet-scaffold/tests/__SolutionName__.Infrastructure.Tests/obj/Debug/net10.0/__SolutionName__.Infrastructure.Tests.assets.cache +0 -0
- package/template/dotnet-scaffold/tests/__SolutionName__.Infrastructure.Tests/obj/Debug/net10.0/__SolutionName__.Infrastructure.Tests.csproj.AssemblyReference.cache +0 -0
- package/template/dotnet-scaffold/tests/__SolutionName__.Infrastructure.Tests/obj/__SolutionName__.Infrastructure.Tests.csproj.nuget.dgspec.json +0 -1412
- package/template/dotnet-scaffold/tests/__SolutionName__.Infrastructure.Tests/obj/__SolutionName__.Infrastructure.Tests.csproj.nuget.g.props +0 -26
- package/template/dotnet-scaffold/tests/__SolutionName__.Infrastructure.Tests/obj/__SolutionName__.Infrastructure.Tests.csproj.nuget.g.targets +0 -13
- package/template/dotnet-scaffold/tests/__SolutionName__.Infrastructure.Tests/obj/project.assets.json +0 -2130
- package/template/dotnet-scaffold/tests/__SolutionName__.Infrastructure.Tests/obj/project.nuget.cache +0 -38
|
@@ -1,93 +1,93 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const fs = require('fs');
|
|
4
|
-
const path = require('path');
|
|
5
|
-
const { spawnSync } = require('child_process');
|
|
6
|
-
|
|
7
|
-
// Map of secret prereq name → environment variable that satisfies it.
|
|
8
|
-
const SECRET_ENV = { 'anthropic-api-key': 'ANTHROPIC_API_KEY' };
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Default command runner. Captures stdout+stderr (java prints version to stderr)
|
|
12
|
-
* and never throws. Tests inject their own `ctx.run` to avoid touching the host.
|
|
13
|
-
* @param {string} cmd
|
|
14
|
-
* @returns {{code:number, stdout:string, stderr:string}}
|
|
15
|
-
*/
|
|
16
|
-
function defaultRun(cmd) {
|
|
17
|
-
const r = spawnSync(cmd, { shell: true, encoding: 'utf8', windowsHide: true });
|
|
18
|
-
return {
|
|
19
|
-
code: typeof r.status === 'number' ? r.status : 1,
|
|
20
|
-
stdout: r.stdout || '',
|
|
21
|
-
stderr: r.stderr || '',
|
|
22
|
-
};
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/** Extract a numeric version tuple from arbitrary command output. */
|
|
26
|
-
function parseVersion(text) {
|
|
27
|
-
const m = String(text).match(/(\d+)(?:\.(\d+))?(?:\.(\d+))?/);
|
|
28
|
-
if (!m) return null;
|
|
29
|
-
return [Number(m[1] || 0), Number(m[2] || 0), Number(m[3] || 0)];
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
/** found >= min ? (min may be "18" or "3.8") */
|
|
33
|
-
function versionGte(found, min) {
|
|
34
|
-
if (!found) return false;
|
|
35
|
-
const want = parseVersion(min) || [0, 0, 0];
|
|
36
|
-
for (let i = 0; i < 3; i++) {
|
|
37
|
-
if (found[i] > want[i]) return true;
|
|
38
|
-
if (found[i] < want[i]) return false;
|
|
39
|
-
}
|
|
40
|
-
return true;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* Detect whether a single prereq is satisfied. Detection NEVER mutates the host.
|
|
45
|
-
* @param {string} name prereq id
|
|
46
|
-
* @param {object} prereq manifest prereq entry
|
|
47
|
-
* @param {object} ctx { run, platform, projectDir }
|
|
48
|
-
* @returns {{name:string, present:boolean, version:?string, reason:string}}
|
|
49
|
-
*/
|
|
50
|
-
function detectPrereq(name, prereq, ctx) {
|
|
51
|
-
const run = ctx.run || defaultRun;
|
|
52
|
-
|
|
53
|
-
switch (prereq.kind) {
|
|
54
|
-
case 'secret': {
|
|
55
|
-
const envVar = SECRET_ENV[name];
|
|
56
|
-
const present = !!(envVar && process.env[envVar]);
|
|
57
|
-
return { name, present, version: null, reason: present ? `${envVar} set` : `${envVar} unset` };
|
|
58
|
-
}
|
|
59
|
-
case 'venv': {
|
|
60
|
-
const dir = path.join(ctx.projectDir, '.claude', 'skills', '.venv');
|
|
61
|
-
const present = fs.existsSync(dir);
|
|
62
|
-
return { name, present, version: null, reason: present ? 'venv exists' : 'venv missing' };
|
|
63
|
-
}
|
|
64
|
-
case 'mcp': {
|
|
65
|
-
const r = run('claude mcp list');
|
|
66
|
-
const present = r.code === 0 && /playwright/i.test(r.stdout + r.stderr);
|
|
67
|
-
return { name, present, version: null, reason: present ? 'mcp registered' : 'mcp not registered' };
|
|
68
|
-
}
|
|
69
|
-
case 'note':
|
|
70
|
-
// Nothing to probe directly; satisfied iff its `needs` are (handled by caller).
|
|
71
|
-
return { name, present: false, version: null, reason: 'note (self-bootstrap)' };
|
|
72
|
-
default: {
|
|
73
|
-
// package
|
|
74
|
-
if (!prereq.detect) return { name, present: false, version: null, reason: 'no detect command' };
|
|
75
|
-
let r = run(prereq.detect);
|
|
76
|
-
if (r.code !== 0 && name === 'python') r = run('python3 --version'); // unix fallback
|
|
77
|
-
const out = r.stdout + r.stderr;
|
|
78
|
-
const ver = parseVersion(out);
|
|
79
|
-
if (r.code !== 0) return { name, present: false, version: null, reason: 'not found' };
|
|
80
|
-
if (prereq.minVersion && !versionGte(ver, prereq.minVersion)) {
|
|
81
|
-
return {
|
|
82
|
-
name,
|
|
83
|
-
present: false,
|
|
84
|
-
version: ver ? ver.join('.') : null,
|
|
85
|
-
reason: `version ${ver ? ver.join('.') : '?'} < required ${prereq.minVersion}`,
|
|
86
|
-
};
|
|
87
|
-
}
|
|
88
|
-
return { name, present: true, version: ver ? ver.join('.') : null, reason: 'present' };
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
module.exports = { detectPrereq, parseVersion, versionGte, defaultRun, SECRET_ENV };
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const { spawnSync } = require('child_process');
|
|
6
|
+
|
|
7
|
+
// Map of secret prereq name → environment variable that satisfies it.
|
|
8
|
+
const SECRET_ENV = { 'anthropic-api-key': 'ANTHROPIC_API_KEY' };
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Default command runner. Captures stdout+stderr (java prints version to stderr)
|
|
12
|
+
* and never throws. Tests inject their own `ctx.run` to avoid touching the host.
|
|
13
|
+
* @param {string} cmd
|
|
14
|
+
* @returns {{code:number, stdout:string, stderr:string}}
|
|
15
|
+
*/
|
|
16
|
+
function defaultRun(cmd) {
|
|
17
|
+
const r = spawnSync(cmd, { shell: true, encoding: 'utf8', windowsHide: true });
|
|
18
|
+
return {
|
|
19
|
+
code: typeof r.status === 'number' ? r.status : 1,
|
|
20
|
+
stdout: r.stdout || '',
|
|
21
|
+
stderr: r.stderr || '',
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Extract a numeric version tuple from arbitrary command output. */
|
|
26
|
+
function parseVersion(text) {
|
|
27
|
+
const m = String(text).match(/(\d+)(?:\.(\d+))?(?:\.(\d+))?/);
|
|
28
|
+
if (!m) return null;
|
|
29
|
+
return [Number(m[1] || 0), Number(m[2] || 0), Number(m[3] || 0)];
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** found >= min ? (min may be "18" or "3.8") */
|
|
33
|
+
function versionGte(found, min) {
|
|
34
|
+
if (!found) return false;
|
|
35
|
+
const want = parseVersion(min) || [0, 0, 0];
|
|
36
|
+
for (let i = 0; i < 3; i++) {
|
|
37
|
+
if (found[i] > want[i]) return true;
|
|
38
|
+
if (found[i] < want[i]) return false;
|
|
39
|
+
}
|
|
40
|
+
return true;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Detect whether a single prereq is satisfied. Detection NEVER mutates the host.
|
|
45
|
+
* @param {string} name prereq id
|
|
46
|
+
* @param {object} prereq manifest prereq entry
|
|
47
|
+
* @param {object} ctx { run, platform, projectDir }
|
|
48
|
+
* @returns {{name:string, present:boolean, version:?string, reason:string}}
|
|
49
|
+
*/
|
|
50
|
+
function detectPrereq(name, prereq, ctx) {
|
|
51
|
+
const run = ctx.run || defaultRun;
|
|
52
|
+
|
|
53
|
+
switch (prereq.kind) {
|
|
54
|
+
case 'secret': {
|
|
55
|
+
const envVar = SECRET_ENV[name];
|
|
56
|
+
const present = !!(envVar && process.env[envVar]);
|
|
57
|
+
return { name, present, version: null, reason: present ? `${envVar} set` : `${envVar} unset` };
|
|
58
|
+
}
|
|
59
|
+
case 'venv': {
|
|
60
|
+
const dir = path.join(ctx.projectDir, '.claude', 'skills', '.venv');
|
|
61
|
+
const present = fs.existsSync(dir);
|
|
62
|
+
return { name, present, version: null, reason: present ? 'venv exists' : 'venv missing' };
|
|
63
|
+
}
|
|
64
|
+
case 'mcp': {
|
|
65
|
+
const r = run('claude mcp list');
|
|
66
|
+
const present = r.code === 0 && /playwright/i.test(r.stdout + r.stderr);
|
|
67
|
+
return { name, present, version: null, reason: present ? 'mcp registered' : 'mcp not registered' };
|
|
68
|
+
}
|
|
69
|
+
case 'note':
|
|
70
|
+
// Nothing to probe directly; satisfied iff its `needs` are (handled by caller).
|
|
71
|
+
return { name, present: false, version: null, reason: 'note (self-bootstrap)' };
|
|
72
|
+
default: {
|
|
73
|
+
// package
|
|
74
|
+
if (!prereq.detect) return { name, present: false, version: null, reason: 'no detect command' };
|
|
75
|
+
let r = run(prereq.detect);
|
|
76
|
+
if (r.code !== 0 && name === 'python') r = run('python3 --version'); // unix fallback
|
|
77
|
+
const out = r.stdout + r.stderr;
|
|
78
|
+
const ver = parseVersion(out);
|
|
79
|
+
if (r.code !== 0) return { name, present: false, version: null, reason: 'not found' };
|
|
80
|
+
if (prereq.minVersion && !versionGte(ver, prereq.minVersion)) {
|
|
81
|
+
return {
|
|
82
|
+
name,
|
|
83
|
+
present: false,
|
|
84
|
+
version: ver ? ver.join('.') : null,
|
|
85
|
+
reason: `version ${ver ? ver.join('.') : '?'} < required ${prereq.minVersion}`,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
return { name, present: true, version: ver ? ver.join('.') : null, reason: 'present' };
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
module.exports = { detectPrereq, parseVersion, versionGte, defaultRun, SECRET_ENV };
|
package/src/self-update.js
CHANGED
|
@@ -1,73 +1,77 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
// Self-update gate for `musketeer muster`. Before the picker applies the bundled
|
|
4
|
-
// template, make sure the CLI is at the latest PUBLISHED version — otherwise
|
|
5
|
-
// "muster" would refresh installed skills to a stale package's template.
|
|
6
|
-
//
|
|
7
|
-
// When behind: promote (npm i -g @latest), then re-exec `musketeer muster` so the
|
|
8
|
-
// picker runs under the freshly-installed template. A running Node process can't
|
|
9
|
-
// reliably hot-swap its own package files mid-run, so re-exec is the safe path.
|
|
10
|
-
//
|
|
11
|
-
// Guards:
|
|
12
|
-
// MUSKETEER_REEXEC=1 set on the child so it never re-checks (no loop)
|
|
13
|
-
// MUSKETEER_NO_SELF_UPDATE=1 user opt-out (offline / pinned version)
|
|
14
|
-
|
|
15
|
-
const { spawnSync } = require('child_process');
|
|
16
|
-
const { fetchLatest, compareVersions, defaultRun } = require('./promote');
|
|
17
|
-
const pkg = require('../package.json');
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Decide whether muster should self-update first. Pure — injectable exec/env.
|
|
21
|
-
* @param {{exec?:Function, current?:string, name?:string, env?:object}} [o]
|
|
22
|
-
* @returns {{action:'proceed'|'update', latest:?string}}
|
|
23
|
-
*/
|
|
24
|
-
function decide(o = {}) {
|
|
25
|
-
const exec = o.exec || defaultRun;
|
|
26
|
-
const current = o.current || pkg.version;
|
|
27
|
-
const name = o.name || pkg.name;
|
|
28
|
-
const env = o.env || process.env;
|
|
29
|
-
|
|
30
|
-
if (env.MUSKETEER_REEXEC === '1' || env.MUSKETEER_NO_SELF_UPDATE === '1') {
|
|
31
|
-
return { action: 'proceed', latest: null };
|
|
32
|
-
}
|
|
33
|
-
const latest = fetchLatest(exec, name);
|
|
34
|
-
if (!latest) return { action: 'proceed', latest: null }; // offline / unknown — proceed as-is
|
|
35
|
-
if (compareVersions(latest, current) <= 0) return { action: 'proceed', latest }; // already latest
|
|
36
|
-
return { action: 'update', latest };
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* Ensure the CLI is latest before muster applies the template.
|
|
41
|
-
* @param {object} [io] injectable { log, exec, spawn, env, name, current } for tests
|
|
42
|
-
* @returns {?number} an exit code if it re-exec'd a fresh muster (caller should
|
|
43
|
-
* exit with it); null to mean "proceed in-process".
|
|
44
|
-
*/
|
|
45
|
-
function ensureLatest(io = {}) {
|
|
46
|
-
const log = io.log || ((m) => process.stdout.write(m + '\n'));
|
|
47
|
-
const exec = io.exec || defaultRun;
|
|
48
|
-
const spawn = io.spawn || spawnSync;
|
|
49
|
-
const env = io.env || process.env;
|
|
50
|
-
const name = io.name || pkg.name;
|
|
51
|
-
const current = io.current || pkg.version;
|
|
52
|
-
|
|
53
|
-
const d = decide({ exec, current, name, env });
|
|
54
|
-
if (d.action === 'proceed') return null;
|
|
55
|
-
|
|
56
|
-
log(`Updating musketeer ${current} → ${d.latest} before muster…`);
|
|
57
|
-
const ins = exec(`npm i -g ${name}@latest`);
|
|
58
|
-
if (ins.code !== 0) {
|
|
59
|
-
log('Self-update failed — continuing with the current version.');
|
|
60
|
-
if (ins.stderr) log(ins.stderr.trim());
|
|
61
|
-
return null;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
// Re-exec the now-latest binary's muster, inheriting the TTY for the picker.
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// Self-update gate for `musketeer muster`. Before the picker applies the bundled
|
|
4
|
+
// template, make sure the CLI is at the latest PUBLISHED version — otherwise
|
|
5
|
+
// "muster" would refresh installed skills to a stale package's template.
|
|
6
|
+
//
|
|
7
|
+
// When behind: promote (npm i -g @latest), then re-exec `musketeer muster` so the
|
|
8
|
+
// picker runs under the freshly-installed template. A running Node process can't
|
|
9
|
+
// reliably hot-swap its own package files mid-run, so re-exec is the safe path.
|
|
10
|
+
//
|
|
11
|
+
// Guards:
|
|
12
|
+
// MUSKETEER_REEXEC=1 set on the child so it never re-checks (no loop)
|
|
13
|
+
// MUSKETEER_NO_SELF_UPDATE=1 user opt-out (offline / pinned version)
|
|
14
|
+
|
|
15
|
+
const { spawnSync } = require('child_process');
|
|
16
|
+
const { fetchLatest, compareVersions, defaultRun } = require('./promote');
|
|
17
|
+
const pkg = require('../package.json');
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Decide whether muster should self-update first. Pure — injectable exec/env.
|
|
21
|
+
* @param {{exec?:Function, current?:string, name?:string, env?:object}} [o]
|
|
22
|
+
* @returns {{action:'proceed'|'update', latest:?string}}
|
|
23
|
+
*/
|
|
24
|
+
function decide(o = {}) {
|
|
25
|
+
const exec = o.exec || defaultRun;
|
|
26
|
+
const current = o.current || pkg.version;
|
|
27
|
+
const name = o.name || pkg.name;
|
|
28
|
+
const env = o.env || process.env;
|
|
29
|
+
|
|
30
|
+
if (env.MUSKETEER_REEXEC === '1' || env.MUSKETEER_NO_SELF_UPDATE === '1') {
|
|
31
|
+
return { action: 'proceed', latest: null };
|
|
32
|
+
}
|
|
33
|
+
const latest = fetchLatest(exec, name);
|
|
34
|
+
if (!latest) return { action: 'proceed', latest: null }; // offline / unknown — proceed as-is
|
|
35
|
+
if (compareVersions(latest, current) <= 0) return { action: 'proceed', latest }; // already latest
|
|
36
|
+
return { action: 'update', latest };
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Ensure the CLI is latest before muster applies the template.
|
|
41
|
+
* @param {object} [io] injectable { log, exec, spawn, env, name, current } for tests
|
|
42
|
+
* @returns {?number} an exit code if it re-exec'd a fresh muster (caller should
|
|
43
|
+
* exit with it); null to mean "proceed in-process".
|
|
44
|
+
*/
|
|
45
|
+
function ensureLatest(io = {}) {
|
|
46
|
+
const log = io.log || ((m) => process.stdout.write(m + '\n'));
|
|
47
|
+
const exec = io.exec || defaultRun;
|
|
48
|
+
const spawn = io.spawn || spawnSync;
|
|
49
|
+
const env = io.env || process.env;
|
|
50
|
+
const name = io.name || pkg.name;
|
|
51
|
+
const current = io.current || pkg.version;
|
|
52
|
+
|
|
53
|
+
const d = decide({ exec, current, name, env });
|
|
54
|
+
if (d.action === 'proceed') return null;
|
|
55
|
+
|
|
56
|
+
log(`Updating musketeer ${current} → ${d.latest} before muster…`);
|
|
57
|
+
const ins = exec(`npm i -g ${name}@latest`);
|
|
58
|
+
if (ins.code !== 0) {
|
|
59
|
+
log('Self-update failed — continuing with the current version.');
|
|
60
|
+
if (ins.stderr) log(ins.stderr.trim());
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Re-exec the now-latest binary's muster, inheriting the TTY for the picker.
|
|
65
|
+
// The caller's flags come along: dropping them here would send a scripted
|
|
66
|
+
// `muster --keep` back to the interactive picker, which is the one place it
|
|
67
|
+
// cannot run.
|
|
68
|
+
const args = io.args || [];
|
|
69
|
+
const r = spawn('musketeer', ['muster', ...args], {
|
|
70
|
+
stdio: 'inherit',
|
|
71
|
+
shell: true,
|
|
72
|
+
env: { ...env, MUSKETEER_REEXEC: '1' },
|
|
73
|
+
});
|
|
74
|
+
return typeof r.status === 'number' ? r.status : 0;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
module.exports = { decide, ensureLatest };
|
|
@@ -1,70 +1,85 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
// PreToolUse guard: block an `adr new` title
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
//
|
|
14
|
-
//
|
|
15
|
-
//
|
|
16
|
-
//
|
|
17
|
-
//
|
|
18
|
-
//
|
|
19
|
-
//
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// PreToolUse guard: block an `adr new` title the tool cannot render safely.
|
|
3
|
+
//
|
|
4
|
+
// The tool builds the ADR by running plain JS string replacements over the
|
|
5
|
+
// template, in a fixed order: DATE, TITLE, NUMBER, STATUS. `String.replace`
|
|
6
|
+
// with a string pattern rewrites the FIRST occurrence, and by the time STATUS
|
|
7
|
+
// is substituted the title is already sitting in the document — on line 1,
|
|
8
|
+
// ahead of the real `STATUS` placeholder in the `## Status` section.
|
|
9
|
+
//
|
|
10
|
+
// So an uppercase STATUS inside the title captures the substitution meant for
|
|
11
|
+
// the status line. Verified against @meza/adr-tools 2.0.4:
|
|
12
|
+
// adr new -q -- "Use STATUS codes for errors"
|
|
13
|
+
// writes `# 1: Use Accepted codes for errors` and leaves the real status as the
|
|
14
|
+
// literal word STATUS — exit 0, no warning, wrong data in the index.
|
|
15
|
+
//
|
|
16
|
+
// Only STATUS is blocked, and only in uppercase:
|
|
17
|
+
// - the replacement is case-sensitive, so "Use status codes" is fine;
|
|
18
|
+
// - DATE is substituted before the title is inserted, so it cannot be caught;
|
|
19
|
+
// - NUMBER and TITLE are substituted at points that precede the title text in
|
|
20
|
+
// the template shipped with this skill, so the real placeholder always wins.
|
|
21
|
+
// That last one holds because of where the tokens sit in
|
|
22
|
+
// `references/adr-template.md` — revisit this guard if that file is ever
|
|
23
|
+
// reordered so `## Status` precedes the heading.
|
|
24
|
+
//
|
|
25
|
+
// A title starting with `-` is the second case: the tool parses its own flags
|
|
26
|
+
// with commander, so without a `--` separator the title is read as an unknown
|
|
27
|
+
// option. That one fails cleanly (exit 1, no file created), but it is still
|
|
28
|
+
// worth catching early with a clearer reason than "unknown option".
|
|
29
|
+
//
|
|
30
|
+
// Blocking, not warning: the STATUS case corrupts data with no error at all, so
|
|
31
|
+
// there is nothing later in the pipeline that will catch it.
|
|
32
|
+
//
|
|
33
|
+
// The `|` and `&` characters used to be blocked here too. Those were hazards of
|
|
34
|
+
// the old bash implementation, which substituted the title into
|
|
35
|
+
// `sed -e "s|TITLE|$title|"`. Substitution is JS now, and both characters were
|
|
36
|
+
// re-verified as harmless, so blocking them would only refuse valid titles.
|
|
37
|
+
|
|
38
|
+
const { invokesAdr } = require("./lib/adr/command-scan.cjs");
|
|
39
|
+
|
|
40
|
+
let raw = "";
|
|
41
|
+
process.stdin.on("data", (chunk) => (raw += chunk));
|
|
42
|
+
process.stdin.on("end", () => {
|
|
43
|
+
let input;
|
|
44
|
+
try {
|
|
45
|
+
input = JSON.parse(raw || "{}");
|
|
46
|
+
} catch {
|
|
47
|
+
process.exit(0); // unparseable payload — fail open, don't block legit work
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const command = (input && input.tool_input && input.tool_input.command) || "";
|
|
51
|
+
if (!invokesAdr(command, "new")) process.exit(0);
|
|
52
|
+
|
|
53
|
+
// The skill always places `--` immediately before a quoted title. Extract
|
|
54
|
+
// that argument; anything else about the command's shape is not this hook's
|
|
55
|
+
// concern.
|
|
56
|
+
const afterDashDash = command.match(/--\s+(["'])((?:(?!\1).)*)\1/);
|
|
57
|
+
const title = afterDashDash ? afterDashDash[2] : null;
|
|
58
|
+
|
|
59
|
+
let reason = null;
|
|
60
|
+
if (!afterDashDash) {
|
|
61
|
+
reason =
|
|
62
|
+
"no `--` before the title (or no quoted title found after it). Without `--`, " +
|
|
63
|
+
"a title starting with `-` is parsed as an unknown option and no ADR is " +
|
|
64
|
+
'created. Always: adr new -q [-s STEM]... -- "Title".';
|
|
65
|
+
} else if (title.includes("STATUS")) {
|
|
66
|
+
reason =
|
|
67
|
+
"the title contains `STATUS` in uppercase. The tool substitutes STATUS into the " +
|
|
68
|
+
'template after the title is already in the document, so "Use STATUS codes" ' +
|
|
69
|
+
'becomes "Use Accepted codes" and the real Status section is left as the literal ' +
|
|
70
|
+
"word STATUS — it exits 0, so nothing else will catch this. Lowercase `status` is fine.";
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (reason) {
|
|
74
|
+
process.stdout.write(
|
|
75
|
+
JSON.stringify({
|
|
76
|
+
hookSpecificOutput: {
|
|
77
|
+
hookEventName: "PreToolUse",
|
|
78
|
+
permissionDecision: "deny",
|
|
79
|
+
permissionDecisionReason: `Refusing this \`adr new\` call: ${reason} Rename the title and retry.`,
|
|
80
|
+
},
|
|
81
|
+
})
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
process.exit(0);
|
|
85
|
+
});
|