cc4pm 1.8.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/.claude-plugin/README.md +17 -0
- package/.claude-plugin/plugin.json +25 -0
- package/LICENSE +21 -0
- package/README.md +157 -0
- package/README.zh-CN.md +134 -0
- package/contexts/dev.md +20 -0
- package/contexts/research.md +26 -0
- package/contexts/review.md +22 -0
- package/examples/CLAUDE.md +100 -0
- package/examples/statusline.json +19 -0
- package/examples/user-CLAUDE.md +109 -0
- package/install.sh +17 -0
- package/manifests/install-components.json +173 -0
- package/manifests/install-modules.json +335 -0
- package/manifests/install-profiles.json +75 -0
- package/package.json +117 -0
- package/schemas/ecc-install-config.schema.json +58 -0
- package/schemas/hooks.schema.json +197 -0
- package/schemas/install-components.schema.json +56 -0
- package/schemas/install-modules.schema.json +105 -0
- package/schemas/install-profiles.schema.json +45 -0
- package/schemas/install-state.schema.json +210 -0
- package/schemas/package-manager.schema.json +23 -0
- package/schemas/plugin.schema.json +58 -0
- package/scripts/ci/catalog.js +83 -0
- package/scripts/ci/validate-agents.js +81 -0
- package/scripts/ci/validate-commands.js +135 -0
- package/scripts/ci/validate-hooks.js +239 -0
- package/scripts/ci/validate-install-manifests.js +211 -0
- package/scripts/ci/validate-no-personal-paths.js +63 -0
- package/scripts/ci/validate-rules.js +81 -0
- package/scripts/ci/validate-skills.js +54 -0
- package/scripts/claw.js +468 -0
- package/scripts/doctor.js +110 -0
- package/scripts/ecc.js +194 -0
- package/scripts/hooks/auto-tmux-dev.js +88 -0
- package/scripts/hooks/check-console-log.js +71 -0
- package/scripts/hooks/check-hook-enabled.js +12 -0
- package/scripts/hooks/cost-tracker.js +78 -0
- package/scripts/hooks/doc-file-warning.js +63 -0
- package/scripts/hooks/evaluate-session.js +100 -0
- package/scripts/hooks/insaits-security-monitor.py +269 -0
- package/scripts/hooks/insaits-security-wrapper.js +88 -0
- package/scripts/hooks/post-bash-build-complete.js +27 -0
- package/scripts/hooks/post-bash-pr-created.js +36 -0
- package/scripts/hooks/post-edit-console-warn.js +54 -0
- package/scripts/hooks/post-edit-format.js +109 -0
- package/scripts/hooks/post-edit-typecheck.js +96 -0
- package/scripts/hooks/pre-bash-dev-server-block.js +187 -0
- package/scripts/hooks/pre-bash-git-push-reminder.js +28 -0
- package/scripts/hooks/pre-bash-tmux-reminder.js +33 -0
- package/scripts/hooks/pre-compact.js +48 -0
- package/scripts/hooks/pre-write-doc-warn.js +9 -0
- package/scripts/hooks/quality-gate.js +168 -0
- package/scripts/hooks/run-with-flags-shell.sh +32 -0
- package/scripts/hooks/run-with-flags.js +120 -0
- package/scripts/hooks/session-end-marker.js +15 -0
- package/scripts/hooks/session-end.js +299 -0
- package/scripts/hooks/session-start.js +97 -0
- package/scripts/hooks/suggest-compact.js +80 -0
- package/scripts/install-apply.js +137 -0
- package/scripts/install-plan.js +254 -0
- package/scripts/lib/hook-flags.js +74 -0
- package/scripts/lib/install/apply.js +23 -0
- package/scripts/lib/install/config.js +82 -0
- package/scripts/lib/install/request.js +113 -0
- package/scripts/lib/install/runtime.js +42 -0
- package/scripts/lib/install-executor.js +605 -0
- package/scripts/lib/install-lifecycle.js +763 -0
- package/scripts/lib/install-manifests.js +305 -0
- package/scripts/lib/install-state.js +120 -0
- package/scripts/lib/install-targets/antigravity-project.js +9 -0
- package/scripts/lib/install-targets/claude-home.js +10 -0
- package/scripts/lib/install-targets/codex-home.js +10 -0
- package/scripts/lib/install-targets/cursor-project.js +10 -0
- package/scripts/lib/install-targets/helpers.js +89 -0
- package/scripts/lib/install-targets/opencode-home.js +10 -0
- package/scripts/lib/install-targets/registry.js +64 -0
- package/scripts/lib/orchestration-session.js +299 -0
- package/scripts/lib/package-manager.d.ts +119 -0
- package/scripts/lib/package-manager.js +431 -0
- package/scripts/lib/project-detect.js +428 -0
- package/scripts/lib/resolve-formatter.js +185 -0
- package/scripts/lib/session-adapters/canonical-session.js +138 -0
- package/scripts/lib/session-adapters/claude-history.js +149 -0
- package/scripts/lib/session-adapters/dmux-tmux.js +80 -0
- package/scripts/lib/session-adapters/registry.js +111 -0
- package/scripts/lib/session-aliases.d.ts +136 -0
- package/scripts/lib/session-aliases.js +481 -0
- package/scripts/lib/session-manager.d.ts +131 -0
- package/scripts/lib/session-manager.js +464 -0
- package/scripts/lib/shell-split.js +86 -0
- package/scripts/lib/skill-improvement/amendify.js +89 -0
- package/scripts/lib/skill-improvement/evaluate.js +59 -0
- package/scripts/lib/skill-improvement/health.js +118 -0
- package/scripts/lib/skill-improvement/observations.js +108 -0
- package/scripts/lib/tmux-worktree-orchestrator.js +491 -0
- package/scripts/lib/utils.d.ts +183 -0
- package/scripts/lib/utils.js +543 -0
- package/scripts/list-installed.js +90 -0
- package/scripts/orchestrate-codex-worker.sh +92 -0
- package/scripts/orchestrate-worktrees.js +108 -0
- package/scripts/orchestration-status.js +62 -0
- package/scripts/repair.js +97 -0
- package/scripts/session-inspect.js +150 -0
- package/scripts/setup-package-manager.js +204 -0
- package/scripts/skill-create-output.js +244 -0
- package/scripts/uninstall.js +96 -0
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Inspect selective-install profiles and module plans without mutating targets.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const {
|
|
7
|
+
listInstallComponents,
|
|
8
|
+
listInstallModules,
|
|
9
|
+
listInstallProfiles,
|
|
10
|
+
resolveInstallPlan,
|
|
11
|
+
} = require('./lib/install-manifests');
|
|
12
|
+
const { loadInstallConfig } = require('./lib/install/config');
|
|
13
|
+
const { normalizeInstallRequest } = require('./lib/install/request');
|
|
14
|
+
|
|
15
|
+
function showHelp() {
|
|
16
|
+
console.log(`
|
|
17
|
+
Inspect cc4pm selective-install manifests
|
|
18
|
+
|
|
19
|
+
Usage:
|
|
20
|
+
node scripts/install-plan.js --list-profiles
|
|
21
|
+
node scripts/install-plan.js --list-modules
|
|
22
|
+
node scripts/install-plan.js --list-components [--family <family>] [--target <target>] [--json]
|
|
23
|
+
node scripts/install-plan.js --profile <name> [--with <component>]... [--without <component>]... [--target <target>] [--json]
|
|
24
|
+
node scripts/install-plan.js --modules <id,id,...> [--with <component>]... [--without <component>]... [--target <target>] [--json]
|
|
25
|
+
node scripts/install-plan.js --config <path> [--json]
|
|
26
|
+
|
|
27
|
+
Options:
|
|
28
|
+
--list-profiles List available install profiles
|
|
29
|
+
--list-modules List install modules
|
|
30
|
+
--list-components List user-facing install components
|
|
31
|
+
--family <family> Filter listed components by family
|
|
32
|
+
--profile <name> Resolve an install profile
|
|
33
|
+
--modules <ids> Resolve explicit module IDs (comma-separated)
|
|
34
|
+
--with <component> Include a user-facing install component
|
|
35
|
+
--without <component>
|
|
36
|
+
Exclude a user-facing install component
|
|
37
|
+
--config <path> Load install intent from ecc-install.json
|
|
38
|
+
--target <target> Filter plan for a specific target
|
|
39
|
+
--json Emit machine-readable JSON
|
|
40
|
+
--help Show this help text
|
|
41
|
+
`);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function parseArgs(argv) {
|
|
45
|
+
const args = argv.slice(2);
|
|
46
|
+
const parsed = {
|
|
47
|
+
json: false,
|
|
48
|
+
help: false,
|
|
49
|
+
profileId: null,
|
|
50
|
+
moduleIds: [],
|
|
51
|
+
includeComponentIds: [],
|
|
52
|
+
excludeComponentIds: [],
|
|
53
|
+
configPath: null,
|
|
54
|
+
target: null,
|
|
55
|
+
family: null,
|
|
56
|
+
listProfiles: false,
|
|
57
|
+
listModules: false,
|
|
58
|
+
listComponents: false,
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
62
|
+
const arg = args[index];
|
|
63
|
+
if (arg === '--help' || arg === '-h') {
|
|
64
|
+
parsed.help = true;
|
|
65
|
+
} else if (arg === '--json') {
|
|
66
|
+
parsed.json = true;
|
|
67
|
+
} else if (arg === '--list-profiles') {
|
|
68
|
+
parsed.listProfiles = true;
|
|
69
|
+
} else if (arg === '--list-modules') {
|
|
70
|
+
parsed.listModules = true;
|
|
71
|
+
} else if (arg === '--list-components') {
|
|
72
|
+
parsed.listComponents = true;
|
|
73
|
+
} else if (arg === '--family') {
|
|
74
|
+
parsed.family = args[index + 1] || null;
|
|
75
|
+
index += 1;
|
|
76
|
+
} else if (arg === '--profile') {
|
|
77
|
+
parsed.profileId = args[index + 1] || null;
|
|
78
|
+
index += 1;
|
|
79
|
+
} else if (arg === '--modules') {
|
|
80
|
+
const raw = args[index + 1] || '';
|
|
81
|
+
parsed.moduleIds = raw.split(',').map(value => value.trim()).filter(Boolean);
|
|
82
|
+
index += 1;
|
|
83
|
+
} else if (arg === '--with') {
|
|
84
|
+
const componentId = args[index + 1] || '';
|
|
85
|
+
if (componentId.trim()) {
|
|
86
|
+
parsed.includeComponentIds.push(componentId.trim());
|
|
87
|
+
}
|
|
88
|
+
index += 1;
|
|
89
|
+
} else if (arg === '--without') {
|
|
90
|
+
const componentId = args[index + 1] || '';
|
|
91
|
+
if (componentId.trim()) {
|
|
92
|
+
parsed.excludeComponentIds.push(componentId.trim());
|
|
93
|
+
}
|
|
94
|
+
index += 1;
|
|
95
|
+
} else if (arg === '--config') {
|
|
96
|
+
parsed.configPath = args[index + 1] || null;
|
|
97
|
+
index += 1;
|
|
98
|
+
} else if (arg === '--target') {
|
|
99
|
+
parsed.target = args[index + 1] || null;
|
|
100
|
+
index += 1;
|
|
101
|
+
} else {
|
|
102
|
+
throw new Error(`Unknown argument: ${arg}`);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return parsed;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function printProfiles(profiles) {
|
|
110
|
+
console.log('Install profiles:\n');
|
|
111
|
+
for (const profile of profiles) {
|
|
112
|
+
console.log(`- ${profile.id} (${profile.moduleCount} modules)`);
|
|
113
|
+
console.log(` ${profile.description}`);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function printModules(modules) {
|
|
118
|
+
console.log('Install modules:\n');
|
|
119
|
+
for (const module of modules) {
|
|
120
|
+
console.log(`- ${module.id} [${module.kind}]`);
|
|
121
|
+
console.log(
|
|
122
|
+
` targets=${module.targets.join(', ')} default=${module.defaultInstall} cost=${module.cost} stability=${module.stability}`
|
|
123
|
+
);
|
|
124
|
+
console.log(` ${module.description}`);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function printComponents(components) {
|
|
129
|
+
console.log('Install components:\n');
|
|
130
|
+
for (const component of components) {
|
|
131
|
+
console.log(`- ${component.id} [${component.family}]`);
|
|
132
|
+
console.log(` targets=${component.targets.join(', ')} modules=${component.moduleIds.join(', ')}`);
|
|
133
|
+
console.log(` ${component.description}`);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function printPlan(plan) {
|
|
138
|
+
console.log('Install plan:\n');
|
|
139
|
+
console.log(
|
|
140
|
+
'Note: target filtering and operation output currently reflect scaffold-level adapter planning, not a byte-for-byte mirror of legacy install.sh copy paths.\n'
|
|
141
|
+
);
|
|
142
|
+
console.log(`Profile: ${plan.profileId || '(custom modules)'}`);
|
|
143
|
+
console.log(`Target: ${plan.target || '(all targets)'}`);
|
|
144
|
+
console.log(`Included components: ${plan.includedComponentIds.join(', ') || '(none)'}`);
|
|
145
|
+
console.log(`Excluded components: ${plan.excludedComponentIds.join(', ') || '(none)'}`);
|
|
146
|
+
console.log(`Requested: ${plan.requestedModuleIds.join(', ')}`);
|
|
147
|
+
if (plan.targetAdapterId) {
|
|
148
|
+
console.log(`Adapter: ${plan.targetAdapterId}`);
|
|
149
|
+
console.log(`Target root: ${plan.targetRoot}`);
|
|
150
|
+
console.log(`Install-state: ${plan.installStatePath}`);
|
|
151
|
+
}
|
|
152
|
+
console.log('');
|
|
153
|
+
console.log(`Selected modules (${plan.selectedModuleIds.length}):`);
|
|
154
|
+
for (const module of plan.selectedModules) {
|
|
155
|
+
console.log(`- ${module.id} [${module.kind}]`);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (plan.skippedModuleIds.length > 0) {
|
|
159
|
+
console.log('');
|
|
160
|
+
console.log(`Skipped for target ${plan.target} (${plan.skippedModuleIds.length}):`);
|
|
161
|
+
for (const module of plan.skippedModules) {
|
|
162
|
+
console.log(`- ${module.id} [${module.kind}]`);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (plan.excludedModuleIds.length > 0) {
|
|
167
|
+
console.log('');
|
|
168
|
+
console.log(`Excluded by selection (${plan.excludedModuleIds.length}):`);
|
|
169
|
+
for (const module of plan.excludedModules) {
|
|
170
|
+
console.log(`- ${module.id} [${module.kind}]`);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
if (plan.operations.length > 0) {
|
|
175
|
+
console.log('');
|
|
176
|
+
console.log(`Operation plan (${plan.operations.length}):`);
|
|
177
|
+
for (const operation of plan.operations) {
|
|
178
|
+
console.log(
|
|
179
|
+
`- ${operation.moduleId}: ${operation.sourceRelativePath} -> ${operation.destinationPath} [${operation.strategy}]`
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function main() {
|
|
186
|
+
try {
|
|
187
|
+
const options = parseArgs(process.argv);
|
|
188
|
+
|
|
189
|
+
if (options.help || process.argv.length <= 2) {
|
|
190
|
+
showHelp();
|
|
191
|
+
process.exit(0);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
if (options.listProfiles) {
|
|
195
|
+
const profiles = listInstallProfiles();
|
|
196
|
+
if (options.json) {
|
|
197
|
+
console.log(JSON.stringify({ profiles }, null, 2));
|
|
198
|
+
} else {
|
|
199
|
+
printProfiles(profiles);
|
|
200
|
+
}
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
if (options.listModules) {
|
|
205
|
+
const modules = listInstallModules();
|
|
206
|
+
if (options.json) {
|
|
207
|
+
console.log(JSON.stringify({ modules }, null, 2));
|
|
208
|
+
} else {
|
|
209
|
+
printModules(modules);
|
|
210
|
+
}
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
if (options.listComponents) {
|
|
215
|
+
const components = listInstallComponents({
|
|
216
|
+
family: options.family,
|
|
217
|
+
target: options.target,
|
|
218
|
+
});
|
|
219
|
+
if (options.json) {
|
|
220
|
+
console.log(JSON.stringify({ components }, null, 2));
|
|
221
|
+
} else {
|
|
222
|
+
printComponents(components);
|
|
223
|
+
}
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const config = options.configPath
|
|
228
|
+
? loadInstallConfig(options.configPath, { cwd: process.cwd() })
|
|
229
|
+
: null;
|
|
230
|
+
const request = normalizeInstallRequest({
|
|
231
|
+
...options,
|
|
232
|
+
languages: [],
|
|
233
|
+
config,
|
|
234
|
+
});
|
|
235
|
+
const plan = resolveInstallPlan({
|
|
236
|
+
profileId: request.profileId,
|
|
237
|
+
moduleIds: request.moduleIds,
|
|
238
|
+
includeComponentIds: request.includeComponentIds,
|
|
239
|
+
excludeComponentIds: request.excludeComponentIds,
|
|
240
|
+
target: request.target,
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
if (options.json) {
|
|
244
|
+
console.log(JSON.stringify(plan, null, 2));
|
|
245
|
+
} else {
|
|
246
|
+
printPlan(plan);
|
|
247
|
+
}
|
|
248
|
+
} catch (error) {
|
|
249
|
+
console.error(`Error: ${error.message}`);
|
|
250
|
+
process.exit(1);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
main();
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Shared hook enable/disable controls.
|
|
4
|
+
*
|
|
5
|
+
* Controls:
|
|
6
|
+
* - ECC_HOOK_PROFILE=minimal|standard|strict (default: standard)
|
|
7
|
+
* - ECC_DISABLED_HOOKS=comma,separated,hook,ids
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
'use strict';
|
|
11
|
+
|
|
12
|
+
const VALID_PROFILES = new Set(['minimal', 'standard', 'strict']);
|
|
13
|
+
|
|
14
|
+
function normalizeId(value) {
|
|
15
|
+
return String(value || '').trim().toLowerCase();
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function getHookProfile() {
|
|
19
|
+
const raw = String(process.env.ECC_HOOK_PROFILE || 'standard').trim().toLowerCase();
|
|
20
|
+
return VALID_PROFILES.has(raw) ? raw : 'standard';
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function getDisabledHookIds() {
|
|
24
|
+
const raw = String(process.env.ECC_DISABLED_HOOKS || '');
|
|
25
|
+
if (!raw.trim()) return new Set();
|
|
26
|
+
|
|
27
|
+
return new Set(
|
|
28
|
+
raw
|
|
29
|
+
.split(',')
|
|
30
|
+
.map(v => normalizeId(v))
|
|
31
|
+
.filter(Boolean)
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function parseProfiles(rawProfiles, fallback = ['standard', 'strict']) {
|
|
36
|
+
if (!rawProfiles) return [...fallback];
|
|
37
|
+
|
|
38
|
+
if (Array.isArray(rawProfiles)) {
|
|
39
|
+
const parsed = rawProfiles
|
|
40
|
+
.map(v => String(v || '').trim().toLowerCase())
|
|
41
|
+
.filter(v => VALID_PROFILES.has(v));
|
|
42
|
+
return parsed.length > 0 ? parsed : [...fallback];
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const parsed = String(rawProfiles)
|
|
46
|
+
.split(',')
|
|
47
|
+
.map(v => v.trim().toLowerCase())
|
|
48
|
+
.filter(v => VALID_PROFILES.has(v));
|
|
49
|
+
|
|
50
|
+
return parsed.length > 0 ? parsed : [...fallback];
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function isHookEnabled(hookId, options = {}) {
|
|
54
|
+
const id = normalizeId(hookId);
|
|
55
|
+
if (!id) return true;
|
|
56
|
+
|
|
57
|
+
const disabled = getDisabledHookIds();
|
|
58
|
+
if (disabled.has(id)) {
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const profile = getHookProfile();
|
|
63
|
+
const allowedProfiles = parseProfiles(options.profiles);
|
|
64
|
+
return allowedProfiles.includes(profile);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
module.exports = {
|
|
68
|
+
VALID_PROFILES,
|
|
69
|
+
normalizeId,
|
|
70
|
+
getHookProfile,
|
|
71
|
+
getDisabledHookIds,
|
|
72
|
+
parseProfiles,
|
|
73
|
+
isHookEnabled,
|
|
74
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
|
|
5
|
+
const { writeInstallState } = require('../install-state');
|
|
6
|
+
|
|
7
|
+
function applyInstallPlan(plan) {
|
|
8
|
+
for (const operation of plan.operations) {
|
|
9
|
+
fs.mkdirSync(require('path').dirname(operation.destinationPath), { recursive: true });
|
|
10
|
+
fs.copyFileSync(operation.sourcePath, operation.destinationPath);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
writeInstallState(plan.installStatePath, plan.statePreview);
|
|
14
|
+
|
|
15
|
+
return {
|
|
16
|
+
...plan,
|
|
17
|
+
applied: true,
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
module.exports = {
|
|
22
|
+
applyInstallPlan,
|
|
23
|
+
};
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const Ajv = require('ajv');
|
|
6
|
+
|
|
7
|
+
const DEFAULT_INSTALL_CONFIG = 'ecc-install.json';
|
|
8
|
+
const CONFIG_SCHEMA_PATH = path.join(__dirname, '..', '..', '..', 'schemas', 'ecc-install-config.schema.json');
|
|
9
|
+
|
|
10
|
+
let cachedValidator = null;
|
|
11
|
+
|
|
12
|
+
function readJson(filePath, label) {
|
|
13
|
+
try {
|
|
14
|
+
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
15
|
+
} catch (error) {
|
|
16
|
+
throw new Error(`Invalid JSON in ${label}: ${error.message}`);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function getValidator() {
|
|
21
|
+
if (cachedValidator) {
|
|
22
|
+
return cachedValidator;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const schema = readJson(CONFIG_SCHEMA_PATH, 'ecc-install-config.schema.json');
|
|
26
|
+
const ajv = new Ajv({ allErrors: true });
|
|
27
|
+
cachedValidator = ajv.compile(schema);
|
|
28
|
+
return cachedValidator;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function dedupeStrings(values) {
|
|
32
|
+
return [...new Set((Array.isArray(values) ? values : []).map(value => String(value).trim()).filter(Boolean))];
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function formatValidationErrors(errors = []) {
|
|
36
|
+
return errors.map(error => `${error.instancePath || '/'} ${error.message}`).join('; ');
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function resolveInstallConfigPath(configPath, options = {}) {
|
|
40
|
+
if (!configPath) {
|
|
41
|
+
throw new Error('An install config path is required');
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const cwd = options.cwd || process.cwd();
|
|
45
|
+
return path.isAbsolute(configPath)
|
|
46
|
+
? configPath
|
|
47
|
+
: path.resolve(cwd, configPath);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function loadInstallConfig(configPath, options = {}) {
|
|
51
|
+
const resolvedPath = resolveInstallConfigPath(configPath, options);
|
|
52
|
+
|
|
53
|
+
if (!fs.existsSync(resolvedPath)) {
|
|
54
|
+
throw new Error(`Install config not found: ${resolvedPath}`);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const raw = readJson(resolvedPath, path.basename(resolvedPath));
|
|
58
|
+
const validator = getValidator();
|
|
59
|
+
|
|
60
|
+
if (!validator(raw)) {
|
|
61
|
+
throw new Error(
|
|
62
|
+
`Invalid install config ${resolvedPath}: ${formatValidationErrors(validator.errors)}`
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return {
|
|
67
|
+
path: resolvedPath,
|
|
68
|
+
version: raw.version,
|
|
69
|
+
target: raw.target || null,
|
|
70
|
+
profileId: raw.profile || null,
|
|
71
|
+
moduleIds: dedupeStrings(raw.modules),
|
|
72
|
+
includeComponentIds: dedupeStrings(raw.include),
|
|
73
|
+
excludeComponentIds: dedupeStrings(raw.exclude),
|
|
74
|
+
options: raw.options && typeof raw.options === 'object' ? { ...raw.options } : {},
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
module.exports = {
|
|
79
|
+
DEFAULT_INSTALL_CONFIG,
|
|
80
|
+
loadInstallConfig,
|
|
81
|
+
resolveInstallConfigPath,
|
|
82
|
+
};
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const LEGACY_INSTALL_TARGETS = ['claude', 'cursor', 'antigravity'];
|
|
4
|
+
|
|
5
|
+
function dedupeStrings(values) {
|
|
6
|
+
return [...new Set((Array.isArray(values) ? values : []).map(value => String(value).trim()).filter(Boolean))];
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function parseInstallArgs(argv) {
|
|
10
|
+
const args = argv.slice(2);
|
|
11
|
+
const parsed = {
|
|
12
|
+
target: null,
|
|
13
|
+
dryRun: false,
|
|
14
|
+
json: false,
|
|
15
|
+
help: false,
|
|
16
|
+
configPath: null,
|
|
17
|
+
profileId: null,
|
|
18
|
+
moduleIds: [],
|
|
19
|
+
includeComponentIds: [],
|
|
20
|
+
excludeComponentIds: [],
|
|
21
|
+
languages: [],
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
25
|
+
const arg = args[index];
|
|
26
|
+
|
|
27
|
+
if (arg === '--target') {
|
|
28
|
+
parsed.target = args[index + 1] || null;
|
|
29
|
+
index += 1;
|
|
30
|
+
} else if (arg === '--config') {
|
|
31
|
+
parsed.configPath = args[index + 1] || null;
|
|
32
|
+
index += 1;
|
|
33
|
+
} else if (arg === '--profile') {
|
|
34
|
+
parsed.profileId = args[index + 1] || null;
|
|
35
|
+
index += 1;
|
|
36
|
+
} else if (arg === '--modules') {
|
|
37
|
+
const raw = args[index + 1] || '';
|
|
38
|
+
parsed.moduleIds = raw.split(',').map(value => value.trim()).filter(Boolean);
|
|
39
|
+
index += 1;
|
|
40
|
+
} else if (arg === '--with') {
|
|
41
|
+
const componentId = args[index + 1] || '';
|
|
42
|
+
if (componentId.trim()) {
|
|
43
|
+
parsed.includeComponentIds.push(componentId.trim());
|
|
44
|
+
}
|
|
45
|
+
index += 1;
|
|
46
|
+
} else if (arg === '--without') {
|
|
47
|
+
const componentId = args[index + 1] || '';
|
|
48
|
+
if (componentId.trim()) {
|
|
49
|
+
parsed.excludeComponentIds.push(componentId.trim());
|
|
50
|
+
}
|
|
51
|
+
index += 1;
|
|
52
|
+
} else if (arg === '--dry-run') {
|
|
53
|
+
parsed.dryRun = true;
|
|
54
|
+
} else if (arg === '--json') {
|
|
55
|
+
parsed.json = true;
|
|
56
|
+
} else if (arg === '--help' || arg === '-h') {
|
|
57
|
+
parsed.help = true;
|
|
58
|
+
} else if (arg.startsWith('--')) {
|
|
59
|
+
throw new Error(`Unknown argument: ${arg}`);
|
|
60
|
+
} else {
|
|
61
|
+
parsed.languages.push(arg);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return parsed;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function normalizeInstallRequest(options = {}) {
|
|
69
|
+
const config = options.config && typeof options.config === 'object'
|
|
70
|
+
? options.config
|
|
71
|
+
: null;
|
|
72
|
+
const profileId = options.profileId || config?.profileId || null;
|
|
73
|
+
const moduleIds = dedupeStrings([...(config?.moduleIds || []), ...(options.moduleIds || [])]);
|
|
74
|
+
const includeComponentIds = dedupeStrings([
|
|
75
|
+
...(config?.includeComponentIds || []),
|
|
76
|
+
...(options.includeComponentIds || []),
|
|
77
|
+
]);
|
|
78
|
+
const excludeComponentIds = dedupeStrings([
|
|
79
|
+
...(config?.excludeComponentIds || []),
|
|
80
|
+
...(options.excludeComponentIds || []),
|
|
81
|
+
]);
|
|
82
|
+
const languages = Array.isArray(options.languages) ? [...options.languages] : [];
|
|
83
|
+
const target = options.target || config?.target || 'claude';
|
|
84
|
+
const hasManifestBaseSelection = Boolean(profileId) || moduleIds.length > 0 || includeComponentIds.length > 0;
|
|
85
|
+
const usingManifestMode = hasManifestBaseSelection || excludeComponentIds.length > 0;
|
|
86
|
+
|
|
87
|
+
if (usingManifestMode && languages.length > 0) {
|
|
88
|
+
throw new Error(
|
|
89
|
+
'Legacy language arguments cannot be combined with --profile, --modules, --with, --without, or manifest config selections'
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (!options.help && !hasManifestBaseSelection && languages.length === 0) {
|
|
94
|
+
throw new Error('No install profile, module IDs, included components, or legacy languages were provided');
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return {
|
|
98
|
+
mode: usingManifestMode ? 'manifest' : 'legacy',
|
|
99
|
+
target,
|
|
100
|
+
profileId,
|
|
101
|
+
moduleIds,
|
|
102
|
+
includeComponentIds,
|
|
103
|
+
excludeComponentIds,
|
|
104
|
+
languages,
|
|
105
|
+
configPath: config?.path || options.configPath || null,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
module.exports = {
|
|
110
|
+
LEGACY_INSTALL_TARGETS,
|
|
111
|
+
normalizeInstallRequest,
|
|
112
|
+
parseInstallArgs,
|
|
113
|
+
};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const {
|
|
4
|
+
createLegacyInstallPlan,
|
|
5
|
+
createManifestInstallPlan,
|
|
6
|
+
} = require('../install-executor');
|
|
7
|
+
|
|
8
|
+
function createInstallPlanFromRequest(request, options = {}) {
|
|
9
|
+
if (!request || typeof request !== 'object') {
|
|
10
|
+
throw new Error('A normalized install request is required');
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
if (request.mode === 'manifest') {
|
|
14
|
+
return createManifestInstallPlan({
|
|
15
|
+
target: request.target,
|
|
16
|
+
profileId: request.profileId,
|
|
17
|
+
moduleIds: request.moduleIds,
|
|
18
|
+
includeComponentIds: request.includeComponentIds,
|
|
19
|
+
excludeComponentIds: request.excludeComponentIds,
|
|
20
|
+
projectRoot: options.projectRoot,
|
|
21
|
+
homeDir: options.homeDir,
|
|
22
|
+
sourceRoot: options.sourceRoot,
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (request.mode === 'legacy') {
|
|
27
|
+
return createLegacyInstallPlan({
|
|
28
|
+
target: request.target,
|
|
29
|
+
languages: request.languages,
|
|
30
|
+
projectRoot: options.projectRoot,
|
|
31
|
+
homeDir: options.homeDir,
|
|
32
|
+
claudeRulesDir: options.claudeRulesDir,
|
|
33
|
+
sourceRoot: options.sourceRoot,
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
throw new Error(`Unsupported install request mode: ${request.mode}`);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
module.exports = {
|
|
41
|
+
createInstallPlanFromRequest,
|
|
42
|
+
};
|