@cregis-dev/cckit 0.6.5 → 0.6.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/README.md +221 -221
  2. package/package.json +1 -1
  3. package/registry.json +145 -128
  4. package/src/cli.js +79 -79
  5. package/src/commands/init.js +174 -161
  6. package/src/commands/status.js +125 -85
  7. package/src/commands/update.js +192 -151
  8. package/src/core/config.js +82 -74
  9. package/src/core/orchestrator.js +79 -79
  10. package/src/core/registry.js +60 -60
  11. package/src/steps/add-plugin.js +148 -0
  12. package/src/steps/configure-user.js +181 -181
  13. package/src/steps/enable-plugins.js +97 -97
  14. package/src/steps/install-bmad.js +85 -85
  15. package/src/steps/install-mcp.js +70 -70
  16. package/src/steps/install-rules.js +69 -69
  17. package/src/steps/install-skills.js +56 -56
  18. package/src/utils/compare-versions.js +106 -0
  19. package/src/utils/fs.js +33 -33
  20. package/src/utils/manifest.js +101 -99
  21. package/src/utils/prompt.js +41 -41
  22. package/templates/mcp/claude-code/.mcp.json +40 -40
  23. package/templates/rules/README.md +103 -103
  24. package/templates/rules/common/agents.md +49 -49
  25. package/templates/rules/common/coding-style.md +48 -48
  26. package/templates/rules/common/development-workflow.md +37 -37
  27. package/templates/rules/common/git-workflow.md +24 -24
  28. package/templates/rules/common/hooks.md +30 -30
  29. package/templates/rules/common/patterns.md +31 -31
  30. package/templates/rules/common/performance.md +55 -55
  31. package/templates/rules/common/security.md +29 -29
  32. package/templates/rules/common/testing.md +29 -29
  33. package/templates/rules/golang/coding-style.md +32 -32
  34. package/templates/rules/golang/hooks.md +17 -17
  35. package/templates/rules/golang/patterns.md +45 -45
  36. package/templates/rules/golang/security.md +34 -34
  37. package/templates/rules/golang/testing.md +31 -31
  38. package/templates/rules/python/coding-style.md +42 -42
  39. package/templates/rules/python/hooks.md +19 -19
  40. package/templates/rules/python/patterns.md +39 -39
  41. package/templates/rules/python/security.md +30 -30
  42. package/templates/rules/python/testing.md +38 -38
  43. package/templates/rules/swift/coding-style.md +47 -47
  44. package/templates/rules/swift/hooks.md +20 -20
  45. package/templates/rules/swift/patterns.md +66 -66
  46. package/templates/rules/swift/security.md +33 -33
  47. package/templates/rules/swift/testing.md +45 -45
  48. package/templates/rules/typescript/coding-style.md +65 -65
  49. package/templates/rules/typescript/hooks.md +22 -22
  50. package/templates/rules/typescript/patterns.md +52 -52
  51. package/templates/rules/typescript/security.md +28 -28
  52. package/templates/rules/typescript/testing.md +18 -18
