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,305 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const os = require('os');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const { planInstallTargetScaffold } = require('./install-targets/registry');
|
|
5
|
+
|
|
6
|
+
const DEFAULT_REPO_ROOT = path.join(__dirname, '../..');
|
|
7
|
+
const SUPPORTED_INSTALL_TARGETS = ['claude', 'cursor', 'antigravity', 'codex', 'opencode'];
|
|
8
|
+
const COMPONENT_FAMILY_PREFIXES = {
|
|
9
|
+
baseline: 'baseline:',
|
|
10
|
+
language: 'lang:',
|
|
11
|
+
framework: 'framework:',
|
|
12
|
+
capability: 'capability:',
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
function readJson(filePath, label) {
|
|
16
|
+
try {
|
|
17
|
+
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
18
|
+
} catch (error) {
|
|
19
|
+
throw new Error(`Failed to read ${label}: ${error.message}`);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function dedupeStrings(values) {
|
|
24
|
+
return [...new Set((Array.isArray(values) ? values : []).map(value => String(value).trim()).filter(Boolean))];
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function intersectTargets(modules) {
|
|
28
|
+
if (!Array.isArray(modules) || modules.length === 0) {
|
|
29
|
+
return [];
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return SUPPORTED_INSTALL_TARGETS.filter(target => (
|
|
33
|
+
modules.every(module => Array.isArray(module.targets) && module.targets.includes(target))
|
|
34
|
+
));
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function getManifestPaths(repoRoot = DEFAULT_REPO_ROOT) {
|
|
38
|
+
return {
|
|
39
|
+
modulesPath: path.join(repoRoot, 'manifests', 'install-modules.json'),
|
|
40
|
+
profilesPath: path.join(repoRoot, 'manifests', 'install-profiles.json'),
|
|
41
|
+
componentsPath: path.join(repoRoot, 'manifests', 'install-components.json'),
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function loadInstallManifests(options = {}) {
|
|
46
|
+
const repoRoot = options.repoRoot || DEFAULT_REPO_ROOT;
|
|
47
|
+
const { modulesPath, profilesPath, componentsPath } = getManifestPaths(repoRoot);
|
|
48
|
+
|
|
49
|
+
if (!fs.existsSync(modulesPath) || !fs.existsSync(profilesPath)) {
|
|
50
|
+
throw new Error(`Install manifests not found under ${repoRoot}`);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const modulesData = readJson(modulesPath, 'install-modules.json');
|
|
54
|
+
const profilesData = readJson(profilesPath, 'install-profiles.json');
|
|
55
|
+
const componentsData = fs.existsSync(componentsPath)
|
|
56
|
+
? readJson(componentsPath, 'install-components.json')
|
|
57
|
+
: { version: null, components: [] };
|
|
58
|
+
const modules = Array.isArray(modulesData.modules) ? modulesData.modules : [];
|
|
59
|
+
const profiles = profilesData && typeof profilesData.profiles === 'object'
|
|
60
|
+
? profilesData.profiles
|
|
61
|
+
: {};
|
|
62
|
+
const components = Array.isArray(componentsData.components) ? componentsData.components : [];
|
|
63
|
+
const modulesById = new Map(modules.map(module => [module.id, module]));
|
|
64
|
+
const componentsById = new Map(components.map(component => [component.id, component]));
|
|
65
|
+
|
|
66
|
+
return {
|
|
67
|
+
repoRoot,
|
|
68
|
+
modulesPath,
|
|
69
|
+
profilesPath,
|
|
70
|
+
componentsPath,
|
|
71
|
+
modules,
|
|
72
|
+
profiles,
|
|
73
|
+
components,
|
|
74
|
+
modulesById,
|
|
75
|
+
componentsById,
|
|
76
|
+
modulesVersion: modulesData.version,
|
|
77
|
+
profilesVersion: profilesData.version,
|
|
78
|
+
componentsVersion: componentsData.version,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function listInstallProfiles(options = {}) {
|
|
83
|
+
const manifests = loadInstallManifests(options);
|
|
84
|
+
return Object.entries(manifests.profiles).map(([id, profile]) => ({
|
|
85
|
+
id,
|
|
86
|
+
description: profile.description,
|
|
87
|
+
moduleCount: Array.isArray(profile.modules) ? profile.modules.length : 0,
|
|
88
|
+
}));
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function listInstallModules(options = {}) {
|
|
92
|
+
const manifests = loadInstallManifests(options);
|
|
93
|
+
return manifests.modules.map(module => ({
|
|
94
|
+
id: module.id,
|
|
95
|
+
kind: module.kind,
|
|
96
|
+
description: module.description,
|
|
97
|
+
targets: module.targets,
|
|
98
|
+
defaultInstall: module.defaultInstall,
|
|
99
|
+
cost: module.cost,
|
|
100
|
+
stability: module.stability,
|
|
101
|
+
dependencyCount: Array.isArray(module.dependencies) ? module.dependencies.length : 0,
|
|
102
|
+
}));
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function listInstallComponents(options = {}) {
|
|
106
|
+
const manifests = loadInstallManifests(options);
|
|
107
|
+
const family = options.family || null;
|
|
108
|
+
const target = options.target || null;
|
|
109
|
+
|
|
110
|
+
if (family && !Object.hasOwn(COMPONENT_FAMILY_PREFIXES, family)) {
|
|
111
|
+
throw new Error(
|
|
112
|
+
`Unknown component family: ${family}. Expected one of ${Object.keys(COMPONENT_FAMILY_PREFIXES).join(', ')}`
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (target && !SUPPORTED_INSTALL_TARGETS.includes(target)) {
|
|
117
|
+
throw new Error(
|
|
118
|
+
`Unknown install target: ${target}. Expected one of ${SUPPORTED_INSTALL_TARGETS.join(', ')}`
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return manifests.components
|
|
123
|
+
.filter(component => !family || component.family === family)
|
|
124
|
+
.map(component => {
|
|
125
|
+
const moduleIds = dedupeStrings(component.modules);
|
|
126
|
+
const modules = moduleIds
|
|
127
|
+
.map(moduleId => manifests.modulesById.get(moduleId))
|
|
128
|
+
.filter(Boolean);
|
|
129
|
+
const targets = intersectTargets(modules);
|
|
130
|
+
|
|
131
|
+
return {
|
|
132
|
+
id: component.id,
|
|
133
|
+
family: component.family,
|
|
134
|
+
description: component.description,
|
|
135
|
+
moduleIds,
|
|
136
|
+
moduleCount: moduleIds.length,
|
|
137
|
+
targets,
|
|
138
|
+
};
|
|
139
|
+
})
|
|
140
|
+
.filter(component => !target || component.targets.includes(target));
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function expandComponentIdsToModuleIds(componentIds, manifests) {
|
|
144
|
+
const expandedModuleIds = [];
|
|
145
|
+
|
|
146
|
+
for (const componentId of dedupeStrings(componentIds)) {
|
|
147
|
+
const component = manifests.componentsById.get(componentId);
|
|
148
|
+
if (!component) {
|
|
149
|
+
throw new Error(`Unknown install component: ${componentId}`);
|
|
150
|
+
}
|
|
151
|
+
expandedModuleIds.push(...component.modules);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return dedupeStrings(expandedModuleIds);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function resolveInstallPlan(options = {}) {
|
|
158
|
+
const manifests = loadInstallManifests(options);
|
|
159
|
+
const profileId = options.profileId || null;
|
|
160
|
+
const explicitModuleIds = dedupeStrings(options.moduleIds);
|
|
161
|
+
const includedComponentIds = dedupeStrings(options.includeComponentIds);
|
|
162
|
+
const excludedComponentIds = dedupeStrings(options.excludeComponentIds);
|
|
163
|
+
const requestedModuleIds = [];
|
|
164
|
+
|
|
165
|
+
if (profileId) {
|
|
166
|
+
const profile = manifests.profiles[profileId];
|
|
167
|
+
if (!profile) {
|
|
168
|
+
throw new Error(`Unknown install profile: ${profileId}`);
|
|
169
|
+
}
|
|
170
|
+
requestedModuleIds.push(...profile.modules);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
requestedModuleIds.push(...explicitModuleIds);
|
|
174
|
+
requestedModuleIds.push(...expandComponentIdsToModuleIds(includedComponentIds, manifests));
|
|
175
|
+
|
|
176
|
+
const excludedModuleIds = expandComponentIdsToModuleIds(excludedComponentIds, manifests);
|
|
177
|
+
const excludedModuleOwners = new Map();
|
|
178
|
+
for (const componentId of excludedComponentIds) {
|
|
179
|
+
const component = manifests.componentsById.get(componentId);
|
|
180
|
+
if (!component) {
|
|
181
|
+
throw new Error(`Unknown install component: ${componentId}`);
|
|
182
|
+
}
|
|
183
|
+
for (const moduleId of component.modules) {
|
|
184
|
+
const owners = excludedModuleOwners.get(moduleId) || [];
|
|
185
|
+
owners.push(componentId);
|
|
186
|
+
excludedModuleOwners.set(moduleId, owners);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const target = options.target || null;
|
|
191
|
+
if (target && !SUPPORTED_INSTALL_TARGETS.includes(target)) {
|
|
192
|
+
throw new Error(
|
|
193
|
+
`Unknown install target: ${target}. Expected one of ${SUPPORTED_INSTALL_TARGETS.join(', ')}`
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const effectiveRequestedIds = dedupeStrings(
|
|
198
|
+
requestedModuleIds.filter(moduleId => !excludedModuleOwners.has(moduleId))
|
|
199
|
+
);
|
|
200
|
+
|
|
201
|
+
if (requestedModuleIds.length === 0) {
|
|
202
|
+
throw new Error('No install profile, module IDs, or included component IDs were provided');
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
if (effectiveRequestedIds.length === 0) {
|
|
206
|
+
throw new Error('Selection excludes every requested install module');
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const selectedIds = new Set();
|
|
210
|
+
const skippedTargetIds = new Set();
|
|
211
|
+
const excludedIds = new Set(excludedModuleIds);
|
|
212
|
+
const visitingIds = new Set();
|
|
213
|
+
const resolvedIds = new Set();
|
|
214
|
+
|
|
215
|
+
function resolveModule(moduleId, dependencyOf) {
|
|
216
|
+
const module = manifests.modulesById.get(moduleId);
|
|
217
|
+
if (!module) {
|
|
218
|
+
throw new Error(`Unknown install module: ${moduleId}`);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
if (excludedModuleOwners.has(moduleId)) {
|
|
222
|
+
if (dependencyOf) {
|
|
223
|
+
const owners = excludedModuleOwners.get(moduleId) || [];
|
|
224
|
+
throw new Error(
|
|
225
|
+
`Module ${dependencyOf} depends on excluded module ${moduleId}${owners.length > 0 ? ` (excluded by ${owners.join(', ')})` : ''}`
|
|
226
|
+
);
|
|
227
|
+
}
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
if (target && !module.targets.includes(target)) {
|
|
232
|
+
if (dependencyOf) {
|
|
233
|
+
throw new Error(
|
|
234
|
+
`Module ${dependencyOf} depends on ${moduleId}, which does not support target ${target}`
|
|
235
|
+
);
|
|
236
|
+
}
|
|
237
|
+
skippedTargetIds.add(moduleId);
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
if (resolvedIds.has(moduleId)) {
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
if (visitingIds.has(moduleId)) {
|
|
246
|
+
throw new Error(`Circular install dependency detected at ${moduleId}`);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
visitingIds.add(moduleId);
|
|
250
|
+
for (const dependencyId of module.dependencies) {
|
|
251
|
+
resolveModule(dependencyId, moduleId);
|
|
252
|
+
}
|
|
253
|
+
visitingIds.delete(moduleId);
|
|
254
|
+
resolvedIds.add(moduleId);
|
|
255
|
+
selectedIds.add(moduleId);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
for (const moduleId of effectiveRequestedIds) {
|
|
259
|
+
resolveModule(moduleId, null);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
const selectedModules = manifests.modules.filter(module => selectedIds.has(module.id));
|
|
263
|
+
const skippedModules = manifests.modules.filter(module => skippedTargetIds.has(module.id));
|
|
264
|
+
const excludedModules = manifests.modules.filter(module => excludedIds.has(module.id));
|
|
265
|
+
const scaffoldPlan = target
|
|
266
|
+
? planInstallTargetScaffold({
|
|
267
|
+
target,
|
|
268
|
+
repoRoot: manifests.repoRoot,
|
|
269
|
+
projectRoot: options.projectRoot || manifests.repoRoot,
|
|
270
|
+
homeDir: options.homeDir || os.homedir(),
|
|
271
|
+
modules: selectedModules,
|
|
272
|
+
})
|
|
273
|
+
: null;
|
|
274
|
+
|
|
275
|
+
return {
|
|
276
|
+
repoRoot: manifests.repoRoot,
|
|
277
|
+
profileId,
|
|
278
|
+
target,
|
|
279
|
+
requestedModuleIds: effectiveRequestedIds,
|
|
280
|
+
explicitModuleIds,
|
|
281
|
+
includedComponentIds,
|
|
282
|
+
excludedComponentIds,
|
|
283
|
+
selectedModuleIds: selectedModules.map(module => module.id),
|
|
284
|
+
skippedModuleIds: skippedModules.map(module => module.id),
|
|
285
|
+
excludedModuleIds: excludedModules.map(module => module.id),
|
|
286
|
+
selectedModules,
|
|
287
|
+
skippedModules,
|
|
288
|
+
excludedModules,
|
|
289
|
+
targetAdapterId: scaffoldPlan ? scaffoldPlan.adapter.id : null,
|
|
290
|
+
targetRoot: scaffoldPlan ? scaffoldPlan.targetRoot : null,
|
|
291
|
+
installStatePath: scaffoldPlan ? scaffoldPlan.installStatePath : null,
|
|
292
|
+
operations: scaffoldPlan ? scaffoldPlan.operations : [],
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
module.exports = {
|
|
297
|
+
DEFAULT_REPO_ROOT,
|
|
298
|
+
SUPPORTED_INSTALL_TARGETS,
|
|
299
|
+
getManifestPaths,
|
|
300
|
+
loadInstallManifests,
|
|
301
|
+
listInstallComponents,
|
|
302
|
+
listInstallModules,
|
|
303
|
+
listInstallProfiles,
|
|
304
|
+
resolveInstallPlan,
|
|
305
|
+
};
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const Ajv = require('ajv');
|
|
4
|
+
|
|
5
|
+
const SCHEMA_PATH = path.join(__dirname, '..', '..', 'schemas', 'install-state.schema.json');
|
|
6
|
+
|
|
7
|
+
let cachedValidator = null;
|
|
8
|
+
|
|
9
|
+
function readJson(filePath, label) {
|
|
10
|
+
try {
|
|
11
|
+
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
12
|
+
} catch (error) {
|
|
13
|
+
throw new Error(`Failed to read ${label}: ${error.message}`);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function getValidator() {
|
|
18
|
+
if (cachedValidator) {
|
|
19
|
+
return cachedValidator;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const schema = readJson(SCHEMA_PATH, 'install-state schema');
|
|
23
|
+
const ajv = new Ajv({ allErrors: true });
|
|
24
|
+
cachedValidator = ajv.compile(schema);
|
|
25
|
+
return cachedValidator;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function formatValidationErrors(errors = []) {
|
|
29
|
+
return errors
|
|
30
|
+
.map(error => `${error.instancePath || '/'} ${error.message}`)
|
|
31
|
+
.join('; ');
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function validateInstallState(state) {
|
|
35
|
+
const validator = getValidator();
|
|
36
|
+
const valid = validator(state);
|
|
37
|
+
return {
|
|
38
|
+
valid,
|
|
39
|
+
errors: validator.errors || [],
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function assertValidInstallState(state, label) {
|
|
44
|
+
const result = validateInstallState(state);
|
|
45
|
+
if (!result.valid) {
|
|
46
|
+
throw new Error(`Invalid install-state${label ? ` (${label})` : ''}: ${formatValidationErrors(result.errors)}`);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function createInstallState(options) {
|
|
51
|
+
const installedAt = options.installedAt || new Date().toISOString();
|
|
52
|
+
const state = {
|
|
53
|
+
schemaVersion: 'ecc.install.v1',
|
|
54
|
+
installedAt,
|
|
55
|
+
target: {
|
|
56
|
+
id: options.adapter.id,
|
|
57
|
+
target: options.adapter.target || undefined,
|
|
58
|
+
kind: options.adapter.kind || undefined,
|
|
59
|
+
root: options.targetRoot,
|
|
60
|
+
installStatePath: options.installStatePath,
|
|
61
|
+
},
|
|
62
|
+
request: {
|
|
63
|
+
profile: options.request.profile || null,
|
|
64
|
+
modules: Array.isArray(options.request.modules) ? [...options.request.modules] : [],
|
|
65
|
+
includeComponents: Array.isArray(options.request.includeComponents)
|
|
66
|
+
? [...options.request.includeComponents]
|
|
67
|
+
: [],
|
|
68
|
+
excludeComponents: Array.isArray(options.request.excludeComponents)
|
|
69
|
+
? [...options.request.excludeComponents]
|
|
70
|
+
: [],
|
|
71
|
+
legacyLanguages: Array.isArray(options.request.legacyLanguages)
|
|
72
|
+
? [...options.request.legacyLanguages]
|
|
73
|
+
: [],
|
|
74
|
+
legacyMode: Boolean(options.request.legacyMode),
|
|
75
|
+
},
|
|
76
|
+
resolution: {
|
|
77
|
+
selectedModules: Array.isArray(options.resolution.selectedModules)
|
|
78
|
+
? [...options.resolution.selectedModules]
|
|
79
|
+
: [],
|
|
80
|
+
skippedModules: Array.isArray(options.resolution.skippedModules)
|
|
81
|
+
? [...options.resolution.skippedModules]
|
|
82
|
+
: [],
|
|
83
|
+
},
|
|
84
|
+
source: {
|
|
85
|
+
repoVersion: options.source.repoVersion || null,
|
|
86
|
+
repoCommit: options.source.repoCommit || null,
|
|
87
|
+
manifestVersion: options.source.manifestVersion,
|
|
88
|
+
},
|
|
89
|
+
operations: Array.isArray(options.operations)
|
|
90
|
+
? options.operations.map(operation => ({ ...operation }))
|
|
91
|
+
: [],
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
if (options.lastValidatedAt) {
|
|
95
|
+
state.lastValidatedAt = options.lastValidatedAt;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
assertValidInstallState(state, 'create');
|
|
99
|
+
return state;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function readInstallState(filePath) {
|
|
103
|
+
const state = readJson(filePath, 'install-state');
|
|
104
|
+
assertValidInstallState(state, filePath);
|
|
105
|
+
return state;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function writeInstallState(filePath, state) {
|
|
109
|
+
assertValidInstallState(state, filePath);
|
|
110
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
111
|
+
fs.writeFileSync(filePath, `${JSON.stringify(state, null, 2)}\n`);
|
|
112
|
+
return state;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
module.exports = {
|
|
116
|
+
createInstallState,
|
|
117
|
+
readInstallState,
|
|
118
|
+
validateInstallState,
|
|
119
|
+
writeInstallState,
|
|
120
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
const { createInstallTargetAdapter } = require('./helpers');
|
|
2
|
+
|
|
3
|
+
module.exports = createInstallTargetAdapter({
|
|
4
|
+
id: 'antigravity-project',
|
|
5
|
+
target: 'antigravity',
|
|
6
|
+
kind: 'project',
|
|
7
|
+
rootSegments: ['.agent'],
|
|
8
|
+
installStatePathSegments: ['ecc-install-state.json'],
|
|
9
|
+
});
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
const { createInstallTargetAdapter } = require('./helpers');
|
|
2
|
+
|
|
3
|
+
module.exports = createInstallTargetAdapter({
|
|
4
|
+
id: 'claude-home',
|
|
5
|
+
target: 'claude',
|
|
6
|
+
kind: 'home',
|
|
7
|
+
rootSegments: ['.claude'],
|
|
8
|
+
installStatePathSegments: ['ecc', 'install-state.json'],
|
|
9
|
+
nativeRootRelativePath: '.claude-plugin',
|
|
10
|
+
});
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
const { createInstallTargetAdapter } = require('./helpers');
|
|
2
|
+
|
|
3
|
+
module.exports = createInstallTargetAdapter({
|
|
4
|
+
id: 'codex-home',
|
|
5
|
+
target: 'codex',
|
|
6
|
+
kind: 'home',
|
|
7
|
+
rootSegments: ['.codex'],
|
|
8
|
+
installStatePathSegments: ['ecc-install-state.json'],
|
|
9
|
+
nativeRootRelativePath: '.codex',
|
|
10
|
+
});
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
const { createInstallTargetAdapter } = require('./helpers');
|
|
2
|
+
|
|
3
|
+
module.exports = createInstallTargetAdapter({
|
|
4
|
+
id: 'cursor-project',
|
|
5
|
+
target: 'cursor',
|
|
6
|
+
kind: 'project',
|
|
7
|
+
rootSegments: ['.cursor'],
|
|
8
|
+
installStatePathSegments: ['ecc-install-state.json'],
|
|
9
|
+
nativeRootRelativePath: '.cursor',
|
|
10
|
+
});
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
const os = require('os');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
|
|
4
|
+
function normalizeRelativePath(relativePath) {
|
|
5
|
+
return String(relativePath || '')
|
|
6
|
+
.replace(/\\/g, '/')
|
|
7
|
+
.replace(/^\.\/+/, '')
|
|
8
|
+
.replace(/\/+$/, '');
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function resolveBaseRoot(scope, input = {}) {
|
|
12
|
+
if (scope === 'home') {
|
|
13
|
+
return input.homeDir || os.homedir();
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
if (scope === 'project') {
|
|
17
|
+
const projectRoot = input.projectRoot || input.repoRoot;
|
|
18
|
+
if (!projectRoot) {
|
|
19
|
+
throw new Error('projectRoot or repoRoot is required for project install targets');
|
|
20
|
+
}
|
|
21
|
+
return projectRoot;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
throw new Error(`Unsupported install target scope: ${scope}`);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function createInstallTargetAdapter(config) {
|
|
28
|
+
const adapter = {
|
|
29
|
+
id: config.id,
|
|
30
|
+
target: config.target,
|
|
31
|
+
kind: config.kind,
|
|
32
|
+
nativeRootRelativePath: config.nativeRootRelativePath || null,
|
|
33
|
+
supports(target) {
|
|
34
|
+
return target === config.target || target === config.id;
|
|
35
|
+
},
|
|
36
|
+
resolveRoot(input = {}) {
|
|
37
|
+
const baseRoot = resolveBaseRoot(config.kind, input);
|
|
38
|
+
return path.join(baseRoot, ...config.rootSegments);
|
|
39
|
+
},
|
|
40
|
+
getInstallStatePath(input = {}) {
|
|
41
|
+
const root = adapter.resolveRoot(input);
|
|
42
|
+
return path.join(root, ...config.installStatePathSegments);
|
|
43
|
+
},
|
|
44
|
+
resolveDestinationPath(sourceRelativePath, input = {}) {
|
|
45
|
+
const normalizedSourcePath = normalizeRelativePath(sourceRelativePath);
|
|
46
|
+
const targetRoot = adapter.resolveRoot(input);
|
|
47
|
+
|
|
48
|
+
if (
|
|
49
|
+
config.nativeRootRelativePath
|
|
50
|
+
&& normalizedSourcePath === normalizeRelativePath(config.nativeRootRelativePath)
|
|
51
|
+
) {
|
|
52
|
+
return targetRoot;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return path.join(targetRoot, normalizedSourcePath);
|
|
56
|
+
},
|
|
57
|
+
determineStrategy(sourceRelativePath) {
|
|
58
|
+
const normalizedSourcePath = normalizeRelativePath(sourceRelativePath);
|
|
59
|
+
|
|
60
|
+
if (
|
|
61
|
+
config.nativeRootRelativePath
|
|
62
|
+
&& normalizedSourcePath === normalizeRelativePath(config.nativeRootRelativePath)
|
|
63
|
+
) {
|
|
64
|
+
return 'sync-root-children';
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return 'preserve-relative-path';
|
|
68
|
+
},
|
|
69
|
+
createScaffoldOperation(moduleId, sourceRelativePath, input = {}) {
|
|
70
|
+
const normalizedSourcePath = normalizeRelativePath(sourceRelativePath);
|
|
71
|
+
return {
|
|
72
|
+
kind: 'copy-path',
|
|
73
|
+
moduleId,
|
|
74
|
+
sourceRelativePath: normalizedSourcePath,
|
|
75
|
+
destinationPath: adapter.resolveDestinationPath(normalizedSourcePath, input),
|
|
76
|
+
strategy: adapter.determineStrategy(normalizedSourcePath),
|
|
77
|
+
ownership: 'managed',
|
|
78
|
+
scaffoldOnly: true,
|
|
79
|
+
};
|
|
80
|
+
},
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
return Object.freeze(adapter);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
module.exports = {
|
|
87
|
+
createInstallTargetAdapter,
|
|
88
|
+
normalizeRelativePath,
|
|
89
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
const { createInstallTargetAdapter } = require('./helpers');
|
|
2
|
+
|
|
3
|
+
module.exports = createInstallTargetAdapter({
|
|
4
|
+
id: 'opencode-home',
|
|
5
|
+
target: 'opencode',
|
|
6
|
+
kind: 'home',
|
|
7
|
+
rootSegments: ['.opencode'],
|
|
8
|
+
installStatePathSegments: ['ecc-install-state.json'],
|
|
9
|
+
nativeRootRelativePath: '.opencode',
|
|
10
|
+
});
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
const antigravityProject = require('./antigravity-project');
|
|
2
|
+
const claudeHome = require('./claude-home');
|
|
3
|
+
const codexHome = require('./codex-home');
|
|
4
|
+
const cursorProject = require('./cursor-project');
|
|
5
|
+
const opencodeHome = require('./opencode-home');
|
|
6
|
+
|
|
7
|
+
const ADAPTERS = Object.freeze([
|
|
8
|
+
claudeHome,
|
|
9
|
+
cursorProject,
|
|
10
|
+
antigravityProject,
|
|
11
|
+
codexHome,
|
|
12
|
+
opencodeHome,
|
|
13
|
+
]);
|
|
14
|
+
|
|
15
|
+
function listInstallTargetAdapters() {
|
|
16
|
+
return ADAPTERS.slice();
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function getInstallTargetAdapter(targetOrAdapterId) {
|
|
20
|
+
const adapter = ADAPTERS.find(candidate => candidate.supports(targetOrAdapterId));
|
|
21
|
+
|
|
22
|
+
if (!adapter) {
|
|
23
|
+
throw new Error(`Unknown install target adapter: ${targetOrAdapterId}`);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return adapter;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function planInstallTargetScaffold(options = {}) {
|
|
30
|
+
const adapter = getInstallTargetAdapter(options.target);
|
|
31
|
+
const modules = Array.isArray(options.modules) ? options.modules : [];
|
|
32
|
+
const planningInput = {
|
|
33
|
+
repoRoot: options.repoRoot,
|
|
34
|
+
projectRoot: options.projectRoot || options.repoRoot,
|
|
35
|
+
homeDir: options.homeDir,
|
|
36
|
+
};
|
|
37
|
+
const targetRoot = adapter.resolveRoot(planningInput);
|
|
38
|
+
const installStatePath = adapter.getInstallStatePath(planningInput);
|
|
39
|
+
const operations = modules.flatMap(module => {
|
|
40
|
+
const paths = Array.isArray(module.paths) ? module.paths : [];
|
|
41
|
+
return paths.map(sourceRelativePath => adapter.createScaffoldOperation(
|
|
42
|
+
module.id,
|
|
43
|
+
sourceRelativePath,
|
|
44
|
+
planningInput
|
|
45
|
+
));
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
return {
|
|
49
|
+
adapter: {
|
|
50
|
+
id: adapter.id,
|
|
51
|
+
target: adapter.target,
|
|
52
|
+
kind: adapter.kind,
|
|
53
|
+
},
|
|
54
|
+
targetRoot,
|
|
55
|
+
installStatePath,
|
|
56
|
+
operations,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
module.exports = {
|
|
61
|
+
getInstallTargetAdapter,
|
|
62
|
+
listInstallTargetAdapters,
|
|
63
|
+
planInstallTargetScaffold,
|
|
64
|
+
};
|