claude-cli-advanced-starter-pack 1.0.12 → 1.0.14
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/README.md +527 -345
- package/package.json +1 -1
- package/src/commands/init.js +336 -42
- package/src/commands/test-setup.js +7 -6
- package/src/data/releases.json +102 -5
- package/src/testing/config.js +213 -84
- package/src/utils/smart-merge.js +457 -0
- package/src/utils/version-check.js +213 -0
- package/templates/commands/create-task-list.template.md +332 -17
- package/templates/commands/update-smart.template.md +111 -0
- package/templates/hooks/ccasp-update-check.template.js +74 -0
- package/templates/hooks/usage-tracking.template.js +222 -0
package/package.json
CHANGED
package/src/commands/init.js
CHANGED
|
@@ -15,6 +15,19 @@ import { fileURLToPath } from 'url';
|
|
|
15
15
|
import { showHeader, showSuccess, showError, showWarning, showInfo } from '../cli/menu.js';
|
|
16
16
|
import { getVersion } from '../utils.js';
|
|
17
17
|
import { createBackup } from './setup-wizard.js';
|
|
18
|
+
import {
|
|
19
|
+
loadUsageTracking,
|
|
20
|
+
getCustomizedUsedAssets,
|
|
21
|
+
isAssetCustomized,
|
|
22
|
+
} from '../utils/version-check.js';
|
|
23
|
+
import {
|
|
24
|
+
getAssetsNeedingMerge,
|
|
25
|
+
compareAssetVersions,
|
|
26
|
+
getLocalAsset,
|
|
27
|
+
getTemplateAsset,
|
|
28
|
+
generateMergeExplanation,
|
|
29
|
+
formatMergeOptions,
|
|
30
|
+
} from '../utils/smart-merge.js';
|
|
18
31
|
|
|
19
32
|
const __filename = fileURLToPath(import.meta.url);
|
|
20
33
|
const __dirname = dirname(__filename);
|
|
@@ -29,7 +42,7 @@ const OPTIONAL_FEATURES = [
|
|
|
29
42
|
label: 'Token Budget Management',
|
|
30
43
|
description: 'Monitor and manage Claude API token usage with automatic compaction warnings, archive suggestions, and respawn thresholds. Includes hooks that track usage per session.',
|
|
31
44
|
commands: ['context-audit'],
|
|
32
|
-
hooks: ['
|
|
45
|
+
hooks: ['context-guardian'], // Only include hooks with templates
|
|
33
46
|
default: false,
|
|
34
47
|
requiresPostConfig: false,
|
|
35
48
|
},
|
|
@@ -38,7 +51,7 @@ const OPTIONAL_FEATURES = [
|
|
|
38
51
|
label: 'Happy Engineering Integration',
|
|
39
52
|
description: 'Integration with Happy Coder mobile app for remote session control, checkpoint management, and mobile-optimized responses. Requires Happy Coder app installed separately.',
|
|
40
53
|
commands: ['happy-start'],
|
|
41
|
-
hooks: ['happy-checkpoint-manager',
|
|
54
|
+
hooks: ['happy-checkpoint-manager'], // Only include hooks with templates
|
|
42
55
|
default: false,
|
|
43
56
|
requiresPostConfig: true,
|
|
44
57
|
},
|
|
@@ -47,7 +60,7 @@ const OPTIONAL_FEATURES = [
|
|
|
47
60
|
label: 'GitHub Project Board Integration',
|
|
48
61
|
description: 'Connect Claude to your GitHub Project Board for automated issue creation, progress tracking, and PR merge automation. Requires gh CLI authentication.',
|
|
49
62
|
commands: ['github-update', 'github-task-start'],
|
|
50
|
-
hooks: ['github-progress-hook',
|
|
63
|
+
hooks: ['github-progress-hook'], // Only include hooks with templates
|
|
51
64
|
default: true,
|
|
52
65
|
requiresPostConfig: true,
|
|
53
66
|
},
|
|
@@ -245,6 +258,12 @@ const AVAILABLE_COMMANDS = [
|
|
|
245
258
|
category: 'Maintenance',
|
|
246
259
|
selected: true,
|
|
247
260
|
},
|
|
261
|
+
{
|
|
262
|
+
name: 'update-smart',
|
|
263
|
+
description: 'Smart merge manager for customized assets during updates',
|
|
264
|
+
category: 'Maintenance',
|
|
265
|
+
selected: true,
|
|
266
|
+
},
|
|
248
267
|
];
|
|
249
268
|
|
|
250
269
|
/**
|
|
@@ -1482,7 +1501,7 @@ module.exports = async function ccaspUpdateCheck(context) {
|
|
|
1482
1501
|
}
|
|
1483
1502
|
|
|
1484
1503
|
/**
|
|
1485
|
-
* Generate settings.json with CCASP update check hook
|
|
1504
|
+
* Generate settings.json with CCASP update check hook and usage tracking
|
|
1486
1505
|
*/
|
|
1487
1506
|
function generateSettingsJson(projectName) {
|
|
1488
1507
|
return JSON.stringify({
|
|
@@ -1502,6 +1521,17 @@ function generateSettingsJson(projectName) {
|
|
|
1502
1521
|
}
|
|
1503
1522
|
]
|
|
1504
1523
|
}
|
|
1524
|
+
],
|
|
1525
|
+
"PostToolUse": [
|
|
1526
|
+
{
|
|
1527
|
+
"matcher": "Skill|Read",
|
|
1528
|
+
"hooks": [
|
|
1529
|
+
{
|
|
1530
|
+
"type": "command",
|
|
1531
|
+
"command": "node .claude/hooks/usage-tracking.js"
|
|
1532
|
+
}
|
|
1533
|
+
]
|
|
1534
|
+
}
|
|
1505
1535
|
]
|
|
1506
1536
|
}
|
|
1507
1537
|
}, null, 2);
|
|
@@ -1738,6 +1768,19 @@ export async function runInit(options = {}) {
|
|
|
1738
1768
|
console.log(chalk.blue(' ○ hooks/ccasp-update-check.js exists (preserved)'));
|
|
1739
1769
|
}
|
|
1740
1770
|
|
|
1771
|
+
// Deploy the usage tracking hook (tracks command/skill/agent usage for smart merge)
|
|
1772
|
+
const usageTrackingHookPath = join(hooksDir, 'usage-tracking.js');
|
|
1773
|
+
if (!existsSync(usageTrackingHookPath)) {
|
|
1774
|
+
const templatePath = join(__dirname, '..', '..', 'templates', 'hooks', 'usage-tracking.template.js');
|
|
1775
|
+
if (existsSync(templatePath)) {
|
|
1776
|
+
const hookContent = readFileSync(templatePath, 'utf8');
|
|
1777
|
+
writeFileSync(usageTrackingHookPath, hookContent, 'utf8');
|
|
1778
|
+
console.log(chalk.green(' ✓ Created hooks/usage-tracking.js (smart merge tracking)'));
|
|
1779
|
+
}
|
|
1780
|
+
} else {
|
|
1781
|
+
console.log(chalk.blue(' ○ hooks/usage-tracking.js exists (preserved)'));
|
|
1782
|
+
}
|
|
1783
|
+
|
|
1741
1784
|
console.log('');
|
|
1742
1785
|
|
|
1743
1786
|
// Step 4: Select optional features
|
|
@@ -1775,6 +1818,25 @@ export async function runInit(options = {}) {
|
|
|
1775
1818
|
const enabledFeatures = OPTIONAL_FEATURES.filter((f) => selectedFeatures.includes(f.name));
|
|
1776
1819
|
const featuresRequiringConfig = enabledFeatures.filter((f) => f.requiresPostConfig);
|
|
1777
1820
|
|
|
1821
|
+
// Collect feature-specific commands and hooks to deploy
|
|
1822
|
+
const featureCommands = [];
|
|
1823
|
+
const featureHooks = [];
|
|
1824
|
+
for (const feature of enabledFeatures) {
|
|
1825
|
+
featureCommands.push(...feature.commands);
|
|
1826
|
+
featureHooks.push(...feature.hooks);
|
|
1827
|
+
}
|
|
1828
|
+
|
|
1829
|
+
if (featureCommands.length > 0) {
|
|
1830
|
+
console.log('');
|
|
1831
|
+
console.log(chalk.green(` ✓ Selected features will add ${featureCommands.length} command(s):`));
|
|
1832
|
+
console.log(chalk.dim(` ${featureCommands.map(c => '/' + c).join(', ')}`));
|
|
1833
|
+
}
|
|
1834
|
+
|
|
1835
|
+
if (featureHooks.length > 0) {
|
|
1836
|
+
console.log(chalk.green(` ✓ Selected features will add ${featureHooks.length} hook(s):`));
|
|
1837
|
+
console.log(chalk.dim(` ${featureHooks.join(', ')}`));
|
|
1838
|
+
}
|
|
1839
|
+
|
|
1778
1840
|
if (featuresRequiringConfig.length > 0) {
|
|
1779
1841
|
console.log('');
|
|
1780
1842
|
console.log(chalk.yellow(' ℹ The following features require configuration after installation:'));
|
|
@@ -1835,67 +1897,246 @@ export async function runInit(options = {}) {
|
|
|
1835
1897
|
},
|
|
1836
1898
|
]);
|
|
1837
1899
|
|
|
1838
|
-
// Always include required commands
|
|
1900
|
+
// Always include required commands AND feature-specific commands
|
|
1839
1901
|
const requiredCommands = AVAILABLE_COMMANDS.filter(c => c.required).map(c => c.name);
|
|
1840
|
-
const finalCommands = [...new Set([...requiredCommands, ...selectedCommands])];
|
|
1902
|
+
const finalCommands = [...new Set([...requiredCommands, ...selectedCommands, ...featureCommands])];
|
|
1841
1903
|
|
|
1842
1904
|
if (finalCommands.length === 0) {
|
|
1843
1905
|
showWarning('No commands selected. Nothing to install.');
|
|
1844
1906
|
return;
|
|
1845
1907
|
}
|
|
1846
1908
|
|
|
1909
|
+
// Show what feature commands were auto-added
|
|
1910
|
+
const autoAddedCommands = featureCommands.filter(c => !selectedCommands.includes(c) && !requiredCommands.includes(c));
|
|
1911
|
+
if (autoAddedCommands.length > 0) {
|
|
1912
|
+
console.log(chalk.cyan(` ℹ Auto-including ${autoAddedCommands.length} feature command(s): ${autoAddedCommands.map(c => '/' + c).join(', ')}`));
|
|
1913
|
+
}
|
|
1914
|
+
|
|
1847
1915
|
console.log('');
|
|
1848
1916
|
|
|
1849
1917
|
// Step 6: Check for existing commands that would be overwritten
|
|
1850
1918
|
const commandsToOverwrite = finalCommands.filter(cmd => existingCmdNames.includes(cmd));
|
|
1851
1919
|
|
|
1920
|
+
// Track commands that need smart merge handling
|
|
1921
|
+
const smartMergeDecisions = {};
|
|
1922
|
+
|
|
1852
1923
|
let overwrite = options.force || false;
|
|
1853
1924
|
if (commandsToOverwrite.length > 0 && !overwrite) {
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1925
|
+
// Check for customized assets that have been used
|
|
1926
|
+
const assetsNeedingMerge = getAssetsNeedingMerge(process.cwd());
|
|
1927
|
+
const customizedCommands = commandsToOverwrite.filter(cmd =>
|
|
1928
|
+
assetsNeedingMerge.commands?.some(a => a.name === cmd)
|
|
1929
|
+
);
|
|
1930
|
+
|
|
1931
|
+
// Show smart merge prompt for customized commands
|
|
1932
|
+
if (customizedCommands.length > 0) {
|
|
1933
|
+
console.log(chalk.cyan.bold('\n 🔀 Smart Merge Available'));
|
|
1934
|
+
console.log(chalk.dim(' The following commands have been customized and used:\n'));
|
|
1935
|
+
|
|
1936
|
+
for (const cmd of customizedCommands) {
|
|
1937
|
+
const assetInfo = assetsNeedingMerge.commands.find(a => a.name === cmd);
|
|
1938
|
+
console.log(chalk.cyan(` • /${cmd}`));
|
|
1939
|
+
console.log(chalk.dim(` Used ${assetInfo.usageData.useCount} time(s), last: ${new Date(assetInfo.usageData.lastUsed).toLocaleDateString()}`));
|
|
1940
|
+
console.log(chalk.dim(` Change: ${assetInfo.comparison.significance.level} significance - ${assetInfo.comparison.summary}`));
|
|
1941
|
+
}
|
|
1942
|
+
console.log('');
|
|
1859
1943
|
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1944
|
+
const { smartMergeAction } = await inquirer.prompt([
|
|
1945
|
+
{
|
|
1946
|
+
type: 'list',
|
|
1947
|
+
name: 'smartMergeAction',
|
|
1948
|
+
message: 'How would you like to handle your customized commands?',
|
|
1949
|
+
choices: [
|
|
1950
|
+
{ name: '🔍 Explore each one - Let Claude explain the changes', value: 'explore' },
|
|
1951
|
+
{ name: '📋 Skip all customized - Keep your versions', value: 'skip-customized' },
|
|
1952
|
+
{ name: '🔄 Replace all - Use new versions (lose customizations)', value: 'replace-all' },
|
|
1953
|
+
{ name: '❌ Cancel installation', value: 'cancel' },
|
|
1954
|
+
],
|
|
1955
|
+
},
|
|
1956
|
+
]);
|
|
1957
|
+
|
|
1958
|
+
if (smartMergeAction === 'cancel') {
|
|
1959
|
+
console.log(chalk.dim('\nCancelled. No changes made.'));
|
|
1960
|
+
return;
|
|
1961
|
+
}
|
|
1873
1962
|
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1963
|
+
if (smartMergeAction === 'explore') {
|
|
1964
|
+
// Individual exploration for each customized command
|
|
1965
|
+
console.log(chalk.cyan('\n Exploring customized commands...\n'));
|
|
1966
|
+
|
|
1967
|
+
for (const cmd of customizedCommands) {
|
|
1968
|
+
const assetInfo = assetsNeedingMerge.commands.find(a => a.name === cmd);
|
|
1969
|
+
const local = getLocalAsset('commands', cmd, process.cwd());
|
|
1970
|
+
const template = getTemplateAsset('commands', cmd);
|
|
1971
|
+
|
|
1972
|
+
// Show merge explanation
|
|
1973
|
+
console.log(chalk.bold(`\n ┌${'─'.repeat(60)}┐`));
|
|
1974
|
+
console.log(chalk.bold(` │ /${cmd.padEnd(58)} │`));
|
|
1975
|
+
console.log(chalk.bold(` └${'─'.repeat(60)}┘`));
|
|
1976
|
+
|
|
1977
|
+
const explanation = generateMergeExplanation(
|
|
1978
|
+
'commands',
|
|
1979
|
+
cmd,
|
|
1980
|
+
assetInfo.comparison,
|
|
1981
|
+
local?.content,
|
|
1982
|
+
template?.content
|
|
1983
|
+
);
|
|
1984
|
+
|
|
1985
|
+
// Display condensed explanation
|
|
1986
|
+
console.log(chalk.dim('\n ' + explanation.split('\n').slice(0, 15).join('\n ')));
|
|
1987
|
+
|
|
1988
|
+
const { decision } = await inquirer.prompt([
|
|
1989
|
+
{
|
|
1990
|
+
type: 'list',
|
|
1991
|
+
name: 'decision',
|
|
1992
|
+
message: `What would you like to do with /${cmd}?`,
|
|
1993
|
+
choices: [
|
|
1994
|
+
{ name: 'Skip - Keep your customized version', value: 'skip' },
|
|
1995
|
+
{ name: 'Backup & Replace - Save yours, use new version', value: 'backup' },
|
|
1996
|
+
{ name: 'Replace - Use new version (no backup)', value: 'replace' },
|
|
1997
|
+
{ name: 'Show full diff', value: 'diff' },
|
|
1998
|
+
],
|
|
1999
|
+
},
|
|
2000
|
+
]);
|
|
2001
|
+
|
|
2002
|
+
if (decision === 'diff') {
|
|
2003
|
+
// Show full diff
|
|
2004
|
+
console.log(chalk.dim('\n--- Your Version ---'));
|
|
2005
|
+
console.log(local?.content?.slice(0, 500) + (local?.content?.length > 500 ? '\n...(truncated)' : ''));
|
|
2006
|
+
console.log(chalk.dim('\n--- Update Version ---'));
|
|
2007
|
+
console.log(template?.content?.slice(0, 500) + (template?.content?.length > 500 ? '\n...(truncated)' : ''));
|
|
2008
|
+
|
|
2009
|
+
// Re-prompt after showing diff
|
|
2010
|
+
const { finalDecision } = await inquirer.prompt([
|
|
2011
|
+
{
|
|
2012
|
+
type: 'list',
|
|
2013
|
+
name: 'finalDecision',
|
|
2014
|
+
message: `Final decision for /${cmd}?`,
|
|
2015
|
+
choices: [
|
|
2016
|
+
{ name: 'Skip - Keep your version', value: 'skip' },
|
|
2017
|
+
{ name: 'Backup & Replace', value: 'backup' },
|
|
2018
|
+
{ name: 'Replace without backup', value: 'replace' },
|
|
2019
|
+
],
|
|
2020
|
+
},
|
|
2021
|
+
]);
|
|
2022
|
+
smartMergeDecisions[cmd] = finalDecision;
|
|
2023
|
+
} else {
|
|
2024
|
+
smartMergeDecisions[cmd] = decision;
|
|
2025
|
+
}
|
|
2026
|
+
}
|
|
2027
|
+
} else if (smartMergeAction === 'skip-customized') {
|
|
2028
|
+
// Mark all customized commands as skip
|
|
2029
|
+
for (const cmd of customizedCommands) {
|
|
2030
|
+
smartMergeDecisions[cmd] = 'skip';
|
|
2031
|
+
}
|
|
2032
|
+
console.log(chalk.green(`\n ✓ Will preserve ${customizedCommands.length} customized command(s)`));
|
|
2033
|
+
} else if (smartMergeAction === 'replace-all') {
|
|
2034
|
+
// Mark all customized commands as replace with backup
|
|
2035
|
+
for (const cmd of customizedCommands) {
|
|
2036
|
+
smartMergeDecisions[cmd] = 'backup';
|
|
2037
|
+
}
|
|
2038
|
+
console.log(chalk.yellow(`\n ⚠ Will backup and replace ${customizedCommands.length} customized command(s)`));
|
|
2039
|
+
}
|
|
2040
|
+
|
|
2041
|
+
// Remove customized commands from the standard overwrite flow
|
|
2042
|
+
// (they're handled by smart merge decisions)
|
|
2043
|
+
const nonCustomizedToOverwrite = commandsToOverwrite.filter(c => !customizedCommands.includes(c));
|
|
2044
|
+
|
|
2045
|
+
if (nonCustomizedToOverwrite.length > 0) {
|
|
2046
|
+
console.log(chalk.yellow.bold('\n ⚠ The following non-customized commands also exist:'));
|
|
2047
|
+
for (const cmd of nonCustomizedToOverwrite) {
|
|
2048
|
+
console.log(chalk.yellow(` • /${cmd}`));
|
|
2049
|
+
}
|
|
2050
|
+
}
|
|
1877
2051
|
}
|
|
1878
2052
|
|
|
1879
|
-
|
|
1880
|
-
const
|
|
1881
|
-
|
|
1882
|
-
if (
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
console.log(
|
|
1890
|
-
|
|
1891
|
-
|
|
2053
|
+
// Standard overwrite prompt for non-customized commands
|
|
2054
|
+
const remainingToOverwrite = commandsToOverwrite.filter(c => !smartMergeDecisions[c]);
|
|
2055
|
+
|
|
2056
|
+
if (remainingToOverwrite.length > 0) {
|
|
2057
|
+
if (!customizedCommands || customizedCommands.length === 0) {
|
|
2058
|
+
console.log(chalk.yellow.bold(' ⚠ The following commands already exist:'));
|
|
2059
|
+
for (const cmd of remainingToOverwrite) {
|
|
2060
|
+
console.log(chalk.yellow(` • /${cmd}`));
|
|
2061
|
+
}
|
|
2062
|
+
}
|
|
2063
|
+
console.log('');
|
|
2064
|
+
|
|
2065
|
+
const { overwriteChoice } = await inquirer.prompt([
|
|
2066
|
+
{
|
|
2067
|
+
type: 'list',
|
|
2068
|
+
name: 'overwriteChoice',
|
|
2069
|
+
message: 'How would you like to handle these existing commands?',
|
|
2070
|
+
choices: [
|
|
2071
|
+
{ name: 'Skip existing - only install new commands (recommended)', value: 'skip' },
|
|
2072
|
+
{ name: 'Overwrite with backup - save existing to .claude/backups/ first', value: 'backup' },
|
|
2073
|
+
{ name: 'Overwrite all - replace existing (no backup)', value: 'overwrite' },
|
|
2074
|
+
{ name: 'Cancel installation', value: 'cancel' },
|
|
2075
|
+
],
|
|
2076
|
+
},
|
|
2077
|
+
]);
|
|
2078
|
+
|
|
2079
|
+
if (overwriteChoice === 'cancel') {
|
|
2080
|
+
console.log(chalk.dim('\nCancelled. No changes made.'));
|
|
2081
|
+
return;
|
|
2082
|
+
}
|
|
2083
|
+
|
|
2084
|
+
overwrite = overwriteChoice === 'overwrite' || overwriteChoice === 'backup';
|
|
2085
|
+
|
|
2086
|
+
// Apply decision to remaining commands
|
|
2087
|
+
for (const cmd of remainingToOverwrite) {
|
|
2088
|
+
smartMergeDecisions[cmd] = overwriteChoice === 'skip' ? 'skip' : (overwriteChoice === 'backup' ? 'backup' : 'replace');
|
|
2089
|
+
}
|
|
2090
|
+
|
|
2091
|
+
if (!overwrite) {
|
|
2092
|
+
// Filter out skipped commands
|
|
2093
|
+
const skippedCommands = Object.entries(smartMergeDecisions)
|
|
2094
|
+
.filter(([, decision]) => decision === 'skip')
|
|
2095
|
+
.map(([cmd]) => cmd);
|
|
2096
|
+
const filtered = finalCommands.filter((c) => !skippedCommands.includes(c) || requiredCommands.includes(c));
|
|
2097
|
+
finalCommands.length = 0;
|
|
2098
|
+
finalCommands.push(...filtered);
|
|
2099
|
+
console.log(chalk.green(`\n ✓ Will install ${finalCommands.length} new command(s), preserving ${skippedCommands.length} existing`));
|
|
2100
|
+
} else if (overwriteChoice === 'backup') {
|
|
2101
|
+
console.log(chalk.cyan(`\n ✓ Will backup and overwrite ${remainingToOverwrite.length} existing command(s)`));
|
|
2102
|
+
} else {
|
|
2103
|
+
console.log(chalk.yellow(`\n ⚠ Will overwrite ${remainingToOverwrite.length} existing command(s)`));
|
|
2104
|
+
}
|
|
2105
|
+
} else if (Object.keys(smartMergeDecisions).length > 0) {
|
|
2106
|
+
// All commands handled by smart merge
|
|
2107
|
+
const skippedCommands = Object.entries(smartMergeDecisions)
|
|
2108
|
+
.filter(([, decision]) => decision === 'skip')
|
|
2109
|
+
.map(([cmd]) => cmd);
|
|
2110
|
+
|
|
2111
|
+
if (skippedCommands.length > 0) {
|
|
2112
|
+
const filtered = finalCommands.filter((c) => !skippedCommands.includes(c) || requiredCommands.includes(c));
|
|
2113
|
+
finalCommands.length = 0;
|
|
2114
|
+
finalCommands.push(...filtered);
|
|
2115
|
+
}
|
|
1892
2116
|
}
|
|
1893
2117
|
}
|
|
1894
2118
|
|
|
1895
2119
|
// Track if we should create backups (set outside the if block for use later)
|
|
2120
|
+
// Now also considers smart merge decisions
|
|
1896
2121
|
const createBackups = options.backup || (typeof overwrite !== 'undefined' && commandsToOverwrite.length > 0 && !options.force);
|
|
1897
2122
|
let backedUpFiles = [];
|
|
1898
2123
|
|
|
2124
|
+
// Helper to check if a command should be backed up based on smart merge decisions
|
|
2125
|
+
const shouldBackupCommand = (cmdName) => {
|
|
2126
|
+
if (smartMergeDecisions[cmdName]) {
|
|
2127
|
+
return smartMergeDecisions[cmdName] === 'backup';
|
|
2128
|
+
}
|
|
2129
|
+
return createBackups;
|
|
2130
|
+
};
|
|
2131
|
+
|
|
2132
|
+
// Helper to check if a command should be skipped based on smart merge decisions
|
|
2133
|
+
const shouldSkipCommand = (cmdName) => {
|
|
2134
|
+
if (smartMergeDecisions[cmdName]) {
|
|
2135
|
+
return smartMergeDecisions[cmdName] === 'skip';
|
|
2136
|
+
}
|
|
2137
|
+
return false;
|
|
2138
|
+
};
|
|
2139
|
+
|
|
1899
2140
|
// Step 7: Install commands
|
|
1900
2141
|
console.log(chalk.bold('Step 6: Installing slash commands\n'));
|
|
1901
2142
|
|
|
@@ -1916,6 +2157,11 @@ export async function runInit(options = {}) {
|
|
|
1916
2157
|
|
|
1917
2158
|
for (const cmdName of finalCommands) {
|
|
1918
2159
|
try {
|
|
2160
|
+
// Skip commands that were marked to skip in smart merge
|
|
2161
|
+
if (shouldSkipCommand(cmdName)) {
|
|
2162
|
+
continue;
|
|
2163
|
+
}
|
|
2164
|
+
|
|
1919
2165
|
const cmdPath = join(commandsDir, `${cmdName}.md`);
|
|
1920
2166
|
|
|
1921
2167
|
let content;
|
|
@@ -1938,8 +2184,8 @@ export async function runInit(options = {}) {
|
|
|
1938
2184
|
}
|
|
1939
2185
|
}
|
|
1940
2186
|
|
|
1941
|
-
// Create backup if overwriting existing file
|
|
1942
|
-
if (existsSync(cmdPath) &&
|
|
2187
|
+
// Create backup if overwriting existing file (respects smart merge decisions)
|
|
2188
|
+
if (existsSync(cmdPath) && shouldBackupCommand(cmdName)) {
|
|
1943
2189
|
const backupPath = createBackup(cmdPath);
|
|
1944
2190
|
if (backupPath) {
|
|
1945
2191
|
backedUpFiles.push({ original: cmdPath, backup: backupPath });
|
|
@@ -1960,6 +2206,45 @@ export async function runInit(options = {}) {
|
|
|
1960
2206
|
console.log(chalk.cyan(`\n 📁 Backed up ${backedUpFiles.length} file(s) to .claude/backups/`));
|
|
1961
2207
|
}
|
|
1962
2208
|
|
|
2209
|
+
// Step 6b: Deploy feature-specific hooks
|
|
2210
|
+
const deployedHooks = [];
|
|
2211
|
+
const failedHooks = [];
|
|
2212
|
+
|
|
2213
|
+
if (featureHooks.length > 0) {
|
|
2214
|
+
console.log(chalk.bold('\nStep 6b: Deploying feature hooks\n'));
|
|
2215
|
+
|
|
2216
|
+
for (const hookName of featureHooks) {
|
|
2217
|
+
try {
|
|
2218
|
+
const hookPath = join(hooksDir, `${hookName}.js`);
|
|
2219
|
+
|
|
2220
|
+
// Skip if already exists
|
|
2221
|
+
if (existsSync(hookPath)) {
|
|
2222
|
+
console.log(chalk.blue(` ○ hooks/${hookName}.js exists (preserved)`));
|
|
2223
|
+
continue;
|
|
2224
|
+
}
|
|
2225
|
+
|
|
2226
|
+
// Try to load from templates/hooks/ folder
|
|
2227
|
+
const templatePath = join(__dirname, '..', '..', 'templates', 'hooks', `${hookName}.template.js`);
|
|
2228
|
+
if (existsSync(templatePath)) {
|
|
2229
|
+
const hookContent = readFileSync(templatePath, 'utf8');
|
|
2230
|
+
writeFileSync(hookPath, hookContent, 'utf8');
|
|
2231
|
+
deployedHooks.push(hookName);
|
|
2232
|
+
console.log(chalk.green(` ✓ Created hooks/${hookName}.js`));
|
|
2233
|
+
} else {
|
|
2234
|
+
failedHooks.push({ name: hookName, error: 'No template found' });
|
|
2235
|
+
console.log(chalk.yellow(` ⚠ Skipped hooks/${hookName}.js (no template)`));
|
|
2236
|
+
}
|
|
2237
|
+
} catch (error) {
|
|
2238
|
+
failedHooks.push({ name: hookName, error: error.message });
|
|
2239
|
+
console.log(chalk.red(` ✗ Failed: hooks/${hookName}.js - ${error.message}`));
|
|
2240
|
+
}
|
|
2241
|
+
}
|
|
2242
|
+
|
|
2243
|
+
if (deployedHooks.length > 0) {
|
|
2244
|
+
console.log(chalk.green(`\n ✓ Deployed ${deployedHooks.length} feature hook(s)`));
|
|
2245
|
+
}
|
|
2246
|
+
}
|
|
2247
|
+
|
|
1963
2248
|
// Step 7: Generate INDEX.md
|
|
1964
2249
|
const indexPath = join(commandsDir, 'INDEX.md');
|
|
1965
2250
|
const indexContent = generateIndexFile(installed, projectName);
|
|
@@ -2087,6 +2372,15 @@ export async function runInit(options = {}) {
|
|
|
2087
2372
|
},
|
|
2088
2373
|
// Track which features need post-install configuration
|
|
2089
2374
|
_pendingConfiguration: featuresRequiringConfig.map((f) => f.name),
|
|
2375
|
+
// Track what was deployed for verification
|
|
2376
|
+
_deployment: {
|
|
2377
|
+
commands: installed,
|
|
2378
|
+
featureCommands: featureCommands.filter(c => installed.includes(c)),
|
|
2379
|
+
hooks: deployedHooks,
|
|
2380
|
+
featureHooks: featureHooks,
|
|
2381
|
+
enabledFeatures: selectedFeatures,
|
|
2382
|
+
timestamp: new Date().toISOString(),
|
|
2383
|
+
},
|
|
2090
2384
|
};
|
|
2091
2385
|
|
|
2092
2386
|
if (!existsSync(techStackPath)) {
|
|
@@ -243,8 +243,8 @@ export async function runTestSetup(options) {
|
|
|
243
243
|
console.log(chalk.dim(` export ${passwordVar}="your_password"`));
|
|
244
244
|
}
|
|
245
245
|
} else if (credentialSource === 'config') {
|
|
246
|
-
showWarning('
|
|
247
|
-
console.log(chalk.dim('
|
|
246
|
+
showWarning('Storing credentials in config is not recommended for security.');
|
|
247
|
+
console.log(chalk.dim('Consider using environment variables instead.'));
|
|
248
248
|
console.log('');
|
|
249
249
|
|
|
250
250
|
const { username, password } = await inquirer.prompt([
|
|
@@ -398,9 +398,9 @@ export async function runTestSetup(options) {
|
|
|
398
398
|
|
|
399
399
|
const testingConfig = createTestingConfig(config);
|
|
400
400
|
|
|
401
|
-
// Save main config
|
|
401
|
+
// Save main config to tech-stack.json
|
|
402
402
|
const configPath = saveTestingConfig(testingConfig);
|
|
403
|
-
spinner.text = 'Saved testing.json';
|
|
403
|
+
spinner.text = 'Saved testing config to tech-stack.json';
|
|
404
404
|
|
|
405
405
|
// Save testing rules markdown
|
|
406
406
|
const { generateRules } = await inquirer.prompt([
|
|
@@ -415,7 +415,7 @@ export async function runTestSetup(options) {
|
|
|
415
415
|
let rulesPath = null;
|
|
416
416
|
if (generateRules) {
|
|
417
417
|
rulesPath = saveTestingRules(testingConfig);
|
|
418
|
-
spinner.text = 'Saved TESTING_RULES.md';
|
|
418
|
+
spinner.text = 'Saved TESTING_RULES.md to .claude/task-lists/';
|
|
419
419
|
}
|
|
420
420
|
|
|
421
421
|
spinner.succeed('Configuration files created');
|
|
@@ -427,7 +427,8 @@ export async function runTestSetup(options) {
|
|
|
427
427
|
`Credentials: ${CREDENTIAL_SOURCES[config.credentialSource].name}`,
|
|
428
428
|
`Playwright: ${config.playwrightEnabled ? 'Enabled' : 'Disabled'}`,
|
|
429
429
|
'',
|
|
430
|
-
`Config: ${configPath}`,
|
|
430
|
+
`Config saved to: ${configPath}`,
|
|
431
|
+
'Testing config is now stored in tech-stack.json under the "testing" section.',
|
|
431
432
|
];
|
|
432
433
|
|
|
433
434
|
if (rulesPath) {
|