@houseofwolvesllc/claude-scrum-skill 2.1.1 → 2.1.2
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/.claude-plugin/plugin.json +1 -1
- package/bin/install.js +166 -63
- package/package.json +3 -2
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-scrum-skill",
|
|
3
3
|
"description": "Complete scrum pipeline — PRD to production release with Claude as scrum master. Includes project scaffolding, sprint planning, status tracking, sprint releases, and full-project emulation testing.",
|
|
4
|
-
"version": "2.1.
|
|
4
|
+
"version": "2.1.2",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "House of Wolves LLC"
|
|
7
7
|
},
|
package/bin/install.js
CHANGED
|
@@ -7,24 +7,7 @@ const HOME = process.env.HOME || process.env.USERPROFILE;
|
|
|
7
7
|
const SOURCE_DIR = path.join(__dirname, '..', 'skills');
|
|
8
8
|
const WORKFLOWS_SOURCE_DIR = path.join(__dirname, '..', 'lib', 'workflows');
|
|
9
9
|
const IS_GLOBAL = process.env.npm_config_global === 'true';
|
|
10
|
-
|
|
11
|
-
// Global install → ~/.claude/skills/
|
|
12
|
-
// Local install → <project>/.claude/skills/
|
|
13
|
-
let skillsDir;
|
|
14
|
-
if (IS_GLOBAL) {
|
|
15
|
-
skillsDir = path.join(HOME, '.claude', 'skills');
|
|
16
|
-
} else {
|
|
17
|
-
// Walk up from node_modules to find the project root
|
|
18
|
-
let projectRoot = path.resolve(__dirname, '..');
|
|
19
|
-
while (projectRoot !== path.dirname(projectRoot)) {
|
|
20
|
-
if (path.basename(projectRoot) === 'node_modules') {
|
|
21
|
-
projectRoot = path.dirname(projectRoot);
|
|
22
|
-
break;
|
|
23
|
-
}
|
|
24
|
-
projectRoot = path.dirname(projectRoot);
|
|
25
|
-
}
|
|
26
|
-
skillsDir = path.join(projectRoot, '.claude', 'skills');
|
|
27
|
-
}
|
|
10
|
+
const CONFIG_FILENAME = 'config.json';
|
|
28
11
|
|
|
29
12
|
const skills = [
|
|
30
13
|
'project-scaffold',
|
|
@@ -37,76 +20,196 @@ const skills = [
|
|
|
37
20
|
'project-cleanup'
|
|
38
21
|
];
|
|
39
22
|
|
|
40
|
-
function
|
|
41
|
-
|
|
23
|
+
function main() {
|
|
24
|
+
const skillsDir = resolveSkillsDir();
|
|
25
|
+
const location = IS_GLOBAL ? 'global (~/.claude/skills/)' : `project (${skillsDir})`;
|
|
26
|
+
console.log(`\n📋 Installing claude-scrum-skill (${location})...\n`);
|
|
42
27
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
28
|
+
fs.mkdirSync(skillsDir, { recursive: true });
|
|
29
|
+
|
|
30
|
+
installSharedReferences(skillsDir);
|
|
31
|
+
const installed = installSkills(skillsDir);
|
|
32
|
+
installWorkflows(skillsDir);
|
|
33
|
+
|
|
34
|
+
if (!IS_GLOBAL) {
|
|
35
|
+
ensureGitignoreEntry(skillsDir);
|
|
50
36
|
}
|
|
37
|
+
|
|
38
|
+
console.log(`\n✨ Installed ${installed} skills to ${skillsDir}`);
|
|
39
|
+
console.log(' Restart Claude Code for the skills to become available.\n');
|
|
40
|
+
console.log(' Run /project-scaffold <prd-path> to get started.\n');
|
|
51
41
|
}
|
|
52
42
|
|
|
53
|
-
|
|
54
|
-
|
|
43
|
+
function resolveSkillsDir() {
|
|
44
|
+
if (IS_GLOBAL) {
|
|
45
|
+
return path.join(HOME, '.claude', 'skills');
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Walk up from node_modules to find the project root.
|
|
49
|
+
let projectRoot = path.resolve(__dirname, '..');
|
|
50
|
+
while (projectRoot !== path.dirname(projectRoot)) {
|
|
51
|
+
if (path.basename(projectRoot) === 'node_modules') {
|
|
52
|
+
projectRoot = path.dirname(projectRoot);
|
|
53
|
+
break;
|
|
54
|
+
}
|
|
55
|
+
projectRoot = path.dirname(projectRoot);
|
|
56
|
+
}
|
|
57
|
+
return path.join(projectRoot, '.claude', 'skills');
|
|
58
|
+
}
|
|
55
59
|
|
|
56
|
-
|
|
60
|
+
function installSharedReferences(skillsDir) {
|
|
61
|
+
const sharedSrc = path.join(SOURCE_DIR, 'shared');
|
|
62
|
+
const sharedDest = path.join(skillsDir, 'shared');
|
|
63
|
+
if (!fs.existsSync(sharedSrc)) return;
|
|
57
64
|
|
|
58
|
-
//
|
|
59
|
-
const
|
|
60
|
-
|
|
61
|
-
if (fs.existsSync(sharedSrc)) {
|
|
62
|
-
copyRecursive(sharedSrc, sharedDest);
|
|
65
|
+
// config.json is user-owned: copy everything else, then merge it explicitly.
|
|
66
|
+
const skipConfig = path.join(sharedSrc, CONFIG_FILENAME);
|
|
67
|
+
copyRecursive(sharedSrc, sharedDest, skipConfig);
|
|
63
68
|
console.log(' 📁 shared references');
|
|
69
|
+
|
|
70
|
+
installSharedConfig(sharedSrc, sharedDest);
|
|
64
71
|
}
|
|
65
72
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
const
|
|
69
|
-
const dest = path.join(skillsDir, skill);
|
|
73
|
+
function installSharedConfig(sharedSrc, sharedDest) {
|
|
74
|
+
const defaultPath = path.join(sharedSrc, CONFIG_FILENAME);
|
|
75
|
+
const destPath = path.join(sharedDest, CONFIG_FILENAME);
|
|
70
76
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
console.log(
|
|
74
|
-
|
|
75
|
-
}
|
|
76
|
-
console.log(` ⚠️ ${skill} — source not found, skipping`);
|
|
77
|
+
try {
|
|
78
|
+
const { action } = installConfig(defaultPath, destPath);
|
|
79
|
+
console.log(configLogLine(action));
|
|
80
|
+
} catch (error) {
|
|
81
|
+
console.log(` ⚠️ config install failed (${error.message}) — skills still installed`);
|
|
77
82
|
}
|
|
78
83
|
}
|
|
79
84
|
|
|
85
|
+
function configLogLine(action) {
|
|
86
|
+
switch (action) {
|
|
87
|
+
case 'default':
|
|
88
|
+
return ' 📁 config (default)';
|
|
89
|
+
case 'merged':
|
|
90
|
+
return ' 🔧 config (merged — your settings preserved)';
|
|
91
|
+
case 'recovered':
|
|
92
|
+
return ` ⚠️ config was invalid JSON — backed up to ${CONFIG_FILENAME}.bak, wrote default`;
|
|
93
|
+
default:
|
|
94
|
+
throw new Error(`unknown config action: ${action}`);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function installSkills(skillsDir) {
|
|
99
|
+
let installed = 0;
|
|
100
|
+
for (const skill of skills) {
|
|
101
|
+
const src = path.join(SOURCE_DIR, skill);
|
|
102
|
+
const dest = path.join(skillsDir, skill);
|
|
103
|
+
|
|
104
|
+
if (fs.existsSync(src)) {
|
|
105
|
+
copyRecursive(src, dest);
|
|
106
|
+
console.log(` ✅ ${skill}`);
|
|
107
|
+
installed++;
|
|
108
|
+
} else {
|
|
109
|
+
console.log(` ⚠️ ${skill} — source not found, skipping`);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return installed;
|
|
113
|
+
}
|
|
114
|
+
|
|
80
115
|
// Copy lib/workflows/ → <skillsDir>/_workflows/ (v2.0.0+).
|
|
81
|
-
// Underscore prefix prevents Claude Code from registering as a skill.
|
|
82
|
-
|
|
116
|
+
// Underscore prefix prevents Claude Code from registering it as a skill.
|
|
117
|
+
function installWorkflows(skillsDir) {
|
|
118
|
+
if (!fs.existsSync(WORKFLOWS_SOURCE_DIR)) return;
|
|
83
119
|
const workflowsDest = path.join(skillsDir, '_workflows');
|
|
84
120
|
copyRecursive(WORKFLOWS_SOURCE_DIR, workflowsDest);
|
|
85
121
|
console.log(' ⚙️ _workflows (lib/workflows + schemas)');
|
|
86
122
|
}
|
|
87
123
|
|
|
88
|
-
|
|
89
|
-
if (!IS_GLOBAL) {
|
|
124
|
+
function ensureGitignoreEntry(skillsDir) {
|
|
90
125
|
const projectRoot = path.resolve(skillsDir, '..', '..');
|
|
91
126
|
const gitignorePath = path.join(projectRoot, '.gitignore');
|
|
92
127
|
const entry = '.claude-scrum-skill';
|
|
93
128
|
|
|
94
|
-
|
|
95
|
-
if (
|
|
96
|
-
|
|
97
|
-
|
|
129
|
+
const existing = fs.existsSync(gitignorePath) ? fs.readFileSync(gitignorePath, 'utf8') : '';
|
|
130
|
+
if (existing.split('\n').some(line => line.trim() === entry)) return;
|
|
131
|
+
|
|
132
|
+
const prefix = existing.length > 0 && !existing.endsWith('\n') ? '\n' : '';
|
|
133
|
+
fs.appendFileSync(gitignorePath, `${prefix}${entry}\n`);
|
|
134
|
+
console.log(`\n 📝 Added ${entry} to .gitignore`);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function copyRecursive(src, dest, skipPath) {
|
|
138
|
+
if (!fs.existsSync(src)) return;
|
|
139
|
+
if (skipPath && path.resolve(src) === path.resolve(skipPath)) return;
|
|
140
|
+
|
|
141
|
+
if (fs.statSync(src).isDirectory()) {
|
|
142
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
143
|
+
for (const item of fs.readdirSync(src)) {
|
|
144
|
+
copyRecursive(path.join(src, item), path.join(dest, item), skipPath);
|
|
145
|
+
}
|
|
146
|
+
} else {
|
|
147
|
+
fs.copyFileSync(src, dest);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function isPlainObject(value) {
|
|
152
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Pure deep-merge: returns a new object. Recurses plain objects; arrays and
|
|
156
|
+
// scalars are leaves with the override winning. Orphan override keys survive.
|
|
157
|
+
function deepMerge(defaults, overrides) {
|
|
158
|
+
const merged = { ...defaults };
|
|
159
|
+
|
|
160
|
+
for (const key of Object.keys(overrides)) {
|
|
161
|
+
const defaultValue = defaults[key];
|
|
162
|
+
const overrideValue = overrides[key];
|
|
163
|
+
|
|
164
|
+
if (isPlainObject(defaultValue) && isPlainObject(overrideValue)) {
|
|
165
|
+
merged[key] = deepMerge(defaultValue, overrideValue);
|
|
166
|
+
} else {
|
|
167
|
+
merged[key] = overrideValue;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return merged;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function serializeConfig(config) {
|
|
175
|
+
return `${JSON.stringify(config, null, 2)}\n`;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Three-branch config installer. Returns { action }:
|
|
179
|
+
// 'default' — dest absent, default copied verbatim.
|
|
180
|
+
// 'merged' — dest valid JSON, deep-merged with user values winning.
|
|
181
|
+
// 'recovered' — dest invalid JSON, backed up to .bak, default written.
|
|
182
|
+
function installConfig(defaultPath, destPath) {
|
|
183
|
+
const defaults = JSON.parse(fs.readFileSync(defaultPath, 'utf8'));
|
|
184
|
+
|
|
185
|
+
if (!fs.existsSync(destPath)) {
|
|
186
|
+
fs.writeFileSync(destPath, serializeConfig(defaults));
|
|
187
|
+
return { action: 'default' };
|
|
98
188
|
}
|
|
99
189
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
fs.
|
|
106
|
-
|
|
190
|
+
const rawDest = fs.readFileSync(destPath, 'utf8');
|
|
191
|
+
const userConfig = parseConfig(rawDest);
|
|
192
|
+
|
|
193
|
+
if (userConfig === undefined) {
|
|
194
|
+
fs.copyFileSync(destPath, `${destPath}.bak`);
|
|
195
|
+
fs.writeFileSync(destPath, serializeConfig(defaults));
|
|
196
|
+
return { action: 'recovered' };
|
|
107
197
|
}
|
|
198
|
+
|
|
199
|
+
fs.writeFileSync(destPath, serializeConfig(deepMerge(defaults, userConfig)));
|
|
200
|
+
return { action: 'merged' };
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function parseConfig(raw) {
|
|
204
|
+
try {
|
|
205
|
+
return JSON.parse(raw);
|
|
206
|
+
} catch {
|
|
207
|
+
return undefined;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
if (require.main === module) {
|
|
212
|
+
main();
|
|
108
213
|
}
|
|
109
214
|
|
|
110
|
-
|
|
111
|
-
console.log(' Restart Claude Code for the skills to become available.\n');
|
|
112
|
-
console.log(' Run /project-scaffold <prd-path> to get started.\n');
|
|
215
|
+
module.exports = { deepMerge, installConfig };
|
package/package.json
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@houseofwolvesllc/claude-scrum-skill",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.2",
|
|
4
4
|
"description": "Claude Code skills for scrum project management — PRD to production release pipeline with project scaffolding, sprint planning, status tracking, sprint releases, full-project emulation testing, autonomous orchestration, and project cleanup.",
|
|
5
5
|
"bin": {},
|
|
6
6
|
"publishConfig": {
|
|
7
7
|
"access": "public"
|
|
8
8
|
},
|
|
9
9
|
"scripts": {
|
|
10
|
-
"postinstall": "node bin/install.js"
|
|
10
|
+
"postinstall": "node bin/install.js",
|
|
11
|
+
"test": "node --test"
|
|
11
12
|
},
|
|
12
13
|
"keywords": [
|
|
13
14
|
"claude",
|