@chris1807/claude-kit 2.1.16 → 2.1.18
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/bin/cli.js
CHANGED
|
@@ -11,6 +11,8 @@ const __filename = fileURLToPath(import.meta.url);
|
|
|
11
11
|
const __dirname = dirname(__filename);
|
|
12
12
|
const TEMPLATES_DIR = join(__dirname, '..', 'templates');
|
|
13
13
|
const GLOBAL_CLAUDE_DIR = join(process.env.HOME || process.env.USERPROFILE, '.claude');
|
|
14
|
+
const PKG = await fs.readJson(join(__dirname, '..', 'package.json'));
|
|
15
|
+
const KIT_VERSION = PKG.version;
|
|
14
16
|
|
|
15
17
|
// ============================================================================
|
|
16
18
|
// CLI Arguments
|
|
@@ -110,6 +112,47 @@ async function installFile(src, dest, label) {
|
|
|
110
112
|
return true;
|
|
111
113
|
}
|
|
112
114
|
|
|
115
|
+
// ============================================================================
|
|
116
|
+
// Helper: additive merge for settings.json
|
|
117
|
+
// ============================================================================
|
|
118
|
+
// Top-level keys: keep user's value if present, add kit's only if missing.
|
|
119
|
+
// `hooks`: union of matcher blocks; within each block, union of hook commands
|
|
120
|
+
// (compared by `command` string). Never removes user-added entries.
|
|
121
|
+
function mergeSettings(existing, template) {
|
|
122
|
+
const merged = { ...existing };
|
|
123
|
+
for (const [key, value] of Object.entries(template)) {
|
|
124
|
+
if (key === 'hooks') {
|
|
125
|
+
merged.hooks = mergeHooks(existing.hooks || {}, value);
|
|
126
|
+
} else if (!(key in merged)) {
|
|
127
|
+
merged[key] = value;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
return merged;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function mergeHooks(existing, template) {
|
|
134
|
+
const merged = { ...existing };
|
|
135
|
+
for (const [event, templateBlocks] of Object.entries(template)) {
|
|
136
|
+
const resultBlocks = [...(merged[event] || [])];
|
|
137
|
+
for (const tplBlock of templateBlocks) {
|
|
138
|
+
const idx = resultBlocks.findIndex(b => b.matcher === tplBlock.matcher);
|
|
139
|
+
if (idx === -1) {
|
|
140
|
+
resultBlocks.push(tplBlock);
|
|
141
|
+
} else {
|
|
142
|
+
const newHooks = [...(resultBlocks[idx].hooks || [])];
|
|
143
|
+
for (const tplHook of tplBlock.hooks || []) {
|
|
144
|
+
if (!newHooks.some(h => h.command === tplHook.command)) {
|
|
145
|
+
newHooks.push(tplHook);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
resultBlocks[idx] = { ...resultBlocks[idx], hooks: newHooks };
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
merged[event] = resultBlocks;
|
|
152
|
+
}
|
|
153
|
+
return merged;
|
|
154
|
+
}
|
|
155
|
+
|
|
113
156
|
// ============================================================================
|
|
114
157
|
// Step 1: Global Agents (always)
|
|
115
158
|
// ============================================================================
|
|
@@ -243,6 +286,7 @@ async function main() {
|
|
|
243
286
|
// ── MCP Servers (interactive selection) ───────────────────────────────
|
|
244
287
|
let dbType = 'none';
|
|
245
288
|
let selectedServers = [];
|
|
289
|
+
let adoOrgChoice = null;
|
|
246
290
|
if (components.includes('mcp')) {
|
|
247
291
|
console.log(chalk.yellow.bold('\n🔌 MCP Servers → .mcp.json\n'));
|
|
248
292
|
|
|
@@ -379,6 +423,7 @@ async function main() {
|
|
|
379
423
|
|
|
380
424
|
dbType = mcpChoices.db;
|
|
381
425
|
selectedServers = mcpChoices.servers;
|
|
426
|
+
adoOrgChoice = mcpChoices.adoOrg || null;
|
|
382
427
|
|
|
383
428
|
const mcpPath = join(targetDir, '.mcp.json');
|
|
384
429
|
if (await fs.pathExists(mcpPath)) {
|
|
@@ -388,15 +433,16 @@ async function main() {
|
|
|
388
433
|
if (newContent.trim() === existingContent.trim()) {
|
|
389
434
|
console.log(chalk.gray(' = .mcp.json (identical, skipped)'));
|
|
390
435
|
} else {
|
|
436
|
+
// In --all mode, merge by default — preserves user-added MCP servers.
|
|
391
437
|
const { action } = installAll
|
|
392
|
-
? { action: '
|
|
438
|
+
? { action: 'merge' }
|
|
393
439
|
: await inquirer.prompt([{
|
|
394
440
|
type: 'list',
|
|
395
441
|
name: 'action',
|
|
396
442
|
message: '.mcp.json already exists:',
|
|
397
443
|
choices: [
|
|
444
|
+
{ name: 'Merge (add missing servers, keep yours)', value: 'merge' },
|
|
398
445
|
{ name: 'Overwrite with new config', value: 'overwrite' },
|
|
399
|
-
{ name: 'Merge (add missing servers)', value: 'merge' },
|
|
400
446
|
{ name: 'Skip', value: 'skip' },
|
|
401
447
|
],
|
|
402
448
|
}]);
|
|
@@ -422,11 +468,27 @@ async function main() {
|
|
|
422
468
|
// ── Settings ──────────────────────────────────────────────────────────
|
|
423
469
|
if (components.includes('settings')) {
|
|
424
470
|
console.log(chalk.yellow.bold('\n⚙️ Settings → .claude/settings.json\n'));
|
|
425
|
-
await
|
|
426
|
-
join(TEMPLATES_DIR, 'infrastructure', 'settings.json')
|
|
427
|
-
join(targetDir, '.claude', 'settings.json'),
|
|
428
|
-
'settings.json'
|
|
471
|
+
const templateSettings = await fs.readJson(
|
|
472
|
+
join(TEMPLATES_DIR, 'infrastructure', 'settings.json')
|
|
429
473
|
);
|
|
474
|
+
const settingsPath = join(targetDir, '.claude', 'settings.json');
|
|
475
|
+
await fs.ensureDir(dirname(settingsPath));
|
|
476
|
+
|
|
477
|
+
if (!await fs.pathExists(settingsPath)) {
|
|
478
|
+
await fs.writeJson(settingsPath, templateSettings, { spaces: 2 });
|
|
479
|
+
console.log(chalk.green(' ✓ settings.json'));
|
|
480
|
+
} else {
|
|
481
|
+
const existing = await fs.readJson(settingsPath);
|
|
482
|
+
const merged = mergeSettings(existing, templateSettings);
|
|
483
|
+
const before = JSON.stringify(existing);
|
|
484
|
+
const after = JSON.stringify(merged);
|
|
485
|
+
if (before === after) {
|
|
486
|
+
console.log(chalk.gray(' = settings.json (no kit changes needed)'));
|
|
487
|
+
} else {
|
|
488
|
+
await fs.writeJson(settingsPath, merged, { spaces: 2 });
|
|
489
|
+
console.log(chalk.green(' ✓ settings.json (merged — kit hooks added, user customizations preserved)'));
|
|
490
|
+
}
|
|
491
|
+
}
|
|
430
492
|
}
|
|
431
493
|
|
|
432
494
|
// ── CLAUDE.md Workflow ────────────────────────────────────────────────
|
|
@@ -468,18 +530,27 @@ async function main() {
|
|
|
468
530
|
existing = await fs.readFile(claudeMdPath, 'utf8');
|
|
469
531
|
|
|
470
532
|
if (existing.includes('Claude Kit Workflow') || existing.includes('Care Solutions AI Workflow')) {
|
|
471
|
-
|
|
472
|
-
|
|
533
|
+
// Check whether the existing workflow section matches the current template.
|
|
534
|
+
// If it does, nothing to do. If not, replace it (auto in --all, prompt otherwise).
|
|
535
|
+
const cleaned = existing.replace(/\n## (?:Claude Kit Workflow|Care Solutions AI Workflow)[\s\S]*$/, '').trimEnd();
|
|
536
|
+
const expected = `${cleaned}\n\n${workflowContent}`;
|
|
537
|
+
if (existing.trim() === expected.trim()) {
|
|
538
|
+
console.log(chalk.gray(' = Workflow section up to date'));
|
|
539
|
+
} else if (installAll) {
|
|
540
|
+
await fs.writeFile(claudeMdPath, expected);
|
|
541
|
+
console.log(chalk.green(' ✓ Workflow section re-synced to current template'));
|
|
542
|
+
} else {
|
|
473
543
|
const { replace } = await inquirer.prompt([{
|
|
474
544
|
type: 'confirm',
|
|
475
545
|
name: 'replace',
|
|
476
|
-
message: '
|
|
477
|
-
default:
|
|
546
|
+
message: 'Workflow section differs from template — replace?',
|
|
547
|
+
default: true,
|
|
478
548
|
}]);
|
|
479
549
|
if (replace) {
|
|
480
|
-
|
|
481
|
-
await fs.writeFile(claudeMdPath, `${cleaned}\n\n${workflowContent}`);
|
|
550
|
+
await fs.writeFile(claudeMdPath, expected);
|
|
482
551
|
console.log(chalk.green(' ✓ Workflow section replaced'));
|
|
552
|
+
} else {
|
|
553
|
+
console.log(chalk.gray(' ⊘ Workflow section left as-is'));
|
|
483
554
|
}
|
|
484
555
|
}
|
|
485
556
|
} else {
|
|
@@ -518,6 +589,19 @@ async function main() {
|
|
|
518
589
|
}
|
|
519
590
|
}
|
|
520
591
|
|
|
592
|
+
// ── Install Manifest ──────────────────────────────────────────────────
|
|
593
|
+
// Records the kit version and the choices made (db, adoOrg) so the
|
|
594
|
+
// kit-update-check hook can re-run the installer non-interactively.
|
|
595
|
+
const manifestPath = join(targetDir, '.claude', '.kit-install.json');
|
|
596
|
+
await fs.ensureDir(dirname(manifestPath));
|
|
597
|
+
await fs.writeJson(manifestPath, {
|
|
598
|
+
version: KIT_VERSION,
|
|
599
|
+
choices: {
|
|
600
|
+
db: dbType,
|
|
601
|
+
adoOrg: selectedServers.includes('azuredevops') ? adoOrgChoice : null,
|
|
602
|
+
},
|
|
603
|
+
}, { spaces: 2 });
|
|
604
|
+
|
|
521
605
|
// ── Summary ───────────────────────────────────────────────────────────
|
|
522
606
|
const agentCount = (await fs.pathExists(join(targetDir, '.claude', 'agents')))
|
|
523
607
|
? (await fs.readdir(join(targetDir, '.claude', 'agents'))).filter(f => f.endsWith('.md')).length
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@chris1807/claude-kit",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.18",
|
|
4
4
|
"description": "Claude Code starter kit for Azure DevOps teams — agents, hooks, MCP servers, slash commands, and end-to-end work item → PR → release → deploy workflow automation",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -129,11 +129,37 @@ Run a build check **before** any other quality checks. Use the `build-validator`
|
|
|
129
129
|
|
|
130
130
|
## Step 7: Quality Checks
|
|
131
131
|
|
|
132
|
-
1. **
|
|
133
|
-
2. **Run
|
|
134
|
-
3. **Run lint** — ESLint and dotnet format
|
|
132
|
+
1. **Run the full test suite** — every unit test in the repo, plus integration tests. Not just the tests added in this change. A failure in an unrelated test means this change broke something else; treat it as a regression, fix it, and re-run until the entire suite is green
|
|
133
|
+
2. **Run lint** — ESLint and dotnet format
|
|
135
134
|
|
|
136
|
-
## Step 8:
|
|
135
|
+
## Step 8: Code Review
|
|
136
|
+
|
|
137
|
+
Spawn the `reviewer` agent to review the diff for quality, security, Clean Architecture compliance, and CLAUDE.md adherence. The agent is read-only — it reports findings, you act on them.
|
|
138
|
+
|
|
139
|
+
Present the findings to the user grouped by severity:
|
|
140
|
+
|
|
141
|
+
```
|
|
142
|
+
## Code Review Findings
|
|
143
|
+
|
|
144
|
+
### Must-fix (blocking)
|
|
145
|
+
- {file:line} — {issue + why it blocks}
|
|
146
|
+
|
|
147
|
+
### Should-fix (recommended)
|
|
148
|
+
- {file:line} — {issue + suggested change}
|
|
149
|
+
|
|
150
|
+
### Nits (optional)
|
|
151
|
+
- {file:line} — {minor note}
|
|
152
|
+
|
|
153
|
+
Address must-fix items? (yes / select / skip)
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
- `yes` → fix every must-fix item, then re-run the reviewer agent on the updated diff
|
|
157
|
+
- `select` → ask which items to address; fix only those, then re-run the reviewer agent
|
|
158
|
+
- `skip` → proceed without fixes (only allowed if there are no must-fix items, or the user explicitly overrides)
|
|
159
|
+
|
|
160
|
+
Loop until the reviewer reports no must-fix items, or the user explicitly accepts remaining findings. Do not proceed to UAT with unresolved must-fix items unless the user overrides.
|
|
161
|
+
|
|
162
|
+
## Step 9: UAT Gate
|
|
137
163
|
|
|
138
164
|
### If Hot Fix:
|
|
139
165
|
Skip manual UAT. Present an abbreviated confirmation:
|
|
@@ -165,7 +191,7 @@ Did manual testing pass?
|
|
|
165
191
|
|
|
166
192
|
Wait for the user's response before proceeding. Do NOT create a PR until confirmed.
|
|
167
193
|
|
|
168
|
-
## Step
|
|
194
|
+
## Step 10: Push, Create PR, and Update Work Item
|
|
169
195
|
|
|
170
196
|
1. Push the branch: `git push -u origin HEAD`
|
|
171
197
|
2. Create a PR via Azure DevOps MCP:
|
|
@@ -202,10 +202,9 @@ Run a build check **before** any other quality checks. Use the `build-validator`
|
|
|
202
202
|
|
|
203
203
|
## Step 9: Quality Checks
|
|
204
204
|
|
|
205
|
-
1. **
|
|
206
|
-
2. **Run
|
|
207
|
-
3. **
|
|
208
|
-
4. **Acceptance Criteria check** — re-read the work item's full Acceptance Criteria (the same list captured in Step 3). For each AC, identify the test or piece of code that proves it's met. If any AC has no covering test or visible code path, flag it before moving to the UAT gate:
|
|
205
|
+
1. **Run the full test suite** — every unit test in the repo, plus integration tests. Not just the tests added in this rework. A failure in an unrelated test means this rework broke something else; treat it as a regression, fix it, and re-run until the entire suite is green
|
|
206
|
+
2. **Run lint** — ESLint and dotnet format
|
|
207
|
+
3. **Acceptance Criteria check** — re-read the work item's full Acceptance Criteria (the same list captured in Step 3). For each AC, identify the test or piece of code that proves it's met. If any AC has no covering test or visible code path, flag it before moving on:
|
|
209
208
|
|
|
210
209
|
```
|
|
211
210
|
⚠ AC #{n} ({short form}) has no covering test or clear code path.
|
|
@@ -214,7 +213,34 @@ Run a build check **before** any other quality checks. Use the `build-validator`
|
|
|
214
213
|
|
|
215
214
|
Do not advance to Step 10 with any AC unverified.
|
|
216
215
|
|
|
217
|
-
## Step 10:
|
|
216
|
+
## Step 10: Code Review
|
|
217
|
+
|
|
218
|
+
Spawn the `reviewer` agent to review the rework diff for quality, security, Clean Architecture compliance, and CLAUDE.md adherence. Focus the review on the files changed since the last PR — call out any regression risk introduced by the rework. The agent is read-only — it reports findings, you act on them.
|
|
219
|
+
|
|
220
|
+
Present the findings to the user grouped by severity:
|
|
221
|
+
|
|
222
|
+
```
|
|
223
|
+
## Code Review Findings
|
|
224
|
+
|
|
225
|
+
### Must-fix (blocking)
|
|
226
|
+
- {file:line} — {issue + why it blocks}
|
|
227
|
+
|
|
228
|
+
### Should-fix (recommended)
|
|
229
|
+
- {file:line} — {issue + suggested change}
|
|
230
|
+
|
|
231
|
+
### Nits (optional)
|
|
232
|
+
- {file:line} — {minor note}
|
|
233
|
+
|
|
234
|
+
Address must-fix items? (yes / select / skip)
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
- `yes` → fix every must-fix item, then re-run the reviewer agent on the updated diff
|
|
238
|
+
- `select` → ask which items to address; fix only those, then re-run the reviewer agent
|
|
239
|
+
- `skip` → proceed without fixes (only allowed if there are no must-fix items, or the user explicitly overrides)
|
|
240
|
+
|
|
241
|
+
Loop until the reviewer reports no must-fix items, or the user explicitly accepts remaining findings. Do not proceed to UAT with unresolved must-fix items unless the user overrides.
|
|
242
|
+
|
|
243
|
+
## Step 11: UAT Gate
|
|
218
244
|
|
|
219
245
|
### If Hot Fix:
|
|
220
246
|
Skip manual UAT. Present an abbreviated confirmation:
|
|
@@ -246,7 +272,7 @@ Did manual testing pass?
|
|
|
246
272
|
|
|
247
273
|
Wait for the user's response before proceeding. Do NOT push until confirmed.
|
|
248
274
|
|
|
249
|
-
## Step
|
|
275
|
+
## Step 12: Push and Update
|
|
250
276
|
|
|
251
277
|
1. Push the changes: `git push`
|
|
252
278
|
2. Add a comment on the existing PR summarizing what was changed in the rework
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# kit-update-check.sh — SessionStart hook
|
|
3
|
+
#
|
|
4
|
+
# Checks npm for a newer @chris1807/claude-kit and re-runs the installer
|
|
5
|
+
# non-interactively when one is available. Must never block claude startup —
|
|
6
|
+
# every external call is timeout-bounded and every failure exits 0 silently.
|
|
7
|
+
#
|
|
8
|
+
# Throttled to once per 24h via a marker file. Reads .claude/.kit-install.json
|
|
9
|
+
# for the installed version and the choices to pass to the re-install (db, adoOrg).
|
|
10
|
+
|
|
11
|
+
set +e # never propagate failures
|
|
12
|
+
|
|
13
|
+
HOOK_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
14
|
+
CLAUDE_DIR="$(cd "$HOOK_DIR/.." && pwd)"
|
|
15
|
+
PROJECT_DIR="$(cd "$CLAUDE_DIR/.." && pwd)"
|
|
16
|
+
MANIFEST="$CLAUDE_DIR/.kit-install.json"
|
|
17
|
+
MARKER="$CLAUDE_DIR/.kit-update-check"
|
|
18
|
+
LOG="$CLAUDE_DIR/.kit-update.log"
|
|
19
|
+
|
|
20
|
+
# No manifest = legacy install or global-only; nothing to do.
|
|
21
|
+
[ -f "$MANIFEST" ] || exit 0
|
|
22
|
+
|
|
23
|
+
# Throttle to one check per 24h. A failed check still touches the marker so
|
|
24
|
+
# unreachable npm doesn't retry every session.
|
|
25
|
+
if [ -f "$MARKER" ]; then
|
|
26
|
+
if command -v stat >/dev/null 2>&1; then
|
|
27
|
+
LAST=$(stat -f %m "$MARKER" 2>/dev/null || stat -c %Y "$MARKER" 2>/dev/null || echo 0)
|
|
28
|
+
NOW=$(date +%s)
|
|
29
|
+
if [ "$((NOW - LAST))" -lt 86400 ]; then
|
|
30
|
+
exit 0
|
|
31
|
+
fi
|
|
32
|
+
fi
|
|
33
|
+
fi
|
|
34
|
+
|
|
35
|
+
# Crude JSON extraction (avoids requiring jq).
|
|
36
|
+
extract() {
|
|
37
|
+
grep -o "\"$1\"[[:space:]]*:[[:space:]]*\"[^\"]*\"" "$MANIFEST" | head -1 | sed 's/.*"\([^"]*\)"$/\1/'
|
|
38
|
+
}
|
|
39
|
+
INSTALLED=$(extract version)
|
|
40
|
+
DB=$(extract db)
|
|
41
|
+
ADO_ORG=$(extract adoOrg)
|
|
42
|
+
|
|
43
|
+
# Fetch latest with a 5s timeout — must never hang. `timeout` isn't on macOS
|
|
44
|
+
# by default; perl's alarm() is the most portable fallback.
|
|
45
|
+
if command -v timeout >/dev/null 2>&1; then
|
|
46
|
+
LATEST=$(timeout 5 npm view @chris1807/claude-kit version 2>/dev/null)
|
|
47
|
+
elif command -v gtimeout >/dev/null 2>&1; then
|
|
48
|
+
LATEST=$(gtimeout 5 npm view @chris1807/claude-kit version 2>/dev/null)
|
|
49
|
+
elif command -v perl >/dev/null 2>&1; then
|
|
50
|
+
LATEST=$(perl -e 'alarm 5; exec @ARGV or exit 1' npm view @chris1807/claude-kit version 2>/dev/null)
|
|
51
|
+
else
|
|
52
|
+
# No timeout mechanism available — skip rather than risk a hang.
|
|
53
|
+
touch "$MARKER"
|
|
54
|
+
exit 0
|
|
55
|
+
fi
|
|
56
|
+
|
|
57
|
+
touch "$MARKER"
|
|
58
|
+
|
|
59
|
+
[ -z "$LATEST" ] && exit 0
|
|
60
|
+
[ -z "$INSTALLED" ] && exit 0
|
|
61
|
+
[ "$LATEST" = "$INSTALLED" ] && exit 0
|
|
62
|
+
|
|
63
|
+
# Only proceed if LATEST sorts strictly greater than INSTALLED.
|
|
64
|
+
NEWER=$(printf '%s\n%s\n' "$INSTALLED" "$LATEST" | sort -V | tail -1)
|
|
65
|
+
[ "$NEWER" = "$LATEST" ] || exit 0
|
|
66
|
+
[ "$NEWER" = "$INSTALLED" ] && exit 0
|
|
67
|
+
|
|
68
|
+
echo "claude-kit: updating $INSTALLED → $LATEST..." >&2
|
|
69
|
+
|
|
70
|
+
ARGS=("@chris1807/claude-kit" "init" "$PROJECT_DIR" "--all")
|
|
71
|
+
if [ -n "$DB" ] && [ "$DB" != "null" ]; then
|
|
72
|
+
ARGS+=("--db=$DB")
|
|
73
|
+
fi
|
|
74
|
+
if [ -n "$ADO_ORG" ] && [ "$ADO_ORG" != "null" ]; then
|
|
75
|
+
ARGS+=("--ado-org=$ADO_ORG")
|
|
76
|
+
fi
|
|
77
|
+
|
|
78
|
+
if npx -y "${ARGS[@]}" >"$LOG" 2>&1; then
|
|
79
|
+
echo "claude-kit: updated to $LATEST (log: $LOG)" >&2
|
|
80
|
+
else
|
|
81
|
+
echo "claude-kit: update failed (see $LOG)" >&2
|
|
82
|
+
fi
|
|
83
|
+
|
|
84
|
+
exit 0
|