@@ -1,79 +1,79 @@
1
- /**
2
- * Orchestrator — executes installation steps in sequence
3
- * and collects results into a unified RunReport.
4
- *
5
- * Each step is independently retriable; the orchestrator
6
- * never throws — it captures failures in step results.
7
- */
8
-
9
- /**
10
- * @typedef {object} StepResult
11
- * @property {string} stepId - Stable identifier
12
- * @property {string} name - Human-readable label
13
- * @property {boolean} success
14
- * @property {boolean} skipped
15
- * @property {object} details - Step-specific metadata
16
- * @property {string} [error] - Error message if !success
17
- */
18
-
19
- /**
20
- * @typedef {object} StepSpec
21
- * @property {string} id
22
- * @property {Function} fn - async (opts, logger, _deps?) => StepResult
23
- * @property {object} opts - Step-specific options
24
- */
25
-
26
- /**
27
- * @typedef {object} RunReport
28
- * @property {StepResult[]} steps
29
- * @property {boolean} success - True if all non-skipped steps succeeded
30
- * @property {object} fileRegistry - Hash registry (currently MCP only)
31
- */
32
-
33
- /**
34
- * Execute steps sequentially, collecting results.
35
- *
36
- * @param {StepSpec[]} steps - Ordered step specifications
37
- * @param {object} logger - Logger instance
38
- * @returns {Promise<RunReport>}
39
- */
40
- export async function orchestrate(steps, logger) {
41
- const results = []
42
- const fileRegistry = {}
43
-
44
- for (let i = 0; i < steps.length; i++) {
45
- const { id, fn, opts } = steps[i]
46
- logger.step(i + 1, steps.length, id)
47
-
48
- try {
49
- const result = await fn(opts, logger)
50
- results.push(result)
51
-
52
- // Collect file hashes from steps that produce them
53
- if (result.details?.hash && result.details?.target) {
54
- fileRegistry[result.details.target] = result.details.hash
55
- }
56
-
57
- if (result.skipped) {
58
- logger.debug(` Skipped: ${id}`)
59
- } else if (result.success) {
60
- logger.success(result.name || id)
61
- } else {
62
- logger.warn(`${result.name || id}: ${result.error || 'completed with errors'}`)
63
- }
64
- } catch (err) {
65
- logger.error(`${id} threw unexpectedly: ${err.message}`)
66
- results.push({
67
- stepId: id,
68
- name: id,
69
- success: false,
70
- skipped: false,
71
- details: {},
72
- error: err.message,
73
- })
74
- }
75
- }
76
-
77
- const success = results.every(r => r.skipped || r.success)
78
- return { steps: results, success, fileRegistry }
79
- }
1
+ /**
2
+ * Orchestrator — executes installation steps in sequence
3
+ * and collects results into a unified RunReport.
4
+ *
5
+ * Each step is independently retriable; the orchestrator
6
+ * never throws — it captures failures in step results.
7
+ */
8
+
9
+ /**
10
+ * @typedef {object} StepResult
11
+ * @property {string} stepId - Stable identifier
12
+ * @property {string} name - Human-readable label
13
+ * @property {boolean} success
14
+ * @property {boolean} skipped
15
+ * @property {object} details - Step-specific metadata
16
+ * @property {string} [error] - Error message if !success
17
+ */
18
+
19
+ /**
20
+ * @typedef {object} StepSpec
21
+ * @property {string} id
22
+ * @property {Function} fn - async (opts, logger, _deps?) => StepResult
23
+ * @property {object} opts - Step-specific options
24
+ */
25
+
26
+ /**
27
+ * @typedef {object} RunReport
28
+ * @property {StepResult[]} steps
29
+ * @property {boolean} success - True if all non-skipped steps succeeded
30
+ * @property {object} fileRegistry - Hash registry (currently MCP only)
31
+ */
32
+
33
+ /**
34
+ * Execute steps sequentially, collecting results.
35
+ *
36
+ * @param {StepSpec[]} steps - Ordered step specifications
37
+ * @param {object} logger - Logger instance
38
+ * @returns {Promise<RunReport>}
39
+ */
40
+ export async function orchestrate(steps, logger) {
41
+ const results = []
42
+ const fileRegistry = {}
43
+
44
+ for (let i = 0; i < steps.length; i++) {
45
+ const { id, fn, opts } = steps[i]
46
+ logger.step(i + 1, steps.length, id)
47
+
48
+ try {
49
+ const result = await fn(opts, logger)
50
+ results.push(result)
51
+
52
+ // Collect file hashes from steps that produce them
53
+ if (result.details?.hash && result.details?.target) {
54
+ fileRegistry[result.details.target] = result.details.hash
55
+ }
56
+
57
+ if (result.skipped) {
58
+ logger.debug(` Skipped: ${id}`)
59
+ } else if (result.success) {
60
+ logger.success(result.name || id)
61
+ } else {
62
+ logger.warn(`${result.name || id}: ${result.error || 'completed with errors'}`)
63
+ }
64
+ } catch (err) {
65
+ logger.error(`${id} threw unexpectedly: ${err.message}`)
66
+ results.push({
67
+ stepId: id,
68
+ name: id,
69
+ success: false,
70
+ skipped: false,
71
+ details: {},
72
+ error: err.message,
73
+ })
74
+ }
75
+ }
76
+
77
+ const success = results.every(r => r.skipped || r.success)
78
+ return { steps: results, success, fileRegistry }
79
+ }
@@ -1,60 +1,60 @@
1
- import fse from 'fs-extra'
2
- import path from 'node:path'
3
- import { fileURLToPath } from 'node:url'
4
-
5
- const __dirname = path.dirname(fileURLToPath(import.meta.url))
6
-
7
- /**
8
- * Load and parse registry.json from disk.
9
- *
10
- * @param {string} [customPath] - Override path to registry.json
11
- * @returns {Promise<object>} Parsed registry object
12
- */
13
- export async function loadRegistry(customPath) {
14
- const registryPath = customPath || path.resolve(__dirname, '../../registry.json')
15
- const content = await fse.readFile(registryPath, 'utf8')
16
- return JSON.parse(content)
17
- }
18
-
19
- /**
20
- * Get IDs of all plugins marked as default.
21
- *
22
- * @param {object} registry - Loaded registry object
23
- * @returns {string[]} Array of plugin IDs
24
- */
25
- export function getDefaultPlugins(registry) {
26
- return Object.entries(registry.plugins || {})
27
- .filter(([, def]) => def.default)
28
- .map(([id]) => id)
29
- }
30
-
31
- /**
32
- * Resolve requested plugin IDs to plugin definitions.
33
- *
34
- * @param {object} registry - Registry object
35
- * @param {object} config - Merged config (uses config.plugins)
36
- * @returns {Array<{id: string, def: object}>} Resolved plugin entries
37
- */
38
- export function resolvePlugins(registry, config) {
39
- const pluginIds = config.plugins === 'default'
40
- ? getDefaultPlugins(registry)
41
- : config.plugins === 'none'
42
- ? []
43
- : Array.isArray(config.plugins) ? config.plugins : []
44
-
45
- return pluginIds
46
- .map(id => ({ id, def: registry.plugins?.[id] }))
47
- .filter(p => p.def)
48
- }
49
-
50
- /**
51
- * Resolve a model alias to a model ID using the registry's model map.
52
- *
53
- * @param {object} userSettings - registry.userSettings
54
- * @param {string} [preferredAlias] - User-specified model alias (e.g. 'kimi')
55
- * @returns {string} Resolved model ID (e.g. 'Kimi-K2.5')
56
- */
57
- export function resolveModelId(userSettings = {}, preferredAlias) {
58
- const alias = preferredAlias || userSettings.defaultModel || 'minimax'
59
- return (userSettings.models || {})[alias] || alias
60
- }
1
+ import fse from 'fs-extra'
2
+ import path from 'node:path'
3
+ import { fileURLToPath } from 'node:url'
4
+
5
+ const __dirname = path.dirname(fileURLToPath(import.meta.url))
6
+
7
+ /**
8
+ * Load and parse registry.json from disk.
9
+ *
10
+ * @param {string} [customPath] - Override path to registry.json
11
+ * @returns {Promise<object>} Parsed registry object
12
+ */
13
+ export async function loadRegistry(customPath) {
14
+ const registryPath = customPath || path.resolve(__dirname, '../../registry.json')
15
+ const content = await fse.readFile(registryPath, 'utf8')
16
+ return JSON.parse(content)
17
+ }
18
+
19
+ /**
20
+ * Get IDs of all plugins marked as default.
21
+ *
22
+ * @param {object} registry - Loaded registry object
23
+ * @returns {string[]} Array of plugin IDs
24
+ */
25
+ export function getDefaultPlugins(registry) {
26
+ return Object.entries(registry.plugins || {})
27
+ .filter(([, def]) => def.default)
28
+ .map(([id]) => id)
29
+ }
30
+
31
+ /**
32
+ * Resolve requested plugin IDs to plugin definitions.
33
+ *
34
+ * @param {object} registry - Registry object
35
+ * @param {object} config - Merged config (uses config.plugins)
36
+ * @returns {Array<{id: string, def: object}>} Resolved plugin entries
37
+ */
38
+ export function resolvePlugins(registry, config) {
39
+ const pluginIds = config.plugins === 'default'
40
+ ? getDefaultPlugins(registry)
41
+ : config.plugins === 'none'
42
+ ? []
43
+ : Array.isArray(config.plugins) ? config.plugins : []
44
+
45
+ return pluginIds
46
+ .map(id => ({ id, def: registry.plugins?.[id] }))
47
+ .filter(p => p.def)
48
+ }
49
+
50
+ /**
51
+ * Resolve a model alias to a model ID using the registry's model map.
52
+ *
53
+ * @param {object} userSettings - registry.userSettings
54
+ * @param {string} [preferredAlias] - User-specified model alias (e.g. 'kimi')
55
+ * @returns {string} Resolved model ID (e.g. 'Kimi-K2.5')
56
+ */
57
+ export function resolveModelId(userSettings = {}, preferredAlias) {
58
+ const alias = preferredAlias || userSettings.defaultModel || 'minimax'
59
+ return (userSettings.models || {})[alias] || alias
60
+ }
@@ -0,0 +1,148 @@
1
+ /**
2
+ * Step: Install plugins using Claude Code CLI.
3
+ *
4
+ * Uses `claude plugin install <plugin>@<marketplace> --scope <scope>` commands
5
+ * to install plugins at project scope.
6
+ *
7
+ * Requires `claude` CLI to be installed. If not found, the step is skipped
8
+ * with a warning instead of failing the entire installation.
9
+ */
10
+
11
+ import { spawn, execSync } from 'node:child_process'
12
+ import path from 'node:path'
13
+ import { fileURLToPath } from 'node:url'
14
+
15
+ const __dirname = path.dirname(fileURLToPath(import.meta.url))
16
+
17
+ /**
18
+ * Check if the `claude` CLI is available on PATH.
19
+ *
20
+ * @returns {boolean}
21
+ */
22
+ function isClaudeCliAvailable() {
23
+ try {
24
+ execSync('command -v claude', { stdio: 'pipe' })
25
+ return true
26
+ } catch {
27
+ return false
28
+ }
29
+ }
30
+
31
+ /**
32
+ * @param {object} opts
33
+ * @param {string} opts.targetDir - Project root
34
+ * @param {Array<{id: string, def: object}>} opts.plugins - Resolved plugins with scope
35
+ * @param {boolean} opts.skip - Skip this step
36
+ * @param {object} logger
37
+ * @param {object} [_deps] - Injectable dependencies for testing
38
+ * @returns {Promise<import('../core/orchestrator.js').StepResult>}
39
+ */
40
+ export async function addPlugin(opts, logger, _deps = {}) {
41
+ if (opts.skip) {
42
+ return { stepId: 'add-plugin', name: 'Add Plugin (CLI)', success: true, skipped: true, details: {} }
43
+ }
44
+
45
+ // Check if claude CLI is available
46
+ const checkCli = _deps.isClaudeCliAvailable || isClaudeCliAvailable
47
+ if (!checkCli()) {
48
+ logger.warn('Claude Code CLI not found. Skipping CLI plugin installation.')
49
+ logger.warn('Install Claude Code CLI (https://docs.anthropic.com/en/docs/claude-code) to enable this step.')
50
+ return {
51
+ stepId: 'add-plugin',
52
+ name: 'Add Plugin (CLI)',
53
+ success: true,
54
+ skipped: true,
55
+ details: { reason: 'claude CLI not found' },
56
+ }
57
+ }
58
+
59
+ const projectPlugins = opts.plugins.filter(p => p.def.scope === 'project' || !p.def.scope)
60
+
61
+ if (projectPlugins.length === 0) {
62
+ logger.debug('No project-scoped plugins to install via CLI')
63
+ return {
64
+ stepId: 'add-plugin',
65
+ name: 'Add Plugin (CLI)',
66
+ success: true,
67
+ skipped: false,
68
+ details: { installed: [], errors: [] },
69
+ }
70
+ }
71
+
72
+ const installed = []
73
+ const errors = []
74
+
75
+ for (const { id, def } of projectPlugins) {
76
+ const pluginKey = `${id}@${def.marketplace}`
77
+ const scope = def.scope || 'project'
78
+
79
+ try {
80
+ logger.info(` Installing ${pluginKey} (scope: ${scope})...`)
81
+
82
+ await execCommand('claude', [
83
+ 'plugin',
84
+ 'install',
85
+ pluginKey,
86
+ '--scope',
87
+ scope,
88
+ ], logger)
89
+
90
+ installed.push({ id: pluginKey, scope })
91
+ logger.success(` ✓ ${pluginKey} installed`)
92
+ } catch (err) {
93
+ errors.push({ id: pluginKey, error: err.message })
94
+ logger.warn(` ✗ Failed to install ${pluginKey}: ${err.message}`)
95
+ }
96
+ }
97
+
98
+ return {
99
+ stepId: 'add-plugin',
100
+ name: 'Add Plugin (CLI)',
101
+ success: errors.length === 0,
102
+ skipped: false,
103
+ details: {
104
+ installed,
105
+ errors,
106
+ },
107
+ }
108
+ }
109
+
110
+ /**
111
+ * Execute a command and return a promise.
112
+ *
113
+ * @param {string} command - Command to run
114
+ * @param {string[]} args - Command arguments
115
+ * @param {object} logger - Logger instance
116
+ * @returns {Promise<void>}
117
+ */
118
+ function execCommand(command, args, logger) {
119
+ return new Promise((resolve, reject) => {
120
+ const proc = spawn(command, args, {
121
+ shell: true,
122
+ stdio: 'pipe',
123
+ })
124
+
125
+ let stdout = ''
126
+ let stderr = ''
127
+
128
+ proc.stdout.on('data', (data) => {
129
+ stdout += data.toString()
130
+ })
131
+
132
+ proc.stderr.on('data', (data) => {
133
+ stderr += data.toString()
134
+ })
135
+
136
+ proc.on('close', (code) => {
137
+ if (code === 0) {
138
+ resolve()
139
+ } else {
140
+ reject(new Error(stderr || stdout || `Exit code: ${code}`))
141
+ }
142
+ })
143
+
144
+ proc.on('error', (err) => {
145
+ reject(err)
146
+ })
147
+ })
148
+ }